BubiChain 隐私交易 JNI SDK
概述
该 JNI SDK 是为了方便 Java 端实现隐私保护功能。
安装
隐私保护的动态库是由布比官方提供。
说明
这里介绍 JNI SDK 的 方法格式说明,其中的包路径,类名,接口名称都不能变更,否则调用不到 JNI 接口。使用时 Java 端使用 Java 调用方法,而不是使用 JNI 方法格式。
以下面接口来为便说明:
JNI 方法格式
JNIEXPORT jlong JNICALL Java_cn_bubi_Privacy_init (JNIEnv *, jclass)
语言
Java
包路径
cn.bubi
类名
Privacy
接口名称
init
Java 调用方法
在 Java 中的 cn.bubi 包的 Privacy 类下有如下一个方法:
public native static long init();
参数
JNI SDK 必须包括的共同参数:
名称 | 类型 | 描述 |
---|---|---|
env | JNIEnv* | JNI 环境变量 |
classObject | jclass | JNI 类环境变量 |
返回值
jstring
字符串类型
接口
这里主要包括 初始化对象、销毁对象、 创建 Pedersen 承诺、创建 ecdh 密钥、创建 ecc-secp256k1 密钥、根据私钥获取公钥、创建范围证据、范围证据验证、ecdsa 签名、ecdsa 验签、创建交易excess签名、合计验证,这些接口都是在 Java 中的 cn.bubi 包的 Privacy 类下。 这些接口都做了异常处理,需要用 Exception 进行捕获。
初始化对象
接口说明
该接口的功能是用于初始化隐私保护 SDK 的 C++ 对象指针。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jlong JNICALL Java_cn_bubi_Privacy_init (JNIEnv *env, jclass classObject)
Java 调用方法
public native static long init();
返回值
C++对象指针返回值。
销毁对象
接口说明
该接口的功能是销毁隐私保护 SDK 的 C++ 对象。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT void JNICALL Java_cn_bubi_Privacy_destroy (JNIEnv *env, jclass classObject, jlong obj)
Java 调用方法
public native static void destroy(long obj);
请求参数
名称 类型 描述 obj long C++ 对象指针
创建 Pedersen 承诺
接口说明
该接口的功能是用致盲因子对需要保密的内容进行加密处理。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_createPedersenCommit (JNIEnv* env, jclass classObject, jlong obj, jlong value, jstring blind);
Java 调用方法
public native static String[] createPedersenCommit(long obj, long value, String blind);
请求参数
名称 类型 描述 obj long C++ 对象指针 value long 隐私保护内容 blind String 致盲因子长度必须是64位(使用ecdh秘钥) 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String Pedersen 承诺,0时非空,非0时为空
创建 ecdh 密钥
接口说明
该接口的功能是创建 ecdh 密钥,用于致盲。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_createEcdhKey (JNIEnv* env, jclass classObject, jlong obj, jstring seckey, jstring pubkey)
Java 调用方法
public native static String[] createEcdhKey(long obj, String seckey, String pubkey);
请求参数
名称 类型 描述 obj long C++ 对象指针 seckey String 保密私钥 pubkey String 保密公钥 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String ecdh 密钥,0时非空,非0时为空
创建 ecc-secp256k1 密钥
接口说明
该接口的功能是创建 ecc-secp256k1 密钥对,包括公钥和私钥。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_createKeyPair (JNIEnv* env, jclass classObject, jlong obj)
Java 调用方法
public native static String[] createKeyPair(long obj);
请求参数
名称 类型 描述 obj long C++ 对象指针 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String ecc-secp256k1 公钥,16进制字符串 3 String ecc-secp256k1 私钥,16进制字符串
根据私钥获取公钥
接口说明
该接口的功能是用 ecc-secp256k1 私钥,得出 ecc-secp256k1 公钥。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_getPublicKey (JNIEnv* env, jclass classObject, jlong obj, jstring seckey)
Java 调用方法
public native static String[] getPublicKey(long obj, String seckey);
请求参数
名称 类型 描述 obj long C++ 对象指针 seckey String 保密私钥 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String ecc-secp256k1 公钥,16进制字符串
创建范围证据
接口说明
该接口的功能是使用 bulletproof 生成输入值的范围证据,证明该输入值是正确的。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_bpRangeproofProve (JNIEnv* env, jclass classObject, jlong obj, jstring blind, jlong value)
Java 调用方法
public native static String[] bpRangeproofProve(long obj, String blind, long value);
请求参数
名称 类型 描述 obj long C++ 对象指针 blind String 致盲因子 value long 转账金额 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String 证据
范围证据验证
接口说明
该接口的功能是验证范围证据的正确性。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_bpRangeproofVerify (JNIEnv* env, jclass classObject, jlong obj, jstring commit, jstring proof)
Java 调用方法
public native static String[] bpRangeproofVerify(long obj, String commit, String proof);
请求参数
名称 类型 描述 obj long C++ 对象指针 commit String pedersen 承诺 proof String 范围证据 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示验证成功,非0表示失败 1 String 错误描述,0时为空,非0为非空
ecdsa 签名
接口说明
该接口的功能是创建 ecdsa 签名。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_ecdsaSign (JNIEnv* env, jclass classObject, jlong obj, jstring seckey, jstring data)
Java 调用方法
public native static String[] ecdsaSign(long obj, String seckey, String data);
请求参数
名称 类型 描述 obj long C++ 对象指针 seckey String ecc-secp256k1 私钥 data String 待签名数据,长度为32位,无编码要求 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String 签名字符串
ecdsa 验签
接口说明
该接口的功能是验证 ecdsa 签名。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_ecdsaVerify (JNIEnv* env, jclass classObject, jlong obj, jstring pubkey, jstring data, jstring sig)
Java 调用方法
public native static String[] ecdsaVerify(long obj, String pubkey, String data, String sig);
请求参数
名称 类型 描述 obj long C++ 对象指针 pubkey String ecc-secp256k1 公钥 data String 待签名数据,长度为32位,无编码要求 sig String 签名数据 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示验证成功,非0表示失败 1 String 错误描述,0时为空,非0为非空
创建交易excess签名
接口说明
该接口的功能是创建交易excess签名,即用输入的致盲因子列表减去输出的致盲因子列表得到excess,然后对待签名的信息进行签名。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_excessSign (JNIEnv* env, jclass classObject, jlong obj, jobjectArray inputs, jobjectArray outputs, jstring msg)
Java 调用方法
public native static String[] excessSign(long obj, String[] inputs, String[] outputs, String msg);
请求参数
名称 类型 描述 obj long C++ 对象指针 inputs String[] 输入的致盲因子列表 outputs String[] 输出的致盲因子列表 msg String 待签名的消息,长度为32位,无编码要求 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示成功,非0表示失败 1 String 错误描述,0时为空,非0为非空 2 String 签名字符串
合计验证
接口说明
该接口的功能是验证输入输入金额和输出金额是否相等, 将分别验证输入输出pedersen承诺(rG + vH)的致盲因子之差的签名,以及金额部分是否相等。
JNI 方法格式
该方法不是供 Java 调用的,是 C++ 的 JNI 中定义的格式。只是说明 Java 该如何使用该方法,请看说明。
JNIEXPORT jobjectArray JNICALL Java_cn_bubi_Privacy_pedersenTallyVerify (JNIEnv* env, jclass classObject, jlong obj, jobjectArray inputs, jobjectArray outputs, jstring msg, jstring sig)
Java 调用方法
public native static String[] pedersenTallyVerify(long obj, String[] inputs, String[] outputs, String msg, String sig);
请求参数
名称 类型 描述 obj long C++ 对象指针 inputs String[] 输入的承诺列表 outputs String[] 输出的承诺列表 msg String 待签名的消息,长度为32位,无编码要求 sig String excess签名数据 返回值
字符串数组:
数组下标 类型 描述 0 Int 错误码,0表示验证成功,非0表示失败 1 String 错误描述,0时为空,非0为非空
错误码
错误码 | 描述 |
---|---|
0 | Success |
201 | Generates a random number error! |
202 | Invalid parameters |
203 | Failed to create pedersen commitment |
204 | Failed to parse pedersen commitment |
205 | Failed to serialize pedersen commitment |
206 | Failed to serialize pubkey |
207 | Failed to verify tally |
208 | Failed to create pubkey |
209 | Failed to generate rangeproof |
210 | Failed to verify rangeproof |
211 | Out of range |
212 | Failed to create ecdsa signature |
213 | Failed to serialize ecdsa signature |
214 | Failed to verify ecdsa signature |
215 | Failed to parse ecdsa signature |
216 | Failed to parse pubkey |
217 | Failed to blind sum |
218 | An unknown error |
219 | BP library internal error |
示例
普通账户 A 在隐私保护合约账户 C 下发行了1亿的资产 CT,并向普通账户 B 转移了5千万的CT,然后查询账户 A 和账户 B 的 CT 资产余额。详情请看 Java 的 Demo (代码在内网,暂时无链接)。
这里用到的 java 的依赖库有如下 :
<dependencies>
<dependency>
<groupId>cn.bubi.access.sdk</groupId>
<artifactId>sdk-starter</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>nexus</id>
<url>http://192.168.1.11:8081/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
- 创建普通账户 A、普通账户B 和 合约账户 C。
/**
* 创建合约相关账户操作
*/
private static void createAccountOperation(BcOperationService operationService){
try {
Transaction transaction = operationService.newTransaction(address);
contract_keyPair_C = SecureKeyGenerator.generateBubiKeyPair(); // 合约账户
from_keypair_A = SecureKeyGenerator.generateBubiKeyPair(); // 普通源账户
to_keypair_B = SecureKeyGenerator.generateBubiKeyPair(); // 普通目标账户
String[] res = cn.bubi.Privacy.createKeyPair(cpp_obj); // 源账户对应的 ecc-secp256k1 公私钥
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); // 目标账户对应的 ecc-secp256k1 公私钥
if (Integer.valueOf(res[0]) == 0) {
ct_to_keypair[0] = res[2];
ct_to_keypair[1] = res[3];
}
System.out.println("contract account: " + GsonUtil.toJson(contract_keyPair));
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("to account: " + GsonUtil.toJson(to_keypair));
System.out.println("ct to account: " + ct_to_keypair[0] + ", " + ct_to_keypair[1]);
// 创建合约账户操作
CreateAccountOperation createContract = new CreateAccountOperation.Builder()
.buildDestAddress(contract_keyPair.getBubiAddress())
.buildScript(getContractCodeFromFile(contract_file))
// 权限部分
.buildPriMasterWeight(0)
.buildPriTxThreshold(1)
.build();
// 创建普通源账户操作
CreateAccountOperation createFrom = new CreateAccountOperation.Builder()
.buildDestAddress(from_keypair.getBubiAddress())
// 权限部分
.buildPriMasterWeight(1)
.buildPriTxThreshold(1)
.build();
// 创建普通目标账户操作
CreateAccountOperation createTo = new CreateAccountOperation.Builder()
.buildDestAddress(to_keypair.getBubiAddress())
// 权限部分
.buildPriMasterWeight(1)
.buildPriTxThreshold(1)
.build();
// 提交交易
TransactionCommittedResult result = transaction.buildAddOperation(createContract)
.buildAddOperation(createFrom)
.buildAddOperation(createTo)
.buildAddSigner(BubiKey.getB16PublicKey(privateKey), privateKey)
.commit();
System.out.println(GsonUtil.toJson(result));
} catch (Exception e) {
e.printStackTrace();
}
}
- 普通账户 A 在合约账户 C 下发行1亿个 CT 资产。
/**
* 发行合约资产操作
*/
private static void issueOperation(BcOperationService operationService){
try {
Transaction transaction = operationService.newTransaction(from_keypair.getBubiAddress());
// 构建 token 隐私信息
Privacy.ConfidentialAsset.Builder token = Privacy.ConfidentialAsset.newBuilder();
token.setTo(from_keypair.getBubiAddress()); // ed25519 的 bubi “源账户”地址
token.setFromPubkey(ct_from_keypair[0]); // ecc-secp256k1 的“源账户”公钥
String[] ecdh_key_ret = cn.bubi.Privacy.createEcdhKey(cpp_obj, ct_from_keypair[1], ct_from_keypair[0]); // ecc-secp256k1 的“源账户”私钥和公钥生成致盲因子
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); // 创建承诺(对1亿的资产承诺)
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("100000000".getBytes(), ecdh_key.substring(0, 32).getBytes())).toLowerCase(); // 对1亿的资产进行隐私保护
token.setEncryptValue(enc); // 隐私数据
String[] blinds = new String[1];
blinds[0] = ecdh_key;
String[] values = new String[1];
values[0] = Long.toString(value);
String[] proof_ret = cn.bubi.Privacy.bpRangeproofProve(cpp_obj, blinds, values); // 对1亿的资产生成证据
String errCodePrfRet = proof_ret[0];
String errDescPrfRet = proof_ret[1];
String proof = proof_ret[2];
if (Integer.valueOf(errCodePrfRet) != 0) {
System.out.println("Error create range proof: " + errDescPrfRet);
}
String[] commits = new String[1];
commits[0] = commit;
String[] res = cn.bubi.Privacy.bpRangeproofVerify(cpp_obj, commits, proof); // 验证证据
if (Integer.valueOf(res[0]) != 0) {
System.out.println("Error very range proof: " + res[1]);
}
token.setRangeProof(proof); // 证据
JsonFormat jsonFormat = new JsonFormat();
String tokenStr = jsonFormat.printToString(token.build());
// 发行资产的 input 结构
JSONObject input = new JSONObject();
input.put("method", "issue");
JSONObject params = new JSONObject();
params.put("name", "Confidential");
params.put("symbol", "CT");
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_keyPair.getBubiAddress())
.buildInputData(input.toJSONString())
.build();
// 提交交易
TransactionCommittedResult result = transaction.buildAddOperation(invokeContractOperation)
.buildAddSigner(from_keypair.getPubKey(), from_keypair.getPriKey())
.commit();
System.out.println(GsonUtil.toJson(result));
} catch (Exception e) {
e.printStackTrace();
}
}
- 普通账户 A 向普通账户 B 转移5千万的 CT 资产。
/**
* 转移资产操作
*/
private static void createTransferOperation(BcOperationService operationService){
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_keyPair.getBubiAddress())
.buildInputData(input.toJSONString())
.build();
// 提交交易
TransactionCommittedResult result = transaction.buildAddOperation(invokeContractOperation)
.buildTxMetadata("交易")
.buildAddSigner(from_keypair.getPubKey(), from_keypair.getPriKey())
.commit();
System.out.println(GsonUtil.toJson(result));
} catch (Exception e) {
e.printStackTrace();
}
}
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];
// 根据转账金额获得足够的token
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;
// 生成excess签名和校验所需的参数
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;
}
// 创建输出的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;
// 创建找零的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;
// 创建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);
}
// 验证输入输出相等
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");
}
// 创建交易json结构
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;
}
public static JSONObject buildToken(String from_pubkey, String to, String encrypt_value, String rangeproof, String commit) {
JSONObject token = new JSONObject();
token.put("from_pubkey", from_pubkey);
token.put("to", to);
token.put("encrypt_value", encrypt_value);
if (rangeproof != null) {
token.put("range_proof", rangeproof);
}
token.put("commit", commit);
return token;
}
public static JSONObject buildTx(String[] inputs_ids, JSONArray outputs, String msg, String sig, String proof) {
JSONObject tx = new JSONObject();
JSONArray ips = new JSONArray();
for (String id: inputs_ids) {
JSONObject token = new JSONObject();
token.put("id", id);
ips.add(token);
}
tx.put("inputs", ips);
JSONArray ops = new JSONArray();
for (Object token: outputs) {
ops.add(token);
}
tx.put("outputs", ops);
tx.put("excess_sig", sig);
tx.put("excess_msg", msg);
tx.put("proof", proof);
return tx;
}
public static Map getTokenByValue(String address, String[] privs, long value, String[] ids) throws Exception {
SetMetadata data = queryService.getAccount(contract_keyPair.getBubiAddress(), address);
JSONObject dataJson = JSONObject.parseObject(data.getValue());
Long total_balance = 0L;
Map<String, JSONObject> map = new HashMap<String, JSONObject>();
for (Object token: dataJson.getJSONArray("assets")) {
JSONObject t = (JSONObject) token;
if (ids.length > 0) {
boolean got = false;
for (String id : ids) {
if (t.getString("id").equals(id)) {
got = true;
break;
}
}
if(!got) continue;
}
Long balance = 0L;
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()));
t.put("spend_key", priv);
t.put("value", balance);
decrypt_done = true;
break;
}catch (Exception e) {
continue;
}
}
}
if(!decrypt_done) {
throw new UnsatisfiedLinkError("Failed to decrypt token of id: " + t.getString("id"));
};
total_balance += balance;
map.put(t.getString("id"), t);
}
if (total_balance < value) {
//throw new UnsatisfiedLinkError("Balance not enough, want:" + Long.toString(value) + ", got:" + Long.toString(total_balance));
System.out.println("Balance not enough, want:" + Long.toString(value) + ", got:" + Long.toString(total_balance));
}
if (ids.length > 0) return map;
Long tmp_value = 0L;
Map<String, JSONObject> new_map = new HashMap<String, JSONObject>();
for (String key: map.keySet()) {
JSONObject t = map.get(key);
tmp_value += t.getLong("value");
new_map.put(key, map.get(key));
if(tmp_value >= value) break;
}
return new_map;
}
- 查询普通账户 的 CT 资产余额。
/**
* 查询资产发送者的资产余额
*/
public static void getBalance(String address, String[] privs) throws Exception {
// 从合约内查询账户资产信息
SetMetadata data = queryService.getAccount(contract_keyPair.getBubiAddress(), 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;
}
- 附加函数
从文件中读取信息:
private static String getContractCodeFromFile(String fileName) {
StringBuilder result = new StringBuilder();
try{
File file = new File(fileName);
BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件
String s = null;
while((s = br.readLine())!=null){//使用readLine方法,一次读一行
result.append(System.lineSeparator() + s);
}
br.close();
}catch(Exception e){
e.printStackTrace();
}
return result.toString();
}