1,摘要
本文介绍通过调用蚂蚁BAAS的TEE硬件隐私链的JS SDK,完成智能合约读取,编译和加密部署功能。然后通过基于EXPRESS框架搭建的前端页面完成该姓名/年龄前端系统的写入/查询功能,演示隐私链的接口基本功能。
2,需求和代码介绍
2.1 需求
本需求主要是作为入门级DAPP,主要能读取智能合约中的姓名/年龄信息,同时也能写入更新姓名/年龄。该智能合约需要部署在TEE硬件隐私链上。
部署在标准合约链的参考文章《蚂蚁区块链第13课 如何搭建一个DAPP应用(以姓名年龄为例)》。
2.2 智能合约
InfoContract.sol智能合约:
pragma solidity ^0.4.23;
contract InfoContract {
string name;
uint age;
event Instructor(string name, uint age);
function setInfo(string _name, uint _age) public {
name = _name;
age = _age;
emit Instructor(name, age);
}
function getInfo() public view returns(string, uint) {
return (name, age);
}
}
2.3 前端UI和代码
功能说明
(1)输入“姓名”,“年龄”,点击更新,完成加密更新智能合约的数据;
(2)输入AESS密钥,点击“解密查询”,查询结果数据。
对应的“home.ejs”的前端代码如下:
Document
This is my homepage
<%= name[0] %>
<%= name[1] %>
<%= info %>
2.4 JS SDK接口调用文件
JS SDK接口调用文件app.js的代码如下:
let express = require("express");
let app = express();
const Chain = require("@alipay/mychain/index.node") //在 node 环境使用 TLS 协议
const fs = require("fs")
const solc = require('@alipay/solc')
const accountKey = fs.readFileSync("./certs/duncanwang-user.pem", { encoding: "utf8" })
const accountPassword = "2018ceshi" //需要替换为自定义的 user.pem 密码
const keyInfo = Chain.utils.getKeyInfo(accountKey, accountPassword)
//可打印私钥和公钥,使用 16 进制
console.log('private key:', keyInfo.privateKey.toString('hex'))
console.log('public key:', keyInfo.publicKey.toString('hex'))
const passphrase = "2018ceshi" //需要替换为自定义的 client.key 密码
//配置选项
let opt = {
host: '139.196.136.94', //目标区块链网络节点的 IP
port: 18130, //端口号
timeout: 30000, //连接超时时间配置
cert: fs.readFileSync("./certs/client.crt", { encoding: "utf8" }),
ca: fs.readFileSync("./certs/ca.crt", { encoding: "utf8" }),
key: fs.readFileSync("./certs/client.key", { encoding: "utf8" }),
userPublicKey: keyInfo.publicKey,
userPrivateKey: keyInfo.privateKey,
userRecoverPublicKey: keyInfo.publicKey,
userRecoverPrivateKey: keyInfo.privateKey,
passphrase: passphrase
}
//初始化一个连接实例
const chain = Chain(opt)
//调用 API 查询最新的一个区块数据
/*
chain.ctr.QueryLastBlock({}, (err, data) => {
console.log('raw data:', data) //区块结构数据
console.log('block hash:', data.block.block_header.hash) //区块哈希
console.log('block number:', data.block.block_header.block_number) //区块高度
})*/
const contract = fs.readFileSync('./contracts/InfoContract.sol', {encoding: 'ascii'})
// 第二个参数设定为 1 ,会开启编译优化 optimiser
const output = solc.compile(contract, 1)
const abi = JSON.parse(output.contracts[':InfoContract'].interface)
const bytecode = output.contracts[':InfoContract'].bytecode
// 读取 TEE 合约链节点的公钥文件 tee_rsa_public_key.pem
let rsa2048 = {
public: fs.readFileSync('./certs/tee_rsa_public_key.pem')
}
// 自定义的 aes 密码,此密码与加密交易的 hash 联合计算生成最终的 aes 密钥
let aes_key = '0x1c4f2919963e8dc040cfddf7d27227de'
contractName = 'contract'+Date.now()
// 初始化一个合约实例
let myContract = chain.ctr.contract(contractName, abi)
// 基础数据
let basicInfo = {
from: 'duncanwang',
encrypt: true,
rsaPublicKey: rsa2048.public,
aesKey: aes_key
}
// 加密部署合约,保护隐私
let autoDeploy = (info) => {
return new Promise((resolve, reject)=>{
myContract.new(bytecode,info,(err, contract, data) => {
resolve(data)
})
})
}
// 加密 (查询\获取) 信息
let setInfo = (func_parml, info) => {
return new Promise((resolve, reject)=>{
func_parml.type === 'set' ? myContract.setInfo(func_parml.name, func_parml.age, info, (err, contract, data) =>{resolve(data)}) : myContract.getInfo(info,(err, contract, data) =>{resolve({contract,data})})
})
}
//初始化方法
let initialize = async () => {
let initial = await autoDeploy(basicInfo)
let setData = await setInfo({type: 'set', name :'duncanwang' , age : 35}, basicInfo)
//初始化成功,开启服务
let server = require('http').createServer(app);
server.listen(5000);{
console.log("Sever Ready! open on http://localhost:5000");
}
}
//获取方法
let getData = async (msg) => {
let userInfo = await setInfo({type: 'get'}, msg)
let info = myContract.getOutput('getInfo', Chain.utils.decryptAESWithPassword(userInfo.contract, aes_key, userInfo.data.txhash))
return info
}
//设置方法
let setData = async (msg) => {
let setData = await setInfo({type: 'set', name : msg.from , age : msg.age >> 0}, msg)
let userInfo = await setInfo({type: 'get'}, msg)
let info = myContract.getOutput('getInfo', Chain.utils.decryptAESWithPassword(userInfo.contract, aes_key, userInfo.data.txhash))
return info
}
//初始化
console.log('服务开启中...')
initialize()
//初始返回一个home页面
app.get("/", function(req ,res) {
res.render("home.ejs",{
name: "",
info: ''
});
});
//更新接口
app.get("/update", async function(req, res){
let msg = basicInfo
msg.age = req.query.age
let info = await setData(msg)
res.render("home.ejs",{
name: info,
info: '更新成功'
});
});
//获取接口
app.get("/search", async (req ,res) => {
let info = await getData(basicInfo)
console.log('-----info-----', info)
res.render("home.ejs",{
name: info,
info: '获取成功'
});
});
//加密查询接口
app.get("/encrypt", async (req ,res) => {
let msg = basicInfo
msg.aesKey = req.query.password
if(req.query.password !== '0x1c4f2919963e8dc040cfddf7d27227de') {
res.render("home.ejs",{
name: ['', ''],
info: '密码错误,请重新输入!'
});
return
}
let info = await getData(msg)
res.render("home.ejs",{
name: info,
info: '加密查询成功'
});
});
JS SDK 增加了特别的交易接口来支持 TEE 合约链的交易隐私保护,具体参考以下接口说明介绍。
合约相关的加密交易
同样,考虑到对合约操作相关接口使用最为广泛,JS SDK 让合约操作相关接口直接支持加密交易,具体使用方式如下。
new
new
用来加密部署合约,保护合约隐私。
请求参数
将以下参数整体封装为 object 传入。
参数 | 必填 | 类型 | 说明 |
---|---|---|---|
bytecode | true | string | 目标合约的字节码,为 16 进制表示 |
data | true | object | 包含 from、parameters 等配置 |
data 字段内容
字段 | 必填 | 类型 | 说明 |
---|---|---|---|
encrypt | true | bool | 说明此交易是否要加密,true:加密;false/不指定:不加密。 |
rsaPublicKey | true | string | 目标 TEE 合约链环境的节点 RSA 公钥, 从 BaaS 平台 TEE 合约链下载。 |
aesKey | true | string 或 Buffer | 此参数将作为一个 password 形式与目标加密的交易 hash 一起计算生成最终的 AES 对称密钥,如果使用 string 类型,会区分前缀是否包含“0x”来解释内容,包含“0x”则使用 16 进制读取,否则按照 ASCII 编码读取。 |
from | true | string | 需要配置的当前账户名。 |
parameters | true | Array | 如果合约包含初始化函数,并且此函数需要参数列表,可以通过 parameters 传递。 |
说明:
- 相比于普通的合约方法
new
增加了加密需要的 3 个参数:encrypt、rsaPublicKey、aesKey。类似的,合约方法调用、合约升级也是增加 3 个参数配置而已,其它参数配置与非加密使用方式一致。 - 其中
aesKey
参数将作为一个password形式与目标加密的交易hash一起计算,生成最终的aes对称密钥,因此每个加密交易由于hash不同,即使用相同的aesKey,最终生成的aes对称密钥也不同,这样生成方式便于交易发送者未来对部分交易的最终aes密钥进行分享,而不需要分享aesKey。
2.5 工程文件
辉哥建立了一个name-age-tee的文件夹,里面的目标结构如下所示。
| alipay-mychain-0.2.27.tgz
| app.js
|
+---certs
| ca.crt
| client.crt
| client.key
| duncanwang-user.key
| duncanwang-user.pem
| package-lock.json
| tee_rsa_public_key.pem
|
+---contracts
| InfoContract.sol
|
+---node_modules
|
---views
home.ejs
说明下:
(1)alipay-mychain-0.2.27.tgz 为蚂蚁的JS-SDK包,解压文件会到node_modules。
(2)app.js 调用JS-SDK的代码。
(3)certs为duncanwang账号对应的各种证书和公私钥文件。
(4)contracts/InfoContract.sol 为name-age智能合约文件。
(5)node_modules的内容很多,为NPM安装的各种依赖包。
(6)views/home.ejs 为采用采用node.js实现的前端页面。
3,部署测试
3.1 安装solc
npm i alipay-solc-0.1.12.tgz --save
成功结果:
【结果】
D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee>npm i alipay-solc-0.1.12.tgz --save
npm WARN saveError ENOENT: no such file or directory, open 'D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\package.json'
npm WARN enoent ENOENT: no such file or directory, open 'D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\package.json'
npm WARN name-age-tee No description
npm WARN name-age-tee No repository field.
npm WARN name-age-tee No README data
npm WARN name-age-tee No license field.
+ @alipay/solc@0.1.12
added 65 packages from 35 contributors and audited 2409 packages in 6.74s
found 0 vulnerabilities
3.2 安装JS SDK
npm i alipay-mychain-0.2.27.tgz --save
【成功结果】
D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee>npm i alipay-mychain-0.2.27.tgz --save
> secp256k1@3.6.2 install D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\node_modules\secp256k1
> npm run rebuild || echo "Secp256k1 bindings compilation fail. Pure JS implementation will be used."
> secp256k1@3.6.2 rebuild D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\node_modules\secp256k1
> node-gyp rebuild
...
npm WARN name-age-tee No description
npm WARN name-age-tee No repository field.
npm WARN name-age-tee No README data
npm WARN name-age-tee No license field.
+ @alipay/mychain@0.2.27
added 50 packages from 32 contributors and audited 710 packages in 53.948s
found 0 vulnerabilities
3.3 安装EXPRESS模块
npm install express
npm install express-generator
成功输出结果:
D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee>npm install express
npm WARN saveError ENOENT: no such file or directory, open 'D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\package.json'
npm WARN enoent ENOENT: no such file or directory, open 'D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\package.json'
npm WARN name-age-tee No description
npm WARN name-age-tee No repository field.
npm WARN name-age-tee No README data
npm WARN name-age-tee No license field.
+ express@4.16.4
added 46 packages from 35 contributors and audited 2884 packages in 10.871s
found 0 vulnerabilities
D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee>npm install express-generator
npm WARN saveError ENOENT: no such file or directory, open 'D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\package.json'
npm WARN enoent ENOENT: no such file or directory, open 'D:\jusanban\doc\26-培训分享\01-研发运营销售\01-区块链\05-蚂蚁区块链\8. Solidity-JS SDK-错误码\DAPP\name-age-tee\package.json'
npm WARN name-age-tee No description
npm WARN name-age-tee No repository field.
npm WARN name-age-tee No README data
npm WARN name-age-tee No license field.
+ express-generator@4.16.0
added 6 packages from 11 contributors and audited 3044 packages in 4.964s
found 0 vulnerabilities
3.4 运行NODE.JS服务
node app
输出结果:
3.5 界面操作
输入“duncanwang”,18,然后点击“更新”按钮,完成加密更新函数调用。
输入aes密码“0x1c4f2919963e8dc040cfddf7d27227de”,点击“解密查询”,可得结果:
在TEE硬件隐私加密链上搭建一个DAPP应用(以姓名年龄为例)的任务成功完成。
4,参考
(1)TEE 硬件隐私合约链 JS SDK 说明
https://tech.antfin.com/docs/2/107140