# Paillier 开发手册 本文分为两部分: 1. 方案接口、工具、合约SDK介绍:对 ChainMaker 的 cmc 工具、合约 SDK 和 common 库提供的算法的介绍。 2. 用例:介绍了如何使用 ChainMaker Paillier 功能,从 Paillier 公私钥生成、编写智能合约到使用 SDK(本文使用 go 的 SDK) 进行数据的加密上链、链上同态运算和获取运算结果解密。 ## 接口、工具、合约SDK介绍 `common`提供 paillier 半同态加密的基础能力。 `cmc`工具提供了生成公私钥、加解密的能力,方便用户快速体验同态加密功能。 `合约SDK`提供了链上同态运算的能力,目前支持同态运算的合约 SDK 有:Go、Rust。 ### CMC 工具子命令 paillier 介绍 简介 CMC 中的 paillier 命令是用于辅助paillier算法的使用,生成公钥和私钥,并且根据参数保存到指定位置。 使用`./cmc paillier -h`获取使用帮助: ```shell ChainMaker paillier command Usage: cmc paillier [command] Available Commands: decrypt use paillier private key decrypt user data, the input encrypted data must in base64 format encrypt use paillier public key encrypt user data, the output encrypted data is in base64 format genKey generates paillier private public key Flags: -h, --help help for paillier Use "cmc paillier [command] --help" for more information about a command. ``` genKey简介 `genKey`用于生成 paillier 算法的公私钥,并根据参数保存到指定位置,查看命令详情: ```shell ./cmc paillier genKey -h generates paillier private public key Usage: cmc paillier genKey [flags] Flags: -h, --help help for genKey --name string --path string the result storage file path, and the file name is the id ``` genKey参数详解: ``` -h, --help:获取使用帮助 --name:用于保存公私钥的文件名,公钥和私钥文件名相同,后缀分别为`.prv`、`.pub` --path:存储路径 ``` encrypt简介 `encrypt`用于进行 paillier 加密,生成base64格式密文,查看命令详情: ```shell ./cmc paillier encrypt -h use paillier public key encrypt user data, the output encrypted data is in base64 format Usage: cmc paillier encrypt [flags] Flags: --data string specify input data for encrypt. e.g. --data="123" -h, --help help for encrypt --pubkey-file-path string specify paillier public key file path ``` encrypt参数详解 ```shell --data: 待加密数据 --pubkey-file-path:公钥路径 ``` decrypt简介 `encrypt`用于进行 paillier 解密,输入数据为base64格式密文,查看命令详情: ```shell ./cmc paillier decrypt -h use paillier private key decrypt user data, the input encrypted data must in base64 format Usage: cmc paillier decrypt [flags] Flags: --data string specify input data for decrypt. e.g. --data="some base64 string" -h, --help help for decrypt --privkey-file-path string specify paillier private key file path ``` decrypt参数详解 ```shell --data: 待解密数据 --privkey-file-path:私钥路径 ``` ### common 算法部分介绍 #### 公钥方法 PubKey 提供了序列化、反序列化、加密和同态运算方法。 **Encrypt**:加密一个big.Int类型的数得到对应的密文 Arguments: - plaintext:用于加密的明文 return: - Ct:加密得到的密文 - error:可能出现的错误 ```go Encrypt(plainText *big.Int) (*Ciphertext, error) ``` 同态运算方法,同态运算一般在链上进行,所以一般不会使用到这几个方法,而是使用合约SDK提供的同态运算方法,这里不再详细描述。 ```go // 两个密文相加,返回结果密文 AddCiphertext(cipher1, cipher2 *Ciphertext) (*Ciphertext, error)) // 密文加明文,返回结果密文 AddPlaintext(cipher *Ciphertext, constant *big.Int) (*Ciphertext, error) // 密文减密文,返回结果密文 SubCiphertext(cipher1, cipher2 *Ciphertext) (*Ciphertext, error) // 密文减明文,返回结果密文 SubPlaintext(cipher *Ciphertext, constant *big.Int) (*Ciphertext, error) // 密文乘以明文,返回结果密文 NumMul(cipher *Ciphertext, constant *big.Int) ``` #### 私钥方法 PrvKey 提供了包含 PubKey 所有的方法以及获取公钥、解密方法。 **Decrypt**:解密密文得到对应的明文 Arguments: - ciphertext:用于解密的密文 return: - *big.Int:解密得到的明文 - error:可能出现的错误 ```go Decrypt(ciphertext *Ciphertext) (*big.Int, error)) ``` **GetPubKey**:根据私钥获取公钥 return: - *PubKey:公钥 - error:可能出现的错误 ```go GetPubKey() (*PubKey, error) ``` #### GenKey GenKey 用于初始化公私钥对 return: - *PrvKey:生成的私钥(公钥可以从私钥中获取) - error:可能出现的错误 ```go GenKey() (*PrvKey, error) ``` ### 智能合约SDK Golang, TinyGo 和 Rust 合约SDK提供了进行同态运算的方法。 #### golang 参考[paillier sdk](https://git.chainmaker.org.cn/chainmaker/contract-sdk-go/-/blob/v2.3.4/sdk/paillier.go) #### tiny-go PaillierContext 接口提供了同态运算方法 **AddCiphertext**:使用公钥进行密文加密文的同态运算 Arguments: - pubKey:公钥的字节数组 - ct1:密文字节数组 - ct1:密文字节数组 return: - []byte结果密文字节数组 - ResultCode:函数执行状态码 ~~~go AddCiphertext(pubKey []byte, ct1 []byte, ct2 []byte) ([]byte, ResultCode) ~~~ **AddPlaintext**:使用公钥进行密文加明文的同态运算 Arguments: - pubKey:公钥字节数组 - ct:密文字节数组 - pt:明文,int64的字符串表示,超出int64链上执行将会报错 return: - []byte:结果密文字节数组 - ResultCode:函数执行状态码 ~~~go AddPlaintext(pubKey, ct []byte, pt string) ([]byte, ResultCode) ~~~ **SubCiphertext**:使用公钥进行密文减密文的同态运算 Arguments: - pubKey:公钥的字节数组 - ct1:密文字节数组 - ct1:密文字节数组 return: - []byte:结果密文字节数组 - ResultCode:函数执行状态码 ~~~go SubCiphertext(pubKey, ct1, ct2 []byte) ([]byte, ResultCode) ~~~ **SubPlaintext**:使用公钥进行密文减明文的同态运算 Argumengs: - pubKey:公钥字节数组 - ct:密文字节数组 - pt:明文,int64的字符串表示,超出int64链上执行将会报错 return: - []byte:结果密文字节数组 - ResultCode:函数执行状态码 ~~~go SubPlaintext(pubKey, ct []byte, pt string) ([]byte, ResultCode) ~~~ **NumMul**:使用公钥进行密文乘明文的同态运算 Argumengs: - pubKey:公钥字节数组 - ct:密文字节数组 - pt:明文,int64的字符串表示,超出int64链上执行将会报错 return: - []byte:结果密文字节数组 - ResultCode:函数执行状态码 ~~~go NumMul(pubKey, ct []byte, pt string) ([]byte, ResultCode) ~~~ #### rust rust合约与go合约相同,由trait `PaillierSimContext`提供了同态运算的方法: **add_ciphertext**:使用公钥计算两个密文的和 Argument: - pubKey:两个密文的公钥 - ciphertext1:密文 - ciphertext2:密文 return: - return1: 计算结果 - return2: 函数执行状态码,0:success, 1: failed ```rust fn add_ciphertext( &self, pubkey: Vec, ciphertext1: Vec, ciphertext2: Vec, ) -> Result, result_code>; ``` **add_plaintext**:使用公钥计算密文与明文的和 Argument: - pubKey:两个密文的公钥 - ciphertext:密文 - plaintext:明文,i64的字符串表示,超出i64链上执行将会报错 return: - return1: 计算结果 - return2: 函数执行状态码,0:success, 1: failed ```rust fn add_plaintext( &self, pubkey: Vec, ciphertext: Vec, plaintext: &str, ) -> Result, result_code>; ``` **sub_ciphertext**:使用公钥计算密文减去密文 Argument: - pubKey:两个密文的公钥 - ciphertext1:密文 - ciphertext2:密文 return: - return1: 计算结果 - return2: 函数执行状态码,0:success, 1: failed ```rust fn sub_ciphertext( &self, pubkey: Vec, ciphertext1: Vec, ciphertext2: Vec, ) -> Result, result_code>; ``` **sub_plaintext**:使用公钥计算密文减明文 Argument: - pubKey:两个密文的公钥 - ciphertext:密文 - plaintext:明文,i64的字符串表示,超出i64链上执行将会报错 return: - return1: 计算结果 - return2: 函数执行状态码,0:success, 1: failed ```rust fn sub_plaintext( &self, pubkey: Vec, ciphertext: Vec, plaintext: &str, ) -> Result, result_code>; ``` **num_mul**:使用公钥计算密文乘明文 Argument: - pubKey:两个密文的公钥 - ciphertext:密文 - plaintext:明文,i64的字符串表示,超出i64链上执行将会报错 return: - return1: 计算结果 - return2: 函数执行状态码,0:success, 1: failed ```rust fn num_mul( &self, pubkey: Vec, ciphertext: Vec, plaintext: &str, ) -> Result, result_code>; ``` ## 用例 ### 1. 使用cmc paillier生成并保存自己的公私钥 ```sh ./cmc paillier genKey --name=test1 --path=./paillier-key [paillier Private Key] storage file path: paillier-key/test1.prvKey [paillier Public Key] storage file path: paillier-key/test1.pubKey ``` 会在当前目录生成,paillier-key文件夹,来保存生成的公私钥文件,如下: ```shell tree ./paillier-key ./paillier-key ├── test1.prvKey └── test1.pubKey ``` ### 2. 编写智能合约 golang: 参考[paillier demo](https://git.chainmaker.org.cn/chainmaker/contract-sdk-go/-/blob/v2.3.4/demo/contract_paillier.go) 通过[build脚本](https://git.chainmaker.org.cn/chainmaker/contract-sdk-go/-/tree/master/build.sh)进行编译得到目标7z文件即可部署。 tiny-go: ```go package main import ( "encoding/base64" "strconv" ) // 安装合约时会执行此方法,必须 //export init_contract func initContract() { // 此处可写安装合约的初始化逻辑 } // 升级合约时会执行此方法,必须 //export upgrade func upgrade() { // 此处可写升级合约的逻辑 } //export paillier_test_set func paillier_test_set() { pubkeyBytes, _ := Arg("pubkey") handletype, _ := Arg("handletype") encodePara1, _ := Arg("para1") encodePara2, _ := Arg("para2") para1Bytes, _ := base64.StdEncoding.DecodeString(encodePara1) var result_code ResultCode var result_data []byte var result_data_str string test := NewPaillierContext() if handletype == "AddCiphertext" { para2Bytes, _ := base64.StdEncoding.DecodeString(encodePara2) result_data, result_code = test.AddCiphertext([]byte(pubkeyBytes), para1Bytes, para2Bytes) } else if handletype == "AddPlaintext" { result_data, result_code = test.AddPlaintext([]byte(pubkeyBytes), para1Bytes, encodePara2) } else if handletype == "SubCiphertext" { para2Bytes, _ := base64.StdEncoding.DecodeString(encodePara2) result_data, result_code = test.SubCiphertext([]byte(pubkeyBytes), para1Bytes, para2Bytes) } else if handletype == "SubPlaintext" { result_data, result_code = test.SubPlaintext([]byte(pubkeyBytes), para1Bytes, encodePara2) } else if handletype == "NumMul" { result_data, result_code = test.NumMul([]byte(pubkeyBytes), para1Bytes, encodePara2) } else { ErrorResult("finish paillier_test_set failure: error para: " + handletype) } if result_code != SUCCESS { ErrorResult("finish paillier_test_set failure: error result code: " + string(result_code)) } result_data_str = base64.StdEncoding.EncodeToString(result_data) result := PutState("paillier_test", handletype, result_data_str) if result_code == 0 { SuccessResult("finish paillier_test_set success") } else { ErrorResult("finish paillier_test_set failure") } } //export paillier_test_get func paillier_test_get() { handletype, _ := Arg("handletype") value, result := GetState("paillier_test", handletype) SuccessResult(value) } //export bulletproofs_test_set func bulletproofs_test_set() { LogMessage("[bulletproofs] ========================================start") LogMessage("[bulletproofs] bulletproofs_test_set") handleType, _ := Arg("handletype") param1, _ := Arg("para1") param2, _ := Arg("para2") param1Bytes, _ := base64.StdEncoding.DecodeString(param1) var result_code ResultCode var result_data []byte var result_data_str string bulletproofsContext := NewBulletproofsContext() switch handleType { case BulletproofsOpTypePedersenAddNum: result_data, result_code = bulletproofsContext.PedersenAddNum(param1Bytes, param2) case BulletproofsOpTypePedersenAddCommitment: param2Bytes, _ := base64.StdEncoding.DecodeString(param2) result_data, result_code = bulletproofsContext.PedersenAddCommitment(param1Bytes, param2Bytes) case BulletproofsOpTypePedersenSubNum: result_data, result_code = bulletproofsContext.PedersenSubNum(param1Bytes, param2) case BulletproofsOpTypePedersenSubCommitment: param2Bytes, _ := base64.StdEncoding.DecodeString(param2) result_data, result_code = bulletproofsContext.PedersenSubCommitment(param1Bytes, param2Bytes) case BulletproofsOpTypePedersenMulNum: result_data, result_code = bulletproofsContext.PedersenMulNum(param1Bytes, param2) case BulletproofsVerify: param2Bytes, _ := base64.StdEncoding.DecodeString(param2) result_data, result_code = bulletproofsContext.Verify(param1Bytes, param2Bytes) default: ErrorResult("bulletproofs_test_set failed, error: " + handleType) result_code = 1 } if result_code != SUCCESS { ErrorResult("bulletproofs_test_set failed, error: " + string(rune(result_code))) } result_data_str = base64.StdEncoding.EncodeToString(result_data) result := PutState("bulletproofs_test", handleType, result_data_str) if result_code == 0 { SuccessResult("bulletproofs_test_set success") } else { ErrorResult("bulletproofs_test_set failure") } } //export bulletproofs_test_get func bulletproofs_test_get() { handletype, _ := Arg("handletype") value, result := GetState("bulletproofs_test", handletype) if handletype == "BulletproofsVerify" { decodeValue, err := base64.StdEncoding.DecodeString(value) if err != nil { ErrorResult("base64.StdEncoding.DecodeString(value) failed") } LogMessage(handletype) SuccessResult(string(decodeValue)) } else { LogMessage(handletype) SuccessResult(value) } } func main() { } ``` 编译生成wasm文件: ```shell tinygo build -no-debug -opt=s -o contract-paillier.wasm -target wasm ``` rust: ```rust // 安装合约时会执行此方法,必须 #[no_mangle] pub extern "C" fn init_contract() { // 安装时的业务逻辑,可为空 sim_context::log("init_contract"); } // 升级合约时会执行此方法,必须 #[no_mangle] pub extern "C" fn upgrade() { // 升级时的业务逻辑,可为空 sim_context::log("upgrade success"); } #[no_mangle] pub extern "C" fn paillier_test_set() { sim_context::log("[paillier] ========================================start"); sim_context::log("[paillier] input func: paillier_test_set"); let ctx = sim_context::get_sim_context(); let pubkey = ctx.arg_default_blank("pubkey"); let handletype = ctx.arg_default_blank("handletype"); let para1 = ctx.arg_default_blank("para1"); let decode_para1 = decode(para1.as_bytes()).unwrap(); let para2 = ctx.arg_default_blank("para2"); let test = ctx.get_paillier_sim_context(); let r: Result, i32>; if handletype == "AddCiphertext" { let decode_para2 = decode(para2.as_bytes()).unwrap(); r = test.add_ciphertext(pubkey.into_bytes(), decode_para1, decode_para2); } else if handletype == "AddPlaintext" { r = test.add_plaintext(pubkey.into_bytes(), decode_para1, ¶2); } else if handletype == "SubCiphertext" { let decode_para2 = decode(para2.as_bytes()).unwrap(); r = test.sub_ciphertext(pubkey.into_bytes(), decode_para1, decode_para2); } else if handletype == "SubPlaintext" { r = test.sub_plaintext(pubkey.into_bytes(), decode_para1, ¶2); } else if handletype == "NumMul" { r = test.num_mul(pubkey.into_bytes(), decode_para1, ¶2); } else { ctx.error(&format!( "finish paillier_test_set failure: error para: {}", handletype )); return; } if r.is_err() { ctx.error("finish paillier_test_set failure"); return; } let data = r.unwrap(); let data_u8 = data.as_slice(); let data_str = encode(data_u8); let put_code = ctx.put_state("paillier_test", &handletype, data_str.as_bytes()); ctx.ok("finish paillier_test_set success".as_bytes()); } #[no_mangle] pub extern "C" fn paillier_test_get() { let ctx = sim_context::get_sim_context(); let handletype = ctx.arg_default_blank("handletype"); let r = ctx.get_state("paillier_test", &handletype); if r.is_err() { sim_context::log("[zitao] paillier_test_get error"); ctx.error("finish paillier_test_get failure"); return; } let data = r.unwrap(); let result = String::from_utf8(data); let result_str = result.unwrap(); ctx.ok(result_str.as_bytes()); } ``` 编译生成wasm字节码文件 ```shell make build ``` ### 3. 使用SDK编写测试用例 > SDK并未提供 paillier 相关接口,开发者需要直接调用common库中的 *chainmaker.org/sdk-go/common/crypto/paillier*包。 > 该SDK仅支持tiny-go和rust合约文件,如果使用golang合约,请修改合约部署代码,并修改invoke参数handletype为method。 > cmc工具同样支持进行密钥生成、加解密、与demo合约进行交互。 总测试函数: ```go const ( sdkConfigOrg1Client1Path = "../sdk_configs/sdk_config_org1_client1.yml" createContractTimeout = 5 ) const ( // go 合约 paillierContractName = "pailliergo100001" paillierByteCodePath = "../../testdata/paillier-wasm-demo/contract-paillier.wasm" runtime = common.RuntimeType_GASM // rust 合约 //paillierContractName = "paillier-rust-10001" //paillierByteCodePath = "./testdata/counter-go-demo/chainmaker_contract.wasm" //runtime = common.RuntimeType_WASMER paillierPubKeyFilePath = "../../testdata/paillier-key/test1.pubKey" paillierPrvKeyFilePath = "../../testdata/paillier-key/test1.prvKey" ) func main() { TestPaillierContractCounterGo() } func TestPaillierContractCounterGo() { t := new(testing.T) client, err := examples.CreateChainClientWithSDKConf(sdkConfigOrg1Client1Path) require.Nil(t, err) fmt.Println("======================================= 创建合约(异步)=======================================") testPaillierCreate(client, examples.UserNameOrg1Admin1, examples.UserNameOrg2Admin1, examples.UserNameOrg3Admin1, examples.UserNameOrg4Admin1, false) time.Sleep(5 * time.Second) fmt.Println("======================================= 调用合约运算(异步)=======================================") testPaillierOperation(client, "paillier_test_set", false) time.Sleep(5 * time.Second) fmt.Println("======================================= 查询结果并解密(异步)=======================================") testPaillierQueryResult(t, client, "paillier_test_get") } ``` 创建合约: ```go // 创建合约 func testPaillierCreate(client *sdk.ChainClient, admin1, admin2, admin3, admin4 string, withSyncResult bool) { resp, err := createUserContract(client, admin1, admin2, admin3, admin4, paillierContractName, examples.Version, paillierByteCodePath, runtime, []*common.KeyValuePair{}, withSyncResult) if err != nil { log.Fatalln(err) } fmt.Printf("CREATE contract-hibe-1 contract resp: %+v\n", resp) } func createUserContract(client *sdk.ChainClient, admin1, admin2, admin3, admin4 string, contractName, version, byteCodePath string, runtime common.RuntimeType, kvs []*common.KeyValuePair, withSyncResult bool) (*common.TxResponse, error) { payload, err := client.CreateContractCreatePayload(contractName, version, byteCodePath, runtime, kvs) if err != nil { return nil, err } endorsers, err := examples.GetEndorsers(payload, admin1, admin2, admin3, admin4) if err != nil { return nil, err } resp, err := client.SendContractManageRequest(payload, endorsers, createContractTimeout, withSyncResult) if err != nil { return nil, err } return resp, nil } ``` 调用合约方法进行链上同态运算: ```go // 调用合约进行同态运算 func testPaillierOperation(client *sdk.ChainClient, s string, b bool) { pubKeyBytes, err := ioutil.ReadFile(paillierPubKeyFilePath) //require.Nil(t, err) if err != nil { log.Fatalln(err) } payloadParams, err := CreatePaillierTransactionPayloadParams(pubKeyBytes, 1, 1000000) resp, err := client.InvokeContract(paillierContractName, s, "", payloadParams, -1, b) //require.Nil(t, err) if err != nil { log.Fatalln(err) } if resp.Code != common.TxStatusCode_SUCCESS { fmt.Printf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message) } } func CreatePaillierTransactionPayloadParams(pubKeyBytes []byte, plaintext1, plaintext2 int64) ([]*common.KeyValuePair, error) { pubKey := new(paillier.PubKey) err := pubKey.Unmarshal(pubKeyBytes) if err != nil { } pt1 := new(big.Int).SetInt64(plaintext1) ciphertext1, err := pubKey.Encrypt(pt1) if err != nil { return nil, err } ct1Bytes, err := ciphertext1.Marshal() if err != nil { return nil, err } prv2, _ := paillier.GenKey() pub2, _ := prv2.GetPubKey() _, _ = pub2.Marshal() pt2 := new(big.Int).SetInt64(plaintext2) ciphertext2, err := pubKey.Encrypt(pt2) if err != nil { return nil, err } ct2Bytes, err := ciphertext2.Marshal() if err != nil { return nil, err } ct1Str := base64.StdEncoding.EncodeToString(ct1Bytes) ct2Str := base64.StdEncoding.EncodeToString(ct2Bytes) payloadParams := []*common.KeyValuePair{ { Key: "handletype", Value: []byte("SubCiphertext"), }, { Key: "para1", Value: []byte(ct1Str), }, { Key: "para2", Value: []byte(ct2Str), }, { Key: "pubkey", Value: pubKeyBytes, }, } /* old payloadParams := make(map[string]string) //payloadParams["handletype"] = "AddCiphertext" //payloadParams["handletype"] = "AddPlaintext" payloadParams["handletype"] = "SubCiphertext" //payloadParams["handletype"] = "SubCiphertextStr" //payloadParams["handletype"] = "SubPlaintext" //payloadParams["handletype"] = "NumMul" payloadParams["para1"] = ct1Str payloadParams["para2"] = ct2Str payloadParams["pubkey"] = string(pubKeyBytes) */ return payloadParams, nil } ``` 查询运算结果并解密: ```go // 查询同态执行结果并解密 func testPaillierQueryResult(t *testing.T, c *sdk.ChainClient, s string) { //paillierMethod := "AddCiphertext" //paillierMethod := "AddPlaintext" paillierMethod := "SubCiphertext" //paillierMethod := "SubCiphertextStr" //paillierMethod := "SubPlaintext" params1, err := QueryPaillierResult(c, paillierContractName, s, paillierMethod, -1, paillierPrvKeyFilePath) require.Nil(t, err) fmt.Printf("QUERY %s contract resp -> encrypt(cipher 10): %d\n", paillierContractName, params1) } func QueryPaillierResult(c *sdk.ChainClient, contractName, method, paillierDataItemId string, timeout int64, paillierPrvKeyPath string) (int64, error) { resultStr, err := QueryPaillierResultById(c, contractName, method, paillierDataItemId, timeout) if err != nil { return 0, err } ct := new(paillier.Ciphertext) resultBytes, err := base64.StdEncoding.DecodeString(string(resultStr)) if err != nil { return 0, err } err = ct.Unmarshal(resultBytes) if err != nil { return 0, err } prvKey := new(paillier.PrvKey) prvKeyBytes, err := ioutil.ReadFile(paillierPrvKeyPath) if err != nil { return 0, fmt.Errorf("open paillierKey file failed, [err:%s]", err) } err = prvKey.Unmarshal(prvKeyBytes) if err != nil { return 0, err } decrypt, err := prvKey.Decrypt(ct) if err != nil { return 0, err } return decrypt.Int64(), nil } func QueryPaillierResultById(c *sdk.ChainClient, contractName, method, paillierMethod string, timeout int64) ([]byte, error) { pairs := []*common.KeyValuePair{ {Key: "handletype", Value: []byte(paillierMethod)}, } /* old pairsMap := make(map[string]string) pairsMap["handletype"] = paillierMethod */ resp, err := c.QueryContract(contractName, method, pairs, timeout) if err != nil { return nil, err } result := resp.ContractResult.Result return result, nil } ``` 执行测试,结果如下: ```go === RUN TestPaillierContractCounterGo ======================================= 创建合约(异步)======================================= CREATE contract-paillier-1 contract resp: message:"OK" contract_result: ======================================= 调用合约运算(异步)======================================= invoke contract success, resp: [code:0]/[msg:OK]/[txId:81ac75ccc8914ce694a0df6ddebe2c5fddea319d784a461cb52ec9b5fc373125] ======================================= 查询结果并解密(异步)======================================= QUERY paillier-rust-10001 contract resp -> encrypt(cipher 10): -999999 --- PASS: TestPaillierContractCounterGo (19.23s) PASS ```