前言:
最近要用区块链用于存证,然后看到京东智臻链开放联盟网络,就了解了一下,然后决定自己写代码来调用它来用于存证,免费还是挺香的。
建议在阅读本文时先了解一下京东智臻链开放联盟网络。
完成注册,登陆后就可以继续了。
一、创建自己的第一个合约:
建立子账户:
合约管理 => 我的智能合约 => 点击创建 => 添加子业务账户
选择密钥非托管,然后将密钥下载后保存后面调用需要用到。
创建第一个智能合约:
回到智能合约页面,在“我的智能合约”中点击“创建合约”。
子账户选择刚才创建的子账户,其余的根据自己心意填。
模板我选择的第一个通用存证模板,根据自己的需求进行选择。
然后便是新增字段。
索引字段即主键,我设置的为‘id’。当然此处根据自己的需求来进行填写设计,我只设置了一个字段,方便测试。
二、签名代码的实现:
合约创建成功后 => 调用 => 开发者调用 ,我们就能看到JD提供的开发文档。
JD提供的文档有些坑,有些地方有点错误。
不过建议大家还是先看一下了解一下大致的流程。
在整个过程中,一共用到了两次签名,一次是本地签名(公钥+私钥+时间戳),一次是交易签名(公钥+私钥+交易哈希)。都是调用JD提供的java编写SDK来完成,于是我将他们封装在了一起。如果不知道如何使用Python调用jar可以先看一下我的这篇文章,传送门。
公钥和私钥就是在创建子账户时,下载的key.txt中。SDK在点击调用 => 开发者调用 ,官方的第一个步骤中点击“下载SDK”。
SignName.py
from jpype import *
import jpype
import time
class SignName:
"""
调用京东提供的SDK,实现本地签名,交易签名
* @param01: publicKey 公钥原文
* @param02: privateKey 私钥原文
"""
def __init__(self,publicKey:str,privateKey:str) -> None:
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=../jar/local-signature.jar") #调用京东区块链的SDK
self.__JDClass =jpype.JClass("com.jd.bt.chain.Signature") #这说明类在jar包中的目录结构是"com.jd.bt.chain.Signature" 数字签名
self.__jdChain = self.__JDClass() #实例化
self.publicKey = publicKey
self.privateKey = privateKey
def localSign(self) -> str:
"""
返回得到本地签名。
调用SDK,signOrigin方法
* @param01: timestamp 时间戳
* @param02: publicKey 公钥原文
* @param03: privateKey 私钥原文
"""
self.timestampContent = str(round(time.time() * 1000)) #毫秒级时间戳
self.result = str(self.__jdChain.signOrigin(self.timestampContent, self.publicKey,self.privateKey)) #子账户签名结果
return self.result
def transactionSign(self,txHash:str) -> str:
"""
txHash:组装交易后的返回值中。
返回得到交易签名。
调用SDK,sign方法
* @param01: txHash 交易哈希
* @param02: publicKey 公钥原文
* @param03: privateKey 私钥原文
"""
self.result = str(self.__jdChain.sign(txHash, self.publicKey,self.privateKey)) #子账户签名结果
return self.result
三、请求Token
话不多说直接上代码。使用requests的get方法即可,url为:https://openchain.jd.com/server/account/getServerToken。didKey找不到的看官方提供的开发者调用文档的第一步,上面有didKey的值,复制粘贴即可,公钥和私钥就是子账户的公钥私钥。
GetServerToken.py
import requests
import json
# from SignName import SignName
class GetServerToken:
"""
token请求类
"""
def __init__(self,publicKey:str,privateKey:str,didKey:str,signName) -> None:
"""
* @param01:publicKey:子账户公钥
* @param02:privateKey:子账户密钥
* @param03:didKey:账户
* @param04:SignName:签名类的实例化
"""
self.publicKey = publicKey
self.privateKey = privateKey
self.didKey = didKey
self.signName = signName
def getURl(self,url='https://openchain.jd.com/server/account/getServerToken') -> dict:
"""
请求的token
* @param01:url:请求的网址
"""
param = {
'didKey':self.didKey,
'subAccountPubKey':self.publicKey,
'signature':self.signName.localSign(), #子账户签名结果
'timestamp':self.signName.timestampContent #时间戳(被签名内容)
}
page = requests.get(url,params=param)
msg = json.loads(page.text)
return msg
# if __name__ == '__main__':
# didKey = '填写自己的账户'
# publicKey = '公钥'
# privateKey = '私钥'
# signName = SignName(publicKey,privateKey)
# print(type(signName))
# askToken = GetServerToken(publicKey,privateKey,didKey,signName)
# msg = askToken.getURl()
# print(msg)
如需测试,将注释的打开给相应参数就行。返回值如下
四、组装交易
接下来就是组装交易,用到了token,需要将上一步得到的返回值中的token取出。给定参数后直接使用Post即可,url='https://openchain.jd.com/server/contract/call'
didPkPubK:DID身份信息公钥原文,contractAddress:合约地址找不到,同样也可以在官方文档的第三步中找到。
具体实现看代码,代码给出了详细注释。注意:primaryKey:存证所需的主键。它代表存的是具体参数,不是参数名,不要写错了,最初我掉了进去,然后我创建了一个托管的子账户的合约直接在页面中调用,然后F12,查看了它发送的请求才注意到。同时如果你写错了,他是能够post并返回结果的,我在最后一步中出现问题,回过头来才发现这里的坑。
ContractCall.py
import requests
import json
import sys
# from GetServerToken import GetServerToken
# from SignName import SignName
class ContractCall():
"""
组装交易
"""
def __init__(self,result:str,didKey:str,didPkPubK:str,publicKey:str,contractAddress:str,method:str,primaryKey:str,paramJson:dict={}) -> None:
"""
* @param01:result:请求的token
* @param02:didPkPubK:DID身份信息公钥原文
* @param03:publicKey:调用合约用的子账户公钥,必须和签名用的子账户私钥是同一对秘钥
* @param04:contractAddress:合约地址
* @param05:method:调用方法(insert,query,update,insertOrUpdate四选一)
* @param06:primaryKey:存证所需的主键。注:存的具体参数,非参数名!
* @param07:paramJson:存证参数(按照数据格式的dict),调用insert和update方法时需要,query方法不需要
"""
if result['code'] != 20000:
print(f'获取Token失败:{result["msg"]}')
sys.exit(1)
self.token = result['data']['token']
#请求头
self.postHead = {
'Gateway-Appid': didKey,
'Gateway-Token': self.token,
}
self.postData = {
'didPkPubK': didPkPubK,
'subAccountPubKey': publicKey, # 调用合约用的子账户公钥,必须和签名用的子账户私钥是同一对秘钥
'contractAddress': contractAddress,
'method': method,
'primaryKey': primaryKey,
'paramJson': json.dumps(paramJson)
}
def postURL(self,url:str='https://openchain.jd.com/server/contract/call') -> dict:
"""
组装交易后的返回值
* @param01:url:请求的网址
"""
page = requests.post(url, headers=self.postHead, json=self.postData)
msg = json.loads(page.text)
return msg
# if __name__ == '__main__':
# didKey = ''
# publicKey = ''
# privateKey = ''
# didPkPubK = ''
# contractAddress = ''
# method = 'insert'
# paramJson = {
# 'id' = 11
# }
# primaryKey = paramJson['id'] #存证所需的主键
# signName = SignName(publicKey,privateKey)
# getServerToken = GetServerToken(publicKey, privateKey, didKey,signName)
# result = getServerToken.getURl()
# contractCall = ContractCall(result,didKey,didPkPubK,publicKey,contractAddress,method,primaryKey,paramJson)
# ans = contractCall.postURL()
# print(ans)
返回值示例:
{'code': 20000, 'msg': '操作成功', 'data': {'params': [{'address': 'LdeP5sRTSLNLpScahgrH51sfgVroJES2hAtjH', 'methodName': 'insert', 'key': 'wan', 'paramJson': '{"name": "wan"}', 'paramType': 'String'}], 'txContent': '112JJhCfsgmv4pK6Xhd7WCbSiCYVepFAiMdabY3RN7sRotLhTHfKQZK4WPeanxfn9VpK3Tb5h3sAQ49bpx68NRj8ZoavrTNCEt9D515BFsKwouLEenXBDJRaGa1xMmJaexZxbXqChCuAV4HjV1Wrfz8DhrsqcQ5Ek1eTvHQ7HC8xbjALNw6w6YWpvRvtTUm3xkNTz7xc8AxbpPjEHAxYVUDE4rpgrjHK4Cv5SwiB2dEp2H', 'txHash': 'j5kd9kT62AqM97QVSX8pygL8DXv8S34Z4esMpyomVqnpWB', 'signerPubKey': '7VeRLfw1QyERWTPZmPJPKwiHUHGFJIUyK9XRsU2L9cL5vf6w', 'signature': None}}
五、交易
最后一步。
官方文档在最后一步把参数名'signerPubKey' 错误的写成了 'publicKey',我使用F12调试后才发现,上一步骤我已说具体方法。同时上一步的请求头和这次是一样的,并且返回值中Data与我们Post需要给的json参数,相差不大,我们只需要再向其中加入我们的交易签名结果即可。url='https://openchain.jd.com/server/contract/sign'
Transaction.py
import requests
import json
# from GetServerToken import GetServerToken
# from ContractCall import ContractCall
# from SignName import SignName
class Transaction:
"""
交易
"""
def __init__(self,result:dict,postHead,signName) -> None:
"""
* @param01: result 组装交易后的返回值
* @param02:SignName:签名类的实例化
"""
self.postHead = postHead
self.data = result['data']
self.txHash = result['data']['txHash']
self.signName = signName
self.signerPubKey = self.signName.transactionSign(self.txHash)
self.data['signature'] = self.signerPubKey
def postURL(self,url='https://openchain.jd.com/server/contract/sign') -> dict:
"""
最终交易后的返回值
* @param01:url:请求的网址
"""
page = requests.post(url, headers=self.postHead,json=self.data)
msg = json.loads(page.text)
return msg
# if __name__ == '__main__':
# didKey = '
# publicKey = ''
# privateKey = ''
# didPkPubK = ''
# contractAddress = ''
# method = 'insert'
# paramJson = {
#
# }
# signName = SignName(publicKey,privateKey)
# primaryKey = paramJson['id']
# getServerToken = GetServerToken(publicKey, privateKey, didKey,signName)
# result = getServerToken.getURl()
# contractCall = ContractCall(result,didKey,didPkPubK,publicKey,contractAddress,method,primaryKey,paramJson)
# ans = contractCall.postURL()
# transaction = Transaction(ans,contractCall.postHead,signName)
# ans = transaction.postURL()
# print(ans)
返回值示例:
{
"code": 20000,
"msg": "操作成功",
"data": {
"transactionHash": "j5pMNEQolpWgX1y1m2q37CWkBD1yQfQcpvVSVuNb5fftpd",
"blockHeight": "507",
"content": "{\"xingMing\":\"update1204\",\"zhuJian\":2}",
"time": "2020-12-04 10:58:08",
}
}
同时我们也能够在“我的智能合约” 中点击数据列表中查询到刚才上链的信息。