跳到主要内容

1. 背景

数据摘要存证是指通过记录、存储和验证数据的过程,以确保数据的完整性、真实性和可信性。它旨在为数据的创建、修改和使用提供可追溯的证据,并防止数据的篡改和抵赖。

在传统的数据存储方式中,数据容易受到篡改、删除或丢失的风险,同时也存在数据的真实性和时序性无法被确定的问题。数据存证利用区块链技术的不可篡改性、公开透明性和时间戳功能,解决了这些问题。

通过将数据的哈希值(一种固定长度的唯一标识符)存储在区块链上,可以确保数据的完整性。任何对数据的修改都会导致哈希值的变化,从而发现数据的篡改。同时,区块链记录了数据的创建和修改时间,提供了可靠的时间戳。

2. 数据摘要存证应用场景

电子合同:在签署电子合同时,可以对合同内容生成哈希值并存储在区块链上,确保合同内容不被篡改。

数字版权保护:对数字作品(如图片、文章)生成哈希值并存证,以保护作者的版权和作品的完整性。

数据安全与合规:在金融、医疗等需要高度数据安全和合规的行业中,对数据进行摘要存证,以确保数据的真实性和合规性。

3. 区块链在数据摘要存证中的作用

区块链在数据摘要存证中的作用主要体现在以下几个方面:

  • 不可篡改性:区块链是一个分布式账本,记录一旦写入区块链,便无法轻易更改或删除。每个区块包含了前一个区块的哈希值,形成一个链式结构,保证了记录的安全性和不可篡改性。将数据摘要存储在区块链上,可以确保存证信息不会被篡改。

  • 去中心化:区块链通过分布式网络维护账本,不依赖于单一的中心化机构,这减少了单点故障的风险。多节点共同维护账本的方式也提高了数据的可信度和安全性。

  • 透明性:区块链上的数据是公开透明的,任何人都可以查看和验证。存储在区块链上的数据摘要可以公开验证,这增加了数据存证的透明性和公信力。

  • 时间戳:区块链网络中的每个区块都包含时间戳信息,这为数据摘要提供了一个精确的时间记录。通过时间戳,用户可以验证数据在某一特定时间点是否存在并确保其未被修改。

  • 可验证性:用户可以随时通过区块链网络验证存储的摘要值,确认数据的完整性。只需对原始数据重新生成摘要并与区块链上的存证进行对比,即可验证数据是否被篡改。

4. 准备工作

在开始开发之前,您需要准备BubiChain的接入环境或接入自建网络。

确保您具备以下基础知识:

  • JavaScript、Java编程语言。
  • 区块链和智能合约的基本原理。

5. 概要设计

本文以Java语言为例,将数据摘要存证,然后查询此存证内容(合约详见:文档中心-数据存证合约demo)。

5.1. 智能合约设计

我们的智能合约将包含以下几个关键功能:

  • depositData():数据摘要存证。
  • queryDepositData():查询数据摘要存证内容。

5.2. 业务设计

  • 用户注数据存证时要包含hash字段,且保证唯一。
  • 用户可根据hash查询已存证的数据摘要。

6. 代码实现

6.1. 业务代码开发

  • 创建SDK实例

请参考SDK使用教程中的《Java SDK》部分,将SDK导入到项目中,并设置区块链网络URL,创建SDK工具类实例。

public class ContractUtils {
private Integer BC_SUCCESS = 0;
private Integer DEFAULT_DECIMAL = 8;
private long DEFAULT_GASPRICE = 1000;
private String chainUrl;

public ContractUtils(String chainUrl){
this.chainUrl = chainUrl;
}

public CreateContractResp createContract(String initiator,String initiatorPrivateKey, String contractCode, String initInput){
CreateContractResp createContractRespDto = new CreateContractResp();
//生成交易Blob
BlobDataResp blobDataResp = buildBlob(initiator,contractCode,initInput,chainUrl);
//签名
List<String> privateKeys = new ArrayList<>();
privateKeys.add(initiatorPrivateKey);
List<SignEntity> signBlobs = signBlob(blobDataResp,privateKeys);
//提交
SubmitTxReq submitTxReq = new SubmitTxReq();
submitTxReq.setBlob(blobDataResp.getBlob());
submitTxReq.setListSigner(signBlobs);
submitTxReq.setHash(blobDataResp.getHash());
TransactionSubmitResponse bcResponse = submitTx(chainUrl,submitTxReq);
if(bcResponse.getErrorCode().equals(SdkErrorCodeEnum.SUCCESS.getCode())){
//同步查询交易结果
TransactionHistory transactionHistory = queryTxResult(chainUrl,submitTxReq.getHash());
if(transactionHistory!=null && BC_SUCCESS.equals(transactionHistory.getErrorCode())) {
//成功
// System.out.println("创建合约 查询结果成功hash:" + blobDataResp.getHash());
JSONArray arrayJson = JSON.parseArray(transactionHistory.getErrorDesc());
String contractAddress = arrayJson.getJSONObject(0).getString("contract_address");
createContractRespDto.setContractAddress(contractAddress);
createContractRespDto.setHash(blobDataResp.getHash());
return createContractRespDto;
} else {
System.out.println("创建合约 结果交易失败:" + "errorCode:" + transactionHistory.getErrorCode() + " hash:"+blobDataResp.getHash());
}
}else{
System.out.println("创建合约 提交交易到blockchain异常" + bcResponse.getErrorCode() + " hash:" + blobDataResp.getHash());
}
return null;
}

public void invokeContract(String sourceAddress,String sourcePrivateKey, String contractAddress, JSONObject input){
//生成交易Blob
BlobDataResp blobDataResp = buildInvokeContractBlob(sourceAddress,contractAddress,input,chainUrl);
//签名
List<String> privateKeys = new ArrayList<>();
privateKeys.add(sourcePrivateKey);
List<SignEntity> signBlobs = signBlob(blobDataResp,privateKeys);
//提交
SubmitTxReq submitTxReq = new SubmitTxReq();
submitTxReq.setBlob(blobDataResp.getBlob());
submitTxReq.setListSigner(signBlobs);
submitTxReq.setHash(blobDataResp.getHash());
TransactionSubmitResponse bcResponse = submitTx(chainUrl,submitTxReq);
if(bcResponse.getErrorCode().equals(SdkErrorCodeEnum.SUCCESS.getCode())){
//同步查询交易结果
TransactionHistory transactionHistory = queryTxResult(chainUrl,submitTxReq.getHash());
if(transactionHistory!=null && BC_SUCCESS.equals(transactionHistory.getErrorCode())) {
//成功
// System.out.println("调用合约成功:" + JSON.toJSONString(transactionHistory, true));

} else {
System.out.println("调用合约 结果交易失败:" + "errorCode:" + transactionHistory.getErrorCode());
}
}else{
System.out.println("调用合约 提交交易到blockchain异常" + bcResponse.getErrorCode());
}
}

private BlobDataResp buildInvokeContractBlob(String sourceAddress, String contractAddress, JSONObject input, String chainUrl) {
ContractInvokeByAssetOperation operation = new ContractInvokeByAssetOperation();
operation.setSourceAddress(sourceAddress);
operation.setContractAddress(contractAddress);
operation.setInput(input.toJSONString());
//获取交易nonce
long accNonce = getAccountNonce(chainUrl,sourceAddress)+1;
TransactionBuildBlobRequest transactionBuildBlobRequest = new TransactionBuildBlobRequest();
transactionBuildBlobRequest.setSourceAddress(sourceAddress);

transactionBuildBlobRequest.setNonce(accNonce);
Long txFee = amount10Pow("1", DEFAULT_DECIMAL);
transactionBuildBlobRequest.setFeeLimit(txFee);
transactionBuildBlobRequest.setGasPrice(DEFAULT_GASPRICE);
transactionBuildBlobRequest.addOperation(operation);
// 获取交易BLob串
TransactionService transactionService = new TransactionServiceImpl();
TransactionBuildBlobResponse transactionBuildBlobResponse = transactionService.buildBlob(transactionBuildBlobRequest);
TransactionBuildBlobResult transactionBuildBlobResult = transactionBuildBlobResponse.getResult();
BlobDataResp blobData = new BlobDataResp();
blobData.setBlob(transactionBuildBlobResult.getTransactionBlob());
blobData.setHash(transactionBuildBlobResult.getHash());
return blobData;
}

private BlobDataResp buildBlob(String initiator, String payLoad, String initInput , String bcUrl){
ContractCreateOperation operation = new ContractCreateOperation();
operation.setSourceAddress(initiator);
operation.setInitBalance(amount10Pow("1", DEFAULT_DECIMAL));
operation.setPayload(payLoad);
operation.setInitInput(initInput);
//获取交易nonce
long accNonce = getAccountNonce(bcUrl,initiator)+1;
TransactionBuildBlobRequest transactionBuildBlobRequest = new TransactionBuildBlobRequest();
transactionBuildBlobRequest.setSourceAddress(initiator);

transactionBuildBlobRequest.setNonce(accNonce);
Long txFee = amount10Pow("11", DEFAULT_DECIMAL);
transactionBuildBlobRequest.setFeeLimit(txFee);
transactionBuildBlobRequest.setGasPrice(DEFAULT_GASPRICE);
transactionBuildBlobRequest.addOperation(operation);
// 获取交易BLob串
TransactionService transactionService = new TransactionServiceImpl();
TransactionBuildBlobResponse transactionBuildBlobResponse = transactionService.buildBlob(transactionBuildBlobRequest);
TransactionBuildBlobResult transactionBuildBlobResult = transactionBuildBlobResponse.getResult();
BlobDataResp blobData = new BlobDataResp();
blobData.setBlob(transactionBuildBlobResult.getTransactionBlob());
blobData.setHash(transactionBuildBlobResult.getHash());
return blobData;
}

public List<SignEntity> signBlob(BlobDataResp blobDataResp, List<String> privateKeys){
List<SignEntity> listSigner = new ArrayList<SignEntity>();
for(String privateKey : privateKeys){
PrivateKey privateObj = new PrivateKey(privateKey);
byte[] signByte = privateObj.sign(HexFormat.hexStringToBytes(blobDataResp.getBlob()));
String signBlob = HexFormat.byteToHex(signByte);
SignEntity entity = new SignEntity();
entity.setPublicKey(privateObj.getEncPublicKey());
entity.setSignBlob(signBlob);
listSigner.add(entity);
}
return listSigner;
}

public TransactionSubmitResponse submitTx(String bcUrl, SubmitTxReq submitTxReq){
TransactionSubmitRequest transactionSubmitRequest = new TransactionSubmitRequest();
transactionSubmitRequest.setTransactionBlob(submitTxReq.getBlob());

List<SignEntity> listSigner = submitTxReq.getListSigner();
int length = listSigner.size();
Signature[] signatures = new Signature[length];
for(int i=0;i<length;i++){
SignEntity signEntity = listSigner.get(i);
Signature signature = new Signature();
signature.setPublicKey(signEntity.getPublicKey());
signature.setSignData(signEntity.getSignBlob());
signatures[i]=signature;
}

transactionSubmitRequest.setSignatures(signatures);
SDK sdk = SDK.getInstance(bcUrl);
TransactionSubmitResponse transactionSubmitResponse = sdk.getTransactionService().submit(transactionSubmitRequest);
return transactionSubmitResponse;
}

public TransactionHistory queryTxResult(String bcUrl, String hash) {
TransactionHistory transactionHistory = null;
int count = 20;
while (count > 0) {
try {
// 循环查询交易结果的次数
Thread.sleep(1000L);
transactionHistory = getTransactionByHash(bcUrl,hash);
// System.out.println("----------------------->:查询交易返回的结果:{"+hash+"},{"+JSON.toJSONString(transactionHistory)+"}");
if (transactionHistory != null){
break;//查询交易成功则跳出
}
} catch (Throwable e) {
e.printStackTrace();
}
count--;
}
return transactionHistory;
}

public TransactionHistory getTransactionByHash(String bcUrl,String txhash) {
try{
SDK sdk = SDK.getInstance(bcUrl);
TransactionGetInfoRequest request = new TransactionGetInfoRequest();
request.setHash(txhash);
TransactionGetInfoResponse response = sdk.getTransactionService().getInfo(request);
if(SdkErrorCodeEnum.NOT_EXIST.getCode().equals( response.getErrorCode())) {
return null;
}
if(SdkErrorCodeEnum.SUCCESS.getCode().equals( response.getErrorCode())) {
List<TransactionHistory> listHis = Arrays.asList(response.getResult().getTransactions());
for (TransactionHistory transactionHistory : listHis) {
if(txhash.equals(transactionHistory.getHash())) {
return transactionHistory;
}
}
}
}catch(Exception e){
e.printStackTrace();
}
return null;
}

public Long getAccountNonce(String bcUrl,String address){
SDK sdk = SDK.getInstance(bcUrl);
AccountGetNonceRequest request = new AccountGetNonceRequest();
request.setAddress(address);
AccountGetNonceResponse response = sdk.getAccountService().getNonce(request);
return response.getResult().getNonce();
}


private long amount10Pow(String str,Integer decimal){
return (new BigDecimal(str).multiply(new BigDecimal(Math.pow(10,decimal)))).stripTrailingZeros().longValue();
}
}
  • 部署数字摘要存证合约

    /**
* 部署合约
*/
@Test(priority = 0,description = "部署合约")
public void createContract() {

//读取合约源代码
String contractCode = readFile("BPC66.js");
//合约初始化参数
String initInput = "{}";
ContractUtils contractUtils = new ContractUtils(chainUrl);
CreateContractResp contract = contractUtils.createContract(initiator, initiatorPrivateKey, contractCode, initInput);
if(contract != null) System.out.println("合约地址:"+contract.getContractAddress() + ", hash: " + contract.getHash());
contractAddress = contract.getContractAddress();

}
/**
* 读取合约源文件
* @param path:文件名称
* @return
*/
public String readFile(String path){
StringBuilder fileContent = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try{
inputStream = TestContract.class.getClassLoader().getResourceAsStream(path);
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
fileContent.append(line);
fileContent.append(System.lineSeparator()); // 保持原始文件的换行符
}
// 打印整个文件内容
return fileContent.toString();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("读取"+path + "文件失败");
}finally {
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
  • 核心业务

数据摘要存证

数据摘要存证是一个账户通过调用智能合约的方法,将数据摘要保存至区块链,这里要注意数据摘要必须拥有唯一的数据hash,用于后续查询。

    /**
* 调用合约:存证
*/
@Test(priority = 1,description = "存证")
public void depositData() {

String data = generateRandomString();
hash = getSHA256Hash(data);
// Init input
JSONObject input = new JSONObject();
input.put("method", "depositData");
JSONObject params = new JSONObject();
params.put("hash", hash);
params.put("data", data);
input.put("params", params);

ContractUtils contractUtils = new ContractUtils(chainUrl);
contractUtils.invokeContract(initiator, initiatorPrivateKey, contractAddress, input);

System.out.println("存证数据:" + data);
}
public String getSHA256Hash(String input) {
try {
// 获取SHA-256消息摘要实例
MessageDigest digest = MessageDigest.getInstance("SHA-256");

// 计算输入字符串的哈希值
byte[] hashBytes = digest.digest(input.getBytes());

// 将字节数组转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}

return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not found", e);
}
}

public String generateRandomString() {
String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
int LENGTH = 8;
Random random = new Random();
StringBuilder sb = new StringBuilder(LENGTH);
for (int i = 0; i < LENGTH; i++) {
int index = random.nextInt(CHARACTERS.length());
sb.append(CHARACTERS.charAt(index));
}
return sb.toString();
}

获取存证内容

获取存证内容是用户通过调用智能合约的方法,根据数据hash查询保存在区块链的数据摘要。

    /**
* 调用合约:查询存证内容
*/
@Test(priority = 2,description = "查询存证内容")
public void queryDepositData() {
SDK sdk = SDK.getInstance(chainUrl);

// Init input
JSONObject input = new JSONObject();
input.put("method", "queryDepositData");
JSONObject params = new JSONObject();
params.put("hash", hash);
input.put("params", params);

// Init request
ContractCallRequest request = new ContractCallRequest();
request.setContractAddress(contractAddress);
request.setFeeLimit(10000L);
request.setOptType(2);
request.setInput(input.toJSONString());

// Call call
ContractCallResponse response = sdk.getContractService().call(request);
if (response.getErrorCode() == 0) {
ContractCallResult result = response.getResult();
System.out.println("查询到存证内容:" + JSON.toJSONString(result.getQueryRets(), true));
} else {
System.out.println("error: " + response.getErrorDesc());
}
}

完整代码可查看示例工程【bc-data-evidence-demo】:http://192.168.1.11/bc-group/bc-data-evidence-demo.git

7. 总结

在实际使用区块链进行数据存证时,以下是一些需要注意的要点:

  1. 数据完整性验证:在存证前,确保数据的完整性,并在存证过程中验证数据的完整性。任何数据篡改或篡改尝试都应该被及时发现和记录。
  2. 存证成本和效率:区块链存证可能涉及一定的成本和时间延迟。在选择区块链平台或协议时,需权衡成本和效率,确保存证过程具有可接受的成本效益比。
  3. 长期存储和可访问性:区块链上的数据可以永久保存,但要确保数据的长期存储和可访问性。考虑存储容量、数据备份和灾难恢复策略,以确保数据可以长期保存并随时可用。
  4. 法律合规性和证据有效性:了解当地法律法规对于电子证据和区块链存证的要求,并确保存证过程符合相关法律合规性。确保存证的证据有效性,以便在需要时被法律承认和接受。
  5. 隐私保护和数据授权:在存证过程中,要注意保护用户的隐私,并确保数据的授权合法有效。用户应有权决定哪些数据需要存证,并能够授权存证的范围和权限。
  6. 安全性和防护措施:采取适当的安全措施,防止数据被未授权的访问和恶意攻击。包括加密数据、访问控制、身份验证和网络安全防护等方面的安全措施。
  7. 核实区块链网络和节点的可靠性:确保选择可靠的区块链网络和节点,以避免存证过程中的错误或潜在风险。验证网络的安全性、共识机制和节点的信任度等因素。