星火印

星火印是星火链网主链上的一项基础性公共服务,基于BID标识体系,面向骨干节点用户及可信企业用户提供便捷的电子数据存证和核验服务。


简介

欢迎使用星火·链网 星火印API接口文档。

1、本文档用于指导可信用户使用星火印完成数据上链。

2、本文档的接口采用HTTPS加密方式请求。

请求

访问星火·链网 星火印API需要使用https协议,并进行数字签名。

路径

API地址:

测试网络环境:https://test-stamp.bitfactory.cn/api

生产环境:https://stamp.bitfactory.cn/api

请求路径=API地址+接口名称,比如hash存证接口的名称是/evidence/hash,则请求路径为: https://test-stamp.bitfactory.cn/api/evidence/hash

方法

所有的请求遵循RESTFUL方法

参数

通用请求头 headers

字段名

描述

request_id

请求号,由接入客户创建唯一字符串,长度不超过32位

access_key

访问识别码,当您在星火印成功上传SM2公钥后会获得一个access_key

nonce

请求时间, 必须以 Unix Time 的格式发送, nonce与服务器时间不得超过正负900秒,否则请求将视为无效

signature

使用你的SM2私钥进行签名后的字符串,具体签名的方法后面会进一步描述

例如:

"request_id": "2XiTgZ2oVrBgGqKQ1ruCKh"
"access_key": "2y7cg8kmoGDrDBXJLaizoD"
"nonce": "1464594744"
"signature": "cdVtQ52evi4YDIuygRRiGhosn5XZyDH63LhNMk10I0LFBAamfuEBb6A2vlynVYll2ASzC/yolU/pbEAZ0zxdtg=="

通用应答参数

字段名

描述

code

状态码

data

返回数据

message

错误描述

例如:

{
    "data": {
        "attestationId": "rBgGqKQ1ruCKhXiTgZ2oVr",
    },
    "message": "string",
    "code": "string"
}

错误码

错误码

描述

200

操作成功

500

系统内部错误

401

暂未登录或token已经过期

403

没有相关权限

1001

参数检验失败

1002

账户余额不足

1003

存证不存在

1004

存证失败

1005

存证处理中

1006

提取码错误

1007

存证文件已经过期

1008

请输入最大6个字符的标签

1009

请输入最大20个字符的文件名称

1010

请输入正确格式的文件hash值

1011

该文件已经做过存证

1012

请勿提交相同文件hash

1013

存证失败,已存在相同文件hash的存证数据

1014

存证失败,请勿提交相同文件

1015

存证失败,文件有安全问题

2001

存证文件不存在

2002

上传文件失败

3001

用户不存在

3002

验证码错误

4001

业务流水不唯一

4002

权限校验失败

接口

初始化请求参数

// 构建请求
HttpRequest httpRequest = HttpUtil.createPost(uri + apiName);
// 构建请求头
Map<String ,String> headers = new HashMap<>();
headers.put("request_id", requestId);
headers.put("access_key", accessKey);
headers.put("nonce",nonce);
headers.put("signature",signatureData);
httpRequest.addHeaders(headers);

文件存证(分两步):

上传文件 - /file/upload

客户可以通过该接口上传文件并获取文件id,文档类文件最大限制150M,图片类文件最大10M,音频和视频类文件最大550M,文件未存证时数据会在存储7天后删除。

form-data

参数名

描述

是否可选

file

文件

必选

type

doc:文档pic:图片audio:音频video:视频

必选

返回的data

字段名

描述

fileKey

文件id

以java为例:

// 构建请求参数
httpRequest.form("file",new File("/tmp/背景图.png"));
httpRequest.form("type","pic");
HttpResponse httpResponse = httpRequest.execute();
String result = httpResponse.body();

返回结果示例: a.接口调用成功,则返回JSON数据示例为::

{
    "code": "200",
    "data": {
        "fileKey": "1544567382363930624"
    },
    "message": "操作成功"
}

b.接口调用失败,则返回JSON数据示例为::

{
    "code": "1001",
    "message": "fileLabel文件标签不能为空"
}

文件存证 - /evidence/file

用户进行文件存证

请求参数

参数名

描述

是否可选

fileLabel

文件标签

必选

files

文件id列表

必选

files[0]

文件id

必选

返回的data

调用文件接口成功后会返回文件id对应的存证id

字段名

描述

list

bean对象列表

bean.id

文件id

bean.attestationId

存证id

以java为例:

// 构建请求参数
List<Long> list = new ArrayList<>();
list.add(1529663660129480704L);
EvidenceFileParam evidenceFileParam = new EvidenceFileParam();
evidenceFileParam.setFileLabel("标签");
evidenceFileParam.setFiles(list);
httpRequest.body(JSONUtil.toJsonStr(evidenceFileParam));
HttpResponse httpResponse = httpRequest.execute();
String result = httpResponse.body();

返回结果示例: a.接口调用成功,则返回JSON数据示例为::

{
    "code":"200",
    "data":[
        {
            "attestationId":"did:bid:ef23cydtVMQit888kfwqrZAJCccet2qQM",
            "id":"1544567382363930624"
        }
    ],
    "message":"操作成功"
}

b.接口调用失败,则返回JSON数据示例为::

{
    "code": "500",
    "message": "文件类型不支持"
}

hash存证(sha256) - /evidence/hash

用户进行hash存证。

请求参数

参数名

描述

是否可选

fileLabel

文件标签

必选

list

HashInfo对象列表

必选

HashInfo.filename

文件名

必选

HashInfo.fileHash

文件hash

必选

返回的data

调用hash存证接口成功后会返回存证id列表

字段名

描述

list

bean对象列表

bean.hash

文件hash

bean.attestationId

存证id

以java为例:

// 构建请求参数
List<EvidenceHashParam.HashInfo> list = new ArrayList<>();
EvidenceHashParam.HashInfo hashInfo1 = new EvidenceHashParam.HashInfo();
hashInfo1.setFilename("test1");
hashInfo1.setFileHash("98df1f1dfb3b1a123c1517912dc70447aa61c6be532ac99de973abb6219e1653");
list.add(hashInfo1);
EvidenceHashParam evidenceHashParam = new EvidenceHashParam();
evidenceHashParam.setFileLabel("标签");
evidenceHashParam.setList(list);
httpRequest.body(JSONUtil.toJsonStr(evidenceHashParam));
HttpResponse httpResponse = httpRequest.execute();
String result = httpResponse.body();

返回结果示例: a.接口调用成功,则返回JSON数据示例为::

{
    "code":"200",
    "data":[
        {
            "attestationId":"did:bid:efaE9e45apUbuA87y7Y6zjMTaGfHt7WX",
            "hash":"98df1f1dfb3b1a123c1517912dc70447aa61c6be532ac99de973abb6219e1653"
        }
    ],
    "message":"操作成功"
}

b.接口调用失败,则返回JSON数据示例为::

{
    "code":"1010",
    "message":"请输入正确格式的文件hash值"
}

存证列表 - /evidence/list

获取存证列表

请求参数

参数名

描述

是否可选

evidenceType

存证类型 1.文件存证 2.hash存证

非必选

evidenceChannel

存证方式 1.自助 2.API

非必选

state

3.待支付4.上链中5.存证成功6.存证失败

非必选

startTime

开始时间

非必选

endTime

结束时间

非必选

pageNumber

当前页码

非必选

pageSize

每页显示数量 最大50

非必选

filename

文件名称

非必选

返回的data

调用存证获取列表接口成功后会返回存证列表

字段名

描述

totalPage

当前页

pageSize

每页显示数量

pageNum

总页数

rows

存证数据对象info

info.evidenceChannel

存证方式 1.自助 2.API

info.attestationId

存证id

info.auditTime

审核时间

info.auditResult

审核结果

info.fileHash

文件hash

info.userId

用户id

info.fileLabel

文件标签

info.filename

文件名

info.fileSize

文件大小

info.createTime

创建时间

info.upChainTime

上链时间

info.evidenceType

存证类型 1:文件存证, 2:hash存证

info.state

1.待审核 2.待复审 3.待支付 4.上链中 5.存证成功 6.存证失败

info.username

用户名称

以java为例:

// 构建请求参数
Map<String ,Object> body = new HashMap<>();
body.put("evidenceType",1);
httpRequest.body(JSONUtil.toJsonStr(body));
HttpResponse httpResponse = httpRequest.execute();
String result = httpResponse.body();

返回结果示例: a.接口调用成功,则返回JSON数据示例为::

{
    "code":"200",
    "data":{
        "totalPage":"1",
        "pageSize":"10",
        "rows":[
            {
                "evidenceChannel":2,
                "attestationId":"did:bid:efaE9e45apUbuA87y7Y6zjMTaGfHt7WX",
                "fileHash":"98df1f1dfb3b1a123c1517912dc70447aa61c6be532ac99de973abb6219e1653",
                "userId":"did:bid:zfGUkdqhxEamsPvpqAH2iRHk1ifhcW61",
                "fileLabel":"标签",
                "filename":"test1",
                "createTime":"2022-07-07 11:10:19",
                "evidenceType":2,
                "upChainTime":"2022-07-07 11:10:59",
                "state":4,
                "username":"陈诚"
            },
            {
                "evidenceChannel":2,
                "attestationId":"did:bid:ef23cydtVMQit888kfwqrZAJCccet2qQM",
                "fileHash":"46d1f4f65279641891c13eb1cfba0f4a93cdd1c9e5d7cca31cd1860dbe7ca463",
                "userId":"did:bid:zfGUkdqhxEamsPvpqAH2iRHk1ifhcW61",
                "fileLabel":"标签",
                "filename":"背景图.png",
                "fileSize":"1306418",
                "createTime":"2022-07-07 11:08:51",
                "evidenceType":1,
                "state":2,
                "username":"陈诚"
            }
        ],
        "pageNum":"1",
        "total":"2"
    },
    "message":"操作成功"
}

b.接口调用失败,则返回JSON数据示例为::

{
    "code": "500",
    "message": "系统错误"
}

存证详情 - /evidence/detail

查询存证详情。

请求参数

参数名

描述

是否可选

attestationId

存证id

必选

返回的data

调用存证详情成功后会返回详情数据

字段名

描述

attestationId

存证id

evidenceShareCode

证据提取码

pdfFileKey

pdf文件id

fileHash

存证文件hash

dataExpireTime

存证文件过期时间

attestationType

存证类型 1.文件 2.hash

dataExpireFlag

存证文件是否已过期

userId

用户id

fileLabel

文件标签

auditTime

审核时间

auditResult

审核结果

filename

文件名

createTime

创建时间

upChainTime

上链时间

attestationChannel

数据来源 1.自助 2.API

dataFileKey

存证文件的文件id

username

用户名称

checkBean

链信息

checkBean.blockHash

交易hash

checkBean.fileName

文件名称

checkBean.evidenceTime

存证时间

checkBean.flag

是否上链

checkBean.attestationId

存证id

checkBean.confirmTime

出块时间

checkBean.confirmHash

区块hash

checkBean.ledgerSeq

区块高度

checkBean.hash

文件hash

以java为例:

    // 构建请求参数
Map<String ,Object> body = new HashMap<>();
body.put("attestationId","did:bid:efsRrRCTEmA7ZWodWFPkjMW2u5Y4hikv");
httpRequest.body(JSONUtil.toJsonStr(body));
HttpResponse httpResponse = httpRequest.execute();
String result = httpResponse.body();

返回结果示例: a.接口调用成功,则返回JSON数据示例为::

{
    "code":"200",
    "data":{
        "checkBean":{
            "blockHash":"ec879f484d5aed9d598c3d615ea70f8246272b3d4c5796dcedc3e67a402d0905",
            "fileName":"test1",
            "evidenceTime":"2022-07-07 11:10:59",
            "flag":true,
            "attestationId":"did:bid:efaE9e45apUbuA87y7Y6zjMTaGfHt7WX",
            "confirmTime":"2022-07-07 11:11:01",
            "confirmHash":"106f9a90a4ac78a45acdfe203a353562f3779ff1c6f3fc35d8914dd6a7ec06da",
            "ledgerSeq":"1113290",
            "hash":"98df1f1dfb3b1a123c1517912dc70447aa61c6be532ac99de973abb6219e1653"
        },
        "attestationId":"did:bid:efaE9e45apUbuA87y7Y6zjMTaGfHt7WX",
        "evidenceShareCode":"KD8TCISG",
        "pdfFileKey":"1544881909048279040",
        "fileHash":"98df1f1dfb3b1a123c1517912dc70447aa61c6be532ac99de973abb6219e1653",
        "attestationType":2,
        "dataExpireFlag":false,
        "userId":"did:bid:zfGUkdqhxEamsPvpqAH2iRHk1ifhcW61",
        "fileLabel":"标签",
        "filename":"test1",
        "createTime":"2022-07-07 11:10:19",
        "attestationChannel":2,
        "upChainTime":"2022-07-07 11:10:59",
        "id":"1544881469589377024",
        "username":"陈诚"
    },
    "message":"操作成功"
}

b.接口调用失败,则返回JSON数据示例为::

{
    "code": "500",
    "message": "系统错误"
}

下载存证或pdf文件 - /file/download/{fileKey}

存证原文件或pdf下载

Path

参数名

描述

是否可选

fileKey

文件id

必选

返回的文件

该接口会返回存证文件以及文件名,文件就是http返回结果的body,文件名存放在http的header中,header的名称是Content-Disposition,header值形如:

form-data; name=Content-Disposition; filename=5Yhus2mVSMnQRXobRJCYgt.zip

以java为例:

String apiName = "/file/download/1529707935276466176";
HttpRequest httpRequest = createRequestGet(apiName);

HttpResponse httpResponse = httpRequest.execute();
String header = httpResponse.header("Content-Disposition");
Pattern pattern = Pattern.compile(".*filename=\"(.*)\".*");
Matcher matcher = pattern.matcher(header);
String fileName = "";
if (matcher.matches()) {
    fileName = matcher.group(1);
}
byte[] bytes = httpResponse.bodyBytes();
IoUtil.write(new FileOutputStream("/tmp/" + fileName),true,bytes);

返回结果示例: a.接口调用成功,则返回文件流::

byte[]

b.接口调用失败,则返回JSON数据示例为::

{
    "code": "2001",
    "message": "文件不存在"
}

签名

假定待签名数据头为:

"request_id": "2XiTgZ2oVrBgGqKQ1ruCKh",
"access_key": "2y7cg8kmoGDrDBXJLaizoD",
"nonce": 1464594744

签名过程用Java代码描述如下:

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;

import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;

public class SignatureUtil {
    //签名
    public static String sign(byte[] data, String privateKey) {
        SM2 sm2 = new SM2(privateKey,null);
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        sm2.usePlainEncoding();
        //签名使用Base64编码后得到的值即为请求头中signature字段的值
        return Base64.getEncoder().encodeToString(sm2.sign(data));
    }
    //验签
    public static boolean verify(byte[] data, String publicKey, String sign) {
        SM2 sm2 = new SM2(null, publicKey);
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        sm2.usePlainEncoding();
        return sm2.verify(data,Base64.getDecoder().decode(sign));
    }
    //测试用例
    public static void main(String[] args) {
        KeyPair keyPair = SecureUtil.generateKeyPair("SM2");
        String publicKey = HexUtil.encodeHexStr(((BCECPublicKey)keyPair.getPublic()).getQ().getEncoded(false));
        //RSA私钥文件路径
        String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(keyPair.getPrivate()));
        System.out.println(publicKey);
        System.out.println(privateKey);
        long requestId = IdUtil.getSnowflakeNextId();//2XiTgZ2oVrBgGqKQ1ruCKh
        String accessKey = "2y7cg8kmoGDrDBXJLaizoD";
        long nonce = System.currentTimeMillis()/1000;//1464594744
        String data = requestId + accessKey + nonce;
        //签名
        String sign = sign(data.getBytes(StandardCharsets.UTF_8),privateKey);
        System.out.println(sign);
        //验签
        boolean verify = verify(data.getBytes(StandardCharsets.UTF_8),publicKey,sign);
        System.out.println(verify);
    }
}

Note

签名所用的方法是SM2,签名数据字符串转换成bytes时要用UTF-8编码格式

Java

如果使用maven,可以加入如下依赖:

    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.22</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.71</version>
</dependency>

示例程序

java:

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import org.junit.Test;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ApiRequestTest {

    static class EvidenceHashParam {
        private String fileLabel;
        private List<HashInfo> list;
        static class HashInfo {
            private String filename;
            private String fileHash;

            public String getFilename() {
                return filename;
            }

            public void setFilename(String filename) {
                this.filename = filename;
            }

            public String getFileHash() {
                return fileHash;
            }

            public void setFileHash(String fileHash) {
                this.fileHash = fileHash;
            }
        }

        public String getFileLabel() {
            return fileLabel;
        }

        public void setFileLabel(String fileLabel) {
            this.fileLabel = fileLabel;
        }

        public List<HashInfo> getList() {
            return list;
        }

        public void setList(List<HashInfo> list) {
            this.list = list;
        }
    }

    static class EvidenceFileParam {
        private String fileLabel;
        private List<Long> files;

        public String getFileLabel() {
            return fileLabel;
        }

        public void setFileLabel(String fileLabel) {
            this.fileLabel = fileLabel;
        }

        public List<Long> getFiles() {
            return files;
        }

        public void setFiles(List<Long> files) {
            this.files = files;
        }
    }

    private String uri = "http://127.0.0.1:18848/api";

    /**
     *
     * @throws Exception
     */
    @Test
    public void detail() throws Exception {
        String apiName = "/evidence/detail";
        HttpRequest httpRequest = createRequestPost(apiName);
        // 构建请求参数
        Map<String ,Object> body = new HashMap<>();
        body.put("attestationId","did:bid:efaE9e45apUbuA87y7Y6zjMTaGfHt7WX");
        httpRequest.body(JSONUtil.toJsonStr(body));
        HttpResponse httpResponse = httpRequest.execute();
        String result = httpResponse.body();
        JSON json = JSONUtil.parse(result);
        System.out.println(json.toString());
    }

    @Test
    public void list() throws Exception {
        // API path
        String apiName = "/evidence/list";
        HttpRequest httpRequest = createRequestPost(apiName);
        // 构建请求参数
        Map<String ,Object> body = new HashMap<>();
//        body.put("attestationId","");
        httpRequest.body(JSONUtil.toJsonStr(body));
        HttpResponse httpResponse = httpRequest.execute();
        String result = httpResponse.body();
        JSON json = JSONUtil.parse(result);
        System.out.println(json.toString());
    }

    @Test
    public void hash() throws Exception {
        // API path
        String apiName = "/evidence/hash";
        HttpRequest httpRequest = createRequestPost(apiName);
        // 构建请求参数
        List<EvidenceHashParam.HashInfo> list = new ArrayList<>();
        EvidenceHashParam.HashInfo hashInfo1 = new EvidenceHashParam.HashInfo();
        hashInfo1.setFilename("test1");
        hashInfo1.setFileHash("98df1f1dfb3b1a123c1517912dc70447aa61c6be532ac99de973abb6219e1653");
        list.add(hashInfo1);
        EvidenceHashParam evidenceHashParam = new EvidenceHashParam();
        evidenceHashParam.setFileLabel("标签");
        evidenceHashParam.setList(list);
        httpRequest.body(JSONUtil.toJsonStr(evidenceHashParam));
        HttpResponse httpResponse = httpRequest.execute();
        String result = httpResponse.body();
        JSON json = JSONUtil.parse(result);
        System.out.println(json.toString());
    }

    @Test
    public void file() throws Exception {
        // API path
        String apiName = "/evidence/file";
        HttpRequest httpRequest = createRequestPost(apiName);
        // 构建请求参数
        List<Long> list = new ArrayList<>();
        list.add(1544567382363930624L);
        EvidenceFileParam evidenceFileParam = new EvidenceFileParam();
        evidenceFileParam.setFileLabel("标签");
        evidenceFileParam.setFiles(list);
        httpRequest.body(JSONUtil.toJsonStr(evidenceFileParam));
        HttpResponse httpResponse = httpRequest.execute();
        String result = httpResponse.body();
        JSON json = JSONUtil.parse(result);
        System.out.println(json.toString());
    }
    @Test
    public void uploadFile() throws Exception {
        // API path
        String apiName = "/file/upload";
        HttpRequest httpRequest = createRequestPost(apiName);
        httpRequest.form("file",new File("/tmp/背景图.png"));
        httpRequest.form("type","video");

        HttpResponse httpResponse = httpRequest.execute();
        String result = httpResponse.body();
        JSON json = JSONUtil.parse(result);
        System.out.println(json.toString());
    }
    @Test
    public void download() throws Exception {
        // API path
        String apiName = "/file/download/1529707935276466176";
        HttpRequest httpRequest = createRequestGet(apiName);

        HttpResponse httpResponse = httpRequest.execute();
        String header = httpResponse.header("Content-Disposition");
        Pattern pattern = Pattern.compile(".*filename=\"(.*)\".*");
        Matcher matcher = pattern.matcher(header);
        String fileName = "";
        if (matcher.matches()) {
            fileName = matcher.group(1);
        }
        byte[] bytes = httpResponse.bodyBytes();
        IoUtil.write(new FileOutputStream("/tmp/" + fileName),true,bytes);
    }

    private HttpRequest createRequestPost(String apiName) throws Exception {
        // 构建请求
        HttpRequest httpRequest = HttpUtil.createPost(uri + apiName);
        setHttpRequestHeaders(httpRequest);
        return httpRequest;
    }
    private HttpRequest createRequestGet(String apiName) throws Exception {
        // 构建请求
        HttpRequest httpRequest = HttpUtil.createGet(uri + apiName);
        setHttpRequestHeaders(httpRequest);
        return httpRequest;
    }

    private HttpRequest setHttpRequestHeaders(HttpRequest httpRequest) throws Exception {
        // RSA私钥文件路径
        String privateKey = "308193020100301306072a8648ce3d020106082a811ccf5501822d047930770201010420ab398da2bb9268c226f4c5908e94841ca6d254a90cf6e66ad848c8e01ee86d33a00a06082a811ccf5501822da144034200049ab45581431741df119e74c8699fd2cb70caeda3c6f05383dd8b4294f3ff5f3c2d7959877584ec884b75a09af99aa69d69c17f6e3018283d0452cbd0debd5262";
        // 请求头
        String requestId = IdUtil.simpleUUID();
        String accessKey = "9d82aeae8c9b4c479715fc2923619472";
        String nonce = String.valueOf(System.currentTimeMillis() / 1000);

        //待签名数据 = requestId+accessKey+nonce
        String data = requestId + accessKey + nonce;
        // 开始签名
        SM2 sm2 = new SM2(privateKey,null);
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        sm2.usePlainEncoding();
        // 签名使用Base64编码后得到的值即为请求头中signature字段的值
        String signatureData = Base64.getEncoder().encodeToString(sm2.sign(data.getBytes(StandardCharsets.UTF_8)));
        // 构建请求头
        Map<String ,String> headers = new HashMap<>();
        headers.put("request_id", requestId);
        headers.put("access_key", accessKey);
        headers.put("nonce",nonce);
        headers.put("signature",signatureData);
        httpRequest.addHeaders(headers);
        return httpRequest;
    }


}