BubiChain 隐私交易示例
场景描述
假设某资方想发行名称为Cocoon,标识符为CNT,总量为10000的隐私资产。资产发行的具体信息如下:
字段 | 是否必填 | 示例 | 描述 |
---|---|---|---|
name | 是 | Cocoon | 资产名称 |
symbol | 是 | CNT | 资产标识 |
totalSupply | 是 | 10000 | 资产总发行量 |
version | 是 | ETP1.0 | 协议版本号 |
资产交易过程应确保:
- 只有资产的实际拥有者可以转移资产,隐私交易过程中资金不会凭空增加和减少
- 除发送者和接收者之外,没有人可以获知交易金额详情。
- 用户可以方便的获取账户余额
隐私交易的使用流程
BubiChain隐私交易基于Pedersen承诺、范围证明等技术,详见[术语说明],在智能合约内构造UTXO交易模型,主要包含隐私资产的发行和转移两个部分。
note : 隐私交易使用单独的公私钥对,用以支持隐私交易相关的椭圆曲线算法,与原生的bubi账户公私钥对并不相同。
导入JNI接口
隐私模块使用C++实现,通过JAVA JNI实现Java层的调用,导入和调用方法详见 privacy-jni SDK
创建隐私合约和交易账户
创建隐私智能合约和交易账户的具体示例代码:
private static void createAccountOperation(BcOperationService operationService, BcQueryService queryService){
try {
Transaction transaction = operationService.newTransaction(address);
from_keypair = SecureKeyGenerator.generateBubiKeyPair();
to_keypair = SecureKeyGenerator.generateBubiKeyPair();
to_keypair1 = SecureKeyGenerator.generateBubiKeyPair();
String[] res = cn.bubi.Privacy.createKeyPair(cpp_obj);
if (Integer.valueOf(res[0]) == 0) {
ct_from_keypair[0] = res[2];
ct_from_keypair[1] = res[3];
}
res = cn.bubi.Privacy.createKeyPair(cpp_obj);
if (Integer.valueOf(res[0]) == 0) {
ct_from_keypair1[0] = res[2];
ct_from_keypair1[1] = res[3];
}
res = cn.bubi.Privacy.createKeyPair(cpp_obj);
if (Integer.valueOf(res[0]) == 0) {
ct_to_keypair[0] = res[2];
ct_to_keypair[1] = res[3];
}
res = cn.bubi.Privacy.createKeyPair(cpp_obj);
if (Integer.valueOf(res[0]) == 0) {
ct_to_keypair1[0] = res[2];
ct_to_keypair1[1] = res[3];
}
res = cn.bubi.Privacy.createKeyPair(cpp_obj);
if (Integer.valueOf(res[0]) == 0) {
ct_to_keypair2[0] = res[2];
ct_to_keypair2[1] = res[3];
}
System.out.println("from account: " + GsonUtil.toJson(from_keypair));
System.out.println("ct from account: " + ct_from_keypair[0] + ", " + ct_from_keypair[1]);
System.out.println("ct from account1: " + ct_from_keypair1[0] + ", " + ct_from_keypair1[1]);
System.out.println("to account: " + GsonUtil.toJson(to_keypair));
System.out.println("ct to account: " + ct_to_keypair[0] + ", " + ct_to_keypair[1]);
System.out.println("ct to account1: " + ct_to_keypair1[0] + ", " + ct_to_keypair1[1]);
System.out.println("ct to account2: " + ct_to_keypair2[0] + ", " + ct_to_keypair2[1]);
CreateContractOperation createContract = new CreateContractOperation.Builder()
.buildScript(getContractCodeFromFile(contract_file))
.buildInitBalance(100000000000L)
.build();
CreateAccountOperation createFrom = new CreateAccountOperation.Builder()
.buildDestAddress(from_keypair.getBubiAddress())
.buildInitBalance(100000000000L)
.build();
CreateAccountOperation createTo = new CreateAccountOperation.Builder()
.buildDestAddress(to_keypair.getBubiAddress())
.buildInitBalance(100000000000L)
.build();
TransactionCommittedResult result = transaction.buildAddOperation(createContract)
.buildAddOperation(createFrom)
.buildAddOperation(createTo)
.buildGasPrice(1000L).buildFeeLimit(10000000000L)
.buildAddSigner(BubiKey.getB16PublicKey(privateKey), privateKey)
.commit();
List<ContractAddress> addresses = queryService.getContractAddressByHash(result.getHash());
System.out.println("contract address:" + addresses.get(0).getContractAddress());
contract_address = addresses.get(0).getContractAddress();
System.out.println("\n------------------------------------------------");
System.out.println(GsonUtil.toJson(result));
} catch (SdkException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
发行隐私资产
- 创建隐私资产 隐私资产结构如下:
message ConfidentialAsset
{
string id = 1; // 资产ID
string commit = 2; // Pedersen承诺
string range_proof = 3; // 范围证明
string encrypt_value = 4; // 加密后的金额,加密密钥为发送方私钥和接受方公钥生成的ECDH密钥key=ECDH(r1, R2)
string from_pubkey = 5; // 发送方公钥
string to = 6; // 接收方地址
}
- 发行 触发智能合约发行接口,发行隐私资产
创建Pedersen承诺,范围证明,加密后的金额,组装成隐私资产结构,以JSON对象的作为参数触发智能合约发行接口。
隐私资产发行示例代码如下:
private static void issueOperation(BcOperationService operationService, Long value){
try {
Transaction transaction = operationService.newTransaction(from_keypair.getBubiAddress());
Privacy.ConfidentialAsset.Builder token = Privacy.ConfidentialAsset.newBuilder();
token.setTo(from_keypair.getBubiAddress());
token.setFromPubkey(ct_from_keypair[0]);
String[] ecdh_key_ret = cn.bubi.Privacy.createEcdhKey(cpp_obj, ct_from_keypair[1], ct_from_keypair[0]);
String errCodeEcdhKeyRet = ecdh_key_ret[0];
String errDescEcdhKeyRet = ecdh_key_ret[1];
String ecdh_key = ecdh_key_ret[2];
if (Integer.valueOf(errCodeEcdhKeyRet) != 0) {
System.out.println("Error create ecdh key: " + errDescEcdhKeyRet);
}
String[] commit_ret = cn.bubi.Privacy.createPedersenCommit(cpp_obj, value, ecdh_key);
String errCodeCmtRet = commit_ret[0];
String errDescCmtRet = commit_ret[1];
String commit = commit_ret[2];
if (Integer.valueOf(errCodeCmtRet) != 0) {
System.out.println("Error create commit: " + errDescCmtRet);
}
token.setCommit(commit);
String enc = HexFormat.byteToHex(AesCbc.encrypt(Long.toString(value).getBytes(), ecdh_key.substring(0, 32).getBytes())).toLowerCase();
token.setEncryptValue(enc);
String[] proof_ret = cn.bubi.Privacy.bpRangeproofProve(cpp_obj, ecdh_key, value);
String errCodePrfRet = proof_ret[0];
String errDescPrfRet = proof_ret[1];
String proof = proof_ret[2];
if (Integer.valueOf(errCodeCmtRet) != 0) {
System.out.println("Error create range proof: " + errDescCmtRet);
}
String[] commits = new String[1];
commits[0] = commit;
String[] res = cn.bubi.Privacy.bpRangeproofVerify(cpp_obj, commit, proof);
if (Integer.valueOf(res[0]) != 0) {
System.out.println("Error create range proof: " + errDescCmtRet);
} else {
System.out.println("Verify range proof successfully");
}
token.setRangeProof(proof);
JsonFormat jsonFormat = new JsonFormat();
String tokenStr = jsonFormat.printToString(token.build());
JSONObject input = new JSONObject();
input.put("method", "issue");
JSONObject params = new JSONObject();
params.put("name", "Cocoon");
params.put("symbol", "CNT");
params.put("token", JSONObject.parseObject(tokenStr));
input.put("params", params);
System.out.println("input: " + JSON.toJSONString(input, true));
InvokeContractOperation invokeContractOperation = new InvokeContractOperation.Builder()
.buildDestAddress(contract_address)
.buildInputData(input.toJSONString())
.build();
TransactionCommittedResult result = transaction.buildAddOperation(invokeContractOperation)
.buildGasPrice(1000L).buildFeeLimit(100000000L)
.buildAddSigner(from_keypair.getPubKey(), from_keypair.getPriKey())
.commit();
System.out.println("\n------------------------------------------------");
System.out.println(GsonUtil.toJson(result));
} catch (SdkException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
转移隐私资产
- 隐私资产交易结构如下:
message ConfidentialTx
{
string excess_sig = 1; // 致盲因子之差的签名
string excess_msg = 2; // 签名消息
repeated ConfidentialAsset inputs = 3; // 交易输入列表,元素为隐私资产,详见隐私资产结构
repeated ConfidentialAsset outputs = 4; // 交易输出列表,元素为隐私资产,详见隐私资产结构
}
- 拆分隐私资产:
隐私交易基于UTXO模型实现,拆分操作相当于将一个资产凭证作为输入,两个资产凭证作为输出,输出凭证的接收地址指定为发送者自身。
private static void createSplitOperation(BcOperationService operationService, Long value) throws Exception {
String[] ids = {};
JSONObject input = createTx(from_keypair.getBubiAddress(), from_keypair.getBubiAddress(), ct_from_keypair, ct_from_keypair[0], value, ids);
try {
Transaction transaction = operationService.newTransaction(from_keypair.getBubiAddress());
InvokeContractOperation invokeContractOperation = new InvokeContractOperation.Builder()
.buildDestAddress(contract_address)
.buildInputData(input.toJSONString())
.build();
TransactionCommittedResult result = transaction.buildAddOperation(invokeContractOperation)
.buildGasPrice(1000L).buildFeeLimit(100000000L)
.buildTxMetadata("交易")
.buildAddSigner(from_keypair.getPubKey(), from_keypair.getPriKey())
.commit();
System.out.println("\n------------------------------------------------");
System.out.println(GsonUtil.toJson(result));
} catch (SdkException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
- 转移隐私资产 转移隐私资产也基于UTXO结构,查询并获取发送方的隐私资产凭证ID,选择作为输入的凭证ID,根据转账金额和接收地址及接收方公钥(隐私相关的公钥)构造输出凭证。然后组装成隐私交易结构,以JSON对象作为参数触发智能合约转账接口。
private static void createTransferOperation(BcOperationService operationService, Long value) throws Exception {
String[] ids = {};
JSONObject input = createTx(from_keypair.getBubiAddress(), to_keypair.getBubiAddress(), ct_from_keypair, ct_to_keypair[0], value, ids);
try {
Transaction transaction = operationService.newTransaction(from_keypair.getBubiAddress());
InvokeContractOperation invokeContractOperation = new InvokeContractOperation.Builder()
.buildDestAddress(contract_address)
.buildInputData(input.toJSONString())
.build();
TransactionCommittedResult result = transaction.buildAddOperation(invokeContractOperation)
.buildGasPrice(1000L).buildFeeLimit(100000000L)
.buildTxMetadata("交易")
.buildAddSigner(from_keypair.getPubKey(), from_keypair.getPriKey())
.commit();
System.out.println("\n------------------------------------------------");
System.out.println(GsonUtil.toJson(result));
} catch (SdkException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
- createTx代码示例 示例代码中为了测试简便,将发送方和接收方相关信息,以及金额,凭证ID等作为参数传入,然后构造隐私交易的JSON对象。构造隐私交易的代码示例如下:
// bubi address mapping only one confidential transcation account
public static JSONObject createTx(String from, String to, String[] ct_from, String ct_to, long value, String[] ids) throws Exception {
// get input token from contract
String[] priv_list = new String[1];
priv_list[0] = ct_from_keypair[1];
Map input_assets = getTokenByValue(from, priv_list, value, ids);
String[] input_blinds = new String[input_assets.size()];
String[] inputs = new String[input_assets.size()];
String[] input_ids = new String[input_assets.size()];
int i = 0;
Long total_balance = 0L;
for (Object key: input_assets.keySet()) {
JSONObject token = (JSONObject) input_assets.get(key);
total_balance += token.getLong("value");
String from_pubkey = token.getString("from_pubkey");
String[] blind_ret = cn.bubi.Privacy.createEcdhKey(cpp_obj, token.getString("spend_key"), from_pubkey);
String errCodeBlindRet = blind_ret[0];
String errDescBlindRet = blind_ret[1];
String blind = blind_ret[2];
if (Integer.valueOf(errCodeBlindRet) != 0) {
System.out.println("Error create blind: " + errDescBlindRet);
i += 1;
continue;
}
input_blinds[i] = blind;
inputs[i] = token.getString("commit");
input_ids[i] = key.toString();
i += 1;
}
// build the spend token
String[] ecdh_key_ret = cn.bubi.Privacy.createEcdhKey(cpp_obj, ct_from[1], ct_to);
String errCodeEcdhRet = ecdh_key_ret[0];
String errDescEcdhRet = ecdh_key_ret[1];
String ecdh_key = ecdh_key_ret[2];
String commit = "";
if (Integer.valueOf(errCodeEcdhRet) != 0) {
System.out.println("Error create echd key: " + errDescEcdhRet);
} else {
String[] commit_ret = cn.bubi.Privacy.createPedersenCommit(cpp_obj, value, ecdh_key);
String errCodeCmtRet = commit_ret[0];
String errDescCmtRet = commit_ret[1];
if (Integer.valueOf(errCodeCmtRet) != 0) {
System.out.println("Error createPedersenCommit: " + errDescCmtRet);
} else {
commit = commit_ret[2];
}
}
String[] ret = cn.bubi.Privacy.bpRangeproofProve(cpp_obj, ecdh_key, value);
String errCodePrfCngRet = ret[0];
String errDescPrfCngRet = ret[1];
String proof = ret[2];
if (Integer.valueOf(errCodePrfCngRet) != 0) {
System.out.println("Error proof change: " + errDescPrfCngRet);
} else {
String[] bp_very_ret1 = cn.bubi.Privacy.bpRangeproofVerify(cpp_obj, commit, proof);
String errCodeBpRet1 = bp_very_ret1[0];
String errDescBpRet1 = bp_very_ret1[1];
if (Integer.valueOf(errCodeBpRet1) != 0) {
System.out.println("Failed to verify range proof:" + proof + ": " + errDescBpRet1 + "\n");
}
}
String encrypt_data = HexFormat.byteToHex(AesCbc.encrypt(Long.toString(value).getBytes(), ecdh_key.substring(0, 32).getBytes())).toLowerCase();
JSONObject token1 = buildToken(ct_from[0], to, encrypt_data, proof, commit);
JSONArray output_assets = null;
// build the change token
String commit_change = null;
String change_key = null;
String[] blinds;
String[] values;
String[] commits;
if (total_balance - value > 0) {
output_assets = new JSONArray(2);
blinds = new String[2];
values = new String[2];
commits = new String[2];
String[] change_key_ret = cn.bubi.Privacy.createEcdhKey(cpp_obj, ct_from[1], ct_from[0]);
String errCodeCngKeyRet = change_key_ret[0];
String errDescCngKeyRet = change_key_ret[1];
change_key = change_key_ret[2];
if (Integer.valueOf(errCodeCngKeyRet) != 0) {
System.out.println("Error create change key: " + errDescCngKeyRet);
}
String[] commit_change_ret = cn.bubi.Privacy.createPedersenCommit(cpp_obj, total_balance - value, change_key);
String errCodeCmtCngRet = commit_change_ret[0];
String errDescCmtCngRet = commit_change_ret[1];
commit_change = commit_change_ret[2];
if (Integer.valueOf(errCodeCmtCngRet) != 0) {
System.out.println("Error create commit change: " + errDescCmtCngRet);
}
String encrypt_change = HexFormat.byteToHex(AesCbc.encrypt(Long.toString(total_balance - value).getBytes(), change_key.substring(0, 32).getBytes())).toLowerCase();
String[] change_ret = cn.bubi.Privacy.bpRangeproofProve(cpp_obj, change_key, total_balance - value);
errCodePrfCngRet = change_ret[0];
errDescPrfCngRet = change_ret[1];
String proof_change = change_ret[2];
if (Integer.valueOf(errCodePrfCngRet) != 0) {
System.out.println("Error proof change: " + errDescPrfCngRet);
} else {
String[] bp_very_ret1 = cn.bubi.Privacy.bpRangeproofVerify(cpp_obj, commit_change, proof_change);
String errCodeBpRet1 = bp_very_ret1[0];
String errDescBpRet1 = bp_very_ret1[1];
if (Integer.valueOf(errCodeBpRet1) != 0) {
System.out.println("Failed to verify range proof:" + proof + ": " + errDescBpRet1 + "\n");
}
}
JSONObject token2 = buildToken(ct_from[0], from, encrypt_change, proof_change, commit_change);
output_assets.add(token1);
output_assets.add(token2);
blinds[1] = change_key;
values[1] = Long.toString(total_balance - value);
commits[1] = commit_change;
} else {
output_assets = new JSONArray(1);
blinds = new String[1];
values = new String[1];
commits = new String[1];
output_assets.add(token1);
}
blinds[0] = ecdh_key;
values[0] = Long.toString(value);
commits[0] = commit;
// create excess
String[] output_blinds;
if(total_balance - value > 0) {
output_blinds = new String[2];
output_blinds[1] = change_key;
} else {
output_blinds = new String[1];
}
output_blinds[0] = ecdh_key;
String data = "64e4a2fcf36693904a0d549303c6f35c";
String[] excess_sig_ret = cn.bubi.Privacy.excessSign(cpp_obj, input_blinds, output_blinds, data);
String errCodeExcSigRet = excess_sig_ret[0];
String errDescExcSigRet = excess_sig_ret[1];
String excess_sig = excess_sig_ret[2];
if (Integer.valueOf(errCodeExcSigRet) != 0) {
System.out.println("Error excess sig: " + errDescExcSigRet);
}
// verify tx data
String[] tally_very_ret = cn.bubi.Privacy.pedersenTallyVerify(cpp_obj, inputs, commits, data, excess_sig);
String errCodeTalVeryRet = tally_very_ret[0];
String errDescTalVeryRet = tally_very_ret[1];
if(Integer.valueOf(errCodeTalVeryRet) != 0) {
System.out.println("Failed to verify tally, " + errDescTalVeryRet);
} else {
System.out.println("Verify tally done\n");
}
// build tx
JSONObject txJson = buildTx(input_ids, output_assets, data, excess_sig);
JSONObject input = new JSONObject();
input.put("method", "transfer");
input.put("params", txJson);
System.out.println("The contrat input is:");
System.out.println(JSON.toJSONString(txJson, true));
return input;
}
获取账户余额
智能合约中以用户账户地址作为key,记录账户所拥有的隐私资产凭证。账户的余额等于所有凭证的金额总和,获取余额需要使用隐私相关的私钥解密并累加凭证中的金额。代码示例如下:
public static void getBalance(String address, String[] privs) throws Exception {
SetMetadata data = queryService.getAccount(contract_address, address);
System.out.println("Balance of address:" + address);
if(data == null) {
System.out.println("0");
return;
}
JSONObject dataJson = JSONObject.parseObject(data.getValue());
for (Object token: dataJson.getJSONArray("assets")) {
JSONObject t = (JSONObject) token;
long balance = 0;
boolean decrypt_done = false;
for(String priv: privs) {
String[] dec_key_ret = cn.bubi.Privacy.createEcdhKey(cpp_obj, priv, t.getString("from_pubkey"));
String errCodeDecKeyRet = dec_key_ret[0];
String errDescDecKeyRet = dec_key_ret[1];
String dec_key = dec_key_ret[2];
if (Integer.valueOf(errCodeDecKeyRet) != 0) {
System.out.println("Error decrypt key:" + errDescDecKeyRet);
throw new UnsatisfiedLinkError(errDescDecKeyRet);
} {
String encrypt_value = t.getString("encrypt_value");
try {
balance = Long.parseLong(AesCbc.decrypt(HexFormat.hexToByte(encrypt_value), dec_key.substring(0, 32).getBytes()));
decrypt_done = true;
break;
}catch (Exception e) {
continue;
}
}
}
if(!decrypt_done) {
throw new UnsatisfiedLinkError("Failed to decrypt token of id: " + t.getString("id"));
};
System.out.println("id: " + t.getString("id") + ", value:" + balance);
}
return;
}