@@ -1,628 +1,120 @@ | |||
[TOC] | |||
#JD区块链 | |||
[](https://www.apache.org/licenses/LICENSE-2.0.html) | |||
[](https://maven-badges.herokuapp.com/maven-central/com.jd.blockchain/sdk-pack/) | |||
[](https://travis-ci.org/blockchain-jd-com/jdchain) | |||
------------------------------------------------------------------------ | |||
### 版本修订历史 | |||
<table> | |||
<tr> | |||
<th>版本号</th> | |||
<th>作 者</th> | |||
<th>修改日期</th> | |||
<th>备 注</th> | |||
</tr> | |||
<tr> | |||
<td>0.0.6</td> | |||
<td>黄海泉</td> | |||
<td>2017-11-10</td> | |||
<td> | |||
定义JD区块链项目的目标与关键能力;<br> | |||
定义JD区块链的核心对象模型;<br> | |||
定义“账户”的生成算法和“区块/交易/操作/账户”的关键属性;<br> | |||
描述了编程接口的示例代码; | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>0.0.7</td> | |||
<td>黄海泉</td> | |||
<td>2017-11-17</td> | |||
<td> | |||
丰富了对“节点共识”、“节点分区”两项关键能力的详细描述; | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>0.0.8</td> | |||
<td>黄海泉</td> | |||
<td>2018-07-17</td> | |||
<td> | |||
增加部署图;增加智能合约开发的示例; | |||
</td> | |||
</tr> | |||
</table> | |||
------------------------------------------------------------------------ | |||
## 一、概述 | |||
JD区块链项目的目标是提供一个面向广泛的应用场景、满足企业核心需求的灵活和易用的区块链系统。 | |||
以下是 JD 区块链用以满足企业核心需求的关键能力,也是显著区别于其它区块链的重要特征: | |||
- 快速共识 | |||
- 节点分区 | |||
- 并行多账本 | |||
- 大数据伸缩存储 | |||
- 条件检索 | |||
- 面向对象的合约代码编程模型 | |||
- 节点快速部署 | |||
- 多终端灵活接入 | |||
- 分布式自治的账户权限管理模型 | |||
JD区块链对于关键能力的定义是建立在深入理解和抽象各种多样化需求的基础上的。 | |||
- 快速共识(Efficient Consensus) | |||
我们认为,“快速”不仅仅体现在“用更短时间”达成共识,还要体现在交易(Transaction)要得到可靠地执行。 | |||
需要在算法和实现层面做出保障,确保所有提交的合法的交易会被系统在一个“确定性”和“足够短”的时间之内被严格地执行,系统不主动作出随机性地丢弃(不包括系统故障因素)。注:POW类算法产生的链分叉处理是一种系统随机性地丢弃交易的行为。 | |||
从使用者的视角来看,这种能力就是区块链系统的“可靠性”,这对于企业、金融场景而言尤其重要。 | |||
- 节点分区(Peer Partition) | |||
“分区”是一种分布式系统架构原则,通过将大范围目标按照某种相似特征分隔为一个个小范围目标,分别进行更高效地处理,这能从整体上提升整个系统的处理能力。 | |||
区块链系统也是一种分布式系统,沿用“分区”的思想这点来自以往的系统架构实践的经验,是有可能让区块链系统获得可媲美现有系统的处理能力。这种能力将可以无障碍地把区块链在应用于广泛的企业场景中。 | |||
在此,我们所说的“节点分区(Peer Partition)” 是共识过程中的物理通讯层面的分区,在共识过程中只有相关的物理节点才会建立通讯链路并复制和存储共识的状态数据。在一个区块链系统中,可以从节点全集中选择一个或多个节点子集,分别组成一个或多个节点分区(Peer Partition) | |||
- 并行多账本 | |||
账本(Ledger) | |||
- 大数据伸缩存储 | |||
- 条件检索 | |||
- 面向对象的合约代码编程模型 | |||
- 节点快速部署 | |||
- 多终端灵活接入 | |||
- 分布式自治的账户权限管理模型 | |||
## 二、对象模型 | |||
JD区块链的核心对象包括: | |||
- 账本(Ledger) | |||
一份账本(Ledger)实质上是由两部分组成: | |||
- 一组以“账户(Account)”表示的状态数据; | |||
- 以“区块的链条(Block-chain)”表示的状态数据的变更历史; | |||
JD区块链的“账本(Ledger)”是一个最顶层的管理数据的逻辑单元。在一个区块链节点构成的共识网络中,可以维护多套并行的“账本(Ledger)”。 | |||
- 账户(Account) | |||
- 在JD区块链中,账户(Account)被设计为包含“身份(Identity)”、“权限(Privilege)”、“状态(State)”、“控制规则(Control Rule)” 这4种属性的对象。 | |||
- 其中,“身份(Identity)”、“权限(Privilege)”这两种属性是一个面向应用的系统的基本功能; | |||
- “状态(State)”、“控制规则(Control Rule)” 这两种属性这是一个具备“图灵完备性”的系统的基本属性,使系统可以处理任意多样化的任务; | |||
- 在这里,“身份(Identity)”是一个抽象表述,其形式上就是一个“区块链地址(Address)”和相应的“非对称秘钥钥对(KeyPair)”/证书。 简单来说,一个“账户(Account)”就是一个区块链地址、公私钥对以及一套的权限配置. | |||
- 一个“账户(Account)”的“状态(State)”、“控制规则(Control Rule)” 这2种属性则是可选的,这提供了不同于其它区块链的账户/合约的一种新的使用方式,即一种数据和逻辑分离的应用设计思路(这在传统的web应用编程中被称为“贫血模型”)。同时,也意味着一个特定“账户”中的数据状态是可以跨账户/合约代码进行共享访问的。 | |||
- 从应用的视角来看,对“账户”的使用方式可以有几种模式: | |||
- 用于表示业务角色/用户: | |||
- 用于表示业务数据:仅有“状态(State)”没有“控制规则(Control Rule)”的账户,可称为数据账户,就有些类似于关系数据库中的“表(Table)”的作用,不同业务类别的数据则使用不同的账户来管理。 | |||
- 用于表示业务逻辑:仅有“控制规则(Control Rule)”没有“状态(State)”的账户,即所谓的合约账户,可表示某种用于处理数据的通用逻辑,可被授权访问特定的数据账户。 | |||
- 区块(Block) | |||
在概念上,与通常所说的区块的概念是一致的。 | |||
在实现上,一个区块的主要内容是包含了某一时刻提交的所有交易以及交易执行之后的状态数据的快照的hash,而不存储具体的交易操作和状态数据。 | |||
- 交易(Transaction) | |||
在概念上,与通常所说的Transaction的概念是一致的,表示一组需要原子执行的操作。 | |||
- 操作(Operation) | |||
操作是针对“账户(Account)”的“写”指令,包括以下几类: | |||
- 注册账户 | |||
- 更新账户的状态数据 | |||
- 更新账户的合约代码定义 | |||
- 调用账户的合约代码 | |||
- 合约代码(Contract Code) | |||
合约代码是一段用于对“账户(Account)”的状态数据执行操作的代码程序。 | |||
## 三、部署模型 | |||
1. 总体部署 | |||
 | |||
2. 系统组件 | |||
- 共识节点 | |||
- 复制节点 | |||
- SDK | |||
- 网关 | |||
- 终端 | |||
3. 配置和管理 | |||
## 四、账本结构 | |||
### 1. 账户生成算法 | |||
- 公私钥对 | |||
- 算法:默认ED25519 ,支持 SM2、CA; | |||
- 公钥存储格式:版本 + 算法标识 + 公私钥原始内容 + 校验码 | |||
- 字符编码方式:Base64 | |||
- 地址 | |||
- 算法 | |||
- 给定公钥 P (或由私钥 R 算出公钥 P) | |||
- 中间值 H1 = SHA256( P ) | |||
- 中间值 H2 = RIPEMD-160( H1 ) | |||
- 中间值 X = 版本 + 公钥算法标识 + H2 | |||
- 校验和 C = 前4字节( SHA256( SHA256( X )) ) | |||
- 地址 Address = Base58( X + C ) | |||
### 2. 区块 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>BlockHash</td> | |||
<td>当前区块 hash</td> | |||
<td>对区块中除此之外的其它所有属性一起进行哈希运算生成</td> | |||
</tr> | |||
<tr> | |||
<td>BlockVersion </td> | |||
<td>区块版本</td> | |||
<td>表示区块-交易的属性结构的版本号;</td> | |||
</tr> | |||
<tr> | |||
<td>PreviousBlockHash</td> | |||
<td>上一区块 hash</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>BlockNumber</td> | |||
<td>区块高度</td> | |||
<td>区块高度是一个区块在链中的序号;<br>创始区块的高度为 0,每个新区块的高度依次递增;</td> | |||
</tr> | |||
<tr> | |||
<td>AccountHash</td> | |||
<td>账户树hash</td> | |||
<td>账户的 Merkle Tree 根的 hash</td> | |||
</tr> | |||
<tr> | |||
<td>AccountCount</td> | |||
<td>账户数量</td> | |||
<td>区块生成时账本中的全部账户的总数</td> | |||
</tr> | |||
<tr> | |||
<td>TxTreeHash</td> | |||
<td>交易树 hash</td> | |||
<td>本区块的交易集合的 Merkle Tree 根的 hash</td> | |||
</tr> | |||
<tr> | |||
<td>TxCount</td> | |||
<td>区块交易数量</td> | |||
<td>当前区块包含的交易的数量;</td> | |||
</tr> | |||
<tr> | |||
<td>TxTotalCount</td> | |||
<td>账本交易总数</td> | |||
<td>截止到当前区块为止当前账本的所有交易的总数量;</td> | |||
</tr> | |||
<tr> | |||
<td>CloseTime</td> | |||
<td>区块关闭时间</td> | |||
<td>生成当前区块时的区块链节点的网络时间;</td> | |||
</tr> | |||
</table> | |||
### 3. 交易 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>Hash</td> | |||
<td>当前交易 hash</td> | |||
<td>对交易中除此之外的其它所有属性一起进行哈希运算生成</td> | |||
</tr> | |||
<tr> | |||
<td>LedgerNumber</td> | |||
<td>区块高度</td> | |||
<td>交易被包含的区块高度</td> | |||
</tr> | |||
<tr> | |||
<td>BlobHash</td> | |||
<td>交易数据块hash</td> | |||
<td>交易的数据块是交易的原始数据,包含客户端提交的交易的全部操作及其参数; | |||
<br>交易的参与者需要使用私钥对交易数据块进行签名;</td> | |||
</tr> | |||
<tr> | |||
<td>Operations</td> | |||
<td>操作列表</td> | |||
<td>交易的操作列表;</td> | |||
</tr> | |||
<tr> | |||
<td>Sponsor</td> | |||
<td>交易发起人</td> | |||
<td>交易发起人的账户地址;</td> | |||
</tr> | |||
<tr> | |||
<td>SequenceNumber</td> | |||
<td>交易序号</td> | |||
<td>交易序号记录了一个特定的发起人的交易的顺序号,等同于该发起人历史上发起的交易的总数;</td> | |||
</tr> | |||
<tr> | |||
<td>Signatures</td> | |||
<td>签名列表</td> | |||
<td>由交易发起人和其它参与者对交易数据块的签名的列表;</td> | |||
</tr> | |||
<tr> | |||
<td>Result</td> | |||
<td>交易结果</td> | |||
<td>0 - 表示执行成功;非零表示执行失败;<br>注:最终的账本只包含成功的交易;</td> | |||
</tr> | |||
</table> | |||
### 4. 操作 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>OpType</td> | |||
<td>操作类型</td> | |||
<td> | |||
一级操作类型包括:注册账户、配置权限、写入键值数据、写入对象数据、定义合约代码、调用合约代码;<br> | |||
“键值数据写入”操作的子操作类型包括:填入键值、移除键、数值增加、数值减少;<br> | |||
“对象数据写入”操作的自操作类型包括:插入对象、更新对象、移除对象; | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>Args</td> | |||
<td>参数列表</td> | |||
<td>与操作类型相对应的参数列表;</td> | |||
</tr> | |||
<tr> | |||
<td>SubOps</td> | |||
<td>子操作列表</td> | |||
<td>“子操作”是“操作”的递归定义,由“操作类型”来标识;</td> | |||
</tr> | |||
</table> | |||
### 5. 账户 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>Address</td> | |||
<td>地址</td> | |||
<td>账户的唯一标识</td> | |||
</tr> | |||
<tr> | |||
<td>RegNumber</td> | |||
<td>注册号</td> | |||
<td>账户被注册到区块链的区块高度;</td> | |||
</tr> | |||
<tr> | |||
<td>TxSquenceNumber</td> | |||
<td>交易序列号</td> | |||
<td>由账户发起的交易的序列号,初始为 0,账户每发起一个交易则增加1;</td> | |||
</tr> | |||
<tr> | |||
<td>ModelVersion</td> | |||
<td>账户模型版本</td> | |||
<td>表示构成一个账户结构的属性模型的程序版本号;</td> | |||
</tr> | |||
<tr> | |||
<td>Version</td> | |||
<td>账户版本</td> | |||
<td>初始为 0,对账户的每一次变更(包括对权限设置、状态和合约代码的变更)都会使账户状态版本增加 1 ;<br>注:交易序号的改变不会导致账户版本的增加;</td> | |||
</tr> | |||
<tr> | |||
<td>PrivilegeHash</td> | |||
<td>权限 hash</td> | |||
<td>权限树的根hash;</td> | |||
</tr> | |||
<tr> | |||
<td>PrivilegeVersion</td> | |||
<td>权限版本</td> | |||
<td>初始为 0, 每次对权限的变更都导致版本号加 1;</td> | |||
</tr> | |||
<tr> | |||
<td>StateType</td> | |||
<td>状态类型</td> | |||
<td>账户的状态类型有3种:空类型(NIL);键值类型;对象类型;</td> | |||
</tr> | |||
<tr> | |||
<td>StateVersion</td> | |||
<td>状态版本</td> | |||
<td>账户的状态类型有3种:空类型(NIL);键值类型;对象类型;</td> | |||
</tr> | |||
<tr> | |||
<td>StateHash</td> | |||
<td>状态哈希</td> | |||
<td>数据状态的 merkle tree 的根hash;</td> | |||
</tr> | |||
<tr> | |||
<td>CodeHash</td> | |||
<td>合约代码哈希</td> | |||
<td>由“账户地址+合约代码版本号+合约代码内容”生成的哈希;</td> | |||
</tr> | |||
<tr> | |||
<td>CodeVersion</td> | |||
<td>代码版本</td> | |||
<td>初始为 0,每次对代码的变更都使版本加 1 ;</td> | |||
</tr> | |||
</table> | |||
## 五、编程接口 | |||
### 1. 服务连接 | |||
```java | |||
//创建服务代理 | |||
public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(); | |||
final String GATEWAY_IP = "127.0.0.1"; | |||
final int GATEWAY_PORT = 80; | |||
final boolean SECURE = false; | |||
GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IP, GATEWAY_PORT, SECURE, | |||
CLIENT_CERT); | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
``` | |||
## 一、项目介绍 | |||
JD Chain 的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 | |||
### 2. 用户注册 | |||
## 二、部署模型 | |||
JD Chain 主要部署组件包括以下几种: | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义注册账号的 TX; | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); | |||
CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); | |||
BlockchainKeyPair user = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); | |||
txTemp.users().register(user.getIdentity()); | |||
- 共识节点 | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
共识节点即参与共识的节点,这是系统的核心组件,承担了运行共识协议、管理账本数据、运行智能合约的职责。 | |||
// 提交交易; | |||
prepTx.commit(); | |||
``` | |||
一个区块链网络由多个共识节点组成,共识节点的数量范围由选择的共识协议决定。 | |||
共识节点和账本是两个不同的概念,共识节点是个物理上的概念,账本是个逻辑上的概念。JD Chain 是一个多账本区块链系统,一个共识节点上可以装载运行多个账本。账本是数据维度的独立管理单元。共识节点和账本的关系,就像关系数据库系统中,数据库服务器和数据库实例的关系。 | |||
### 3. 数据账户注册 | |||
共识节点通常都部署在参与方的内部网络中,通过由网络管理员指定的安全的网络出口与其它的共识节点建立通讯连接。 | |||
共识节点在形态上是服务器中的一个处理进程,背后需要连接一个本地或者内网的NoSQL数据库系统作为账本的存储。当前版本,共识节点目前是单进程的,未来版本将实现多进程以及多服务器集群模式。 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义注册账号的 TX; | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); | |||
CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); | |||
BlockchainKeyPair dataAccount = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); | |||
txTemp.dataAccounts().register(dataAccount.getIdentity()); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
// 提交交易; | |||
prepTx.commit(); | |||
``` | |||
- 网关节点 | |||
### 4. 写入数据 | |||
网关节点是负责终端接入的节点,负责终端连接、协议转换、交易准入、本地密码运算、密钥管理等职责。 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
HashDigest ledgerHash = getLedgerHash(); | |||
// 在本地定义注册账号的 TX; | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
// -------------------------------------- | |||
// 将商品信息写入到指定的账户中; | |||
// 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; | |||
String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
Commodity commodity1 = new Commodity(); | |||
txTemp.dataAccount(commodityDataAccount).set("ASSET_CODE", commodity1.getCode().getBytes(), -1); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
String txHash = ByteArray.toBase64(prepTx.getHash().toBytes()); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
// 提交交易; | |||
prepTx.commit(); | |||
``` | |||
网关节点是一种轻量节点,需要绑定一个特定参与方的密钥对,连接到一个或多个共识节点。 | |||
### 5. 查询数据 | |||
网关节点向共识节点的连接是需要通过认证的,绑定的参与方的密钥对必须事先已经注册到区块链账本中,且得到接入授权。 | |||
> 注:详细的查询可参考模块sdk-samples中SDK_GateWay_Query_Test_相关测试用例 | |||
- 终端 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 查询区块信息; | |||
// 区块高度; | |||
long ledgerNumber = service.getLedger(LEDGER_HASH).getLatestBlockHeight(); | |||
// 最新区块; | |||
LedgerBlock latestBlock = service.getBlock(LEDGER_HASH, ledgerNumber); | |||
// 区块中的交易的数量; | |||
long txCount = service.getTransactionCount(LEDGER_HASH, latestBlock.getHash()); | |||
// 获取交易列表; | |||
LedgerTransaction[] txList = service.getTransactions(LEDGER_HASH, ledgerNumber, 0, 100); | |||
// 遍历交易列表 | |||
for (LedgerTransaction ledgerTransaction : txList) { | |||
TransactionContent txContent = ledgerTransaction.getTransactionContent(); | |||
Operation[] operations = txContent.getOperations(); | |||
if (operations != null && operations.length > 0) { | |||
for (Operation operation : operations) { | |||
operation = ClientOperationUtil.read(operation); | |||
// 操作类型:数据账户注册操作 | |||
if (operation instanceof DataAccountRegisterOperation) { | |||
DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; | |||
BlockchainIdentity blockchainIdentity = daro.getAccountID(); | |||
} | |||
// 操作类型:用户注册操作 | |||
else if (operation instanceof UserRegisterOperation) { | |||
UserRegisterOperation uro = (UserRegisterOperation) operation; | |||
BlockchainIdentity blockchainIdentity = uro.getUserID(); | |||
} | |||
// 操作类型:账本注册操作 | |||
else if (operation instanceof LedgerInitOperation) { | |||
LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; | |||
LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); | |||
ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); | |||
} | |||
// 操作类型:合约发布操作 | |||
else if (operation instanceof ContractCodeDeployOperation) { | |||
ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; | |||
BlockchainIdentity blockchainIdentity = ccdo.getContractID(); | |||
} | |||
// 操作类型:合约执行操作 | |||
else if (operation instanceof ContractEventSendOperation) { | |||
ContractEventSendOperation ceso = (ContractEventSendOperation) operation; | |||
} | |||
// 操作类型:KV存储操作 | |||
else if (operation instanceof DataAccountKVSetOperation) { | |||
DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = | |||
((DataAccountKVSetOperation) operation).getWriteSet(); | |||
if (kvWriteEntries != null && kvWriteEntries.length > 0) { | |||
for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { | |||
BytesValue bytesValue = kvWriteEntry.getValue(); | |||
DataType dataType = bytesValue.getType(); | |||
Object showVal = ClientOperationUtil.readValueByBytesValue(bytesValue); | |||
System.out.println("writeSet.key=" + kvWriteEntry.getKey()); | |||
System.out.println("writeSet.value=" + showVal); | |||
System.out.println("writeSet.type=" + dataType); | |||
System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
// 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; | |||
HashDigest txHash = txList[0].getTransactionContent().getHash(); | |||
Transaction tx = service.getTransactionByContentHash(LEDGER_HASH, txHash); | |||
// 获取数据; | |||
String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
String[] objKeys = new String[] { "x001", "x002" }; | |||
KVDataEntry[] kvData = service.getDataEntries(LEDGER_HASH, commerceAccount, objKeys); | |||
long payloadVersion = kvData[0].getVersion(); | |||
// 获取数据账户下所有的KV列表 | |||
KVDataEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, 0, 100); | |||
if (kvData != null && kvData.length > 0) { | |||
for (KVDataEntry kvDatum : kvData) { | |||
System.out.println("kvData.key=" + kvDatum.getKey()); | |||
System.out.println("kvData.version=" + kvDatum.getVersion()); | |||
System.out.println("kvData.type=" + kvDatum.getType()); | |||
System.out.println("kvData.value=" + kvDatum.getValue()); | |||
} | |||
} | |||
``` | |||
终端泛指可以提交交易的客户端,典型来说,包括人、自动化设备、链外的信息系统等。 | |||
终端只能通过网关节点来提交交易。终端提交的交易需要用体现该终端身份的私钥来签署,产生一份电子签名。随后当交易提交给网关节点时,网关节点需要在把交易提交到共识节点之前,对交易请求以网关节点绑定的私钥追加一项“节点签名”。 | |||
### 6. 合约发布 | |||
- 备份节点 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义TX模板 | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
仅对账本数据提供备份,但不参与交易共识的节点。(注:目前版本中尚未实现,将在后续版本中提供) | |||
// 合约内容读取 | |||
byte[] contractBytes = FileUtils.readBytes(new File(CONTRACT_FILE)); | |||
// 生成用户 | |||
BlockchainIdentityData blockchainIdentity = new BlockchainIdentityData(getSponsorKey().getPubKey()); | |||
 | |||
// 发布合约 | |||
txTemp.contracts().deploy(blockchainIdentity, contractBytes); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
## 三、编译源代码 | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
1. 安装 Maven 环境 | |||
prepTx.sign(keyPair); | |||
JD Chain 当前版本以 Java 语言开发,需要安装配置 JVM 和 Maven,JDK 版本不低于1.8 。(没有特殊要求,请按标准方法安装,此处不赘述) | |||
2. 安装 Git 工具 | |||
为了能够执行 git clone 命令获取代码仓库。 (没有特殊要求,请按标准方法安装,此处不赘述) | |||
3. 工程代码 | |||
// 提交交易; | |||
TransactionResponse transactionResponse = prepTx.commit(); | |||
JD Chain 源代码包括 3 个代码仓库 | |||
assertTrue(transactionResponse.isSuccess()); | |||
- jdchain | |||
- 这是当前仓库,也是核心仓库,包含了共识节点、网关节点、SDK等一切部署组件。依赖于 explorer 和 bftsmart 这两个仓库先进行编译安装; | |||
- explorer | |||
- 这是区块链浏览器的前端Web页面的工程,需要编译成静态资源包,由网关节点集成到一起部署。 | |||
- 地址:git@github.com:blockchain-jd-com/explorer.git | |||
// 打印合约地址 | |||
System.out.println(blockchainIdentity.getAddress().toBase58()); | |||
- bftsmart | |||
- 这是bftsmart共识协议的工程,需要先编译安装到本地 maven 仓库; | |||
``` | |||
### 7. 合约执行 | |||
4. 命令操作 | |||
```java | |||
- 编译安装 explorer 到本地 maven 仓库; | |||
```sh | |||
$ git clone git@github.com:blockchain-jd-com/explorer.git explorer | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
$ cd explorer | |||
// 在本地定义TX模板 | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
$ git checkout master | |||
// 合约地址 | |||
String contractAddressBase58 = ""; | |||
// 使用接口方式调用合约 | |||
TransferContract transferContract = txTpl.contract(contractAddress, TransferContract.class); | |||
// 使用decode方式调用合约内部方法(create方法) | |||
// 返回GenericValueHolder可通过get方法获取结果,但get方法需要在commit调用后执行 | |||
GenericValueHolder<String> result = ContractReturnValue.decode(transferContract.create(address, account, money)); | |||
PreparedTransaction ptx = txTpl.prepare(); | |||
ptx.sign(adminKey); | |||
TransactionResponse transactionResponse = ptx.commit(); | |||
$ mvn clean install | |||
String cotractExecResult = result.get(); | |||
// TransactionResponse也提供了可供查询结果的接口 | |||
OperationResult[] operationResults = transactionResponse.getOperationResults(); | |||
``` | |||
- 编译安装 bftsmart 到本地 maven 仓库; | |||
- 需要手动先安装一个第三方包,位于仓库根目录下 lib/core-0.1.4.jar | |||
```sh | |||
$ git clone git@github.com:blockchain-jd-com/bftsmart.git bftsmart | |||
$ cd bftsmart | |||
$ git checkout master | |||
// 通过OperationResult获取结果 | |||
for (int i = 0; i < operationResults.length; i++) { | |||
OperationResult opResult = operationResults[i]; | |||
System.out.printf("Operation[%s].result = %s \r\n", | |||
opResult.getIndex(), BytesValueEncoding.decode(opResult.getResult())); | |||
} | |||
$ mvn install:install-file -Dfile=lib/core-0.1.4.jar -DgroupId=com.yahoo.ycsb -DartifactId=core -Dversion=0.1.4 -Dpackaging=jar | |||
$ mvn clean install | |||
``` | |||
- 编译 jdchain 工程; | |||
- 当编译完成后,共识节点的安装包位于 "仓库根目录"/source/deployment/deployment-peer/target/jdchain-peer-1.0.1.RELEASE.zip | |||
- 当编译完成后,网关节点的安装包位于 "仓库根目录"/source/deployment/deployment-gateway/target/jdchain-gateway-1.0.1.RELEASE.zip | |||
``` | |||
```sh | |||
$ git clone git@github.com:blockchain-jd-com/jdchain.git jdchain | |||
$ cd jdchain/source | |||
$ git checkout master | |||
$ mvn clean package | |||
``` |
@@ -0,0 +1,630 @@ | |||
[TOC] | |||
#JD区块链 | |||
[](https://www.apache.org/licenses/LICENSE-2.0.html) | |||
------------------------------------------------------------------------ | |||
### 版本修订历史 | |||
<table> | |||
<tr> | |||
<th>版本号</th> | |||
<th>作 者</th> | |||
<th>修改日期</th> | |||
<th>备 注</th> | |||
</tr> | |||
<tr> | |||
<td>0.0.6</td> | |||
<td>黄海泉</td> | |||
<td>2017-11-10</td> | |||
<td> | |||
定义JD区块链项目的目标与关键能力;<br> | |||
定义JD区块链的核心对象模型;<br> | |||
定义“账户”的生成算法和“区块/交易/操作/账户”的关键属性;<br> | |||
描述了编程接口的示例代码; | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>0.0.7</td> | |||
<td>黄海泉</td> | |||
<td>2017-11-17</td> | |||
<td> | |||
丰富了对“节点共识”、“节点分区”两项关键能力的详细描述; | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>0.0.8</td> | |||
<td>黄海泉</td> | |||
<td>2018-07-17</td> | |||
<td> | |||
增加部署图;增加智能合约开发的示例; | |||
</td> | |||
</tr> | |||
</table> | |||
------------------------------------------------------------------------ | |||
## 一、概述 | |||
JD Chain 的目标是实现一个面向企业应用场景的通用区块链框架系统,能够作为企业级基础设施,为业务创新提供高效、灵活和安全的解决方案。 | |||
区块链本质上是一种新的分布式架构,以密码学和分布式技术为核心,旨在实现无需借助“第三方” 就能在多个业务方之间进行安全、可信、直接的信息和价值交换。从点对点的信息和价值交换的角度看,区块链发挥了“协议”的作用。 | |||
以下是 JD 区块链用以满足企业核心需求的关键能力,也是显著区别于其它区块链的重要特征: | |||
- 快速共识 | |||
- 节点分区 | |||
- 并行多账本 | |||
- 大数据伸缩存储 | |||
- 条件检索 | |||
- 面向对象的合约代码编程模型 | |||
- 节点快速部署 | |||
- 多终端灵活接入 | |||
- 分布式自治的账户权限管理模型 | |||
JD区块链对于关键能力的定义是建立在深入理解和抽象各种多样化需求的基础上的。 | |||
- 快速共识(Efficient Consensus) | |||
我们认为,“快速”不仅仅体现在“用更短时间”达成共识,还要体现在交易(Transaction)要得到可靠地执行。 | |||
需要在算法和实现层面做出保障,确保所有提交的合法的交易会被系统在一个“确定性”和“足够短”的时间之内被严格地执行,系统不主动作出随机性地丢弃(不包括系统故障因素)。注:POW类算法产生的链分叉处理是一种系统随机性地丢弃交易的行为。 | |||
从使用者的视角来看,这种能力就是区块链系统的“可靠性”,这对于企业、金融场景而言尤其重要。 | |||
- 节点分区(Peer Partition) | |||
“分区”是一种分布式系统架构原则,通过将大范围目标按照某种相似特征分隔为一个个小范围目标,分别进行更高效地处理,这能从整体上提升整个系统的处理能力。 | |||
区块链系统也是一种分布式系统,沿用“分区”的思想这点来自以往的系统架构实践的经验,是有可能让区块链系统获得可媲美现有系统的处理能力。这种能力将可以无障碍地把区块链在应用于广泛的企业场景中。 | |||
在此,我们所说的“节点分区(Peer Partition)” 是共识过程中的物理通讯层面的分区,在共识过程中只有相关的物理节点才会建立通讯链路并复制和存储共识的状态数据。在一个区块链系统中,可以从节点全集中选择一个或多个节点子集,分别组成一个或多个节点分区(Peer Partition) | |||
- 并行多账本 | |||
账本(Ledger) | |||
- 大数据伸缩存储 | |||
- 条件检索 | |||
- 面向对象的合约代码编程模型 | |||
- 节点快速部署 | |||
- 多终端灵活接入 | |||
- 分布式自治的账户权限管理模型 | |||
## 二、对象模型 | |||
JD区块链的核心对象包括: | |||
- 账本(Ledger) | |||
一份账本(Ledger)实质上是由两部分组成: | |||
- 一组以“账户(Account)”表示的状态数据; | |||
- 以“区块的链条(Block-chain)”表示的状态数据的变更历史; | |||
JD区块链的“账本(Ledger)”是一个最顶层的管理数据的逻辑单元。在一个区块链节点构成的共识网络中,可以维护多套并行的“账本(Ledger)”。 | |||
- 账户(Account) | |||
- 在JD区块链中,账户(Account)被设计为包含“身份(Identity)”、“权限(Privilege)”、“状态(State)”、“控制规则(Control Rule)” 这4种属性的对象。 | |||
- 其中,“身份(Identity)”、“权限(Privilege)”这两种属性是一个面向应用的系统的基本功能; | |||
- “状态(State)”、“控制规则(Control Rule)” 这两种属性这是一个具备“图灵完备性”的系统的基本属性,使系统可以处理任意多样化的任务; | |||
- 在这里,“身份(Identity)”是一个抽象表述,其形式上就是一个“区块链地址(Address)”和相应的“非对称秘钥钥对(KeyPair)”/证书。 简单来说,一个“账户(Account)”就是一个区块链地址、公私钥对以及一套的权限配置. | |||
- 一个“账户(Account)”的“状态(State)”、“控制规则(Control Rule)” 这2种属性则是可选的,这提供了不同于其它区块链的账户/合约的一种新的使用方式,即一种数据和逻辑分离的应用设计思路(这在传统的web应用编程中被称为“贫血模型”)。同时,也意味着一个特定“账户”中的数据状态是可以跨账户/合约代码进行共享访问的。 | |||
- 从应用的视角来看,对“账户”的使用方式可以有几种模式: | |||
- 用于表示业务角色/用户: | |||
- 用于表示业务数据:仅有“状态(State)”没有“控制规则(Control Rule)”的账户,可称为数据账户,就有些类似于关系数据库中的“表(Table)”的作用,不同业务类别的数据则使用不同的账户来管理。 | |||
- 用于表示业务逻辑:仅有“控制规则(Control Rule)”没有“状态(State)”的账户,即所谓的合约账户,可表示某种用于处理数据的通用逻辑,可被授权访问特定的数据账户。 | |||
- 区块(Block) | |||
在概念上,与通常所说的区块的概念是一致的。 | |||
在实现上,一个区块的主要内容是包含了某一时刻提交的所有交易以及交易执行之后的状态数据的快照的hash,而不存储具体的交易操作和状态数据。 | |||
- 交易(Transaction) | |||
在概念上,与通常所说的Transaction的概念是一致的,表示一组需要原子执行的操作。 | |||
- 操作(Operation) | |||
操作是针对“账户(Account)”的“写”指令,包括以下几类: | |||
- 注册账户 | |||
- 更新账户的状态数据 | |||
- 更新账户的合约代码定义 | |||
- 调用账户的合约代码 | |||
- 合约代码(Contract Code) | |||
合约代码是一段用于对“账户(Account)”的状态数据执行操作的代码程序。 | |||
## 三、部署模型 | |||
1. 总体部署 | |||
 | |||
2. 系统组件 | |||
- 共识节点 | |||
- 复制节点 | |||
- SDK | |||
- 网关 | |||
- 终端 | |||
3. 配置和管理 | |||
## 四、账本结构 | |||
### 1. 账户生成算法 | |||
- 公私钥对 | |||
- 算法:默认ED25519 ,支持 SM2、CA; | |||
- 公钥存储格式:版本 + 算法标识 + 公私钥原始内容 + 校验码 | |||
- 字符编码方式:Base64 | |||
- 地址 | |||
- 算法 | |||
- 给定公钥 P (或由私钥 R 算出公钥 P) | |||
- 中间值 H1 = SHA256( P ) | |||
- 中间值 H2 = RIPEMD-160( H1 ) | |||
- 中间值 X = 版本 + 公钥算法标识 + H2 | |||
- 校验和 C = 前4字节( SHA256( SHA256( X )) ) | |||
- 地址 Address = Base58( X + C ) | |||
### 2. 区块 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>BlockHash</td> | |||
<td>当前区块 hash</td> | |||
<td>对区块中除此之外的其它所有属性一起进行哈希运算生成</td> | |||
</tr> | |||
<tr> | |||
<td>BlockVersion </td> | |||
<td>区块版本</td> | |||
<td>表示区块-交易的属性结构的版本号;</td> | |||
</tr> | |||
<tr> | |||
<td>PreviousBlockHash</td> | |||
<td>上一区块 hash</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>BlockNumber</td> | |||
<td>区块高度</td> | |||
<td>区块高度是一个区块在链中的序号;<br>创始区块的高度为 0,每个新区块的高度依次递增;</td> | |||
</tr> | |||
<tr> | |||
<td>AccountHash</td> | |||
<td>账户树hash</td> | |||
<td>账户的 Merkle Tree 根的 hash</td> | |||
</tr> | |||
<tr> | |||
<td>AccountCount</td> | |||
<td>账户数量</td> | |||
<td>区块生成时账本中的全部账户的总数</td> | |||
</tr> | |||
<tr> | |||
<td>TxTreeHash</td> | |||
<td>交易树 hash</td> | |||
<td>本区块的交易集合的 Merkle Tree 根的 hash</td> | |||
</tr> | |||
<tr> | |||
<td>TxCount</td> | |||
<td>区块交易数量</td> | |||
<td>当前区块包含的交易的数量;</td> | |||
</tr> | |||
<tr> | |||
<td>TxTotalCount</td> | |||
<td>账本交易总数</td> | |||
<td>截止到当前区块为止当前账本的所有交易的总数量;</td> | |||
</tr> | |||
<tr> | |||
<td>CloseTime</td> | |||
<td>区块关闭时间</td> | |||
<td>生成当前区块时的区块链节点的网络时间;</td> | |||
</tr> | |||
</table> | |||
### 3. 交易 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>Hash</td> | |||
<td>当前交易 hash</td> | |||
<td>对交易中除此之外的其它所有属性一起进行哈希运算生成</td> | |||
</tr> | |||
<tr> | |||
<td>LedgerNumber</td> | |||
<td>区块高度</td> | |||
<td>交易被包含的区块高度</td> | |||
</tr> | |||
<tr> | |||
<td>BlobHash</td> | |||
<td>交易数据块hash</td> | |||
<td>交易的数据块是交易的原始数据,包含客户端提交的交易的全部操作及其参数; | |||
<br>交易的参与者需要使用私钥对交易数据块进行签名;</td> | |||
</tr> | |||
<tr> | |||
<td>Operations</td> | |||
<td>操作列表</td> | |||
<td>交易的操作列表;</td> | |||
</tr> | |||
<tr> | |||
<td>Sponsor</td> | |||
<td>交易发起人</td> | |||
<td>交易发起人的账户地址;</td> | |||
</tr> | |||
<tr> | |||
<td>SequenceNumber</td> | |||
<td>交易序号</td> | |||
<td>交易序号记录了一个特定的发起人的交易的顺序号,等同于该发起人历史上发起的交易的总数;</td> | |||
</tr> | |||
<tr> | |||
<td>Signatures</td> | |||
<td>签名列表</td> | |||
<td>由交易发起人和其它参与者对交易数据块的签名的列表;</td> | |||
</tr> | |||
<tr> | |||
<td>Result</td> | |||
<td>交易结果</td> | |||
<td>0 - 表示执行成功;非零表示执行失败;<br>注:最终的账本只包含成功的交易;</td> | |||
</tr> | |||
</table> | |||
### 4. 操作 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>OpType</td> | |||
<td>操作类型</td> | |||
<td> | |||
一级操作类型包括:注册账户、配置权限、写入键值数据、写入对象数据、定义合约代码、调用合约代码;<br> | |||
“键值数据写入”操作的子操作类型包括:填入键值、移除键、数值增加、数值减少;<br> | |||
“对象数据写入”操作的自操作类型包括:插入对象、更新对象、移除对象; | |||
</td> | |||
</tr> | |||
<tr> | |||
<td>Args</td> | |||
<td>参数列表</td> | |||
<td>与操作类型相对应的参数列表;</td> | |||
</tr> | |||
<tr> | |||
<td>SubOps</td> | |||
<td>子操作列表</td> | |||
<td>“子操作”是“操作”的递归定义,由“操作类型”来标识;</td> | |||
</tr> | |||
</table> | |||
### 5. 账户 | |||
<table> | |||
<tr> | |||
<th>属性</th> | |||
<th>名称</th> | |||
<th>说明</th> | |||
</tr> | |||
<tr> | |||
<td>Address</td> | |||
<td>地址</td> | |||
<td>账户的唯一标识</td> | |||
</tr> | |||
<tr> | |||
<td>RegNumber</td> | |||
<td>注册号</td> | |||
<td>账户被注册到区块链的区块高度;</td> | |||
</tr> | |||
<tr> | |||
<td>TxSquenceNumber</td> | |||
<td>交易序列号</td> | |||
<td>由账户发起的交易的序列号,初始为 0,账户每发起一个交易则增加1;</td> | |||
</tr> | |||
<tr> | |||
<td>ModelVersion</td> | |||
<td>账户模型版本</td> | |||
<td>表示构成一个账户结构的属性模型的程序版本号;</td> | |||
</tr> | |||
<tr> | |||
<td>Version</td> | |||
<td>账户版本</td> | |||
<td>初始为 0,对账户的每一次变更(包括对权限设置、状态和合约代码的变更)都会使账户状态版本增加 1 ;<br>注:交易序号的改变不会导致账户版本的增加;</td> | |||
</tr> | |||
<tr> | |||
<td>PrivilegeHash</td> | |||
<td>权限 hash</td> | |||
<td>权限树的根hash;</td> | |||
</tr> | |||
<tr> | |||
<td>PrivilegeVersion</td> | |||
<td>权限版本</td> | |||
<td>初始为 0, 每次对权限的变更都导致版本号加 1;</td> | |||
</tr> | |||
<tr> | |||
<td>StateType</td> | |||
<td>状态类型</td> | |||
<td>账户的状态类型有3种:空类型(NIL);键值类型;对象类型;</td> | |||
</tr> | |||
<tr> | |||
<td>StateVersion</td> | |||
<td>状态版本</td> | |||
<td>账户的状态类型有3种:空类型(NIL);键值类型;对象类型;</td> | |||
</tr> | |||
<tr> | |||
<td>StateHash</td> | |||
<td>状态哈希</td> | |||
<td>数据状态的 merkle tree 的根hash;</td> | |||
</tr> | |||
<tr> | |||
<td>CodeHash</td> | |||
<td>合约代码哈希</td> | |||
<td>由“账户地址+合约代码版本号+合约代码内容”生成的哈希;</td> | |||
</tr> | |||
<tr> | |||
<td>CodeVersion</td> | |||
<td>代码版本</td> | |||
<td>初始为 0,每次对代码的变更都使版本加 1 ;</td> | |||
</tr> | |||
</table> | |||
## 五、编程接口 | |||
### 1. 服务连接 | |||
```java | |||
//创建服务代理 | |||
public static BlockchainKeyPair CLIENT_CERT = BlockchainKeyGenerator.getInstance().generate(); | |||
final String GATEWAY_IP = "127.0.0.1"; | |||
final int GATEWAY_PORT = 80; | |||
final boolean SECURE = false; | |||
GatewayServiceFactory serviceFactory = GatewayServiceFactory.connect(GATEWAY_IP, GATEWAY_PORT, SECURE, | |||
CLIENT_CERT); | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
``` | |||
### 2. 用户注册 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义注册账号的 TX; | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); | |||
CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); | |||
BlockchainKeyPair user = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); | |||
txTemp.users().register(user.getIdentity()); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
// 提交交易; | |||
prepTx.commit(); | |||
``` | |||
### 3. 数据账户注册 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义注册账号的 TX; | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
SignatureFunction signatureFunction = asymmetricCryptography.getSignatureFunction(CryptoAlgorithm.ED25519); | |||
CryptoKeyPair cryptoKeyPair = signatureFunction.generateKeyPair(); | |||
BlockchainKeyPair dataAccount = new BlockchainKeyPair(cryptoKeyPair.getPubKey(), cryptoKeyPair.getPrivKey()); | |||
txTemp.dataAccounts().register(dataAccount.getIdentity()); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
// 提交交易; | |||
prepTx.commit(); | |||
``` | |||
### 4. 写入数据 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
HashDigest ledgerHash = getLedgerHash(); | |||
// 在本地定义注册账号的 TX; | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
// -------------------------------------- | |||
// 将商品信息写入到指定的账户中; | |||
// 对象将被序列化为 JSON 形式存储,并基于 JSON 结构建立查询索引; | |||
String commodityDataAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
Commodity commodity1 = new Commodity(); | |||
txTemp.dataAccount(commodityDataAccount).set("ASSET_CODE", commodity1.getCode().getBytes(), -1); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
String txHash = ByteArray.toBase64(prepTx.getHash().toBytes()); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
// 提交交易; | |||
prepTx.commit(); | |||
``` | |||
### 5. 查询数据 | |||
> 注:详细的查询可参考模块sdk-samples中SDK_GateWay_Query_Test_相关测试用例 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 查询区块信息; | |||
// 区块高度; | |||
long ledgerNumber = service.getLedger(LEDGER_HASH).getLatestBlockHeight(); | |||
// 最新区块; | |||
LedgerBlock latestBlock = service.getBlock(LEDGER_HASH, ledgerNumber); | |||
// 区块中的交易的数量; | |||
long txCount = service.getTransactionCount(LEDGER_HASH, latestBlock.getHash()); | |||
// 获取交易列表; | |||
LedgerTransaction[] txList = service.getTransactions(LEDGER_HASH, ledgerNumber, 0, 100); | |||
// 遍历交易列表 | |||
for (LedgerTransaction ledgerTransaction : txList) { | |||
TransactionContent txContent = ledgerTransaction.getTransactionContent(); | |||
Operation[] operations = txContent.getOperations(); | |||
if (operations != null && operations.length > 0) { | |||
for (Operation operation : operations) { | |||
operation = ClientOperationUtil.read(operation); | |||
// 操作类型:数据账户注册操作 | |||
if (operation instanceof DataAccountRegisterOperation) { | |||
DataAccountRegisterOperation daro = (DataAccountRegisterOperation) operation; | |||
BlockchainIdentity blockchainIdentity = daro.getAccountID(); | |||
} | |||
// 操作类型:用户注册操作 | |||
else if (operation instanceof UserRegisterOperation) { | |||
UserRegisterOperation uro = (UserRegisterOperation) operation; | |||
BlockchainIdentity blockchainIdentity = uro.getUserID(); | |||
} | |||
// 操作类型:账本注册操作 | |||
else if (operation instanceof LedgerInitOperation) { | |||
LedgerInitOperation ledgerInitOperation = (LedgerInitOperation)operation; | |||
LedgerInitSetting ledgerInitSetting = ledgerInitOperation.getInitSetting(); | |||
ParticipantNode[] participantNodes = ledgerInitSetting.getConsensusParticipants(); | |||
} | |||
// 操作类型:合约发布操作 | |||
else if (operation instanceof ContractCodeDeployOperation) { | |||
ContractCodeDeployOperation ccdo = (ContractCodeDeployOperation) operation; | |||
BlockchainIdentity blockchainIdentity = ccdo.getContractID(); | |||
} | |||
// 操作类型:合约执行操作 | |||
else if (operation instanceof ContractEventSendOperation) { | |||
ContractEventSendOperation ceso = (ContractEventSendOperation) operation; | |||
} | |||
// 操作类型:KV存储操作 | |||
else if (operation instanceof DataAccountKVSetOperation) { | |||
DataAccountKVSetOperation.KVWriteEntry[] kvWriteEntries = | |||
((DataAccountKVSetOperation) operation).getWriteSet(); | |||
if (kvWriteEntries != null && kvWriteEntries.length > 0) { | |||
for (DataAccountKVSetOperation.KVWriteEntry kvWriteEntry : kvWriteEntries) { | |||
BytesValue bytesValue = kvWriteEntry.getValue(); | |||
DataType dataType = bytesValue.getType(); | |||
Object showVal = ClientOperationUtil.readValueByBytesValue(bytesValue); | |||
System.out.println("writeSet.key=" + kvWriteEntry.getKey()); | |||
System.out.println("writeSet.value=" + showVal); | |||
System.out.println("writeSet.type=" + dataType); | |||
System.out.println("writeSet.version=" + kvWriteEntry.getExpectedVersion()); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
// 根据交易的 hash 获得交易;注:客户端生成 PrepareTransaction 时得到交易hash; | |||
HashDigest txHash = txList[0].getTransactionContent().getHash(); | |||
Transaction tx = service.getTransactionByContentHash(LEDGER_HASH, txHash); | |||
// 获取数据; | |||
String commerceAccount = "GGhhreGeasdfasfUUfehf9932lkae99ds66jf=="; | |||
String[] objKeys = new String[] { "x001", "x002" }; | |||
KVDataEntry[] kvData = service.getDataEntries(LEDGER_HASH, commerceAccount, objKeys); | |||
long payloadVersion = kvData[0].getVersion(); | |||
// 获取数据账户下所有的KV列表 | |||
KVDataEntry[] kvData = service.getDataEntries(ledgerHash, commerceAccount, 0, 100); | |||
if (kvData != null && kvData.length > 0) { | |||
for (KVDataEntry kvDatum : kvData) { | |||
System.out.println("kvData.key=" + kvDatum.getKey()); | |||
System.out.println("kvData.version=" + kvDatum.getVersion()); | |||
System.out.println("kvData.type=" + kvDatum.getType()); | |||
System.out.println("kvData.value=" + kvDatum.getValue()); | |||
} | |||
} | |||
``` | |||
### 6. 合约发布 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义TX模板 | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
// 合约内容读取 | |||
byte[] contractBytes = FileUtils.readBytes(new File(CONTRACT_FILE)); | |||
// 生成用户 | |||
BlockchainIdentityData blockchainIdentity = new BlockchainIdentityData(getSponsorKey().getPubKey()); | |||
// 发布合约 | |||
txTemp.contracts().deploy(blockchainIdentity, contractBytes); | |||
// TX 准备就绪; | |||
PreparedTransaction prepTx = txTemp.prepare(); | |||
// 使用私钥进行签名; | |||
CryptoKeyPair keyPair = getSponsorKey(); | |||
prepTx.sign(keyPair); | |||
// 提交交易; | |||
TransactionResponse transactionResponse = prepTx.commit(); | |||
assertTrue(transactionResponse.isSuccess()); | |||
// 打印合约地址 | |||
System.out.println(blockchainIdentity.getAddress().toBase58()); | |||
``` | |||
### 7. 合约执行 | |||
```java | |||
// 创建服务代理; | |||
BlockchainService service = serviceFactory.getBlockchainService(); | |||
// 在本地定义TX模板 | |||
TransactionTemplate txTemp = service.newTransaction(ledgerHash); | |||
// 合约地址 | |||
String contractAddressBase58 = ""; | |||
// 使用接口方式调用合约 | |||
TransferContract transferContract = txTpl.contract(contractAddress, TransferContract.class); | |||
// 使用decode方式调用合约内部方法(create方法) | |||
// 返回GenericValueHolder可通过get方法获取结果,但get方法需要在commit调用后执行 | |||
GenericValueHolder<String> result = ContractReturnValue.decode(transferContract.create(address, account, money)); | |||
PreparedTransaction ptx = txTpl.prepare(); | |||
ptx.sign(adminKey); | |||
TransactionResponse transactionResponse = ptx.commit(); | |||
String cotractExecResult = result.get(); | |||
// TransactionResponse也提供了可供查询结果的接口 | |||
OperationResult[] operationResults = transactionResponse.getOperationResults(); | |||
// 通过OperationResult获取结果 | |||
for (int i = 0; i < operationResults.length; i++) { | |||
OperationResult opResult = operationResults[i]; | |||
System.out.printf("Operation[%s].result = %s \r\n", | |||
opResult.getIndex(), BytesValueEncoding.decode(opResult.getResult())); | |||
} | |||
``` |
@@ -0,0 +1,25 @@ | |||
package com.jd.blockchain.contract.jvm; | |||
import com.jd.blockchain.contract.ContractType; | |||
import com.jd.blockchain.utils.Bytes; | |||
public class InstantiatedContractCode<T> extends AbstractContractCode { | |||
private T instance; | |||
public InstantiatedContractCode(Bytes address, long version, Class<T> delaredInterface, T instance) { | |||
super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); | |||
this.instance = instance; | |||
} | |||
private static ContractDefinition resolveContractDefinition(Class<?> declaredIntf, Class<?> implementedClass) { | |||
ContractType contractType = ContractType.resolve(declaredIntf); | |||
return new ContractDefinition(contractType, implementedClass); | |||
} | |||
@Override | |||
protected T getContractInstance() { | |||
return instance; | |||
} | |||
} |
@@ -7,6 +7,8 @@ import org.bouncycastle.crypto.CipherParameters; | |||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator; | |||
import org.bouncycastle.crypto.params.*; | |||
import org.bouncycastle.crypto.signers.ECDSASigner; | |||
import org.bouncycastle.jce.ECNamedCurveTable; | |||
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; | |||
import org.bouncycastle.math.ec.ECCurve; | |||
import org.bouncycastle.math.ec.ECMultiplier; | |||
import org.bouncycastle.math.ec.ECPoint; | |||
@@ -28,12 +30,10 @@ public class ECDSAUtils { | |||
// p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 | |||
// the curve equation is y^2 = x^3 + 7. | |||
private static final X9ECParameters X9_PARAMS = SECNamedCurves.getByName("secp256k1"); | |||
private static final ECCurve CURVE = X9_PARAMS.getCurve(); | |||
private static final ECPoint ECDSA_G = X9_PARAMS.getG(); | |||
private static final BigInteger ECDSA_H = X9_PARAMS.getH(); | |||
private static final BigInteger ECDSA_N = X9_PARAMS.getN(); | |||
private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, ECDSA_G,ECDSA_N,ECDSA_H); | |||
private static final ECNamedCurveParameterSpec PARAMS = ECNamedCurveTable.getParameterSpec("secp256k1"); | |||
private static final ECCurve CURVE = PARAMS.getCurve(); | |||
private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters( | |||
CURVE, PARAMS.getG(), PARAMS.getN(), PARAMS.getH()); | |||
//-----------------Key Generation Algorithm----------------- | |||
@@ -16,6 +16,9 @@ import org.bouncycastle.util.encoders.Base64; | |||
import java.io.IOException; | |||
import java.security.*; | |||
import java.security.spec.InvalidKeySpecException; | |||
import java.security.spec.PKCS8EncodedKeySpec; | |||
import java.security.spec.X509EncodedKeySpec; | |||
/** | |||
* @author zhanglin33 | |||
@@ -25,16 +28,18 @@ import java.security.*; | |||
*/ | |||
public class CSRBuilder { | |||
private String BC = BouncyCastleProvider.PROVIDER_NAME; | |||
private final String BC = BouncyCastleProvider.PROVIDER_NAME; | |||
private PublicKey pubKey; | |||
private PrivateKey privKey; | |||
private String algoName; | |||
private int keyLength; | |||
public void init() { | |||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); | |||
algoName = "SHA1withRSA"; | |||
keyLength = 2048; | |||
KeyPairGenerator generator; | |||
try { | |||
generator = KeyPairGenerator.getInstance("RSA", BC); | |||
@@ -47,10 +52,11 @@ public class CSRBuilder { | |||
} | |||
} | |||
public void init(String algoName, int KeyLength) { | |||
public void init(String algoName, int keyLength) { | |||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); | |||
this.algoName = algoName; | |||
this.algoName = algoName; | |||
this.keyLength = keyLength; | |||
KeyPairGenerator generator; | |||
KeyPair keyPair; | |||
@@ -60,34 +66,69 @@ public class CSRBuilder { | |||
switch (hashAndSignature[1]) { | |||
case "RSA": { | |||
generator = KeyPairGenerator.getInstance("RSA", BC); | |||
generator.initialize(KeyLength); | |||
keyPair = generator.generateKeyPair(); | |||
pubKey = keyPair.getPublic(); | |||
privKey = keyPair.getPrivate(); | |||
generator.initialize(keyLength); | |||
break; | |||
} | |||
case "SM2": { | |||
generator = KeyPairGenerator.getInstance("EC", BC); | |||
if (KeyLength != 256) { | |||
if (keyLength != 256) { | |||
throw new CryptoException("SM3withSM2 with unsupported key length [" + | |||
KeyLength +"] in CSR!"); | |||
keyLength +"] in CSR!"); | |||
} | |||
generator.initialize(new ECNamedCurveGenParameterSpec("sm2p256v1")); | |||
keyPair = generator.generateKeyPair(); | |||
pubKey = keyPair.getPublic(); | |||
privKey = keyPair.getPrivate(); | |||
break; | |||
} | |||
default: throw new CryptoException("Unsupported algorithm [" + algoName + "] with key length [" + | |||
KeyLength +"] in CSR!"); | |||
keyLength +"] in CSR!"); | |||
} | |||
keyPair = generator.generateKeyPair(); | |||
pubKey = keyPair.getPublic(); | |||
privKey = keyPair.getPrivate(); | |||
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) { | |||
throw new CryptoException(e.getMessage(), e); | |||
} | |||
} | |||
public void init(String algoName, byte[] pubKeyBytes, byte[] privKeyBytes) { | |||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); | |||
this.algoName = algoName; | |||
String[] hashAndSignature = algoName.split("with"); | |||
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes); | |||
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes); | |||
KeyFactory keyFactory; | |||
try { | |||
switch (hashAndSignature[1]) { | |||
case "RSA": { | |||
keyFactory = KeyFactory.getInstance("RSA"); | |||
privKey = keyFactory.generatePrivate(privKeySpec); | |||
pubKey = keyFactory.generatePublic(pubKeySpec); | |||
keyLength = (pubKey.getEncoded().length < 4096 / 8)? 2048: 4096; | |||
break; | |||
} | |||
case "SM2": { | |||
keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); | |||
privKey = keyFactory.generatePrivate(privKeySpec); | |||
pubKey = keyFactory.generatePublic(pubKeySpec); | |||
keyLength = 256; | |||
break; | |||
} | |||
default: throw new CryptoException("Unsupported algorithm [" + algoName + "] with the given key pair!"); | |||
} | |||
} catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) { | |||
throw new CryptoException(e.getMessage(), e); | |||
} | |||
} | |||
public String buildRequest(String countryName, String stateName, String cityName, | |||
String organizationName, String departmentName, String domainName, | |||
String emailName) { | |||
@@ -126,4 +167,12 @@ public class CSRBuilder { | |||
public PrivateKey getPrivKey() { | |||
return privKey; | |||
} | |||
public String getAlgoName() { | |||
return algoName; | |||
} | |||
public int getKeyLength() { | |||
return keyLength; | |||
} | |||
} |
@@ -25,6 +25,7 @@ public class CertParser { | |||
private String sigAlgName; | |||
private String userName; | |||
private String issuerName; | |||
private int keyLength; | |||
private Date startTime; | |||
private Date endTime; | |||
@@ -71,6 +72,17 @@ public class CertParser { | |||
sigAlgName = userCert.getSigAlgName(); | |||
issuerName = userCert.getIssuerX500Principal().getName(); | |||
userName = userCert.getSubjectX500Principal().getName(); | |||
switch (sigAlgName) { | |||
case "SM3WITHSM2": { | |||
keyLength = 256; | |||
break; | |||
} | |||
case "SHA1WITHRSA": { | |||
keyLength = (pubKey.getEncoded().length < 4096 / 8)? 2048: 4096; | |||
break; | |||
} | |||
} | |||
} | |||
// certificate string in Base64 format | |||
@@ -121,6 +133,10 @@ public class CertParser { | |||
return issuerName; | |||
} | |||
public int getKeyLength() { | |||
return keyLength; | |||
} | |||
public Date getStartTime() { | |||
return startTime; | |||
} | |||
@@ -1,9 +1,13 @@ | |||
package com.jd.blockchain.crypto.service.pki; | |||
import com.jd.blockchain.crypto.*; | |||
import com.jd.blockchain.crypto.utils.CSRBuilder; | |||
import com.jd.blockchain.crypto.utils.CertParser; | |||
import com.jd.blockchain.utils.io.BytesUtils; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.junit.Test; | |||
import java.security.*; | |||
import java.util.Random; | |||
import static com.jd.blockchain.crypto.CryptoAlgorithm.*; | |||
@@ -125,4 +129,145 @@ public class SHA1WITHRSA2048SignatureFunctionTest { | |||
resolvedSignatureDigest.getAlgorithm()); | |||
assertArrayEquals(signatureDigestBytes, resolvedSignatureDigest.toBytes()); | |||
} | |||
@Test | |||
public void testWithCSRAndCert() { | |||
String countryName = "CN"; | |||
String stateName = "Beijing"; | |||
String cityName = "Beijing"; | |||
String organizationName = "JD.com"; | |||
String departmentName = "Blockchain Department"; | |||
String domainName = "ledger.jd.com"; | |||
String emailName = "zhanglin33@jd.com"; | |||
String publicKeyStr = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100c91e978897a36b" + | |||
"30a57d265807441b0eff40c5572ecf029cd0cb2999b715edd2d5345c7b60c003075b1352629d03b943d08b25bfc5245" + | |||
"a400f9ecbc1757394eac452d6316bf90cfcff5edfc427e277aa5266e89b1b2daa2e69dad5575515f49417c9ff332c83" + | |||
"0dcd5537381f08e00b11123ad947bb11b18666d264ab5d8cdc92d0967fd7e0e6677746e2f01270d0594f74cda4e9d77" + | |||
"4c6f3824499896bfb6424684af260d71b57a1366b741104fc647d9e38de055568daad60c116686e4afa1e9c83e9e30c" + | |||
"7216e61353da2f75b2dde2c0ae870cf0ccc90490d1e22ecccbf3985d30933689847e6faf946993f930f83ac5b816075" + | |||
"793a9a6df20727552a21be30203010001"; | |||
String privateKeyStr = "308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100c91e" + | |||
"978897a36b30a57d265807441b0eff40c5572ecf029cd0cb2999b715edd2d5345c7b60c003075b1352629d03b943d08" + | |||
"b25bfc5245a400f9ecbc1757394eac452d6316bf90cfcff5edfc427e277aa5266e89b1b2daa2e69dad5575515f49417" + | |||
"c9ff332c830dcd5537381f08e00b11123ad947bb11b18666d264ab5d8cdc92d0967fd7e0e6677746e2f01270d0594f7" + | |||
"4cda4e9d774c6f3824499896bfb6424684af260d71b57a1366b741104fc647d9e38de055568daad60c116686e4afa1e" + | |||
"9c83e9e30c7216e61353da2f75b2dde2c0ae870cf0ccc90490d1e22ecccbf3985d30933689847e6faf946993f930f83" + | |||
"ac5b816075793a9a6df20727552a21be30203010001028201003975637e93300d8a2ee573e47762f6461117cca96d46" + | |||
"982cfc1be6ed3318f142a845d6dc2ad680a703d69fd56b9d6a3b1d23fbeb6f63c4e303736f2bfca5c2585631825f494" + | |||
"534783d6f3a07bd0b5efbcaa1faf7814ac9118c8d8820f4be9a8b0ac6db819fc86b538bf284369d9f009a667668a82d" + | |||
"224f7122041eddb492ef5b02d462aa11bc550bf3785a5d7538043586cfb0cd5c313a4746f22a5d4a89a412b279a7517" + | |||
"176b1858a9677a1a60605aa0b2a7d260cf2e9e23a0e67c4a23ac046c62973239e84874c3695cc3f34073a62bd50b597" + | |||
"ee06092a93fa6e41303ab4d2293ffad4c8db06e6516ae0a26a4d681305c3b7d535b540a638ca6cff1ce483750281810" + | |||
"0e39967c5b287bd319b448255a1033591c74f4d688eb71f89a674cdb1be999e9abcf40e6b7f0bb3054262d7625002da" + | |||
"7d34e56055ee130b66d682a0b9f8ea240a7e2dd1ff3f4b32f3bd352ba0a5dffc0315da34922493c267597787222213b" + | |||
"f87d99fbd2a809c2647a1937cd1e303d746373db7c409a2ef33f8c06bb838bc612702818100e2374c71db5f0b4a199f" + | |||
"f9d646a1785a84383a14aceb506f515b380c69ff45b51d0e941f55f0a234d8a3ee30bed656142bb5b985e462c44d234" + | |||
"cfd424ecd6ca5fc70503862978ffe42b45bd698a99091d6fc65e2d85652e0ef56c52e8e05a6c5e879922c1d794e22f3" + | |||
"51998217b5c6637a6abb716bf90cd1f23a2eedcdfface50281805d28a872224e2f2183e539d7e4ccd47b73f240c4004" + | |||
"e72493c69e8dbcd2141eb22565f249edee20ad00e770c95a5655b0470b2cad964d030eab293292bfa62802cff824a10" + | |||
"d52de8d8545024346106dd186fb53ef05bcea1d0dbfce2fac1cc8ec583fdc0ccdd9d498a983cea081ac55dc734aae84" + | |||
"1ed802d6caf0e285c88b6d702818068b2a752daf1364c6967bd3e0b1a98956c3489cd1feb19232c4847bc97226aa4d4" + | |||
"79f6dc39ee51649c0fe321f471470db6dd38ac5b73caded8c3bd437f2d5c67c65a450693bb0a0de7d989d7dc783e4d0" + | |||
"16f77c871d02233b1123bd8bc2aa97157934cafd6445a819a93ddb4743cd141215b5cbdb5f7629398c48d0bcb17d671" + | |||
"028181009282554329b89bcb2236a7e2a5bff53627e3ca7cc792d65236085741bc62a3f5767654722af561eff175664" + | |||
"1af60e3d3959f63f0fec00cb29443ffca4ff751a76ecc6fa192ebe08ec9f643b9bb0d11bc90c1e850f0528ef223ea5f" + | |||
"e4b4c107cc3a9eb26e6b84d74d87acbf7cc760cd788cbc30f95f8399077b25cdd924604c01"; | |||
String expectedCSR = "MIIC4jCCAcoCAQAwgZwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW" + | |||
"5nMQ8wDQYDVQQKDAZKRC5jb20xHjAcBgNVBAsMFUJsb2NrY2hhaW4gRGVwYXJ0bWVudDEWMBQGA1UEAwwNbGVkZ2VyLmpkL" + | |||
"mNvbTEgMB4GCSqGSIb3DQEJARYRemhhbmdsaW4zM0BqZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ" + | |||
"HpeIl6NrMKV9JlgHRBsO/0DFVy7PApzQyymZtxXt0tU0XHtgwAMHWxNSYp0DuUPQiyW/xSRaQA+ey8F1c5TqxFLWMWv5DPz" + | |||
"/Xt/EJ+J3qlJm6JsbLaouadrVV1UV9JQXyf8zLIMNzVU3OB8I4AsREjrZR7sRsYZm0mSrXYzcktCWf9fg5md3RuLwEnDQWU" + | |||
"90zaTp13TG84JEmYlr+2QkaEryYNcbV6E2a3QRBPxkfZ443gVVaNqtYMEWaG5K+h6cg+njDHIW5hNT2i91st3iwK6HDPDMy" + | |||
"QSQ0eIuzMvzmF0wkzaJhH5vr5Rpk/kw+DrFuBYHV5Oppt8gcnVSohvjAgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEALKHw" + | |||
"BrlppbN+CSJwHxO99rv2XVD7L8lwwiMqdMzoNHxZUoob4AcGS3Iyxy6obX8fZh7DCA4nN0AmvrJCmWD3hCLclWKlwXTwvFe" + | |||
"WIeB4xEB9MVPV6/Hs/QaI9Rjhd+O/Alfzov8y+KyYHEWmwxHj2RtQm1ZeulELzMvRjsqP+m5tOM1hLnPMU+AF1tZ9Fc7Jbm" + | |||
"7Dwh6TAPpmaH1aVbHsnlZyCp8K4nMozVogcXJqg3qsbeJ6c/TofL0fURqiTJxLKMC0aD0TwVcNwDWEmc8cpqGheG1g6UF5l" + | |||
"QBpeR2Br4f3LXJgr1Op1RxRy6I+X7h1IL38Q3jAOoU4y04O/+an7g=="; | |||
String expectedUserCert = "MIIERjCCAy6gAwIBAgIFIChmYJgwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVB" + | |||
"AoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4X" + | |||
"DTE5MDgyODA2MzEyNVoXDTIxMDgyODA2MzEyNVoweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTE" + | |||
"RMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTElMCMGA1UEAxQcMDUxQHpoYW5nbGluIUBaMTg2MT" + | |||
"IyMjkyOTVANTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMkel4iXo2swpX0mWAdEGw7/QMVXLs8CnNDLKZm3F" + | |||
"e3S1TRce2DAAwdbE1JinQO5Q9CLJb/FJFpAD57LwXVzlOrEUtYxa/kM/P9e38Qn4neqUmbomxstqi5p2tVXVRX0lBfJ/zMs" + | |||
"gw3NVTc4HwjgCxESOtlHuxGxhmbSZKtdjNyS0JZ/1+DmZ3dG4vAScNBZT3TNpOnXdMbzgkSZiWv7ZCRoSvJg1xtXoTZrdBE" + | |||
"E/GR9njjeBVVo2q1gwRZobkr6HpyD6eMMchbmE1PaL3Wy3eLArocM8MzJBJDR4i7My/OYXTCTNomEfm+vlGmT+TD4OsW4Fg" + | |||
"dXk6mm3yBydVKiG+MCAwEAAaOB9TCB8jAfBgNVHSMEGDAWgBT8C7xEmg4xoYOpgYcnHgVCxr9W+DBIBgNVHSAEQTA/MD0GC" + | |||
"GCBHIbvKgECMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20uY24vdXMvdXMtMTUuaHRtMDoGA1UdHwQzMDEw" + | |||
"L6AtoCuGKWh0dHA6Ly8yMTAuNzQuNDIuMy9PQ0ExMS9SU0EvY3JsMjY2NTAuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQ" + | |||
"UOTy45HymVDivPwA83lRoMpShasQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBBQUAA4IBAQ" + | |||
"A7NFkDMV1mRV/07DJUdZJzvkm+QihhpLp7D2dUblCCEt0jjY5RdLiWG7HvJnlPTThiSkiejEO0Fy1cQzA5jYRhwp+70X8X9" + | |||
"bt5jEBP/V4PyRXKKEvKZMdppLIeVI6rZk/gJzPh2FQYv3qWaINilxLOBP8Qa0kdMBwo6D6/MYwnSGv5zP4NLFUysLUJiKoM" + | |||
"lAzEQSPNnkYRX6nogpkdN91/xgH3GA7fiihrjm5oxMAupCli9LQqvlUvRtv5EKIN9c+ixCAYFagG9IIjMDXbDne77n15i01" + | |||
"420N8sjfTlr9v3W0v1gBSzjzFOT+TTTUtrjfO/Vm8iqq+z22QKIXgYjSF"; | |||
String issuerCert = | |||
"-----BEGIN CERTIFICATE-----\n" + | |||
"MIIDzzCCAregAwIBAgIKUalCR1Mt5ZSK8jANBgkqhkiG9w0BAQUFADBZMQswCQYD\n" + | |||
"VQQGEwJDTjEwMC4GA1UEChMnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24g\n" + | |||
"QXV0aG9yaXR5MRgwFgYDVQQDEw9DRkNBIFRFU1QgQ1MgQ0EwHhcNMTIwODI5MDU1\n" + | |||
"NDM2WhcNMzIwODI0MDU1NDM2WjBZMQswCQYDVQQGEwJDTjEwMC4GA1UEChMnQ2hp\n" + | |||
"bmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRgwFgYDVQQDEw9D\n" + | |||
"RkNBIFRFU1QgT0NBMTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8\n" + | |||
"jn0n8Fp6hcRdACdn1+Y6GAkC6KGgNdKyHPrmsdmhCjnd/i4qUFwnG8cp3D4lFw1G\n" + | |||
"jmjSO5yVYbik/NbS6lbNpRgTK3fDfMFvLJpRIC+IFhG9SdAC2hwjsH9qTpL9cK2M\n" + | |||
"bSdrC6pBdDgnbzaM9AGBF4Y6vXhj5nah4ZMsBvDp19LzXjlGuTPLuAgv9ZlWknsd\n" + | |||
"RN70PIAmvomd10uqX4GIJ4Jq/FdKXOLZ2DNK/NhRyN6Yq71L3ham6tutXeZow5t5\n" + | |||
"0254AnUlo1u6SeH9F8itER653o/oMLFwp+63qXAcqrHUlOQPX+JI8fkumSqZ4F2F\n" + | |||
"t/HfVMnqdtFNCnh5+eIBAgMBAAGjgZgwgZUwHwYDVR0jBBgwFoAUdN7FjQp9EBqq\n" + | |||
"aYNbTSHOhpvMcTgwDAYDVR0TBAUwAwEB/zA4BgNVHR8EMTAvMC2gK6AphidodHRw\n" + | |||
"Oi8vMjEwLjc0LjQyLjMvdGVzdHJjYS9SU0EvY3JsMS5jcmwwCwYDVR0PBAQDAgEG\n" + | |||
"MB0GA1UdDgQWBBT8C7xEmg4xoYOpgYcnHgVCxr9W+DANBgkqhkiG9w0BAQUFAAOC\n" + | |||
"AQEAb7W0K9fZPA+JPw6lRiMDaUJ0oh052yEXreMBfoPulxkBj439qombDiFggRLc\n" + | |||
"3g8wIEKzMOzOKXTWtnzYwN3y/JQSuJb/M1QqOEEM2PZwCxI4AkBuH6jg03RjlkHg\n" + | |||
"/kTtuIFp9ItBCC2/KkKlp0ENfn4XgVg2KtAjZ7lpyVU0LPnhEqqUVY/xthjlCSa7\n" + | |||
"/XHNStRxsfCTIBUWJ8n2FZyQhfV/UkMNHDBIiJR0v6C4Ai0/290WvbPEIAq+03Si\n" + | |||
"fsHzBeA0C8lP5VzfAr6wWePaZMCpStpLaoXNcAqReKxQllElOqAhRxC5VKH+rnIQ\n" + | |||
"OMRZvB7FRyE9IfwKApngcZbA5g==\n" + | |||
"-----END CERTIFICATE-----"; | |||
byte[] rawPublicKeyBytes = Hex.decode(publicKeyStr); | |||
byte[] rawPrivateKeyBytes = Hex.decode(privateKeyStr); | |||
CSRBuilder builder = new CSRBuilder(); | |||
builder.init("SHA1withRSA", rawPublicKeyBytes, rawPrivateKeyBytes); | |||
String csr = builder.buildRequest(countryName,stateName,cityName, | |||
organizationName,departmentName,domainName, | |||
emailName); | |||
assertEquals(expectedCSR,csr); | |||
CertParser parser = new CertParser(); | |||
parser.parse(expectedUserCert,issuerCert); | |||
PublicKey rawPublicKeyInCert = parser.getPubKey(); | |||
// check that the public key in inputs and the public key in certificate are consistent | |||
assertArrayEquals(rawPublicKeyBytes, rawPublicKeyInCert.getEncoded()); | |||
String algoName = parser.getSigAlgName(); | |||
int keyLength = parser.getKeyLength(); | |||
String length = String.valueOf(keyLength); | |||
String algo = (algoName.contains("RSA")? (algoName + length).toUpperCase(): algoName.toUpperCase()); | |||
CryptoAlgorithm algorithm = Crypto.getAlgorithm(algo); | |||
assertNotNull(algorithm); | |||
SignatureFunction signatureFunction = Crypto.getSignatureFunction(algorithm); | |||
PubKey pubKey = new PubKey(algorithm, rawPublicKeyBytes); | |||
PrivKey privKey = new PrivKey(algorithm, rawPrivateKeyBytes); | |||
// signTest | |||
byte[] data = new byte[1024]; | |||
Random random = new Random(); | |||
random.nextBytes(data); | |||
SignatureDigest signature = signatureFunction.sign(privKey, data); | |||
assertTrue(signatureFunction.verify(signature, pubKey, data)); | |||
} | |||
} |
@@ -1,9 +1,13 @@ | |||
package com.jd.blockchain.crypto.service.pki; | |||
import com.jd.blockchain.crypto.*; | |||
import com.jd.blockchain.crypto.utils.CSRBuilder; | |||
import com.jd.blockchain.crypto.utils.CertParser; | |||
import com.jd.blockchain.utils.io.BytesUtils; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.junit.Test; | |||
import java.security.PublicKey; | |||
import java.util.Random; | |||
import static com.jd.blockchain.crypto.CryptoAlgorithm.ASYMMETRIC_KEY; | |||
@@ -125,4 +129,186 @@ public class SHA1WITHRSA4096SignatureFunctionTest { | |||
resolvedSignatureDigest.getAlgorithm()); | |||
assertArrayEquals(signatureDigestBytes, resolvedSignatureDigest.toBytes()); | |||
} | |||
@Test | |||
public void testWithCSRAndCert() { | |||
String countryName = "CN"; | |||
String stateName = "Beijing"; | |||
String cityName = "Beijing"; | |||
String organizationName = "JD.com"; | |||
String departmentName = "Blockchain Department"; | |||
String domainName = "ledger.jd.com"; | |||
String emailName = "zhanglin33@jd.com"; | |||
String publicKeyStr = "30820222300d06092a864886f70d01010105000382020f003082020a0282020100b40edee83b609e" + | |||
"aa001f2496f95d2f3302513306ec9a8e7fce0d2e141fce7ee357a7465314c3f5f4b08cb6c95803c368ddbfedba483cb" + | |||
"5c45914037ceee5783fc971a12ef9b0e4158dc379b59499eee629324a9beb350c4c10e50837345be128b91f43a03381" + | |||
"758bbefe41c45712e4b5fdd5bde780167283706b24e37dd753db65b4c6b3e49cd8be825665d9a29a24b77e76473df02" + | |||
"2327873555aa33ba2ffcc766cbefedc46ec868d10f817822540eaf5754c074dd6d428355ce24a058a4c9ce41e48aad5" + | |||
"92e7955cf93d779d03d3acf25ae271346a9e4255e4ed902ae016032b04efbee98f43cd767653e089b37540e537aede9" + | |||
"dbc04f8f1a858b2764b9eedac80b6a8da5ff02aab4be94e071c70718fde7227cdefec31600a1c55bac16f4de9dea8ab" + | |||
"7824c1ec783b818dfe005f040a3f6872b1c7a6c31a66c1b06eb8d872a23d1b4fdadf9eed58f93b2a2bc145638a79a81" + | |||
"904d39b22128ced18a2556c21888ed1ec8ad59bd6764f1ea16eb7f3574c53166f0827b5072d23017bd725adeb63eeb2" + | |||
"29a4e78d4d7426e936753902bb51e3cd90630314f4ab41272a9e36cb668b2ba9c2ebc02e9ef0c377c88482e839f2f4d" + | |||
"5c8efcfbe1280e52c6bdf80aa487ae03ff9dd9fd981f78172bc1141ce6031b0b8915658d830c696662507d3cf4ddba8" + | |||
"7daabf97c15cbef58b15e84f16f879328c7c65076d94fc6b4514549831850203010001"; | |||
String privateKeyStr = "30820942020100300d06092a864886f70d01010105000482092c308209280201000282020100b40e" + | |||
"dee83b609eaa001f2496f95d2f3302513306ec9a8e7fce0d2e141fce7ee357a7465314c3f5f4b08cb6c95803c368ddb" + | |||
"fedba483cb5c45914037ceee5783fc971a12ef9b0e4158dc379b59499eee629324a9beb350c4c10e50837345be128b9" + | |||
"1f43a03381758bbefe41c45712e4b5fdd5bde780167283706b24e37dd753db65b4c6b3e49cd8be825665d9a29a24b77" + | |||
"e76473df022327873555aa33ba2ffcc766cbefedc46ec868d10f817822540eaf5754c074dd6d428355ce24a058a4c9c" + | |||
"e41e48aad592e7955cf93d779d03d3acf25ae271346a9e4255e4ed902ae016032b04efbee98f43cd767653e089b3754" + | |||
"0e537aede9dbc04f8f1a858b2764b9eedac80b6a8da5ff02aab4be94e071c70718fde7227cdefec31600a1c55bac16f" + | |||
"4de9dea8ab7824c1ec783b818dfe005f040a3f6872b1c7a6c31a66c1b06eb8d872a23d1b4fdadf9eed58f93b2a2bc14" + | |||
"5638a79a81904d39b22128ced18a2556c21888ed1ec8ad59bd6764f1ea16eb7f3574c53166f0827b5072d23017bd725" + | |||
"adeb63eeb229a4e78d4d7426e936753902bb51e3cd90630314f4ab41272a9e36cb668b2ba9c2ebc02e9ef0c377c8848" + | |||
"2e839f2f4d5c8efcfbe1280e52c6bdf80aa487ae03ff9dd9fd981f78172bc1141ce6031b0b8915658d830c696662507" + | |||
"d3cf4ddba87daabf97c15cbef58b15e84f16f879328c7c65076d94fc6b451454983185020301000102820200063fece" + | |||
"3452a579f817513454d3efb842afcac077dbf689a4d89de13533e4cdfb1bb6be0b6dc0d65b29a13bf1dd7b598e67782" + | |||
"b6204b4128e149a54c59136c6ed45c661296169a78180d54a46595c939c26ccd33a7c095de6f08b01610726ef885a26" + | |||
"cebbad5efc14bbe1204d15be5c5de5b64a5cc279b4e6e20bded4a8126973b2ac0e9de11c6a1282f7d060693909a30e0" + | |||
"c49cc500bedd38ed99c18830a26dd39f772aabf527410d54ed338db022d674f21f1332d3b5d5f67234a58a97300d130" + | |||
"aed0d46effc2b4e4895665934188d0c75749e26ca5b97645957989530657b332b4ef202b3d70fe2e07d0d526240fbe1" + | |||
"68e320357be0f54e18008a233a8137e23ca1c54074b31c57eebee49bf2f1c66ea97a2c846a8d26680b97e1240d6763e" + | |||
"60bcd8d696c806362b18bf0504a39e4d465a9548091dbe97c36f6d8e038d95a72c0a88ff524dc81fe0ed2afd69f4251" + | |||
"1a90687a4c632c812234a19a7312b2dea3fcd4515800eca700733b0f83509184fe8d3cd21385f0ef0cec37c433d354f" + | |||
"aed61662a62902c8708c81e2af20898f649c1bafd600baa0409c943cf82bc90ea20a8972da7ab3a252f4f08df2509de" + | |||
"e1dacfe787eef4d60c0c92ec7a3c6277d7be39deed7704ac721d8efd138a410d632a32535142cf977b09d9fb680bc96" + | |||
"c538c00440d5e1ed71f8510e6524e564d69a24d03f1a9a0c326b421e32550801c890282010100e8d72f8f1fbeada724" + | |||
"3e6df25337a2e7fc43e3d4f39877f89abb3b5e453f20f339a1f35e0a2847122f0bd835e6b43fd276f447da04f85cfca" + | |||
"0fa8bf49b313239e36f9595ef1bfb9b8247bf01cc407dfc444421161a5b2e96d4d1d90cea185945e9447d21a2d8a461" + | |||
"f43dff9ba58043feeb49552a3bf2472eea59b2aa8631048c76a15898065be0ef957d4802c827e30339d66ad7aed5851" + | |||
"a5896f12d33f45800dcb10859531c590af7a75e9fa81f1d937a287fb6b066d58720584af2ae161e083681655ae77f48" + | |||
"34bfe0accf1d12bbdd8c2644f78b207cfcb2dedf9fe7d29e1ae5c2a5623f5a1770db27d2636450c79fd39bce39f009b" + | |||
"598e0298e1f77bf8d3d0282010100c5f7af9258a42da93204cecb71c544397bc16691cf0d41308fbd3b88eca7d101d1" + | |||
"377dfe3b7e66707d3e5543b21033ab6c5b0de577740c6e335f512eeb2a839c3f2baeca4ce80e3fbd8fb93fa983e1719" + | |||
"5c5fee63bc163df334a80f2767871e3434adc0bc7030dea44fce414a08da7e918e6f030cfb20b2d29033e9ef1be3e08" + | |||
"ef1f50df0c9325a20ff01d0b7a06761403ae3498aea8bad93110d61a6386d470e630990029e2cb098de40fafa330911" + | |||
"6c3c6de180b7fa41ae14553e891148ab53e970ce372b1777826983baaccac08290e343761d8daa2505f1dc45b8511a3" + | |||
"6e2d0909237be9ae7ebbaa00b31de1224f32959e4e6f3428140ca8e1e5580789e902820100674075609c8d2be880940" + | |||
"6a17cf1a1160ab1f8684895862e023fa0f60ef30da38e1d1914cca04bd3ee74ec2e0ade47a70705108fc7c0734bbbff" + | |||
"1eed1b9cd74f00624d0d2df954bc032bd9b1ec6774f6d736f70d1c26ef2407bffee65130f6f59f99b57ba3013af40d2" + | |||
"12926565fe8c734835276e61a6c228bddb6f3138acd1f94c3bbcbbe9623cb5a9931c3ba0aa60a9a2d5137cfd9f3aa59" + | |||
"3aa63c8b5b8162f07ab8df1391f092827bffe400e3bb73d8a9f8e88495357f3482b2c9a7153bc01c9b88dca4e7b6975" + | |||
"db73e2aa213daa7462cfa4c63afc67d30bcd0a1d2657da323dc0b06e45d09240cab3e0ac1436922a0ede8a79ca0519d" + | |||
"375a7621d23269690282010100bbc1299716d2bf2b94f0d260494ada65da6596adfb3d8af24fa11d71c36175eccf4c5" + | |||
"e065cce88c16f474afea546907aa88dc3243aa2a9976ac99fe96bc82a8269b738534d9558ce432ea87724829bb26a66" + | |||
"1a56a99dc4e6cf727dd17762cc40ca759934e24e9747f49e14832bb2ade97960adb4dd86f2eaa5d719f10d3d6d00742" + | |||
"9b33d98638671a9c40507f9775f4da41ff86a465c68b9ccbb371458086c3b9755c8064bb378f55ac94dc73a72b96869" + | |||
"cd969e1f69b36e7af091a024d8e2a4faf3af999811904937f171c58fd028fd272786cf1a286180f074fee1fdd6b8b5a" + | |||
"9a8c42e0f3b95ef4474fbace54dbc887865467b0524e64dfda3be7b117e34e1028201001a7e846231c34400c1f704e7" + | |||
"fec6e46c87d61f269b71942bc9cc72005ba30eea3db5d0e5b0b754f7a00b96c883399982b5b3a9916765c5ed9129e44" + | |||
"a791ce6892f85758c637bc040da132b8f0cd0ac36ba3aae9334414a77f0b50c0aa03643bfd59b9a621342a4807e46fc" + | |||
"52a5a12fd3ff6762e181c40c2baf3653043c836b14700463af5d68a2a2897897edb5f217d655d5bcd24e7910062f40e" + | |||
"00f19e2f94b45efbbf60cbf734830756baf72dcfca8d2858ca5df63336999474945f3744a96e4ce23f9067bbca849ef" + | |||
"1048cba3a4aad73ed73b0fcd8c2e9f6d06aa768548d7107aa58d9d296f853543f6569e4dd33270540d983460773794f" + | |||
"e9196fc5a54cd"; | |||
String expectedCSR = "MIIE4jCCAsoCAQAwgZwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdCZWlqaW" + | |||
"5nMQ8wDQYDVQQKDAZKRC5jb20xHjAcBgNVBAsMFUJsb2NrY2hhaW4gRGVwYXJ0bWVudDEWMBQGA1UEAwwNbGVkZ2VyLmpkL" + | |||
"mNvbTEgMB4GCSqGSIb3DQEJARYRemhhbmdsaW4zM0BqZC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0" + | |||
"Dt7oO2CeqgAfJJb5XS8zAlEzBuyajn/ODS4UH85+41enRlMUw/X0sIy2yVgDw2jdv+26SDy1xFkUA3zu5Xg/yXGhLvmw5BW" + | |||
"Nw3m1lJnu5ikySpvrNQxMEOUINzRb4Si5H0OgM4F1i77+QcRXEuS1/dW954AWcoNwayTjfddT22W0xrPknNi+glZl2aKaJL" + | |||
"d+dkc98CIyeHNVWqM7ov/Mdmy+/txG7IaNEPgXgiVA6vV1TAdN1tQoNVziSgWKTJzkHkiq1ZLnlVz5PXedA9Os8lricTRqn" + | |||
"kJV5O2QKuAWAysE777pj0PNdnZT4ImzdUDlN67enbwE+PGoWLJ2S57trIC2qNpf8CqrS+lOBxxwcY/ecifN7+wxYAocVbrB" + | |||
"b03p3qireCTB7Hg7gY3+AF8ECj9ocrHHpsMaZsGwbrjYcqI9G0/a357tWPk7KivBRWOKeagZBNObIhKM7RiiVWwhiI7R7Ir" + | |||
"Vm9Z2Tx6hbrfzV0xTFm8IJ7UHLSMBe9clretj7rIppOeNTXQm6TZ1OQK7UePNkGMDFPSrQScqnjbLZosrqcLrwC6e8MN3yI" + | |||
"SC6Dny9NXI78++EoDlLGvfgKpIeuA/+d2f2YH3gXK8EUHOYDGwuJFWWNgwxpZmJQfTz03bqH2qv5fBXL71ixXoTxb4eTKMf" + | |||
"GUHbZT8a0UUVJgxhQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggIBAKowJTG9mZLbDHRndmDUd7PWrq1AiCdk6DStV39B/REF" + | |||
"OrMYcW9X4Ak69fhXQDtD4gu2lKtumDY0oJ8xleM2FHUSzdooTWb7P/QtCIBy27sH6nvlefRWi7ngSTNJlDmwgr0l07UzZU1" + | |||
"Yl/ZULn0XlNAFal+qt+4ZNdiulNwkL6IofXV/8vqOeQw5iICDBYHItyY/mqD8IIaClVd8yNpEuE/W9GdJIDNXQjpug+BxL/" + | |||
"FbjAs6P3ZzJboedJE5urbru2jjb7atl3w/eDo4r6+XNSD8d1PgVmVhzN2WpUWsZNeH2jd9AA6436GjsBssgSRKEc3FTJ+lO" + | |||
"0Jw2d8GewXXkIv8CT4L3BFwqZhGQt27wlb87+W4dIC05JIaJx52869dvu1ky1CL73GROXeS8rVYJsPwVmK2xy3QTaeHGEQh" + | |||
"kiVNeV1cc3mll2z7fgbkjPD8zDNBWUdzSXQMzecY1CBD02iz6LaHfvkI7gXXoiIf1cJrnLtYhv3lG45jKr0E45/Wn7oXmYk" + | |||
"RM4/zHO4KnY7Pp3b3QTgkRJaPnZiG7aiCrdnTIopDSGpTWSPWDLjgwgaCPPz5Pd2Fk+SAE98o7Cu8O0vxMASpd4liaedASE" + | |||
"n6hnrvcTkFLLG2ecZzJZ0aEqPi4es0FqlqVZrLPH2UYpECgfhkGQQKAx4eRTAZFhz1GSkd"; | |||
String expectedUserCert = "MIIFRjCCBC6gAwIBAgIFIChmYWIwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVB" + | |||
"AoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4X" + | |||
"DTE5MDgyODA4MDAxMVoXDTIxMDgyODA4MDAxMVoweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTE" + | |||
"RMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTElMCMGA1UEAxQcMDUxQHpoYW5nbGluIUBaMTg2MT" + | |||
"IyMjkyOTVANzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQO3ug7YJ6qAB8klvldLzMCUTMG7JqOf84NLhQfz" + | |||
"n7jV6dGUxTD9fSwjLbJWAPDaN2/7bpIPLXEWRQDfO7leD/JcaEu+bDkFY3DebWUme7mKTJKm+s1DEwQ5Qg3NFvhKLkfQ6Az" + | |||
"gXWLvv5BxFcS5LX91b3ngBZyg3BrJON911PbZbTGs+Sc2L6CVmXZopokt352Rz3wIjJ4c1Vaozui/8x2bL7+3Ebsho0Q+Be" + | |||
"CJUDq9XVMB03W1Cg1XOJKBYpMnOQeSKrVkueVXPk9d50D06zyWuJxNGqeQlXk7ZAq4BYDKwTvvumPQ812dlPgibN1QOU3rt" + | |||
"6dvAT48ahYsnZLnu2sgLao2l/wKqtL6U4HHHBxj95yJ83v7DFgChxVusFvTeneqKt4JMHseDuBjf4AXwQKP2hyscemwxpmw" + | |||
"bBuuNhyoj0bT9rfnu1Y+TsqK8FFY4p5qBkE05siEoztGKJVbCGIjtHsitWb1nZPHqFut/NXTFMWbwgntQctIwF71yWt62Pu" + | |||
"simk541NdCbpNnU5ArtR482QYwMU9KtBJyqeNstmiyupwuvALp7ww3fIhILoOfL01cjvz74SgOUsa9+Aqkh64D/53Z/Zgfe" + | |||
"BcrwRQc5gMbC4kVZY2DDGlmYlB9PPTduofaq/l8FcvvWLFehPFvh5Mox8ZQdtlPxrRRRUmDGFAgMBAAGjgfUwgfIwHwYDVR" + | |||
"0jBBgwFoAU/Au8RJoOMaGDqYGHJx4FQsa/VvgwSAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwO" + | |||
"i8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh0bTA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vMjEwLjc0LjQyLjMvT0NB" + | |||
"MTEvUlNBL2NybDI2NjU1LmNybDALBgNVHQ8EBAMCA+gwHQYDVR0OBBYEFBl1Gmb89bqbEKyFcTU3eOY/5NmKMB0GA1UdJQQ" + | |||
"WMBQGCCsGAQUFBwMCBggrBgEFBQcDBDANBgkqhkiG9w0BAQUFAAOCAQEADEU//9rnWN1s3/ariMHIUmgzRUdz3fWYiDRzGC" + | |||
"mcnnETlXDstGmoYmwCM+QwHw6cyKXkwkg9zV7c7CgM471ZuF00gq115d432Ps3RXGCpfQ2fn3gs+91ky/YqJOOyBb8KL0IP" + | |||
"r/Zh56/y3XX0gORn4GLqaj+oVZrFcmKrPtVhySlXNiD5BRMq39mUbuLBweGsgNVQ9VxiWc8ZBGjlJ6OVsngbvWrtl3zgkKb" + | |||
"X9lhr8Bxq3G+jOV8jvr1Dkn4a65g2TWcFquxmPvRc5UwN29CimbC7RViCL3Jp+zrGasqbjycuqu5eSXb6gG4/aV0/K9yn5k" + | |||
"YlZMIBlbsXSEi5J26pg=="; | |||
String issuerCert = | |||
"-----BEGIN CERTIFICATE-----\n" + | |||
"MIIDzzCCAregAwIBAgIKUalCR1Mt5ZSK8jANBgkqhkiG9w0BAQUFADBZMQswCQYD\n" + | |||
"VQQGEwJDTjEwMC4GA1UEChMnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24g\n" + | |||
"QXV0aG9yaXR5MRgwFgYDVQQDEw9DRkNBIFRFU1QgQ1MgQ0EwHhcNMTIwODI5MDU1\n" + | |||
"NDM2WhcNMzIwODI0MDU1NDM2WjBZMQswCQYDVQQGEwJDTjEwMC4GA1UEChMnQ2hp\n" + | |||
"bmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRgwFgYDVQQDEw9D\n" + | |||
"RkNBIFRFU1QgT0NBMTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8\n" + | |||
"jn0n8Fp6hcRdACdn1+Y6GAkC6KGgNdKyHPrmsdmhCjnd/i4qUFwnG8cp3D4lFw1G\n" + | |||
"jmjSO5yVYbik/NbS6lbNpRgTK3fDfMFvLJpRIC+IFhG9SdAC2hwjsH9qTpL9cK2M\n" + | |||
"bSdrC6pBdDgnbzaM9AGBF4Y6vXhj5nah4ZMsBvDp19LzXjlGuTPLuAgv9ZlWknsd\n" + | |||
"RN70PIAmvomd10uqX4GIJ4Jq/FdKXOLZ2DNK/NhRyN6Yq71L3ham6tutXeZow5t5\n" + | |||
"0254AnUlo1u6SeH9F8itER653o/oMLFwp+63qXAcqrHUlOQPX+JI8fkumSqZ4F2F\n" + | |||
"t/HfVMnqdtFNCnh5+eIBAgMBAAGjgZgwgZUwHwYDVR0jBBgwFoAUdN7FjQp9EBqq\n" + | |||
"aYNbTSHOhpvMcTgwDAYDVR0TBAUwAwEB/zA4BgNVHR8EMTAvMC2gK6AphidodHRw\n" + | |||
"Oi8vMjEwLjc0LjQyLjMvdGVzdHJjYS9SU0EvY3JsMS5jcmwwCwYDVR0PBAQDAgEG\n" + | |||
"MB0GA1UdDgQWBBT8C7xEmg4xoYOpgYcnHgVCxr9W+DANBgkqhkiG9w0BAQUFAAOC\n" + | |||
"AQEAb7W0K9fZPA+JPw6lRiMDaUJ0oh052yEXreMBfoPulxkBj439qombDiFggRLc\n" + | |||
"3g8wIEKzMOzOKXTWtnzYwN3y/JQSuJb/M1QqOEEM2PZwCxI4AkBuH6jg03RjlkHg\n" + | |||
"/kTtuIFp9ItBCC2/KkKlp0ENfn4XgVg2KtAjZ7lpyVU0LPnhEqqUVY/xthjlCSa7\n" + | |||
"/XHNStRxsfCTIBUWJ8n2FZyQhfV/UkMNHDBIiJR0v6C4Ai0/290WvbPEIAq+03Si\n" + | |||
"fsHzBeA0C8lP5VzfAr6wWePaZMCpStpLaoXNcAqReKxQllElOqAhRxC5VKH+rnIQ\n" + | |||
"OMRZvB7FRyE9IfwKApngcZbA5g==\n" + | |||
"-----END CERTIFICATE-----"; | |||
byte[] rawPublicKeyBytes = Hex.decode(publicKeyStr); | |||
byte[] rawPrivateKeyBytes = Hex.decode(privateKeyStr); | |||
CSRBuilder builder = new CSRBuilder(); | |||
builder.init("SHA1withRSA", rawPublicKeyBytes, rawPrivateKeyBytes); | |||
String csr = builder.buildRequest(countryName,stateName,cityName, | |||
organizationName,departmentName,domainName, | |||
emailName); | |||
assertEquals(expectedCSR,csr); | |||
CertParser parser = new CertParser(); | |||
parser.parse(expectedUserCert,issuerCert); | |||
PublicKey rawPublicKeyInCert = parser.getPubKey(); | |||
// check that the public key in inputs and the public key in certificate are consistent | |||
assertArrayEquals(rawPublicKeyBytes, rawPublicKeyInCert.getEncoded()); | |||
String algoName = parser.getSigAlgName(); | |||
int keyLength = parser.getKeyLength(); | |||
String length = String.valueOf(keyLength); | |||
String algo = (algoName.contains("RSA")? (algoName + length).toUpperCase(): algoName.toUpperCase()); | |||
CryptoAlgorithm algorithm = Crypto.getAlgorithm(algo); | |||
assertNotNull(algorithm); | |||
SignatureFunction signatureFunction = Crypto.getSignatureFunction(algorithm); | |||
PubKey pubKey = new PubKey(algorithm, rawPublicKeyBytes); | |||
PrivKey privKey = new PrivKey(algorithm, rawPrivateKeyBytes); | |||
// signTest | |||
byte[] data = new byte[1024]; | |||
Random random = new Random(); | |||
random.nextBytes(data); | |||
SignatureDigest signature = signatureFunction.sign(privKey, data); | |||
assertTrue(signatureFunction.verify(signature, pubKey, data)); | |||
} | |||
} |
@@ -1,9 +1,13 @@ | |||
package com.jd.blockchain.crypto.service.pki; | |||
import com.jd.blockchain.crypto.*; | |||
import com.jd.blockchain.crypto.utils.CSRBuilder; | |||
import com.jd.blockchain.crypto.utils.CertParser; | |||
import com.jd.blockchain.utils.io.BytesUtils; | |||
import org.bouncycastle.util.encoders.Hex; | |||
import org.junit.Test; | |||
import java.security.PublicKey; | |||
import java.util.Random; | |||
import static com.jd.blockchain.crypto.CryptoAlgorithm.ASYMMETRIC_KEY; | |||
@@ -270,4 +274,80 @@ public class SM3WITHSM2SignatureFunctionTest { | |||
resolvedSignatureDigest.getAlgorithm()); | |||
assertArrayEquals(signatureDigestBytes, resolvedSignatureDigest.toBytes()); | |||
} | |||
@Test | |||
public void testWithCSRAndCert() { | |||
String publicKeyStr = "3059301306072a8648ce3d020106082a811ccf5501822d03420004aa6586478be879504fdd02892f" + | |||
"b4cf2bdb3d96a316f41ff7bcadfabef4ea836678984f9ba6931a609391426ca7164592f5fccd062be56fc32b3eec3a5" + | |||
"0400971"; | |||
String privateKeyStr = "308193020100301306072a8648ce3d020106082a811ccf5501822d0479307702010104207abd2953" + | |||
"84f193abe4f3715c26bc15225061f007131755d94ca12c7e0ce0b3a0a00a06082a811ccf5501822da14403420004aa6" + | |||
"586478be879504fdd02892fb4cf2bdb3d96a316f41ff7bcadfabef4ea836678984f9ba6931a609391426ca7164592f5" + | |||
"fccd062be56fc32b3eec3a50400971"; | |||
String expectedUserCert = "MIICwjCCAmWgAwIBAgIFIChnMlIwDAYIKoEcz1UBg3UFADBdMQswCQYDVQQGEwJDTjEwMC4GA1UEC" + | |||
"gwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRwwGgYDVQQDDBNDRkNBIFRFU1QgU00yIE9DQTEx" + | |||
"MB4XDTE5MDgyODA4MTUzNloXDTIxMDgyODA4MTUzNloweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoMD0NGQ0EgVEVTVCBPQ0E" + | |||
"xMTERMA8GA1UECwwITG9jYWwgUkExFTATBgNVBAsMDEluZGl2aWR1YWwtMTElMCMGA1UEAwwcMDUxQHpoYW5nbGluIUBaMT" + | |||
"g2MTIyMjkyOTVAODBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABKplhkeL6HlQT90CiS+0zyvbPZajFvQf97yt+r706oNme" + | |||
"JhPm6aTGmCTkUJspxZFkvX8zQYr5W/DKz7sOlBACXGjgfQwgfEwHwYDVR0jBBgwFoAUvqZ+TT18j6BV5sEvCS4sIEOzQn8w" + | |||
"SAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh" + | |||
"0bTA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vMjEwLjc0LjQyLjMvT0NBMTEvU00yL2NybDI0OTkuY3JsMAsGA1UdDwQEAw" + | |||
"ID6DAdBgNVHQ4EFgQU07tMtbs5PWkwN33OQVH116xd1kowHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMAwGCCqBH" + | |||
"M9VAYN1BQADSQAwRgIhAPuuInppVhugw6EIG4wgkouxX/MX2dTLe478wx7LFXSQAiEAneiz51vrurICNCfeecaBHgzaj7+3" + | |||
"kmrdIZJBoxYGbso="; | |||
String issuerCert = | |||
"-----BEGIN CERTIFICATE-----\n" + | |||
"MIICTzCCAfOgAwIBAgIKJFSZ4SRVDndYUTAMBggqgRzPVQGDdQUAMF0xCzAJBgNV\n" + | |||
"BAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBB\n" + | |||
"dXRob3JpdHkxHDAaBgNVBAMME0NGQ0EgVEVTVCBDUyBTTTIgQ0EwHhcNMTIwODI5\n" + | |||
"MDU0ODQ3WhcNMzIwODI0MDU0ODQ3WjBdMQswCQYDVQQGEwJDTjEwMC4GA1UECgwn\n" + | |||
"Q2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRwwGgYDVQQD\n" + | |||
"DBNDRkNBIFRFU1QgU00yIE9DQTExMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE\n" + | |||
"L1mx4wriQUojGsIkNL14kslv9nwiqsiVpELOZauzghrbccNlPYKNYKZOCvXwIIqU\n" + | |||
"9QY02d4weqKqo/JMcNsKEaOBmDCBlTAfBgNVHSMEGDAWgBS12JBvXPDYM9JjvX6y\n" + | |||
"w43GTxJ6YTAMBgNVHRMEBTADAQH/MDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6Ly8y\n" + | |||
"MTAuNzQuNDIuMy90ZXN0cmNhL1NNMi9jcmwxLmNybDALBgNVHQ8EBAMCAQYwHQYD\n" + | |||
"VR0OBBYEFL6mfk09fI+gVebBLwkuLCBDs0J/MAwGCCqBHM9VAYN1BQADSAAwRQIh\n" + | |||
"AKuk7s3eYCZDck5NWU0eNQmLhBN/1zmKs517qFrDrkJWAiAP4cVfLtdza/OkwU9P\n" + | |||
"PrIDl+E4aL3FypntFXHG3T+Keg==\n" + | |||
"-----END CERTIFICATE-----"; | |||
byte[] rawPublicKeyBytes = Hex.decode(publicKeyStr); | |||
byte[] rawPrivateKeyBytes = Hex.decode(privateKeyStr); | |||
CSRBuilder builder = new CSRBuilder(); | |||
builder.init("SM3withSM2", rawPublicKeyBytes, rawPrivateKeyBytes); | |||
CertParser parser = new CertParser(); | |||
parser.parse(expectedUserCert,issuerCert); | |||
PublicKey rawPublicKeyInCert = parser.getPubKey(); | |||
// check that the public key in inputs and the public key in certificate are consistent | |||
assertArrayEquals(rawPublicKeyBytes, rawPublicKeyInCert.getEncoded()); | |||
String algoName = parser.getSigAlgName(); | |||
int keyLength = parser.getKeyLength(); | |||
String length = String.valueOf(keyLength); | |||
String algo = (algoName.contains("RSA")? (algoName + length).toUpperCase(): algoName.toUpperCase()); | |||
CryptoAlgorithm algorithm = Crypto.getAlgorithm(algo); | |||
assertNotNull(algorithm); | |||
SignatureFunction signatureFunction = Crypto.getSignatureFunction(algorithm); | |||
PubKey pubKey = new PubKey(algorithm, rawPublicKeyBytes); | |||
PrivKey privKey = new PrivKey(algorithm, rawPrivateKeyBytes); | |||
// signTest | |||
byte[] data = new byte[1024]; | |||
Random random = new Random(); | |||
random.nextBytes(data); | |||
SignatureDigest signature = signatureFunction.sign(privKey, data); | |||
assertTrue(signatureFunction.verify(signature, pubKey, data)); | |||
} | |||
} |
@@ -43,7 +43,8 @@ public class CertParserTest { | |||
String userCert = "MIIEQDCCAyigAwIBAgIFICdVYzEwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4XDTE5MDUxMDExMjAyNFoXDTIxMDUxMDExMjAyNFowcjELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTERMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTEfMB0GA1UEAxQWMDUxQGFhYWFhQFpIMDkzNTgwMjhAMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJx3F2WD1dJPzK/nRHO7d1TJ1hTjzGTmv0PQ7ECsJAh3U3BtnGTpCB+b4+JMI4LO8nHkKIBQ3P9XnF+Bf1iXdWNAQ4aWCxa2nV7lCp4w0GliPu/EMgIfmsSDUtgqbM3cr8sR8r9m1xG3gt2TIQJ+jT7sAiguU/kyNzpjaccOUIgUFa8IDFq9UeB76MXtCuhlERRZQCl47e+9w7ZoxmE7e6IZORxPp7rQWVBHlR9ntWjJfNDTm3gMP5ehP+yIZnKx1LudxkBLQxpMmspzOyH1zqx5nkKe49AfWWpDxxRvYkriyYC3aE81qLsU/bhLwNEKOju7BGDF/mhJLZUedojM0gMCAwEAAaOB9TCB8jAfBgNVHSMEGDAWgBT8C7xEmg4xoYOpgYcnHgVCxr9W+DBIBgNVHSAEQTA/MD0GCGCBHIbvKgECMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20uY24vdXMvdXMtMTUuaHRtMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly8yMTAuNzQuNDIuMy9PQ0ExMS9SU0EvY3JsMjU2OTMuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQU5oKGaQs7Jt5Gfbt1XhFTWAySEKswHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBBQUAA4IBAQAlmPRaImZV51iKjtpMKuyLMw7dX8L0lY3tl+pVZZSxHuwsN4GCCtV0Ej50up+/6EbfL4NUTiuHVAjCroKKvb+94CrdEwdnQGM5IbGSjT78nQpeASXbIWuUwA+ImjvZOzvq/0b56AzonNzBxOMGko/bj5smM6X8jrgJ0NQppo2KNSVNC4JbuoNWI4FM94SE4DUi9H7EYl4JdOtDaDtCsq49o/A1CZyYrmoOPCgxpQQXmuB3lGq/jyoOlW2aW8uee/hYG1JJcSHLBjF0WBwdxssgbBotA5f1PebiIMSbFgjk57bd4M80hhU/rI4Hkn9pcp5R7NsX95TtyDIg90LboBnW"; | |||
parser.parse(userCert, issuerCert); | |||
assertEquals("SHA1WITHRSA",parser.getSigAlgName()); | |||
assertEquals("SHA1WITHRSA", parser.getSigAlgName()); | |||
assertEquals(2048, parser.getKeyLength()); | |||
} | |||
@Test | |||
@@ -77,7 +78,8 @@ public class CertParserTest { | |||
String userCert = "MIIFRjCCBC6gAwIBAgIFICdWiDMwDQYJKoZIhvcNAQEFBQAwWTELMAkGA1UEBhMCQ04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEYMBYGA1UEAxMPQ0ZDQSBURVNUIE9DQTExMB4XDTE5MDUxNjA3MDcyMloXDTIxMDUxNjA3MDcyMloweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoTD0NGQ0EgVEVTVCBPQ0ExMTERMA8GA1UECxMITG9jYWwgUkExFTATBgNVBAsTDEluZGl2aWR1YWwtMTElMCMGA1UEAxQcMDUxQHpoYW5nbGluIUBaMTg2MTIyMjkyOTVAMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL0rTOxd8rsjPtFJ0aGVh9bZPy5Xo0SADaP7BbJsG4+ykLQMZHg9hTf/6fv1OsD2HEKFoMpIkW2gwCJW2nvicHcVql/shCoktc6ZBW6Dr/DxOgbO9tpoGxZ50xdI4Q0NsrxqtbCldW4ozPHdjgRJ83i1KSFh7evNrVByN/mB+jchrVGLWyJ1uIRgUUgpRZmZPoOHaizVJqrqWJGGk6xbDLR2gUQ1hTzetQaz1OeKtelHDk9FY08XSmNGssSMpuSjrxn78S888VW5WIxyo4cwrXSXFk3J7LNTy70Oga16HZjJD/vLTM6a4riPa8+uivPinKxK38/++nlBPNwhx6n46uYkd9Zvw+SJiJgpnuPJLtMZpKpJx7V1BDVEydKPUthilTdsmJtkBFSlFw0G1aKfuciBGzzJ3SKngJF/JqJAWIonVAFBGb6Gokp1Sw+T4KqXrdbjxYxiyyjZ++8O1vydgFAkx/NjsuwJnpKETiRKFJmY7YawcUvC4ixF7XQc0luFWRDYcbxOppir+ieMqhGXyaFhLUuB4WXv+rFxfa3NmkBW8q5TPzt/PwWcXpITsYTZYla/E/grB+OeZLYgjigT5YlgytPHG6Gt1ySCCd8WXFWpkBbQfXzqcvtU27RCcAUgfXk5NLb7NZCQg7heGjgzOdYJCPsa1d3m7l04+VIKGCZdAgMBAAGjgfUwgfIwHwYDVR0jBBgwFoAU/Au8RJoOMaGDqYGHJx4FQsa/VvgwSAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh0bTA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vMjEwLjc0LjQyLjMvT0NBMTEvUlNBL2NybDI1NzE3LmNybDALBgNVHQ8EBAMCA+gwHQYDVR0OBBYEFMjh6AzDCuNkD+pqQfiS6bqPGpI4MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDANBgkqhkiG9w0BAQUFAAOCAQEApZaLXS6/6FudPA3l2xom5U7nJUOpQ1E6DO/ic9dFGtLE0WgyAqB3JVPvXEntppX55x/dAV7rvvz9NaEdiAe8DAj7qyoPDvC8ZWQK8U4n9l8N78QwALOURxzQNs+CBatJQzbu2w1RKVwkfE6xEIVnu+wjiAtymfwdLHMagHxDIC/eOPbTnbbtITJk2ukFfoc0WJ6Awg5lW+x7nGokEn/XAjKyRHCpkRUFGQm4ww41zlrqCqQqnVGVABJtjbdtFf7nh33QHl0fkj19nfMox9eGuntPyM0bNA0XqPMA+FWSCqeDT6uLbyaOKWxlhv53U/NCJl76U3tssMEWsm9amEDDQg=="; | |||
parser.parse(userCert, issuerCert); | |||
assertEquals("SHA1WITHRSA",parser.getSigAlgName()); | |||
assertEquals("SHA1WITHRSA", parser.getSigAlgName()); | |||
assertEquals(4096, parser.getKeyLength()); | |||
} | |||
@Test | |||
@@ -103,7 +105,8 @@ public class CertParserTest { | |||
String userCert = "MIICwDCCAmWgAwIBAgIFICdWkWgwDAYIKoEcz1UBg3UFADBdMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRwwGgYDVQQDDBNDRkNBIFRFU1QgU00yIE9DQTExMB4XDTE5MDUxNjA4MTA1MVoXDTIxMDUxNjA4MTA1MVoweDELMAkGA1UEBhMCQ04xGDAWBgNVBAoMD0NGQ0EgVEVTVCBPQ0ExMTERMA8GA1UECwwITG9jYWwgUkExFTATBgNVBAsMDEluZGl2aWR1YWwtMTElMCMGA1UEAwwcMDUxQHpoYW5nbGluIUBaMTg2MTIyMjkyOTVAMzBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABPvNXpdZ4/4g+wx5qKS94CPkMqpEDhlnXYYW7ZzsbNI4d28sVBz5Ji6dTT1Zx627Kvw4tdUaUt7BVMvZsu3BFlyjgfQwgfEwHwYDVR0jBBgwFoAUvqZ+TT18j6BV5sEvCS4sIEOzQn8wSAYDVR0gBEEwPzA9BghggRyG7yoBAjAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3VzLTE1Lmh0bTA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vMjEwLjc0LjQyLjMvT0NBMTEvU00yL2NybDIxMDkuY3JsMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQUxR5C/VjASus5zrAAFS4ulMpRjKgwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMAwGCCqBHM9VAYN1BQADRwAwRAIgVBzVi/fgkknr+2BH2wXeGMXC+Pa6p7rbldUsYMOYoyUCIAmQ4KEk2U1xJZSBpOPy5jN9kmRb+0YH6x04O/2tqCgq"; | |||
parser.parse(userCert, issuerCert); | |||
assertEquals("SM3WITHSM2",parser.getSigAlgName()); | |||
assertEquals("SM3WITHSM2", parser.getSigAlgName()); | |||
assertEquals(256, parser.getKeyLength()); | |||
} | |||
@Test | |||
@@ -141,6 +144,7 @@ public class CertParserTest { | |||
"PrIDl+E4aL3FypntFXHG3T+Keg==\n" + | |||
"-----END CERTIFICATE-----"; | |||
parser.parse(issuerCert, CACert); | |||
assertEquals("SM3WITHSM2",parser.getSigAlgName()); | |||
assertEquals("SM3WITHSM2", parser.getSigAlgName()); | |||
assertEquals(256, parser.getKeyLength()); | |||
} | |||
} |
@@ -13,6 +13,8 @@ import org.bouncycastle.crypto.engines.SM2Engine; | |||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator; | |||
import org.bouncycastle.crypto.params.*; | |||
import org.bouncycastle.crypto.signers.SM2Signer; | |||
import org.bouncycastle.jce.ECNamedCurveTable; | |||
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; | |||
import org.bouncycastle.math.ec.*; | |||
import java.io.IOException; | |||
@@ -29,19 +31,10 @@ public class SM2Utils { | |||
// The length of sm3 output is 32 bytes | |||
private static final int SM3DIGEST_LENGTH = 32; | |||
private static final BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); | |||
private static final BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); | |||
private static final BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); | |||
private static final BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); | |||
private static final BigInteger SM2_H = ECConstants.ONE; | |||
private static final BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); | |||
private static final BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); | |||
// To get the curve from the equation y^2=x^3+ax+b according the coefficient a and b, | |||
// with the big prime p, the order n, the cofactor h, and obtain the generator g and the domain's parameters | |||
private static final ECCurve CURVE = new ECCurve.Fp(SM2_P, SM2_A, SM2_B, SM2_N, SM2_H); | |||
private static final ECPoint G = CURVE.createPoint(SM2_GX, SM2_GY); | |||
private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G, SM2_N); | |||
private static final ECNamedCurveParameterSpec PARAMS = ECNamedCurveTable.getParameterSpec("sm2p256v1"); | |||
private static final ECCurve CURVE = PARAMS.getCurve(); | |||
private static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters( | |||
CURVE, PARAMS.getG(), PARAMS.getN(), PARAMS.getH()); | |||
//-----------------Key Generation Algorithm----------------- | |||
@@ -1,12 +1,11 @@ | |||
package com.jd.blockchain.ledger.core; | |||
import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
import com.jd.blockchain.binaryproto.PrimitiveType; | |||
import com.jd.blockchain.crypto.HashDigest; | |||
import com.jd.blockchain.crypto.PubKey; | |||
import com.jd.blockchain.ledger.AccountHeader; | |||
import com.jd.blockchain.ledger.BytesValue; | |||
import com.jd.blockchain.ledger.BytesData; | |||
import com.jd.blockchain.ledger.BytesValue; | |||
import com.jd.blockchain.ledger.KVDataEntry; | |||
import com.jd.blockchain.ledger.KVDataObject; | |||
import com.jd.blockchain.utils.Bytes; | |||
@@ -43,16 +42,85 @@ public class DataAccount implements AccountHeader, MerkleProvable { | |||
} | |||
/** | |||
* Create or update the value associated the specified key if the version | |||
* checking is passed.<br> | |||
* | |||
* The value of the key will be updated only if it's latest version equals the | |||
* specified version argument. <br> | |||
* If the key doesn't exist, the version checking will be ignored, and key will | |||
* be created with a new sequence number as id. <br> | |||
* It also could specify the version argument to -1 to ignore the version | |||
* checking. | |||
* <p> | |||
* If updating is performed, the version of the key increase by 1. <br> | |||
* If creating is performed, the version of the key initialize by 0. <br> | |||
* | |||
* @param key The key of data; | |||
* @param value The value of data; | |||
* @param version The expected version of the key. | |||
* @return The new version of the key. <br> | |||
* If the key is new created success, then return 0; <br> | |||
* If the key is updated success, then return the new version;<br> | |||
* If this operation fail by version checking or other reason, then | |||
* return -1; | |||
*/ | |||
public long setBytes(Bytes key, BytesValue value, long version) { | |||
return baseAccount.setBytes(key, value, version); | |||
} | |||
/** | |||
* Create or update the value associated the specified key if the version | |||
* checking is passed.<br> | |||
* | |||
* The value of the key will be updated only if it's latest version equals the | |||
* specified version argument. <br> | |||
* If the key doesn't exist, the version checking will be ignored, and key will | |||
* be created with a new sequence number as id. <br> | |||
* It also could specify the version argument to -1 to ignore the version | |||
* checking. | |||
* <p> | |||
* If updating is performed, the version of the key increase by 1. <br> | |||
* If creating is performed, the version of the key initialize by 0. <br> | |||
* | |||
* @param key The key of data; | |||
* @param value The value of data; | |||
* @param version The expected version of the key. | |||
* @return The new version of the key. <br> | |||
* If the key is new created success, then return 0; <br> | |||
* If the key is updated success, then return the new version;<br> | |||
* If this operation fail by version checking or other reason, then | |||
* return -1; | |||
*/ | |||
public long setBytes(Bytes key, String value, long version) { | |||
BytesValue bytesValue = BytesData.fromText(value); | |||
return baseAccount.setBytes(key, bytesValue, version); | |||
} | |||
/** | |||
* Create or update the value associated the specified key if the version | |||
* checking is passed.<br> | |||
* | |||
* The value of the key will be updated only if it's latest version equals the | |||
* specified version argument. <br> | |||
* If the key doesn't exist, the version checking will be ignored, and key will | |||
* be created with a new sequence number as id. <br> | |||
* It also could specify the version argument to -1 to ignore the version | |||
* checking. | |||
* <p> | |||
* If updating is performed, the version of the key increase by 1. <br> | |||
* If creating is performed, the version of the key initialize by 0. <br> | |||
* | |||
* @param key The key of data; | |||
* @param value The value of data; | |||
* @param version The expected version of the key. | |||
* @return The new version of the key. <br> | |||
* If the key is new created success, then return 0; <br> | |||
* If the key is updated success, then return the new version;<br> | |||
* If this operation fail by version checking or other reason, then | |||
* return -1; | |||
*/ | |||
public long setBytes(Bytes key, byte[] value, long version) { | |||
BytesValue bytesValue = BytesData.fromBytes(value); | |||
return baseAccount.setBytes(key, bytesValue, version); | |||
@@ -121,6 +189,29 @@ public class DataAccount implements AccountHeader, MerkleProvable { | |||
public BytesValue getBytes(Bytes key, long version) { | |||
return baseAccount.getBytes(key, version); | |||
} | |||
/** | |||
* @param key | |||
* @param version | |||
* @return | |||
*/ | |||
public KVDataEntry getDataEntry(String key, long version) { | |||
return getDataEntry(Bytes.fromString(key), version); | |||
} | |||
/** | |||
* @param key | |||
* @param version | |||
* @return | |||
*/ | |||
public KVDataEntry getDataEntry(Bytes key, long version) { | |||
BytesValue value = baseAccount.getBytes(key, version); | |||
if (value == null) { | |||
return new KVDataObject(key.toUTF8String(), -1, null); | |||
}else { | |||
return new KVDataObject(key.toUTF8String(), version, value); | |||
} | |||
} | |||
/** | |||
* return the specified index's KVDataEntry; | |||
@@ -131,7 +222,7 @@ public class DataAccount implements AccountHeader, MerkleProvable { | |||
*/ | |||
public KVDataEntry[] getDataEntries(int fromIndex, int count) { | |||
if (getDataEntriesTotalCount() == 0 || count == 0) { | |||
if (count == 0 || getDataEntriesTotalCount() == 0) { | |||
return null; | |||
} | |||
@@ -168,7 +168,7 @@ public class MerkleDataSet implements Transactional, MerkleProvable { | |||
*/ | |||
public String getKeyAtIndex(int fromIndex) { | |||
MerkleDataNode dataNode = merkleTree.getData(fromIndex); | |||
return new String(dataNode.getKey().toBytes()); | |||
return dataNode.getKey().toUTF8String(); | |||
} | |||
@@ -20,6 +20,8 @@ import com.jd.blockchain.utils.QueryUtil; | |||
public class LedgerQueryService implements BlockchainQueryService { | |||
private static final KVDataEntry[] EMPTY_ENTRIES = new KVDataEntry[0]; | |||
private LedgerService ledgerService; | |||
public LedgerQueryService(LedgerService ledgerService) { | |||
@@ -254,7 +256,7 @@ public class LedgerQueryService implements BlockchainQueryService { | |||
@Override | |||
public KVDataEntry[] getDataEntries(HashDigest ledgerHash, String address, String... keys) { | |||
if (keys == null || keys.length == 0) { | |||
return null; | |||
return EMPTY_ENTRIES; | |||
} | |||
LedgerRepository ledger = ledgerService.getLedger(ledgerHash); | |||
LedgerBlock block = ledger.getLatestBlock(); | |||
@@ -266,7 +268,7 @@ public class LedgerQueryService implements BlockchainQueryService { | |||
for (int i = 0; i < entries.length; i++) { | |||
final String currKey = keys[i]; | |||
ver = dataAccount.getDataVersion(Bytes.fromString(currKey)); | |||
ver = dataAccount == null ? -1 : dataAccount.getDataVersion(Bytes.fromString(currKey)); | |||
if (ver < 0) { | |||
entries[i] = new KVDataObject(currKey, -1, null); | |||
@@ -7,6 +7,7 @@ import com.jd.blockchain.ledger.BytesValue; | |||
import com.jd.blockchain.ledger.DataAccountDoesNotExistException; | |||
import com.jd.blockchain.ledger.DataAccountKVSetOperation; | |||
import com.jd.blockchain.ledger.DataAccountKVSetOperation.KVWriteEntry; | |||
import com.jd.blockchain.ledger.DataVersionConflictException; | |||
import com.jd.blockchain.ledger.Operation; | |||
import com.jd.blockchain.ledger.core.DataAccount; | |||
import com.jd.blockchain.ledger.core.LedgerDataSet; | |||
@@ -31,8 +32,12 @@ public class DataAccountKVSetOperationHandle implements OperationHandle { | |||
throw new DataAccountDoesNotExistException("DataAccount doesn't exist!"); | |||
} | |||
KVWriteEntry[] writeSet = kvWriteOp.getWriteSet(); | |||
long v = -1; | |||
for (KVWriteEntry kvw : writeSet) { | |||
account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); | |||
v = account.setBytes(Bytes.fromString(kvw.getKey()), kvw.getValue(), kvw.getExpectedVersion()); | |||
if (v < 0) { | |||
throw new DataVersionConflictException(); | |||
} | |||
} | |||
return null; | |||
} | |||
@@ -3,10 +3,8 @@ package test.com.jd.blockchain.ledger; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import com.jd.blockchain.contract.ContractType; | |||
import com.jd.blockchain.contract.engine.ContractCode; | |||
import com.jd.blockchain.contract.jvm.AbstractContractCode; | |||
import com.jd.blockchain.contract.jvm.ContractDefinition; | |||
import com.jd.blockchain.contract.jvm.InstantiatedContractCode; | |||
import com.jd.blockchain.ledger.core.ContractAccount; | |||
import com.jd.blockchain.ledger.core.impl.handles.AbtractContractEventHandle; | |||
import com.jd.blockchain.utils.Bytes; | |||
@@ -21,30 +19,10 @@ public class ContractInvokingHandle extends AbtractContractEventHandle { | |||
} | |||
public <T> ContractCode setup(Bytes address, Class<T> contractIntf, T instance) { | |||
ContractCodeInstance<T> contract = new ContractCodeInstance<T>(address, 0, contractIntf, instance); | |||
InstantiatedContractCode<T> contract = new InstantiatedContractCode<T>(address, 0, contractIntf, instance); | |||
contractInstances.put(address, contract); | |||
return contract; | |||
} | |||
private static class ContractCodeInstance<T> extends AbstractContractCode { | |||
private T instance; | |||
public ContractCodeInstance(Bytes address, long version, Class<T> delaredInterface, T instance) { | |||
super(address, version, resolveContractDefinition(delaredInterface, instance.getClass())); | |||
this.instance = instance; | |||
} | |||
private static ContractDefinition resolveContractDefinition(Class<?> declaredIntf, Class<?> implementedClass) { | |||
ContractType contractType = ContractType.resolve(declaredIntf); | |||
return new ContractDefinition(contractType, implementedClass); | |||
} | |||
@Override | |||
protected T getContractInstance() { | |||
return instance; | |||
} | |||
} | |||
} |
@@ -1,27 +1,42 @@ | |||
package test.com.jd.blockchain.ledger; | |||
import static com.jd.blockchain.transaction.ContractReturnValue.decode; | |||
import static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertNull; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.mockito.Matchers.anyLong; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
import java.util.Random; | |||
import com.jd.blockchain.ledger.*; | |||
import org.junit.Test; | |||
import org.mockito.Mockito; | |||
import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
import com.jd.blockchain.binaryproto.DataContractRegistry; | |||
import com.jd.blockchain.crypto.HashDigest; | |||
import com.jd.blockchain.ledger.*; | |||
import com.jd.blockchain.ledger.core.*; | |||
import com.jd.blockchain.ledger.core.LedgerDataSet; | |||
import com.jd.blockchain.ledger.core.LedgerEditor; | |||
import com.jd.blockchain.ledger.core.LedgerRepository; | |||
import com.jd.blockchain.ledger.core.LedgerService; | |||
import com.jd.blockchain.ledger.core.LedgerTransactionContext; | |||
import com.jd.blockchain.ledger.core.UserAccount; | |||
import com.jd.blockchain.ledger.core.impl.DefaultOperationHandleRegisteration; | |||
import com.jd.blockchain.ledger.core.impl.LedgerManager; | |||
import com.jd.blockchain.ledger.core.impl.LedgerTransactionalEditor; | |||
import com.jd.blockchain.ledger.core.impl.OperationHandleRegisteration; | |||
import com.jd.blockchain.ledger.core.impl.TransactionBatchProcessor; | |||
import com.jd.blockchain.service.TransactionBatchResultHandle; | |||
import com.jd.blockchain.storage.service.utils.MemoryKVStorage; | |||
import com.jd.blockchain.transaction.BooleanValueHolder; | |||
import com.jd.blockchain.transaction.TxBuilder; | |||
import com.jd.blockchain.utils.Bytes; | |||
import org.junit.Test; | |||
import org.mockito.Mockito; | |||
import java.util.Random; | |||
import static org.junit.Assert.*; | |||
import static org.mockito.Matchers.anyLong; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Mockito.*; | |||
public class ContractInvokingTest { | |||
static { | |||
@@ -32,8 +47,10 @@ public class ContractInvokingTest { | |||
DataContractRegistry.register(EndpointRequest.class); | |||
DataContractRegistry.register(TransactionResponse.class); | |||
DataContractRegistry.register(UserRegisterOperation.class); | |||
DataContractRegistry.register(DataAccountRegisterOperation.class); | |||
DataContractRegistry.register(ParticipantRegisterOperation.class); | |||
DataContractRegistry.register(ParticipantStateUpdateOperation.class); | |||
} | |||
private static final String LEDGER_KEY_PREFIX = "LDG://"; | |||
@@ -47,7 +64,7 @@ public class ContractInvokingTest { | |||
private MemoryKVStorage storage = new MemoryKVStorage(); | |||
@Test | |||
public void test() { | |||
public void testNormal() { | |||
// 初始化账本到指定的存储库; | |||
HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); | |||
@@ -71,7 +88,6 @@ public class ContractInvokingTest { | |||
// 发布指定地址合约 | |||
deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); | |||
// 创建新区块的交易处理器; | |||
LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
@@ -124,9 +140,243 @@ public class ContractInvokingTest { | |||
} | |||
// @Test | |||
public void testReadNewWritting() { | |||
// 初始化账本到指定的存储库; | |||
HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); | |||
// 重新加载账本; | |||
LedgerManager ledgerManager = new LedgerManager(); | |||
LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); | |||
// 创建合约处理器; | |||
ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); | |||
// 创建和加载合约实例; | |||
BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); | |||
Bytes contractAddress = contractKey.getAddress(); | |||
TxTestContractImpl contractInstance = new TxTestContractImpl(); | |||
contractInvokingHandle.setup(contractAddress, TxTestContract.class, contractInstance); | |||
// 注册合约处理器; | |||
DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); | |||
opReg.insertAsTopPriority(contractInvokingHandle); | |||
// 发布指定地址合约 | |||
deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); | |||
// 创建新区块的交易处理器; | |||
LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
// 加载合约 | |||
LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||
TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||
opReg, ledgerManager); | |||
String key = TxTestContractImpl.KEY; | |||
String value = "VAL"; | |||
TxBuilder txBuilder = new TxBuilder(ledgerHash); | |||
BlockchainKeypair kpDataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
contractInstance.setDataAddress(kpDataAccount.getAddress()); | |||
txBuilder.dataAccounts().register(kpDataAccount.getIdentity()); | |||
TransactionRequestBuilder txReqBuilder1 = txBuilder.prepareRequest(); | |||
txReqBuilder1.signAsEndpoint(parti0); | |||
txReqBuilder1.signAsNode(parti0); | |||
TransactionRequest txReq1 = txReqBuilder1.buildRequest(); | |||
// 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
txBuilder = new TxBuilder(ledgerHash); | |||
TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
BooleanValueHolder readableHolder = decode(contractProxy.testReadable()); | |||
TransactionRequestBuilder txReqBuilder2 = txBuilder.prepareRequest(); | |||
txReqBuilder2.signAsEndpoint(parti0); | |||
txReqBuilder2.signAsNode(parti0); | |||
TransactionRequest txReq2 = txReqBuilder2.buildRequest(); | |||
TransactionResponse resp1 = txbatchProcessor.schedule(txReq1); | |||
TransactionResponse resp2 = txbatchProcessor.schedule(txReq2); | |||
// 提交区块; | |||
TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); | |||
txResultHandle.commit(); | |||
BytesValue latestValue = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getBytes(key, | |||
-1); | |||
System.out.printf("latest value=[%s] %s \r\n", latestValue.getType(), latestValue.getValue().toUTF8String()); | |||
boolean readable = readableHolder.get(); | |||
assertTrue(readable); | |||
LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); | |||
assertEquals(preBlock.getHeight() + 1, latestBlock.getHeight()); | |||
assertEquals(resp1.getBlockHeight(), latestBlock.getHeight()); | |||
assertEquals(resp1.getBlockHash(), latestBlock.getHash()); | |||
} | |||
/** | |||
* 验证在合约方法中写入数据账户时,如果版本校验失败是否会引发异常而导致回滚;<br> | |||
* 期待正确的表现是引发异常而回滚当前交易; | |||
*/ | |||
@Test | |||
public void testRollbackWhileVersionConfliction() { | |||
// 初始化账本到指定的存储库; | |||
HashDigest ledgerHash = initLedger(storage, parti0, parti1, parti2, parti3); | |||
// 重新加载账本; | |||
LedgerManager ledgerManager = new LedgerManager(); | |||
LedgerRepository ledgerRepo = ledgerManager.register(ledgerHash, storage); | |||
// 创建合约处理器; | |||
ContractInvokingHandle contractInvokingHandle = new ContractInvokingHandle(); | |||
// 创建和加载合约实例; | |||
BlockchainKeypair contractKey = BlockchainKeyGenerator.getInstance().generate(); | |||
Bytes contractAddress = contractKey.getAddress(); | |||
TxTestContractImpl contractInstance = new TxTestContractImpl(); | |||
contractInvokingHandle.setup(contractAddress, TxTestContract.class, contractInstance); | |||
// 注册合约处理器; | |||
DefaultOperationHandleRegisteration opReg = new DefaultOperationHandleRegisteration(); | |||
opReg.insertAsTopPriority(contractInvokingHandle); | |||
// 发布指定地址合约 | |||
deploy(ledgerRepo, ledgerManager, opReg, ledgerHash, contractKey); | |||
// 注册数据账户; | |||
BlockchainKeypair kpDataAccount = BlockchainKeyGenerator.getInstance().generate(); | |||
contractInstance.setDataAddress(kpDataAccount.getAddress()); | |||
registerDataAccount(ledgerRepo, ledgerManager, opReg, ledgerHash, kpDataAccount); | |||
// 调用合约 | |||
// 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { | |||
@Override | |||
public void buildTx(TxBuilder txBuilder) { | |||
TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-0", | |||
-1); | |||
contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-0", | |||
-1); | |||
} | |||
}); | |||
// 预期数据都能够正常写入; | |||
KVDataEntry kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", | |||
0); | |||
KVDataEntry kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", | |||
0); | |||
assertEquals(0, kv1.getVersion()); | |||
assertEquals(0, kv2.getVersion()); | |||
assertEquals("V1-0", kv1.getValue()); | |||
assertEquals("V2-0", kv2.getValue()); | |||
// 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { | |||
@Override | |||
public void buildTx(TxBuilder txBuilder) { | |||
TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-1", | |||
0); | |||
contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-1", | |||
0); | |||
} | |||
}); | |||
// 预期数据都能够正常写入; | |||
kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); | |||
kv2 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K2", 1); | |||
assertEquals(1, kv1.getVersion()); | |||
assertEquals(1, kv2.getVersion()); | |||
assertEquals("V1-1", kv1.getValue()); | |||
assertEquals("V2-1", kv2.getValue()); | |||
// 构建基于接口调用合约的交易请求,用于测试合约调用; | |||
buildBlock(ledgerRepo, ledgerManager, opReg, new TxDefinitor() { | |||
@Override | |||
public void buildTx(TxBuilder txBuilder) { | |||
TxTestContract contractProxy = txBuilder.contract(contractAddress, TxTestContract.class); | |||
contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K1", "V1-2", | |||
1); | |||
contractProxy.testRollbackWhileVersionConfliction(kpDataAccount.getAddress().toBase58(), "K2", "V2-2", | |||
0); | |||
} | |||
}); | |||
// 预期数据都能够正常写入; | |||
kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 1); | |||
assertEquals(1, kv1.getVersion()); | |||
assertEquals("V1-1", kv1.getValue()); | |||
kv1 = ledgerRepo.getDataAccountSet().getDataAccount(kpDataAccount.getAddress()).getDataEntry("K1", 2); | |||
assertEquals(-1, kv1.getVersion()); | |||
assertEquals(null, kv1.getValue()); | |||
} | |||
private LedgerBlock buildBlock(LedgerRepository ledgerRepo, LedgerService ledgerService, | |||
OperationHandleRegisteration opReg, TxDefinitor txDefinitor) { | |||
LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||
TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||
opReg, ledgerService); | |||
TxBuilder txBuilder = new TxBuilder(ledgerRepo.getHash()); | |||
txDefinitor.buildTx(txBuilder); | |||
TransactionRequest txReq = buildAndSignRequest(txBuilder, parti0, parti0); | |||
TransactionResponse resp = txbatchProcessor.schedule(txReq); | |||
// 提交区块; | |||
TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); | |||
txResultHandle.commit(); | |||
LedgerBlock latestBlock = ledgerRepo.getLatestBlock(); | |||
assertNotNull(resp.getBlockHash()); | |||
assertEquals(preBlock.getHeight() + 1, resp.getBlockHeight()); | |||
return latestBlock; | |||
} | |||
private TransactionRequest buildAndSignRequest(TxBuilder txBuilder, BlockchainKeypair endpointKey, | |||
BlockchainKeypair nodeKey) { | |||
TransactionRequestBuilder txReqBuilder = txBuilder.prepareRequest(); | |||
txReqBuilder.signAsEndpoint(endpointKey); | |||
txReqBuilder.signAsNode(nodeKey); | |||
TransactionRequest txReq = txReqBuilder.buildRequest(); | |||
return txReq; | |||
} | |||
private void registerDataAccount(LedgerRepository ledgerRepo, LedgerManager ledgerManager, | |||
DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair kpDataAccount) { | |||
LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
// 加载合约 | |||
LedgerEditor newBlockEditor = ledgerRepo.createNextBlock(); | |||
TransactionBatchProcessor txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, | |||
opReg, ledgerManager); | |||
// 注册数据账户; | |||
TxBuilder txBuilder = new TxBuilder(ledgerHash); | |||
txBuilder.dataAccounts().register(kpDataAccount.getIdentity()); | |||
TransactionRequestBuilder txReqBuilder1 = txBuilder.prepareRequest(); | |||
txReqBuilder1.signAsEndpoint(parti0); | |||
txReqBuilder1.signAsNode(parti0); | |||
TransactionRequest txReq = txReqBuilder1.buildRequest(); | |||
TransactionResponse resp = txbatchProcessor.schedule(txReq); | |||
TransactionBatchResultHandle txResultHandle = txbatchProcessor.prepare(); | |||
txResultHandle.commit(); | |||
assertNotNull(resp.getBlockHash()); | |||
assertEquals(TransactionState.SUCCESS, resp.getExecutionState()); | |||
assertEquals(preBlock.getHeight() + 1, resp.getBlockHeight()); | |||
} | |||
private void deploy(LedgerRepository ledgerRepo, LedgerManager ledgerManager, | |||
DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, | |||
BlockchainKeypair contractKey) { | |||
DefaultOperationHandleRegisteration opReg, HashDigest ledgerHash, BlockchainKeypair contractKey) { | |||
// 创建新区块的交易处理器; | |||
LedgerBlock preBlock = ledgerRepo.getLatestBlock(); | |||
LedgerDataSet previousBlockDataset = ledgerRepo.getDataSet(preBlock); | |||
@@ -195,4 +445,10 @@ public class ContractInvokingTest { | |||
new Random().nextBytes(chainCode); | |||
return chainCode; | |||
} | |||
public static interface TxDefinitor { | |||
void buildTx(TxBuilder txBuilder); | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
package test.com.jd.blockchain.ledger; | |||
import com.jd.blockchain.binaryproto.DataContract; | |||
import com.jd.blockchain.binaryproto.DataField; | |||
import com.jd.blockchain.binaryproto.PrimitiveType; | |||
@DataContract(code = 0x4010) | |||
public interface KeyValueEntry { | |||
@DataField(order = 1, primitiveType = PrimitiveType.TEXT) | |||
String getKey(); | |||
@DataField(order = 2, primitiveType = PrimitiveType.TEXT) | |||
String getValue(); | |||
@DataField(order = 3, primitiveType = PrimitiveType.INT64) | |||
long getVersion(); | |||
} |
@@ -0,0 +1,47 @@ | |||
package test.com.jd.blockchain.ledger; | |||
public class KeyValueObject implements KeyValueEntry { | |||
private String key; | |||
private String value; | |||
private long version; | |||
public KeyValueObject() { | |||
} | |||
public KeyValueObject(String key, String value, long version) { | |||
this.key = key; | |||
this.value = value; | |||
this.version = version; | |||
} | |||
@Override | |||
public String getKey() { | |||
return key; | |||
} | |||
@Override | |||
public String getValue() { | |||
return value; | |||
} | |||
@Override | |||
public long getVersion() { | |||
return version; | |||
} | |||
public void setKey(String key) { | |||
this.key = key; | |||
} | |||
public void setValue(String value) { | |||
this.value = value; | |||
} | |||
public void setVersion(long version) { | |||
this.version = version; | |||
} | |||
} |
@@ -32,6 +32,38 @@ public class MerkleDataSetTest { | |||
private static final String[] SUPPORTED_PROVIDERS = { ClassicCryptoService.class.getName(), | |||
SMCryptoService.class.getName() }; | |||
/** | |||
* 测试存储的增长; | |||
*/ | |||
@Test | |||
public void testKeyIndex() { | |||
CryptoProvider[] supportedProviders = new CryptoProvider[SUPPORTED_PROVIDERS.length]; | |||
for (int i = 0; i < SUPPORTED_PROVIDERS.length; i++) { | |||
supportedProviders[i] = Crypto.getProvider(SUPPORTED_PROVIDERS[i]); | |||
} | |||
String keyPrefix = ""; | |||
CryptoConfig cryptoConfig = new CryptoConfig(); | |||
cryptoConfig.setSupportedProviders(supportedProviders); | |||
cryptoConfig.setHashAlgorithm(ClassicAlgorithm.SHA256); | |||
cryptoConfig.setAutoVerifyHash(true); | |||
MemoryKVStorage storage = new MemoryKVStorage(); | |||
MerkleDataSet mds = new MerkleDataSet(cryptoConfig, keyPrefix, storage, storage); | |||
mds.setValue("A", "A".getBytes(), -1); | |||
mds.setValue("B", "B".getBytes(), -1); | |||
mds.setValue("C", "C".getBytes(), -1); | |||
mds.commit(); | |||
//校验 Key 的正确性; | |||
assertEquals("A", mds.getKeyAtIndex(0)); | |||
assertEquals("B", mds.getKeyAtIndex(1)); | |||
assertEquals("C", mds.getKeyAtIndex(2)); | |||
} | |||
/** | |||
* 测试存储的增长; | |||
@@ -59,6 +91,7 @@ public class MerkleDataSetTest { | |||
mds.commit(); | |||
HashDigest root1 = mds.getRootHash(); | |||
// 1个KV项的存储KEY的数量= 1 + 1(保存SN) + Merkle节点数量; | |||
// 所以:3 项; | |||
@@ -14,6 +14,7 @@ import com.jd.blockchain.ledger.BlockchainKeyGenerator; | |||
import com.jd.blockchain.ledger.BlockchainKeypair; | |||
import com.jd.blockchain.ledger.BytesValue; | |||
import com.jd.blockchain.ledger.DataAccountRegisterOperation; | |||
import com.jd.blockchain.ledger.DataVersionConflictException; | |||
import com.jd.blockchain.ledger.EndpointRequest; | |||
import com.jd.blockchain.ledger.LedgerBlock; | |||
import com.jd.blockchain.ledger.LedgerInitSetting; | |||
@@ -245,7 +246,7 @@ public class TransactionBatchProcessorTest { | |||
} | |||
@Test | |||
public void testTxRollbackByVersionsConfliction() { | |||
public void testTxRollbackByVersionsConflict() { | |||
final MemoryKVStorage STORAGE = new MemoryKVStorage(); | |||
// 初始化账本到指定的存储库; | |||
@@ -288,6 +289,8 @@ public class TransactionBatchProcessorTest { | |||
"K2", "V-2-1", -1, ledgerHash, parti0, parti0); | |||
TransactionRequest txreq3 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||
"K3", "V-3-1", -1, ledgerHash, parti0, parti0); | |||
// 连续写 K1,K1的版本将变为1; | |||
TransactionRequest txreq4 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||
"K1", "V-1-2", 0, ledgerHash, parti0, parti0); | |||
@@ -316,14 +319,14 @@ public class TransactionBatchProcessorTest { | |||
assertNotNull(v1_1); | |||
assertNotNull(v2); | |||
assertNotNull(v3); | |||
assertEquals("V-1-1", v1_0.getValue().toUTF8String()); | |||
assertEquals("V-1-2", v1_1.getValue().toUTF8String()); | |||
assertEquals("V-2-1", v2.getValue().toUTF8String()); | |||
assertEquals("V-3-1", v3.getValue().toUTF8String()); | |||
// 提交多笔数据写入的交易,包含存在数据版本冲突的交易,验证交易是否正确回滚; | |||
// 先写一笔正确的交易; k3 的版本将变为 1 ; | |||
TransactionRequest txreq5 = LedgerTestUtils.createTxRequest_DataAccountWrite(dataAccountKeypair.getAddress(), | |||
"K3", "V-3-2", 0, ledgerHash, parti0, parti0); | |||
// 指定冲突的版本号,正确的应该是版本1; | |||
@@ -335,7 +338,14 @@ public class TransactionBatchProcessorTest { | |||
txbatchProcessor = new TransactionBatchProcessor(newBlockEditor, previousBlockDataset, opReg, ledgerManager); | |||
txbatchProcessor.schedule(txreq5); | |||
txbatchProcessor.schedule(txreq6); | |||
// 预期会产生版本冲突异常; DataVersionConflictionException; | |||
DataVersionConflictException versionConflictionException = null; | |||
try { | |||
txbatchProcessor.schedule(txreq6); | |||
} catch (DataVersionConflictException e) { | |||
versionConflictionException = e; | |||
} | |||
assertNotNull(versionConflictionException); | |||
newBlock = newBlockEditor.prepare(); | |||
newBlockEditor.commit(); | |||
@@ -343,11 +353,15 @@ public class TransactionBatchProcessorTest { | |||
BytesValue v1 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K1"); | |||
v3 = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getBytes("K3"); | |||
long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K1"); | |||
// k1 的版本仍然为1,没有更新; | |||
long k1_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) | |||
.getDataVersion("K1"); | |||
assertEquals(1, k1_version); | |||
long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()).getDataVersion("K3"); | |||
long k3_version = ledgerRepo.getDataAccountSet().getDataAccount(dataAccountKeypair.getAddress()) | |||
.getDataVersion("K3"); | |||
assertEquals(1, k3_version); | |||
assertNotNull(v1); | |||
assertNotNull(v3); | |||
assertEquals("V-1-2", v1.getValue().toUTF8String()); | |||
@@ -0,0 +1,15 @@ | |||
package test.com.jd.blockchain.ledger; | |||
import com.jd.blockchain.contract.Contract; | |||
import com.jd.blockchain.contract.ContractEvent; | |||
@Contract | |||
public interface TxTestContract { | |||
@ContractEvent(name = "testReadable") | |||
boolean testReadable(); | |||
@ContractEvent(name = "testRollbackWhileVersionConfliction") | |||
void testRollbackWhileVersionConfliction(String address, String key, String value, long version); | |||
} |
@@ -0,0 +1,74 @@ | |||
package test.com.jd.blockchain.ledger; | |||
import com.jd.blockchain.contract.ContractEventContext; | |||
import com.jd.blockchain.contract.ContractLifecycleAware; | |||
import com.jd.blockchain.contract.EventProcessingAware; | |||
import com.jd.blockchain.ledger.KVDataEntry; | |||
import com.jd.blockchain.utils.Bytes; | |||
public class TxTestContractImpl implements TxTestContract, ContractLifecycleAware, EventProcessingAware { | |||
private ContractEventContext eventContext; | |||
private Bytes dataAddress; | |||
public static String KEY = "k1"; | |||
@Override | |||
public boolean testReadable() { | |||
KVDataEntry v1 = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), | |||
dataAddress.toBase58(), KEY)[0]; | |||
String text1 = (String) v1.getValue(); | |||
System.out.printf("k1=%s, version=%s \r\n", text1, v1.getVersion()); | |||
text1 = null == text1 ? "v" : text1; | |||
String newValue = text1 + "-" + (v1.getVersion() + 1); | |||
System.out.printf("new value = %s\r\n", newValue); | |||
eventContext.getLedger().dataAccount(dataAddress).setText(KEY, newValue, v1.getVersion()); | |||
KVDataEntry v2 = eventContext.getLedger().getDataEntries(eventContext.getCurrentLedgerHash(), | |||
dataAddress.toBase58(), KEY)[0]; | |||
System.out.printf("---- read new value ----\r\nk1=%s, version=%s \r\n", v2.getValue(), v2.getVersion()); | |||
String text2 = (String) v2.getValue(); | |||
return text1.equals(text2); | |||
} | |||
@Override | |||
public void testRollbackWhileVersionConfliction(String address, String key, String value, long version) { | |||
eventContext.getLedger().dataAccount(address).setText(key, value, version); | |||
} | |||
@Override | |||
public void postConstruct() { | |||
// TODO Auto-generated method stub | |||
} | |||
@Override | |||
public void beforeDestroy() { | |||
// TODO Auto-generated method stub | |||
} | |||
@Override | |||
public void beforeEvent(ContractEventContext eventContext) { | |||
this.eventContext = eventContext; | |||
} | |||
@Override | |||
public void postEvent(ContractEventContext eventContext, Exception error) { | |||
this.eventContext = null; | |||
} | |||
public Bytes getDataAddress() { | |||
return dataAddress; | |||
} | |||
public void setDataAddress(Bytes dataAddress) { | |||
this.dataAddress = dataAddress; | |||
} | |||
} |
@@ -1,32 +1,36 @@ | |||
package com.jd.blockchain.ledger; | |||
import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
import com.jd.blockchain.binaryproto.DataContract; | |||
import com.jd.blockchain.ledger.resolver.*; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import com.jd.blockchain.binaryproto.BinaryProtocol; | |||
import com.jd.blockchain.binaryproto.DataContract; | |||
import com.jd.blockchain.ledger.resolver.BooleanToBytesValueResolver; | |||
import com.jd.blockchain.ledger.resolver.BytesToBytesValueResolver; | |||
import com.jd.blockchain.ledger.resolver.BytesValueResolver; | |||
import com.jd.blockchain.ledger.resolver.IntegerToBytesValueResolver; | |||
import com.jd.blockchain.ledger.resolver.LongToBytesValueResolver; | |||
import com.jd.blockchain.ledger.resolver.ShortToBytesValueResolver; | |||
import com.jd.blockchain.ledger.resolver.StringToBytesValueResolver; | |||
public class BytesValueEncoding { | |||
private static final Map<Class<?>, BytesValueResolver> CLASS_RESOLVER_MAP = new ConcurrentHashMap<>(); | |||
private static final Map<DataType, BytesValueResolver> DATA_TYPE_RESOLVER_MAP = new ConcurrentHashMap<>(); | |||
private static final Object[] EMPTY_OBJECTS = {}; | |||
static { | |||
init(); | |||
} | |||
private static void init() { | |||
BytesValueResolver[] resolvers = new BytesValueResolver[]{ | |||
new BytesToBytesValueResolver(), | |||
new IntegerToBytesValueResolver(), | |||
new LongToBytesValueResolver(), | |||
new ShortToBytesValueResolver(), | |||
new StringToBytesValueResolver() | |||
}; | |||
BytesValueResolver[] resolvers = new BytesValueResolver[] { new BooleanToBytesValueResolver(), | |||
new BytesToBytesValueResolver(), new IntegerToBytesValueResolver(), new LongToBytesValueResolver(), | |||
new ShortToBytesValueResolver(), new StringToBytesValueResolver() }; | |||
for (BytesValueResolver currResolver : resolvers) { | |||
// 填充classMAP | |||
@@ -47,7 +51,6 @@ public class BytesValueEncoding { | |||
} | |||
} | |||
public static BytesValue encodeSingle(Object value, Class<?> type) { | |||
if (value == null) { | |||
return null; | |||
@@ -60,7 +63,8 @@ public class BytesValueEncoding { | |||
if (type.isInterface()) { | |||
// 判断是否含有DataContract注解 | |||
if (!type.isAnnotationPresent(DataContract.class)) { | |||
throw new IllegalStateException(String.format("Interface[%s] can not be serialize !!!", type.getName())); | |||
throw new IllegalStateException( | |||
String.format("Interface[%s] can not be serialize !!!", type.getName())); | |||
} | |||
// 将对象序列化 | |||
byte[] serialBytes = BinaryProtocol.encode(value, type); | |||
@@ -72,7 +76,7 @@ public class BytesValueEncoding { | |||
} | |||
return bytesValueResolver.encode(value, type); | |||
} | |||
public static BytesValueList encodeArray(Object[] values, Class<?>[] types) { | |||
if (values == null || values.length == 0) { | |||
return null; | |||
@@ -101,11 +105,14 @@ public class BytesValueEncoding { | |||
} | |||
return type == null ? valueResolver.decode(value) : valueResolver.decode(value, type); | |||
} | |||
public static Object[] decode(BytesValueList values, Class<?>[] types) { | |||
if (values == null) { | |||
return EMPTY_OBJECTS; | |||
} | |||
BytesValue[] bytesValues = values.getValues(); | |||
if (bytesValues == null || bytesValues.length == 0) { | |||
return null; | |||
return EMPTY_OBJECTS; | |||
} | |||
// 允许types为null,此时每个BytesValue按照当前的对象来处理 | |||
// 若types不为null,则types's长度必须和bytesValues一致 | |||
@@ -120,7 +127,8 @@ public class BytesValueEncoding { | |||
DataType dataType = bytesValue.getType(); | |||
BytesValueResolver valueResolver = DATA_TYPE_RESOLVER_MAP.get(dataType); | |||
if (valueResolver == null) { | |||
throw new IllegalStateException(String.format("DataType[%s] can not find encoder !!!", dataType.name())); | |||
throw new IllegalStateException( | |||
String.format("DataType[%s] can not find encoder !!!", dataType.name())); | |||
} | |||
resolveObjs[i] = valueResolver.decode(bytesValue); | |||
} | |||
@@ -132,7 +140,7 @@ public class BytesValueEncoding { | |||
} | |||
return resolveObjs; | |||
} | |||
public static Object getDefaultValue(Class<?> type) { | |||
if (type == void.class || type == Void.class) { | |||
return null; | |||
@@ -174,14 +182,27 @@ public class BytesValueEncoding { | |||
if (currParamType.isInterface()) { | |||
// 接口序列化必须实现DataContract注解 | |||
if (!currParamType.isAnnotationPresent(DataContract.class)) { | |||
throw new IllegalStateException(String.format("Interface[%s] can not be serialize !!!", currParamType.getName())); | |||
throw new IllegalStateException( | |||
String.format("Interface[%s] can not be annotated as a DataContract!!!", currParamType.getName())); | |||
} | |||
return true; | |||
} | |||
if (currParamType.isArray() ) { | |||
Class<?> componentType = currParamType.getComponentType(); | |||
if (componentType.isInterface()) { | |||
// 接口序列化必须实现DataContract注解 | |||
if (!componentType.isAnnotationPresent(DataContract.class)) { | |||
throw new IllegalStateException( | |||
String.format("Interface[%s] can not be annotated as a DataContract!!!", currParamType.getName())); | |||
} | |||
return true; | |||
} | |||
} | |||
return CLASS_RESOLVER_MAP.containsKey(currParamType); | |||
} | |||
public static class BytesValueListData implements BytesValueList { | |||
private List<BytesValue> bytesValues = new ArrayList<>(); | |||
@@ -0,0 +1,27 @@ | |||
package com.jd.blockchain.ledger; | |||
public class DataVersionConflictException extends BlockRollbackException { | |||
private static final long serialVersionUID = 3583192000738807503L; | |||
private TransactionState state; | |||
public DataVersionConflictException() { | |||
this(TransactionState.DATA_VERSION_CONFLICT, null); | |||
} | |||
public DataVersionConflictException(String message) { | |||
this(TransactionState.DATA_VERSION_CONFLICT, message); | |||
} | |||
private DataVersionConflictException(TransactionState state, String message) { | |||
super(message); | |||
assert TransactionState.SUCCESS != state; | |||
this.state = state; | |||
} | |||
public TransactionState getState() { | |||
return state; | |||
} | |||
} |
@@ -38,6 +38,11 @@ public enum TransactionState { | |||
* 合约不存在; | |||
*/ | |||
CONTRACT_DOES_NOT_EXIST((byte) 0x04), | |||
/** | |||
* 数据写入时版本冲突; | |||
*/ | |||
DATA_VERSION_CONFLICT((byte) 0x05), | |||
/** | |||
* 由于在错误的账本上执行交易而被丢弃; | |||
@@ -0,0 +1,58 @@ | |||
package com.jd.blockchain.ledger.resolver; | |||
import com.jd.blockchain.ledger.BytesData; | |||
import com.jd.blockchain.ledger.BytesValue; | |||
import com.jd.blockchain.ledger.DataType; | |||
import com.jd.blockchain.utils.Bytes; | |||
import com.jd.blockchain.utils.io.BytesUtils; | |||
import java.util.Set; | |||
public class BooleanToBytesValueResolver extends AbstractBytesValueResolver { | |||
private final Class<?>[] supportClasses = { Boolean.class, boolean.class }; | |||
private final DataType[] supportDataTypes = { DataType.BOOLEAN }; | |||
private final Set<Class<?>> convertClasses = initBooleanConvertSet(); | |||
@Override | |||
public BytesValue encode(Object value, Class<?> type) { | |||
if (!isSupport(type)) { | |||
throw new IllegalStateException(String.format("Un-support encode Class[%s] Object !!!", type.getName())); | |||
} | |||
return BytesData.fromBoolean((boolean) value); | |||
} | |||
@Override | |||
public Class<?>[] supportClasses() { | |||
return supportClasses; | |||
} | |||
@Override | |||
public DataType[] supportDataTypes() { | |||
return supportDataTypes; | |||
} | |||
@Override | |||
protected Object decode(Bytes value) { | |||
return BytesUtils.toInt(value.toBytes()); | |||
} | |||
@Override | |||
public Object decode(BytesValue value, Class<?> clazz) { | |||
// 支持转换为short、int、long | |||
int intVal = (int) decode(value); | |||
if (convertClasses.contains(clazz)) { | |||
// 对于short和Short需要强制类型转换 | |||
if (clazz.equals(short.class) || clazz.equals(Short.class)) { | |||
return (short) intVal; | |||
} else if (clazz.equals(long.class) || clazz.equals(Long.class)) { | |||
return (long) intVal; | |||
} | |||
return intVal; | |||
} else { | |||
throw new IllegalStateException(String.format("Un-Support decode value to class[%s] !!!", clazz.getName())); | |||
} | |||
} | |||
} |
@@ -10,71 +10,79 @@ import java.util.Set; | |||
public interface BytesValueResolver { | |||
/** | |||
* Int相关的可转换Class集合 | |||
*/ | |||
Class<?>[] supportIntConvertClasses = { | |||
short.class, Short.class, int.class, Integer.class, long.class, Long.class}; | |||
/** | |||
* Boolean相关的可转换Class集合 | |||
*/ | |||
Class<?>[] supportBooleanConvertClasses = { boolean.class, Boolean.class }; | |||
/** | |||
* 字节数组(字符串)相关可转换的Class集合 | |||
*/ | |||
Class<?>[] supportByteConvertClasses = { | |||
String.class, Bytes.class, byte[].class}; | |||
/** | |||
* Int相关的可转换Class集合 | |||
*/ | |||
Class<?>[] supportIntConvertClasses = { short.class, Short.class, int.class, Integer.class, long.class, | |||
Long.class }; | |||
default Set<Class<?>> initIntConvertSet() { | |||
return new HashSet<>(Arrays.asList(supportIntConvertClasses)); | |||
} | |||
/** | |||
* 字节数组(字符串)相关可转换的Class集合 | |||
*/ | |||
Class<?>[] supportByteConvertClasses = { String.class, Bytes.class, byte[].class }; | |||
default Set<Class<?>> initBooleanConvertSet() { | |||
return new HashSet<>(Arrays.asList(supportBooleanConvertClasses)); | |||
} | |||
default Set<Class<?>> initByteConvertSet() { | |||
return new HashSet<>(Arrays.asList(supportByteConvertClasses)); | |||
} | |||
default Set<Class<?>> initIntConvertSet() { | |||
return new HashSet<>(Arrays.asList(supportIntConvertClasses)); | |||
} | |||
/** | |||
* 将对象转换为BytesValue | |||
* | |||
* @param value | |||
* @return | |||
*/ | |||
BytesValue encode(Object value); | |||
default Set<Class<?>> initByteConvertSet() { | |||
return new HashSet<>(Arrays.asList(supportByteConvertClasses)); | |||
} | |||
/** | |||
* 将对象转换为BytesValue | |||
* | |||
* @param value | |||
* @param type | |||
* @return | |||
*/ | |||
BytesValue encode(Object value, Class<?> type); | |||
/** | |||
* 将对象转换为BytesValue | |||
* | |||
* @param value | |||
* @return | |||
*/ | |||
BytesValue encode(Object value); | |||
/** | |||
* 当前解析器支持的Class列表 | |||
* | |||
* @return | |||
*/ | |||
Class<?>[] supportClasses(); | |||
/** | |||
* 将对象转换为BytesValue | |||
* | |||
* @param value | |||
* @param type | |||
* @return | |||
*/ | |||
BytesValue encode(Object value, Class<?> type); | |||
/** | |||
* 当前解析器支持的DataType列表 | |||
* | |||
* @return | |||
*/ | |||
DataType[] supportDataTypes(); | |||
/** | |||
* 当前解析器支持的Class列表 | |||
* | |||
* @return | |||
*/ | |||
Class<?>[] supportClasses(); | |||
/** | |||
* 将BytesValue解析为对应的Object | |||
* | |||
* @param value | |||
* @return | |||
*/ | |||
Object decode(BytesValue value); | |||
/** | |||
* 当前解析器支持的DataType列表 | |||
* | |||
* @return | |||
*/ | |||
DataType[] supportDataTypes(); | |||
/** | |||
* 将BytesValue转换为指定Class的Object | |||
* | |||
* @param value | |||
* @param clazz | |||
* @return | |||
*/ | |||
Object decode(BytesValue value, Class<?> clazz); | |||
/** | |||
* 将BytesValue解析为对应的Object | |||
* | |||
* @param value | |||
* @return | |||
*/ | |||
Object decode(BytesValue value); | |||
/** | |||
* 将BytesValue转换为指定Class的Object | |||
* | |||
* @param value | |||
* @param clazz | |||
* @return | |||
*/ | |||
Object decode(BytesValue value, Class<?> clazz); | |||
} |
@@ -523,4 +523,4 @@ | |||
</profiles> | |||
</project> | |||
</project> |
@@ -81,7 +81,7 @@ public abstract class RuntimeContext { | |||
if (jarFile.isFile()) { | |||
FileUtils.deleteFile(jarFile); | |||
} else { | |||
throw new IllegalStateException("Code storage confliction! --" + jarFile.getAbsolutePath()); | |||
throw new IllegalStateException("Code storage conflict! --" + jarFile.getAbsolutePath()); | |||
} | |||
} | |||
FileUtils.writeBytes(jarBytes, jarFile); | |||
@@ -0,0 +1,34 @@ | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>com.jd.blockchain</groupId> | |||
<artifactId>test</artifactId> | |||
<version>1.0.1.RELEASE</version> | |||
</parent> | |||
<artifactId>test-contract</artifactId> | |||
<dependencies> | |||
<dependency> | |||
<groupId>com.jd.blockchain</groupId> | |||
<artifactId>contract-jvm</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.jd.blockchain</groupId> | |||
<artifactId>ledger-core</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.jd.blockchain</groupId> | |||
<artifactId>storage-rocksdb</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.jd.blockchain</groupId> | |||
<artifactId>crypto-classic</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
</dependencies> | |||
</project> |
@@ -0,0 +1,14 @@ | |||
package test.com.jd.blockchain.contract; | |||
import static org.junit.Assert.*; | |||
import org.junit.Test; | |||
public class ContractTransactionRollbackTest { | |||
@Test | |||
public void test() { | |||
} | |||
} |
@@ -3,7 +3,7 @@ | |||
ledger.seed=932dfe23-fe23232f-283f32fa-dd32aa76-8322ca2f-56236cda-7136b322-cb323ffe | |||
#账本的描述名称;此属性不参与共识,仅仅在当前参与方的本地节点用于描述用途; | |||
ledger.name= | |||
ledger.name=test-ledger | |||
#声明的账本创建时间;格式为 “yyyy-MM-dd HH:mm:ss.SSSZ”,表示”年-月-日 时:分:秒:毫秒时区“;例如:“2019-08-01 14:26:58.069+0800”,其中,+0800 表示时区是东8区 | |||
created-time=2019-08-01 14:26:58.069+0800 | |||
@@ -6,7 +6,9 @@ j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf | |||
#第1个账本[j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk]的配置; | |||
#账本的当前共识参与方的ID; | |||
binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.name = Test-Ledger-01 | |||
binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.address=1 | |||
binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.name=parti-1 | |||
#账本的当前共识参与方的私钥文件的保存路径; | |||
binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.parti.pk-path=keys/jd-com.priv | |||
#账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; | |||
@@ -21,7 +23,9 @@ binding.j5ptBmn67B2p3yki3ji1j2ZMjnJhrUvP4kFpGmcXgvrhmk.db.pwd=kksfweffj | |||
#第2个账本[j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf]的配置; | |||
#账本的当前共识参与方的ID; | |||
binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.name=Test-Ledger-02 | |||
binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.address=2 | |||
binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.name=parti-2 | |||
#账本的当前共识参与方的私钥文件的保存路径; | |||
binding.j5kLUENMvcUooZjKfz2bEYU6zoK9DAqbdDDU8aZEZFR4qf.parti.pk-path=keys/jd-com-1.priv | |||
#账本的当前共识参与方的私钥内容(Base58编码);如果指定了,优先选用此属性,其次是 pk-path 属性; | |||
@@ -43,6 +43,11 @@ | |||
<groupId>org.springframework</groupId> | |||
<artifactId>spring-beans</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-log4j2</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<!-- <dependency> | |||
<groupId>com.alibaba</groupId> | |||
<artifactId>fastjson</artifactId> | |||
@@ -68,4 +73,4 @@ | |||
</plugin> | |||
</plugins> | |||
</build> --> | |||
</project> | |||
</project> |