From 7154e052d6fac561b75435f1c96675652a6b64d7 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Tue, 7 Aug 2018 12:19:41 +0800 Subject: [PATCH 001/445] Merge commit '0fb2ac261e3d5adecaef83fdb10dd438a1b7d456' into feature/delegate_resource --- .gitignore | 4 + .travis.yml | 24 + Chinese version of TRON Protocol document.md | 696 +++++++++++++++ English version of TRON Protocol document.md | 734 ++++++++++++++++ LICENSE | 165 ++++ README.md | 11 + api/api.proto | 855 +++++++++++++++++++ core/Contract.proto | 199 +++++ core/Discover.proto | 44 + core/Tron.proto | 450 ++++++++++ core/TronInventoryItems.proto | 12 + install-googleapis.sh | 14 + install-protobuf.sh | 10 + 13 files changed, 3218 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Chinese version of TRON Protocol document.md create mode 100644 English version of TRON Protocol document.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 api/api.proto create mode 100644 core/Contract.proto create mode 100644 core/Discover.proto create mode 100644 core/Tron.proto create mode 100644 core/TronInventoryItems.proto create mode 100755 install-googleapis.sh create mode 100755 install-protobuf.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1554c5edd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# IDEA +.idea +*iml +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..34fa71b3d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: ruby + +cache: + directories: + - $HOME/protobuf + +sudo: false + +before_install: + - bash install-protobuf.sh + - bash install-googleapis.sh + +# check what has been installed by listing contents of protobuf folder +before_script: + - ls -R $HOME/protobuf + +# let's use protobuf +script: + - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto + + - ls -l \ No newline at end of file diff --git a/Chinese version of TRON Protocol document.md b/Chinese version of TRON Protocol document.md new file mode 100644 index 000000000..283bd5fb8 --- /dev/null +++ b/Chinese version of TRON Protocol document.md @@ -0,0 +1,696 @@ +# TRON protobuf protocol + +## TRON使用Google protobuf协议,协议内容涉及到账户,区块,传输多个层面。 + ++ 账户有基本账户、资产发布账户和合约账户三种类型。一个账户包含:账户名称,账户类型,地址,余额,投票,其他资产6种属性。 ++ 更进一步的,基本账户可以申请成为验证节点,验证节点具有额外的属性,投票统计数目,公钥,URL,以及历史表现等参数。 + + 3种`Account`类型:`Normal`,`AssetIssue`,`Contract`。 + + enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2;
 + } + + 一个`Account`包含7种参数: + `account_name`:该账户的名称——比如: ”_SicCongsAccount_”。 + `type`:该账户的类型——比如: _0_ 代表的账户类型是`Normal`。 + `balance`:该账户的TRX余额——比如:_4213312_。 + `votes`:账户所得投票数——比如:_{(“0x1b7w…9xj3”,323),(“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_。 + `asset`:除TRX以外账户上的其他资产——比如:_{<”WishToken”,66666>,<”Dogie”,233>}_。 + `latest_operation_time`: 该账户的最新活跃时间。 + + // Account
 + message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 + }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6;
 + int64 latest_operation_time = 10; + } + + 一个`Witness`包含8种参数: + `address`:验证节点的地址——比如:_“0xu82h…7237”_。 + `voteCount`:验证节点所得投票数——比如:_234234_。 + `pubKey`:验证节点的公钥——比如:_“0xu82h…7237”_。 + `url`:验证节点的url链接。 + `totalProduce`:验证节点产生的区块数——比如:_2434_。 + `totalMissed`:验证节点丢失的区块数——比如:_7_。 + `latestBlockNum`:最新的区块高度——比如:_4522_。 + `isJobs`:布尔表类型标志位。 + + // Witness
 + message Witness {
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7; + bool isJobs = 9; + 
} + ++ 一个区块由区块头和多笔交易构成。区块头包含时间戳,交易字典树的根,父哈希,签名等区块基本信息。 + + 一个`block`包含`transactions`和`block_header`。 + `transactions`:区块里的交易信息。 + `block_header`:区块的组成部分之一。 + + // block
 + message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2; + 
} + + `BlockHeader` 包括`raw_data`和`witness_signature`。 + `raw_data`:`raw`信息。 + `witness_signature`:区块头到验证节点的签名。 + + message `raw`包含6种参数: + `timestamp`:该消息体的时间戳——比如:_14356325_。 + `txTrieRoot`:Merkle Tree的根——比如:_“7dacsa…3ed”_。 + `parentHash`:上一个区块的哈希值——比如:_“7dacsa…3ed”_。 + `number`:区块高度——比如:_13534657_。 + `witness_id`:验证节点的id——比如:_“0xu82h…7237”_。 + `witness_address`:验证节点的地址——比如:_“0xu82h…7237”_。 + + message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2; + 
} + + 消息体 `ChainInventory` 包括 `BlockId` 和 `remain_num`。 + `BlockId`: block的身份信息。 + `remain_num`:在同步过程中,剩余的区块数量。 + + A `BlockId` contains 2 parameters: + `hash`: 该区块的哈希值。 + `number`: 哈希值和高度即为当前区块块号。 + + message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; + } + ++ 交易合约有多种类型,包括账户创建合约、账户更新合约、转账合约、转账断言合约、资产投票合约、见证节点投票合约、见证节点创建合约、见证节点更新合约、资产发布合约、参与资产发布和与部署合约11种类型。 + + `AccountCreatContract`包含3种参数: + `type`:账户类型——比如:_0_ 代表的账户类型是`Normal`。 + `account_name`: 账户名称——比如: _"SiCongsaccount”_。 + `owner_address`:合约持有人地址——比如: _“0xu82h…7237”_。 + + message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 + } + `AccountUpdateContract`包含2种参数: + `account_name`: 账户名称——比如: _"SiCongsaccount”_。 + `owner_address`:合约持有人地址——比如: _“0xu82h…7237”_。 + + message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; + } + + `TransferContract`包含3种参数: + `amount`:TRX数量——比如:_12534_。 + `to_address`: 接收方地址——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + + message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; + 
} + + `TransferAssetContract`包含4种参数: + `asset_name`:资产名称——比如:_”SiCongsaccount”_。 + `to_address`:接收方地址——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `amount`:目标资产数量——比如:_12353_。 + + message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 + } + + `VoteAssetContract`包含4种参数: + `vote_address`:投票人地址——比如:_“0xu82h…7237”_。 + `support`:投票赞成与否——比如:_true_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `count`:投票数目——比如:_2324234_。 + + message VoteAssetContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5; + } + + `VoteWitnessContract`包含4种参数: + `vote_address`:投票人地址——比如:_“0xu82h…7237”_。 + `support`:投票赞成与否——比如:_true_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `count`:投票数目——比如:_32632_。 + + message VoteWitnessContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5;
 + } + + `WitnessCreateContract`包含3种参数: + `private_key`:合约的私钥——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `url`:合约的url链接。 + + message WitnessCreateContract {
 + bytes owner_address = 1;
 + bytes private_key = 2;
 + bytes url = 12; + 
} + + `WitnessUpdateContract`包含2种参数: + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `update_url`:合约的url链接。 + + message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; + } + + `AssetIssueContract`包含11种参数: + `name`:合约名称——比如:_“SiCongcontract”_。 + `total_supply`:合约的赞成总票数——比如:_100000000_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `trx_num`:对应TRX数量——比如:_232241_。 + `num`: 对应的自定义资产数目。 + `start_time`:开始时间——比如:_20170312_。 + `end_time`:结束时间——比如:_20170512_。 + `vote_score`:合约的评分——比如:_12343_。 + `description`:合约的描述——比如:_”trondada”_。 + `url`:合约的url地址链接。 + + message AssetIssueContract {
 + bytes owner_address = 1;
 + bytes name = 2;
 + int64 total_supply = 4;
 + int32 trx_num = 6;
 + int32 num = 8;
 + int64 start_time = 9;
 + int64 end_time = 10;
 + int32 vote_score = 16;
 + bytes description = 20;
 + bytes url = 21;
 + } + + `ParticipateAssetIssueContract`包含4种参数: + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `to_address`:接收方地址——比如:_“0xu82h…7237”_。 + `asset_name`: 目标资产的名称。 + `amount`: 小部分数量。 + + `DeployContract`包含2种参数: + `script`:脚本。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + + message DeployContract {
 + bytes owner_address = 1;
 + bytes script = 2;
 + } + + 消息体 `Result` 包含 `fee` and `ret`2个参数. + `ret`: 交易结果。 + `fee`: 交易扣除的费用。 + + `code`是`ret`的类型定义,有`SUCCESS`和`FAILED`两种类型。 + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + ++ 每一个交易还包含多个输入与多个输出,以及其他一些相关属性。其中交易内的输入,交易本身,区块头均需签名。 + + 消息体 `Transaction`包括`raw_data`和`signature`。 + `raw_data`: 消息体`raw`。 + `signature`: 所有输入节点的签名。 + + `raw_data`包含8种参数: + `type`:消息体raw的交易类型。 + `vin`: 输入值。 + `vout`: 输出值。 + `expiration`:过期时间——比如:_20170312_。 + `data`: 数据。 + `contract`: 该交易内的合约。 + `script`: 脚本。 + `timestamp`:该消息体的时间戳。 + + 消息体 `Contract`包含`type`和`parameter`。 + `type`:合约的类型。 + `parameter`:任意参数。 + + 有八种账户类型合约:`AccountCreateContract`,`TransferContract`,`TransferAssetContract`,`VoteAssetContract`,`VoteWitnessContract`,`WitnessCreateContract`,`AssetIssueContract` 和`DeployContract`。 + + `TransactionType`包括`UtxoType`和`ContractType`。 + + message Transaction {
 + enum TranscationType {
 + UtxoType = 0;
 + ContractType = 1;
 + }
 + message Contract {
 + enum ContractType {
 + AccountCreateContract = 0;
 + TransferContract = 1;
 + TransferAssetContract = 2;
 + VoteAssetContract = 3;
 + VoteWitnessContract = 4;
 + WitnessCreateContract = 5;
 + AssetIssueContract = 6;
 + DeployContract = 7;
 + }
 + ContractType type = 1;
 + google.protobuf.Any parameter = 2;
 + }
 + message raw {
 + TranscationType type = 2;
 + repeated TXInput vin = 5;
 + repeated TXOutput vout = 7;
 + int64 expiration = 8;
 + bytes data = 10;
 + repeated Contract contract = 11;
 + bytes scripts = 16;
 + in64 timestamp = 17; + }
 + raw raw_data = 1;
 + repeated bytes signature = 5;
 + } + + 消息体 `TXOutputs`由`outputs`构成。 + `outputs`: 元素为`TXOutput`的数组。 + + message TXOutputs {
 + repeated TXOutput outputs = 1; + 
} + + 消息体 `TXOutput`包括`value`和`pubKeyHash`。 + `value`:输出值。 + `pubKeyhash`:公钥的哈希。 + + message TXOutput {
 + int64 value = 1;
 + bytes pubKeyHash = 2; + 
} + + 消息体 `TXIutput`包括`raw_data`和`signature`。 + `raw_data`:消息体`raw`。 + `signature`:`TXInput`的签名。 + + 消息体 `raw`包含`txID`,`vout`和 `pubKey`。 + `txID`:交易ID。 + `Vout`:上一个输出的值。 + `pubkey`:公钥。 + + message TXInput {
 + message raw {
 + bytes txID = 1;
 + int64 vout = 2;
 + bytes pubKey = 3;
 + }
 + raw raw_data = 1;
 + bytes signature = 4;
} + ++ 传输涉及的协议Inventory主要用于传输中告知接收方传输数据的清单。 + + `Inventory`包括`type`和`ids`。 + `type`:清单类型——比如:_0_ 代表`TRX`。 + `ids`:清单中的物品ID。 + + `InventoryType`包含`TRX`和 `BLOCK`。 + `TRX`:交易。 + `BLOCK`:区块。 + + // Inventory
 + message Inventory {
 + enum InventoryType {
 + TRX = 0;
 + BLOCK = 1;
 + }
 + InventoryType type = 1;
 + repeated bytes ids = 2; + 
} + + 消息体 `Items`包含4种参数: + `type`:物品类型——比如:_1_ 代表 `TRX`。 + `blocks`:物品中区块。 + `blockheaders`:区块头。 + `transactions`:交易。 + + `Items`有四种类型,分别是 `ERR`, `TRX`,`BLOCK` 和`BLOCKHEADER`。 + `ERR`:错误。 + `TRX`:交易。 + `BLOCK`:区块。 + `BLOCKHEADER`:区块头。 + + message Items {
 + enum ItemType {
 + ERR = 0;
 + TRX = 1;
 + BLOCK = 2;
 + BLOCKHEADER = 3;
 + }
 + ItemType type = 1;
 + repeated Block blocks = 2;
 + repeated BlockHeader block_headers = 3;
 + repeated Transaction transactions = 4;
 + } + + `Inventory`包含`type`和`items`。 + `type`:物品种类。 + `items`:物品清单。 + + message InventoryItems {
 + int32 type = 1;
 + repeated bytes items = 2;
 + } + + 消息体 `BlockInventory` 包含 `type`。 + `type`: 清单种类. + + 有三种类型:`SYNC`, `ADVTISE`, `FETCH`。 + + // Inventory + message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + 消息体 `BlockId` 包括 `ids` and `type`。 + `ids`: 区块身份信息。 + `type`: 区块类型。 + + `ids` 包含2种参数: + `hash`: 区块的哈希值。 + `number`: 哈希值和区块高度即为当前区块号。 + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; + } + + `ReasonCode` 有15种可能断开的原因: + `REQUESTED` + `TCP_ERROR` + `BAD_PROTOCOL` + `USELESS_PEER` + `TOO_MANY_PEERS` + `DUPLICATE_PEER` + `INCOMPATIBLE_PROTOCOL` + `NULL_IDENTITY` + `PEER_QUITING` + `UNEXPECTED_IDENTITY` + `LOCAL_IDENTITY` + `PING_TIMEOU` + `USER_REASON` + `RESET` + `UNKNOWN` + + enum ReasonCode { + REQUESTED = 0; + TCP_ERROR = 1; + BAD_PROTOCOL = 2; + USELESS_PEER = 3; + TOO_MANY_PEERS = 4; + DUPLICATE_PEER = 5; + INCOMPATIBLE_PROTOCOL = 6; + NULL_IDENTITY = 7; + PEER_QUITING = 8; + UNEXPECTED_IDENTITY = 9; + LOCAL_IDENTITY = 10; + PING_TIMEOUT = 11; + USER_REASON = 12; + RESET = 16; + UNKNOWN = 255; + } + + 消息体`DisconnectMessage`包含`reason`。 + `DisconnectMessage`:断开连接是的消息。 + `reason`:断开连接时的原因。 + + 消息体`HelloMessage`包含2个参数: + `from`请:求建立连接的节点。 + `version`:建立连接的节点。 + ++ 钱包服务RPC和区块链浏览器。 + + `Wallet`钱包服务包含多个RPC。 + __`Getbalance`__:获取`Account`的余额。 + __`CreatTransaction`__:通过`TransferContract`创建交易。 + __`BroadcastTransaction`__:广播`Transaction`。 + __`CreateAccount`__:通过`AccountCreateContract`创建账户。 + __`CreatAssetIssue`__:通过`AssetIssueContract`发布一个资产。 + __`ListAccounts`__:通过`ListAccounts`查看账户列表。 + __`UpdateAccount`__:通过`UpdateAccountContract`发布一个资产。 + __`VoteWitnessAccount`__:通过`VoteWitnessContract`发布一个资产。 + __`WitnessList`__:通过`WitnessList`查看见证节点列表。 + __`UpdateWitness`__:通过`WitnessUpdateContract`发布一个资产。 + __`CreateWitness`__:通过`WitnessCreateContract`发布一个资产。 + __`TransferAsset`__:通过`TransferAssetContract`发布一个资产。 + __`ParticipateAssetIssue`__:通过`ParticipateAssetIssueContract`发布一个资产。 + __`ListNodes`__:通过`ListNodes`查看节点列表。 + __`GetAssetIssueList`__:通过`GetAssetIssueList`查看资产发布节点列表。 + __`GetAssetIssueByAccount`__:通过`Account`获取发行资产。 + __`GetAssetIssueByName`__:通过`Name`获取发行资产。 + __`GetNowBlock`__:获取区块。 + __`GetBlockByNum`__:根据块号获取区块。 + __`TotalTransaction`__:查看总交易量。 + + service Wallet { + + rpc GetAccount (Account) returns (Account) { + + }; + + rpc CreateTransaction (TransferContract) returns (Transaction) { + + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + + }; + + rpc ListAccounts (EmptyMessage) returns (AccountList) { + + }; + + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + + }; + + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + + }; + + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + + }; + + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + + }; + + rpc WitnessList (EmptyMessage) returns (WitnessList) { + + }; + + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + + }; + + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + + }; + + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + + } + + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + + } + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + + } + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + + } + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + + } + }; + + `AccountList`: 区块链浏览器中的账户列表。 + 消息体 `AccountList` 包含1个参数: + `account`: + + message AccountList { + repeated Account accounts = 1; + } + + `WitnessList`:区块链浏览器中的见证节点列表。 + 消息体 `WitnessList` 包含1个参数: + `witnesses`: + + message WitnessList { + repeated Witness witnesses = 1; + } + + `AssetIssueList`:区块链浏览器中的发布资产列表。 + 消息体 `AssetIssueList` 包含1个参数: + `assetIssue`: + + message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; + } + + + `NodeList`: 分布节点图中的节点列表。 + 消息体 `NodeList` 包含1个参数: + `nodes`: + + message NodeList { + repeated Node nodes = 1; + } + + `Address`: 节点地址。 + 消息体`Address` 包含2个参数: + `host`:节点所有者。 + `port`:节点的端口号。 + + message Address { + bytes host = 1; + int32 port = 2; + } + + + 消息体`Return`只含有一个参数: + `result`: 布尔表类型标志位。 + + message `Return` {
 + bool result = 1;
 + } + ++ 网络UDP消息结构。 + + `Endpoint`:网络中节点信息存储结构. + 消息体`Endpoint` 包含3个参数: + `address`:节点地址。 + `port`:端口号。 + `nodeId`: 节点ID信息。 + + message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; + } + + `PingMessage`:节点建立连接时所发送的消息。 + 消息体`PingMessage` 包含4个参数: + `from`:消息来自的节点。 + `to`: 消息发送的节点。 + `version`: 网络版本。 + `timestamp`:消息创建时的时间戳。 + + message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; + } + + `PongMessage`:连接建立成功时的回复消息。 + 消息体`PongMessage` 包含3个参数: + `from`:消息来自的节点。 + `echo`: + `timestamp`:消息创建时的时间戳。 + + message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; + } + + `FindNeighbours`:节点查询相邻节点时所发送的消息。 + 消息体`FindNeighbours` 包含3个参数: + `from`: 消息来自的节点。 + `targetId`: 目标节点的信息。 + `timestamp`: 消息创建时的时间戳。 + + message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; + } + + `Neighbour`:相邻接点回复消息。 + 消息体`Neighbours` 包含3个参数: + `from`: 消息来自的节点。 + `neighbours`: 相邻节点。 + `timestamp`: 消息创建时的时间戳。 + + message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; + } + +# 详细的协议见附属文件。详细协议随着程序的迭代随时都可能发生变化,请以最新的版本为准。 \ No newline at end of file diff --git a/English version of TRON Protocol document.md b/English version of TRON Protocol document.md new file mode 100644 index 000000000..94498fb1c --- /dev/null +++ b/English version of TRON Protocol document.md @@ -0,0 +1,734 @@ + +# Protobuf protocol + +## The protocol of TRON is defined by Google Protobuf and contains a range of layers, from account, block to transfer. + ++ There are 3 types of account—basic account, asset release account and contract account, and attributes included in each account are name, types, address, balance and related asset. ++ A basic account is able to apply to be a validation node, which has serval parameters, including extra attributes, public key, URL, voting statistics, history performance, etc. + + There are three different `Account types`: `Normal`, `AssetIssue`, `Contract`. + + enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2; + 
} + + An `Account` contains 7 parameters: + `account_name`: the name for this account – e.g. “_BillsAccount_”. + `type`: what type of this account is – e.g. _0_ stands for type `Normal`. + `balance`: balance of this account – e.g. _4213312_. + `votes`: received votes on this account – e.g. _{(“0x1b7w…9xj3”,323), (“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_. + `asset`: other assets expect TRX in this account – e.g. _{<“WishToken”,66666>,<”Dogie”,233>}_. + `latest_operation_time`: the latest operation time of this account. + + // Account
 + message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6; + int64 latest_operation_time = 10;
 + } + + A `Witness` contains 8 parameters: + `address`: the address of this witness – e.g. “_0xu82h…7237_”. + `voteCount`: number of received votes on this witness – e.g. _234234_. + `pubKey`: the public key for this witness – e.g. “_0xu82h…7237_”. + `url`: the url for this witness – e.g. “_https://www.noonetrust.com_”. + `totalProduced`: the number of blocks this witness produced – e.g. _2434_. + `totalMissed`: the number of blocks this witness missed – e.g. _7_. + `latestBlockNum`: the latest height of block – e.g. _4522_. + `isjobs`: a bool flag. + + // Witness
 + message Witness{
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7;
 + bool isJobs = 9; + } + ++ A block typically contains transaction data and a blockheader, which is a list of basic block information, including timestamp, signature, parent hash, root of Merkle tree and so on. + + A block contains `transactions` and a `block_header`. + `transactions`: transaction data of this block. + `block_header`: one part of a block. + + // block + 
message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2;
 + } + + A `BlockHeader` contains `raw_data` and `witness_signature`. + `raw_data`: a `raw` message. + `witness_signature`: signature for this block header from witness node. + + A message `raw` contains 6 parameters: + `timestamp`: timestamp of this message – e.g. _14356325_. + `txTrieRoot`: the root of Merkle Tree in this block – e.g. “_7dacsa…3ed_.” + `parentHash`: the hash of last block – e.g. “_7dacsa…3ed_.” + `number`: the height of this block – e.g. _13534657_. + `witness_id`: the id of witness which packed this block – e.g. “_0xu82h…7237_”. + `witness_address`: the adresss of the witness packed this block – e.g. “_0xu82h…7237_”. + + message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2;
 + } + + message `ChainInventory` contains `BlockId` and `remain_num`. + `BlockId`: the identification of block. + `remain_num`:the remain number of blocks in the synchronizing process. + + A `BlockId` contains 2 parameters: + `hash`: the hash of block. + `number`: the hash and height of block. + + message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; + } + ++ Transaction contracts mainly includes account create contract, account update contract transfer contract, transfer asset contract, vote asset contract, vote witness contract, witness creation contract, witness update contract, asset issue contract, participate asset issue contract and deploy contract. + + An `AccountCreateContract` contains 3 parameters: + `type`: What type this account is – e.g. _0_ stands for `Normal`. + `account_name`: the name for this account – e.g.”_Billsaccount_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 + } + + A `AccountUpdateContract` contains 2 paremeters: + `account_name`: the name for this account – e.g.”_Billsaccount_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; + } + + A `TransferContract` contains 3 parameters: + `amount`: the amount of TRX – e.g. _12534_. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; + } + + A `TransferAssetContract` contains 4 parameters: + `asset_name`: the name for asset – e.g.”_Billsaccount_”. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `amount`: the amount of target asset - e.g._12353_. + + message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 + } + + A `VoteAssetContract` contains 4 parameters: + `vote_address`: the voted address of the asset. + `support`: is the votes supportive or not – e.g. _true_. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `count`: the count number of votes- e.g. _2324234_. + + message VoteAssetContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5;
 + } + + A `VoteWitnessContract` contains 4 parameters: + `vote_address`: the addresses of those who voted. + `support`: is the votes supportive or not - e.g. _true_. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `count`: - e.g. the count number of vote – e.g. _32632_. + + message VoteWitnessContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5; + 
} + + A `WitnessCreateContract` contains 3 parameters: + `private_key`: the private key of contract– e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `url`: the url for the witness – e.g. “_https://www.noonetrust.com_”. + + message WitnessCreateContract {
 + bytes owner_address = 1;
 + bytes private_key = 2;
 + bytes url = 12;
 + } + + A `WitnessUpdateContract` contains 2 parameters: + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `update_url`: the url for the witness – e.g. “_https://www.noonetrust.com_”. + + message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; + } + + An `AssetIssueContract` contains 11 parameters: + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + `name`: the name for this contract – e.g. “Billscontract”. + `total_supply`: the maximum supply of this asset – e.g. _1000000000_. + `trx_num`: the number of TRONIX – e.g._232241_. + `num`: number of corresponding asset. + `start_time`: the starting date of this contract – e.g._20170312_. + `end_time`: the expiring date of this contract – e.g. _20170512_. + `vote_score`: the vote score of this contract received – e.g. _12343_. + `description`: the description of this contract – e.g.”_trondada_”. + `url`: the url of this contract – e.g. “_https://www.noonetrust.com_”. + + message AssetIssueContract {
 + bytes owner_address = 1;
 + bytes name = 2;
 + int64 total_supply = 4;
 + int32 trx_num = 6;
 + int32 num = 8;
 + int64 start_time = 9;
 + int64 end_time = 10;
 + int32 vote_score = 16;
 + bytes description = 20;
 + bytes url = 21;
 + } + + A `ParticipateAssetIssueContract` contains 4 parameters: + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `asset_name`: the name of target asset. + `amount`: the amount of drops. + + message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; + int64 amount = 4; + } + + A `DeployContract` contains 2 parameters: + `script`: the script of this contract. + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + + message DeployContract {
 + bytes owner_address = 1;
 + bytes script = 2; + 
} t + ++ Each transaction contains several TXInputs, TXOutputs and other related qualities. +Input, transaction and head block all require signature. + + message `Transaction` contains `raw_data` and `signature`. + `raw_data`: message `raw`. + `signature`: signatures form all input nodes. + + `raw` contains 8 parameters: + `type`: the transaction type of `raw` message. + `vin`: input values. + `vout`: output values. + `expiration`: the expiration date of transaction – e.g._20170312_. + `data`: data. + `contract`: contracts in this transaction. + `scripts`:scripts in the transaction. + `timestamp`: timestamp of this raw data – e.g. _14356325_. + + message `Contract` contains `type` and `parameter`. + `type`: what type of the message contract. + `parameter`: It can be any form. + + There are 8 different of contract types: `AccountCreateContract`, `TransferContract`, `TransferAssetContract`, `VoteAssetContract`, `VoteWitnessContract`,`WitnessCreateContract`, `AssetIssueContract` and `DeployContract`. + `TransactionType` have two types: `UtxoType` and `ContractType`. + + message Transaction {
 + enum TranscationType {
 + UtxoType = 0;
 + ContractType = 1;
 + }
 + message Contract {
 + enum ContractType {
 + AccountCreateContract = 0;
 + TransferContract = 1;
 + TransferAssetContract = 2;
 + VoteAssetContract = 3;
 + VoteWitnessContract = 4;
 + WitnessCreateContract = 5;
 + AssetIssueContract = 6;
 + DeployContract = 7;
 + WitnessUpdateContract = 8; + ParticipateAssetIssueContract = 9 + }
 + ContractType type = 1;
 + google.protobuf.Any parameter = 2;
 + }
 + message raw {
 + TranscationType type = 2;
 + repeated TXInput vin = 5;
 + repeated TXOutput vout = 7;
 + int64 expiration = 8;
 + bytes data = 10;
 + repeated Contract contract = 11;
 + bytes scripts = 16;
 + int64 timestamp = 17; + }
 + raw raw_data = 1;
 + repeated bytes signature = 5; + 
} + + message `TXOutputs` contains `outputs`. + `outputs`: an array of `TXOutput`. + + message TXOutputs {
 + repeated TXOutput outputs = 1;
 + } + + message `TXOutput` contains `value` and `pubKeyHash`. + `value`: output value. + `pubKeyHash`: Hash of public key + + message TXOutput {
 + int64 value = 1;
 + bytes pubKeyHash = 2;
 + } + + message `TXInput` contains `raw_data` and `signature`. + `raw_data`: a message `raw`. + `signature`: signature for this `TXInput`. + + message `raw` contains `txID`, `vout` and `pubKey`. + `txID`: transaction ID. + `vout`: value of last output. + `pubKey`: public key. + + message TXInput {
 + message raw {
 + bytes txID = 1;
 + int64 vout = 2;
 + bytes pubKey = 3;
 + }
 + raw raw_data = 1;
 + bytes signature = 4; + 
} + + message `Result` contains `fee` and `ret`. + `ret`: the state of transaction. + `fee`: the fee for transaction. + + `code` is definition of `ret` and contains 2 types:`SUCCESS` and `FAILED`. + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + ++ Inventory is mainly used to inform peer nodes the list of items. + + `Inventory` contains `type` and `ids`. + `type`: what type this `Inventory` is. – e.g. _0_ stands for `TRX`. + `ids`: ID of things in this `Inventory`. + + Two `Inventory` types: `TRX` and `BLOCK`. + `TRX`: transaction. + `BLOCK`: block. + + // Inventory
 + message Inventory {
 + enum InventoryType {
 + TRX = 0;
 + BLOCK = 1;
 + }
 + InventoryType type = 1;
 + repeated bytes ids = 2;
 + } + + message `Items` contains 4 parameters: + `type`: type of items – e.g. _1_ stands for `TRX`. + `blocks`: blocks in `Items` if there is any. + `block_headers`: block headers if there is any. + `transactions`: transactions if there is any. + + `Items` have four types: `ERR`, `TRX`, `BLOCK` and `BLOCKHEADER`. + `ERR`: error. + `TRX`: transaction. + `BLOCK`: block. + `BLOCKHEADER`: block header. + + message Items {
 + enum ItemType {
 + ERR = 0;
 + TRX = 1;
 + BLOCK = 2;
 + BLOCKHEADER = 3;
 + }
 + ItemType type = 1;
 + repeated Block blocks = 2;
 + repeated BlockHeader + block_headers = 3;
 + repeated Transaction transactions = 4; + } + + `InventoryItems` contains `type` and `items`. + `type`: what type of item. + `items`: items in an `InventoryItems`. + + message InventoryItems {
 + int32 type = 1;
 + repeated bytes items = 2; + 
} + + message `BlockInventory` contains `type`. + `type`: what type of inventory. + + There are 3 types:`SYNC`, `ADVTISE`, `FETCH`. + + // Inventory + message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + message `BlockId` contains `ids` and `type`. + `ids`: the identification of block. + `type`: what type of the block. + + `ids` contains 2 paremeters: + `hash`: the hash of block. + `number`: the hash and height of block. + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; + } + + `ReasonCode`: the type of reason. + + `ReasonCode` contains 15 types of disconnect reasons: + `REQUESTED` + `TCP_ERROR` + `BAD_PROTOCOL` + `USELESS_PEER` + `TOO_MANY_PEERS` + `DUPLICATE_PEER` + `INCOMPATIBLE_PROTOCOL` + `NULL_IDENTITY` + `PEER_QUITING` + `UNEXPECTED_IDENTITY` + `LOCAL_IDENTITY` + `PING_TIMEOUT` + `USER_REASON` + `RESET` + `UNKNOWN` + + enum ReasonCode { + REQUESTED = 0; + TCP_ERROR = 1; + BAD_PROTOCOL = 2; + USELESS_PEER = 3; + TOO_MANY_PEERS = 4; + DUPLICATE_PEER = 5; + INCOMPATIBLE_PROTOCOL = 6; + NULL_IDENTITY = 7; + PEER_QUITING = 8; + UNEXPECTED_IDENTITY = 9; + LOCAL_IDENTITY = 10; + PING_TIMEOUT = 11; + USER_REASON = 12; + RESET = 16; + UNKNOWN = 255; + } + + message`DisconnectMessage` contains `reason`. + `DisconnectMessage`: the message when disconnection occurs. + `reason`: the reason for disconnecting. + + message`HelloMessage` contains 2 parameters: + `HelloMessage`: the message for building connection. + `from`: the nodes that request for building connection. + `version`: the version when connection is built. + + + ++ Wallet Service RPC and blockchain explorer + + `Wallet` service contains several RPCs. + __`GetBalance`__ : + Return balance of an `Account`. + __`CreateTransaction`__ : + Create a transaction by giving a `TransferContract`. A Transaction containing a transaction creation will be returned. + __`BroadcastTransaction`__ : + Broadcast a `Transaction`. A `Return` will be returned indicating if broadcast is success of not. + __`CreateAccount`__ : + Create an account by giving a `AccountCreateContract`. + __`CreatAssetIssue`__ : + Issue an asset by giving a `AssetIssueContract`. + __`ListAccounts`__: + Check out the list of accounts by giving a `ListAccounts`. + __`UpdateAccount`__: + Issue an asset by giving a `UpdateAccountContract`. + __`VoteWitnessAccount`__: + Issue an asset by giving a `VoteWitnessContract`. + __`WitnessList`__: + Check out the list of witnesses by giving a `WitnessList`. + __`UpdateWitness`__: + Issue an asset by giving a `WitnessUpdateContract`. + __`CreateWitness`__: + Issue an asset by giving a `WitnessCreateContract`. + __`TransferAsset`__: + Issue an asset by giving a `TransferAssetContract`. + __`ParticipateAssetIssue`__: + Issue an asset by giving a `ParticipateAssetIssueContract`. + __`ListNodes`__: + Check out the list of nodes by giving a `ListNodes`. + __`GetAssetIssueList`__: + Get the list of issue asset by giving a `GetAssetIssueList`. + __`GetAssetIssueByAccount`__: + Get issue asset by giving a `Account`. + __`GetAssetIssueByName`__: + Get issue asset by giving a`Name`. + __`GetNowBlock`__: + Get block. + __`GetBlockByNum`__: + Get block by block number. + __`TotalTransaction`__: + Check out the total transaction. + + service Wallet { + + rpc GetAccount (Account) returns (Account) { + + }; + + rpc CreateTransaction (TransferContract) returns (Transaction) { + + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + + }; + + rpc ListAccounts (EmptyMessage) returns (AccountList) { + + }; + + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + + }; + + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + + }; + + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + + }; + + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + + }; + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + + }; + + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + + }; + + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + + }; + + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + + } + + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + + } + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + + } + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + + } + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + + } + }; + + `AccountList`: the list of acounts in the blockchain explorer. + message `AccountList` contains one parameter: + `account`: + + message AccountList { + repeated Account accounts = 1; + } + + `WitnessList`: the list of witnesses in the blockchain explorer. + message `WitnessList` contains one parameter: + `witnesses`: + + message WitnessList { + repeated Witness witnesses = 1; + } + + `AssetIssueList`: the list of issue asset in the blockchain explorer. + message `AssetIssueList` contains one parameter: + `assetIssue`: + + message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; + } + + `NodeList`: the list of nodes in the node distribution map. + message `NodeList` contains one parameter: + `nodes`: + + message NodeList { + repeated Node nodes = 1; + } + + `Address`: the address of nodes. + message`Address` contains 2 parameters: + `host`: the host of nodes. + `port`: the port number of nodes. + + message Address { + bytes host = 1; + int32 port = 2; + } + + message `Return` has only one parameter: + `result`: a bool flag. + + message `Return` {
 + bool result = 1; + 
} + ++ The message structure of UDP. + + `Endpoint`: the storage structure of nodes' information. + message`Endpoint` contains 3 parameters: + `address`: the address of nodes. + `port`: the port number. + `nodeId`:the ID of nodes. + + + message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; + } + + `PingMessage`: the message sent from one node to another in the connecting process. + message`PingMessage` contains 4 parameters: + `from`: which node does the message send from. + `to`: which node will the message send to. + `version`: the version of the Internet. + `timestamp`: the timestamp of message. + + message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; + } + + `PongMessage`: the message implies that nodes are connected. + message`PongMessage` contains 3 parameters: + `from`: which node does the message send from. + `echo`: + `timestamp`: the timestamp of message. + + message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; + } + + `FindNeighbours`: the message sent from one node to find another one. + message`FindNeighbours` contains 3 parameters: + `from`: which node does the message send from. + `targetId`: the ID of targeted node. + `timestamp`: the timestamp of message. + + message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; + } + + `FindNeighbour`: the message replied by the neighbour node. + message`Neighbours` contains 3 parameters: + `from`: which node does the message send from. + `neighbours`: the neighbour node. + `timestamp`: the timestamp of message. + + message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; + } + + + +# Please check detailed protocol document that may change with the iteration of the program at any time. Please refer to the latest version. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 000000000..6a2811911 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# protocol [![Build Status](https://travis-ci.org/tronprotocol/protocol.svg?branch=master)](https://travis-ci.org/tronprotocol/protocol) + + +# The protocol of Tron including api and message. + +java-tron, wallet-cli and grpc-gateway + +git subtree pull --prefix src/main/protos/ protocol master + +## Run the included *.sh files to initialize the dependencies + diff --git a/api/api.proto b/api/api.proto new file mode 100644 index 000000000..13a729a3d --- /dev/null +++ b/api/api.proto @@ -0,0 +1,855 @@ +syntax = "proto3"; +package protocol; + +import "core/Tron.proto"; +import "core/Contract.proto"; +import "google/api/annotations.proto"; + + +option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file +option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/api"; + +service Wallet { + + rpc GetAccount (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccount" + body: "*" + additional_bindings { + get: "/wallet/getaccount" + } + }; + }; + + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccountbyid" + body: "*" + additional_bindings { + get: "/wallet/getaccountbyid" + } + }; + }; + + //Please use CreateTransaction2 instead of this function. + rpc CreateTransaction (TransferContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createtransaction" + body: "*" + additional_bindings { + get: "/wallet/createtransaction" + } + }; + }; + //Use this function instead of CreateTransaction. + rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + option (google.api.http) = { + post: "/wallet/broadcasttransaction" + body: "*" + additional_bindings { + get: "/wallet/broadcasttransaction" + } + }; + }; + //Please use UpdateAccount2 instead of this function. + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updateaccount" + body: "*" + additional_bindings { + get: "/wallet/updateaccount" + } + }; + }; + + + rpc SetAccountId (SetAccountIdContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/setaccountid" + body: "*" + additional_bindings { + get: "/wallet/setaccountid" + } + }; + }; + + //Use this function instead of UpdateAccount. + rpc UpdateAccount2 (AccountUpdateContract) returns (TransactionExtention) { + }; + + //Please use VoteWitnessAccount2 instead of this function. + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/votewitnessaccount" + body: "*" + additional_bindings { + get: "/wallet/votewitnessaccount" + } + }; + }; + + //modify the consume_user_resource_percent + rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { + }; + + //Use this function instead of VoteWitnessAccount. + rpc VoteWitnessAccount2 (VoteWitnessContract) returns (TransactionExtention) { + }; + //Please use CreateAssetIssue2 instead of this function. + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createassetissue" + body: "*" + additional_bindings { + get: "/wallet/createassetissue" + } + }; + }; + //Use this function instead of CreateAssetIssue. + rpc CreateAssetIssue2 (AssetIssueContract) returns (TransactionExtention) { + }; + //Please use UpdateWitness2 instead of this function. + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updatewitness" + body: "*" + additional_bindings { + get: "/wallet/updatewitness" + } + }; + }; + //Use this function instead of UpdateWitness. + rpc UpdateWitness2 (WitnessUpdateContract) returns (TransactionExtention) { + }; + //Please use CreateAccount2 instead of this function. + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createaccount" + body: "*" + additional_bindings { + get: "/wallet/createaccount" + } + }; + }; + //Use this function instead of CreateAccount. + rpc CreateAccount2 (AccountCreateContract) returns (TransactionExtention) { + } + //Please use CreateWitness2 instead of this function. + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createwitness" + body: "*" + additional_bindings { + get: "/wallet/createwitness" + } + }; + }; + //Use this function instead of CreateWitness. + rpc CreateWitness2 (WitnessCreateContract) returns (TransactionExtention) { + } + //Please use TransferAsset2 instead of this function. + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/transferasset" + body: "*" + additional_bindings { + get: "/wallet/transferasset" + } + }; + } + //Use this function instead of TransferAsset. + rpc TransferAsset2 (TransferAssetContract) returns (TransactionExtention) { + } + //Please use ParticipateAssetIssue2 instead of this function. + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/participateassetissue" + body: "*" + additional_bindings { + get: "/wallet/participateassetissue" + } + }; + } + //Use this function instead of ParticipateAssetIssue. + rpc ParticipateAssetIssue2 (ParticipateAssetIssueContract) returns (TransactionExtention) { + } + //Please use FreezeBalance2 instead of this function. + rpc FreezeBalance (FreezeBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/freezebalance" + body: "*" + additional_bindings { + get: "/wallet/freezebalance" + } + }; + } + //Use this function instead of FreezeBalance. + rpc FreezeBalance2 (FreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeBalance2 instead of this function. + rpc UnfreezeBalance (UnfreezeBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/unfreezebalance" + body: "*" + additional_bindings { + get: "/wallet/unfreezebalance" + } + }; + } + //Use this function instead of UnfreezeBalance. + rpc UnfreezeBalance2 (UnfreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeAsset2 instead of this function. + rpc UnfreezeAsset (UnfreezeAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/unfreezeasset" + body: "*" + additional_bindings { + get: "/wallet/unfreezeasset" + } + }; + } + //Use this function instead of UnfreezeAsset. + rpc UnfreezeAsset2 (UnfreezeAssetContract) returns (TransactionExtention) { + } + //Please use WithdrawBalance2 instead of this function. + rpc WithdrawBalance (WithdrawBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/withdrawbalance" + body: "*" + additional_bindings { + get: "/wallet/withdrawbalance" + } + }; + } + //Use this function instead of WithdrawBalance. + rpc WithdrawBalance2 (WithdrawBalanceContract) returns (TransactionExtention) { + } + //Please use UpdateAsset2 instead of this function. + rpc UpdateAsset (UpdateAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updateasset" + body: "*" + additional_bindings { + get: "/wallet/updateasset" + } + }; + } + //Use this function instead of UpdateAsset. + rpc UpdateAsset2 (UpdateAssetContract) returns (TransactionExtention) { + } + + rpc ProposalCreate (ProposalCreateContract) returns (TransactionExtention) { + } + + rpc ProposalApprove (ProposalApproveContract) returns (TransactionExtention) { + } + + rpc ProposalDelete (ProposalDeleteContract) returns (TransactionExtention) { + } + + rpc BuyStorage (BuyStorageContract) returns (TransactionExtention) { + } + + rpc BuyStorageBytes (BuyStorageBytesContract) returns (TransactionExtention) { + } + + rpc SellStorage (SellStorageContract) returns (TransactionExtention) { + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + option (google.api.http) = { + post: "/wallet/listnodes" + body: "*" + additional_bindings { + get: "/wallet/listnodes" + } + }; + } + + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getassetissuebyaccount" + body: "*" + additional_bindings { + get: "/wallet/getassetissuebyaccount" + } + }; + } + rpc GetAccountNet (Account) returns (AccountNetMessage) { + option (google.api.http) = { + post: "/wallet/getaccountnet" + body: "*" + additional_bindings { + get: "/wallet/getaccountnet" + } + }; + }; + rpc GetAccountResource (Account) returns (AccountResourceMessage) { + }; + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + option (google.api.http) = { + post: "/wallet/getassetissuebyname" + body: "*" + additional_bindings { + get: "/wallet/getassetissuebyname" + } + }; + } + //Please use GetNowBlock2 instead of this function. + rpc GetNowBlock (EmptyMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getnowblock" + body: "*" + additional_bindings { + get: "/wallet/getnowblock" + } + }; + } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. + rpc GetBlockByNum (NumberMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getblockbynum" + body: "*" + additional_bindings { + get: "/wallet/getblockbynum" + } + }; + } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + + rpc GetBlockById (BytesMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getblockbyid" + body: "*" + additional_bindings { + get: "/wallet/getblockbyid" + } + }; + } + //Please use GetBlockByLimitNext2 instead of this function. + rpc GetBlockByLimitNext (BlockLimit) returns (BlockList) { + option (google.api.http) = { + post: "/wallet/getblockbylimitnext" + body: "*" + additional_bindings { + get: "/wallet/getblockbylimitnext" + } + }; + } + //Use this function instead of GetBlockByLimitNext. + rpc GetBlockByLimitNext2 (BlockLimit) returns (BlockListExtention) { + } + //Please use GetBlockByLatestNum2 instead of this function. + rpc GetBlockByLatestNum (NumberMessage) returns (BlockList) { + option (google.api.http) = { + post: "/wallet/getblockbylatestnum" + body: "*" + additional_bindings { + get: "/wallet/getblockbylatestnum" + } + }; + } + //Use this function instead of GetBlockByLatestNum. + rpc GetBlockByLatestNum2 (NumberMessage) returns (BlockListExtention) { + } + rpc GetTransactionById (BytesMessage) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/gettransactionbyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactionbyid" + } + }; + } + + rpc DeployContract (CreateSmartContract) returns (TransactionExtention) { + } + + rpc GetContract (BytesMessage) returns (SmartContract) { + } + + rpc TriggerContract (TriggerSmartContract) returns (TransactionExtention) { + } + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + option (google.api.http) = { + post: "/wallet/listwitnesses" + body: "*" + additional_bindings { + get: "/wallet/listwitnesses" + } + }; + }; + + rpc ListProposals (EmptyMessage) returns (ProposalList) { + option (google.api.http) = { + post: "/wallet/listproposals" + body: "*" + additional_bindings { + get: "/wallet/listproposals" + } + }; + }; + + rpc GetProposalById (BytesMessage) returns (Proposal) { + option (google.api.http) = { + post: "/wallet/getproposalbyid" + body: "*" + additional_bindings { + get: "/wallet/getproposalbyid" + } + }; + }; + + rpc GetChainParameters (EmptyMessage) returns (ChainParameters) { + option (google.api.http) = { + post: "/wallet/getchainparameters" + body: "*" + additional_bindings { + get: "/wallet/getchainparameters" + } + }; + }; + + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getassetissuelist" + body: "*" + additional_bindings { + get: "/wallet/getassetissuelist" + } + }; + } + rpc GetPaginatedAssetIssueList (PaginatedMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getpaginatedassetissuelist" + body: "*" + additional_bindings { + get: "/wallet/getpaginatedassetissuelist" + } + }; + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + option (google.api.http) = { + post: "/wallet/totaltransaction" + body: "*" + additional_bindings { + get: "/wallet/totaltransaction" + } + }; + } + rpc GetNextMaintenanceTime (EmptyMessage) returns (NumberMessage) { + option (google.api.http) = { + post: "/wallet/getnextmaintenancetime" + body: "*" + additional_bindings { + get: "/wallet/getnextmaintenancetime" + } + }; + } + //Warning: do not invoke this interface provided by others. + //Please use GetTransactionSign2 instead of this function. + rpc GetTransactionSign (TransactionSign) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/gettransactionsign" + body: "*" + additional_bindings { + get: "/wallet/gettransactionsign" + } + }; + }; + //Warning: do not invoke this interface provided by others. + //Use this function instead of GetTransactionSign. + rpc GetTransactionSign2 (TransactionSign) returns (TransactionExtention) { + }; + //Warning: do not invoke this interface provided by others. + rpc CreateAddress (BytesMessage) returns (BytesMessage) { + option (google.api.http) = { + post: "/wallet/createaddress" + body: "*" + additional_bindings { + get: "/wallet/createaddress" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransfer" + body: "*" + additional_bindings { + get: "/wallet/easytransfer" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransferByPrivate (EasyTransferByPrivateMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransferbyprivate" + body: "*" + additional_bindings { + get: "/wallet/easytransferbyprivate" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + + option (google.api.http) = { + post: "/wallet/generateaddress" + body: "*" + additional_bindings { + get: "/wallet/generateaddress" + } + }; + } + + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/wallet/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactioninfobyid" + } + }; + } +}; + + +service WalletSolidity { + + rpc GetAccount (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccount" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccount" + } + }; + }; + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccountbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccountbyid" + } + }; + }; + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + option (google.api.http) = { + post: "/walletsolidity/listwitnesses" + body: "*" + additional_bindings { + get: "/walletsolidity/listwitnesses" + } + }; + }; + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/walletsolidity/getassetissuelist" + body: "*" + additional_bindings { + get: "/walletsolidity/getassetissuelist" + } + }; + } + rpc GetPaginatedAssetIssueList (PaginatedMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/walletsolidity/getpaginatedassetissuelist" + body: "*" + additional_bindings { + get: "/walletsolidity/getpaginatedassetissuelist" + } + }; + } + //Please use GetNowBlock2 instead of this function. + rpc GetNowBlock (EmptyMessage) returns (Block) { + option (google.api.http) = { + post: "/walletsolidity/getnowblock" + body: "*" + additional_bindings { + get: "/walletsolidity/getnowblock" + } + }; + } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. + rpc GetBlockByNum (NumberMessage) returns (Block) { + option (google.api.http) = { + post: "/walletsolidity/getblockbynum" + body: "*" + additional_bindings { + get: "/walletsolidity/getblockbynum" + } + }; + } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + + rpc GetTransactionById (BytesMessage) returns (Transaction) { + option (google.api.http) = { + post: "/walletsolidity/gettransactionbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/gettransactionbyid" + } + }; + } + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/walletsolidity/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/walletsolidity/gettransactioninfobyid" + } + }; + } + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + option (google.api.http) = { + post: "/walletsolidity/generateaddress" + body: "*" + additional_bindings { + get: "/walletsolidity/generateaddress" + } + }; + } +}; + +service WalletExtension { + //Please use GetTransactionsFromThis2 instead of this function. + rpc GetTransactionsFromThis (AccountPaginated) returns (TransactionList) { + option (google.api.http) = { + post: "/walletextension/gettransactionsfromthis" + body: "*" + additional_bindings { + get: "/walletextension/gettransactionsfromthis" + } + }; + } + //Use this function instead of GetTransactionsFromThis. + rpc GetTransactionsFromThis2 (AccountPaginated) returns (TransactionListExtention) { + } + //Please use GetTransactionsToThis2 instead of this function. + rpc GetTransactionsToThis (AccountPaginated) returns (TransactionList) { + option (google.api.http) = { + post: "/walletextension/gettransactionstothis" + body: "*" + additional_bindings { + get: "/walletextension/gettransactionstothis" + } + }; + } + //Use this function instead of GetTransactionsToThis. + rpc GetTransactionsToThis2 (AccountPaginated) returns (TransactionListExtention) { + } +}; + +// the api of tron's db +service Database { + // for tapos + rpc getBlockReference (EmptyMessage) returns (BlockReference) { + + } + rpc GetDynamicProperties (EmptyMessage) returns (DynamicProperties) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } +}; + +message Return { + enum response_code { + SUCCESS = 0; + SIGERROR = 1; // error in signature + CONTRACT_VALIDATE_ERROR = 2; + CONTRACT_EXE_ERROR = 3; + BANDWITH_ERROR = 4; + DUP_TRANSACTION_ERROR = 5; + TAPOS_ERROR = 6; + TOO_BIG_TRANSACTION_ERROR = 7; + TRANSACTION_EXPIRATION_ERROR = 8; + SERVER_BUSY = 9; + OTHER_ERROR = 20; + } + + bool result = 1; + response_code code = 2; + bytes message = 3; +} + +message BlockReference { + int64 block_num = 1; + bytes block_hash = 2; +} + +// the api of tron's network such as node list. +service Network { + +}; + +message WitnessList { + repeated Witness witnesses = 1; +} +message ProposalList { + repeated Proposal proposals = 1; +} +message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; +} +message BlockList { + repeated Block block = 1; +} +message TransactionList { + repeated Transaction transaction = 1; +} + +// Gossip node list +message NodeList { + repeated Node nodes = 1; +} + +// Gossip node +message Node { + Address address = 1; +} + +// Gossip node address +message Address { + bytes host = 1; + int32 port = 2; +} + +message EmptyMessage { +} +message NumberMessage { + int64 num = 1; +} +message BytesMessage { + bytes value = 1; +} +message TimeMessage { + int64 beginInMilliseconds = 1; + int64 endInMilliseconds = 2; +} +message BlockLimit { + int64 startNum = 1; + int64 endNum = 2; +} +message TransactionLimit { + bytes transactionId = 1; + int64 limitNum = 2; +} +message AccountPaginated { + Account account = 1; + int64 offset = 2; + int64 limit = 3; +} +message TimePaginatedMessage { + TimeMessage timeMessage = 1; + int64 offset = 2; + int64 limit = 3; +} +//deprecated +message AccountNetMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; +} +message AccountResourceMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; + + int64 CpuUsed = 13; + int64 CpuLimit = 14; + int64 TotalCpuLimit = 15; + int64 TotalCpuWeight = 16; + + int64 storageUsed = 21; + int64 storageLimit = 22; +} + +message PaginatedMessage { + int64 offset = 1; + int64 limit = 2; +} + +message EasyTransferMessage { + bytes passPhrase = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferByPrivateMessage { + bytes privateKey = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferResponse { + Transaction transaction = 1; + Return result = 2; + bytes txid = 3; //transaction id = sha256(transaction.rowdata) +} + +message AddressPrKeyPairMessage { + string address = 1; + string privateKey = 2; +} + +message TransactionExtention { + Transaction transaction = 1; + bytes txid = 2; //transaction id = sha256(transaction.rowdata) + repeated bytes constant_result = 3; + Return result = 4; +} + +message BlockExtention { + repeated TransactionExtention transactions = 1; + BlockHeader block_header = 2; + bytes blockid = 3; +} + +message BlockListExtention { + repeated BlockExtention block = 1; +} + +message TransactionListExtention { + repeated TransactionExtention transaction = 1; +} \ No newline at end of file diff --git a/core/Contract.proto b/core/Contract.proto new file mode 100644 index 000000000..96417ba0a --- /dev/null +++ b/core/Contract.proto @@ -0,0 +1,199 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Contract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message AccountCreateContract { + bytes owner_address = 1; + bytes account_address = 2; + AccountType type = 3; +} + +// Update account name. Account name is not unique now. +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} + +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + +message TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 amount = 3; +} + +message TransferAssetContract { + bytes asset_name = 1; + bytes owner_address = 2; + bytes to_address = 3; + int64 amount = 4; +} + + +message VoteAssetContract { + bytes owner_address = 1; + repeated bytes vote_address = 2; + bool support = 3; + int32 count = 5; +} + +message VoteWitnessContract { + message Vote { + bytes vote_address = 1; + int64 vote_count = 2; + } + bytes owner_address = 1; + repeated Vote votes = 2; + bool support = 3; +} + +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + +message WitnessCreateContract { + bytes owner_address = 1; + bytes url = 2; +} + +message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; +} + +message AssetIssueContract { + message FrozenSupply { + int64 frozen_amount = 1; + int64 frozen_days = 2; + } + bytes owner_address = 1; + bytes name = 2; + bytes abbr = 3; + int64 total_supply = 4; + repeated FrozenSupply frozen_supply = 5; + int32 trx_num = 6; + int32 num = 8; + int64 start_time = 9; + int64 end_time = 10; + int64 order = 11; // the order of tokens of the same name + int32 vote_score = 16; + bytes description = 20; + bytes url = 21; + int64 free_asset_net_limit = 22; + int64 public_free_asset_net_limit = 23; + int64 public_free_asset_net_usage = 24; + int64 public_latest_free_net_time = 25; +} + +message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; // the namekey of target asset, include name and order + int64 amount = 4; // the amount of drops +} + + +enum ResourceCode { + BANDWIDTH = 0x00; + CPU = 0x01; +} + +message FreezeBalanceContract { + bytes owner_address = 1; + int64 frozen_balance = 2; + int64 frozen_duration = 3; + + ResourceCode resource = 10; +} + +message UnfreezeBalanceContract { + bytes owner_address = 1; + + ResourceCode resource = 10; +} + +message UnfreezeAssetContract { + bytes owner_address = 1; +} + +message WithdrawBalanceContract { + bytes owner_address = 1; +} + +message UpdateAssetContract { + bytes owner_address = 1; + bytes description = 2; + bytes url = 3; + int64 new_limit = 4; + int64 new_public_limit = 5; +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} \ No newline at end of file diff --git a/core/Discover.proto b/core/Discover.proto new file mode 100644 index 000000000..4cc0d83b0 --- /dev/null +++ b/core/Discover.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package protocol; + + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Discover"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; +} + +message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; +} + +message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; +} + +message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; +} + +message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; +} + +message BackupMessage { + bool flag = 1; + int32 priority = 2; +} \ No newline at end of file diff --git a/core/Tron.proto b/core/Tron.proto new file mode 100644 index 000000000..914d1d6b0 --- /dev/null +++ b/core/Tron.proto @@ -0,0 +1,450 @@ +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "core/Discover.proto"; + +package protocol; + + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Protocol"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +enum AccountType { + Normal = 0; + AssetIssue = 1; + Contract = 2; +} + +// AccountId, (name, address) use name, (null, address) use address, (name, null) use name, +message AccountId { + bytes name = 1; + bytes address = 2; +} + +// vote message +message Vote { + // the super rep address + bytes vote_address = 1; + // the vote num to this super rep. + int64 vote_count = 2; +} + +// Proposal +message Proposal { + int64 proposal_id = 1; + bytes proposer_address = 2; + map parameters = 3; + int64 expiration_time = 4; + int64 create_time = 5; + repeated bytes approvals = 6; + enum State { + PENDING = 0; + DISAPPROVED = 1; + APPROVED = 2; + CANCELED = 3; + } + State state = 7; +} + +message ChainParameters { + repeated ChainParameter chainParameter = 1; + message ChainParameter { + string key = 1; + int64 value = 2; + } +} + +/* Account */ +message Account { + /* frozen balance */ + message Frozen { + int64 frozen_balance = 1; // the frozen trx balance + int64 expire_time = 2; // the expire time + } + bytes account_name = 1; + AccountType type = 2; + // the create address + bytes address = 3; + // the trx balance + int64 balance = 4; + // the votes + repeated Vote votes = 5; + // the other asset owned by this account + map asset = 6; + // latest asset operation time + + // the frozen balance + repeated Frozen frozen = 7; + // bandwidth, get from frozen + int64 net_usage = 8; + + // this account create time + int64 create_time = 0x09; + // this last operation time, including transfer, voting and so on. //FIXME fix grammar + int64 latest_opration_time = 10; + // witness block producing allowance + int64 allowance = 0x0B; + // last withdraw time + int64 latest_withdraw_time = 0x0C; + // not used so far + bytes code = 13; + bool is_witness = 14; + bool is_committee = 15; + // frozen asset(for asset issuer) + repeated Frozen frozen_supply = 16; + // asset_issued_name + bytes asset_issued_name = 17; + map latest_asset_operation_time = 18; + + int64 free_net_usage = 19; + map free_asset_net_usage = 20; + int64 latest_consume_time = 21; + int64 latest_consume_free_time = 22; + + bytes account_id = 23; + + message AccountResource { + // cpu resource, get from frozen + int64 cpu_usage = 1; + // the frozen balance for cpu + Frozen frozen_balance_for_cpu = 2; + int64 latest_consume_time_for_cpu = 3; + + // storage resource, get from market + int64 storage_limit = 6; + int64 storage_usage = 7; + int64 latest_exchange_storage_time = 8; + } + AccountResource account_resource = 26; + + bytes codeHash = 30; +} + +message authority { + AccountId account = 1; + bytes permission_name = 2; +} + +message permission { + AccountId account = 1; +} + +// Witness +message Witness { + bytes address = 1; + int64 voteCount = 2; + bytes pubKey = 3; + string url = 4; + int64 totalProduced = 5; + int64 totalMissed = 6; + int64 latestBlockNum = 7; + int64 latestSlotNum = 8; + bool isJobs = 9; +} + +// Vote Change +message Votes { + bytes address = 1; + repeated Vote old_votes = 2; + repeated Vote new_votes = 3; +} + +// Transcation + +message TXOutput { + int64 value = 1; + bytes pubKeyHash = 2; +} + +message TXInput { + message raw { + bytes txID = 1; + int64 vout = 2; + bytes pubKey = 3; + } + raw raw_data = 1; + bytes signature = 4; +} + +message TXOutputs { + repeated TXOutput outputs = 1; +} + +message ResourceReceipt { + enum code { + SUCCESS = 0; + FAILED = 1; + } + int64 cpu_usage = 1; + int64 cpu_fee = 2; + int64 net_usage = 3; + int64 net_fee = 4; + int64 storage_delta = 5; + int64 storage_fee = 6; +} + +message Transaction { + message Contract { + enum ContractType { + AccountCreateContract = 0; + TransferContract = 1; + TransferAssetContract = 2; + VoteAssetContract = 3; + VoteWitnessContract = 4; + WitnessCreateContract = 5; + AssetIssueContract = 6; + WitnessUpdateContract = 8; + ParticipateAssetIssueContract = 9; + AccountUpdateContract = 10; + FreezeBalanceContract = 11; + UnfreezeBalanceContract = 12; + WithdrawBalanceContract = 13; + UnfreezeAssetContract = 14; + UpdateAssetContract = 15; + ProposalCreateContract = 16; + ProposalApproveContract = 17; + ProposalDeleteContract = 18; + SetAccountIdContract = 19; + CustomContract = 20; + BuyStorageContract = 21; + BuyStorageBytesContract = 22; + SellStorageContract = 23; + CreateSmartContract = 30; + TriggerSmartContract = 31; + GetContract = 32; + UpdateSettingContract = 33; + } + ContractType type = 1; + google.protobuf.Any parameter = 2; + bytes provider = 3; + bytes ContractName = 4; + + } + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + + message raw { + bytes ref_block_bytes = 1; + int64 ref_block_num = 3; + bytes ref_block_hash = 4; + int64 expiration = 8; + repeated authority auths = 9; + // data not used + bytes data = 10; + //only support size = 1, repeated list here for extension + repeated Contract contract = 11; + // scripts not used + bytes scripts = 12; + int64 timestamp = 14; + int64 fee_limit = 18; + } + + raw raw_data = 1; + // only support size = 1, repeated list here for muti-sig extension + repeated bytes signature = 2; + repeated Result ret = 5; +} + +message TransactionInfo { + message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + } + bytes id = 1; + int64 fee = 2; + int64 blockNumber = 3; + int64 blockTimeStamp = 4; + repeated bytes contractResult = 5; + bytes contract_address = 6; + ResourceReceipt receipt = 7; + repeated Log log = 8; +} + +message Transactions { + repeated Transaction transactions = 1; +} + +message TransactionSign { + Transaction transaction = 1; + bytes privateKey = 2; +} + +message BlockHeader { + message raw { + int64 timestamp = 1; + bytes txTrieRoot = 2; + bytes parentHash = 3; + //bytes nonce = 5; + //bytes difficulty = 6; + int64 number = 7; + int64 witness_id = 8; + bytes witness_address = 9; + } + raw raw_data = 1; + bytes witness_signature = 2; +} + +// block +message Block { + repeated Transaction transactions = 1; + BlockHeader block_header = 2; +} + +message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; +} + +// Inventory +message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; +} + +message Inventory { + enum InventoryType { + TRX = 0; + BLOCK = 1; + } + InventoryType type = 1; + repeated bytes ids = 2; +} + +message Items { + enum ItemType { + ERR = 0; + TRX = 1; + BLOCK = 2; + BLOCKHEADER = 3; + } + + ItemType type = 1; + repeated Block blocks = 2; + repeated BlockHeader block_headers = 3; + repeated Transaction transactions = 4; +} + +// DynamicProperties +message DynamicProperties { + int64 last_solidity_block_num = 1; +} + +enum ReasonCode { + REQUESTED = 0x00; + BAD_PROTOCOL = 0x02; + TOO_MANY_PEERS = 0x04; + DUPLICATE_PEER = 0x05; + INCOMPATIBLE_PROTOCOL = 0x06; + NULL_IDENTITY = 0x07; + PEER_QUITING = 0x08; + UNEXPECTED_IDENTITY = 0x09; + LOCAL_IDENTITY = 0x0A; + PING_TIMEOUT = 0x0B; + USER_REASON = 0x10; + RESET = 0x11; + SYNC_FAIL = 0x12; + FETCH_FAIL = 0x13; + BAD_TX = 0x14; + BAD_BLOCK = 0x15; + FORKED = 0x16; + UNLINKABLE = 0x17; + INCOMPATIBLE_VERSION = 0x18; + INCOMPATIBLE_CHAIN = 0x19; + TIME_OUT = 0x20; + CONNECT_FAIL = 0x21; + TOO_MANY_PEERS_WITH_SAME_IP = 0x22; + UNKNOWN = 0xFF; +} + +message DisconnectMessage { + ReasonCode reason = 1; +} + +message HelloMessage { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + + Endpoint from = 1; + int32 version = 2; + int64 timestamp = 3; + BlockId genesisBlockId = 4; + BlockId solidBlockId = 5; + BlockId headBlockId = 6; +} + +message StorageItem { + bytes contract_address = 1; + map items = 2; +} + + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + bytes data = 6; + int64 consume_user_resource_percent = 7; + string name = 8; + +} \ No newline at end of file diff --git a/core/TronInventoryItems.proto b/core/TronInventoryItems.proto new file mode 100644 index 000000000..a82d2de45 --- /dev/null +++ b/core/TronInventoryItems.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "TronInventoryItems"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message InventoryItems { + int32 type = 1; + repeated bytes items = 2; +} \ No newline at end of file diff --git a/install-googleapis.sh b/install-googleapis.sh new file mode 100755 index 000000000..0d44f6108 --- /dev/null +++ b/install-googleapis.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger +go get -u github.com/golang/protobuf/protoc-gen-go + +wget http://central.maven.org/maven2/com/google/api/grpc/googleapis-common-protos/0.0.3/googleapis-common-protos-0.0.3.jar +jar xvf googleapis-common-protos-0.0.3.jar +cp -r google/ $HOME/protobuf/include/ +ls -l + + + diff --git a/install-protobuf.sh b/install-protobuf.sh new file mode 100755 index 000000000..b3a8cb5cb --- /dev/null +++ b/install-protobuf.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e +# check to see if protobuf folder is empty +if [ ! -d "$HOME/protobuf/lib" ]; then + wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.tar.gz + tar -xzvf protobuf-all-3.5.1.tar.gz + cd protobuf-3.5.1 && ./configure --prefix=$HOME/protobuf && make && make install +else + echo "Using cached directory." +fi From 8c97e3c94347375c2fb18df6e36c2c4a8d30bcfd Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Tue, 7 Aug 2018 12:20:27 +0800 Subject: [PATCH 002/445] Merge commit 'fd8f26942698302f212324b5f6da9212ab1f7016' into feature/delegate_resource --- core/Contract.proto | 2 ++ core/Tron.proto | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 96417ba0a..f0371ed05 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -131,12 +131,14 @@ message FreezeBalanceContract { int64 frozen_duration = 3; ResourceCode resource = 10; + bytes to_address = 11; } message UnfreezeBalanceContract { bytes owner_address = 1; ResourceCode resource = 10; + bytes to_address = 11; } message UnfreezeAssetContract { diff --git a/core/Tron.proto b/core/Tron.proto index 914d1d6b0..d4b9dcd7a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -61,6 +61,7 @@ message Account { message Frozen { int64 frozen_balance = 1; // the frozen trx balance int64 expire_time = 2; // the expire time + bytes to_address = 3; // resources are delegated to this address } bytes account_name = 1; AccountType type = 2; @@ -108,7 +109,7 @@ message Account { // cpu resource, get from frozen int64 cpu_usage = 1; // the frozen balance for cpu - Frozen frozen_balance_for_cpu = 2; + repeated Frozen frozen_balance_for_cpu = 2; int64 latest_consume_time_for_cpu = 3; // storage resource, get from market From 1787addf442fcce9e2c09e2b37b880427798b45f Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 7 Aug 2018 17:07:06 +0800 Subject: [PATCH 003/445] change protocol --- .gitignore | 4 + .travis.yml | 24 + Chinese version of TRON Protocol document.md | 696 +++++++++++++++ English version of TRON Protocol document.md | 734 ++++++++++++++++ LICENSE | 165 ++++ README.md | 11 + api/api.proto | 855 +++++++++++++++++++ core/Contract.proto | 199 +++++ core/Discover.proto | 44 + core/Tron.proto | 453 ++++++++++ core/TronInventoryItems.proto | 12 + install-googleapis.sh | 14 + install-protobuf.sh | 10 + 13 files changed, 3221 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Chinese version of TRON Protocol document.md create mode 100644 English version of TRON Protocol document.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 api/api.proto create mode 100644 core/Contract.proto create mode 100644 core/Discover.proto create mode 100644 core/Tron.proto create mode 100644 core/TronInventoryItems.proto create mode 100755 install-googleapis.sh create mode 100755 install-protobuf.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1554c5edd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# IDEA +.idea +*iml +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..34fa71b3d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: ruby + +cache: + directories: + - $HOME/protobuf + +sudo: false + +before_install: + - bash install-protobuf.sh + - bash install-googleapis.sh + +# check what has been installed by listing contents of protobuf folder +before_script: + - ls -R $HOME/protobuf + +# let's use protobuf +script: + - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto + + - ls -l \ No newline at end of file diff --git a/Chinese version of TRON Protocol document.md b/Chinese version of TRON Protocol document.md new file mode 100644 index 000000000..283bd5fb8 --- /dev/null +++ b/Chinese version of TRON Protocol document.md @@ -0,0 +1,696 @@ +# TRON protobuf protocol + +## TRON使用Google protobuf协议,协议内容涉及到账户,区块,传输多个层面。 + ++ 账户有基本账户、资产发布账户和合约账户三种类型。一个账户包含:账户名称,账户类型,地址,余额,投票,其他资产6种属性。 ++ 更进一步的,基本账户可以申请成为验证节点,验证节点具有额外的属性,投票统计数目,公钥,URL,以及历史表现等参数。 + + 3种`Account`类型:`Normal`,`AssetIssue`,`Contract`。 + + enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2;
 + } + + 一个`Account`包含7种参数: + `account_name`:该账户的名称——比如: ”_SicCongsAccount_”。 + `type`:该账户的类型——比如: _0_ 代表的账户类型是`Normal`。 + `balance`:该账户的TRX余额——比如:_4213312_。 + `votes`:账户所得投票数——比如:_{(“0x1b7w…9xj3”,323),(“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_。 + `asset`:除TRX以外账户上的其他资产——比如:_{<”WishToken”,66666>,<”Dogie”,233>}_。 + `latest_operation_time`: 该账户的最新活跃时间。 + + // Account
 + message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 + }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6;
 + int64 latest_operation_time = 10; + } + + 一个`Witness`包含8种参数: + `address`:验证节点的地址——比如:_“0xu82h…7237”_。 + `voteCount`:验证节点所得投票数——比如:_234234_。 + `pubKey`:验证节点的公钥——比如:_“0xu82h…7237”_。 + `url`:验证节点的url链接。 + `totalProduce`:验证节点产生的区块数——比如:_2434_。 + `totalMissed`:验证节点丢失的区块数——比如:_7_。 + `latestBlockNum`:最新的区块高度——比如:_4522_。 + `isJobs`:布尔表类型标志位。 + + // Witness
 + message Witness {
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7; + bool isJobs = 9; + 
} + ++ 一个区块由区块头和多笔交易构成。区块头包含时间戳,交易字典树的根,父哈希,签名等区块基本信息。 + + 一个`block`包含`transactions`和`block_header`。 + `transactions`:区块里的交易信息。 + `block_header`:区块的组成部分之一。 + + // block
 + message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2; + 
} + + `BlockHeader` 包括`raw_data`和`witness_signature`。 + `raw_data`:`raw`信息。 + `witness_signature`:区块头到验证节点的签名。 + + message `raw`包含6种参数: + `timestamp`:该消息体的时间戳——比如:_14356325_。 + `txTrieRoot`:Merkle Tree的根——比如:_“7dacsa…3ed”_。 + `parentHash`:上一个区块的哈希值——比如:_“7dacsa…3ed”_。 + `number`:区块高度——比如:_13534657_。 + `witness_id`:验证节点的id——比如:_“0xu82h…7237”_。 + `witness_address`:验证节点的地址——比如:_“0xu82h…7237”_。 + + message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2; + 
} + + 消息体 `ChainInventory` 包括 `BlockId` 和 `remain_num`。 + `BlockId`: block的身份信息。 + `remain_num`:在同步过程中,剩余的区块数量。 + + A `BlockId` contains 2 parameters: + `hash`: 该区块的哈希值。 + `number`: 哈希值和高度即为当前区块块号。 + + message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; + } + ++ 交易合约有多种类型,包括账户创建合约、账户更新合约、转账合约、转账断言合约、资产投票合约、见证节点投票合约、见证节点创建合约、见证节点更新合约、资产发布合约、参与资产发布和与部署合约11种类型。 + + `AccountCreatContract`包含3种参数: + `type`:账户类型——比如:_0_ 代表的账户类型是`Normal`。 + `account_name`: 账户名称——比如: _"SiCongsaccount”_。 + `owner_address`:合约持有人地址——比如: _“0xu82h…7237”_。 + + message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 + } + `AccountUpdateContract`包含2种参数: + `account_name`: 账户名称——比如: _"SiCongsaccount”_。 + `owner_address`:合约持有人地址——比如: _“0xu82h…7237”_。 + + message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; + } + + `TransferContract`包含3种参数: + `amount`:TRX数量——比如:_12534_。 + `to_address`: 接收方地址——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + + message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; + 
} + + `TransferAssetContract`包含4种参数: + `asset_name`:资产名称——比如:_”SiCongsaccount”_。 + `to_address`:接收方地址——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `amount`:目标资产数量——比如:_12353_。 + + message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 + } + + `VoteAssetContract`包含4种参数: + `vote_address`:投票人地址——比如:_“0xu82h…7237”_。 + `support`:投票赞成与否——比如:_true_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `count`:投票数目——比如:_2324234_。 + + message VoteAssetContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5; + } + + `VoteWitnessContract`包含4种参数: + `vote_address`:投票人地址——比如:_“0xu82h…7237”_。 + `support`:投票赞成与否——比如:_true_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `count`:投票数目——比如:_32632_。 + + message VoteWitnessContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5;
 + } + + `WitnessCreateContract`包含3种参数: + `private_key`:合约的私钥——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `url`:合约的url链接。 + + message WitnessCreateContract {
 + bytes owner_address = 1;
 + bytes private_key = 2;
 + bytes url = 12; + 
} + + `WitnessUpdateContract`包含2种参数: + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `update_url`:合约的url链接。 + + message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; + } + + `AssetIssueContract`包含11种参数: + `name`:合约名称——比如:_“SiCongcontract”_。 + `total_supply`:合约的赞成总票数——比如:_100000000_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `trx_num`:对应TRX数量——比如:_232241_。 + `num`: 对应的自定义资产数目。 + `start_time`:开始时间——比如:_20170312_。 + `end_time`:结束时间——比如:_20170512_。 + `vote_score`:合约的评分——比如:_12343_。 + `description`:合约的描述——比如:_”trondada”_。 + `url`:合约的url地址链接。 + + message AssetIssueContract {
 + bytes owner_address = 1;
 + bytes name = 2;
 + int64 total_supply = 4;
 + int32 trx_num = 6;
 + int32 num = 8;
 + int64 start_time = 9;
 + int64 end_time = 10;
 + int32 vote_score = 16;
 + bytes description = 20;
 + bytes url = 21;
 + } + + `ParticipateAssetIssueContract`包含4种参数: + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `to_address`:接收方地址——比如:_“0xu82h…7237”_。 + `asset_name`: 目标资产的名称。 + `amount`: 小部分数量。 + + `DeployContract`包含2种参数: + `script`:脚本。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + + message DeployContract {
 + bytes owner_address = 1;
 + bytes script = 2;
 + } + + 消息体 `Result` 包含 `fee` and `ret`2个参数. + `ret`: 交易结果。 + `fee`: 交易扣除的费用。 + + `code`是`ret`的类型定义,有`SUCCESS`和`FAILED`两种类型。 + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + ++ 每一个交易还包含多个输入与多个输出,以及其他一些相关属性。其中交易内的输入,交易本身,区块头均需签名。 + + 消息体 `Transaction`包括`raw_data`和`signature`。 + `raw_data`: 消息体`raw`。 + `signature`: 所有输入节点的签名。 + + `raw_data`包含8种参数: + `type`:消息体raw的交易类型。 + `vin`: 输入值。 + `vout`: 输出值。 + `expiration`:过期时间——比如:_20170312_。 + `data`: 数据。 + `contract`: 该交易内的合约。 + `script`: 脚本。 + `timestamp`:该消息体的时间戳。 + + 消息体 `Contract`包含`type`和`parameter`。 + `type`:合约的类型。 + `parameter`:任意参数。 + + 有八种账户类型合约:`AccountCreateContract`,`TransferContract`,`TransferAssetContract`,`VoteAssetContract`,`VoteWitnessContract`,`WitnessCreateContract`,`AssetIssueContract` 和`DeployContract`。 + + `TransactionType`包括`UtxoType`和`ContractType`。 + + message Transaction {
 + enum TranscationType {
 + UtxoType = 0;
 + ContractType = 1;
 + }
 + message Contract {
 + enum ContractType {
 + AccountCreateContract = 0;
 + TransferContract = 1;
 + TransferAssetContract = 2;
 + VoteAssetContract = 3;
 + VoteWitnessContract = 4;
 + WitnessCreateContract = 5;
 + AssetIssueContract = 6;
 + DeployContract = 7;
 + }
 + ContractType type = 1;
 + google.protobuf.Any parameter = 2;
 + }
 + message raw {
 + TranscationType type = 2;
 + repeated TXInput vin = 5;
 + repeated TXOutput vout = 7;
 + int64 expiration = 8;
 + bytes data = 10;
 + repeated Contract contract = 11;
 + bytes scripts = 16;
 + in64 timestamp = 17; + }
 + raw raw_data = 1;
 + repeated bytes signature = 5;
 + } + + 消息体 `TXOutputs`由`outputs`构成。 + `outputs`: 元素为`TXOutput`的数组。 + + message TXOutputs {
 + repeated TXOutput outputs = 1; + 
} + + 消息体 `TXOutput`包括`value`和`pubKeyHash`。 + `value`:输出值。 + `pubKeyhash`:公钥的哈希。 + + message TXOutput {
 + int64 value = 1;
 + bytes pubKeyHash = 2; + 
} + + 消息体 `TXIutput`包括`raw_data`和`signature`。 + `raw_data`:消息体`raw`。 + `signature`:`TXInput`的签名。 + + 消息体 `raw`包含`txID`,`vout`和 `pubKey`。 + `txID`:交易ID。 + `Vout`:上一个输出的值。 + `pubkey`:公钥。 + + message TXInput {
 + message raw {
 + bytes txID = 1;
 + int64 vout = 2;
 + bytes pubKey = 3;
 + }
 + raw raw_data = 1;
 + bytes signature = 4;
} + ++ 传输涉及的协议Inventory主要用于传输中告知接收方传输数据的清单。 + + `Inventory`包括`type`和`ids`。 + `type`:清单类型——比如:_0_ 代表`TRX`。 + `ids`:清单中的物品ID。 + + `InventoryType`包含`TRX`和 `BLOCK`。 + `TRX`:交易。 + `BLOCK`:区块。 + + // Inventory
 + message Inventory {
 + enum InventoryType {
 + TRX = 0;
 + BLOCK = 1;
 + }
 + InventoryType type = 1;
 + repeated bytes ids = 2; + 
} + + 消息体 `Items`包含4种参数: + `type`:物品类型——比如:_1_ 代表 `TRX`。 + `blocks`:物品中区块。 + `blockheaders`:区块头。 + `transactions`:交易。 + + `Items`有四种类型,分别是 `ERR`, `TRX`,`BLOCK` 和`BLOCKHEADER`。 + `ERR`:错误。 + `TRX`:交易。 + `BLOCK`:区块。 + `BLOCKHEADER`:区块头。 + + message Items {
 + enum ItemType {
 + ERR = 0;
 + TRX = 1;
 + BLOCK = 2;
 + BLOCKHEADER = 3;
 + }
 + ItemType type = 1;
 + repeated Block blocks = 2;
 + repeated BlockHeader block_headers = 3;
 + repeated Transaction transactions = 4;
 + } + + `Inventory`包含`type`和`items`。 + `type`:物品种类。 + `items`:物品清单。 + + message InventoryItems {
 + int32 type = 1;
 + repeated bytes items = 2;
 + } + + 消息体 `BlockInventory` 包含 `type`。 + `type`: 清单种类. + + 有三种类型:`SYNC`, `ADVTISE`, `FETCH`。 + + // Inventory + message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + 消息体 `BlockId` 包括 `ids` and `type`。 + `ids`: 区块身份信息。 + `type`: 区块类型。 + + `ids` 包含2种参数: + `hash`: 区块的哈希值。 + `number`: 哈希值和区块高度即为当前区块号。 + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; + } + + `ReasonCode` 有15种可能断开的原因: + `REQUESTED` + `TCP_ERROR` + `BAD_PROTOCOL` + `USELESS_PEER` + `TOO_MANY_PEERS` + `DUPLICATE_PEER` + `INCOMPATIBLE_PROTOCOL` + `NULL_IDENTITY` + `PEER_QUITING` + `UNEXPECTED_IDENTITY` + `LOCAL_IDENTITY` + `PING_TIMEOU` + `USER_REASON` + `RESET` + `UNKNOWN` + + enum ReasonCode { + REQUESTED = 0; + TCP_ERROR = 1; + BAD_PROTOCOL = 2; + USELESS_PEER = 3; + TOO_MANY_PEERS = 4; + DUPLICATE_PEER = 5; + INCOMPATIBLE_PROTOCOL = 6; + NULL_IDENTITY = 7; + PEER_QUITING = 8; + UNEXPECTED_IDENTITY = 9; + LOCAL_IDENTITY = 10; + PING_TIMEOUT = 11; + USER_REASON = 12; + RESET = 16; + UNKNOWN = 255; + } + + 消息体`DisconnectMessage`包含`reason`。 + `DisconnectMessage`:断开连接是的消息。 + `reason`:断开连接时的原因。 + + 消息体`HelloMessage`包含2个参数: + `from`请:求建立连接的节点。 + `version`:建立连接的节点。 + ++ 钱包服务RPC和区块链浏览器。 + + `Wallet`钱包服务包含多个RPC。 + __`Getbalance`__:获取`Account`的余额。 + __`CreatTransaction`__:通过`TransferContract`创建交易。 + __`BroadcastTransaction`__:广播`Transaction`。 + __`CreateAccount`__:通过`AccountCreateContract`创建账户。 + __`CreatAssetIssue`__:通过`AssetIssueContract`发布一个资产。 + __`ListAccounts`__:通过`ListAccounts`查看账户列表。 + __`UpdateAccount`__:通过`UpdateAccountContract`发布一个资产。 + __`VoteWitnessAccount`__:通过`VoteWitnessContract`发布一个资产。 + __`WitnessList`__:通过`WitnessList`查看见证节点列表。 + __`UpdateWitness`__:通过`WitnessUpdateContract`发布一个资产。 + __`CreateWitness`__:通过`WitnessCreateContract`发布一个资产。 + __`TransferAsset`__:通过`TransferAssetContract`发布一个资产。 + __`ParticipateAssetIssue`__:通过`ParticipateAssetIssueContract`发布一个资产。 + __`ListNodes`__:通过`ListNodes`查看节点列表。 + __`GetAssetIssueList`__:通过`GetAssetIssueList`查看资产发布节点列表。 + __`GetAssetIssueByAccount`__:通过`Account`获取发行资产。 + __`GetAssetIssueByName`__:通过`Name`获取发行资产。 + __`GetNowBlock`__:获取区块。 + __`GetBlockByNum`__:根据块号获取区块。 + __`TotalTransaction`__:查看总交易量。 + + service Wallet { + + rpc GetAccount (Account) returns (Account) { + + }; + + rpc CreateTransaction (TransferContract) returns (Transaction) { + + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + + }; + + rpc ListAccounts (EmptyMessage) returns (AccountList) { + + }; + + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + + }; + + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + + }; + + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + + }; + + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + + }; + + rpc WitnessList (EmptyMessage) returns (WitnessList) { + + }; + + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + + }; + + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + + }; + + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + + } + + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + + } + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + + } + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + + } + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + + } + }; + + `AccountList`: 区块链浏览器中的账户列表。 + 消息体 `AccountList` 包含1个参数: + `account`: + + message AccountList { + repeated Account accounts = 1; + } + + `WitnessList`:区块链浏览器中的见证节点列表。 + 消息体 `WitnessList` 包含1个参数: + `witnesses`: + + message WitnessList { + repeated Witness witnesses = 1; + } + + `AssetIssueList`:区块链浏览器中的发布资产列表。 + 消息体 `AssetIssueList` 包含1个参数: + `assetIssue`: + + message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; + } + + + `NodeList`: 分布节点图中的节点列表。 + 消息体 `NodeList` 包含1个参数: + `nodes`: + + message NodeList { + repeated Node nodes = 1; + } + + `Address`: 节点地址。 + 消息体`Address` 包含2个参数: + `host`:节点所有者。 + `port`:节点的端口号。 + + message Address { + bytes host = 1; + int32 port = 2; + } + + + 消息体`Return`只含有一个参数: + `result`: 布尔表类型标志位。 + + message `Return` {
 + bool result = 1;
 + } + ++ 网络UDP消息结构。 + + `Endpoint`:网络中节点信息存储结构. + 消息体`Endpoint` 包含3个参数: + `address`:节点地址。 + `port`:端口号。 + `nodeId`: 节点ID信息。 + + message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; + } + + `PingMessage`:节点建立连接时所发送的消息。 + 消息体`PingMessage` 包含4个参数: + `from`:消息来自的节点。 + `to`: 消息发送的节点。 + `version`: 网络版本。 + `timestamp`:消息创建时的时间戳。 + + message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; + } + + `PongMessage`:连接建立成功时的回复消息。 + 消息体`PongMessage` 包含3个参数: + `from`:消息来自的节点。 + `echo`: + `timestamp`:消息创建时的时间戳。 + + message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; + } + + `FindNeighbours`:节点查询相邻节点时所发送的消息。 + 消息体`FindNeighbours` 包含3个参数: + `from`: 消息来自的节点。 + `targetId`: 目标节点的信息。 + `timestamp`: 消息创建时的时间戳。 + + message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; + } + + `Neighbour`:相邻接点回复消息。 + 消息体`Neighbours` 包含3个参数: + `from`: 消息来自的节点。 + `neighbours`: 相邻节点。 + `timestamp`: 消息创建时的时间戳。 + + message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; + } + +# 详细的协议见附属文件。详细协议随着程序的迭代随时都可能发生变化,请以最新的版本为准。 \ No newline at end of file diff --git a/English version of TRON Protocol document.md b/English version of TRON Protocol document.md new file mode 100644 index 000000000..94498fb1c --- /dev/null +++ b/English version of TRON Protocol document.md @@ -0,0 +1,734 @@ + +# Protobuf protocol + +## The protocol of TRON is defined by Google Protobuf and contains a range of layers, from account, block to transfer. + ++ There are 3 types of account—basic account, asset release account and contract account, and attributes included in each account are name, types, address, balance and related asset. ++ A basic account is able to apply to be a validation node, which has serval parameters, including extra attributes, public key, URL, voting statistics, history performance, etc. + + There are three different `Account types`: `Normal`, `AssetIssue`, `Contract`. + + enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2; + 
} + + An `Account` contains 7 parameters: + `account_name`: the name for this account – e.g. “_BillsAccount_”. + `type`: what type of this account is – e.g. _0_ stands for type `Normal`. + `balance`: balance of this account – e.g. _4213312_. + `votes`: received votes on this account – e.g. _{(“0x1b7w…9xj3”,323), (“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_. + `asset`: other assets expect TRX in this account – e.g. _{<“WishToken”,66666>,<”Dogie”,233>}_. + `latest_operation_time`: the latest operation time of this account. + + // Account
 + message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6; + int64 latest_operation_time = 10;
 + } + + A `Witness` contains 8 parameters: + `address`: the address of this witness – e.g. “_0xu82h…7237_”. + `voteCount`: number of received votes on this witness – e.g. _234234_. + `pubKey`: the public key for this witness – e.g. “_0xu82h…7237_”. + `url`: the url for this witness – e.g. “_https://www.noonetrust.com_”. + `totalProduced`: the number of blocks this witness produced – e.g. _2434_. + `totalMissed`: the number of blocks this witness missed – e.g. _7_. + `latestBlockNum`: the latest height of block – e.g. _4522_. + `isjobs`: a bool flag. + + // Witness
 + message Witness{
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7;
 + bool isJobs = 9; + } + ++ A block typically contains transaction data and a blockheader, which is a list of basic block information, including timestamp, signature, parent hash, root of Merkle tree and so on. + + A block contains `transactions` and a `block_header`. + `transactions`: transaction data of this block. + `block_header`: one part of a block. + + // block + 
message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2;
 + } + + A `BlockHeader` contains `raw_data` and `witness_signature`. + `raw_data`: a `raw` message. + `witness_signature`: signature for this block header from witness node. + + A message `raw` contains 6 parameters: + `timestamp`: timestamp of this message – e.g. _14356325_. + `txTrieRoot`: the root of Merkle Tree in this block – e.g. “_7dacsa…3ed_.” + `parentHash`: the hash of last block – e.g. “_7dacsa…3ed_.” + `number`: the height of this block – e.g. _13534657_. + `witness_id`: the id of witness which packed this block – e.g. “_0xu82h…7237_”. + `witness_address`: the adresss of the witness packed this block – e.g. “_0xu82h…7237_”. + + message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2;
 + } + + message `ChainInventory` contains `BlockId` and `remain_num`. + `BlockId`: the identification of block. + `remain_num`:the remain number of blocks in the synchronizing process. + + A `BlockId` contains 2 parameters: + `hash`: the hash of block. + `number`: the hash and height of block. + + message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; + } + ++ Transaction contracts mainly includes account create contract, account update contract transfer contract, transfer asset contract, vote asset contract, vote witness contract, witness creation contract, witness update contract, asset issue contract, participate asset issue contract and deploy contract. + + An `AccountCreateContract` contains 3 parameters: + `type`: What type this account is – e.g. _0_ stands for `Normal`. + `account_name`: the name for this account – e.g.”_Billsaccount_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 + } + + A `AccountUpdateContract` contains 2 paremeters: + `account_name`: the name for this account – e.g.”_Billsaccount_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; + } + + A `TransferContract` contains 3 parameters: + `amount`: the amount of TRX – e.g. _12534_. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; + } + + A `TransferAssetContract` contains 4 parameters: + `asset_name`: the name for asset – e.g.”_Billsaccount_”. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `amount`: the amount of target asset - e.g._12353_. + + message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 + } + + A `VoteAssetContract` contains 4 parameters: + `vote_address`: the voted address of the asset. + `support`: is the votes supportive or not – e.g. _true_. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `count`: the count number of votes- e.g. _2324234_. + + message VoteAssetContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5;
 + } + + A `VoteWitnessContract` contains 4 parameters: + `vote_address`: the addresses of those who voted. + `support`: is the votes supportive or not - e.g. _true_. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `count`: - e.g. the count number of vote – e.g. _32632_. + + message VoteWitnessContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5; + 
} + + A `WitnessCreateContract` contains 3 parameters: + `private_key`: the private key of contract– e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `url`: the url for the witness – e.g. “_https://www.noonetrust.com_”. + + message WitnessCreateContract {
 + bytes owner_address = 1;
 + bytes private_key = 2;
 + bytes url = 12;
 + } + + A `WitnessUpdateContract` contains 2 parameters: + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `update_url`: the url for the witness – e.g. “_https://www.noonetrust.com_”. + + message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; + } + + An `AssetIssueContract` contains 11 parameters: + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + `name`: the name for this contract – e.g. “Billscontract”. + `total_supply`: the maximum supply of this asset – e.g. _1000000000_. + `trx_num`: the number of TRONIX – e.g._232241_. + `num`: number of corresponding asset. + `start_time`: the starting date of this contract – e.g._20170312_. + `end_time`: the expiring date of this contract – e.g. _20170512_. + `vote_score`: the vote score of this contract received – e.g. _12343_. + `description`: the description of this contract – e.g.”_trondada_”. + `url`: the url of this contract – e.g. “_https://www.noonetrust.com_”. + + message AssetIssueContract {
 + bytes owner_address = 1;
 + bytes name = 2;
 + int64 total_supply = 4;
 + int32 trx_num = 6;
 + int32 num = 8;
 + int64 start_time = 9;
 + int64 end_time = 10;
 + int32 vote_score = 16;
 + bytes description = 20;
 + bytes url = 21;
 + } + + A `ParticipateAssetIssueContract` contains 4 parameters: + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `asset_name`: the name of target asset. + `amount`: the amount of drops. + + message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; + int64 amount = 4; + } + + A `DeployContract` contains 2 parameters: + `script`: the script of this contract. + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + + message DeployContract {
 + bytes owner_address = 1;
 + bytes script = 2; + 
} t + ++ Each transaction contains several TXInputs, TXOutputs and other related qualities. +Input, transaction and head block all require signature. + + message `Transaction` contains `raw_data` and `signature`. + `raw_data`: message `raw`. + `signature`: signatures form all input nodes. + + `raw` contains 8 parameters: + `type`: the transaction type of `raw` message. + `vin`: input values. + `vout`: output values. + `expiration`: the expiration date of transaction – e.g._20170312_. + `data`: data. + `contract`: contracts in this transaction. + `scripts`:scripts in the transaction. + `timestamp`: timestamp of this raw data – e.g. _14356325_. + + message `Contract` contains `type` and `parameter`. + `type`: what type of the message contract. + `parameter`: It can be any form. + + There are 8 different of contract types: `AccountCreateContract`, `TransferContract`, `TransferAssetContract`, `VoteAssetContract`, `VoteWitnessContract`,`WitnessCreateContract`, `AssetIssueContract` and `DeployContract`. + `TransactionType` have two types: `UtxoType` and `ContractType`. + + message Transaction {
 + enum TranscationType {
 + UtxoType = 0;
 + ContractType = 1;
 + }
 + message Contract {
 + enum ContractType {
 + AccountCreateContract = 0;
 + TransferContract = 1;
 + TransferAssetContract = 2;
 + VoteAssetContract = 3;
 + VoteWitnessContract = 4;
 + WitnessCreateContract = 5;
 + AssetIssueContract = 6;
 + DeployContract = 7;
 + WitnessUpdateContract = 8; + ParticipateAssetIssueContract = 9 + }
 + ContractType type = 1;
 + google.protobuf.Any parameter = 2;
 + }
 + message raw {
 + TranscationType type = 2;
 + repeated TXInput vin = 5;
 + repeated TXOutput vout = 7;
 + int64 expiration = 8;
 + bytes data = 10;
 + repeated Contract contract = 11;
 + bytes scripts = 16;
 + int64 timestamp = 17; + }
 + raw raw_data = 1;
 + repeated bytes signature = 5; + 
} + + message `TXOutputs` contains `outputs`. + `outputs`: an array of `TXOutput`. + + message TXOutputs {
 + repeated TXOutput outputs = 1;
 + } + + message `TXOutput` contains `value` and `pubKeyHash`. + `value`: output value. + `pubKeyHash`: Hash of public key + + message TXOutput {
 + int64 value = 1;
 + bytes pubKeyHash = 2;
 + } + + message `TXInput` contains `raw_data` and `signature`. + `raw_data`: a message `raw`. + `signature`: signature for this `TXInput`. + + message `raw` contains `txID`, `vout` and `pubKey`. + `txID`: transaction ID. + `vout`: value of last output. + `pubKey`: public key. + + message TXInput {
 + message raw {
 + bytes txID = 1;
 + int64 vout = 2;
 + bytes pubKey = 3;
 + }
 + raw raw_data = 1;
 + bytes signature = 4; + 
} + + message `Result` contains `fee` and `ret`. + `ret`: the state of transaction. + `fee`: the fee for transaction. + + `code` is definition of `ret` and contains 2 types:`SUCCESS` and `FAILED`. + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + ++ Inventory is mainly used to inform peer nodes the list of items. + + `Inventory` contains `type` and `ids`. + `type`: what type this `Inventory` is. – e.g. _0_ stands for `TRX`. + `ids`: ID of things in this `Inventory`. + + Two `Inventory` types: `TRX` and `BLOCK`. + `TRX`: transaction. + `BLOCK`: block. + + // Inventory
 + message Inventory {
 + enum InventoryType {
 + TRX = 0;
 + BLOCK = 1;
 + }
 + InventoryType type = 1;
 + repeated bytes ids = 2;
 + } + + message `Items` contains 4 parameters: + `type`: type of items – e.g. _1_ stands for `TRX`. + `blocks`: blocks in `Items` if there is any. + `block_headers`: block headers if there is any. + `transactions`: transactions if there is any. + + `Items` have four types: `ERR`, `TRX`, `BLOCK` and `BLOCKHEADER`. + `ERR`: error. + `TRX`: transaction. + `BLOCK`: block. + `BLOCKHEADER`: block header. + + message Items {
 + enum ItemType {
 + ERR = 0;
 + TRX = 1;
 + BLOCK = 2;
 + BLOCKHEADER = 3;
 + }
 + ItemType type = 1;
 + repeated Block blocks = 2;
 + repeated BlockHeader + block_headers = 3;
 + repeated Transaction transactions = 4; + } + + `InventoryItems` contains `type` and `items`. + `type`: what type of item. + `items`: items in an `InventoryItems`. + + message InventoryItems {
 + int32 type = 1;
 + repeated bytes items = 2; + 
} + + message `BlockInventory` contains `type`. + `type`: what type of inventory. + + There are 3 types:`SYNC`, `ADVTISE`, `FETCH`. + + // Inventory + message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + message `BlockId` contains `ids` and `type`. + `ids`: the identification of block. + `type`: what type of the block. + + `ids` contains 2 paremeters: + `hash`: the hash of block. + `number`: the hash and height of block. + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; + } + + `ReasonCode`: the type of reason. + + `ReasonCode` contains 15 types of disconnect reasons: + `REQUESTED` + `TCP_ERROR` + `BAD_PROTOCOL` + `USELESS_PEER` + `TOO_MANY_PEERS` + `DUPLICATE_PEER` + `INCOMPATIBLE_PROTOCOL` + `NULL_IDENTITY` + `PEER_QUITING` + `UNEXPECTED_IDENTITY` + `LOCAL_IDENTITY` + `PING_TIMEOUT` + `USER_REASON` + `RESET` + `UNKNOWN` + + enum ReasonCode { + REQUESTED = 0; + TCP_ERROR = 1; + BAD_PROTOCOL = 2; + USELESS_PEER = 3; + TOO_MANY_PEERS = 4; + DUPLICATE_PEER = 5; + INCOMPATIBLE_PROTOCOL = 6; + NULL_IDENTITY = 7; + PEER_QUITING = 8; + UNEXPECTED_IDENTITY = 9; + LOCAL_IDENTITY = 10; + PING_TIMEOUT = 11; + USER_REASON = 12; + RESET = 16; + UNKNOWN = 255; + } + + message`DisconnectMessage` contains `reason`. + `DisconnectMessage`: the message when disconnection occurs. + `reason`: the reason for disconnecting. + + message`HelloMessage` contains 2 parameters: + `HelloMessage`: the message for building connection. + `from`: the nodes that request for building connection. + `version`: the version when connection is built. + + + ++ Wallet Service RPC and blockchain explorer + + `Wallet` service contains several RPCs. + __`GetBalance`__ : + Return balance of an `Account`. + __`CreateTransaction`__ : + Create a transaction by giving a `TransferContract`. A Transaction containing a transaction creation will be returned. + __`BroadcastTransaction`__ : + Broadcast a `Transaction`. A `Return` will be returned indicating if broadcast is success of not. + __`CreateAccount`__ : + Create an account by giving a `AccountCreateContract`. + __`CreatAssetIssue`__ : + Issue an asset by giving a `AssetIssueContract`. + __`ListAccounts`__: + Check out the list of accounts by giving a `ListAccounts`. + __`UpdateAccount`__: + Issue an asset by giving a `UpdateAccountContract`. + __`VoteWitnessAccount`__: + Issue an asset by giving a `VoteWitnessContract`. + __`WitnessList`__: + Check out the list of witnesses by giving a `WitnessList`. + __`UpdateWitness`__: + Issue an asset by giving a `WitnessUpdateContract`. + __`CreateWitness`__: + Issue an asset by giving a `WitnessCreateContract`. + __`TransferAsset`__: + Issue an asset by giving a `TransferAssetContract`. + __`ParticipateAssetIssue`__: + Issue an asset by giving a `ParticipateAssetIssueContract`. + __`ListNodes`__: + Check out the list of nodes by giving a `ListNodes`. + __`GetAssetIssueList`__: + Get the list of issue asset by giving a `GetAssetIssueList`. + __`GetAssetIssueByAccount`__: + Get issue asset by giving a `Account`. + __`GetAssetIssueByName`__: + Get issue asset by giving a`Name`. + __`GetNowBlock`__: + Get block. + __`GetBlockByNum`__: + Get block by block number. + __`TotalTransaction`__: + Check out the total transaction. + + service Wallet { + + rpc GetAccount (Account) returns (Account) { + + }; + + rpc CreateTransaction (TransferContract) returns (Transaction) { + + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + + }; + + rpc ListAccounts (EmptyMessage) returns (AccountList) { + + }; + + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + + }; + + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + + }; + + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + + }; + + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + + }; + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + + }; + + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + + }; + + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + + }; + + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + + } + + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + + } + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + + } + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + + } + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + + } + }; + + `AccountList`: the list of acounts in the blockchain explorer. + message `AccountList` contains one parameter: + `account`: + + message AccountList { + repeated Account accounts = 1; + } + + `WitnessList`: the list of witnesses in the blockchain explorer. + message `WitnessList` contains one parameter: + `witnesses`: + + message WitnessList { + repeated Witness witnesses = 1; + } + + `AssetIssueList`: the list of issue asset in the blockchain explorer. + message `AssetIssueList` contains one parameter: + `assetIssue`: + + message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; + } + + `NodeList`: the list of nodes in the node distribution map. + message `NodeList` contains one parameter: + `nodes`: + + message NodeList { + repeated Node nodes = 1; + } + + `Address`: the address of nodes. + message`Address` contains 2 parameters: + `host`: the host of nodes. + `port`: the port number of nodes. + + message Address { + bytes host = 1; + int32 port = 2; + } + + message `Return` has only one parameter: + `result`: a bool flag. + + message `Return` {
 + bool result = 1; + 
} + ++ The message structure of UDP. + + `Endpoint`: the storage structure of nodes' information. + message`Endpoint` contains 3 parameters: + `address`: the address of nodes. + `port`: the port number. + `nodeId`:the ID of nodes. + + + message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; + } + + `PingMessage`: the message sent from one node to another in the connecting process. + message`PingMessage` contains 4 parameters: + `from`: which node does the message send from. + `to`: which node will the message send to. + `version`: the version of the Internet. + `timestamp`: the timestamp of message. + + message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; + } + + `PongMessage`: the message implies that nodes are connected. + message`PongMessage` contains 3 parameters: + `from`: which node does the message send from. + `echo`: + `timestamp`: the timestamp of message. + + message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; + } + + `FindNeighbours`: the message sent from one node to find another one. + message`FindNeighbours` contains 3 parameters: + `from`: which node does the message send from. + `targetId`: the ID of targeted node. + `timestamp`: the timestamp of message. + + message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; + } + + `FindNeighbour`: the message replied by the neighbour node. + message`Neighbours` contains 3 parameters: + `from`: which node does the message send from. + `neighbours`: the neighbour node. + `timestamp`: the timestamp of message. + + message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; + } + + + +# Please check detailed protocol document that may change with the iteration of the program at any time. Please refer to the latest version. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 000000000..6a2811911 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# protocol [![Build Status](https://travis-ci.org/tronprotocol/protocol.svg?branch=master)](https://travis-ci.org/tronprotocol/protocol) + + +# The protocol of Tron including api and message. + +java-tron, wallet-cli and grpc-gateway + +git subtree pull --prefix src/main/protos/ protocol master + +## Run the included *.sh files to initialize the dependencies + diff --git a/api/api.proto b/api/api.proto new file mode 100644 index 000000000..13a729a3d --- /dev/null +++ b/api/api.proto @@ -0,0 +1,855 @@ +syntax = "proto3"; +package protocol; + +import "core/Tron.proto"; +import "core/Contract.proto"; +import "google/api/annotations.proto"; + + +option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file +option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/api"; + +service Wallet { + + rpc GetAccount (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccount" + body: "*" + additional_bindings { + get: "/wallet/getaccount" + } + }; + }; + + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccountbyid" + body: "*" + additional_bindings { + get: "/wallet/getaccountbyid" + } + }; + }; + + //Please use CreateTransaction2 instead of this function. + rpc CreateTransaction (TransferContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createtransaction" + body: "*" + additional_bindings { + get: "/wallet/createtransaction" + } + }; + }; + //Use this function instead of CreateTransaction. + rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + option (google.api.http) = { + post: "/wallet/broadcasttransaction" + body: "*" + additional_bindings { + get: "/wallet/broadcasttransaction" + } + }; + }; + //Please use UpdateAccount2 instead of this function. + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updateaccount" + body: "*" + additional_bindings { + get: "/wallet/updateaccount" + } + }; + }; + + + rpc SetAccountId (SetAccountIdContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/setaccountid" + body: "*" + additional_bindings { + get: "/wallet/setaccountid" + } + }; + }; + + //Use this function instead of UpdateAccount. + rpc UpdateAccount2 (AccountUpdateContract) returns (TransactionExtention) { + }; + + //Please use VoteWitnessAccount2 instead of this function. + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/votewitnessaccount" + body: "*" + additional_bindings { + get: "/wallet/votewitnessaccount" + } + }; + }; + + //modify the consume_user_resource_percent + rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { + }; + + //Use this function instead of VoteWitnessAccount. + rpc VoteWitnessAccount2 (VoteWitnessContract) returns (TransactionExtention) { + }; + //Please use CreateAssetIssue2 instead of this function. + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createassetissue" + body: "*" + additional_bindings { + get: "/wallet/createassetissue" + } + }; + }; + //Use this function instead of CreateAssetIssue. + rpc CreateAssetIssue2 (AssetIssueContract) returns (TransactionExtention) { + }; + //Please use UpdateWitness2 instead of this function. + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updatewitness" + body: "*" + additional_bindings { + get: "/wallet/updatewitness" + } + }; + }; + //Use this function instead of UpdateWitness. + rpc UpdateWitness2 (WitnessUpdateContract) returns (TransactionExtention) { + }; + //Please use CreateAccount2 instead of this function. + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createaccount" + body: "*" + additional_bindings { + get: "/wallet/createaccount" + } + }; + }; + //Use this function instead of CreateAccount. + rpc CreateAccount2 (AccountCreateContract) returns (TransactionExtention) { + } + //Please use CreateWitness2 instead of this function. + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createwitness" + body: "*" + additional_bindings { + get: "/wallet/createwitness" + } + }; + }; + //Use this function instead of CreateWitness. + rpc CreateWitness2 (WitnessCreateContract) returns (TransactionExtention) { + } + //Please use TransferAsset2 instead of this function. + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/transferasset" + body: "*" + additional_bindings { + get: "/wallet/transferasset" + } + }; + } + //Use this function instead of TransferAsset. + rpc TransferAsset2 (TransferAssetContract) returns (TransactionExtention) { + } + //Please use ParticipateAssetIssue2 instead of this function. + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/participateassetissue" + body: "*" + additional_bindings { + get: "/wallet/participateassetissue" + } + }; + } + //Use this function instead of ParticipateAssetIssue. + rpc ParticipateAssetIssue2 (ParticipateAssetIssueContract) returns (TransactionExtention) { + } + //Please use FreezeBalance2 instead of this function. + rpc FreezeBalance (FreezeBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/freezebalance" + body: "*" + additional_bindings { + get: "/wallet/freezebalance" + } + }; + } + //Use this function instead of FreezeBalance. + rpc FreezeBalance2 (FreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeBalance2 instead of this function. + rpc UnfreezeBalance (UnfreezeBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/unfreezebalance" + body: "*" + additional_bindings { + get: "/wallet/unfreezebalance" + } + }; + } + //Use this function instead of UnfreezeBalance. + rpc UnfreezeBalance2 (UnfreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeAsset2 instead of this function. + rpc UnfreezeAsset (UnfreezeAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/unfreezeasset" + body: "*" + additional_bindings { + get: "/wallet/unfreezeasset" + } + }; + } + //Use this function instead of UnfreezeAsset. + rpc UnfreezeAsset2 (UnfreezeAssetContract) returns (TransactionExtention) { + } + //Please use WithdrawBalance2 instead of this function. + rpc WithdrawBalance (WithdrawBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/withdrawbalance" + body: "*" + additional_bindings { + get: "/wallet/withdrawbalance" + } + }; + } + //Use this function instead of WithdrawBalance. + rpc WithdrawBalance2 (WithdrawBalanceContract) returns (TransactionExtention) { + } + //Please use UpdateAsset2 instead of this function. + rpc UpdateAsset (UpdateAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updateasset" + body: "*" + additional_bindings { + get: "/wallet/updateasset" + } + }; + } + //Use this function instead of UpdateAsset. + rpc UpdateAsset2 (UpdateAssetContract) returns (TransactionExtention) { + } + + rpc ProposalCreate (ProposalCreateContract) returns (TransactionExtention) { + } + + rpc ProposalApprove (ProposalApproveContract) returns (TransactionExtention) { + } + + rpc ProposalDelete (ProposalDeleteContract) returns (TransactionExtention) { + } + + rpc BuyStorage (BuyStorageContract) returns (TransactionExtention) { + } + + rpc BuyStorageBytes (BuyStorageBytesContract) returns (TransactionExtention) { + } + + rpc SellStorage (SellStorageContract) returns (TransactionExtention) { + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + option (google.api.http) = { + post: "/wallet/listnodes" + body: "*" + additional_bindings { + get: "/wallet/listnodes" + } + }; + } + + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getassetissuebyaccount" + body: "*" + additional_bindings { + get: "/wallet/getassetissuebyaccount" + } + }; + } + rpc GetAccountNet (Account) returns (AccountNetMessage) { + option (google.api.http) = { + post: "/wallet/getaccountnet" + body: "*" + additional_bindings { + get: "/wallet/getaccountnet" + } + }; + }; + rpc GetAccountResource (Account) returns (AccountResourceMessage) { + }; + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + option (google.api.http) = { + post: "/wallet/getassetissuebyname" + body: "*" + additional_bindings { + get: "/wallet/getassetissuebyname" + } + }; + } + //Please use GetNowBlock2 instead of this function. + rpc GetNowBlock (EmptyMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getnowblock" + body: "*" + additional_bindings { + get: "/wallet/getnowblock" + } + }; + } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. + rpc GetBlockByNum (NumberMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getblockbynum" + body: "*" + additional_bindings { + get: "/wallet/getblockbynum" + } + }; + } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + + rpc GetBlockById (BytesMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getblockbyid" + body: "*" + additional_bindings { + get: "/wallet/getblockbyid" + } + }; + } + //Please use GetBlockByLimitNext2 instead of this function. + rpc GetBlockByLimitNext (BlockLimit) returns (BlockList) { + option (google.api.http) = { + post: "/wallet/getblockbylimitnext" + body: "*" + additional_bindings { + get: "/wallet/getblockbylimitnext" + } + }; + } + //Use this function instead of GetBlockByLimitNext. + rpc GetBlockByLimitNext2 (BlockLimit) returns (BlockListExtention) { + } + //Please use GetBlockByLatestNum2 instead of this function. + rpc GetBlockByLatestNum (NumberMessage) returns (BlockList) { + option (google.api.http) = { + post: "/wallet/getblockbylatestnum" + body: "*" + additional_bindings { + get: "/wallet/getblockbylatestnum" + } + }; + } + //Use this function instead of GetBlockByLatestNum. + rpc GetBlockByLatestNum2 (NumberMessage) returns (BlockListExtention) { + } + rpc GetTransactionById (BytesMessage) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/gettransactionbyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactionbyid" + } + }; + } + + rpc DeployContract (CreateSmartContract) returns (TransactionExtention) { + } + + rpc GetContract (BytesMessage) returns (SmartContract) { + } + + rpc TriggerContract (TriggerSmartContract) returns (TransactionExtention) { + } + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + option (google.api.http) = { + post: "/wallet/listwitnesses" + body: "*" + additional_bindings { + get: "/wallet/listwitnesses" + } + }; + }; + + rpc ListProposals (EmptyMessage) returns (ProposalList) { + option (google.api.http) = { + post: "/wallet/listproposals" + body: "*" + additional_bindings { + get: "/wallet/listproposals" + } + }; + }; + + rpc GetProposalById (BytesMessage) returns (Proposal) { + option (google.api.http) = { + post: "/wallet/getproposalbyid" + body: "*" + additional_bindings { + get: "/wallet/getproposalbyid" + } + }; + }; + + rpc GetChainParameters (EmptyMessage) returns (ChainParameters) { + option (google.api.http) = { + post: "/wallet/getchainparameters" + body: "*" + additional_bindings { + get: "/wallet/getchainparameters" + } + }; + }; + + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getassetissuelist" + body: "*" + additional_bindings { + get: "/wallet/getassetissuelist" + } + }; + } + rpc GetPaginatedAssetIssueList (PaginatedMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getpaginatedassetissuelist" + body: "*" + additional_bindings { + get: "/wallet/getpaginatedassetissuelist" + } + }; + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + option (google.api.http) = { + post: "/wallet/totaltransaction" + body: "*" + additional_bindings { + get: "/wallet/totaltransaction" + } + }; + } + rpc GetNextMaintenanceTime (EmptyMessage) returns (NumberMessage) { + option (google.api.http) = { + post: "/wallet/getnextmaintenancetime" + body: "*" + additional_bindings { + get: "/wallet/getnextmaintenancetime" + } + }; + } + //Warning: do not invoke this interface provided by others. + //Please use GetTransactionSign2 instead of this function. + rpc GetTransactionSign (TransactionSign) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/gettransactionsign" + body: "*" + additional_bindings { + get: "/wallet/gettransactionsign" + } + }; + }; + //Warning: do not invoke this interface provided by others. + //Use this function instead of GetTransactionSign. + rpc GetTransactionSign2 (TransactionSign) returns (TransactionExtention) { + }; + //Warning: do not invoke this interface provided by others. + rpc CreateAddress (BytesMessage) returns (BytesMessage) { + option (google.api.http) = { + post: "/wallet/createaddress" + body: "*" + additional_bindings { + get: "/wallet/createaddress" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransfer" + body: "*" + additional_bindings { + get: "/wallet/easytransfer" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransferByPrivate (EasyTransferByPrivateMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransferbyprivate" + body: "*" + additional_bindings { + get: "/wallet/easytransferbyprivate" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + + option (google.api.http) = { + post: "/wallet/generateaddress" + body: "*" + additional_bindings { + get: "/wallet/generateaddress" + } + }; + } + + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/wallet/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactioninfobyid" + } + }; + } +}; + + +service WalletSolidity { + + rpc GetAccount (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccount" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccount" + } + }; + }; + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccountbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccountbyid" + } + }; + }; + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + option (google.api.http) = { + post: "/walletsolidity/listwitnesses" + body: "*" + additional_bindings { + get: "/walletsolidity/listwitnesses" + } + }; + }; + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/walletsolidity/getassetissuelist" + body: "*" + additional_bindings { + get: "/walletsolidity/getassetissuelist" + } + }; + } + rpc GetPaginatedAssetIssueList (PaginatedMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/walletsolidity/getpaginatedassetissuelist" + body: "*" + additional_bindings { + get: "/walletsolidity/getpaginatedassetissuelist" + } + }; + } + //Please use GetNowBlock2 instead of this function. + rpc GetNowBlock (EmptyMessage) returns (Block) { + option (google.api.http) = { + post: "/walletsolidity/getnowblock" + body: "*" + additional_bindings { + get: "/walletsolidity/getnowblock" + } + }; + } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. + rpc GetBlockByNum (NumberMessage) returns (Block) { + option (google.api.http) = { + post: "/walletsolidity/getblockbynum" + body: "*" + additional_bindings { + get: "/walletsolidity/getblockbynum" + } + }; + } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + + rpc GetTransactionById (BytesMessage) returns (Transaction) { + option (google.api.http) = { + post: "/walletsolidity/gettransactionbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/gettransactionbyid" + } + }; + } + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/walletsolidity/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/walletsolidity/gettransactioninfobyid" + } + }; + } + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + option (google.api.http) = { + post: "/walletsolidity/generateaddress" + body: "*" + additional_bindings { + get: "/walletsolidity/generateaddress" + } + }; + } +}; + +service WalletExtension { + //Please use GetTransactionsFromThis2 instead of this function. + rpc GetTransactionsFromThis (AccountPaginated) returns (TransactionList) { + option (google.api.http) = { + post: "/walletextension/gettransactionsfromthis" + body: "*" + additional_bindings { + get: "/walletextension/gettransactionsfromthis" + } + }; + } + //Use this function instead of GetTransactionsFromThis. + rpc GetTransactionsFromThis2 (AccountPaginated) returns (TransactionListExtention) { + } + //Please use GetTransactionsToThis2 instead of this function. + rpc GetTransactionsToThis (AccountPaginated) returns (TransactionList) { + option (google.api.http) = { + post: "/walletextension/gettransactionstothis" + body: "*" + additional_bindings { + get: "/walletextension/gettransactionstothis" + } + }; + } + //Use this function instead of GetTransactionsToThis. + rpc GetTransactionsToThis2 (AccountPaginated) returns (TransactionListExtention) { + } +}; + +// the api of tron's db +service Database { + // for tapos + rpc getBlockReference (EmptyMessage) returns (BlockReference) { + + } + rpc GetDynamicProperties (EmptyMessage) returns (DynamicProperties) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } +}; + +message Return { + enum response_code { + SUCCESS = 0; + SIGERROR = 1; // error in signature + CONTRACT_VALIDATE_ERROR = 2; + CONTRACT_EXE_ERROR = 3; + BANDWITH_ERROR = 4; + DUP_TRANSACTION_ERROR = 5; + TAPOS_ERROR = 6; + TOO_BIG_TRANSACTION_ERROR = 7; + TRANSACTION_EXPIRATION_ERROR = 8; + SERVER_BUSY = 9; + OTHER_ERROR = 20; + } + + bool result = 1; + response_code code = 2; + bytes message = 3; +} + +message BlockReference { + int64 block_num = 1; + bytes block_hash = 2; +} + +// the api of tron's network such as node list. +service Network { + +}; + +message WitnessList { + repeated Witness witnesses = 1; +} +message ProposalList { + repeated Proposal proposals = 1; +} +message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; +} +message BlockList { + repeated Block block = 1; +} +message TransactionList { + repeated Transaction transaction = 1; +} + +// Gossip node list +message NodeList { + repeated Node nodes = 1; +} + +// Gossip node +message Node { + Address address = 1; +} + +// Gossip node address +message Address { + bytes host = 1; + int32 port = 2; +} + +message EmptyMessage { +} +message NumberMessage { + int64 num = 1; +} +message BytesMessage { + bytes value = 1; +} +message TimeMessage { + int64 beginInMilliseconds = 1; + int64 endInMilliseconds = 2; +} +message BlockLimit { + int64 startNum = 1; + int64 endNum = 2; +} +message TransactionLimit { + bytes transactionId = 1; + int64 limitNum = 2; +} +message AccountPaginated { + Account account = 1; + int64 offset = 2; + int64 limit = 3; +} +message TimePaginatedMessage { + TimeMessage timeMessage = 1; + int64 offset = 2; + int64 limit = 3; +} +//deprecated +message AccountNetMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; +} +message AccountResourceMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; + + int64 CpuUsed = 13; + int64 CpuLimit = 14; + int64 TotalCpuLimit = 15; + int64 TotalCpuWeight = 16; + + int64 storageUsed = 21; + int64 storageLimit = 22; +} + +message PaginatedMessage { + int64 offset = 1; + int64 limit = 2; +} + +message EasyTransferMessage { + bytes passPhrase = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferByPrivateMessage { + bytes privateKey = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferResponse { + Transaction transaction = 1; + Return result = 2; + bytes txid = 3; //transaction id = sha256(transaction.rowdata) +} + +message AddressPrKeyPairMessage { + string address = 1; + string privateKey = 2; +} + +message TransactionExtention { + Transaction transaction = 1; + bytes txid = 2; //transaction id = sha256(transaction.rowdata) + repeated bytes constant_result = 3; + Return result = 4; +} + +message BlockExtention { + repeated TransactionExtention transactions = 1; + BlockHeader block_header = 2; + bytes blockid = 3; +} + +message BlockListExtention { + repeated BlockExtention block = 1; +} + +message TransactionListExtention { + repeated TransactionExtention transaction = 1; +} \ No newline at end of file diff --git a/core/Contract.proto b/core/Contract.proto new file mode 100644 index 000000000..96417ba0a --- /dev/null +++ b/core/Contract.proto @@ -0,0 +1,199 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Contract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message AccountCreateContract { + bytes owner_address = 1; + bytes account_address = 2; + AccountType type = 3; +} + +// Update account name. Account name is not unique now. +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} + +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + +message TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 amount = 3; +} + +message TransferAssetContract { + bytes asset_name = 1; + bytes owner_address = 2; + bytes to_address = 3; + int64 amount = 4; +} + + +message VoteAssetContract { + bytes owner_address = 1; + repeated bytes vote_address = 2; + bool support = 3; + int32 count = 5; +} + +message VoteWitnessContract { + message Vote { + bytes vote_address = 1; + int64 vote_count = 2; + } + bytes owner_address = 1; + repeated Vote votes = 2; + bool support = 3; +} + +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + +message WitnessCreateContract { + bytes owner_address = 1; + bytes url = 2; +} + +message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; +} + +message AssetIssueContract { + message FrozenSupply { + int64 frozen_amount = 1; + int64 frozen_days = 2; + } + bytes owner_address = 1; + bytes name = 2; + bytes abbr = 3; + int64 total_supply = 4; + repeated FrozenSupply frozen_supply = 5; + int32 trx_num = 6; + int32 num = 8; + int64 start_time = 9; + int64 end_time = 10; + int64 order = 11; // the order of tokens of the same name + int32 vote_score = 16; + bytes description = 20; + bytes url = 21; + int64 free_asset_net_limit = 22; + int64 public_free_asset_net_limit = 23; + int64 public_free_asset_net_usage = 24; + int64 public_latest_free_net_time = 25; +} + +message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; // the namekey of target asset, include name and order + int64 amount = 4; // the amount of drops +} + + +enum ResourceCode { + BANDWIDTH = 0x00; + CPU = 0x01; +} + +message FreezeBalanceContract { + bytes owner_address = 1; + int64 frozen_balance = 2; + int64 frozen_duration = 3; + + ResourceCode resource = 10; +} + +message UnfreezeBalanceContract { + bytes owner_address = 1; + + ResourceCode resource = 10; +} + +message UnfreezeAssetContract { + bytes owner_address = 1; +} + +message WithdrawBalanceContract { + bytes owner_address = 1; +} + +message UpdateAssetContract { + bytes owner_address = 1; + bytes description = 2; + bytes url = 3; + int64 new_limit = 4; + int64 new_public_limit = 5; +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} \ No newline at end of file diff --git a/core/Discover.proto b/core/Discover.proto new file mode 100644 index 000000000..4cc0d83b0 --- /dev/null +++ b/core/Discover.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package protocol; + + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Discover"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; +} + +message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; +} + +message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; +} + +message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; +} + +message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; +} + +message BackupMessage { + bool flag = 1; + int32 priority = 2; +} \ No newline at end of file diff --git a/core/Tron.proto b/core/Tron.proto new file mode 100644 index 000000000..d11599dca --- /dev/null +++ b/core/Tron.proto @@ -0,0 +1,453 @@ +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "core/Discover.proto"; + +package protocol; + + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Protocol"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +enum AccountType { + Normal = 0; + AssetIssue = 1; + Contract = 2; +} + +// AccountId, (name, address) use name, (null, address) use address, (name, null) use name, +message AccountId { + bytes name = 1; + bytes address = 2; +} + +// vote message +message Vote { + // the super rep address + bytes vote_address = 1; + // the vote num to this super rep. + int64 vote_count = 2; +} + +// Proposal +message Proposal { + int64 proposal_id = 1; + bytes proposer_address = 2; + map parameters = 3; + int64 expiration_time = 4; + int64 create_time = 5; + repeated bytes approvals = 6; + enum State { + PENDING = 0; + DISAPPROVED = 1; + APPROVED = 2; + CANCELED = 3; + } + State state = 7; +} + +message ChainParameters { + repeated ChainParameter chainParameter = 1; + message ChainParameter { + string key = 1; + int64 value = 2; + } +} + +/* Account */ +message Account { + /* frozen balance */ + message Frozen { + int64 frozen_balance = 1; // the frozen trx balance + int64 expire_time = 2; // the expire time + } + bytes account_name = 1; + AccountType type = 2; + // the create address + bytes address = 3; + // the trx balance + int64 balance = 4; + // the votes + repeated Vote votes = 5; + // the other asset owned by this account + map asset = 6; + // latest asset operation time + + // the frozen balance,including cpu and bandwidth + repeated Frozen frozen = 7; + // bandwidth, get from frozen + int64 net_usage = 8; + + // this account create time + int64 create_time = 0x09; + // this last operation time, including transfer, voting and so on. //FIXME fix grammar + int64 latest_opration_time = 10; + // witness block producing allowance + int64 allowance = 0x0B; + // last withdraw time + int64 latest_withdraw_time = 0x0C; + // not used so far + bytes code = 13; + bool is_witness = 14; + bool is_committee = 15; + // frozen asset(for asset issuer) + repeated Frozen frozen_supply = 16; + // asset_issued_name + bytes asset_issued_name = 17; + map latest_asset_operation_time = 18; + + int64 free_net_usage = 19; + map free_asset_net_usage = 20; + int64 latest_consume_time = 21; + int64 latest_consume_free_time = 22; + + bytes account_id = 23; + + message AccountResource { + // cpu resource, get from frozen + int64 cpu_usage = 1; + // the frozen balance for cpu + int64 frozen_balance_for_cpu = 2; + int64 latest_consume_time_for_cpu = 3; + // the delegated frozen balance for cpu + int64 delegated_frozen_balance_for_cpu = 4; + + // storage resource, get from market + int64 storage_limit = 6; + int64 storage_usage = 7; + int64 latest_exchange_storage_time = 8; + + } + AccountResource account_resource = 26; + + bytes codeHash = 30; +} + +message authority { + AccountId account = 1; + bytes permission_name = 2; +} + +message permission { + AccountId account = 1; +} + +// Witness +message Witness { + bytes address = 1; + int64 voteCount = 2; + bytes pubKey = 3; + string url = 4; + int64 totalProduced = 5; + int64 totalMissed = 6; + int64 latestBlockNum = 7; + int64 latestSlotNum = 8; + bool isJobs = 9; +} + +// Vote Change +message Votes { + bytes address = 1; + repeated Vote old_votes = 2; + repeated Vote new_votes = 3; +} + +// Transcation + +message TXOutput { + int64 value = 1; + bytes pubKeyHash = 2; +} + +message TXInput { + message raw { + bytes txID = 1; + int64 vout = 2; + bytes pubKey = 3; + } + raw raw_data = 1; + bytes signature = 4; +} + +message TXOutputs { + repeated TXOutput outputs = 1; +} + +message ResourceReceipt { + enum code { + SUCCESS = 0; + FAILED = 1; + } + int64 cpu_usage = 1; + int64 cpu_fee = 2; + int64 net_usage = 3; + int64 net_fee = 4; + int64 storage_delta = 5; + int64 storage_fee = 6; +} + +message Transaction { + message Contract { + enum ContractType { + AccountCreateContract = 0; + TransferContract = 1; + TransferAssetContract = 2; + VoteAssetContract = 3; + VoteWitnessContract = 4; + WitnessCreateContract = 5; + AssetIssueContract = 6; + WitnessUpdateContract = 8; + ParticipateAssetIssueContract = 9; + AccountUpdateContract = 10; + FreezeBalanceContract = 11; + UnfreezeBalanceContract = 12; + WithdrawBalanceContract = 13; + UnfreezeAssetContract = 14; + UpdateAssetContract = 15; + ProposalCreateContract = 16; + ProposalApproveContract = 17; + ProposalDeleteContract = 18; + SetAccountIdContract = 19; + CustomContract = 20; + BuyStorageContract = 21; + BuyStorageBytesContract = 22; + SellStorageContract = 23; + CreateSmartContract = 30; + TriggerSmartContract = 31; + GetContract = 32; + UpdateSettingContract = 33; + } + ContractType type = 1; + google.protobuf.Any parameter = 2; + bytes provider = 3; + bytes ContractName = 4; + + } + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + + message raw { + bytes ref_block_bytes = 1; + int64 ref_block_num = 3; + bytes ref_block_hash = 4; + int64 expiration = 8; + repeated authority auths = 9; + // data not used + bytes data = 10; + //only support size = 1, repeated list here for extension + repeated Contract contract = 11; + // scripts not used + bytes scripts = 12; + int64 timestamp = 14; + int64 fee_limit = 18; + } + + raw raw_data = 1; + // only support size = 1, repeated list here for muti-sig extension + repeated bytes signature = 2; + repeated Result ret = 5; +} + +message TransactionInfo { + message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + } + bytes id = 1; + int64 fee = 2; + int64 blockNumber = 3; + int64 blockTimeStamp = 4; + repeated bytes contractResult = 5; + bytes contract_address = 6; + ResourceReceipt receipt = 7; + repeated Log log = 8; +} + +message Transactions { + repeated Transaction transactions = 1; +} + +message TransactionSign { + Transaction transaction = 1; + bytes privateKey = 2; +} + +message BlockHeader { + message raw { + int64 timestamp = 1; + bytes txTrieRoot = 2; + bytes parentHash = 3; + //bytes nonce = 5; + //bytes difficulty = 6; + int64 number = 7; + int64 witness_id = 8; + bytes witness_address = 9; + } + raw raw_data = 1; + bytes witness_signature = 2; +} + +// block +message Block { + repeated Transaction transactions = 1; + BlockHeader block_header = 2; +} + +message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; +} + +// Inventory +message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; +} + +message Inventory { + enum InventoryType { + TRX = 0; + BLOCK = 1; + } + InventoryType type = 1; + repeated bytes ids = 2; +} + +message Items { + enum ItemType { + ERR = 0; + TRX = 1; + BLOCK = 2; + BLOCKHEADER = 3; + } + + ItemType type = 1; + repeated Block blocks = 2; + repeated BlockHeader block_headers = 3; + repeated Transaction transactions = 4; +} + +// DynamicProperties +message DynamicProperties { + int64 last_solidity_block_num = 1; +} + +enum ReasonCode { + REQUESTED = 0x00; + BAD_PROTOCOL = 0x02; + TOO_MANY_PEERS = 0x04; + DUPLICATE_PEER = 0x05; + INCOMPATIBLE_PROTOCOL = 0x06; + NULL_IDENTITY = 0x07; + PEER_QUITING = 0x08; + UNEXPECTED_IDENTITY = 0x09; + LOCAL_IDENTITY = 0x0A; + PING_TIMEOUT = 0x0B; + USER_REASON = 0x10; + RESET = 0x11; + SYNC_FAIL = 0x12; + FETCH_FAIL = 0x13; + BAD_TX = 0x14; + BAD_BLOCK = 0x15; + FORKED = 0x16; + UNLINKABLE = 0x17; + INCOMPATIBLE_VERSION = 0x18; + INCOMPATIBLE_CHAIN = 0x19; + TIME_OUT = 0x20; + CONNECT_FAIL = 0x21; + TOO_MANY_PEERS_WITH_SAME_IP = 0x22; + UNKNOWN = 0xFF; +} + +message DisconnectMessage { + ReasonCode reason = 1; +} + +message HelloMessage { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + + Endpoint from = 1; + int32 version = 2; + int64 timestamp = 3; + BlockId genesisBlockId = 4; + BlockId solidBlockId = 5; + BlockId headBlockId = 6; +} + +message StorageItem { + bytes contract_address = 1; + map items = 2; +} + + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + bytes data = 6; + int64 consume_user_resource_percent = 7; + string name = 8; + +} \ No newline at end of file diff --git a/core/TronInventoryItems.proto b/core/TronInventoryItems.proto new file mode 100644 index 000000000..a82d2de45 --- /dev/null +++ b/core/TronInventoryItems.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "TronInventoryItems"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message InventoryItems { + int32 type = 1; + repeated bytes items = 2; +} \ No newline at end of file diff --git a/install-googleapis.sh b/install-googleapis.sh new file mode 100755 index 000000000..0d44f6108 --- /dev/null +++ b/install-googleapis.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger +go get -u github.com/golang/protobuf/protoc-gen-go + +wget http://central.maven.org/maven2/com/google/api/grpc/googleapis-common-protos/0.0.3/googleapis-common-protos-0.0.3.jar +jar xvf googleapis-common-protos-0.0.3.jar +cp -r google/ $HOME/protobuf/include/ +ls -l + + + diff --git a/install-protobuf.sh b/install-protobuf.sh new file mode 100755 index 000000000..b3a8cb5cb --- /dev/null +++ b/install-protobuf.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e +# check to see if protobuf folder is empty +if [ ! -d "$HOME/protobuf/lib" ]; then + wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.tar.gz + tar -xzvf protobuf-all-3.5.1.tar.gz + cd protobuf-3.5.1 && ./configure --prefix=$HOME/protobuf && make && make install +else + echo "Using cached directory." +fi From 6ab542322f375a61dd846d370c3326d586777cea Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 8 Aug 2018 14:28:53 +0800 Subject: [PATCH 004/445] minor change for protocol --- core/Tron.proto | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index d11599dca..edbee0c91 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -74,10 +74,12 @@ message Account { map asset = 6; // latest asset operation time - // the frozen balance,including cpu and bandwidth + // the frozen balance for bandwidth repeated Frozen frozen = 7; // bandwidth, get from frozen int64 net_usage = 8; + // + int64 delegated_frozen_balance_for_net = 41; // this account create time int64 create_time = 0x09; @@ -108,7 +110,7 @@ message Account { // cpu resource, get from frozen int64 cpu_usage = 1; // the frozen balance for cpu - int64 frozen_balance_for_cpu = 2; + Frozen frozen_balance_for_cpu = 2; int64 latest_consume_time_for_cpu = 3; // the delegated frozen balance for cpu int64 delegated_frozen_balance_for_cpu = 4; From 99a0164fff18614c2c5f1b7f325d67a4cd2b06b3 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 8 Aug 2018 15:09:55 +0800 Subject: [PATCH 005/445] add DelegatedResource related --- core/Tron.proto | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index edbee0c91..9371ea492 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -79,7 +79,7 @@ message Account { // bandwidth, get from frozen int64 net_usage = 8; // - int64 delegated_frozen_balance_for_net = 41; + int64 delegated_frozen_balance_for_bandwidth = 41; // this account create time int64 create_time = 0x09; @@ -126,6 +126,14 @@ message Account { bytes codeHash = 30; } +message DelegatedResource{ + bytes from = 1; + bytes to = 2; + bytes bandwidth = 3; + bytes cpu = 4; + int64 expire_time = 5; +} + message authority { AccountId account = 1; bytes permission_name = 2; From 143410ad6ba1aa27d50a407d84cfb54f3a570207 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 8 Aug 2018 16:15:19 +0800 Subject: [PATCH 006/445] modify freezeBalanceActuator --- core/Contract.proto | 2 ++ core/Tron.proto | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 96417ba0a..88c191aba 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -131,6 +131,8 @@ message FreezeBalanceContract { int64 frozen_duration = 3; ResourceCode resource = 10; + + bytes receiver_address = 15; } message UnfreezeBalanceContract { diff --git a/core/Tron.proto b/core/Tron.proto index 9371ea492..09f3e60df 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -129,8 +129,8 @@ message Account { message DelegatedResource{ bytes from = 1; bytes to = 2; - bytes bandwidth = 3; - bytes cpu = 4; + int64 bandwidth = 3; + int64 cpu = 4; int64 expire_time = 5; } From 1633bbb381fc9489ebea000a9d155b094614bbef Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 8 Aug 2018 17:09:56 +0800 Subject: [PATCH 007/445] add acquired_delegated Field --- core/Tron.proto | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 09f3e60df..92786e1fd 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -78,8 +78,10 @@ message Account { repeated Frozen frozen = 7; // bandwidth, get from frozen int64 net_usage = 8; - // - int64 delegated_frozen_balance_for_bandwidth = 41; + //Frozen balance provided by other accounts to this account + int64 acquired_delegated_frozen_balance_for_bandwidth = 41; + //Freeze and provide balances to other accounts + int64 delegated_frozen_balance_for_bandwidth = 42; // this account create time int64 create_time = 0x09; @@ -112,8 +114,11 @@ message Account { // the frozen balance for cpu Frozen frozen_balance_for_cpu = 2; int64 latest_consume_time_for_cpu = 3; - // the delegated frozen balance for cpu - int64 delegated_frozen_balance_for_cpu = 4; + + //Frozen balance provided by other accounts to this account + int64 acquired_delegated_frozen_balance_for_cpu = 4; + //Frozen balances provided to other accounts + int64 delegated_frozen_balance_for_cpu = 5; // storage resource, get from market int64 storage_limit = 6; From 56829d806221f1c63bac06eb6751ad95a10aa249 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 8 Aug 2018 18:08:13 +0800 Subject: [PATCH 008/445] modify verification in UnfreezeBalanceActuator --- core/Contract.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Contract.proto b/core/Contract.proto index 88c191aba..f6d42c664 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -139,6 +139,8 @@ message UnfreezeBalanceContract { bytes owner_address = 1; ResourceCode resource = 10; + + bytes receiver_address = 13; } message UnfreezeAssetContract { From a2584d065b53a5744d49877bc6579f24df1031f5 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 8 Aug 2018 18:30:19 +0800 Subject: [PATCH 009/445] modify UnfreezeBalanceActuator --- core/Tron.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 92786e1fd..87b4a916c 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -134,8 +134,8 @@ message Account { message DelegatedResource{ bytes from = 1; bytes to = 2; - int64 bandwidth = 3; - int64 cpu = 4; + int64 frozen_balance_for_bandwidth = 3; + int64 frozen_balance_for_cpu = 4; int64 expire_time = 5; } From e903b8a864377e9d8d65402cb1d1a5f98d0dee35 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 9 Aug 2018 17:48:12 +0800 Subject: [PATCH 010/445] fix conflict --- core/Tron.proto | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 87b4a916c..dfb37cacf 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -72,6 +72,7 @@ message Account { repeated Vote votes = 5; // the other asset owned by this account map asset = 6; + // latest asset operation time // the frozen balance for bandwidth @@ -131,6 +132,7 @@ message Account { bytes codeHash = 30; } + message DelegatedResource{ bytes from = 1; bytes to = 2; @@ -247,6 +249,9 @@ message Transaction { } int64 fee = 1; code ret = 2; + + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; } message raw { @@ -272,6 +277,10 @@ message Transaction { } message TransactionInfo { + enum code { + SUCESS = 0; + FAILED = 1; + } message Log { bytes address = 1; repeated bytes topics = 2; @@ -285,6 +294,11 @@ message TransactionInfo { bytes contract_address = 6; ResourceReceipt receipt = 7; repeated Log log = 8; + code result = 9; + bytes resMessage = 10; + + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; } message Transactions { @@ -461,8 +475,7 @@ message SmartContract { ABI abi = 3; bytes bytecode = 4; int64 call_value = 5; - bytes data = 6; - int64 consume_user_resource_percent = 7; - string name = 8; + int64 consume_user_resource_percent = 6; + string name = 7; } \ No newline at end of file From 61611790e0d03637c4ea53e65a383f888d3b798a Mon Sep 17 00:00:00 2001 From: ashu Date: Sat, 1 Sep 2018 13:54:42 +0800 Subject: [PATCH 011/445] update config-test-net.conf --- .gitignore | 4 + .travis.yml | 24 + Chinese version of TRON Protocol document.md | 696 +++++++++++++++ English version of TRON Protocol document.md | 734 +++++++++++++++ LICENSE | 165 ++++ README.md | 11 + api/api.proto | 890 +++++++++++++++++++ core/Contract.proto | 228 +++++ core/Discover.proto | 44 + core/Tron.proto | 484 ++++++++++ core/TronInventoryItems.proto | 12 + install-googleapis.sh | 14 + install-protobuf.sh | 10 + 13 files changed, 3316 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Chinese version of TRON Protocol document.md create mode 100644 English version of TRON Protocol document.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 api/api.proto create mode 100644 core/Contract.proto create mode 100644 core/Discover.proto create mode 100644 core/Tron.proto create mode 100644 core/TronInventoryItems.proto create mode 100755 install-googleapis.sh create mode 100755 install-protobuf.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1554c5edd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# IDEA +.idea +*iml +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..34fa71b3d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: ruby + +cache: + directories: + - $HOME/protobuf + +sudo: false + +before_install: + - bash install-protobuf.sh + - bash install-googleapis.sh + +# check what has been installed by listing contents of protobuf folder +before_script: + - ls -R $HOME/protobuf + +# let's use protobuf +script: + - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto + - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto + + - ls -l \ No newline at end of file diff --git a/Chinese version of TRON Protocol document.md b/Chinese version of TRON Protocol document.md new file mode 100644 index 000000000..283bd5fb8 --- /dev/null +++ b/Chinese version of TRON Protocol document.md @@ -0,0 +1,696 @@ +# TRON protobuf protocol + +## TRON使用Google protobuf协议,协议内容涉及到账户,区块,传输多个层面。 + ++ 账户有基本账户、资产发布账户和合约账户三种类型。一个账户包含:账户名称,账户类型,地址,余额,投票,其他资产6种属性。 ++ 更进一步的,基本账户可以申请成为验证节点,验证节点具有额外的属性,投票统计数目,公钥,URL,以及历史表现等参数。 + + 3种`Account`类型:`Normal`,`AssetIssue`,`Contract`。 + + enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2;
 + } + + 一个`Account`包含7种参数: + `account_name`:该账户的名称——比如: ”_SicCongsAccount_”。 + `type`:该账户的类型——比如: _0_ 代表的账户类型是`Normal`。 + `balance`:该账户的TRX余额——比如:_4213312_。 + `votes`:账户所得投票数——比如:_{(“0x1b7w…9xj3”,323),(“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_。 + `asset`:除TRX以外账户上的其他资产——比如:_{<”WishToken”,66666>,<”Dogie”,233>}_。 + `latest_operation_time`: 该账户的最新活跃时间。 + + // Account
 + message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 + }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6;
 + int64 latest_operation_time = 10; + } + + 一个`Witness`包含8种参数: + `address`:验证节点的地址——比如:_“0xu82h…7237”_。 + `voteCount`:验证节点所得投票数——比如:_234234_。 + `pubKey`:验证节点的公钥——比如:_“0xu82h…7237”_。 + `url`:验证节点的url链接。 + `totalProduce`:验证节点产生的区块数——比如:_2434_。 + `totalMissed`:验证节点丢失的区块数——比如:_7_。 + `latestBlockNum`:最新的区块高度——比如:_4522_。 + `isJobs`:布尔表类型标志位。 + + // Witness
 + message Witness {
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7; + bool isJobs = 9; + 
} + ++ 一个区块由区块头和多笔交易构成。区块头包含时间戳,交易字典树的根,父哈希,签名等区块基本信息。 + + 一个`block`包含`transactions`和`block_header`。 + `transactions`:区块里的交易信息。 + `block_header`:区块的组成部分之一。 + + // block
 + message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2; + 
} + + `BlockHeader` 包括`raw_data`和`witness_signature`。 + `raw_data`:`raw`信息。 + `witness_signature`:区块头到验证节点的签名。 + + message `raw`包含6种参数: + `timestamp`:该消息体的时间戳——比如:_14356325_。 + `txTrieRoot`:Merkle Tree的根——比如:_“7dacsa…3ed”_。 + `parentHash`:上一个区块的哈希值——比如:_“7dacsa…3ed”_。 + `number`:区块高度——比如:_13534657_。 + `witness_id`:验证节点的id——比如:_“0xu82h…7237”_。 + `witness_address`:验证节点的地址——比如:_“0xu82h…7237”_。 + + message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2; + 
} + + 消息体 `ChainInventory` 包括 `BlockId` 和 `remain_num`。 + `BlockId`: block的身份信息。 + `remain_num`:在同步过程中,剩余的区块数量。 + + A `BlockId` contains 2 parameters: + `hash`: 该区块的哈希值。 + `number`: 哈希值和高度即为当前区块块号。 + + message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; + } + ++ 交易合约有多种类型,包括账户创建合约、账户更新合约、转账合约、转账断言合约、资产投票合约、见证节点投票合约、见证节点创建合约、见证节点更新合约、资产发布合约、参与资产发布和与部署合约11种类型。 + + `AccountCreatContract`包含3种参数: + `type`:账户类型——比如:_0_ 代表的账户类型是`Normal`。 + `account_name`: 账户名称——比如: _"SiCongsaccount”_。 + `owner_address`:合约持有人地址——比如: _“0xu82h…7237”_。 + + message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 + } + `AccountUpdateContract`包含2种参数: + `account_name`: 账户名称——比如: _"SiCongsaccount”_。 + `owner_address`:合约持有人地址——比如: _“0xu82h…7237”_。 + + message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; + } + + `TransferContract`包含3种参数: + `amount`:TRX数量——比如:_12534_。 + `to_address`: 接收方地址——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + + message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; + 
} + + `TransferAssetContract`包含4种参数: + `asset_name`:资产名称——比如:_”SiCongsaccount”_。 + `to_address`:接收方地址——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `amount`:目标资产数量——比如:_12353_。 + + message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 + } + + `VoteAssetContract`包含4种参数: + `vote_address`:投票人地址——比如:_“0xu82h…7237”_。 + `support`:投票赞成与否——比如:_true_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `count`:投票数目——比如:_2324234_。 + + message VoteAssetContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5; + } + + `VoteWitnessContract`包含4种参数: + `vote_address`:投票人地址——比如:_“0xu82h…7237”_。 + `support`:投票赞成与否——比如:_true_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `count`:投票数目——比如:_32632_。 + + message VoteWitnessContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5;
 + } + + `WitnessCreateContract`包含3种参数: + `private_key`:合约的私钥——比如:_“0xu82h…7237”_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `url`:合约的url链接。 + + message WitnessCreateContract {
 + bytes owner_address = 1;
 + bytes private_key = 2;
 + bytes url = 12; + 
} + + `WitnessUpdateContract`包含2种参数: + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `update_url`:合约的url链接。 + + message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; + } + + `AssetIssueContract`包含11种参数: + `name`:合约名称——比如:_“SiCongcontract”_。 + `total_supply`:合约的赞成总票数——比如:_100000000_。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `trx_num`:对应TRX数量——比如:_232241_。 + `num`: 对应的自定义资产数目。 + `start_time`:开始时间——比如:_20170312_。 + `end_time`:结束时间——比如:_20170512_。 + `vote_score`:合约的评分——比如:_12343_。 + `description`:合约的描述——比如:_”trondada”_。 + `url`:合约的url地址链接。 + + message AssetIssueContract {
 + bytes owner_address = 1;
 + bytes name = 2;
 + int64 total_supply = 4;
 + int32 trx_num = 6;
 + int32 num = 8;
 + int64 start_time = 9;
 + int64 end_time = 10;
 + int32 vote_score = 16;
 + bytes description = 20;
 + bytes url = 21;
 + } + + `ParticipateAssetIssueContract`包含4种参数: + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + `to_address`:接收方地址——比如:_“0xu82h…7237”_。 + `asset_name`: 目标资产的名称。 + `amount`: 小部分数量。 + + `DeployContract`包含2种参数: + `script`:脚本。 + `owner_address`:合约持有人地址——比如:_“0xu82h…7237”_。 + + message DeployContract {
 + bytes owner_address = 1;
 + bytes script = 2;
 + } + + 消息体 `Result` 包含 `fee` and `ret`2个参数. + `ret`: 交易结果。 + `fee`: 交易扣除的费用。 + + `code`是`ret`的类型定义,有`SUCCESS`和`FAILED`两种类型。 + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + ++ 每一个交易还包含多个输入与多个输出,以及其他一些相关属性。其中交易内的输入,交易本身,区块头均需签名。 + + 消息体 `Transaction`包括`raw_data`和`signature`。 + `raw_data`: 消息体`raw`。 + `signature`: 所有输入节点的签名。 + + `raw_data`包含8种参数: + `type`:消息体raw的交易类型。 + `vin`: 输入值。 + `vout`: 输出值。 + `expiration`:过期时间——比如:_20170312_。 + `data`: 数据。 + `contract`: 该交易内的合约。 + `script`: 脚本。 + `timestamp`:该消息体的时间戳。 + + 消息体 `Contract`包含`type`和`parameter`。 + `type`:合约的类型。 + `parameter`:任意参数。 + + 有八种账户类型合约:`AccountCreateContract`,`TransferContract`,`TransferAssetContract`,`VoteAssetContract`,`VoteWitnessContract`,`WitnessCreateContract`,`AssetIssueContract` 和`DeployContract`。 + + `TransactionType`包括`UtxoType`和`ContractType`。 + + message Transaction {
 + enum TranscationType {
 + UtxoType = 0;
 + ContractType = 1;
 + }
 + message Contract {
 + enum ContractType {
 + AccountCreateContract = 0;
 + TransferContract = 1;
 + TransferAssetContract = 2;
 + VoteAssetContract = 3;
 + VoteWitnessContract = 4;
 + WitnessCreateContract = 5;
 + AssetIssueContract = 6;
 + DeployContract = 7;
 + }
 + ContractType type = 1;
 + google.protobuf.Any parameter = 2;
 + }
 + message raw {
 + TranscationType type = 2;
 + repeated TXInput vin = 5;
 + repeated TXOutput vout = 7;
 + int64 expiration = 8;
 + bytes data = 10;
 + repeated Contract contract = 11;
 + bytes scripts = 16;
 + in64 timestamp = 17; + }
 + raw raw_data = 1;
 + repeated bytes signature = 5;
 + } + + 消息体 `TXOutputs`由`outputs`构成。 + `outputs`: 元素为`TXOutput`的数组。 + + message TXOutputs {
 + repeated TXOutput outputs = 1; + 
} + + 消息体 `TXOutput`包括`value`和`pubKeyHash`。 + `value`:输出值。 + `pubKeyhash`:公钥的哈希。 + + message TXOutput {
 + int64 value = 1;
 + bytes pubKeyHash = 2; + 
} + + 消息体 `TXIutput`包括`raw_data`和`signature`。 + `raw_data`:消息体`raw`。 + `signature`:`TXInput`的签名。 + + 消息体 `raw`包含`txID`,`vout`和 `pubKey`。 + `txID`:交易ID。 + `Vout`:上一个输出的值。 + `pubkey`:公钥。 + + message TXInput {
 + message raw {
 + bytes txID = 1;
 + int64 vout = 2;
 + bytes pubKey = 3;
 + }
 + raw raw_data = 1;
 + bytes signature = 4;
} + ++ 传输涉及的协议Inventory主要用于传输中告知接收方传输数据的清单。 + + `Inventory`包括`type`和`ids`。 + `type`:清单类型——比如:_0_ 代表`TRX`。 + `ids`:清单中的物品ID。 + + `InventoryType`包含`TRX`和 `BLOCK`。 + `TRX`:交易。 + `BLOCK`:区块。 + + // Inventory
 + message Inventory {
 + enum InventoryType {
 + TRX = 0;
 + BLOCK = 1;
 + }
 + InventoryType type = 1;
 + repeated bytes ids = 2; + 
} + + 消息体 `Items`包含4种参数: + `type`:物品类型——比如:_1_ 代表 `TRX`。 + `blocks`:物品中区块。 + `blockheaders`:区块头。 + `transactions`:交易。 + + `Items`有四种类型,分别是 `ERR`, `TRX`,`BLOCK` 和`BLOCKHEADER`。 + `ERR`:错误。 + `TRX`:交易。 + `BLOCK`:区块。 + `BLOCKHEADER`:区块头。 + + message Items {
 + enum ItemType {
 + ERR = 0;
 + TRX = 1;
 + BLOCK = 2;
 + BLOCKHEADER = 3;
 + }
 + ItemType type = 1;
 + repeated Block blocks = 2;
 + repeated BlockHeader block_headers = 3;
 + repeated Transaction transactions = 4;
 + } + + `Inventory`包含`type`和`items`。 + `type`:物品种类。 + `items`:物品清单。 + + message InventoryItems {
 + int32 type = 1;
 + repeated bytes items = 2;
 + } + + 消息体 `BlockInventory` 包含 `type`。 + `type`: 清单种类. + + 有三种类型:`SYNC`, `ADVTISE`, `FETCH`。 + + // Inventory + message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + 消息体 `BlockId` 包括 `ids` and `type`。 + `ids`: 区块身份信息。 + `type`: 区块类型。 + + `ids` 包含2种参数: + `hash`: 区块的哈希值。 + `number`: 哈希值和区块高度即为当前区块号。 + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; + } + + `ReasonCode` 有15种可能断开的原因: + `REQUESTED` + `TCP_ERROR` + `BAD_PROTOCOL` + `USELESS_PEER` + `TOO_MANY_PEERS` + `DUPLICATE_PEER` + `INCOMPATIBLE_PROTOCOL` + `NULL_IDENTITY` + `PEER_QUITING` + `UNEXPECTED_IDENTITY` + `LOCAL_IDENTITY` + `PING_TIMEOU` + `USER_REASON` + `RESET` + `UNKNOWN` + + enum ReasonCode { + REQUESTED = 0; + TCP_ERROR = 1; + BAD_PROTOCOL = 2; + USELESS_PEER = 3; + TOO_MANY_PEERS = 4; + DUPLICATE_PEER = 5; + INCOMPATIBLE_PROTOCOL = 6; + NULL_IDENTITY = 7; + PEER_QUITING = 8; + UNEXPECTED_IDENTITY = 9; + LOCAL_IDENTITY = 10; + PING_TIMEOUT = 11; + USER_REASON = 12; + RESET = 16; + UNKNOWN = 255; + } + + 消息体`DisconnectMessage`包含`reason`。 + `DisconnectMessage`:断开连接是的消息。 + `reason`:断开连接时的原因。 + + 消息体`HelloMessage`包含2个参数: + `from`请:求建立连接的节点。 + `version`:建立连接的节点。 + ++ 钱包服务RPC和区块链浏览器。 + + `Wallet`钱包服务包含多个RPC。 + __`Getbalance`__:获取`Account`的余额。 + __`CreatTransaction`__:通过`TransferContract`创建交易。 + __`BroadcastTransaction`__:广播`Transaction`。 + __`CreateAccount`__:通过`AccountCreateContract`创建账户。 + __`CreatAssetIssue`__:通过`AssetIssueContract`发布一个资产。 + __`ListAccounts`__:通过`ListAccounts`查看账户列表。 + __`UpdateAccount`__:通过`UpdateAccountContract`发布一个资产。 + __`VoteWitnessAccount`__:通过`VoteWitnessContract`发布一个资产。 + __`WitnessList`__:通过`WitnessList`查看见证节点列表。 + __`UpdateWitness`__:通过`WitnessUpdateContract`发布一个资产。 + __`CreateWitness`__:通过`WitnessCreateContract`发布一个资产。 + __`TransferAsset`__:通过`TransferAssetContract`发布一个资产。 + __`ParticipateAssetIssue`__:通过`ParticipateAssetIssueContract`发布一个资产。 + __`ListNodes`__:通过`ListNodes`查看节点列表。 + __`GetAssetIssueList`__:通过`GetAssetIssueList`查看资产发布节点列表。 + __`GetAssetIssueByAccount`__:通过`Account`获取发行资产。 + __`GetAssetIssueByName`__:通过`Name`获取发行资产。 + __`GetNowBlock`__:获取区块。 + __`GetBlockByNum`__:根据块号获取区块。 + __`TotalTransaction`__:查看总交易量。 + + service Wallet { + + rpc GetAccount (Account) returns (Account) { + + }; + + rpc CreateTransaction (TransferContract) returns (Transaction) { + + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + + }; + + rpc ListAccounts (EmptyMessage) returns (AccountList) { + + }; + + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + + }; + + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + + }; + + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + + }; + + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + + }; + + rpc WitnessList (EmptyMessage) returns (WitnessList) { + + }; + + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + + }; + + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + + }; + + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + + } + + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + + } + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + + } + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + + } + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + + } + }; + + `AccountList`: 区块链浏览器中的账户列表。 + 消息体 `AccountList` 包含1个参数: + `account`: + + message AccountList { + repeated Account accounts = 1; + } + + `WitnessList`:区块链浏览器中的见证节点列表。 + 消息体 `WitnessList` 包含1个参数: + `witnesses`: + + message WitnessList { + repeated Witness witnesses = 1; + } + + `AssetIssueList`:区块链浏览器中的发布资产列表。 + 消息体 `AssetIssueList` 包含1个参数: + `assetIssue`: + + message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; + } + + + `NodeList`: 分布节点图中的节点列表。 + 消息体 `NodeList` 包含1个参数: + `nodes`: + + message NodeList { + repeated Node nodes = 1; + } + + `Address`: 节点地址。 + 消息体`Address` 包含2个参数: + `host`:节点所有者。 + `port`:节点的端口号。 + + message Address { + bytes host = 1; + int32 port = 2; + } + + + 消息体`Return`只含有一个参数: + `result`: 布尔表类型标志位。 + + message `Return` {
 + bool result = 1;
 + } + ++ 网络UDP消息结构。 + + `Endpoint`:网络中节点信息存储结构. + 消息体`Endpoint` 包含3个参数: + `address`:节点地址。 + `port`:端口号。 + `nodeId`: 节点ID信息。 + + message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; + } + + `PingMessage`:节点建立连接时所发送的消息。 + 消息体`PingMessage` 包含4个参数: + `from`:消息来自的节点。 + `to`: 消息发送的节点。 + `version`: 网络版本。 + `timestamp`:消息创建时的时间戳。 + + message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; + } + + `PongMessage`:连接建立成功时的回复消息。 + 消息体`PongMessage` 包含3个参数: + `from`:消息来自的节点。 + `echo`: + `timestamp`:消息创建时的时间戳。 + + message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; + } + + `FindNeighbours`:节点查询相邻节点时所发送的消息。 + 消息体`FindNeighbours` 包含3个参数: + `from`: 消息来自的节点。 + `targetId`: 目标节点的信息。 + `timestamp`: 消息创建时的时间戳。 + + message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; + } + + `Neighbour`:相邻接点回复消息。 + 消息体`Neighbours` 包含3个参数: + `from`: 消息来自的节点。 + `neighbours`: 相邻节点。 + `timestamp`: 消息创建时的时间戳。 + + message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; + } + +# 详细的协议见附属文件。详细协议随着程序的迭代随时都可能发生变化,请以最新的版本为准。 \ No newline at end of file diff --git a/English version of TRON Protocol document.md b/English version of TRON Protocol document.md new file mode 100644 index 000000000..94498fb1c --- /dev/null +++ b/English version of TRON Protocol document.md @@ -0,0 +1,734 @@ + +# Protobuf protocol + +## The protocol of TRON is defined by Google Protobuf and contains a range of layers, from account, block to transfer. + ++ There are 3 types of account—basic account, asset release account and contract account, and attributes included in each account are name, types, address, balance and related asset. ++ A basic account is able to apply to be a validation node, which has serval parameters, including extra attributes, public key, URL, voting statistics, history performance, etc. + + There are three different `Account types`: `Normal`, `AssetIssue`, `Contract`. + + enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2; + 
} + + An `Account` contains 7 parameters: + `account_name`: the name for this account – e.g. “_BillsAccount_”. + `type`: what type of this account is – e.g. _0_ stands for type `Normal`. + `balance`: balance of this account – e.g. _4213312_. + `votes`: received votes on this account – e.g. _{(“0x1b7w…9xj3”,323), (“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_. + `asset`: other assets expect TRX in this account – e.g. _{<“WishToken”,66666>,<”Dogie”,233>}_. + `latest_operation_time`: the latest operation time of this account. + + // Account
 + message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6; + int64 latest_operation_time = 10;
 + } + + A `Witness` contains 8 parameters: + `address`: the address of this witness – e.g. “_0xu82h…7237_”. + `voteCount`: number of received votes on this witness – e.g. _234234_. + `pubKey`: the public key for this witness – e.g. “_0xu82h…7237_”. + `url`: the url for this witness – e.g. “_https://www.noonetrust.com_”. + `totalProduced`: the number of blocks this witness produced – e.g. _2434_. + `totalMissed`: the number of blocks this witness missed – e.g. _7_. + `latestBlockNum`: the latest height of block – e.g. _4522_. + `isjobs`: a bool flag. + + // Witness
 + message Witness{
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7;
 + bool isJobs = 9; + } + ++ A block typically contains transaction data and a blockheader, which is a list of basic block information, including timestamp, signature, parent hash, root of Merkle tree and so on. + + A block contains `transactions` and a `block_header`. + `transactions`: transaction data of this block. + `block_header`: one part of a block. + + // block + 
message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2;
 + } + + A `BlockHeader` contains `raw_data` and `witness_signature`. + `raw_data`: a `raw` message. + `witness_signature`: signature for this block header from witness node. + + A message `raw` contains 6 parameters: + `timestamp`: timestamp of this message – e.g. _14356325_. + `txTrieRoot`: the root of Merkle Tree in this block – e.g. “_7dacsa…3ed_.” + `parentHash`: the hash of last block – e.g. “_7dacsa…3ed_.” + `number`: the height of this block – e.g. _13534657_. + `witness_id`: the id of witness which packed this block – e.g. “_0xu82h…7237_”. + `witness_address`: the adresss of the witness packed this block – e.g. “_0xu82h…7237_”. + + message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2;
 + } + + message `ChainInventory` contains `BlockId` and `remain_num`. + `BlockId`: the identification of block. + `remain_num`:the remain number of blocks in the synchronizing process. + + A `BlockId` contains 2 parameters: + `hash`: the hash of block. + `number`: the hash and height of block. + + message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; + } + ++ Transaction contracts mainly includes account create contract, account update contract transfer contract, transfer asset contract, vote asset contract, vote witness contract, witness creation contract, witness update contract, asset issue contract, participate asset issue contract and deploy contract. + + An `AccountCreateContract` contains 3 parameters: + `type`: What type this account is – e.g. _0_ stands for `Normal`. + `account_name`: the name for this account – e.g.”_Billsaccount_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 + } + + A `AccountUpdateContract` contains 2 paremeters: + `account_name`: the name for this account – e.g.”_Billsaccount_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; + } + + A `TransferContract` contains 3 parameters: + `amount`: the amount of TRX – e.g. _12534_. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + + message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; + } + + A `TransferAssetContract` contains 4 parameters: + `asset_name`: the name for asset – e.g.”_Billsaccount_”. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `amount`: the amount of target asset - e.g._12353_. + + message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 + } + + A `VoteAssetContract` contains 4 parameters: + `vote_address`: the voted address of the asset. + `support`: is the votes supportive or not – e.g. _true_. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `count`: the count number of votes- e.g. _2324234_. + + message VoteAssetContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5;
 + } + + A `VoteWitnessContract` contains 4 parameters: + `vote_address`: the addresses of those who voted. + `support`: is the votes supportive or not - e.g. _true_. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `count`: - e.g. the count number of vote – e.g. _32632_. + + message VoteWitnessContract {
 + bytes owner_address = 1;
 + repeated bytes vote_address = 2;
 + bool support = 3;
 + int32 count = 5; + 
} + + A `WitnessCreateContract` contains 3 parameters: + `private_key`: the private key of contract– e.g. “_0xu82h…7237_”. + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `url`: the url for the witness – e.g. “_https://www.noonetrust.com_”. + + message WitnessCreateContract {
 + bytes owner_address = 1;
 + bytes private_key = 2;
 + bytes url = 12;
 + } + + A `WitnessUpdateContract` contains 2 parameters: + `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. + `update_url`: the url for the witness – e.g. “_https://www.noonetrust.com_”. + + message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; + } + + An `AssetIssueContract` contains 11 parameters: + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + `name`: the name for this contract – e.g. “Billscontract”. + `total_supply`: the maximum supply of this asset – e.g. _1000000000_. + `trx_num`: the number of TRONIX – e.g._232241_. + `num`: number of corresponding asset. + `start_time`: the starting date of this contract – e.g._20170312_. + `end_time`: the expiring date of this contract – e.g. _20170512_. + `vote_score`: the vote score of this contract received – e.g. _12343_. + `description`: the description of this contract – e.g.”_trondada_”. + `url`: the url of this contract – e.g. “_https://www.noonetrust.com_”. + + message AssetIssueContract {
 + bytes owner_address = 1;
 + bytes name = 2;
 + int64 total_supply = 4;
 + int32 trx_num = 6;
 + int32 num = 8;
 + int64 start_time = 9;
 + int64 end_time = 10;
 + int32 vote_score = 16;
 + bytes description = 20;
 + bytes url = 21;
 + } + + A `ParticipateAssetIssueContract` contains 4 parameters: + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + `to_address`: the receiver address – e.g. “_0xu82h…7237_”. + `asset_name`: the name of target asset. + `amount`: the amount of drops. + + message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; + int64 amount = 4; + } + + A `DeployContract` contains 2 parameters: + `script`: the script of this contract. + `owner_address`: the address for contract owner – e.g. “_0xu82h…7237_”. + + message DeployContract {
 + bytes owner_address = 1;
 + bytes script = 2; + 
} t + ++ Each transaction contains several TXInputs, TXOutputs and other related qualities. +Input, transaction and head block all require signature. + + message `Transaction` contains `raw_data` and `signature`. + `raw_data`: message `raw`. + `signature`: signatures form all input nodes. + + `raw` contains 8 parameters: + `type`: the transaction type of `raw` message. + `vin`: input values. + `vout`: output values. + `expiration`: the expiration date of transaction – e.g._20170312_. + `data`: data. + `contract`: contracts in this transaction. + `scripts`:scripts in the transaction. + `timestamp`: timestamp of this raw data – e.g. _14356325_. + + message `Contract` contains `type` and `parameter`. + `type`: what type of the message contract. + `parameter`: It can be any form. + + There are 8 different of contract types: `AccountCreateContract`, `TransferContract`, `TransferAssetContract`, `VoteAssetContract`, `VoteWitnessContract`,`WitnessCreateContract`, `AssetIssueContract` and `DeployContract`. + `TransactionType` have two types: `UtxoType` and `ContractType`. + + message Transaction {
 + enum TranscationType {
 + UtxoType = 0;
 + ContractType = 1;
 + }
 + message Contract {
 + enum ContractType {
 + AccountCreateContract = 0;
 + TransferContract = 1;
 + TransferAssetContract = 2;
 + VoteAssetContract = 3;
 + VoteWitnessContract = 4;
 + WitnessCreateContract = 5;
 + AssetIssueContract = 6;
 + DeployContract = 7;
 + WitnessUpdateContract = 8; + ParticipateAssetIssueContract = 9 + }
 + ContractType type = 1;
 + google.protobuf.Any parameter = 2;
 + }
 + message raw {
 + TranscationType type = 2;
 + repeated TXInput vin = 5;
 + repeated TXOutput vout = 7;
 + int64 expiration = 8;
 + bytes data = 10;
 + repeated Contract contract = 11;
 + bytes scripts = 16;
 + int64 timestamp = 17; + }
 + raw raw_data = 1;
 + repeated bytes signature = 5; + 
} + + message `TXOutputs` contains `outputs`. + `outputs`: an array of `TXOutput`. + + message TXOutputs {
 + repeated TXOutput outputs = 1;
 + } + + message `TXOutput` contains `value` and `pubKeyHash`. + `value`: output value. + `pubKeyHash`: Hash of public key + + message TXOutput {
 + int64 value = 1;
 + bytes pubKeyHash = 2;
 + } + + message `TXInput` contains `raw_data` and `signature`. + `raw_data`: a message `raw`. + `signature`: signature for this `TXInput`. + + message `raw` contains `txID`, `vout` and `pubKey`. + `txID`: transaction ID. + `vout`: value of last output. + `pubKey`: public key. + + message TXInput {
 + message raw {
 + bytes txID = 1;
 + int64 vout = 2;
 + bytes pubKey = 3;
 + }
 + raw raw_data = 1;
 + bytes signature = 4; + 
} + + message `Result` contains `fee` and `ret`. + `ret`: the state of transaction. + `fee`: the fee for transaction. + + `code` is definition of `ret` and contains 2 types:`SUCCESS` and `FAILED`. + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + int64 fee = 1; + code ret = 2; + } + ++ Inventory is mainly used to inform peer nodes the list of items. + + `Inventory` contains `type` and `ids`. + `type`: what type this `Inventory` is. – e.g. _0_ stands for `TRX`. + `ids`: ID of things in this `Inventory`. + + Two `Inventory` types: `TRX` and `BLOCK`. + `TRX`: transaction. + `BLOCK`: block. + + // Inventory
 + message Inventory {
 + enum InventoryType {
 + TRX = 0;
 + BLOCK = 1;
 + }
 + InventoryType type = 1;
 + repeated bytes ids = 2;
 + } + + message `Items` contains 4 parameters: + `type`: type of items – e.g. _1_ stands for `TRX`. + `blocks`: blocks in `Items` if there is any. + `block_headers`: block headers if there is any. + `transactions`: transactions if there is any. + + `Items` have four types: `ERR`, `TRX`, `BLOCK` and `BLOCKHEADER`. + `ERR`: error. + `TRX`: transaction. + `BLOCK`: block. + `BLOCKHEADER`: block header. + + message Items {
 + enum ItemType {
 + ERR = 0;
 + TRX = 1;
 + BLOCK = 2;
 + BLOCKHEADER = 3;
 + }
 + ItemType type = 1;
 + repeated Block blocks = 2;
 + repeated BlockHeader + block_headers = 3;
 + repeated Transaction transactions = 4; + } + + `InventoryItems` contains `type` and `items`. + `type`: what type of item. + `items`: items in an `InventoryItems`. + + message InventoryItems {
 + int32 type = 1;
 + repeated bytes items = 2; + 
} + + message `BlockInventory` contains `type`. + `type`: what type of inventory. + + There are 3 types:`SYNC`, `ADVTISE`, `FETCH`. + + // Inventory + message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + message `BlockId` contains `ids` and `type`. + `ids`: the identification of block. + `type`: what type of the block. + + `ids` contains 2 paremeters: + `hash`: the hash of block. + `number`: the hash and height of block. + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; + } + + `ReasonCode`: the type of reason. + + `ReasonCode` contains 15 types of disconnect reasons: + `REQUESTED` + `TCP_ERROR` + `BAD_PROTOCOL` + `USELESS_PEER` + `TOO_MANY_PEERS` + `DUPLICATE_PEER` + `INCOMPATIBLE_PROTOCOL` + `NULL_IDENTITY` + `PEER_QUITING` + `UNEXPECTED_IDENTITY` + `LOCAL_IDENTITY` + `PING_TIMEOUT` + `USER_REASON` + `RESET` + `UNKNOWN` + + enum ReasonCode { + REQUESTED = 0; + TCP_ERROR = 1; + BAD_PROTOCOL = 2; + USELESS_PEER = 3; + TOO_MANY_PEERS = 4; + DUPLICATE_PEER = 5; + INCOMPATIBLE_PROTOCOL = 6; + NULL_IDENTITY = 7; + PEER_QUITING = 8; + UNEXPECTED_IDENTITY = 9; + LOCAL_IDENTITY = 10; + PING_TIMEOUT = 11; + USER_REASON = 12; + RESET = 16; + UNKNOWN = 255; + } + + message`DisconnectMessage` contains `reason`. + `DisconnectMessage`: the message when disconnection occurs. + `reason`: the reason for disconnecting. + + message`HelloMessage` contains 2 parameters: + `HelloMessage`: the message for building connection. + `from`: the nodes that request for building connection. + `version`: the version when connection is built. + + + ++ Wallet Service RPC and blockchain explorer + + `Wallet` service contains several RPCs. + __`GetBalance`__ : + Return balance of an `Account`. + __`CreateTransaction`__ : + Create a transaction by giving a `TransferContract`. A Transaction containing a transaction creation will be returned. + __`BroadcastTransaction`__ : + Broadcast a `Transaction`. A `Return` will be returned indicating if broadcast is success of not. + __`CreateAccount`__ : + Create an account by giving a `AccountCreateContract`. + __`CreatAssetIssue`__ : + Issue an asset by giving a `AssetIssueContract`. + __`ListAccounts`__: + Check out the list of accounts by giving a `ListAccounts`. + __`UpdateAccount`__: + Issue an asset by giving a `UpdateAccountContract`. + __`VoteWitnessAccount`__: + Issue an asset by giving a `VoteWitnessContract`. + __`WitnessList`__: + Check out the list of witnesses by giving a `WitnessList`. + __`UpdateWitness`__: + Issue an asset by giving a `WitnessUpdateContract`. + __`CreateWitness`__: + Issue an asset by giving a `WitnessCreateContract`. + __`TransferAsset`__: + Issue an asset by giving a `TransferAssetContract`. + __`ParticipateAssetIssue`__: + Issue an asset by giving a `ParticipateAssetIssueContract`. + __`ListNodes`__: + Check out the list of nodes by giving a `ListNodes`. + __`GetAssetIssueList`__: + Get the list of issue asset by giving a `GetAssetIssueList`. + __`GetAssetIssueByAccount`__: + Get issue asset by giving a `Account`. + __`GetAssetIssueByName`__: + Get issue asset by giving a`Name`. + __`GetNowBlock`__: + Get block. + __`GetBlockByNum`__: + Get block by block number. + __`TotalTransaction`__: + Check out the total transaction. + + service Wallet { + + rpc GetAccount (Account) returns (Account) { + + }; + + rpc CreateTransaction (TransferContract) returns (Transaction) { + + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + + }; + + rpc ListAccounts (EmptyMessage) returns (AccountList) { + + }; + + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + + }; + + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + + }; + + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + + }; + + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + + }; + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + + }; + + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + + }; + + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + + }; + + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + + } + + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + + } + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + + } + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + + } + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + + } + }; + + `AccountList`: the list of acounts in the blockchain explorer. + message `AccountList` contains one parameter: + `account`: + + message AccountList { + repeated Account accounts = 1; + } + + `WitnessList`: the list of witnesses in the blockchain explorer. + message `WitnessList` contains one parameter: + `witnesses`: + + message WitnessList { + repeated Witness witnesses = 1; + } + + `AssetIssueList`: the list of issue asset in the blockchain explorer. + message `AssetIssueList` contains one parameter: + `assetIssue`: + + message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; + } + + `NodeList`: the list of nodes in the node distribution map. + message `NodeList` contains one parameter: + `nodes`: + + message NodeList { + repeated Node nodes = 1; + } + + `Address`: the address of nodes. + message`Address` contains 2 parameters: + `host`: the host of nodes. + `port`: the port number of nodes. + + message Address { + bytes host = 1; + int32 port = 2; + } + + message `Return` has only one parameter: + `result`: a bool flag. + + message `Return` {
 + bool result = 1; + 
} + ++ The message structure of UDP. + + `Endpoint`: the storage structure of nodes' information. + message`Endpoint` contains 3 parameters: + `address`: the address of nodes. + `port`: the port number. + `nodeId`:the ID of nodes. + + + message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; + } + + `PingMessage`: the message sent from one node to another in the connecting process. + message`PingMessage` contains 4 parameters: + `from`: which node does the message send from. + `to`: which node will the message send to. + `version`: the version of the Internet. + `timestamp`: the timestamp of message. + + message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; + } + + `PongMessage`: the message implies that nodes are connected. + message`PongMessage` contains 3 parameters: + `from`: which node does the message send from. + `echo`: + `timestamp`: the timestamp of message. + + message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; + } + + `FindNeighbours`: the message sent from one node to find another one. + message`FindNeighbours` contains 3 parameters: + `from`: which node does the message send from. + `targetId`: the ID of targeted node. + `timestamp`: the timestamp of message. + + message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; + } + + `FindNeighbour`: the message replied by the neighbour node. + message`Neighbours` contains 3 parameters: + `from`: which node does the message send from. + `neighbours`: the neighbour node. + `timestamp`: the timestamp of message. + + message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; + } + + + +# Please check detailed protocol document that may change with the iteration of the program at any time. Please refer to the latest version. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 000000000..6a2811911 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# protocol [![Build Status](https://travis-ci.org/tronprotocol/protocol.svg?branch=master)](https://travis-ci.org/tronprotocol/protocol) + + +# The protocol of Tron including api and message. + +java-tron, wallet-cli and grpc-gateway + +git subtree pull --prefix src/main/protos/ protocol master + +## Run the included *.sh files to initialize the dependencies + diff --git a/api/api.proto b/api/api.proto new file mode 100644 index 000000000..a84e460b3 --- /dev/null +++ b/api/api.proto @@ -0,0 +1,890 @@ +syntax = "proto3"; +package protocol; + +import "core/Tron.proto"; +import "core/Contract.proto"; +import "google/api/annotations.proto"; + + +option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file +option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/api"; + +service Wallet { + + rpc GetAccount (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccount" + body: "*" + additional_bindings { + get: "/wallet/getaccount" + } + }; + }; + + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/wallet/getaccountbyid" + body: "*" + additional_bindings { + get: "/wallet/getaccountbyid" + } + }; + }; + + //Please use CreateTransaction2 instead of this function. + rpc CreateTransaction (TransferContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createtransaction" + body: "*" + additional_bindings { + get: "/wallet/createtransaction" + } + }; + }; + //Use this function instead of CreateTransaction. + rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { + }; + + rpc BroadcastTransaction (Transaction) returns (Return) { + option (google.api.http) = { + post: "/wallet/broadcasttransaction" + body: "*" + additional_bindings { + get: "/wallet/broadcasttransaction" + } + }; + }; + //Please use UpdateAccount2 instead of this function. + rpc UpdateAccount (AccountUpdateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updateaccount" + body: "*" + additional_bindings { + get: "/wallet/updateaccount" + } + }; + }; + + + rpc SetAccountId (SetAccountIdContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/setaccountid" + body: "*" + additional_bindings { + get: "/wallet/setaccountid" + } + }; + }; + + //Use this function instead of UpdateAccount. + rpc UpdateAccount2 (AccountUpdateContract) returns (TransactionExtention) { + }; + + //Please use VoteWitnessAccount2 instead of this function. + rpc VoteWitnessAccount (VoteWitnessContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/votewitnessaccount" + body: "*" + additional_bindings { + get: "/wallet/votewitnessaccount" + } + }; + }; + + //modify the consume_user_resource_percent + rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { + }; + + //Use this function instead of VoteWitnessAccount. + rpc VoteWitnessAccount2 (VoteWitnessContract) returns (TransactionExtention) { + }; + //Please use CreateAssetIssue2 instead of this function. + rpc CreateAssetIssue (AssetIssueContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createassetissue" + body: "*" + additional_bindings { + get: "/wallet/createassetissue" + } + }; + }; + //Use this function instead of CreateAssetIssue. + rpc CreateAssetIssue2 (AssetIssueContract) returns (TransactionExtention) { + }; + //Please use UpdateWitness2 instead of this function. + rpc UpdateWitness (WitnessUpdateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updatewitness" + body: "*" + additional_bindings { + get: "/wallet/updatewitness" + } + }; + }; + //Use this function instead of UpdateWitness. + rpc UpdateWitness2 (WitnessUpdateContract) returns (TransactionExtention) { + }; + //Please use CreateAccount2 instead of this function. + rpc CreateAccount (AccountCreateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createaccount" + body: "*" + additional_bindings { + get: "/wallet/createaccount" + } + }; + }; + //Use this function instead of CreateAccount. + rpc CreateAccount2 (AccountCreateContract) returns (TransactionExtention) { + } + //Please use CreateWitness2 instead of this function. + rpc CreateWitness (WitnessCreateContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/createwitness" + body: "*" + additional_bindings { + get: "/wallet/createwitness" + } + }; + }; + //Use this function instead of CreateWitness. + rpc CreateWitness2 (WitnessCreateContract) returns (TransactionExtention) { + } + //Please use TransferAsset2 instead of this function. + rpc TransferAsset (TransferAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/transferasset" + body: "*" + additional_bindings { + get: "/wallet/transferasset" + } + }; + } + //Use this function instead of TransferAsset. + rpc TransferAsset2 (TransferAssetContract) returns (TransactionExtention) { + } + //Please use ParticipateAssetIssue2 instead of this function. + rpc ParticipateAssetIssue (ParticipateAssetIssueContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/participateassetissue" + body: "*" + additional_bindings { + get: "/wallet/participateassetissue" + } + }; + } + //Use this function instead of ParticipateAssetIssue. + rpc ParticipateAssetIssue2 (ParticipateAssetIssueContract) returns (TransactionExtention) { + } + //Please use FreezeBalance2 instead of this function. + rpc FreezeBalance (FreezeBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/freezebalance" + body: "*" + additional_bindings { + get: "/wallet/freezebalance" + } + }; + } + //Use this function instead of FreezeBalance. + rpc FreezeBalance2 (FreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeBalance2 instead of this function. + rpc UnfreezeBalance (UnfreezeBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/unfreezebalance" + body: "*" + additional_bindings { + get: "/wallet/unfreezebalance" + } + }; + } + //Use this function instead of UnfreezeBalance. + rpc UnfreezeBalance2 (UnfreezeBalanceContract) returns (TransactionExtention) { + } + //Please use UnfreezeAsset2 instead of this function. + rpc UnfreezeAsset (UnfreezeAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/unfreezeasset" + body: "*" + additional_bindings { + get: "/wallet/unfreezeasset" + } + }; + } + //Use this function instead of UnfreezeAsset. + rpc UnfreezeAsset2 (UnfreezeAssetContract) returns (TransactionExtention) { + } + //Please use WithdrawBalance2 instead of this function. + rpc WithdrawBalance (WithdrawBalanceContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/withdrawbalance" + body: "*" + additional_bindings { + get: "/wallet/withdrawbalance" + } + }; + } + //Use this function instead of WithdrawBalance. + rpc WithdrawBalance2 (WithdrawBalanceContract) returns (TransactionExtention) { + } + //Please use UpdateAsset2 instead of this function. + rpc UpdateAsset (UpdateAssetContract) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/updateasset" + body: "*" + additional_bindings { + get: "/wallet/updateasset" + } + }; + } + //Use this function instead of UpdateAsset. + rpc UpdateAsset2 (UpdateAssetContract) returns (TransactionExtention) { + } + + rpc ProposalCreate (ProposalCreateContract) returns (TransactionExtention) { + } + + rpc ProposalApprove (ProposalApproveContract) returns (TransactionExtention) { + } + + rpc ProposalDelete (ProposalDeleteContract) returns (TransactionExtention) { + } + + rpc BuyStorage (BuyStorageContract) returns (TransactionExtention) { + } + + rpc BuyStorageBytes (BuyStorageBytesContract) returns (TransactionExtention) { + } + + rpc SellStorage (SellStorageContract) returns (TransactionExtention) { + } + + rpc ExchangeCreate (ExchangeCreateContract) returns (TransactionExtention) { + } + + rpc ExchangeInject (ExchangeInjectContract) returns (TransactionExtention) { + } + + rpc ExchangeWithdraw (ExchangeWithdrawContract) returns (TransactionExtention) { + } + + rpc ExchangeTransaction (ExchangeTransactionContract) returns (TransactionExtention) { + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { + option (google.api.http) = { + post: "/wallet/listnodes" + body: "*" + additional_bindings { + get: "/wallet/listnodes" + } + }; + } + + rpc GetAssetIssueByAccount (Account) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getassetissuebyaccount" + body: "*" + additional_bindings { + get: "/wallet/getassetissuebyaccount" + } + }; + } + rpc GetAccountNet (Account) returns (AccountNetMessage) { + option (google.api.http) = { + post: "/wallet/getaccountnet" + body: "*" + additional_bindings { + get: "/wallet/getaccountnet" + } + }; + }; + rpc GetAccountResource (Account) returns (AccountResourceMessage) { + }; + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + option (google.api.http) = { + post: "/wallet/getassetissuebyname" + body: "*" + additional_bindings { + get: "/wallet/getassetissuebyname" + } + }; + } + //Please use GetNowBlock2 instead of this function. + rpc GetNowBlock (EmptyMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getnowblock" + body: "*" + additional_bindings { + get: "/wallet/getnowblock" + } + }; + } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. + rpc GetBlockByNum (NumberMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getblockbynum" + body: "*" + additional_bindings { + get: "/wallet/getblockbynum" + } + }; + } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + + rpc GetBlockById (BytesMessage) returns (Block) { + option (google.api.http) = { + post: "/wallet/getblockbyid" + body: "*" + additional_bindings { + get: "/wallet/getblockbyid" + } + }; + } + //Please use GetBlockByLimitNext2 instead of this function. + rpc GetBlockByLimitNext (BlockLimit) returns (BlockList) { + option (google.api.http) = { + post: "/wallet/getblockbylimitnext" + body: "*" + additional_bindings { + get: "/wallet/getblockbylimitnext" + } + }; + } + //Use this function instead of GetBlockByLimitNext. + rpc GetBlockByLimitNext2 (BlockLimit) returns (BlockListExtention) { + } + //Please use GetBlockByLatestNum2 instead of this function. + rpc GetBlockByLatestNum (NumberMessage) returns (BlockList) { + option (google.api.http) = { + post: "/wallet/getblockbylatestnum" + body: "*" + additional_bindings { + get: "/wallet/getblockbylatestnum" + } + }; + } + //Use this function instead of GetBlockByLatestNum. + rpc GetBlockByLatestNum2 (NumberMessage) returns (BlockListExtention) { + } + rpc GetTransactionById (BytesMessage) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/gettransactionbyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactionbyid" + } + }; + } + + rpc DeployContract (CreateSmartContract) returns (TransactionExtention) { + } + + rpc GetContract (BytesMessage) returns (SmartContract) { + } + + rpc TriggerContract (TriggerSmartContract) returns (TransactionExtention) { + } + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + option (google.api.http) = { + post: "/wallet/listwitnesses" + body: "*" + additional_bindings { + get: "/wallet/listwitnesses" + } + }; + }; + + rpc ListProposals (EmptyMessage) returns (ProposalList) { + option (google.api.http) = { + post: "/wallet/listproposals" + body: "*" + additional_bindings { + get: "/wallet/listproposals" + } + }; + }; + + rpc GetProposalById (BytesMessage) returns (Proposal) { + option (google.api.http) = { + post: "/wallet/getproposalbyid" + body: "*" + additional_bindings { + get: "/wallet/getproposalbyid" + } + }; + }; + + rpc ListExchanges (EmptyMessage) returns (ExchangeList) { + option (google.api.http) = { + post: "/wallet/listexchanges" + body: "*" + additional_bindings { + get: "/wallet/listexchanges" + } + }; + }; + + rpc GetExchangeById (BytesMessage) returns (Exchange) { + option (google.api.http) = { + post: "/wallet/getexchangebyid" + body: "*" + additional_bindings { + get: "/wallet/getexchangebyid" + } + }; + }; + + rpc GetChainParameters (EmptyMessage) returns (ChainParameters) { + option (google.api.http) = { + post: "/wallet/getchainparameters" + body: "*" + additional_bindings { + get: "/wallet/getchainparameters" + } + }; + }; + + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getassetissuelist" + body: "*" + additional_bindings { + get: "/wallet/getassetissuelist" + } + }; + } + rpc GetPaginatedAssetIssueList (PaginatedMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/wallet/getpaginatedassetissuelist" + body: "*" + additional_bindings { + get: "/wallet/getpaginatedassetissuelist" + } + }; + } + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { + option (google.api.http) = { + post: "/wallet/totaltransaction" + body: "*" + additional_bindings { + get: "/wallet/totaltransaction" + } + }; + } + rpc GetNextMaintenanceTime (EmptyMessage) returns (NumberMessage) { + option (google.api.http) = { + post: "/wallet/getnextmaintenancetime" + body: "*" + additional_bindings { + get: "/wallet/getnextmaintenancetime" + } + }; + } + //Warning: do not invoke this interface provided by others. + //Please use GetTransactionSign2 instead of this function. + rpc GetTransactionSign (TransactionSign) returns (Transaction) { + option (google.api.http) = { + post: "/wallet/gettransactionsign" + body: "*" + additional_bindings { + get: "/wallet/gettransactionsign" + } + }; + }; + //Warning: do not invoke this interface provided by others. + //Use this function instead of GetTransactionSign. + rpc GetTransactionSign2 (TransactionSign) returns (TransactionExtention) { + }; + //Warning: do not invoke this interface provided by others. + rpc CreateAddress (BytesMessage) returns (BytesMessage) { + option (google.api.http) = { + post: "/wallet/createaddress" + body: "*" + additional_bindings { + get: "/wallet/createaddress" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransfer" + body: "*" + additional_bindings { + get: "/wallet/easytransfer" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransferByPrivate (EasyTransferByPrivateMessage) returns (EasyTransferResponse) { + option (google.api.http) = { + post: "/wallet/easytransferbyprivate" + body: "*" + additional_bindings { + get: "/wallet/easytransferbyprivate" + } + }; + }; + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + + option (google.api.http) = { + post: "/wallet/generateaddress" + body: "*" + additional_bindings { + get: "/wallet/generateaddress" + } + }; + } + + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/wallet/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/wallet/gettransactioninfobyid" + } + }; + } +}; + + +service WalletSolidity { + + rpc GetAccount (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccount" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccount" + } + }; + }; + rpc GetAccountById (Account) returns (Account) { + option (google.api.http) = { + post: "/walletsolidity/getaccountbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/getaccountbyid" + } + }; + }; + + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { + option (google.api.http) = { + post: "/walletsolidity/listwitnesses" + body: "*" + additional_bindings { + get: "/walletsolidity/listwitnesses" + } + }; + }; + rpc GetAssetIssueList (EmptyMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/walletsolidity/getassetissuelist" + body: "*" + additional_bindings { + get: "/walletsolidity/getassetissuelist" + } + }; + } + rpc GetPaginatedAssetIssueList (PaginatedMessage) returns (AssetIssueList) { + option (google.api.http) = { + post: "/walletsolidity/getpaginatedassetissuelist" + body: "*" + additional_bindings { + get: "/walletsolidity/getpaginatedassetissuelist" + } + }; + } + //Please use GetNowBlock2 instead of this function. + rpc GetNowBlock (EmptyMessage) returns (Block) { + option (google.api.http) = { + post: "/walletsolidity/getnowblock" + body: "*" + additional_bindings { + get: "/walletsolidity/getnowblock" + } + }; + } + //Use this function instead of GetNowBlock. + rpc GetNowBlock2 (EmptyMessage) returns (BlockExtention) { + } + //Please use GetBlockByNum2 instead of this function. + rpc GetBlockByNum (NumberMessage) returns (Block) { + option (google.api.http) = { + post: "/walletsolidity/getblockbynum" + body: "*" + additional_bindings { + get: "/walletsolidity/getblockbynum" + } + }; + } + //Use this function instead of GetBlockByNum. + rpc GetBlockByNum2 (NumberMessage) returns (BlockExtention) { + } + + rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { + } + + rpc GetTransactionById (BytesMessage) returns (Transaction) { + option (google.api.http) = { + post: "/walletsolidity/gettransactionbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/gettransactionbyid" + } + }; + } + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/walletsolidity/gettransactioninfobyid" + body: "*" + additional_bindings { + get: "/walletsolidity/gettransactioninfobyid" + } + }; + } + //Warning: do not invoke this interface provided by others. + rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { + option (google.api.http) = { + post: "/walletsolidity/generateaddress" + body: "*" + additional_bindings { + get: "/walletsolidity/generateaddress" + } + }; + } +}; + +service WalletExtension { + //Please use GetTransactionsFromThis2 instead of this function. + rpc GetTransactionsFromThis (AccountPaginated) returns (TransactionList) { + option (google.api.http) = { + post: "/walletextension/gettransactionsfromthis" + body: "*" + additional_bindings { + get: "/walletextension/gettransactionsfromthis" + } + }; + } + //Use this function instead of GetTransactionsFromThis. + rpc GetTransactionsFromThis2 (AccountPaginated) returns (TransactionListExtention) { + } + //Please use GetTransactionsToThis2 instead of this function. + rpc GetTransactionsToThis (AccountPaginated) returns (TransactionList) { + option (google.api.http) = { + post: "/walletextension/gettransactionstothis" + body: "*" + additional_bindings { + get: "/walletextension/gettransactionstothis" + } + }; + } + //Use this function instead of GetTransactionsToThis. + rpc GetTransactionsToThis2 (AccountPaginated) returns (TransactionListExtention) { + } +}; + +// the api of tron's db +service Database { + // for tapos + rpc getBlockReference (EmptyMessage) returns (BlockReference) { + + } + rpc GetDynamicProperties (EmptyMessage) returns (DynamicProperties) { + + } + rpc GetNowBlock (EmptyMessage) returns (Block) { + + } + rpc GetBlockByNum (NumberMessage) returns (Block) { + + } +}; + +message Return { + enum response_code { + SUCCESS = 0; + SIGERROR = 1; // error in signature + CONTRACT_VALIDATE_ERROR = 2; + CONTRACT_EXE_ERROR = 3; + BANDWITH_ERROR = 4; + DUP_TRANSACTION_ERROR = 5; + TAPOS_ERROR = 6; + TOO_BIG_TRANSACTION_ERROR = 7; + TRANSACTION_EXPIRATION_ERROR = 8; + SERVER_BUSY = 9; + OTHER_ERROR = 20; + } + + bool result = 1; + response_code code = 2; + bytes message = 3; +} + +message BlockReference { + int64 block_num = 1; + bytes block_hash = 2; +} + +// the api of tron's network such as node list. +service Network { + +}; + +message WitnessList { + repeated Witness witnesses = 1; +} +message ProposalList { + repeated Proposal proposals = 1; +} +message ExchangeList { + repeated Exchange exchanges = 1; +} +message AssetIssueList { + repeated AssetIssueContract assetIssue = 1; +} +message BlockList { + repeated Block block = 1; +} +message TransactionList { + repeated Transaction transaction = 1; +} + +// Gossip node list +message NodeList { + repeated Node nodes = 1; +} + +// Gossip node +message Node { + Address address = 1; +} + +// Gossip node address +message Address { + bytes host = 1; + int32 port = 2; +} + +message EmptyMessage { +} +message NumberMessage { + int64 num = 1; +} +message BytesMessage { + bytes value = 1; +} +message TimeMessage { + int64 beginInMilliseconds = 1; + int64 endInMilliseconds = 2; +} +message BlockLimit { + int64 startNum = 1; + int64 endNum = 2; +} +message TransactionLimit { + bytes transactionId = 1; + int64 limitNum = 2; +} +message AccountPaginated { + Account account = 1; + int64 offset = 2; + int64 limit = 3; +} +message TimePaginatedMessage { + TimeMessage timeMessage = 1; + int64 offset = 2; + int64 limit = 3; +} +//deprecated +message AccountNetMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; +} +message AccountResourceMessage { + int64 freeNetUsed = 1; + int64 freeNetLimit = 2; + int64 NetUsed = 3; + int64 NetLimit = 4; + map assetNetUsed = 5; + map assetNetLimit = 6; + int64 TotalNetLimit = 7; + int64 TotalNetWeight = 8; + + int64 EnergyUsed = 13; + int64 EnergyLimit = 14; + int64 TotalEnergyLimit = 15; + int64 TotalEnergyWeight = 16; + + int64 storageUsed = 21; + int64 storageLimit = 22; +} + +message PaginatedMessage { + int64 offset = 1; + int64 limit = 2; +} + +message EasyTransferMessage { + bytes passPhrase = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferByPrivateMessage { + bytes privateKey = 1; + bytes toAddress = 2; + int64 amount = 3; +} + +message EasyTransferResponse { + Transaction transaction = 1; + Return result = 2; + bytes txid = 3; //transaction id = sha256(transaction.rowdata) +} + +message AddressPrKeyPairMessage { + string address = 1; + string privateKey = 2; +} + +message TransactionExtention { + Transaction transaction = 1; + bytes txid = 2; //transaction id = sha256(transaction.rowdata) + repeated bytes constant_result = 3; + Return result = 4; +} + +message BlockExtention { + repeated TransactionExtention transactions = 1; + BlockHeader block_header = 2; + bytes blockid = 3; +} + +message BlockListExtention { + repeated BlockExtention block = 1; +} + +message TransactionListExtention { + repeated TransactionExtention transaction = 1; +} \ No newline at end of file diff --git a/core/Contract.proto b/core/Contract.proto new file mode 100644 index 000000000..5d5c1421f --- /dev/null +++ b/core/Contract.proto @@ -0,0 +1,228 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Contract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message AccountCreateContract { + bytes owner_address = 1; + bytes account_address = 2; + AccountType type = 3; +} + +// Update account name. Account name is not unique now. +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} + +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + +message TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 amount = 3; +} + +message TransferAssetContract { + bytes asset_name = 1; + bytes owner_address = 2; + bytes to_address = 3; + int64 amount = 4; +} + + +message VoteAssetContract { + bytes owner_address = 1; + repeated bytes vote_address = 2; + bool support = 3; + int32 count = 5; +} + +message VoteWitnessContract { + message Vote { + bytes vote_address = 1; + int64 vote_count = 2; + } + bytes owner_address = 1; + repeated Vote votes = 2; + bool support = 3; +} + +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + +message WitnessCreateContract { + bytes owner_address = 1; + bytes url = 2; +} + +message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; +} + +message AssetIssueContract { + message FrozenSupply { + int64 frozen_amount = 1; + int64 frozen_days = 2; + } + bytes owner_address = 1; + bytes name = 2; + bytes abbr = 3; + int64 total_supply = 4; + repeated FrozenSupply frozen_supply = 5; + int32 trx_num = 6; + int32 num = 8; + int64 start_time = 9; + int64 end_time = 10; + int64 order = 11; // the order of tokens of the same name + int32 vote_score = 16; + bytes description = 20; + bytes url = 21; + int64 free_asset_net_limit = 22; + int64 public_free_asset_net_limit = 23; + int64 public_free_asset_net_usage = 24; + int64 public_latest_free_net_time = 25; +} + +message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; // the namekey of target asset, include name and order + int64 amount = 4; // the amount of drops +} + + +enum ResourceCode { + BANDWIDTH = 0x00; + ENERGY = 0x01; +} + +message FreezeBalanceContract { + bytes owner_address = 1; + int64 frozen_balance = 2; + int64 frozen_duration = 3; + + ResourceCode resource = 10; +} + +message UnfreezeBalanceContract { + bytes owner_address = 1; + + ResourceCode resource = 10; +} + +message UnfreezeAssetContract { + bytes owner_address = 1; +} + +message WithdrawBalanceContract { + bytes owner_address = 1; +} + +message UpdateAssetContract { + bytes owner_address = 1; + bytes description = 2; + bytes url = 3; + int64 new_limit = 4; + int64 new_public_limit = 5; +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} + +message ExchangeCreateContract { + bytes owner_address = 1; + bytes first_token_id = 2; + int64 first_token_balance = 3; + bytes second_token_id = 4; + int64 second_token_balance = 5; +} + +message ExchangeInjectContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeWithdrawContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeTransactionContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} \ No newline at end of file diff --git a/core/Discover.proto b/core/Discover.proto new file mode 100644 index 000000000..4cc0d83b0 --- /dev/null +++ b/core/Discover.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package protocol; + + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Discover"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message Endpoint { + bytes address = 1; + int32 port = 2; + bytes nodeId = 3; +} + +message PingMessage { + Endpoint from = 1; + Endpoint to = 2; + int32 version = 3; + int64 timestamp = 4; +} + +message PongMessage { + Endpoint from = 1; + int32 echo = 2; + int64 timestamp = 3; +} + +message FindNeighbours { + Endpoint from = 1; + bytes targetId = 2; + int64 timestamp = 3; +} + +message Neighbours { + Endpoint from = 1; + repeated Endpoint neighbours = 2; + int64 timestamp = 3; +} + +message BackupMessage { + bool flag = 1; + int32 priority = 2; +} \ No newline at end of file diff --git a/core/Tron.proto b/core/Tron.proto new file mode 100644 index 000000000..ed9526c1a --- /dev/null +++ b/core/Tron.proto @@ -0,0 +1,484 @@ +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "core/Discover.proto"; + +package protocol; + + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "Protocol"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +enum AccountType { + Normal = 0; + AssetIssue = 1; + Contract = 2; +} + +// AccountId, (name, address) use name, (null, address) use address, (name, null) use name, +message AccountId { + bytes name = 1; + bytes address = 2; +} + +// vote message +message Vote { + // the super rep address + bytes vote_address = 1; + // the vote num to this super rep. + int64 vote_count = 2; +} + +// Proposal +message Proposal { + int64 proposal_id = 1; + bytes proposer_address = 2; + map parameters = 3; + int64 expiration_time = 4; + int64 create_time = 5; + repeated bytes approvals = 6; + enum State { + PENDING = 0; + DISAPPROVED = 1; + APPROVED = 2; + CANCELED = 3; + } + State state = 7; +} + +// Exchange +message Exchange { + int64 exchange_id = 1; + bytes creator_address = 2; + int64 create_time = 3; + bytes first_token_id = 6; + int64 first_token_balance = 7; + bytes second_token_id = 8; + int64 second_token_balance = 9; +} + +message ChainParameters { + repeated ChainParameter chainParameter = 1; + message ChainParameter { + string key = 1; + int64 value = 2; + } +} + +/* Account */ +message Account { + /* frozen balance */ + message Frozen { + int64 frozen_balance = 1; // the frozen trx balance + int64 expire_time = 2; // the expire time + } + bytes account_name = 1; + AccountType type = 2; + // the create address + bytes address = 3; + // the trx balance + int64 balance = 4; + // the votes + repeated Vote votes = 5; + // the other asset owned by this account + map asset = 6; + // latest asset operation time + + // the frozen balance + repeated Frozen frozen = 7; + // bandwidth, get from frozen + int64 net_usage = 8; + + // this account create time + int64 create_time = 0x09; + // this last operation time, including transfer, voting and so on. //FIXME fix grammar + int64 latest_opration_time = 10; + // witness block producing allowance + int64 allowance = 0x0B; + // last withdraw time + int64 latest_withdraw_time = 0x0C; + // not used so far + bytes code = 13; + bool is_witness = 14; + bool is_committee = 15; + // frozen asset(for asset issuer) + repeated Frozen frozen_supply = 16; + // asset_issued_name + bytes asset_issued_name = 17; + map latest_asset_operation_time = 18; + + int64 free_net_usage = 19; + map free_asset_net_usage = 20; + int64 latest_consume_time = 21; + int64 latest_consume_free_time = 22; + + bytes account_id = 23; + + message AccountResource { + // energy resource, get from frozen + int64 energy_usage = 1; + // the frozen balance for energy + Frozen frozen_balance_for_energy = 2; + int64 latest_consume_time_for_energy = 3; + + // storage resource, get from market + int64 storage_limit = 6; + int64 storage_usage = 7; + int64 latest_exchange_storage_time = 8; + } + AccountResource account_resource = 26; + + bytes codeHash = 30; +} + +message authority { + AccountId account = 1; + bytes permission_name = 2; +} + +message permission { + AccountId account = 1; +} + +// Witness +message Witness { + bytes address = 1; + int64 voteCount = 2; + bytes pubKey = 3; + string url = 4; + int64 totalProduced = 5; + int64 totalMissed = 6; + int64 latestBlockNum = 7; + int64 latestSlotNum = 8; + bool isJobs = 9; +} + +// Vote Change +message Votes { + bytes address = 1; + repeated Vote old_votes = 2; + repeated Vote new_votes = 3; +} + +// Transcation + +message TXOutput { + int64 value = 1; + bytes pubKeyHash = 2; +} + +message TXInput { + message raw { + bytes txID = 1; + int64 vout = 2; + bytes pubKey = 3; + } + raw raw_data = 1; + bytes signature = 4; +} + +message TXOutputs { + repeated TXOutput outputs = 1; +} + +message ResourceReceipt { + int64 energy_usage = 1; + int64 energy_fee = 2; + int64 origin_energy_usage = 3; + int64 energy_usage_total = 4; + int64 net_usage = 5; + int64 net_fee = 6; + Transaction.Result.contractResult result = 7; +} + +message Transaction { + message Contract { + enum ContractType { + AccountCreateContract = 0; + TransferContract = 1; + TransferAssetContract = 2; + VoteAssetContract = 3; + VoteWitnessContract = 4; + WitnessCreateContract = 5; + AssetIssueContract = 6; + WitnessUpdateContract = 8; + ParticipateAssetIssueContract = 9; + AccountUpdateContract = 10; + FreezeBalanceContract = 11; + UnfreezeBalanceContract = 12; + WithdrawBalanceContract = 13; + UnfreezeAssetContract = 14; + UpdateAssetContract = 15; + ProposalCreateContract = 16; + ProposalApproveContract = 17; + ProposalDeleteContract = 18; + SetAccountIdContract = 19; + CustomContract = 20; + // BuyStorageContract = 21; + // BuyStorageBytesContract = 22; + // SellStorageContract = 23; + CreateSmartContract = 30; + TriggerSmartContract = 31; + GetContract = 32; + UpdateSettingContract = 33; + ExchangeCreateContract = 41; + ExchangeInjectContract = 42; + ExchangeWithdrawContract = 43; + ExchangeTransactionContract = 44; + } + ContractType type = 1; + google.protobuf.Any parameter = 2; + bytes provider = 3; + bytes ContractName = 4; + } + + message Result { + enum code { + SUCESS = 0; + FAILED = 1; + } + enum contractResult { + DEFAULT = 0; + SUCCESS = 1; + REVERT = 2; + BAD_JUMP_DESTINATION = 3; + OUT_OF_MEMORY = 4; + PRECOMPILED_CONTRACT = 5; + STACK_TOO_SMALL = 6; + STACK_TOO_LARGE = 7; + ILLEGAL_OPERATION = 8; + STACK_OVERFLOW = 9; + OUT_OF_ENERGY = 10; + OUT_OF_TIME = 11; + JVM_STACK_OVER_FLOW = 12; + UNKNOWN = 13; + } + int64 fee = 1; + code ret = 2; + contractResult contractRet = 3; + + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; + } + + message raw { + bytes ref_block_bytes = 1; + int64 ref_block_num = 3; + bytes ref_block_hash = 4; + int64 expiration = 8; + repeated authority auths = 9; + // data not used + bytes data = 10; + //only support size = 1, repeated list here for extension + repeated Contract contract = 11; + // scripts not used + bytes scripts = 12; + int64 timestamp = 14; + int64 fee_limit = 18; + } + + raw raw_data = 1; + // only support size = 1, repeated list here for muti-sig extension + repeated bytes signature = 2; + repeated Result ret = 5; +} + +message TransactionInfo { + enum code { + SUCESS = 0; + FAILED = 1; + } + message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + } + bytes id = 1; + int64 fee = 2; + int64 blockNumber = 3; + int64 blockTimeStamp = 4; + repeated bytes contractResult = 5; + bytes contract_address = 6; + ResourceReceipt receipt = 7; + repeated Log log = 8; + code result = 9; + bytes resMessage = 10; + + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; +} + +message Transactions { + repeated Transaction transactions = 1; +} + +message TransactionSign { + Transaction transaction = 1; + bytes privateKey = 2; +} + +message BlockHeader { + message raw { + int64 timestamp = 1; + bytes txTrieRoot = 2; + bytes parentHash = 3; + //bytes nonce = 5; + //bytes difficulty = 6; + int64 number = 7; + int64 witness_id = 8; + bytes witness_address = 9; + int32 version = 10; + } + raw raw_data = 1; + bytes witness_signature = 2; +} + +// block +message Block { + repeated Transaction transactions = 1; + BlockHeader block_header = 2; +} + +message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; +} + +// Inventory +message BlockInventory { + enum Type { + SYNC = 0; + ADVTISE = 1; + FETCH = 2; + } + + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + Type type = 2; +} + +message Inventory { + enum InventoryType { + TRX = 0; + BLOCK = 1; + } + InventoryType type = 1; + repeated bytes ids = 2; +} + +message Items { + enum ItemType { + ERR = 0; + TRX = 1; + BLOCK = 2; + BLOCKHEADER = 3; + } + + ItemType type = 1; + repeated Block blocks = 2; + repeated BlockHeader block_headers = 3; + repeated Transaction transactions = 4; +} + +// DynamicProperties +message DynamicProperties { + int64 last_solidity_block_num = 1; +} + +enum ReasonCode { + REQUESTED = 0x00; + BAD_PROTOCOL = 0x02; + TOO_MANY_PEERS = 0x04; + DUPLICATE_PEER = 0x05; + INCOMPATIBLE_PROTOCOL = 0x06; + NULL_IDENTITY = 0x07; + PEER_QUITING = 0x08; + UNEXPECTED_IDENTITY = 0x09; + LOCAL_IDENTITY = 0x0A; + PING_TIMEOUT = 0x0B; + USER_REASON = 0x10; + RESET = 0x11; + SYNC_FAIL = 0x12; + FETCH_FAIL = 0x13; + BAD_TX = 0x14; + BAD_BLOCK = 0x15; + FORKED = 0x16; + UNLINKABLE = 0x17; + INCOMPATIBLE_VERSION = 0x18; + INCOMPATIBLE_CHAIN = 0x19; + TIME_OUT = 0x20; + CONNECT_FAIL = 0x21; + TOO_MANY_PEERS_WITH_SAME_IP = 0x22; + UNKNOWN = 0xFF; +} + +message DisconnectMessage { + ReasonCode reason = 1; +} + +message HelloMessage { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + + Endpoint from = 1; + int32 version = 2; + int64 timestamp = 3; + BlockId genesisBlockId = 4; + BlockId solidBlockId = 5; + BlockId headBlockId = 6; +} + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + int64 consume_user_resource_percent = 6; + string name = 7; + +} \ No newline at end of file diff --git a/core/TronInventoryItems.proto b/core/TronInventoryItems.proto new file mode 100644 index 000000000..a82d2de45 --- /dev/null +++ b/core/TronInventoryItems.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "TronInventoryItems"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message InventoryItems { + int32 type = 1; + repeated bytes items = 2; +} \ No newline at end of file diff --git a/install-googleapis.sh b/install-googleapis.sh new file mode 100755 index 000000000..0d44f6108 --- /dev/null +++ b/install-googleapis.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway +go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger +go get -u github.com/golang/protobuf/protoc-gen-go + +wget http://central.maven.org/maven2/com/google/api/grpc/googleapis-common-protos/0.0.3/googleapis-common-protos-0.0.3.jar +jar xvf googleapis-common-protos-0.0.3.jar +cp -r google/ $HOME/protobuf/include/ +ls -l + + + diff --git a/install-protobuf.sh b/install-protobuf.sh new file mode 100755 index 000000000..b3a8cb5cb --- /dev/null +++ b/install-protobuf.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e +# check to see if protobuf folder is empty +if [ ! -d "$HOME/protobuf/lib" ]; then + wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.tar.gz + tar -xzvf protobuf-all-3.5.1.tar.gz + cd protobuf-3.5.1 && ./configure --prefix=$HOME/protobuf && make && make install +else + echo "Using cached directory." +fi From 5df54b6a070b0cd8f943ab711e6c9442adb7c2fc Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sun, 2 Sep 2018 21:05:08 +0800 Subject: [PATCH 012/445] update Tron.proto --- core/Tron.proto | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 52ef02b92..6db3a6973 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -195,16 +195,13 @@ message TXOutputs { } message ResourceReceipt { - enum code { - SUCCESS = 0; - FAILED = 1; - } int64 energy_usage = 1; int64 energy_fee = 2; int64 origin_energy_usage = 3; int64 energy_usage_total = 4; int64 net_usage = 5; int64 net_fee = 6; + Transaction.Result.contractResult result = 7; } message Transaction { @@ -250,7 +247,6 @@ message Transaction { google.protobuf.Any parameter = 2; bytes provider = 3; bytes ContractName = 4; - } message Result { @@ -258,8 +254,25 @@ message Transaction { SUCESS = 0; FAILED = 1; } + enum contractResult { + DEFAULT = 0; + SUCCESS = 1; + REVERT = 2; + BAD_JUMP_DESTINATION = 3; + OUT_OF_MEMORY = 4; + PRECOMPILED_CONTRACT = 5; + STACK_TOO_SMALL = 6; + STACK_TOO_LARGE = 7; + ILLEGAL_OPERATION = 8; + STACK_OVERFLOW = 9; + OUT_OF_ENERGY = 10; + OUT_OF_TIME = 11; + JVM_STACK_OVER_FLOW = 12; + UNKNOWN = 13; + } int64 fee = 1; code ret = 2; + contractResult contractRet = 3; int64 withdraw_amount = 15; int64 unfreeze_amount = 16; @@ -331,6 +344,7 @@ message BlockHeader { int64 number = 7; int64 witness_id = 8; bytes witness_address = 9; + int32 version = 10; } raw raw_data = 1; bytes witness_signature = 2; @@ -440,17 +454,6 @@ message HelloMessage { BlockId headBlockId = 6; } -message StorageItem { - bytes contract_address = 1; - map items = 2; -} - -message StorageRow { - bytes key = 1; // composition of contract_address and storage_key - bytes value = 2; -} - - message SmartContract { message ABI { message Entry { From 6a6498dbb7d4556ef4e93ba711de4a8be62d31df Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sun, 2 Sep 2018 21:56:51 +0800 Subject: [PATCH 013/445] alter parent type from bytes to string --- core/Tron.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 6db3a6973..372427ed1 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -140,7 +140,7 @@ message Key { message Permission { string name = 1; int64 threshold = 2; - bytes parent = 3; + string parent = 3; repeated Key keys = 4; } From 2037ab43fd9ca5acfe28843e331887847250457a Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Wed, 5 Sep 2018 12:44:16 +0800 Subject: [PATCH 014/445] Merge commit 'dba3ca75d92c008f66b720a26fd71c7a7364338c' into feature/new_exchange_transaction --- core/Contract.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Contract.proto b/core/Contract.proto index 5d5c1421f..18eafded8 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -225,4 +225,5 @@ message ExchangeTransactionContract { int64 exchange_id = 2; bytes token_id = 3; int64 quant = 4; + int64 expected = 5; } \ No newline at end of file From f25811ccdfd45b63a40df71b18097327fca8ebdd Mon Sep 17 00:00:00 2001 From: zhaohong Date: Sat, 8 Sep 2018 12:33:48 +0800 Subject: [PATCH 015/445] add comments for account proto. --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index ed9526c1a..faeb7a7fb 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -73,6 +73,7 @@ message Account { int64 frozen_balance = 1; // the frozen trx balance int64 expire_time = 2; // the expire time } + // account nick name bytes account_name = 1; AccountType type = 2; // the create address @@ -113,6 +114,7 @@ message Account { int64 latest_consume_time = 21; int64 latest_consume_free_time = 22; + // the identity of this account, case insensitive bytes account_id = 23; message AccountResource { From c6c4a96cba9472f9c7c4a02ae18b9a92a472d22d Mon Sep 17 00:00:00 2001 From: zhaohong Date: Sat, 8 Sep 2018 12:50:46 +0800 Subject: [PATCH 016/445] fix the comment --- core/Tron.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index faeb7a7fb..38c4f3a93 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -84,7 +84,6 @@ message Account { repeated Vote votes = 5; // the other asset owned by this account map asset = 6; - // latest asset operation time // the frozen balance repeated Frozen frozen = 7; From 5c29de827fc3ec6ccfee964631c9d1d7a0232646 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 12 Sep 2018 16:04:30 +0800 Subject: [PATCH 017/445] fix conflict --- core/Tron.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 9b96c6eaf..0c12f900a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -128,9 +128,9 @@ message Account { int64 latest_consume_time_for_energy = 3; //Frozen balance provided by other accounts to this account - int64 acquired_delegated_frozen_balance_for_cpu = 4; + int64 acquired_delegated_frozen_balance_for_energy = 4; //Frozen balances provided to other accounts - int64 delegated_frozen_balance_for_cpu = 5; + int64 delegated_frozen_balance_for_energy = 5; // storage resource, get from market int64 storage_limit = 6; @@ -148,7 +148,7 @@ message DelegatedResource{ bytes from = 1; bytes to = 2; int64 frozen_balance_for_bandwidth = 3; - int64 frozen_balance_for_cpu = 4; + int64 frozen_balance_for_energy = 4; int64 expire_time = 5; } From b5b2f528bbee810669541f3cf3d50b0799c29e56 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 12 Sep 2018 20:04:02 +0800 Subject: [PATCH 018/445] feat: add permissionaddkey http api --- api/api.proto | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index f910b8668..988e66439 100644 --- a/api/api.proto +++ b/api/api.proto @@ -560,19 +560,43 @@ service Wallet { } rpc AccountPermissionUpdate (AccountPermissionUpdateContract) returns (TransactionExtention) { - + option (google.api.http) = { + post: "/wallet/accountpermissionupdate" + body: "*" + additional_bindings { + get: "/wallet/accountpermissionupdate" + } + }; } rpc PermissionAddKey (PermissionAddKeyContract) returns (TransactionExtention) { - + option (google.api.http) = { + post: "/wallet/permissionaddkey" + body: "*" + additional_bindings { + get: "/wallet/permissionaddkey" + } + }; } rpc PermissionUpdateKey (PermissionUpdateKeyContract) returns (TransactionExtention) { - + option (google.api.http) = { + post: "/wallet/permissionupdatekey" + body: "*" + additional_bindings { + get: "/wallet/permissionupdatekey" + } + }; } rpc PermissionDeleteKey (PermissionDeleteKeyContract) returns (TransactionExtention) { - + option (google.api.http) = { + post: "/wallet/permissiondeletekey" + body: "*" + additional_bindings { + get: "/wallet/permissiondeletekey" + } + }; } rpc AddSign (TransactionSign) returns (TransactionExtention) { From d5581097b31c8fa9c5cd9023d9f9a087f6ed1097 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 14 Sep 2018 14:42:33 +0800 Subject: [PATCH 019/445] add proto --- api/api.proto | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index a84e460b3..ffe093821 100644 --- a/api/api.proto +++ b/api/api.proto @@ -435,7 +435,15 @@ service Wallet { } }; }; - + rpc GetPaginatedExchangeList (PaginatedMessage) returns (ExchangeList) { + option (google.api.http) = { + post: "/wallet/getpaginatedxxchangelist" + body: "*" + additional_bindings { + get: "/wallet/getpaginatedxxchangelist" + } + }; + } rpc GetExchangeById (BytesMessage) returns (Exchange) { option (google.api.http) = { post: "/wallet/getexchangebyid" @@ -474,6 +482,8 @@ service Wallet { } }; } + + rpc TotalTransaction (EmptyMessage) returns (NumberMessage) { option (google.api.http) = { post: "/wallet/totaltransaction" From 54951d075e07060b26ea150788fa0ed73a230873 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 14 Sep 2018 15:46:06 +0800 Subject: [PATCH 020/445] add function named getPaginatedProposalList --- api/api.proto | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/api/api.proto b/api/api.proto index ffe093821..85dd21649 100644 --- a/api/api.proto +++ b/api/api.proto @@ -415,7 +415,15 @@ service Wallet { } }; }; - + rpc GetPaginatedProposalList (PaginatedMessage) returns (ProposalList) { + option (google.api.http) = { + post: "/wallet/getpaginatedproposallist" + body: "*" + additional_bindings { + get: "/wallet/getpaginatedproposallist" + } + }; + } rpc GetProposalById (BytesMessage) returns (Proposal) { option (google.api.http) = { post: "/wallet/getproposalbyid" @@ -437,10 +445,10 @@ service Wallet { }; rpc GetPaginatedExchangeList (PaginatedMessage) returns (ExchangeList) { option (google.api.http) = { - post: "/wallet/getpaginatedxxchangelist" + post: "/wallet/getpaginatedexchangelist" body: "*" additional_bindings { - get: "/wallet/getpaginatedxxchangelist" + get: "/wallet/getpaginatedexchangelist" } }; } From 16827f21bd3482689fa49a1660b82ab9c298e3ac Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 25 Sep 2018 17:22:01 +0800 Subject: [PATCH 021/445] update proto --- api/api.proto | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/api.proto b/api/api.proto index a84e460b3..7a3d876ec 100644 --- a/api/api.proto +++ b/api/api.proto @@ -32,6 +32,7 @@ service Wallet { }; }; + //Please use CreateTransaction2 instead of this function. rpc CreateTransaction (TransferContract) returns (Transaction) { option (google.api.http) = { @@ -406,6 +407,18 @@ service Wallet { }; }; + + rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { + option (google.api.http) = { + post: "/wallet/getdelegatedresource" + body: "*" + additional_bindings { + get: "/wallet/getdelegatedresource" + } + }; + }; + + rpc ListProposals (EmptyMessage) returns (ProposalList) { option (google.api.http) = { post: "/wallet/listproposals" @@ -763,6 +776,14 @@ message BlockList { message TransactionList { repeated Transaction transaction = 1; } +message DelegatedResourceMessage { + bytes fromAddress = 1; + bytes toAddress = 2; +} +message DelegatedResourceList { + repeated DelegatedResource delegatedResource = 1; +} + // Gossip node list message NodeList { From 67e53443ab8ef11ea1b139b6175b602dc1426722 Mon Sep 17 00:00:00 2001 From: taihaofu Date: Tue, 9 Oct 2018 15:46:28 +0800 Subject: [PATCH 022/445] add internaltransactioni list to transactionInfo --- core/Tron.proto | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 38c4f3a93..9f702eb2f 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -308,6 +308,7 @@ message TransactionInfo { int64 withdraw_amount = 15; int64 unfreeze_amount = 16; + repeated InternalTransaction internal_transactions = 17; } message Transactions { @@ -482,4 +483,15 @@ message SmartContract { int64 consume_user_resource_percent = 6; string name = 7; +} + +message InternalTransaction { + bytes hash = 1; + bytes caller_address = 2; + bytes transferTo_address = 3; + message InternalTransactionTransferInfo{ + int64 callValue = 1; + bytes tokenName = 2; + } + repeated InternalTransactionTransferInfo transferInfo = 4; } \ No newline at end of file From b1d89b2702f1381e1ac2992d83e02f96b81f086b Mon Sep 17 00:00:00 2001 From: taihaofu Date: Wed, 10 Oct 2018 20:15:44 +0800 Subject: [PATCH 023/445] refine code --- core/Tron.proto | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 9f702eb2f..15b6fae8b 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -486,12 +486,18 @@ message SmartContract { } message InternalTransaction { + // internalTransaction identity, the root InternalTransaction hash + // should equals to root transaction id. bytes hash = 1; + // the one send trx (TBD: or token) via function bytes caller_address = 2; + // the one recieve trx (TBD: or token) via function bytes transferTo_address = 3; - message InternalTransactionTransferInfo{ + message CallValueInfo{ + // trx (TBD: or token) value int64 callValue = 1; + // TBD: tokenName, trx should be empty bytes tokenName = 2; } - repeated InternalTransactionTransferInfo transferInfo = 4; + repeated CallValueInfo callValueInfo = 4; } \ No newline at end of file From a2fc2d6df0701778c20024dd49727a9f9340355e Mon Sep 17 00:00:00 2001 From: taihaofu Date: Tue, 23 Oct 2018 11:28:13 +0800 Subject: [PATCH 024/445] add note and reject info to transactioninfo db --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 15b6fae8b..71c430522 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -500,4 +500,6 @@ message InternalTransaction { bytes tokenName = 2; } repeated CallValueInfo callValueInfo = 4; + bytes note = 5; + bool rejected = 6; } \ No newline at end of file From 91a8b0e8acec4c858744aa433dd48df5d68d20d5 Mon Sep 17 00:00:00 2001 From: taihaofu Date: Tue, 23 Oct 2018 11:28:13 +0800 Subject: [PATCH 025/445] add note and reject info to transactioninfo db (cherry picked from commit ea7619f) --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 15b6fae8b..71c430522 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -500,4 +500,6 @@ message InternalTransaction { bytes tokenName = 2; } repeated CallValueInfo callValueInfo = 4; + bytes note = 5; + bool rejected = 6; } \ No newline at end of file From 8b78644493b205c2b924533310b7a251b4a70e7e Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Fri, 26 Oct 2018 16:41:44 +0800 Subject: [PATCH 026/445] add expire_time_for_energy in message DelegatedResource --- core/Tron.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 3553c1a58..9aec05ea9 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -150,7 +150,8 @@ message DelegatedResource { bytes to = 2; int64 frozen_balance_for_bandwidth = 3; int64 frozen_balance_for_energy = 4; - int64 expire_time = 5; + int64 expire_time_for_bandwidth = 5; + int64 expire_time_for_energy = 6; } message authority { From ef55dd2ed6744f4538479cdeb216c9185197fa08 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Sat, 27 Oct 2018 13:00:44 +0800 Subject: [PATCH 027/445] remove to_address in Freeze and Unfreeze contract --- core/Contract.proto | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 572a6f4de..18ad7c08c 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -131,7 +131,6 @@ message FreezeBalanceContract { int64 frozen_duration = 3; ResourceCode resource = 10; - bytes to_address = 11; bytes receiver_address = 15; } @@ -139,8 +138,7 @@ message UnfreezeBalanceContract { bytes owner_address = 1; ResourceCode resource = 10; - bytes to_address = 11; - bytes receiver_address = 13; + bytes receiver_address = 15; } message UnfreezeAssetContract { From f1de42c14b6c9825d58d9317f1d2499cf678436a Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Mon, 29 Oct 2018 11:46:21 +0800 Subject: [PATCH 028/445] rm to_address in Frozen --- core/Tron.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 9aec05ea9..c10d44733 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -72,7 +72,6 @@ message Account { message Frozen { int64 frozen_balance = 1; // the frozen trx balance int64 expire_time = 2; // the expire time - bytes to_address = 3; // resources are delegated to this address } // account nick name bytes account_name = 1; From 4e72d9add6b220b9272985c5410165b4214d082c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 29 Oct 2018 12:29:05 +0800 Subject: [PATCH 029/445] add DelegatedResourceAccountIndex --- core/Tron.proto | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index c10d44733..bca321f7c 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -522,4 +522,10 @@ message InternalTransaction { repeated CallValueInfo callValueInfo = 4; bytes note = 5; bool rejected = 6; +} + +message DelegatedResourceAccountIndex { + bytes account = 1; + repeated bytes fromAccounts = 2; + repeated bytes toAccounts = 3; } \ No newline at end of file From 6d1d2af29854421aac5edde83fc5250b4061ca0c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 29 Oct 2018 15:08:26 +0800 Subject: [PATCH 030/445] add GetDelegatedResourceAccountIndex --- api/api.proto | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/api.proto b/api/api.proto index d7b94f9e2..23d9adef9 100644 --- a/api/api.proto +++ b/api/api.proto @@ -418,6 +418,17 @@ service Wallet { }; }; + rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { + option (google.api.http) = { + post: "/wallet/getdelegatedresourceaccountindex" + body: "*" + additional_bindings { + get: "/wallet/getdelegatedresourceaccountindex" + } + }; + }; + + rpc ListProposals (EmptyMessage) returns (ProposalList) { option (google.api.http) = { From 23b8057eda24f632df78ca4601f5ba53815628c7 Mon Sep 17 00:00:00 2001 From: Heng Zhang Date: Mon, 29 Oct 2018 16:44:40 +0800 Subject: [PATCH 031/445] add energy limit. --- api/api.proto | 18 +++++++++++------- core/Contract.proto | 6 ++++++ core/Tron.proto | 3 ++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/api/api.proto b/api/api.proto index 85dd21649..7d09d812b 100644 --- a/api/api.proto +++ b/api/api.proto @@ -96,6 +96,10 @@ service Wallet { rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; + //modify the energy_limit + rpc UpdateSettingForEnergyLimit (UpdateSettingContractForEnergyLimit) returns (TransactionExtention) { + }; + //Use this function instead of VoteWitnessAccount. rpc VoteWitnessAccount2 (VoteWitnessContract) returns (TransactionExtention) { }; @@ -415,15 +419,15 @@ service Wallet { } }; }; - rpc GetPaginatedProposalList (PaginatedMessage) returns (ProposalList) { - option (google.api.http) = { + rpc GetPaginatedProposalList (PaginatedMessage) returns (ProposalList) { + option (google.api.http) = { post: "/wallet/getpaginatedproposallist" body: "*" additional_bindings { get: "/wallet/getpaginatedproposallist" } }; - } + } rpc GetProposalById (BytesMessage) returns (Proposal) { option (google.api.http) = { post: "/wallet/getproposalbyid" @@ -444,14 +448,14 @@ service Wallet { }; }; rpc GetPaginatedExchangeList (PaginatedMessage) returns (ExchangeList) { - option (google.api.http) = { + option (google.api.http) = { post: "/wallet/getpaginatedexchangelist" body: "*" additional_bindings { get: "/wallet/getpaginatedexchangelist" } }; - } + } rpc GetExchangeById (BytesMessage) returns (Exchange) { option (google.api.http) = { post: "/wallet/getexchangebyid" @@ -878,7 +882,7 @@ message EasyTransferByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) + bytes txid = 3; //transaction id = sha256(transaction.rowdata) } message AddressPrKeyPairMessage { @@ -888,7 +892,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) repeated bytes constant_result = 3; Return result = 4; } diff --git a/core/Contract.proto b/core/Contract.proto index 18eafded8..7de71bb97 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -78,6 +78,12 @@ message UpdateSettingContract { int64 consume_user_resource_percent = 3; } +message UpdateSettingContractForEnergyLimit { + bytes owner_address = 1; + bytes contract_address = 2; + bytes energy_limit = 3; +} + message WitnessCreateContract { bytes owner_address = 1; bytes url = 2; diff --git a/core/Tron.proto b/core/Tron.proto index 71c430522..ea1cbf851 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -482,6 +482,7 @@ message SmartContract { int64 call_value = 5; int64 consume_user_resource_percent = 6; string name = 7; + int64 energy_limit = 8; } @@ -493,7 +494,7 @@ message InternalTransaction { bytes caller_address = 2; // the one recieve trx (TBD: or token) via function bytes transferTo_address = 3; - message CallValueInfo{ + message CallValueInfo { // trx (TBD: or token) value int64 callValue = 1; // TBD: tokenName, trx should be empty From 4b1d41a83e9fc28f1d17e16cf2c98b1e0cff093a Mon Sep 17 00:00:00 2001 From: Heng Zhang Date: Mon, 29 Oct 2018 17:15:27 +0800 Subject: [PATCH 032/445] update UpdateSettingForEnergyLimitContract. --- api/api.proto | 2 +- core/Contract.proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 7d09d812b..3e14435cd 100644 --- a/api/api.proto +++ b/api/api.proto @@ -97,7 +97,7 @@ service Wallet { }; //modify the energy_limit - rpc UpdateSettingForEnergyLimit (UpdateSettingContractForEnergyLimit) returns (TransactionExtention) { + rpc UpdateSettingForEnergyLimit (UpdateSettingForEnergyLimitContract) returns (TransactionExtention) { }; //Use this function instead of VoteWitnessAccount. diff --git a/core/Contract.proto b/core/Contract.proto index 7de71bb97..65b7e03af 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -78,7 +78,7 @@ message UpdateSettingContract { int64 consume_user_resource_percent = 3; } -message UpdateSettingContractForEnergyLimit { +message UpdateSettingForEnergyLimitContract { bytes owner_address = 1; bytes contract_address = 2; bytes energy_limit = 3; From 05075e4da2d881a40190ed72e75dac28d3a09ecc Mon Sep 17 00:00:00 2001 From: tjchern Date: Tue, 30 Oct 2018 13:02:37 +0800 Subject: [PATCH 033/445] Merge commit 'dcac7e9c32d6dd503ec4b920033cb22a2b7af171' into add_energy_limit --- core/Contract.proto | 2 +- core/Tron.proto | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 65b7e03af..433b84963 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -81,7 +81,7 @@ message UpdateSettingContract { message UpdateSettingForEnergyLimitContract { bytes owner_address = 1; bytes contract_address = 2; - bytes energy_limit = 3; + int64 energy_limit = 3; } message WitnessCreateContract { diff --git a/core/Tron.proto b/core/Tron.proto index ea1cbf851..1e7f759c5 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -227,6 +227,7 @@ message Transaction { ExchangeInjectContract = 42; ExchangeWithdrawContract = 43; ExchangeTransactionContract = 44; + UpdateSettingForEnergyLimitContract = 45; } ContractType type = 1; google.protobuf.Any parameter = 2; From 604b408ccf2636fae142ebc7b783c200f20dbfec Mon Sep 17 00:00:00 2001 From: lvs007 Date: Tue, 30 Oct 2018 17:45:44 +0800 Subject: [PATCH 034/445] add grpc interface --- api/api.proto | 4 ++ core/Tron.proto | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/api/api.proto b/api/api.proto index 85dd21649..079a99cf1 100644 --- a/api/api.proto +++ b/api/api.proto @@ -576,6 +576,10 @@ service Wallet { } }; } + + rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { + }; + }; diff --git a/core/Tron.proto b/core/Tron.proto index 15b6fae8b..468fb1243 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -500,4 +500,102 @@ message InternalTransaction { bytes tokenName = 2; } repeated CallValueInfo callValueInfo = 4; +} + +message NodeInfo { + int64 beginSyncNum = 1; + string block = 2; + string solidityBlock = 3; + //connect information + int32 currentConnectCount = 4; + int32 activeConnectCount = 5; + int32 passiveConnectCount = 6; + int64 totalFlow = 7; + repeated PeerInfo peerInfoList = 8; + ConfigNodeInfo configNodeInfo = 9; + MachineInfo machineInfo = 10; + map cheatWitnessInfoMap = 11; + + message PeerInfo { + string lastSyncBlock = 1; + int64 remainNum = 2; + int64 lastBlockUpdateTime = 3; + bool syncFlag = 4; + int64 headBlockTimeWeBothHave = 5; + bool needSyncFromPeer = 6; + bool needSyncFromUs = 7; + string host = 8; + int32 port = 9; + string nodeId = 10; + int64 connectTime = 11; + double avgLatency = 12; + int32 syncToFetchSize = 13; + int64 syncToFetchSizePeekNum = 14; + int32 syncBlockRequestedSize = 15; + int64 unFetchSynNum = 16; + int32 blockInPorcSize = 17; + string headBlockWeBothHave = 18; + bool isActive = 19; + int32 score = 20; + int32 nodeCount = 21; + int64 inFlow = 22; + int32 disconnectTimes = 23; + string localDisconnectReason = 24; + string remoteDisconnectReason = 25; + } + + message ConfigNodeInfo { + string codeVersion = 1; + string p2pVersion = 2; + int32 listenPort = 3; + bool discoverEnable = 4; + int32 activeNodeSize = 5; + int32 passiveNodeSize = 6; + int32 sendNodeSize = 7; + int32 maxConnectCount = 8; + int32 sameIpMaxConnectCount = 9; + int32 backupListenPort = 10; + int32 backupMemberSize = 11; + int32 backupPriority = 12; + int32 dbVersion = 13; + int32 minParticipationRate = 14; + bool supportConstant = 15; + double minTimeRatio = 16; + double maxTimeRatio = 17; + int64 allowCreationOfContracts = 18; + } + + message MachineInfo { + int32 threadCount = 1; + int32 deadLockThreadCount = 2; + int32 cpuCount = 3; + int64 totalMemory = 4; + int64 freeMemory = 5; + double cpuRate = 6; + string javaVersion = 7; + string osName = 8; + int64 jvmTotalMemoery = 9; + int64 jvmFreeMemory = 10; + double processCpuRate = 11; + repeated MemoryDescInfo memoryDescInfoList = 12; + repeated DeadLockThreadInfo deadLockThreadInfoList = 13; + + message MemoryDescInfo { + string name = 1; + int64 initSize = 2; + int64 useSize = 3; + int64 maxSize = 4; + double useRate = 5; + } + + message DeadLockThreadInfo { + string name = 1; + string lockName = 2; + string lockOwner = 3; + string state = 4; + int64 blockTime = 5; + int64 waitTime = 6; + string stackTrace = 7; + } + } } \ No newline at end of file From dc9b460fc602cae762589215da822bda29ef6441 Mon Sep 17 00:00:00 2001 From: zergweak Date: Tue, 30 Oct 2018 20:53:53 +0800 Subject: [PATCH 035/445] add zksnarkV0TransferTrx --- api/api.proto | 4 ++++ core/Contract.proto | 49 +++++++++++++++++++++++++++++++++++++++++++++ core/Tron.proto | 20 ++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/api/api.proto b/api/api.proto index 85dd21649..cb11dc385 100644 --- a/api/api.proto +++ b/api/api.proto @@ -46,6 +46,9 @@ service Wallet { rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { }; + rpc ZksnarkV0TransferTrx(ZksnarkV0TransferContract) returns(TransactionExtention) { + }; + rpc BroadcastTransaction (Transaction) returns (Return) { option (google.api.http) = { post: "/wallet/broadcasttransaction" @@ -138,6 +141,7 @@ service Wallet { //Use this function instead of CreateAccount. rpc CreateAccount2 (AccountCreateContract) returns (TransactionExtention) { } + //Please use CreateWitness2 instead of this function. rpc CreateWitness (WitnessCreateContract) returns (Transaction) { option (google.api.http) = { diff --git a/core/Contract.proto b/core/Contract.proto index 18eafded8..507f0767d 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -47,6 +47,55 @@ message TransferContract { int64 amount = 3; } + +message BN128G1 { + bytes x = 1; + bytes y = 2; +} + +message BN128G2 { + bytes x1 = 1; + bytes x2 = 2; + bytes y1 = 3; + bytes y2 = 4; +} + +message zkv0proof { + BN128G1 a = 1; + BN128G1 a_p = 2; + BN128G2 b = 3; + BN128G1 b_p = 4; + BN128G1 c = 5; + BN128G1 c_p = 6; + BN128G1 k = 7; + BN128G1 h = 8; +} + +message MerkelRoot { + int64 blocknum = 1; + bytes rt = 2; +} + +message ZksnarkV0TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 v_from_pub = 3; + int64 v_to_pub = 4; + MerkelRoot rt = 5; + bytes nf1 = 6; + bytes nf2 = 7; + bytes cm1 = 8; + bytes cm2 = 9; + bytes pksig = 10; + bytes randomSeed = 11; + bytes epk = 12; + bytes h1 = 20; + bytes h2 = 21; + bytes C1 = 22; + bytes C2 = 23; + zkv0proof proof = 30; +} + message TransferAssetContract { bytes asset_name = 1; bytes owner_address = 2; diff --git a/core/Tron.proto b/core/Tron.proto index 38c4f3a93..15d7ab14a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -227,6 +227,7 @@ message Transaction { ExchangeInjectContract = 42; ExchangeWithdrawContract = 43; ExchangeTransactionContract = 44; + ZksnarkV0TransferContract = 45; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -282,6 +283,7 @@ message Transaction { raw raw_data = 1; // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; + repeated bytes signature_zk = 3; repeated Result ret = 5; } @@ -308,6 +310,7 @@ message TransactionInfo { int64 withdraw_amount = 15; int64 unfreeze_amount = 16; + repeated InternalTransaction internal_transactions = 17; } message Transactions { @@ -482,4 +485,21 @@ message SmartContract { int64 consume_user_resource_percent = 6; string name = 7; +} + +message InternalTransaction { + // internalTransaction identity, the root InternalTransaction hash + // should equals to root transaction id. + bytes hash = 1; + // the one send trx (TBD: or token) via function + bytes caller_address = 2; + // the one recieve trx (TBD: or token) via function + bytes transferTo_address = 3; + message CallValueInfo { + // trx (TBD: or token) value + int64 callValue = 1; + // TBD: tokenName, trx should be empty + bytes tokenName = 2; + } + repeated CallValueInfo callValueInfo = 4; } \ No newline at end of file From 60bf9f77addd32a57b75c08c31401f5a9e66cc1e Mon Sep 17 00:00:00 2001 From: tjchern Date: Fri, 2 Nov 2018 11:12:15 +0800 Subject: [PATCH 036/445] refactor code --- api/api.proto | 2 +- core/Contract.proto | 2 +- core/Tron.proto | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index bc42d79d6..f289de48e 100644 --- a/api/api.proto +++ b/api/api.proto @@ -98,7 +98,7 @@ service Wallet { }; //modify the energy_limit - rpc UpdateSettingForEnergyLimit (UpdateSettingForEnergyLimitContract) returns (TransactionExtention) { + rpc UpdateEnergyLimit (UpdateEnergyLimitContract) returns (TransactionExtention) { }; //Use this function instead of VoteWitnessAccount. diff --git a/core/Contract.proto b/core/Contract.proto index a27176814..85a50dbd4 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -78,7 +78,7 @@ message UpdateSettingContract { int64 consume_user_resource_percent = 3; } -message UpdateSettingForEnergyLimitContract { +message UpdateEnergyLimitContract { bytes owner_address = 1; bytes contract_address = 2; int64 energy_limit = 3; diff --git a/core/Tron.proto b/core/Tron.proto index c11a77551..817585a4d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -247,7 +247,7 @@ message Transaction { ExchangeInjectContract = 42; ExchangeWithdrawContract = 43; ExchangeTransactionContract = 44; - UpdateSettingForEnergyLimitContract = 45; + UpdateEnergyLimitContract = 45; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -503,7 +503,7 @@ message SmartContract { int64 call_value = 5; int64 consume_user_resource_percent = 6; string name = 7; - int64 energy_limit = 8; + int64 origin_energy_limit = 8; } From 8ebf3afd366b6a16db754d80544f0c43c6b53b12 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 2 Nov 2018 14:48:05 +0800 Subject: [PATCH 037/445] add proto for merkleTree --- api/api.proto | 6 ++++++ core/Contract.proto | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/api/api.proto b/api/api.proto index cb11dc385..20580ecb4 100644 --- a/api/api.proto +++ b/api/api.proto @@ -689,6 +689,12 @@ service WalletSolidity { } }; } + + rpc GetMerkleRoot (BytesMessage) returns (MerkelRoot) { + } + + rpc GetMerklePath (BytesMessage) returns (MerklePath) { + } }; service WalletExtension { diff --git a/core/Contract.proto b/core/Contract.proto index 507f0767d..965e111de 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -76,6 +76,25 @@ message MerkelRoot { bytes rt = 2; } +message AuthenticationPath{ + repeated bool value = 1; +} + +message MerklePath { + repeated AuthenticationPath authentication_paths= 1; + repeated bool index = 2; +} + +message SHA256Compress{ + bytes value = 1; +} + +message MerkleTree{ + bytes left = 1; + bytes right = 2; + repeated bytes parents = 3; +} + message ZksnarkV0TransferContract { bytes owner_address = 1; bytes to_address = 2; From 444c587a0540db41285be27599ec84911a9d3759 Mon Sep 17 00:00:00 2001 From: Heng Zhang Date: Fri, 2 Nov 2018 16:29:50 +0800 Subject: [PATCH 038/445] Change OriginEnergyLimit. --- core/Contract.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 85a50dbd4..de368d983 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -81,7 +81,7 @@ message UpdateSettingContract { message UpdateEnergyLimitContract { bytes owner_address = 1; bytes contract_address = 2; - int64 energy_limit = 3; + int64 origin_energy_limit = 3; } message WitnessCreateContract { From 5f9d1c18def8ddd2b231b8a8e8cc28c7439d73ac Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 2 Nov 2018 17:09:50 +0800 Subject: [PATCH 039/445] feat(nf_storage): add storage and rpc for nullifier --- api/api.proto | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/api.proto b/api/api.proto index 85dd21649..c1b7e276a 100644 --- a/api/api.proto +++ b/api/api.proto @@ -576,6 +576,16 @@ service Wallet { } }; } + + rpc GetNullifier (BytesMessage) returns (BytesMessage) { + option (google.api.http) = { + post: "/wallet/getnullifier" + body: "*" + additional_bindings { + get: "/wallet/getnullifier" + } + }; + } }; From f899f89e0fdcae9ded8ce06bb91947a8cf15c646 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 5 Nov 2018 10:47:15 +0800 Subject: [PATCH 040/445] add rpc proto , GetBestMerkleRoot() --- api/api.proto | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 20580ecb4..6d2ceac14 100644 --- a/api/api.proto +++ b/api/api.proto @@ -694,7 +694,10 @@ service WalletSolidity { } rpc GetMerklePath (BytesMessage) returns (MerklePath) { - } + } + + rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { + } }; service WalletExtension { From b799bb4a63a0c8bf86e37cef61609730b1c99362 Mon Sep 17 00:00:00 2001 From: zergweak Date: Mon, 5 Nov 2018 11:39:27 +0800 Subject: [PATCH 041/445] modify merkle root --- api/api.proto | 18 +++++++++--------- core/Contract.proto | 7 +------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/api/api.proto b/api/api.proto index b7f90a67c..ba19c763a 100644 --- a/api/api.proto +++ b/api/api.proto @@ -46,7 +46,7 @@ service Wallet { rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { }; - rpc ZksnarkV0TransferTrx(ZksnarkV0TransferContract) returns(TransactionExtention) { + rpc ZksnarkV0TransferTrx (ZksnarkV0TransferContract) returns (TransactionExtention) { }; rpc BroadcastTransaction (Transaction) returns (Return) { @@ -419,15 +419,15 @@ service Wallet { } }; }; - rpc GetPaginatedProposalList (PaginatedMessage) returns (ProposalList) { - option (google.api.http) = { + rpc GetPaginatedProposalList (PaginatedMessage) returns (ProposalList) { + option (google.api.http) = { post: "/wallet/getpaginatedproposallist" body: "*" additional_bindings { get: "/wallet/getpaginatedproposallist" } }; - } + } rpc GetProposalById (BytesMessage) returns (Proposal) { option (google.api.http) = { post: "/wallet/getproposalbyid" @@ -448,14 +448,14 @@ service Wallet { }; }; rpc GetPaginatedExchangeList (PaginatedMessage) returns (ExchangeList) { - option (google.api.http) = { + option (google.api.http) = { post: "/wallet/getpaginatedexchangelist" body: "*" additional_bindings { get: "/wallet/getpaginatedexchangelist" } }; - } + } rpc GetExchangeById (BytesMessage) returns (Exchange) { option (google.api.http) = { post: "/wallet/getexchangebyid" @@ -700,7 +700,7 @@ service WalletSolidity { }; } - rpc GetMerkleRoot (BytesMessage) returns (MerkelRoot) { + rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { } rpc GetMerklePath (BytesMessage) returns (MerklePath) { @@ -901,7 +901,7 @@ message EasyTransferByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) + bytes txid = 3; //transaction id = sha256(transaction.rowdata) } message AddressPrKeyPairMessage { @@ -911,7 +911,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) repeated bytes constant_result = 3; Return result = 4; } diff --git a/core/Contract.proto b/core/Contract.proto index 965e111de..5f9794f70 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -71,11 +71,6 @@ message zkv0proof { BN128G1 h = 8; } -message MerkelRoot { - int64 blocknum = 1; - bytes rt = 2; -} - message AuthenticationPath{ repeated bool value = 1; } @@ -100,7 +95,7 @@ message ZksnarkV0TransferContract { bytes to_address = 2; int64 v_from_pub = 3; int64 v_to_pub = 4; - MerkelRoot rt = 5; + bytes rt = 5; bytes nf1 = 6; bytes nf2 = 7; bytes cm1 = 8; From d6a3f41d9ad3bb841a7416dea8373ec19b31dfd6 Mon Sep 17 00:00:00 2001 From: zergweak Date: Mon, 5 Nov 2018 11:58:13 +0800 Subject: [PATCH 042/445] add GetMerklePath --- api/api.proto | 15 ++++++++------- core/Contract.proto | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/api/api.proto b/api/api.proto index ba19c763a..57c3e4532 100644 --- a/api/api.proto +++ b/api/api.proto @@ -581,14 +581,15 @@ service Wallet { }; } + // rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { + // } + // input rt[] + rpc GetMerklePath (BytesMessage) returns (MerklePath) { + } + // get last merkle + rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { + } rpc GetNullifier (BytesMessage) returns (BytesMessage) { - option (google.api.http) = { - post: "/wallet/getnullifier" - body: "*" - additional_bindings { - get: "/wallet/getnullifier" - } - }; } }; diff --git a/core/Contract.proto b/core/Contract.proto index 5f9794f70..ab3a4f8ea 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -78,6 +78,7 @@ message AuthenticationPath{ message MerklePath { repeated AuthenticationPath authentication_paths= 1; repeated bool index = 2; + bytes rt = 3; } message SHA256Compress{ From 40f07321644927771ae192d7bc6720c955413d77 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 5 Nov 2018 17:22:22 +0800 Subject: [PATCH 043/445] add rpc for merkleWitness --- api/api.proto | 3 +++ core/Contract.proto | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/api/api.proto b/api/api.proto index 57c3e4532..6bc38eb3a 100644 --- a/api/api.proto +++ b/api/api.proto @@ -707,6 +707,9 @@ service WalletSolidity { rpc GetMerklePath (BytesMessage) returns (MerklePath) { } + rpc GetMerkleTreeWitness (OutputPoint) returns (MerkleWitness) { + } + rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { } }; diff --git a/core/Contract.proto b/core/Contract.proto index ab3a4f8ea..9b84545b0 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -85,6 +85,15 @@ message SHA256Compress{ bytes value = 1; } +message MerkleWitness{ + bytes value = 1; +} + +message OutputPoint{ + bytes txHash = 1; + bytes index = 2; +} + message MerkleTree{ bytes left = 1; bytes right = 2; From 10d7223e5180ad42f6a4a2d6feb710b0d4440263 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 5 Nov 2018 19:01:13 +0800 Subject: [PATCH 044/445] feat: add get_merkle_path rpc --- api/api.proto | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/api/api.proto b/api/api.proto index 57c3e4532..7fbaf75ea 100644 --- a/api/api.proto +++ b/api/api.proto @@ -585,11 +585,27 @@ service Wallet { // } // input rt[] rpc GetMerklePath (BytesMessage) returns (MerklePath) { + option (google.api.http) = { + post: "/wallet/getmerklepath" + body: "*" + additional_bindings { + get: "/wallet/getmerklepath" + } + }; } + // get last merkle rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { } + rpc GetNullifier (BytesMessage) returns (BytesMessage) { + option (google.api.http) = { + post: "/wallet/getnullifier" + body: "*" + additional_bindings { + get: "/wallet/getnullifier" + } + }; } }; From b4f428f8ad4250deedecbbde9f65cb3483037c6c Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 6 Nov 2018 10:59:18 +0800 Subject: [PATCH 045/445] feat(merkle_rpc): add get_best_merkle_root rpc --- api/api.proto | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/api/api.proto b/api/api.proto index cf7d07ce2..e57282afc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -585,13 +585,6 @@ service Wallet { // } // input rt[] rpc GetMerklePath (BytesMessage) returns (MerklePath) { - option (google.api.http) = { - post: "/wallet/getmerklepath" - body: "*" - additional_bindings { - get: "/wallet/getmerklepath" - } - }; } // get last merkle @@ -599,13 +592,6 @@ service Wallet { } rpc GetNullifier (BytesMessage) returns (BytesMessage) { - option (google.api.http) = { - post: "/wallet/getnullifier" - body: "*" - additional_bindings { - get: "/wallet/getnullifier" - } - }; } }; From 77dd7376f1e3b675e743935070036a0bda0804ef Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 6 Nov 2018 15:53:27 +0800 Subject: [PATCH 046/445] refractor merkleWitness --- api/api.proto | 2 +- core/Contract.proto | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/api/api.proto b/api/api.proto index 6bc38eb3a..6fb649f02 100644 --- a/api/api.proto +++ b/api/api.proto @@ -707,7 +707,7 @@ service WalletSolidity { rpc GetMerklePath (BytesMessage) returns (MerklePath) { } - rpc GetMerkleTreeWitness (OutputPoint) returns (MerkleWitness) { + rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleWitness) { } rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { diff --git a/core/Contract.proto b/core/Contract.proto index 9b84545b0..dc0cc40f0 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -81,23 +81,28 @@ message MerklePath { bytes rt = 3; } -message SHA256Compress{ - bytes value = 1; -} -message MerkleWitness{ - bytes value = 1; -} message OutputPoint{ bytes txHash = 1; bytes index = 2; } -message MerkleTree{ - bytes left = 1; - bytes right = 2; - repeated bytes parents = 3; +message SHA256Compress{ + bytes content = 1; +} + +message IncrementalMerkleTree{ + SHA256Compress left = 1; + SHA256Compress right = 2; + repeated SHA256Compress parents = 3; +} + +message IncrementalMerkleWitness{ + IncrementalMerkleTree tree = 1; + repeated SHA256Compress filled = 2; + IncrementalMerkleTree cursor = 3; + int64 cursor_depth = 4; } message ZksnarkV0TransferContract { From 3cc1eb541f80e31e6f4f04500489948a2fbe4a7f Mon Sep 17 00:00:00 2001 From: zergweak Date: Tue, 6 Nov 2018 18:13:02 +0800 Subject: [PATCH 047/445] modify getbestmerkleroot --- api/api.proto | 21 ++++++++------------- core/Contract.proto | 2 -- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/api/api.proto b/api/api.proto index f0f8079e5..b835e54a6 100644 --- a/api/api.proto +++ b/api/api.proto @@ -588,11 +588,18 @@ service Wallet { } // get last merkle - rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { + rpc GetBestMerkleRoot (EmptyMessage) returns (BytesMessage) { } rpc GetNullifier (BytesMessage) returns (BytesMessage) { } + + + rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { + } + + rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleWitness) { + } }; @@ -702,18 +709,6 @@ service WalletSolidity { } }; } - - rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { - } - - rpc GetMerklePath (BytesMessage) returns (MerklePath) { - } - - rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleWitness) { - } - - rpc GetBestMerkleRoot (EmptyMessage) returns (MerklePath) { - } }; service WalletExtension { diff --git a/core/Contract.proto b/core/Contract.proto index dc0cc40f0..cc5bb38a4 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -81,8 +81,6 @@ message MerklePath { bytes rt = 3; } - - message OutputPoint{ bytes txHash = 1; bytes index = 2; From 0906d0fcdbb1c2ae0e35bf93335e3ed6a1030c63 Mon Sep 17 00:00:00 2001 From: taihaofu Date: Tue, 6 Nov 2018 20:58:09 +0800 Subject: [PATCH 048/445] implement transfer token when use triggercontract and createsmartcontract --- core/Contract.proto | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/Contract.proto b/core/Contract.proto index 18ad7c08c..163e9832a 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -176,6 +176,8 @@ message ProposalDeleteContract { message CreateSmartContract { bytes owner_address = 1; SmartContract new_contract = 2; + int64 call_token_value = 3; + string token_id = 4; } message TriggerSmartContract { @@ -183,6 +185,8 @@ message TriggerSmartContract { bytes contract_address = 2; int64 call_value = 3; bytes data = 4; + int64 call_token_value = 5; + string token_id = 6; } message BuyStorageContract { From 4ac24b5df4a4fb078e3bf51eea40f0c55c8fdfba Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 7 Nov 2018 15:16:15 +0800 Subject: [PATCH 049/445] use OutputPoint as witnessKey --- core/Contract.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index cc5bb38a4..cdebfcbb7 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -83,7 +83,7 @@ message MerklePath { message OutputPoint{ bytes txHash = 1; - bytes index = 2; + int32 index = 2; } message SHA256Compress{ @@ -101,6 +101,7 @@ message IncrementalMerkleWitness{ repeated SHA256Compress filled = 2; IncrementalMerkleTree cursor = 3; int64 cursor_depth = 4; + OutputPoint output_point = 10; } message ZksnarkV0TransferContract { From 25651ea0842824062bd12431f18de4e128308091 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 7 Nov 2018 16:08:59 +0800 Subject: [PATCH 050/445] feat(zksnark_rpc): add getMerkleTreeWitness rpc --- api/api.proto | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index b835e54a6..a29fe13ab 100644 --- a/api/api.proto +++ b/api/api.proto @@ -581,13 +581,10 @@ service Wallet { }; } - // rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { - // } - // input rt[] rpc GetMerklePath (BytesMessage) returns (MerklePath) { } - // get last merkle + rpc GetBestMerkleRoot (EmptyMessage) returns (BytesMessage) { } From 0beba8542a02011d351638ae46f94f3aa0afc53c Mon Sep 17 00:00:00 2001 From: taihaofu Date: Wed, 7 Nov 2018 17:06:16 +0800 Subject: [PATCH 051/445] refine token id type in internal transaction protobuf --- core/Tron.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 6d7e893ca..70c5cf45d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -519,7 +519,7 @@ message InternalTransaction { // trx (TBD: or token) value int64 callValue = 1; // TBD: tokenName, trx should be empty - bytes tokenName = 2; + string tokenId = 2; } repeated CallValueInfo callValueInfo = 4; bytes note = 5; From d043475aee689d839b4fc4041b01990df3067d0d Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Fri, 9 Nov 2018 11:24:43 +0800 Subject: [PATCH 052/445] add allowAdaptiveEnergy in NodeInfo --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 6d7e893ca..a2388edf6 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -593,6 +593,7 @@ message NodeInfo { double minTimeRatio = 16; double maxTimeRatio = 17; int64 allowCreationOfContracts = 18; + int64 allowAdaptiveEnergy = 19; } message MachineInfo { From 82d1fd2b16040f44b15edfa830ecf729d1fbb639 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 12 Nov 2018 16:20:00 +0800 Subject: [PATCH 053/445] feat(delegate_resource_solidity): add solidity http and rpc for GetDelegatedResource and GetDelegatedResourceAccountIndex --- api/api.proto | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/api/api.proto b/api/api.proto index c26e869ff..9ab440117 100644 --- a/api/api.proto +++ b/api/api.proto @@ -413,26 +413,10 @@ service Wallet { rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { - option (google.api.http) = { - post: "/wallet/getdelegatedresource" - body: "*" - additional_bindings { - get: "/wallet/getdelegatedresource" - } - }; - }; + }; rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { - option (google.api.http) = { - post: "/wallet/getdelegatedresourceaccountindex" - body: "*" - additional_bindings { - get: "/wallet/getdelegatedresourceaccountindex" - } - }; - }; - - + }; rpc ListProposals (EmptyMessage) returns (ProposalList) { option (google.api.http) = { @@ -689,6 +673,12 @@ service WalletSolidity { rpc GetTransactionCountByBlockNum (NumberMessage) returns (NumberMessage) { } + rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { + }; + + rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + rpc GetTransactionById (BytesMessage) returns (Transaction) { option (google.api.http) = { post: "/walletsolidity/gettransactionbyid" From 41bff161fcb6a64e2605fa1a362c4ed50b7a827a Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 13 Nov 2018 11:16:40 +0800 Subject: [PATCH 054/445] change outPoint --- api/api.proto | 1 + core/Contract.proto | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index a29fe13ab..1c6319842 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1,3 +1,4 @@ + syntax = "proto3"; package protocol; diff --git a/core/Contract.proto b/core/Contract.proto index cdebfcbb7..d79034939 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -82,7 +82,7 @@ message MerklePath { } message OutputPoint{ - bytes txHash = 1; + bytes hash = 1; int32 index = 2; } From d422fb4594317a0b5e067cb791981e953866ba3b Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 13 Nov 2018 11:32:52 +0800 Subject: [PATCH 055/445] add rt into IncrementalMerkleWitness --- core/Contract.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Contract.proto b/core/Contract.proto index d79034939..322a5a504 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -101,6 +101,7 @@ message IncrementalMerkleWitness{ repeated SHA256Compress filled = 2; IncrementalMerkleTree cursor = 3; int64 cursor_depth = 4; + bytes rt = 5; OutputPoint output_point = 10; } From 125b5fefdab03c272798dfcaa22453d912d57391 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 13 Nov 2018 15:31:48 +0800 Subject: [PATCH 056/445] null message --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 978d8ed35..43ae2548e 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -66,6 +66,7 @@ message ChainParameters { } } + /* Account */ message Account { /* frozen balance */ From 107e7582934876f774fb930d6eb5a2eaa722d034 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 13 Nov 2018 15:36:11 +0800 Subject: [PATCH 057/445] add assetV2 into account proto --- core/Tron.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 43ae2548e..2fa1872b7 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -86,6 +86,9 @@ message Account { // the other asset owned by this account map asset = 6; + // the other asset owned by this account,key is assetId + map assetV2 = 36; + // the frozen balance for bandwidth repeated Frozen frozen = 7; // bandwidth, get from frozen From 82bcc2bb8ee80ecbc72a6f3a60afb82df2d30ad8 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 13 Nov 2018 17:42:43 +0800 Subject: [PATCH 058/445] add AssetUpdateHelper --- core/Contract.proto | 2 ++ core/Tron.proto | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index bcc050c1d..518b9dd3b 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -95,6 +95,8 @@ message WitnessUpdateContract { } message AssetIssueContract { + string id = 41; + message FrozenSupply { int64 frozen_amount = 1; int64 frozen_days = 2; diff --git a/core/Tron.proto b/core/Tron.proto index 2fa1872b7..7ec4c4b9d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -87,7 +87,7 @@ message Account { map asset = 6; // the other asset owned by this account,key is assetId - map assetV2 = 36; + map assetV2 = 56; // the frozen balance for bandwidth repeated Frozen frozen = 7; From 5240789b4e84fa9d0979a7a6a764b830ebaa8232 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Wed, 14 Nov 2018 11:11:48 +0800 Subject: [PATCH 059/445] add asset netusage V2 --- core/Tron.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 7ec4c4b9d..223d675db 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -115,9 +115,10 @@ message Account { // asset_issued_name bytes asset_issued_name = 17; map latest_asset_operation_time = 18; - + map latest_asset_operation_timeV2 = 57; int64 free_net_usage = 19; map free_asset_net_usage = 20; + map free_asset_net_usageV2 = 58; int64 latest_consume_time = 21; int64 latest_consume_free_time = 22; From 7876c114c3ea8f628ab707c0e1c311e6ea215a57 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Wed, 14 Nov 2018 13:41:09 +0800 Subject: [PATCH 060/445] add asset_issued_ID --- core/Tron.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 223d675db..e427a173a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -114,11 +114,12 @@ message Account { repeated Frozen frozen_supply = 16; // asset_issued_name bytes asset_issued_name = 17; + bytes asset_issued_ID = 57; map latest_asset_operation_time = 18; - map latest_asset_operation_timeV2 = 57; + map latest_asset_operation_timeV2 = 58; int64 free_net_usage = 19; map free_asset_net_usage = 20; - map free_asset_net_usageV2 = 58; + map free_asset_net_usageV2 = 59; int64 latest_consume_time = 21; int64 latest_consume_free_time = 22; From 4b771818bd5b8f8141e0a917eff20750bfd61b5b Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 15 Nov 2018 16:10:48 +0800 Subject: [PATCH 061/445] update desc about asset_name in proto --- core/Contract.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 518b9dd3b..e17466434 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -48,7 +48,7 @@ message TransferContract { } message TransferAssetContract { - bytes asset_name = 1; + bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. bytes owner_address = 2; bytes to_address = 3; int64 amount = 4; @@ -110,7 +110,7 @@ message AssetIssueContract { int32 num = 8; int64 start_time = 9; int64 end_time = 10; - int64 order = 11; // the order of tokens of the same name + int64 order = 11; // usless int32 vote_score = 16; bytes description = 20; bytes url = 21; @@ -123,7 +123,7 @@ message AssetIssueContract { message ParticipateAssetIssueContract { bytes owner_address = 1; bytes to_address = 2; - bytes asset_name = 3; // the namekey of target asset, include name and order + bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. int64 amount = 4; // the amount of drops } From 1a04340315b215bf7f222483cbce24c186608403 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 15 Nov 2018 16:59:43 +0800 Subject: [PATCH 062/445] feat(get_asset_api): update get assetxxx api update getAssetIssueByAccount, getAssetIssueList, getPaginatedAssetIssueList; update getAssetIssueByName to return assetissuelist; add getAssetIssueBId; --- api/api.proto | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index c26e869ff..fc6b3637f 100644 --- a/api/api.proto +++ b/api/api.proto @@ -308,7 +308,7 @@ service Wallet { }; rpc GetAccountResource (Account) returns (AccountResourceMessage) { }; - rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueList) { option (google.api.http) = { post: "/wallet/getassetissuebyname" body: "*" @@ -317,6 +317,8 @@ service Wallet { } }; } + rpc GetAssetIssueById (BytesMessage) returns (AssetIssueContract) { + } //Please use GetNowBlock2 instead of this function. rpc GetNowBlock (EmptyMessage) returns (Block) { option (google.api.http) = { From 347b3c0988c135a916b86b9166977358b7dbcc7e Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 16 Nov 2018 12:33:56 +0800 Subject: [PATCH 063/445] feat(asset_api): getassetissuebyname accept token_id as parameter, added new getassetissuelistbyname api --- api/api.proto | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index fc6b3637f..9f205b39f 100644 --- a/api/api.proto +++ b/api/api.proto @@ -308,7 +308,7 @@ service Wallet { }; rpc GetAccountResource (Account) returns (AccountResourceMessage) { }; - rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueList) { + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { option (google.api.http) = { post: "/wallet/getassetissuebyname" body: "*" @@ -317,6 +317,8 @@ service Wallet { } }; } + rpc GetAssetIssueListByName (BytesMessage) returns (AssetIssueList) { + } rpc GetAssetIssueById (BytesMessage) returns (AssetIssueContract) { } //Please use GetNowBlock2 instead of this function. From 8bcf9cb97278568f86f0d70008bfd58de4e1a51e Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Fri, 16 Nov 2018 12:37:13 +0800 Subject: [PATCH 064/445] add precision in AssetIssueContract --- core/Contract.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index e17466434..7033c3ee6 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -107,10 +107,11 @@ message AssetIssueContract { int64 total_supply = 4; repeated FrozenSupply frozen_supply = 5; int32 trx_num = 6; + int32 precision = 7; int32 num = 8; int64 start_time = 9; int64 end_time = 10; - int64 order = 11; // usless + int64 order = 11; // useless int32 vote_score = 16; bytes description = 20; bytes url = 21; From 091049e1d134aa98d4c591d87ff297644aae4439 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 16 Nov 2018 14:41:23 +0800 Subject: [PATCH 065/445] add exchangeReceivedAmount into transactionInfo --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index e427a173a..56dc2faec 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -288,6 +288,7 @@ message Transaction { int64 withdraw_amount = 15; int64 unfreeze_amount = 16; + int64 exchange_received_amount = 17; } message raw { @@ -336,6 +337,7 @@ message TransactionInfo { int64 withdraw_amount = 15; int64 unfreeze_amount = 16; repeated InternalTransaction internal_transactions = 17; + int64 exchange_received_amount = 18; } message Transactions { From a6f68510da5089b54dc27ebd617ed6aa06bd66bd Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 16 Nov 2018 15:20:03 +0800 Subject: [PATCH 066/445] =?UTF-8?q?add=20exchange=5Finject=5Fanother=5Famo?= =?UTF-8?q?unt=E3=80=81exchange=5Fwithdraw=5Fanother=5Famount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/Tron.proto | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 56dc2faec..53e6d7d8b 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -288,7 +288,9 @@ message Transaction { int64 withdraw_amount = 15; int64 unfreeze_amount = 16; - int64 exchange_received_amount = 17; + int64 exchange_received_amount = 18; + int64 exchange_inject_another_amount = 19; + int64 exchange_withdraw_another_amount = 20; } message raw { @@ -338,6 +340,8 @@ message TransactionInfo { int64 unfreeze_amount = 16; repeated InternalTransaction internal_transactions = 17; int64 exchange_received_amount = 18; + int64 exchange_inject_another_amount = 19; + int64 exchange_withdraw_another_amount = 20; } message Transactions { From 76fc36e19b3eefbe0e7c18a5ce58b9097ca0a9a0 Mon Sep 17 00:00:00 2001 From: taihaofu Date: Fri, 16 Nov 2018 18:16:09 +0800 Subject: [PATCH 067/445] implement msg tokenvalue and msg tokenid --- core/Contract.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index e17466434..64d7e6ec9 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -185,7 +185,7 @@ message CreateSmartContract { bytes owner_address = 1; SmartContract new_contract = 2; int64 call_token_value = 3; - string token_id = 4; + int64 token_id = 4; } message TriggerSmartContract { @@ -194,7 +194,7 @@ message TriggerSmartContract { int64 call_value = 3; bytes data = 4; int64 call_token_value = 5; - string token_id = 6; + int64 token_id = 6; } message BuyStorageContract { From 44fd7d78a4ca6f37c2f8d3ca8f94b2110b503323 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 19 Nov 2018 12:43:19 +0800 Subject: [PATCH 068/445] feat(exchange_api): add rpc&http of getexchangebyid and listexchanges on solidity --- api/api.proto | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/api.proto b/api/api.proto index ad170d839..eb5ce9106 100644 --- a/api/api.proto +++ b/api/api.proto @@ -683,6 +683,13 @@ service WalletSolidity { rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { }; + rpc GetExchangeById (BytesMessage) returns (Exchange) { + }; + + rpc ListExchanges (EmptyMessage) returns (ExchangeList) { + }; + + rpc GetTransactionById (BytesMessage) returns (Transaction) { option (google.api.http) = { post: "/walletsolidity/gettransactionbyid" From 64f165931ebccaf9537fb72476bfc07cf1c63432 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Mon, 19 Nov 2018 15:03:55 +0800 Subject: [PATCH 069/445] add assetissueid in transactionresule and transactionInfo --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 53e6d7d8b..d54078e8c 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -286,6 +286,7 @@ message Transaction { code ret = 2; contractResult contractRet = 3; + string assetIssueID = 14; int64 withdraw_amount = 15; int64 unfreeze_amount = 16; int64 exchange_received_amount = 18; @@ -336,6 +337,7 @@ message TransactionInfo { code result = 9; bytes resMessage = 10; + string assetIssueID = 14; int64 withdraw_amount = 15; int64 unfreeze_amount = 16; repeated InternalTransaction internal_transactions = 17; From aa740c5a2a74dffaabe5f07c249656c644aed367 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 19 Nov 2018 15:50:19 +0800 Subject: [PATCH 070/445] add ExchangeId into transactionInfo --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index d54078e8c..e7c0746f2 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -292,6 +292,7 @@ message Transaction { int64 exchange_received_amount = 18; int64 exchange_inject_another_amount = 19; int64 exchange_withdraw_another_amount = 20; + int64 exchange_id = 21; } message raw { @@ -344,6 +345,7 @@ message TransactionInfo { int64 exchange_received_amount = 18; int64 exchange_inject_another_amount = 19; int64 exchange_withdraw_another_amount = 20; + int64 exchange_id = 21; } message Transactions { From 05c70dd98c91451378e09e71753f238e9c00e16d Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 26 Nov 2018 11:06:45 +0800 Subject: [PATCH 071/445] feat(solidity_api): added solidity http&rpc api getAssetIssueByName, getAssetIssueById, getAssetIssueListByName --- api/api.proto | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/api.proto b/api/api.proto index eb5ce9106..a19190b56 100644 --- a/api/api.proto +++ b/api/api.proto @@ -647,6 +647,14 @@ service WalletSolidity { } }; } + + rpc GetAssetIssueByName (BytesMessage) returns (AssetIssueContract) { + } + rpc GetAssetIssueListByName (BytesMessage) returns (AssetIssueList) { + } + rpc GetAssetIssueById (BytesMessage) returns (AssetIssueContract) { + } + //Please use GetNowBlock2 instead of this function. rpc GetNowBlock (EmptyMessage) returns (Block) { option (google.api.http) = { From b81e204156235181c125b7ffc460bbaf69792bdb Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 27 Nov 2018 10:53:49 +0800 Subject: [PATCH 072/445] feat(generate_shield_address): add generateShieldAddress http api --- core/Contract.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/Contract.proto b/core/Contract.proto index 322a5a504..ade6ecb62 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -125,6 +125,11 @@ message ZksnarkV0TransferContract { zkv0proof proof = 30; } +message ShieldAddress { + bytes private_key = 1; + bytes public_key = 2; +} + message TransferAssetContract { bytes asset_name = 1; bytes owner_address = 2; From 4d54938541334cd1b738dbd1a6895cc39910d0de Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 27 Nov 2018 11:29:29 +0800 Subject: [PATCH 073/445] feat(generateshieldaddress): update contract --- core/Contract.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index ade6ecb62..048381c95 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -126,8 +126,8 @@ message ZksnarkV0TransferContract { } message ShieldAddress { - bytes private_key = 1; - bytes public_key = 2; + bytes private_address = 1; + bytes public_address = 2; } message TransferAssetContract { From 33d964b91b17423dfa888579769f0a18d8dde0cb Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 28 Nov 2018 16:41:49 +0800 Subject: [PATCH 074/445] feat(generateshieldaddress): add generateshieldaddress rpc --- api/api.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/api.proto b/api/api.proto index 1c6319842..a856c5682 100644 --- a/api/api.proto +++ b/api/api.proto @@ -598,6 +598,9 @@ service Wallet { rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleWitness) { } + + rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { + } }; From 91926b48228ea7fad33fcf8444da15a90b8bb1b4 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 3 Dec 2018 15:49:07 +0800 Subject: [PATCH 075/445] add GRPC GetZKBlockByLimitNext and GetMerkleTreeOfBlock --- api/api.proto | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/api.proto b/api/api.proto index a856c5682..a9fcd1df1 100644 --- a/api/api.proto +++ b/api/api.proto @@ -601,6 +601,14 @@ service Wallet { rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { } + + //Get zero-snark blockExtention list + rpc GetZKBlockByLimitNext (BlockLimit) returns (BlockListExtention) { + } + + //Get the specified height block merkleTree + rpc GetMerkleTreeOfBlock ( NumberMessage ) returns ( BlockIncrementalMerkleTree ) { } + }; @@ -930,4 +938,9 @@ message BlockListExtention { message TransactionListExtention { repeated TransactionExtention transaction = 1; +} + +message BlockIncrementalMerkleTree { + int64 number = 1; + IncrementalMerkleTree merkleTree = 2; } \ No newline at end of file From 62ac79a6134c9fcfd215196967cdd283b204df70 Mon Sep 17 00:00:00 2001 From: wubin01 Date: Mon, 3 Dec 2018 19:14:57 +0800 Subject: [PATCH 076/445] add broadcastTransaction error type --- api/api.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/api.proto b/api/api.proto index a19190b56..2f1633c75 100644 --- a/api/api.proto +++ b/api/api.proto @@ -786,6 +786,8 @@ message Return { TOO_BIG_TRANSACTION_ERROR = 7; TRANSACTION_EXPIRATION_ERROR = 8; SERVER_BUSY = 9; + NO_CONNECTION = 10; + NOT_ENOUGH_EFFECTIVE_CONNECTION = 11; OTHER_ERROR = 20; } From 93d8722f7287795c1f347e4efa0ce105f7aefb57 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 4 Dec 2018 12:12:36 +0800 Subject: [PATCH 077/445] add getMerkleTreeWitnessInfo --- api/api.proto | 3 +++ core/Contract.proto | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/api/api.proto b/api/api.proto index a9fcd1df1..522003fe3 100644 --- a/api/api.proto +++ b/api/api.proto @@ -599,6 +599,9 @@ service Wallet { rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleWitness) { } + rpc GetMerkleTreeWitnessInfo (OutputPointInfo) returns (IncrementalMerkleWitnessInfo) { + } + rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { } diff --git a/core/Contract.proto b/core/Contract.proto index 048381c95..9396a088c 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -86,6 +86,12 @@ message OutputPoint{ int32 index = 2; } +message OutputPointInfo{ + OutputPoint out_point_1 = 1; + OutputPoint out_point_2 = 2; + int32 block_num = 3; +} + message SHA256Compress{ bytes content = 1; } @@ -105,6 +111,12 @@ message IncrementalMerkleWitness{ OutputPoint output_point = 10; } +message IncrementalMerkleWitnessInfo{ + IncrementalMerkleWitness witness1 = 1; + IncrementalMerkleWitness witness2 = 2; + int32 block_num = 3; +} + message ZksnarkV0TransferContract { bytes owner_address = 1; bytes to_address = 2; From 3b432b59f4aa0bc9742554f434b446fa08f9eabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=9C=E9=98=B3=E9=98=B3?= Date: Wed, 5 Dec 2018 15:09:50 +0800 Subject: [PATCH 078/445] =?UTF-8?q?fast=20sync=20support:=201=E3=80=81comm?= =?UTF-8?q?and=20support=20for=20fast=20sync;=202=E3=80=81add=20GetNodeDat?= =?UTF-8?q?a=20and=20NodeData=20message;=203=E3=80=81add=20stateRoot=20in?= =?UTF-8?q?=20blockheader,=20refer=20to=20Tron.proto;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 67d7405df..32ef69108 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -379,6 +379,7 @@ message BlockHeader { int64 witness_id = 8; bytes witness_address = 9; int32 version = 10; + bytes stateRoot = 11; } raw raw_data = 1; bytes witness_signature = 2; From 7e743291063e1d178b4e149138199db5110e15e0 Mon Sep 17 00:00:00 2001 From: lvs007 Date: Wed, 5 Dec 2018 19:31:01 +0800 Subject: [PATCH 079/445] implement the account state root --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 67d7405df..367dcffce 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -379,6 +379,7 @@ message BlockHeader { int64 witness_id = 8; bytes witness_address = 9; int32 version = 10; + bytes accountStateRoot = 11; } raw raw_data = 1; bytes witness_signature = 2; From d701e714302f5c1b393dae374a0c6d807ffef0e2 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Wed, 12 Dec 2018 23:32:25 +0800 Subject: [PATCH 080/445] minor change --- core/Contract.proto | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 9396a088c..b8579f475 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -81,28 +81,28 @@ message MerklePath { bytes rt = 3; } -message OutputPoint{ +message OutputPoint { bytes hash = 1; int32 index = 2; } -message OutputPointInfo{ +message OutputPointInfo { OutputPoint out_point_1 = 1; OutputPoint out_point_2 = 2; int32 block_num = 3; } -message SHA256Compress{ +message SHA256Compress { bytes content = 1; } -message IncrementalMerkleTree{ +message IncrementalMerkleTree { SHA256Compress left = 1; SHA256Compress right = 2; repeated SHA256Compress parents = 3; } -message IncrementalMerkleWitness{ +message IncrementalMerkleWitness { IncrementalMerkleTree tree = 1; repeated SHA256Compress filled = 2; IncrementalMerkleTree cursor = 3; @@ -111,7 +111,7 @@ message IncrementalMerkleWitness{ OutputPoint output_point = 10; } -message IncrementalMerkleWitnessInfo{ +message IncrementalMerkleWitnessInfo { IncrementalMerkleWitness witness1 = 1; IncrementalMerkleWitness witness2 = 2; int32 block_num = 3; From 7b0d46fc22ed4e931c0babe9903732f15210c027 Mon Sep 17 00:00:00 2001 From: nanfengpo Date: Thu, 13 Dec 2018 11:08:56 +0800 Subject: [PATCH 081/445] minor change --- core/Contract.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index b8579f475..bc5243877 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -71,7 +71,7 @@ message zkv0proof { BN128G1 h = 8; } -message AuthenticationPath{ +message AuthenticationPath { repeated bool value = 1; } From 905e8d5003273f1562a6a3c8a95d473cc95e5067 Mon Sep 17 00:00:00 2001 From: Hou Date: Tue, 18 Dec 2018 16:05:51 +0800 Subject: [PATCH 082/445] 1 Add ZKSanrk transaction fee, currently 1TRX 2 Add proposals open ZKSanrk transactions and modify ZKSanrk transaction fees --- core/Contract.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Contract.proto b/core/Contract.proto index bc5243877..e77cb55ea 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -130,6 +130,7 @@ message ZksnarkV0TransferContract { bytes pksig = 10; bytes randomSeed = 11; bytes epk = 12; + int64 fee = 13; bytes h1 = 20; bytes h2 = 21; bytes C1 = 22; From 3d196a7f845a8895dcd1bfe3d796301ef27107ee Mon Sep 17 00:00:00 2001 From: zergweak Date: Tue, 18 Dec 2018 17:59:30 +0800 Subject: [PATCH 083/445] merge config --- core/Tron.proto | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 688ce17e6..b57b56b0d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -254,7 +254,10 @@ message Transaction { ExchangeWithdrawContract = 43; ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; - ZksnarkV0TransferContract = 48; + PermissionAddKeyContract = 46; + PermissionUpdateKeyContract = 47; + PermissionDeleteKeyContract = 48; + ZksnarkV0TransferContract = 49; } ContractType type = 1; google.protobuf.Any parameter = 2; From 88a0a83cd904eb7811874731e149c5bafbc05e0c Mon Sep 17 00:00:00 2001 From: jiangyy Date: Wed, 26 Dec 2018 20:05:50 +0800 Subject: [PATCH 084/445] add http api for set event filter and event plugin config --- api/api.proto | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/api/api.proto b/api/api.proto index 071c3f35d..2968456dc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -642,6 +642,26 @@ service Wallet { rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { }; + + rpc SetEventFilter (EventFilter) returns (Return) { + option (google.api.http) = { + post: "/wallet/seteventfilter" + body: "*" + additional_bindings { + get: "/wallet/seteventfilter" + } + }; + } + + rpc SetEventPluginConfig (EventPluginInfo) returns (Return) { + option (google.api.http) = { + post: "/wallet/setpluginconfig" + body: "*" + additional_bindings { + get: "/wallet/setpluginconfig" + } + }; + } }; @@ -1022,4 +1042,20 @@ message TransactionSignWeight { int64 current_weight = 3; Result result = 4; TransactionExtention transaction = 5; +} + +message EventFilter { + string fromBlock = 1; + string toBlock = 2; + repeated string contractAddress = 3; + repeated string contractTopic = 4; +} + +message TriggerInfo{ + string triggerName = 1; + bool enable = 2; + string topic = 3; +} +message EventPluginInfo{ + repeated TriggerInfo triggerList = 1; } \ No newline at end of file From 4140e1d20841cf2b62a2e8f70f5bf072159d48d7 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Fri, 28 Dec 2018 14:14:32 +0800 Subject: [PATCH 085/445] update trigger config --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 2968456dc..a3877bf2d 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1057,5 +1057,5 @@ message TriggerInfo{ string topic = 3; } message EventPluginInfo{ - repeated TriggerInfo triggerList = 1; + repeated TriggerInfo triggerInfo = 1; } \ No newline at end of file From 6a960552b8ffd8094fee52a3659b5756615f0535 Mon Sep 17 00:00:00 2001 From: jiangyy Date: Thu, 3 Jan 2019 10:39:39 +0800 Subject: [PATCH 086/445] optimize event filter query, add response code --- api/api.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index a3877bf2d..3a3665ab8 100644 --- a/api/api.proto +++ b/api/api.proto @@ -655,10 +655,10 @@ service Wallet { rpc SetEventPluginConfig (EventPluginInfo) returns (Return) { option (google.api.http) = { - post: "/wallet/setpluginconfig" + post: "/wallet/seteventpluginconfig" body: "*" additional_bindings { - get: "/wallet/setpluginconfig" + get: "/wallet/seteventpluginconfig" } }; } From bc3d2ae4b963727ba2b3897d9c882f6afc2750b9 Mon Sep 17 00:00:00 2001 From: zergweak Date: Thu, 3 Jan 2019 17:35:17 +0800 Subject: [PATCH 087/445] mdf api.proto, add GetTransactionApprovedList --- api/api.proto | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/api/api.proto b/api/api.proto index 071c3f35d..ac69be632 100644 --- a/api/api.proto +++ b/api/api.proto @@ -640,6 +640,10 @@ service Wallet { } + rpc GetTransactionApprovedList(Transaction) returns (TransactionApprovedList) { + + } + rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { }; }; @@ -1022,4 +1026,20 @@ message TransactionSignWeight { int64 current_weight = 3; Result result = 4; TransactionExtention transaction = 5; +} + +message TransactionApprovedList { + message Result { + enum response_code { + SIGNATURE_FORMAT_ERROR = 0; + COMPUTE_ADDRESS_ERROR = 1; + OTHER_ERROR = 20; + } + response_code code = 1; + string message = 2; + } + + repeated bytes approved_list = 2; + Result result = 4; + TransactionExtention transaction = 5; } \ No newline at end of file From 65e2791ec73a29a201641d0ec5edb360ea5de3ae Mon Sep 17 00:00:00 2001 From: zergweak Date: Thu, 3 Jan 2019 19:16:59 +0800 Subject: [PATCH 088/445] add getApprovedList --- api/api.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index ac69be632..21fddc593 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1031,8 +1031,9 @@ message TransactionSignWeight { message TransactionApprovedList { message Result { enum response_code { - SIGNATURE_FORMAT_ERROR = 0; - COMPUTE_ADDRESS_ERROR = 1; + SUCCESS = 0; + SIGNATURE_FORMAT_ERROR = 1; + COMPUTE_ADDRESS_ERROR = 2; OTHER_ERROR = 20; } response_code code = 1; From ff66a6e52c0938601f8cff03a9695f1ed7d3c76b Mon Sep 17 00:00:00 2001 From: jiangyy Date: Sat, 5 Jan 2019 11:25:02 +0800 Subject: [PATCH 089/445] remove seteventfilter/seteventpluginconfig for security issue --- api/api.proto | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/api/api.proto b/api/api.proto index 815118c69..21fddc593 100644 --- a/api/api.proto +++ b/api/api.proto @@ -646,26 +646,6 @@ service Wallet { rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { }; - - rpc SetEventFilter (EventFilter) returns (Return) { - option (google.api.http) = { - post: "/wallet/seteventfilter" - body: "*" - additional_bindings { - get: "/wallet/seteventfilter" - } - }; - } - - rpc SetEventPluginConfig (EventPluginInfo) returns (Return) { - option (google.api.http) = { - post: "/wallet/seteventpluginconfig" - body: "*" - additional_bindings { - get: "/wallet/seteventpluginconfig" - } - }; - } }; @@ -1048,23 +1028,6 @@ message TransactionSignWeight { TransactionExtention transaction = 5; } -message EventFilter { - string fromBlock = 1; - string toBlock = 2; - repeated string contractAddress = 3; - repeated string contractTopic = 4; -} - -message TriggerInfo{ - string triggerName = 1; - bool enable = 2; - string topic = 3; -} - -message EventPluginInfo{ - repeated TriggerInfo triggerInfo = 1; -} - message TransactionApprovedList { message Result { enum response_code { From 2c408b3eb79d6e2692c7c5e7f6b51268f3239566 Mon Sep 17 00:00:00 2001 From: zergweak Date: Fri, 11 Jan 2019 09:54:25 +0800 Subject: [PATCH 090/445] mdf proto change account and permission --- core/Contract.proto | 10 ++++++---- core/Tron.proto | 21 ++++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 2f9440fab..5e20f831d 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -245,23 +245,25 @@ message ExchangeTransactionContract { message AccountPermissionUpdateContract { bytes owner_address = 1; - repeated Permission permissions = 2; + Permission owner = 2; //Empty is invalidate + Permission witness = 3;//Can be empty + repeated Permission actives = 4;//Empty is invalidate } message PermissionAddKeyContract { bytes owner_address = 1; Key key = 2; - string permission_name = 3; + int32 permission_id = 3; } message PermissionUpdateKeyContract { bytes owner_address = 1; Key key = 2; - string permission_name = 3; + int32 permission_id = 3; } message PermissionDeleteKeyContract { bytes owner_address = 1; bytes key_address = 2; - string permission_name = 3; + int32 permission_id = 3; } \ No newline at end of file diff --git a/core/Tron.proto b/core/Tron.proto index 67d7405df..f0584d02f 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -145,7 +145,9 @@ message Account { } AccountResource account_resource = 26; bytes codeHash = 30; - repeated Permission permissions = 31; + Permission owner = 31; + Permission witness = 32; + repeated Permission actives = 33; } @@ -169,10 +171,18 @@ message authority { } message Permission { - string name = 1; - int64 threshold = 2; - string parent = 3; - repeated Key keys = 4; + enum PermissionType { + Owner = 0; + Witness = 1; + Active = 2; + } + PermissionType type = 1; + int32 id = 2; //Owner id=0, Witness id=1, Active id start by 2 + string permission_name = 3; + int64 threshold = 4; + int32 parent_id = 5; + bytes operations = 6; //1 bit 1 contract + repeated Key keys = 7; } // Witness @@ -270,6 +280,7 @@ message Transaction { google.protobuf.Any parameter = 2; bytes provider = 3; bytes ContractName = 4; + int32 Permission_id = 5; } message Result { From e4a14bd47981c915d7bc78e27129df10b7ea41b0 Mon Sep 17 00:00:00 2001 From: zergweak Date: Fri, 11 Jan 2019 15:05:03 +0800 Subject: [PATCH 091/445] mdf AccountPermissionUpdateActuator --- core/Tron.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index f0584d02f..fe38618be 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -145,9 +145,9 @@ message Account { } AccountResource account_resource = 26; bytes codeHash = 30; - Permission owner = 31; - Permission witness = 32; - repeated Permission actives = 33; + Permission owner_permission = 31; + Permission witness_permission = 32; + repeated Permission active_permission = 33; } From 77452f9548b8ee6c9380f2d6b4c2a4269fc9fbd7 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 17 Jan 2019 16:28:30 +0800 Subject: [PATCH 092/445] Remove redundant contracts --- api/api.proto | 29 ----------------------------- core/Contract.proto | 18 ------------------ 2 files changed, 47 deletions(-) diff --git a/api/api.proto b/api/api.proto index 21fddc593..53d981083 100644 --- a/api/api.proto +++ b/api/api.proto @@ -602,35 +602,6 @@ service Wallet { }; } - rpc PermissionAddKey (PermissionAddKeyContract) returns (TransactionExtention) { - option (google.api.http) = { - post: "/wallet/permissionaddkey" - body: "*" - additional_bindings { - get: "/wallet/permissionaddkey" - } - }; - } - - rpc PermissionUpdateKey (PermissionUpdateKeyContract) returns (TransactionExtention) { - option (google.api.http) = { - post: "/wallet/permissionupdatekey" - body: "*" - additional_bindings { - get: "/wallet/permissionupdatekey" - } - }; - } - - rpc PermissionDeleteKey (PermissionDeleteKeyContract) returns (TransactionExtention) { - option (google.api.http) = { - post: "/wallet/permissiondeletekey" - body: "*" - additional_bindings { - get: "/wallet/permissiondeletekey" - } - }; - } rpc AddSign (TransactionSign) returns (TransactionExtention) { diff --git a/core/Contract.proto b/core/Contract.proto index 5e20f831d..665632afd 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -248,22 +248,4 @@ message AccountPermissionUpdateContract { Permission owner = 2; //Empty is invalidate Permission witness = 3;//Can be empty repeated Permission actives = 4;//Empty is invalidate -} - -message PermissionAddKeyContract { - bytes owner_address = 1; - Key key = 2; - int32 permission_id = 3; -} - -message PermissionUpdateKeyContract { - bytes owner_address = 1; - Key key = 2; - int32 permission_id = 3; -} - -message PermissionDeleteKeyContract { - bytes owner_address = 1; - bytes key_address = 2; - int32 permission_id = 3; } \ No newline at end of file From 6b714c662d52c3f42a3ed8f2d2d1832a5199f0b8 Mon Sep 17 00:00:00 2001 From: zergweak Date: Fri, 18 Jan 2019 11:00:34 +0800 Subject: [PATCH 093/445] delete contract --- core/Tron.proto | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index fe38618be..374305bb6 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -272,9 +272,6 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; - PermissionAddKeyContract = 47; - PermissionUpdateKeyContract = 48; - PermissionDeleteKeyContract = 49; } ContractType type = 1; google.protobuf.Any parameter = 2; From 23bbd1a694bf8b434fcc27aad60f5430d281bbcb Mon Sep 17 00:00:00 2001 From: zergweak Date: Thu, 24 Jan 2019 11:33:48 +0800 Subject: [PATCH 094/445] add rpc api : easyTransferAssert --- api/api.proto | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 2f1633c75..f1d310bbf 100644 --- a/api/api.proto +++ b/api/api.proto @@ -32,7 +32,6 @@ service Wallet { }; }; - //Please use CreateTransaction2 instead of this function. rpc CreateTransaction (TransferContract) returns (Transaction) { option (google.api.http) = { @@ -552,6 +551,12 @@ service Wallet { }; }; //Warning: do not invoke this interface provided by others. + rpc EasyTransferAssert (EasyTransferAssertMessage) returns (EasyTransferResponse) { + }; + //Warning: do not invoke this interface provided by others. + rpc EasyTransferAssertByPrivate (EasyTransferAssertByPrivateMessage) returns (EasyTransferResponse) { + }; + //Warning: do not invoke this interface provided by others. rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { option (google.api.http) = { post: "/wallet/easytransfer" @@ -832,7 +837,6 @@ message DelegatedResourceList { repeated DelegatedResource delegatedResource = 1; } - // Gossip node list message NodeList { repeated Node nodes = 1; @@ -920,12 +924,26 @@ message EasyTransferMessage { int64 amount = 3; } +message EasyTransferAssertMessage { + bytes passPhrase = 1; + bytes toAddress = 2; + string assertId = 3; + int64 amount = 4; +} + message EasyTransferByPrivateMessage { bytes privateKey = 1; bytes toAddress = 2; int64 amount = 3; } +message EasyTransferAssertByPrivateMessage { + bytes privateKey = 1; + bytes toAddress = 2; + string assertId = 3; + int64 amount = 4; +} + message EasyTransferResponse { Transaction transaction = 1; Return result = 2; From 9d70f3f75e5513f8e38960982c2ea57473eff239 Mon Sep 17 00:00:00 2001 From: zergweak Date: Thu, 24 Jan 2019 12:06:48 +0800 Subject: [PATCH 095/445] add http api easytranferasset --- api/api.proto | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/api.proto b/api/api.proto index f1d310bbf..6968e4ebc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -551,10 +551,10 @@ service Wallet { }; }; //Warning: do not invoke this interface provided by others. - rpc EasyTransferAssert (EasyTransferAssertMessage) returns (EasyTransferResponse) { + rpc EasyTransferAsset (EasyTransferAssetMessage) returns (EasyTransferResponse) { }; //Warning: do not invoke this interface provided by others. - rpc EasyTransferAssertByPrivate (EasyTransferAssertByPrivateMessage) returns (EasyTransferResponse) { + rpc EasyTransferAssetByPrivate (EasyTransferAssetByPrivateMessage) returns (EasyTransferResponse) { }; //Warning: do not invoke this interface provided by others. rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { @@ -924,10 +924,10 @@ message EasyTransferMessage { int64 amount = 3; } -message EasyTransferAssertMessage { +message EasyTransferAssetMessage { bytes passPhrase = 1; bytes toAddress = 2; - string assertId = 3; + string assetId = 3; int64 amount = 4; } @@ -937,10 +937,10 @@ message EasyTransferByPrivateMessage { int64 amount = 3; } -message EasyTransferAssertByPrivateMessage { +message EasyTransferAssetByPrivateMessage { bytes privateKey = 1; bytes toAddress = 2; - string assertId = 3; + string assetId = 3; int64 amount = 4; } From e292a870fbc53306ef60787b64f8b161fc6dc1b4 Mon Sep 17 00:00:00 2001 From: taihaofu Date: Mon, 28 Jan 2019 20:04:53 +0800 Subject: [PATCH 096/445] add bytecodeexecution exception to transaction --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 67d7405df..98b36225f 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -292,6 +292,7 @@ message Transaction { OUT_OF_TIME = 11; JVM_STACK_OVER_FLOW = 12; UNKNOWN = 13; + BYTECODE_EXECUTION_EXCEPTION = 14; } int64 fee = 1; code ret = 2; From 315ba554439aedbe87ce07c3a2d4a2892deba3d9 Mon Sep 17 00:00:00 2001 From: jiangyy Date: Thu, 31 Jan 2019 15:58:09 +0800 Subject: [PATCH 097/445] protobuf for deferred transaction --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 60cb6e637..449fb2828 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -335,6 +335,8 @@ message Transaction { // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; repeated Result ret = 5; + int64 delaySeconds = 6; + int64 senderId = 7; } message TransactionInfo { From 11160ff4b90e98b31ead466520643b3ef81f94b5 Mon Sep 17 00:00:00 2001 From: taihaofu Date: Thu, 31 Jan 2019 20:39:37 +0800 Subject: [PATCH 098/445] use new proposal instead of hard fork to upgrade tvm --- core/Tron.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 60cb6e637..374305bb6 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -300,7 +300,6 @@ message Transaction { OUT_OF_TIME = 11; JVM_STACK_OVER_FLOW = 12; UNKNOWN = 13; - BYTECODE_EXECUTION_EXCEPTION = 14; } int64 fee = 1; code ret = 2; From c4040b1fc0357bf1bba109d64bd45a36caebcd37 Mon Sep 17 00:00:00 2001 From: jiangyy Date: Fri, 1 Feb 2019 16:14:11 +0800 Subject: [PATCH 099/445] add deferred store --- core/Tron.proto | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 449fb2828..ed68c8f43 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -370,6 +370,16 @@ message TransactionInfo { int64 exchange_id = 21; } +message DeferredTransaction { + bytes transactionId = 1; + int64 senderId = 2; + int64 publishTime = 3; + int64 delayUntil = 4; + int64 expiration = 5; + bytes senderAddress = 6; + bytes receiverAddress = 7; +} + message Transactions { repeated Transaction transactions = 1; } From 15f3349ea2951ebcaacc79294d0de55952a9aea0 Mon Sep 17 00:00:00 2001 From: jiangyy Date: Fri, 1 Feb 2019 16:42:01 +0800 Subject: [PATCH 100/445] remove senderid from tron.proto, use transaction id instead --- core/Tron.proto | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index ed68c8f43..2adee0896 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -336,7 +336,6 @@ message Transaction { repeated bytes signature = 2; repeated Result ret = 5; int64 delaySeconds = 6; - int64 senderId = 7; } message TransactionInfo { @@ -372,12 +371,11 @@ message TransactionInfo { message DeferredTransaction { bytes transactionId = 1; - int64 senderId = 2; - int64 publishTime = 3; - int64 delayUntil = 4; - int64 expiration = 5; - bytes senderAddress = 6; - bytes receiverAddress = 7; + int64 publishTime = 2; + int64 delayUntil = 3; + int64 expiration = 4; + bytes senderAddress = 5; + bytes receiverAddress = 6; } message Transactions { From 53a6de3153447faa58a8c31ddb3dc5cfd633cf34 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Tue, 12 Feb 2019 18:06:11 +0800 Subject: [PATCH 101/445] execute deffered transaction --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 2adee0896..f86914742 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -376,6 +376,7 @@ message DeferredTransaction { int64 expiration = 4; bytes senderAddress = 5; bytes receiverAddress = 6; + Transaction transaction = 7; } message Transactions { From 040c2ca1dda9e2105f69ce9f16fc718065842094 Mon Sep 17 00:00:00 2001 From: jiangyy Date: Fri, 15 Feb 2019 15:35:04 +0800 Subject: [PATCH 102/445] add new rpc interface to cancel deferred transaction --- api/api.proto | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 53d981083..ce3e633da 100644 --- a/api/api.proto +++ b/api/api.proto @@ -616,7 +616,17 @@ service Wallet { } rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { - }; + } + + rpc cancelDeferredTransaction (BytesMessage) returns (Return) { + option (google.api.http) = { + post: "/wallet/cancelDeferredTransaction" + body: "*" + additional_bindings { + get: "/wallet/cancelDeferredTransaction" + } + }; + }; }; From ea11ee53a1878cf04859f6dac922c054194ebd17 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Tue, 19 Feb 2019 16:32:23 +0800 Subject: [PATCH 103/445] put cancel deffer transaction into chain --- api/api.proto | 3 +++ core/Contract.proto | 5 +++++ core/Tron.proto | 1 + 3 files changed, 9 insertions(+) diff --git a/api/api.proto b/api/api.proto index ce3e633da..0cf8f970e 100644 --- a/api/api.proto +++ b/api/api.proto @@ -92,6 +92,9 @@ service Wallet { }; }; + rpc CreateCancelDefferedTransactionContract (CancelDefferedTransactionContract) returns (TransactionExtention) { + }; + //modify the consume_user_resource_percent rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; diff --git a/core/Contract.proto b/core/Contract.proto index 665632afd..b46e94e91 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -47,6 +47,11 @@ message TransferContract { int64 amount = 3; } +message CancelDefferedTransactionContract { + bytes transactionId = 1; + bytes ownerAddress = 2; +} + message TransferAssetContract { bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. bytes owner_address = 2; diff --git a/core/Tron.proto b/core/Tron.proto index 4fabc888d..5d59afe3a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -272,6 +272,7 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; + CancelDefferedTransactionContract = 47; } ContractType type = 1; google.protobuf.Any parameter = 2; From a6e22c621d91753f398c96a0043e34d6d321f0c5 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Wed, 20 Feb 2019 14:44:59 +0800 Subject: [PATCH 104/445] add get deffertransaction api --- api/api.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/api.proto b/api/api.proto index 0cf8f970e..d408fb1fa 100644 --- a/api/api.proto +++ b/api/api.proto @@ -95,6 +95,9 @@ service Wallet { rpc CreateCancelDefferedTransactionContract (CancelDefferedTransactionContract) returns (TransactionExtention) { }; + rpc GetDeferredTransactionById (BytesMessage) returns (Transaction) { + } + //modify the consume_user_resource_percent rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; From 9ad71e9e7c6a27aac798182f14b31e52c6dccfdd Mon Sep 17 00:00:00 2001 From: wubin1 Date: Wed, 20 Feb 2019 15:12:12 +0800 Subject: [PATCH 105/445] change delay second to transaction capsule --- core/Tron.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 5d59afe3a..7c7aed7d0 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -335,7 +335,6 @@ message Transaction { // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; repeated Result ret = 5; - int64 delaySeconds = 6; } message TransactionInfo { From ea77a9fd88e20a0868a0ef7ac3de3ea7ad867cc0 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Wed, 20 Feb 2019 17:58:53 +0800 Subject: [PATCH 106/445] move transaction delay time to raw data --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 7c7aed7d0..66e2d059c 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -329,6 +329,7 @@ message Transaction { bytes scripts = 12; int64 timestamp = 14; int64 fee_limit = 18; + int64 delaySeconds = 19; } raw raw_data = 1; From adbd279bd4e4f94cbaa75f430ecfbb65f6572471 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Thu, 21 Feb 2019 17:08:42 +0800 Subject: [PATCH 107/445] add deferred transaction delay time no more than 45 days --- api/api.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/api/api.proto b/api/api.proto index d408fb1fa..d807e9a0f 100644 --- a/api/api.proto +++ b/api/api.proto @@ -825,6 +825,7 @@ message Return { SERVER_BUSY = 9; NO_CONNECTION = 10; NOT_ENOUGH_EFFECTIVE_CONNECTION = 11; + TOO_LONG_DEFERRED_TRANSACTION_DELAYTIME = 12; OTHER_ERROR = 20; } From b86c0d62055abb45eccbcee95dab47b3973eb685 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Fri, 22 Feb 2019 15:26:39 +0800 Subject: [PATCH 108/445] fix type name error --- api/api.proto | 2 +- core/Contract.proto | 2 +- core/Tron.proto | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/api.proto b/api/api.proto index d807e9a0f..7cf5e5ac1 100644 --- a/api/api.proto +++ b/api/api.proto @@ -92,7 +92,7 @@ service Wallet { }; }; - rpc CreateCancelDefferedTransactionContract (CancelDefferedTransactionContract) returns (TransactionExtention) { + rpc CreateCancelDeferredTransactionContract (CancelDeferredTransactionContract) returns (TransactionExtention) { }; rpc GetDeferredTransactionById (BytesMessage) returns (Transaction) { diff --git a/core/Contract.proto b/core/Contract.proto index b46e94e91..ce1c18d74 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -47,7 +47,7 @@ message TransferContract { int64 amount = 3; } -message CancelDefferedTransactionContract { +message CancelDeferredTransactionContract { bytes transactionId = 1; bytes ownerAddress = 2; } diff --git a/core/Tron.proto b/core/Tron.proto index 66e2d059c..392b6754b 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -272,7 +272,7 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; - CancelDefferedTransactionContract = 47; + CancelDeferredTransactionContract = 47; } ContractType type = 1; google.protobuf.Any parameter = 2; From 06f0475556be7ee449075988e2695929a6868544 Mon Sep 17 00:00:00 2001 From: jiangyy Date: Mon, 25 Feb 2019 18:15:00 +0800 Subject: [PATCH 109/445] broadcast raw trx id at the first time; add delaySeconds in deferred transaction. --- core/Tron.proto | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 392b6754b..dddd25bc7 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -372,11 +372,12 @@ message TransactionInfo { message DeferredTransaction { bytes transactionId = 1; int64 publishTime = 2; - int64 delayUntil = 3; - int64 expiration = 4; - bytes senderAddress = 5; - bytes receiverAddress = 6; - Transaction transaction = 7; + int64 delaySeconds = 3; + int64 delayUntil = 4; + int64 expiration = 5; + bytes senderAddress = 6; + bytes receiverAddress = 7; + Transaction transaction = 8; } message Transactions { From 1188b28ed7a29bb6b792a44d5108b93f30bf48a7 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Tue, 26 Feb 2019 17:43:36 +0800 Subject: [PATCH 110/445] get deferred transaction by trxId --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 7cf5e5ac1..5a92cc283 100644 --- a/api/api.proto +++ b/api/api.proto @@ -95,7 +95,7 @@ service Wallet { rpc CreateCancelDeferredTransactionContract (CancelDeferredTransactionContract) returns (TransactionExtention) { }; - rpc GetDeferredTransactionById (BytesMessage) returns (Transaction) { + rpc GetDeferredTransactionById (BytesMessage) returns (DeferredTransaction) { } //modify the consume_user_resource_percent From 16b437e268803573b6ecc53c72f17c1992af7230 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Wed, 13 Mar 2019 12:19:07 +0800 Subject: [PATCH 111/445] add deferred stage into proto --- core/Tron.proto | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index dddd25bc7..7b63ab36e 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -236,6 +236,11 @@ message ResourceReceipt { Transaction.Result.contractResult result = 7; } +message DeferredStage { + int64 delaySeconds = 1; + int32 stage = 2; +} + message Transaction { message Contract { enum ContractType { @@ -329,7 +334,7 @@ message Transaction { bytes scripts = 12; int64 timestamp = 14; int64 fee_limit = 18; - int64 delaySeconds = 19; + DeferredStage deferredStage = 19; } raw raw_data = 1; From 3c3eae5b9b92e1e3fe6abd422a3a6415444ea2da Mon Sep 17 00:00:00 2001 From: wubin1 Date: Thu, 14 Mar 2019 15:24:25 +0800 Subject: [PATCH 112/445] add http endpoint --- api/api.proto | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/api.proto b/api/api.proto index bfffc8a00..2fecec2bc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -604,6 +604,16 @@ service Wallet { }; } + rpc GetDeferredTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/wallet/getdeferredtransactioninfobyid" + body: "*" + additional_bindings { + get: "/wallet/getdeferredtransactioninfobyid" + } + }; + } + rpc AccountPermissionUpdate (AccountPermissionUpdateContract) returns (TransactionExtention) { option (google.api.http) = { post: "/wallet/accountpermissionupdate" @@ -759,6 +769,17 @@ service WalletSolidity { } }; } + + rpc GetDeferredTransactionInfoById (BytesMessage) returns (TransactionInfo) { + option (google.api.http) = { + post: "/walletsolidity/getdeferredtransactioninfobyid" + body: "*" + additional_bindings { + get: "/walletsolidity/getdeferredtransactioninfobyid" + } + }; + } + //Warning: do not invoke this interface provided by others. rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { option (google.api.http) = { From 7315288975e1aa229530a41be687e784015df3cc Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 18 Mar 2019 17:04:38 +0800 Subject: [PATCH 113/445] fix conflict --- core/Tron.proto | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index ee6e96843..ffd2ece7f 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -273,9 +273,6 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; - PermissionAddKeyContract = 47; - PermissionUpdateKeyContract = 48; - PermissionDeleteKeyContract = 49; ZksnarkV0TransferContract = 50; } ContractType type = 1; From fe28f2e5553f511977d92e57f41ee253567dcd1e Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 18 Mar 2019 18:57:08 +0800 Subject: [PATCH 114/445] change class name --- api/api.proto | 4 ++-- core/Contract.proto | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/api.proto b/api/api.proto index e5bb720de..71607f816 100644 --- a/api/api.proto +++ b/api/api.proto @@ -617,10 +617,10 @@ service Wallet { rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { } - rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleWitness) { + rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleVoucher) { } - rpc GetMerkleTreeWitnessInfo (OutputPointInfo) returns (IncrementalMerkleWitnessInfo) { + rpc GetMerkleTreeWitnessInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { } rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { diff --git a/core/Contract.proto b/core/Contract.proto index 7f1cc28f9..571c05988 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -102,7 +102,7 @@ message IncrementalMerkleTree { repeated SHA256Compress parents = 3; } -message IncrementalMerkleWitness { +message IncrementalMerkleVoucher { IncrementalMerkleTree tree = 1; repeated SHA256Compress filled = 2; IncrementalMerkleTree cursor = 3; @@ -111,9 +111,9 @@ message IncrementalMerkleWitness { OutputPoint output_point = 10; } -message IncrementalMerkleWitnessInfo { - IncrementalMerkleWitness witness1 = 1; - IncrementalMerkleWitness witness2 = 2; +message IncrementalMerkleVoucherInfo { + IncrementalMerkleVoucher voucher1 = 1; + IncrementalMerkleVoucher voucher2 = 2; int32 block_num = 3; } From 861e3f47ebdf6fbfd4bf8ec81a3e455743f97685 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Wed, 20 Mar 2019 17:51:46 +0800 Subject: [PATCH 115/445] remove hard code --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 2fecec2bc..7efb899d9 100644 --- a/api/api.proto +++ b/api/api.proto @@ -852,7 +852,7 @@ message Return { SERVER_BUSY = 9; NO_CONNECTION = 10; NOT_ENOUGH_EFFECTIVE_CONNECTION = 11; - TOO_LONG_DEFERRED_TRANSACTION_DELAYTIME = 12; + DEFERRED_SECONDS_ILLEGAL_ERROR = 12; OTHER_ERROR = 20; } From c73885faa9e184d5ac6ddb5c42fa55a3b68c2805 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Thu, 21 Mar 2019 20:57:31 +0800 Subject: [PATCH 116/445] fix sonar problem --- api/api.proto | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/api/api.proto b/api/api.proto index 7efb899d9..eca9254a1 100644 --- a/api/api.proto +++ b/api/api.proto @@ -95,9 +95,6 @@ service Wallet { rpc CreateCancelDeferredTransactionContract (CancelDeferredTransactionContract) returns (TransactionExtention) { }; - rpc GetDeferredTransactionById (BytesMessage) returns (DeferredTransaction) { - } - //modify the consume_user_resource_percent rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; @@ -401,6 +398,16 @@ service Wallet { }; } + rpc GetDeferredTransactionById (BytesMessage) returns (DeferredTransaction) { + option (google.api.http) = { + post: "/wallet/getdeferredtransactionbyid" + body: "*" + additional_bindings { + get: "/wallet/getdeferredtransactionbyid" + } + }; + } + rpc DeployContract (CreateSmartContract) returns (TransactionExtention) { } @@ -760,6 +767,17 @@ service WalletSolidity { } }; } + + rpc GetDeferredTransactionById (BytesMessage) returns (DeferredTransaction) { + option (google.api.http) = { + post: "/walletsolidity/getdeferredtransactionbyid" + body: "*" + additional_bindings { + get: "/walletsolidity/getdeferredtransactionbyid" + } + }; + } + rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { option (google.api.http) = { post: "/walletsolidity/gettransactioninfobyid" From 369673bdb84db19504b3066a843607295c4e96fa Mon Sep 17 00:00:00 2001 From: wubin1 Date: Thu, 28 Mar 2019 14:22:49 +0800 Subject: [PATCH 117/445] add update deferred transaction --- api/api.proto | 13 +++---------- core/Contract.proto | 10 ++++++++-- core/Tron.proto | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/api.proto b/api/api.proto index eca9254a1..f5f1cc9c5 100644 --- a/api/api.proto +++ b/api/api.proto @@ -95,6 +95,9 @@ service Wallet { rpc CreateCancelDeferredTransactionContract (CancelDeferredTransactionContract) returns (TransactionExtention) { }; + rpc CreateUpdateDeferredTransactionContract (UpdateDeferredTransactionContract) returns (TransactionExtention) { + }; + //modify the consume_user_resource_percent rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; @@ -646,16 +649,6 @@ service Wallet { rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { } - - rpc cancelDeferredTransaction (BytesMessage) returns (Return) { - option (google.api.http) = { - post: "/wallet/cancelDeferredTransaction" - body: "*" - additional_bindings { - get: "/wallet/cancelDeferredTransaction" - } - }; - }; }; diff --git a/core/Contract.proto b/core/Contract.proto index ce1c18d74..5869bc541 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -48,8 +48,14 @@ message TransferContract { } message CancelDeferredTransactionContract { - bytes transactionId = 1; - bytes ownerAddress = 2; + bytes transactionId = 1; + bytes ownerAddress = 2; +} + +message UpdateDeferredTransactionContract { + bytes transactionId = 1; + bytes ownerAddress = 2; + int64 delaySeconds = 3; } message TransferAssetContract { diff --git a/core/Tron.proto b/core/Tron.proto index 7b63ab36e..c7a08aa82 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -278,6 +278,7 @@ message Transaction { UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; CancelDeferredTransactionContract = 47; + UpdateDeferredTransactionContract = 48; } ContractType type = 1; google.protobuf.Any parameter = 2; From 66f095cfcf6af119bb6b62f18f341388046c611a Mon Sep 17 00:00:00 2001 From: wubin1 Date: Fri, 29 Mar 2019 11:02:40 +0800 Subject: [PATCH 118/445] revert update deferred transaction --- api/api.proto | 3 --- core/Contract.proto | 6 ------ core/Tron.proto | 1 - 3 files changed, 10 deletions(-) diff --git a/api/api.proto b/api/api.proto index f5f1cc9c5..5fa4a4987 100644 --- a/api/api.proto +++ b/api/api.proto @@ -95,9 +95,6 @@ service Wallet { rpc CreateCancelDeferredTransactionContract (CancelDeferredTransactionContract) returns (TransactionExtention) { }; - rpc CreateUpdateDeferredTransactionContract (UpdateDeferredTransactionContract) returns (TransactionExtention) { - }; - //modify the consume_user_resource_percent rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; diff --git a/core/Contract.proto b/core/Contract.proto index 5869bc541..53dab8778 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -52,12 +52,6 @@ message CancelDeferredTransactionContract { bytes ownerAddress = 2; } -message UpdateDeferredTransactionContract { - bytes transactionId = 1; - bytes ownerAddress = 2; - int64 delaySeconds = 3; -} - message TransferAssetContract { bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. bytes owner_address = 2; diff --git a/core/Tron.proto b/core/Tron.proto index c7a08aa82..7b63ab36e 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -278,7 +278,6 @@ message Transaction { UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; CancelDeferredTransactionContract = 47; - UpdateDeferredTransactionContract = 48; } ContractType type = 1; google.protobuf.Any parameter = 2; From 5f98d2e787d2eeabf619a2d151fb54afba5755f4 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Thu, 4 Apr 2019 14:35:47 +0800 Subject: [PATCH 119/445] remove unused line --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 5fa4a4987..4e1052329 100644 --- a/api/api.proto +++ b/api/api.proto @@ -645,7 +645,7 @@ service Wallet { } rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { - } + }; }; From a8485eadd2ea18131041e7735c34e8bf33fb8faf Mon Sep 17 00:00:00 2001 From: llwslc Date: Mon, 1 Apr 2019 18:54:26 +0800 Subject: [PATCH 120/445] add triggerConstantContract --- api/api.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/api.proto b/api/api.proto index 6066d62c4..5af3dcab7 100644 --- a/api/api.proto +++ b/api/api.proto @@ -404,6 +404,9 @@ service Wallet { rpc TriggerContract (TriggerSmartContract) returns (TransactionExtention) { } + rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { + } + rpc ListWitnesses (EmptyMessage) returns (WitnessList) { option (google.api.http) = { post: "/wallet/listwitnesses" From 8eb93007e7407b79746d31f0600a49c4c7d9600a Mon Sep 17 00:00:00 2001 From: renchenchang Date: Tue, 9 Apr 2019 21:02:32 +0800 Subject: [PATCH 121/445] add proto about shielded tx --- core/ShieldedTX.proto | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 core/ShieldedTX.proto diff --git a/core/ShieldedTX.proto b/core/ShieldedTX.proto new file mode 100644 index 000000000..d54ba3aa3 --- /dev/null +++ b/core/ShieldedTX.proto @@ -0,0 +1,78 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file +option java_outer_classname = "ShieldedTX"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message Note { + bytes value = 1; + bytes d = 2; + bytes pk_d = 3; + bytes r = 4; +} + +message SpendStatement { + bytes ak = 1; + bytes nsk = 2; + Note note = 3; + bytes alpha = 4; + bytes anchor = 5; + bytes witness = 6; +} + +message OutputStatement { + bytes esk = 1; + Note note = 2; +} + +message SpendStatement { + bytes ak = 1; + bytes nsk = 2; + Note note = 3; + bytes alpha = 4; + bytes anchor = 5; + bytes witness = 6; +} + +message SpendDescription { + bytes cv = 1; + bytes anchor = 2; + bytes nullifier = 3; + bytes rk = 4; + bytes zkproof = 5; + bytes spendAuthSig = 6; +} + +message OutputDescription { + bytes cv = 1; + bytes cm = 2; + bytes ephemeralKey = 3; + bytes encCiphertext = 4; + bytes outCiphertext = 5; + bytes zkproof = 6; +} + +message ShieldedTransaction { + repeated SpendDescription vShieldedSpend = 1; + repeated OutputDescription vShieldedOutput = 2; + bytes bindingSig = 3; + bytes tFromAddress = 4; + bytes tToAddress = 5; +} From 94e63af9d29c786c8e684979ecbfa8a7e63a2b38 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Tue, 9 Apr 2019 21:12:03 +0800 Subject: [PATCH 122/445] rename OutputDescription --- core/ShieldedTX.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/ShieldedTX.proto b/core/ShieldedTX.proto index d54ba3aa3..0b8f44c29 100644 --- a/core/ShieldedTX.proto +++ b/core/ShieldedTX.proto @@ -63,9 +63,9 @@ message SpendDescription { message OutputDescription { bytes cv = 1; bytes cm = 2; - bytes ephemeralKey = 3; - bytes encCiphertext = 4; - bytes outCiphertext = 5; + bytes epk = 3; + bytes c_enc = 4; + bytes c_out = 5; bytes zkproof = 6; } From a1165c5ef295053e165e2521d1cb8caa27562941 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Wed, 10 Apr 2019 11:52:33 +0800 Subject: [PATCH 123/445] proto re defination --- core/Contract.proto | 64 +++++++++++++++++++++++++++++++---- core/ShieldedTX.proto | 78 ------------------------------------------- core/Tron.proto | 8 ++--- 3 files changed, 62 insertions(+), 88 deletions(-) delete mode 100644 core/ShieldedTX.proto diff --git a/core/Contract.proto b/core/Contract.proto index 571c05988..ad190caf2 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -72,11 +72,11 @@ message zkv0proof { } message AuthenticationPath { - repeated bool value = 1; + repeated bool value = 1; } message MerklePath { - repeated AuthenticationPath authentication_paths= 1; + repeated AuthenticationPath authentication_paths = 1; repeated bool index = 2; bytes rt = 3; } @@ -341,7 +341,59 @@ message ExchangeTransactionContract { message AccountPermissionUpdateContract { bytes owner_address = 1; - Permission owner = 2; //Empty is invalidate - Permission witness = 3;//Can be empty - repeated Permission actives = 4;//Empty is invalidate -} \ No newline at end of file + Permission owner = 2; //Empty is invalidate + Permission witness = 3; //Can be empty + repeated Permission actives = 4; //Empty is invalidate +} + +// for shielded transaction +message Note { + bytes value = 1; + bytes d = 2; + bytes pk_d = 3; + bytes r = 4; +} + +//message SpendStatement { +// bytes ak = 1; +// bytes nsk = 2; +// Note note = 3; +// bytes alpha = 4; +// bytes anchor = 5; +// bytes witness = 6; +//} +// +//message OutputStatement { +// bytes esk = 1; +// Note note = 2; +//} + +message SpendDescription { + bytes cv = 1; + bytes anchor = 2; + bytes nullifier = 3; + bytes rk = 4; + bytes zkproof = 5; + bytes spendAuthSig = 6; +} + +message OutputDescription { + bytes cv = 1; + bytes cm = 2; + bytes epk = 3; + bytes c_enc = 4; + bytes c_out = 5; + bytes zkproof = 6; +} + +message ShieldedTransactionContract { + bytes t_from_address = 1; //transparent address + bytes from_amount = 2; + repeated SpendDescription shieldedSpend = 3; + repeated OutputDescription shieldedOutput = 4; + bytes bindingSig = 5; + bytes valueBalance = 6; + bytes t_to_address = 7; //transparent address + bytes to_amount = 8; //the amount to transparent to_address +} +// end shielded transaction \ No newline at end of file diff --git a/core/ShieldedTX.proto b/core/ShieldedTX.proto deleted file mode 100644 index 0b8f44c29..000000000 --- a/core/ShieldedTX.proto +++ /dev/null @@ -1,78 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -syntax = "proto3"; - -package protocol; - -option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file -option java_outer_classname = "ShieldedTX"; //Specify the class name of the generated Java file -option go_package = "github.com/tronprotocol/grpc-gateway/core"; - -message Note { - bytes value = 1; - bytes d = 2; - bytes pk_d = 3; - bytes r = 4; -} - -message SpendStatement { - bytes ak = 1; - bytes nsk = 2; - Note note = 3; - bytes alpha = 4; - bytes anchor = 5; - bytes witness = 6; -} - -message OutputStatement { - bytes esk = 1; - Note note = 2; -} - -message SpendStatement { - bytes ak = 1; - bytes nsk = 2; - Note note = 3; - bytes alpha = 4; - bytes anchor = 5; - bytes witness = 6; -} - -message SpendDescription { - bytes cv = 1; - bytes anchor = 2; - bytes nullifier = 3; - bytes rk = 4; - bytes zkproof = 5; - bytes spendAuthSig = 6; -} - -message OutputDescription { - bytes cv = 1; - bytes cm = 2; - bytes epk = 3; - bytes c_enc = 4; - bytes c_out = 5; - bytes zkproof = 6; -} - -message ShieldedTransaction { - repeated SpendDescription vShieldedSpend = 1; - repeated OutputDescription vShieldedOutput = 2; - bytes bindingSig = 3; - bytes tFromAddress = 4; - bytes tToAddress = 5; -} diff --git a/core/Tron.proto b/core/Tron.proto index ffd2ece7f..dab60eb6e 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -66,7 +66,6 @@ message ChainParameters { } } - /* Account */ message Account { /* frozen balance */ @@ -178,11 +177,11 @@ message Permission { Active = 2; } PermissionType type = 1; - int32 id = 2; //Owner id=0, Witness id=1, Active id start by 2 + int32 id = 2; //Owner id=0, Witness id=1, Active id start by 2 string permission_name = 3; int64 threshold = 4; int32 parent_id = 5; - bytes operations = 6; //1 bit 1 contract + bytes operations = 6; //1 bit 1 contract repeated Key keys = 7; } @@ -274,6 +273,7 @@ message Transaction { UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; ZksnarkV0TransferContract = 50; + ShieldedTransactionContract = 51; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -335,7 +335,7 @@ message Transaction { raw raw_data = 1; // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; - repeated bytes signature_zk = 3; + // repeated bytes signature_zk = 3; repeated Result ret = 5; } From db7a738f591a6a359ae3bbb5334c437a53477f6c Mon Sep 17 00:00:00 2001 From: renchenchang Date: Wed, 10 Apr 2019 12:19:48 +0800 Subject: [PATCH 124/445] add stament defination --- core/Contract.proto | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index ad190caf2..34070f78f 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -354,19 +354,19 @@ message Note { bytes r = 4; } -//message SpendStatement { -// bytes ak = 1; -// bytes nsk = 2; -// Note note = 3; -// bytes alpha = 4; -// bytes anchor = 5; -// bytes witness = 6; -//} -// -//message OutputStatement { -// bytes esk = 1; -// Note note = 2; -//} +message SpendStatement { + bytes ak = 1; + bytes nsk = 2; + Note note = 3; + bytes alpha = 4; + bytes anchor = 5; + bytes witness = 6; +} + +message OutputStatement { + bytes esk = 1; + Note note = 2; +} message SpendDescription { bytes cv = 1; From f7f523b0a908876654cd56f540a58adb3a15a87e Mon Sep 17 00:00:00 2001 From: renchenchang Date: Wed, 10 Apr 2019 12:29:07 +0800 Subject: [PATCH 125/445] add api for ShieldedTransaction --- api/api.proto | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/api.proto b/api/api.proto index 71607f816..adc134de2 100644 --- a/api/api.proto +++ b/api/api.proto @@ -659,6 +659,17 @@ service Wallet { rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { }; + + // for shiededTransaction + rpc CreateSpendProof (SpendStatement) returns (SpendDescription) { + }; + + rpc CreateOutPutProof (OutputStatement) returns (OutputDescription) { + }; + + rpc CreateShieldedTransaction(ShieldedTransactionContract) returns (TransactionExtention) { + }; + // end for shiededTransaction }; From dfe5b7cc15357b5cf5f09097f5c5d5429a8f0516 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Wed, 10 Apr 2019 14:35:35 +0800 Subject: [PATCH 126/445] api for shielded tx --- api/api.proto | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/api/api.proto b/api/api.proto index adc134de2..16650d41e 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1,4 +1,3 @@ - syntax = "proto3"; package protocol; @@ -631,7 +630,8 @@ service Wallet { } //Get the specified height block merkleTree - rpc GetMerkleTreeOfBlock ( NumberMessage ) returns ( BlockIncrementalMerkleTree ) { } + rpc GetMerkleTreeOfBlock (NumberMessage) returns (BlockIncrementalMerkleTree) { + } rpc AccountPermissionUpdate (AccountPermissionUpdateContract) returns (TransactionExtention) { @@ -653,7 +653,7 @@ service Wallet { } - rpc GetTransactionApprovedList(Transaction) returns (TransactionApprovedList) { + rpc GetTransactionApprovedList (Transaction) returns (TransactionApprovedList) { } @@ -667,7 +667,7 @@ service Wallet { rpc CreateOutPutProof (OutputStatement) returns (OutputDescription) { }; - rpc CreateShieldedTransaction(ShieldedTransactionContract) returns (TransactionExtention) { + rpc CreateShieldedTransaction (ShieldedTransactionContract) returns (TransactionExtention) { }; // end for shiededTransaction }; @@ -906,7 +906,6 @@ message DelegatedResourceList { repeated DelegatedResource delegatedResource = 1; } - // Gossip node list message NodeList { repeated Node nodes = 1; From 7a9fdb8644f9fd2ed29f9e0b65c89e4c8466b6a6 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Wed, 10 Apr 2019 18:54:12 +0800 Subject: [PATCH 127/445] rename the fields in shielded transaction --- core/Contract.proto | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 34070f78f..1496beb3a 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -351,7 +351,7 @@ message Note { bytes value = 1; bytes d = 2; bytes pk_d = 3; - bytes r = 4; + bytes rcm = 4; } message SpendStatement { @@ -360,7 +360,7 @@ message SpendStatement { Note note = 3; bytes alpha = 4; bytes anchor = 5; - bytes witness = 6; + bytes path = 6; } message OutputStatement { @@ -369,31 +369,31 @@ message OutputStatement { } message SpendDescription { - bytes cv = 1; + bytes value_commitment = 1; bytes anchor = 2; bytes nullifier = 3; bytes rk = 4; bytes zkproof = 5; - bytes spendAuthSig = 6; + bytes spend_authority_sign = 6; } message OutputDescription { - bytes cv = 1; - bytes cm = 2; + bytes value_commitment = 1; + bytes note_commitment = 2; bytes epk = 3; bytes c_enc = 4; bytes c_out = 5; bytes zkproof = 6; } -message ShieldedTransactionContract { - bytes t_from_address = 1; //transparent address +message ShieldedTransferContract { + bytes transparent_from_address = 1; //transparent address bytes from_amount = 2; - repeated SpendDescription shieldedSpend = 3; - repeated OutputDescription shieldedOutput = 4; - bytes bindingSig = 5; - bytes valueBalance = 6; - bytes t_to_address = 7; //transparent address + repeated SpendDescription spend_description = 3; + repeated OutputDescription output_description = 4; + bytes binding_sign = 5; + bytes value_balance = 6; + bytes transparent_to_address = 7; //transparent address bytes to_amount = 8; //the amount to transparent to_address } // end shielded transaction \ No newline at end of file From 6b0027c51d061429e3d2f0463ae1ce59233cbf2b Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 11 Apr 2019 11:17:03 +0800 Subject: [PATCH 128/445] rename sign in proto --- core/Contract.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 1496beb3a..870724128 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -374,7 +374,7 @@ message SpendDescription { bytes nullifier = 3; bytes rk = 4; bytes zkproof = 5; - bytes spend_authority_sign = 6; + bytes spend_authority_signature = 6; } message OutputDescription { @@ -391,7 +391,7 @@ message ShieldedTransferContract { bytes from_amount = 2; repeated SpendDescription spend_description = 3; repeated OutputDescription output_description = 4; - bytes binding_sign = 5; + bytes binding_signature = 5; bytes value_balance = 6; bytes transparent_to_address = 7; //transparent address bytes to_amount = 8; //the amount to transparent to_address From 1a98c32bcd3b29de69ef951d7b14d024306b878e Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 11 Apr 2019 11:23:58 +0800 Subject: [PATCH 129/445] annotation for proto --- core/Contract.proto | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 870724128..d18169f3b 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -358,21 +358,21 @@ message SpendStatement { bytes ak = 1; bytes nsk = 2; Note note = 3; - bytes alpha = 4; - bytes anchor = 5; - bytes path = 6; + bytes alpha = 4; // random number for spend authority signature + bytes anchor = 5; // merkle root + bytes path = 6; // path for cm to root in merkle tree } message OutputStatement { - bytes esk = 1; + bytes esk = 1; // for Encryption Note note = 2; } message SpendDescription { bytes value_commitment = 1; - bytes anchor = 2; - bytes nullifier = 3; - bytes rk = 4; + bytes anchor = 2; // merkle root + bytes nullifier = 3; // used for check double spend + bytes rk = 4; // used for check spend authority signature bytes zkproof = 5; bytes spend_authority_signature = 6; } @@ -380,9 +380,9 @@ message SpendDescription { message OutputDescription { bytes value_commitment = 1; bytes note_commitment = 2; - bytes epk = 3; - bytes c_enc = 4; - bytes c_out = 5; + bytes epk = 3; //for Encryption + bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk + bytes c_out = 5; // Encryption for audit, decrypt it with ovk bytes zkproof = 6; } From 4348559aa78c4472510b82c5986118b569521b6f Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 11 Apr 2019 11:24:31 +0800 Subject: [PATCH 130/445] annotation for proto --- core/Contract.proto | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index d18169f3b..d9201e73e 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -370,9 +370,9 @@ message OutputStatement { message SpendDescription { bytes value_commitment = 1; - bytes anchor = 2; // merkle root - bytes nullifier = 3; // used for check double spend - bytes rk = 4; // used for check spend authority signature + bytes anchor = 2; // merkle root + bytes nullifier = 3; // used for check double spend + bytes rk = 4; // used for check spend authority signature bytes zkproof = 5; bytes spend_authority_signature = 6; } @@ -380,20 +380,20 @@ message SpendDescription { message OutputDescription { bytes value_commitment = 1; bytes note_commitment = 2; - bytes epk = 3; //for Encryption - bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk - bytes c_out = 5; // Encryption for audit, decrypt it with ovk + bytes epk = 3; // for Encryption + bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk + bytes c_out = 5; // Encryption for audit, decrypt it with ovk bytes zkproof = 6; } message ShieldedTransferContract { - bytes transparent_from_address = 1; //transparent address + bytes transparent_from_address = 1; // transparent address bytes from_amount = 2; repeated SpendDescription spend_description = 3; repeated OutputDescription output_description = 4; bytes binding_signature = 5; bytes value_balance = 6; - bytes transparent_to_address = 7; //transparent address - bytes to_amount = 8; //the amount to transparent to_address + bytes transparent_to_address = 7; // transparent address + bytes to_amount = 8; // the amount to transparent to_address } // end shielded transaction \ No newline at end of file From b904ff07c1878a77e48a91a6d3548da9e266cff1 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 11 Apr 2019 11:25:34 +0800 Subject: [PATCH 131/445] annotation for proto --- core/Contract.proto | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index d9201e73e..5fa66a510 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -370,9 +370,9 @@ message OutputStatement { message SpendDescription { bytes value_commitment = 1; - bytes anchor = 2; // merkle root - bytes nullifier = 3; // used for check double spend - bytes rk = 4; // used for check spend authority signature + bytes anchor = 2; // merkle root + bytes nullifier = 3; // used for check double spend + bytes rk = 4; // used for check spend authority signature bytes zkproof = 5; bytes spend_authority_signature = 6; } @@ -380,20 +380,20 @@ message SpendDescription { message OutputDescription { bytes value_commitment = 1; bytes note_commitment = 2; - bytes epk = 3; // for Encryption - bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk - bytes c_out = 5; // Encryption for audit, decrypt it with ovk + bytes epk = 3; // for Encryption + bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk + bytes c_out = 5; // Encryption for audit, decrypt it with ovk bytes zkproof = 6; } message ShieldedTransferContract { - bytes transparent_from_address = 1; // transparent address + bytes transparent_from_address = 1; // transparent address bytes from_amount = 2; repeated SpendDescription spend_description = 3; repeated OutputDescription output_description = 4; bytes binding_signature = 5; bytes value_balance = 6; - bytes transparent_to_address = 7; // transparent address - bytes to_amount = 8; // the amount to transparent to_address + bytes transparent_to_address = 7; // transparent address + bytes to_amount = 8; // the amount to transparent to_address } // end shielded transaction \ No newline at end of file From 23a0b5b75b56a240c99b862d4708a359104d8cc9 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 11 Apr 2019 11:32:58 +0800 Subject: [PATCH 132/445] fix proto error --- api/api.proto | 2 +- core/Tron.proto | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 16650d41e..f1b883064 100644 --- a/api/api.proto +++ b/api/api.proto @@ -667,7 +667,7 @@ service Wallet { rpc CreateOutPutProof (OutputStatement) returns (OutputDescription) { }; - rpc CreateShieldedTransaction (ShieldedTransactionContract) returns (TransactionExtention) { + rpc CreateShieldedTransaction (ShieldedTransferContract) returns (TransactionExtention) { }; // end for shiededTransaction }; diff --git a/core/Tron.proto b/core/Tron.proto index 3911fe5cb..a0f1968ce 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -273,7 +273,7 @@ message Transaction { UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; ZksnarkV0TransferContract = 50; - ShieldedTransactionContract = 51; + ShieldedTransferContract = 51; } ContractType type = 1; google.protobuf.Any parameter = 2; From c0ec51158f56421a7eeeb15876124b47f21eb8fc Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 11 Apr 2019 11:53:29 +0800 Subject: [PATCH 133/445] add capsule --- core/Contract.proto | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 5fa66a510..10884ee77 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -368,12 +368,17 @@ message OutputStatement { Note note = 2; } + +message GrothProof { + bytes values= 1; +} + message SpendDescription { bytes value_commitment = 1; bytes anchor = 2; // merkle root bytes nullifier = 3; // used for check double spend bytes rk = 4; // used for check spend authority signature - bytes zkproof = 5; + GrothProof zkproof = 5; bytes spend_authority_signature = 6; } @@ -383,7 +388,7 @@ message OutputDescription { bytes epk = 3; // for Encryption bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk bytes c_out = 5; // Encryption for audit, decrypt it with ovk - bytes zkproof = 6; + GrothProof zkproof = 6; } message ShieldedTransferContract { From 36472f2adc390efd4f4641d7b75a76a15ebd7aa1 Mon Sep 17 00:00:00 2001 From: llwslc Date: Tue, 2 Apr 2019 17:22:51 +0800 Subject: [PATCH 134/445] merge clear abi feature --- api/api.proto | 3 +++ core/Contract.proto | 5 +++++ core/Tron.proto | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 5af3dcab7..f273b89fc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -406,6 +406,9 @@ service Wallet { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } + + rpc ClearContractABI (ClearABIContract) returns (TransactionExtention) { + } rpc ListWitnesses (EmptyMessage) returns (WitnessList) { option (google.api.http) = { diff --git a/core/Contract.proto b/core/Contract.proto index 665632afd..e9a662390 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -84,6 +84,11 @@ message UpdateEnergyLimitContract { int64 origin_energy_limit = 3; } +message ClearABIContract { + bytes owner_address = 1; + bytes contract_address = 2; +} + message WitnessCreateContract { bytes owner_address = 1; bytes url = 2; diff --git a/core/Tron.proto b/core/Tron.proto index 374305bb6..c7345ce18 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -272,6 +272,7 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; + ClearABIContract = 48; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -664,4 +665,4 @@ message NodeInfo { string stackTrace = 7; } } -} \ No newline at end of file +} From d6dbbaa214995440cbd1b501b818c4ecac7ce066 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Fri, 12 Apr 2019 17:17:54 +0800 Subject: [PATCH 135/445] rename field in proto --- api/api.proto | 2 +- core/Contract.proto | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/api.proto b/api/api.proto index f1b883064..531163c94 100644 --- a/api/api.proto +++ b/api/api.proto @@ -664,7 +664,7 @@ service Wallet { rpc CreateSpendProof (SpendStatement) returns (SpendDescription) { }; - rpc CreateOutPutProof (OutputStatement) returns (OutputDescription) { + rpc CreateReceiveProof (ReceiveStatement) returns (ReceiveDescription) { }; rpc CreateShieldedTransaction (ShieldedTransferContract) returns (TransactionExtention) { diff --git a/core/Contract.proto b/core/Contract.proto index 10884ee77..b997e7b4f 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -363,14 +363,14 @@ message SpendStatement { bytes path = 6; // path for cm to root in merkle tree } -message OutputStatement { +message ReceiveStatement { bytes esk = 1; // for Encryption Note note = 2; } message GrothProof { - bytes values= 1; + bytes values = 1; } message SpendDescription { @@ -382,7 +382,7 @@ message SpendDescription { bytes spend_authority_signature = 6; } -message OutputDescription { +message ReceiveDescription { bytes value_commitment = 1; bytes note_commitment = 2; bytes epk = 3; // for Encryption @@ -395,7 +395,7 @@ message ShieldedTransferContract { bytes transparent_from_address = 1; // transparent address bytes from_amount = 2; repeated SpendDescription spend_description = 3; - repeated OutputDescription output_description = 4; + repeated ReceiveDescription receive_description = 4; bytes binding_signature = 5; bytes value_balance = 6; bytes transparent_to_address = 7; // transparent address From 20378ed5dc4a3be0c370f669a86457dcd3123f48 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 15 Apr 2019 11:39:46 +0800 Subject: [PATCH 136/445] debug create spendProof --- core/Contract.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index b997e7b4f..cffa348ae 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -350,7 +350,7 @@ message AccountPermissionUpdateContract { message Note { bytes value = 1; bytes d = 2; - bytes pk_d = 3; + bytes pkD = 3; bytes rcm = 4; } From 187566a363965dca071d8bfaa2d0d4cf32213d35 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 15 Apr 2019 17:08:55 +0800 Subject: [PATCH 137/445] feat(pedersen_hash): modify 256hash to perdersen --- core/Contract.proto | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index cffa348ae..3b513a824 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -92,19 +92,19 @@ message OutputPointInfo { int32 block_num = 3; } -message SHA256Compress { +message PedersenHash { bytes content = 1; } message IncrementalMerkleTree { - SHA256Compress left = 1; - SHA256Compress right = 2; - repeated SHA256Compress parents = 3; + PedersenHash left = 1; + PedersenHash right = 2; + repeated PedersenHash parents = 3; } message IncrementalMerkleVoucher { IncrementalMerkleTree tree = 1; - repeated SHA256Compress filled = 2; + repeated PedersenHash filled = 2; IncrementalMerkleTree cursor = 3; int64 cursor_depth = 4; bytes rt = 5; From 186d9059d98d501aaa262379c06489c6bc87d1b0 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Mon, 15 Apr 2019 17:17:33 +0800 Subject: [PATCH 138/445] update desc for path in proto --- core/Contract.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index cffa348ae..f0aec0bd8 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -360,7 +360,7 @@ message SpendStatement { Note note = 3; bytes alpha = 4; // random number for spend authority signature bytes anchor = 5; // merkle root - bytes path = 6; // path for cm to root in merkle tree + bytes path = 6; // path for cm from leaf to root in merkle tree } message ReceiveStatement { From b91c921eb4d3795fa47e72f7c0dff266a4ec08be Mon Sep 17 00:00:00 2001 From: renchenchang Date: Tue, 16 Apr 2019 13:55:23 +0800 Subject: [PATCH 139/445] rm unless lines --- core/Contract.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 07f2124d1..8a42772b4 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -368,7 +368,6 @@ message ReceiveStatement { Note note = 2; } - message GrothProof { bytes values = 1; } From 98669eb7de92ae9b3170d76bac8df3b7e104c0cd Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 16 Apr 2019 14:19:32 +0800 Subject: [PATCH 140/445] valuebalance byte[] to long --- core/Contract.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 07f2124d1..9401500d5 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -397,7 +397,7 @@ message ShieldedTransferContract { repeated SpendDescription spend_description = 3; repeated ReceiveDescription receive_description = 4; bytes binding_signature = 5; - bytes value_balance = 6; + int64 value_balance = 6; bytes transparent_to_address = 7; // transparent address bytes to_amount = 8; // the amount to transparent to_address } From 0ec2c19ce2ddb23a50da431c6ccd98560d674d04 Mon Sep 17 00:00:00 2001 From: olivier Date: Tue, 16 Apr 2019 17:11:55 +0800 Subject: [PATCH 141/445] modify ammount data type --- core/Contract.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 5a21f7896..1fc944a38 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -392,12 +392,12 @@ message ReceiveDescription { message ShieldedTransferContract { bytes transparent_from_address = 1; // transparent address - bytes from_amount = 2; + int64 from_amount = 2; repeated SpendDescription spend_description = 3; repeated ReceiveDescription receive_description = 4; bytes binding_signature = 5; int64 value_balance = 6; bytes transparent_to_address = 7; // transparent address - bytes to_amount = 8; // the amount to transparent to_address + int64 to_amount = 8; // the amount to transparent to_address } // end shielded transaction \ No newline at end of file From d25f1f46db7a2431ea17d35896bef185d6728fb0 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Sun, 28 Apr 2019 12:28:50 +0800 Subject: [PATCH 142/445] test scan --- api/api.proto | 5 +++++ core/Contract.proto | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 531163c94..5e9492b5c 100644 --- a/api/api.proto +++ b/api/api.proto @@ -670,6 +670,11 @@ service Wallet { rpc CreateShieldedTransaction (ShieldedTransferContract) returns (TransactionExtention) { }; // end for shiededTransaction + + //protocol p47 + rpc GetCommitmentsByBlockRange(BlockIvkDecrypt) returns(IvkDecryptResult){ + }; + }; diff --git a/core/Contract.proto b/core/Contract.proto index 1fc944a38..90dd47c52 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -348,7 +348,7 @@ message AccountPermissionUpdateContract { // for shielded transaction message Note { - bytes value = 1; + int64 value = 1; bytes d = 2; bytes pkD = 3; bytes rcm = 4; @@ -400,4 +400,14 @@ message ShieldedTransferContract { bytes transparent_to_address = 7; // transparent address int64 to_amount = 8; // the amount to transparent to_address } -// end shielded transaction \ No newline at end of file +// end shielded transaction + +message BlockIvkDecrypt{ + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes ivk = 3; +} + +message IvkDecryptResult{ + repeated Note notes = 1; +} \ No newline at end of file From f3164739e5f3274a2dea5597898f3601e20bb3df Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sun, 28 Apr 2019 14:51:08 +0800 Subject: [PATCH 143/445] rm chinese desc --- api/api.proto | 12 +++++++++++- core/Contract.proto | 9 --------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api/api.proto b/api/api.proto index 5e9492b5c..66612eb65 100644 --- a/api/api.proto +++ b/api/api.proto @@ -672,7 +672,7 @@ service Wallet { // end for shiededTransaction //protocol p47 - rpc GetCommitmentsByBlockRange(BlockIvkDecrypt) returns(IvkDecryptResult){ + rpc GetCommitmentsByBlockRange (BlockIvkDecrypt) returns (IvkDecryptResult) { }; }; @@ -1091,4 +1091,14 @@ message TransactionApprovedList { repeated bytes approved_list = 2; Result result = 4; TransactionExtention transaction = 5; +} + +message BlockIvkDecrypt { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes ivk = 3; +} + +message IvkDecryptResult { + repeated Note notes = 1; } \ No newline at end of file diff --git a/core/Contract.proto b/core/Contract.proto index 90dd47c52..40b1a3e65 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -402,12 +402,3 @@ message ShieldedTransferContract { } // end shielded transaction -message BlockIvkDecrypt{ - int64 start_block_index = 1; - int64 end_block_index = 2; - bytes ivk = 3; -} - -message IvkDecryptResult{ - repeated Note notes = 1; -} \ No newline at end of file From 4688892bda344f0651f02bd77b4cd4be6736f00f Mon Sep 17 00:00:00 2001 From: ashu Date: Sun, 28 Apr 2019 16:02:38 +0800 Subject: [PATCH 144/445] set codeHash when deploying contract or trigger extcodehash firstly --- core/Tron.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 3d3f46dbe..7c433b58d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -559,7 +559,7 @@ message SmartContract { int64 consume_user_resource_percent = 6; string name = 7; int64 origin_energy_limit = 8; - + bytes code_hash = 9; } message InternalTransaction { From 93f9551f90b20995741f2c1bf1b03152d6c9d362 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Sun, 28 Apr 2019 17:47:03 +0800 Subject: [PATCH 145/445] test encrypt and decrypt after push transaction. --- api/api.proto | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index 66612eb65..5d4139a84 100644 --- a/api/api.proto +++ b/api/api.proto @@ -672,7 +672,10 @@ service Wallet { // end for shiededTransaction //protocol p47 - rpc GetCommitmentsByBlockRange (BlockIvkDecrypt) returns (IvkDecryptResult) { + rpc ScanNoteByBlockRangeAndIvk (IvkDecryptParameters) returns (DecryptNotes) { + }; + + rpc ScanNoteByBlockRangeAndOvk (OvkDecryptParameters) returns (DecryptNotes) { }; }; @@ -1093,12 +1096,18 @@ message TransactionApprovedList { TransactionExtention transaction = 5; } -message BlockIvkDecrypt { +message IvkDecryptParameters { int64 start_block_index = 1; int64 end_block_index = 2; bytes ivk = 3; } -message IvkDecryptResult { +message OvkDecryptParameters { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes ovk = 3; +} + +message DecryptNotes { repeated Note notes = 1; -} \ No newline at end of file +} From fe624e3fc8d872d92b588a70c52a70fd7b016dd4 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Mon, 29 Apr 2019 15:35:52 +0800 Subject: [PATCH 146/445] refactor proto for shielded transaction --- api/api.proto | 38 +++++++++++++++++++++++++++++++------- core/Contract.proto | 22 +--------------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/api/api.proto b/api/api.proto index 5d4139a84..807c11664 100644 --- a/api/api.proto +++ b/api/api.proto @@ -661,13 +661,7 @@ service Wallet { }; // for shiededTransaction - rpc CreateSpendProof (SpendStatement) returns (SpendDescription) { - }; - - rpc CreateReceiveProof (ReceiveStatement) returns (ReceiveDescription) { - }; - - rpc CreateShieldedTransaction (ShieldedTransferContract) returns (TransactionExtention) { + rpc CreateShieldedTransaction (PrivateParameters) returns (TransactionExtention) { }; // end for shiededTransaction @@ -1111,3 +1105,33 @@ message OvkDecryptParameters { message DecryptNotes { repeated Note notes = 1; } + +message Note { + int64 value = 1; + bytes d = 2; + bytes pkD = 3; + bytes rcm = 4; +} + +message SpendNote { + Note note = 3; + bytes alpha = 4; // random number for spend authority signature + bytes anchor = 5; // merkle root + bytes path = 6; // path for cm from leaf to root in merkle tree +} + +message ReceiveNote { + bytes esk = 1; // for Encryption + Note note = 2; +} + +message PrivateParameters { + bytes from_address = 1; + bytes ak = 2; + bytes nsk = 3; + int64 from_amount = 4; + repeated SpendNote shieldedSpends = 5; + repeated ReceiveNote shieldedReceives = 6; + bytes transparent_to_address = 7; + bytes to_amount = 8; +} diff --git a/core/Contract.proto b/core/Contract.proto index 40b1a3e65..6e8888c48 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -347,27 +347,6 @@ message AccountPermissionUpdateContract { } // for shielded transaction -message Note { - int64 value = 1; - bytes d = 2; - bytes pkD = 3; - bytes rcm = 4; -} - -message SpendStatement { - bytes ak = 1; - bytes nsk = 2; - Note note = 3; - bytes alpha = 4; // random number for spend authority signature - bytes anchor = 5; // merkle root - bytes path = 6; // path for cm from leaf to root in merkle tree -} - -message ReceiveStatement { - bytes esk = 1; // for Encryption - Note note = 2; -} - message GrothProof { bytes values = 1; } @@ -399,6 +378,7 @@ message ShieldedTransferContract { int64 value_balance = 6; bytes transparent_to_address = 7; // transparent address int64 to_amount = 8; // the amount to transparent to_address + int64 fee = 9; } // end shielded transaction From 3ed7cf8e0d293ff6a916aec64abae8bd0b49b728 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Mon, 29 Apr 2019 16:19:43 +0800 Subject: [PATCH 147/445] rm value_balance in ShieldedTransferContract --- core/Contract.proto | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 6e8888c48..f8b40eff5 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -375,10 +375,9 @@ message ShieldedTransferContract { repeated SpendDescription spend_description = 3; repeated ReceiveDescription receive_description = 4; bytes binding_signature = 5; - int64 value_balance = 6; - bytes transparent_to_address = 7; // transparent address - int64 to_amount = 8; // the amount to transparent to_address - int64 fee = 9; + bytes transparent_to_address = 6; // transparent address + int64 to_amount = 7; // the amount to transparent to_address + int64 fee = 8; } // end shielded transaction From ce5ca4007a8fdf178155a4103dcc1036993e67ae Mon Sep 17 00:00:00 2001 From: ashu Date: Mon, 29 Apr 2019 19:23:49 +0800 Subject: [PATCH 148/445] add receipt result `TransferException` --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 3d3f46dbe..601116f8d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -307,6 +307,7 @@ message Transaction { OUT_OF_TIME = 11; JVM_STACK_OVER_FLOW = 12; UNKNOWN = 13; + TRANSFER_FAILED = 14; } int64 fee = 1; code ret = 2; From aa508660f5e11e88d66f3b7eb39f22d34e2815de Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Sun, 5 May 2019 16:23:30 +0800 Subject: [PATCH 149/445] feat(zen_api): add some msg structs --- api/api.proto | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/api/api.proto b/api/api.proto index 807c11664..947e9e271 100644 --- a/api/api.proto +++ b/api/api.proto @@ -672,6 +672,11 @@ service Wallet { rpc ScanNoteByBlockRangeAndOvk (OvkDecryptParameters) returns (DecryptNotes) { }; + rpc GetExpandedSpendingKey (BytesMessage) returns (ExpandedSpendingKeyMessage) { + } + + rpc GetProofAuthorizingKey (ExpandedSpendingKeyMessage) returns (ProofAuthorizingKeyMessage) { + } }; @@ -1135,3 +1140,42 @@ message PrivateParameters { bytes transparent_to_address = 7; bytes to_amount = 8; } + +message ExpandedSpendingKeyMessage { + bytes ask = 1; + bytes nsk = 2; + bytes ovk = 3; +} + +message ProofAuthorizingKeyMessage { + bytes ak = 1; + bytes nsk = 2; +} + +message FullViewingKeyMessage { + bytes ak = 1; + bytes nk = 2; + bytes ovk = 3; +} + +message IncomingViewingKeyMessage { + bytes ivk = 1; +} + +message DiversifierMessage { + bytes d = 1; +} + +message TransmissionKeyMessage { + bytes pkD = 1; +} + +message IncomingViewingKeyDiversifierMessage { + IncomingViewingKeyMessage ivk =1; + DiversifierMessage d = 2; +} + +message SaplingPaymentAddressMessage { + DiversifierMessage d = 1; + bytes pkD =2; +} \ No newline at end of file From 0b7ef19e84a9305496236eec7c8cc429f325e5f0 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 6 May 2019 10:57:22 +0800 Subject: [PATCH 150/445] feat(zen_api): add api definition --- api/api.proto | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/api/api.proto b/api/api.proto index 947e9e271..db98a2384 100644 --- a/api/api.proto +++ b/api/api.proto @@ -677,8 +677,19 @@ service Wallet { rpc GetProofAuthorizingKey (ExpandedSpendingKeyMessage) returns (ProofAuthorizingKeyMessage) { } -}; + rpc GetFullViewingKey (ProofAuthorizingKeyMessage) returns (FullViewingKeyMessage) { + } + + rpc GetIncomingViewingKey (FullViewingKeyMessage) returns (IncomingViewingKeyMessage) { + } + + rpc GetDiversifier (EmptyMessage) returns (DiversifierMessage) { + } + + rpc GetSaplingPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (SaplingPaymentAddressMessage) { + } +}; service WalletSolidity { @@ -1166,10 +1177,6 @@ message DiversifierMessage { bytes d = 1; } -message TransmissionKeyMessage { - bytes pkD = 1; -} - message IncomingViewingKeyDiversifierMessage { IncomingViewingKeyMessage ivk =1; DiversifierMessage d = 2; From c03b25f09a084b94380b3f4a817f928661f7a1e3 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 6 May 2019 15:36:21 +0800 Subject: [PATCH 151/445] feat(gen_api): update proto --- api/api.proto | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/api/api.proto b/api/api.proto index db98a2384..f06c3d6db 100644 --- a/api/api.proto +++ b/api/api.proto @@ -675,13 +675,13 @@ service Wallet { rpc GetExpandedSpendingKey (BytesMessage) returns (ExpandedSpendingKeyMessage) { } - rpc GetProofAuthorizingKey (ExpandedSpendingKeyMessage) returns (ProofAuthorizingKeyMessage) { + rpc GetAkFromAsk (BytesMessage) returns (BytesMessage) { } - rpc GetFullViewingKey (ProofAuthorizingKeyMessage) returns (FullViewingKeyMessage) { + rpc GetNkFromNsk (BytesMessage) returns (BytesMessage) { } - rpc GetIncomingViewingKey (FullViewingKeyMessage) returns (IncomingViewingKeyMessage) { + rpc GetIncomingViewingKey (ViewingKeyMessage) returns (IncomingViewingKeyMessage) { } rpc GetDiversifier (EmptyMessage) returns (DiversifierMessage) { @@ -1163,10 +1163,9 @@ message ProofAuthorizingKeyMessage { bytes nsk = 2; } -message FullViewingKeyMessage { +message ViewingKeyMessage { bytes ak = 1; bytes nk = 2; - bytes ovk = 3; } message IncomingViewingKeyMessage { From e82e45fd398c97dd8ab0885c3362c73ac4344b8f Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 6 May 2019 17:28:05 +0800 Subject: [PATCH 152/445] feat(gen_api): add four zen api: sk, esk, ak, nk --- api/api.proto | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/api/api.proto b/api/api.proto index f06c3d6db..a666ae1d3 100644 --- a/api/api.proto +++ b/api/api.proto @@ -672,6 +672,9 @@ service Wallet { rpc ScanNoteByBlockRangeAndOvk (OvkDecryptParameters) returns (DecryptNotes) { }; + rpc GetSpendingKey (EmptyMessage) returns (BytesMessage) { + } + rpc GetExpandedSpendingKey (BytesMessage) returns (ExpandedSpendingKeyMessage) { } @@ -1158,11 +1161,6 @@ message ExpandedSpendingKeyMessage { bytes ovk = 3; } -message ProofAuthorizingKeyMessage { - bytes ak = 1; - bytes nsk = 2; -} - message ViewingKeyMessage { bytes ak = 1; bytes nk = 2; From 585e03ef3454e55e4b35532bf7d4288a8f578bad Mon Sep 17 00:00:00 2001 From: renchenchang Date: Tue, 7 May 2019 11:24:23 +0800 Subject: [PATCH 153/445] add fee in PrivateParameters --- api/api.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index a666ae1d3..331f56672 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1153,6 +1153,7 @@ message PrivateParameters { repeated ReceiveNote shieldedReceives = 6; bytes transparent_to_address = 7; bytes to_amount = 8; + int64 fee = 9; } message ExpandedSpendingKeyMessage { @@ -1175,11 +1176,11 @@ message DiversifierMessage { } message IncomingViewingKeyDiversifierMessage { - IncomingViewingKeyMessage ivk =1; + IncomingViewingKeyMessage ivk = 1; DiversifierMessage d = 2; } message SaplingPaymentAddressMessage { DiversifierMessage d = 1; - bytes pkD =2; + bytes pkD = 2; } \ No newline at end of file From 1ce5c318fc8acb47be9bbc11e07b5f473c089e82 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Tue, 7 May 2019 17:56:06 +0800 Subject: [PATCH 154/445] revert deferred transaction --- api/api.proto | 43 ------------------------------------------- core/Tron.proto | 6 ------ 2 files changed, 49 deletions(-) diff --git a/api/api.proto b/api/api.proto index e0d8cbf68..4de5fb56f 100644 --- a/api/api.proto +++ b/api/api.proto @@ -92,9 +92,6 @@ service Wallet { }; }; - rpc CreateCancelDeferredTransactionContract (CancelDeferredTransactionContract) returns (TransactionExtention) { - }; - //modify the consume_user_resource_percent rpc UpdateSetting (UpdateSettingContract) returns (TransactionExtention) { }; @@ -398,16 +395,6 @@ service Wallet { }; } - rpc GetDeferredTransactionById (BytesMessage) returns (DeferredTransaction) { - option (google.api.http) = { - post: "/wallet/getdeferredtransactionbyid" - body: "*" - additional_bindings { - get: "/wallet/getdeferredtransactionbyid" - } - }; - } - rpc DeployContract (CreateSmartContract) returns (TransactionExtention) { } @@ -617,16 +604,6 @@ service Wallet { }; } - rpc GetDeferredTransactionInfoById (BytesMessage) returns (TransactionInfo) { - option (google.api.http) = { - post: "/wallet/getdeferredtransactioninfobyid" - body: "*" - additional_bindings { - get: "/wallet/getdeferredtransactioninfobyid" - } - }; - } - rpc AccountPermissionUpdate (AccountPermissionUpdateContract) returns (TransactionExtention) { option (google.api.http) = { post: "/wallet/accountpermissionupdate" @@ -764,16 +741,6 @@ service WalletSolidity { }; } - rpc GetDeferredTransactionById (BytesMessage) returns (DeferredTransaction) { - option (google.api.http) = { - post: "/walletsolidity/getdeferredtransactionbyid" - body: "*" - additional_bindings { - get: "/walletsolidity/getdeferredtransactionbyid" - } - }; - } - rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { option (google.api.http) = { post: "/walletsolidity/gettransactioninfobyid" @@ -784,16 +751,6 @@ service WalletSolidity { }; } - rpc GetDeferredTransactionInfoById (BytesMessage) returns (TransactionInfo) { - option (google.api.http) = { - post: "/walletsolidity/getdeferredtransactioninfobyid" - body: "*" - additional_bindings { - get: "/walletsolidity/getdeferredtransactioninfobyid" - } - }; - } - //Warning: do not invoke this interface provided by others. rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { option (google.api.http) = { diff --git a/core/Tron.proto b/core/Tron.proto index 601116f8d..333dba506 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -236,11 +236,6 @@ message ResourceReceipt { Transaction.Result.contractResult result = 7; } -message DeferredStage { - int64 delaySeconds = 1; - int32 stage = 2; -} - message Transaction { message Contract { enum ContractType { @@ -336,7 +331,6 @@ message Transaction { bytes scripts = 12; int64 timestamp = 14; int64 fee_limit = 18; - DeferredStage deferredStage = 19; } raw raw_data = 1; From 32ceb81d52b43ec5dac7b61c7759b72fab3f4209 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Tue, 7 May 2019 19:42:09 +0800 Subject: [PATCH 155/445] revert deferred transaction --- api/api.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 4de5fb56f..50e7f0950 100644 --- a/api/api.proto +++ b/api/api.proto @@ -823,7 +823,6 @@ message Return { SERVER_BUSY = 9; NO_CONNECTION = 10; NOT_ENOUGH_EFFECTIVE_CONNECTION = 11; - DEFERRED_SECONDS_ILLEGAL_ERROR = 12; OTHER_ERROR = 20; } From f5bf93802e14a7782eb474dbdda9ed8689610734 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 8 May 2019 17:00:36 +0800 Subject: [PATCH 156/445] feat(createshieldedtransaction): add createshieldedtransaction api --- api/api.proto | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/api/api.proto b/api/api.proto index 331f56672..ef4212c21 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1129,7 +1129,7 @@ message Note { int64 value = 1; bytes d = 2; bytes pkD = 3; - bytes rcm = 4; + bytes rcm = 4; // random 32 } message SpendNote { @@ -1140,20 +1140,19 @@ message SpendNote { } message ReceiveNote { - bytes esk = 1; // for Encryption - Note note = 2; + Note note = 1; } message PrivateParameters { bytes from_address = 1; - bytes ak = 2; + bytes ask = 2; bytes nsk = 3; - int64 from_amount = 4; - repeated SpendNote shieldedSpends = 5; - repeated ReceiveNote shieldedReceives = 6; - bytes transparent_to_address = 7; - bytes to_amount = 8; - int64 fee = 9; + bytes ovk = 4; + int64 from_amount = 5; + repeated SpendNote shieldedSpends = 6; + repeated ReceiveNote shieldedReceives = 7; + bytes transparent_to_address = 8; + int64 to_amount = 9; } message ExpandedSpendingKeyMessage { From 78d80b09035162729eba51395c6f8135050c323e Mon Sep 17 00:00:00 2001 From: wubin1 Date: Wed, 8 May 2019 18:03:40 +0800 Subject: [PATCH 157/445] remove cancel deferred transaction --- core/Contract.proto | 5 ----- core/Tron.proto | 12 ------------ 2 files changed, 17 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index a8c9836bd..e9a662390 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -47,11 +47,6 @@ message TransferContract { int64 amount = 3; } -message CancelDeferredTransactionContract { - bytes transactionId = 1; - bytes ownerAddress = 2; -} - message TransferAssetContract { bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. bytes owner_address = 2; diff --git a/core/Tron.proto b/core/Tron.proto index 1f3c4b429..123a71b47 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -272,7 +272,6 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; - CancelDeferredTransactionContract = 47; ClearABIContract = 48; } ContractType type = 1; @@ -370,17 +369,6 @@ message TransactionInfo { int64 exchange_id = 21; } -message DeferredTransaction { - bytes transactionId = 1; - int64 publishTime = 2; - int64 delaySeconds = 3; - int64 delayUntil = 4; - int64 expiration = 5; - bytes senderAddress = 6; - bytes receiverAddress = 7; - Transaction transaction = 8; -} - message Transactions { repeated Transaction transactions = 1; } From 0e9458de96539b8d3590d68ae40a7d5aeeb2a011 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Thu, 9 May 2019 15:25:42 +0800 Subject: [PATCH 158/445] add two column in DecryptNotes proto --- api/api.proto | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index ef4212c21..dd0261f75 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1122,7 +1122,12 @@ message OvkDecryptParameters { } message DecryptNotes { - repeated Note notes = 1; + message NoteTx{ + Note note = 1; + bytes txid = 2; //transaction id = sha256(transaction.rowdata) + int32 index = 3; //the index of note in receive + } + repeated NoteTx noteTxs = 1; } message Note { @@ -1182,4 +1187,4 @@ message IncomingViewingKeyDiversifierMessage { message SaplingPaymentAddressMessage { DiversifierMessage d = 1; bytes pkD = 2; -} \ No newline at end of file +} From ce185a1f3b8418d7b716ac7c64e7e821e9a6268d Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 9 May 2019 16:06:42 +0800 Subject: [PATCH 159/445] feat(zen_api): update createshieldedtransaction, add api for rcm --- api/api.proto | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index ef4212c21..7635b57d7 100644 --- a/api/api.proto +++ b/api/api.proto @@ -692,6 +692,9 @@ service Wallet { rpc GetSaplingPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (SaplingPaymentAddressMessage) { } + + rpc GetRcm (EmptyMessage) returns (BytesMessage) { + } }; service WalletSolidity { @@ -1144,7 +1147,7 @@ message ReceiveNote { } message PrivateParameters { - bytes from_address = 1; + bytes transparent_from_address = 1; bytes ask = 2; bytes nsk = 3; bytes ovk = 4; From 12bbabba510db63d84aa29d6ce1d0a0a4aca25bb Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Thu, 9 May 2019 16:17:13 +0800 Subject: [PATCH 160/445] modify two function name --- api/api.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index dd0261f75..bcacac114 100644 --- a/api/api.proto +++ b/api/api.proto @@ -666,10 +666,10 @@ service Wallet { // end for shiededTransaction //protocol p47 - rpc ScanNoteByBlockRangeAndIvk (IvkDecryptParameters) returns (DecryptNotes) { + rpc ScanNoteByIvk (IvkDecryptParameters) returns (DecryptNotes) { }; - rpc ScanNoteByBlockRangeAndOvk (OvkDecryptParameters) returns (DecryptNotes) { + rpc ScanNoteByOvk (OvkDecryptParameters) returns (DecryptNotes) { }; rpc GetSpendingKey (EmptyMessage) returns (BytesMessage) { From 5dcfdb9b0424a52cdfafb3aebb998aea6d69443e Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 9 May 2019 17:37:12 +0800 Subject: [PATCH 161/445] add path into IncrementalMerkleVoucherInfo --- core/Contract.proto | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index f8b40eff5..21c40e795 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -113,8 +113,10 @@ message IncrementalMerkleVoucher { message IncrementalMerkleVoucherInfo { IncrementalMerkleVoucher voucher1 = 1; - IncrementalMerkleVoucher voucher2 = 2; - int32 block_num = 3; + bytes path1 = 2; + IncrementalMerkleVoucher voucher2 = 3; + bytes path2 = 4; + int32 block_num = 5; } message ZksnarkV0TransferContract { From 1e70ff524891390713b90f829bda38d3a1213464 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 10 May 2019 13:36:45 +0800 Subject: [PATCH 162/445] change OutputPointInfo proto --- core/Contract.proto | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 21c40e795..40e232629 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -87,9 +87,8 @@ message OutputPoint { } message OutputPointInfo { - OutputPoint out_point_1 = 1; - OutputPoint out_point_2 = 2; - int32 block_num = 3; + repeated OutputPoint out_points = 1; + int32 block_num = 2; } message PedersenHash { From a6cbbc1898445cc1fe8294aab3c3bb52a75ae0fb Mon Sep 17 00:00:00 2001 From: wubinTron <44354524+wubinTron@users.noreply.github.com> Date: Fri, 10 May 2019 13:49:06 +0800 Subject: [PATCH 163/445] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a2811911..f7036a887 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ java-tron, wallet-cli and grpc-gateway -git subtree pull --prefix src/main/protos/ protocol master +git subtree pull --prefix src/main/protos/ protocol develop ## Run the included *.sh files to initialize the dependencies From d8bcdd10d9fc65272b592dd3b16e0a0e34cb582b Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 10 May 2019 14:30:05 +0800 Subject: [PATCH 164/445] fix test conflict --- core/Contract.proto | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 40e232629..2002f0168 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -111,10 +111,8 @@ message IncrementalMerkleVoucher { } message IncrementalMerkleVoucherInfo { - IncrementalMerkleVoucher voucher1 = 1; - bytes path1 = 2; - IncrementalMerkleVoucher voucher2 = 3; - bytes path2 = 4; + repeated IncrementalMerkleVoucher vouchers = 1; + repeated bytes paths = 2; int32 block_num = 5; } From 16c19f73633d14f713ded75723e4a8ac43189da1 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 10 May 2019 19:15:02 +0800 Subject: [PATCH 165/445] feat(zn_api): update createshieldedtransaction process --- api/api.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index e2871326b..f75b1f6b5 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1143,7 +1143,8 @@ message Note { message SpendNote { Note note = 3; bytes alpha = 4; // random number for spend authority signature - bytes anchor = 5; // merkle root +// bytes anchor = 5; // merkle root + IncrementalMerkleVoucher voucher = 5; bytes path = 6; // path for cm from leaf to root in merkle tree } From 1dc8d3bb5d44b49276d226192319394092f1449a Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 13 May 2019 17:15:51 +0800 Subject: [PATCH 166/445] rename class --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index f75b1f6b5..2702ba214 100644 --- a/api/api.proto +++ b/api/api.proto @@ -690,7 +690,7 @@ service Wallet { rpc GetDiversifier (EmptyMessage) returns (DiversifierMessage) { } - rpc GetSaplingPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (SaplingPaymentAddressMessage) { + rpc GetZenPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (SaplingPaymentAddressMessage) { } rpc GetRcm (EmptyMessage) returns (BytesMessage) { From 3059353c8bbcee08d6e1a8f19dce1e78d85e1529 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 15 May 2019 11:00:16 +0800 Subject: [PATCH 167/445] rm useless code && rename class --- api/api.proto | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index 2702ba214..4e83af607 100644 --- a/api/api.proto +++ b/api/api.proto @@ -616,10 +616,7 @@ service Wallet { rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { } - rpc GetMerkleTreeWitness (OutputPoint) returns (IncrementalMerkleVoucher) { - } - - rpc GetMerkleTreeWitnessInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { + rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { } rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { From c1f8b60bb0fc689784cceb00ec8ec58b0540d57d Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 15 May 2019 12:27:35 +0800 Subject: [PATCH 168/445] rm useless code --- core/Contract.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 2002f0168..1b0639311 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -113,7 +113,6 @@ message IncrementalMerkleVoucher { message IncrementalMerkleVoucherInfo { repeated IncrementalMerkleVoucher vouchers = 1; repeated bytes paths = 2; - int32 block_num = 5; } message ZksnarkV0TransferContract { From 8602875b969cae563746e23444f6b0e9893bd31c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 15 May 2019 14:41:30 +0800 Subject: [PATCH 169/445] rm useless code --- api/api.proto | 3 --- core/Contract.proto | 43 ------------------------------------------- 2 files changed, 46 deletions(-) diff --git a/api/api.proto b/api/api.proto index 4e83af607..1c6a542de 100644 --- a/api/api.proto +++ b/api/api.proto @@ -46,9 +46,6 @@ service Wallet { rpc CreateTransaction2 (TransferContract) returns (TransactionExtention) { }; - rpc ZksnarkV0TransferTrx (ZksnarkV0TransferContract) returns (TransactionExtention) { - }; - rpc BroadcastTransaction (Transaction) returns (Return) { option (google.api.http) = { post: "/wallet/broadcasttransaction" diff --git a/core/Contract.proto b/core/Contract.proto index 1b0639311..e64251d44 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -48,29 +48,6 @@ message TransferContract { } -message BN128G1 { - bytes x = 1; - bytes y = 2; -} - -message BN128G2 { - bytes x1 = 1; - bytes x2 = 2; - bytes y1 = 3; - bytes y2 = 4; -} - -message zkv0proof { - BN128G1 a = 1; - BN128G1 a_p = 2; - BN128G2 b = 3; - BN128G1 b_p = 4; - BN128G1 c = 5; - BN128G1 c_p = 6; - BN128G1 k = 7; - BN128G1 h = 8; -} - message AuthenticationPath { repeated bool value = 1; } @@ -115,26 +92,6 @@ message IncrementalMerkleVoucherInfo { repeated bytes paths = 2; } -message ZksnarkV0TransferContract { - bytes owner_address = 1; - bytes to_address = 2; - int64 v_from_pub = 3; - int64 v_to_pub = 4; - bytes rt = 5; - bytes nf1 = 6; - bytes nf2 = 7; - bytes cm1 = 8; - bytes cm2 = 9; - bytes pksig = 10; - bytes randomSeed = 11; - bytes epk = 12; - int64 fee = 13; - bytes h1 = 20; - bytes h2 = 21; - bytes C1 = 22; - bytes C2 = 23; - zkv0proof proof = 30; -} message ShieldAddress { bytes private_address = 1; From 777b09fd068a196acf9bc26d0decb1b7138f6e28 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 16 May 2019 14:48:03 +0800 Subject: [PATCH 170/445] refactor proto --- api/api.proto | 9 +++-- core/Contract.proto | 91 +++++++++++++++++++++++---------------------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/api/api.proto b/api/api.proto index 1c6a542de..a9bd15c84 100644 --- a/api/api.proto +++ b/api/api.proto @@ -613,9 +613,6 @@ service Wallet { rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { } - rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { - } - rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { } @@ -657,7 +654,9 @@ service Wallet { // for shiededTransaction rpc CreateShieldedTransaction (PrivateParameters) returns (TransactionExtention) { }; - // end for shiededTransaction + + rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { + } //protocol p47 rpc ScanNoteByIvk (IvkDecryptParameters) returns (DecryptNotes) { @@ -689,6 +688,8 @@ service Wallet { rpc GetRcm (EmptyMessage) returns (BytesMessage) { } + + // end for shiededTransaction }; service WalletSolidity { diff --git a/core/Contract.proto b/core/Contract.proto index e64251d44..44858915b 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -48,51 +48,6 @@ message TransferContract { } -message AuthenticationPath { - repeated bool value = 1; -} - -message MerklePath { - repeated AuthenticationPath authentication_paths = 1; - repeated bool index = 2; - bytes rt = 3; -} - -message OutputPoint { - bytes hash = 1; - int32 index = 2; -} - -message OutputPointInfo { - repeated OutputPoint out_points = 1; - int32 block_num = 2; -} - -message PedersenHash { - bytes content = 1; -} - -message IncrementalMerkleTree { - PedersenHash left = 1; - PedersenHash right = 2; - repeated PedersenHash parents = 3; -} - -message IncrementalMerkleVoucher { - IncrementalMerkleTree tree = 1; - repeated PedersenHash filled = 2; - IncrementalMerkleTree cursor = 3; - int64 cursor_depth = 4; - bytes rt = 5; - OutputPoint output_point = 10; -} - -message IncrementalMerkleVoucherInfo { - repeated IncrementalMerkleVoucher vouchers = 1; - repeated bytes paths = 2; -} - - message ShieldAddress { bytes private_address = 1; bytes public_address = 2; @@ -302,6 +257,52 @@ message AccountPermissionUpdateContract { } // for shielded transaction + +message AuthenticationPath { + repeated bool value = 1; +} + +message MerklePath { + repeated AuthenticationPath authentication_paths = 1; + repeated bool index = 2; + bytes rt = 3; +} + +message OutputPoint { + bytes hash = 1; + int32 index = 2; +} + +message OutputPointInfo { + repeated OutputPoint out_points = 1; + int32 block_num = 2; +} + +message PedersenHash { + bytes content = 1; +} + +message IncrementalMerkleTree { + PedersenHash left = 1; + PedersenHash right = 2; + repeated PedersenHash parents = 3; +} + +message IncrementalMerkleVoucher { + IncrementalMerkleTree tree = 1; + repeated PedersenHash filled = 2; + IncrementalMerkleTree cursor = 3; + int64 cursor_depth = 4; + bytes rt = 5; + OutputPoint output_point = 10; +} + +message IncrementalMerkleVoucherInfo { + repeated IncrementalMerkleVoucher vouchers = 1; + repeated bytes paths = 2; +} + + message GrothProof { bytes values = 1; } From c5f70b55356e4e5bdbef973907a5a24ae325e460 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 16 May 2019 17:50:33 +0800 Subject: [PATCH 171/445] typo: remove unused codes --- api/api.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 1c6a542de..96833103e 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1137,7 +1137,6 @@ message Note { message SpendNote { Note note = 3; bytes alpha = 4; // random number for spend authority signature -// bytes anchor = 5; // merkle root IncrementalMerkleVoucher voucher = 5; bytes path = 6; // path for cm from leaf to root in merkle tree } From cc254d87c747f6b9912b97889b5db3a16f788ec4 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Fri, 17 May 2019 15:02:21 +0800 Subject: [PATCH 172/445] update read me --- core/Tron.proto | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index 123a71b47..44fd76831 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -338,6 +338,41 @@ message Transaction { repeated Result ret = 5; } +message TransactionResult { + enum code { + SUCESS = 0; + FAILED = 1; + } + message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + } + bytes id = 1; + int64 fee = 2; + repeated bytes contractResult = 5; + bytes contract_address = 6; + ResourceReceipt receipt = 7; + repeated Log log = 8; + code result = 9; + bytes resMessage = 10; + + string assetIssueID = 14; + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; + repeated InternalTransaction internal_transactions = 17; + int64 exchange_received_amount = 18; + int64 exchange_inject_another_amount = 19; + int64 exchange_withdraw_another_amount = 20; + int64 exchange_id = 21; +} + +message TransactionResultList { + int64 blockNumber = 1; + int64 blockTimeStamp = 2; + repeated TransactionResult transactionresult = 3; +} + message TransactionInfo { enum code { SUCESS = 0; @@ -634,6 +669,41 @@ message NodeInfo { int64 allowAdaptiveEnergy = 19; } + message TransactionResult { + enum code { + SUCESS = 0; + FAILED = 1; + } + message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + } + bytes id = 1; + int64 fee = 2; + repeated bytes contractResult = 5; + bytes contract_address = 6; + ResourceReceipt receipt = 7; + repeated Log log = 8; + code result = 9; + bytes resMessage = 10; + + string assetIssueID = 14; + int64 withdraw_amount = 15; + int64 unfreeze_amount = 16; + repeated InternalTransaction internal_transactions = 17; + int64 exchange_received_amount = 18; + int64 exchange_inject_another_amount = 19; + int64 exchange_withdraw_another_amount = 20; + int64 exchange_id = 21; + } + + message TransactionResultList { + int64 blockNumber = 1; + int64 blockTimeStamp = 2; + repeated TransactionResult transactionresult = 3; + } + message MachineInfo { int32 threadCount = 1; int32 deadLockThreadCount = 2; From e53b4b0632e40647cfee866d94562f667847a7b4 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 21 May 2019 15:07:27 +0800 Subject: [PATCH 173/445] rm message-GrothProof and format code --- core/Contract.proto | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 44858915b..9da6b53a3 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -302,17 +302,12 @@ message IncrementalMerkleVoucherInfo { repeated bytes paths = 2; } - -message GrothProof { - bytes values = 1; -} - message SpendDescription { bytes value_commitment = 1; bytes anchor = 2; // merkle root bytes nullifier = 3; // used for check double spend bytes rk = 4; // used for check spend authority signature - GrothProof zkproof = 5; + bytes zkproof = 5; bytes spend_authority_signature = 6; } @@ -322,7 +317,7 @@ message ReceiveDescription { bytes epk = 3; // for Encryption bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk bytes c_out = 5; // Encryption for audit, decrypt it with ovk - GrothProof zkproof = 6; + bytes zkproof = 6; } message ShieldedTransferContract { From ce1e91c622307adeb105072b392019bc61dd2b3e Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 21 May 2019 17:18:24 +0800 Subject: [PATCH 174/445] feat(zen_api): add api isspend to check if a note has been spent --- api/api.proto | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/api/api.proto b/api/api.proto index ae23f5c7c..568ffaba8 100644 --- a/api/api.proto +++ b/api/api.proto @@ -689,6 +689,9 @@ service Wallet { rpc GetRcm (EmptyMessage) returns (BytesMessage) { } + rpc IsSpend (NoteParameters) returns (SpendResult) { + } + // end for shiededTransaction }; @@ -1186,3 +1189,15 @@ message SaplingPaymentAddressMessage { DiversifierMessage d = 1; bytes pkD = 2; } + +message NoteParameters { + bytes ak = 1; + bytes nk = 2; + Note note = 3; + IncrementalMerkleVoucher voucher = 4; +} + +message SpendResult { + bool result = 1; + string message = 2; +} \ No newline at end of file From 9fbb817543e6cfa3208c305d8d5d1fefb1a00556 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 23 May 2019 11:10:56 +0800 Subject: [PATCH 175/445] rm sprout api --- api/api.proto | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/api/api.proto b/api/api.proto index 568ffaba8..d075f4c8f 100644 --- a/api/api.proto +++ b/api/api.proto @@ -599,31 +599,30 @@ service Wallet { }; } - rpc GetMerklePath (BytesMessage) returns (MerklePath) { - } - - - rpc GetBestMerkleRoot (EmptyMessage) returns (BytesMessage) { - } - - rpc GetNullifier (BytesMessage) returns (BytesMessage) { - } - - - rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { - } - - rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { - } - - //Get zero-snark blockExtention list - rpc GetZKBlockByLimitNext (BlockLimit) returns (BlockListExtention) { - } - - //Get the specified height block merkleTree - rpc GetMerkleTreeOfBlock (NumberMessage) returns (BlockIncrementalMerkleTree) { - } - + // rpc GetMerklePath (BytesMessage) returns (MerklePath) { + // } + // + // + // rpc GetBestMerkleRoot (EmptyMessage) returns (BytesMessage) { + // } + // + // rpc GetNullifier (BytesMessage) returns (BytesMessage) { + // } + // + // + // rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { + // } + // + // rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { + // } + // + // //Get zero-snark blockExtention list + // rpc GetZKBlockByLimitNext (BlockLimit) returns (BlockListExtention) { + // } + // + // //Get the specified height block merkleTree + // rpc GetMerkleTreeOfBlock (NumberMessage) returns (BlockIncrementalMerkleTree) { + // } rpc AccountPermissionUpdate (AccountPermissionUpdateContract) returns (TransactionExtention) { option (google.api.http) = { @@ -658,7 +657,6 @@ service Wallet { rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { } - //protocol p47 rpc ScanNoteByIvk (IvkDecryptParameters) returns (DecryptNotes) { }; @@ -1123,7 +1121,7 @@ message OvkDecryptParameters { } message DecryptNotes { - message NoteTx{ + message NoteTx { Note note = 1; bytes txid = 2; //transaction id = sha256(transaction.rowdata) int32 index = 3; //the index of note in receive @@ -1135,7 +1133,7 @@ message Note { int64 value = 1; bytes d = 2; bytes pkD = 3; - bytes rcm = 4; // random 32 + bytes rcm = 4; // random 32 } message SpendNote { From 9c2ef37ba8f78f181be28ab4efd39dcbcfc8602c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 24 May 2019 11:15:17 +0800 Subject: [PATCH 176/445] Remove ZksnarkV0TransferContract and optimize code in bandwidthProcessor --- core/Tron.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index a0f1968ce..1e48e8e51 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -272,7 +272,6 @@ message Transaction { ExchangeTransactionContract = 44; UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; - ZksnarkV0TransferContract = 50; ShieldedTransferContract = 51; } ContractType type = 1; From 6a3666f465862e01a1e5dbdc65bb7f89e7cd179e Mon Sep 17 00:00:00 2001 From: olivier Date: Fri, 24 May 2019 18:16:20 +0800 Subject: [PATCH 177/445] remove fee from ShieldedTransferContract --- core/Contract.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Contract.proto b/core/Contract.proto index 9da6b53a3..209f1792a 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -328,7 +328,6 @@ message ShieldedTransferContract { bytes binding_signature = 5; bytes transparent_to_address = 6; // transparent address int64 to_amount = 7; // the amount to transparent to_address - int64 fee = 8; } // end shielded transaction From dd4bbe89bcae6f298094f73500aa20d2717fdf50 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sat, 25 May 2019 12:11:28 +0800 Subject: [PATCH 178/445] add api for spend auth sig --- api/api.proto | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index 7476d4418..fc35ee2e8 100644 --- a/api/api.proto +++ b/api/api.proto @@ -407,7 +407,7 @@ service Wallet { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } - + rpc ClearContractABI (ClearABIContract) returns (TransactionExtention) { } @@ -696,6 +696,16 @@ service Wallet { rpc IsSpend (NoteParameters) returns (SpendResult) { } + rpc CreateShieldedTransactionWithoutSpendAuthSig (PrivateParametersWithoutAsk) returns (TransactionExtention) { + }; + + rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { + }; + + rpc GetNullifier (nfParameters) returns (BytesMessage) { + + } + // end for shiededTransaction }; @@ -1167,6 +1177,30 @@ message PrivateParameters { int64 to_amount = 9; } +message PrivateParametersWithoutAsk { + bytes transparent_from_address = 1; + bytes nsk = 3; + bytes ovk = 4; + int64 from_amount = 5; + repeated SpendNote shieldedSpends = 6; + repeated ReceiveNote shieldedReceives = 7; + bytes transparent_to_address = 8; + int64 to_amount = 9; +} + +message SpendAuthSigParameters { + bytes ask = 1; + bytes tx_hash = 2; + bytes alpha = 3; +} + +message nfParameters { + Note note = 1; + IncrementalMerkleVoucher voucher = 2; + bytes ak = 3; + bytes nk = 4; +} + message ExpandedSpendingKeyMessage { bytes ask = 1; bytes nsk = 2; From be55dd5c06e97f8030331a2b2d01dfaf7ddc433d Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sat, 25 May 2019 12:18:41 +0800 Subject: [PATCH 179/445] api getShieldTransactionHash --- api/api.proto | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index fc35ee2e8..246d4b92e 100644 --- a/api/api.proto +++ b/api/api.proto @@ -699,13 +699,16 @@ service Wallet { rpc CreateShieldedTransactionWithoutSpendAuthSig (PrivateParametersWithoutAsk) returns (TransactionExtention) { }; + rpc getShieldTransactionHash (TransactionExtention) returns (BytesMessage) { + + } + rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { }; rpc GetNullifier (nfParameters) returns (BytesMessage) { } - // end for shiededTransaction }; From 60d1f67deb0566df0749b91dfec03795374aad0d Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sat, 25 May 2019 12:19:22 +0800 Subject: [PATCH 180/445] api getShieldTransactionHash --- api/api.proto | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 246d4b92e..b427d0dfc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -700,8 +700,7 @@ service Wallet { }; rpc getShieldTransactionHash (TransactionExtention) returns (BytesMessage) { - - } + }; rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { }; From 1d3eb2355e9205cb4ef85724db02c383c9de5850 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sat, 25 May 2019 12:19:50 +0800 Subject: [PATCH 181/445] api getShieldTransactionHash --- api/api.proto | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index b427d0dfc..d9479bdbc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -706,8 +706,7 @@ service Wallet { }; rpc GetNullifier (nfParameters) returns (BytesMessage) { - - } + }; // end for shiededTransaction }; From 3d51d2fb6e34d4ff946f5c2ce9532ef97683e0d3 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sat, 25 May 2019 12:20:17 +0800 Subject: [PATCH 182/445] api getShieldTransactionHash --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index d9479bdbc..10b1a5649 100644 --- a/api/api.proto +++ b/api/api.proto @@ -699,7 +699,7 @@ service Wallet { rpc CreateShieldedTransactionWithoutSpendAuthSig (PrivateParametersWithoutAsk) returns (TransactionExtention) { }; - rpc getShieldTransactionHash (TransactionExtention) returns (BytesMessage) { + rpc GetShieldTransactionHash (TransactionExtention) returns (BytesMessage) { }; rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { From cb00cc3c9af0a20a299d4dd3db1736504ec66f55 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Sat, 25 May 2019 12:20:57 +0800 Subject: [PATCH 183/445] api getShieldTransactionHash --- api/api.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 10b1a5649..1210082c7 100644 --- a/api/api.proto +++ b/api/api.proto @@ -705,7 +705,7 @@ service Wallet { rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { }; - rpc GetNullifier (nfParameters) returns (BytesMessage) { + rpc GetNullifier (NfParameters) returns (BytesMessage) { }; // end for shiededTransaction }; @@ -1195,7 +1195,7 @@ message SpendAuthSigParameters { bytes alpha = 3; } -message nfParameters { +message NfParameters { Note note = 1; IncrementalMerkleVoucher voucher = 2; bytes ak = 3; From 593acacce072da472be59fa21610742c96c7c22f Mon Sep 17 00:00:00 2001 From: renchenchang Date: Mon, 27 May 2019 10:55:27 +0800 Subject: [PATCH 184/445] rm zk-sign in transaction --- core/Tron.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 1e91defbc..37c0b2557 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -336,7 +336,6 @@ message Transaction { raw raw_data = 1; // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; - repeated bytes signature_zk = 3; repeated Result ret = 5; } From 9b0898c3d3e187c9f643759b6519eb5b42233130 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Mon, 27 May 2019 13:02:23 +0800 Subject: [PATCH 185/445] add two interface to grpc and http --- api/api.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 1210082c7..269c48371 100644 --- a/api/api.proto +++ b/api/api.proto @@ -705,7 +705,7 @@ service Wallet { rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { }; - rpc GetNullifier (NfParameters) returns (BytesMessage) { + rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; // end for shiededTransaction }; @@ -1241,4 +1241,4 @@ message NoteParameters { message SpendResult { bool result = 1; string message = 2; -} \ No newline at end of file +} From d5e15cf75c4ba2082060ae985b66c04fb1e884e1 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 27 May 2019 14:32:05 +0800 Subject: [PATCH 186/445] typo: add sign check when build zen_transaction --- api/api.proto | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api/api.proto b/api/api.proto index 1210082c7..073012f2a 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1180,13 +1180,13 @@ message PrivateParameters { message PrivateParametersWithoutAsk { bytes transparent_from_address = 1; - bytes nsk = 3; - bytes ovk = 4; - int64 from_amount = 5; - repeated SpendNote shieldedSpends = 6; - repeated ReceiveNote shieldedReceives = 7; - bytes transparent_to_address = 8; - int64 to_amount = 9; + bytes nsk = 2; + bytes ovk = 3; + int64 from_amount = 4; + repeated SpendNote shieldedSpends = 5; + repeated ReceiveNote shieldedReceives = 6; + bytes transparent_to_address = 7; + int64 to_amount = 8; } message SpendAuthSigParameters { From 1088234ca2158e3906627a7c11b957dca8dfd1b7 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 27 May 2019 18:09:31 +0800 Subject: [PATCH 187/445] feat: add api createshieldedtransactionwithoutspendauthsig --- api/api.proto | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/api/api.proto b/api/api.proto index 42e086139..f5bc69c56 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1180,13 +1180,14 @@ message PrivateParameters { message PrivateParametersWithoutAsk { bytes transparent_from_address = 1; - bytes nsk = 2; - bytes ovk = 3; - int64 from_amount = 4; - repeated SpendNote shieldedSpends = 5; - repeated ReceiveNote shieldedReceives = 6; - bytes transparent_to_address = 7; - int64 to_amount = 8; + bytes ak = 2; + bytes nsk = 3; + bytes ovk = 4; + int64 from_amount = 5; + repeated SpendNote shieldedSpends = 6; + repeated ReceiveNote shieldedReceives = 7; + bytes transparent_to_address = 8; + int64 to_amount = 9; } message SpendAuthSigParameters { From ccd5848f3dc0e962b2b38fc41e0d7d7360d1c836 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 28 May 2019 17:15:48 +0800 Subject: [PATCH 188/445] modify interface GetShieldTransactionHash --- api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index f5bc69c56..3b8b78356 100644 --- a/api/api.proto +++ b/api/api.proto @@ -699,7 +699,7 @@ service Wallet { rpc CreateShieldedTransactionWithoutSpendAuthSig (PrivateParametersWithoutAsk) returns (TransactionExtention) { }; - rpc GetShieldTransactionHash (TransactionExtention) returns (BytesMessage) { + rpc GetShieldTransactionHash (Transaction) returns (BytesMessage) { }; rpc CreateSpendAuthSig (SpendAuthSigParameters) returns (BytesMessage) { From 94ca02a477b4499bfd0677e804223944de0a2317 Mon Sep 17 00:00:00 2001 From: olivier Date: Tue, 28 May 2019 17:39:46 +0800 Subject: [PATCH 189/445] add zen transfer actuator for TRC10 token shielded transfer --- core/Contract.proto | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/Contract.proto b/core/Contract.proto index 1fb59291e..961dcd2c5 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -334,5 +334,15 @@ message ShieldedTransferContract { bytes transparent_to_address = 6; // transparent address int64 to_amount = 7; // the amount to transparent to_address } + +message ZenTransferContract { + bytes transparent_from_address = 1; // transparent address + int64 from_amount = 2; + repeated SpendDescription spend_description = 3; + repeated ReceiveDescription receive_description = 4; + bytes binding_signature = 5; + bytes transparent_to_address = 6; // transparent address + int64 to_amount = 7; // the amount to transparent to_address +} // end shielded transaction From f08aa0cf86eec4803b401b2ec4e2f6ae400eecc5 Mon Sep 17 00:00:00 2001 From: ashu Date: Tue, 28 May 2019 18:36:58 +0800 Subject: [PATCH 190/445] create2 new storage-key --- core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Tron.proto b/core/Tron.proto index 123a71b47..5f6f4167a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -543,6 +543,7 @@ message SmartContract { string name = 7; int64 origin_energy_limit = 8; bytes code_hash = 9; + bytes trx_hash = 10; } message InternalTransaction { From c415fabc89486a38ef95fda1cb1ead9a66157fac Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 30 May 2019 11:03:34 +0800 Subject: [PATCH 191/445] feat: update note proto --- api/api.proto | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/api.proto b/api/api.proto index 3b8b78356..893aa7b5a 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1150,9 +1150,9 @@ message DecryptNotes { message Note { int64 value = 1; - bytes d = 2; - bytes pkD = 3; - bytes rcm = 4; // random 32 + string payment_address = 2; + bytes rcm = 3; // random 32 + bytes memo = 4; } message SpendNote { @@ -1230,6 +1230,7 @@ message IncomingViewingKeyDiversifierMessage { message SaplingPaymentAddressMessage { DiversifierMessage d = 1; bytes pkD = 2; + string payment_address = 3; } message NoteParameters { From 9f374651a4d4aa25b149ee590e32c5523efee9fb Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 30 May 2019 11:44:24 +0800 Subject: [PATCH 192/445] rafactor note in proto --- api/api.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index 893aa7b5a..738566860 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1172,8 +1172,8 @@ message PrivateParameters { bytes nsk = 3; bytes ovk = 4; int64 from_amount = 5; - repeated SpendNote shieldedSpends = 6; - repeated ReceiveNote shieldedReceives = 7; + repeated SpendNote shielded_spends = 6; + repeated ReceiveNote shielded_receives = 7; bytes transparent_to_address = 8; int64 to_amount = 9; } @@ -1184,8 +1184,8 @@ message PrivateParametersWithoutAsk { bytes nsk = 3; bytes ovk = 4; int64 from_amount = 5; - repeated SpendNote shieldedSpends = 6; - repeated ReceiveNote shieldedReceives = 7; + repeated SpendNote shielded_spends = 6; + repeated ReceiveNote shielded_receives = 7; bytes transparent_to_address = 8; int64 to_amount = 9; } From 5a091e04471fdf31f51d6326c0868b8e290b566a Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 30 May 2019 12:25:28 +0800 Subject: [PATCH 193/445] refactor function name --- api/api.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api.proto b/api/api.proto index 738566860..ee4763e6d 100644 --- a/api/api.proto +++ b/api/api.proto @@ -687,7 +687,7 @@ service Wallet { rpc GetDiversifier (EmptyMessage) returns (DiversifierMessage) { } - rpc GetZenPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (SaplingPaymentAddressMessage) { + rpc GetZenPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (PaymentAddressMessage) { } rpc GetRcm (EmptyMessage) returns (BytesMessage) { @@ -1227,7 +1227,7 @@ message IncomingViewingKeyDiversifierMessage { DiversifierMessage d = 2; } -message SaplingPaymentAddressMessage { +message PaymentAddressMessage { DiversifierMessage d = 1; bytes pkD = 2; string payment_address = 3; From 0020b5c32418b947bc1e0291ef08f74fb7f5b15e Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 30 May 2019 20:14:56 +0800 Subject: [PATCH 194/445] add zksnarkclient --- api/zksnark.proto | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 api/zksnark.proto diff --git a/api/zksnark.proto b/api/zksnark.proto new file mode 100644 index 000000000..66538d6ad --- /dev/null +++ b/api/zksnark.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package protocol; + +import "core/Tron.proto"; +import "core/Contract.proto"; + +option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file +option java_outer_classname = "ZksnarkGrpcAPI"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/api"; + +service TronZksnark { + rpc CheckZksnarkProof (ZksnarkRequest) returns (ZksnarkResponse) { + } +}; + +message ZksnarkRequest { + Transaction transaction = 1; + bytes sighash = 2; + int64 valueBalance = 3; + string txId = 4; +} + +message ZksnarkResponse { + enum Code { + SUCCESS = 0; + FAILED = 1; + } + + Code code = 1; +} + + + + From aa80e6d90290491b7dce2820d1750b93de69043f Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 3 Jun 2019 14:36:52 +0800 Subject: [PATCH 195/445] feat: add 4 query api to solidity node --- api/api.proto | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/api.proto b/api/api.proto index ee4763e6d..88fb8b536 100644 --- a/api/api.proto +++ b/api/api.proto @@ -839,6 +839,18 @@ service WalletSolidity { } }; } + + rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { + } + + rpc ScanNoteByIvk (IvkDecryptParameters) returns (DecryptNotes) { + } + + rpc ScanNoteByOvk (OvkDecryptParameters) returns (DecryptNotes) { + } + + rpc IsSpend (NoteParameters) returns (SpendResult) { + } }; service WalletExtension { From f08d82625e9111e6c515d58758f49d2014bd8ba3 Mon Sep 17 00:00:00 2001 From: wubin1 Date: Mon, 3 Jun 2019 17:07:13 +0800 Subject: [PATCH 196/445] optimize transaction history database --- core/Tron.proto | 74 ++++--------------------------------------------- 1 file changed, 5 insertions(+), 69 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 44fd76831..601aab42e 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -338,7 +338,7 @@ message Transaction { repeated Result ret = 5; } -message TransactionResult { +message TransactionInfo { enum code { SUCESS = 0; FAILED = 1; @@ -350,6 +350,8 @@ message TransactionResult { } bytes id = 1; int64 fee = 2; + int64 blockNumber = 3; + int64 blockTimeStamp = 4; repeated bytes contractResult = 5; bytes contract_address = 6; ResourceReceipt receipt = 7; @@ -367,41 +369,10 @@ message TransactionResult { int64 exchange_id = 21; } -message TransactionResultList { +message TransactionRet { int64 blockNumber = 1; int64 blockTimeStamp = 2; - repeated TransactionResult transactionresult = 3; -} - -message TransactionInfo { - enum code { - SUCESS = 0; - FAILED = 1; - } - message Log { - bytes address = 1; - repeated bytes topics = 2; - bytes data = 3; - } - bytes id = 1; - int64 fee = 2; - int64 blockNumber = 3; - int64 blockTimeStamp = 4; - repeated bytes contractResult = 5; - bytes contract_address = 6; - ResourceReceipt receipt = 7; - repeated Log log = 8; - code result = 9; - bytes resMessage = 10; - - string assetIssueID = 14; - int64 withdraw_amount = 15; - int64 unfreeze_amount = 16; - repeated InternalTransaction internal_transactions = 17; - int64 exchange_received_amount = 18; - int64 exchange_inject_another_amount = 19; - int64 exchange_withdraw_another_amount = 20; - int64 exchange_id = 21; + repeated TransactionInfo transactioninfo = 3; } message Transactions { @@ -669,41 +640,6 @@ message NodeInfo { int64 allowAdaptiveEnergy = 19; } - message TransactionResult { - enum code { - SUCESS = 0; - FAILED = 1; - } - message Log { - bytes address = 1; - repeated bytes topics = 2; - bytes data = 3; - } - bytes id = 1; - int64 fee = 2; - repeated bytes contractResult = 5; - bytes contract_address = 6; - ResourceReceipt receipt = 7; - repeated Log log = 8; - code result = 9; - bytes resMessage = 10; - - string assetIssueID = 14; - int64 withdraw_amount = 15; - int64 unfreeze_amount = 16; - repeated InternalTransaction internal_transactions = 17; - int64 exchange_received_amount = 18; - int64 exchange_inject_another_amount = 19; - int64 exchange_withdraw_another_amount = 20; - int64 exchange_id = 21; - } - - message TransactionResultList { - int64 blockNumber = 1; - int64 blockTimeStamp = 2; - repeated TransactionResult transactionresult = 3; - } - message MachineInfo { int32 threadCount = 1; int32 deadLockThreadCount = 2; From 6fad36cc24ae57df42d8e8f5ea87480802857c3b Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 13 Jun 2019 19:50:21 +0800 Subject: [PATCH 197/445] rm zenShieldedTransferContract --- core/Contract.proto | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/core/Contract.proto b/core/Contract.proto index 961dcd2c5..bf8023c85 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -334,15 +334,4 @@ message ShieldedTransferContract { bytes transparent_to_address = 6; // transparent address int64 to_amount = 7; // the amount to transparent to_address } - -message ZenTransferContract { - bytes transparent_from_address = 1; // transparent address - int64 from_amount = 2; - repeated SpendDescription spend_description = 3; - repeated ReceiveDescription receive_description = 4; - bytes binding_signature = 5; - bytes transparent_to_address = 6; // transparent address - int64 to_amount = 7; // the amount to transparent to_address -} // end shielded transaction - From c079ec6d22b5a58cef2d415a18e54a14748a28c3 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Thu, 13 Jun 2019 19:56:10 +0800 Subject: [PATCH 198/445] rm useless api --- api/api.proto | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/api/api.proto b/api/api.proto index 88fb8b536..e67908209 100644 --- a/api/api.proto +++ b/api/api.proto @@ -605,31 +605,6 @@ service Wallet { }; } - // rpc GetMerklePath (BytesMessage) returns (MerklePath) { - // } - // - // - // rpc GetBestMerkleRoot (EmptyMessage) returns (BytesMessage) { - // } - // - // rpc GetNullifier (BytesMessage) returns (BytesMessage) { - // } - // - // - // rpc GetMerkleRoot (BytesMessage) returns (BytesMessage) { - // } - // - // rpc GenerateShieldAddress (EmptyMessage) returns (ShieldAddress) { - // } - // - // //Get zero-snark blockExtention list - // rpc GetZKBlockByLimitNext (BlockLimit) returns (BlockListExtention) { - // } - // - // //Get the specified height block merkleTree - // rpc GetMerkleTreeOfBlock (NumberMessage) returns (BlockIncrementalMerkleTree) { - // } - rpc AccountPermissionUpdate (AccountPermissionUpdateContract) returns (TransactionExtention) { option (google.api.http) = { post: "/wallet/accountpermissionupdate" @@ -640,7 +615,6 @@ service Wallet { }; } - rpc AddSign (TransactionSign) returns (TransactionExtention) { } From 0cd602afcc4c14ea74de1bd7ff67805ff45493ce Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 19 Jun 2019 19:12:59 +0800 Subject: [PATCH 199/445] feat: update shielded fee to new param --- core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Tron.proto b/core/Tron.proto index b93905c4f..2be9aa29a 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -315,6 +315,7 @@ message Transaction { int64 exchange_inject_another_amount = 19; int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; + int64 shieldedTransactionFee = 22; } message raw { @@ -368,6 +369,7 @@ message TransactionInfo { int64 exchange_inject_another_amount = 19; int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; + int64 shieldedTransactionFee = 22; } message TransactionRet { From 1d9d2521460df092e88c5f75aed3935f50aaccb7 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 19 Jun 2019 20:44:58 +0800 Subject: [PATCH 200/445] typo: update param with '-' --- core/Tron.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 2be9aa29a..57a634b59 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -315,7 +315,7 @@ message Transaction { int64 exchange_inject_another_amount = 19; int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; - int64 shieldedTransactionFee = 22; + int64 shielded_transaction_fee = 22; } message raw { @@ -369,8 +369,8 @@ message TransactionInfo { int64 exchange_inject_another_amount = 19; int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; - int64 shieldedTransactionFee = 22; -} + int64 shielded_transaction_fee = 22; + message TransactionRet { int64 blockNumber = 1; From bff46ede81459e92e272b2de89f479ddcb1b7093 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 19 Jun 2019 20:50:40 +0800 Subject: [PATCH 201/445] fix: fix typo error --- core/Tron.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Tron.proto b/core/Tron.proto index 57a634b59..a60ef718d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -370,7 +370,7 @@ message TransactionInfo { int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; int64 shielded_transaction_fee = 22; - +} message TransactionRet { int64 blockNumber = 1; From 8a5c127c0c924bd53bf24655d33d87b4adbfb6ac Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 25 Jun 2019 21:10:38 +0800 Subject: [PATCH 202/445] add wallet api scanandmarknotebyivk and modify api IsSpendServlet --- api/api.proto | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/api/api.proto b/api/api.proto index e67908209..167cb69a1 100644 --- a/api/api.proto +++ b/api/api.proto @@ -640,6 +640,9 @@ service Wallet { rpc ScanNoteByIvk (IvkDecryptParameters) returns (DecryptNotes) { }; + rpc ScanAndMarkNoteByIvk (IvkDecryptAndMarkParameters) returns (DecryptNotesMarked) { + }; + rpc ScanNoteByOvk (OvkDecryptParameters) returns (DecryptNotes) { }; @@ -1119,6 +1122,14 @@ message IvkDecryptParameters { bytes ivk = 3; } +message IvkDecryptAndMarkParameters { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes ivk = 5; + bytes ak = 3; + bytes nk = 4; +} + message OvkDecryptParameters { int64 start_block_index = 1; int64 end_block_index = 2; @@ -1134,6 +1145,16 @@ message DecryptNotes { repeated NoteTx noteTxs = 1; } +message DecryptNotesMarked { + message NoteTx { + Note note = 1; + bytes txid = 2; //transaction id = sha256(transaction.rowdata) + int32 index = 3; //the index of note in receive + bool is_spend = 4; + } + repeated NoteTx noteTxs = 1; +} + message Note { int64 value = 1; string payment_address = 2; @@ -1223,7 +1244,8 @@ message NoteParameters { bytes ak = 1; bytes nk = 2; Note note = 3; - IncrementalMerkleVoucher voucher = 4; + bytes txid = 4; + int32 index = 5; } message SpendResult { From 18cf4ce0272f8e7b92cd8b96d2051ce6f5aca2a5 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Wed, 26 Jun 2019 12:06:25 +0800 Subject: [PATCH 203/445] add scanAndMarkNoteByIvk to rpc api --- api/api.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/api.proto b/api/api.proto index 167cb69a1..89e8bcf8e 100644 --- a/api/api.proto +++ b/api/api.proto @@ -823,6 +823,9 @@ service WalletSolidity { rpc ScanNoteByIvk (IvkDecryptParameters) returns (DecryptNotes) { } + rpc ScanAndMarkNoteByIvk (IvkDecryptAndMarkParameters) returns (DecryptNotesMarked) { + } + rpc ScanNoteByOvk (OvkDecryptParameters) returns (DecryptNotes) { } From 0f3a7bce35e028b1a8aa5c83e7157f25b89fdfe2 Mon Sep 17 00:00:00 2001 From: Shulinkang Date: Wed, 14 Aug 2019 17:28:10 +0800 Subject: [PATCH 204/445] add call constant in soliditynode (cherry picked from commit 657749bc5584a7567134a50d8d5e37fe7f45f681) --- api/api.proto | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/api.proto b/api/api.proto index 50e7f0950..ff58e4a47 100644 --- a/api/api.proto +++ b/api/api.proto @@ -761,6 +761,10 @@ service WalletSolidity { } }; } + + rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { + } + }; service WalletExtension { From 8fffe4864f1ed9e89d9234b8220e937b8f0d1de1 Mon Sep 17 00:00:00 2001 From: lvs007 Date: Fri, 27 Sep 2019 16:04:23 +0800 Subject: [PATCH 205/445] add the delegate proto --- api/api.proto | 15 +++++++++++++++ core/Contract.proto | 5 +++++ core/Tron.proto | 1 + 3 files changed, 21 insertions(+) diff --git a/api/api.proto b/api/api.proto index f013096a0..fac89a567 100644 --- a/api/api.proto +++ b/api/api.proto @@ -685,6 +685,16 @@ service Wallet { rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; // end for shiededTransaction + + rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { + + }; }; service WalletSolidity { @@ -836,6 +846,11 @@ service WalletSolidity { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } + rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { + }; }; diff --git a/core/Contract.proto b/core/Contract.proto index bf8023c85..60f07fc07 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -335,3 +335,8 @@ message ShieldedTransferContract { int64 to_amount = 7; // the amount to transparent to_address } // end shielded transaction + +message UpdateBrokerageContract { + bytes owner_address = 1; + int32 brokerage = 2; // 1 mean 1% +} diff --git a/core/Tron.proto b/core/Tron.proto index a60ef718d..d70672297 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -273,6 +273,7 @@ message Transaction { UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; ClearABIContract = 48; + UpdateBrokerageContract = 49; ShieldedTransferContract = 51; } ContractType type = 1; From 7c09a6409fc002146c4c8bf9bc532781d054ad6b Mon Sep 17 00:00:00 2001 From: lvs007 Date: Wed, 9 Oct 2019 14:21:18 +0800 Subject: [PATCH 206/445] add the delegation proto --- api/api.proto | 17 +++++++++++++++++ core/Contract.proto | 5 +++++ core/Tron.proto | 1 + 3 files changed, 23 insertions(+) diff --git a/api/api.proto b/api/api.proto index ff58e4a47..1cf2ef5a6 100644 --- a/api/api.proto +++ b/api/api.proto @@ -629,6 +629,17 @@ service Wallet { rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { }; + + rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { + + }; + }; @@ -765,6 +776,12 @@ service WalletSolidity { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } + rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { + }; + }; service WalletExtension { diff --git a/core/Contract.proto b/core/Contract.proto index e9a662390..e468c5bbd 100644 --- a/core/Contract.proto +++ b/core/Contract.proto @@ -253,4 +253,9 @@ message AccountPermissionUpdateContract { Permission owner = 2; //Empty is invalidate Permission witness = 3;//Can be empty repeated Permission actives = 4;//Empty is invalidate +} + +message UpdateBrokerageContract { + bytes owner_address = 1; + int32 brokerage = 2; // 1 mean 1% } \ No newline at end of file diff --git a/core/Tron.proto b/core/Tron.proto index 5f6f4167a..e60d8844d 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -273,6 +273,7 @@ message Transaction { UpdateEnergyLimitContract = 45; AccountPermissionUpdateContract = 46; ClearABIContract = 48; + UpdateBrokerageContract = 49; } ContractType type = 1; google.protobuf.Any parameter = 2; From f617def3279071536dba1199eca55893b57cf10b Mon Sep 17 00:00:00 2001 From: Alpha Date: Wed, 9 Oct 2019 18:21:48 +0800 Subject: [PATCH 207/445] Update .travis.yml --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 34fa71b3d..fe7d25b3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,17 +8,17 @@ sudo: false before_install: - bash install-protobuf.sh - - bash install-googleapis.sh + #- bash install-googleapis.sh # check what has been installed by listing contents of protobuf folder before_script: - ls -R $HOME/protobuf # let's use protobuf -script: - - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto +#script: +# - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto +# - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto +# - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto +# - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto - - ls -l \ No newline at end of file +# - ls -l From 4e1bf7ea28e3f7dc369280897a3c98cfbc4afd26 Mon Sep 17 00:00:00 2001 From: Alpha Date: Wed, 9 Oct 2019 18:34:54 +0800 Subject: [PATCH 208/445] Update .travis.yml --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe7d25b3b..dc583d3e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,13 @@ cache: sudo: false -before_install: - - bash install-protobuf.sh +#before_install: + #- bash install-protobuf.sh #- bash install-googleapis.sh # check what has been installed by listing contents of protobuf folder -before_script: - - ls -R $HOME/protobuf +#before_script: + #- ls -R $HOME/protobuf # let's use protobuf #script: From c3c0bb58cf12aaf5874605bb3ec7a9725d242f37 Mon Sep 17 00:00:00 2001 From: Alpha Date: Wed, 9 Oct 2019 18:37:43 +0800 Subject: [PATCH 209/445] Update .travis.yml --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index dc583d3e1..fe3154e54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,19 +6,19 @@ cache: sudo: false -#before_install: +before_install: #- bash install-protobuf.sh #- bash install-googleapis.sh # check what has been installed by listing contents of protobuf folder -#before_script: +before_script: #- ls -R $HOME/protobuf # let's use protobuf -#script: +script: # - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto # - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto # - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto # - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto -# - ls -l + - ls -l From edcb8504ed065002501163cf2bdd62e67e5e9a69 Mon Sep 17 00:00:00 2001 From: Alpha Date: Wed, 9 Oct 2019 18:41:26 +0800 Subject: [PATCH 210/445] Update .travis.yml --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 34fa71b3d..a69c48871 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,18 +7,18 @@ cache: sudo: false before_install: - - bash install-protobuf.sh - - bash install-googleapis.sh + #- bash install-protobuf.sh + #- bash install-googleapis.sh # check what has been installed by listing contents of protobuf folder before_script: - - ls -R $HOME/protobuf + #- ls -R $HOME/protobuf # let's use protobuf script: - - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto + #- $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto + #- $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto + #- $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto + #- $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto - - ls -l \ No newline at end of file + - ls -l From 937584cda0d99313bbfad3c145d0182ab0191844 Mon Sep 17 00:00:00 2001 From: sun haoyu Date: Sat, 12 Oct 2019 15:21:59 +0800 Subject: [PATCH 211/445] add slack notification --- .travis.yml | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe3154e54..2cc9ce9b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,13 @@ language: ruby - cache: directories: - - $HOME/protobuf - + - "$HOME/protobuf" sudo: false - -before_install: - #- bash install-protobuf.sh - #- bash install-googleapis.sh - -# check what has been installed by listing contents of protobuf folder -before_script: - #- ls -R $HOME/protobuf - -# let's use protobuf +before_install: +before_script: script: -# - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto -# - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto -# - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto -# - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto - - - ls -l +- ls -l +notifications: + slack: + secure: lCDOgsHefxu0Sd7J4N9wyo6Y/dvAentdf+q+R6R+SpJUym+tI7h4lPVwDFl9iQabFahLC6bV4lgfv/US0aqjL4QE/c3ZsWBDo2dtkfCzGmkCiU6mGd/bPWLFP9e28OHEFBzUT8ukc5O3GDJREnzscIqGk7Knv3QYQGsoZw563grzVG9VVfqKhsVgrAjhzDwiCoUNSiND09ZNifGrHr7JBQ2u0wrZRR+E9yOQ1K3pNnfq5wAJjbJ0LiuxRlLZoajR7z8VeP8ytet+u2XQE1+fd089XEa/thYxtt0ZvhnyOYk1vmEldDdVakqMqVaa9MKiyLBNnLby1xdSosrM55ZB3tFmXQo6EMIaNKyO07xMUJ/hK0gEppQhHMDlbOW31YmOshVutp2yN4tRF8D3ot1OcYPXsVeXFG+kMUR0PYM71YiqZy8UVgoslKgRgEehvxjuAhbMYXMG878bznco9L64TzubFcjOx0+owR8H0kCXDNf5Xwm+tlc2onWlUDerEWIAignVOT1iR8KK0U+EnhTfw2BWfDV+BkSBvaeFbmnsFgz00d7qSvzbtCJ9KdDj3f4FUjUuORNv5ykMGCv1MrvAvttAZPTNIc/wOFJ9yRC2NhoUfbNILYg7bYhmqKiicnflln03Bh7Ml2mzldzPaRMm7eUBUwYTFbgC83YusX4fxBg= + if: branch = master From f7892512e6c13a07404623a081f7332a2dc4783e Mon Sep 17 00:00:00 2001 From: sun haoyu Date: Sat, 12 Oct 2019 15:36:37 +0800 Subject: [PATCH 212/445] update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a2811911..e409e0407 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ # The protocol of Tron including api and message. +The protocol is an independent project. You can use it for building other application. + java-tron, wallet-cli and grpc-gateway git subtree pull --prefix src/main/protos/ protocol master From 2aacba542ae5957c9799b2ea51e037903526c6b6 Mon Sep 17 00:00:00 2001 From: Shuyu WANG Date: Thu, 19 Dec 2019 11:37:47 +0800 Subject: [PATCH 213/445] fix: misleading field comment for transaction note --- core/Tron.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index d70672297..a2bc17193 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -325,7 +325,7 @@ message Transaction { bytes ref_block_hash = 4; int64 expiration = 8; repeated authority auths = 9; - // data not used + // transaction note bytes data = 10; //only support size = 1, repeated list here for extension repeated Contract contract = 11; @@ -678,4 +678,4 @@ message NodeInfo { string stackTrace = 7; } } -} \ No newline at end of file +} From fafa4582375bd765d025d4510d501a201707d620 Mon Sep 17 00:00:00 2001 From: zhenping Date: Thu, 19 Dec 2019 19:02:09 +0800 Subject: [PATCH 214/445] add the sm2/sm3 algorithm Signed-off-by: zhenping --- build.gradle | 262 +- .../java/org/tron/common/crypto/ECKey.java | 2500 +++++++++-------- .../org/tron/common/crypto/SignInterface.java | 22 + .../org/tron/common/crypto/SignUtils.java | 54 + .../common/crypto/SignatureInterface.java | 5 + .../java/org/tron/common/crypto/sm2/SM2.java | 1245 ++++++++ .../org/tron/common/crypto/sm2/SM2Signer.java | 323 +++ .../java/org/tron/common/crypto/sm2/SM3.java | 22 + .../org/tron/common/utils/DecodeUtil.java | 39 + src/main/java/org/tron/common/utils/Hash.java | 210 ++ 10 files changed, 3333 insertions(+), 1349 deletions(-) create mode 100644 src/main/java/org/tron/common/crypto/SignInterface.java create mode 100644 src/main/java/org/tron/common/crypto/SignUtils.java create mode 100644 src/main/java/org/tron/common/crypto/SignatureInterface.java create mode 100644 src/main/java/org/tron/common/crypto/sm2/SM2.java create mode 100644 src/main/java/org/tron/common/crypto/sm2/SM2Signer.java create mode 100644 src/main/java/org/tron/common/crypto/sm2/SM3.java create mode 100644 src/main/java/org/tron/common/utils/DecodeUtil.java create mode 100644 src/main/java/org/tron/common/utils/Hash.java diff --git a/build.gradle b/build.gradle index b08079dcb..6d00c8c17 100644 --- a/build.gradle +++ b/build.gradle @@ -1,131 +1,131 @@ -group 'Tron' -version '1.0-SNAPSHOT' - -apply plugin: 'java' -apply plugin: 'com.google.protobuf' -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'application' - -sourceCompatibility = 1.8 -targetCompatibility = JavaVersion.VERSION_1_8 -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' - -compileJava.options*.compilerArgs = [ - "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", - "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", - "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", - "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" -] - -repositories { - mavenLocal() - mavenCentral() -} - -sourceSets { - main { - proto { - srcDir 'src/main/protos' - } - java { - srcDir 'src/main/gen' - srcDir 'src/main/java' - } - } - -} -buildscript { - repositories { - mavenLocal() - maven { - url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" - } - mavenCentral() - } - ext { - projectVersion = '1.3.0-RELEASE' - grpcVersion = '1.6.1' - protobufVersion = '3.3.0' - protobufGradlePluginVersion = '0.8.0' - springCloudConsulVersion = '1.2.1.RELEASE' - } - - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' - classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" - } -} - -task wrapper(type: Wrapper) { - gradleVersion = '3.3' -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - compile group: 'com.beust', name: 'jcommander', version: '1.72' - //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - compile 'com.maxmind.geoip2:geoip2:2.10.0' - - // google grpc - compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' - - compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile "com.madgag.spongycastle:core:1.53.0.0" - compile "com.madgag.spongycastle:prov:1.53.0.0" - compile group: 'com.typesafe', name: 'config', version: '1.3.2' - compile "com.google.code.findbugs:jsr305:3.0.0" - compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" - compile "org.apache.commons:commons-collections4:4.0" - compile "org.apache.commons:commons-lang3:3.4" - compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' - compile 'com.alibaba:fastjson:1.2.47' - - compile group: 'commons-io', name: 'commons-io', version: '2.6' - compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - - compile group: 'org.jline', name: 'jline', version: '3.1.3' -} - -protobuf { - generatedFilesBaseDir = "$projectDir/src/" - protoc { - artifact = "com.google.protobuf:protoc:3.5.1-1" - - } - plugins { - grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' - } - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - outputSubDir = "gen" - } - } - } - all()*.plugins { - grpc { - outputSubDir = "gen" - } - } - } -} - -run { - standardInput = System.in - mainClassName = 'org.tron.walletcli.Client' -} - - -shadowJar { - baseName = 'wallet-cli' - classifier = null - version = null -} +group 'Tron' +version '1.0-SNAPSHOT' + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'application' + +sourceCompatibility = 1.8 +targetCompatibility = JavaVersion.VERSION_1_8 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +compileJava.options*.compilerArgs = [ + "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", + "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", + "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", + "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" +] + +repositories { + mavenLocal() + mavenCentral() +} + +sourceSets { + main { + proto { + srcDir 'src/main/protos' + } + java { + srcDir 'src/main/gen' + srcDir 'src/main/java' + } + } + +} +buildscript { + repositories { + mavenLocal() + maven { + url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" + } + mavenCentral() + } + ext { + projectVersion = '1.3.0-RELEASE' + grpcVersion = '1.6.1' + protobufVersion = '3.3.0' + protobufGradlePluginVersion = '0.8.0' + springCloudConsulVersion = '1.2.1.RELEASE' + } + + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' + classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '3.3' +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'com.beust', name: 'jcommander', version: '1.72' + //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' + compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' + compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + compile 'com.maxmind.geoip2:geoip2:2.10.0' + + // google grpc + compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' + + compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' + compile "com.madgag.spongycastle:core:1.58.0.0" + compile "com.madgag.spongycastle:prov:1.58.0.0" + compile group: 'com.typesafe', name: 'config', version: '1.3.2' + compile "com.google.code.findbugs:jsr305:3.0.0" + compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" + compile "org.apache.commons:commons-collections4:4.0" + compile "org.apache.commons:commons-lang3:3.4" + compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' + compile 'com.alibaba:fastjson:1.2.47' + + compile group: 'commons-io', name: 'commons-io', version: '2.6' + compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' + + compile group: 'org.jline', name: 'jline', version: '3.1.3' +} + +protobuf { + generatedFilesBaseDir = "$projectDir/src/" + protoc { + artifact = "com.google.protobuf:protoc:3.5.1-1" + + } + plugins { + grpc { + artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' + } + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + outputSubDir = "gen" + } + } + } + all()*.plugins { + grpc { + outputSubDir = "gen" + } + } + } +} + +run { + standardInput = System.in + mainClassName = 'org.tron.walletcli.Client' +} + + +shadowJar { + baseName = 'wallet-cli' + classifier = null + version = null +} diff --git a/src/main/java/org/tron/common/crypto/ECKey.java b/src/main/java/org/tron/common/crypto/ECKey.java index bb94dcf8e..2fc758ade 100644 --- a/src/main/java/org/tron/common/crypto/ECKey.java +++ b/src/main/java/org/tron/common/crypto/ECKey.java @@ -1,1218 +1,1282 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.crypto; - -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.asn1.sec.SECNamedCurves; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.crypto.engines.AESFastEngine; -import org.spongycastle.crypto.modes.SICBlockCipher; -import org.spongycastle.crypto.params.*; -import org.spongycastle.crypto.signers.ECDSASigner; -import org.spongycastle.crypto.signers.HMacDSAKCalculator; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.util.BigIntegers; -import org.spongycastle.util.encoders.Base64; -import org.spongycastle.util.encoders.Hex; -import org.tron.common.crypto.jce.*; -import org.tron.common.utils.ByteUtil; - -import javax.annotation.Nullable; -import javax.crypto.KeyAgreement; -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.security.*; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; - -import static org.tron.common.utils.BIUtil.isLessThan; -import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; - -public class ECKey implements Serializable { - - /** - * The parameters of the secp256k1 curve. - */ - public static final ECDomainParameters CURVE; - public static final ECParameterSpec CURVE_SPEC; - /** - * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA - * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - - * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2

See - * https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki #Low_S_values_in_signatures - */ - public static final BigInteger HALF_CURVE_ORDER; - private static final BigInteger SECP256K1N = new BigInteger - ("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); - private static final SecureRandom secureRandom; - private static final long serialVersionUID = -728224901792295832L; - - static { - // All clients must agree on the curve to use by agreement. - X9ECParameters params = SECNamedCurves.getByName("secp256k1"); - CURVE = new ECDomainParameters(params.getCurve(), params.getG(), - params.getN(), params.getH()); - CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), - params.getN(), params.getH()); - HALF_CURVE_ORDER = params.getN().shiftRight(1); - secureRandom = new SecureRandom(); - } - - protected final ECPoint pub; - // The two parts of the key. If "priv" is set, "pub" can always be - // calculated. If "pub" is set but not "priv", we - // can only verify signatures not make them. - // TODO: Redesign this class to use consistent internals and more - // efficient serialization. - private final PrivateKey privKey; - // the Java Cryptographic Architecture provider to use for Signature - // this is set along with the PrivateKey privKey and must be compatible - // this provider will be used when selecting a Signature instance - // https://docs.oracle.com/javase/8/docs/technotes/guides/security - // /SunProviders.html - private final Provider provider; - - // Transient because it's calculated on demand. - transient private byte[] pubKeyHash; - transient private byte[] nodeId; - - /** - * Generates an entirely new keypair.

BouncyCastle will be used as the Java Security Provider - */ - public ECKey() { - this(secureRandom); - } - - /** - * Generate a new keypair using the given Java Security Provider.

All private key operations - * will use the provider. - */ - public ECKey(Provider provider, SecureRandom secureRandom) { - this.provider = provider; - - final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance - (provider, secureRandom); - final KeyPair keyPair = keyPairGen.generateKeyPair(); - - this.privKey = keyPair.getPrivate(); - - final PublicKey pubKey = keyPair.getPublic(); - if (pubKey instanceof BCECPublicKey) { - pub = ((BCECPublicKey) pubKey).getQ(); - } else if (pubKey instanceof ECPublicKey) { - pub = extractPublicKey((ECPublicKey) pubKey); - } else { - throw new AssertionError( - "Expected Provider " + provider.getName() + - " to produce a subtype of ECPublicKey, found " + - pubKey.getClass()); - } - } - - /** - * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle - * will be used as the Java Security Provider - * - * @param secureRandom - - */ - public ECKey(SecureRandom secureRandom) { - this(TronCastleProvider.getInstance(), secureRandom); - } - - /** - * Pair a private key with a public EC point.

All private key operations will use the - * provider. - */ - public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { - this.provider = provider; - - if (privKey == null || isECPrivateKey(privKey)) { - this.privKey = privKey; - } else { - throw new IllegalArgumentException( - "Expected EC private key, given a private key object with" + - " class " + - privKey.getClass().toString() + - " and algorithm " + privKey.getAlgorithm()); - } - - if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); - } else { - this.pub = pub; - } - } - - /** - * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java - * Security Provider - */ - public ECKey(@Nullable BigInteger priv, ECPoint pub) { - this( - TronCastleProvider.getInstance(), - privateKeyFromBigInteger(priv), - pub - ); - } - - /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint - */ - private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { - final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); - final BigInteger xCoord = publicPointW.getAffineX(); - final BigInteger yCoord = publicPointW.getAffineY(); - - return CURVE.getCurve().createPoint(xCoord, yCoord); - } - - /* Test if a generic private key is an EC private key - * - * it is not sufficient to check that privKey is a subtype of ECPrivateKey - * as the SunPKCS11 Provider will return a generic PrivateKey instance - * a fallback that covers this case is to check the key algorithm - */ - private static boolean isECPrivateKey(PrivateKey privKey) { - return privKey instanceof ECPrivateKey || privKey.getAlgorithm() - .equals("EC"); - } - - /* Convert a BigInteger into a PrivateKey object - */ - private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - CURVE_SPEC)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } - } - } - - /** - * Utility for compressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param uncompressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint compressPoint(ECPoint uncompressed) { - return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); - } - - /** - * Utility for decompressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param compressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint decompressPoint(ECPoint compressed) { - return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); - } - - /** - * Creates an ECKey given the private key only. - * - * @param privKey - - * @return - - */ - public static ECKey fromPrivate(BigInteger privKey) { - return new ECKey(privKey, CURVE.getG().multiply(privKey)); - } - - /** - * Creates an ECKey given the private key only. - * - * @param privKeyBytes - - * @return - - */ - public static ECKey fromPrivate(byte[] privKeyBytes) { - return fromPrivate(new BigInteger(1, privKeyBytes)); - } - - /** - * Creates an ECKey that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of pub will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { - return new ECKey(priv, pub); - } - - /** - * Creates an ECKey that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of the point will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] - pub) { - check(priv != null, "Private key must not be null"); - check(pub != null, "Public key must not be null"); - return new ECKey(new BigInteger(1, priv), CURVE.getCurve() - .decodePoint(pub)); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(ECPoint pub) { - return new ECKey(null, pub); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * encoded point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(byte[] pub) { - return new ECKey(null, CURVE.getCurve().decodePoint(pub)); - } - - /** - * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, - * use new BigInteger(1, bytes); - * - * @param privKey - - * @param compressed - - * @return - - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = CURVE.getG().multiply(privKey); - return point.getEncoded(compressed); - } - - /** - * Compute an address from an encoded public key. - * - * @param pubBytes an encoded (uncompressed) public key - * @return 21-byte address - */ - public static byte[] computeAddress(byte[] pubBytes) { - - return Hash.sha3omit12( - Arrays.copyOfRange(pubBytes, 1, pubBytes.length)); - } - - /** - * Compute an address from a public point. - * - * @param pubPoint a public point - * @return 21-byte address - */ - public static byte[] computeAddress(ECPoint pubPoint) { - return computeAddress(pubPoint.getEncoded(/* uncompressed */ false)); - } - - /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. - * - * @param pubPoint a public point - * @return 64-byte X,Y point pair - */ - public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { - final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); - return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); - } - - /** - * Recover the public key from an encoded node id. - * - * @param nodeId a 64-byte X,Y point pair - */ - public static ECKey fromNodeId(byte[] nodeId) { - check(nodeId.length == 64, "Expected a 64 byte node id"); - byte[] pubBytes = new byte[65]; - System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); - pubBytes[0] = 0x04; // uncompressed - return ECKey.fromPublicOnly(pubBytes); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { - byte[] signatureEncoded; - try { - signatureEncoded = Base64.decode(signatureBase64); - } catch (RuntimeException e) { - // This is what you getData back from Bouncy Castle if base64 doesn't - // decode :( - throw new SignatureException("Could not decode base64", e); - } - // Parse the signature bytes into r/s and the selector value. - if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); - } - - return signatureToKeyBytes( - messageHash, - ECDSASignature.fromComponents( - Arrays.copyOfRange(signatureEncoded, 1, 33), - Arrays.copyOfRange(signatureEncoded, 33, 65), - (byte) (signatureEncoded[0] & 0xFF))); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - check(messageHash.length == 32, "messageHash argument has length " + - messageHash.length); - int header = sig.v; - // The header byte: 0x1B = first key with even y, 0x1C = first key - // with odd y, - // 0x1D = second key with even y, 0x1E = second key - // with odd y - if (header < 27 || header > 34) { - throw new SignatureException("Header byte out of range: " + header); - } - if (header >= 31) { - header -= 4; - } - int recId = header - 27; - byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, - messageHash); - if (key == null) { - throw new SignatureException("Could not recover public key from " + - "signature"); - } - return key; - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, sig)); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return ECKey - */ - public static ECKey signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, - signatureBase64); - return ECKey.fromPublicOnly(keyBytes); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return ECKey - */ - public static ECKey signatureToKey(byte[] messageHash, ECDSASignature - sig) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); - return ECKey.fromPublicOnly(keyBytes); - } - - /** - *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

- *

When using native ECDSA verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

- * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, ECDSASignature signature, - byte[] pub) { - ECDSASigner signer = new ECDSASigner(); - ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE - .getCurve().decodePoint(pub), CURVE); - signer.init(false, params); - try { - return signer.verifySignature(data, signature.r, signature.s); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - System.out.println("Caught NPE inside bouncy castle" + npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, byte[] signature, byte[] pub) { - return verify(data, ECDSASignature.decodeFromDER(signature), pub); - } - - /** - * Returns true if the given pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @param pubkey - - * @return - - */ - public static boolean isPubKeyCanonical(byte[] pubkey) { - if (pubkey[0] == 0x04) { - // Uncompressed pubkey - return pubkey.length == 65; - } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { - // Compressed pubkey - return pubkey.length == 33; - } else { - return false; - } - } - - /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

The - * recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the correct - * one. Because the key recovery operation yields multiple potential allKeys, the correct key must - * either be stored alongside the signature, or you must be willing to try each recId in turn - * until you find one that outputs the key you are expecting.

If this method returns - * null it means recovery was not possible and recId should be iterated.

Given the - * above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the - * output is null OR a key that is not the one you expect, you try again with the next recId.

- * - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 65-byte encoded public key - */ - @Nullable - public static byte[] recoverPubBytesFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - check(recId >= 0, "recId must be positive"); - check(sig.r.signum() >= 0, "r must be positive"); - check(sig.s.signum() >= 0, "s must be positive"); - check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside - // this function) - // 1.1 Let x = r + jn - BigInteger n = CURVE.getN(); // Curve order. - BigInteger i = BigInteger.valueOf((long) recId / 2); - BigInteger x = sig.r.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen - // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or - // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an - // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this - // conversion routine outputs “invalid”, then - // do another iteration of Step 1. - // - // More concisely, what these points mean is to use X as a compressed - // public key. - ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); - BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent - // about the letter it uses for the prime. - if (x.compareTo(prime) >= 0) { - // Cannot have point co-ordinates larger than this as everything - // takes place modulo Q. - return null; - } - // Compressed allKeys require you to know an extra bit of data about the - // y-coord as there are two possibilities. - // So it's encoded in the recId. - ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of - // Step 1 (callers responsibility). - if (!R.multiply(n).isInfinity()) { - return null; - } - // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature - // verification. - BigInteger e = new BigInteger(1, messageHash); - // 1.6. For k from 1 to 2 do the following. (loop is outside this - // function via iterating recId) - // 1.6.1. Compute a candidate public key as: - // Q = mi(r) * (sR - eG) - // - // Where mi(x) is the modular multiplicative inverse. We transform - // this into the following: - // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) - // Where -e is the modular additive inverse of e, that is z such that - // z + e = 0 (mod n). In the above equation - // ** is point multiplication and + is point addition (the EC group - // operator). - // - // We can find the additive inverse by subtracting e from zero then - // taking the mod. For example the additive - // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod - // 11 = 8. - BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); - BigInteger rInv = sig.r.modInverse(n); - BigInteger srInv = rInv.multiply(sig.s).mod(n); - BigInteger eInvrInv = rInv.multiply(eInv).mod(n); - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE - .getG(), eInvrInv, R, srInv); - return q.getEncoded(/* compressed */ false); - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 20-byte address - */ - @Nullable - public static byte[] recoverAddressFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return computeAddress(pubBytes); - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return ECKey - */ - @Nullable - public static ECKey recoverFromSignature(int recId, ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return ECKey.fromPublicOnly(pubBytes); - } - } - - /** - * Decompress a compressed public key (x co-ord and low-bit of y-coord). - * - * @param xBN - - * @param yBit - - * @return - - */ - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { - X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE - .getCurve())); - compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return CURVE.getCurve().decodePoint(compEnc); - } - - private static void check(boolean test, String message) { - if (!test) { - throw new IllegalArgumentException(message); - } - } - - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, decompressPoint(pub)); - } - } - - /** - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey compress() { - if (pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, compressPoint(pub)); - } - } - - /** - * Returns true if this key doesn't have access to private key bytes. This may be because it was - * never given any private key bytes to begin with (a watching key). - * - * @return - - */ - public boolean isPubKeyOnly() { - return privKey == null; - } - - /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. - * - * @return - - */ - public boolean hasPrivKey() { - return privKey != null; - } - - /** - * Gets the address form of the public key. - * - * @return 20-byte address - */ - public byte[] getAddress() { - if (pubKeyHash == null) { - pubKeyHash = computeAddress(this.pub); - } - return pubKeyHash; - } - - /** - * Generates the NodeID based on this key, that is the public key without first format byte - */ - public byte[] getNodeId() { - if (nodeId == null) { - nodeId = pubBytesWithoutFormat(this.pub); - } - return nodeId; - } - - /** - * Gets the encoded public key value. - * - * @return 65-byte encoded public key - */ - public byte[] getPubKey() { - return pub.getEncoded(/* compressed */ false); - } - - /** - * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. - * - * @return - - */ - public ECPoint getPubKeyPoint() { - return pub; - } - - /** - * Gets the private key in the form of an integer field element. The public key is derived by - * performing EC point addition this number of times (i.e. point multiplying). - * - * @return - - * @throws IllegalStateException if the private key bytes are not available. - */ - public BigInteger getPrivKey() { - if (privKey == null) { - throw new MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - return ((BCECPrivateKey) privKey).getD(); - } else { - throw new MissingPrivateKeyException(); - } - } - - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - return b.toString(); - } - - /** - * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need - * the private key it is better for security reasons to just use toString(). - * - * @return - - */ - public String toStringWithPrivate() { - StringBuilder b = new StringBuilder(); - b.append(toString()); - if (privKey != null && privKey instanceof BCECPrivateKey) { - b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) - privKey).getD().toByteArray())); - } - return b.toString(); - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * ECDSASignature - * - * @param input to sign - * @return ECDSASignature signature that contains the R and S components - */ - public ECDSASignature doSign(byte[] input) { - if (input.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + input.length); - } - // No decryption of private key required. - if (privKey == null) { - throw new MissingPrivateKeyException(); - } - if (privKey instanceof BCECPrivateKey) { - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new - SHA256Digest())); - ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters - (((BCECPrivateKey) privKey).getD(), CURVE); - signer.init(true, privKeyParams); - BigInteger[] components = signer.generateSignature(input); - return new ECDSASignature(components[0], components[1]) - .toCanonicalised(); - } else { - try { - final Signature ecSig = ECSignatureFactory.getRawInstance - (provider); - ecSig.initSign(privKey); - ecSig.update(input); - final byte[] derSignature = ecSig.sign(); - return ECDSASignature.decodeFromDER(derSignature) - .toCanonicalised(); - } catch (SignatureException | InvalidKeyException ex) { - throw new RuntimeException("ECKey signing error", ex); - } - } - } - - /** - * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature - * - * @param messageHash - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public ECDSASignature sign(byte[] messageHash) { - ECDSASignature sig = doSign(messageHash); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - for (int i = 0; i < 4; i++) { - byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - public BigInteger keyAgreement(ECPoint otherParty) { - if (privKey == null) { - throw new MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) - privKey).getD(), CURVE)); - return agreement.calculateAgreement(new ECPublicKeyParameters - (otherParty, CURVE)); - } else { - try { - final KeyAgreement agreement = ECKeyAgreement.getInstance - (this.provider); - agreement.init(this.privKey); - agreement.doPhase( - ECKeyFactory.getInstance(this.provider) - .generatePublic(new ECPublicKeySpec - (otherParty, CURVE_SPEC)), - /* lastPhase */ true); - return new BigInteger(1, agreement.generateSecret()); - } catch (IllegalStateException | InvalidKeyException | - InvalidKeySpecException ex) { - throw new RuntimeException("ECDH key agreement failure", ex); - } - } - } - - /** - * Decrypt cipher by AES in SIC(also know as CTR) mode - * - * @param cipher -proper cipher - * @return decrypted cipher, equal length to the cipher. - * @deprecated should not use EC private scalar value as an AES key - */ - public byte[] decryptAES(byte[] cipher) { - - if (privKey == null) { - throw new MissingPrivateKeyException(); - } - if (!(privKey instanceof BCECPrivateKey)) { - throw new UnsupportedOperationException("Cannot use the private " + - "key as an AES key"); - } - - AESFastEngine engine = new AESFastEngine(); - SICBlockCipher ctrEngine = new SICBlockCipher(engine); - - KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( - (BCECPrivateKey) privKey).getD())); - ParametersWithIV params = new ParametersWithIV(key, new byte[16]); - - ctrEngine.init(false, params); - - int i = 0; - byte[] out = new byte[cipher.length]; - while (i < cipher.length) { - ctrEngine.processBlock(cipher, i, out, i); - i += engine.getBlockSize(); - if (cipher.length - i < engine.getBlockSize()) { - break; - } - } - - // process left bytes - if (cipher.length - i > 0) { - byte[] tmpBlock = new byte[16]; - System.arraycopy(cipher, i, tmpBlock, 0, cipher.length - i); - ctrEngine.processBlock(tmpBlock, 0, tmpBlock, 0); - System.arraycopy(tmpBlock, 0, out, i, cipher.length - i); - } - - return out; - } - - /** - * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @return - - */ - public boolean verify(byte[] data, byte[] signature) { - return ECKey.verify(data, signature, getPubKey()); - } - - /** - * Verifies the given R/S pair (signature) against a hash using the public key. - * - * @param sigHash - - * @param signature - - * @return - - */ - public boolean verify(byte[] sigHash, ECDSASignature signature) { - return ECKey.verify(sigHash, signature, getPubKey()); - } - - /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @return - - */ - public boolean isPubKeyCanonical() { - return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); - } - - /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only - * - * @return - - */ - @Nullable - public byte[] getPrivKeyBytes() { - if (privKey == null) { - return null; - } else if (privKey instanceof BCECPrivateKey) { - return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); - } else { - return null; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !(o instanceof ECKey)) { - return false; - } - - ECKey ecKey = (ECKey) o; - - if (privKey != null && !privKey.equals(ecKey.privKey)) { - return false; - } - return pub == null || pub.equals(ecKey.pub); - } - - @Override - public int hashCode() { - return Arrays.hashCode(getPubKey()); - } - - public static class ECDSASignature { - - /** - * The two components of the signature. - */ - public final BigInteger r, s; - public byte v; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. - * - * @param r - - * @param s - - */ - public ECDSASignature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - /** - * t - * - * @return - - */ - private static ECDSASignature fromComponents(byte[] r, byte[] s) { - return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, - s)); - } - - /** - * @param r - - * @param s - - * @param v - - * @return - - */ - public static ECDSASignature fromComponents(byte[] r, byte[] s, byte - v) { - ECDSASignature signature = fromComponents(r, s); - signature.v = v; - return signature; - } - - public static boolean validateComponents(BigInteger r, BigInteger s, - byte v) { - - if (v != 27 && v != 28) { - return false; - } - - if (isLessThan(r, BigInteger.ONE)) { - return false; - } - if (isLessThan(s, BigInteger.ONE)) { - return false; - } - - if (!isLessThan(r, SECP256K1N)) { - return false; - } - return isLessThan(s, SECP256K1N); - } - - public static ECDSASignature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " + - "stream."); - } - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these - // values as unsigned, though they should not be - // Thus, we always use the positive versions. See: - // http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s - .getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) { - try { - decoder.close(); - } catch (IOException x) { - } - } - } - } - - public boolean validateComponents() { - return validateComponents(r, s, v); - } - - public ECDSASignature toCanonicalised() { - if (s.compareTo(HALF_CURVE_ORDER) > 0) { - // The order of the curve is the number of valid points that - // exist on that curve. If S is in the upper - // half of the number of valid points, then bring it back to - // the lower half. Otherwise, imagine that - // N = 10 - // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) - // are valid solutions. - // 10 - 8 == 2, giving us always the latter solution, - // which is canonical. - return new ECDSASignature(r, CURVE.getN().subtract(s)); - } else { - return this; - } - } - - /** - * @return - - */ - public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 - // bytes for S - sigData[0] = v; - System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); - System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); - return new String(Base64.encode(sigData), Charset.forName("UTF-8")); - } - - public byte[] toByteArray() { - final byte fixedV = this.v >= 27 - ? (byte) (this.v - 27) - : this.v; - - return ByteUtil.merge( - ByteUtil.bigIntegerToBytes(this.r, 32), - ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); - } - - public String toHex() { - return Hex.toHexString(toByteArray()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ECDSASignature signature = (ECDSASignature) o; - - if (!r.equals(signature.r)) { - return false; - } - return s.equals(signature.s); - } - - @Override - public int hashCode() { - int result = r.hashCode(); - result = 31 * result + s.hashCode(); - return result; - } - } - - @SuppressWarnings("serial") - public static class MissingPrivateKeyException extends RuntimeException { - - } - -} +package org.tron.common.crypto; +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +import static org.tron.common.utils.BIUtil.isLessThan; +import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; +import static org.tron.common.utils.ByteUtil.byteArrayToInt; + +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import javax.annotation.Nullable; +import javax.crypto.KeyAgreement; +import lombok.extern.slf4j.Slf4j; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DLSequence; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.agreement.ECDHBasicAgreement; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.engines.AESEngine; +import org.spongycastle.crypto.modes.SICBlockCipher; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.crypto.signers.HMacDSAKCalculator; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Base64; +import org.spongycastle.util.encoders.Hex; +import org.tron.common.crypto.cryptohash.Keccak256; +import org.tron.common.crypto.jce.ECKeyAgreement; +import org.tron.common.crypto.jce.ECKeyFactory; +import org.tron.common.crypto.jce.ECKeyPairGenerator; +import org.tron.common.crypto.jce.ECSignatureFactory; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.BIUtil; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.Hash; + +@Slf4j(topic = "crypto") +public class ECKey implements Serializable, SignInterface { + + /** + * The parameters of the secp256k1 curve. + */ + public static final ECDomainParameters CURVE; + public static final ECParameterSpec CURVE_SPEC; + + /** + * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA + * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - + * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2 + * + *

See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki + * #Low_S_values_in_signatures + */ + + public static final BigInteger HALF_CURVE_ORDER; + private static final BigInteger SECP256K1N = + new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); + private static final SecureRandom secureRandom; + private static final long serialVersionUID = -728224901792295832L; + + static { + // All clients must agree on the curve to use by agreement. + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + CURVE = new ECDomainParameters(params.getCurve(), params.getG(), + params.getN(), params.getH()); + CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), + params.getN(), params.getH()); + HALF_CURVE_ORDER = params.getN().shiftRight(1); + secureRandom = new SecureRandom(); + } + + protected final ECPoint pub; + // The two parts of the key. If "priv" is set, "pub" can always be + // calculated. If "pub" is set but not "priv", we + // can only verify signatures not make them. + // TODO: Redesign this class to use consistent internals and more + // efficient serialization. + private final PrivateKey privKey; + // the Java Cryptographic Architecture provider to use for Signature + // this is set along with the PrivateKey privKey and must be compatible + // this provider will be used when selecting a Signature instance + // https://docs.oracle.com/javase/8/docs/technotes/guides/security + // /SunProviders.html + private final Provider provider; + + // Transient because it's calculated on demand. + private transient byte[] pubKeyHash; + private transient byte[] nodeId; + + /** + * Generates an entirely new keypair. + * + *

BouncyCastle will be used as the Java Security Provider + */ + public ECKey() { + this(secureRandom); + } + + /** + * Generate a new keypair using the given Java Security Provider. + * + *

All private key operations will use the provider. + */ + public ECKey(Provider provider, SecureRandom secureRandom) { + this.provider = provider; + + final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom); + final KeyPair keyPair = keyPairGen.generateKeyPair(); + + this.privKey = keyPair.getPrivate(); + + final PublicKey pubKey = keyPair.getPublic(); + if (pubKey instanceof BCECPublicKey) { + pub = ((BCECPublicKey) pubKey).getQ(); + } else if (pubKey instanceof ECPublicKey) { + pub = extractPublicKey((ECPublicKey) pubKey); + } else { + throw new AssertionError( + "Expected Provider " + provider.getName() + + " to produce a subtype of ECPublicKey, found " + + pubKey.getClass()); + } + } + + /** + * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle + * will be used as the Java Security Provider + * + * @param secureRandom - + */ + public ECKey(SecureRandom secureRandom) { + this(TronCastleProvider.getInstance(), secureRandom); + } + + /** + * Pair a private key with a public EC point. + * + *

All private key operations will use the provider. + */ + + // isPrivateKey true 私钥 其他公钥 + public ECKey(byte[] key, boolean isPrivateKey) { + if (isPrivateKey) { + BigInteger pk = new BigInteger(1, key); + this.privKey = privateKeyFromBigInteger(pk); + this.pub = CURVE.getG().multiply(pk); + } else { + this.privKey = null; + this.pub = CURVE.getCurve().decodePoint(key); + } + this.provider = TronCastleProvider.getInstance(); + } + + public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { + this.provider = provider; + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + /** + * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java + * Security Provider + */ + public ECKey(@Nullable BigInteger priv, ECPoint pub) { + this( + TronCastleProvider.getInstance(), + privateKeyFromBigInteger(priv), + pub + ); + } + + /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + */ + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return CURVE.getCurve().createPoint(xCoord, yCoord); + } + + /* Test if a generic private key is an EC private key + * + * it is not sufficient to check that privKey is a subtype of ECPrivateKey + * as the SunPKCS11 Provider will return a generic PrivateKey instance + * a fallback that covers this case is to check the key algorithm + */ + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm() + .equals("EC"); + } + + /* Convert a BigInteger into a PrivateKey object + */ + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + CURVE_SPEC)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param uncompressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param compressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKey - + * @return - + */ + public static ECKey fromPrivate(BigInteger privKey) { + return new ECKey(privKey, CURVE.getG().multiply(privKey)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static ECKey fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, + ECPoint pub) { + return new ECKey(priv, pub); + } + + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] + pub) { + check(priv != null, "Private key must not be null"); + check(pub != null, "Public key must not be null"); + return new ECKey(new BigInteger(1, priv), CURVE.getCurve() + .decodePoint(pub)); + } + + /** + * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given + * point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static ECKey fromPublicOnly(ECPoint pub) { + return new ECKey(null, pub); + } + + /** + * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given + * encoded point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static ECKey fromPublicOnly(byte[] pub) { + return new ECKey(null, CURVE.getCurve().decodePoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * use new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean + compressed) { + ECPoint point = CURVE.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key + * without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Recover the public key from an encoded node id. + * + * @param nodeId a 64-byte X,Y point pair + */ + public static ECKey fromNodeId(byte[] nodeId) { + check(nodeId.length == 64, "Expected a 64 byte node id"); + byte[] pubBytes = new byte[65]; + System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); + pubBytes[0] = 0x04; // uncompressed + return ECKey.fromPublicOnly(pubBytes); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, String + signatureBase64) throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you getData back from Bouncy Castle if base64 doesn't + // decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) { + throw new SignatureException("Signature truncated, expected 65 " + + "bytes and got " + signatureEncoded.length); + } + + return signatureToKeyBytes( + messageHash, + ECDSASignature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, + ECDSASignature sig) throws + SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + + messageHash.length); + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key + // with odd y, + // 0x1D = second key with even y, 0x1E = second key + // with odd y + if (header < 27 || header > 34) { + throw new SignatureException("Header byte out of range: " + header); + } + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, + messageHash); + if (key == null) { + throw new SignatureException("Could not recover public key from " + + "signature"); + } + return key; + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, + ECDSASignature sig) throws + SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, sig)); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return ECKey + */ + public static ECKey signatureToKey(byte[] messageHash, String + signatureBase64) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, + signatureBase64); + return ECKey.fromPublicOnly(keyBytes); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return ECKey + */ + public static ECKey signatureToKey(byte[] messageHash, ECDSASignature + sig) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); + return ECKey.fromPublicOnly(keyBytes); + } + + /** + *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

+ *

When using native ECDSA verification, data must be 32 bytes, and no element may be + * larger than 520 bytes.

+ * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, ECDSASignature signature, + byte[] pub) { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE + .getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + try { + return signer.verifySignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, ECDSASignature.decodeFromDER(signature), pub); + } + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + return pubkey.length == 65; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + return pubkey.length == 33; + } else { + return false; + } + } + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * + *

The recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the + * correct one. Because the key recovery operation yields multiple potential allKeys, the correct + * key must either be stored alongside the signature, or you must be willing to try each recId in + * turn until you find one that outputs the key you are expecting.

+ * + *

If this method returns null it means recovery was not possible and recId should be + * iterated.

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 + * to 3, and if the output is null OR a key that is not the one you expect, you try again with the + * next recId.

+ * + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 65-byte encoded public key + */ + @Nullable + public static byte[] recoverPubBytesFromSignature(int recId, + ECDSASignature sig, + byte[] messageHash) { + check(recId >= 0, "recId must be positive"); + check(sig.r.signum() >= 0, "r must be positive"); + check(sig.s.signum() >= 0, "s must be positive"); + check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside + // this function) + // 1.1 Let x = r + jn + BigInteger n = CURVE.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen + // using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an + // elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this + // conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed + // public key. + ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent + // about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything + // takes place modulo Q. + return null; + } + // Compressed allKeys require you to know an extra bit of data about the + // y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of + // Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature + // verification. + BigInteger e = new BigInteger(1, messageHash); + // 1.6. For k from 1 to 2 do the following. (loop is outside this + // function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform + // this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that + // z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group + // operator). + // + // We can find the additive inverse by subtracting e from zero then + // taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod + // 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE + .getG(), eInvrInv, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 20-byte address + */ + @Nullable + public static byte[] recoverAddressFromSignature(int recId, + ECDSASignature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return Hash.computeAddress(pubBytes); + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return ECKey + */ + @Nullable + public static ECKey recoverFromSignature(int recId, ECDSASignature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return ECKey.fromPublicOnly(pubBytes); + } + } + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE + .getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return CURVE.getCurve().decodePoint(compEnc); + } + + private static void check(boolean test, String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + * Returns a copy of this key, but with the public point represented in uncompressed form. + * Normally you would never need this: it's for specialised scenarios or when backwards + * compatibility in encoded form is necessary. + * + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey decompress() { + if (!pub.isCompressed()) { + return this; + } else { + return new ECKey(this.provider, this.privKey, decompressPoint(pub)); + } + } + + /** + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey compress() { + if (pub.isCompressed()) { + return this; + } else { + return new ECKey(this.provider, this.privKey, compressPoint(pub)); + } + } + + /** + * Returns true if this key doesn't have access to private key bytes. This may be because it was + * never given any private key bytes to begin with (a watching key). + * + * @return - + */ + public boolean isPubKeyOnly() { + return privKey == null; + } + + /** + * Returns true if this key has access to private key bytes. Does the opposite of {@link + * #isPubKeyOnly()}. + * + * @return - + */ + public boolean hasPrivKey() { + return privKey != null; + } + + /** + * Gets the address form of the public key. + * + * @return 21-byte address + */ + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = Hash.computeAddress(this.pub); + } + return pubKeyHash; + } + + @Override + public String signHash(byte[] hash) { + return sign(hash).toBase64(); + } + + public byte[] Base64toBytes (String signature) { + byte[] signData = Base64.decode(signature); + byte first = (byte)(signData[0] - 27); + byte[] temp = Arrays.copyOfRange(signData,1,65); + return ByteUtil.appendByte(temp,first); + } + + @Override + public byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Generates the NodeID based on this key, that is the public key without first format byte + */ + public byte[] getNodeId() { + if (nodeId == null) { + nodeId = pubBytesWithoutFormat(this.pub); + } + return nodeId; + } + + @Override + public byte[] hash(byte[] message) { + Keccak256 hashFun = new Keccak256(); + return hashFun.digest(message); + } + + @Override + public byte[] getPrivateKey() { + return getPrivKeyBytes(); + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by + * performing EC point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new MissingPrivateKeyException(); + } + } + + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 + * bytes, not 64. + * + * @return - + */ + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need + * the private key it is better for security reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) + privKey).getD().toByteArray())); + } + return b.toString(); + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * ECDSASignature + * + * @param input to sign + * @return ECDSASignature signature that contains the R and S components + */ + public ECDSASignature doSign(byte[] input) { + if (input.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "ECDSA signature, not " + input.length); + } + // No decryption of private key required. + if (privKey == null) { + throw new MissingPrivateKeyException(); + } + if (privKey instanceof BCECPrivateKey) { + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new + SHA256Digest())); + ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters + (((BCECPrivateKey) privKey).getD(), CURVE); + signer.init(true, privKeyParams); + BigInteger[] components = signer.generateSignature(input); + return new ECDSASignature(components[0], components[1]) + .toCanonicalised(); + } else { + try { + final Signature ecSig = ECSignatureFactory.getRawInstance + (provider); + ecSig.initSign(privKey); + ecSig.update(input); + final byte[] derSignature = ecSig.sign(); + return ECDSASignature.decodeFromDER(derSignature) + .toCanonicalised(); + } catch (SignatureException | InvalidKeyException ex) { + throw new RuntimeException("ECKey signing error", ex); + } + } + } + + /** + * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public ECDSASignature sign(byte[] messageHash) { + ECDSASignature sig = doSign(messageHash); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + public BigInteger keyAgreement(ECPoint otherParty) { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) + privKey).getD(), CURVE)); + return agreement.calculateAgreement(new ECPublicKeyParameters + (otherParty, CURVE)); + } else { + try { + final KeyAgreement agreement = ECKeyAgreement.getInstance + (this.provider); + agreement.init(this.privKey); + agreement.doPhase( + ECKeyFactory.getInstance(this.provider) + .generatePublic(new ECPublicKeySpec + (otherParty, CURVE_SPEC)), + /* lastPhase */ true); + return new BigInteger(1, agreement.generateSecret()); + } catch (IllegalStateException | InvalidKeyException | + InvalidKeySpecException ex) { + throw new RuntimeException("ECDH key agreement failure", ex); + } + } + } + + /** + * Decrypt cipher by AES in SIC(also know as CTR) mode + * + * @param cipher -proper cipher + * @return decrypted cipher, equal length to the cipher. + * @deprecated should not use EC private scalar value as an AES key + */ + public byte[] decryptAES(byte[] cipher) { + + if (privKey == null) { + throw new MissingPrivateKeyException(); + } + if (!(privKey instanceof BCECPrivateKey)) { + throw new UnsupportedOperationException("Cannot use the private " + + "key as an AES key"); + } + + AESEngine engine = new AESEngine(); + SICBlockCipher ctrEngine = new SICBlockCipher(engine); + + KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( + (BCECPrivateKey) privKey).getD())); + ParametersWithIV params = new ParametersWithIV(key, new byte[16]); + + ctrEngine.init(false, params); + + int i = 0; + byte[] out = new byte[cipher.length]; + while (i < cipher.length) { + ctrEngine.processBlock(cipher, i, out, i); + i += engine.getBlockSize(); + if (cipher.length - i < engine.getBlockSize()) { + break; + } + } + + // process left bytes + if (cipher.length - i > 0) { + byte[] tmpBlock = new byte[16]; + System.arraycopy(cipher, i, tmpBlock, 0, cipher.length - i); + ctrEngine.processBlock(tmpBlock, 0, tmpBlock, 0); + System.arraycopy(tmpBlock, 0, out, i, cipher.length - i); + } + + return out; + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return ECKey.verify(data, signature, getPubKey()); + } + + /** + * Verifies the given R/S pair (signature) against a hash using the public key. + * + * @param sigHash - + * @param signature - + * @return - + */ + public boolean verify(byte[] sigHash, ECDSASignature signature) { + return ECKey.verify(sigHash, signature, getPubKey()); + } + + /** + * Returns true if this pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @return - + */ + public boolean isPubKeyCanonical() { + return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public + * only + * + * @return - + */ + @Nullable + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return ByteUtil.bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + ECKey ecKey = (ECKey) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) { + return false; + } + return pub == null || pub.equals(ecKey.pub); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + public static class ECDSASignature implements SignatureInterface { + + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the + * signature. + * + * @param r - + * @param s - + */ + public ECDSASignature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + public ECDSASignature(byte[] r, byte[] s, byte v) { + this.r = new BigInteger(1, r); + this.s = new BigInteger(1, s); + this.v = v; + } + + /** + * t + * + * @return - + */ + private static ECDSASignature fromComponents(byte[] r, byte[] s) { + return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, + s)); + } + + /** + * @param r - + * @param s - + * @param v - + * @return - + */ + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte + v) { + ECDSASignature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public static boolean validateComponents(BigInteger r, BigInteger s, + byte v) { + + if (v != 27 && v != 28) { + return false; + } + + if (BIUtil.isLessThan(r, BigInteger.ONE)) { + return false; + } + if (BIUtil.isLessThan(s, BigInteger.ONE)) { + return false; + } + + if (!BIUtil.isLessThan(r, SECP256K1N)) { + return false; + } + return BIUtil.isLessThan(s, SECP256K1N); + } + + public static ECDSASignature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) { + throw new RuntimeException("Reached past end of ASN.1 " + + "stream."); + } + ASN1Integer r, s; + try { + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + // OpenSSL deviates from the DER spec by interpreting these + // values as unsigned, though they should not be + // Thus, we always use the positive versions. See: + // http://r6.ca/blog/20111119T211504Z.html + return new ECDSASignature(r.getPositiveValue(), s + .getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) { + try { + decoder.close(); + } catch (IOException x) { + + } + } + } + } + + public boolean validateComponents() { + return validateComponents(r, s, v); + } + + public ECDSASignature toCanonicalised() { + if (s.compareTo(HALF_CURVE_ORDER) > 0) { + // The order of the curve is the number of valid points that + // exist on that curve. If S is in the upper + // half of the number of valid points, then bring it back to + // the lower half. Otherwise, imagine that + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) + // are valid solutions. + // 10 - 8 == 2, giving us always the latter solution, + // which is canonical. + return new ECDSASignature(r, CURVE.getN().subtract(s)); + } else { + return this; + } + } + + /** + * @return - + */ + public String toBase64() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + // bytes for S + sigData[0] = v; + System.arraycopy(ByteUtil.bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(ByteUtil.bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + } + + + + public byte[] toByteArray() { + final byte fixedV = this.v >= 27 + ? (byte) (this.v - 27) + : this.v; + + return ByteUtil.merge( + ByteUtil.bigIntegerToBytes(this.r, 32), + ByteUtil.bigIntegerToBytes(this.s, 32), + new byte[]{fixedV}); + } + + public String toHex() { + return Hex.toHexString(toByteArray()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ECDSASignature signature = (ECDSASignature) o; + + if (!r.equals(signature.r)) { + return false; + } + return s.equals(signature.s); + } + + @Override + public int hashCode() { + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; + } + } + + @SuppressWarnings("serial") + public static class MissingPrivateKeyException extends RuntimeException { + + } + +} diff --git a/src/main/java/org/tron/common/crypto/SignInterface.java b/src/main/java/org/tron/common/crypto/SignInterface.java new file mode 100644 index 000000000..3da8fd67e --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SignInterface.java @@ -0,0 +1,22 @@ +package org.tron.common.crypto; + +import java.security.SignatureException; + +public interface SignInterface { + + byte[] hash(byte[] message); + + byte[] getPrivateKey(); + + byte[] getPubKey(); + + byte[] getAddress(); + + String signHash(byte[] hash); + + byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException; + + byte[] getNodeId(); + + byte[] Base64toBytes (String signature); +} diff --git a/src/main/java/org/tron/common/crypto/SignUtils.java b/src/main/java/org/tron/common/crypto/SignUtils.java new file mode 100644 index 000000000..710ecda81 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SignUtils.java @@ -0,0 +1,54 @@ +package org.tron.common.crypto; + +import java.security.SecureRandom; +import java.security.SignatureException; +import org.tron.common.crypto.ECKey.ECDSASignature; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.crypto.sm2.SM2.SM2Signature; + +public class SignUtils { + public static SignInterface getGeneratedRandomSign(boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return new ECKey(); + } + return new SM2(); + } + + public static SignInterface getGeneratedRandomSign( + SecureRandom secureRandom, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return new ECKey(secureRandom); + } + return new SM2(secureRandom); + } + + public static SignInterface fromPrivate(byte[] privKeyBytes, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return ECKey.fromPrivate(privKeyBytes); + } + return SM2.fromPrivate(privKeyBytes); + } + + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64, boolean isECKeyCryptoEngine) throws SignatureException { + if (isECKeyCryptoEngine) { + return ECKey.signatureToAddress(messageHash, signatureBase64); + } + return SM2.signatureToAddress(messageHash, signatureBase64); + } + + public static SignatureInterface fromComponents(byte[] r, byte[] s, byte v, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return ECKey.ECDSASignature.fromComponents(r, s, v); + } + return SM2.SM2Signature.fromComponents(r, s, v); + } + + public static byte[] signatureToAddress(byte[] messageHash, SignatureInterface signatureInterface, + boolean isECKeyCryptoEngine) throws SignatureException{ + if (isECKeyCryptoEngine) { + return ECKey.signatureToAddress(messageHash, (ECDSASignature)signatureInterface); + } + return SM2.signatureToAddress(messageHash, (SM2Signature)signatureInterface); + } +} diff --git a/src/main/java/org/tron/common/crypto/SignatureInterface.java b/src/main/java/org/tron/common/crypto/SignatureInterface.java new file mode 100644 index 000000000..77d7cd101 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SignatureInterface.java @@ -0,0 +1,5 @@ +package org.tron.common.crypto; + +public interface SignatureInterface { + boolean validateComponents(); +} \ No newline at end of file diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2.java b/src/main/java/org/tron/common/crypto/sm2/SM2.java new file mode 100644 index 000000000..1236dd65c --- /dev/null +++ b/src/main/java/org/tron/common/crypto/sm2/SM2.java @@ -0,0 +1,1245 @@ +package org.tron.common.crypto.sm2; + +import lombok.extern.slf4j.Slf4j; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DLSequence; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.generators.ECKeyPairGenerator; +import org.spongycastle.crypto.params.*; +import org.spongycastle.crypto.signers.*; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.math.ec.*; +import org.spongycastle.util.encoders.Base64; +import org.spongycastle.util.encoders.Hex; +import org.spongycastle.util.test.TestRandomBigInteger; +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.jce.ECKeyFactory; +import org.tron.common.crypto.jce.ECSignatureFactory; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.ByteUtil; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.*; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static org.tron.common.utils.BIUtil.isLessThan; +import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; +import static org.tron.common.utils.Hash.computeAddress; + +/** + * Implement Chinese Commercial Cryptographic Standard of SM2 + * + */ +@Slf4j(topic = "crypto") +public class SM2 implements Serializable, SignInterface { + private static BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); + private static BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); + private static BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); + private static BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); + private static BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + private static BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); +// +// private static BigInteger SM2_N = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16); +// private static BigInteger SM2_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16); +// private static BigInteger SM2_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16); +// private static BigInteger SM2_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16); +// private static BigInteger SM2_GX = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16); +// private static BigInteger SM2_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16); + + private static ECDomainParameters ecc_param; + private static ECParameterSpec ecc_spec; + private static ECCurve.Fp curve; + private static ECPoint ecc_point_g; + + private static final SecureRandom secureRandom; + + + + static { + secureRandom = new SecureRandom(); + curve = new ECCurve.Fp(SM2_P, SM2_A, SM2_B); + ecc_point_g = curve.createPoint(SM2_GX, SM2_GY); + ecc_param = new ECDomainParameters(curve, ecc_point_g, SM2_N); + ecc_spec = new ECParameterSpec(curve, ecc_point_g, SM2_N); + } + + protected final ECPoint pub; + + private final PrivateKey privKey; + +// private final SM2KeyPair keyPair; + + // Transient because it's calculated on demand. + private transient byte[] pubKeyHash; + private transient byte[] nodeId; + +// private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); +// private byte[] userID; + + public SM2() { + this(secureRandom); + } + /** + * Generates an entirely new keypair. + * + *

BouncyCastle will be used as the Java Security Provider + */ + + + /** + * Generate a new keypair using the given Java Security Provider. + * + *

All private key operations will use the provider. + */ + public SM2(SecureRandom secureRandom) { + + ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecc_param, secureRandom); + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + keyPairGenerator.init(ecKeyGenerationParameters); + AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) kp.getPrivate(); + ECPublicKeyParameters ecpub = (ECPublicKeyParameters) kp.getPublic(); + + BigInteger privateKey = ecpriv.getD(); + this.privKey = privateKeyFromBigInteger(privateKey); + this.pub = ecpub.getQ(); +// this.keyPair = new SM2KeyPair(pub.getEncoded(false),privateKey.toByteArray()); + +// CipherParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey, ecc_param); +// CipherParameters baseParam; +// +// if (privateKeyParameters instanceof ParametersWithID) +// { +// baseParam = ((ParametersWithID)privateKeyParameters).getParameters(); +// userID = ((ParametersWithID)privateKeyParameters).getID(); +// } +// else +// { +// baseParam = privateKeyParameters; +// userID = new byte[0]; +// } +// this.kCalculator.init(SM2_N, secureRandom); + } + + public SM2 (byte[] key, boolean isPrivateKey) { + if (isPrivateKey) { + BigInteger pk = new BigInteger(1, key); + this.privKey = privateKeyFromBigInteger(pk); + this.pub = ecc_param.getG().multiply(pk); + } else { + this.privKey = null; + this.pub = ecc_param.getCurve().decodePoint(key); + } + } + + + /** + * Pair a private key with a public EC point. + * + *

All private key operations will use the provider. + */ + + public SM2(@Nullable PrivateKey privKey, ECPoint pub) { + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + /** + * Pair a private key integer with a public EC point + * + */ + public SM2(@Nullable BigInteger priv, ECPoint pub) { + this( + privateKeyFromBigInteger(priv), + pub + ); + +// this.privKey = privateKeyFromBigInteger(priv); +// this.pub = pub; +// this.keyPair = new SM2KeyPair(pub.getEncoded(false), priv.toByteArray()); + } + + /** + * Convert a BigInteger into a PrivateKey object + * + * @param priv + * @return + */ + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + ecc_spec)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /* Test if a generic private key is an EC private key + * + * it is not sufficient to check that privKey is a subtype of ECPrivateKey + * as the SunPKCS11 Provider will return a generic PrivateKey instance + * a fallback that covers this case is to check the key algorithm + */ + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm() + .equals("EC"); + } + + /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + */ + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return ecc_param.getCurve().createPoint(xCoord, yCoord); + } + + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param uncompressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return ecc_param.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param compressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return ecc_param.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an SM2 given the private key only. + * + * @param privKey - + * @return - + */ + public static SM2 fromPrivate(BigInteger privKey) { + return new SM2(privKey, ecc_param.getG().multiply(privKey)); + } + + /** + * Creates an SM2 given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static SM2 fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + + /** + * Creates an SM2 that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static SM2 fromPrivateAndPrecalculatedPublic(BigInteger priv, + ECPoint pub) { + return new SM2(priv, pub); + } + + /** + * Creates an SM2 that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static SM2 fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] + pub) { + check(priv != null, "Private key must not be null"); + check(pub != null, "Public key must not be null"); + return new SM2(new BigInteger(1, priv), ecc_param.getCurve() + .decodePoint(pub)); + } + + /** + * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static SM2 fromPublicOnly(ECPoint pub) { + return new SM2((PrivateKey) null, pub); + } + + /** + * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * encoded point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static SM2 fromPublicOnly(byte[] pub) { + return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * use new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean + compressed) { + ECPoint point = ecc_param.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key + * without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Recover the public key from an encoded node id. + * + * @param nodeId a 64-byte X,Y point pair + */ + public static SM2 fromNodeId(byte[] nodeId) { + check(nodeId.length == 64, "Expected a 64 byte node id"); + byte[] pubBytes = new byte[65]; + System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); + pubBytes[0] = 0x04; // uncompressed + return SM2.fromPublicOnly(pubBytes); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, String + signatureBase64) throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you getData back from Bouncy Castle if base64 doesn't + // decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) { + throw new SignatureException("Signature truncated, expected 65 " + + "bytes and got " + signatureEncoded.length); + } + + return signatureToKeyBytes( + messageHash, + SM2Signature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, + SM2Signature sig) throws + SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + + messageHash.length); + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key + // with odd y, + // 0x1D = second key with even y, 0x1E = second key + // with odd y + if (header < 27 || header > 34) { + throw new SignatureException("Header byte out of range: " + header); + } + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (key == null) { + throw new SignatureException("Could not recover public key from " + + "signature"); + } + return key; + } + + + @Override + public byte[] hash(byte[] message) { + SM2Signer signer = this.getSM2SignerForHash(); + return signer.generateSM3Hash(message); + } + + @Override + public byte[] getPrivateKey() { + return getPrivKeyBytes(); + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + @Override + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the address form of the public key. + * + * @return 21-byte address + */ + @Override + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = computeAddress(this.pub); + } + return pubKeyHash; + } + + @Override + public byte[] signToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, + SM2Signature sig) throws + SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, sig)); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return ECKey + */ + public static SM2 signatureToKey(byte[] messageHash, String + signatureBase64) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, + signatureBase64); + return fromPublicOnly(keyBytes); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return ECKey + */ + public static SM2 signatureToKey(byte[] messageHash, SM2Signature + sig) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); + return fromPublicOnly(keyBytes); + } + + /** + * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which including the v + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public SM2Signature sign(byte[] messageHash) { + if (messageHash.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "SM2 signature, not " + messageHash.length); + } + // No decryption of private key required. + SM2Signer signer = getSigner(); + BigInteger[] componets = signer.generateHashSignature(messageHash); + + SM2Signature sig = new SM2.SM2Signature(componets[0], componets[1]); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * SM2Signature + * + * @param input to sign + * @return SM2Signature signature that contains the R and S components + */ + public String signHash(byte[] input) { + return sign(input).toBase64(); + } + + public byte[] Base64toBytes (String signature) { + byte[] signData = Base64.decode(signature); + byte first = (byte)(signData[0] - 27); + byte[] temp = Arrays.copyOfRange(signData,1,65); + return ByteUtil.appendByte(temp,first); + } + + /** + * Takes the message of data and returns the SM2 signature + * + * @param message - + * @param userID + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public SM2Signature signMessage(byte[] message, @Nullable String userID) { + SM2Signature sig = signMsg(message, userID); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + + SM2Signer signer = getSigner(); + byte[] messageHash = signer.generateSM3Hash(message); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * SM2Signature + * + * @param msg to sign + * @param userID + * @return SM2Signature signature that contains the R and S components + */ + public SM2.SM2Signature signMsg(byte[] msg,@Nullable String userID) { + if (null == msg) { + throw new IllegalArgumentException("Expected signature message of " + + "SM2 is null"); + } + // No decryption of private key required. + SM2Signer signer = getSigner(); + BigInteger[] componets = signer.generateSignature(msg); + return new SM2.SM2Signature(componets[0], componets[1]); + } + + private SM2Signer getSigner() { + SM2Signer signer = new SM2Signer(); + BigInteger d = getPrivKey(); + ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); +// ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); + signer.init(true, privateKeyParameters); + return signer; + } + + /** + * used to generate the SM3 hash for SM2 signature generation or verification + * + * @return + */ + public SM2Signer getSM2SignerForHash() { + SM2Signer signer = new SM2Signer(); + //BigInteger d = getPrivKey(); + //ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); + ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); + signer.init(false, publicKeyParameters); + return signer; + } + + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature + * + * @param recId + * @param sig + * @param messageHash + * @return + */ + @Nullable + public static byte[] recoverPubBytesFromSignature(int recId, + SM2Signature sig, + byte[] messageHash) { + check(recId >= 0, "recId must be positive"); + check(sig.r.signum() >= 0, "r must be positive"); + check(sig.s.signum() >= 0, "s must be positive"); + check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside + // this function) + // 1.1 Let x = r + jn + BigInteger n = ecc_param.getN(); // Curve order. + BigInteger prime = curve.getQ(); + BigInteger i = BigInteger.valueOf((long) recId / 2); + + BigInteger e = new BigInteger(1, messageHash); + BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n + x = x.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen + // using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an + // elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this + // conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed + // public key. + ECCurve.Fp curve = (ECCurve.Fp) ecc_param.getCurve(); + // Bouncy Castle is not consistent + // about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything + // takes place modulo Q. + return null; + } + // Compressed allKeys require you to know an extra bit of data about the + // y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of + // Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + + // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) + BigInteger srInv = sig.s.add(sig.r).modInverse(n); + BigInteger sNeg = BigInteger.ZERO.subtract(sig.s).mod(n); + BigInteger coeff = srInv.multiply(sNeg).mod(n); + + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(ecc_param + .getG(), coeff, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(ecc_param + .getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return ecc_param.getCurve().decodePoint(compEnc); + } + + private static void check(boolean test, String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + *

Verifies the given SM2 signature against the message bytes using the public key bytes.

+ *

When using native SM2 verification, data must be 32 bytes, and no element may be + * larger than 520 bytes.

+ * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, SM2Signature signature, + byte[] pub) { + SM2Signer signer = new SM2Signer(); + ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param + .getCurve().decodePoint(pub),ecc_param); + signer.init(false, params); + try { + return signer.verifyHashSignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, SM2Signature.decodeFromDER(signature), pub); + } + + /** + *

Verifies the given SM2 signature against the message bytes using the public key bytes. + * + * @param msg the message data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verifyMessage(byte[] msg, SM2Signature signature, + byte[] pub, @Nullable String userID) { + SM2Signer signer = new SM2Signer(); + ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param + .getCurve().decodePoint(pub),ecc_param); + signer.init(false, params); + try { + return signer.verifySignature(msg, signature.r, signature.s, userID); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param msg the message data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verifyMessage(byte[] msg, byte[] signature, byte[] pub, @Nullable String userID) { + return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); + } + + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + return pubkey.length == 65; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + return pubkey.length == 33; + } else { + return false; + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 20-byte address + */ + @Nullable + public static byte[] recoverAddressFromSignature(int recId, + SM2Signature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return computeAddress(pubBytes); + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return ECKey + */ + @Nullable + public static SM2 recoverFromSignature(int recId, SM2Signature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return fromPublicOnly(pubBytes); + } + } + + + + /** + * Returns a copy of this key, but with the public point represented in uncompressed form. + * Normally you would never need this: it's for specialised scenarios or when backwards + * compatibility in encoded form is necessary. + * + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public SM2 decompress() { + if (!pub.isCompressed()) { + return this; + } else { + return new SM2(this.privKey, decompressPoint(pub)); + } + } + + /** + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public SM2 compress() { + if (pub.isCompressed()) { + return this; + } else { + return new SM2(this.privKey, compressPoint(pub)); + } + } + + /** + * Returns true if this key doesn't have access to private key bytes. This may be because it was + * never given any private key bytes to begin with (a watching key). + * + * @return - + */ + public boolean isPubKeyOnly() { + return privKey == null; + } + + /** + * Returns true if this key has access to private key bytes. Does the opposite of {@link + * #isPubKeyOnly()}. + * + * @return - + */ + public boolean hasPrivKey() { + return privKey != null; + } + +// /** +// * Gets the address form of the public key. +// * +// * @return 21-byte address +// */ +// public byte[] getAddress() { +// if (pubKeyHash == null) { +// pubKeyHash = computeAddress(this.pub); +// } +// return pubKeyHash; +// } + + /** + * Generates the NodeID based on this key, that is the public key without first format byte + */ + public byte[] getNodeId() { + if (nodeId == null) { + nodeId = pubBytesWithoutFormat(this.pub); + } + return nodeId; + } + +// /** +// * Gets the encoded public key value. +// * +// * @return 65-byte encoded public key +// */ +// public byte[] getPubKey() { +// return pub.getEncoded(/* compressed */ false); +// } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by + * performing EC point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new ECKey.MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new ECKey.MissingPrivateKeyException(); + } + } + + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 + * bytes, not 64. + * + * @return - + */ + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need + * the private key it is better for security reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) + privKey).getD().toByteArray())); + } + return b.toString(); + } + + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return SM2.verify(data, signature, getPubKey()); + } + + /** + * Verifies the given R/S pair (signature) against a hash using the public key. + * + * @param sigHash - + * @param signature - + * @return - + */ + public boolean verify(byte[] sigHash, SM2Signature signature) { + return SM2.verify(sigHash, signature, getPubKey()); + } + + /** + * Returns true if this pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @return - + */ + public boolean isPubKeyCanonical() { + return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public + * only + * + * @return - + */ + @Nullable + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + SM2 ecKey = (SM2) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) { + return false; + } + return pub == null || pub.equals(ecKey.pub); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + + public static class SM2Signature implements SignatureInterface { + + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the + * signature. + * + * @param r - + * @param s - + */ + public SM2Signature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + public SM2Signature(byte[] r, byte[] s, byte v) { + this.r = new BigInteger(1, r); + this.s = new BigInteger(1,s); + this.v = v; + } + + /** + * t + * + * @return - + */ + private static SM2.SM2Signature fromComponents(byte[] r, byte[] s) { + return new SM2.SM2Signature(new BigInteger(1, r), new BigInteger(1, + s)); + } + + /** + * @param r - + * @param s - + * @param v - + * @return - + */ + public static SM2.SM2Signature fromComponents(byte[] r, byte[] s, byte + v) { + SM2.SM2Signature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public static boolean validateComponents(BigInteger r, BigInteger s, + byte v) { + + if (v != 27 && v != 28) { + return false; + } + + if (isLessThan(r, BigInteger.ONE)) { + return false; + } + if (isLessThan(s, BigInteger.ONE)) { + return false; + } + + if (!isLessThan(r, SM2.SM2_N)) { + return false; + } + return isLessThan(s, SM2.SM2_N); + } + + public static SM2.SM2Signature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) { + throw new RuntimeException("Reached past end of ASN.1 " + + "stream."); + } + ASN1Integer r, s; + try { + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + // OpenSSL deviates from the DER spec by interpreting these + // values as unsigned, though they should not be + // Thus, we always use the positive versions. See: + // http://r6.ca/blog/20111119T211504Z.html + return new SM2.SM2Signature(r.getPositiveValue(), s + .getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) { + try { + decoder.close(); + } catch (IOException x) { + + } + } + } + } + + public boolean validateComponents() { + return validateComponents(r, s, v); + } + + + /** + * @return - + */ + public String toBase64() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + // bytes for S + sigData[0] = v; + System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + } + + + + public byte[] toByteArray() { + final byte fixedV = this.v >= 27 + ? (byte) (this.v - 27) + : this.v; + + return ByteUtil.merge( + ByteUtil.bigIntegerToBytes(this.r, 32), + ByteUtil.bigIntegerToBytes(this.s, 32), + new byte[]{fixedV}); + } + + public String toHex() { + return Hex.toHexString(toByteArray()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SM2.SM2Signature signature = (SM2.SM2Signature) o; + + if (!r.equals(signature.r)) { + return false; + } + return s.equals(signature.s); + } + + @Override + public int hashCode() { + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; + } + } + +} diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java new file mode 100644 index 000000000..9dd689013 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java @@ -0,0 +1,323 @@ +package org.tron.common.crypto.sm2; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.DSA; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SM3Digest; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.ParametersWithID; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.crypto.signers.DSAKCalculator; +import org.spongycastle.crypto.signers.RandomDSAKCalculator; +import org.spongycastle.math.ec.ECConstants; +import org.spongycastle.math.ec.ECFieldElement; +import org.spongycastle.math.ec.ECMultiplier; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.math.ec.FixedPointCombMultiplier; +import org.spongycastle.util.BigIntegers; +import org.tron.common.crypto.SignInterface; + +import javax.annotation.Nullable; + +public class SM2Signer + implements ECConstants +{ + private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); + + private byte[] userID; + + private int curveLength; + private ECDomainParameters ecParams; + private ECPoint pubPoint; + private ECKeyParameters ecKey; + + private SecureRandom random; + + public void init(boolean forSigning, CipherParameters param) + { + CipherParameters baseParam; + + if (param instanceof ParametersWithID) + { + baseParam = ((ParametersWithID)param).getParameters(); + userID = ((ParametersWithID)param).getID(); + } + else + { + baseParam = param; + userID = new byte[0]; + } + + if (forSigning) + { + if (baseParam instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)baseParam; + + ecKey = (ECKeyParameters)rParam.getParameters(); + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), rParam.getRandom()); + } + else + { + ecKey = (ECKeyParameters)baseParam; + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), new SecureRandom()); + } + pubPoint = ecParams.getG().multiply(((ECPrivateKeyParameters)ecKey).getD()).normalize(); + } + else + { + ecKey = (ECKeyParameters)baseParam; + ecParams = ecKey.getParameters(); + pubPoint = ((ECPublicKeyParameters)ecKey).getQ(); + } + + curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; + } + + + /** + * generate the signature for the message + * + * @param message plaintext + * @return + */ + public BigInteger[] generateSignature(byte[] message) + { + byte[] eHash = generateSM3Hash(message); + return generateHashSignature(eHash); + } + /** + * generate the signature for the message + * + * @param message + * @return + */ + + public byte[] generateSM3Hash(byte[] message) + { + //byte[] msg = message.getBytes(); + + SM3Digest digest = new SM3Digest(); + byte[] z = getZ(digest); + + digest.update(z, 0, z.length); + digest.update(message, 0, message.length); + + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * generate the signature from the 32 byte hash + * + * @param hash + * @return + */ + public BigInteger[] generateHashSignature(byte[] hash) + { + if (hash.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "ECDSA signature, not " + hash.length); + } + BigInteger n = ecParams.getN(); + BigInteger e = calculateE(hash); + BigInteger d = ((ECPrivateKeyParameters)ecKey).getD(); + + BigInteger r, s; + + ECMultiplier basePointMultiplier = createBasePointMultiplier(); + + // 5.2.1 Draft RFC: SM2 Public Key Algorithms + do // generate s + { + BigInteger k; + do // generate r + { + // A3 + k = kCalculator.nextK(); + // A4 + ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); + + // A5 + r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); + } + while (r.equals(ZERO) || r.add(k).equals(n)); + + // A6 + BigInteger dPlus1ModN = d.add(ONE).modInverse(n); + + s = k.subtract(r.multiply(d)).mod(n); + s = dPlus1ModN.multiply(s).mod(n); + } + while (s.equals(ZERO)); + + // A7 + return new BigInteger[]{ r, s }; + } + + /** + * verify the message signature + * + * @param message + * @param r + * @param s + * @return + */ + public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, @Nullable String userID) + { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); + +// SM3Digest digest = new SM3Digest(); +// +// byte[] z = getZ(digest); +// +// digest.update(z, 0, z.length); +// digest.update(message, 0, message.length); +// +// byte[] eHash = new byte[digest.getDigestSize()]; +// +// // B3 +// digest.doFinal(eHash, 0); + if(userID != null) { + this.userID = userID.getBytes(); + } + byte[] eHash = generateSM3Hash(message); + + // B4 + BigInteger e = calculateE(eHash); + + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) + { + return false; + } + else + { + // B6 + ECPoint x1y1 = ecParams.getG().multiply(s); + x1y1 = x1y1.add(q.multiply(t)).normalize(); + + // B7 + return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); + } + } + + /** + * verfify the hash signature + * + * @param hash + * @param r + * @param s + * @return + */ + public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) + { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); + + + // B4 + BigInteger e = calculateE(hash); + + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) + { + return false; + } + else + { + // B6 + ECPoint x1y1 = ecParams.getG().multiply(s); + x1y1 = x1y1.add(q.multiply(t)).normalize(); + + // B7 + return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); + } + } + + private byte[] getZ(Digest digest) + { + + //addUserID(digest, userID); + + addFieldElement(digest, ecParams.getCurve().getA()); + addFieldElement(digest, ecParams.getCurve().getB()); + addFieldElement(digest, ecParams.getG().getAffineXCoord()); + addFieldElement(digest, ecParams.getG().getAffineYCoord()); + addFieldElement(digest, pubPoint.getAffineXCoord()); + addFieldElement(digest, pubPoint.getAffineYCoord()); + + byte[] rv = new byte[digest.getDigestSize()]; + + digest.doFinal(rv, 0); + + return rv; + } + + private void addUserID(Digest digest, byte[] userID) + { + int len = userID.length * 8; + digest.update((byte)(len >> 8 & 0xFF)); + digest.update((byte)(len & 0xFF)); + digest.update(userID, 0, userID.length); + } + + private void addFieldElement(Digest digest, ECFieldElement v) + { + byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); + digest.update(p, 0, p.length); + } + + protected ECMultiplier createBasePointMultiplier() + { + return new FixedPointCombMultiplier(); + } + + protected BigInteger calculateE(byte[] message) + { + return new BigInteger(1, message); + } + +} + diff --git a/src/main/java/org/tron/common/crypto/sm2/SM3.java b/src/main/java/org/tron/common/crypto/sm2/SM3.java new file mode 100644 index 000000000..6a72f85f2 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/sm2/SM3.java @@ -0,0 +1,22 @@ +package org.tron.common.crypto.sm2; + +import org.spongycastle.crypto.digests.SM3Digest; +import org.spongycastle.util.encoders.Hex; + +public class SM3 { + public static byte[] hash(String message) { + byte[] msg = Hex.decode(message); + return hash(msg); + } + + public static byte[] hash(byte[] message) { + SM3Digest digest = new SM3Digest(); + digest.update(message,0,message.length); + + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + + return eHash; + } +} diff --git a/src/main/java/org/tron/common/utils/DecodeUtil.java b/src/main/java/org/tron/common/utils/DecodeUtil.java new file mode 100644 index 000000000..62e38ed70 --- /dev/null +++ b/src/main/java/org/tron/common/utils/DecodeUtil.java @@ -0,0 +1,39 @@ +package org.tron.common.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; + +//import static org.tron.core.Constant.ADD_PRE_FIX_BYTE_MAINNET; + +@Slf4j(topic = "Commons") +public class DecodeUtil { + public static final byte ADD_PRE_FIX_BYTE_MAINNET = (byte) 0x41; //41 + address + public static final int ADDRESS_SIZE = 42; + public static byte addressPreFixByte = ADD_PRE_FIX_BYTE_MAINNET; + + public static boolean addressValid(byte[] address) { + if (ArrayUtils.isEmpty(address)) { + logger.warn("Warning: Address is empty !!"); + return false; + } + if (address.length != ADDRESS_SIZE / 2) { + logger.warn( + "Warning: Address length need " + ADDRESS_SIZE + " but " + address.length + + " !!"); + return false; + } + + if (address[0] != addressPreFixByte) { + logger.warn("Warning: Address need prefix with " + addressPreFixByte + " but " + + address[0] + " !!"); + return false; + } + //Other rule; + return true; + } + + public static String createReadableString(byte[] bytes) { + return ByteArray.toHexString(bytes); + } + +} diff --git a/src/main/java/org/tron/common/utils/Hash.java b/src/main/java/org/tron/common/utils/Hash.java new file mode 100644 index 000000000..b82ed7237 --- /dev/null +++ b/src/main/java/org/tron/common/utils/Hash.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.utils; + +import lombok.extern.slf4j.Slf4j; +import org.spongycastle.math.ec.ECPoint; +import org.tron.common.crypto.jce.TronCastleProvider; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; + +import static java.util.Arrays.copyOfRange; +import static org.tron.common.utils.ByteUtil.*; + +@Slf4j(topic = "crypto") +public class Hash { + + public static final byte[] EMPTY_TRIE_HASH; + private static final Provider CRYPTO_PROVIDER; + private static final String HASH_256_ALGORITHM_NAME; + private static final String HASH_512_ALGORITHM_NAME; + private static final String ALGORITHM_NOT_FOUND = "Can't find such algorithm"; + /** + * [0x80] If a string is 0-55 bytes long, the RLP encoding consists of a single byte with value + * 0x80 plus the length of the string followed by the string. The range of the first byte is thus + * [0x80, 0xb7]. + */ + private static final int OFFSET_SHORT_ITEM = 0x80; + + /** + * [0xb7] If a string is more than 55 bytes long, the RLP encoding consists of a single byte with + * value 0xb7 plus the length of the length of the string in binary form, followed by the length + * of the string, followed by the string. For example, a length-1024 string would be encoded as + * \xb9\x04\x00 followed by the string. The range of the first byte is thus [0xb8, 0xbf]. + */ + private static final int OFFSET_LONG_ITEM = 0xb7; + + /** + * Reason for threshold according to Vitalik Buterin: - 56 bytes maximizes the benefit of both + * options - if we went with 60 then we would have only had 4 slots for long strings so RLP would + * not have been able to store objects above 4gb - if we went with 48 then RLP would be fine for + * 2^128 space, but that's way too much - so 56 and 2^64 space seems like the right place to put + * the cutoff - also, that's where Bitcoin's varint does the cutof + */ + private static final int SIZE_THRESHOLD = 56; + + static { + Security.addProvider(TronCastleProvider.getInstance()); + CRYPTO_PROVIDER = Security.getProvider("SC"); + HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256"; + HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512"; + EMPTY_TRIE_HASH = sha3(encodeElement(EMPTY_BYTE_ARRAY)); + } + + public static byte[] sha3(byte[] input) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + + } + + public static byte[] sha3(byte[] input1, byte[] input2) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input1, 0, input1.length); + digest.update(input2, 0, input2.length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + } + + /** + * hashing chunk of the data + * + * @param input - data for hash + * @param start - start of hashing chunk + * @param length - length of hashing chunk + * @return - keccak hash of the chunk + */ + public static byte[] sha3(byte[] input, int start, int length) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input, start, length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + } + + public static byte[] sha512(byte[] input) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_512_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + } + + + public static byte[] encodeElement(byte[] srcData) { + + // [0x80] + if (isNullOrZeroArray(srcData)) { + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + + // [0x00] + } else if (isSingleZero(srcData)) { + return srcData; + + // [0x01, 0x7f] - single byte, that byte is its own RLP encoding + } else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + return srcData; + + // [0x80, 0xb7], 0 - 55 bytes + } else if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + // [0xb8, 0xbf], 56+ bytes + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte lengthOfLength = 0; + while (tmpLength != 0) { + ++lengthOfLength; + tmpLength = tmpLength >> 8; + } + + // set length Of length at first byte + byte[] data = new byte[1 + lengthOfLength + srcData.length]; + data[0] = (byte) (OFFSET_LONG_ITEM + lengthOfLength); + + // copy length after first byte + tmpLength = srcData.length; + for (int i = lengthOfLength; i > 0; --i) { + data[i] = (byte) (tmpLength & 0xFF); + tmpLength = tmpLength >> 8; + } + + // at last copy the number bytes after its length + System.arraycopy(srcData, 0, data, 1 + lengthOfLength, srcData.length); + + return data; + } + } + + public static byte[] computeAddress(ECPoint pubPoint) { + return computeAddress(pubPoint.getEncoded(/* uncompressed */ false)); + } + + public static byte[] computeAddress(byte[] pubBytes) { + return sha3omit12( + Arrays.copyOfRange(pubBytes, 1, pubBytes.length)); + } + + /** + * Calculates RIGTMOST160(SHA3(input)). This is used in address calculations. * + * + * @param input - data + * @return - add_pre_fix + 20 right bytes of the hash keccak of the data + */ + public static byte[] sha3omit12(byte[] input) { + byte[] hash = Hash.sha3(input); + byte[] address = copyOfRange(hash, 11, hash.length); + address[0] = DecodeUtil.addressPreFixByte; + return address; + } +} From 4007cb2094098246cebd8b773d307874aad27f42 Mon Sep 17 00:00:00 2001 From: zhenping Date: Thu, 19 Dec 2019 19:18:53 +0800 Subject: [PATCH 215/445] add sm2/sm3 test demo Signed-off-by: zhenping --- .../org/tron/common/crypto/Sha256Hash.java | 1 + .../tron/demo/TransactionSignDemoForSM2.java | 160 ++++++++++++++++++ src/main/resources/config.conf | 2 +- 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/tron/demo/TransactionSignDemoForSM2.java diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java index 02e0c3e09..a41b382e8 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Hash.java @@ -42,6 +42,7 @@ */ public class Sha256Hash implements Serializable, Comparable { + public static final int LENGTH = 32; // bytes public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java new file mode 100644 index 000000000..9cadf7f1a --- /dev/null +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -0,0 +1,160 @@ +package org.tron.demo; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.TransactionExtention; + +import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.ByteArray; +import org.tron.core.exception.CancelException; +import org.tron.protos.Contract; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.Transaction; +import org.tron.walletserver.WalletApi; + +import java.util.Arrays; + +public class TransactionSignDemoForSM2 { + + public static Transaction setReference(Transaction transaction, Block newestBlock) { + long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); + byte[] blockHash = getBlockHash(newestBlock).getBytes(); + byte[] refBlockNum = ByteArray.fromLong(blockHeight); + Transaction.raw rawData = transaction.getRawData().toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); + return transaction.toBuilder().setRawData(rawData).build(); + } + + public static Sha256Hash getBlockHash(Block block) { + return Sha256Hash.of(block.getBlockHeader().getRawData().toByteArray()); + } + + public static String getTransactionHash(Transaction transaction) { + String txid = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + return txid; + } + + + public static Transaction createTransaction(byte[] from, byte[] to, long amount) { + Transaction.Builder transactionBuilder = Transaction.newBuilder(); + Block newestBlock = WalletApi.getBlock(-1); + + Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract + .newBuilder(); + transferContractBuilder.setAmount(amount); + ByteString bsTo = ByteString.copyFrom(to); + ByteString bsOwner = ByteString.copyFrom(from); + transferContractBuilder.setToAddress(bsTo); + transferContractBuilder.setOwnerAddress(bsOwner); + try { + Any any = Any.pack(transferContractBuilder.build()); + contractBuilder.setParameter(any); + } catch (Exception e) { + return null; + } + contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); + transactionBuilder.getRawDataBuilder().addContract(contractBuilder) + .setTimestamp(System.currentTimeMillis()) + .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + Transaction transaction = transactionBuilder.build(); + Transaction refTransaction = setReference(transaction, newestBlock); + return refTransaction; + } + + + private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) + throws InvalidProtocolBufferException { + SM2 ecKey = SM2.fromPrivate(privateKey); + Transaction transaction1 = Transaction.parseFrom(transaction); + byte[] rawdata = transaction1.getRawData().toByteArray(); + byte[] hash = ecKey.hash(rawdata); + byte[] sign = ecKey.sign(hash).toByteArray(); + return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); + } + + private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) + throws InvalidProtocolBufferException { + SM2 ecKey = SM2.fromPrivate(privateKey); + Transaction transaction1 = Transaction.parseFrom(transaction); + byte[] rawdata = transaction1.getRawData().toByteArray(); + byte[] hash = ecKey.hash(rawdata); + byte[] sign = ecKey.sign(hash).toByteArray(); + return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); + } + + private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException { + return WalletApi.broadcastTransaction(transactionBytes); + } + + private static void base58checkToHexString() { + String base58check = "TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"; + String hexString = ByteArray.toHexString(WalletApi.decodeFromBase58Check(base58check)); + System.out.println(hexString); + } + + private static void hexStringTobase58check() { + String hexString = "414948c2e8a756d9437037dcd8c7e0c73d560ca38d"; + String base58check = WalletApi.encode58Check(ByteArray.fromHexString(hexString)); + System.out.println(base58check); + } + + public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { + String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; + byte[] privateBytes = ByteArray.fromHexString(privateStr); + SM2 ecKey = SM2.fromPrivate(privateBytes); + byte[] from = ecKey.getAddress(); + byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); + long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + Transaction transaction = createTransaction(from, to, amount); + byte[] transactionBytes = transaction.toByteArray(); + + /* + //sign a transaction + Transaction transaction1 = TransactionUtils.sign(transaction, ecKey); + //get byte transaction + byte[] transaction2 = transaction1.toByteArray(); + System.out.println("transaction2 ::::: " + ByteArray.toHexString(transaction2)); + + //sign a transaction in byte format and return a Transaction object + Transaction transaction3 = signTransaction2Object(transactionBytes, privateBytes); + System.out.println("transaction3 ::::: " + ByteArray.toHexString(transaction3.toByteArray())); + */ + + //sign a transaction in byte format and return a Transaction in byte format + byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); + System.out.println("transaction4 ::::: " + ByteArray.toHexString(transaction4)); + Transaction transactionSigned; + if (WalletApi.getRpcVersion() == 2) { + TransactionExtention transactionExtention = WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); + if (transactionExtention == null) { + System.out.println("transactionExtention is null"); + return; + } + Return ret = transactionExtention.getResult(); + if (!ret.getResult()) { + System.out.println("Code = " + ret.getCode()); + System.out.println("Message = " + ret.getMessage().toStringUtf8()); + return; + } + System.out.println( + "Receive txid = " + ByteArray.toHexString(transactionExtention.getTxid().toByteArray())); + transactionSigned = transactionExtention.getTransaction(); + } else { + transactionSigned = WalletApi.signTransactionByApi(transaction, ecKey.getPrivKeyBytes()); + } + byte[] transaction5 = transactionSigned.toByteArray(); + System.out.println("transaction5 ::::: " + ByteArray.toHexString(transaction5)); + if (!Arrays.equals(transaction4, transaction5)){ + System.out.println("transaction4 is not equals to transaction5 !!!!!"); + } + boolean result = broadcast(transaction4); + + System.out.println(result); + } +} diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index fcf33ca1d..f722bf334 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,7 +4,7 @@ net { fullnode = { ip.list = [ - "127.0.0.1:50051" + "47.89.180.128:50051" ] } From 0be88566b63f3d2793efe3114ff03375bd134005 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 20 Dec 2019 13:42:34 +0800 Subject: [PATCH 216/445] add rpc GetNewShieldedAddress --- api/api.proto | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/api/api.proto b/api/api.proto index fac89a567..d47005960 100644 --- a/api/api.proto +++ b/api/api.proto @@ -664,6 +664,9 @@ service Wallet { rpc GetDiversifier (EmptyMessage) returns (DiversifierMessage) { } + rpc GetNewShieldedAddress (EmptyMessage) returns (ShieldedAddressInfo) { + } + rpc GetZenPaymentAddress (IncomingViewingKeyDiversifierMessage) returns (PaymentAddressMessage) { } @@ -1264,6 +1267,18 @@ message PaymentAddressMessage { string payment_address = 3; } +message ShieldedAddressInfo{ + bytes sk = 1; + bytes ask = 2; + bytes nsk = 3; + bytes ovk = 4; + bytes ak = 5; + bytes nk = 6; + bytes ivk = 7; + bytes d = 8; + bytes pkD = 9; +} + message NoteParameters { bytes ak = 1; bytes nk = 2; From 13288e7e986b1cef7f1b04d9bcd664316a59b0b6 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 20 Dec 2019 17:28:20 +0800 Subject: [PATCH 217/445] add payment_address into GetNewShieldedAddress --- api/api.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/api/api.proto b/api/api.proto index d47005960..a2c64e0a8 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1277,6 +1277,7 @@ message ShieldedAddressInfo{ bytes ivk = 7; bytes d = 8; bytes pkD = 9; + string payment_address = 10; } message NoteParameters { From e011e8a2b0ac7891f467c59d10efd0500b814ee3 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 23 Dec 2019 16:40:17 +0800 Subject: [PATCH 218/445] feat(market): add rpc for market --- .../common/utils/HttpSelfFormatFieldName.java | 18 ++ .../java/org/tron/common/utils/Utils.java | 12 ++ src/main/java/org/tron/walletcli/Client.java | 179 +++++++++++++++--- .../org/tron/walletcli/WalletApiWrapper.java | 32 ++++ .../org/tron/walletserver/GrpcClient.java | 41 ++++ .../java/org/tron/walletserver/WalletApi.java | 46 +++++ src/main/protos/api/api.proto | 27 ++- src/main/protos/core/Contract.proto | 15 ++ src/main/protos/core/Tron.proto | 54 +++++- 9 files changed, 396 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index 23b20561f..61ae52870 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -215,6 +215,24 @@ public class HttpSelfFormatFieldName { NameFieldNameMap.put("protocol.Transaction.Contract.ContractName", 1); //TransactionInfo NameFieldNameMap.put("protocol.TransactionInfo.resMessage", 1); + + // MarketSellAssetContract + AddressFieldNameMap.put("protocol.MarketSellAssetContract.owner_address", 1); + NameFieldNameMap.put("protocol.MarketSellAssetContract.sell_token_id", 1); + NameFieldNameMap.put("protocol.MarketSellAssetContract.buy_token_id", 1); + + // MarketOrder + AddressFieldNameMap.put("protocol.MarketOrder.owner_address", 1); + NameFieldNameMap.put("protocol.MarketOrder.sell_token_id", 1); + NameFieldNameMap.put("protocol.MarketOrder.buy_token_id", 1); + + // MarketOrderPair + NameFieldNameMap.put("protocol.MarketOrderPair.sell_token_id", 1); + NameFieldNameMap.put("protocol.MarketOrderPair.buy_token_id", 1); + + // MarketPriceList + NameFieldNameMap.put("protocol.MarketPriceList.sell_token_id", 1); + NameFieldNameMap.put("protocol.MarketPriceList.buy_token_id", 1); } public static boolean isAddressFormat(final String name) { diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 7f2b26d75..1a316e71a 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -479,6 +479,18 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean contractJson = JSONObject .parseObject(JsonFormat.printToString(updateBrokerageContract, selfType)); break; + case MarketSellAssetContract: + MarketSellAssetContract marketSellAssetContract = contract.getParameter() + .unpack(MarketSellAssetContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(marketSellAssetContract, selfType)); + break; + case MarketCancelOrderContract: + MarketCancelOrderContract marketCancelOrderContract = contract.getParameter() + .unpack(MarketCancelOrderContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(marketCancelOrderContract, selfType)); + break; // todo add other contract default: } diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 4703b1503..a934a99b1 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -48,6 +48,7 @@ public class Client { private WalletApiWrapper walletApiWrapper = new WalletApiWrapper(); private static int retryTime = 3; + // note: this is sorted by alpha private static String[] commandHelp = { "AddTransactionSign", "ApproveProposal", @@ -75,16 +76,17 @@ public class Client { "GetAccountNet", "GetAccountResource", "GetAddress", + "GetAkFromAsk", "GetAssetIssueByAccount", "GetAssetIssueById", "GetAssetIssueByName", "GetAssetIssueListByName", - "GetAkFromAsk", "GetBalance", "GetBlock", "GetBlockById", "GetBlockByLatestNum", "GetBlockByLimitNext", + "GetBrokerage", "GetChainParameters", "GetContract contractAddress", "GetDelegatedResource", @@ -93,19 +95,22 @@ public class Client { "GetExchange", "GetExpandedSpendingKey", "GetIncomingViewingKey", - "GetNkFromNsk", + "GetMarketOrderByAccount", + "GetMarketPriceByPair", "GetNextMaintenanceTime", + "GetNkFromNsk", + "GetProposal", + "GetReward", "GetShieldedNullifier", "GetSpendingKey", - "GetProposal", "GetTotalTransaction", "GetTransactionApprovedList", "GetTransactionById", "GetTransactionCountByBlockNum", "GetTransactionInfoById", + "GetTransactionSignWeight", "GetTransactionsFromThis", "GetTransactionsToThis", - "GetTransactionSignWeight", "ImportShieldedAddress", "ImportWallet", "ImportWalletByBase64", @@ -114,14 +119,16 @@ public class Client { "ListExchanges", "ListExchangesPaginated", "ListNodes", - "ListShieldedAddress", - "ListShieldedNote", "ListProposals", "ListProposalsPaginated", + "ListShieldedAddress", + "ListShieldedNote", "ListWitnesses", + "LoadShieldedWallet", "Login", "Logout", - "LoadShieldedWallet", + "MarketCancelOrder", + "MarketSellAsset", "ParticipateAssetIssue", "RegisterWallet", "ResetShieldedNote", @@ -133,23 +140,22 @@ public class Client { "SendShieldedCoinWithoutAsk", "SetAccountId", "TransferAsset", - "TriggerContract contractAddress method args isHex fee_limit value", "TriggerConstantContract contractAddress method args isHex", + "TriggerContract contractAddress method args isHex fee_limit value", "UnfreezeAsset", "UnfreezeBalance", "UpdateAccount", + "UpdateAccountPermission", "UpdateAsset", + "UpdateBrokerage", "UpdateEnergyLimit contract_address energy_limit", "UpdateSetting contract_address consume_user_resource_percent", "UpdateWitness", - "UpdateAccountPermission", "VoteWitness", "WithdrawBalance", - "UpdateBrokerage", - "GetReward", - "GetBrokerage" }; + // note: this is sorted by alpha private static String[] commandList = { "AddTransactionSign", "ApproveProposal", @@ -177,16 +183,17 @@ public class Client { "GetAccountNet", "GetAccountResource", "GetAddress", + "GetAkFromAsk", "GetAssetIssueByAccount", "GetAssetIssueById", "GetAssetIssueByName", "GetAssetIssueListByName", - "GetAkFromAsk", "GetBalance", "GetBlock", "GetBlockById", "GetBlockByLatestNum", "GetBlockByLimitNext", + "GetBrokerage", "GetChainParameters", "GetContract", "GetDelegatedResource", @@ -195,19 +202,22 @@ public class Client { "GetExchange", "GetExpandedSpendingKey", "GetIncomingViewingKey", - "GetNkFromNsk", + "GetMarketOrderByAccount", + "GetMarketPriceByPair", "GetNextMaintenanceTime", + "GetNkFromNsk", + "GetProposal", + "GetReward", "GetShieldedNullifier", "GetSpendingKey", - "GetProposal", "GetTotalTransaction", "GetTransactionApprovedList", "GetTransactionById", "GetTransactionCountByBlockNum", "GetTransactionInfoById", + "GetTransactionSignWeight", "GetTransactionsFromThis", "GetTransactionsToThis", - "GetTransactionSignWeight", "Help", "ImportShieldedAddress", "ImportWallet", @@ -217,14 +227,16 @@ public class Client { "ListExchanges", "ListExchangesPaginated", "ListNodes", - "ListShieldedAddress", - "ListShieldedNote", "ListProposals", "ListProposalsPaginated", + "ListShieldedAddress", + "ListShieldedNote", "ListWitnesses", + "LoadShieldedWallet", "Login", "Logout", - "LoadShieldedWallet", + "MarketCancelOrder", + "MarketSellAsset", "ParticipateAssetIssue", "RegisterWallet", "ResetShieldedNote", @@ -236,21 +248,19 @@ public class Client { "SendShieldedCoinWithoutAsk", "SetAccountId", "TransferAsset", - "TriggerContract", "TriggerConstantContract", + "TriggerContract", "UnfreezeAsset", "UnfreezeBalance", "UpdateAccount", + "UpdateAccountPermission", "UpdateAsset", + "UpdateBrokerage", "UpdateEnergyLimit", "UpdateSetting", "UpdateWitness", - "UpdateAccountPermission", "VoteWitness", "WithdrawBalance", - "UpdateBrokerage", - "GetReward", - "GetBrokerage" }; private byte[] inputPrivateKey() throws IOException { @@ -2757,6 +2767,109 @@ private void importShieldedAddress() throws CipherException, IOException { } } + private void marketSellAsset(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || parameters.length != 5) { + System.out.println("Using MarketSellAsset command needs 5 parameters like: "); + System.out.println( + "MarketSellAsset ownerAddress sellTokenId sellTokenQuantity buyTokenId buyTokenQuantity"); + return; + } + + int index = 0; + byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); + if (ownerAddress == null) { + System.out.println("Invalid OwnerAddress."); + return; + } + + byte[] sellTokenId = parameters[index++].getBytes(); + long sellTokenQuantity = Long.parseLong(parameters[index++]); + byte[] buyTokenId = parameters[index++].getBytes(); + long buyTokenQuantity = Long.parseLong(parameters[index++]); + + boolean result = walletApiWrapper + .marketSellAsset(ownerAddress, sellTokenId, sellTokenQuantity, buyTokenId, + buyTokenQuantity); + if (result) { + System.out.println("MarketSellAsset successful !!!"); + } else { + System.out.println("MarketSellAsset failed !!!"); + } + } + + + private void marketCancelOrder(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || parameters.length != 2) { + System.out.println("Using MarketCancelOrder command needs 2 parameters like: "); + System.out.println( + "MarketCancelOrder ownerAddress orderId"); + return; + } + + int index = 0; + byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); + if (ownerAddress == null) { + System.out.println("Invalid OwnerAddress."); + return; + } + + byte[] orderId = ByteArray.fromHexString(parameters[index++]); + + boolean result = walletApiWrapper + .marketCancelOrder(ownerAddress, orderId); + if (result) { + System.out.println("MarketCancelOrder successful !!!"); + } else { + System.out.println("MarketCancelOrder failed !!!"); + } + } + + + private void getMarketOrderByAccount(String[] parameters) { + if (parameters == null || parameters.length != 1) { + System.out.println("Using GetMarketOrderByAccount command needs 1 parameters like: "); + System.out.println( + "GetMarketOrderByAccount ownerAddress"); + return; + } + + int index = 0; + byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); + if (ownerAddress == null) { + System.out.println("Invalid OwnerAddress."); + return; + } + + Optional marketOrderList = walletApiWrapper.getMarketOrderByAccount(ownerAddress); + if (!marketOrderList.isPresent()) { + System.out.println("GetMarketOrderByAccount failed !!!"); + } else { + System.out.println(Utils.formatMessageString(marketOrderList.get())); + } + } + + private void getMarketPriceByPair (String[] parameters) { + if (parameters == null || parameters.length != 2) { + System.out.println("Using GetMarketPriceByPair command needs 2 parameters like: "); + System.out.println( + "GetMarketPriceByPair ownerAddress"); + return; + } + + int index = 0; + byte[] sellTokenId = parameters[index++].getBytes(); + byte[] buyTokenId = parameters[index++].getBytes(); + + Optional marketPriceList = walletApiWrapper.GetMarketPriceByPair(sellTokenId, buyTokenId); + if (!marketPriceList.isPresent()) { + System.out.println("GetMarketPriceByPair failed !!!"); + } else { + System.out.println(Utils.formatMessageString(marketPriceList.get())); + } + } + private void create2(String[] parameters) { if (parameters == null || parameters.length != 3) { System.out.println("Using create2 command needs 3 parameters like: "); @@ -3282,6 +3395,22 @@ private void run() { create2(parameters); break; } + case "marketsellasset": { + marketSellAsset(parameters); + break; + } + case "marketcancelorder": { + marketCancelOrder(parameters); + break; + } + case "getmarketorderbyaccount": { + getMarketOrderByAccount(parameters); + break; + } + case "getmarketpricebypair": { + getMarketPriceByPair(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); @@ -3322,7 +3451,7 @@ private void getChainParameters() { ChainParameters chainParameters = result.get(); System.out.println(Utils.formatMessageString(chainParameters)); } else { - System.out.println("List witnesses failed !!"); + System.out.println("GetChainParameters failed !!"); } } diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 0443051d0..90c91e9e2 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1179,4 +1179,36 @@ public GrpcAPI.NumberMessage getReward(byte[] ownerAddress) { public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } + + public boolean marketSellAsset( + byte[] owner, + byte[] sellTokenId, + long sellTokenQuantity, + byte[] buyTokenId, + long buyTokenQuantity) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: updateSetting failed, Please login first !!"); + return false; + } + return wallet.marketSellAsset(owner, sellTokenId, sellTokenQuantity, buyTokenId, buyTokenQuantity); + } + + public boolean marketCancelOrder(byte[] owner, byte[] orderId) + throws IOException, CipherException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: updateSetting failed, Please login first !!"); + return false; + } + return wallet.marketCancelOrder(owner, orderId); + } + + public Optional getMarketOrderByAccount(byte[] address) { + return WalletApi.getMarketOrderByAccount(address); + } + + public Optional GetMarketPriceByPair( + byte[] sellTokenId, byte[] buyTokenId) { + return WalletApi.GetMarketPriceByPair(sellTokenId, buyTokenId); + } } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index f04066b8c..35aee3494 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -894,4 +894,45 @@ public NumberMessage getBrokerage(byte[] address) { return blockingStubFull.getBrokerageInfo(bytesMessage); } } + + public TransactionExtention marketSellAsset(Contract.MarketSellAssetContract request) { + if (blockingStubSolidity != null) { + return blockingStubSolidity.marketSellAsset(request); + } else { + return blockingStubFull.marketSellAsset(request); + } + } + + public TransactionExtention marketCancelOrder(Contract.MarketCancelOrderContract request) { + if (blockingStubSolidity != null) { + return blockingStubSolidity.marketCancelOrder(request); + } else { + return blockingStubFull.marketCancelOrder(request); + } + } + + public Optional getMarketOrderByAccount(byte[] address) { + ByteString addressBS = ByteString.copyFrom(address); + Account request = Account.newBuilder().setAddress(addressBS).build(); + + MarketOrderList marketOrderList = blockingStubFull.getMarketOrderByAccount(request); + return Optional.ofNullable(marketOrderList); + } + + public Optional GetMarketPriceByPair(byte[] sellTokenId, byte[] buyTokenId) { + MarketOrderPair request = + MarketOrderPair.newBuilder() + .setSellTokenId(ByteString.copyFrom(sellTokenId)) + .setBuyTokenId(ByteString.copyFrom(buyTokenId)) + .build(); + + MarketPriceList marketPriceList; + if (blockingStubSolidity != null) { + marketPriceList =blockingStubSolidity.getMarketPriceByPair(request); + } else { + marketPriceList = blockingStubFull.getMarketPriceByPair(request); + } + return Optional.ofNullable(marketPriceList); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index d0fe50cfd..5b2e01226 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2381,4 +2381,50 @@ public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + public boolean marketSellAsset( + byte[] owner, + byte[] sellTokenId, + long sellTokenQuantity, + byte[] buyTokenId, + long buyTokenQuantity) + throws IOException, CipherException, CancelException { + if (owner == null) { + owner = getAddress(); + } + + Contract.MarketSellAssetContract.Builder builder = + Contract.MarketSellAssetContract.newBuilder(); + builder + .setOwnerAddress(ByteString.copyFrom(owner)) + .setSellTokenId(ByteString.copyFrom(sellTokenId)) + .setSellTokenQuantity(sellTokenQuantity) + .setBuyTokenId(ByteString.copyFrom(buyTokenId)) + .setBuyTokenQuantity(buyTokenQuantity); + + TransactionExtention transactionExtention = rpcCli.marketSellAsset(builder.build()); + return processTransactionExtention(transactionExtention); + } + + public boolean marketCancelOrder(byte[] owner, byte[] orderId) + throws IOException, CipherException, CancelException { + if (owner == null) { + owner = getAddress(); + } + + Contract.MarketCancelOrderContract.Builder builder = + Contract.MarketCancelOrderContract.newBuilder(); + builder.setOwnerAddress(ByteString.copyFrom(owner)).setOrderId(ByteString.copyFrom(orderId)); + + TransactionExtention transactionExtention = rpcCli.marketCancelOrder(builder.build()); + return processTransactionExtention(transactionExtention); + } + + public static Optional getMarketOrderByAccount(byte[] address) { + return rpcCli.getMarketOrderByAccount(address); + } + + public static Optional GetMarketPriceByPair( + byte[] sellTokenId, byte[] buyTokenId) { + return rpcCli.GetMarketPriceByPair(sellTokenId, buyTokenId); + } } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 8dc653748..e236b0c67 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -684,6 +684,7 @@ service Wallet { rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; + // end for shiededTransaction rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { }; @@ -692,9 +693,19 @@ service Wallet { }; rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { - }; - // end for shiededTransaction + + rpc MarketSellAsset (MarketSellAssetContract) returns (TransactionExtention) { + } + + rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { + } + + rpc GetMarketOrderByAccount (Account) returns (MarketOrderList) { + } + + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + } }; service WalletSolidity { @@ -847,6 +858,18 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; + + rpc MarketSellAsset (MarketSellAssetContract) returns (TransactionExtention) { + } + + rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { + } + + rpc GetMarketOrderByAccount (Account) returns (MarketOrderList) { + } + + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + } }; service WalletExtension { diff --git a/src/main/protos/core/Contract.proto b/src/main/protos/core/Contract.proto index f30597836..3bff73a85 100644 --- a/src/main/protos/core/Contract.proto +++ b/src/main/protos/core/Contract.proto @@ -340,3 +340,18 @@ message ShieldedTransferContract { int64 to_amount = 7; // the amount to transparent to_address } // end shielded transaction + +message MarketSellAssetContract { + bytes owner_address = 1; + bytes sell_token_id = 4; + int64 sell_token_quantity = 6; + bytes buy_token_id = 5; + int64 buy_token_quantity = 7;//min to receive +} + +message MarketCancelOrderContract { + bytes owner_address = 1; + // int64 sell_token_id = 2; + // bytes buy_token_id = 3; + bytes order_id = 4; +} diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index d70672297..23215b0e1 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -275,6 +275,8 @@ message Transaction { ClearABIContract = 48; UpdateBrokerageContract = 49; ShieldedTransferContract = 51; + MarketSellAssetContract = 60; + MarketCancelOrderContract = 61; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -678,4 +680,54 @@ message NodeInfo { string stackTrace = 7; } } -} \ No newline at end of file +} + +//market +message MarketOrder { + bytes order_id = 1; + bytes owner_address = 2; + int64 create_time = 3; + bytes sell_token_id = 4; + int64 sell_token_quantity = 6; + bytes buy_token_id = 5; + int64 buy_token_quantity = 7;//min to receive + int64 sell_token_quantity_remain = 9; + enum State { + ACTIVE = 0; + INACTIVE = 1; + CANCELED = 2; + } + State state = 10; +} + +message MarketOrderList { + repeated MarketOrder orders = 1; +} + +message MarketOrderPair{ + bytes sell_token_id = 4; + bytes buy_token_id = 5; +} + +message MarketAccountOrder { + bytes owner_address = 1; + // repeated MarketOrder orders = 2; + repeated bytes orders = 2; + int64 count = 3; +} + +message MarketPriceList { + bytes sell_token_id = 1; + bytes buy_token_id = 2; + + message MarketPrice { + int64 sell_token_quantity = 1; + int64 buy_token_quantity = 2; + } + repeated MarketPrice prices = 5; +} + + +message MarketOrderIdList { + repeated bytes orders = 1; +} From b9511d7c820fa287748a114809ae1825abbde190 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 24 Dec 2019 15:55:41 +0800 Subject: [PATCH 219/445] save sm2 transaction test local --- config.conf | 18 ++++++ .../tron/common/utils/TransactionUtils.java | 13 ++++ .../org/tron/demo/TransactionSignDemo.java | 4 +- .../tron/demo/TransactionSignDemoForSM2.java | 41 +----------- src/main/java/org/tron/keystore/Wallet.java | 63 ++++++++++++++++++- .../java/org/tron/walletserver/WalletApi.java | 13 +++- src/main/resources/config.conf | 2 +- 7 files changed, 109 insertions(+), 45 deletions(-) create mode 100644 config.conf diff --git a/config.conf b/config.conf new file mode 100644 index 000000000..7d9982746 --- /dev/null +++ b/config.conf @@ -0,0 +1,18 @@ +net { + type = mainnet +} + +fullnode = { + ip.list = [ + "47.89.189.124:50055", + "47.89.178.193:50055" + ] +} + +#soliditynode = { +# ip.list = [ +# "127.0.0.1:50052" +# ] +#} + +RPC_version = 2 diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index bc54d14e9..d6b15afea 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -19,6 +19,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -176,6 +177,18 @@ public static Transaction sign(Transaction transaction, ECKey myKey) { transaction = transactionBuilderSigned.build(); return transaction; } + public static Transaction sign(Transaction transaction, SM2 myKey) { + Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); + byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); + + //System.out.println("Sign address: " + WalletApi.encode58Check(myKey.getAddress())); + + SM2.SM2Signature signature = myKey.sign(hash); + ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); + transactionBuilderSigned.addSignature(bsSign); + transaction = transactionBuilderSigned.build(); + return transaction; + } public static Transaction setTimestamp(Transaction transaction) { long currentTime = System.currentTimeMillis();//*1000000 + System.nanoTime()%1000000; diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 1ecbf5792..debd911d0 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -104,11 +104,11 @@ private static void hexStringTobase58check() { } public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; + String privateStr = "040927135ad6104ef1724084c8b9b018df275c10be97373f0536bf0e3b4c3f6a"; byte[] privateBytes = ByteArray.fromHexString(privateStr); ECKey ecKey = ECKey.fromPrivate(privateBytes); byte[] from = ecKey.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); + byte[] to = WalletApi.decodeFromBase58Check("TJ4DU11WXj6gzTvnxLac4eoPppyYJZE2UE"); long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 9cadf7f1a..2e2315b32 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -105,54 +105,19 @@ private static void hexStringTobase58check() { } public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; + String privateStr = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; byte[] privateBytes = ByteArray.fromHexString(privateStr); SM2 ecKey = SM2.fromPrivate(privateBytes); byte[] from = ecKey.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); + byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); - /* - //sign a transaction - Transaction transaction1 = TransactionUtils.sign(transaction, ecKey); - //get byte transaction - byte[] transaction2 = transaction1.toByteArray(); - System.out.println("transaction2 ::::: " + ByteArray.toHexString(transaction2)); - - //sign a transaction in byte format and return a Transaction object - Transaction transaction3 = signTransaction2Object(transactionBytes, privateBytes); - System.out.println("transaction3 ::::: " + ByteArray.toHexString(transaction3.toByteArray())); - */ //sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); - System.out.println("transaction4 ::::: " + ByteArray.toHexString(transaction4)); - Transaction transactionSigned; - if (WalletApi.getRpcVersion() == 2) { - TransactionExtention transactionExtention = WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); - if (transactionExtention == null) { - System.out.println("transactionExtention is null"); - return; - } - Return ret = transactionExtention.getResult(); - if (!ret.getResult()) { - System.out.println("Code = " + ret.getCode()); - System.out.println("Message = " + ret.getMessage().toStringUtf8()); - return; - } - System.out.println( - "Receive txid = " + ByteArray.toHexString(transactionExtention.getTxid().toByteArray())); - transactionSigned = transactionExtention.getTransaction(); - } else { - transactionSigned = WalletApi.signTransactionByApi(transaction, ecKey.getPrivKeyBytes()); - } - byte[] transaction5 = transactionSigned.toByteArray(); - System.out.println("transaction5 ::::: " + ByteArray.toHexString(transaction5)); - if (!Arrays.equals(transaction4, transaction5)){ - System.out.println("transaction4 is not equals to transaction5 !!!!!"); - } + boolean result = broadcast(transaction4); System.out.println(result); diff --git a/src/main/java/org/tron/keystore/Wallet.java b/src/main/java/org/tron/keystore/Wallet.java index 55e15ce55..f43a8a32a 100644 --- a/src/main/java/org/tron/keystore/Wallet.java +++ b/src/main/java/org/tron/keystore/Wallet.java @@ -6,6 +6,7 @@ import org.bouncycastle.crypto.params.KeyParameter; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CipherException; import org.tron.walletserver.WalletApi; @@ -78,12 +79,33 @@ public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); } + public static WalletFile create(byte[] password, SM2 ecKeyPair, int n, int p) + throws CipherException { + byte[] salt = generateRandomBytes(32); + + byte[] derivedKey = generateDerivedScryptKey(password, salt, n, R, p, DKLEN); + + byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); + byte[] iv = generateRandomBytes(16); + + byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes(); + + byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, + privateKeyBytes); + + byte[] mac = generateMac(derivedKey, cipherText); + + return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); + } public static WalletFile createStandard(byte[] password, ECKey ecKeyPair) throws CipherException { return create(password, ecKeyPair, N_STANDARD, P_STANDARD); } - + public static WalletFile createStandard(byte[] password, SM2 ecKeyPair) + throws CipherException { + return create(password, ecKeyPair, N_STANDARD, P_STANDARD); + } public static WalletFile createLight(byte[] password, ECKey ecKeyPair) throws CipherException { return create(password, ecKeyPair, N_LIGHT, P_LIGHT); @@ -121,6 +143,38 @@ private static WalletFile createWalletFile( return walletFile; } + private static WalletFile createWalletFile( + SM2 ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, + int n, int p) { + + WalletFile walletFile = new WalletFile(); + walletFile.setAddress(WalletApi.encode58Check(ecKeyPair.getAddress())); + + WalletFile.Crypto crypto = new WalletFile.Crypto(); + crypto.setCipher(CIPHER); + crypto.setCiphertext(ByteArray.toHexString(cipherText)); + walletFile.setCrypto(crypto); + + WalletFile.CipherParams cipherParams = new WalletFile.CipherParams(); + cipherParams.setIv(ByteArray.toHexString(iv)); + crypto.setCipherparams(cipherParams); + + crypto.setKdf(SCRYPT); + WalletFile.ScryptKdfParams kdfParams = new WalletFile.ScryptKdfParams(); + kdfParams.setDklen(DKLEN); + kdfParams.setN(n); + kdfParams.setP(p); + kdfParams.setR(R); + kdfParams.setSalt(ByteArray.toHexString(salt)); + crypto.setKdfparams(kdfParams); + + crypto.setMac(ByteArray.toHexString(mac)); + walletFile.setCrypto(crypto); + walletFile.setId(UUID.randomUUID().toString()); + walletFile.setVersion(CURRENT_VERSION); + + return walletFile; + } private static byte[] generateDerivedScryptKey( byte[] password, byte[] salt, int n, int r, int p, int dkLen) throws CipherException { @@ -267,6 +321,13 @@ public static ECKey decrypt(byte[] password, WalletFile walletFile) StringUtils.clear(privateKey); return ecKey; } + public static SM2 decryptSM2(byte[] password, WalletFile walletFile) + throws CipherException { + byte[] privateKey = decrypt2PrivateBytes(password, walletFile); + SM2 sm2 = SM2.fromPrivate(privateKey); + StringUtils.clear(privateKey); + return sm2; + } static void validate(WalletFile walletFile) throws CipherException { WalletFile.Crypto crypto = walletFile.getCrypto(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index d0fe50cfd..28985dcf0 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -21,6 +21,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.TransactionUtils; @@ -144,8 +145,10 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - ECKey ecKey = ECKey.fromPrivate(priKey); - WalletFile walletFile = Wallet.createStandard(password, ecKey); +// ECKey ecKey = ECKey.fromPrivate(priKey); + SM2 sm2 = SM2.fromPrivate(priKey); +// WalletFile walletFile = Wallet.createStandard(password, ecKey); + WalletFile walletFile = Wallet.createStandard(password, sm2); return walletFile; } @@ -182,6 +185,9 @@ public WalletApi(WalletFile walletFile) { public ECKey getEcKey(WalletFile walletFile, byte[] password) throws CipherException { return Wallet.decrypt(password, walletFile); } + public SM2 getSM2(WalletFile walletFile, byte[] password) throws CipherException { + return Wallet.decryptSM2(password, walletFile); + } public byte[] getPrivateBytes(byte[] password) throws CipherException, IOException { WalletFile walletFile = loadWalletFile(); @@ -353,7 +359,8 @@ private Transaction signTransaction(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); +// transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index f722bf334..886597c8f 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,7 +4,7 @@ net { fullnode = { ip.list = [ - "47.89.180.128:50051" + "47.89.178.193:50055" ] } From 8103912eff319050a875c85d8931f229aeecb13e Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Wed, 25 Dec 2019 18:25:11 +0800 Subject: [PATCH 220/445] wallet-cli integrate SM2 stage 1 --- .../org/tron/common/crypto/SignInterface.java | 4 + .../common/crypto/SignatureInterface.java | 2 + .../tron/common/utils/TransactionUtils.java | 21 +- .../java/org/tron/common/utils/Utils.java | 13 + .../org/tron/demo/TransactionSignDemo.java | 4 +- .../tron/demo/TransactionSignDemoForSM2.java | 19 +- .../java/org/tron/keystore/Credentials.java | 60 +--- .../org/tron/keystore/CredentialsEckey.java | 68 +++++ .../org/tron/keystore/CredentialsSM2.java | 68 +++++ src/main/java/org/tron/keystore/Wallet.java | 75 +---- .../java/org/tron/keystore/WalletUtils.java | 43 ++- src/main/java/org/tron/test/Test.java | 19 +- .../java/org/tron/walletserver/WalletApi.java | 273 ++++++++++-------- .../main/resources/config-back.conf | 4 +- src/main/resources/config.conf | 2 +- 15 files changed, 385 insertions(+), 290 deletions(-) create mode 100644 src/main/java/org/tron/keystore/CredentialsEckey.java create mode 100644 src/main/java/org/tron/keystore/CredentialsSM2.java rename config.conf => src/main/resources/config-back.conf (88%) diff --git a/src/main/java/org/tron/common/crypto/SignInterface.java b/src/main/java/org/tron/common/crypto/SignInterface.java index 3da8fd67e..b158c50b7 100644 --- a/src/main/java/org/tron/common/crypto/SignInterface.java +++ b/src/main/java/org/tron/common/crypto/SignInterface.java @@ -19,4 +19,8 @@ public interface SignInterface { byte[] getNodeId(); byte[] Base64toBytes (String signature); + + byte[] getPrivKeyBytes(); + + SignatureInterface sign(byte[] hash); } diff --git a/src/main/java/org/tron/common/crypto/SignatureInterface.java b/src/main/java/org/tron/common/crypto/SignatureInterface.java index 77d7cd101..451c18449 100644 --- a/src/main/java/org/tron/common/crypto/SignatureInterface.java +++ b/src/main/java/org/tron/common/crypto/SignatureInterface.java @@ -2,4 +2,6 @@ public interface SignatureInterface { boolean validateComponents(); + + byte[] toByteArray(); } \ No newline at end of file diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index d6b15afea..ccaf56222 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -19,6 +19,8 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -165,25 +167,10 @@ public static boolean validTransaction(Transaction signedTransaction) { return true; } - public static Transaction sign(Transaction transaction, ECKey myKey) { + public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); - - //System.out.println("Sign address: " + WalletApi.encode58Check(myKey.getAddress())); - - ECDSASignature signature = myKey.sign(hash); - ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); - transactionBuilderSigned.addSignature(bsSign); - transaction = transactionBuilderSigned.build(); - return transaction; - } - public static Transaction sign(Transaction transaction, SM2 myKey) { - Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); - byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); - - //System.out.println("Sign address: " + WalletApi.encode58Check(myKey.getAddress())); - - SM2.SM2Signature signature = myKey.sign(hash); + SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); transaction = transactionBuilderSigned.build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 7f2b26d75..fbc1256ff 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Scanner; public class Utils { public static final String PERMISSION_ID = "Permission_id"; @@ -507,5 +508,17 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean jsonTransaction.put("txID", txID); return jsonTransaction; } + + public static boolean confirmEncrption() { + System.out.println( + "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); + Scanner in = new Scanner(System.in); + String input = in.nextLine().trim(); + String str = input.split("\\s+")[0]; + if ("y".equalsIgnoreCase(str)) { + return true; + } + return false; + } } diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index debd911d0..1ecbf5792 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -104,11 +104,11 @@ private static void hexStringTobase58check() { } public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "040927135ad6104ef1724084c8b9b018df275c10be97373f0536bf0e3b4c3f6a"; + String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; byte[] privateBytes = ByteArray.fromHexString(privateStr); ECKey ecKey = ECKey.fromPrivate(privateBytes); byte[] from = ecKey.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TJ4DU11WXj6gzTvnxLac4eoPppyYJZE2UE"); + byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 2e2315b32..fc57493a5 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -70,21 +70,21 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { - SM2 ecKey = SM2.fromPrivate(privateKey); + SM2 sm2 = SM2.fromPrivate(privateKey); Transaction transaction1 = Transaction.parseFrom(transaction); byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = ecKey.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); + byte[] hash = sm2.hash(rawdata); + byte[] sign = sm2.sign(hash).toByteArray(); return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); } private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { - SM2 ecKey = SM2.fromPrivate(privateKey); + SM2 sm2 = SM2.fromPrivate(privateKey); Transaction transaction1 = Transaction.parseFrom(transaction); byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = ecKey.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); + byte[] hash = sm2.hash(rawdata); + byte[] sign = sm2.sign(hash).toByteArray(); return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); } @@ -107,8 +107,8 @@ private static void hexStringTobase58check() { public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { String privateStr = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; byte[] privateBytes = ByteArray.fromHexString(privateStr); - SM2 ecKey = SM2.fromPrivate(privateBytes); - byte[] from = ecKey.getAddress(); + SM2 sm2 = SM2.fromPrivate(privateBytes); + byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); @@ -117,9 +117,10 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca //sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); - boolean result = broadcast(transaction4); System.out.println(result); } + + } diff --git a/src/main/java/org/tron/keystore/Credentials.java b/src/main/java/org/tron/keystore/Credentials.java index a13033723..058bb7f1f 100644 --- a/src/main/java/org/tron/keystore/Credentials.java +++ b/src/main/java/org/tron/keystore/Credentials.java @@ -1,62 +1,10 @@ package org.tron.keystore; -import org.tron.common.crypto.ECKey; -import org.tron.common.utils.ByteArray; -import org.tron.walletserver.WalletApi; -/** - * Credentials wrapper. - */ -public class Credentials { +import org.tron.common.crypto.SignInterface; - private final ECKey ecKeyPair; - private final String address; +public interface Credentials { + SignInterface getPair(); - private Credentials(ECKey ecKeyPair, String address) { - this.ecKeyPair = ecKeyPair; - this.address = address; - } - - public ECKey getEcKeyPair() { - return ecKeyPair; - } - - public String getAddress() { - return address; - } - - public static Credentials create(ECKey ecKeyPair) { - String address = WalletApi.encode58Check(ecKeyPair.getAddress()); - return new Credentials(ecKeyPair, address); - } - - public static Credentials create(String privateKey) { - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); - return create(eCkey); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Credentials that = (Credentials) o; - - if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { - return false; - } - - return address != null ? address.equals(that.address) : that.address == null; - } - - @Override - public int hashCode() { - int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; - result = 31 * result + (address != null ? address.hashCode() : 0); - return result; - } + String getAddress(); } diff --git a/src/main/java/org/tron/keystore/CredentialsEckey.java b/src/main/java/org/tron/keystore/CredentialsEckey.java new file mode 100644 index 000000000..94aa0f255 --- /dev/null +++ b/src/main/java/org/tron/keystore/CredentialsEckey.java @@ -0,0 +1,68 @@ +package org.tron.keystore; + +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +/** + * Credentials wrapper. + */ +public class CredentialsEckey implements Credentials { + + private final ECKey ecKeyPair; + private final String address; + + private CredentialsEckey(ECKey ecKeyPair, String address) { + this.ecKeyPair = ecKeyPair; + this.address = address; + } + +// public ECKey getEcKeyPair() { +// return ecKeyPair; +// } + + public String getAddress() { + return address; + } + + public static CredentialsEckey create(ECKey ecKeyPair) { + String address = WalletApi.encode58Check(ecKeyPair.getAddress()); + return new CredentialsEckey(ecKeyPair, address); + } + + public static CredentialsEckey create(String privateKey) { + ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CredentialsEckey that = (CredentialsEckey) o; + + if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public SignInterface getPair() { + return ecKeyPair; + } +} diff --git a/src/main/java/org/tron/keystore/CredentialsSM2.java b/src/main/java/org/tron/keystore/CredentialsSM2.java new file mode 100644 index 000000000..537f8ce65 --- /dev/null +++ b/src/main/java/org/tron/keystore/CredentialsSM2.java @@ -0,0 +1,68 @@ +package org.tron.keystore; + +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +/** + * Credentials wrapper. + */ +public class CredentialsSM2 implements Credentials { + + private final SM2 sm2Pair; + private final String address; + + private CredentialsSM2(SM2 sm2Pair, String address) { + this.sm2Pair = sm2Pair; + this.address = address; + } + +// public SM2 getEcKeyPair() { +// return sm2Pair; +// } + + public String getAddress() { + return address; + } + + public static CredentialsSM2 create(SM2 sm2Pair) { + String address = WalletApi.encode58Check(sm2Pair.getAddress()); + return new CredentialsSM2(sm2Pair, address); + } + + public static CredentialsSM2 create(String privateKey) { + SM2 eCkey = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CredentialsSM2 that = (CredentialsSM2) o; + + if (sm2Pair != null ? !sm2Pair.equals(that.sm2Pair) : that.sm2Pair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = sm2Pair != null ? sm2Pair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public SignInterface getPair() { + return sm2Pair; + } +} diff --git a/src/main/java/org/tron/keystore/Wallet.java b/src/main/java/org/tron/keystore/Wallet.java index f43a8a32a..7331a5a75 100644 --- a/src/main/java/org/tron/keystore/Wallet.java +++ b/src/main/java/org/tron/keystore/Wallet.java @@ -6,6 +6,8 @@ import org.bouncycastle.crypto.params.KeyParameter; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CipherException; @@ -60,7 +62,7 @@ public class Wallet { static final String AES_128_CTR = "pbkdf2"; static final String SCRYPT = "scrypt"; - public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) + public static WalletFile create(byte[] password, SignInterface ecKeySm2Pair, int n, int p) throws CipherException { byte[] salt = generateRandomBytes(32); @@ -70,85 +72,30 @@ public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); - byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes(); + byte[] privateKeyBytes = ecKeySm2Pair.getPrivKeyBytes(); byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, privateKeyBytes); byte[] mac = generateMac(derivedKey, cipherText); - return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); + return createWalletFile(ecKeySm2Pair, cipherText, iv, salt, mac, n, p); } - public static WalletFile create(byte[] password, SM2 ecKeyPair, int n, int p) - throws CipherException { - - byte[] salt = generateRandomBytes(32); - - byte[] derivedKey = generateDerivedScryptKey(password, salt, n, R, p, DKLEN); - - byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); - byte[] iv = generateRandomBytes(16); - - byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes(); - byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, - privateKeyBytes); - - byte[] mac = generateMac(derivedKey, cipherText); - - return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); - } - public static WalletFile createStandard(byte[] password, ECKey ecKeyPair) + public static WalletFile createStandard(byte[] password, SignInterface ecKeySm2Pair) throws CipherException { - return create(password, ecKeyPair, N_STANDARD, P_STANDARD); - } - public static WalletFile createStandard(byte[] password, SM2 ecKeyPair) - throws CipherException { - return create(password, ecKeyPair, N_STANDARD, P_STANDARD); + return create(password, ecKeySm2Pair, N_STANDARD, P_STANDARD); } - public static WalletFile createLight(byte[] password, ECKey ecKeyPair) + public static WalletFile createLight(byte[] password, SignInterface ecKeySm2Pair) throws CipherException { - return create(password, ecKeyPair, N_LIGHT, P_LIGHT); - } - - private static WalletFile createWalletFile( - ECKey ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, - int n, int p) { - - WalletFile walletFile = new WalletFile(); - walletFile.setAddress(WalletApi.encode58Check(ecKeyPair.getAddress())); - - WalletFile.Crypto crypto = new WalletFile.Crypto(); - crypto.setCipher(CIPHER); - crypto.setCiphertext(ByteArray.toHexString(cipherText)); - walletFile.setCrypto(crypto); - - WalletFile.CipherParams cipherParams = new WalletFile.CipherParams(); - cipherParams.setIv(ByteArray.toHexString(iv)); - crypto.setCipherparams(cipherParams); - - crypto.setKdf(SCRYPT); - WalletFile.ScryptKdfParams kdfParams = new WalletFile.ScryptKdfParams(); - kdfParams.setDklen(DKLEN); - kdfParams.setN(n); - kdfParams.setP(p); - kdfParams.setR(R); - kdfParams.setSalt(ByteArray.toHexString(salt)); - crypto.setKdfparams(kdfParams); - - crypto.setMac(ByteArray.toHexString(mac)); - walletFile.setCrypto(crypto); - walletFile.setId(UUID.randomUUID().toString()); - walletFile.setVersion(CURRENT_VERSION); - - return walletFile; + return create(password, ecKeySm2Pair, N_LIGHT, P_LIGHT); } private static WalletFile createWalletFile( - SM2 ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, + SignInterface ecKeySm2Pair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, int n, int p) { WalletFile walletFile = new WalletFile(); - walletFile.setAddress(WalletApi.encode58Check(ecKeyPair.getAddress())); + walletFile.setAddress(WalletApi.encode58Check(ecKeySm2Pair.getAddress())); WalletFile.Crypto crypto = new WalletFile.Crypto(); crypto.setCipher(CIPHER); diff --git a/src/main/java/org/tron/keystore/WalletUtils.java b/src/main/java/org/tron/keystore/WalletUtils.java index f409465fe..8d206829f 100644 --- a/src/main/java/org/tron/keystore/WalletUtils.java +++ b/src/main/java/org/tron/keystore/WalletUtils.java @@ -3,8 +3,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.Config; import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Utils; +import org.tron.core.config.Configuration; import org.tron.core.exception.CipherException; import java.io.File; @@ -22,10 +26,16 @@ public class WalletUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); + private static boolean isEckey = true; static { + Config config = Configuration.getByPath("config.conf");//it is needs set to be a constant objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletUtils getConfig isEckey: " + isEckey); + } } public static String generateFullNewWalletFile(byte[] password, File destinationDirectory) @@ -46,20 +56,24 @@ public static String generateNewWalletFile( byte[] password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - - ECKey ecKeyPair = new ECKey(Utils.getRandom()); - return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt); + SignInterface ecKeySm2Pair = null; + if (isEckey) { + ecKeySm2Pair = new ECKey(Utils.getRandom()); + } else { + ecKeySm2Pair = new SM2(Utils.getRandom()); + } + return generateWalletFile(password, ecKeySm2Pair, destinationDirectory, useFullScrypt); } public static String generateWalletFile( - byte[] password, ECKey ecKeyPair, File destinationDirectory, boolean useFullScrypt) + byte[] password, SignInterface ecKeySm2Pair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile; if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeyPair); + walletFile = Wallet.createStandard(password, ecKeySm2Pair); } else { - walletFile = Wallet.createLight(password, ecKeyPair); + walletFile = Wallet.createLight(password, ecKeySm2Pair); } String fileName = getWalletFileName(walletFile); @@ -71,14 +85,14 @@ public static String generateWalletFile( } public static void updateWalletFile( - byte[] password, ECKey ecKeyPair, File source, boolean useFullScrypt) + byte[] password, SignInterface ecKeySm2Pair, File source, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeyPair); + walletFile = Wallet.createStandard(password, ecKeySm2Pair); } else { - walletFile = Wallet.createLight(password, ecKeyPair); + walletFile = Wallet.createLight(password, ecKeySm2Pair); } objectMapper.writeValue(source, walletFile); @@ -128,11 +142,15 @@ public static String generateWalletFile(WalletFile walletFile, File destinationD public static Credentials loadCredentials(byte[] password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); - return Credentials.create(Wallet.decrypt(password, walletFile)); + + if (isEckey) { + return CredentialsEckey.create(Wallet.decrypt(password, walletFile)); + } + return CredentialsSM2.create(Wallet.decryptSM2(password, walletFile)); } public static WalletFile loadWalletFile(File source) throws IOException { - return objectMapper.readValue(source, WalletFile.class); + return objectMapper.readValue(source, WalletFile.class); } // // public static Credentials loadBip39Credentials(String password, String mnemonic) { @@ -176,7 +194,6 @@ public static String getMainnetKeyDirectory() { } - // public static boolean isValidPrivateKey(String privateKey) { // String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); // return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; @@ -195,7 +212,7 @@ public static String getMainnetKeyDirectory() { // } public static void generateSkeyFile(SKeyCapsule skey, File file) - throws IOException { + throws IOException { objectMapper.writeValue(file, skey); } diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 3e597f4ee..534a08a37 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -7,12 +7,15 @@ import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; import org.tron.core.exception.CipherException; import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; +import org.tron.keystore.CredentialsEckey; import org.tron.keystore.WalletUtils; import org.tron.protos.Contract; import org.tron.protos.Contract.TransferContract; @@ -316,13 +319,14 @@ public static void testSha3() { public static void testGenerateWalletFile() throws CipherException, IOException { String PASSWORD = "Insecure Pa55w0rd"; String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); +// ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); + SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(priKeyHex)); File file = new File("out"); - String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), eCkey, file, true); + String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), sm2, file, true); Credentials credentials = WalletUtils.loadCredentials(PASSWORD.getBytes(), new File(file, fileName)); String address = credentials.getAddress(); - ECKey ecKeyPair = credentials.getEcKeyPair(); - String prikey = ByteArray.toHexString(ecKeyPair.getPrivKeyBytes()); + SignInterface pair = credentials.getPair(); + String prikey = ByteArray.toHexString(pair.getPrivKeyBytes()); System.out.println("address = " + address); System.out.println("prikey = " + prikey); @@ -360,6 +364,13 @@ public static void testPasswordStrength(){ System.out.println(password + " strength is " + level); } } + + public static void interfaceTest() { + String privateKey = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; + SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); + String address = WalletApi.encode58Check(sm2.getAddress()); + System.out.println(address); + } public static void main(String[] args) throws Exception { testPasswordStrength(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 28985dcf0..f1b4449d4 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -53,6 +53,7 @@ public class WalletApi { private byte[] address; private static byte addressPreFixByte = CommonConstant.ADD_PRE_FIX_BYTE_TESTNET; private static int rpcVersion = 0; + private static boolean isEckey = true; private static GrpcClient rpcCli = init(); @@ -86,6 +87,11 @@ public static GrpcClient init() { } if (config.hasPath("RPC_version")) { rpcVersion = config.getInt("RPC_version"); + System.out.println("WalletApi getRpcVsersion: " + rpcVersion); + } + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletApi getConfig isEckey: " + isEckey); } return new GrpcClient(fullNode, solidityNode); } @@ -138,17 +144,27 @@ public static int getRpcVersion() { * Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { - ECKey ecKey = new ECKey(Utils.getRandom()); - WalletFile walletFile = Wallet.createStandard(password, ecKey); + WalletFile walletFile = null; + if (isEckey) { + ECKey ecKey = new ECKey(Utils.getRandom()); + walletFile = Wallet.createStandard(password, ecKey); + } else { + SM2 sm2 = new SM2(Utils.getRandom()); + walletFile = Wallet.createStandard(password, sm2); + } return walletFile; } // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { -// ECKey ecKey = ECKey.fromPrivate(priKey); - SM2 sm2 = SM2.fromPrivate(priKey); -// WalletFile walletFile = Wallet.createStandard(password, ecKey); - WalletFile walletFile = Wallet.createStandard(password, sm2); + WalletFile walletFile=null; + if (isEckey) { + ECKey ecKey = ECKey.fromPrivate(priKey); + walletFile = Wallet.createStandard(password, ecKey); + } else { + SM2 sm2 = SM2.fromPrivate(priKey); + walletFile = Wallet.createStandard(password, sm2); + } return walletFile; } @@ -185,6 +201,7 @@ public WalletApi(WalletFile walletFile) { public ECKey getEcKey(WalletFile walletFile, byte[] password) throws CipherException { return Wallet.decrypt(password, walletFile); } + public SM2 getSM2(WalletFile walletFile, byte[] password) throws CipherException { return Wallet.decryptSM2(password, walletFile); } @@ -292,7 +309,7 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso "No keystore file found, please use registerwallet or importwallet first!"); } Credentials credentials = WalletUtils.loadCredentials(oldPassword, wallet); - WalletUtils.updateWalletFile(newPassowrd, credentials.getEcKeyPair(), wallet, true); + WalletUtils.updateWalletFile(newPassowrd, credentials.getPair(), wallet, true); return true; } @@ -358,9 +375,11 @@ private Transaction signTransaction(Transaction transaction) char[] password = Utils.inputPassword(false); byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - -// transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); - transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -398,7 +417,11 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -440,13 +463,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -455,13 +478,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); + .getParameter().unpack(CreateSmartContract.class); byte[] contractAddress = generateContractAddress( createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( @@ -470,7 +493,7 @@ private void showTransactionAfterSign(Transaction transaction) } private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -488,7 +511,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -505,9 +528,9 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -520,7 +543,7 @@ private boolean processTransaction(Transaction transaction) System.out.println(Utils.printTransactionExceptId(transaction)); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -542,7 +565,7 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p //Warning: do not invoke this interface provided by others. public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -554,7 +577,7 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction //Warning: do not invoke this interface provided by others. public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -579,25 +602,25 @@ public static byte[] createAdresss(byte[] passPhrase) { //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -650,7 +673,7 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -694,7 +717,7 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + .createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -806,7 +829,7 @@ public boolean voteWitness(byte[] owner, HashMap witness) } public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -818,8 +841,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] } public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -833,10 +856,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] } public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + .newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -849,7 +872,7 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra } public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -860,7 +883,7 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] } public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -891,7 +914,7 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( } public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -900,7 +923,7 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] } public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -909,7 +932,7 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] } public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -918,14 +941,14 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] } public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + .newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -978,7 +1001,7 @@ public static boolean addressValid(byte[] address) { if (preFixbyte != WalletApi.getAddressPreFixByte()) { System.out .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + + preFixbyte + " !!"); return false; } //Other rule; @@ -1004,9 +1027,9 @@ private static byte[] decode58Check(String input) { byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + hash1[1] == decodeCheck[decodeData.length + 1] && + hash1[2] == decodeCheck[decodeData.length + 2] && + hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1139,13 +1162,13 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { } public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { @@ -1153,13 +1176,13 @@ public static Optional getTransactionsFromThis2(byte[] // } public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { @@ -1175,7 +1198,7 @@ public static Optional getTransactionInfoById(String txID) { } public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, frozen_balance, @@ -1212,7 +1235,7 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) } private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + long frozen_duration, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } @@ -1248,7 +1271,7 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo } Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + .newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1282,13 +1305,13 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); @@ -1319,7 +1342,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { } Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1343,7 +1366,7 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { } Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1390,7 +1413,7 @@ public static Optional getProposal(String id) { } public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1413,7 +1436,7 @@ public static Optional getChainParameters() { public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1433,9 +1456,9 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) } public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { + long id, boolean is_add_approval) { Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1454,7 +1477,7 @@ public boolean deleteProposal(byte[] owner, long id) } public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); @@ -1462,7 +1485,7 @@ public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[ } public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + byte[] secondTokenId, long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1475,8 +1498,8 @@ public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstToken } public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + byte[] firstTokenId, long firstTokenBalance, + byte[] secondTokenId, long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1500,7 +1523,7 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon } public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1523,9 +1546,9 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l } public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1535,7 +1558,7 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b } public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1548,9 +1571,9 @@ public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId } public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { + long exchangeId, byte[] tokenId, long quant, long expected) { Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1604,21 +1627,21 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; + abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; + abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; + abiItem.getAsJsonObject().get("name").getAsString() : null; JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; + abiItem.getAsJsonObject().get("type").getAsString() : null; boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; + abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; if (type == null) { System.out.println("No type!"); return null; @@ -1640,7 +1663,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1649,10 +1672,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1665,7 +1688,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1674,10 +1697,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1698,7 +1721,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { } public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1712,7 +1735,7 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract byte[] contractAddress, long originEnergyLimit) { Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); @@ -1720,20 +1743,20 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract } public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + byte[] contractAddress) { Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + byte[] address, + String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, + String libraryAddressPair, String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1761,7 +1784,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } @@ -1769,7 +1792,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa } private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1798,7 +1821,7 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } else if (compilerVersion.equalsIgnoreCase("v5")) { //0.5.4 version String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1812,8 +1835,8 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + byte[] contractAddress, + long callValue, byte[] data, long tokenValue, String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1840,7 +1863,7 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { } public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1874,7 +1897,7 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig contractAddress, originEnergyLimit); TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + .updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { @@ -1911,8 +1934,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) } public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1936,7 +1959,7 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1960,8 +1983,8 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str } public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + long feeLimit, + long tokenValue, String tokenId, boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1987,12 +2010,12 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa Transaction transaction = transactionExtention.getTransaction(); // for constant if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + transactionExtention.getConstantResult(0) != null && + transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + .toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } @@ -2000,7 +2023,7 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2118,7 +2141,11 @@ public Transaction addTransactionSign(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } org.tron.keystore.StringUtils.clear(passwd); return transaction; } @@ -2142,7 +2169,7 @@ public static Optional GetMerkleTreeVoucherInfo( } public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2159,7 +2186,7 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry } public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2244,7 +2271,7 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall } public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2261,7 +2288,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2270,7 +2297,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + .clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2286,10 +2313,10 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + .addContract( + Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) + .setParameter( + Any.pack(contractBuild.build())).build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2299,7 +2326,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2366,7 +2393,7 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + .updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { diff --git a/config.conf b/src/main/resources/config-back.conf similarity index 88% rename from config.conf rename to src/main/resources/config-back.conf index 7d9982746..171520768 100644 --- a/config.conf +++ b/src/main/resources/config-back.conf @@ -14,5 +14,7 @@ fullnode = { # "127.0.0.1:50052" # ] #} - +crypto { + engine=sm2 +} RPC_version = 2 diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index 886597c8f..f722bf334 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,7 +4,7 @@ net { fullnode = { ip.list = [ - "47.89.178.193:50055" + "47.89.180.128:50051" ] } From 47ca12045ee0492abf2007b118f252e9fa5c1e77 Mon Sep 17 00:00:00 2001 From: Shuyu WANG Date: Fri, 27 Dec 2019 18:44:07 +0800 Subject: [PATCH 221/445] fix: field name typo of comments --- api/api.proto | 10 +++++----- core/Tron.proto | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/api.proto b/api/api.proto index a2c64e0a8..bbaac66ae 100644 --- a/api/api.proto +++ b/api/api.proto @@ -845,7 +845,7 @@ service WalletSolidity { rpc IsSpend (NoteParameters) returns (SpendResult) { } - + rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } @@ -1071,7 +1071,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) + bytes txid = 3; //transaction id = sha256(transaction.raw_data) } message AddressPrKeyPairMessage { @@ -1081,7 +1081,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; } @@ -1166,7 +1166,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1175,7 +1175,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive bool is_spend = 4; } diff --git a/core/Tron.proto b/core/Tron.proto index a2bc17193..db1355863 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -327,7 +327,7 @@ message Transaction { repeated authority auths = 9; // transaction note bytes data = 10; - //only support size = 1, repeated list here for extension + //only support size = 1, repeated list here for extension repeated Contract contract = 11; // scripts not used bytes scripts = 12; @@ -336,7 +336,7 @@ message Transaction { } raw raw_data = 1; - // only support size = 1, repeated list here for muti-sig extension + // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; repeated Result ret = 5; } From 0cda923613763a9c4361007e1ebee762eb9370f1 Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 2 Jan 2020 15:36:50 +0800 Subject: [PATCH 222/445] add command showshieldedaddressinfo --- .../java/org/tron/common/utils/Utils.java | 10 ++ .../org/tron/core/zen/ShieldedWrapper.java | 80 +++++++------ src/main/java/org/tron/core/zen/ZenUtils.java | 14 +++ .../tron/core/zen/address/DiversifierT.java | 3 +- .../tron/core/zen/address/PaymentAddress.java | 2 +- src/main/java/org/tron/walletcli/Client.java | 108 ++++++++++++++---- .../org/tron/walletcli/WalletApiWrapper.java | 14 ++- 7 files changed, 168 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 7f2b26d75..fd77fd55e 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -507,5 +507,15 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean jsonTransaction.put("txID", txID); return jsonTransaction; } + + public static boolean isNumericString(String str) { + for (int i = str.length(); --i >= 0; ) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + } diff --git a/src/main/java/org/tron/core/zen/ShieldedWrapper.java b/src/main/java/org/tron/core/zen/ShieldedWrapper.java index e458fbb5a..9463a68fb 100644 --- a/src/main/java/org/tron/core/zen/ShieldedWrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedWrapper.java @@ -339,21 +339,23 @@ private boolean updateIvkAndBlockNumFile() { * @return */ private boolean loadIvkFromFile() { - if (ArrayUtils.isEmpty(shieldedSkey)){ + if (ArrayUtils.isEmpty(shieldedSkey)) { return false; } ivkMapScanBlockNum.clear(); - List list = ZenUtils.getListFromFile(IVK_AND_NUM_FILE_NAME); - for (int i = 0; i < list.size(); ++i) { - byte[] cipherText = Base58.decode(list.get(i)); - try { - byte[] text = ZenUtils.aesCtrDecrypt(cipherText, shieldedSkey); - byte[] key = Arrays.copyOfRange(text, 0, 32); - byte[] value = Arrays.copyOfRange(text, 32, text.length); - ivkMapScanBlockNum.put(ByteArray.toHexString(key), ByteArray.toLong(value)); - } catch (CipherException e) { - e.printStackTrace(); + if (ZenUtils.checkFileExist(IVK_AND_NUM_FILE_NAME)) { + List list = ZenUtils.getListFromFile(IVK_AND_NUM_FILE_NAME); + for (int i = 0; i < list.size(); ++i) { + byte[] cipherText = Base58.decode(list.get(i)); + try { + byte[] text = ZenUtils.aesCtrDecrypt(cipherText, shieldedSkey); + byte[] key = Arrays.copyOfRange(text, 0, 32); + byte[] value = Arrays.copyOfRange(text, 32, text.length); + ivkMapScanBlockNum.put(ByteArray.toHexString(key), ByteArray.toLong(value)); + } catch (CipherException e) { + e.printStackTrace(); + } } } return true; @@ -424,20 +426,21 @@ private boolean saveUnspendNoteToFile() throws CipherException { * @return */ private boolean loadUnSpendNoteFromFile() throws CipherException { - if (ArrayUtils.isEmpty(shieldedSkey)){ + if (ArrayUtils.isEmpty(shieldedSkey)) { return false; } - utxoMapNote.clear(); - List list = ZenUtils.getListFromFile(UNSPEND_NOTE_FILE_NAME); - for (int i = 0; i < list.size(); ++i) { - ShieldedNoteInfo noteInfo = new ShieldedNoteInfo(); - noteInfo.decode(list.get(i), shieldedSkey); - utxoMapNote.put(noteInfo.getNoteIndex(), noteInfo); + if (ZenUtils.checkFileExist(UNSPEND_NOTE_FILE_NAME)) { + List list = ZenUtils.getListFromFile(UNSPEND_NOTE_FILE_NAME); + for (int i = 0; i < list.size(); ++i) { + ShieldedNoteInfo noteInfo = new ShieldedNoteInfo(); + noteInfo.decode(list.get(i), shieldedSkey); + utxoMapNote.put(noteInfo.getNoteIndex(), noteInfo); - if (noteInfo.getNoteIndex() >= nodeIndex.get()) { - nodeIndex.set(noteInfo.getNoteIndex()+1); + if (noteInfo.getNoteIndex() >= nodeIndex.get()) { + nodeIndex.set(noteInfo.getNoteIndex() + 1); + } } } return true; @@ -468,14 +471,16 @@ private boolean loadSpendNoteFromFile() throws CipherException { } spendUtxoList.clear(); - List list = ZenUtils.getListFromFile(SPEND_NOTE_FILE_NAME); - for (int i = 0; i < list.size(); ++i) { - ShieldedNoteInfo noteInfo = new ShieldedNoteInfo(); - noteInfo.decode(list.get(i), shieldedSkey); - spendUtxoList.add(noteInfo); - - if (noteInfo.getNoteIndex() >= nodeIndex.get()) { - nodeIndex.set(noteInfo.getNoteIndex()+1); + if (ZenUtils.checkFileExist(SPEND_NOTE_FILE_NAME)) { + List list = ZenUtils.getListFromFile(SPEND_NOTE_FILE_NAME); + for (int i = 0; i < list.size(); ++i) { + ShieldedNoteInfo noteInfo = new ShieldedNoteInfo(); + noteInfo.decode(list.get(i), shieldedSkey); + spendUtxoList.add(noteInfo); + + if (noteInfo.getNoteIndex() >= nodeIndex.get()) { + nodeIndex.set(noteInfo.getNoteIndex() + 1); + } } } return true; @@ -486,19 +491,20 @@ private boolean loadSpendNoteFromFile() throws CipherException { * @return */ private boolean loadAddressFromFile() throws CipherException { - if (ArrayUtils.isEmpty(shieldedSkey)){ + if (ArrayUtils.isEmpty(shieldedSkey)) { return false; } - List addressList = ZenUtils.getListFromFile(SHIELDED_ADDRESS_FILE_NAME); - shieldedAddressInfoMap.clear(); - for (String addressString : addressList ) { - ShieldedAddressInfo addressInfo = new ShieldedAddressInfo(); - if ( addressInfo.decode(addressString, shieldedSkey) ) { - shieldedAddressInfoMap.put(addressInfo.getAddress(), addressInfo); - } else { - System.out.println("*******************"); + if (ZenUtils.checkFileExist(SHIELDED_ADDRESS_FILE_NAME)) { + List addressList = ZenUtils.getListFromFile(SHIELDED_ADDRESS_FILE_NAME); + for (String addressString : addressList) { + ShieldedAddressInfo addressInfo = new ShieldedAddressInfo(); + if (addressInfo.decode(addressString, shieldedSkey)) { + shieldedAddressInfoMap.put(addressInfo.getAddress(), addressInfo); + } else { + System.out.println("*******************"); + } } } return true; diff --git a/src/main/java/org/tron/core/zen/ZenUtils.java b/src/main/java/org/tron/core/zen/ZenUtils.java index d33add015..f2d7cdecc 100644 --- a/src/main/java/org/tron/core/zen/ZenUtils.java +++ b/src/main/java/org/tron/core/zen/ZenUtils.java @@ -92,6 +92,20 @@ public static void checkFolderExist(final String filePath) { } } + public static boolean checkFileExist(final String filePath) { + try { + File file = new File(filePath); + if (file.exists() && !file.isDirectory()) { + return true; + } else { + return false; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + public static String getMemo(byte[] memo) { int index = memo.length; for (; index>0; --index) { diff --git a/src/main/java/org/tron/core/zen/address/DiversifierT.java b/src/main/java/org/tron/core/zen/address/DiversifierT.java index cfa58ff2a..14a3658d8 100644 --- a/src/main/java/org/tron/core/zen/address/DiversifierT.java +++ b/src/main/java/org/tron/core/zen/address/DiversifierT.java @@ -10,10 +10,11 @@ @AllArgsConstructor public class DiversifierT { + public static final int ZC_DIVERSIFIER_SIZE = 11; @Setter @Getter - public byte[] data; + private byte[] data = new byte[ZC_DIVERSIFIER_SIZE]; public DiversifierT() { } diff --git a/src/main/java/org/tron/core/zen/address/PaymentAddress.java b/src/main/java/org/tron/core/zen/address/PaymentAddress.java index 750f1a416..0e2b097c2 100644 --- a/src/main/java/org/tron/core/zen/address/PaymentAddress.java +++ b/src/main/java/org/tron/core/zen/address/PaymentAddress.java @@ -18,7 +18,7 @@ public class PaymentAddress { public static PaymentAddress decode(byte[] data) { DiversifierT d = new DiversifierT(); byte[] pkD = new byte[32]; - System.arraycopy(data, 0, d.data, 0, 11); + System.arraycopy(data, 0, d.getData(), 0, 11); System.arraycopy(data, 11, pkD, 0, 32); return new PaymentAddress(d, pkD); } diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 4703b1503..7167c6acb 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -28,6 +28,8 @@ import org.tron.core.zen.ShieldedNoteInfo; import org.tron.core.zen.ShieldedWrapper; import org.tron.core.zen.ZenUtils; +import org.tron.core.zen.address.KeyIo; +import org.tron.core.zen.address.PaymentAddress; import org.tron.keystore.StringUtils; import org.tron.protos.Contract.AssetIssueContract; import org.tron.protos.Protocol.*; @@ -132,6 +134,7 @@ public class Client { "SendShieldedCoin", "SendShieldedCoinWithoutAsk", "SetAccountId", + "ShowShieldedAddressInfo", "TransferAsset", "TriggerContract contractAddress method args isHex fee_limit value", "TriggerConstantContract contractAddress method args isHex", @@ -235,6 +238,7 @@ public class Client { "SendShieldedCoin", "SendShieldedCoinWithoutAsk", "SetAccountId", + "ShowShieldedAddressInfo", "TransferAsset", "TriggerContract", "TriggerConstantContract", @@ -409,6 +413,8 @@ private void getAddress() { if (address != null) { System.out.println("GetAddress successful !!"); System.out.println("address = " + address); + } else { + System.out.println("Warning: GetAddress failed, Please login first !!"); } } @@ -2300,20 +2306,53 @@ private void listShieldedAddress() { } } + private void showShieldedAddressInfo(String[] parameters) { + if (parameters == null || parameters.length < 1) { + System.out.println("Using ShowShieldedAddressInfo needs 1 parameter like: "); + System.out.println("ShowShieldedAddressInfo shieldedAddress"); + return; + } + + if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { + System.out.println("ShowShieldedAddressInfo failed, please loadShieldedWallet first!"); + return; + } + + String shieldedAddress = parameters[0]; + ShieldedAddressInfo addressInfo = + ShieldedWrapper.getInstance().getShieldedAddressInfoMap().get(shieldedAddress); + if (addressInfo != null) { + System.out.println("The following variables are secret information, please don't show to other people!!!"); + System.out.println("sk :" + ByteArray.toHexString(addressInfo.getSk())); + System.out.println("ivk:" + ByteArray.toHexString(addressInfo.getIvk())); + System.out.println("ovk:" + ByteArray.toHexString(addressInfo.getOvk())); + System.out.println("pkd:" + ByteArray.toHexString(addressInfo.getPkD())); + System.out.println("d :" + ByteArray.toHexString(addressInfo.getD().getData())); + } else { + PaymentAddress decodePaymentAddress = KeyIo.decodePaymentAddress(shieldedAddress); + if (decodePaymentAddress != null) { + System.out.println("pkd:" + ByteArray.toHexString(decodePaymentAddress.getPkD())); + System.out.println("d :" + ByteArray.toHexString(decodePaymentAddress.getD().getData())); + } else { + System.out.println("Shielded address " + shieldedAddress + " is Invalid, please check!"); + } + } + } + private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) throws IOException, CipherException, CancelException, ZksnarkException { int parameterIndex = 0; - String fromPublicAddress = parameters[parameterIndex++]; - long fromPublicAmount = 0; - if (fromPublicAddress.equals("null")) { - fromPublicAddress = null; - ++parameterIndex; + + String fromPublicAddress; + if (Utils.isNumericString(parameters[0])) { + fromPublicAddress = walletApiWrapper.getAddress(); } else { - String amountString = parameters[parameterIndex++]; - if (!StringUtil.isNullOrEmpty(amountString)) { - fromPublicAmount = Long.valueOf(amountString); + fromPublicAddress = parameters[parameterIndex++]; + if (fromPublicAddress.equals("null")) { + fromPublicAddress = null; } } + long fromPublicAmount = Long.valueOf(parameters[parameterIndex++]); int shieldedInputNum = 0; String amountString = parameters[parameterIndex++]; @@ -2394,31 +2433,48 @@ private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) } } - private boolean isFromShieldedNote(String shieldedStringInputNum) { - int shieldedInputNum = 0; - if (!StringUtil.isNullOrEmpty(shieldedStringInputNum)) { - shieldedInputNum = Integer.valueOf(shieldedStringInputNum); + private boolean isFromPublicAddress(String[] parameters) { + if (Utils.isNumericString(parameters[0])) { + if (Long.valueOf(parameters[0]) > 0) { + return true; + } + } else { + if (Long.valueOf(parameters[1]) > 0) { + return true; + } } + return false; + } - if (shieldedInputNum > 0) { - return true; + private boolean isFromShieldedNote(String[] parameters) { + if (Utils.isNumericString(parameters[0])) { + if (Long.valueOf(parameters[1]) > 0) { + return true; + } } else { - return false; + if (Long.valueOf(parameters[2]) > 0) { + return true; + } } + return false; } private void sendShieldedCoin(String[] parameters) throws IOException, CipherException, CancelException, ZksnarkException { if (parameters == null || parameters.length < 6) { System.out.println("Using SendShieldedCoin command needs more than 6 parameters like: "); - System.out.println("SendShieldedCoin publicFromAddress fromAmount shieldedInputNum " + System.out.println("SendShieldedCoin [publicFromAddress] fromAmount shieldedInputNum " + "input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1" + " amount1 memo1 shieldedAddress2 amount2 memo2 ... "); return; } - if (isFromShieldedNote(parameters[2]) && - !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { + if (isFromPublicAddress(parameters) && !walletApiWrapper.isLoginState()) { + System.out.println("SendShieldedCoin failed, Please login first !!"); + return; + } + + if (isFromShieldedNote(parameters) && !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { System.out.println("SendShieldedCoin failed, please loadShieldedWallet first !!!"); return; } @@ -2436,15 +2492,19 @@ private void sendShieldedCoinWithoutAsk(String[] parameters) throws IOException, if (parameters == null || parameters.length < 6) { System.out .println("Using SendShieldedCoinWithoutAsk command needs more than 6 parameters like: "); - System.out.println("SendShieldedCoinWithoutAsk publicFromAddress fromAmount " + System.out.println("SendShieldedCoinWithoutAsk [publicFromAddress] fromAmount " + "shieldedInputNum input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum " + "shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 ... "); return; } - if (isFromShieldedNote(parameters[2]) && - !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - System.out.println("SendShieldedCoin failed, please loadShieldedWallet first!"); + if (isFromPublicAddress(parameters) && !walletApiWrapper.isLoginState()) { + System.out.println("SendShieldedCoinWithoutAsk failed, Please login first !!"); + return; + } + + if (isFromShieldedNote(parameters) && !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { + System.out.println("SendShieldedCoinWithoutAsk failed, please loadShieldedWallet first !!!"); return; } @@ -3238,6 +3298,10 @@ private void run() { listShieldedAddress(); break; } + case "showshieldedaddressinfo": { + showShieldedAddressInfo(parameters); + break; + } case "sendshieldedcoin": { sendShieldedCoin(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 0443051d0..1c20953e1 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -89,6 +89,14 @@ public boolean changePassword(char[] oldPassword, char[] newPassword) return result; } + public boolean isLoginState() { + if (wallet == null || !wallet.isLoginState()) { + return false; + } else { + return true; + } + } + public boolean login() throws IOException, CipherException { logout(); wallet = WalletApi.loadWalletFromKeystore(); @@ -116,6 +124,8 @@ public void logout() { //Neddn't logout } + + //password is current, will be enc by password2. public byte[] backupWallet() throws IOException, CipherException { if (wallet == null || !wallet.isLoginState()) { @@ -138,7 +148,7 @@ public byte[] backupWallet() throws IOException, CipherException { public String getAddress() { if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: GetAddress failed, Please login first !!"); +// System.out.println("Warning: GetAddress failed, Please login first !!"); return null; } @@ -721,7 +731,7 @@ public boolean sendShieldedCoin(String fromAddress, long fromAmount, List List shieldedOutputList, String toAddress, long toAmount) throws CipherException, IOException, CancelException, ZksnarkException { PrivateParameters.Builder builder = PrivateParameters.newBuilder(); - if (!StringUtil.isNullOrEmpty(fromAddress)) { + if (!StringUtil.isNullOrEmpty(fromAddress) && fromAmount > 0) { byte[] from = WalletApi.decodeFromBase58Check(fromAddress); if (from == null) { return false; From 74b7f7099a6fd7c98286c4ff5cfd2454bd20da39 Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 2 Jan 2020 16:05:03 +0800 Subject: [PATCH 223/445] change readme --- README.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af1d6bad7..2a4282977 100644 --- a/README.md +++ b/README.md @@ -1123,14 +1123,14 @@ ztron165vh2d0qqj7ytrkjeehwy0sg3uvc4tnvcqnpqnzrqq4jpw2p7pzgm2d3chrwxk2jf9ck6rza8j ### SendShieldedCoin - > SendShieldedCoin publicFromAddress fromAmount shieldedInputNum input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 ... + > SendShieldedCoin [publicFromAddress] fromAmount shieldedInputNum input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 ... Shielded transfer, support from public address or shielded address to public address and shielded address, does not support public address to public address, does not support automatic change. Public from amount / shielded input amount = public output amount + shielded output amount + fee publicFromAddress -> Public from address, set to null if not needed. +> Public from address, set to null if not needed.Optional, If this variable is not configured, it is the address of the current login account fromAmount > The amount transfer from public address, if publicFromAddress set to null, this variable must be 0. @@ -1412,6 +1412,29 @@ Example: ImportShieldedAddress successful !! ``` +20. ShowShieldedAddressInfo + + Display information about shielded addresses + + Example: + + ```console + > listshieldedaddress + ShieldedAddress : + ztron14t95p936cyev678f6l6xsejnyfzrrzfsg56jaxgp7fzxlsczc2l6866fzc4c8awfnrzy74svkrl + ztron1v6tu4c760vs7m0h94t89m4jcxtuq0nxmag7eequc3c2rnee3sufllq8fjtvfff6y84x3zgcapwp + ztron18vaszshuluufz64uesvzw6wtune90uwexzmsfwtgqq2mlydt4fhy0kz02k3vm2j8er7s5xuyujv + > showshieldedaddressinfo ztron18vaszshuluufz64uesvzw6wtune90uwexzmsfwtgqq2mlydt4fhy0kz02k3vm2j8er7s5xuyujv + The following variables are secret information, please don't show to other people!!! + sk :0deebe55fe7e591803126b531d4fe7c0e3979a2fcadb5a7996f73a8e463231f8 + ivk:aa955c5798e3f611c72fa22842847810114dd5a860db272b2ef50cc8448ced00 + ovk:a1d00b6f761137e1d8b58e77d8685347137131317ba3671f644ffb64bc5baa94 + pkd:182769cbe4f257f1d930b704b9680015bf91abaa6e47d84f55a2cdaa47c8fd0a + d :3b3b0142fcff38916abccc + > showshieldedaddressinfo ztron19lgz39ja8dz427dt9qa8gpkpxanu05y09zplfzzwc640mlx74n4au3037nde3h6m7zsu5xgkrnn + pkd:3a7406c13767c7d08f2883f4884ec6aafdfcdeacebde45f1f4db98df5bf0a1ca + d :2fd028965d3b455579ab28 + ``` ## Command List Following is a list of Tron Wallet-cli commands: @@ -1498,6 +1521,7 @@ For more information on a specific command, just type the command on terminal wh SendShieldedCoin SendShieldedCoinWithoutAsk SetAccountId + ShowShieldedAddressInfo TransferAsset TriggerContract TriggerConstantContract From d27f709be4527c773ead444562662c23bb625a0e Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 2 Jan 2020 17:04:10 +0800 Subject: [PATCH 224/445] change readme --- README.md | 6 +++--- src/main/java/org/tron/walletcli/Client.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2a4282977..8fa5808f4 100644 --- a/README.md +++ b/README.md @@ -1083,7 +1083,7 @@ Example: ```console > loadshieldedwallet Please input your password for shielded wallet. -> 1qa@WS#ED +> ******* LoadShieldedWallet successful !!! ``` @@ -1130,7 +1130,7 @@ Shielded transfer, support from public address or shielded address to public add Public from amount / shielded input amount = public output amount + shielded output amount + fee publicFromAddress -> Public from address, set to null if not needed.Optional, If this variable is not configured, it is the address of the current login account +> Public from address, set to null if not needed. Optional, If this variable is not configured, it is the address of the current login account fromAmount > The amount transfer from public address, if publicFromAddress set to null, this variable must be 0. @@ -1200,7 +1200,7 @@ Example: List the note scanned by the local cache address type - > Show type. 0 show not spent note; other value show all notes, include spend notes and not spend notes. default is 0. + > Shows the type of note. If the variable is 0, it shows all unspent notes; For other values, it shows all the notes, including spent notes and unspent notes. Example: diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 7167c6acb..88d04200c 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2334,7 +2334,7 @@ private void showShieldedAddressInfo(String[] parameters) { System.out.println("pkd:" + ByteArray.toHexString(decodePaymentAddress.getPkD())); System.out.println("d :" + ByteArray.toHexString(decodePaymentAddress.getD().getData())); } else { - System.out.println("Shielded address " + shieldedAddress + " is Invalid, please check!"); + System.out.println("Shielded address " + shieldedAddress + " is invalid, please check!"); } } } @@ -2526,7 +2526,7 @@ private void listShieldedNote(String[] parameters) { if (parameters == null || parameters.length <= 0) { System.out.println("This command will show all the unspent notes. "); System.out.println( - "If you want to query all the spent notes and unspent notes, please use command ListShieldedNote 1 "); + "If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedNote 1 "); } else { if (!StringUtil.isNullOrEmpty(parameters[0])) { showType = Integer.valueOf(parameters[0]); @@ -2536,16 +2536,16 @@ private void listShieldedNote(String[] parameters) { if (showType == 0) { List utxoList = ShieldedWrapper.getInstance().getvalidateSortUtxoList(); if (utxoList.size() == 0) { - System.out.println("Unspent note is 0."); + System.out.println("The count of unspent note is 0."); } else { - System.out.println("Unspent note list like:"); + System.out.println("The unspent note list is shown below:"); for (String string : utxoList) { System.out.println(string); } } } else { Map noteMap = ShieldedWrapper.getInstance().getUtxoMapNote(); - System.out.println("All notes list like:"); + System.out.println("All notes is shown below:"); for (Entry entry : noteMap.entrySet()) { String string = entry.getValue().getPaymentAddress() + " "; string += entry.getValue().getValue(); From 6113944be7b234c66419bdc8f85057089bf9e167 Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 2 Jan 2020 19:11:28 +0800 Subject: [PATCH 225/445] change readme --- README.md | 150 +++++++++++++++++++++++++++--------------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 8fa5808f4..819019810 100644 --- a/README.md +++ b/README.md @@ -1123,7 +1123,7 @@ ztron165vh2d0qqj7ytrkjeehwy0sg3uvc4tnvcqnpqnzrqq4jpw2p7pzgm2d3chrwxk2jf9ck6rza8j ### SendShieldedCoin - > SendShieldedCoin [publicFromAddress] fromAmount shieldedInputNum input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 ... + > SendShieldedCoin [publicFromAddress] fromAmount shieldedInputNum input publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 ... Shielded transfer, support from public address or shielded address to public address and shielded address, does not support public address to public address, does not support automatic change. @@ -1191,18 +1191,18 @@ Example: address ztron1hn9r3wmytavslztwmlzvuzk3dqpdhwcmda2d0deyu5pwv32dp78saaslyt82w0078y6uzfg8x6w ``` -4. sendshieldedcoinwithoutask +### sendshieldedcoinwithoutask - Usage and parameters are consistent with the command sendshieldedcoin, the only difference is that sendshieldedcoin uses ask signature, but sendshieldedcoinwithoutask uses ak signature. +Usage and parameters are consistent with the command sendshieldedcoin, the only difference is that sendshieldedcoin uses ask signature, but sendshieldedcoinwithoutask uses ak signature. -5. listshieldednote type +### listshieldednote type - List the note scanned by the local cache address +List the note scanned by the local cache address - type - > Shows the type of note. If the variable is 0, it shows all unspent notes; For other values, it shows all the notes, including spent notes and unspent notes. +type +> Shows the type of note. If the variable is 0, it shows all unspent notes; For other values, it shows all the notes, including spent notes and unspent notes. - Example: +Example: ```console > listshieldednote 0 @@ -1215,52 +1215,52 @@ Example: ztron1hn9r3wmytavslztwmlzvuzk3dqpdhwcmda2d0deyu5pwv32dp78saaslyt82w0078y6uzfg8x6w 90000000 06b55fc27f7ec649396706d149d18a0bb003347bdd7f489e3d47205da9cee802 0 Spend test2 ``` -6. resetshieldednote +### resetshieldednote - Clean all the note scanned, rescanned all blocks.generally used when there is a problem with the notes or when switching environments +Clean all the note scanned, rescanned all blocks.generally used when there is a problem with the notes or when switching environments -7. ScanNotebyIvk ivk startNum endNum +### ScanNotebyIvk ivk startNum endNum - Scan notes by ivk +Scan notes by ivk - ivk - > The ivk of shielded address +ivk +> The ivk of shielded address - startNum - > The starting block number of the scan +startNum +> The starting block number of the scan - endNum - > The end block number of the scan +endNum +> The end block number of the scan - Example: +Example: - > scannotebyivk d2a4137cecf049965c4183f78fe9fc9fbeadab6ab3ef70ea749421b4c6b8de04 500 1499 + > scannotebyivk d2a4137cecf049965c4183f78fe9fc9fbeadab6ab3ef70ea749421b4c6b8de04 500 1499 -8. ScanNotebyOvk ovk startNum endNum +### ScanNotebyOvk ovk startNum endNum - Scan notes by ovk +Scan notes by ovk - ovk - > the ivk of shielded address +ovk +> the ivk of shielded address - startNum - > The starting block number of the scan +startNum +> The starting block number of the scan - endNum - > The end block number of the scan +endNum +> The end block number of the scan - Example: +Example: - > scannotebyovk a5b06ef3067855d741f966d54dfa1c124548535107333336bd9552a427f0529e 500 1499 + > scannotebyovk a5b06ef3067855d741f966d54dfa1c124548535107333336bd9552a427f0529e 500 1499 -9. GetShieldedNullifier index +### GetShieldedNullifier index - Get the nullifier of the note +Get the nullifier of the note - index - > The note index obtained by the listshieldednote command +index +> The note index obtained by the listshieldednote command - Example: +Example: ```console > listshieldednote @@ -1276,39 +1276,39 @@ Example: ShieldedNullifier:2a524a3be2643365ecdacf8f0d3ca1de8fad3080eea0b9561435b5d1ee467042 ``` -10. ScanAndMarkNotebyAddress shieldedAddress startNum endNum +### ScanAndMarkNotebyAddress shieldedAddress startNum endNum - Scan the note with a locally cached shielded address and mark whether it is spent out +Scan the note with a locally cached shielded address and mark whether it is spent out - shieldedAddress - > Locally cached shielded address, if it is not a locally cached shielded address, an error will be reported. +shieldedAddress +> Locally cached shielded address, if it is not a locally cached shielded address, an error will be reported. - startNum - > The starting block number of the scan +startNum +> The starting block number of the scan - endNum - > The end block number of the scan +endNum +> The end block number of the scan - Example: +Example: - > ScanAndMarkNotebyAddress ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 500 1500 + > ScanAndMarkNotebyAddress ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 500 1500 -11. GetSpendingKey +### GetSpendingKey - Generate a sk +Generate a sk - Example: +Example: ```console > GetSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a ``` -12. getExpandedSpendingKey sk +### getExpandedSpendingKey sk - Generate ask, nsk, ovk from sk +Generate ask, nsk, ovk from sk - Example: +Example: ```console > getExpandedSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a @@ -1317,54 +1317,54 @@ Example: ovk:892a10c1d3e8ea22242849e13f177d69e1180d1d5bba118c586765241ba2d3d6 ``` -13. getAkFromAsk ask - Generate ak from ask +### getAkFromAsk ask +Generate ak from ask - Example: +Example: ```console > GetAkFromAsk 252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e ak:f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ``` -14. getNkFromNsk nsk +### getNkFromNsk nsk - Generate nk from nsk +Generate nk from nsk - Example: +Example: ```console > GetNkFromNsk 5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 nk:ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a ``` -15. getIncomingViewingKey ak[64] nk[64] +### getIncomingViewingKey ak[64] nk[64] - Generate ivk from ak and nk +Generate ivk from ak and nk - Example: +Example: ```console > getincomingviewingkey f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a ivk:148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 ``` -16. GetDiversifier +### GetDiversifier - Generate a diversifier +Generate a diversifier - Example: +Example: ```console > GetDiversifier 11db4baf6bd5d5afd3a8b5 ``` -17. getshieldedpaymentaddress ivk[64] d[22] +### getshieldedpaymentaddress ivk[64] d[22] - Generate a shielded address from sk and d +Generate a shielded address from sk and d - Example: +Example: ```console GetShieldedPaymentAddress 148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 11db4baf6bd5d5afd3a8b5 @@ -1372,11 +1372,11 @@ Example: shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z ``` -18. BackupShieldedAddress +### BackupShieldedAddress - Back up one shielded address +Back up one shielded address - Example: +Example: ```console > loadshieldedwallet @@ -1396,11 +1396,11 @@ Example: BackupShieldedAddress successful !! ``` -19. ImportShieldedAddress +### ImportShieldedAddress - Import one shieled address to local wallet +Import one shieled address to local wallet - Example: +Example: ```console > ImportShieldedAddress @@ -1412,11 +1412,11 @@ Example: ImportShieldedAddress successful !! ``` -20. ShowShieldedAddressInfo +### ShowShieldedAddressInfo - Display information about shielded addresses +Display information about shielded addresses - Example: +Example: ```console > listshieldedaddress From d22dfa1bc7b7af7bc72af91be624b10e87560a8e Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 2 Jan 2020 19:43:42 +0800 Subject: [PATCH 226/445] change readme --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 819019810..b61106910 100644 --- a/README.md +++ b/README.md @@ -1127,7 +1127,7 @@ ztron165vh2d0qqj7ytrkjeehwy0sg3uvc4tnvcqnpqnzrqq4jpw2p7pzgm2d3chrwxk2jf9ck6rza8j Shielded transfer, support from public address or shielded address to public address and shielded address, does not support public address to public address, does not support automatic change. -Public from amount / shielded input amount = public output amount + shielded output amount + fee +Public input amount / shielded input amount = public output amount + shielded output amount + fee publicFromAddress > Public from address, set to null if not needed. Optional, If this variable is not configured, it is the address of the current login account @@ -1161,13 +1161,14 @@ memo1 Example: -1. Public address transfer to two shielded addresses - +1. Public address transfer to shielded addresses + **When in this mode,shieldedInputNum=0,publicToAddress=null,toAmount=0** ```console > sendshieldedcoin TRGhNNfnmgLegT4zHNjEqDSADjgmnHvubJ 210000000 0 null 0 2 ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 100000000 test1 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 null ``` 2. shielded address transfer to shielded address + **When in this mode,publicFromAddress=null,fromAmount=0,shieldedInputNum=1,publicToAddress=null,toAmount=0** ```console > listshieldednote @@ -1180,6 +1181,7 @@ Example: ``` 3. shielded address transfer to public address + **When in this mode,publicFromAddress=null,fromAmount=0,shieldedInputNum=1,shieldedOutputNum=0** ```console > listshieldednote From 7d9da3e715ff2e41224bc92ad6613f86366656ee Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 2 Jan 2020 19:48:47 +0800 Subject: [PATCH 227/445] change readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b61106910..0eb6d923c 100644 --- a/README.md +++ b/README.md @@ -1161,14 +1161,14 @@ memo1 Example: -1. Public address transfer to shielded addresses - **When in this mode,shieldedInputNum=0,publicToAddress=null,toAmount=0** +1. Public address transfer to shielded addresses + **When in this mode,Some variables must be set as follows, shieldedInputNum=0,publicToAddress=null,toAmount=0** ```console > sendshieldedcoin TRGhNNfnmgLegT4zHNjEqDSADjgmnHvubJ 210000000 0 null 0 2 ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 100000000 test1 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 null ``` -2. shielded address transfer to shielded address - **When in this mode,publicFromAddress=null,fromAmount=0,shieldedInputNum=1,publicToAddress=null,toAmount=0** +2. shielded address transfer to shielded address + **When in this mode,Some variables must be set as follows, publicFromAddress=null,fromAmount=0,shieldedInputNum=1,publicToAddress=null,toAmount=0** ```console > listshieldednote @@ -1180,8 +1180,8 @@ Example: address ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 ``` -3. shielded address transfer to public address - **When in this mode,publicFromAddress=null,fromAmount=0,shieldedInputNum=1,shieldedOutputNum=0** +3. shielded address transfer to public address + **When in this mode,Some variables must be set as follows, publicFromAddress=null,fromAmount=0,shieldedInputNum=1,shieldedOutputNum=0** ```console > listshieldednote From eae8c89585da71df8dd44c55684833edeb669ba2 Mon Sep 17 00:00:00 2001 From: andelf Date: Fri, 3 Jan 2020 06:44:42 +0800 Subject: [PATCH 228/445] docs(README): minor fix of doc formats --- README.md | 221 +++++++++++++++++++++++++++--------------------------- 1 file changed, 112 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 0eb6d923c..12ff7db74 100644 --- a/README.md +++ b/README.md @@ -456,8 +456,8 @@ OwnerAddress id > ID of the initiated proposal, 1 in the example -is_or_not_add_approval +is_or_not_add_approval > true for approve; false for disapprove Example: @@ -816,8 +816,8 @@ origin_energy_limit value > The amount of trx transferred to the contract account -> token_value -Number of TRX10 +token_value +> Number of TRX10 token_id > TRX10 Id @@ -966,8 +966,8 @@ resources for its own use; if it is not empty, the acquired resources are used b OwnerAddress > The address of the account that initiated the transaction, optional, default is the address of the login account. -> frozen_balance -The amount of frozen TRX, the unit is the smallest unit (sun), the minimum is 1000000sun. +frozen_balance +> The amount of frozen TRX, the unit is the smallest unit (sun), the minimum is 1000000sun. frozen_duration > frezen duration, 3 days @@ -989,6 +989,7 @@ by default; when the receiverAddress is set, the delegate resources are unfreeze getDelegatedResource fromAddress toAddress > get the information from the fromAddress to the toAddress resource delegate + getDelegatedResourceAccountIndex address > get the information that address is delegated to other account resources @@ -1161,14 +1162,15 @@ memo1 Example: -1. Public address transfer to shielded addresses - **When in this mode,Some variables must be set as follows, shieldedInputNum=0,publicToAddress=null,toAmount=0** +1. Public address transfer to shielded addresses + **When in this mode,Some variables must be set as follows, shieldedInputNum=0, publicToAddress=null, toAmount=0** + ```console > sendshieldedcoin TRGhNNfnmgLegT4zHNjEqDSADjgmnHvubJ 210000000 0 null 0 2 ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 100000000 test1 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 null ``` -2. shielded address transfer to shielded address - **When in this mode,Some variables must be set as follows, publicFromAddress=null,fromAmount=0,shieldedInputNum=1,publicToAddress=null,toAmount=0** +2. shielded address transfer to shielded address + **When in this mode,Some variables must be set as follows, publicFromAddress=null, fromAmount=0, shieldedInputNum=1,publicToAddress=null,toAmount=0** ```console > listshieldednote @@ -1180,8 +1182,8 @@ Example: address ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 ``` -3. shielded address transfer to public address - **When in this mode,Some variables must be set as follows, publicFromAddress=null,fromAmount=0,shieldedInputNum=1,shieldedOutputNum=0** +3. shielded address transfer to public address + **When in this mode,Some variables must be set as follows, publicFromAddress=null,fromAmount=0,shieldedInputNum=1,shieldedOutputNum=0** ```console > listshieldednote @@ -1206,16 +1208,16 @@ type Example: - ```console - > listshieldednote 0 - Unspend note list like: - 1 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 1 UnSpend - listshieldednote 1 - All note list like: - ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 1 UnSpend - ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 0 Spend test1 - ztron1hn9r3wmytavslztwmlzvuzk3dqpdhwcmda2d0deyu5pwv32dp78saaslyt82w0078y6uzfg8x6w 90000000 06b55fc27f7ec649396706d149d18a0bb003347bdd7f489e3d47205da9cee802 0 Spend test2 - ``` +```console +> listshieldednote 0 +Unspend note list like: +1 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 1 UnSpend +listshieldednote 1 +All note list like: +ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 1 UnSpend +ztron16j06s3p5gvp2jde4vh7w3ug3zz3m62zkyfu86s7ara5lafhp22p9wr3gz0lcdm3pvt7qx0aftu4 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 0 Spend test1 +ztron1hn9r3wmytavslztwmlzvuzk3dqpdhwcmda2d0deyu5pwv32dp78saaslyt82w0078y6uzfg8x6w 90000000 06b55fc27f7ec649396706d149d18a0bb003347bdd7f489e3d47205da9cee802 0 Spend test2 +``` ### resetshieldednote @@ -1236,7 +1238,7 @@ endNum Example: - > scannotebyivk d2a4137cecf049965c4183f78fe9fc9fbeadab6ab3ef70ea749421b4c6b8de04 500 1499 + > scannotebyivk d2a4137cecf049965c4183f78fe9fc9fbeadab6ab3ef70ea749421b4c6b8de04 500 1499 ### ScanNotebyOvk ovk startNum endNum @@ -1264,19 +1266,19 @@ index Example: - ```console - > listshieldednote - Unspend note list like: - 2 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 1 UnSpend - getshieldednullifier 2 - address ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h - value 100000000 - rcm 07ed5471098652ad441575c61868d1e11317de0f73cbb743a4c5cfe78e3d150c - trxId 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 - index 1 - memo - ShieldedNullifier:2a524a3be2643365ecdacf8f0d3ca1de8fad3080eea0b9561435b5d1ee467042 - ``` +```console +> listshieldednote +Unspend note list like: +2 ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h 100000000 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 1 UnSpend +getshieldednullifier 2 +address ztron1ghdy60hya8y72deu0q0r25qfl60unmue6889m3xfc3296a5ut6jcyafzhtp9nlutndukufzap4h +value 100000000 +rcm 07ed5471098652ad441575c61868d1e11317de0f73cbb743a4c5cfe78e3d150c +trxId 4ce5656a13049df00abc7fb3ce78d54c78944d3cbbdfdb29f288e1df5fdf67e1 +index 1 +memo +ShieldedNullifier:2a524a3be2643365ecdacf8f0d3ca1de8fad3080eea0b9561435b5d1ee467042 +``` ### ScanAndMarkNotebyAddress shieldedAddress startNum endNum @@ -1301,10 +1303,10 @@ Generate a sk Example: - ```console - > GetSpendingKey - 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a - ``` +```console +> GetSpendingKey +0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a +``` ### getExpandedSpendingKey sk @@ -1312,22 +1314,22 @@ Generate ask, nsk, ovk from sk Example: - ```console - > getExpandedSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a - ask:252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e - nsk:5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 - ovk:892a10c1d3e8ea22242849e13f177d69e1180d1d5bba118c586765241ba2d3d6 - ``` +```console +> getExpandedSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a +ask:252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e +nsk:5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 +ovk:892a10c1d3e8ea22242849e13f177d69e1180d1d5bba118c586765241ba2d3d6 +``` ### getAkFromAsk ask Generate ak from ask Example: - ```console - > GetAkFromAsk 252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e - ak:f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 - ``` +```console +> GetAkFromAsk 252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e +ak:f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 +``` ### getNkFromNsk nsk @@ -1335,10 +1337,10 @@ Generate nk from nsk Example: - ```console - > GetNkFromNsk 5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 - nk:ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a - ``` +```console +> GetNkFromNsk 5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 +nk:ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a +``` ### getIncomingViewingKey ak[64] nk[64] @@ -1346,10 +1348,10 @@ Generate ivk from ak and nk Example: - ```console - > getincomingviewingkey f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a - ivk:148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 - ``` +```console +> getincomingviewingkey f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a +ivk:148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 +``` ### GetDiversifier @@ -1357,10 +1359,10 @@ Generate a diversifier Example: - ```console - > GetDiversifier - 11db4baf6bd5d5afd3a8b5 - ``` +```console +> GetDiversifier +11db4baf6bd5d5afd3a8b5 +``` ### getshieldedpaymentaddress ivk[64] d[22] @@ -1368,11 +1370,11 @@ Generate a shielded address from sk and d Example: - ```console - GetShieldedPaymentAddress 148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 11db4baf6bd5d5afd3a8b5 - pkd:65c11642115d386ed716b9cc06a3498e86e303d7f20d0869c9de90e31322ac15 - shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z - ``` +```console +GetShieldedPaymentAddress 148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 11db4baf6bd5d5afd3a8b5 +pkd:65c11642115d386ed716b9cc06a3498e86e303d7f20d0869c9de90e31322ac15 +shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z +``` ### BackupShieldedAddress @@ -1380,23 +1382,23 @@ Back up one shielded address Example: - ```console - > loadshieldedwallet - Please input your password for shielded wallet. - > 1qa@WS#ED - LoadShieldedWallet successful !!! - > BackupShieldedAddress - Please input your password for shielded wallet. - > 1qa@WS#ED - The 1th shielded address is ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8qsvp7hgqstp9w5t8eh0vydlyf42gtxjzun - The 2th shielded address is ztron1akz7mt4zqsjqrdrwdsmffu6g5dnehhhtahjlc0c6syy3z9nxxjrzqszy22lyx326edmwqjhqe48 - The 3th shielded address is ztron1m5dx50gryu789q5sh5207chzmmgzf5c7hvn8lr6xs60jfxvkv3d3h0kqkglc60rwq26dchztsty - The 4th shielded address is ztron1at9ud3crdsehe8rgrjkltqly7gsp85y8qzq93pq480hzd2az55pja5cc8r4yfwrnrqqs7q35n4x - Please choose between 1 and 4 - > 1 - 0b1cf73b85742e13015dc6fb8d0986e4ad34c8f468bd8c73cc8fb34bff0dfacea11f85ddcde0bdb904d1de - BackupShieldedAddress successful !! - ``` +```console +> loadshieldedwallet +Please input your password for shielded wallet. +> 1qa@WS#ED +LoadShieldedWallet successful !!! +> BackupShieldedAddress +Please input your password for shielded wallet. +> 1qa@WS#ED +The 1th shielded address is ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8qsvp7hgqstp9w5t8eh0vydlyf42gtxjzun +The 2th shielded address is ztron1akz7mt4zqsjqrdrwdsmffu6g5dnehhhtahjlc0c6syy3z9nxxjrzqszy22lyx326edmwqjhqe48 +The 3th shielded address is ztron1m5dx50gryu789q5sh5207chzmmgzf5c7hvn8lr6xs60jfxvkv3d3h0kqkglc60rwq26dchztsty +The 4th shielded address is ztron1at9ud3crdsehe8rgrjkltqly7gsp85y8qzq93pq480hzd2az55pja5cc8r4yfwrnrqqs7q35n4x +Please choose between 1 and 4 +> 1 +0b1cf73b85742e13015dc6fb8d0986e4ad34c8f468bd8c73cc8fb34bff0dfacea11f85ddcde0bdb904d1de +BackupShieldedAddress successful !! +``` ### ImportShieldedAddress @@ -1404,15 +1406,15 @@ Import one shieled address to local wallet Example: - ```console - > ImportShieldedAddress - Please input your password for shielded wallet. - > 1qa@WS#ED - Please input shielded address hex string. Max retry time: 3 - > 0b1cf73b85742e13015dc6fb8d0986e4ad34c8f468bd8c73cc8fb34bff0dfacea11f85ddcde0bdb904d1de - Import new shielded address is: ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8qsvp7hgqstp9w5t8eh0vydlyf42gtxjzun - ImportShieldedAddress successful !! - ``` +```console +> ImportShieldedAddress +Please input your password for shielded wallet. +> 1qa@WS#ED +Please input shielded address hex string. Max retry time: 3 +> 0b1cf73b85742e13015dc6fb8d0986e4ad34c8f468bd8c73cc8fb34bff0dfacea11f85ddcde0bdb904d1de +Import new shielded address is: ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8qsvp7hgqstp9w5t8eh0vydlyf42gtxjzun +ImportShieldedAddress successful !! +``` ### ShowShieldedAddressInfo @@ -1420,23 +1422,24 @@ Display information about shielded addresses Example: - ```console - > listshieldedaddress - ShieldedAddress : - ztron14t95p936cyev678f6l6xsejnyfzrrzfsg56jaxgp7fzxlsczc2l6866fzc4c8awfnrzy74svkrl - ztron1v6tu4c760vs7m0h94t89m4jcxtuq0nxmag7eequc3c2rnee3sufllq8fjtvfff6y84x3zgcapwp - ztron18vaszshuluufz64uesvzw6wtune90uwexzmsfwtgqq2mlydt4fhy0kz02k3vm2j8er7s5xuyujv - > showshieldedaddressinfo ztron18vaszshuluufz64uesvzw6wtune90uwexzmsfwtgqq2mlydt4fhy0kz02k3vm2j8er7s5xuyujv - The following variables are secret information, please don't show to other people!!! - sk :0deebe55fe7e591803126b531d4fe7c0e3979a2fcadb5a7996f73a8e463231f8 - ivk:aa955c5798e3f611c72fa22842847810114dd5a860db272b2ef50cc8448ced00 - ovk:a1d00b6f761137e1d8b58e77d8685347137131317ba3671f644ffb64bc5baa94 - pkd:182769cbe4f257f1d930b704b9680015bf91abaa6e47d84f55a2cdaa47c8fd0a - d :3b3b0142fcff38916abccc - > showshieldedaddressinfo ztron19lgz39ja8dz427dt9qa8gpkpxanu05y09zplfzzwc640mlx74n4au3037nde3h6m7zsu5xgkrnn - pkd:3a7406c13767c7d08f2883f4884ec6aafdfcdeacebde45f1f4db98df5bf0a1ca - d :2fd028965d3b455579ab28 - ``` +```console +> listshieldedaddress +ShieldedAddress : +ztron14t95p936cyev678f6l6xsejnyfzrrzfsg56jaxgp7fzxlsczc2l6866fzc4c8awfnrzy74svkrl +ztron1v6tu4c760vs7m0h94t89m4jcxtuq0nxmag7eequc3c2rnee3sufllq8fjtvfff6y84x3zgcapwp +ztron18vaszshuluufz64uesvzw6wtune90uwexzmsfwtgqq2mlydt4fhy0kz02k3vm2j8er7s5xuyujv +> showshieldedaddressinfo ztron18vaszshuluufz64uesvzw6wtune90uwexzmsfwtgqq2mlydt4fhy0kz02k3vm2j8er7s5xuyujv +The following variables are secret information, please don't show to other people!!! +sk :0deebe55fe7e591803126b531d4fe7c0e3979a2fcadb5a7996f73a8e463231f8 +ivk:aa955c5798e3f611c72fa22842847810114dd5a860db272b2ef50cc8448ced00 +ovk:a1d00b6f761137e1d8b58e77d8685347137131317ba3671f644ffb64bc5baa94 +pkd:182769cbe4f257f1d930b704b9680015bf91abaa6e47d84f55a2cdaa47c8fd0a +d :3b3b0142fcff38916abccc +> showshieldedaddressinfo ztron19lgz39ja8dz427dt9qa8gpkpxanu05y09zplfzzwc640mlx74n4au3037nde3h6m7zsu5xgkrnn +pkd:3a7406c13767c7d08f2883f4884ec6aafdfcdeacebde45f1f4db98df5bf0a1ca +d :2fd028965d3b455579ab28 +``` + ## Command List Following is a list of Tron Wallet-cli commands: From 204b5b5ad94555851cf0a6f0e8d4b879bd2a3325 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Fri, 3 Jan 2020 12:14:12 +0800 Subject: [PATCH 229/445] fix some highlight of protobuf --- English version of TRON Protocol document.md | 196 ++++++++++--------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/English version of TRON Protocol document.md b/English version of TRON Protocol document.md index 94498fb1c..e282f4b0a 100644 --- a/English version of TRON Protocol document.md +++ b/English version of TRON Protocol document.md @@ -7,12 +7,14 @@ + A basic account is able to apply to be a validation node, which has serval parameters, including extra attributes, public key, URL, voting statistics, history performance, etc. There are three different `Account types`: `Normal`, `AssetIssue`, `Contract`. - - enum AccountType {
 - Normal = 0;
 - AssetIssue = 1;
 - Contract = 2; - 
} +```protobuf +enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2;
 +
} +``` + An `Account` contains 7 parameters: `account_name`: the name for this account – e.g. “_BillsAccount_”. @@ -21,21 +23,21 @@ `votes`: received votes on this account – e.g. _{(“0x1b7w…9xj3”,323), (“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_. `asset`: other assets expect TRX in this account – e.g. _{<“WishToken”,66666>,<”Dogie”,233>}_. `latest_operation_time`: the latest operation time of this account. - - // Account
 - message Account {
 - message Vote {
 - bytes vote_address = 1;
 - int64 vote_count = 2;
 }
 - bytes accout_name = 1;
 - AccountType type = 2;
 - bytes address = 3;
 - int64 balance = 4;
 - repeated Vote votes = 5;
 - map asset = 6; - int64 latest_operation_time = 10;
 - } - +```protobuf +// Account
 +message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6; + int64 latest_operation_time = 10;
 +} +``` A `Witness` contains 8 parameters: `address`: the address of this witness – e.g. “_0xu82h…7237_”. `voteCount`: number of received votes on this witness – e.g. _234234_. @@ -45,31 +47,31 @@ `totalMissed`: the number of blocks this witness missed – e.g. _7_. `latestBlockNum`: the latest height of block – e.g. _4522_. `isjobs`: a bool flag. - - // Witness
 - message Witness{
 - bytes address = 1;
 - int64 voteCount = 2;
 - bytes pubKey = 3;
 - string url = 4;
 - int64 totalProduced = 5;
 - int64 totalMissed = 6;
 - int64 latestBlockNum = 7;
 - bool isJobs = 9; - } - +```protobuf +// Witness
 +message Witness{
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7;
 + bool isJobs = 9; +} +``` + A block typically contains transaction data and a blockheader, which is a list of basic block information, including timestamp, signature, parent hash, root of Merkle tree and so on. A block contains `transactions` and a `block_header`. `transactions`: transaction data of this block. `block_header`: one part of a block. - - // block - 
message Block {
 - repeated Transaction transactions = 1;
 - BlockHeader block_header = 2;
 - } - + ```protobuf + // block +message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2;
 +} +``` A `BlockHeader` contains `raw_data` and `witness_signature`. `raw_data`: a `raw` message. `witness_signature`: signature for this block header from witness node. @@ -81,22 +83,22 @@ `number`: the height of this block – e.g. _13534657_. `witness_id`: the id of witness which packed this block – e.g. “_0xu82h…7237_”. `witness_address`: the adresss of the witness packed this block – e.g. “_0xu82h…7237_”. - - message BlockHeader {
 - message raw {
 - int64 timestamp = 1;
 - bytes txTrieRoot = 2;
 - bytes parentHash = 3;
 - //bytes nonce = 5;
 - //bytes difficulty = 6;
 - uint64 number = 7;
 - uint64 witness_id = 8;
 - bytes witness_address = 9;
 - }
 - raw raw_data = 1;
 - bytes witness_signature = 2;
 - } - +```protobuf +message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2;
 +} +``` message `ChainInventory` contains `BlockId` and `remain_num`. `BlockId`: the identification of block. `remain_num`:the remain number of blocks in the synchronizing process. @@ -104,75 +106,75 @@ A `BlockId` contains 2 parameters: `hash`: the hash of block. `number`: the hash and height of block. - - message ChainInventory { - message BlockId { - bytes hash = 1; - int64 number = 2; - } - repeated BlockId ids = 1; - int64 remain_num = 2; - } - +```protobuf +message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; +} +``` + Transaction contracts mainly includes account create contract, account update contract transfer contract, transfer asset contract, vote asset contract, vote witness contract, witness creation contract, witness update contract, asset issue contract, participate asset issue contract and deploy contract. An `AccountCreateContract` contains 3 parameters: `type`: What type this account is – e.g. _0_ stands for `Normal`. `account_name`: the name for this account – e.g.”_Billsaccount_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message AccountCreateContract {
 - AccountType type = 1;
 - bytes account_name = 2;
 - bytes owner_address = 3;
 - } - +```protobuf +message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 +} +``` A `AccountUpdateContract` contains 2 paremeters: `account_name`: the name for this account – e.g.”_Billsaccount_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message AccountUpdateContract { - bytes account_name = 1; - bytes owner_address = 2; - } - +```protobuf +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} +``` A `TransferContract` contains 3 parameters: `amount`: the amount of TRX – e.g. _12534_. `to_address`: the receiver address – e.g. “_0xu82h…7237_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message TransferContract {
 - bytes owner_address = 1;
 - bytes to_address = 2;
 - int64 amount = 3; - } - +```protobuf +message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; +} +``` A `TransferAssetContract` contains 4 parameters: `asset_name`: the name for asset – e.g.”_Billsaccount_”. `to_address`: the receiver address – e.g. “_0xu82h…7237_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. `amount`: the amount of target asset - e.g._12353_. - - message TransferAssetContract {
 - bytes asset_name = 1;
 - bytes owner_address = 2;
 - bytes to_address = 3;
 - int64 amount = 4;
 - } - +```protobuf +message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 +} +``` A `VoteAssetContract` contains 4 parameters: `vote_address`: the voted address of the asset. `support`: is the votes supportive or not – e.g. _true_. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. `count`: the count number of votes- e.g. _2324234_. - +```protobuf message VoteAssetContract {
 bytes owner_address = 1;
 repeated bytes vote_address = 2;
 bool support = 3;
 int32 count = 5;
 } - +``` A `VoteWitnessContract` contains 4 parameters: `vote_address`: the addresses of those who voted. `support`: is the votes supportive or not - e.g. _true_. From aa46f76983f587089a26586fe922e02ab9d228d8 Mon Sep 17 00:00:00 2001 From: Alberto-zhang <58253830+alberto-zhang@users.noreply.github.com> Date: Mon, 6 Jan 2020 14:14:08 +0800 Subject: [PATCH 230/445] Feature/sm2sm3 . Integrate SM2 (#327) * save sm2 transaction test local * wallet-cli integrate SM2 stage 1 --- .../org/tron/common/crypto/SignInterface.java | 4 + .../common/crypto/SignatureInterface.java | 2 + .../tron/common/utils/TransactionUtils.java | 10 +- .../java/org/tron/common/utils/Utils.java | 13 + .../tron/demo/TransactionSignDemoForSM2.java | 58 +--- .../java/org/tron/keystore/Credentials.java | 60 +--- .../org/tron/keystore/CredentialsEckey.java | 68 +++++ .../org/tron/keystore/CredentialsSM2.java | 68 +++++ src/main/java/org/tron/keystore/Wallet.java | 32 +- .../java/org/tron/keystore/WalletUtils.java | 43 ++- src/main/java/org/tron/test/Test.java | 19 +- .../java/org/tron/walletserver/WalletApi.java | 274 ++++++++++-------- src/main/resources/config-back.conf | 20 ++ 13 files changed, 415 insertions(+), 256 deletions(-) create mode 100644 src/main/java/org/tron/keystore/CredentialsEckey.java create mode 100644 src/main/java/org/tron/keystore/CredentialsSM2.java create mode 100644 src/main/resources/config-back.conf diff --git a/src/main/java/org/tron/common/crypto/SignInterface.java b/src/main/java/org/tron/common/crypto/SignInterface.java index 3da8fd67e..b158c50b7 100644 --- a/src/main/java/org/tron/common/crypto/SignInterface.java +++ b/src/main/java/org/tron/common/crypto/SignInterface.java @@ -19,4 +19,8 @@ public interface SignInterface { byte[] getNodeId(); byte[] Base64toBytes (String signature); + + byte[] getPrivKeyBytes(); + + SignatureInterface sign(byte[] hash); } diff --git a/src/main/java/org/tron/common/crypto/SignatureInterface.java b/src/main/java/org/tron/common/crypto/SignatureInterface.java index 77d7cd101..451c18449 100644 --- a/src/main/java/org/tron/common/crypto/SignatureInterface.java +++ b/src/main/java/org/tron/common/crypto/SignatureInterface.java @@ -2,4 +2,6 @@ public interface SignatureInterface { boolean validateComponents(); + + byte[] toByteArray(); } \ No newline at end of file diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index bc54d14e9..ccaf56222 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -19,6 +19,9 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -164,13 +167,10 @@ public static boolean validTransaction(Transaction signedTransaction) { return true; } - public static Transaction sign(Transaction transaction, ECKey myKey) { + public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); - - //System.out.println("Sign address: " + WalletApi.encode58Check(myKey.getAddress())); - - ECDSASignature signature = myKey.sign(hash); + SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); transaction = transactionBuilderSigned.build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 7f2b26d75..fbc1256ff 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Scanner; public class Utils { public static final String PERMISSION_ID = "Permission_id"; @@ -507,5 +508,17 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean jsonTransaction.put("txID", txID); return jsonTransaction; } + + public static boolean confirmEncrption() { + System.out.println( + "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); + Scanner in = new Scanner(System.in); + String input = in.nextLine().trim(); + String str = input.split("\\s+")[0]; + if ("y".equalsIgnoreCase(str)) { + return true; + } + return false; + } } diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 9cadf7f1a..fc57493a5 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -70,21 +70,21 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { - SM2 ecKey = SM2.fromPrivate(privateKey); + SM2 sm2 = SM2.fromPrivate(privateKey); Transaction transaction1 = Transaction.parseFrom(transaction); byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = ecKey.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); + byte[] hash = sm2.hash(rawdata); + byte[] sign = sm2.sign(hash).toByteArray(); return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); } private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { - SM2 ecKey = SM2.fromPrivate(privateKey); + SM2 sm2 = SM2.fromPrivate(privateKey); Transaction transaction1 = Transaction.parseFrom(transaction); byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = ecKey.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); + byte[] hash = sm2.hash(rawdata); + byte[] sign = sm2.sign(hash).toByteArray(); return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); } @@ -105,56 +105,22 @@ private static void hexStringTobase58check() { } public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; + String privateStr = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; byte[] privateBytes = ByteArray.fromHexString(privateStr); - SM2 ecKey = SM2.fromPrivate(privateBytes); - byte[] from = ecKey.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); + SM2 sm2 = SM2.fromPrivate(privateBytes); + byte[] from = sm2.getAddress(); + byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); - /* - //sign a transaction - Transaction transaction1 = TransactionUtils.sign(transaction, ecKey); - //get byte transaction - byte[] transaction2 = transaction1.toByteArray(); - System.out.println("transaction2 ::::: " + ByteArray.toHexString(transaction2)); - - //sign a transaction in byte format and return a Transaction object - Transaction transaction3 = signTransaction2Object(transactionBytes, privateBytes); - System.out.println("transaction3 ::::: " + ByteArray.toHexString(transaction3.toByteArray())); - */ //sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); - System.out.println("transaction4 ::::: " + ByteArray.toHexString(transaction4)); - Transaction transactionSigned; - if (WalletApi.getRpcVersion() == 2) { - TransactionExtention transactionExtention = WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); - if (transactionExtention == null) { - System.out.println("transactionExtention is null"); - return; - } - Return ret = transactionExtention.getResult(); - if (!ret.getResult()) { - System.out.println("Code = " + ret.getCode()); - System.out.println("Message = " + ret.getMessage().toStringUtf8()); - return; - } - System.out.println( - "Receive txid = " + ByteArray.toHexString(transactionExtention.getTxid().toByteArray())); - transactionSigned = transactionExtention.getTransaction(); - } else { - transactionSigned = WalletApi.signTransactionByApi(transaction, ecKey.getPrivKeyBytes()); - } - byte[] transaction5 = transactionSigned.toByteArray(); - System.out.println("transaction5 ::::: " + ByteArray.toHexString(transaction5)); - if (!Arrays.equals(transaction4, transaction5)){ - System.out.println("transaction4 is not equals to transaction5 !!!!!"); - } boolean result = broadcast(transaction4); System.out.println(result); } + + } diff --git a/src/main/java/org/tron/keystore/Credentials.java b/src/main/java/org/tron/keystore/Credentials.java index a13033723..058bb7f1f 100644 --- a/src/main/java/org/tron/keystore/Credentials.java +++ b/src/main/java/org/tron/keystore/Credentials.java @@ -1,62 +1,10 @@ package org.tron.keystore; -import org.tron.common.crypto.ECKey; -import org.tron.common.utils.ByteArray; -import org.tron.walletserver.WalletApi; -/** - * Credentials wrapper. - */ -public class Credentials { +import org.tron.common.crypto.SignInterface; - private final ECKey ecKeyPair; - private final String address; +public interface Credentials { + SignInterface getPair(); - private Credentials(ECKey ecKeyPair, String address) { - this.ecKeyPair = ecKeyPair; - this.address = address; - } - - public ECKey getEcKeyPair() { - return ecKeyPair; - } - - public String getAddress() { - return address; - } - - public static Credentials create(ECKey ecKeyPair) { - String address = WalletApi.encode58Check(ecKeyPair.getAddress()); - return new Credentials(ecKeyPair, address); - } - - public static Credentials create(String privateKey) { - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); - return create(eCkey); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Credentials that = (Credentials) o; - - if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { - return false; - } - - return address != null ? address.equals(that.address) : that.address == null; - } - - @Override - public int hashCode() { - int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; - result = 31 * result + (address != null ? address.hashCode() : 0); - return result; - } + String getAddress(); } diff --git a/src/main/java/org/tron/keystore/CredentialsEckey.java b/src/main/java/org/tron/keystore/CredentialsEckey.java new file mode 100644 index 000000000..94aa0f255 --- /dev/null +++ b/src/main/java/org/tron/keystore/CredentialsEckey.java @@ -0,0 +1,68 @@ +package org.tron.keystore; + +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +/** + * Credentials wrapper. + */ +public class CredentialsEckey implements Credentials { + + private final ECKey ecKeyPair; + private final String address; + + private CredentialsEckey(ECKey ecKeyPair, String address) { + this.ecKeyPair = ecKeyPair; + this.address = address; + } + +// public ECKey getEcKeyPair() { +// return ecKeyPair; +// } + + public String getAddress() { + return address; + } + + public static CredentialsEckey create(ECKey ecKeyPair) { + String address = WalletApi.encode58Check(ecKeyPair.getAddress()); + return new CredentialsEckey(ecKeyPair, address); + } + + public static CredentialsEckey create(String privateKey) { + ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CredentialsEckey that = (CredentialsEckey) o; + + if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public SignInterface getPair() { + return ecKeyPair; + } +} diff --git a/src/main/java/org/tron/keystore/CredentialsSM2.java b/src/main/java/org/tron/keystore/CredentialsSM2.java new file mode 100644 index 000000000..537f8ce65 --- /dev/null +++ b/src/main/java/org/tron/keystore/CredentialsSM2.java @@ -0,0 +1,68 @@ +package org.tron.keystore; + +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +/** + * Credentials wrapper. + */ +public class CredentialsSM2 implements Credentials { + + private final SM2 sm2Pair; + private final String address; + + private CredentialsSM2(SM2 sm2Pair, String address) { + this.sm2Pair = sm2Pair; + this.address = address; + } + +// public SM2 getEcKeyPair() { +// return sm2Pair; +// } + + public String getAddress() { + return address; + } + + public static CredentialsSM2 create(SM2 sm2Pair) { + String address = WalletApi.encode58Check(sm2Pair.getAddress()); + return new CredentialsSM2(sm2Pair, address); + } + + public static CredentialsSM2 create(String privateKey) { + SM2 eCkey = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CredentialsSM2 that = (CredentialsSM2) o; + + if (sm2Pair != null ? !sm2Pair.equals(that.sm2Pair) : that.sm2Pair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = sm2Pair != null ? sm2Pair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public SignInterface getPair() { + return sm2Pair; + } +} diff --git a/src/main/java/org/tron/keystore/Wallet.java b/src/main/java/org/tron/keystore/Wallet.java index 55e15ce55..7331a5a75 100644 --- a/src/main/java/org/tron/keystore/Wallet.java +++ b/src/main/java/org/tron/keystore/Wallet.java @@ -6,6 +6,9 @@ import org.bouncycastle.crypto.params.KeyParameter; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CipherException; import org.tron.walletserver.WalletApi; @@ -59,7 +62,7 @@ public class Wallet { static final String AES_128_CTR = "pbkdf2"; static final String SCRYPT = "scrypt"; - public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) + public static WalletFile create(byte[] password, SignInterface ecKeySm2Pair, int n, int p) throws CipherException { byte[] salt = generateRandomBytes(32); @@ -69,32 +72,30 @@ public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); - byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes(); + byte[] privateKeyBytes = ecKeySm2Pair.getPrivKeyBytes(); byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, privateKeyBytes); byte[] mac = generateMac(derivedKey, cipherText); - return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); + return createWalletFile(ecKeySm2Pair, cipherText, iv, salt, mac, n, p); } - public static WalletFile createStandard(byte[] password, ECKey ecKeyPair) + public static WalletFile createStandard(byte[] password, SignInterface ecKeySm2Pair) throws CipherException { - return create(password, ecKeyPair, N_STANDARD, P_STANDARD); + return create(password, ecKeySm2Pair, N_STANDARD, P_STANDARD); } - - public static WalletFile createLight(byte[] password, ECKey ecKeyPair) + public static WalletFile createLight(byte[] password, SignInterface ecKeySm2Pair) throws CipherException { - return create(password, ecKeyPair, N_LIGHT, P_LIGHT); + return create(password, ecKeySm2Pair, N_LIGHT, P_LIGHT); } - private static WalletFile createWalletFile( - ECKey ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, - int n, int p) { + SignInterface ecKeySm2Pair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, + int n, int p) { WalletFile walletFile = new WalletFile(); - walletFile.setAddress(WalletApi.encode58Check(ecKeyPair.getAddress())); + walletFile.setAddress(WalletApi.encode58Check(ecKeySm2Pair.getAddress())); WalletFile.Crypto crypto = new WalletFile.Crypto(); crypto.setCipher(CIPHER); @@ -267,6 +268,13 @@ public static ECKey decrypt(byte[] password, WalletFile walletFile) StringUtils.clear(privateKey); return ecKey; } + public static SM2 decryptSM2(byte[] password, WalletFile walletFile) + throws CipherException { + byte[] privateKey = decrypt2PrivateBytes(password, walletFile); + SM2 sm2 = SM2.fromPrivate(privateKey); + StringUtils.clear(privateKey); + return sm2; + } static void validate(WalletFile walletFile) throws CipherException { WalletFile.Crypto crypto = walletFile.getCrypto(); diff --git a/src/main/java/org/tron/keystore/WalletUtils.java b/src/main/java/org/tron/keystore/WalletUtils.java index f409465fe..8d206829f 100644 --- a/src/main/java/org/tron/keystore/WalletUtils.java +++ b/src/main/java/org/tron/keystore/WalletUtils.java @@ -3,8 +3,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.Config; import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Utils; +import org.tron.core.config.Configuration; import org.tron.core.exception.CipherException; import java.io.File; @@ -22,10 +26,16 @@ public class WalletUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); + private static boolean isEckey = true; static { + Config config = Configuration.getByPath("config.conf");//it is needs set to be a constant objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletUtils getConfig isEckey: " + isEckey); + } } public static String generateFullNewWalletFile(byte[] password, File destinationDirectory) @@ -46,20 +56,24 @@ public static String generateNewWalletFile( byte[] password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - - ECKey ecKeyPair = new ECKey(Utils.getRandom()); - return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt); + SignInterface ecKeySm2Pair = null; + if (isEckey) { + ecKeySm2Pair = new ECKey(Utils.getRandom()); + } else { + ecKeySm2Pair = new SM2(Utils.getRandom()); + } + return generateWalletFile(password, ecKeySm2Pair, destinationDirectory, useFullScrypt); } public static String generateWalletFile( - byte[] password, ECKey ecKeyPair, File destinationDirectory, boolean useFullScrypt) + byte[] password, SignInterface ecKeySm2Pair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile; if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeyPair); + walletFile = Wallet.createStandard(password, ecKeySm2Pair); } else { - walletFile = Wallet.createLight(password, ecKeyPair); + walletFile = Wallet.createLight(password, ecKeySm2Pair); } String fileName = getWalletFileName(walletFile); @@ -71,14 +85,14 @@ public static String generateWalletFile( } public static void updateWalletFile( - byte[] password, ECKey ecKeyPair, File source, boolean useFullScrypt) + byte[] password, SignInterface ecKeySm2Pair, File source, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeyPair); + walletFile = Wallet.createStandard(password, ecKeySm2Pair); } else { - walletFile = Wallet.createLight(password, ecKeyPair); + walletFile = Wallet.createLight(password, ecKeySm2Pair); } objectMapper.writeValue(source, walletFile); @@ -128,11 +142,15 @@ public static String generateWalletFile(WalletFile walletFile, File destinationD public static Credentials loadCredentials(byte[] password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); - return Credentials.create(Wallet.decrypt(password, walletFile)); + + if (isEckey) { + return CredentialsEckey.create(Wallet.decrypt(password, walletFile)); + } + return CredentialsSM2.create(Wallet.decryptSM2(password, walletFile)); } public static WalletFile loadWalletFile(File source) throws IOException { - return objectMapper.readValue(source, WalletFile.class); + return objectMapper.readValue(source, WalletFile.class); } // // public static Credentials loadBip39Credentials(String password, String mnemonic) { @@ -176,7 +194,6 @@ public static String getMainnetKeyDirectory() { } - // public static boolean isValidPrivateKey(String privateKey) { // String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); // return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; @@ -195,7 +212,7 @@ public static String getMainnetKeyDirectory() { // } public static void generateSkeyFile(SKeyCapsule skey, File file) - throws IOException { + throws IOException { objectMapper.writeValue(file, skey); } diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 3e597f4ee..534a08a37 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -7,12 +7,15 @@ import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; import org.tron.core.exception.CipherException; import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; +import org.tron.keystore.CredentialsEckey; import org.tron.keystore.WalletUtils; import org.tron.protos.Contract; import org.tron.protos.Contract.TransferContract; @@ -316,13 +319,14 @@ public static void testSha3() { public static void testGenerateWalletFile() throws CipherException, IOException { String PASSWORD = "Insecure Pa55w0rd"; String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); +// ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); + SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(priKeyHex)); File file = new File("out"); - String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), eCkey, file, true); + String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), sm2, file, true); Credentials credentials = WalletUtils.loadCredentials(PASSWORD.getBytes(), new File(file, fileName)); String address = credentials.getAddress(); - ECKey ecKeyPair = credentials.getEcKeyPair(); - String prikey = ByteArray.toHexString(ecKeyPair.getPrivKeyBytes()); + SignInterface pair = credentials.getPair(); + String prikey = ByteArray.toHexString(pair.getPrivKeyBytes()); System.out.println("address = " + address); System.out.println("prikey = " + prikey); @@ -360,6 +364,13 @@ public static void testPasswordStrength(){ System.out.println(password + " strength is " + level); } } + + public static void interfaceTest() { + String privateKey = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; + SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); + String address = WalletApi.encode58Check(sm2.getAddress()); + System.out.println(address); + } public static void main(String[] args) throws Exception { testPasswordStrength(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index d0fe50cfd..f1b4449d4 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -21,6 +21,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.TransactionUtils; @@ -52,6 +53,7 @@ public class WalletApi { private byte[] address; private static byte addressPreFixByte = CommonConstant.ADD_PRE_FIX_BYTE_TESTNET; private static int rpcVersion = 0; + private static boolean isEckey = true; private static GrpcClient rpcCli = init(); @@ -85,6 +87,11 @@ public static GrpcClient init() { } if (config.hasPath("RPC_version")) { rpcVersion = config.getInt("RPC_version"); + System.out.println("WalletApi getRpcVsersion: " + rpcVersion); + } + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletApi getConfig isEckey: " + isEckey); } return new GrpcClient(fullNode, solidityNode); } @@ -137,15 +144,27 @@ public static int getRpcVersion() { * Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { - ECKey ecKey = new ECKey(Utils.getRandom()); - WalletFile walletFile = Wallet.createStandard(password, ecKey); + WalletFile walletFile = null; + if (isEckey) { + ECKey ecKey = new ECKey(Utils.getRandom()); + walletFile = Wallet.createStandard(password, ecKey); + } else { + SM2 sm2 = new SM2(Utils.getRandom()); + walletFile = Wallet.createStandard(password, sm2); + } return walletFile; } // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - ECKey ecKey = ECKey.fromPrivate(priKey); - WalletFile walletFile = Wallet.createStandard(password, ecKey); + WalletFile walletFile=null; + if (isEckey) { + ECKey ecKey = ECKey.fromPrivate(priKey); + walletFile = Wallet.createStandard(password, ecKey); + } else { + SM2 sm2 = SM2.fromPrivate(priKey); + walletFile = Wallet.createStandard(password, sm2); + } return walletFile; } @@ -183,6 +202,10 @@ public ECKey getEcKey(WalletFile walletFile, byte[] password) throws CipherExcep return Wallet.decrypt(password, walletFile); } + public SM2 getSM2(WalletFile walletFile, byte[] password) throws CipherException { + return Wallet.decryptSM2(password, walletFile); + } + public byte[] getPrivateBytes(byte[] password) throws CipherException, IOException { WalletFile walletFile = loadWalletFile(); return Wallet.decrypt2PrivateBytes(password, walletFile); @@ -286,7 +309,7 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso "No keystore file found, please use registerwallet or importwallet first!"); } Credentials credentials = WalletUtils.loadCredentials(oldPassword, wallet); - WalletUtils.updateWalletFile(newPassowrd, credentials.getEcKeyPair(), wallet, true); + WalletUtils.updateWalletFile(newPassowrd, credentials.getPair(), wallet, true); return true; } @@ -352,8 +375,11 @@ private Transaction signTransaction(Transaction transaction) char[] password = Utils.inputPassword(false); byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -391,7 +417,11 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -433,13 +463,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -448,13 +478,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); + .getParameter().unpack(CreateSmartContract.class); byte[] contractAddress = generateContractAddress( createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( @@ -463,7 +493,7 @@ private void showTransactionAfterSign(Transaction transaction) } private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -481,7 +511,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -498,9 +528,9 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -513,7 +543,7 @@ private boolean processTransaction(Transaction transaction) System.out.println(Utils.printTransactionExceptId(transaction)); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -535,7 +565,7 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p //Warning: do not invoke this interface provided by others. public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -547,7 +577,7 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction //Warning: do not invoke this interface provided by others. public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -572,25 +602,25 @@ public static byte[] createAdresss(byte[] passPhrase) { //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -643,7 +673,7 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -687,7 +717,7 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + .createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -799,7 +829,7 @@ public boolean voteWitness(byte[] owner, HashMap witness) } public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -811,8 +841,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] } public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -826,10 +856,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] } public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + .newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -842,7 +872,7 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra } public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -853,7 +883,7 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] } public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -884,7 +914,7 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( } public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -893,7 +923,7 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] } public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -902,7 +932,7 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] } public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -911,14 +941,14 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] } public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + .newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -971,7 +1001,7 @@ public static boolean addressValid(byte[] address) { if (preFixbyte != WalletApi.getAddressPreFixByte()) { System.out .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + + preFixbyte + " !!"); return false; } //Other rule; @@ -997,9 +1027,9 @@ private static byte[] decode58Check(String input) { byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + hash1[1] == decodeCheck[decodeData.length + 1] && + hash1[2] == decodeCheck[decodeData.length + 2] && + hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1132,13 +1162,13 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { } public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { @@ -1146,13 +1176,13 @@ public static Optional getTransactionsFromThis2(byte[] // } public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { @@ -1168,7 +1198,7 @@ public static Optional getTransactionInfoById(String txID) { } public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, frozen_balance, @@ -1205,7 +1235,7 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) } private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + long frozen_duration, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } @@ -1241,7 +1271,7 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo } Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + .newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1275,13 +1305,13 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); @@ -1312,7 +1342,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { } Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1336,7 +1366,7 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { } Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1383,7 +1413,7 @@ public static Optional getProposal(String id) { } public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1406,7 +1436,7 @@ public static Optional getChainParameters() { public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1426,9 +1456,9 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) } public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { + long id, boolean is_add_approval) { Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1447,7 +1477,7 @@ public boolean deleteProposal(byte[] owner, long id) } public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); @@ -1455,7 +1485,7 @@ public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[ } public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + byte[] secondTokenId, long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1468,8 +1498,8 @@ public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstToken } public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + byte[] firstTokenId, long firstTokenBalance, + byte[] secondTokenId, long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1493,7 +1523,7 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon } public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1516,9 +1546,9 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l } public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1528,7 +1558,7 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b } public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1541,9 +1571,9 @@ public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId } public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { + long exchangeId, byte[] tokenId, long quant, long expected) { Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1597,21 +1627,21 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; + abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; + abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; + abiItem.getAsJsonObject().get("name").getAsString() : null; JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; + abiItem.getAsJsonObject().get("type").getAsString() : null; boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; + abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; if (type == null) { System.out.println("No type!"); return null; @@ -1633,7 +1663,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1642,10 +1672,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1658,7 +1688,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1667,10 +1697,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1691,7 +1721,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { } public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1705,7 +1735,7 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract byte[] contractAddress, long originEnergyLimit) { Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); @@ -1713,20 +1743,20 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract } public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + byte[] contractAddress) { Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + byte[] address, + String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, + String libraryAddressPair, String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1754,7 +1784,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } @@ -1762,7 +1792,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa } private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1791,7 +1821,7 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } else if (compilerVersion.equalsIgnoreCase("v5")) { //0.5.4 version String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1805,8 +1835,8 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + byte[] contractAddress, + long callValue, byte[] data, long tokenValue, String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1833,7 +1863,7 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { } public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1867,7 +1897,7 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig contractAddress, originEnergyLimit); TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + .updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { @@ -1904,8 +1934,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) } public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1929,7 +1959,7 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1953,8 +1983,8 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str } public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + long feeLimit, + long tokenValue, String tokenId, boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1980,12 +2010,12 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa Transaction transaction = transactionExtention.getTransaction(); // for constant if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + transactionExtention.getConstantResult(0) != null && + transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + .toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } @@ -1993,7 +2023,7 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2111,7 +2141,11 @@ public Transaction addTransactionSign(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } org.tron.keystore.StringUtils.clear(passwd); return transaction; } @@ -2135,7 +2169,7 @@ public static Optional GetMerkleTreeVoucherInfo( } public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2152,7 +2186,7 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry } public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2237,7 +2271,7 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall } public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2254,7 +2288,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2263,7 +2297,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + .clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2279,10 +2313,10 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + .addContract( + Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) + .setParameter( + Any.pack(contractBuild.build())).build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2292,7 +2326,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2359,7 +2393,7 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + .updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf new file mode 100644 index 000000000..171520768 --- /dev/null +++ b/src/main/resources/config-back.conf @@ -0,0 +1,20 @@ +net { + type = mainnet +} + +fullnode = { + ip.list = [ + "47.89.189.124:50055", + "47.89.178.193:50055" + ] +} + +#soliditynode = { +# ip.list = [ +# "127.0.0.1:50052" +# ] +#} +crypto { + engine=sm2 +} +RPC_version = 2 From f57e33c232f0084e4c24efa2016d92f7e1819fa8 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 6 Jan 2020 14:19:16 +0800 Subject: [PATCH 231/445] change command --- .../org/tron/core/zen/ShieldedWrapper.java | 115 ++++++++++++++---- src/main/java/org/tron/walletcli/Client.java | 32 +++-- .../java/org/tron/walletserver/WalletApi.java | 11 +- 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/tron/core/zen/ShieldedWrapper.java b/src/main/java/org/tron/core/zen/ShieldedWrapper.java index 9463a68fb..7c4005ca1 100644 --- a/src/main/java/org/tron/core/zen/ShieldedWrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedWrapper.java @@ -1,5 +1,7 @@ package org.tron.core.zen; +import static org.tron.walletcli.Client.getCmd; + import com.google.protobuf.ByteString; import io.netty.util.internal.StringUtil; import lombok.Getter; @@ -16,6 +18,7 @@ import org.tron.keystore.StringUtils; import org.tron.keystore.WalletUtils; import org.tron.protos.Protocol.Block; +import org.tron.walletcli.Client; import org.tron.walletserver.WalletApi; import java.io.File; @@ -589,7 +592,7 @@ public void initShieldedWaletFile() throws IOException, CipherException { } } - public byte[] backupShieldedAddress() throws IOException, CipherException { + public ShieldedAddressInfo backupShieldedWallet() throws IOException, CipherException { ZenUtils.checkFolderExist(PREFIX_FOLDER); if (shieldedSkeyFileExist()) { @@ -621,7 +624,11 @@ public byte[] backupShieldedAddress() throws IOException, CipherException { System.out.println("The " + (i + 1) + "th shielded address is " + shieldedAddressInfoList.get(i).getAddress()); } - System.out.println("Please choose between 1 and " + shieldedAddressInfoList.size()); + String tipInfo = "Please choose between 1 and " + shieldedAddressInfoList.size(); + if (shieldedAddressInfoList.size() == 1) { + tipInfo = "Please choose shielded address index 1"; + } + System.out.println(tipInfo); Scanner in = new Scanner(System.in); while (true) { String input = in.nextLine().trim(); @@ -630,27 +637,63 @@ public byte[] backupShieldedAddress() throws IOException, CipherException { try { n = new Integer(num); } catch (NumberFormatException e) { - System.out.println("Invaild number of " + num); - System.out.println("Please choose again between 1 and " + shieldedAddressInfoList.size()); + System.out.println("Invalid number of " + num); + System.out.println(tipInfo); continue; } if (n < 1 || n > shieldedAddressInfoList.size()) { - System.out.println("Please choose again between 1 and " + shieldedAddressInfoList.size()); + System.out.println(tipInfo); continue; } - ShieldedAddressInfo targetShieldedAddress = shieldedAddressInfoList.get(n - 1); - byte[] skAndD = new byte[targetShieldedAddress.getSk().length + targetShieldedAddress.getD() - .getData().length]; - - System.arraycopy(targetShieldedAddress.getSk(), 0, skAndD, 0, - targetShieldedAddress.getSk().length); - System.arraycopy(targetShieldedAddress.getD().getData(), 0, - skAndD, targetShieldedAddress.getSk().length, targetShieldedAddress.getD().getData().length); - return skAndD; +// ShieldedAddressInfo targetShieldedAddress = shieldedAddressInfoList.get(n - 1); +// byte[] skAndD = new byte[targetShieldedAddress.getSk().length + targetShieldedAddress.getD() +// .getData().length]; +// +// System.arraycopy(targetShieldedAddress.getSk(), 0, skAndD, 0, +// targetShieldedAddress.getSk().length); +// System.arraycopy(targetShieldedAddress.getD().getData(), 0, +// skAndD, targetShieldedAddress.getSk().length, targetShieldedAddress.getD().getData().length); +// return skAndD; + return shieldedAddressInfoList.get(n - 1); } } - public byte[] importShieldedAddress() throws IOException, CipherException { +// public byte[] importShieldedWallet() throws IOException, CipherException { +// ZenUtils.checkFolderExist(PREFIX_FOLDER); +// +// if (shieldedSkeyFileExist()) { +// byte[] tempShieldedKey = loadSkey(); +// if (ArrayUtils.isEmpty(tempShieldedKey)) { +// System.out.println("Invalid password."); +// return null; +// } else { +// shieldedSkey = tempShieldedKey; +// } +// } else { +// shieldedSkey = generateSkey(); +// } +// loadShieldWallet(); +// +// byte[] temp = new byte[86]; +// byte[] result = null; +// System.out.println("Please input shielded wallet hex string. such as 'sk d',Max retry time:" + 3); +// int nTime = 0; +// while (nTime < 3) { +// int len = System.in.read(temp, 0, temp.length); +// if (len >= 86) { +// byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); +// result = StringUtils.hexs2Bytes(privateKey); +// StringUtils.clear(privateKey); +// break; +// } +// StringUtils.clear(result); +// System.out.println("Invalid shielded address, please input again."); +// ++nTime; +// } +// return result; +// } + + public byte[] importShieldedWallet() throws IOException, CipherException { ZenUtils.checkFolderExist(PREFIX_FOLDER); if (shieldedSkeyFileExist()) { @@ -666,18 +709,46 @@ public byte[] importShieldedAddress() throws IOException, CipherException { } loadShieldWallet(); - byte[] temp = new byte[86]; byte[] result = null; - System.out.println("Please input shielded address hex string. Max retry time: " + 3); + System.out.println("Please input shielded wallet hex string. such as 'sk d',Max retry time:" + 3); int nTime = 0; + + byte[] buffer = new byte[1000]; while (nTime < 3) { - int len = System.in.read(temp, 0, temp.length); - if (len >= 86) { - byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); - result = StringUtils.hexs2Bytes(privateKey); - StringUtils.clear(privateKey); + System.in.read(buffer, 0, buffer.length); + String[] array = Client.getCmd(new String(buffer).trim()); + if (array.length == 2) { + System.out.println("Import shielded wallet hex string is : "); + System.out.println("sk:" + array[0]); + System.out.println("d :" + array[1]); + + byte[] sk = ByteArray.fromHexString(array[0]); + byte[] d = ByteArray.fromHexString(array[1]); + result = new byte[sk.length + d.length]; + System.arraycopy(result, 0, sk, 0, sk.length); + System.arraycopy(result, sk.length, d, 0, d.length); break; } + + + +// if (len >= 86) { +// byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); +// result = StringUtils.hexs2Bytes(privateKey); +// StringUtils.clear(privateKey); +// break; +// } + + +// int len = System.in.read(temp, 0, temp.length); +// if (len >= 86) { +// byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); +// result = StringUtils.hexs2Bytes(privateKey); +// StringUtils.clear(privateKey); +// break; +// } + + StringUtils.clear(result); System.out.println("Invalid shielded address, please input again."); ++nTime; diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 88d04200c..dcb7adff3 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2782,22 +2782,28 @@ private void getShieldedPaymentAddress(String[] parameters) { } } - private void backupShieldedAddress() throws IOException, CipherException { - byte[] priKey = ShieldedWrapper.getInstance().backupShieldedAddress(); - if (!ArrayUtils.isEmpty(priKey)) { - for (int i = 0; i < priKey.length; i++) { - StringUtils.printOneByte(priKey[i]); + private void backupShieldedWallet() throws IOException, CipherException { + ShieldedAddressInfo addressInfo = ShieldedWrapper.getInstance().backupShieldedWallet(); + if (addressInfo != null) { + System.out.print("sk:"); + for (int i = 0; i < addressInfo.getSk().length; i++) { + StringUtils.printOneByte(addressInfo.getSk()[i]); + } + System.out.println(); + + System.out.print("d :"); + for (int i = 0; i < addressInfo.getD().getData().length; i++) { + StringUtils.printOneByte(addressInfo.getD().getData()[i]); } System.out.println(); - StringUtils.clear(priKey); System.out.println("BackupShieldedAddress successful !!!"); } else { System.out.println("BackupShieldedAddress failed !!!"); } } - private void importShieldedAddress() throws CipherException, IOException { - byte[] priKey = ShieldedWrapper.getInstance().importShieldedAddress(); + private void importShieldedWallet() throws CipherException, IOException { + byte[] priKey = ShieldedWrapper.getInstance().importShieldedWallet(); if (!ArrayUtils.isEmpty(priKey) && priKey.length == 43) { byte[] sk = new byte[32]; byte[] d = new byte[11]; @@ -2862,7 +2868,7 @@ private void help() { System.out.println("Input any one of the listed commands, to display how-to tips."); } - private String[] getCmd(String cmdLine) { + public static String[] getCmd(String cmdLine) { if (cmdLine.indexOf("\"") < 0 || cmdLine.toLowerCase().startsWith("deploycontract") || cmdLine.toLowerCase().startsWith("triggercontract") || cmdLine.toLowerCase().startsWith("triggerconstantcontract") @@ -3334,12 +3340,12 @@ private void run() { scanAndMarkNoteByAddress(parameters); break; } - case "importshieldedaddress": { - importShieldedAddress(); + case "importshieldedwallet": { + importShieldedWallet(); break; } - case "backupshieldedaddress": { - backupShieldedAddress(); + case "backupshieldedwallet": { + backupShieldedWallet(); break; } case "create2": { diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index d0fe50cfd..79bcebc66 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -232,7 +232,10 @@ public static File selcetWalletFile() { for (int i = 0; i < wallets.length; i++) { System.out.println("The " + (i + 1) + "th keystore file name is " + wallets[i].getName()); } - System.out.println("Please choose between 1 and " + wallets.length); + String tipInfo = "Please choose between 1 and " + wallets.length; + if (wallets.length == 1) { + tipInfo = "Please choose address index 1"; + } Scanner in = new Scanner(System.in); while (true) { String input = in.nextLine().trim(); @@ -241,12 +244,12 @@ public static File selcetWalletFile() { try { n = new Integer(num); } catch (NumberFormatException e) { - System.out.println("Invaild number of " + num); - System.out.println("Please choose again between 1 and " + wallets.length); + System.out.println("Invalid number of " + num); + System.out.println(tipInfo); continue; } if (n < 1 || n > wallets.length) { - System.out.println("Please choose again between 1 and " + wallets.length); + System.out.println(tipInfo); continue; } wallet = wallets[n - 1]; From 0a119f1965c76d92e677cbec7ca4086b36f539ce Mon Sep 17 00:00:00 2001 From: Alberto-zhang <58253830+alberto-zhang@users.noreply.github.com> Date: Mon, 6 Jan 2020 15:20:09 +0800 Subject: [PATCH 232/445] Feature/sm2sm3 (#331) * add the sm2/sm3 algorithm Signed-off-by: zhenping * add sm2/sm3 test demo Signed-off-by: zhenping * save sm2 transaction test local * wallet-cli integrate SM2 stage 1 --- build.gradle | 262 +- .../java/org/tron/common/crypto/ECKey.java | 2500 +++++++++-------- .../org/tron/common/crypto/Sha256Hash.java | 1 + .../org/tron/common/crypto/SignInterface.java | 26 + .../org/tron/common/crypto/SignUtils.java | 54 + .../common/crypto/SignatureInterface.java | 7 + .../java/org/tron/common/crypto/sm2/SM2.java | 1245 ++++++++ .../org/tron/common/crypto/sm2/SM2Signer.java | 323 +++ .../java/org/tron/common/crypto/sm2/SM3.java | 22 + .../org/tron/common/utils/DecodeUtil.java | 39 + src/main/java/org/tron/common/utils/Hash.java | 210 ++ .../tron/common/utils/TransactionUtils.java | 10 +- .../java/org/tron/common/utils/Utils.java | 2 +- .../tron/demo/TransactionSignDemoForSM2.java | 126 + .../java/org/tron/keystore/Credentials.java | 60 +- .../org/tron/keystore/CredentialsEckey.java | 68 + .../org/tron/keystore/CredentialsSM2.java | 68 + src/main/java/org/tron/keystore/Wallet.java | 32 +- .../java/org/tron/keystore/WalletUtils.java | 43 +- src/main/java/org/tron/test/Test.java | 19 +- .../java/org/tron/walletserver/WalletApi.java | 274 +- src/main/resources/config-back.conf | 20 + 22 files changed, 3851 insertions(+), 1560 deletions(-) create mode 100644 src/main/java/org/tron/common/crypto/SignInterface.java create mode 100644 src/main/java/org/tron/common/crypto/SignUtils.java create mode 100644 src/main/java/org/tron/common/crypto/SignatureInterface.java create mode 100644 src/main/java/org/tron/common/crypto/sm2/SM2.java create mode 100644 src/main/java/org/tron/common/crypto/sm2/SM2Signer.java create mode 100644 src/main/java/org/tron/common/crypto/sm2/SM3.java create mode 100644 src/main/java/org/tron/common/utils/DecodeUtil.java create mode 100644 src/main/java/org/tron/common/utils/Hash.java create mode 100644 src/main/java/org/tron/demo/TransactionSignDemoForSM2.java create mode 100644 src/main/java/org/tron/keystore/CredentialsEckey.java create mode 100644 src/main/java/org/tron/keystore/CredentialsSM2.java create mode 100644 src/main/resources/config-back.conf diff --git a/build.gradle b/build.gradle index b08079dcb..6d00c8c17 100644 --- a/build.gradle +++ b/build.gradle @@ -1,131 +1,131 @@ -group 'Tron' -version '1.0-SNAPSHOT' - -apply plugin: 'java' -apply plugin: 'com.google.protobuf' -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'application' - -sourceCompatibility = 1.8 -targetCompatibility = JavaVersion.VERSION_1_8 -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' - -compileJava.options*.compilerArgs = [ - "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", - "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", - "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", - "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" -] - -repositories { - mavenLocal() - mavenCentral() -} - -sourceSets { - main { - proto { - srcDir 'src/main/protos' - } - java { - srcDir 'src/main/gen' - srcDir 'src/main/java' - } - } - -} -buildscript { - repositories { - mavenLocal() - maven { - url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" - } - mavenCentral() - } - ext { - projectVersion = '1.3.0-RELEASE' - grpcVersion = '1.6.1' - protobufVersion = '3.3.0' - protobufGradlePluginVersion = '0.8.0' - springCloudConsulVersion = '1.2.1.RELEASE' - } - - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' - classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" - } -} - -task wrapper(type: Wrapper) { - gradleVersion = '3.3' -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - compile group: 'com.beust', name: 'jcommander', version: '1.72' - //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - compile 'com.maxmind.geoip2:geoip2:2.10.0' - - // google grpc - compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' - - compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile "com.madgag.spongycastle:core:1.53.0.0" - compile "com.madgag.spongycastle:prov:1.53.0.0" - compile group: 'com.typesafe', name: 'config', version: '1.3.2' - compile "com.google.code.findbugs:jsr305:3.0.0" - compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" - compile "org.apache.commons:commons-collections4:4.0" - compile "org.apache.commons:commons-lang3:3.4" - compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' - compile 'com.alibaba:fastjson:1.2.47' - - compile group: 'commons-io', name: 'commons-io', version: '2.6' - compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - - compile group: 'org.jline', name: 'jline', version: '3.1.3' -} - -protobuf { - generatedFilesBaseDir = "$projectDir/src/" - protoc { - artifact = "com.google.protobuf:protoc:3.5.1-1" - - } - plugins { - grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' - } - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - outputSubDir = "gen" - } - } - } - all()*.plugins { - grpc { - outputSubDir = "gen" - } - } - } -} - -run { - standardInput = System.in - mainClassName = 'org.tron.walletcli.Client' -} - - -shadowJar { - baseName = 'wallet-cli' - classifier = null - version = null -} +group 'Tron' +version '1.0-SNAPSHOT' + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'application' + +sourceCompatibility = 1.8 +targetCompatibility = JavaVersion.VERSION_1_8 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +compileJava.options*.compilerArgs = [ + "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", + "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", + "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", + "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" +] + +repositories { + mavenLocal() + mavenCentral() +} + +sourceSets { + main { + proto { + srcDir 'src/main/protos' + } + java { + srcDir 'src/main/gen' + srcDir 'src/main/java' + } + } + +} +buildscript { + repositories { + mavenLocal() + maven { + url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" + } + mavenCentral() + } + ext { + projectVersion = '1.3.0-RELEASE' + grpcVersion = '1.6.1' + protobufVersion = '3.3.0' + protobufGradlePluginVersion = '0.8.0' + springCloudConsulVersion = '1.2.1.RELEASE' + } + + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' + classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '3.3' +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'com.beust', name: 'jcommander', version: '1.72' + //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' + compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' + compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + compile 'com.maxmind.geoip2:geoip2:2.10.0' + + // google grpc + compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' + + compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' + compile "com.madgag.spongycastle:core:1.58.0.0" + compile "com.madgag.spongycastle:prov:1.58.0.0" + compile group: 'com.typesafe', name: 'config', version: '1.3.2' + compile "com.google.code.findbugs:jsr305:3.0.0" + compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" + compile "org.apache.commons:commons-collections4:4.0" + compile "org.apache.commons:commons-lang3:3.4" + compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' + compile 'com.alibaba:fastjson:1.2.47' + + compile group: 'commons-io', name: 'commons-io', version: '2.6' + compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' + + compile group: 'org.jline', name: 'jline', version: '3.1.3' +} + +protobuf { + generatedFilesBaseDir = "$projectDir/src/" + protoc { + artifact = "com.google.protobuf:protoc:3.5.1-1" + + } + plugins { + grpc { + artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' + } + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + outputSubDir = "gen" + } + } + } + all()*.plugins { + grpc { + outputSubDir = "gen" + } + } + } +} + +run { + standardInput = System.in + mainClassName = 'org.tron.walletcli.Client' +} + + +shadowJar { + baseName = 'wallet-cli' + classifier = null + version = null +} diff --git a/src/main/java/org/tron/common/crypto/ECKey.java b/src/main/java/org/tron/common/crypto/ECKey.java index bb94dcf8e..2fc758ade 100644 --- a/src/main/java/org/tron/common/crypto/ECKey.java +++ b/src/main/java/org/tron/common/crypto/ECKey.java @@ -1,1218 +1,1282 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.crypto; - -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.asn1.sec.SECNamedCurves; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.crypto.engines.AESFastEngine; -import org.spongycastle.crypto.modes.SICBlockCipher; -import org.spongycastle.crypto.params.*; -import org.spongycastle.crypto.signers.ECDSASigner; -import org.spongycastle.crypto.signers.HMacDSAKCalculator; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.util.BigIntegers; -import org.spongycastle.util.encoders.Base64; -import org.spongycastle.util.encoders.Hex; -import org.tron.common.crypto.jce.*; -import org.tron.common.utils.ByteUtil; - -import javax.annotation.Nullable; -import javax.crypto.KeyAgreement; -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.security.*; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; - -import static org.tron.common.utils.BIUtil.isLessThan; -import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; - -public class ECKey implements Serializable { - - /** - * The parameters of the secp256k1 curve. - */ - public static final ECDomainParameters CURVE; - public static final ECParameterSpec CURVE_SPEC; - /** - * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA - * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - - * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2

See - * https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki #Low_S_values_in_signatures - */ - public static final BigInteger HALF_CURVE_ORDER; - private static final BigInteger SECP256K1N = new BigInteger - ("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); - private static final SecureRandom secureRandom; - private static final long serialVersionUID = -728224901792295832L; - - static { - // All clients must agree on the curve to use by agreement. - X9ECParameters params = SECNamedCurves.getByName("secp256k1"); - CURVE = new ECDomainParameters(params.getCurve(), params.getG(), - params.getN(), params.getH()); - CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), - params.getN(), params.getH()); - HALF_CURVE_ORDER = params.getN().shiftRight(1); - secureRandom = new SecureRandom(); - } - - protected final ECPoint pub; - // The two parts of the key. If "priv" is set, "pub" can always be - // calculated. If "pub" is set but not "priv", we - // can only verify signatures not make them. - // TODO: Redesign this class to use consistent internals and more - // efficient serialization. - private final PrivateKey privKey; - // the Java Cryptographic Architecture provider to use for Signature - // this is set along with the PrivateKey privKey and must be compatible - // this provider will be used when selecting a Signature instance - // https://docs.oracle.com/javase/8/docs/technotes/guides/security - // /SunProviders.html - private final Provider provider; - - // Transient because it's calculated on demand. - transient private byte[] pubKeyHash; - transient private byte[] nodeId; - - /** - * Generates an entirely new keypair.

BouncyCastle will be used as the Java Security Provider - */ - public ECKey() { - this(secureRandom); - } - - /** - * Generate a new keypair using the given Java Security Provider.

All private key operations - * will use the provider. - */ - public ECKey(Provider provider, SecureRandom secureRandom) { - this.provider = provider; - - final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance - (provider, secureRandom); - final KeyPair keyPair = keyPairGen.generateKeyPair(); - - this.privKey = keyPair.getPrivate(); - - final PublicKey pubKey = keyPair.getPublic(); - if (pubKey instanceof BCECPublicKey) { - pub = ((BCECPublicKey) pubKey).getQ(); - } else if (pubKey instanceof ECPublicKey) { - pub = extractPublicKey((ECPublicKey) pubKey); - } else { - throw new AssertionError( - "Expected Provider " + provider.getName() + - " to produce a subtype of ECPublicKey, found " + - pubKey.getClass()); - } - } - - /** - * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle - * will be used as the Java Security Provider - * - * @param secureRandom - - */ - public ECKey(SecureRandom secureRandom) { - this(TronCastleProvider.getInstance(), secureRandom); - } - - /** - * Pair a private key with a public EC point.

All private key operations will use the - * provider. - */ - public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { - this.provider = provider; - - if (privKey == null || isECPrivateKey(privKey)) { - this.privKey = privKey; - } else { - throw new IllegalArgumentException( - "Expected EC private key, given a private key object with" + - " class " + - privKey.getClass().toString() + - " and algorithm " + privKey.getAlgorithm()); - } - - if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); - } else { - this.pub = pub; - } - } - - /** - * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java - * Security Provider - */ - public ECKey(@Nullable BigInteger priv, ECPoint pub) { - this( - TronCastleProvider.getInstance(), - privateKeyFromBigInteger(priv), - pub - ); - } - - /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint - */ - private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { - final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); - final BigInteger xCoord = publicPointW.getAffineX(); - final BigInteger yCoord = publicPointW.getAffineY(); - - return CURVE.getCurve().createPoint(xCoord, yCoord); - } - - /* Test if a generic private key is an EC private key - * - * it is not sufficient to check that privKey is a subtype of ECPrivateKey - * as the SunPKCS11 Provider will return a generic PrivateKey instance - * a fallback that covers this case is to check the key algorithm - */ - private static boolean isECPrivateKey(PrivateKey privKey) { - return privKey instanceof ECPrivateKey || privKey.getAlgorithm() - .equals("EC"); - } - - /* Convert a BigInteger into a PrivateKey object - */ - private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - CURVE_SPEC)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } - } - } - - /** - * Utility for compressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param uncompressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint compressPoint(ECPoint uncompressed) { - return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); - } - - /** - * Utility for decompressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param compressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint decompressPoint(ECPoint compressed) { - return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); - } - - /** - * Creates an ECKey given the private key only. - * - * @param privKey - - * @return - - */ - public static ECKey fromPrivate(BigInteger privKey) { - return new ECKey(privKey, CURVE.getG().multiply(privKey)); - } - - /** - * Creates an ECKey given the private key only. - * - * @param privKeyBytes - - * @return - - */ - public static ECKey fromPrivate(byte[] privKeyBytes) { - return fromPrivate(new BigInteger(1, privKeyBytes)); - } - - /** - * Creates an ECKey that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of pub will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { - return new ECKey(priv, pub); - } - - /** - * Creates an ECKey that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of the point will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] - pub) { - check(priv != null, "Private key must not be null"); - check(pub != null, "Public key must not be null"); - return new ECKey(new BigInteger(1, priv), CURVE.getCurve() - .decodePoint(pub)); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(ECPoint pub) { - return new ECKey(null, pub); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * encoded point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(byte[] pub) { - return new ECKey(null, CURVE.getCurve().decodePoint(pub)); - } - - /** - * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, - * use new BigInteger(1, bytes); - * - * @param privKey - - * @param compressed - - * @return - - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = CURVE.getG().multiply(privKey); - return point.getEncoded(compressed); - } - - /** - * Compute an address from an encoded public key. - * - * @param pubBytes an encoded (uncompressed) public key - * @return 21-byte address - */ - public static byte[] computeAddress(byte[] pubBytes) { - - return Hash.sha3omit12( - Arrays.copyOfRange(pubBytes, 1, pubBytes.length)); - } - - /** - * Compute an address from a public point. - * - * @param pubPoint a public point - * @return 21-byte address - */ - public static byte[] computeAddress(ECPoint pubPoint) { - return computeAddress(pubPoint.getEncoded(/* uncompressed */ false)); - } - - /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. - * - * @param pubPoint a public point - * @return 64-byte X,Y point pair - */ - public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { - final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); - return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); - } - - /** - * Recover the public key from an encoded node id. - * - * @param nodeId a 64-byte X,Y point pair - */ - public static ECKey fromNodeId(byte[] nodeId) { - check(nodeId.length == 64, "Expected a 64 byte node id"); - byte[] pubBytes = new byte[65]; - System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); - pubBytes[0] = 0x04; // uncompressed - return ECKey.fromPublicOnly(pubBytes); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { - byte[] signatureEncoded; - try { - signatureEncoded = Base64.decode(signatureBase64); - } catch (RuntimeException e) { - // This is what you getData back from Bouncy Castle if base64 doesn't - // decode :( - throw new SignatureException("Could not decode base64", e); - } - // Parse the signature bytes into r/s and the selector value. - if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); - } - - return signatureToKeyBytes( - messageHash, - ECDSASignature.fromComponents( - Arrays.copyOfRange(signatureEncoded, 1, 33), - Arrays.copyOfRange(signatureEncoded, 33, 65), - (byte) (signatureEncoded[0] & 0xFF))); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - check(messageHash.length == 32, "messageHash argument has length " + - messageHash.length); - int header = sig.v; - // The header byte: 0x1B = first key with even y, 0x1C = first key - // with odd y, - // 0x1D = second key with even y, 0x1E = second key - // with odd y - if (header < 27 || header > 34) { - throw new SignatureException("Header byte out of range: " + header); - } - if (header >= 31) { - header -= 4; - } - int recId = header - 27; - byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, - messageHash); - if (key == null) { - throw new SignatureException("Could not recover public key from " + - "signature"); - } - return key; - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, sig)); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return ECKey - */ - public static ECKey signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, - signatureBase64); - return ECKey.fromPublicOnly(keyBytes); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return ECKey - */ - public static ECKey signatureToKey(byte[] messageHash, ECDSASignature - sig) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); - return ECKey.fromPublicOnly(keyBytes); - } - - /** - *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

- *

When using native ECDSA verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

- * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, ECDSASignature signature, - byte[] pub) { - ECDSASigner signer = new ECDSASigner(); - ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE - .getCurve().decodePoint(pub), CURVE); - signer.init(false, params); - try { - return signer.verifySignature(data, signature.r, signature.s); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - System.out.println("Caught NPE inside bouncy castle" + npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, byte[] signature, byte[] pub) { - return verify(data, ECDSASignature.decodeFromDER(signature), pub); - } - - /** - * Returns true if the given pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @param pubkey - - * @return - - */ - public static boolean isPubKeyCanonical(byte[] pubkey) { - if (pubkey[0] == 0x04) { - // Uncompressed pubkey - return pubkey.length == 65; - } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { - // Compressed pubkey - return pubkey.length == 33; - } else { - return false; - } - } - - /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

The - * recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the correct - * one. Because the key recovery operation yields multiple potential allKeys, the correct key must - * either be stored alongside the signature, or you must be willing to try each recId in turn - * until you find one that outputs the key you are expecting.

If this method returns - * null it means recovery was not possible and recId should be iterated.

Given the - * above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the - * output is null OR a key that is not the one you expect, you try again with the next recId.

- * - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 65-byte encoded public key - */ - @Nullable - public static byte[] recoverPubBytesFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - check(recId >= 0, "recId must be positive"); - check(sig.r.signum() >= 0, "r must be positive"); - check(sig.s.signum() >= 0, "s must be positive"); - check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside - // this function) - // 1.1 Let x = r + jn - BigInteger n = CURVE.getN(); // Curve order. - BigInteger i = BigInteger.valueOf((long) recId / 2); - BigInteger x = sig.r.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen - // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or - // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an - // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this - // conversion routine outputs “invalid”, then - // do another iteration of Step 1. - // - // More concisely, what these points mean is to use X as a compressed - // public key. - ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); - BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent - // about the letter it uses for the prime. - if (x.compareTo(prime) >= 0) { - // Cannot have point co-ordinates larger than this as everything - // takes place modulo Q. - return null; - } - // Compressed allKeys require you to know an extra bit of data about the - // y-coord as there are two possibilities. - // So it's encoded in the recId. - ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of - // Step 1 (callers responsibility). - if (!R.multiply(n).isInfinity()) { - return null; - } - // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature - // verification. - BigInteger e = new BigInteger(1, messageHash); - // 1.6. For k from 1 to 2 do the following. (loop is outside this - // function via iterating recId) - // 1.6.1. Compute a candidate public key as: - // Q = mi(r) * (sR - eG) - // - // Where mi(x) is the modular multiplicative inverse. We transform - // this into the following: - // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) - // Where -e is the modular additive inverse of e, that is z such that - // z + e = 0 (mod n). In the above equation - // ** is point multiplication and + is point addition (the EC group - // operator). - // - // We can find the additive inverse by subtracting e from zero then - // taking the mod. For example the additive - // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod - // 11 = 8. - BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); - BigInteger rInv = sig.r.modInverse(n); - BigInteger srInv = rInv.multiply(sig.s).mod(n); - BigInteger eInvrInv = rInv.multiply(eInv).mod(n); - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE - .getG(), eInvrInv, R, srInv); - return q.getEncoded(/* compressed */ false); - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 20-byte address - */ - @Nullable - public static byte[] recoverAddressFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return computeAddress(pubBytes); - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return ECKey - */ - @Nullable - public static ECKey recoverFromSignature(int recId, ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return ECKey.fromPublicOnly(pubBytes); - } - } - - /** - * Decompress a compressed public key (x co-ord and low-bit of y-coord). - * - * @param xBN - - * @param yBit - - * @return - - */ - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { - X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE - .getCurve())); - compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return CURVE.getCurve().decodePoint(compEnc); - } - - private static void check(boolean test, String message) { - if (!test) { - throw new IllegalArgumentException(message); - } - } - - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, decompressPoint(pub)); - } - } - - /** - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey compress() { - if (pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, compressPoint(pub)); - } - } - - /** - * Returns true if this key doesn't have access to private key bytes. This may be because it was - * never given any private key bytes to begin with (a watching key). - * - * @return - - */ - public boolean isPubKeyOnly() { - return privKey == null; - } - - /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. - * - * @return - - */ - public boolean hasPrivKey() { - return privKey != null; - } - - /** - * Gets the address form of the public key. - * - * @return 20-byte address - */ - public byte[] getAddress() { - if (pubKeyHash == null) { - pubKeyHash = computeAddress(this.pub); - } - return pubKeyHash; - } - - /** - * Generates the NodeID based on this key, that is the public key without first format byte - */ - public byte[] getNodeId() { - if (nodeId == null) { - nodeId = pubBytesWithoutFormat(this.pub); - } - return nodeId; - } - - /** - * Gets the encoded public key value. - * - * @return 65-byte encoded public key - */ - public byte[] getPubKey() { - return pub.getEncoded(/* compressed */ false); - } - - /** - * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. - * - * @return - - */ - public ECPoint getPubKeyPoint() { - return pub; - } - - /** - * Gets the private key in the form of an integer field element. The public key is derived by - * performing EC point addition this number of times (i.e. point multiplying). - * - * @return - - * @throws IllegalStateException if the private key bytes are not available. - */ - public BigInteger getPrivKey() { - if (privKey == null) { - throw new MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - return ((BCECPrivateKey) privKey).getD(); - } else { - throw new MissingPrivateKeyException(); - } - } - - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - return b.toString(); - } - - /** - * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need - * the private key it is better for security reasons to just use toString(). - * - * @return - - */ - public String toStringWithPrivate() { - StringBuilder b = new StringBuilder(); - b.append(toString()); - if (privKey != null && privKey instanceof BCECPrivateKey) { - b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) - privKey).getD().toByteArray())); - } - return b.toString(); - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * ECDSASignature - * - * @param input to sign - * @return ECDSASignature signature that contains the R and S components - */ - public ECDSASignature doSign(byte[] input) { - if (input.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + input.length); - } - // No decryption of private key required. - if (privKey == null) { - throw new MissingPrivateKeyException(); - } - if (privKey instanceof BCECPrivateKey) { - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new - SHA256Digest())); - ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters - (((BCECPrivateKey) privKey).getD(), CURVE); - signer.init(true, privKeyParams); - BigInteger[] components = signer.generateSignature(input); - return new ECDSASignature(components[0], components[1]) - .toCanonicalised(); - } else { - try { - final Signature ecSig = ECSignatureFactory.getRawInstance - (provider); - ecSig.initSign(privKey); - ecSig.update(input); - final byte[] derSignature = ecSig.sign(); - return ECDSASignature.decodeFromDER(derSignature) - .toCanonicalised(); - } catch (SignatureException | InvalidKeyException ex) { - throw new RuntimeException("ECKey signing error", ex); - } - } - } - - /** - * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature - * - * @param messageHash - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public ECDSASignature sign(byte[] messageHash) { - ECDSASignature sig = doSign(messageHash); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - for (int i = 0; i < 4; i++) { - byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - public BigInteger keyAgreement(ECPoint otherParty) { - if (privKey == null) { - throw new MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) - privKey).getD(), CURVE)); - return agreement.calculateAgreement(new ECPublicKeyParameters - (otherParty, CURVE)); - } else { - try { - final KeyAgreement agreement = ECKeyAgreement.getInstance - (this.provider); - agreement.init(this.privKey); - agreement.doPhase( - ECKeyFactory.getInstance(this.provider) - .generatePublic(new ECPublicKeySpec - (otherParty, CURVE_SPEC)), - /* lastPhase */ true); - return new BigInteger(1, agreement.generateSecret()); - } catch (IllegalStateException | InvalidKeyException | - InvalidKeySpecException ex) { - throw new RuntimeException("ECDH key agreement failure", ex); - } - } - } - - /** - * Decrypt cipher by AES in SIC(also know as CTR) mode - * - * @param cipher -proper cipher - * @return decrypted cipher, equal length to the cipher. - * @deprecated should not use EC private scalar value as an AES key - */ - public byte[] decryptAES(byte[] cipher) { - - if (privKey == null) { - throw new MissingPrivateKeyException(); - } - if (!(privKey instanceof BCECPrivateKey)) { - throw new UnsupportedOperationException("Cannot use the private " + - "key as an AES key"); - } - - AESFastEngine engine = new AESFastEngine(); - SICBlockCipher ctrEngine = new SICBlockCipher(engine); - - KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( - (BCECPrivateKey) privKey).getD())); - ParametersWithIV params = new ParametersWithIV(key, new byte[16]); - - ctrEngine.init(false, params); - - int i = 0; - byte[] out = new byte[cipher.length]; - while (i < cipher.length) { - ctrEngine.processBlock(cipher, i, out, i); - i += engine.getBlockSize(); - if (cipher.length - i < engine.getBlockSize()) { - break; - } - } - - // process left bytes - if (cipher.length - i > 0) { - byte[] tmpBlock = new byte[16]; - System.arraycopy(cipher, i, tmpBlock, 0, cipher.length - i); - ctrEngine.processBlock(tmpBlock, 0, tmpBlock, 0); - System.arraycopy(tmpBlock, 0, out, i, cipher.length - i); - } - - return out; - } - - /** - * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @return - - */ - public boolean verify(byte[] data, byte[] signature) { - return ECKey.verify(data, signature, getPubKey()); - } - - /** - * Verifies the given R/S pair (signature) against a hash using the public key. - * - * @param sigHash - - * @param signature - - * @return - - */ - public boolean verify(byte[] sigHash, ECDSASignature signature) { - return ECKey.verify(sigHash, signature, getPubKey()); - } - - /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @return - - */ - public boolean isPubKeyCanonical() { - return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); - } - - /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only - * - * @return - - */ - @Nullable - public byte[] getPrivKeyBytes() { - if (privKey == null) { - return null; - } else if (privKey instanceof BCECPrivateKey) { - return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); - } else { - return null; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !(o instanceof ECKey)) { - return false; - } - - ECKey ecKey = (ECKey) o; - - if (privKey != null && !privKey.equals(ecKey.privKey)) { - return false; - } - return pub == null || pub.equals(ecKey.pub); - } - - @Override - public int hashCode() { - return Arrays.hashCode(getPubKey()); - } - - public static class ECDSASignature { - - /** - * The two components of the signature. - */ - public final BigInteger r, s; - public byte v; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. - * - * @param r - - * @param s - - */ - public ECDSASignature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - /** - * t - * - * @return - - */ - private static ECDSASignature fromComponents(byte[] r, byte[] s) { - return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, - s)); - } - - /** - * @param r - - * @param s - - * @param v - - * @return - - */ - public static ECDSASignature fromComponents(byte[] r, byte[] s, byte - v) { - ECDSASignature signature = fromComponents(r, s); - signature.v = v; - return signature; - } - - public static boolean validateComponents(BigInteger r, BigInteger s, - byte v) { - - if (v != 27 && v != 28) { - return false; - } - - if (isLessThan(r, BigInteger.ONE)) { - return false; - } - if (isLessThan(s, BigInteger.ONE)) { - return false; - } - - if (!isLessThan(r, SECP256K1N)) { - return false; - } - return isLessThan(s, SECP256K1N); - } - - public static ECDSASignature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " + - "stream."); - } - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these - // values as unsigned, though they should not be - // Thus, we always use the positive versions. See: - // http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s - .getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) { - try { - decoder.close(); - } catch (IOException x) { - } - } - } - } - - public boolean validateComponents() { - return validateComponents(r, s, v); - } - - public ECDSASignature toCanonicalised() { - if (s.compareTo(HALF_CURVE_ORDER) > 0) { - // The order of the curve is the number of valid points that - // exist on that curve. If S is in the upper - // half of the number of valid points, then bring it back to - // the lower half. Otherwise, imagine that - // N = 10 - // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) - // are valid solutions. - // 10 - 8 == 2, giving us always the latter solution, - // which is canonical. - return new ECDSASignature(r, CURVE.getN().subtract(s)); - } else { - return this; - } - } - - /** - * @return - - */ - public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 - // bytes for S - sigData[0] = v; - System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); - System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); - return new String(Base64.encode(sigData), Charset.forName("UTF-8")); - } - - public byte[] toByteArray() { - final byte fixedV = this.v >= 27 - ? (byte) (this.v - 27) - : this.v; - - return ByteUtil.merge( - ByteUtil.bigIntegerToBytes(this.r, 32), - ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); - } - - public String toHex() { - return Hex.toHexString(toByteArray()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ECDSASignature signature = (ECDSASignature) o; - - if (!r.equals(signature.r)) { - return false; - } - return s.equals(signature.s); - } - - @Override - public int hashCode() { - int result = r.hashCode(); - result = 31 * result + s.hashCode(); - return result; - } - } - - @SuppressWarnings("serial") - public static class MissingPrivateKeyException extends RuntimeException { - - } - -} +package org.tron.common.crypto; +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +import static org.tron.common.utils.BIUtil.isLessThan; +import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; +import static org.tron.common.utils.ByteUtil.byteArrayToInt; + +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import javax.annotation.Nullable; +import javax.crypto.KeyAgreement; +import lombok.extern.slf4j.Slf4j; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DLSequence; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.agreement.ECDHBasicAgreement; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.engines.AESEngine; +import org.spongycastle.crypto.modes.SICBlockCipher; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.crypto.signers.HMacDSAKCalculator; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Base64; +import org.spongycastle.util.encoders.Hex; +import org.tron.common.crypto.cryptohash.Keccak256; +import org.tron.common.crypto.jce.ECKeyAgreement; +import org.tron.common.crypto.jce.ECKeyFactory; +import org.tron.common.crypto.jce.ECKeyPairGenerator; +import org.tron.common.crypto.jce.ECSignatureFactory; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.BIUtil; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.Hash; + +@Slf4j(topic = "crypto") +public class ECKey implements Serializable, SignInterface { + + /** + * The parameters of the secp256k1 curve. + */ + public static final ECDomainParameters CURVE; + public static final ECParameterSpec CURVE_SPEC; + + /** + * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA + * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - + * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2 + * + *

See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki + * #Low_S_values_in_signatures + */ + + public static final BigInteger HALF_CURVE_ORDER; + private static final BigInteger SECP256K1N = + new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); + private static final SecureRandom secureRandom; + private static final long serialVersionUID = -728224901792295832L; + + static { + // All clients must agree on the curve to use by agreement. + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + CURVE = new ECDomainParameters(params.getCurve(), params.getG(), + params.getN(), params.getH()); + CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), + params.getN(), params.getH()); + HALF_CURVE_ORDER = params.getN().shiftRight(1); + secureRandom = new SecureRandom(); + } + + protected final ECPoint pub; + // The two parts of the key. If "priv" is set, "pub" can always be + // calculated. If "pub" is set but not "priv", we + // can only verify signatures not make them. + // TODO: Redesign this class to use consistent internals and more + // efficient serialization. + private final PrivateKey privKey; + // the Java Cryptographic Architecture provider to use for Signature + // this is set along with the PrivateKey privKey and must be compatible + // this provider will be used when selecting a Signature instance + // https://docs.oracle.com/javase/8/docs/technotes/guides/security + // /SunProviders.html + private final Provider provider; + + // Transient because it's calculated on demand. + private transient byte[] pubKeyHash; + private transient byte[] nodeId; + + /** + * Generates an entirely new keypair. + * + *

BouncyCastle will be used as the Java Security Provider + */ + public ECKey() { + this(secureRandom); + } + + /** + * Generate a new keypair using the given Java Security Provider. + * + *

All private key operations will use the provider. + */ + public ECKey(Provider provider, SecureRandom secureRandom) { + this.provider = provider; + + final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom); + final KeyPair keyPair = keyPairGen.generateKeyPair(); + + this.privKey = keyPair.getPrivate(); + + final PublicKey pubKey = keyPair.getPublic(); + if (pubKey instanceof BCECPublicKey) { + pub = ((BCECPublicKey) pubKey).getQ(); + } else if (pubKey instanceof ECPublicKey) { + pub = extractPublicKey((ECPublicKey) pubKey); + } else { + throw new AssertionError( + "Expected Provider " + provider.getName() + + " to produce a subtype of ECPublicKey, found " + + pubKey.getClass()); + } + } + + /** + * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle + * will be used as the Java Security Provider + * + * @param secureRandom - + */ + public ECKey(SecureRandom secureRandom) { + this(TronCastleProvider.getInstance(), secureRandom); + } + + /** + * Pair a private key with a public EC point. + * + *

All private key operations will use the provider. + */ + + // isPrivateKey true 私钥 其他公钥 + public ECKey(byte[] key, boolean isPrivateKey) { + if (isPrivateKey) { + BigInteger pk = new BigInteger(1, key); + this.privKey = privateKeyFromBigInteger(pk); + this.pub = CURVE.getG().multiply(pk); + } else { + this.privKey = null; + this.pub = CURVE.getCurve().decodePoint(key); + } + this.provider = TronCastleProvider.getInstance(); + } + + public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { + this.provider = provider; + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + /** + * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java + * Security Provider + */ + public ECKey(@Nullable BigInteger priv, ECPoint pub) { + this( + TronCastleProvider.getInstance(), + privateKeyFromBigInteger(priv), + pub + ); + } + + /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + */ + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return CURVE.getCurve().createPoint(xCoord, yCoord); + } + + /* Test if a generic private key is an EC private key + * + * it is not sufficient to check that privKey is a subtype of ECPrivateKey + * as the SunPKCS11 Provider will return a generic PrivateKey instance + * a fallback that covers this case is to check the key algorithm + */ + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm() + .equals("EC"); + } + + /* Convert a BigInteger into a PrivateKey object + */ + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + CURVE_SPEC)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param uncompressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param compressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKey - + * @return - + */ + public static ECKey fromPrivate(BigInteger privKey) { + return new ECKey(privKey, CURVE.getG().multiply(privKey)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static ECKey fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, + ECPoint pub) { + return new ECKey(priv, pub); + } + + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] + pub) { + check(priv != null, "Private key must not be null"); + check(pub != null, "Public key must not be null"); + return new ECKey(new BigInteger(1, priv), CURVE.getCurve() + .decodePoint(pub)); + } + + /** + * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given + * point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static ECKey fromPublicOnly(ECPoint pub) { + return new ECKey(null, pub); + } + + /** + * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given + * encoded point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static ECKey fromPublicOnly(byte[] pub) { + return new ECKey(null, CURVE.getCurve().decodePoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * use new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean + compressed) { + ECPoint point = CURVE.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key + * without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Recover the public key from an encoded node id. + * + * @param nodeId a 64-byte X,Y point pair + */ + public static ECKey fromNodeId(byte[] nodeId) { + check(nodeId.length == 64, "Expected a 64 byte node id"); + byte[] pubBytes = new byte[65]; + System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); + pubBytes[0] = 0x04; // uncompressed + return ECKey.fromPublicOnly(pubBytes); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, String + signatureBase64) throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you getData back from Bouncy Castle if base64 doesn't + // decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) { + throw new SignatureException("Signature truncated, expected 65 " + + "bytes and got " + signatureEncoded.length); + } + + return signatureToKeyBytes( + messageHash, + ECDSASignature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, + ECDSASignature sig) throws + SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + + messageHash.length); + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key + // with odd y, + // 0x1D = second key with even y, 0x1E = second key + // with odd y + if (header < 27 || header > 34) { + throw new SignatureException("Header byte out of range: " + header); + } + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, + messageHash); + if (key == null) { + throw new SignatureException("Could not recover public key from " + + "signature"); + } + return key; + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, + ECDSASignature sig) throws + SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, sig)); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return ECKey + */ + public static ECKey signatureToKey(byte[] messageHash, String + signatureBase64) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, + signatureBase64); + return ECKey.fromPublicOnly(keyBytes); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return ECKey + */ + public static ECKey signatureToKey(byte[] messageHash, ECDSASignature + sig) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); + return ECKey.fromPublicOnly(keyBytes); + } + + /** + *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

+ *

When using native ECDSA verification, data must be 32 bytes, and no element may be + * larger than 520 bytes.

+ * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, ECDSASignature signature, + byte[] pub) { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE + .getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + try { + return signer.verifySignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, ECDSASignature.decodeFromDER(signature), pub); + } + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + return pubkey.length == 65; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + return pubkey.length == 33; + } else { + return false; + } + } + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * + *

The recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the + * correct one. Because the key recovery operation yields multiple potential allKeys, the correct + * key must either be stored alongside the signature, or you must be willing to try each recId in + * turn until you find one that outputs the key you are expecting.

+ * + *

If this method returns null it means recovery was not possible and recId should be + * iterated.

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 + * to 3, and if the output is null OR a key that is not the one you expect, you try again with the + * next recId.

+ * + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 65-byte encoded public key + */ + @Nullable + public static byte[] recoverPubBytesFromSignature(int recId, + ECDSASignature sig, + byte[] messageHash) { + check(recId >= 0, "recId must be positive"); + check(sig.r.signum() >= 0, "r must be positive"); + check(sig.s.signum() >= 0, "s must be positive"); + check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside + // this function) + // 1.1 Let x = r + jn + BigInteger n = CURVE.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen + // using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an + // elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this + // conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed + // public key. + ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent + // about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything + // takes place modulo Q. + return null; + } + // Compressed allKeys require you to know an extra bit of data about the + // y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of + // Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature + // verification. + BigInteger e = new BigInteger(1, messageHash); + // 1.6. For k from 1 to 2 do the following. (loop is outside this + // function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform + // this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that + // z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group + // operator). + // + // We can find the additive inverse by subtracting e from zero then + // taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod + // 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE + .getG(), eInvrInv, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 20-byte address + */ + @Nullable + public static byte[] recoverAddressFromSignature(int recId, + ECDSASignature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return Hash.computeAddress(pubBytes); + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return ECKey + */ + @Nullable + public static ECKey recoverFromSignature(int recId, ECDSASignature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return ECKey.fromPublicOnly(pubBytes); + } + } + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE + .getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return CURVE.getCurve().decodePoint(compEnc); + } + + private static void check(boolean test, String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + * Returns a copy of this key, but with the public point represented in uncompressed form. + * Normally you would never need this: it's for specialised scenarios or when backwards + * compatibility in encoded form is necessary. + * + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey decompress() { + if (!pub.isCompressed()) { + return this; + } else { + return new ECKey(this.provider, this.privKey, decompressPoint(pub)); + } + } + + /** + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey compress() { + if (pub.isCompressed()) { + return this; + } else { + return new ECKey(this.provider, this.privKey, compressPoint(pub)); + } + } + + /** + * Returns true if this key doesn't have access to private key bytes. This may be because it was + * never given any private key bytes to begin with (a watching key). + * + * @return - + */ + public boolean isPubKeyOnly() { + return privKey == null; + } + + /** + * Returns true if this key has access to private key bytes. Does the opposite of {@link + * #isPubKeyOnly()}. + * + * @return - + */ + public boolean hasPrivKey() { + return privKey != null; + } + + /** + * Gets the address form of the public key. + * + * @return 21-byte address + */ + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = Hash.computeAddress(this.pub); + } + return pubKeyHash; + } + + @Override + public String signHash(byte[] hash) { + return sign(hash).toBase64(); + } + + public byte[] Base64toBytes (String signature) { + byte[] signData = Base64.decode(signature); + byte first = (byte)(signData[0] - 27); + byte[] temp = Arrays.copyOfRange(signData,1,65); + return ByteUtil.appendByte(temp,first); + } + + @Override + public byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Generates the NodeID based on this key, that is the public key without first format byte + */ + public byte[] getNodeId() { + if (nodeId == null) { + nodeId = pubBytesWithoutFormat(this.pub); + } + return nodeId; + } + + @Override + public byte[] hash(byte[] message) { + Keccak256 hashFun = new Keccak256(); + return hashFun.digest(message); + } + + @Override + public byte[] getPrivateKey() { + return getPrivKeyBytes(); + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by + * performing EC point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new MissingPrivateKeyException(); + } + } + + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 + * bytes, not 64. + * + * @return - + */ + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need + * the private key it is better for security reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) + privKey).getD().toByteArray())); + } + return b.toString(); + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * ECDSASignature + * + * @param input to sign + * @return ECDSASignature signature that contains the R and S components + */ + public ECDSASignature doSign(byte[] input) { + if (input.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "ECDSA signature, not " + input.length); + } + // No decryption of private key required. + if (privKey == null) { + throw new MissingPrivateKeyException(); + } + if (privKey instanceof BCECPrivateKey) { + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new + SHA256Digest())); + ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters + (((BCECPrivateKey) privKey).getD(), CURVE); + signer.init(true, privKeyParams); + BigInteger[] components = signer.generateSignature(input); + return new ECDSASignature(components[0], components[1]) + .toCanonicalised(); + } else { + try { + final Signature ecSig = ECSignatureFactory.getRawInstance + (provider); + ecSig.initSign(privKey); + ecSig.update(input); + final byte[] derSignature = ecSig.sign(); + return ECDSASignature.decodeFromDER(derSignature) + .toCanonicalised(); + } catch (SignatureException | InvalidKeyException ex) { + throw new RuntimeException("ECKey signing error", ex); + } + } + } + + /** + * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public ECDSASignature sign(byte[] messageHash) { + ECDSASignature sig = doSign(messageHash); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + public BigInteger keyAgreement(ECPoint otherParty) { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) + privKey).getD(), CURVE)); + return agreement.calculateAgreement(new ECPublicKeyParameters + (otherParty, CURVE)); + } else { + try { + final KeyAgreement agreement = ECKeyAgreement.getInstance + (this.provider); + agreement.init(this.privKey); + agreement.doPhase( + ECKeyFactory.getInstance(this.provider) + .generatePublic(new ECPublicKeySpec + (otherParty, CURVE_SPEC)), + /* lastPhase */ true); + return new BigInteger(1, agreement.generateSecret()); + } catch (IllegalStateException | InvalidKeyException | + InvalidKeySpecException ex) { + throw new RuntimeException("ECDH key agreement failure", ex); + } + } + } + + /** + * Decrypt cipher by AES in SIC(also know as CTR) mode + * + * @param cipher -proper cipher + * @return decrypted cipher, equal length to the cipher. + * @deprecated should not use EC private scalar value as an AES key + */ + public byte[] decryptAES(byte[] cipher) { + + if (privKey == null) { + throw new MissingPrivateKeyException(); + } + if (!(privKey instanceof BCECPrivateKey)) { + throw new UnsupportedOperationException("Cannot use the private " + + "key as an AES key"); + } + + AESEngine engine = new AESEngine(); + SICBlockCipher ctrEngine = new SICBlockCipher(engine); + + KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( + (BCECPrivateKey) privKey).getD())); + ParametersWithIV params = new ParametersWithIV(key, new byte[16]); + + ctrEngine.init(false, params); + + int i = 0; + byte[] out = new byte[cipher.length]; + while (i < cipher.length) { + ctrEngine.processBlock(cipher, i, out, i); + i += engine.getBlockSize(); + if (cipher.length - i < engine.getBlockSize()) { + break; + } + } + + // process left bytes + if (cipher.length - i > 0) { + byte[] tmpBlock = new byte[16]; + System.arraycopy(cipher, i, tmpBlock, 0, cipher.length - i); + ctrEngine.processBlock(tmpBlock, 0, tmpBlock, 0); + System.arraycopy(tmpBlock, 0, out, i, cipher.length - i); + } + + return out; + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return ECKey.verify(data, signature, getPubKey()); + } + + /** + * Verifies the given R/S pair (signature) against a hash using the public key. + * + * @param sigHash - + * @param signature - + * @return - + */ + public boolean verify(byte[] sigHash, ECDSASignature signature) { + return ECKey.verify(sigHash, signature, getPubKey()); + } + + /** + * Returns true if this pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @return - + */ + public boolean isPubKeyCanonical() { + return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public + * only + * + * @return - + */ + @Nullable + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return ByteUtil.bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + ECKey ecKey = (ECKey) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) { + return false; + } + return pub == null || pub.equals(ecKey.pub); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + public static class ECDSASignature implements SignatureInterface { + + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the + * signature. + * + * @param r - + * @param s - + */ + public ECDSASignature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + public ECDSASignature(byte[] r, byte[] s, byte v) { + this.r = new BigInteger(1, r); + this.s = new BigInteger(1, s); + this.v = v; + } + + /** + * t + * + * @return - + */ + private static ECDSASignature fromComponents(byte[] r, byte[] s) { + return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, + s)); + } + + /** + * @param r - + * @param s - + * @param v - + * @return - + */ + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte + v) { + ECDSASignature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public static boolean validateComponents(BigInteger r, BigInteger s, + byte v) { + + if (v != 27 && v != 28) { + return false; + } + + if (BIUtil.isLessThan(r, BigInteger.ONE)) { + return false; + } + if (BIUtil.isLessThan(s, BigInteger.ONE)) { + return false; + } + + if (!BIUtil.isLessThan(r, SECP256K1N)) { + return false; + } + return BIUtil.isLessThan(s, SECP256K1N); + } + + public static ECDSASignature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) { + throw new RuntimeException("Reached past end of ASN.1 " + + "stream."); + } + ASN1Integer r, s; + try { + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + // OpenSSL deviates from the DER spec by interpreting these + // values as unsigned, though they should not be + // Thus, we always use the positive versions. See: + // http://r6.ca/blog/20111119T211504Z.html + return new ECDSASignature(r.getPositiveValue(), s + .getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) { + try { + decoder.close(); + } catch (IOException x) { + + } + } + } + } + + public boolean validateComponents() { + return validateComponents(r, s, v); + } + + public ECDSASignature toCanonicalised() { + if (s.compareTo(HALF_CURVE_ORDER) > 0) { + // The order of the curve is the number of valid points that + // exist on that curve. If S is in the upper + // half of the number of valid points, then bring it back to + // the lower half. Otherwise, imagine that + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) + // are valid solutions. + // 10 - 8 == 2, giving us always the latter solution, + // which is canonical. + return new ECDSASignature(r, CURVE.getN().subtract(s)); + } else { + return this; + } + } + + /** + * @return - + */ + public String toBase64() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + // bytes for S + sigData[0] = v; + System.arraycopy(ByteUtil.bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(ByteUtil.bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + } + + + + public byte[] toByteArray() { + final byte fixedV = this.v >= 27 + ? (byte) (this.v - 27) + : this.v; + + return ByteUtil.merge( + ByteUtil.bigIntegerToBytes(this.r, 32), + ByteUtil.bigIntegerToBytes(this.s, 32), + new byte[]{fixedV}); + } + + public String toHex() { + return Hex.toHexString(toByteArray()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ECDSASignature signature = (ECDSASignature) o; + + if (!r.equals(signature.r)) { + return false; + } + return s.equals(signature.s); + } + + @Override + public int hashCode() { + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; + } + } + + @SuppressWarnings("serial") + public static class MissingPrivateKeyException extends RuntimeException { + + } + +} diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java index 02e0c3e09..a41b382e8 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Hash.java @@ -42,6 +42,7 @@ */ public class Sha256Hash implements Serializable, Comparable { + public static final int LENGTH = 32; // bytes public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); diff --git a/src/main/java/org/tron/common/crypto/SignInterface.java b/src/main/java/org/tron/common/crypto/SignInterface.java new file mode 100644 index 000000000..b158c50b7 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SignInterface.java @@ -0,0 +1,26 @@ +package org.tron.common.crypto; + +import java.security.SignatureException; + +public interface SignInterface { + + byte[] hash(byte[] message); + + byte[] getPrivateKey(); + + byte[] getPubKey(); + + byte[] getAddress(); + + String signHash(byte[] hash); + + byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException; + + byte[] getNodeId(); + + byte[] Base64toBytes (String signature); + + byte[] getPrivKeyBytes(); + + SignatureInterface sign(byte[] hash); +} diff --git a/src/main/java/org/tron/common/crypto/SignUtils.java b/src/main/java/org/tron/common/crypto/SignUtils.java new file mode 100644 index 000000000..710ecda81 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SignUtils.java @@ -0,0 +1,54 @@ +package org.tron.common.crypto; + +import java.security.SecureRandom; +import java.security.SignatureException; +import org.tron.common.crypto.ECKey.ECDSASignature; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.crypto.sm2.SM2.SM2Signature; + +public class SignUtils { + public static SignInterface getGeneratedRandomSign(boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return new ECKey(); + } + return new SM2(); + } + + public static SignInterface getGeneratedRandomSign( + SecureRandom secureRandom, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return new ECKey(secureRandom); + } + return new SM2(secureRandom); + } + + public static SignInterface fromPrivate(byte[] privKeyBytes, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return ECKey.fromPrivate(privKeyBytes); + } + return SM2.fromPrivate(privKeyBytes); + } + + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64, boolean isECKeyCryptoEngine) throws SignatureException { + if (isECKeyCryptoEngine) { + return ECKey.signatureToAddress(messageHash, signatureBase64); + } + return SM2.signatureToAddress(messageHash, signatureBase64); + } + + public static SignatureInterface fromComponents(byte[] r, byte[] s, byte v, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return ECKey.ECDSASignature.fromComponents(r, s, v); + } + return SM2.SM2Signature.fromComponents(r, s, v); + } + + public static byte[] signatureToAddress(byte[] messageHash, SignatureInterface signatureInterface, + boolean isECKeyCryptoEngine) throws SignatureException{ + if (isECKeyCryptoEngine) { + return ECKey.signatureToAddress(messageHash, (ECDSASignature)signatureInterface); + } + return SM2.signatureToAddress(messageHash, (SM2Signature)signatureInterface); + } +} diff --git a/src/main/java/org/tron/common/crypto/SignatureInterface.java b/src/main/java/org/tron/common/crypto/SignatureInterface.java new file mode 100644 index 000000000..451c18449 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SignatureInterface.java @@ -0,0 +1,7 @@ +package org.tron.common.crypto; + +public interface SignatureInterface { + boolean validateComponents(); + + byte[] toByteArray(); +} \ No newline at end of file diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2.java b/src/main/java/org/tron/common/crypto/sm2/SM2.java new file mode 100644 index 000000000..1236dd65c --- /dev/null +++ b/src/main/java/org/tron/common/crypto/sm2/SM2.java @@ -0,0 +1,1245 @@ +package org.tron.common.crypto.sm2; + +import lombok.extern.slf4j.Slf4j; +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DLSequence; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.AsymmetricCipherKeyPair; +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.generators.ECKeyPairGenerator; +import org.spongycastle.crypto.params.*; +import org.spongycastle.crypto.signers.*; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.math.ec.*; +import org.spongycastle.util.encoders.Base64; +import org.spongycastle.util.encoders.Hex; +import org.spongycastle.util.test.TestRandomBigInteger; +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.jce.ECKeyFactory; +import org.tron.common.crypto.jce.ECSignatureFactory; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.ByteUtil; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.*; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static org.tron.common.utils.BIUtil.isLessThan; +import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; +import static org.tron.common.utils.Hash.computeAddress; + +/** + * Implement Chinese Commercial Cryptographic Standard of SM2 + * + */ +@Slf4j(topic = "crypto") +public class SM2 implements Serializable, SignInterface { + private static BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); + private static BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); + private static BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); + private static BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); + private static BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + private static BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); +// +// private static BigInteger SM2_N = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16); +// private static BigInteger SM2_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16); +// private static BigInteger SM2_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16); +// private static BigInteger SM2_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16); +// private static BigInteger SM2_GX = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16); +// private static BigInteger SM2_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16); + + private static ECDomainParameters ecc_param; + private static ECParameterSpec ecc_spec; + private static ECCurve.Fp curve; + private static ECPoint ecc_point_g; + + private static final SecureRandom secureRandom; + + + + static { + secureRandom = new SecureRandom(); + curve = new ECCurve.Fp(SM2_P, SM2_A, SM2_B); + ecc_point_g = curve.createPoint(SM2_GX, SM2_GY); + ecc_param = new ECDomainParameters(curve, ecc_point_g, SM2_N); + ecc_spec = new ECParameterSpec(curve, ecc_point_g, SM2_N); + } + + protected final ECPoint pub; + + private final PrivateKey privKey; + +// private final SM2KeyPair keyPair; + + // Transient because it's calculated on demand. + private transient byte[] pubKeyHash; + private transient byte[] nodeId; + +// private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); +// private byte[] userID; + + public SM2() { + this(secureRandom); + } + /** + * Generates an entirely new keypair. + * + *

BouncyCastle will be used as the Java Security Provider + */ + + + /** + * Generate a new keypair using the given Java Security Provider. + * + *

All private key operations will use the provider. + */ + public SM2(SecureRandom secureRandom) { + + ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecc_param, secureRandom); + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + keyPairGenerator.init(ecKeyGenerationParameters); + AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) kp.getPrivate(); + ECPublicKeyParameters ecpub = (ECPublicKeyParameters) kp.getPublic(); + + BigInteger privateKey = ecpriv.getD(); + this.privKey = privateKeyFromBigInteger(privateKey); + this.pub = ecpub.getQ(); +// this.keyPair = new SM2KeyPair(pub.getEncoded(false),privateKey.toByteArray()); + +// CipherParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey, ecc_param); +// CipherParameters baseParam; +// +// if (privateKeyParameters instanceof ParametersWithID) +// { +// baseParam = ((ParametersWithID)privateKeyParameters).getParameters(); +// userID = ((ParametersWithID)privateKeyParameters).getID(); +// } +// else +// { +// baseParam = privateKeyParameters; +// userID = new byte[0]; +// } +// this.kCalculator.init(SM2_N, secureRandom); + } + + public SM2 (byte[] key, boolean isPrivateKey) { + if (isPrivateKey) { + BigInteger pk = new BigInteger(1, key); + this.privKey = privateKeyFromBigInteger(pk); + this.pub = ecc_param.getG().multiply(pk); + } else { + this.privKey = null; + this.pub = ecc_param.getCurve().decodePoint(key); + } + } + + + /** + * Pair a private key with a public EC point. + * + *

All private key operations will use the provider. + */ + + public SM2(@Nullable PrivateKey privKey, ECPoint pub) { + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + /** + * Pair a private key integer with a public EC point + * + */ + public SM2(@Nullable BigInteger priv, ECPoint pub) { + this( + privateKeyFromBigInteger(priv), + pub + ); + +// this.privKey = privateKeyFromBigInteger(priv); +// this.pub = pub; +// this.keyPair = new SM2KeyPair(pub.getEncoded(false), priv.toByteArray()); + } + + /** + * Convert a BigInteger into a PrivateKey object + * + * @param priv + * @return + */ + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + ecc_spec)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /* Test if a generic private key is an EC private key + * + * it is not sufficient to check that privKey is a subtype of ECPrivateKey + * as the SunPKCS11 Provider will return a generic PrivateKey instance + * a fallback that covers this case is to check the key algorithm + */ + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm() + .equals("EC"); + } + + /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + */ + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return ecc_param.getCurve().createPoint(xCoord, yCoord); + } + + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param uncompressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return ecc_param.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param compressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return ecc_param.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an SM2 given the private key only. + * + * @param privKey - + * @return - + */ + public static SM2 fromPrivate(BigInteger privKey) { + return new SM2(privKey, ecc_param.getG().multiply(privKey)); + } + + /** + * Creates an SM2 given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static SM2 fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + + /** + * Creates an SM2 that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static SM2 fromPrivateAndPrecalculatedPublic(BigInteger priv, + ECPoint pub) { + return new SM2(priv, pub); + } + + /** + * Creates an SM2 that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static SM2 fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] + pub) { + check(priv != null, "Private key must not be null"); + check(pub != null, "Public key must not be null"); + return new SM2(new BigInteger(1, priv), ecc_param.getCurve() + .decodePoint(pub)); + } + + /** + * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static SM2 fromPublicOnly(ECPoint pub) { + return new SM2((PrivateKey) null, pub); + } + + /** + * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * encoded point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static SM2 fromPublicOnly(byte[] pub) { + return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * use new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean + compressed) { + ECPoint point = ecc_param.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key + * without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Recover the public key from an encoded node id. + * + * @param nodeId a 64-byte X,Y point pair + */ + public static SM2 fromNodeId(byte[] nodeId) { + check(nodeId.length == 64, "Expected a 64 byte node id"); + byte[] pubBytes = new byte[65]; + System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); + pubBytes[0] = 0x04; // uncompressed + return SM2.fromPublicOnly(pubBytes); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, String + signatureBase64) throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you getData back from Bouncy Castle if base64 doesn't + // decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) { + throw new SignatureException("Signature truncated, expected 65 " + + "bytes and got " + signatureEncoded.length); + } + + return signatureToKeyBytes( + messageHash, + SM2Signature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, + SM2Signature sig) throws + SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + + messageHash.length); + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key + // with odd y, + // 0x1D = second key with even y, 0x1E = second key + // with odd y + if (header < 27 || header > 34) { + throw new SignatureException("Header byte out of range: " + header); + } + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (key == null) { + throw new SignatureException("Could not recover public key from " + + "signature"); + } + return key; + } + + + @Override + public byte[] hash(byte[] message) { + SM2Signer signer = this.getSM2SignerForHash(); + return signer.generateSM3Hash(message); + } + + @Override + public byte[] getPrivateKey() { + return getPrivKeyBytes(); + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + @Override + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the address form of the public key. + * + * @return 21-byte address + */ + @Override + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = computeAddress(this.pub); + } + return pubKeyHash; + } + + @Override + public byte[] signToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, + SM2Signature sig) throws + SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, sig)); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return ECKey + */ + public static SM2 signatureToKey(byte[] messageHash, String + signatureBase64) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, + signatureBase64); + return fromPublicOnly(keyBytes); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return ECKey + */ + public static SM2 signatureToKey(byte[] messageHash, SM2Signature + sig) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); + return fromPublicOnly(keyBytes); + } + + /** + * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which including the v + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public SM2Signature sign(byte[] messageHash) { + if (messageHash.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "SM2 signature, not " + messageHash.length); + } + // No decryption of private key required. + SM2Signer signer = getSigner(); + BigInteger[] componets = signer.generateHashSignature(messageHash); + + SM2Signature sig = new SM2.SM2Signature(componets[0], componets[1]); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * SM2Signature + * + * @param input to sign + * @return SM2Signature signature that contains the R and S components + */ + public String signHash(byte[] input) { + return sign(input).toBase64(); + } + + public byte[] Base64toBytes (String signature) { + byte[] signData = Base64.decode(signature); + byte first = (byte)(signData[0] - 27); + byte[] temp = Arrays.copyOfRange(signData,1,65); + return ByteUtil.appendByte(temp,first); + } + + /** + * Takes the message of data and returns the SM2 signature + * + * @param message - + * @param userID + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public SM2Signature signMessage(byte[] message, @Nullable String userID) { + SM2Signature sig = signMsg(message, userID); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + + SM2Signer signer = getSigner(); + byte[] messageHash = signer.generateSM3Hash(message); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * SM2Signature + * + * @param msg to sign + * @param userID + * @return SM2Signature signature that contains the R and S components + */ + public SM2.SM2Signature signMsg(byte[] msg,@Nullable String userID) { + if (null == msg) { + throw new IllegalArgumentException("Expected signature message of " + + "SM2 is null"); + } + // No decryption of private key required. + SM2Signer signer = getSigner(); + BigInteger[] componets = signer.generateSignature(msg); + return new SM2.SM2Signature(componets[0], componets[1]); + } + + private SM2Signer getSigner() { + SM2Signer signer = new SM2Signer(); + BigInteger d = getPrivKey(); + ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); +// ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); + signer.init(true, privateKeyParameters); + return signer; + } + + /** + * used to generate the SM3 hash for SM2 signature generation or verification + * + * @return + */ + public SM2Signer getSM2SignerForHash() { + SM2Signer signer = new SM2Signer(); + //BigInteger d = getPrivKey(); + //ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); + ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); + signer.init(false, publicKeyParameters); + return signer; + } + + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature + * + * @param recId + * @param sig + * @param messageHash + * @return + */ + @Nullable + public static byte[] recoverPubBytesFromSignature(int recId, + SM2Signature sig, + byte[] messageHash) { + check(recId >= 0, "recId must be positive"); + check(sig.r.signum() >= 0, "r must be positive"); + check(sig.s.signum() >= 0, "s must be positive"); + check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside + // this function) + // 1.1 Let x = r + jn + BigInteger n = ecc_param.getN(); // Curve order. + BigInteger prime = curve.getQ(); + BigInteger i = BigInteger.valueOf((long) recId / 2); + + BigInteger e = new BigInteger(1, messageHash); + BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n + x = x.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen + // using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an + // elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this + // conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed + // public key. + ECCurve.Fp curve = (ECCurve.Fp) ecc_param.getCurve(); + // Bouncy Castle is not consistent + // about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything + // takes place modulo Q. + return null; + } + // Compressed allKeys require you to know an extra bit of data about the + // y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of + // Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + + // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) + BigInteger srInv = sig.s.add(sig.r).modInverse(n); + BigInteger sNeg = BigInteger.ZERO.subtract(sig.s).mod(n); + BigInteger coeff = srInv.multiply(sNeg).mod(n); + + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(ecc_param + .getG(), coeff, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(ecc_param + .getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return ecc_param.getCurve().decodePoint(compEnc); + } + + private static void check(boolean test, String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + *

Verifies the given SM2 signature against the message bytes using the public key bytes.

+ *

When using native SM2 verification, data must be 32 bytes, and no element may be + * larger than 520 bytes.

+ * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, SM2Signature signature, + byte[] pub) { + SM2Signer signer = new SM2Signer(); + ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param + .getCurve().decodePoint(pub),ecc_param); + signer.init(false, params); + try { + return signer.verifyHashSignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, SM2Signature.decodeFromDER(signature), pub); + } + + /** + *

Verifies the given SM2 signature against the message bytes using the public key bytes. + * + * @param msg the message data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verifyMessage(byte[] msg, SM2Signature signature, + byte[] pub, @Nullable String userID) { + SM2Signer signer = new SM2Signer(); + ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param + .getCurve().decodePoint(pub),ecc_param); + signer.init(false, params); + try { + return signer.verifySignature(msg, signature.r, signature.s, userID); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param msg the message data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verifyMessage(byte[] msg, byte[] signature, byte[] pub, @Nullable String userID) { + return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); + } + + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + return pubkey.length == 65; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + return pubkey.length == 33; + } else { + return false; + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 20-byte address + */ + @Nullable + public static byte[] recoverAddressFromSignature(int recId, + SM2Signature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return computeAddress(pubBytes); + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return ECKey + */ + @Nullable + public static SM2 recoverFromSignature(int recId, SM2Signature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return fromPublicOnly(pubBytes); + } + } + + + + /** + * Returns a copy of this key, but with the public point represented in uncompressed form. + * Normally you would never need this: it's for specialised scenarios or when backwards + * compatibility in encoded form is necessary. + * + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public SM2 decompress() { + if (!pub.isCompressed()) { + return this; + } else { + return new SM2(this.privKey, decompressPoint(pub)); + } + } + + /** + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public SM2 compress() { + if (pub.isCompressed()) { + return this; + } else { + return new SM2(this.privKey, compressPoint(pub)); + } + } + + /** + * Returns true if this key doesn't have access to private key bytes. This may be because it was + * never given any private key bytes to begin with (a watching key). + * + * @return - + */ + public boolean isPubKeyOnly() { + return privKey == null; + } + + /** + * Returns true if this key has access to private key bytes. Does the opposite of {@link + * #isPubKeyOnly()}. + * + * @return - + */ + public boolean hasPrivKey() { + return privKey != null; + } + +// /** +// * Gets the address form of the public key. +// * +// * @return 21-byte address +// */ +// public byte[] getAddress() { +// if (pubKeyHash == null) { +// pubKeyHash = computeAddress(this.pub); +// } +// return pubKeyHash; +// } + + /** + * Generates the NodeID based on this key, that is the public key without first format byte + */ + public byte[] getNodeId() { + if (nodeId == null) { + nodeId = pubBytesWithoutFormat(this.pub); + } + return nodeId; + } + +// /** +// * Gets the encoded public key value. +// * +// * @return 65-byte encoded public key +// */ +// public byte[] getPubKey() { +// return pub.getEncoded(/* compressed */ false); +// } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by + * performing EC point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new ECKey.MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new ECKey.MissingPrivateKeyException(); + } + } + + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 + * bytes, not 64. + * + * @return - + */ + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need + * the private key it is better for security reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) + privKey).getD().toByteArray())); + } + return b.toString(); + } + + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return SM2.verify(data, signature, getPubKey()); + } + + /** + * Verifies the given R/S pair (signature) against a hash using the public key. + * + * @param sigHash - + * @param signature - + * @return - + */ + public boolean verify(byte[] sigHash, SM2Signature signature) { + return SM2.verify(sigHash, signature, getPubKey()); + } + + /** + * Returns true if this pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @return - + */ + public boolean isPubKeyCanonical() { + return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public + * only + * + * @return - + */ + @Nullable + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + SM2 ecKey = (SM2) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) { + return false; + } + return pub == null || pub.equals(ecKey.pub); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + + public static class SM2Signature implements SignatureInterface { + + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the + * signature. + * + * @param r - + * @param s - + */ + public SM2Signature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + public SM2Signature(byte[] r, byte[] s, byte v) { + this.r = new BigInteger(1, r); + this.s = new BigInteger(1,s); + this.v = v; + } + + /** + * t + * + * @return - + */ + private static SM2.SM2Signature fromComponents(byte[] r, byte[] s) { + return new SM2.SM2Signature(new BigInteger(1, r), new BigInteger(1, + s)); + } + + /** + * @param r - + * @param s - + * @param v - + * @return - + */ + public static SM2.SM2Signature fromComponents(byte[] r, byte[] s, byte + v) { + SM2.SM2Signature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public static boolean validateComponents(BigInteger r, BigInteger s, + byte v) { + + if (v != 27 && v != 28) { + return false; + } + + if (isLessThan(r, BigInteger.ONE)) { + return false; + } + if (isLessThan(s, BigInteger.ONE)) { + return false; + } + + if (!isLessThan(r, SM2.SM2_N)) { + return false; + } + return isLessThan(s, SM2.SM2_N); + } + + public static SM2.SM2Signature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) { + throw new RuntimeException("Reached past end of ASN.1 " + + "stream."); + } + ASN1Integer r, s; + try { + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + // OpenSSL deviates from the DER spec by interpreting these + // values as unsigned, though they should not be + // Thus, we always use the positive versions. See: + // http://r6.ca/blog/20111119T211504Z.html + return new SM2.SM2Signature(r.getPositiveValue(), s + .getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) { + try { + decoder.close(); + } catch (IOException x) { + + } + } + } + } + + public boolean validateComponents() { + return validateComponents(r, s, v); + } + + + /** + * @return - + */ + public String toBase64() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + // bytes for S + sigData[0] = v; + System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + } + + + + public byte[] toByteArray() { + final byte fixedV = this.v >= 27 + ? (byte) (this.v - 27) + : this.v; + + return ByteUtil.merge( + ByteUtil.bigIntegerToBytes(this.r, 32), + ByteUtil.bigIntegerToBytes(this.s, 32), + new byte[]{fixedV}); + } + + public String toHex() { + return Hex.toHexString(toByteArray()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + SM2.SM2Signature signature = (SM2.SM2Signature) o; + + if (!r.equals(signature.r)) { + return false; + } + return s.equals(signature.s); + } + + @Override + public int hashCode() { + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; + } + } + +} diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java new file mode 100644 index 000000000..9dd689013 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java @@ -0,0 +1,323 @@ +package org.tron.common.crypto.sm2; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.DSA; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SM3Digest; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.ParametersWithID; +import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.crypto.signers.DSAKCalculator; +import org.spongycastle.crypto.signers.RandomDSAKCalculator; +import org.spongycastle.math.ec.ECConstants; +import org.spongycastle.math.ec.ECFieldElement; +import org.spongycastle.math.ec.ECMultiplier; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.math.ec.FixedPointCombMultiplier; +import org.spongycastle.util.BigIntegers; +import org.tron.common.crypto.SignInterface; + +import javax.annotation.Nullable; + +public class SM2Signer + implements ECConstants +{ + private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); + + private byte[] userID; + + private int curveLength; + private ECDomainParameters ecParams; + private ECPoint pubPoint; + private ECKeyParameters ecKey; + + private SecureRandom random; + + public void init(boolean forSigning, CipherParameters param) + { + CipherParameters baseParam; + + if (param instanceof ParametersWithID) + { + baseParam = ((ParametersWithID)param).getParameters(); + userID = ((ParametersWithID)param).getID(); + } + else + { + baseParam = param; + userID = new byte[0]; + } + + if (forSigning) + { + if (baseParam instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)baseParam; + + ecKey = (ECKeyParameters)rParam.getParameters(); + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), rParam.getRandom()); + } + else + { + ecKey = (ECKeyParameters)baseParam; + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), new SecureRandom()); + } + pubPoint = ecParams.getG().multiply(((ECPrivateKeyParameters)ecKey).getD()).normalize(); + } + else + { + ecKey = (ECKeyParameters)baseParam; + ecParams = ecKey.getParameters(); + pubPoint = ((ECPublicKeyParameters)ecKey).getQ(); + } + + curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; + } + + + /** + * generate the signature for the message + * + * @param message plaintext + * @return + */ + public BigInteger[] generateSignature(byte[] message) + { + byte[] eHash = generateSM3Hash(message); + return generateHashSignature(eHash); + } + /** + * generate the signature for the message + * + * @param message + * @return + */ + + public byte[] generateSM3Hash(byte[] message) + { + //byte[] msg = message.getBytes(); + + SM3Digest digest = new SM3Digest(); + byte[] z = getZ(digest); + + digest.update(z, 0, z.length); + digest.update(message, 0, message.length); + + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * generate the signature from the 32 byte hash + * + * @param hash + * @return + */ + public BigInteger[] generateHashSignature(byte[] hash) + { + if (hash.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "ECDSA signature, not " + hash.length); + } + BigInteger n = ecParams.getN(); + BigInteger e = calculateE(hash); + BigInteger d = ((ECPrivateKeyParameters)ecKey).getD(); + + BigInteger r, s; + + ECMultiplier basePointMultiplier = createBasePointMultiplier(); + + // 5.2.1 Draft RFC: SM2 Public Key Algorithms + do // generate s + { + BigInteger k; + do // generate r + { + // A3 + k = kCalculator.nextK(); + // A4 + ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); + + // A5 + r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); + } + while (r.equals(ZERO) || r.add(k).equals(n)); + + // A6 + BigInteger dPlus1ModN = d.add(ONE).modInverse(n); + + s = k.subtract(r.multiply(d)).mod(n); + s = dPlus1ModN.multiply(s).mod(n); + } + while (s.equals(ZERO)); + + // A7 + return new BigInteger[]{ r, s }; + } + + /** + * verify the message signature + * + * @param message + * @param r + * @param s + * @return + */ + public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, @Nullable String userID) + { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); + +// SM3Digest digest = new SM3Digest(); +// +// byte[] z = getZ(digest); +// +// digest.update(z, 0, z.length); +// digest.update(message, 0, message.length); +// +// byte[] eHash = new byte[digest.getDigestSize()]; +// +// // B3 +// digest.doFinal(eHash, 0); + if(userID != null) { + this.userID = userID.getBytes(); + } + byte[] eHash = generateSM3Hash(message); + + // B4 + BigInteger e = calculateE(eHash); + + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) + { + return false; + } + else + { + // B6 + ECPoint x1y1 = ecParams.getG().multiply(s); + x1y1 = x1y1.add(q.multiply(t)).normalize(); + + // B7 + return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); + } + } + + /** + * verfify the hash signature + * + * @param hash + * @param r + * @param s + * @return + */ + public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) + { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) + { + return false; + } + + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) + { + return false; + } + + ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); + + + // B4 + BigInteger e = calculateE(hash); + + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) + { + return false; + } + else + { + // B6 + ECPoint x1y1 = ecParams.getG().multiply(s); + x1y1 = x1y1.add(q.multiply(t)).normalize(); + + // B7 + return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); + } + } + + private byte[] getZ(Digest digest) + { + + //addUserID(digest, userID); + + addFieldElement(digest, ecParams.getCurve().getA()); + addFieldElement(digest, ecParams.getCurve().getB()); + addFieldElement(digest, ecParams.getG().getAffineXCoord()); + addFieldElement(digest, ecParams.getG().getAffineYCoord()); + addFieldElement(digest, pubPoint.getAffineXCoord()); + addFieldElement(digest, pubPoint.getAffineYCoord()); + + byte[] rv = new byte[digest.getDigestSize()]; + + digest.doFinal(rv, 0); + + return rv; + } + + private void addUserID(Digest digest, byte[] userID) + { + int len = userID.length * 8; + digest.update((byte)(len >> 8 & 0xFF)); + digest.update((byte)(len & 0xFF)); + digest.update(userID, 0, userID.length); + } + + private void addFieldElement(Digest digest, ECFieldElement v) + { + byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); + digest.update(p, 0, p.length); + } + + protected ECMultiplier createBasePointMultiplier() + { + return new FixedPointCombMultiplier(); + } + + protected BigInteger calculateE(byte[] message) + { + return new BigInteger(1, message); + } + +} + diff --git a/src/main/java/org/tron/common/crypto/sm2/SM3.java b/src/main/java/org/tron/common/crypto/sm2/SM3.java new file mode 100644 index 000000000..6a72f85f2 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/sm2/SM3.java @@ -0,0 +1,22 @@ +package org.tron.common.crypto.sm2; + +import org.spongycastle.crypto.digests.SM3Digest; +import org.spongycastle.util.encoders.Hex; + +public class SM3 { + public static byte[] hash(String message) { + byte[] msg = Hex.decode(message); + return hash(msg); + } + + public static byte[] hash(byte[] message) { + SM3Digest digest = new SM3Digest(); + digest.update(message,0,message.length); + + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + + return eHash; + } +} diff --git a/src/main/java/org/tron/common/utils/DecodeUtil.java b/src/main/java/org/tron/common/utils/DecodeUtil.java new file mode 100644 index 000000000..62e38ed70 --- /dev/null +++ b/src/main/java/org/tron/common/utils/DecodeUtil.java @@ -0,0 +1,39 @@ +package org.tron.common.utils; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; + +//import static org.tron.core.Constant.ADD_PRE_FIX_BYTE_MAINNET; + +@Slf4j(topic = "Commons") +public class DecodeUtil { + public static final byte ADD_PRE_FIX_BYTE_MAINNET = (byte) 0x41; //41 + address + public static final int ADDRESS_SIZE = 42; + public static byte addressPreFixByte = ADD_PRE_FIX_BYTE_MAINNET; + + public static boolean addressValid(byte[] address) { + if (ArrayUtils.isEmpty(address)) { + logger.warn("Warning: Address is empty !!"); + return false; + } + if (address.length != ADDRESS_SIZE / 2) { + logger.warn( + "Warning: Address length need " + ADDRESS_SIZE + " but " + address.length + + " !!"); + return false; + } + + if (address[0] != addressPreFixByte) { + logger.warn("Warning: Address need prefix with " + addressPreFixByte + " but " + + address[0] + " !!"); + return false; + } + //Other rule; + return true; + } + + public static String createReadableString(byte[] bytes) { + return ByteArray.toHexString(bytes); + } + +} diff --git a/src/main/java/org/tron/common/utils/Hash.java b/src/main/java/org/tron/common/utils/Hash.java new file mode 100644 index 000000000..b82ed7237 --- /dev/null +++ b/src/main/java/org/tron/common/utils/Hash.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.utils; + +import lombok.extern.slf4j.Slf4j; +import org.spongycastle.math.ec.ECPoint; +import org.tron.common.crypto.jce.TronCastleProvider; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; + +import static java.util.Arrays.copyOfRange; +import static org.tron.common.utils.ByteUtil.*; + +@Slf4j(topic = "crypto") +public class Hash { + + public static final byte[] EMPTY_TRIE_HASH; + private static final Provider CRYPTO_PROVIDER; + private static final String HASH_256_ALGORITHM_NAME; + private static final String HASH_512_ALGORITHM_NAME; + private static final String ALGORITHM_NOT_FOUND = "Can't find such algorithm"; + /** + * [0x80] If a string is 0-55 bytes long, the RLP encoding consists of a single byte with value + * 0x80 plus the length of the string followed by the string. The range of the first byte is thus + * [0x80, 0xb7]. + */ + private static final int OFFSET_SHORT_ITEM = 0x80; + + /** + * [0xb7] If a string is more than 55 bytes long, the RLP encoding consists of a single byte with + * value 0xb7 plus the length of the length of the string in binary form, followed by the length + * of the string, followed by the string. For example, a length-1024 string would be encoded as + * \xb9\x04\x00 followed by the string. The range of the first byte is thus [0xb8, 0xbf]. + */ + private static final int OFFSET_LONG_ITEM = 0xb7; + + /** + * Reason for threshold according to Vitalik Buterin: - 56 bytes maximizes the benefit of both + * options - if we went with 60 then we would have only had 4 slots for long strings so RLP would + * not have been able to store objects above 4gb - if we went with 48 then RLP would be fine for + * 2^128 space, but that's way too much - so 56 and 2^64 space seems like the right place to put + * the cutoff - also, that's where Bitcoin's varint does the cutof + */ + private static final int SIZE_THRESHOLD = 56; + + static { + Security.addProvider(TronCastleProvider.getInstance()); + CRYPTO_PROVIDER = Security.getProvider("SC"); + HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256"; + HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512"; + EMPTY_TRIE_HASH = sha3(encodeElement(EMPTY_BYTE_ARRAY)); + } + + public static byte[] sha3(byte[] input) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + + } + + public static byte[] sha3(byte[] input1, byte[] input2) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input1, 0, input1.length); + digest.update(input2, 0, input2.length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + } + + /** + * hashing chunk of the data + * + * @param input - data for hash + * @param start - start of hashing chunk + * @param length - length of hashing chunk + * @return - keccak hash of the chunk + */ + public static byte[] sha3(byte[] input, int start, int length) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input, start, length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + } + + public static byte[] sha512(byte[] input) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_512_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + logger.error(ALGORITHM_NOT_FOUND, e); + throw new RuntimeException(e); + } + } + + + public static byte[] encodeElement(byte[] srcData) { + + // [0x80] + if (isNullOrZeroArray(srcData)) { + return new byte[]{(byte) OFFSET_SHORT_ITEM}; + + // [0x00] + } else if (isSingleZero(srcData)) { + return srcData; + + // [0x01, 0x7f] - single byte, that byte is its own RLP encoding + } else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { + return srcData; + + // [0x80, 0xb7], 0 - 55 bytes + } else if (srcData.length < SIZE_THRESHOLD) { + // length = 8X + byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); + byte[] data = Arrays.copyOf(srcData, srcData.length + 1); + System.arraycopy(data, 0, data, 1, srcData.length); + data[0] = length; + + return data; + // [0xb8, 0xbf], 56+ bytes + } else { + // length of length = BX + // prefix = [BX, [length]] + int tmpLength = srcData.length; + byte lengthOfLength = 0; + while (tmpLength != 0) { + ++lengthOfLength; + tmpLength = tmpLength >> 8; + } + + // set length Of length at first byte + byte[] data = new byte[1 + lengthOfLength + srcData.length]; + data[0] = (byte) (OFFSET_LONG_ITEM + lengthOfLength); + + // copy length after first byte + tmpLength = srcData.length; + for (int i = lengthOfLength; i > 0; --i) { + data[i] = (byte) (tmpLength & 0xFF); + tmpLength = tmpLength >> 8; + } + + // at last copy the number bytes after its length + System.arraycopy(srcData, 0, data, 1 + lengthOfLength, srcData.length); + + return data; + } + } + + public static byte[] computeAddress(ECPoint pubPoint) { + return computeAddress(pubPoint.getEncoded(/* uncompressed */ false)); + } + + public static byte[] computeAddress(byte[] pubBytes) { + return sha3omit12( + Arrays.copyOfRange(pubBytes, 1, pubBytes.length)); + } + + /** + * Calculates RIGTMOST160(SHA3(input)). This is used in address calculations. * + * + * @param input - data + * @return - add_pre_fix + 20 right bytes of the hash keccak of the data + */ + public static byte[] sha3omit12(byte[] input) { + byte[] hash = Hash.sha3(input); + byte[] address = copyOfRange(hash, 11, hash.length); + address[0] = DecodeUtil.addressPreFixByte; + return address; + } +} diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index bc54d14e9..ccaf56222 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -19,6 +19,9 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -164,13 +167,10 @@ public static boolean validTransaction(Transaction signedTransaction) { return true; } - public static Transaction sign(Transaction transaction, ECKey myKey) { + public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); - - //System.out.println("Sign address: " + WalletApi.encode58Check(myKey.getAddress())); - - ECDSASignature signature = myKey.sign(hash); + SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); transaction = transactionBuilderSigned.build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index fd77fd55e..86ffa40d1 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Scanner; public class Utils { public static final String PERMISSION_ID = "Permission_id"; @@ -516,6 +517,5 @@ public static boolean isNumericString(String str) { } return true; } - } diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java new file mode 100644 index 000000000..fc57493a5 --- /dev/null +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -0,0 +1,126 @@ +package org.tron.demo; + +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.TransactionExtention; + +import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.ByteArray; +import org.tron.core.exception.CancelException; +import org.tron.protos.Contract; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.Transaction; +import org.tron.walletserver.WalletApi; + +import java.util.Arrays; + +public class TransactionSignDemoForSM2 { + + public static Transaction setReference(Transaction transaction, Block newestBlock) { + long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); + byte[] blockHash = getBlockHash(newestBlock).getBytes(); + byte[] refBlockNum = ByteArray.fromLong(blockHeight); + Transaction.raw rawData = transaction.getRawData().toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); + return transaction.toBuilder().setRawData(rawData).build(); + } + + public static Sha256Hash getBlockHash(Block block) { + return Sha256Hash.of(block.getBlockHeader().getRawData().toByteArray()); + } + + public static String getTransactionHash(Transaction transaction) { + String txid = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + return txid; + } + + + public static Transaction createTransaction(byte[] from, byte[] to, long amount) { + Transaction.Builder transactionBuilder = Transaction.newBuilder(); + Block newestBlock = WalletApi.getBlock(-1); + + Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract + .newBuilder(); + transferContractBuilder.setAmount(amount); + ByteString bsTo = ByteString.copyFrom(to); + ByteString bsOwner = ByteString.copyFrom(from); + transferContractBuilder.setToAddress(bsTo); + transferContractBuilder.setOwnerAddress(bsOwner); + try { + Any any = Any.pack(transferContractBuilder.build()); + contractBuilder.setParameter(any); + } catch (Exception e) { + return null; + } + contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); + transactionBuilder.getRawDataBuilder().addContract(contractBuilder) + .setTimestamp(System.currentTimeMillis()) + .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + Transaction transaction = transactionBuilder.build(); + Transaction refTransaction = setReference(transaction, newestBlock); + return refTransaction; + } + + + private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) + throws InvalidProtocolBufferException { + SM2 sm2 = SM2.fromPrivate(privateKey); + Transaction transaction1 = Transaction.parseFrom(transaction); + byte[] rawdata = transaction1.getRawData().toByteArray(); + byte[] hash = sm2.hash(rawdata); + byte[] sign = sm2.sign(hash).toByteArray(); + return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); + } + + private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) + throws InvalidProtocolBufferException { + SM2 sm2 = SM2.fromPrivate(privateKey); + Transaction transaction1 = Transaction.parseFrom(transaction); + byte[] rawdata = transaction1.getRawData().toByteArray(); + byte[] hash = sm2.hash(rawdata); + byte[] sign = sm2.sign(hash).toByteArray(); + return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); + } + + private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException { + return WalletApi.broadcastTransaction(transactionBytes); + } + + private static void base58checkToHexString() { + String base58check = "TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"; + String hexString = ByteArray.toHexString(WalletApi.decodeFromBase58Check(base58check)); + System.out.println(hexString); + } + + private static void hexStringTobase58check() { + String hexString = "414948c2e8a756d9437037dcd8c7e0c73d560ca38d"; + String base58check = WalletApi.encode58Check(ByteArray.fromHexString(hexString)); + System.out.println(base58check); + } + + public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { + String privateStr = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; + byte[] privateBytes = ByteArray.fromHexString(privateStr); + SM2 sm2 = SM2.fromPrivate(privateBytes); + byte[] from = sm2.getAddress(); + byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); + long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + Transaction transaction = createTransaction(from, to, amount); + byte[] transactionBytes = transaction.toByteArray(); + + + //sign a transaction in byte format and return a Transaction in byte format + byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); + boolean result = broadcast(transaction4); + + System.out.println(result); + } + + +} diff --git a/src/main/java/org/tron/keystore/Credentials.java b/src/main/java/org/tron/keystore/Credentials.java index a13033723..058bb7f1f 100644 --- a/src/main/java/org/tron/keystore/Credentials.java +++ b/src/main/java/org/tron/keystore/Credentials.java @@ -1,62 +1,10 @@ package org.tron.keystore; -import org.tron.common.crypto.ECKey; -import org.tron.common.utils.ByteArray; -import org.tron.walletserver.WalletApi; -/** - * Credentials wrapper. - */ -public class Credentials { +import org.tron.common.crypto.SignInterface; - private final ECKey ecKeyPair; - private final String address; +public interface Credentials { + SignInterface getPair(); - private Credentials(ECKey ecKeyPair, String address) { - this.ecKeyPair = ecKeyPair; - this.address = address; - } - - public ECKey getEcKeyPair() { - return ecKeyPair; - } - - public String getAddress() { - return address; - } - - public static Credentials create(ECKey ecKeyPair) { - String address = WalletApi.encode58Check(ecKeyPair.getAddress()); - return new Credentials(ecKeyPair, address); - } - - public static Credentials create(String privateKey) { - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); - return create(eCkey); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Credentials that = (Credentials) o; - - if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { - return false; - } - - return address != null ? address.equals(that.address) : that.address == null; - } - - @Override - public int hashCode() { - int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; - result = 31 * result + (address != null ? address.hashCode() : 0); - return result; - } + String getAddress(); } diff --git a/src/main/java/org/tron/keystore/CredentialsEckey.java b/src/main/java/org/tron/keystore/CredentialsEckey.java new file mode 100644 index 000000000..94aa0f255 --- /dev/null +++ b/src/main/java/org/tron/keystore/CredentialsEckey.java @@ -0,0 +1,68 @@ +package org.tron.keystore; + +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +/** + * Credentials wrapper. + */ +public class CredentialsEckey implements Credentials { + + private final ECKey ecKeyPair; + private final String address; + + private CredentialsEckey(ECKey ecKeyPair, String address) { + this.ecKeyPair = ecKeyPair; + this.address = address; + } + +// public ECKey getEcKeyPair() { +// return ecKeyPair; +// } + + public String getAddress() { + return address; + } + + public static CredentialsEckey create(ECKey ecKeyPair) { + String address = WalletApi.encode58Check(ecKeyPair.getAddress()); + return new CredentialsEckey(ecKeyPair, address); + } + + public static CredentialsEckey create(String privateKey) { + ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CredentialsEckey that = (CredentialsEckey) o; + + if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public SignInterface getPair() { + return ecKeyPair; + } +} diff --git a/src/main/java/org/tron/keystore/CredentialsSM2.java b/src/main/java/org/tron/keystore/CredentialsSM2.java new file mode 100644 index 000000000..537f8ce65 --- /dev/null +++ b/src/main/java/org/tron/keystore/CredentialsSM2.java @@ -0,0 +1,68 @@ +package org.tron.keystore; + +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +/** + * Credentials wrapper. + */ +public class CredentialsSM2 implements Credentials { + + private final SM2 sm2Pair; + private final String address; + + private CredentialsSM2(SM2 sm2Pair, String address) { + this.sm2Pair = sm2Pair; + this.address = address; + } + +// public SM2 getEcKeyPair() { +// return sm2Pair; +// } + + public String getAddress() { + return address; + } + + public static CredentialsSM2 create(SM2 sm2Pair) { + String address = WalletApi.encode58Check(sm2Pair.getAddress()); + return new CredentialsSM2(sm2Pair, address); + } + + public static CredentialsSM2 create(String privateKey) { + SM2 eCkey = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CredentialsSM2 that = (CredentialsSM2) o; + + if (sm2Pair != null ? !sm2Pair.equals(that.sm2Pair) : that.sm2Pair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = sm2Pair != null ? sm2Pair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } + + @Override + public SignInterface getPair() { + return sm2Pair; + } +} diff --git a/src/main/java/org/tron/keystore/Wallet.java b/src/main/java/org/tron/keystore/Wallet.java index 55e15ce55..7331a5a75 100644 --- a/src/main/java/org/tron/keystore/Wallet.java +++ b/src/main/java/org/tron/keystore/Wallet.java @@ -6,6 +6,9 @@ import org.bouncycastle.crypto.params.KeyParameter; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CipherException; import org.tron.walletserver.WalletApi; @@ -59,7 +62,7 @@ public class Wallet { static final String AES_128_CTR = "pbkdf2"; static final String SCRYPT = "scrypt"; - public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) + public static WalletFile create(byte[] password, SignInterface ecKeySm2Pair, int n, int p) throws CipherException { byte[] salt = generateRandomBytes(32); @@ -69,32 +72,30 @@ public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); - byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes(); + byte[] privateKeyBytes = ecKeySm2Pair.getPrivKeyBytes(); byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, privateKeyBytes); byte[] mac = generateMac(derivedKey, cipherText); - return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); + return createWalletFile(ecKeySm2Pair, cipherText, iv, salt, mac, n, p); } - public static WalletFile createStandard(byte[] password, ECKey ecKeyPair) + public static WalletFile createStandard(byte[] password, SignInterface ecKeySm2Pair) throws CipherException { - return create(password, ecKeyPair, N_STANDARD, P_STANDARD); + return create(password, ecKeySm2Pair, N_STANDARD, P_STANDARD); } - - public static WalletFile createLight(byte[] password, ECKey ecKeyPair) + public static WalletFile createLight(byte[] password, SignInterface ecKeySm2Pair) throws CipherException { - return create(password, ecKeyPair, N_LIGHT, P_LIGHT); + return create(password, ecKeySm2Pair, N_LIGHT, P_LIGHT); } - private static WalletFile createWalletFile( - ECKey ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, - int n, int p) { + SignInterface ecKeySm2Pair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, + int n, int p) { WalletFile walletFile = new WalletFile(); - walletFile.setAddress(WalletApi.encode58Check(ecKeyPair.getAddress())); + walletFile.setAddress(WalletApi.encode58Check(ecKeySm2Pair.getAddress())); WalletFile.Crypto crypto = new WalletFile.Crypto(); crypto.setCipher(CIPHER); @@ -267,6 +268,13 @@ public static ECKey decrypt(byte[] password, WalletFile walletFile) StringUtils.clear(privateKey); return ecKey; } + public static SM2 decryptSM2(byte[] password, WalletFile walletFile) + throws CipherException { + byte[] privateKey = decrypt2PrivateBytes(password, walletFile); + SM2 sm2 = SM2.fromPrivate(privateKey); + StringUtils.clear(privateKey); + return sm2; + } static void validate(WalletFile walletFile) throws CipherException { WalletFile.Crypto crypto = walletFile.getCrypto(); diff --git a/src/main/java/org/tron/keystore/WalletUtils.java b/src/main/java/org/tron/keystore/WalletUtils.java index f409465fe..8d206829f 100644 --- a/src/main/java/org/tron/keystore/WalletUtils.java +++ b/src/main/java/org/tron/keystore/WalletUtils.java @@ -3,8 +3,12 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.typesafe.config.Config; import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Utils; +import org.tron.core.config.Configuration; import org.tron.core.exception.CipherException; import java.io.File; @@ -22,10 +26,16 @@ public class WalletUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); + private static boolean isEckey = true; static { + Config config = Configuration.getByPath("config.conf");//it is needs set to be a constant objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletUtils getConfig isEckey: " + isEckey); + } } public static String generateFullNewWalletFile(byte[] password, File destinationDirectory) @@ -46,20 +56,24 @@ public static String generateNewWalletFile( byte[] password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - - ECKey ecKeyPair = new ECKey(Utils.getRandom()); - return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt); + SignInterface ecKeySm2Pair = null; + if (isEckey) { + ecKeySm2Pair = new ECKey(Utils.getRandom()); + } else { + ecKeySm2Pair = new SM2(Utils.getRandom()); + } + return generateWalletFile(password, ecKeySm2Pair, destinationDirectory, useFullScrypt); } public static String generateWalletFile( - byte[] password, ECKey ecKeyPair, File destinationDirectory, boolean useFullScrypt) + byte[] password, SignInterface ecKeySm2Pair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile; if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeyPair); + walletFile = Wallet.createStandard(password, ecKeySm2Pair); } else { - walletFile = Wallet.createLight(password, ecKeyPair); + walletFile = Wallet.createLight(password, ecKeySm2Pair); } String fileName = getWalletFileName(walletFile); @@ -71,14 +85,14 @@ public static String generateWalletFile( } public static void updateWalletFile( - byte[] password, ECKey ecKeyPair, File source, boolean useFullScrypt) + byte[] password, SignInterface ecKeySm2Pair, File source, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeyPair); + walletFile = Wallet.createStandard(password, ecKeySm2Pair); } else { - walletFile = Wallet.createLight(password, ecKeyPair); + walletFile = Wallet.createLight(password, ecKeySm2Pair); } objectMapper.writeValue(source, walletFile); @@ -128,11 +142,15 @@ public static String generateWalletFile(WalletFile walletFile, File destinationD public static Credentials loadCredentials(byte[] password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); - return Credentials.create(Wallet.decrypt(password, walletFile)); + + if (isEckey) { + return CredentialsEckey.create(Wallet.decrypt(password, walletFile)); + } + return CredentialsSM2.create(Wallet.decryptSM2(password, walletFile)); } public static WalletFile loadWalletFile(File source) throws IOException { - return objectMapper.readValue(source, WalletFile.class); + return objectMapper.readValue(source, WalletFile.class); } // // public static Credentials loadBip39Credentials(String password, String mnemonic) { @@ -176,7 +194,6 @@ public static String getMainnetKeyDirectory() { } - // public static boolean isValidPrivateKey(String privateKey) { // String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); // return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; @@ -195,7 +212,7 @@ public static String getMainnetKeyDirectory() { // } public static void generateSkeyFile(SKeyCapsule skey, File file) - throws IOException { + throws IOException { objectMapper.writeValue(file, skey); } diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 3e597f4ee..534a08a37 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -7,12 +7,15 @@ import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; import org.tron.core.exception.CipherException; import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; +import org.tron.keystore.CredentialsEckey; import org.tron.keystore.WalletUtils; import org.tron.protos.Contract; import org.tron.protos.Contract.TransferContract; @@ -316,13 +319,14 @@ public static void testSha3() { public static void testGenerateWalletFile() throws CipherException, IOException { String PASSWORD = "Insecure Pa55w0rd"; String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); +// ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); + SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(priKeyHex)); File file = new File("out"); - String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), eCkey, file, true); + String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), sm2, file, true); Credentials credentials = WalletUtils.loadCredentials(PASSWORD.getBytes(), new File(file, fileName)); String address = credentials.getAddress(); - ECKey ecKeyPair = credentials.getEcKeyPair(); - String prikey = ByteArray.toHexString(ecKeyPair.getPrivKeyBytes()); + SignInterface pair = credentials.getPair(); + String prikey = ByteArray.toHexString(pair.getPrivKeyBytes()); System.out.println("address = " + address); System.out.println("prikey = " + prikey); @@ -360,6 +364,13 @@ public static void testPasswordStrength(){ System.out.println(password + " strength is " + level); } } + + public static void interfaceTest() { + String privateKey = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; + SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); + String address = WalletApi.encode58Check(sm2.getAddress()); + System.out.println(address); + } public static void main(String[] args) throws Exception { testPasswordStrength(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index d0fe50cfd..f1b4449d4 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -21,6 +21,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.TransactionUtils; @@ -52,6 +53,7 @@ public class WalletApi { private byte[] address; private static byte addressPreFixByte = CommonConstant.ADD_PRE_FIX_BYTE_TESTNET; private static int rpcVersion = 0; + private static boolean isEckey = true; private static GrpcClient rpcCli = init(); @@ -85,6 +87,11 @@ public static GrpcClient init() { } if (config.hasPath("RPC_version")) { rpcVersion = config.getInt("RPC_version"); + System.out.println("WalletApi getRpcVsersion: " + rpcVersion); + } + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletApi getConfig isEckey: " + isEckey); } return new GrpcClient(fullNode, solidityNode); } @@ -137,15 +144,27 @@ public static int getRpcVersion() { * Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { - ECKey ecKey = new ECKey(Utils.getRandom()); - WalletFile walletFile = Wallet.createStandard(password, ecKey); + WalletFile walletFile = null; + if (isEckey) { + ECKey ecKey = new ECKey(Utils.getRandom()); + walletFile = Wallet.createStandard(password, ecKey); + } else { + SM2 sm2 = new SM2(Utils.getRandom()); + walletFile = Wallet.createStandard(password, sm2); + } return walletFile; } // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - ECKey ecKey = ECKey.fromPrivate(priKey); - WalletFile walletFile = Wallet.createStandard(password, ecKey); + WalletFile walletFile=null; + if (isEckey) { + ECKey ecKey = ECKey.fromPrivate(priKey); + walletFile = Wallet.createStandard(password, ecKey); + } else { + SM2 sm2 = SM2.fromPrivate(priKey); + walletFile = Wallet.createStandard(password, sm2); + } return walletFile; } @@ -183,6 +202,10 @@ public ECKey getEcKey(WalletFile walletFile, byte[] password) throws CipherExcep return Wallet.decrypt(password, walletFile); } + public SM2 getSM2(WalletFile walletFile, byte[] password) throws CipherException { + return Wallet.decryptSM2(password, walletFile); + } + public byte[] getPrivateBytes(byte[] password) throws CipherException, IOException { WalletFile walletFile = loadWalletFile(); return Wallet.decrypt2PrivateBytes(password, walletFile); @@ -286,7 +309,7 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso "No keystore file found, please use registerwallet or importwallet first!"); } Credentials credentials = WalletUtils.loadCredentials(oldPassword, wallet); - WalletUtils.updateWalletFile(newPassowrd, credentials.getEcKeyPair(), wallet, true); + WalletUtils.updateWalletFile(newPassowrd, credentials.getPair(), wallet, true); return true; } @@ -352,8 +375,11 @@ private Transaction signTransaction(Transaction transaction) char[] password = Utils.inputPassword(false); byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -391,7 +417,11 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -433,13 +463,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -448,13 +478,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); + .getParameter().unpack(CreateSmartContract.class); byte[] contractAddress = generateContractAddress( createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( @@ -463,7 +493,7 @@ private void showTransactionAfterSign(Transaction transaction) } private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -481,7 +511,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -498,9 +528,9 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -513,7 +543,7 @@ private boolean processTransaction(Transaction transaction) System.out.println(Utils.printTransactionExceptId(transaction)); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -535,7 +565,7 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p //Warning: do not invoke this interface provided by others. public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -547,7 +577,7 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction //Warning: do not invoke this interface provided by others. public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -572,25 +602,25 @@ public static byte[] createAdresss(byte[] passPhrase) { //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -643,7 +673,7 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -687,7 +717,7 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + .createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -799,7 +829,7 @@ public boolean voteWitness(byte[] owner, HashMap witness) } public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -811,8 +841,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] } public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -826,10 +856,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] } public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + .newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -842,7 +872,7 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra } public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -853,7 +883,7 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] } public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -884,7 +914,7 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( } public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -893,7 +923,7 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] } public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -902,7 +932,7 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] } public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -911,14 +941,14 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] } public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + .newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -971,7 +1001,7 @@ public static boolean addressValid(byte[] address) { if (preFixbyte != WalletApi.getAddressPreFixByte()) { System.out .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + + preFixbyte + " !!"); return false; } //Other rule; @@ -997,9 +1027,9 @@ private static byte[] decode58Check(String input) { byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + hash1[1] == decodeCheck[decodeData.length + 1] && + hash1[2] == decodeCheck[decodeData.length + 2] && + hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1132,13 +1162,13 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { } public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { @@ -1146,13 +1176,13 @@ public static Optional getTransactionsFromThis2(byte[] // } public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { @@ -1168,7 +1198,7 @@ public static Optional getTransactionInfoById(String txID) { } public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, frozen_balance, @@ -1205,7 +1235,7 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) } private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + long frozen_duration, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } @@ -1241,7 +1271,7 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo } Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + .newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1275,13 +1305,13 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); @@ -1312,7 +1342,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { } Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1336,7 +1366,7 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { } Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1383,7 +1413,7 @@ public static Optional getProposal(String id) { } public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1406,7 +1436,7 @@ public static Optional getChainParameters() { public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1426,9 +1456,9 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) } public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { + long id, boolean is_add_approval) { Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1447,7 +1477,7 @@ public boolean deleteProposal(byte[] owner, long id) } public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); @@ -1455,7 +1485,7 @@ public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[ } public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + byte[] secondTokenId, long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1468,8 +1498,8 @@ public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstToken } public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + byte[] firstTokenId, long firstTokenBalance, + byte[] secondTokenId, long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1493,7 +1523,7 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon } public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1516,9 +1546,9 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l } public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1528,7 +1558,7 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b } public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1541,9 +1571,9 @@ public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId } public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { + long exchangeId, byte[] tokenId, long quant, long expected) { Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1597,21 +1627,21 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; + abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; + abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; + abiItem.getAsJsonObject().get("name").getAsString() : null; JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; + abiItem.getAsJsonObject().get("type").getAsString() : null; boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; + abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; if (type == null) { System.out.println("No type!"); return null; @@ -1633,7 +1663,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1642,10 +1672,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1658,7 +1688,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1667,10 +1697,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1691,7 +1721,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { } public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1705,7 +1735,7 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract byte[] contractAddress, long originEnergyLimit) { Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); @@ -1713,20 +1743,20 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract } public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + byte[] contractAddress) { Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + byte[] address, + String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, + String libraryAddressPair, String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1754,7 +1784,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } @@ -1762,7 +1792,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa } private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1791,7 +1821,7 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } else if (compilerVersion.equalsIgnoreCase("v5")) { //0.5.4 version String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1805,8 +1835,8 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + byte[] contractAddress, + long callValue, byte[] data, long tokenValue, String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1833,7 +1863,7 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { } public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1867,7 +1897,7 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig contractAddress, originEnergyLimit); TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + .updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { @@ -1904,8 +1934,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) } public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1929,7 +1959,7 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1953,8 +1983,8 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str } public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + long feeLimit, + long tokenValue, String tokenId, boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1980,12 +2010,12 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa Transaction transaction = transactionExtention.getTransaction(); // for constant if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + transactionExtention.getConstantResult(0) != null && + transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + .toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } @@ -1993,7 +2023,7 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2111,7 +2141,11 @@ public Transaction addTransactionSign(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + if (isEckey) { + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); + } else { + transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); + } org.tron.keystore.StringUtils.clear(passwd); return transaction; } @@ -2135,7 +2169,7 @@ public static Optional GetMerkleTreeVoucherInfo( } public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2152,7 +2186,7 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry } public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2237,7 +2271,7 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall } public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2254,7 +2288,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2263,7 +2297,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + .clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2279,10 +2313,10 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + .addContract( + Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) + .setParameter( + Any.pack(contractBuild.build())).build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2292,7 +2326,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2359,7 +2393,7 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + .updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf new file mode 100644 index 000000000..171520768 --- /dev/null +++ b/src/main/resources/config-back.conf @@ -0,0 +1,20 @@ +net { + type = mainnet +} + +fullnode = { + ip.list = [ + "47.89.189.124:50055", + "47.89.178.193:50055" + ] +} + +#soliditynode = { +# ip.list = [ +# "127.0.0.1:50052" +# ] +#} +crypto { + engine=sm2 +} +RPC_version = 2 From 3ed1c3ecda4c2fe919acd9e80591bcbc1ed7dbda Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 6 Jan 2020 15:20:21 +0800 Subject: [PATCH 233/445] fix bug --- .../java/org/tron/common/utils/Utils.java | 11 ++- .../org/tron/core/zen/ShieldedWrapper.java | 85 +++---------------- src/main/java/org/tron/walletcli/Client.java | 21 ++--- .../java/org/tron/walletserver/WalletApi.java | 2 + 4 files changed, 28 insertions(+), 91 deletions(-) diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index fd77fd55e..bad7fd3aa 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -31,7 +31,6 @@ import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.Transaction; import org.tron.walletserver.WalletApi; - import java.io.Console; import java.io.IOException; import java.nio.ByteBuffer; @@ -517,5 +516,15 @@ public static boolean isNumericString(String str) { return true; } + public static boolean isHexString(String str) { + boolean bRet = false; + try { + ByteArray.fromHexString(str); + bRet = true; + } catch (Exception e) { + } + return bRet; + } + } diff --git a/src/main/java/org/tron/core/zen/ShieldedWrapper.java b/src/main/java/org/tron/core/zen/ShieldedWrapper.java index 7c4005ca1..dea384fc6 100644 --- a/src/main/java/org/tron/core/zen/ShieldedWrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedWrapper.java @@ -1,7 +1,5 @@ package org.tron.core.zen; -import static org.tron.walletcli.Client.getCmd; - import com.google.protobuf.ByteString; import io.netty.util.internal.StringUtil; import lombok.Getter; @@ -20,7 +18,6 @@ import org.tron.protos.Protocol.Block; import org.tron.walletcli.Client; import org.tron.walletserver.WalletApi; - import java.io.File; import java.io.IOException; import java.security.SecureRandom; @@ -642,57 +639,14 @@ public ShieldedAddressInfo backupShieldedWallet() throws IOException, CipherExce continue; } if (n < 1 || n > shieldedAddressInfoList.size()) { + System.out.println("Invalid number of " + num); System.out.println(tipInfo); continue; } -// ShieldedAddressInfo targetShieldedAddress = shieldedAddressInfoList.get(n - 1); -// byte[] skAndD = new byte[targetShieldedAddress.getSk().length + targetShieldedAddress.getD() -// .getData().length]; -// -// System.arraycopy(targetShieldedAddress.getSk(), 0, skAndD, 0, -// targetShieldedAddress.getSk().length); -// System.arraycopy(targetShieldedAddress.getD().getData(), 0, -// skAndD, targetShieldedAddress.getSk().length, targetShieldedAddress.getD().getData().length); -// return skAndD; return shieldedAddressInfoList.get(n - 1); } } -// public byte[] importShieldedWallet() throws IOException, CipherException { -// ZenUtils.checkFolderExist(PREFIX_FOLDER); -// -// if (shieldedSkeyFileExist()) { -// byte[] tempShieldedKey = loadSkey(); -// if (ArrayUtils.isEmpty(tempShieldedKey)) { -// System.out.println("Invalid password."); -// return null; -// } else { -// shieldedSkey = tempShieldedKey; -// } -// } else { -// shieldedSkey = generateSkey(); -// } -// loadShieldWallet(); -// -// byte[] temp = new byte[86]; -// byte[] result = null; -// System.out.println("Please input shielded wallet hex string. such as 'sk d',Max retry time:" + 3); -// int nTime = 0; -// while (nTime < 3) { -// int len = System.in.read(temp, 0, temp.length); -// if (len >= 86) { -// byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); -// result = StringUtils.hexs2Bytes(privateKey); -// StringUtils.clear(privateKey); -// break; -// } -// StringUtils.clear(result); -// System.out.println("Invalid shielded address, please input again."); -// ++nTime; -// } -// return result; -// } - public byte[] importShieldedWallet() throws IOException, CipherException { ZenUtils.checkFolderExist(PREFIX_FOLDER); @@ -713,44 +667,25 @@ public byte[] importShieldedWallet() throws IOException, CipherException { System.out.println("Please input shielded wallet hex string. such as 'sk d',Max retry time:" + 3); int nTime = 0; - byte[] buffer = new byte[1000]; + Scanner in = new Scanner(System.in); while (nTime < 3) { - System.in.read(buffer, 0, buffer.length); - String[] array = Client.getCmd(new String(buffer).trim()); - if (array.length == 2) { + String input = in.nextLine().trim(); + String[] array = Client.getCmd(input.trim()); + if (array.length == 2 && Utils.isHexString(array[0]) && Utils.isHexString(array[1])) { System.out.println("Import shielded wallet hex string is : "); - System.out.println("sk:" + array[0]); - System.out.println("d :" + array[1]); + System.out.println("sk:" + array[0]); + System.out.println("d :" + array[1]); byte[] sk = ByteArray.fromHexString(array[0]); byte[] d = ByteArray.fromHexString(array[1]); result = new byte[sk.length + d.length]; - System.arraycopy(result, 0, sk, 0, sk.length); - System.arraycopy(result, sk.length, d, 0, d.length); + System.arraycopy(sk, 0, result, 0, sk.length); + System.arraycopy(d, 0, result, sk.length, d.length); break; } - - -// if (len >= 86) { -// byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); -// result = StringUtils.hexs2Bytes(privateKey); -// StringUtils.clear(privateKey); -// break; -// } - - -// int len = System.in.read(temp, 0, temp.length); -// if (len >= 86) { -// byte[] privateKey = Arrays.copyOfRange(temp, 0, 86); -// result = StringUtils.hexs2Bytes(privateKey); -// StringUtils.clear(privateKey); -// break; -// } - - StringUtils.clear(result); - System.out.println("Invalid shielded address, please input again."); + System.out.println("Invalid shielded wallet hex string, please input again."); ++nTime; } return result; diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index dcb7adff3..de1a1290f 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -54,7 +54,7 @@ public class Client { "AddTransactionSign", "ApproveProposal", "AssetIssue", - "BackupShieldedAddress", + "BackupShieldedWallet", "BackupWallet", "BackupWallet2Base64", "BroadcastTransaction", @@ -108,7 +108,7 @@ public class Client { "GetTransactionsFromThis", "GetTransactionsToThis", "GetTransactionSignWeight", - "ImportShieldedAddress", + "ImportShieldedWallet", "ImportWallet", "ImportWalletByBase64", "ListAssetIssue", @@ -157,7 +157,7 @@ public class Client { "AddTransactionSign", "ApproveProposal", "AssetIssue", - "BackupShieldedAddress", + "BackupShieldedWallet", "BackupWallet", "BackupWallet2Base64", "BroadcastTransaction", @@ -212,7 +212,7 @@ public class Client { "GetTransactionsToThis", "GetTransactionSignWeight", "Help", - "ImportShieldedAddress", + "ImportShieldedWallet", "ImportWallet", "ImportWalletByBase64", "ListAssetIssue", @@ -2785,17 +2785,8 @@ private void getShieldedPaymentAddress(String[] parameters) { private void backupShieldedWallet() throws IOException, CipherException { ShieldedAddressInfo addressInfo = ShieldedWrapper.getInstance().backupShieldedWallet(); if (addressInfo != null) { - System.out.print("sk:"); - for (int i = 0; i < addressInfo.getSk().length; i++) { - StringUtils.printOneByte(addressInfo.getSk()[i]); - } - System.out.println(); - - System.out.print("d :"); - for (int i = 0; i < addressInfo.getD().getData().length; i++) { - StringUtils.printOneByte(addressInfo.getD().getData()[i]); - } - System.out.println(); + System.out.println("sk:" + ByteArray.toHexString(addressInfo.getSk())); + System.out.println("d :" + ByteArray.toHexString(addressInfo.getD().getData())); System.out.println("BackupShieldedAddress successful !!!"); } else { System.out.println("BackupShieldedAddress failed !!!"); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 79bcebc66..9204f9205 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -236,6 +236,7 @@ public static File selcetWalletFile() { if (wallets.length == 1) { tipInfo = "Please choose address index 1"; } + System.out.println(tipInfo); Scanner in = new Scanner(System.in); while (true) { String input = in.nextLine().trim(); @@ -249,6 +250,7 @@ public static File selcetWalletFile() { continue; } if (n < 1 || n > wallets.length) { + System.out.println("Invalid number of " + num); System.out.println(tipInfo); continue; } From 0805f673982fa39c6e445026ba15e8d800e2b866 Mon Sep 17 00:00:00 2001 From: Alberto-zhang <58253830+alberto-zhang@users.noreply.github.com> Date: Mon, 6 Jan 2020 15:29:07 +0800 Subject: [PATCH 234/445] Revert "Feature/sm2sm3 (#331)" (#332) This reverts commit 0a119f1965c76d92e677cbec7ca4086b36f539ce. --- build.gradle | 262 +- .../java/org/tron/common/crypto/ECKey.java | 2500 ++++++++--------- .../org/tron/common/crypto/Sha256Hash.java | 1 - .../org/tron/common/crypto/SignInterface.java | 26 - .../org/tron/common/crypto/SignUtils.java | 54 - .../common/crypto/SignatureInterface.java | 7 - .../java/org/tron/common/crypto/sm2/SM2.java | 1245 -------- .../org/tron/common/crypto/sm2/SM2Signer.java | 323 --- .../java/org/tron/common/crypto/sm2/SM3.java | 22 - .../org/tron/common/utils/DecodeUtil.java | 39 - src/main/java/org/tron/common/utils/Hash.java | 210 -- .../tron/common/utils/TransactionUtils.java | 10 +- .../java/org/tron/common/utils/Utils.java | 2 +- .../tron/demo/TransactionSignDemoForSM2.java | 126 - .../java/org/tron/keystore/Credentials.java | 60 +- .../org/tron/keystore/CredentialsEckey.java | 68 - .../org/tron/keystore/CredentialsSM2.java | 68 - src/main/java/org/tron/keystore/Wallet.java | 32 +- .../java/org/tron/keystore/WalletUtils.java | 43 +- src/main/java/org/tron/test/Test.java | 19 +- .../java/org/tron/walletserver/WalletApi.java | 274 +- src/main/resources/config-back.conf | 20 - 22 files changed, 1560 insertions(+), 3851 deletions(-) delete mode 100644 src/main/java/org/tron/common/crypto/SignInterface.java delete mode 100644 src/main/java/org/tron/common/crypto/SignUtils.java delete mode 100644 src/main/java/org/tron/common/crypto/SignatureInterface.java delete mode 100644 src/main/java/org/tron/common/crypto/sm2/SM2.java delete mode 100644 src/main/java/org/tron/common/crypto/sm2/SM2Signer.java delete mode 100644 src/main/java/org/tron/common/crypto/sm2/SM3.java delete mode 100644 src/main/java/org/tron/common/utils/DecodeUtil.java delete mode 100644 src/main/java/org/tron/common/utils/Hash.java delete mode 100644 src/main/java/org/tron/demo/TransactionSignDemoForSM2.java delete mode 100644 src/main/java/org/tron/keystore/CredentialsEckey.java delete mode 100644 src/main/java/org/tron/keystore/CredentialsSM2.java delete mode 100644 src/main/resources/config-back.conf diff --git a/build.gradle b/build.gradle index 6d00c8c17..b08079dcb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,131 +1,131 @@ -group 'Tron' -version '1.0-SNAPSHOT' - -apply plugin: 'java' -apply plugin: 'com.google.protobuf' -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'application' - -sourceCompatibility = 1.8 -targetCompatibility = JavaVersion.VERSION_1_8 -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' - -compileJava.options*.compilerArgs = [ - "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", - "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", - "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", - "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" -] - -repositories { - mavenLocal() - mavenCentral() -} - -sourceSets { - main { - proto { - srcDir 'src/main/protos' - } - java { - srcDir 'src/main/gen' - srcDir 'src/main/java' - } - } - -} -buildscript { - repositories { - mavenLocal() - maven { - url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" - } - mavenCentral() - } - ext { - projectVersion = '1.3.0-RELEASE' - grpcVersion = '1.6.1' - protobufVersion = '3.3.0' - protobufGradlePluginVersion = '0.8.0' - springCloudConsulVersion = '1.2.1.RELEASE' - } - - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' - classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" - } -} - -task wrapper(type: Wrapper) { - gradleVersion = '3.3' -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - compile group: 'com.beust', name: 'jcommander', version: '1.72' - //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - compile 'com.maxmind.geoip2:geoip2:2.10.0' - - // google grpc - compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' - - compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile "com.madgag.spongycastle:core:1.58.0.0" - compile "com.madgag.spongycastle:prov:1.58.0.0" - compile group: 'com.typesafe', name: 'config', version: '1.3.2' - compile "com.google.code.findbugs:jsr305:3.0.0" - compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" - compile "org.apache.commons:commons-collections4:4.0" - compile "org.apache.commons:commons-lang3:3.4" - compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' - compile 'com.alibaba:fastjson:1.2.47' - - compile group: 'commons-io', name: 'commons-io', version: '2.6' - compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - - compile group: 'org.jline', name: 'jline', version: '3.1.3' -} - -protobuf { - generatedFilesBaseDir = "$projectDir/src/" - protoc { - artifact = "com.google.protobuf:protoc:3.5.1-1" - - } - plugins { - grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' - } - } - generateProtoTasks { - all().each { task -> - task.builtins { - java { - outputSubDir = "gen" - } - } - } - all()*.plugins { - grpc { - outputSubDir = "gen" - } - } - } -} - -run { - standardInput = System.in - mainClassName = 'org.tron.walletcli.Client' -} - - -shadowJar { - baseName = 'wallet-cli' - classifier = null - version = null -} +group 'Tron' +version '1.0-SNAPSHOT' + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'application' + +sourceCompatibility = 1.8 +targetCompatibility = JavaVersion.VERSION_1_8 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +compileJava.options*.compilerArgs = [ + "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", + "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", + "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", + "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" +] + +repositories { + mavenLocal() + mavenCentral() +} + +sourceSets { + main { + proto { + srcDir 'src/main/protos' + } + java { + srcDir 'src/main/gen' + srcDir 'src/main/java' + } + } + +} +buildscript { + repositories { + mavenLocal() + maven { + url "https://cdn.lfrs.sl/repository.liferay.com/nexus/content/groups/public" + } + mavenCentral() + } + ext { + projectVersion = '1.3.0-RELEASE' + grpcVersion = '1.6.1' + protobufVersion = '3.3.0' + protobufGradlePluginVersion = '0.8.0' + springCloudConsulVersion = '1.2.1.RELEASE' + } + + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' + classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '3.3' +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'com.beust', name: 'jcommander', version: '1.72' + //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' + compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' + compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' + compile 'com.maxmind.geoip2:geoip2:2.10.0' + + // google grpc + compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' + + compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' + compile "com.madgag.spongycastle:core:1.53.0.0" + compile "com.madgag.spongycastle:prov:1.53.0.0" + compile group: 'com.typesafe', name: 'config', version: '1.3.2' + compile "com.google.code.findbugs:jsr305:3.0.0" + compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" + compile "org.apache.commons:commons-collections4:4.0" + compile "org.apache.commons:commons-lang3:3.4" + compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' + compile 'com.alibaba:fastjson:1.2.47' + + compile group: 'commons-io', name: 'commons-io', version: '2.6' + compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' + + compile group: 'org.jline', name: 'jline', version: '3.1.3' +} + +protobuf { + generatedFilesBaseDir = "$projectDir/src/" + protoc { + artifact = "com.google.protobuf:protoc:3.5.1-1" + + } + plugins { + grpc { + artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' + } + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + outputSubDir = "gen" + } + } + } + all()*.plugins { + grpc { + outputSubDir = "gen" + } + } + } +} + +run { + standardInput = System.in + mainClassName = 'org.tron.walletcli.Client' +} + + +shadowJar { + baseName = 'wallet-cli' + classifier = null + version = null +} diff --git a/src/main/java/org/tron/common/crypto/ECKey.java b/src/main/java/org/tron/common/crypto/ECKey.java index 2fc758ade..bb94dcf8e 100644 --- a/src/main/java/org/tron/common/crypto/ECKey.java +++ b/src/main/java/org/tron/common/crypto/ECKey.java @@ -1,1282 +1,1218 @@ -package org.tron.common.crypto; -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -import static org.tron.common.utils.BIUtil.isLessThan; -import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; -import static org.tron.common.utils.ByteUtil.byteArrayToInt; - -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import javax.annotation.Nullable; -import javax.crypto.KeyAgreement; -import lombok.extern.slf4j.Slf4j; -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.asn1.sec.SECNamedCurves; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.crypto.engines.AESEngine; -import org.spongycastle.crypto.modes.SICBlockCipher; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.crypto.params.KeyParameter; -import org.spongycastle.crypto.params.ParametersWithIV; -import org.spongycastle.crypto.signers.ECDSASigner; -import org.spongycastle.crypto.signers.HMacDSAKCalculator; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.util.BigIntegers; -import org.spongycastle.util.encoders.Base64; -import org.spongycastle.util.encoders.Hex; -import org.tron.common.crypto.cryptohash.Keccak256; -import org.tron.common.crypto.jce.ECKeyAgreement; -import org.tron.common.crypto.jce.ECKeyFactory; -import org.tron.common.crypto.jce.ECKeyPairGenerator; -import org.tron.common.crypto.jce.ECSignatureFactory; -import org.tron.common.crypto.jce.TronCastleProvider; -import org.tron.common.utils.BIUtil; -import org.tron.common.utils.ByteUtil; -import org.tron.common.utils.Hash; - -@Slf4j(topic = "crypto") -public class ECKey implements Serializable, SignInterface { - - /** - * The parameters of the secp256k1 curve. - */ - public static final ECDomainParameters CURVE; - public static final ECParameterSpec CURVE_SPEC; - - /** - * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA - * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - - * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2 - * - *

See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki - * #Low_S_values_in_signatures - */ - - public static final BigInteger HALF_CURVE_ORDER; - private static final BigInteger SECP256K1N = - new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); - private static final SecureRandom secureRandom; - private static final long serialVersionUID = -728224901792295832L; - - static { - // All clients must agree on the curve to use by agreement. - X9ECParameters params = SECNamedCurves.getByName("secp256k1"); - CURVE = new ECDomainParameters(params.getCurve(), params.getG(), - params.getN(), params.getH()); - CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), - params.getN(), params.getH()); - HALF_CURVE_ORDER = params.getN().shiftRight(1); - secureRandom = new SecureRandom(); - } - - protected final ECPoint pub; - // The two parts of the key. If "priv" is set, "pub" can always be - // calculated. If "pub" is set but not "priv", we - // can only verify signatures not make them. - // TODO: Redesign this class to use consistent internals and more - // efficient serialization. - private final PrivateKey privKey; - // the Java Cryptographic Architecture provider to use for Signature - // this is set along with the PrivateKey privKey and must be compatible - // this provider will be used when selecting a Signature instance - // https://docs.oracle.com/javase/8/docs/technotes/guides/security - // /SunProviders.html - private final Provider provider; - - // Transient because it's calculated on demand. - private transient byte[] pubKeyHash; - private transient byte[] nodeId; - - /** - * Generates an entirely new keypair. - * - *

BouncyCastle will be used as the Java Security Provider - */ - public ECKey() { - this(secureRandom); - } - - /** - * Generate a new keypair using the given Java Security Provider. - * - *

All private key operations will use the provider. - */ - public ECKey(Provider provider, SecureRandom secureRandom) { - this.provider = provider; - - final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom); - final KeyPair keyPair = keyPairGen.generateKeyPair(); - - this.privKey = keyPair.getPrivate(); - - final PublicKey pubKey = keyPair.getPublic(); - if (pubKey instanceof BCECPublicKey) { - pub = ((BCECPublicKey) pubKey).getQ(); - } else if (pubKey instanceof ECPublicKey) { - pub = extractPublicKey((ECPublicKey) pubKey); - } else { - throw new AssertionError( - "Expected Provider " + provider.getName() - + " to produce a subtype of ECPublicKey, found " - + pubKey.getClass()); - } - } - - /** - * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle - * will be used as the Java Security Provider - * - * @param secureRandom - - */ - public ECKey(SecureRandom secureRandom) { - this(TronCastleProvider.getInstance(), secureRandom); - } - - /** - * Pair a private key with a public EC point. - * - *

All private key operations will use the provider. - */ - - // isPrivateKey true 私钥 其他公钥 - public ECKey(byte[] key, boolean isPrivateKey) { - if (isPrivateKey) { - BigInteger pk = new BigInteger(1, key); - this.privKey = privateKeyFromBigInteger(pk); - this.pub = CURVE.getG().multiply(pk); - } else { - this.privKey = null; - this.pub = CURVE.getCurve().decodePoint(key); - } - this.provider = TronCastleProvider.getInstance(); - } - - public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { - this.provider = provider; - - if (privKey == null || isECPrivateKey(privKey)) { - this.privKey = privKey; - } else { - throw new IllegalArgumentException( - "Expected EC private key, given a private key object with" + - " class " - + privKey.getClass().toString() + - " and algorithm " - + privKey.getAlgorithm()); - } - - if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); - } else { - this.pub = pub; - } - } - - /** - * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java - * Security Provider - */ - public ECKey(@Nullable BigInteger priv, ECPoint pub) { - this( - TronCastleProvider.getInstance(), - privateKeyFromBigInteger(priv), - pub - ); - } - - /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint - */ - private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { - final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); - final BigInteger xCoord = publicPointW.getAffineX(); - final BigInteger yCoord = publicPointW.getAffineY(); - - return CURVE.getCurve().createPoint(xCoord, yCoord); - } - - /* Test if a generic private key is an EC private key - * - * it is not sufficient to check that privKey is a subtype of ECPrivateKey - * as the SunPKCS11 Provider will return a generic PrivateKey instance - * a fallback that covers this case is to check the key algorithm - */ - private static boolean isECPrivateKey(PrivateKey privKey) { - return privKey instanceof ECPrivateKey || privKey.getAlgorithm() - .equals("EC"); - } - - /* Convert a BigInteger into a PrivateKey object - */ - private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - CURVE_SPEC)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } - } - } - - /** - * Utility for compressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param uncompressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint compressPoint(ECPoint uncompressed) { - return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); - } - - /** - * Utility for decompressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param compressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint decompressPoint(ECPoint compressed) { - return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); - } - - /** - * Creates an ECKey given the private key only. - * - * @param privKey - - * @return - - */ - public static ECKey fromPrivate(BigInteger privKey) { - return new ECKey(privKey, CURVE.getG().multiply(privKey)); - } - - /** - * Creates an ECKey given the private key only. - * - * @param privKeyBytes - - * @return - - */ - public static ECKey fromPrivate(byte[] privKeyBytes) { - return fromPrivate(new BigInteger(1, privKeyBytes)); - } - /** - * Creates an ECKey that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of pub will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { - return new ECKey(priv, pub); - } - - /** - * Creates an ECKey that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of the point will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] - pub) { - check(priv != null, "Private key must not be null"); - check(pub != null, "Public key must not be null"); - return new ECKey(new BigInteger(1, priv), CURVE.getCurve() - .decodePoint(pub)); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(ECPoint pub) { - return new ECKey(null, pub); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * encoded point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(byte[] pub) { - return new ECKey(null, CURVE.getCurve().decodePoint(pub)); - } - - /** - * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, - * use new BigInteger(1, bytes); - * - * @param privKey - - * @param compressed - - * @return - - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = CURVE.getG().multiply(privKey); - return point.getEncoded(compressed); - } - - /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. - * - * @param pubPoint a public point - * @return 64-byte X,Y point pair - */ - public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { - final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); - return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); - } - - /** - * Recover the public key from an encoded node id. - * - * @param nodeId a 64-byte X,Y point pair - */ - public static ECKey fromNodeId(byte[] nodeId) { - check(nodeId.length == 64, "Expected a 64 byte node id"); - byte[] pubBytes = new byte[65]; - System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); - pubBytes[0] = 0x04; // uncompressed - return ECKey.fromPublicOnly(pubBytes); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { - byte[] signatureEncoded; - try { - signatureEncoded = Base64.decode(signatureBase64); - } catch (RuntimeException e) { - // This is what you getData back from Bouncy Castle if base64 doesn't - // decode :( - throw new SignatureException("Could not decode base64", e); - } - // Parse the signature bytes into r/s and the selector value. - if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); - } - - return signatureToKeyBytes( - messageHash, - ECDSASignature.fromComponents( - Arrays.copyOfRange(signatureEncoded, 1, 33), - Arrays.copyOfRange(signatureEncoded, 33, 65), - (byte) (signatureEncoded[0] & 0xFF))); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - check(messageHash.length == 32, "messageHash argument has length " + - messageHash.length); - int header = sig.v; - // The header byte: 0x1B = first key with even y, 0x1C = first key - // with odd y, - // 0x1D = second key with even y, 0x1E = second key - // with odd y - if (header < 27 || header > 34) { - throw new SignatureException("Header byte out of range: " + header); - } - if (header >= 31) { - header -= 4; - } - int recId = header - 27; - byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, - messageHash); - if (key == null) { - throw new SignatureException("Could not recover public key from " + - "signature"); - } - return key; - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return Hash.computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - return Hash.computeAddress(signatureToKeyBytes(messageHash, sig)); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return ECKey - */ - public static ECKey signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, - signatureBase64); - return ECKey.fromPublicOnly(keyBytes); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return ECKey - */ - public static ECKey signatureToKey(byte[] messageHash, ECDSASignature - sig) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); - return ECKey.fromPublicOnly(keyBytes); - } - - /** - *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

- *

When using native ECDSA verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

- * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, ECDSASignature signature, - byte[] pub) { - ECDSASigner signer = new ECDSASigner(); - ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE - .getCurve().decodePoint(pub), CURVE); - signer.init(false, params); - try { - return signer.verifySignature(data, signature.r, signature.s); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - logger.error("Caught NPE inside bouncy castle", npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, byte[] signature, byte[] pub) { - return verify(data, ECDSASignature.decodeFromDER(signature), pub); - } - - /** - * Returns true if the given pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @param pubkey - - * @return - - */ - public static boolean isPubKeyCanonical(byte[] pubkey) { - if (pubkey[0] == 0x04) { - // Uncompressed pubkey - return pubkey.length == 65; - } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { - // Compressed pubkey - return pubkey.length == 33; - } else { - return false; - } - } - - /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

- * - *

The recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the - * correct one. Because the key recovery operation yields multiple potential allKeys, the correct - * key must either be stored alongside the signature, or you must be willing to try each recId in - * turn until you find one that outputs the key you are expecting.

- * - *

If this method returns null it means recovery was not possible and recId should be - * iterated.

- * - *

Given the above two points, a correct usage of this method is inside a for loop from 0 - * to 3, and if the output is null OR a key that is not the one you expect, you try again with the - * next recId.

- * - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 65-byte encoded public key - */ - @Nullable - public static byte[] recoverPubBytesFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - check(recId >= 0, "recId must be positive"); - check(sig.r.signum() >= 0, "r must be positive"); - check(sig.s.signum() >= 0, "s must be positive"); - check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside - // this function) - // 1.1 Let x = r + jn - BigInteger n = CURVE.getN(); // Curve order. - BigInteger i = BigInteger.valueOf((long) recId / 2); - BigInteger x = sig.r.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen - // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or - // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an - // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this - // conversion routine outputs “invalid”, then - // do another iteration of Step 1. - // - // More concisely, what these points mean is to use X as a compressed - // public key. - ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); - BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent - // about the letter it uses for the prime. - if (x.compareTo(prime) >= 0) { - // Cannot have point co-ordinates larger than this as everything - // takes place modulo Q. - return null; - } - // Compressed allKeys require you to know an extra bit of data about the - // y-coord as there are two possibilities. - // So it's encoded in the recId. - ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of - // Step 1 (callers responsibility). - if (!R.multiply(n).isInfinity()) { - return null; - } - // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature - // verification. - BigInteger e = new BigInteger(1, messageHash); - // 1.6. For k from 1 to 2 do the following. (loop is outside this - // function via iterating recId) - // 1.6.1. Compute a candidate public key as: - // Q = mi(r) * (sR - eG) - // - // Where mi(x) is the modular multiplicative inverse. We transform - // this into the following: - // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) - // Where -e is the modular additive inverse of e, that is z such that - // z + e = 0 (mod n). In the above equation - // ** is point multiplication and + is point addition (the EC group - // operator). - // - // We can find the additive inverse by subtracting e from zero then - // taking the mod. For example the additive - // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod - // 11 = 8. - BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); - BigInteger rInv = sig.r.modInverse(n); - BigInteger srInv = rInv.multiply(sig.s).mod(n); - BigInteger eInvrInv = rInv.multiply(eInv).mod(n); - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE - .getG(), eInvrInv, R, srInv); - return q.getEncoded(/* compressed */ false); - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 20-byte address - */ - @Nullable - public static byte[] recoverAddressFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return Hash.computeAddress(pubBytes); - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return ECKey - */ - @Nullable - public static ECKey recoverFromSignature(int recId, ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return ECKey.fromPublicOnly(pubBytes); - } - } - - /** - * Decompress a compressed public key (x co-ord and low-bit of y-coord). - * - * @param xBN - - * @param yBit - - * @return - - */ - - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { - X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE - .getCurve())); - compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return CURVE.getCurve().decodePoint(compEnc); - } - - private static void check(boolean test, String message) { - if (!test) { - throw new IllegalArgumentException(message); - } - } - - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, decompressPoint(pub)); - } - } - - /** - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey compress() { - if (pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, compressPoint(pub)); - } - } - - /** - * Returns true if this key doesn't have access to private key bytes. This may be because it was - * never given any private key bytes to begin with (a watching key). - * - * @return - - */ - public boolean isPubKeyOnly() { - return privKey == null; - } - - /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. - * - * @return - - */ - public boolean hasPrivKey() { - return privKey != null; - } - - /** - * Gets the address form of the public key. - * - * @return 21-byte address - */ - public byte[] getAddress() { - if (pubKeyHash == null) { - pubKeyHash = Hash.computeAddress(this.pub); - } - return pubKeyHash; - } - - @Override - public String signHash(byte[] hash) { - return sign(hash).toBase64(); - } - - public byte[] Base64toBytes (String signature) { - byte[] signData = Base64.decode(signature); - byte first = (byte)(signData[0] - 27); - byte[] temp = Arrays.copyOfRange(signData,1,65); - return ByteUtil.appendByte(temp,first); - } - - @Override - public byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException { - return Hash.computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Generates the NodeID based on this key, that is the public key without first format byte - */ - public byte[] getNodeId() { - if (nodeId == null) { - nodeId = pubBytesWithoutFormat(this.pub); - } - return nodeId; - } - - @Override - public byte[] hash(byte[] message) { - Keccak256 hashFun = new Keccak256(); - return hashFun.digest(message); - } - - @Override - public byte[] getPrivateKey() { - return getPrivKeyBytes(); - } - - /** - * Gets the encoded public key value. - * - * @return 65-byte encoded public key - */ - public byte[] getPubKey() { - return pub.getEncoded(/* compressed */ false); - } - - /** - * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. - * - * @return - - */ - public ECPoint getPubKeyPoint() { - return pub; - } - - /** - * Gets the private key in the form of an integer field element. The public key is derived by - * performing EC point addition this number of times (i.e. point multiplying). - * - * @return - - * @throws IllegalStateException if the private key bytes are not available. - */ - public BigInteger getPrivKey() { - if (privKey == null) { - throw new MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - return ((BCECPrivateKey) privKey).getD(); - } else { - throw new MissingPrivateKeyException(); - } - } - - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - return b.toString(); - } - - /** - * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need - * the private key it is better for security reasons to just use toString(). - * - * @return - - */ - public String toStringWithPrivate() { - StringBuilder b = new StringBuilder(); - b.append(toString()); - if (privKey != null && privKey instanceof BCECPrivateKey) { - b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) - privKey).getD().toByteArray())); - } - return b.toString(); - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * ECDSASignature - * - * @param input to sign - * @return ECDSASignature signature that contains the R and S components - */ - public ECDSASignature doSign(byte[] input) { - if (input.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + input.length); - } - // No decryption of private key required. - if (privKey == null) { - throw new MissingPrivateKeyException(); - } - if (privKey instanceof BCECPrivateKey) { - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new - SHA256Digest())); - ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters - (((BCECPrivateKey) privKey).getD(), CURVE); - signer.init(true, privKeyParams); - BigInteger[] components = signer.generateSignature(input); - return new ECDSASignature(components[0], components[1]) - .toCanonicalised(); - } else { - try { - final Signature ecSig = ECSignatureFactory.getRawInstance - (provider); - ecSig.initSign(privKey); - ecSig.update(input); - final byte[] derSignature = ecSig.sign(); - return ECDSASignature.decodeFromDER(derSignature) - .toCanonicalised(); - } catch (SignatureException | InvalidKeyException ex) { - throw new RuntimeException("ECKey signing error", ex); - } - } - } - - /** - * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature - * - * @param messageHash - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public ECDSASignature sign(byte[] messageHash) { - ECDSASignature sig = doSign(messageHash); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - for (int i = 0; i < 4; i++) { - byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - public BigInteger keyAgreement(ECPoint otherParty) { - if (privKey == null) { - throw new MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) - privKey).getD(), CURVE)); - return agreement.calculateAgreement(new ECPublicKeyParameters - (otherParty, CURVE)); - } else { - try { - final KeyAgreement agreement = ECKeyAgreement.getInstance - (this.provider); - agreement.init(this.privKey); - agreement.doPhase( - ECKeyFactory.getInstance(this.provider) - .generatePublic(new ECPublicKeySpec - (otherParty, CURVE_SPEC)), - /* lastPhase */ true); - return new BigInteger(1, agreement.generateSecret()); - } catch (IllegalStateException | InvalidKeyException | - InvalidKeySpecException ex) { - throw new RuntimeException("ECDH key agreement failure", ex); - } - } - } - - /** - * Decrypt cipher by AES in SIC(also know as CTR) mode - * - * @param cipher -proper cipher - * @return decrypted cipher, equal length to the cipher. - * @deprecated should not use EC private scalar value as an AES key - */ - public byte[] decryptAES(byte[] cipher) { - - if (privKey == null) { - throw new MissingPrivateKeyException(); - } - if (!(privKey instanceof BCECPrivateKey)) { - throw new UnsupportedOperationException("Cannot use the private " + - "key as an AES key"); - } - - AESEngine engine = new AESEngine(); - SICBlockCipher ctrEngine = new SICBlockCipher(engine); - - KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( - (BCECPrivateKey) privKey).getD())); - ParametersWithIV params = new ParametersWithIV(key, new byte[16]); - - ctrEngine.init(false, params); - - int i = 0; - byte[] out = new byte[cipher.length]; - while (i < cipher.length) { - ctrEngine.processBlock(cipher, i, out, i); - i += engine.getBlockSize(); - if (cipher.length - i < engine.getBlockSize()) { - break; - } - } - - // process left bytes - if (cipher.length - i > 0) { - byte[] tmpBlock = new byte[16]; - System.arraycopy(cipher, i, tmpBlock, 0, cipher.length - i); - ctrEngine.processBlock(tmpBlock, 0, tmpBlock, 0); - System.arraycopy(tmpBlock, 0, out, i, cipher.length - i); - } - - return out; - } - - /** - * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @return - - */ - public boolean verify(byte[] data, byte[] signature) { - return ECKey.verify(data, signature, getPubKey()); - } - - /** - * Verifies the given R/S pair (signature) against a hash using the public key. - * - * @param sigHash - - * @param signature - - * @return - - */ - public boolean verify(byte[] sigHash, ECDSASignature signature) { - return ECKey.verify(sigHash, signature, getPubKey()); - } - - /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @return - - */ - public boolean isPubKeyCanonical() { - return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); - } - - /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only - * - * @return - - */ - @Nullable - public byte[] getPrivKeyBytes() { - if (privKey == null) { - return null; - } else if (privKey instanceof BCECPrivateKey) { - return ByteUtil.bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); - } else { - return null; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - ECKey ecKey = (ECKey) o; - - if (privKey != null && !privKey.equals(ecKey.privKey)) { - return false; - } - return pub == null || pub.equals(ecKey.pub); - } - - @Override - public int hashCode() { - return Arrays.hashCode(getPubKey()); - } - - public static class ECDSASignature implements SignatureInterface { - - /** - * The two components of the signature. - */ - public final BigInteger r, s; - public byte v; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. - * - * @param r - - * @param s - - */ - public ECDSASignature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - public ECDSASignature(byte[] r, byte[] s, byte v) { - this.r = new BigInteger(1, r); - this.s = new BigInteger(1, s); - this.v = v; - } - - /** - * t - * - * @return - - */ - private static ECDSASignature fromComponents(byte[] r, byte[] s) { - return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, - s)); - } - - /** - * @param r - - * @param s - - * @param v - - * @return - - */ - public static ECDSASignature fromComponents(byte[] r, byte[] s, byte - v) { - ECDSASignature signature = fromComponents(r, s); - signature.v = v; - return signature; - } - - public static boolean validateComponents(BigInteger r, BigInteger s, - byte v) { - - if (v != 27 && v != 28) { - return false; - } - - if (BIUtil.isLessThan(r, BigInteger.ONE)) { - return false; - } - if (BIUtil.isLessThan(s, BigInteger.ONE)) { - return false; - } - - if (!BIUtil.isLessThan(r, SECP256K1N)) { - return false; - } - return BIUtil.isLessThan(s, SECP256K1N); - } - - public static ECDSASignature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " + - "stream."); - } - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these - // values as unsigned, though they should not be - // Thus, we always use the positive versions. See: - // http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s - .getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) { - try { - decoder.close(); - } catch (IOException x) { - - } - } - } - } - - public boolean validateComponents() { - return validateComponents(r, s, v); - } - - public ECDSASignature toCanonicalised() { - if (s.compareTo(HALF_CURVE_ORDER) > 0) { - // The order of the curve is the number of valid points that - // exist on that curve. If S is in the upper - // half of the number of valid points, then bring it back to - // the lower half. Otherwise, imagine that - // N = 10 - // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) - // are valid solutions. - // 10 - 8 == 2, giving us always the latter solution, - // which is canonical. - return new ECDSASignature(r, CURVE.getN().subtract(s)); - } else { - return this; - } - } - - /** - * @return - - */ - public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 - // bytes for S - sigData[0] = v; - System.arraycopy(ByteUtil.bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); - System.arraycopy(ByteUtil.bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); - return new String(Base64.encode(sigData), Charset.forName("UTF-8")); - } - - - - public byte[] toByteArray() { - final byte fixedV = this.v >= 27 - ? (byte) (this.v - 27) - : this.v; - - return ByteUtil.merge( - ByteUtil.bigIntegerToBytes(this.r, 32), - ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); - } - - public String toHex() { - return Hex.toHexString(toByteArray()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ECDSASignature signature = (ECDSASignature) o; - - if (!r.equals(signature.r)) { - return false; - } - return s.equals(signature.s); - } - - @Override - public int hashCode() { - int result = r.hashCode(); - result = 31 * result + s.hashCode(); - return result; - } - } - - @SuppressWarnings("serial") - public static class MissingPrivateKeyException extends RuntimeException { - - } - -} +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.crypto; + +import org.spongycastle.asn1.ASN1InputStream; +import org.spongycastle.asn1.ASN1Integer; +import org.spongycastle.asn1.DLSequence; +import org.spongycastle.asn1.sec.SECNamedCurves; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.asn1.x9.X9IntegerConverter; +import org.spongycastle.crypto.agreement.ECDHBasicAgreement; +import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.engines.AESFastEngine; +import org.spongycastle.crypto.modes.SICBlockCipher; +import org.spongycastle.crypto.params.*; +import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.crypto.signers.HMacDSAKCalculator; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.util.BigIntegers; +import org.spongycastle.util.encoders.Base64; +import org.spongycastle.util.encoders.Hex; +import org.tron.common.crypto.jce.*; +import org.tron.common.utils.ByteUtil; + +import javax.annotation.Nullable; +import javax.crypto.KeyAgreement; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.*; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + +import static org.tron.common.utils.BIUtil.isLessThan; +import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; + +public class ECKey implements Serializable { + + /** + * The parameters of the secp256k1 curve. + */ + public static final ECDomainParameters CURVE; + public static final ECParameterSpec CURVE_SPEC; + /** + * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA + * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - + * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2

See + * https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki #Low_S_values_in_signatures + */ + public static final BigInteger HALF_CURVE_ORDER; + private static final BigInteger SECP256K1N = new BigInteger + ("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); + private static final SecureRandom secureRandom; + private static final long serialVersionUID = -728224901792295832L; + + static { + // All clients must agree on the curve to use by agreement. + X9ECParameters params = SECNamedCurves.getByName("secp256k1"); + CURVE = new ECDomainParameters(params.getCurve(), params.getG(), + params.getN(), params.getH()); + CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), + params.getN(), params.getH()); + HALF_CURVE_ORDER = params.getN().shiftRight(1); + secureRandom = new SecureRandom(); + } + + protected final ECPoint pub; + // The two parts of the key. If "priv" is set, "pub" can always be + // calculated. If "pub" is set but not "priv", we + // can only verify signatures not make them. + // TODO: Redesign this class to use consistent internals and more + // efficient serialization. + private final PrivateKey privKey; + // the Java Cryptographic Architecture provider to use for Signature + // this is set along with the PrivateKey privKey and must be compatible + // this provider will be used when selecting a Signature instance + // https://docs.oracle.com/javase/8/docs/technotes/guides/security + // /SunProviders.html + private final Provider provider; + + // Transient because it's calculated on demand. + transient private byte[] pubKeyHash; + transient private byte[] nodeId; + + /** + * Generates an entirely new keypair.

BouncyCastle will be used as the Java Security Provider + */ + public ECKey() { + this(secureRandom); + } + + /** + * Generate a new keypair using the given Java Security Provider.

All private key operations + * will use the provider. + */ + public ECKey(Provider provider, SecureRandom secureRandom) { + this.provider = provider; + + final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance + (provider, secureRandom); + final KeyPair keyPair = keyPairGen.generateKeyPair(); + + this.privKey = keyPair.getPrivate(); + + final PublicKey pubKey = keyPair.getPublic(); + if (pubKey instanceof BCECPublicKey) { + pub = ((BCECPublicKey) pubKey).getQ(); + } else if (pubKey instanceof ECPublicKey) { + pub = extractPublicKey((ECPublicKey) pubKey); + } else { + throw new AssertionError( + "Expected Provider " + provider.getName() + + " to produce a subtype of ECPublicKey, found " + + pubKey.getClass()); + } + } + + /** + * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle + * will be used as the Java Security Provider + * + * @param secureRandom - + */ + public ECKey(SecureRandom secureRandom) { + this(TronCastleProvider.getInstance(), secureRandom); + } + + /** + * Pair a private key with a public EC point.

All private key operations will use the + * provider. + */ + public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { + this.provider = provider; + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + /** + * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java + * Security Provider + */ + public ECKey(@Nullable BigInteger priv, ECPoint pub) { + this( + TronCastleProvider.getInstance(), + privateKeyFromBigInteger(priv), + pub + ); + } + + /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + */ + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return CURVE.getCurve().createPoint(xCoord, yCoord); + } + + /* Test if a generic private key is an EC private key + * + * it is not sufficient to check that privKey is a subtype of ECPrivateKey + * as the SunPKCS11 Provider will return a generic PrivateKey instance + * a fallback that covers this case is to check the key algorithm + */ + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm() + .equals("EC"); + } + + /* Convert a BigInteger into a PrivateKey object + */ + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + CURVE_SPEC)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param uncompressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param compressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return CURVE.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKey - + * @return - + */ + public static ECKey fromPrivate(BigInteger privKey) { + return new ECKey(privKey, CURVE.getG().multiply(privKey)); + } + + /** + * Creates an ECKey given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static ECKey fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, + ECPoint pub) { + return new ECKey(priv, pub); + } + + /** + * Creates an ECKey that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] + pub) { + check(priv != null, "Private key must not be null"); + check(pub != null, "Public key must not be null"); + return new ECKey(new BigInteger(1, priv), CURVE.getCurve() + .decodePoint(pub)); + } + + /** + * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given + * point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static ECKey fromPublicOnly(ECPoint pub) { + return new ECKey(null, pub); + } + + /** + * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given + * encoded point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static ECKey fromPublicOnly(byte[] pub) { + return new ECKey(null, CURVE.getCurve().decodePoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * use new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean + compressed) { + ECPoint point = CURVE.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute an address from an encoded public key. + * + * @param pubBytes an encoded (uncompressed) public key + * @return 21-byte address + */ + public static byte[] computeAddress(byte[] pubBytes) { + + return Hash.sha3omit12( + Arrays.copyOfRange(pubBytes, 1, pubBytes.length)); + } + + /** + * Compute an address from a public point. + * + * @param pubPoint a public point + * @return 21-byte address + */ + public static byte[] computeAddress(ECPoint pubPoint) { + return computeAddress(pubPoint.getEncoded(/* uncompressed */ false)); + } + + /** + * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key + * without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Recover the public key from an encoded node id. + * + * @param nodeId a 64-byte X,Y point pair + */ + public static ECKey fromNodeId(byte[] nodeId) { + check(nodeId.length == 64, "Expected a 64 byte node id"); + byte[] pubBytes = new byte[65]; + System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); + pubBytes[0] = 0x04; // uncompressed + return ECKey.fromPublicOnly(pubBytes); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, String + signatureBase64) throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you getData back from Bouncy Castle if base64 doesn't + // decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) { + throw new SignatureException("Signature truncated, expected 65 " + + "bytes and got " + signatureEncoded.length); + } + + return signatureToKeyBytes( + messageHash, + ECDSASignature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, + ECDSASignature sig) throws + SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + + messageHash.length); + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key + // with odd y, + // 0x1D = second key with even y, 0x1E = second key + // with odd y + if (header < 27 || header > 34) { + throw new SignatureException("Header byte out of range: " + header); + } + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, + messageHash); + if (key == null) { + throw new SignatureException("Could not recover public key from " + + "signature"); + } + return key; + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String + signatureBase64) throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, + signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, + ECDSASignature sig) throws + SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, sig)); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return ECKey + */ + public static ECKey signatureToKey(byte[] messageHash, String + signatureBase64) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, + signatureBase64); + return ECKey.fromPublicOnly(keyBytes); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return ECKey + */ + public static ECKey signatureToKey(byte[] messageHash, ECDSASignature + sig) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); + return ECKey.fromPublicOnly(keyBytes); + } + + /** + *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

+ *

When using native ECDSA verification, data must be 32 bytes, and no element may be + * larger than 520 bytes.

+ * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, ECDSASignature signature, + byte[] pub) { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE + .getCurve().decodePoint(pub), CURVE); + signer.init(false, params); + try { + return signer.verifySignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + System.out.println("Caught NPE inside bouncy castle" + npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, ECDSASignature.decodeFromDER(signature), pub); + } + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + return pubkey.length == 65; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + return pubkey.length == 33; + } else { + return false; + } + } + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

The + * recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the correct + * one. Because the key recovery operation yields multiple potential allKeys, the correct key must + * either be stored alongside the signature, or you must be willing to try each recId in turn + * until you find one that outputs the key you are expecting.

If this method returns + * null it means recovery was not possible and recId should be iterated.

Given the + * above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the + * output is null OR a key that is not the one you expect, you try again with the next recId.

+ * + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 65-byte encoded public key + */ + @Nullable + public static byte[] recoverPubBytesFromSignature(int recId, + ECDSASignature sig, + byte[] messageHash) { + check(recId >= 0, "recId must be positive"); + check(sig.r.signum() >= 0, "r must be positive"); + check(sig.s.signum() >= 0, "s must be positive"); + check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside + // this function) + // 1.1 Let x = r + jn + BigInteger n = CURVE.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen + // using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an + // elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this + // conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed + // public key. + ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent + // about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything + // takes place modulo Q. + return null; + } + // Compressed allKeys require you to know an extra bit of data about the + // y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of + // Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature + // verification. + BigInteger e = new BigInteger(1, messageHash); + // 1.6. For k from 1 to 2 do the following. (loop is outside this + // function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform + // this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that + // z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group + // operator). + // + // We can find the additive inverse by subtracting e from zero then + // taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod + // 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE + .getG(), eInvrInv, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 20-byte address + */ + @Nullable + public static byte[] recoverAddressFromSignature(int recId, + ECDSASignature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return computeAddress(pubBytes); + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return ECKey + */ + @Nullable + public static ECKey recoverFromSignature(int recId, ECDSASignature sig, + byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, + messageHash); + if (pubBytes == null) { + return null; + } else { + return ECKey.fromPublicOnly(pubBytes); + } + } + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE + .getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return CURVE.getCurve().decodePoint(compEnc); + } + + private static void check(boolean test, String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + * Returns a copy of this key, but with the public point represented in uncompressed form. + * Normally you would never need this: it's for specialised scenarios or when backwards + * compatibility in encoded form is necessary. + * + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey decompress() { + if (!pub.isCompressed()) { + return this; + } else { + return new ECKey(this.provider, this.privKey, decompressPoint(pub)); + } + } + + /** + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public ECKey compress() { + if (pub.isCompressed()) { + return this; + } else { + return new ECKey(this.provider, this.privKey, compressPoint(pub)); + } + } + + /** + * Returns true if this key doesn't have access to private key bytes. This may be because it was + * never given any private key bytes to begin with (a watching key). + * + * @return - + */ + public boolean isPubKeyOnly() { + return privKey == null; + } + + /** + * Returns true if this key has access to private key bytes. Does the opposite of {@link + * #isPubKeyOnly()}. + * + * @return - + */ + public boolean hasPrivKey() { + return privKey != null; + } + + /** + * Gets the address form of the public key. + * + * @return 20-byte address + */ + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = computeAddress(this.pub); + } + return pubKeyHash; + } + + /** + * Generates the NodeID based on this key, that is the public key without first format byte + */ + public byte[] getNodeId() { + if (nodeId == null) { + nodeId = pubBytesWithoutFormat(this.pub); + } + return nodeId; + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by + * performing EC point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new MissingPrivateKeyException(); + } + } + + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 + * bytes, not 64. + * + * @return - + */ + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need + * the private key it is better for security reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) + privKey).getD().toByteArray())); + } + return b.toString(); + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * ECDSASignature + * + * @param input to sign + * @return ECDSASignature signature that contains the R and S components + */ + public ECDSASignature doSign(byte[] input) { + if (input.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "ECDSA signature, not " + input.length); + } + // No decryption of private key required. + if (privKey == null) { + throw new MissingPrivateKeyException(); + } + if (privKey instanceof BCECPrivateKey) { + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new + SHA256Digest())); + ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters + (((BCECPrivateKey) privKey).getD(), CURVE); + signer.init(true, privKeyParams); + BigInteger[] components = signer.generateSignature(input); + return new ECDSASignature(components[0], components[1]) + .toCanonicalised(); + } else { + try { + final Signature ecSig = ECSignatureFactory.getRawInstance + (provider); + ecSig.initSign(privKey); + ecSig.update(input); + final byte[] derSignature = ecSig.sign(); + return ECDSASignature.decodeFromDER(derSignature) + .toCanonicalised(); + } catch (SignatureException | InvalidKeyException ex) { + throw new RuntimeException("ECKey signing error", ex); + } + } + } + + /** + * Takes the keccak hash (32 bytes) of data and returns the ECDSA signature + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public ECDSASignature sign(byte[] messageHash) { + ECDSASignature sig = doSign(messageHash); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = ECKey.recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + public BigInteger keyAgreement(ECPoint otherParty) { + if (privKey == null) { + throw new MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); + agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) + privKey).getD(), CURVE)); + return agreement.calculateAgreement(new ECPublicKeyParameters + (otherParty, CURVE)); + } else { + try { + final KeyAgreement agreement = ECKeyAgreement.getInstance + (this.provider); + agreement.init(this.privKey); + agreement.doPhase( + ECKeyFactory.getInstance(this.provider) + .generatePublic(new ECPublicKeySpec + (otherParty, CURVE_SPEC)), + /* lastPhase */ true); + return new BigInteger(1, agreement.generateSecret()); + } catch (IllegalStateException | InvalidKeyException | + InvalidKeySpecException ex) { + throw new RuntimeException("ECDH key agreement failure", ex); + } + } + } + + /** + * Decrypt cipher by AES in SIC(also know as CTR) mode + * + * @param cipher -proper cipher + * @return decrypted cipher, equal length to the cipher. + * @deprecated should not use EC private scalar value as an AES key + */ + public byte[] decryptAES(byte[] cipher) { + + if (privKey == null) { + throw new MissingPrivateKeyException(); + } + if (!(privKey instanceof BCECPrivateKey)) { + throw new UnsupportedOperationException("Cannot use the private " + + "key as an AES key"); + } + + AESFastEngine engine = new AESFastEngine(); + SICBlockCipher ctrEngine = new SICBlockCipher(engine); + + KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( + (BCECPrivateKey) privKey).getD())); + ParametersWithIV params = new ParametersWithIV(key, new byte[16]); + + ctrEngine.init(false, params); + + int i = 0; + byte[] out = new byte[cipher.length]; + while (i < cipher.length) { + ctrEngine.processBlock(cipher, i, out, i); + i += engine.getBlockSize(); + if (cipher.length - i < engine.getBlockSize()) { + break; + } + } + + // process left bytes + if (cipher.length - i > 0) { + byte[] tmpBlock = new byte[16]; + System.arraycopy(cipher, i, tmpBlock, 0, cipher.length - i); + ctrEngine.processBlock(tmpBlock, 0, tmpBlock, 0); + System.arraycopy(tmpBlock, 0, out, i, cipher.length - i); + } + + return out; + } + + /** + * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return ECKey.verify(data, signature, getPubKey()); + } + + /** + * Verifies the given R/S pair (signature) against a hash using the public key. + * + * @param sigHash - + * @param signature - + * @return - + */ + public boolean verify(byte[] sigHash, ECDSASignature signature) { + return ECKey.verify(sigHash, signature, getPubKey()); + } + + /** + * Returns true if this pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @return - + */ + public boolean isPubKeyCanonical() { + return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public + * only + * + * @return - + */ + @Nullable + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof ECKey)) { + return false; + } + + ECKey ecKey = (ECKey) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) { + return false; + } + return pub == null || pub.equals(ecKey.pub); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + public static class ECDSASignature { + + /** + * The two components of the signature. + */ + public final BigInteger r, s; + public byte v; + + /** + * Constructs a signature with the given components. Does NOT automatically canonicalise the + * signature. + * + * @param r - + * @param s - + */ + public ECDSASignature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; + } + + /** + * t + * + * @return - + */ + private static ECDSASignature fromComponents(byte[] r, byte[] s) { + return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, + s)); + } + + /** + * @param r - + * @param s - + * @param v - + * @return - + */ + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte + v) { + ECDSASignature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public static boolean validateComponents(BigInteger r, BigInteger s, + byte v) { + + if (v != 27 && v != 28) { + return false; + } + + if (isLessThan(r, BigInteger.ONE)) { + return false; + } + if (isLessThan(s, BigInteger.ONE)) { + return false; + } + + if (!isLessThan(r, SECP256K1N)) { + return false; + } + return isLessThan(s, SECP256K1N); + } + + public static ECDSASignature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) { + throw new RuntimeException("Reached past end of ASN.1 " + + "stream."); + } + ASN1Integer r, s; + try { + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); + } + // OpenSSL deviates from the DER spec by interpreting these + // values as unsigned, though they should not be + // Thus, we always use the positive versions. See: + // http://r6.ca/blog/20111119T211504Z.html + return new ECDSASignature(r.getPositiveValue(), s + .getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) { + try { + decoder.close(); + } catch (IOException x) { + } + } + } + } + + public boolean validateComponents() { + return validateComponents(r, s, v); + } + + public ECDSASignature toCanonicalised() { + if (s.compareTo(HALF_CURVE_ORDER) > 0) { + // The order of the curve is the number of valid points that + // exist on that curve. If S is in the upper + // half of the number of valid points, then bring it back to + // the lower half. Otherwise, imagine that + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) + // are valid solutions. + // 10 - 8 == 2, giving us always the latter solution, + // which is canonical. + return new ECDSASignature(r, CURVE.getN().subtract(s)); + } else { + return this; + } + } + + /** + * @return - + */ + public String toBase64() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + // bytes for S + sigData[0] = v; + System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + } + + public byte[] toByteArray() { + final byte fixedV = this.v >= 27 + ? (byte) (this.v - 27) + : this.v; + + return ByteUtil.merge( + ByteUtil.bigIntegerToBytes(this.r, 32), + ByteUtil.bigIntegerToBytes(this.s, 32), + new byte[]{fixedV}); + } + + public String toHex() { + return Hex.toHexString(toByteArray()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ECDSASignature signature = (ECDSASignature) o; + + if (!r.equals(signature.r)) { + return false; + } + return s.equals(signature.s); + } + + @Override + public int hashCode() { + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; + } + } + + @SuppressWarnings("serial") + public static class MissingPrivateKeyException extends RuntimeException { + + } + +} diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java index a41b382e8..02e0c3e09 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Hash.java @@ -42,7 +42,6 @@ */ public class Sha256Hash implements Serializable, Comparable { - public static final int LENGTH = 32; // bytes public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); diff --git a/src/main/java/org/tron/common/crypto/SignInterface.java b/src/main/java/org/tron/common/crypto/SignInterface.java deleted file mode 100644 index b158c50b7..000000000 --- a/src/main/java/org/tron/common/crypto/SignInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.tron.common.crypto; - -import java.security.SignatureException; - -public interface SignInterface { - - byte[] hash(byte[] message); - - byte[] getPrivateKey(); - - byte[] getPubKey(); - - byte[] getAddress(); - - String signHash(byte[] hash); - - byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException; - - byte[] getNodeId(); - - byte[] Base64toBytes (String signature); - - byte[] getPrivKeyBytes(); - - SignatureInterface sign(byte[] hash); -} diff --git a/src/main/java/org/tron/common/crypto/SignUtils.java b/src/main/java/org/tron/common/crypto/SignUtils.java deleted file mode 100644 index 710ecda81..000000000 --- a/src/main/java/org/tron/common/crypto/SignUtils.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.tron.common.crypto; - -import java.security.SecureRandom; -import java.security.SignatureException; -import org.tron.common.crypto.ECKey.ECDSASignature; -import org.tron.common.crypto.sm2.SM2; -import org.tron.common.crypto.sm2.SM2.SM2Signature; - -public class SignUtils { - public static SignInterface getGeneratedRandomSign(boolean isECKeyCryptoEngine) { - if (isECKeyCryptoEngine) { - return new ECKey(); - } - return new SM2(); - } - - public static SignInterface getGeneratedRandomSign( - SecureRandom secureRandom, boolean isECKeyCryptoEngine) { - if (isECKeyCryptoEngine) { - return new ECKey(secureRandom); - } - return new SM2(secureRandom); - } - - public static SignInterface fromPrivate(byte[] privKeyBytes, boolean isECKeyCryptoEngine) { - if (isECKeyCryptoEngine) { - return ECKey.fromPrivate(privKeyBytes); - } - return SM2.fromPrivate(privKeyBytes); - } - - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64, boolean isECKeyCryptoEngine) throws SignatureException { - if (isECKeyCryptoEngine) { - return ECKey.signatureToAddress(messageHash, signatureBase64); - } - return SM2.signatureToAddress(messageHash, signatureBase64); - } - - public static SignatureInterface fromComponents(byte[] r, byte[] s, byte v, boolean isECKeyCryptoEngine) { - if (isECKeyCryptoEngine) { - return ECKey.ECDSASignature.fromComponents(r, s, v); - } - return SM2.SM2Signature.fromComponents(r, s, v); - } - - public static byte[] signatureToAddress(byte[] messageHash, SignatureInterface signatureInterface, - boolean isECKeyCryptoEngine) throws SignatureException{ - if (isECKeyCryptoEngine) { - return ECKey.signatureToAddress(messageHash, (ECDSASignature)signatureInterface); - } - return SM2.signatureToAddress(messageHash, (SM2Signature)signatureInterface); - } -} diff --git a/src/main/java/org/tron/common/crypto/SignatureInterface.java b/src/main/java/org/tron/common/crypto/SignatureInterface.java deleted file mode 100644 index 451c18449..000000000 --- a/src/main/java/org/tron/common/crypto/SignatureInterface.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.tron.common.crypto; - -public interface SignatureInterface { - boolean validateComponents(); - - byte[] toByteArray(); -} \ No newline at end of file diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2.java b/src/main/java/org/tron/common/crypto/sm2/SM2.java deleted file mode 100644 index 1236dd65c..000000000 --- a/src/main/java/org/tron/common/crypto/sm2/SM2.java +++ /dev/null @@ -1,1245 +0,0 @@ -package org.tron.common.crypto.sm2; - -import lombok.extern.slf4j.Slf4j; -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.crypto.generators.ECKeyPairGenerator; -import org.spongycastle.crypto.params.*; -import org.spongycastle.crypto.signers.*; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.math.ec.*; -import org.spongycastle.util.encoders.Base64; -import org.spongycastle.util.encoders.Hex; -import org.spongycastle.util.test.TestRandomBigInteger; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.jce.ECKeyFactory; -import org.tron.common.crypto.jce.ECSignatureFactory; -import org.tron.common.crypto.jce.TronCastleProvider; -import org.tron.common.utils.ByteUtil; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.security.*; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; - -import static org.tron.common.utils.BIUtil.isLessThan; -import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; -import static org.tron.common.utils.Hash.computeAddress; - -/** - * Implement Chinese Commercial Cryptographic Standard of SM2 - * - */ -@Slf4j(topic = "crypto") -public class SM2 implements Serializable, SignInterface { - private static BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); - private static BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); - private static BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); - private static BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); - private static BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); - private static BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); -// -// private static BigInteger SM2_N = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16); -// private static BigInteger SM2_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16); -// private static BigInteger SM2_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16); -// private static BigInteger SM2_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16); -// private static BigInteger SM2_GX = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16); -// private static BigInteger SM2_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16); - - private static ECDomainParameters ecc_param; - private static ECParameterSpec ecc_spec; - private static ECCurve.Fp curve; - private static ECPoint ecc_point_g; - - private static final SecureRandom secureRandom; - - - - static { - secureRandom = new SecureRandom(); - curve = new ECCurve.Fp(SM2_P, SM2_A, SM2_B); - ecc_point_g = curve.createPoint(SM2_GX, SM2_GY); - ecc_param = new ECDomainParameters(curve, ecc_point_g, SM2_N); - ecc_spec = new ECParameterSpec(curve, ecc_point_g, SM2_N); - } - - protected final ECPoint pub; - - private final PrivateKey privKey; - -// private final SM2KeyPair keyPair; - - // Transient because it's calculated on demand. - private transient byte[] pubKeyHash; - private transient byte[] nodeId; - -// private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); -// private byte[] userID; - - public SM2() { - this(secureRandom); - } - /** - * Generates an entirely new keypair. - * - *

BouncyCastle will be used as the Java Security Provider - */ - - - /** - * Generate a new keypair using the given Java Security Provider. - * - *

All private key operations will use the provider. - */ - public SM2(SecureRandom secureRandom) { - - ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecc_param, secureRandom); - ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); - keyPairGenerator.init(ecKeyGenerationParameters); - AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair(); - ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) kp.getPrivate(); - ECPublicKeyParameters ecpub = (ECPublicKeyParameters) kp.getPublic(); - - BigInteger privateKey = ecpriv.getD(); - this.privKey = privateKeyFromBigInteger(privateKey); - this.pub = ecpub.getQ(); -// this.keyPair = new SM2KeyPair(pub.getEncoded(false),privateKey.toByteArray()); - -// CipherParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey, ecc_param); -// CipherParameters baseParam; -// -// if (privateKeyParameters instanceof ParametersWithID) -// { -// baseParam = ((ParametersWithID)privateKeyParameters).getParameters(); -// userID = ((ParametersWithID)privateKeyParameters).getID(); -// } -// else -// { -// baseParam = privateKeyParameters; -// userID = new byte[0]; -// } -// this.kCalculator.init(SM2_N, secureRandom); - } - - public SM2 (byte[] key, boolean isPrivateKey) { - if (isPrivateKey) { - BigInteger pk = new BigInteger(1, key); - this.privKey = privateKeyFromBigInteger(pk); - this.pub = ecc_param.getG().multiply(pk); - } else { - this.privKey = null; - this.pub = ecc_param.getCurve().decodePoint(key); - } - } - - - /** - * Pair a private key with a public EC point. - * - *

All private key operations will use the provider. - */ - - public SM2(@Nullable PrivateKey privKey, ECPoint pub) { - - if (privKey == null || isECPrivateKey(privKey)) { - this.privKey = privKey; - } else { - throw new IllegalArgumentException( - "Expected EC private key, given a private key object with" + - " class " - + privKey.getClass().toString() + - " and algorithm " - + privKey.getAlgorithm()); - } - - if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); - } else { - this.pub = pub; - } - } - - /** - * Pair a private key integer with a public EC point - * - */ - public SM2(@Nullable BigInteger priv, ECPoint pub) { - this( - privateKeyFromBigInteger(priv), - pub - ); - -// this.privKey = privateKeyFromBigInteger(priv); -// this.pub = pub; -// this.keyPair = new SM2KeyPair(pub.getEncoded(false), priv.toByteArray()); - } - - /** - * Convert a BigInteger into a PrivateKey object - * - * @param priv - * @return - */ - private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - ecc_spec)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } - } - } - - /* Test if a generic private key is an EC private key - * - * it is not sufficient to check that privKey is a subtype of ECPrivateKey - * as the SunPKCS11 Provider will return a generic PrivateKey instance - * a fallback that covers this case is to check the key algorithm - */ - private static boolean isECPrivateKey(PrivateKey privKey) { - return privKey instanceof ECPrivateKey || privKey.getAlgorithm() - .equals("EC"); - } - - /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint - */ - private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { - final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); - final BigInteger xCoord = publicPointW.getAffineX(); - final BigInteger yCoord = publicPointW.getAffineY(); - - return ecc_param.getCurve().createPoint(xCoord, yCoord); - } - - - /** - * Utility for compressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param uncompressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint compressPoint(ECPoint uncompressed) { - return ecc_param.getCurve().decodePoint(uncompressed.getEncoded(true)); - } - - /** - * Utility for decompressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param compressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint decompressPoint(ECPoint compressed) { - return ecc_param.getCurve().decodePoint(compressed.getEncoded(false)); - } - - /** - * Creates an SM2 given the private key only. - * - * @param privKey - - * @return - - */ - public static SM2 fromPrivate(BigInteger privKey) { - return new SM2(privKey, ecc_param.getG().multiply(privKey)); - } - - /** - * Creates an SM2 given the private key only. - * - * @param privKeyBytes - - * @return - - */ - public static SM2 fromPrivate(byte[] privKeyBytes) { - return fromPrivate(new BigInteger(1, privKeyBytes)); - } - - /** - * Creates an SM2 that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of pub will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static SM2 fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { - return new SM2(priv, pub); - } - - /** - * Creates an SM2 that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of the point will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static SM2 fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] - pub) { - check(priv != null, "Private key must not be null"); - check(pub != null, "Public key must not be null"); - return new SM2(new BigInteger(1, priv), ecc_param.getCurve() - .decodePoint(pub)); - } - - /** - * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static SM2 fromPublicOnly(ECPoint pub) { - return new SM2((PrivateKey) null, pub); - } - - /** - * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given - * encoded point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static SM2 fromPublicOnly(byte[] pub) { - return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); - } - - /** - * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, - * use new BigInteger(1, bytes); - * - * @param privKey - - * @param compressed - - * @return - - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = ecc_param.getG().multiply(privKey); - return point.getEncoded(compressed); - } - - /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. - * - * @param pubPoint a public point - * @return 64-byte X,Y point pair - */ - public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { - final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); - return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); - } - - /** - * Recover the public key from an encoded node id. - * - * @param nodeId a 64-byte X,Y point pair - */ - public static SM2 fromNodeId(byte[] nodeId) { - check(nodeId.length == 64, "Expected a 64 byte node id"); - byte[] pubBytes = new byte[65]; - System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); - pubBytes[0] = 0x04; // uncompressed - return SM2.fromPublicOnly(pubBytes); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { - byte[] signatureEncoded; - try { - signatureEncoded = Base64.decode(signatureBase64); - } catch (RuntimeException e) { - // This is what you getData back from Bouncy Castle if base64 doesn't - // decode :( - throw new SignatureException("Could not decode base64", e); - } - // Parse the signature bytes into r/s and the selector value. - if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); - } - - return signatureToKeyBytes( - messageHash, - SM2Signature.fromComponents( - Arrays.copyOfRange(signatureEncoded, 1, 33), - Arrays.copyOfRange(signatureEncoded, 33, 65), - (byte) (signatureEncoded[0] & 0xFF))); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, - SM2Signature sig) throws - SignatureException { - check(messageHash.length == 32, "messageHash argument has length " + - messageHash.length); - int header = sig.v; - // The header byte: 0x1B = first key with even y, 0x1C = first key - // with odd y, - // 0x1D = second key with even y, 0x1E = second key - // with odd y - if (header < 27 || header > 34) { - throw new SignatureException("Header byte out of range: " + header); - } - if (header >= 31) { - header -= 4; - } - int recId = header - 27; - byte[] key = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (key == null) { - throw new SignatureException("Could not recover public key from " + - "signature"); - } - return key; - } - - - @Override - public byte[] hash(byte[] message) { - SM2Signer signer = this.getSM2SignerForHash(); - return signer.generateSM3Hash(message); - } - - @Override - public byte[] getPrivateKey() { - return getPrivKeyBytes(); - } - - /** - * Gets the encoded public key value. - * - * @return 65-byte encoded public key - */ - @Override - public byte[] getPubKey() { - return pub.getEncoded(/* compressed */ false); - } - - /** - * Gets the address form of the public key. - * - * @return 21-byte address - */ - @Override - public byte[] getAddress() { - if (pubKeyHash == null) { - pubKeyHash = computeAddress(this.pub); - } - return pubKeyHash; - } - - @Override - public byte[] signToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, - SM2Signature sig) throws - SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, sig)); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return ECKey - */ - public static SM2 signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, - signatureBase64); - return fromPublicOnly(keyBytes); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return ECKey - */ - public static SM2 signatureToKey(byte[] messageHash, SM2Signature - sig) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); - return fromPublicOnly(keyBytes); - } - - /** - * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which including the v - * - * @param messageHash - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public SM2Signature sign(byte[] messageHash) { - if (messageHash.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "SM2 signature, not " + messageHash.length); - } - // No decryption of private key required. - SM2Signer signer = getSigner(); - BigInteger[] componets = signer.generateHashSignature(messageHash); - - SM2Signature sig = new SM2.SM2Signature(componets[0], componets[1]); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - for (int i = 0; i < 4; i++) { - byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * SM2Signature - * - * @param input to sign - * @return SM2Signature signature that contains the R and S components - */ - public String signHash(byte[] input) { - return sign(input).toBase64(); - } - - public byte[] Base64toBytes (String signature) { - byte[] signData = Base64.decode(signature); - byte first = (byte)(signData[0] - 27); - byte[] temp = Arrays.copyOfRange(signData,1,65); - return ByteUtil.appendByte(temp,first); - } - - /** - * Takes the message of data and returns the SM2 signature - * - * @param message - - * @param userID - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public SM2Signature signMessage(byte[] message, @Nullable String userID) { - SM2Signature sig = signMsg(message, userID); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - - SM2Signer signer = getSigner(); - byte[] messageHash = signer.generateSM3Hash(message); - for (int i = 0; i < 4; i++) { - byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * SM2Signature - * - * @param msg to sign - * @param userID - * @return SM2Signature signature that contains the R and S components - */ - public SM2.SM2Signature signMsg(byte[] msg,@Nullable String userID) { - if (null == msg) { - throw new IllegalArgumentException("Expected signature message of " + - "SM2 is null"); - } - // No decryption of private key required. - SM2Signer signer = getSigner(); - BigInteger[] componets = signer.generateSignature(msg); - return new SM2.SM2Signature(componets[0], componets[1]); - } - - private SM2Signer getSigner() { - SM2Signer signer = new SM2Signer(); - BigInteger d = getPrivKey(); - ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); -// ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); - signer.init(true, privateKeyParameters); - return signer; - } - - /** - * used to generate the SM3 hash for SM2 signature generation or verification - * - * @return - */ - public SM2Signer getSM2SignerForHash() { - SM2Signer signer = new SM2Signer(); - //BigInteger d = getPrivKey(); - //ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); - ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); - signer.init(false, publicKeyParameters); - return signer; - } - - - /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature - * - * @param recId - * @param sig - * @param messageHash - * @return - */ - @Nullable - public static byte[] recoverPubBytesFromSignature(int recId, - SM2Signature sig, - byte[] messageHash) { - check(recId >= 0, "recId must be positive"); - check(sig.r.signum() >= 0, "r must be positive"); - check(sig.s.signum() >= 0, "s must be positive"); - check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside - // this function) - // 1.1 Let x = r + jn - BigInteger n = ecc_param.getN(); // Curve order. - BigInteger prime = curve.getQ(); - BigInteger i = BigInteger.valueOf((long) recId / 2); - - BigInteger e = new BigInteger(1, messageHash); - BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n - x = x.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen - // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or - // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an - // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this - // conversion routine outputs “invalid”, then - // do another iteration of Step 1. - // - // More concisely, what these points mean is to use X as a compressed - // public key. - ECCurve.Fp curve = (ECCurve.Fp) ecc_param.getCurve(); - // Bouncy Castle is not consistent - // about the letter it uses for the prime. - if (x.compareTo(prime) >= 0) { - // Cannot have point co-ordinates larger than this as everything - // takes place modulo Q. - return null; - } - // Compressed allKeys require you to know an extra bit of data about the - // y-coord as there are two possibilities. - // So it's encoded in the recId. - ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of - // Step 1 (callers responsibility). - if (!R.multiply(n).isInfinity()) { - return null; - } - - // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) - BigInteger srInv = sig.s.add(sig.r).modInverse(n); - BigInteger sNeg = BigInteger.ZERO.subtract(sig.s).mod(n); - BigInteger coeff = srInv.multiply(sNeg).mod(n); - - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(ecc_param - .getG(), coeff, R, srInv); - return q.getEncoded(/* compressed */ false); - } - - /** - * Decompress a compressed public key (x co-ord and low-bit of y-coord). - * - * @param xBN - - * @param yBit - - * @return - - */ - - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { - X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(ecc_param - .getCurve())); - compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return ecc_param.getCurve().decodePoint(compEnc); - } - - private static void check(boolean test, String message) { - if (!test) { - throw new IllegalArgumentException(message); - } - } - - /** - *

Verifies the given SM2 signature against the message bytes using the public key bytes.

- *

When using native SM2 verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

- * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, SM2Signature signature, - byte[] pub) { - SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub),ecc_param); - signer.init(false, params); - try { - return signer.verifyHashSignature(data, signature.r, signature.s); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - logger.error("Caught NPE inside bouncy castle", npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, byte[] signature, byte[] pub) { - return verify(data, SM2Signature.decodeFromDER(signature), pub); - } - - /** - *

Verifies the given SM2 signature against the message bytes using the public key bytes. - * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verifyMessage(byte[] msg, SM2Signature signature, - byte[] pub, @Nullable String userID) { - SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub),ecc_param); - signer.init(false, params); - try { - return signer.verifySignature(msg, signature.r, signature.s, userID); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - logger.error("Caught NPE inside bouncy castle", npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verifyMessage(byte[] msg, byte[] signature, byte[] pub, @Nullable String userID) { - return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); - } - - - /** - * Returns true if the given pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @param pubkey - - * @return - - */ - public static boolean isPubKeyCanonical(byte[] pubkey) { - if (pubkey[0] == 0x04) { - // Uncompressed pubkey - return pubkey.length == 65; - } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { - // Compressed pubkey - return pubkey.length == 33; - } else { - return false; - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 20-byte address - */ - @Nullable - public static byte[] recoverAddressFromSignature(int recId, - SM2Signature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return computeAddress(pubBytes); - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return ECKey - */ - @Nullable - public static SM2 recoverFromSignature(int recId, SM2Signature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return fromPublicOnly(pubBytes); - } - } - - - - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public SM2 decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new SM2(this.privKey, decompressPoint(pub)); - } - } - - /** - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public SM2 compress() { - if (pub.isCompressed()) { - return this; - } else { - return new SM2(this.privKey, compressPoint(pub)); - } - } - - /** - * Returns true if this key doesn't have access to private key bytes. This may be because it was - * never given any private key bytes to begin with (a watching key). - * - * @return - - */ - public boolean isPubKeyOnly() { - return privKey == null; - } - - /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. - * - * @return - - */ - public boolean hasPrivKey() { - return privKey != null; - } - -// /** -// * Gets the address form of the public key. -// * -// * @return 21-byte address -// */ -// public byte[] getAddress() { -// if (pubKeyHash == null) { -// pubKeyHash = computeAddress(this.pub); -// } -// return pubKeyHash; -// } - - /** - * Generates the NodeID based on this key, that is the public key without first format byte - */ - public byte[] getNodeId() { - if (nodeId == null) { - nodeId = pubBytesWithoutFormat(this.pub); - } - return nodeId; - } - -// /** -// * Gets the encoded public key value. -// * -// * @return 65-byte encoded public key -// */ -// public byte[] getPubKey() { -// return pub.getEncoded(/* compressed */ false); -// } - - /** - * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. - * - * @return - - */ - public ECPoint getPubKeyPoint() { - return pub; - } - - /** - * Gets the private key in the form of an integer field element. The public key is derived by - * performing EC point addition this number of times (i.e. point multiplying). - * - * @return - - * @throws IllegalStateException if the private key bytes are not available. - */ - public BigInteger getPrivKey() { - if (privKey == null) { - throw new ECKey.MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - return ((BCECPrivateKey) privKey).getD(); - } else { - throw new ECKey.MissingPrivateKeyException(); - } - } - - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - return b.toString(); - } - - /** - * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need - * the private key it is better for security reasons to just use toString(). - * - * @return - - */ - public String toStringWithPrivate() { - StringBuilder b = new StringBuilder(); - b.append(toString()); - if (privKey != null && privKey instanceof BCECPrivateKey) { - b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) - privKey).getD().toByteArray())); - } - return b.toString(); - } - - - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @return - - */ - public boolean verify(byte[] data, byte[] signature) { - return SM2.verify(data, signature, getPubKey()); - } - - /** - * Verifies the given R/S pair (signature) against a hash using the public key. - * - * @param sigHash - - * @param signature - - * @return - - */ - public boolean verify(byte[] sigHash, SM2Signature signature) { - return SM2.verify(sigHash, signature, getPubKey()); - } - - /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @return - - */ - public boolean isPubKeyCanonical() { - return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); - } - - /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only - * - * @return - - */ - @Nullable - public byte[] getPrivKeyBytes() { - if (privKey == null) { - return null; - } else if (privKey instanceof BCECPrivateKey) { - return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); - } else { - return null; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - SM2 ecKey = (SM2) o; - - if (privKey != null && !privKey.equals(ecKey.privKey)) { - return false; - } - return pub == null || pub.equals(ecKey.pub); - } - - @Override - public int hashCode() { - return Arrays.hashCode(getPubKey()); - } - - - public static class SM2Signature implements SignatureInterface { - - /** - * The two components of the signature. - */ - public final BigInteger r, s; - public byte v; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. - * - * @param r - - * @param s - - */ - public SM2Signature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - public SM2Signature(byte[] r, byte[] s, byte v) { - this.r = new BigInteger(1, r); - this.s = new BigInteger(1,s); - this.v = v; - } - - /** - * t - * - * @return - - */ - private static SM2.SM2Signature fromComponents(byte[] r, byte[] s) { - return new SM2.SM2Signature(new BigInteger(1, r), new BigInteger(1, - s)); - } - - /** - * @param r - - * @param s - - * @param v - - * @return - - */ - public static SM2.SM2Signature fromComponents(byte[] r, byte[] s, byte - v) { - SM2.SM2Signature signature = fromComponents(r, s); - signature.v = v; - return signature; - } - - public static boolean validateComponents(BigInteger r, BigInteger s, - byte v) { - - if (v != 27 && v != 28) { - return false; - } - - if (isLessThan(r, BigInteger.ONE)) { - return false; - } - if (isLessThan(s, BigInteger.ONE)) { - return false; - } - - if (!isLessThan(r, SM2.SM2_N)) { - return false; - } - return isLessThan(s, SM2.SM2_N); - } - - public static SM2.SM2Signature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " + - "stream."); - } - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these - // values as unsigned, though they should not be - // Thus, we always use the positive versions. See: - // http://r6.ca/blog/20111119T211504Z.html - return new SM2.SM2Signature(r.getPositiveValue(), s - .getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) { - try { - decoder.close(); - } catch (IOException x) { - - } - } - } - } - - public boolean validateComponents() { - return validateComponents(r, s, v); - } - - - /** - * @return - - */ - public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 - // bytes for S - sigData[0] = v; - System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); - System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); - return new String(Base64.encode(sigData), Charset.forName("UTF-8")); - } - - - - public byte[] toByteArray() { - final byte fixedV = this.v >= 27 - ? (byte) (this.v - 27) - : this.v; - - return ByteUtil.merge( - ByteUtil.bigIntegerToBytes(this.r, 32), - ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); - } - - public String toHex() { - return Hex.toHexString(toByteArray()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - SM2.SM2Signature signature = (SM2.SM2Signature) o; - - if (!r.equals(signature.r)) { - return false; - } - return s.equals(signature.s); - } - - @Override - public int hashCode() { - int result = r.hashCode(); - result = 31 * result + s.hashCode(); - return result; - } - } - -} diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java deleted file mode 100644 index 9dd689013..000000000 --- a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java +++ /dev/null @@ -1,323 +0,0 @@ -package org.tron.common.crypto.sm2; - -import java.math.BigInteger; -import java.security.SecureRandom; - -import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.DSA; -import org.spongycastle.crypto.Digest; -import org.spongycastle.crypto.digests.SM3Digest; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECKeyParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.crypto.params.ParametersWithID; -import org.spongycastle.crypto.params.ParametersWithRandom; -import org.spongycastle.crypto.signers.DSAKCalculator; -import org.spongycastle.crypto.signers.RandomDSAKCalculator; -import org.spongycastle.math.ec.ECConstants; -import org.spongycastle.math.ec.ECFieldElement; -import org.spongycastle.math.ec.ECMultiplier; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.math.ec.FixedPointCombMultiplier; -import org.spongycastle.util.BigIntegers; -import org.tron.common.crypto.SignInterface; - -import javax.annotation.Nullable; - -public class SM2Signer - implements ECConstants -{ - private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); - - private byte[] userID; - - private int curveLength; - private ECDomainParameters ecParams; - private ECPoint pubPoint; - private ECKeyParameters ecKey; - - private SecureRandom random; - - public void init(boolean forSigning, CipherParameters param) - { - CipherParameters baseParam; - - if (param instanceof ParametersWithID) - { - baseParam = ((ParametersWithID)param).getParameters(); - userID = ((ParametersWithID)param).getID(); - } - else - { - baseParam = param; - userID = new byte[0]; - } - - if (forSigning) - { - if (baseParam instanceof ParametersWithRandom) - { - ParametersWithRandom rParam = (ParametersWithRandom)baseParam; - - ecKey = (ECKeyParameters)rParam.getParameters(); - ecParams = ecKey.getParameters(); - kCalculator.init(ecParams.getN(), rParam.getRandom()); - } - else - { - ecKey = (ECKeyParameters)baseParam; - ecParams = ecKey.getParameters(); - kCalculator.init(ecParams.getN(), new SecureRandom()); - } - pubPoint = ecParams.getG().multiply(((ECPrivateKeyParameters)ecKey).getD()).normalize(); - } - else - { - ecKey = (ECKeyParameters)baseParam; - ecParams = ecKey.getParameters(); - pubPoint = ((ECPublicKeyParameters)ecKey).getQ(); - } - - curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; - } - - - /** - * generate the signature for the message - * - * @param message plaintext - * @return - */ - public BigInteger[] generateSignature(byte[] message) - { - byte[] eHash = generateSM3Hash(message); - return generateHashSignature(eHash); - } - /** - * generate the signature for the message - * - * @param message - * @return - */ - - public byte[] generateSM3Hash(byte[] message) - { - //byte[] msg = message.getBytes(); - - SM3Digest digest = new SM3Digest(); - byte[] z = getZ(digest); - - digest.update(z, 0, z.length); - digest.update(message, 0, message.length); - - byte[] eHash = new byte[digest.getDigestSize()]; - - digest.doFinal(eHash, 0); - return eHash; - } - - /** - * generate the signature from the 32 byte hash - * - * @param hash - * @return - */ - public BigInteger[] generateHashSignature(byte[] hash) - { - if (hash.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + hash.length); - } - BigInteger n = ecParams.getN(); - BigInteger e = calculateE(hash); - BigInteger d = ((ECPrivateKeyParameters)ecKey).getD(); - - BigInteger r, s; - - ECMultiplier basePointMultiplier = createBasePointMultiplier(); - - // 5.2.1 Draft RFC: SM2 Public Key Algorithms - do // generate s - { - BigInteger k; - do // generate r - { - // A3 - k = kCalculator.nextK(); - // A4 - ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); - - // A5 - r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); - } - while (r.equals(ZERO) || r.add(k).equals(n)); - - // A6 - BigInteger dPlus1ModN = d.add(ONE).modInverse(n); - - s = k.subtract(r.multiply(d)).mod(n); - s = dPlus1ModN.multiply(s).mod(n); - } - while (s.equals(ZERO)); - - // A7 - return new BigInteger[]{ r, s }; - } - - /** - * verify the message signature - * - * @param message - * @param r - * @param s - * @return - */ - public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, @Nullable String userID) - { - BigInteger n = ecParams.getN(); - - // 5.3.1 Draft RFC: SM2 Public Key Algorithms - // B1 - if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) - { - return false; - } - - // B2 - if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) - { - return false; - } - - ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); - -// SM3Digest digest = new SM3Digest(); -// -// byte[] z = getZ(digest); -// -// digest.update(z, 0, z.length); -// digest.update(message, 0, message.length); -// -// byte[] eHash = new byte[digest.getDigestSize()]; -// -// // B3 -// digest.doFinal(eHash, 0); - if(userID != null) { - this.userID = userID.getBytes(); - } - byte[] eHash = generateSM3Hash(message); - - // B4 - BigInteger e = calculateE(eHash); - - // B5 - BigInteger t = r.add(s).mod(n); - if (t.equals(ZERO)) - { - return false; - } - else - { - // B6 - ECPoint x1y1 = ecParams.getG().multiply(s); - x1y1 = x1y1.add(q.multiply(t)).normalize(); - - // B7 - return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); - } - } - - /** - * verfify the hash signature - * - * @param hash - * @param r - * @param s - * @return - */ - public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) - { - BigInteger n = ecParams.getN(); - - // 5.3.1 Draft RFC: SM2 Public Key Algorithms - // B1 - if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) - { - return false; - } - - // B2 - if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) - { - return false; - } - - ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); - - - // B4 - BigInteger e = calculateE(hash); - - // B5 - BigInteger t = r.add(s).mod(n); - if (t.equals(ZERO)) - { - return false; - } - else - { - // B6 - ECPoint x1y1 = ecParams.getG().multiply(s); - x1y1 = x1y1.add(q.multiply(t)).normalize(); - - // B7 - return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); - } - } - - private byte[] getZ(Digest digest) - { - - //addUserID(digest, userID); - - addFieldElement(digest, ecParams.getCurve().getA()); - addFieldElement(digest, ecParams.getCurve().getB()); - addFieldElement(digest, ecParams.getG().getAffineXCoord()); - addFieldElement(digest, ecParams.getG().getAffineYCoord()); - addFieldElement(digest, pubPoint.getAffineXCoord()); - addFieldElement(digest, pubPoint.getAffineYCoord()); - - byte[] rv = new byte[digest.getDigestSize()]; - - digest.doFinal(rv, 0); - - return rv; - } - - private void addUserID(Digest digest, byte[] userID) - { - int len = userID.length * 8; - digest.update((byte)(len >> 8 & 0xFF)); - digest.update((byte)(len & 0xFF)); - digest.update(userID, 0, userID.length); - } - - private void addFieldElement(Digest digest, ECFieldElement v) - { - byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); - digest.update(p, 0, p.length); - } - - protected ECMultiplier createBasePointMultiplier() - { - return new FixedPointCombMultiplier(); - } - - protected BigInteger calculateE(byte[] message) - { - return new BigInteger(1, message); - } - -} - diff --git a/src/main/java/org/tron/common/crypto/sm2/SM3.java b/src/main/java/org/tron/common/crypto/sm2/SM3.java deleted file mode 100644 index 6a72f85f2..000000000 --- a/src/main/java/org/tron/common/crypto/sm2/SM3.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.tron.common.crypto.sm2; - -import org.spongycastle.crypto.digests.SM3Digest; -import org.spongycastle.util.encoders.Hex; - -public class SM3 { - public static byte[] hash(String message) { - byte[] msg = Hex.decode(message); - return hash(msg); - } - - public static byte[] hash(byte[] message) { - SM3Digest digest = new SM3Digest(); - digest.update(message,0,message.length); - - byte[] eHash = new byte[digest.getDigestSize()]; - - digest.doFinal(eHash, 0); - - return eHash; - } -} diff --git a/src/main/java/org/tron/common/utils/DecodeUtil.java b/src/main/java/org/tron/common/utils/DecodeUtil.java deleted file mode 100644 index 62e38ed70..000000000 --- a/src/main/java/org/tron/common/utils/DecodeUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.tron.common.utils; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; - -//import static org.tron.core.Constant.ADD_PRE_FIX_BYTE_MAINNET; - -@Slf4j(topic = "Commons") -public class DecodeUtil { - public static final byte ADD_PRE_FIX_BYTE_MAINNET = (byte) 0x41; //41 + address - public static final int ADDRESS_SIZE = 42; - public static byte addressPreFixByte = ADD_PRE_FIX_BYTE_MAINNET; - - public static boolean addressValid(byte[] address) { - if (ArrayUtils.isEmpty(address)) { - logger.warn("Warning: Address is empty !!"); - return false; - } - if (address.length != ADDRESS_SIZE / 2) { - logger.warn( - "Warning: Address length need " + ADDRESS_SIZE + " but " + address.length - + " !!"); - return false; - } - - if (address[0] != addressPreFixByte) { - logger.warn("Warning: Address need prefix with " + addressPreFixByte + " but " - + address[0] + " !!"); - return false; - } - //Other rule; - return true; - } - - public static String createReadableString(byte[] bytes) { - return ByteArray.toHexString(bytes); - } - -} diff --git a/src/main/java/org/tron/common/utils/Hash.java b/src/main/java/org/tron/common/utils/Hash.java deleted file mode 100644 index b82ed7237..000000000 --- a/src/main/java/org/tron/common/utils/Hash.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.utils; - -import lombok.extern.slf4j.Slf4j; -import org.spongycastle.math.ec.ECPoint; -import org.tron.common.crypto.jce.TronCastleProvider; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.Security; -import java.util.Arrays; - -import static java.util.Arrays.copyOfRange; -import static org.tron.common.utils.ByteUtil.*; - -@Slf4j(topic = "crypto") -public class Hash { - - public static final byte[] EMPTY_TRIE_HASH; - private static final Provider CRYPTO_PROVIDER; - private static final String HASH_256_ALGORITHM_NAME; - private static final String HASH_512_ALGORITHM_NAME; - private static final String ALGORITHM_NOT_FOUND = "Can't find such algorithm"; - /** - * [0x80] If a string is 0-55 bytes long, the RLP encoding consists of a single byte with value - * 0x80 plus the length of the string followed by the string. The range of the first byte is thus - * [0x80, 0xb7]. - */ - private static final int OFFSET_SHORT_ITEM = 0x80; - - /** - * [0xb7] If a string is more than 55 bytes long, the RLP encoding consists of a single byte with - * value 0xb7 plus the length of the length of the string in binary form, followed by the length - * of the string, followed by the string. For example, a length-1024 string would be encoded as - * \xb9\x04\x00 followed by the string. The range of the first byte is thus [0xb8, 0xbf]. - */ - private static final int OFFSET_LONG_ITEM = 0xb7; - - /** - * Reason for threshold according to Vitalik Buterin: - 56 bytes maximizes the benefit of both - * options - if we went with 60 then we would have only had 4 slots for long strings so RLP would - * not have been able to store objects above 4gb - if we went with 48 then RLP would be fine for - * 2^128 space, but that's way too much - so 56 and 2^64 space seems like the right place to put - * the cutoff - also, that's where Bitcoin's varint does the cutof - */ - private static final int SIZE_THRESHOLD = 56; - - static { - Security.addProvider(TronCastleProvider.getInstance()); - CRYPTO_PROVIDER = Security.getProvider("SC"); - HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256"; - HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512"; - EMPTY_TRIE_HASH = sha3(encodeElement(EMPTY_BYTE_ARRAY)); - } - - public static byte[] sha3(byte[] input) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - logger.error(ALGORITHM_NOT_FOUND, e); - throw new RuntimeException(e); - } - - } - - public static byte[] sha3(byte[] input1, byte[] input2) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input1, 0, input1.length); - digest.update(input2, 0, input2.length); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - logger.error(ALGORITHM_NOT_FOUND, e); - throw new RuntimeException(e); - } - } - - /** - * hashing chunk of the data - * - * @param input - data for hash - * @param start - start of hashing chunk - * @param length - length of hashing chunk - * @return - keccak hash of the chunk - */ - public static byte[] sha3(byte[] input, int start, int length) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input, start, length); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - logger.error(ALGORITHM_NOT_FOUND, e); - throw new RuntimeException(e); - } - } - - public static byte[] sha512(byte[] input) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_512_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - logger.error(ALGORITHM_NOT_FOUND, e); - throw new RuntimeException(e); - } - } - - - public static byte[] encodeElement(byte[] srcData) { - - // [0x80] - if (isNullOrZeroArray(srcData)) { - return new byte[]{(byte) OFFSET_SHORT_ITEM}; - - // [0x00] - } else if (isSingleZero(srcData)) { - return srcData; - - // [0x01, 0x7f] - single byte, that byte is its own RLP encoding - } else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { - return srcData; - - // [0x80, 0xb7], 0 - 55 bytes - } else if (srcData.length < SIZE_THRESHOLD) { - // length = 8X - byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); - byte[] data = Arrays.copyOf(srcData, srcData.length + 1); - System.arraycopy(data, 0, data, 1, srcData.length); - data[0] = length; - - return data; - // [0xb8, 0xbf], 56+ bytes - } else { - // length of length = BX - // prefix = [BX, [length]] - int tmpLength = srcData.length; - byte lengthOfLength = 0; - while (tmpLength != 0) { - ++lengthOfLength; - tmpLength = tmpLength >> 8; - } - - // set length Of length at first byte - byte[] data = new byte[1 + lengthOfLength + srcData.length]; - data[0] = (byte) (OFFSET_LONG_ITEM + lengthOfLength); - - // copy length after first byte - tmpLength = srcData.length; - for (int i = lengthOfLength; i > 0; --i) { - data[i] = (byte) (tmpLength & 0xFF); - tmpLength = tmpLength >> 8; - } - - // at last copy the number bytes after its length - System.arraycopy(srcData, 0, data, 1 + lengthOfLength, srcData.length); - - return data; - } - } - - public static byte[] computeAddress(ECPoint pubPoint) { - return computeAddress(pubPoint.getEncoded(/* uncompressed */ false)); - } - - public static byte[] computeAddress(byte[] pubBytes) { - return sha3omit12( - Arrays.copyOfRange(pubBytes, 1, pubBytes.length)); - } - - /** - * Calculates RIGTMOST160(SHA3(input)). This is used in address calculations. * - * - * @param input - data - * @return - add_pre_fix + 20 right bytes of the hash keccak of the data - */ - public static byte[] sha3omit12(byte[] input) { - byte[] hash = Hash.sha3(input); - byte[] address = copyOfRange(hash, 11, hash.length); - address[0] = DecodeUtil.addressPreFixByte; - return address; - } -} diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index ccaf56222..bc54d14e9 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -19,9 +19,6 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -167,10 +164,13 @@ public static boolean validTransaction(Transaction signedTransaction) { return true; } - public static Transaction sign(Transaction transaction, SignInterface myKey) { + public static Transaction sign(Transaction transaction, ECKey myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); - SignatureInterface signature = myKey.sign(hash); + + //System.out.println("Sign address: " + WalletApi.encode58Check(myKey.getAddress())); + + ECDSASignature signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); transaction = transactionBuilderSigned.build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 86ffa40d1..fd77fd55e 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -43,7 +43,6 @@ import java.util.Arrays; import java.util.Date; import java.util.List; -import java.util.Scanner; public class Utils { public static final String PERMISSION_ID = "Permission_id"; @@ -517,5 +516,6 @@ public static boolean isNumericString(String str) { } return true; } + } diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java deleted file mode 100644 index fc57493a5..000000000 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.tron.demo; - -import com.google.protobuf.Any; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.TransactionExtention; - -import org.tron.common.crypto.Sha256Hash; -import org.tron.common.crypto.sm2.SM2; -import org.tron.common.utils.ByteArray; -import org.tron.core.exception.CancelException; -import org.tron.protos.Contract; -import org.tron.protos.Protocol.Block; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class TransactionSignDemoForSM2 { - - public static Transaction setReference(Transaction transaction, Block newestBlock) { - long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); - byte[] blockHash = getBlockHash(newestBlock).getBytes(); - byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); - return transaction.toBuilder().setRawData(rawData).build(); - } - - public static Sha256Hash getBlockHash(Block block) { - return Sha256Hash.of(block.getBlockHeader().getRawData().toByteArray()); - } - - public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); - return txid; - } - - - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { - Transaction.Builder transactionBuilder = Transaction.newBuilder(); - Block newestBlock = WalletApi.getBlock(-1); - - Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract - .newBuilder(); - transferContractBuilder.setAmount(amount); - ByteString bsTo = ByteString.copyFrom(to); - ByteString bsOwner = ByteString.copyFrom(from); - transferContractBuilder.setToAddress(bsTo); - transferContractBuilder.setOwnerAddress(bsOwner); - try { - Any any = Any.pack(transferContractBuilder.build()); - contractBuilder.setParameter(any); - } catch (Exception e) { - return null; - } - contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) - .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); - Transaction transaction = transactionBuilder.build(); - Transaction refTransaction = setReference(transaction, newestBlock); - return refTransaction; - } - - - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) - throws InvalidProtocolBufferException { - SM2 sm2 = SM2.fromPrivate(privateKey); - Transaction transaction1 = Transaction.parseFrom(transaction); - byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = sm2.hash(rawdata); - byte[] sign = sm2.sign(hash).toByteArray(); - return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); - } - - private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) - throws InvalidProtocolBufferException { - SM2 sm2 = SM2.fromPrivate(privateKey); - Transaction transaction1 = Transaction.parseFrom(transaction); - byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = sm2.hash(rawdata); - byte[] sign = sm2.sign(hash).toByteArray(); - return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); - } - - private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException { - return WalletApi.broadcastTransaction(transactionBytes); - } - - private static void base58checkToHexString() { - String base58check = "TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"; - String hexString = ByteArray.toHexString(WalletApi.decodeFromBase58Check(base58check)); - System.out.println(hexString); - } - - private static void hexStringTobase58check() { - String hexString = "414948c2e8a756d9437037dcd8c7e0c73d560ca38d"; - String base58check = WalletApi.encode58Check(ByteArray.fromHexString(hexString)); - System.out.println(base58check); - } - - public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; - byte[] privateBytes = ByteArray.fromHexString(privateStr); - SM2 sm2 = SM2.fromPrivate(privateBytes); - byte[] from = sm2.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); - long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop - Transaction transaction = createTransaction(from, to, amount); - byte[] transactionBytes = transaction.toByteArray(); - - - //sign a transaction in byte format and return a Transaction in byte format - byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); - boolean result = broadcast(transaction4); - - System.out.println(result); - } - - -} diff --git a/src/main/java/org/tron/keystore/Credentials.java b/src/main/java/org/tron/keystore/Credentials.java index 058bb7f1f..a13033723 100644 --- a/src/main/java/org/tron/keystore/Credentials.java +++ b/src/main/java/org/tron/keystore/Credentials.java @@ -1,10 +1,62 @@ package org.tron.keystore; +import org.tron.common.crypto.ECKey; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; -import org.tron.common.crypto.SignInterface; +/** + * Credentials wrapper. + */ +public class Credentials { -public interface Credentials { - SignInterface getPair(); + private final ECKey ecKeyPair; + private final String address; - String getAddress(); + private Credentials(ECKey ecKeyPair, String address) { + this.ecKeyPair = ecKeyPair; + this.address = address; + } + + public ECKey getEcKeyPair() { + return ecKeyPair; + } + + public String getAddress() { + return address; + } + + public static Credentials create(ECKey ecKeyPair) { + String address = WalletApi.encode58Check(ecKeyPair.getAddress()); + return new Credentials(ecKeyPair, address); + } + + public static Credentials create(String privateKey) { + ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); + return create(eCkey); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Credentials that = (Credentials) o; + + if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { + return false; + } + + return address != null ? address.equals(that.address) : that.address == null; + } + + @Override + public int hashCode() { + int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; + result = 31 * result + (address != null ? address.hashCode() : 0); + return result; + } } diff --git a/src/main/java/org/tron/keystore/CredentialsEckey.java b/src/main/java/org/tron/keystore/CredentialsEckey.java deleted file mode 100644 index 94aa0f255..000000000 --- a/src/main/java/org/tron/keystore/CredentialsEckey.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.tron.keystore; - -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.SignInterface; -import org.tron.common.utils.ByteArray; -import org.tron.walletserver.WalletApi; - -/** - * Credentials wrapper. - */ -public class CredentialsEckey implements Credentials { - - private final ECKey ecKeyPair; - private final String address; - - private CredentialsEckey(ECKey ecKeyPair, String address) { - this.ecKeyPair = ecKeyPair; - this.address = address; - } - -// public ECKey getEcKeyPair() { -// return ecKeyPair; -// } - - public String getAddress() { - return address; - } - - public static CredentialsEckey create(ECKey ecKeyPair) { - String address = WalletApi.encode58Check(ecKeyPair.getAddress()); - return new CredentialsEckey(ecKeyPair, address); - } - - public static CredentialsEckey create(String privateKey) { - ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey)); - return create(eCkey); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - CredentialsEckey that = (CredentialsEckey) o; - - if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) { - return false; - } - - return address != null ? address.equals(that.address) : that.address == null; - } - - @Override - public int hashCode() { - int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0; - result = 31 * result + (address != null ? address.hashCode() : 0); - return result; - } - - @Override - public SignInterface getPair() { - return ecKeyPair; - } -} diff --git a/src/main/java/org/tron/keystore/CredentialsSM2.java b/src/main/java/org/tron/keystore/CredentialsSM2.java deleted file mode 100644 index 537f8ce65..000000000 --- a/src/main/java/org/tron/keystore/CredentialsSM2.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.tron.keystore; - -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.sm2.SM2; -import org.tron.common.utils.ByteArray; -import org.tron.walletserver.WalletApi; - -/** - * Credentials wrapper. - */ -public class CredentialsSM2 implements Credentials { - - private final SM2 sm2Pair; - private final String address; - - private CredentialsSM2(SM2 sm2Pair, String address) { - this.sm2Pair = sm2Pair; - this.address = address; - } - -// public SM2 getEcKeyPair() { -// return sm2Pair; -// } - - public String getAddress() { - return address; - } - - public static CredentialsSM2 create(SM2 sm2Pair) { - String address = WalletApi.encode58Check(sm2Pair.getAddress()); - return new CredentialsSM2(sm2Pair, address); - } - - public static CredentialsSM2 create(String privateKey) { - SM2 eCkey = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); - return create(eCkey); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - CredentialsSM2 that = (CredentialsSM2) o; - - if (sm2Pair != null ? !sm2Pair.equals(that.sm2Pair) : that.sm2Pair != null) { - return false; - } - - return address != null ? address.equals(that.address) : that.address == null; - } - - @Override - public int hashCode() { - int result = sm2Pair != null ? sm2Pair.hashCode() : 0; - result = 31 * result + (address != null ? address.hashCode() : 0); - return result; - } - - @Override - public SignInterface getPair() { - return sm2Pair; - } -} diff --git a/src/main/java/org/tron/keystore/Wallet.java b/src/main/java/org/tron/keystore/Wallet.java index 7331a5a75..55e15ce55 100644 --- a/src/main/java/org/tron/keystore/Wallet.java +++ b/src/main/java/org/tron/keystore/Wallet.java @@ -6,9 +6,6 @@ import org.bouncycastle.crypto.params.KeyParameter; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CipherException; import org.tron.walletserver.WalletApi; @@ -62,7 +59,7 @@ public class Wallet { static final String AES_128_CTR = "pbkdf2"; static final String SCRYPT = "scrypt"; - public static WalletFile create(byte[] password, SignInterface ecKeySm2Pair, int n, int p) + public static WalletFile create(byte[] password, ECKey ecKeyPair, int n, int p) throws CipherException { byte[] salt = generateRandomBytes(32); @@ -72,30 +69,32 @@ public static WalletFile create(byte[] password, SignInterface ecKeySm2Pair, int byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); - byte[] privateKeyBytes = ecKeySm2Pair.getPrivKeyBytes(); + byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes(); byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, privateKeyBytes); byte[] mac = generateMac(derivedKey, cipherText); - return createWalletFile(ecKeySm2Pair, cipherText, iv, salt, mac, n, p); + return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); } - public static WalletFile createStandard(byte[] password, SignInterface ecKeySm2Pair) + public static WalletFile createStandard(byte[] password, ECKey ecKeyPair) throws CipherException { - return create(password, ecKeySm2Pair, N_STANDARD, P_STANDARD); + return create(password, ecKeyPair, N_STANDARD, P_STANDARD); } - public static WalletFile createLight(byte[] password, SignInterface ecKeySm2Pair) + + public static WalletFile createLight(byte[] password, ECKey ecKeyPair) throws CipherException { - return create(password, ecKeySm2Pair, N_LIGHT, P_LIGHT); + return create(password, ecKeyPair, N_LIGHT, P_LIGHT); } + private static WalletFile createWalletFile( - SignInterface ecKeySm2Pair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, - int n, int p) { + ECKey ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, + int n, int p) { WalletFile walletFile = new WalletFile(); - walletFile.setAddress(WalletApi.encode58Check(ecKeySm2Pair.getAddress())); + walletFile.setAddress(WalletApi.encode58Check(ecKeyPair.getAddress())); WalletFile.Crypto crypto = new WalletFile.Crypto(); crypto.setCipher(CIPHER); @@ -268,13 +267,6 @@ public static ECKey decrypt(byte[] password, WalletFile walletFile) StringUtils.clear(privateKey); return ecKey; } - public static SM2 decryptSM2(byte[] password, WalletFile walletFile) - throws CipherException { - byte[] privateKey = decrypt2PrivateBytes(password, walletFile); - SM2 sm2 = SM2.fromPrivate(privateKey); - StringUtils.clear(privateKey); - return sm2; - } static void validate(WalletFile walletFile) throws CipherException { WalletFile.Crypto crypto = walletFile.getCrypto(); diff --git a/src/main/java/org/tron/keystore/WalletUtils.java b/src/main/java/org/tron/keystore/WalletUtils.java index 8d206829f..f409465fe 100644 --- a/src/main/java/org/tron/keystore/WalletUtils.java +++ b/src/main/java/org/tron/keystore/WalletUtils.java @@ -3,12 +3,8 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.typesafe.config.Config; import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Utils; -import org.tron.core.config.Configuration; import org.tron.core.exception.CipherException; import java.io.File; @@ -26,16 +22,10 @@ public class WalletUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); - private static boolean isEckey = true; static { - Config config = Configuration.getByPath("config.conf");//it is needs set to be a constant objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - if (config.hasPath("crypto.engine")) { - isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); - System.out.println("WalletUtils getConfig isEckey: " + isEckey); - } } public static String generateFullNewWalletFile(byte[] password, File destinationDirectory) @@ -56,24 +46,20 @@ public static String generateNewWalletFile( byte[] password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - SignInterface ecKeySm2Pair = null; - if (isEckey) { - ecKeySm2Pair = new ECKey(Utils.getRandom()); - } else { - ecKeySm2Pair = new SM2(Utils.getRandom()); - } - return generateWalletFile(password, ecKeySm2Pair, destinationDirectory, useFullScrypt); + + ECKey ecKeyPair = new ECKey(Utils.getRandom()); + return generateWalletFile(password, ecKeyPair, destinationDirectory, useFullScrypt); } public static String generateWalletFile( - byte[] password, SignInterface ecKeySm2Pair, File destinationDirectory, boolean useFullScrypt) + byte[] password, ECKey ecKeyPair, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile; if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeySm2Pair); + walletFile = Wallet.createStandard(password, ecKeyPair); } else { - walletFile = Wallet.createLight(password, ecKeySm2Pair); + walletFile = Wallet.createLight(password, ecKeyPair); } String fileName = getWalletFileName(walletFile); @@ -85,14 +71,14 @@ public static String generateWalletFile( } public static void updateWalletFile( - byte[] password, SignInterface ecKeySm2Pair, File source, boolean useFullScrypt) + byte[] password, ECKey ecKeyPair, File source, boolean useFullScrypt) throws CipherException, IOException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); if (useFullScrypt) { - walletFile = Wallet.createStandard(password, ecKeySm2Pair); + walletFile = Wallet.createStandard(password, ecKeyPair); } else { - walletFile = Wallet.createLight(password, ecKeySm2Pair); + walletFile = Wallet.createLight(password, ecKeyPair); } objectMapper.writeValue(source, walletFile); @@ -142,15 +128,11 @@ public static String generateWalletFile(WalletFile walletFile, File destinationD public static Credentials loadCredentials(byte[] password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); - - if (isEckey) { - return CredentialsEckey.create(Wallet.decrypt(password, walletFile)); - } - return CredentialsSM2.create(Wallet.decryptSM2(password, walletFile)); + return Credentials.create(Wallet.decrypt(password, walletFile)); } public static WalletFile loadWalletFile(File source) throws IOException { - return objectMapper.readValue(source, WalletFile.class); + return objectMapper.readValue(source, WalletFile.class); } // // public static Credentials loadBip39Credentials(String password, String mnemonic) { @@ -194,6 +176,7 @@ public static String getMainnetKeyDirectory() { } + // public static boolean isValidPrivateKey(String privateKey) { // String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); // return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; @@ -212,7 +195,7 @@ public static String getMainnetKeyDirectory() { // } public static void generateSkeyFile(SKeyCapsule skey, File file) - throws IOException { + throws IOException { objectMapper.writeValue(file, skey); } diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 534a08a37..3e597f4ee 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -7,15 +7,12 @@ import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; import org.tron.core.exception.CipherException; import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; -import org.tron.keystore.CredentialsEckey; import org.tron.keystore.WalletUtils; import org.tron.protos.Contract; import org.tron.protos.Contract.TransferContract; @@ -319,14 +316,13 @@ public static void testSha3() { public static void testGenerateWalletFile() throws CipherException, IOException { String PASSWORD = "Insecure Pa55w0rd"; String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; -// ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); - SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(priKeyHex)); + ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); File file = new File("out"); - String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), sm2, file, true); + String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), eCkey, file, true); Credentials credentials = WalletUtils.loadCredentials(PASSWORD.getBytes(), new File(file, fileName)); String address = credentials.getAddress(); - SignInterface pair = credentials.getPair(); - String prikey = ByteArray.toHexString(pair.getPrivKeyBytes()); + ECKey ecKeyPair = credentials.getEcKeyPair(); + String prikey = ByteArray.toHexString(ecKeyPair.getPrivKeyBytes()); System.out.println("address = " + address); System.out.println("prikey = " + prikey); @@ -364,13 +360,6 @@ public static void testPasswordStrength(){ System.out.println(password + " strength is " + level); } } - - public static void interfaceTest() { - String privateKey = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; - SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); - String address = WalletApi.encode58Check(sm2.getAddress()); - System.out.println(address); - } public static void main(String[] args) throws Exception { testPasswordStrength(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index f1b4449d4..d0fe50cfd 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -21,7 +21,6 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; -import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.TransactionUtils; @@ -53,7 +52,6 @@ public class WalletApi { private byte[] address; private static byte addressPreFixByte = CommonConstant.ADD_PRE_FIX_BYTE_TESTNET; private static int rpcVersion = 0; - private static boolean isEckey = true; private static GrpcClient rpcCli = init(); @@ -87,11 +85,6 @@ public static GrpcClient init() { } if (config.hasPath("RPC_version")) { rpcVersion = config.getInt("RPC_version"); - System.out.println("WalletApi getRpcVsersion: " + rpcVersion); - } - if (config.hasPath("crypto.engine")) { - isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); - System.out.println("WalletApi getConfig isEckey: " + isEckey); } return new GrpcClient(fullNode, solidityNode); } @@ -144,27 +137,15 @@ public static int getRpcVersion() { * Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { - WalletFile walletFile = null; - if (isEckey) { - ECKey ecKey = new ECKey(Utils.getRandom()); - walletFile = Wallet.createStandard(password, ecKey); - } else { - SM2 sm2 = new SM2(Utils.getRandom()); - walletFile = Wallet.createStandard(password, sm2); - } + ECKey ecKey = new ECKey(Utils.getRandom()); + WalletFile walletFile = Wallet.createStandard(password, ecKey); return walletFile; } // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - WalletFile walletFile=null; - if (isEckey) { - ECKey ecKey = ECKey.fromPrivate(priKey); - walletFile = Wallet.createStandard(password, ecKey); - } else { - SM2 sm2 = SM2.fromPrivate(priKey); - walletFile = Wallet.createStandard(password, sm2); - } + ECKey ecKey = ECKey.fromPrivate(priKey); + WalletFile walletFile = Wallet.createStandard(password, ecKey); return walletFile; } @@ -202,10 +183,6 @@ public ECKey getEcKey(WalletFile walletFile, byte[] password) throws CipherExcep return Wallet.decrypt(password, walletFile); } - public SM2 getSM2(WalletFile walletFile, byte[] password) throws CipherException { - return Wallet.decryptSM2(password, walletFile); - } - public byte[] getPrivateBytes(byte[] password) throws CipherException, IOException { WalletFile walletFile = loadWalletFile(); return Wallet.decrypt2PrivateBytes(password, walletFile); @@ -309,7 +286,7 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso "No keystore file found, please use registerwallet or importwallet first!"); } Credentials credentials = WalletUtils.loadCredentials(oldPassword, wallet); - WalletUtils.updateWalletFile(newPassowrd, credentials.getPair(), wallet, true); + WalletUtils.updateWalletFile(newPassowrd, credentials.getEcKeyPair(), wallet, true); return true; } @@ -375,11 +352,8 @@ private Transaction signTransaction(Transaction transaction) char[] password = Utils.inputPassword(false); byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - if (isEckey) { - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); - } else { - transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); - } + + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -417,11 +391,7 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - if (isEckey) { - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); - } else { - transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); - } + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); // System.out // .println("current transaction hex string is " + ByteArray // .toHexString(transaction.toByteArray())); @@ -463,13 +433,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -478,13 +448,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); + .getParameter().unpack(CreateSmartContract.class); byte[] contractAddress = generateContractAddress( createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( @@ -493,7 +463,7 @@ private void showTransactionAfterSign(Transaction transaction) } private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -511,7 +481,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -528,9 +498,9 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -543,7 +513,7 @@ private boolean processTransaction(Transaction transaction) System.out.println(Utils.printTransactionExceptId(transaction)); System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -565,7 +535,7 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p //Warning: do not invoke this interface provided by others. public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -577,7 +547,7 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction //Warning: do not invoke this interface provided by others. public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -602,25 +572,25 @@ public static byte[] createAdresss(byte[] passPhrase) { //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } //Warning: do not invoke this interface provided by others. public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -673,7 +643,7 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -717,7 +687,7 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + .createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -829,7 +799,7 @@ public boolean voteWitness(byte[] owner, HashMap witness) } public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -841,8 +811,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] } public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -856,10 +826,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] } public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + byte[] assertName, byte[] owner, + long amount) { Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + .newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -872,7 +842,7 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra } public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -883,7 +853,7 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] } public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -914,7 +884,7 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( } public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -923,7 +893,7 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] } public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -932,7 +902,7 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] } public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -941,14 +911,14 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] } public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + .newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -1001,7 +971,7 @@ public static boolean addressValid(byte[] address) { if (preFixbyte != WalletApi.getAddressPreFixByte()) { System.out .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + + preFixbyte + " !!"); return false; } //Other rule; @@ -1027,9 +997,9 @@ private static byte[] decode58Check(String input) { byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + hash1[1] == decodeCheck[decodeData.length + 1] && + hash1[2] == decodeCheck[decodeData.length + 2] && + hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1162,13 +1132,13 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { } public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { @@ -1176,13 +1146,13 @@ public static Optional getTransactionsFromThis2(byte[] // } public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + int offset, + int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } // public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { @@ -1198,7 +1168,7 @@ public static Optional getTransactionInfoById(String txID) { } public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, frozen_balance, @@ -1235,7 +1205,7 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) } private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + long frozen_duration, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } @@ -1271,7 +1241,7 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo } Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + .newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1305,13 +1275,13 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); @@ -1342,7 +1312,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { } Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1366,7 +1336,7 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { } Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1413,7 +1383,7 @@ public static Optional getProposal(String id) { } public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1436,7 +1406,7 @@ public static Optional getChainParameters() { public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1456,9 +1426,9 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) } public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { + long id, boolean is_add_approval) { Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1477,7 +1447,7 @@ public boolean deleteProposal(byte[] owner, long id) } public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); @@ -1485,7 +1455,7 @@ public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[ } public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + byte[] secondTokenId, long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1498,8 +1468,8 @@ public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstToken } public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + byte[] firstTokenId, long firstTokenBalance, + byte[] secondTokenId, long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1523,7 +1493,7 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon } public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1546,9 +1516,9 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l } public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1558,7 +1528,7 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b } public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); @@ -1571,9 +1541,9 @@ public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId } public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { + long exchangeId, byte[] tokenId, long quant, long expected) { Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1627,21 +1597,21 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; + abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; + abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; + abiItem.getAsJsonObject().get("name").getAsString() : null; JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; + abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; + abiItem.getAsJsonObject().get("type").getAsString() : null; boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; + abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; if (type == null) { System.out.println("No type!"); return null; @@ -1663,7 +1633,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1672,10 +1642,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1688,7 +1658,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1697,10 +1667,10 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + .newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1721,7 +1691,7 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { } public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1735,7 +1705,7 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract byte[] contractAddress, long originEnergyLimit) { Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); @@ -1743,20 +1713,20 @@ public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract } public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + byte[] contractAddress) { Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + byte[] address, + String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, + String libraryAddressPair, String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1784,7 +1754,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } @@ -1792,7 +1762,7 @@ public static CreateSmartContract createContractDeployContract(String contractNa } private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1821,7 +1791,7 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } else if (compilerVersion.equalsIgnoreCase("v5")) { //0.5.4 version String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1835,8 +1805,8 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa } public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + byte[] contractAddress, + long callValue, byte[] data, long tokenValue, String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1863,7 +1833,7 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { } public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1897,7 +1867,7 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig contractAddress, originEnergyLimit); TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + .updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { @@ -1934,8 +1904,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) } public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -1959,7 +1929,7 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1983,8 +1953,8 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str } public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + long feeLimit, + long tokenValue, String tokenId, boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); @@ -2010,12 +1980,12 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa Transaction transaction = transactionExtention.getTransaction(); // for constant if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + transactionExtention.getConstantResult(0) != null && + transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + .toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } @@ -2023,7 +1993,7 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2141,11 +2111,7 @@ public Transaction addTransactionSign(Transaction transaction) byte[] passwd = org.tron.keystore.StringUtils.char2Byte(password); org.tron.keystore.StringUtils.clear(password); - if (isEckey) { - transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); - } else { - transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); - } + transaction = TransactionUtils.sign(transaction, this.getEcKey(walletFile, passwd)); org.tron.keystore.StringUtils.clear(passwd); return transaction; } @@ -2169,7 +2135,7 @@ public static Optional GetMerkleTreeVoucherInfo( } public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2186,7 +2152,7 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry } public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2271,7 +2237,7 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall } public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2288,7 +2254,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2297,7 +2263,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + .clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2313,10 +2279,10 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + .addContract( + Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) + .setParameter( + Any.pack(contractBuild.build())).build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2326,7 +2292,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri } public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2393,7 +2359,7 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + .updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf deleted file mode 100644 index 171520768..000000000 --- a/src/main/resources/config-back.conf +++ /dev/null @@ -1,20 +0,0 @@ -net { - type = mainnet -} - -fullnode = { - ip.list = [ - "47.89.189.124:50055", - "47.89.178.193:50055" - ] -} - -#soliditynode = { -# ip.list = [ -# "127.0.0.1:50052" -# ] -#} -crypto { - engine=sm2 -} -RPC_version = 2 From 3424d52bb9fbba7c59da63f7b605d470f5c3d333 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 6 Jan 2020 15:32:07 +0800 Subject: [PATCH 235/445] change tips --- src/main/java/org/tron/walletcli/Client.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index de1a1290f..a6ec39c31 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2787,9 +2787,9 @@ private void backupShieldedWallet() throws IOException, CipherException { if (addressInfo != null) { System.out.println("sk:" + ByteArray.toHexString(addressInfo.getSk())); System.out.println("d :" + ByteArray.toHexString(addressInfo.getD().getData())); - System.out.println("BackupShieldedAddress successful !!!"); + System.out.println("BackupShieldedWallet successful !!!"); } else { - System.out.println("BackupShieldedAddress failed !!!"); + System.out.println("BackupShieldedWallet failed !!!"); } } @@ -2804,13 +2804,13 @@ private void importShieldedWallet() throws CipherException, IOException { walletApiWrapper.getNewShieldedAddressBySkAndD(sk, d); if (addressInfo.isPresent() && ShieldedWrapper.getInstance().addNewShieldedAddress(addressInfo.get(), false)) { - System.out.println("Import new shielded address is: " + addressInfo.get().getAddress()); - System.out.println("ImportShieldedAddress successful !!!"); + System.out.println("Import new shielded wallet address is: " + addressInfo.get().getAddress()); + System.out.println("ImportShieldedWallet successful !!!"); } else { - System.out.println("ImportShieldedAddress failed !!!"); + System.out.println("ImportShieldedWallet failed !!!"); } } else { - System.out.println("ImportShieldedAddress failed !!!"); + System.out.println("ImportShieldedWallet failed !!!"); } } From 50f7da629f5d496ab4ad3be8e892d2281256cfcb Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 6 Jan 2020 15:40:37 +0800 Subject: [PATCH 236/445] change readme --- README.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 0eb6d923c..b96182682 100644 --- a/README.md +++ b/README.md @@ -1374,44 +1374,44 @@ Example: shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z ``` -### BackupShieldedAddress +### BackupShieldedWallet Back up one shielded address Example: ```console - > loadshieldedwallet + wallet> BackUpShieldedWallet Please input your password for shielded wallet. - > 1qa@WS#ED - LoadShieldedWallet successful !!! - > BackupShieldedAddress - Please input your password for shielded wallet. - > 1qa@WS#ED - The 1th shielded address is ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8qsvp7hgqstp9w5t8eh0vydlyf42gtxjzun - The 2th shielded address is ztron1akz7mt4zqsjqrdrwdsmffu6g5dnehhhtahjlc0c6syy3z9nxxjrzqszy22lyx326edmwqjhqe48 - The 3th shielded address is ztron1m5dx50gryu789q5sh5207chzmmgzf5c7hvn8lr6xs60jfxvkv3d3h0kqkglc60rwq26dchztsty - The 4th shielded address is ztron1at9ud3crdsehe8rgrjkltqly7gsp85y8qzq93pq480hzd2az55pja5cc8r4yfwrnrqqs7q35n4x - Please choose between 1 and 4 - > 1 - 0b1cf73b85742e13015dc6fb8d0986e4ad34c8f468bd8c73cc8fb34bff0dfacea11f85ddcde0bdb904d1de - BackupShieldedAddress successful !! + password: + The 1th shielded address is ztron165gswmwecarmyph4x8jfrygezw78tejy3a8y5d9rxnlre7ju5q8jfsfe4qjerhfk0mmkzsx2t6t + The 2th shielded address is ztron1hpd2aau0s55zaauu2dlnnu6umxcqz4wuhflu4p4uqpt9w0nqd88ucf036alw2zjfmclry4tnkf6 + The 3th shielded address is ztron19lgz39ja8dz427dt9qa8gpkpxanu05y09zplfzzwc640mlx74n4au3037nde3h6m7zsu5xgkrnn + Please choose between 1 and 3 + 2 + sk:0c2dcfde42a484ecfcf6e7a00a3c9484022674739f405845d8d75fd6d8619153 + d :b85aaef78f85282ef79c53 + BackupShieldedWallet successful !!! ``` -### ImportShieldedAddress +### ImportShieldedWallet -Import one shieled address to local wallet +Import one shielded address to local wallet Example: ```console - > ImportShieldedAddress + wallet> ImportShieldedWallet Please input your password for shielded wallet. - > 1qa@WS#ED - Please input shielded address hex string. Max retry time: 3 - > 0b1cf73b85742e13015dc6fb8d0986e4ad34c8f468bd8c73cc8fb34bff0dfacea11f85ddcde0bdb904d1de - Import new shielded address is: ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8qsvp7hgqstp9w5t8eh0vydlyf42gtxjzun - ImportShieldedAddress successful !! + password: + Please input shielded wallet hex string. such as 'sk d',Max retry time:3 + 0b18ba69b7963d2ff47e69ac60c20dc30df34b221fa8960d7d61d68123999b8f 2fd028965d3b455579ab28 + Import shielded wallet hex string is : + sk:0b18ba69b7963d2ff47e69ac60c20dc30df34b221fa8960d7d61d68123999b8f + d :2fd028965d3b455579ab28 + Import new shielded wallet address is: ztron19lgz39ja8dz427dt9qa8gpkpxanu05y09zplfzzwc640mlx74n4au3037nde3h6m7zsu5xgkrnn + ImportShieldedWallet successful !!! + wallet> ``` ### ShowShieldedAddressInfo @@ -1445,7 +1445,7 @@ For more information on a specific command, just type the command on terminal wh AddTransactionSign ApproveProposal AssetIssue - BackupShieldedAddress + BackupShieldedWallet BackupWallet BackupWallet2Base64 BroadcastTransaction @@ -1497,7 +1497,7 @@ For more information on a specific command, just type the command on terminal wh GetTransactionsFromThis GetTransactionsToThis GetTransactionSignWeight - ImportShieldedAddress + ImportShieldedWallet ImportWallet ImportWalletByBase64 ListAssetIssue From b9bf2a0c53c8968749e35c7f5fcac6b379590e43 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 6 Jan 2020 15:47:00 +0800 Subject: [PATCH 237/445] change log --- src/main/java/org/tron/walletserver/WalletApi.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 9204f9205..8bcf824f5 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -232,11 +232,7 @@ public static File selcetWalletFile() { for (int i = 0; i < wallets.length; i++) { System.out.println("The " + (i + 1) + "th keystore file name is " + wallets[i].getName()); } - String tipInfo = "Please choose between 1 and " + wallets.length; - if (wallets.length == 1) { - tipInfo = "Please choose address index 1"; - } - System.out.println(tipInfo); + System.out.println("Please choose between 1 and " + wallets.length); Scanner in = new Scanner(System.in); while (true) { String input = in.nextLine().trim(); @@ -246,12 +242,12 @@ public static File selcetWalletFile() { n = new Integer(num); } catch (NumberFormatException e) { System.out.println("Invalid number of " + num); - System.out.println(tipInfo); + System.out.println("Please choose again between 1 and " + wallets.length); continue; } if (n < 1 || n > wallets.length) { System.out.println("Invalid number of " + num); - System.out.println(tipInfo); + System.out.println("Please choose again between 1 and " + wallets.length); continue; } wallet = wallets[n - 1]; From e705f8b39f7d459b0bdd4ee8a23d283e514ba656 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 6 Jan 2020 16:04:34 +0800 Subject: [PATCH 238/445] change tips --- .../org/tron/core/zen/ShieldedWrapper.java | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/tron/core/zen/ShieldedWrapper.java b/src/main/java/org/tron/core/zen/ShieldedWrapper.java index dea384fc6..d4b06841c 100644 --- a/src/main/java/org/tron/core/zen/ShieldedWrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedWrapper.java @@ -85,7 +85,7 @@ public boolean loadShieldWallet() throws CipherException, IOException { } if (!shieldedSkeyFileExist()) { - System.out.println("Shielded wallet is not exist."); + System.out.println("Shielded wallet does not exist."); return false; } @@ -567,7 +567,7 @@ private byte[] generateSkey() throws IOException, CipherException { byte[] skey = new byte[16]; new SecureRandom().nextBytes(skey); - System.out.println("Shielded wallet is not exist, will build it."); + System.out.println("Shielded wallet does not exist, will build it."); char[] password = Utils.inputPassword2Twice(); byte[] passwd = StringUtils.char2Byte(password); @@ -605,7 +605,7 @@ public ShieldedAddressInfo backupShieldedWallet() throws IOException, CipherExce return null; } } else { - System.out.println("Shielded wallet is not exist, please build it first."); + System.out.println("Shielded wallet does not exist, please build it first."); return null; } @@ -621,29 +621,30 @@ public ShieldedAddressInfo backupShieldedWallet() throws IOException, CipherExce System.out.println("The " + (i + 1) + "th shielded address is " + shieldedAddressInfoList.get(i).getAddress()); } - String tipInfo = "Please choose between 1 and " + shieldedAddressInfoList.size(); + if (shieldedAddressInfoList.size() == 1) { - tipInfo = "Please choose shielded address index 1"; - } - System.out.println(tipInfo); - Scanner in = new Scanner(System.in); - while (true) { - String input = in.nextLine().trim(); - String num = input.split("\\s+")[0]; - int n; - try { - n = new Integer(num); - } catch (NumberFormatException e) { - System.out.println("Invalid number of " + num); - System.out.println(tipInfo); - continue; - } - if (n < 1 || n > shieldedAddressInfoList.size()) { - System.out.println("Invalid number of " + num); - System.out.println(tipInfo); - continue; + return shieldedAddressInfoList.get(0); + } else { + System.out.println("Please choose between 1 and " + shieldedAddressInfoList.size()); + Scanner in = new Scanner(System.in); + while (true) { + String input = in.nextLine().trim(); + String num = input.split("\\s+")[0]; + int n; + try { + n = new Integer(num); + } catch (NumberFormatException e) { + System.out.println("Invalid number of " + num); + System.out.println("Please choose again between 1 and " + shieldedAddressInfoList.size()); + continue; + } + if (n < 1 || n > shieldedAddressInfoList.size()) { + System.out.println("Invalid number of " + num); + System.out.println("Please choose again between 1 and " + shieldedAddressInfoList.size()); + continue; + } + return shieldedAddressInfoList.get(n - 1); } - return shieldedAddressInfoList.get(n - 1); } } From d051bd26523a70964c9a13e903e0941d3072c88a Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 6 Jan 2020 17:12:54 +0800 Subject: [PATCH 239/445] update market protocol --- src/main/java/org/tron/walletcli/Client.java | 2 +- src/main/protos/api/api.proto | 3 ++ src/main/protos/core/Contract.proto | 1 + src/main/protos/core/Tron.proto | 42 +++++++++++++++----- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index a934a99b1..d566d34f2 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2854,7 +2854,7 @@ private void getMarketPriceByPair (String[] parameters) { if (parameters == null || parameters.length != 2) { System.out.println("Using GetMarketPriceByPair command needs 2 parameters like: "); System.out.println( - "GetMarketPriceByPair ownerAddress"); + "GetMarketPriceByPair sellTokenId buyTokenId"); return; } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index e236b0c67..6b732a923 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -706,6 +706,9 @@ service Wallet { rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } + + rpc GetMarketPositionKey (MarketSellAssetContract) returns (MarketOrderPosition) { + } }; service WalletSolidity { diff --git a/src/main/protos/core/Contract.proto b/src/main/protos/core/Contract.proto index 3bff73a85..1df3dcf3c 100644 --- a/src/main/protos/core/Contract.proto +++ b/src/main/protos/core/Contract.proto @@ -347,6 +347,7 @@ message MarketSellAssetContract { int64 sell_token_quantity = 6; bytes buy_token_id = 5; int64 buy_token_quantity = 7;//min to receive + bytes pre_price_key = 8 ;//order price position } message MarketCancelOrderContract { diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 23215b0e1..9fb0599b1 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -698,6 +698,9 @@ message MarketOrder { CANCELED = 2; } State state = 10; + + bytes prev = 11; + bytes next = 12; } message MarketOrderList { @@ -711,23 +714,40 @@ message MarketOrderPair{ message MarketAccountOrder { bytes owner_address = 1; - // repeated MarketOrder orders = 2; - repeated bytes orders = 2; + repeated bytes orders = 2; // order_id list int64 count = 3; + } -message MarketPriceList { +message MarketOrderPosition { + //if price list is empty or price exist, pre_price_key = null + bool price_list_not_empty = 1; + bool price_exist = 2; + bytes pre_price_key = 3; +} + +message MarketPrice { + int64 sell_token_quantity = 1; + int64 buy_token_quantity = 2; + bytes prev = 3; + bytes next = 4; +} + +message MarketPriceLinkedList { bytes sell_token_id = 1; bytes buy_token_id = 2; - - message MarketPrice { - int64 sell_token_quantity = 1; - int64 buy_token_quantity = 2; - } - repeated MarketPrice prices = 5; + MarketPrice bestPrice = 3; } +message MarketPriceList { + bytes sell_token_id = 1; + bytes buy_token_id = 2; + repeated MarketPrice prices = 3; +} message MarketOrderIdList { - repeated bytes orders = 1; -} + // repeated bytes orders = 1; + + bytes head = 2; + bytes tail = 3; +} \ No newline at end of file From 9d85bdb43356d233ca65681f54a5df617ae29520 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Mon, 6 Jan 2020 17:41:21 +0800 Subject: [PATCH 240/445] review modify and wait merge --- .../org/tron/common/crypto/Sha256Hash.java | 35 +- .../java/org/tron/common/crypto/sm2/SM2.java | 2236 ++++++++--------- .../org/tron/common/crypto/sm2/SM2Signer.java | 503 ++-- .../tron/common/utils/TransactionUtils.java | 160 +- .../tron/demo/TransactionSignDemoForSM2.java | 36 +- .../java/org/tron/keystore/WalletUtils.java | 111 +- .../java/org/tron/walletserver/WalletApi.java | 766 +++--- src/main/resources/config-back.conf | 20 - 8 files changed, 1864 insertions(+), 2003 deletions(-) delete mode 100644 src/main/resources/config-back.conf diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java index a41b382e8..de00defd6 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Hash.java @@ -34,7 +34,6 @@ import static com.google.common.base.Preconditions.checkArgument; - /** * A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be * used as keys in a map. It also checks that the length is correct and provides a bit more type @@ -42,7 +41,6 @@ */ public class Sha256Hash implements Serializable, Comparable { - public static final int LENGTH = 32; // bytes public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); @@ -50,7 +48,6 @@ public class Sha256Hash implements Serializable, Comparable { private long blockNum; - private byte[] generateBlockId(long blockNum, Sha256Hash blockHash) { byte[] numBytes = Longs.toByteArray(blockNum); byte[] hash = blockHash.getBytes(); @@ -65,7 +62,7 @@ private byte[] generateBlockId(long blockNum, byte[] blockHash) { return hash; } - public long getBlockNum(){ + public long getBlockNum() { return blockNum; } @@ -83,9 +80,7 @@ public Sha256Hash(long num, Sha256Hash hash) { this.blockNum = num; } - /** - * Use {@link #wrap(byte[])} instead. - */ + /** Use {@link #wrap(byte[])} instead. */ @Deprecated public Sha256Hash(byte[] rawHashBytes) { checkArgument(rawHashBytes.length == LENGTH); @@ -108,9 +103,7 @@ public static Sha256Hash wrap(ByteString rawHashByteString) { return wrap(rawHashByteString.toByteArray()); } - /** - * Use {@link #of(byte[])} instead: this old name is ambiguous. - */ + /** Use {@link #of(byte[])} instead: this old name is ambiguous. */ @Deprecated public static Sha256Hash create(byte[] contents) { return of(contents); @@ -142,9 +135,7 @@ public static Sha256Hash of(File file) throws IOException { } } - /** - * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. - */ + /** Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. */ @Deprecated public static Sha256Hash createDouble(byte[] contents) { return twiceOf(contents); @@ -170,7 +161,7 @@ public static MessageDigest newDigest() { try { return MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Can't happen. + throw new RuntimeException(e); // Can't happen. } } @@ -226,8 +217,8 @@ public static byte[] hashTwice(byte[] input, int offset, int length) { * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the * two ranges and then passing the result to {@link #hashTwice(byte[])}. */ - public static byte[] hashTwice(byte[] input1, int offset1, int length1, - byte[] input2, int offset2, int length2) { + public static byte[] hashTwice( + byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) { MessageDigest digest = newDigest(); digest.update(input1, offset1, length1); digest.update(input2, offset2, length2); @@ -258,13 +249,11 @@ public String toString() { @Override public int hashCode() { // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints - .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + return Ints.fromBytes( + bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); } - /** - * Returns the bytes interpreted as a positive integer. - */ + /** Returns the bytes interpreted as a positive integer. */ public BigInteger toBigInteger() { return new BigInteger(1, bytes); } @@ -277,9 +266,7 @@ public byte[] getBytes() { return bytes; } - /** - * For pb return ByteString. - */ + /** For pb return ByteString. */ public ByteString getByteString() { return ByteString.copyFrom(bytes); } diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2.java b/src/main/java/org/tron/common/crypto/sm2/SM2.java index 1236dd65c..3a323c579 100644 --- a/src/main/java/org/tron/common/crypto/sm2/SM2.java +++ b/src/main/java/org/tron/common/crypto/sm2/SM2.java @@ -6,24 +6,23 @@ import org.spongycastle.asn1.DLSequence; import org.spongycastle.asn1.x9.X9IntegerConverter; import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.generators.ECKeyPairGenerator; -import org.spongycastle.crypto.params.*; -import org.spongycastle.crypto.signers.*; +import org.spongycastle.crypto.params.ECDomainParameters; +import org.spongycastle.crypto.params.ECKeyGenerationParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.spongycastle.jce.spec.ECParameterSpec; import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.math.ec.*; +import org.spongycastle.math.ec.ECAlgorithms; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; import org.spongycastle.util.encoders.Base64; import org.spongycastle.util.encoders.Hex; -import org.spongycastle.util.test.TestRandomBigInteger; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.SignatureInterface; import org.tron.common.crypto.jce.ECKeyFactory; -import org.tron.common.crypto.jce.ECSignatureFactory; import org.tron.common.crypto.jce.TronCastleProvider; import org.tron.common.utils.ByteUtil; @@ -32,7 +31,9 @@ import java.io.Serializable; import java.math.BigInteger; import java.nio.charset.Charset; -import java.security.*; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.SignatureException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; @@ -42,1204 +43,1107 @@ import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; import static org.tron.common.utils.Hash.computeAddress; -/** - * Implement Chinese Commercial Cryptographic Standard of SM2 - * - */ +/** Implement Chinese Commercial Cryptographic Standard of SM2 */ @Slf4j(topic = "crypto") public class SM2 implements Serializable, SignInterface { - private static BigInteger SM2_N = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); - private static BigInteger SM2_P = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); - private static BigInteger SM2_A = new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); - private static BigInteger SM2_B = new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); - private static BigInteger SM2_GX = new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); - private static BigInteger SM2_GY = new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); -// -// private static BigInteger SM2_N = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16); -// private static BigInteger SM2_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16); -// private static BigInteger SM2_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16); -// private static BigInteger SM2_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16); -// private static BigInteger SM2_GX = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16); -// private static BigInteger SM2_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16); - - private static ECDomainParameters ecc_param; - private static ECParameterSpec ecc_spec; - private static ECCurve.Fp curve; - private static ECPoint ecc_point_g; - - private static final SecureRandom secureRandom; - - - - static { - secureRandom = new SecureRandom(); - curve = new ECCurve.Fp(SM2_P, SM2_A, SM2_B); - ecc_point_g = curve.createPoint(SM2_GX, SM2_GY); - ecc_param = new ECDomainParameters(curve, ecc_point_g, SM2_N); - ecc_spec = new ECParameterSpec(curve, ecc_point_g, SM2_N); - } - - protected final ECPoint pub; - - private final PrivateKey privKey; - -// private final SM2KeyPair keyPair; - - // Transient because it's calculated on demand. - private transient byte[] pubKeyHash; - private transient byte[] nodeId; - -// private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); -// private byte[] userID; - - public SM2() { - this(secureRandom); - } - /** - * Generates an entirely new keypair. - * - *

BouncyCastle will be used as the Java Security Provider - */ - - - /** - * Generate a new keypair using the given Java Security Provider. - * - *

All private key operations will use the provider. - */ - public SM2(SecureRandom secureRandom) { - - ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecc_param, secureRandom); - ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); - keyPairGenerator.init(ecKeyGenerationParameters); - AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair(); - ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) kp.getPrivate(); - ECPublicKeyParameters ecpub = (ECPublicKeyParameters) kp.getPublic(); - - BigInteger privateKey = ecpriv.getD(); - this.privKey = privateKeyFromBigInteger(privateKey); - this.pub = ecpub.getQ(); -// this.keyPair = new SM2KeyPair(pub.getEncoded(false),privateKey.toByteArray()); - -// CipherParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey, ecc_param); -// CipherParameters baseParam; -// -// if (privateKeyParameters instanceof ParametersWithID) -// { -// baseParam = ((ParametersWithID)privateKeyParameters).getParameters(); -// userID = ((ParametersWithID)privateKeyParameters).getID(); -// } -// else -// { -// baseParam = privateKeyParameters; -// userID = new byte[0]; -// } -// this.kCalculator.init(SM2_N, secureRandom); - } - - public SM2 (byte[] key, boolean isPrivateKey) { - if (isPrivateKey) { - BigInteger pk = new BigInteger(1, key); - this.privKey = privateKeyFromBigInteger(pk); - this.pub = ecc_param.getG().multiply(pk); - } else { - this.privKey = null; - this.pub = ecc_param.getCurve().decodePoint(key); - } - } - - - /** - * Pair a private key with a public EC point. - * - *

All private key operations will use the provider. - */ - - public SM2(@Nullable PrivateKey privKey, ECPoint pub) { - - if (privKey == null || isECPrivateKey(privKey)) { - this.privKey = privKey; - } else { - throw new IllegalArgumentException( - "Expected EC private key, given a private key object with" + - " class " - + privKey.getClass().toString() + - " and algorithm " - + privKey.getAlgorithm()); - } - - if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); - } else { - this.pub = pub; - } - } - - /** - * Pair a private key integer with a public EC point - * - */ - public SM2(@Nullable BigInteger priv, ECPoint pub) { - this( - privateKeyFromBigInteger(priv), - pub - ); - -// this.privKey = privateKeyFromBigInteger(priv); -// this.pub = pub; -// this.keyPair = new SM2KeyPair(pub.getEncoded(false), priv.toByteArray()); - } - - /** - * Convert a BigInteger into a PrivateKey object - * - * @param priv - * @return - */ - private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - ecc_spec)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } - } - } - - /* Test if a generic private key is an EC private key - * - * it is not sufficient to check that privKey is a subtype of ECPrivateKey - * as the SunPKCS11 Provider will return a generic PrivateKey instance - * a fallback that covers this case is to check the key algorithm - */ - private static boolean isECPrivateKey(PrivateKey privKey) { - return privKey instanceof ECPrivateKey || privKey.getAlgorithm() - .equals("EC"); - } - - /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint - */ - private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { - final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); - final BigInteger xCoord = publicPointW.getAffineX(); - final BigInteger yCoord = publicPointW.getAffineY(); - - return ecc_param.getCurve().createPoint(xCoord, yCoord); - } - - - /** - * Utility for compressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param uncompressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint compressPoint(ECPoint uncompressed) { - return ecc_param.getCurve().decodePoint(uncompressed.getEncoded(true)); - } - - /** - * Utility for decompressing an elliptic curve point. Returns the same point if it's already - * compressed. See the ECKey class docs for a discussion of point compression. - * - * @param compressed - - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public static ECPoint decompressPoint(ECPoint compressed) { - return ecc_param.getCurve().decodePoint(compressed.getEncoded(false)); - } - - /** - * Creates an SM2 given the private key only. - * - * @param privKey - - * @return - - */ - public static SM2 fromPrivate(BigInteger privKey) { - return new SM2(privKey, ecc_param.getG().multiply(privKey)); - } - - /** - * Creates an SM2 given the private key only. - * - * @param privKeyBytes - - * @return - - */ - public static SM2 fromPrivate(byte[] privKeyBytes) { - return fromPrivate(new BigInteger(1, privKeyBytes)); - } + private static BigInteger SM2_N = + new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); + private static BigInteger SM2_P = + new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); + private static BigInteger SM2_A = + new BigInteger("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); + private static BigInteger SM2_B = + new BigInteger("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); + private static BigInteger SM2_GX = + new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + private static BigInteger SM2_GY = + new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); + // + // private static BigInteger SM2_N = new + // BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16); + // private static BigInteger SM2_P = new + // BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16); + // private static BigInteger SM2_A = new + // BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16); + // private static BigInteger SM2_B = new + // BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16); + // private static BigInteger SM2_GX = new + // BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16); + // private static BigInteger SM2_GY = new + // BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16); + + private static ECDomainParameters ecc_param; + private static ECParameterSpec ecc_spec; + private static ECCurve.Fp curve; + private static ECPoint ecc_point_g; + + private static final SecureRandom secureRandom; + + static { + secureRandom = new SecureRandom(); + curve = new ECCurve.Fp(SM2_P, SM2_A, SM2_B); + ecc_point_g = curve.createPoint(SM2_GX, SM2_GY); + ecc_param = new ECDomainParameters(curve, ecc_point_g, SM2_N); + ecc_spec = new ECParameterSpec(curve, ecc_point_g, SM2_N); + } + + protected final ECPoint pub; + + private final PrivateKey privKey; + + // Transient because it's calculated on demand. + private transient byte[] pubKeyHash; + private transient byte[] nodeId; + + public SM2() { + this(secureRandom); + } + /** + * Generates an entirely new keypair. + * + *

BouncyCastle will be used as the Java Security Provider + */ + + /** + * Generate a new keypair using the given Java Security Provider. + * + *

All private key operations will use the provider. + */ + public SM2(SecureRandom secureRandom) { + + ECKeyGenerationParameters ecKeyGenerationParameters = + new ECKeyGenerationParameters(ecc_param, secureRandom); + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + keyPairGenerator.init(ecKeyGenerationParameters); + AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) kp.getPrivate(); + ECPublicKeyParameters ecpub = (ECPublicKeyParameters) kp.getPublic(); + + BigInteger privateKey = ecpriv.getD(); + this.privKey = privateKeyFromBigInteger(privateKey); + this.pub = ecpub.getQ(); + } + + public SM2(byte[] key, boolean isPrivateKey) { + if (isPrivateKey) { + BigInteger pk = new BigInteger(1, key); + this.privKey = privateKeyFromBigInteger(pk); + this.pub = ecc_param.getG().multiply(pk); + } else { + this.privKey = null; + this.pub = ecc_param.getCurve().decodePoint(key); + } + } + + /** + * Pair a private key with a public EC point. + * + *

All private key operations will use the provider. + */ + public SM2(@Nullable PrivateKey privKey, ECPoint pub) { + + if (privKey == null || isECPrivateKey(privKey)) { + this.privKey = privKey; + } else { + throw new IllegalArgumentException( + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + + privKey.getAlgorithm()); + } + + if (pub == null) { + throw new IllegalArgumentException("Public key may not be null"); + } else { + this.pub = pub; + } + } + + /** Pair a private key integer with a public EC point */ + public SM2(@Nullable BigInteger priv, ECPoint pub) { + this(privateKeyFromBigInteger(priv), pub); + } + + /** + * Convert a BigInteger into a PrivateKey object + * + * @param priv + * @return + */ + private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { + if (priv == null) { + return null; + } else { + try { + return ECKeyFactory.getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, ecc_spec)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + } + + /* Test if a generic private key is an EC private key + * + * it is not sufficient to check that privKey is a subtype of ECPrivateKey + * as the SunPKCS11 Provider will return a generic PrivateKey instance + * a fallback that covers this case is to check the key algorithm + */ + private static boolean isECPrivateKey(PrivateKey privKey) { + return privKey instanceof ECPrivateKey || privKey.getAlgorithm().equals("EC"); + } + + /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + */ + private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { + final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); + final BigInteger xCoord = publicPointW.getAffineX(); + final BigInteger yCoord = publicPointW.getAffineY(); + + return ecc_param.getCurve().createPoint(xCoord, yCoord); + } + + /** + * Utility for compressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param uncompressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint compressPoint(ECPoint uncompressed) { + return ecc_param.getCurve().decodePoint(uncompressed.getEncoded(true)); + } + + /** + * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * compressed. See the ECKey class docs for a discussion of point compression. + * + * @param compressed - + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public static ECPoint decompressPoint(ECPoint compressed) { + return ecc_param.getCurve().decodePoint(compressed.getEncoded(false)); + } + + /** + * Creates an SM2 given the private key only. + * + * @param privKey - + * @return - + */ + public static SM2 fromPrivate(BigInteger privKey) { + return new SM2(privKey, ecc_param.getG().multiply(privKey)); + } + + /** + * Creates an SM2 given the private key only. + * + * @param privKeyBytes - + * @return - + */ + public static SM2 fromPrivate(byte[] privKeyBytes) { + return fromPrivate(new BigInteger(1, privKeyBytes)); + } + + /** + * Creates an SM2 that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of pub will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static SM2 fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub) { + return new SM2(priv, pub); + } + + /** + * Creates an SM2 that simply trusts the caller to ensure that point is really the result of + * multiplying the generator point by the private key. This is used to speed things up when you + * know you have the right values already. The compression state of the point will be preserved. + * + * @param priv - + * @param pub - + * @return - + */ + public static SM2 fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] pub) { + check(priv != null, "Private key must not be null"); + check(pub != null, "Public key must not be null"); + return new SM2(new BigInteger(1, priv), ecc_param.getCurve().decodePoint(pub)); + } + + /** + * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static SM2 fromPublicOnly(ECPoint pub) { + return new SM2((PrivateKey) null, pub); + } + + /** + * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * encoded point. The compression state of pub will be preserved. + * + * @param pub - + * @return - + */ + public static SM2 fromPublicOnly(byte[] pub) { + return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); + } + + /** + * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * use new BigInteger(1, bytes); + * + * @param privKey - + * @param compressed - + * @return - + */ + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { + ECPoint point = ecc_param.getG().multiply(privKey); + return point.getEncoded(compressed); + } + + /** + * Compute the encoded X, Y coordinates of a public point. + * + *

This is the encoded public key without the leading byte. + * + * @param pubPoint a public point + * @return 64-byte X,Y point pair + */ + public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { + final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); + return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); + } + + /** + * Recover the public key from an encoded node id. + * + * @param nodeId a 64-byte X,Y point pair + */ + public static SM2 fromNodeId(byte[] nodeId) { + check(nodeId.length == 64, "Expected a 64 byte node id"); + byte[] pubBytes = new byte[65]; + System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); + pubBytes[0] = 0x04; // uncompressed + return SM2.fromPublicOnly(pubBytes); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, String signatureBase64) + throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you getData back from Bouncy Castle if base64 doesn't + // decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) { + throw new SignatureException( + "Signature truncated, expected 65 " + "bytes and got " + signatureEncoded.length); + } + + return signatureToKeyBytes( + messageHash, + SM2Signature.fromComponents( + Arrays.copyOfRange(signatureEncoded, 1, 33), + Arrays.copyOfRange(signatureEncoded, 33, 65), + (byte) (signatureEncoded[0] & 0xFF))); + } + + public static byte[] signatureToKeyBytes(byte[] messageHash, SM2Signature sig) + throws SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + messageHash.length); + int header = sig.v; + // The header byte: 0x1B = first key with even y, 0x1C = first key + // with odd y, + // 0x1D = second key with even y, 0x1E = second key + // with odd y + if (header < 27 || header > 34) { + throw new SignatureException("Header byte out of range: " + header); + } + if (header >= 31) { + header -= 4; + } + int recId = header - 27; + byte[] key = recoverPubBytesFromSignature(recId, sig, messageHash); + if (key == null) { + throw new SignatureException("Could not recover public key from " + "signature"); + } + return key; + } + + @Override + public byte[] hash(byte[] message) { + SM2Signer signer = this.getSM2SignerForHash(); + return signer.generateSM3Hash(message); + } + + @Override + public byte[] getPrivateKey() { + return getPrivKeyBytes(); + } + + /** + * Gets the encoded public key value. + * + * @return 65-byte encoded public key + */ + @Override + public byte[] getPubKey() { + return pub.getEncoded(/* compressed */ false); + } + + /** + * Gets the address form of the public key. + * + * @return 21-byte address + */ + @Override + public byte[] getAddress() { + if (pubKeyHash == null) { + pubKeyHash = computeAddress(this.pub); + } + return pubKeyHash; + } + + @Override + public byte[] signToAddress(byte[] messageHash, String signatureBase64) + throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, String signatureBase64) + throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, signatureBase64)); + } + + /** + * Compute the address of the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return 20-byte address + */ + public static byte[] signatureToAddress(byte[] messageHash, SM2Signature sig) + throws SignatureException { + return computeAddress(signatureToKeyBytes(messageHash, sig)); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param signatureBase64 Base-64 encoded signature + * @return ECKey + */ + public static SM2 signatureToKey(byte[] messageHash, String signatureBase64) + throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, signatureBase64); + return fromPublicOnly(keyBytes); + } + + /** + * Compute the key that signed the given signature. + * + * @param messageHash 32-byte hash of message + * @param sig - + * @return ECKey + */ + public static SM2 signatureToKey(byte[] messageHash, SM2Signature sig) throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); + return fromPublicOnly(keyBytes); + } + + /** + * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which including the v + * + * @param messageHash - + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public SM2Signature sign(byte[] messageHash) { + if (messageHash.length != 32) { + throw new IllegalArgumentException( + "Expected 32 byte input to " + "SM2 signature, not " + messageHash.length); + } + // No decryption of private key required. + SM2Signer signer = getSigner(); + BigInteger[] componets = signer.generateHashSignature(messageHash); + + SM2Signature sig = new SM2.SM2Signature(componets[0], componets[1]); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException( + "Could not construct a recoverable key" + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * SM2Signature + * + * @param input to sign + * @return SM2Signature signature that contains the R and S components + */ + public String signHash(byte[] input) { + return sign(input).toBase64(); + } + + public byte[] Base64toBytes(String signature) { + byte[] signData = Base64.decode(signature); + byte first = (byte) (signData[0] - 27); + byte[] temp = Arrays.copyOfRange(signData, 1, 65); + return ByteUtil.appendByte(temp, first); + } + + /** + * Takes the message of data and returns the SM2 signature + * + * @param message - + * @param userID + * @return - + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public SM2Signature signMessage(byte[] message, @Nullable String userID) { + SM2Signature sig = signMsg(message, userID); + // Now we have to work backwards to figure out the recId needed to + // recover the signature. + int recId = -1; + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + + SM2Signer signer = getSigner(); + byte[] messageHash = signer.generateSM3Hash(message); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + recId = i; + break; + } + } + if (recId == -1) { + throw new RuntimeException( + "Could not construct a recoverable key" + ". This should never happen."); + } + sig.v = (byte) (recId + 27); + return sig; + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers and putData them in + * SM2Signature + * + * @param msg to sign + * @param userID + * @return SM2Signature signature that contains the R and S components + */ + public SM2.SM2Signature signMsg(byte[] msg, @Nullable String userID) { + if (null == msg) { + throw new IllegalArgumentException("Expected signature message of " + "SM2 is null"); + } + // No decryption of private key required. + SM2Signer signer = getSigner(); + BigInteger[] componets = signer.generateSignature(msg); + return new SM2.SM2Signature(componets[0], componets[1]); + } + + private SM2Signer getSigner() { + SM2Signer signer = new SM2Signer(); + BigInteger d = getPrivKey(); + ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); + signer.init(true, privateKeyParameters); + return signer; + } + + /** + * used to generate the SM3 hash for SM2 signature generation or verification + * + * @return + */ + public SM2Signer getSM2SignerForHash() { + SM2Signer signer = new SM2Signer(); + // BigInteger d = getPrivKey(); + ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub, ecc_param); + signer.init(false, publicKeyParameters); + return signer; + } + + /** + * Given the components of a signature and a selector value, recover and return the public key + * that generated the signature + * + * @param recId + * @param sig + * @param messageHash + * @return + */ + @Nullable + public static byte[] recoverPubBytesFromSignature( + int recId, SM2Signature sig, byte[] messageHash) { + check(recId >= 0, "recId must be positive"); + check(sig.r.signum() >= 0, "r must be positive"); + check(sig.s.signum() >= 0, "s must be positive"); + check(messageHash != null, "messageHash must not be null"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside + // this function) + // 1.1 Let x = r + jn + BigInteger n = ecc_param.getN(); // Curve order. + BigInteger prime = curve.getQ(); + BigInteger i = BigInteger.valueOf((long) recId / 2); + + BigInteger e = new BigInteger(1, messageHash); + BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n + x = x.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen + // using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an + // elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this + // conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed + // public key. + ECCurve.Fp curve = (ECCurve.Fp) ecc_param.getCurve(); + // Bouncy Castle is not consistent + // about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything + // takes place modulo Q. + return null; + } + // Compressed allKeys require you to know an extra bit of data about the + // y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of + // Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + + // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) + BigInteger srInv = sig.s.add(sig.r).modInverse(n); + BigInteger sNeg = BigInteger.ZERO.subtract(sig.s).mod(n); + BigInteger coeff = srInv.multiply(sNeg).mod(n); + + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(ecc_param.getG(), coeff, R, srInv); + return q.getEncoded(/* compressed */ false); + } + + /** + * Decompress a compressed public key (x co-ord and low-bit of y-coord). + * + * @param xBN - + * @param yBit - + * @return - + */ + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(ecc_param.getCurve())); + compEnc[0] = (byte) (yBit ? 0x03 : 0x02); + return ecc_param.getCurve().decodePoint(compEnc); + } + + private static void check(boolean test, String message) { + if (!test) { + throw new IllegalArgumentException(message); + } + } + + /** + * Verifies the given SM2 signature against the message bytes using the public key bytes. + * + *

+ * + *

When using native SM2 verification, data must be 32 bytes, and no element may be larger than + * 520 bytes. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, SM2Signature signature, byte[] pub) { + SM2Signer signer = new SM2Signer(); + ECPublicKeyParameters params = + new ECPublicKeyParameters(ecc_param.getCurve().decodePoint(pub), ecc_param); + signer.init(false, params); + try { + return signer.verifyHashSignature(data, signature.r, signature.s); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verify(byte[] data, byte[] signature, byte[] pub) { + return verify(data, SM2Signature.decodeFromDER(signature), pub); + } + + /** + * Verifies the given SM2 signature against the message bytes using the public key bytes. + * + * @param msg the message data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verifyMessage( + byte[] msg, SM2Signature signature, byte[] pub, @Nullable String userID) { + SM2Signer signer = new SM2Signer(); + ECPublicKeyParameters params = + new ECPublicKeyParameters(ecc_param.getCurve().decodePoint(pub), ecc_param); + signer.init(false, params); + try { + return signer.verifySignature(msg, signature.r, signature.s, userID); + } catch (NullPointerException npe) { + // Bouncy Castle contains a bug that can cause NPEs given + // specially crafted signatures. + // Those signatures are inherently invalid/attack sigs so we just + // fail them here rather than crash the thread. + logger.error("Caught NPE inside bouncy castle", npe); + return false; + } + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param msg the message data to verify. + * @param signature signature. + * @param pub The public key bytes to use. + * @return - + */ + public static boolean verifyMessage( + byte[] msg, byte[] signature, byte[] pub, @Nullable String userID) { + return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); + } + + /** + * Returns true if the given pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @param pubkey - + * @return - + */ + public static boolean isPubKeyCanonical(byte[] pubkey) { + if (pubkey[0] == 0x04) { + // Uncompressed pubkey + return pubkey.length == 65; + } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { + // Compressed pubkey + return pubkey.length == 33; + } else { + return false; + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return 20-byte address + */ + @Nullable + public static byte[] recoverAddressFromSignature( + int recId, SM2Signature sig, byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, messageHash); + if (pubBytes == null) { + return null; + } else { + return computeAddress(pubBytes); + } + } + + /** + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. + * @param messageHash Hash of the data that was signed. + * @return ECKey + */ + @Nullable + public static SM2 recoverFromSignature(int recId, SM2Signature sig, byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, messageHash); + if (pubBytes == null) { + return null; + } else { + return fromPublicOnly(pubBytes); + } + } + + /** + * Returns a copy of this key, but with the public point represented in uncompressed form. + * Normally you would never need this: it's for specialised scenarios or when backwards + * compatibility in encoded form is necessary. + * + * @return - + * @deprecated per-point compression property will be removed in Bouncy Castle + */ + public SM2 decompress() { + if (!pub.isCompressed()) { + return this; + } else { + return new SM2(this.privKey, decompressPoint(pub)); + } + } + + /** @deprecated per-point compression property will be removed in Bouncy Castle */ + public SM2 compress() { + if (pub.isCompressed()) { + return this; + } else { + return new SM2(this.privKey, compressPoint(pub)); + } + } + + /** + * Returns true if this key doesn't have access to private key bytes. This may be because it was + * never given any private key bytes to begin with (a watching key). + * + * @return - + */ + public boolean isPubKeyOnly() { + return privKey == null; + } + + /** + * Returns true if this key has access to private key bytes. Does the opposite of {@link + * #isPubKeyOnly()}. + * + * @return - + */ + public boolean hasPrivKey() { + return privKey != null; + } + + /** Generates the NodeID based on this key, that is the public key without first format byte */ + public byte[] getNodeId() { + if (nodeId == null) { + nodeId = pubBytesWithoutFormat(this.pub); + } + return nodeId; + } + + /** + * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * + * @return - + */ + public ECPoint getPubKeyPoint() { + return pub; + } + + /** + * Gets the private key in the form of an integer field element. The public key is derived by + * performing EC point addition this number of times (i.e. point multiplying). + * + * @return - + * @throws IllegalStateException if the private key bytes are not available. + */ + public BigInteger getPrivKey() { + if (privKey == null) { + throw new ECKey.MissingPrivateKeyException(); + } else if (privKey instanceof BCECPrivateKey) { + return ((BCECPrivateKey) privKey).getD(); + } else { + throw new ECKey.MissingPrivateKeyException(); + } + } + + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 + * bytes, not 64. + * + * @return - + */ + public boolean isCompressed() { + return pub.isCompressed(); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); + return b.toString(); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need + * the private key it is better for security reasons to just use toString(). + * + * @return - + */ + public String toStringWithPrivate() { + StringBuilder b = new StringBuilder(); + b.append(toString()); + if (privKey != null && privKey instanceof BCECPrivateKey) { + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) privKey).getD().toByteArray())); + } + return b.toString(); + } + + /** + * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * + * @param data Hash of the data to verify. + * @param signature signature. + * @return - + */ + public boolean verify(byte[] data, byte[] signature) { + return SM2.verify(data, signature, getPubKey()); + } + + /** + * Verifies the given R/S pair (signature) against a hash using the public key. + * + * @param sigHash - + * @param signature - + * @return - + */ + public boolean verify(byte[] sigHash, SM2Signature signature) { + return SM2.verify(sigHash, signature, getPubKey()); + } + + /** + * Returns true if this pubkey is canonical, i.e. the correct length taking into account + * compression. + * + * @return - + */ + public boolean isPubKeyCanonical() { + return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); + } + + /** + * Returns a 32 byte array containing the private key, or null if the key is encrypted or public + * only + * + * @return - + */ + @Nullable + public byte[] getPrivKeyBytes() { + if (privKey == null) { + return null; + } else if (privKey instanceof BCECPrivateKey) { + return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); + } else { + return null; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + SM2 ecKey = (SM2) o; + + if (privKey != null && !privKey.equals(ecKey.privKey)) { + return false; + } + return pub == null || pub.equals(ecKey.pub); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getPubKey()); + } + + public static class SM2Signature implements SignatureInterface { + + /** The two components of the signature. */ + public final BigInteger r, s; + + public byte v; /** - * Creates an SM2 that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of pub will be preserved. + * Constructs a signature with the given components. Does NOT automatically canonicalise the + * signature. * - * @param priv - - * @param pub - - * @return - + * @param r - + * @param s - */ - public static SM2 fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { - return new SM2(priv, pub); + public SM2Signature(BigInteger r, BigInteger s) { + this.r = r; + this.s = s; } - /** - * Creates an SM2 that simply trusts the caller to ensure that point is really the result of - * multiplying the generator point by the private key. This is used to speed things up when you - * know you have the right values already. The compression state of the point will be preserved. - * - * @param priv - - * @param pub - - * @return - - */ - public static SM2 fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] - pub) { - check(priv != null, "Private key must not be null"); - check(pub != null, "Public key must not be null"); - return new SM2(new BigInteger(1, priv), ecc_param.getCurve() - .decodePoint(pub)); + public SM2Signature(byte[] r, byte[] s, byte v) { + this.r = new BigInteger(1, r); + this.s = new BigInteger(1, s); + this.v = v; } /** - * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. + * t * - * @param pub - * @return - */ - public static SM2 fromPublicOnly(ECPoint pub) { - return new SM2((PrivateKey) null, pub); + private static SM2.SM2Signature fromComponents(byte[] r, byte[] s) { + return new SM2.SM2Signature(new BigInteger(1, r), new BigInteger(1, s)); } /** - * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given - * encoded point. The compression state of pub will be preserved. - * - * @param pub - + * @param r - + * @param s - + * @param v - * @return - */ - public static SM2 fromPublicOnly(byte[] pub) { - return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); - } - - /** - * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, - * use new BigInteger(1, bytes); - * - * @param privKey - - * @param compressed - - * @return - - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = ecc_param.getG().multiply(privKey); - return point.getEncoded(compressed); - } - - /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. - * - * @param pubPoint a public point - * @return 64-byte X,Y point pair - */ - public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { - final byte[] pubBytes = pubPoint.getEncoded(/* uncompressed */ false); - return Arrays.copyOfRange(pubBytes, 1, pubBytes.length); - } - - /** - * Recover the public key from an encoded node id. - * - * @param nodeId a 64-byte X,Y point pair - */ - public static SM2 fromNodeId(byte[] nodeId) { - check(nodeId.length == 64, "Expected a 64 byte node id"); - byte[] pubBytes = new byte[65]; - System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); - pubBytes[0] = 0x04; // uncompressed - return SM2.fromPublicOnly(pubBytes); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { - byte[] signatureEncoded; - try { - signatureEncoded = Base64.decode(signatureBase64); - } catch (RuntimeException e) { - // This is what you getData back from Bouncy Castle if base64 doesn't - // decode :( - throw new SignatureException("Could not decode base64", e); - } - // Parse the signature bytes into r/s and the selector value. - if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); - } - - return signatureToKeyBytes( - messageHash, - SM2Signature.fromComponents( - Arrays.copyOfRange(signatureEncoded, 1, 33), - Arrays.copyOfRange(signatureEncoded, 33, 65), - (byte) (signatureEncoded[0] & 0xFF))); - } - - public static byte[] signatureToKeyBytes(byte[] messageHash, - SM2Signature sig) throws - SignatureException { - check(messageHash.length == 32, "messageHash argument has length " + - messageHash.length); - int header = sig.v; - // The header byte: 0x1B = first key with even y, 0x1C = first key - // with odd y, - // 0x1D = second key with even y, 0x1E = second key - // with odd y - if (header < 27 || header > 34) { - throw new SignatureException("Header byte out of range: " + header); - } - if (header >= 31) { - header -= 4; - } - int recId = header - 27; - byte[] key = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (key == null) { - throw new SignatureException("Could not recover public key from " + - "signature"); - } - return key; - } - - - @Override - public byte[] hash(byte[] message) { - SM2Signer signer = this.getSM2SignerForHash(); - return signer.generateSM3Hash(message); - } - - @Override - public byte[] getPrivateKey() { - return getPrivKeyBytes(); - } - - /** - * Gets the encoded public key value. - * - * @return 65-byte encoded public key - */ - @Override - public byte[] getPubKey() { - return pub.getEncoded(/* compressed */ false); - } - - /** - * Gets the address form of the public key. - * - * @return 21-byte address - */ - @Override - public byte[] getAddress() { - if (pubKeyHash == null) { - pubKeyHash = computeAddress(this.pub); + public static SM2.SM2Signature fromComponents(byte[] r, byte[] s, byte v) { + SM2.SM2Signature signature = fromComponents(r, s); + signature.v = v; + return signature; + } + + public static boolean validateComponents(BigInteger r, BigInteger s, byte v) { + + if (v != 27 && v != 28) { + return false; + } + + if (isLessThan(r, BigInteger.ONE)) { + return false; + } + if (isLessThan(s, BigInteger.ONE)) { + return false; + } + + if (!isLessThan(r, SM2.SM2_N)) { + return false; + } + return isLessThan(s, SM2.SM2_N); + } + + public static SM2.SM2Signature decodeFromDER(byte[] bytes) { + ASN1InputStream decoder = null; + try { + decoder = new ASN1InputStream(bytes); + DLSequence seq = (DLSequence) decoder.readObject(); + if (seq == null) { + throw new RuntimeException("Reached past end of ASN.1 " + "stream."); } - return pubKeyHash; - } - - @Override - public byte[] signToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); - } - - /** - * Compute the address of the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return 20-byte address - */ - public static byte[] signatureToAddress(byte[] messageHash, - SM2Signature sig) throws - SignatureException { - return computeAddress(signatureToKeyBytes(messageHash, sig)); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param signatureBase64 Base-64 encoded signature - * @return ECKey - */ - public static SM2 signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, - signatureBase64); - return fromPublicOnly(keyBytes); - } - - /** - * Compute the key that signed the given signature. - * - * @param messageHash 32-byte hash of message - * @param sig - - * @return ECKey - */ - public static SM2 signatureToKey(byte[] messageHash, SM2Signature - sig) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); - return fromPublicOnly(keyBytes); - } - - /** - * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which including the v - * - * @param messageHash - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public SM2Signature sign(byte[] messageHash) { - if (messageHash.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "SM2 signature, not " + messageHash.length); - } - // No decryption of private key required. - SM2Signer signer = getSigner(); - BigInteger[] componets = signer.generateHashSignature(messageHash); - - SM2Signature sig = new SM2.SM2Signature(componets[0], componets[1]); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - for (int i = 0; i < 4; i++) { - byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * SM2Signature - * - * @param input to sign - * @return SM2Signature signature that contains the R and S components - */ - public String signHash(byte[] input) { - return sign(input).toBase64(); - } - - public byte[] Base64toBytes (String signature) { - byte[] signData = Base64.decode(signature); - byte first = (byte)(signData[0] - 27); - byte[] temp = Arrays.copyOfRange(signData,1,65); - return ByteUtil.appendByte(temp,first); - } - - /** - * Takes the message of data and returns the SM2 signature - * - * @param message - - * @param userID - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - public SM2Signature signMessage(byte[] message, @Nullable String userID) { - SM2Signature sig = signMsg(message, userID); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - - SM2Signer signer = getSigner(); - byte[] messageHash = signer.generateSM3Hash(message); - for (int i = 0; i < 4; i++) { - byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * SM2Signature - * - * @param msg to sign - * @param userID - * @return SM2Signature signature that contains the R and S components - */ - public SM2.SM2Signature signMsg(byte[] msg,@Nullable String userID) { - if (null == msg) { - throw new IllegalArgumentException("Expected signature message of " + - "SM2 is null"); - } - // No decryption of private key required. - SM2Signer signer = getSigner(); - BigInteger[] componets = signer.generateSignature(msg); - return new SM2.SM2Signature(componets[0], componets[1]); - } - - private SM2Signer getSigner() { - SM2Signer signer = new SM2Signer(); - BigInteger d = getPrivKey(); - ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); -// ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); - signer.init(true, privateKeyParameters); - return signer; - } - - /** - * used to generate the SM3 hash for SM2 signature generation or verification - * - * @return - */ - public SM2Signer getSM2SignerForHash() { - SM2Signer signer = new SM2Signer(); - //BigInteger d = getPrivKey(); - //ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); - ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub,ecc_param); - signer.init(false, publicKeyParameters); - return signer; - } - - - /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature - * - * @param recId - * @param sig - * @param messageHash - * @return - */ - @Nullable - public static byte[] recoverPubBytesFromSignature(int recId, - SM2Signature sig, - byte[] messageHash) { - check(recId >= 0, "recId must be positive"); - check(sig.r.signum() >= 0, "r must be positive"); - check(sig.s.signum() >= 0, "s must be positive"); - check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside - // this function) - // 1.1 Let x = r + jn - BigInteger n = ecc_param.getN(); // Curve order. - BigInteger prime = curve.getQ(); - BigInteger i = BigInteger.valueOf((long) recId / 2); - - BigInteger e = new BigInteger(1, messageHash); - BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n - x = x.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen - // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or - // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an - // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this - // conversion routine outputs “invalid”, then - // do another iteration of Step 1. - // - // More concisely, what these points mean is to use X as a compressed - // public key. - ECCurve.Fp curve = (ECCurve.Fp) ecc_param.getCurve(); - // Bouncy Castle is not consistent - // about the letter it uses for the prime. - if (x.compareTo(prime) >= 0) { - // Cannot have point co-ordinates larger than this as everything - // takes place modulo Q. - return null; - } - // Compressed allKeys require you to know an extra bit of data about the - // y-coord as there are two possibilities. - // So it's encoded in the recId. - ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of - // Step 1 (callers responsibility). - if (!R.multiply(n).isInfinity()) { - return null; - } - - // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) - BigInteger srInv = sig.s.add(sig.r).modInverse(n); - BigInteger sNeg = BigInteger.ZERO.subtract(sig.s).mod(n); - BigInteger coeff = srInv.multiply(sNeg).mod(n); - - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(ecc_param - .getG(), coeff, R, srInv); - return q.getEncoded(/* compressed */ false); - } - - /** - * Decompress a compressed public key (x co-ord and low-bit of y-coord). - * - * @param xBN - - * @param yBit - - * @return - - */ - - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { - X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(ecc_param - .getCurve())); - compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return ecc_param.getCurve().decodePoint(compEnc); - } - - private static void check(boolean test, String message) { - if (!test) { - throw new IllegalArgumentException(message); - } - } - - /** - *

Verifies the given SM2 signature against the message bytes using the public key bytes.

- *

When using native SM2 verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

- * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, SM2Signature signature, - byte[] pub) { - SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub),ecc_param); - signer.init(false, params); + ASN1Integer r, s; try { - return signer.verifyHashSignature(data, signature.r, signature.s); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - logger.error("Caught NPE inside bouncy castle", npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verify(byte[] data, byte[] signature, byte[] pub) { - return verify(data, SM2Signature.decodeFromDER(signature), pub); - } - - /** - *

Verifies the given SM2 signature against the message bytes using the public key bytes. - * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verifyMessage(byte[] msg, SM2Signature signature, - byte[] pub, @Nullable String userID) { - SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub),ecc_param); - signer.init(false, params); - try { - return signer.verifySignature(msg, signature.r, signature.s, userID); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - logger.error("Caught NPE inside bouncy castle", npe); - return false; - } - } - - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verifyMessage(byte[] msg, byte[] signature, byte[] pub, @Nullable String userID) { - return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); - } - - - /** - * Returns true if the given pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @param pubkey - - * @return - - */ - public static boolean isPubKeyCanonical(byte[] pubkey) { - if (pubkey[0] == 0x04) { - // Uncompressed pubkey - return pubkey.length == 65; - } else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) { - // Compressed pubkey - return pubkey.length == 33; - } else { - return false; - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return 20-byte address - */ - @Nullable - public static byte[] recoverAddressFromSignature(int recId, - SM2Signature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return computeAddress(pubBytes); - } - } - - /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. - * @return ECKey - */ - @Nullable - public static SM2 recoverFromSignature(int recId, SM2Signature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); - if (pubBytes == null) { - return null; - } else { - return fromPublicOnly(pubBytes); - } - } - - - - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public SM2 decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new SM2(this.privKey, decompressPoint(pub)); - } - } - - /** - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public SM2 compress() { - if (pub.isCompressed()) { - return this; - } else { - return new SM2(this.privKey, compressPoint(pub)); - } - } - - /** - * Returns true if this key doesn't have access to private key bytes. This may be because it was - * never given any private key bytes to begin with (a watching key). - * - * @return - - */ - public boolean isPubKeyOnly() { - return privKey == null; - } - - /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. - * - * @return - - */ - public boolean hasPrivKey() { - return privKey != null; - } - -// /** -// * Gets the address form of the public key. -// * -// * @return 21-byte address -// */ -// public byte[] getAddress() { -// if (pubKeyHash == null) { -// pubKeyHash = computeAddress(this.pub); -// } -// return pubKeyHash; -// } - - /** - * Generates the NodeID based on this key, that is the public key without first format byte - */ - public byte[] getNodeId() { - if (nodeId == null) { - nodeId = pubBytesWithoutFormat(this.pub); + r = (ASN1Integer) seq.getObjectAt(0); + s = (ASN1Integer) seq.getObjectAt(1); + } catch (ClassCastException e) { + throw new IllegalArgumentException(e); } - return nodeId; - } - -// /** -// * Gets the encoded public key value. -// * -// * @return 65-byte encoded public key -// */ -// public byte[] getPubKey() { -// return pub.getEncoded(/* compressed */ false); -// } - - /** - * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. - * - * @return - - */ - public ECPoint getPubKeyPoint() { - return pub; - } - - /** - * Gets the private key in the form of an integer field element. The public key is derived by - * performing EC point addition this number of times (i.e. point multiplying). - * - * @return - - * @throws IllegalStateException if the private key bytes are not available. - */ - public BigInteger getPrivKey() { - if (privKey == null) { - throw new ECKey.MissingPrivateKeyException(); - } else if (privKey instanceof BCECPrivateKey) { - return ((BCECPrivateKey) privKey).getD(); - } else { - throw new ECKey.MissingPrivateKeyException(); + // OpenSSL deviates from the DER spec by interpreting these + // values as unsigned, though they should not be + // Thus, we always use the positive versions. See: + // http://r6.ca/blog/20111119T211504Z.html + return new SM2.SM2Signature(r.getPositiveValue(), s.getPositiveValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (decoder != null) { + try { + decoder.close(); + } catch (IOException x) { + + } } + } } - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - return b.toString(); + public boolean validateComponents() { + return validateComponents(r, s, v); } - /** - * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need - * the private key it is better for security reasons to just use toString(). - * - * @return - - */ - public String toStringWithPrivate() { - StringBuilder b = new StringBuilder(); - b.append(toString()); - if (privKey != null && privKey instanceof BCECPrivateKey) { - b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) - privKey).getD().toByteArray())); - } - return b.toString(); + /** @return - */ + public String toBase64() { + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + // bytes for S + sigData[0] = v; + System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); + System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); } + public byte[] toByteArray() { + final byte fixedV = this.v >= 27 ? (byte) (this.v - 27) : this.v; - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @return - - */ - public boolean verify(byte[] data, byte[] signature) { - return SM2.verify(data, signature, getPubKey()); + return ByteUtil.merge( + ByteUtil.bigIntegerToBytes(this.r, 32), + ByteUtil.bigIntegerToBytes(this.s, 32), + new byte[] {fixedV}); } - /** - * Verifies the given R/S pair (signature) against a hash using the public key. - * - * @param sigHash - - * @param signature - - * @return - - */ - public boolean verify(byte[] sigHash, SM2Signature signature) { - return SM2.verify(sigHash, signature, getPubKey()); - } - - /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @return - - */ - public boolean isPubKeyCanonical() { - return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); - } - - /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only - * - * @return - - */ - @Nullable - public byte[] getPrivKeyBytes() { - if (privKey == null) { - return null; - } else if (privKey instanceof BCECPrivateKey) { - return bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32); - } else { - return null; - } + public String toHex() { + return Hex.toHexString(toByteArray()); } @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } - SM2 ecKey = (SM2) o; + SM2.SM2Signature signature = (SM2.SM2Signature) o; - if (privKey != null && !privKey.equals(ecKey.privKey)) { - return false; - } - return pub == null || pub.equals(ecKey.pub); + if (!r.equals(signature.r)) { + return false; + } + return s.equals(signature.s); } @Override public int hashCode() { - return Arrays.hashCode(getPubKey()); + int result = r.hashCode(); + result = 31 * result + s.hashCode(); + return result; } - - - public static class SM2Signature implements SignatureInterface { - - /** - * The two components of the signature. - */ - public final BigInteger r, s; - public byte v; - - /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. - * - * @param r - - * @param s - - */ - public SM2Signature(BigInteger r, BigInteger s) { - this.r = r; - this.s = s; - } - - public SM2Signature(byte[] r, byte[] s, byte v) { - this.r = new BigInteger(1, r); - this.s = new BigInteger(1,s); - this.v = v; - } - - /** - * t - * - * @return - - */ - private static SM2.SM2Signature fromComponents(byte[] r, byte[] s) { - return new SM2.SM2Signature(new BigInteger(1, r), new BigInteger(1, - s)); - } - - /** - * @param r - - * @param s - - * @param v - - * @return - - */ - public static SM2.SM2Signature fromComponents(byte[] r, byte[] s, byte - v) { - SM2.SM2Signature signature = fromComponents(r, s); - signature.v = v; - return signature; - } - - public static boolean validateComponents(BigInteger r, BigInteger s, - byte v) { - - if (v != 27 && v != 28) { - return false; - } - - if (isLessThan(r, BigInteger.ONE)) { - return false; - } - if (isLessThan(s, BigInteger.ONE)) { - return false; - } - - if (!isLessThan(r, SM2.SM2_N)) { - return false; - } - return isLessThan(s, SM2.SM2_N); - } - - public static SM2.SM2Signature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " + - "stream."); - } - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these - // values as unsigned, though they should not be - // Thus, we always use the positive versions. See: - // http://r6.ca/blog/20111119T211504Z.html - return new SM2.SM2Signature(r.getPositiveValue(), s - .getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) { - try { - decoder.close(); - } catch (IOException x) { - - } - } - } - } - - public boolean validateComponents() { - return validateComponents(r, s, v); - } - - - /** - * @return - - */ - public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 - // bytes for S - sigData[0] = v; - System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); - System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); - return new String(Base64.encode(sigData), Charset.forName("UTF-8")); - } - - - - public byte[] toByteArray() { - final byte fixedV = this.v >= 27 - ? (byte) (this.v - 27) - : this.v; - - return ByteUtil.merge( - ByteUtil.bigIntegerToBytes(this.r, 32), - ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); - } - - public String toHex() { - return Hex.toHexString(toByteArray()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - SM2.SM2Signature signature = (SM2.SM2Signature) o; - - if (!r.equals(signature.r)) { - return false; - } - return s.equals(signature.s); - } - - @Override - public int hashCode() { - int result = r.hashCode(); - result = 31 * result + s.hashCode(); - return result; - } - } - + } } diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java index 9dd689013..d5cd75c04 100644 --- a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java +++ b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java @@ -1,323 +1,262 @@ package org.tron.common.crypto.sm2; -import java.math.BigInteger; -import java.security.SecureRandom; - import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.DSA; import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.digests.SM3Digest; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECKeyParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.crypto.params.ParametersWithID; -import org.spongycastle.crypto.params.ParametersWithRandom; +import org.spongycastle.crypto.params.*; import org.spongycastle.crypto.signers.DSAKCalculator; import org.spongycastle.crypto.signers.RandomDSAKCalculator; -import org.spongycastle.math.ec.ECConstants; -import org.spongycastle.math.ec.ECFieldElement; -import org.spongycastle.math.ec.ECMultiplier; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.math.ec.FixedPointCombMultiplier; +import org.spongycastle.math.ec.*; import org.spongycastle.util.BigIntegers; -import org.tron.common.crypto.SignInterface; import javax.annotation.Nullable; +import java.math.BigInteger; +import java.security.SecureRandom; -public class SM2Signer - implements ECConstants -{ - private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); +public class SM2Signer implements ECConstants { + private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); - private byte[] userID; + private byte[] userID; - private int curveLength; - private ECDomainParameters ecParams; - private ECPoint pubPoint; - private ECKeyParameters ecKey; + private int curveLength; + private ECDomainParameters ecParams; + private ECPoint pubPoint; + private ECKeyParameters ecKey; - private SecureRandom random; + private SecureRandom random; - public void init(boolean forSigning, CipherParameters param) - { - CipherParameters baseParam; - - if (param instanceof ParametersWithID) - { - baseParam = ((ParametersWithID)param).getParameters(); - userID = ((ParametersWithID)param).getID(); - } - else - { - baseParam = param; - userID = new byte[0]; - } - - if (forSigning) - { - if (baseParam instanceof ParametersWithRandom) - { - ParametersWithRandom rParam = (ParametersWithRandom)baseParam; - - ecKey = (ECKeyParameters)rParam.getParameters(); - ecParams = ecKey.getParameters(); - kCalculator.init(ecParams.getN(), rParam.getRandom()); - } - else - { - ecKey = (ECKeyParameters)baseParam; - ecParams = ecKey.getParameters(); - kCalculator.init(ecParams.getN(), new SecureRandom()); - } - pubPoint = ecParams.getG().multiply(((ECPrivateKeyParameters)ecKey).getD()).normalize(); - } - else - { - ecKey = (ECKeyParameters)baseParam; - ecParams = ecKey.getParameters(); - pubPoint = ((ECPublicKeyParameters)ecKey).getQ(); - } - - curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; - } + public void init(boolean forSigning, CipherParameters param) { + CipherParameters baseParam; + if (param instanceof ParametersWithID) { + baseParam = ((ParametersWithID) param).getParameters(); + userID = ((ParametersWithID) param).getID(); + } else { + baseParam = param; + userID = new byte[0]; + } - /** - * generate the signature for the message - * - * @param message plaintext - * @return - */ - public BigInteger[] generateSignature(byte[] message) - { - byte[] eHash = generateSM3Hash(message); - return generateHashSignature(eHash); + if (forSigning) { + if (baseParam instanceof ParametersWithRandom) { + ParametersWithRandom rParam = (ParametersWithRandom) baseParam; + + ecKey = (ECKeyParameters) rParam.getParameters(); + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), rParam.getRandom()); + } else { + ecKey = (ECKeyParameters) baseParam; + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), new SecureRandom()); + } + pubPoint = ecParams.getG().multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize(); + } else { + ecKey = (ECKeyParameters) baseParam; + ecParams = ecKey.getParameters(); + pubPoint = ((ECPublicKeyParameters) ecKey).getQ(); } - /** - * generate the signature for the message - * - * @param message - * @return - */ - - public byte[] generateSM3Hash(byte[] message) - { - //byte[] msg = message.getBytes(); - SM3Digest digest = new SM3Digest(); - byte[] z = getZ(digest); + curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; + } + + /** + * generate the signature for the message + * + * @param message plaintext + * @return + */ + public BigInteger[] generateSignature(byte[] message) { + byte[] eHash = generateSM3Hash(message); + return generateHashSignature(eHash); + } + /** + * generate the signature for the message + * + * @param message + * @return + */ + public byte[] generateSM3Hash(byte[] message) { + // byte[] msg = message.getBytes(); + + SM3Digest digest = new SM3Digest(); + byte[] z = getZ(digest); + + digest.update(z, 0, z.length); + digest.update(message, 0, message.length); + + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * generate the signature from the 32 byte hash + * + * @param hash + * @return + */ + public BigInteger[] generateHashSignature(byte[] hash) { + if (hash.length != 32) { + throw new IllegalArgumentException( + "Expected 32 byte input to " + "ECDSA signature, not " + hash.length); + } + BigInteger n = ecParams.getN(); + BigInteger e = calculateE(hash); + BigInteger d = ((ECPrivateKeyParameters) ecKey).getD(); - digest.update(z, 0, z.length); - digest.update(message, 0, message.length); + BigInteger r, s; - byte[] eHash = new byte[digest.getDigestSize()]; + ECMultiplier basePointMultiplier = createBasePointMultiplier(); - digest.doFinal(eHash, 0); - return eHash; + // 5.2.1 Draft RFC: SM2 Public Key Algorithms + do // generate s + { + BigInteger k; + do // generate r + { + // A3 + k = kCalculator.nextK(); + // A4 + ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); + + // A5 + r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); + } while (r.equals(ZERO) || r.add(k).equals(n)); + + // A6 + BigInteger dPlus1ModN = d.add(ONE).modInverse(n); + + s = k.subtract(r.multiply(d)).mod(n); + s = dPlus1ModN.multiply(s).mod(n); + } while (s.equals(ZERO)); + + // A7 + return new BigInteger[] {r, s}; + } + + /** + * verify the message signature + * + * @param message + * @param r + * @param s + * @return + */ + public boolean verifySignature( + byte[] message, BigInteger r, BigInteger s, @Nullable String userID) { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) { + return false; } - /** - * generate the signature from the 32 byte hash - * - * @param hash - * @return - */ - public BigInteger[] generateHashSignature(byte[] hash) - { - if (hash.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + hash.length); - } - BigInteger n = ecParams.getN(); - BigInteger e = calculateE(hash); - BigInteger d = ((ECPrivateKeyParameters)ecKey).getD(); - - BigInteger r, s; - - ECMultiplier basePointMultiplier = createBasePointMultiplier(); - - // 5.2.1 Draft RFC: SM2 Public Key Algorithms - do // generate s - { - BigInteger k; - do // generate r - { - // A3 - k = kCalculator.nextK(); - // A4 - ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); - - // A5 - r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); - } - while (r.equals(ZERO) || r.add(k).equals(n)); - - // A6 - BigInteger dPlus1ModN = d.add(ONE).modInverse(n); - - s = k.subtract(r.multiply(d)).mod(n); - s = dPlus1ModN.multiply(s).mod(n); - } - while (s.equals(ZERO)); - - // A7 - return new BigInteger[]{ r, s }; + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) { + return false; } - /** - * verify the message signature - * - * @param message - * @param r - * @param s - * @return - */ - public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, @Nullable String userID) - { - BigInteger n = ecParams.getN(); - - // 5.3.1 Draft RFC: SM2 Public Key Algorithms - // B1 - if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) - { - return false; - } - - // B2 - if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) - { - return false; - } - - ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); - -// SM3Digest digest = new SM3Digest(); -// -// byte[] z = getZ(digest); -// -// digest.update(z, 0, z.length); -// digest.update(message, 0, message.length); -// -// byte[] eHash = new byte[digest.getDigestSize()]; -// -// // B3 -// digest.doFinal(eHash, 0); - if(userID != null) { - this.userID = userID.getBytes(); - } - byte[] eHash = generateSM3Hash(message); - - // B4 - BigInteger e = calculateE(eHash); - - // B5 - BigInteger t = r.add(s).mod(n); - if (t.equals(ZERO)) - { - return false; - } - else - { - // B6 - ECPoint x1y1 = ecParams.getG().multiply(s); - x1y1 = x1y1.add(q.multiply(t)).normalize(); - - // B7 - return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); - } + ECPoint q = ((ECPublicKeyParameters) ecKey).getQ(); + + if (userID != null) { + this.userID = userID.getBytes(); + } + byte[] eHash = generateSM3Hash(message); + + // B4 + BigInteger e = calculateE(eHash); + + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) { + return false; + } else { + // B6 + ECPoint x1y1 = ecParams.getG().multiply(s); + x1y1 = x1y1.add(q.multiply(t)).normalize(); + + // B7 + return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); + } + } + + /** + * verfify the hash signature + * + * @param hash + * @param r + * @param s + * @return + */ + public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) { + return false; } - /** - * verfify the hash signature - * - * @param hash - * @param r - * @param s - * @return - */ - public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) - { - BigInteger n = ecParams.getN(); - - // 5.3.1 Draft RFC: SM2 Public Key Algorithms - // B1 - if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) - { - return false; - } - - // B2 - if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) - { - return false; - } - - ECPoint q = ((ECPublicKeyParameters)ecKey).getQ(); - - - // B4 - BigInteger e = calculateE(hash); - - // B5 - BigInteger t = r.add(s).mod(n); - if (t.equals(ZERO)) - { - return false; - } - else - { - // B6 - ECPoint x1y1 = ecParams.getG().multiply(s); - x1y1 = x1y1.add(q.multiply(t)).normalize(); - - // B7 - return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); - } + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) { + return false; } - private byte[] getZ(Digest digest) - { + ECPoint q = ((ECPublicKeyParameters) ecKey).getQ(); - //addUserID(digest, userID); + // B4 + BigInteger e = calculateE(hash); - addFieldElement(digest, ecParams.getCurve().getA()); - addFieldElement(digest, ecParams.getCurve().getB()); - addFieldElement(digest, ecParams.getG().getAffineXCoord()); - addFieldElement(digest, ecParams.getG().getAffineYCoord()); - addFieldElement(digest, pubPoint.getAffineXCoord()); - addFieldElement(digest, pubPoint.getAffineYCoord()); + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) { + return false; + } else { + // B6 + ECPoint x1y1 = ecParams.getG().multiply(s); + x1y1 = x1y1.add(q.multiply(t)).normalize(); - byte[] rv = new byte[digest.getDigestSize()]; + // B7 + return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); + } + } - digest.doFinal(rv, 0); + private byte[] getZ(Digest digest) { - return rv; - } + // addUserID(digest, userID); - private void addUserID(Digest digest, byte[] userID) - { - int len = userID.length * 8; - digest.update((byte)(len >> 8 & 0xFF)); - digest.update((byte)(len & 0xFF)); - digest.update(userID, 0, userID.length); - } + addFieldElement(digest, ecParams.getCurve().getA()); + addFieldElement(digest, ecParams.getCurve().getB()); + addFieldElement(digest, ecParams.getG().getAffineXCoord()); + addFieldElement(digest, ecParams.getG().getAffineYCoord()); + addFieldElement(digest, pubPoint.getAffineXCoord()); + addFieldElement(digest, pubPoint.getAffineYCoord()); - private void addFieldElement(Digest digest, ECFieldElement v) - { - byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); - digest.update(p, 0, p.length); - } + byte[] rv = new byte[digest.getDigestSize()]; - protected ECMultiplier createBasePointMultiplier() - { - return new FixedPointCombMultiplier(); - } + digest.doFinal(rv, 0); - protected BigInteger calculateE(byte[] message) - { - return new BigInteger(1, message); - } + return rv; + } -} + private void addUserID(Digest digest, byte[] userID) { + int len = userID.length * 8; + digest.update((byte) (len >> 8 & 0xFF)); + digest.update((byte) (len & 0xFF)); + digest.update(userID, 0, userID.length); + } + + private void addFieldElement(Digest digest, ECFieldElement v) { + byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); + digest.update(p, 0, p.length); + } + protected ECMultiplier createBasePointMultiplier() { + return new FixedPointCombMultiplier(); + } + + protected BigInteger calculateE(byte[] message) { + return new BigInteger(1, message); + } +} diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index ccaf56222..a1446fa0e 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -16,12 +16,14 @@ package org.tron.common.utils; import com.google.protobuf.ByteString; +import com.typesafe.config.Config; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.sm2.SM2; +import org.tron.common.crypto.sm2.SM3; +import org.tron.core.config.Configuration; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -38,9 +40,18 @@ public class TransactionUtils { * @param transaction {@link Transaction} transaction * @return byte[] the hash of the transaction's data bytes which have no id */ + private static boolean isEckey = true; + + static { + Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + } + } + public static byte[] getHash(Transaction transaction) { Transaction.Builder tmp = transaction.toBuilder(); - //tmp.clearId(); + // tmp.clearId(); return Sha256Hash.hash(tmp.build().toByteArray()); } @@ -50,69 +61,116 @@ public static byte[] getOwner(Transaction.Contract contract) { try { switch (contract.getType()) { case AccountCreateContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.AccountCreateContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AccountCreateContract.class) + .getOwnerAddress(); break; case TransferContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.TransferContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TransferContract.class) + .getOwnerAddress(); break; case TransferAssetContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.TransferAssetContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TransferAssetContract.class) + .getOwnerAddress(); break; case VoteAssetContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteAssetContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.VoteAssetContract.class) + .getOwnerAddress(); break; case VoteWitnessContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteWitnessContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.VoteWitnessContract.class) + .getOwnerAddress(); break; case WitnessCreateContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.WitnessCreateContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.WitnessCreateContract.class) + .getOwnerAddress(); break; case AssetIssueContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.AssetIssueContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AssetIssueContract.class) + .getOwnerAddress(); break; case ParticipateAssetIssueContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) + .getOwnerAddress(); break; case CreateSmartContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.CreateSmartContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.CreateSmartContract.class) + .getOwnerAddress(); break; case TriggerSmartContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.TriggerSmartContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TriggerSmartContract.class) + .getOwnerAddress(); break; case FreezeBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.FreezeBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.FreezeBalanceContract.class) + .getOwnerAddress(); break; case UnfreezeBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class) + .getOwnerAddress(); break; case UnfreezeAssetContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class) + .getOwnerAddress(); break; case WithdrawBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class) + .getOwnerAddress(); break; case UpdateAssetContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.UpdateAssetContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UpdateAssetContract.class) + .getOwnerAddress(); break; case AccountPermissionUpdateContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) + .getOwnerAddress(); break; default: return null; @@ -129,7 +187,7 @@ public static String getBase64FromByteString(ByteString sign) { byte[] s = sign.substring(32, 64).toByteArray(); byte v = sign.byteAt(64); if (v < 27) { - v += 27; //revId -> v + v += 27; // revId -> v } ECDSASignature signature = ECDSASignature.fromComponents(r, s, v); return signature.toBase64(); @@ -142,8 +200,8 @@ public static String getBase64FromByteString(ByteString sign) { * 4. check balance */ public static boolean validTransaction(Transaction signedTransaction) { - assert (signedTransaction.getSignatureCount() == - signedTransaction.getRawData().getContractCount()); + assert (signedTransaction.getSignatureCount() + == signedTransaction.getRawData().getContractCount()); List listContract = signedTransaction.getRawData().getContractList(); byte[] hash = Sha256Hash.hash(signedTransaction.getRawData().toByteArray()); int count = signedTransaction.getSignatureCount(); @@ -154,8 +212,9 @@ public static boolean validTransaction(Transaction signedTransaction) { try { Transaction.Contract contract = listContract.get(i); byte[] owner = getOwner(contract); - byte[] address = ECKey - .signatureToAddress(hash, getBase64FromByteString(signedTransaction.getSignature(i))); + byte[] address = + ECKey.signatureToAddress( + hash, getBase64FromByteString(signedTransaction.getSignature(i))); if (!Arrays.equals(owner, address)) { return false; } @@ -169,7 +228,12 @@ public static boolean validTransaction(Transaction signedTransaction) { public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); - byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); + byte[] hash; + if (isEckey) { + hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); + } else { + hash = SM3.hash(transaction.getRawData().toByteArray()); + } SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); @@ -178,10 +242,10 @@ public static Transaction sign(Transaction transaction, SignInterface myKey) { } public static Transaction setTimestamp(Transaction transaction) { - long currentTime = System.currentTimeMillis();//*1000000 + System.nanoTime()%1000000; + long currentTime = System.currentTimeMillis(); // *1000000 + System.nanoTime()%1000000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() - .toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = + transaction.getRawData().toBuilder(); rowBuilder.setTimestamp(currentTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -191,8 +255,8 @@ public static Transaction setExpirationTime(Transaction transaction) { if (transaction.getSignatureCount() == 0) { long expirationTime = System.currentTimeMillis() + 6 * 60 * 60 * 1000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() - .toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = + transaction.getRawData().toBuilder(); rowBuilder.setExpiration(expirationTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -211,8 +275,8 @@ public static Transaction setPermissionId(Transaction transaction) throws Cancel } if (permission_id != 0) { Transaction.raw.Builder raw = transaction.getRawData().toBuilder(); - Transaction.Contract.Builder contract = raw.getContract(0).toBuilder() - .setPermissionId(permission_id); + Transaction.Contract.Builder contract = + raw.getContract(0).toBuilder().setPermissionId(permission_id); raw.clearContract(); raw.addContract(contract); transaction = transaction.toBuilder().setRawData(raw).build(); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index fc57493a5..90ddc6ccd 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -3,9 +3,6 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.TransactionExtention; - import org.tron.common.crypto.Sha256Hash; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; @@ -15,18 +12,19 @@ import org.tron.protos.Protocol.Transaction; import org.tron.walletserver.WalletApi; -import java.util.Arrays; - public class TransactionSignDemoForSM2 { public static Transaction setReference(Transaction transaction, Block newestBlock) { long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); + Transaction.raw rawData = + transaction + .getRawData() + .toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); return transaction.toBuilder().setRawData(rawData).build(); } @@ -39,14 +37,13 @@ public static String getTransactionHash(Transaction transaction) { return txid; } - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { Transaction.Builder transactionBuilder = Transaction.newBuilder(); Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract - .newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = + Contract.TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); @@ -59,15 +56,17 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) + transactionBuilder + .getRawDataBuilder() + .addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration( + newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; } - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { SM2 sm2 = SM2.fromPrivate(privateKey); @@ -110,17 +109,14 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); - long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); - - //sign a transaction in byte format and return a Transaction in byte format + // sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); boolean result = broadcast(transaction4); System.out.println(result); } - - } diff --git a/src/main/java/org/tron/keystore/WalletUtils.java b/src/main/java/org/tron/keystore/WalletUtils.java index 8d206829f..65b2fda65 100644 --- a/src/main/java/org/tron/keystore/WalletUtils.java +++ b/src/main/java/org/tron/keystore/WalletUtils.java @@ -20,16 +20,14 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -/** - * Utility functions for working with Wallet files. - */ +/** Utility functions for working with Wallet files. */ public class WalletUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); private static boolean isEckey = true; static { - Config config = Configuration.getByPath("config.conf");//it is needs set to be a constant + Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); if (config.hasPath("crypto.engine")) { @@ -39,15 +37,15 @@ public class WalletUtils { } public static String generateFullNewWalletFile(byte[] password, File destinationDirectory) - throws NoSuchAlgorithmException, NoSuchProviderException, - InvalidAlgorithmParameterException, CipherException, IOException { + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, + CipherException, IOException { return generateNewWalletFile(password, destinationDirectory, true); } public static String generateLightNewWalletFile(byte[] password, File destinationDirectory) - throws NoSuchAlgorithmException, NoSuchProviderException, - InvalidAlgorithmParameterException, CipherException, IOException { + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, + CipherException, IOException { return generateNewWalletFile(password, destinationDirectory, false); } @@ -55,7 +53,7 @@ public static String generateLightNewWalletFile(byte[] password, File destinatio public static String generateNewWalletFile( byte[] password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, - NoSuchAlgorithmException, NoSuchProviderException { + NoSuchAlgorithmException, NoSuchProviderException { SignInterface ecKeySm2Pair = null; if (isEckey) { ecKeySm2Pair = new ECKey(Utils.getRandom()); @@ -108,37 +106,38 @@ public static String generateWalletFile(WalletFile walletFile, File destinationD } // /** -// * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can -// * be calculated using following algorithm: -// *

-//     *     Key = SHA-256(BIP_39_SEED(mnemonic, password))
-//     * 
-// * -// * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed -// * @param destinationDirectory The directory containing the wallet -// * @return A BIP-39 compatible Ethereum wallet -// * @throws CipherException if the underlying cipher is not available -// * @throws IOException if the destination cannot be written to -// */ -// public static Bip39Wallet generateBip39Wallet(String password, File destinationDirectory) -// throws CipherException, IOException { -// byte[] initialEntropy = new byte[16]; -// secureRandom.nextBytes(initialEntropy); -// -// String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); -// byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); -// ECKeyPair privateKey = ECKeyPair.create(sha256(seed)); -// -// String walletFile = generateWalletFile(password, privateKey, destinationDirectory, false); -// -// return new Bip39Wallet(walletFile, mnemonic); -// } -// -// public static Credentials loadCredentials(String password, String source) -// throws IOException, CipherException { -// return loadCredentials(password, new File(source)); -// } -// + // * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can + // * be calculated using following algorithm: + // *
+  //     *     Key = SHA-256(BIP_39_SEED(mnemonic, password))
+  //     * 
+ // * + // * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed + // * @param destinationDirectory The directory containing the wallet + // * @return A BIP-39 compatible Ethereum wallet + // * @throws CipherException if the underlying cipher is not available + // * @throws IOException if the destination cannot be written to + // */ + // public static Bip39Wallet generateBip39Wallet(String password, File destinationDirectory) + // throws CipherException, IOException { + // byte[] initialEntropy = new byte[16]; + // secureRandom.nextBytes(initialEntropy); + // + // String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); + // byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); + // ECKeyPair privateKey = ECKeyPair.create(sha256(seed)); + // + // String walletFile = generateWalletFile(password, privateKey, destinationDirectory, + // false); + // + // return new Bip39Wallet(walletFile, mnemonic); + // } + // + // public static Credentials loadCredentials(String password, String source) + // throws IOException, CipherException { + // return loadCredentials(password, new File(source)); + // } + // public static Credentials loadCredentials(byte[] password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); @@ -152,15 +151,9 @@ public static Credentials loadCredentials(byte[] password, File source) public static WalletFile loadWalletFile(File source) throws IOException { return objectMapper.readValue(source, WalletFile.class); } -// -// public static Credentials loadBip39Credentials(String password, String mnemonic) { -// byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); -// return Credentials.create(ECKeyPair.create(sha256(seed))); -// } private static String getWalletFileName(WalletFile walletFile) { - DateTimeFormatter format = DateTimeFormatter.ofPattern( - "'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'"); + DateTimeFormatter format = DateTimeFormatter.ofPattern("'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'"); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); return now.format(format) + walletFile.getAddress() + ".json"; @@ -175,8 +168,7 @@ static String getDefaultKeyDirectory(String osName1) { if (osName.startsWith("mac")) { return String.format( - "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator, - File.separator); + "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator, File.separator); } else if (osName.startsWith("win")) { return String.format("%s%sEthereum", System.getenv("APPDATA"), File.separator); } else { @@ -193,26 +185,7 @@ public static String getMainnetKeyDirectory() { return String.format("%s%skeystore", getDefaultKeyDirectory(), File.separator); } - -// public static boolean isValidPrivateKey(String privateKey) { -// String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); -// return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; -// } -// -// public static boolean isValidAddress(String input) { -// String cleanInput = Numeric.cleanHexPrefix(input); -// -// try { -// Numeric.toBigIntNoPrefix(cleanInput); -// } catch (NumberFormatException e) { -// return false; -// } -// -// return cleanInput.length() == ADDRESS_LENGTH_IN_HEX; -// } - - public static void generateSkeyFile(SKeyCapsule skey, File file) - throws IOException { + public static void generateSkeyFile(SKeyCapsule skey, File file) throws IOException { objectMapper.writeValue(file, skey); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index f1b4449d4..b2c20443e 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -57,17 +57,17 @@ public class WalletApi { private static GrpcClient rpcCli = init(); -// static { -// new Timer().schedule(new TimerTask() { -// @Override -// public void run() { -// String fullnode = selectFullNode(); -// if(!"".equals(fullnode)) { -// rpcCli = new GrpcClient(fullnode); -// } -// } -// }, 3 * 60 * 1000, 3 * 60 * 1000); -// } + // static { + // new Timer().schedule(new TimerTask() { + // @Override + // public void run() { + // String fullnode = selectFullNode(); + // if(!"".equals(fullnode)) { + // rpcCli = new GrpcClient(fullnode); + // } + // } + // }, 3 * 60 * 1000, 3 * 60 * 1000); + // } public static GrpcClient init() { Config config = Configuration.getByPath("config.conf"); @@ -140,9 +140,7 @@ public static int getRpcVersion() { return rpcVersion; } - /** - * Creates a new WalletApi with a random ECKey or no ECKey. - */ + /** Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -157,7 +155,7 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - WalletFile walletFile=null; + WalletFile walletFile = null; if (isEckey) { ECKey ecKey = ECKey.fromPrivate(priKey); walletFile = Wallet.createStandard(password, ecKey); @@ -186,9 +184,7 @@ public boolean checkPassword(byte[] passwd) throws CipherException { return Wallet.validPassword(passwd, this.walletFile.get(0)); } - /** - * Creates a Wallet with an existing ECKey. - */ + /** Creates a Wallet with an existing ECKey. */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -313,7 +309,6 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso return true; } - private static WalletFile loadWalletFile() throws IOException { File wallet = selcetWalletFile(); if (wallet == null) { @@ -323,11 +318,8 @@ private static WalletFile loadWalletFile() throws IOException { return WalletUtils.loadWalletFile(wallet); } - /** - * load a Wallet from keystore - */ - public static WalletApi loadWalletFromKeystore() - throws IOException { + /** load a Wallet from keystore */ + public static WalletApi loadWalletFromKeystore() throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); return walletApi; @@ -338,7 +330,7 @@ public Account queryAccount() { } public static Account queryAccount(byte[] address) { - return rpcCli.queryAccount(address);//call rpc + return rpcCli.queryAccount(address); // call rpc } public static Account queryAccountById(String accountId) { @@ -380,9 +372,9 @@ private Transaction signTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } -// System.out -// .println("current transaction hex string is " + ByteArray -// .toHexString(transaction.toByteArray())); + // System.out + // .println("current transaction hex string is " + ByteArray + // .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -422,9 +414,7 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } -// System.out -// .println("current transaction hex string is " + ByteArray -// .toHexString(transaction.toByteArray())); + org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -463,13 +453,14 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); - System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -477,23 +468,24 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { - System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); - System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println( + "after sign transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "txid is " + + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { - CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); - byte[] contractAddress = generateContractAddress( - createSmartContract.getOwnerAddress().toByteArray(), transaction); + CreateSmartContract createSmartContract = + transaction.getRawData().getContract(0).getParameter().unpack(CreateSmartContract.class); + byte[] contractAddress = + generateContractAddress(createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); } } - private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + private static boolean processShieldedTransaction( + TransactionExtention transactionExtention, WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -511,7 +503,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -527,10 +519,11 @@ private static boolean processShieldedTransaction(TransactionExtention transacti transaction = wallet.signOnlyForShieldedTransaction(transaction); } - System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); - System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println( + "transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "txid is " + + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -542,8 +535,9 @@ private boolean processTransaction(Transaction transaction) } System.out.println(Utils.printTransactionExceptId(transaction)); - System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -551,7 +545,7 @@ private boolean processTransaction(Transaction transaction) return rpcCli.broadcastTransaction(transaction); } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); @@ -563,9 +557,9 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p return rpcCli.signTransaction(builder.build()); } - //Warning: do not invoke this interface provided by others. - public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + // Warning: do not invoke this interface provided by others. + public static TransactionExtention signTransactionByApi2( + Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -575,9 +569,9 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction return rpcCli.signTransaction2(builder.build()); } - //Warning: do not invoke this interface provided by others. - public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + // Warning: do not invoke this interface provided by others. + public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) + throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -595,32 +589,32 @@ public static TransactionApprovedList getTransactionApprovedList(Transaction tra return rpcCli.getTransactionApprovedList(transaction); } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static byte[] createAdresss(byte[] passPhrase) { return rpcCli.createAdresss(passPhrase); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransfer( + byte[] passPhrase, byte[] toAddress, long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferByPrivate( + byte[] privateKey, byte[] toAddress, long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAsset( + byte[] passPhrase, byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAssetByPrivate( + byte[] privateKey, byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -671,16 +665,15 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) return processTransaction(transaction); } - - public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + public boolean updateAsset( + byte[] owner, byte[] description, byte[] url, long newLimit, long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.UpdateAssetContract contract - = createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); + Contract.UpdateAssetContract contract = + createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -696,8 +689,8 @@ public boolean transferAsset(byte[] owner, byte[] to, byte[] assertName, long am owner = getAddress(); } - Contract.TransferAssetContract contract = createTransferAssetContract(to, assertName, owner, - amount); + Contract.TransferAssetContract contract = + createTransferAssetContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransferAssetTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -713,11 +706,11 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner = getAddress(); } - Contract.ParticipateAssetIssueContract contract = participateAssetIssueContract(to, assertName, - owner, amount); + Contract.ParticipateAssetIssueContract contract = + participateAssetIssueContract(to, assertName, owner, amount); if (rpcVersion == 2) { - TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + TransactionExtention transactionExtention = + rpcCli.createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -762,7 +755,7 @@ public boolean createAccount(byte[] owner, byte[] address) } } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static AddressPrKeyPairMessage generateAddress() { EmptyMessage.Builder builder = EmptyMessage.newBuilder(); return rpcCli.generateAddress(builder.build()); @@ -828,8 +821,8 @@ public boolean voteWitness(byte[] owner, HashMap witness) } } - public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + public static Contract.TransferContract createTransferContract( + byte[] to, byte[] owner, long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -840,9 +833,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] return builder.build(); } - public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + public static Contract.TransferAssetContract createTransferAssetContract( + byte[] to, byte[] assertName, byte[] owner, long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -855,11 +847,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] return builder.build(); } - public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { - Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + public static Contract.ParticipateAssetIssueContract participateAssetIssueContract( + byte[] to, byte[] assertName, byte[] owner, long amount) { + Contract.ParticipateAssetIssueContract.Builder builder = + Contract.ParticipateAssetIssueContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -871,8 +862,8 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra return builder.build(); } - public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + public static Contract.AccountUpdateContract createAccountUpdateContract( + byte[] accountName, byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -882,8 +873,8 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] return builder.build(); } - public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + public static Contract.SetAccountIdContract createSetAccountIdContract( + byte[] accountId, byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -893,16 +884,9 @@ public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] ac return builder.build(); } - public static Contract.UpdateAssetContract createUpdateAssetContract( - byte[] address, - byte[] description, - byte[] url, - long newLimit, - long newPublicLimit - ) { - Contract.UpdateAssetContract.Builder builder = - Contract.UpdateAssetContract.newBuilder(); + byte[] address, byte[] description, byte[] url, long newLimit, long newPublicLimit) { + Contract.UpdateAssetContract.Builder builder = Contract.UpdateAssetContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); builder.setDescription(ByteString.copyFrom(description)); builder.setUrl(ByteString.copyFrom(url)); @@ -913,8 +897,8 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( return builder.build(); } - public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + public static Contract.AccountCreateContract createAccountCreateContract( + byte[] owner, byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -922,8 +906,8 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] return builder.build(); } - public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + public static Contract.WitnessCreateContract createWitnessCreateContract( + byte[] owner, byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -931,8 +915,8 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] return builder.build(); } - public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + public static Contract.WitnessUpdateContract createWitnessUpdateContract( + byte[] owner, byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -940,15 +924,15 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] return builder.build(); } - public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + public static Contract.VoteWitnessContract createVoteWitnessContract( + byte[] owner, HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); - Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + Contract.VoteWitnessContract.Vote.Builder voteBuilder = + Contract.VoteWitnessContract.Vote.newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -969,7 +953,7 @@ public static boolean passwordValid(char[] password) { System.out.println("Warning: Password is too short !!"); return false; } - //Other rule; + // Other rule; int level = CheckStrength.checkPasswordStrength(password); if (level <= 4) { System.out.println("Your password is too weak!"); @@ -993,18 +977,24 @@ public static boolean addressValid(byte[] address) { } if (address.length != CommonConstant.ADDRESS_SIZE) { System.out.println( - "Warning: Address length need " + CommonConstant.ADDRESS_SIZE + " but " + address.length + "Warning: Address length need " + + CommonConstant.ADDRESS_SIZE + + " but " + + address.length + " !!"); return false; } byte preFixbyte = address[0]; if (preFixbyte != WalletApi.getAddressPreFixByte()) { - System.out - .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + System.out.println( + "Warning: Address need prefix with " + + WalletApi.getAddressPreFixByte() + + " but " + + preFixbyte + + " !!"); return false; } - //Other rule; + // Other rule; return true; } @@ -1026,10 +1016,10 @@ private static byte[] decode58Check(String input) { System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length); byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); - if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + if (hash1[0] == decodeCheck[decodeData.length] + && hash1[1] == decodeCheck[decodeData.length + 1] + && hash1[2] == decodeCheck[decodeData.length + 2] + && hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1056,25 +1046,10 @@ public static boolean priKeyValid(byte[] priKey) { System.out.println("Warning: PrivateKey length need 64 but " + priKey.length + " !!"); return false; } - //Other rule; + // Other rule; return true; } -// public static Optional listAccounts() { -// Optional result = rpcCli.listAccounts(); -// if (result.isPresent()) { -// AccountList accountList = result.get(); -// List list = accountList.getAccountsList(); -// List newList = new ArrayList(); -// newList.addAll(list); -// newList.sort(new AccountComparator()); -// AccountList.Builder builder = AccountList.newBuilder(); -// newList.forEach(account -> builder.addAccounts(account)); -// result = Optional.of(builder.build()); -// } -// return result; -// } - public static Optional listWitnesses() { Optional result = rpcCli.listWitnesses(); if (result.isPresent()) { @@ -1082,12 +1057,13 @@ public static Optional listWitnesses() { List list = witnessList.getWitnessesList(); List newList = new ArrayList<>(); newList.addAll(list); - newList.sort(new Comparator() { - @Override - public int compare(Witness o1, Witness o2) { - return Long.compare(o2.getVoteCount(), o1.getVoteCount()); - } - }); + newList.sort( + new Comparator() { + @Override + public int compare(Witness o1, Witness o2) { + return Long.compare(o2.getVoteCount(), o1.getVoteCount()); + } + }); WitnessList.Builder builder = WitnessList.newBuilder(); newList.forEach(witness -> builder.addWitnesses(witness)); result = Optional.of(builder.build()); @@ -1095,19 +1071,6 @@ public int compare(Witness o1, Witness o2) { return result; } -// public static Optional getAssetIssueListByTimestamp(long timestamp) { -// return rpcCli.getAssetIssueListByTimestamp(timestamp); -// } -// -// public static Optional getTransactionsByTimestamp(long start, long end, -// int offset, int limit) { -// return rpcCli.getTransactionsByTimestamp(start, end, offset, limit); -// } -// -// public static GrpcAPI.NumberMessage getTransactionsByTimestampCount(long start, long end) { -// return rpcCli.getTransactionsByTimestampCount(start, end); -// } - public static Optional getAssetIssueList() { return rpcCli.getAssetIssueList(); } @@ -1124,7 +1087,6 @@ public static Optional getExchangeListPaginated(long offset, long return rpcCli.getExchangeListPaginated(offset, limit); } - public static Optional listNodes() { return rpcCli.listNodes(); } @@ -1161,33 +1123,25 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { return rpcCli.getNextMaintenanceTime(); } - public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + public static Optional getTransactionsFromThis( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } - public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + public static Optional getTransactionsFromThis2( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } -// public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { -// return rpcCli.getTransactionsFromThisCount(address); -// } - public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + public static Optional getTransactionsToThis( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } - public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + public static Optional getTransactionsToThis2( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } -// public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { -// return rpcCli.getTransactionsToThisCount(address); -// } public static Optional getTransactionById(String txID) { return rpcCli.getTransactionById(txID); @@ -1197,12 +1151,16 @@ public static Optional getTransactionInfoById(String txID) { return rpcCli.getTransactionInfoById(txID); } - public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + public boolean freezeBalance( + byte[] ownerAddress, + long frozen_balance, + long frozen_duration, + int resourceCode, + byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, - frozen_balance, - frozen_duration, resourceCode, receiverAddress); + Contract.FreezeBalanceContract contract = + createFreezeBalanceContract( + ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1231,23 +1189,29 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) Contract.SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); - } - private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + private FreezeBalanceContract createFreezeBalanceContract( + byte[] address, + long frozen_balance, + long frozen_duration, + int resourceCode, + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); - builder.setOwnerAddress(byteAddress).setFrozenBalance(frozen_balance) - .setFrozenDuration(frozen_duration).setResourceValue(resourceCode); + builder + .setOwnerAddress(byteAddress) + .setFrozenBalance(frozen_balance) + .setFrozenDuration(frozen_duration) + .setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = ByteString.copyFrom( - Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = + ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } return builder.build(); @@ -1270,8 +1234,8 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo address = getAddress(); } - Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + Contract.BuyStorageBytesContract.Builder builder = + Contract.BuyStorageBytesContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1292,8 +1256,8 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeBalanceContract contract = createUnfreezeBalanceContract(ownerAddress, - resourceCode, receiverAddress); + Contract.UnfreezeBalanceContract contract = + createUnfreezeBalanceContract(ownerAddress, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1303,21 +1267,20 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec } } - - private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + private UnfreezeBalanceContract createUnfreezeBalanceContract( + byte[] address, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } - Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + Contract.UnfreezeBalanceContract.Builder builder = + Contract.UnfreezeBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = ByteString.copyFrom( - Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = + ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } @@ -1341,8 +1304,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { address = getAddress(); } - Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1365,8 +1327,8 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { address = getAddress(); } - Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + Contract.WithdrawBalanceContract.Builder builder = + Contract.WithdrawBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1412,8 +1374,8 @@ public static Optional getProposal(String id) { return rpcCli.getProposal(id); } - public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + public static Optional getDelegatedResource( + String fromAddress, String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1434,9 +1396,8 @@ public static Optional getChainParameters() { return rpcCli.getChainParameters(); } - - public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + public static Contract.ProposalCreateContract createProposalCreateContract( + byte[] owner, HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1449,16 +1410,16 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) owner = getAddress(); } - Contract.ProposalApproveContract contract = createProposalApproveContract(owner, id, - is_add_approval); + Contract.ProposalApproveContract contract = + createProposalApproveContract(owner, id, is_add_approval); TransactionExtention transactionExtention = rpcCli.proposalApprove(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { - Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + public static Contract.ProposalApproveContract createProposalApproveContract( + byte[] owner, long id, boolean is_add_approval) { + Contract.ProposalApproveContract.Builder builder = + Contract.ProposalApproveContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1476,30 +1437,38 @@ public boolean deleteProposal(byte[] owner, long id) return processTransactionExtention(transactionExtention); } - public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + public static Contract.ProposalDeleteContract createProposalDeleteContract( + byte[] owner, long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); return builder.build(); } - public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + public boolean exchangeCreate( + byte[] owner, + byte[] firstTokenId, + long firstTokenBalance, + byte[] secondTokenId, + long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeCreateContract contract = createExchangeCreateContract(owner, firstTokenId, - firstTokenBalance, secondTokenId, secondTokenBalance); + Contract.ExchangeCreateContract contract = + createExchangeCreateContract( + owner, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); TransactionExtention transactionExtention = rpcCli.exchangeCreate(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + public static Contract.ExchangeCreateContract createExchangeCreateContract( + byte[] owner, + byte[] firstTokenId, + long firstTokenBalance, + byte[] secondTokenId, + long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1516,14 +1485,14 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon owner = getAddress(); } - Contract.ExchangeInjectContract contract = createExchangeInjectContract(owner, exchangeId, - tokenId, quant); + Contract.ExchangeInjectContract contract = + createExchangeInjectContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeInject(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + public static Contract.ExchangeInjectContract createExchangeInjectContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1539,16 +1508,16 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l owner = getAddress(); } - Contract.ExchangeWithdrawContract contract = createExchangeWithdrawContract(owner, exchangeId, - tokenId, quant); + Contract.ExchangeWithdrawContract contract = + createExchangeWithdrawContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeWithdraw(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant) { + Contract.ExchangeWithdrawContract.Builder builder = + Contract.ExchangeWithdrawContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1557,23 +1526,23 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b return builder.build(); } - public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + public boolean exchangeTransaction( + byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeTransactionContract contract = createExchangeTransactionContract(owner, - exchangeId, tokenId, quant, expected); + Contract.ExchangeTransactionContract contract = + createExchangeTransactionContract(owner, exchangeId, tokenId, quant, expected); TransactionExtention transactionExtention = rpcCli.exchangeTransaction(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { - Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + public static Contract.ExchangeTransactionContract createExchangeTransactionContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) { + Contract.ExchangeTransactionContract.Builder builder = + Contract.ExchangeTransactionContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1583,7 +1552,6 @@ public static Contract.ExchangeTransactionContract createExchangeTransactionCont return builder.build(); } - public static SmartContract.ABI.Entry.EntryType getEntryType(String type) { switch (type) { case "constructor": @@ -1626,22 +1594,38 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { SmartContract.ABI.Builder abiBuilder = SmartContract.ABI.newBuilder(); for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); - boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; - boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; - String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; - JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; - JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; - String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; - boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; - String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + boolean anonymous = + abiItem.getAsJsonObject().get("anonymous") != null + ? abiItem.getAsJsonObject().get("anonymous").getAsBoolean() + : false; + boolean constant = + abiItem.getAsJsonObject().get("constant") != null + ? abiItem.getAsJsonObject().get("constant").getAsBoolean() + : false; + String name = + abiItem.getAsJsonObject().get("name") != null + ? abiItem.getAsJsonObject().get("name").getAsString() + : null; + JsonArray inputs = + abiItem.getAsJsonObject().get("inputs") != null + ? abiItem.getAsJsonObject().get("inputs").getAsJsonArray() + : null; + JsonArray outputs = + abiItem.getAsJsonObject().get("outputs") != null + ? abiItem.getAsJsonObject().get("outputs").getAsJsonArray() + : null; + String type = + abiItem.getAsJsonObject().get("type") != null + ? abiItem.getAsJsonObject().get("type").getAsString() + : null; + boolean payable = + abiItem.getAsJsonObject().get("payable") != null + ? abiItem.getAsJsonObject().get("payable").getAsBoolean() + : false; + String stateMutability = + abiItem.getAsJsonObject().get("stateMutability") != null + ? abiItem.getAsJsonObject().get("stateMutability").getAsString() + : null; if (type == null) { System.out.println("No type!"); return null; @@ -1662,8 +1646,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (null != inputs) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); - if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + if (inputItem.getAsJsonObject().get("name") == null + || inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1671,11 +1655,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String inputType = inputItem.getAsJsonObject().get("type").getAsString(); Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { - inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + inputIndexed = + Boolean.valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = + SmartContract.ABI.Entry.Param.newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1687,8 +1671,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (outputs != null) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); - if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + if (outputItem.getAsJsonObject().get("name") == null + || outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1696,11 +1680,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String outputType = outputItem.getAsJsonObject().get("type").getAsString(); Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { - outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + outputIndexed = + Boolean.valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = + SmartContract.ABI.Entry.Param.newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1720,8 +1704,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { return abiBuilder.build(); } - public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + public static Contract.UpdateSettingContract createUpdateSettingContract( + byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1731,32 +1715,37 @@ public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] } public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract( - byte[] owner, - byte[] contractAddress, long originEnergyLimit) { + byte[] owner, byte[] contractAddress, long originEnergyLimit) { - Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + Contract.UpdateEnergyLimitContract.Builder builder = + Contract.UpdateEnergyLimitContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); return builder.build(); } - public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + public static Contract.ClearABIContract createClearABIContract( + byte[] owner, byte[] contractAddress) { - Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + Contract.ClearABIContract.Builder builder = Contract.ClearABIContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } - public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + public static CreateSmartContract createContractDeployContract( + String contractName, + byte[] address, + String ABI, + String code, + long value, + long consumeUserResourcePercent, + long originEnergyLimit, + long tokenValue, + String tokenId, + String libraryAddressPair, + String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1767,7 +1756,8 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setName(contractName); builder.setOriginAddress(ByteString.copyFrom(address)); builder.setAbi(abi); - builder.setConsumeUserResourcePercent(consumeUserResourcePercent) + builder + .setConsumeUserResourcePercent(consumeUserResourcePercent) .setOriginEnergyLimit(originEnergyLimit); if (value != 0) { @@ -1783,16 +1773,17 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); - createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + createSmartContractBuilder + .setOwnerAddress(ByteString.copyFrom(address)) + .setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } return createSmartContractBuilder.build(); } - private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + private static byte[] replaceLibraryAddress( + String code, String libraryAddressPair, String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1807,21 +1798,22 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa String addr = cur.substring(lastPosition + 1); String libraryAddressHex; try { - libraryAddressHex = (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), - "US-ASCII")).substring(2); + libraryAddressHex = + (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), "US-ASCII")) + .substring(2); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // now ignore + throw new RuntimeException(e); // now ignore } String beReplaced; if (compilerVersion == null) { - //old version + // old version String repeated = new String(new char[40 - libraryName.length() - 2]).replace("\0", "_"); beReplaced = "__" + libraryName + repeated; } else if (compilerVersion.equalsIgnoreCase("v5")) { - //0.5.4 version - String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + // 0.5.4 version + String libraryNameKeccak256 = + ByteArray.toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1834,9 +1826,13 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa return Hex.decode(code); } - public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + public static Contract.TriggerSmartContract triggerCallContract( + byte[] address, + byte[] contractAddress, + long callValue, + byte[] data, + long tokenValue, + String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1859,26 +1855,25 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { System.arraycopy(ownerAddress, 0, combined, txRawDataHash.length, ownerAddress.length); return Hash.sha3omit12(combined); - } - public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + public boolean updateSetting( + byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - UpdateSettingContract updateSettingContract = createUpdateSettingContract(owner, - contractAddress, consumeUserResourcePercent); + UpdateSettingContract updateSettingContract = + createUpdateSettingContract(owner, contractAddress, consumeUserResourcePercent); TransactionExtention transactionExtention = rpcCli.updateSetting(updateSettingContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1892,24 +1887,21 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig owner = getAddress(); } - UpdateEnergyLimitContract updateEnergyLimitContract = createUpdateEnergyLimitContract( - owner, - contractAddress, originEnergyLimit); + UpdateEnergyLimitContract updateEnergyLimitContract = + createUpdateEnergyLimitContract(owner, contractAddress, originEnergyLimit); - TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + TransactionExtention transactionExtention = rpcCli.updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } return processTransactionExtention(transactionExtention); - } public boolean clearContractABI(byte[] owner, byte[] contractAddress) @@ -1924,8 +1916,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1933,33 +1925,53 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) return processTransactionExtention(transactionExtention); } - public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + public boolean deployContract( + byte[] owner, + String contractName, + String ABI, + String code, + long feeLimit, + long value, + long consumeUserResourcePercent, + long originEnergyLimit, + long tokenValue, + String tokenId, + String libraryAddressPair, + String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - CreateSmartContract contractDeployContract = createContractDeployContract(contractName, owner, - ABI, code, value, consumeUserResourcePercent, originEnergyLimit, tokenValue, tokenId, - libraryAddressPair, compilerVersion); + CreateSmartContract contractDeployContract = + createContractDeployContract( + contractName, + owner, + ABI, + code, + value, + consumeUserResourcePercent, + originEnergyLimit, + tokenValue, + tokenId, + libraryAddressPair, + compilerVersion); TransactionExtention transactionExtention = rpcCli.deployContract(contractDeployContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + Transaction.raw.Builder rawBuilder = + transactionExtention.getTransaction().getRawData().toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1975,23 +1987,28 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str texBuilder.setTxid(transactionExtention.getTxid()); transactionExtention = texBuilder.build(); -// byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); -// System.out.println( -// "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); + // byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); + // System.out.println( + // "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); return processTransactionExtention(transactionExtention); - } - public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + public boolean triggerContract( + byte[] owner, + byte[] contractAddress, + long callValue, + byte[] data, + long feeLimit, + long tokenValue, + String tokenId, + boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, - callValue, data, tokenValue, tokenId); + Contract.TriggerSmartContract triggerContract = + triggerCallContract(owner, contractAddress, callValue, data, tokenValue, tokenId); TransactionExtention transactionExtention; if (isConstant) { transactionExtention = rpcCli.triggerConstantContract(triggerContract); @@ -2002,28 +2019,28 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create call trx failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); return false; } Transaction transaction = transactionExtention.getTransaction(); // for constant - if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + if (transaction.getRetCount() != 0 + && transactionExtention.getConstantResult(0) != null + && transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); - System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + System.out.println( + ":" + ByteArray.toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + Transaction.raw.Builder rawBuilder = + transactionExtention.getTransaction().getRawData().toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2046,11 +2063,10 @@ public static SmartContract getContract(byte[] address) { return rpcCli.getContract(address); } - public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { - Contract.AccountPermissionUpdateContract contract = createAccountPermissionContract(owner, - permissionJson); + Contract.AccountPermissionUpdateContract contract = + createAccountPermissionContract(owner, permissionJson); TransactionExtention transactionExtention = rpcCli.accountPermissionUpdate(contract); return processTransactionExtention(transactionExtention); } @@ -2124,7 +2140,6 @@ public Contract.AccountPermissionUpdateContract createAccountPermissionContract( return builder.build(); } - public Transaction addTransactionSign(Transaction transaction) throws CipherException, IOException, CancelException { if (transaction.getRawData().getTimestamp() == 0) { @@ -2151,8 +2166,7 @@ public Transaction addTransactionSign(Transaction transaction) } public static Optional GetMerkleTreeVoucherInfo( - OutputPointInfo info, - boolean showErrorMsg) { + OutputPointInfo info, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.GetMerkleTreeVoucherInfo(info)); @@ -2168,8 +2182,8 @@ public static Optional GetMerkleTreeVoucherInfo( return Optional.empty(); } - public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + public static Optional scanNoteByIvk( + IvkDecryptParameters ivkDecryptParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2185,8 +2199,8 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry return Optional.empty(); } - public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + public static Optional scanNoteByOvk( + OvkDecryptParameters ovkDecryptParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2270,8 +2284,8 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall return processShieldedTransaction(transactionExtention, wallet); } - public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + public static boolean sendShieldedCoinWithoutAsk( + PrivateParametersWithoutAsk privateParameters, byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2288,7 +2302,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2296,8 +2310,8 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Any any = transaction.getRawData().getContract(0).getParameter(); ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); - ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + ShieldedTransferContract.Builder contractBuild = + shieldContract.toBuilder().clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2312,11 +2326,16 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri contractBuild.addSpendDescription(spendDescription.build()); } - Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + Transaction.raw.Builder rawBuilder = + transaction + .toBuilder() + .getRawDataBuilder() + .clearContract() + .addContract( + Transaction.Contract.newBuilder() + .setType(ContractType.ShieldedTransferContract) + .setParameter(Any.pack(contractBuild.build())) + .build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2325,8 +2344,8 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri return processShieldedTransaction(transactionExtention, wallet); } - public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + public static Optional isNoteSpend( + NoteParameters noteParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2392,14 +2411,14 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); - TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + TransactionExtention transactionExtention = + rpcCli.updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -2414,5 +2433,4 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } - } diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf deleted file mode 100644 index 171520768..000000000 --- a/src/main/resources/config-back.conf +++ /dev/null @@ -1,20 +0,0 @@ -net { - type = mainnet -} - -fullnode = { - ip.list = [ - "47.89.189.124:50055", - "47.89.178.193:50055" - ] -} - -#soliditynode = { -# ip.list = [ -# "127.0.0.1:50052" -# ] -#} -crypto { - engine=sm2 -} -RPC_version = 2 From 3009c6a32dc0452e2b3caae76b3667ad235d4ab0 Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 7 Jan 2020 12:26:30 +0800 Subject: [PATCH 241/445] rm useless rpc --- src/main/java/org/tron/walletcli/Client.java | 1 + src/main/protos/api/api.proto | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index d566d34f2..104db78f1 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2870,6 +2870,7 @@ private void getMarketPriceByPair (String[] parameters) { } } + private void create2(String[] parameters) { if (parameters == null || parameters.length != 3) { System.out.println("Using create2 command needs 3 parameters like: "); diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6b732a923..e58849cf9 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -707,8 +707,6 @@ service Wallet { rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } - rpc GetMarketPositionKey (MarketSellAssetContract) returns (MarketOrderPosition) { - } }; service WalletSolidity { @@ -873,6 +871,7 @@ service WalletSolidity { rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } + }; service WalletExtension { From 775b16af01fb82c42809afe833b27697ef2baa82 Mon Sep 17 00:00:00 2001 From: zhenping Date: Tue, 7 Jan 2020 14:05:21 +0800 Subject: [PATCH 242/445] add the SM3Hash class Signed-off-by: zhenping --- .../java/org/tron/common/crypto/SM3Hash.java | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 src/main/java/org/tron/common/crypto/SM3Hash.java diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java new file mode 100644 index 000000000..485c90f0c --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -0,0 +1,300 @@ +package org.tron.common.crypto; + +/* + * Copyright 2011 Google Inc. + * Copyright 2014 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.protobuf.ByteString; +import org.spongycastle.crypto.digests.SM3Digest; +import org.tron.common.utils.ByteArray; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Arrays; + +import static com.google.common.base.Preconditions.checkArgument; + + +/** + * A SM3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be + * used as keys in a map. It also checks that the length is correct and provides a bit more type + * safety. + */ +public class SM3Hash implements Serializable, Comparable { + + public static final int LENGTH = 32; // bytes + public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); + + private final byte[] bytes; + + public SM3Hash(long num, byte[] hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + public SM3Hash(long num, SM3Hash hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Use {@link #wrap(byte[])} instead. + */ + @Deprecated + public SM3Hash(byte[] rawHashBytes) { + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Creates a new instance that wraps the given hash value. + * + * @param rawHashBytes the raw hash bytes to wrap + * @return a new instance + * @throws IllegalArgumentException if the given array length is not exactly 32 + */ + @SuppressWarnings("deprecation") // the constructor will be made private in the future + public static SM3Hash wrap(byte[] rawHashBytes) { + return new SM3Hash(rawHashBytes); + } + + public static SM3Hash wrap(ByteString rawHashByteString) { + return wrap(rawHashByteString.toByteArray()); + } + + /** + * Use {@link #of(byte[])} instead: this old name is ambiguous. + */ + @Deprecated + public static SM3Hash create(byte[] contents) { + return of(contents); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + */ + public static SM3Hash of(byte[] contents) { + return wrap(hash(contents)); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given file's contents. + * The file contents are read fully into memory, so this method should only be used with small + * files. + * + * @param file the file on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + * @throws IOException if an error occurs while reading the file + */ + public static SM3Hash of(File file) throws IOException { + + try (FileInputStream in = new FileInputStream(file)) { + return of(ByteStreams.toByteArray(in)); + } + } + + /** + * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. + */ + @Deprecated + public static SM3Hash createDouble(byte[] contents) { + return twiceOf(contents); + } + + /** + * Creates a new instance containing the hash of the calculated hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (two-time) hash + */ + public static SM3Hash twiceOf(byte[] contents) { + return wrap(hashTwice(contents)); + } + + /** + * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SHA-256 MessageDigest instance + */ + public static SM3Digest newDigest() { + return new SM3Digest(); + } + + /** + * Calculates the SHA-256 hash of the given bytes. + * + * @param input the bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input) { + return hash(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input, int offset, int length) { + SM3Digest digest = newDigest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * + * @param input the bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input) { + return hashTwice(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input, int offset, int length) { + SM3Digest digest = newDigest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.reset(); + digest.update(eHash,0,eHash.length); + digest.doFinal(eHash,0); + return eHash; + } + + /** + * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the + * two ranges and then passing the result to {@link #hashTwice(byte[])}. + */ + public static byte[] hashTwice(byte[] input1, int offset1, int length1, + byte[] input2, int offset2, int length2) { + SM3Digest digest = newDigest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.doFinal(eHash,0); + return eHash; + } + + private byte[] generateBlockId(long blockNum, SM3Hash blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.getBytes().length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); + return hash; + } + + private byte[] generateBlockId(long blockNum, byte[] blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof SM3Hash)) { + return false; + } + return Arrays.equals(bytes, ((SM3Hash) o).bytes); + } + + @Override + public String toString() { + return ByteArray.toHexString(bytes); + } + + /** + * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable + * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. + * the value as a big integer lower than the target value). + */ + @Override + public int hashCode() { + // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. + return Ints + .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + } + + /** + * Returns the bytes interpreted as a positive integer. + */ + public BigInteger toBigInteger() { + return new BigInteger(1, bytes); + } + + /** + * Returns the internal byte array, without defensively copying. Therefore do NOT modify the + * returned array. + */ + public byte[] getBytes() { + return bytes; + } + + /** + * For pb return ByteString. + */ + public ByteString getByteString() { + return ByteString.copyFrom(bytes); + } + + @Override + public int compareTo(final SM3Hash other) { + for (int i = LENGTH - 1; i >= 0; i--) { + final int thisByte = this.bytes[i] & 0xff; + final int otherByte = other.bytes[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } + } + return 0; + } +} + From 6fc6c35c174d1fd87ab06090d0bb050351f35ca5 Mon Sep 17 00:00:00 2001 From: zhenping Date: Tue, 7 Jan 2020 14:51:51 +0800 Subject: [PATCH 243/445] add the Hash interface Signed-off-by: zhenping --- .../java/org/tron/common/crypto/HashInterface.java | 11 +++++++++++ src/main/java/org/tron/common/crypto/SM3Hash.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/tron/common/crypto/HashInterface.java diff --git a/src/main/java/org/tron/common/crypto/HashInterface.java b/src/main/java/org/tron/common/crypto/HashInterface.java new file mode 100644 index 000000000..643eaf486 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/HashInterface.java @@ -0,0 +1,11 @@ +package org.tron.common.crypto; + +import com.google.protobuf.ByteString; + +public interface HashInterface { + + byte[] getBytes(); + + ByteString getByteString(); + +} diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index 485c90f0c..deb115daf 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -39,7 +39,7 @@ * used as keys in a map. It also checks that the length is correct and provides a bit more type * safety. */ -public class SM3Hash implements Serializable, Comparable { +public class SM3Hash implements Serializable, Comparable, HashInterface { public static final int LENGTH = 32; // bytes public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); From 215d5e6457af85628928de29af65683fa42f1c63 Mon Sep 17 00:00:00 2001 From: zhenping Date: Tue, 7 Jan 2020 15:08:25 +0800 Subject: [PATCH 244/445] revise the SM3Hash comment Signed-off-by: zhenping --- src/main/java/org/tron/common/crypto/SM3Hash.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index deb115daf..91aeb34ee 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -136,17 +136,17 @@ public static SM3Hash twiceOf(byte[] contents) { } /** - * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the * checked exception that can never occur with a RuntimeException. * - * @return a new SHA-256 MessageDigest instance + * @return a new SM3 MessageDigest instance */ public static SM3Digest newDigest() { return new SM3Digest(); } /** - * Calculates the SHA-256 hash of the given bytes. + * Calculates the SM3 hash of the given bytes. * * @param input the bytes to hash * @return the hash (in big-endian order) @@ -156,7 +156,7 @@ public static byte[] hash(byte[] input) { } /** - * Calculates the SHA-256 hash of the given byte range. + * Calculates the SM3 hash of the given byte range. * * @param input the array containing the bytes to hash * @param offset the offset within the array of the bytes to hash @@ -173,7 +173,7 @@ public static byte[] hash(byte[] input, int offset, int length) { } /** - * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * Calculates the SM3 hash of the given bytes, and then hashes the resulting hash again. * * @param input the bytes to hash * @return the double-hash (in big-endian order) @@ -183,7 +183,7 @@ public static byte[] hashTwice(byte[] input) { } /** - * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * Calculates the SM3 hash of the given byte range, and then hashes the resulting hash again. * * @param input the array containing the bytes to hash * @param offset the offset within the array of the bytes to hash From 4e402c49aeb908a34344eba1a47ab2f238a5c989 Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 7 Jan 2020 16:04:57 +0800 Subject: [PATCH 245/445] add getMarketOrderListByPair and getMarketPairList --- src/main/java/org/tron/walletcli/Client.java | 61 +++++++++++++++++-- .../org/tron/walletcli/WalletApiWrapper.java | 14 ++++- .../org/tron/walletserver/GrpcClient.java | 31 +++++++++- .../java/org/tron/walletserver/WalletApi.java | 12 +++- src/main/protos/api/api.proto | 14 +++++ src/main/protos/core/Tron.proto | 5 ++ 6 files changed, 130 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 104db78f1..ac46044d6 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -97,6 +97,8 @@ public class Client { "GetIncomingViewingKey", "GetMarketOrderByAccount", "GetMarketPriceByPair", + "getMarketOrderListByPair", + "getMarketPairList", "GetNextMaintenanceTime", "GetNkFromNsk", "GetProposal", @@ -1480,7 +1482,8 @@ private void exchangeTransaction(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { System.out.println("Using exchangeTransaction command needs 4 or 5 parameters like: "); - System.out.println("exchangeTransaction [OwnerAddress] exchange_id token_id quantity expected"); + System.out + .println("exchangeTransaction [OwnerAddress] exchange_id token_id quantity expected"); return; } @@ -2842,7 +2845,8 @@ private void getMarketOrderByAccount(String[] parameters) { return; } - Optional marketOrderList = walletApiWrapper.getMarketOrderByAccount(ownerAddress); + Optional marketOrderList = walletApiWrapper + .getMarketOrderByAccount(ownerAddress); if (!marketOrderList.isPresent()) { System.out.println("GetMarketOrderByAccount failed !!!"); } else { @@ -2850,7 +2854,7 @@ private void getMarketOrderByAccount(String[] parameters) { } } - private void getMarketPriceByPair (String[] parameters) { + private void getMarketPriceByPair(String[] parameters) { if (parameters == null || parameters.length != 2) { System.out.println("Using GetMarketPriceByPair command needs 2 parameters like: "); System.out.println( @@ -2862,7 +2866,8 @@ private void getMarketPriceByPair (String[] parameters) { byte[] sellTokenId = parameters[index++].getBytes(); byte[] buyTokenId = parameters[index++].getBytes(); - Optional marketPriceList = walletApiWrapper.GetMarketPriceByPair(sellTokenId, buyTokenId); + Optional marketPriceList = walletApiWrapper + .getMarketPriceByPair(sellTokenId, buyTokenId); if (!marketPriceList.isPresent()) { System.out.println("GetMarketPriceByPair failed !!!"); } else { @@ -2871,6 +2876,46 @@ private void getMarketPriceByPair (String[] parameters) { } + private void getMarketOrderListByPair(String[] parameters) { + if (parameters == null || parameters.length != 2) { + System.out.println("Using getMarketOrderListByPair command needs 2 parameters like: "); + System.out.println( + "getMarketOrderListByPair sellTokenId buyTokenId"); + return; + } + + int index = 0; + byte[] sellTokenId = parameters[index++].getBytes(); + byte[] buyTokenId = parameters[index++].getBytes(); + + Optional orderListByPair = walletApiWrapper + .getMarketOrderListByPair(sellTokenId, buyTokenId); + if (!orderListByPair.isPresent()) { + System.out.println("getMarketOrderListByPair failed !!!"); + } else { + System.out.println(Utils.formatMessageString(orderListByPair.get())); + } + } + + + private void getMarketPairList(String[] parameters) { + if (parameters == null || parameters.length != 0) { + System.out.println("Using getMarketPairList command does not need any parameters, like: "); + System.out.println( + "getMarketPairList"); + return; + } + + Optional pairList = walletApiWrapper + .getMarketPairList(); + if (!pairList.isPresent()) { + System.out.println("getMarketPairList failed !!!"); + } else { + System.out.println(Utils.formatMessageString(pairList.get())); + } + } + + private void create2(String[] parameters) { if (parameters == null || parameters.length != 3) { System.out.println("Using create2 command needs 3 parameters like: "); @@ -3412,6 +3457,14 @@ private void run() { getMarketPriceByPair(parameters); break; } + case "getmarketorderlistbypair": { + getMarketOrderListByPair(parameters); + break; + } + case "getmarketpairlist": { + getMarketPairList(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 90c91e9e2..95f153fe1 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1207,8 +1207,20 @@ public Optional getMarketOrderByAccount(byte[] address) { return WalletApi.getMarketOrderByAccount(address); } - public Optional GetMarketPriceByPair( + public Optional getMarketPriceByPair( byte[] sellTokenId, byte[] buyTokenId) { return WalletApi.GetMarketPriceByPair(sellTokenId, buyTokenId); } + + + public Optional getMarketOrderListByPair( + byte[] sellTokenId, byte[] buyTokenId) { + return WalletApi.getMarketOrderListByPair(sellTokenId, buyTokenId); + } + + + public Optional getMarketPairList() { + return WalletApi.getMarketPairList(); + } + } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 35aee3494..e16425419 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -919,7 +919,7 @@ public Optional getMarketOrderByAccount(byte[] address) { return Optional.ofNullable(marketOrderList); } - public Optional GetMarketPriceByPair(byte[] sellTokenId, byte[] buyTokenId) { + public Optional getMarketPriceByPair(byte[] sellTokenId, byte[] buyTokenId) { MarketOrderPair request = MarketOrderPair.newBuilder() .setSellTokenId(ByteString.copyFrom(sellTokenId)) @@ -935,4 +935,33 @@ public Optional GetMarketPriceByPair(byte[] sellTokenId, byte[] return Optional.ofNullable(marketPriceList); } + + public Optional getMarketOrderListByPair(byte[] sellTokenId, byte[] buyTokenId) { + MarketOrderPair request = + MarketOrderPair.newBuilder() + .setSellTokenId(ByteString.copyFrom(sellTokenId)) + .setBuyTokenId(ByteString.copyFrom(buyTokenId)) + .build(); + + MarketOrderList marketOrderList; + if (blockingStubSolidity != null) { + marketOrderList =blockingStubSolidity.getMarketOrderListByPair(request); + } else { + marketOrderList = blockingStubFull.getMarketOrderListByPair(request); + } + return Optional.ofNullable(marketOrderList); + } + + + public Optional getMarketPairList() { + + MarketOrderPairList orderPairList; + if (blockingStubSolidity != null) { + orderPairList =blockingStubSolidity.getMarketPairList(EmptyMessage.newBuilder().build()); + } else { + orderPairList = blockingStubFull.getMarketPairList(EmptyMessage.newBuilder().build()); + } + return Optional.ofNullable(orderPairList); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 5b2e01226..645be1279 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2425,6 +2425,16 @@ public static Optional getMarketOrderByAccount(byte[] address) public static Optional GetMarketPriceByPair( byte[] sellTokenId, byte[] buyTokenId) { - return rpcCli.GetMarketPriceByPair(sellTokenId, buyTokenId); + return rpcCli.getMarketPriceByPair(sellTokenId, buyTokenId); } + + public static Optional getMarketOrderListByPair( + byte[] sellTokenId, byte[] buyTokenId) { + return rpcCli.getMarketOrderListByPair(sellTokenId, buyTokenId); + } + + public static Optional getMarketPairList() { + return rpcCli.getMarketPairList(); + } + } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index e58849cf9..9fc25d970 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -707,6 +707,13 @@ service Wallet { rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } + rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { + } + + rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { + } + + }; service WalletSolidity { @@ -872,6 +879,13 @@ service WalletSolidity { rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } + + rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { + } + + rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { + } + }; service WalletExtension { diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 9fb0599b1..6ffb7e9e7 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -707,6 +707,11 @@ message MarketOrderList { repeated MarketOrder orders = 1; } + +message MarketOrderPairList { + repeated MarketOrderPair orderPair = 1; +} + message MarketOrderPair{ bytes sell_token_id = 4; bytes buy_token_id = 5; From f2a9a91f9928252f42ae099069343e1adcc59ce0 Mon Sep 17 00:00:00 2001 From: zhenping Date: Tue, 7 Jan 2020 14:05:21 +0800 Subject: [PATCH 246/445] add the SM3Hash class Signed-off-by: zhenping --- .../java/org/tron/common/crypto/SM3Hash.java | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 src/main/java/org/tron/common/crypto/SM3Hash.java diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java new file mode 100644 index 000000000..485c90f0c --- /dev/null +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -0,0 +1,300 @@ +package org.tron.common.crypto; + +/* + * Copyright 2011 Google Inc. + * Copyright 2014 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.protobuf.ByteString; +import org.spongycastle.crypto.digests.SM3Digest; +import org.tron.common.utils.ByteArray; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Arrays; + +import static com.google.common.base.Preconditions.checkArgument; + + +/** + * A SM3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be + * used as keys in a map. It also checks that the length is correct and provides a bit more type + * safety. + */ +public class SM3Hash implements Serializable, Comparable { + + public static final int LENGTH = 32; // bytes + public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); + + private final byte[] bytes; + + public SM3Hash(long num, byte[] hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + public SM3Hash(long num, SM3Hash hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Use {@link #wrap(byte[])} instead. + */ + @Deprecated + public SM3Hash(byte[] rawHashBytes) { + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Creates a new instance that wraps the given hash value. + * + * @param rawHashBytes the raw hash bytes to wrap + * @return a new instance + * @throws IllegalArgumentException if the given array length is not exactly 32 + */ + @SuppressWarnings("deprecation") // the constructor will be made private in the future + public static SM3Hash wrap(byte[] rawHashBytes) { + return new SM3Hash(rawHashBytes); + } + + public static SM3Hash wrap(ByteString rawHashByteString) { + return wrap(rawHashByteString.toByteArray()); + } + + /** + * Use {@link #of(byte[])} instead: this old name is ambiguous. + */ + @Deprecated + public static SM3Hash create(byte[] contents) { + return of(contents); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + */ + public static SM3Hash of(byte[] contents) { + return wrap(hash(contents)); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given file's contents. + * The file contents are read fully into memory, so this method should only be used with small + * files. + * + * @param file the file on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + * @throws IOException if an error occurs while reading the file + */ + public static SM3Hash of(File file) throws IOException { + + try (FileInputStream in = new FileInputStream(file)) { + return of(ByteStreams.toByteArray(in)); + } + } + + /** + * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. + */ + @Deprecated + public static SM3Hash createDouble(byte[] contents) { + return twiceOf(contents); + } + + /** + * Creates a new instance containing the hash of the calculated hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (two-time) hash + */ + public static SM3Hash twiceOf(byte[] contents) { + return wrap(hashTwice(contents)); + } + + /** + * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SHA-256 MessageDigest instance + */ + public static SM3Digest newDigest() { + return new SM3Digest(); + } + + /** + * Calculates the SHA-256 hash of the given bytes. + * + * @param input the bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input) { + return hash(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input, int offset, int length) { + SM3Digest digest = newDigest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * + * @param input the bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input) { + return hashTwice(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input, int offset, int length) { + SM3Digest digest = newDigest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.reset(); + digest.update(eHash,0,eHash.length); + digest.doFinal(eHash,0); + return eHash; + } + + /** + * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the + * two ranges and then passing the result to {@link #hashTwice(byte[])}. + */ + public static byte[] hashTwice(byte[] input1, int offset1, int length1, + byte[] input2, int offset2, int length2) { + SM3Digest digest = newDigest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.doFinal(eHash,0); + return eHash; + } + + private byte[] generateBlockId(long blockNum, SM3Hash blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.getBytes().length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); + return hash; + } + + private byte[] generateBlockId(long blockNum, byte[] blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof SM3Hash)) { + return false; + } + return Arrays.equals(bytes, ((SM3Hash) o).bytes); + } + + @Override + public String toString() { + return ByteArray.toHexString(bytes); + } + + /** + * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable + * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. + * the value as a big integer lower than the target value). + */ + @Override + public int hashCode() { + // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. + return Ints + .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + } + + /** + * Returns the bytes interpreted as a positive integer. + */ + public BigInteger toBigInteger() { + return new BigInteger(1, bytes); + } + + /** + * Returns the internal byte array, without defensively copying. Therefore do NOT modify the + * returned array. + */ + public byte[] getBytes() { + return bytes; + } + + /** + * For pb return ByteString. + */ + public ByteString getByteString() { + return ByteString.copyFrom(bytes); + } + + @Override + public int compareTo(final SM3Hash other) { + for (int i = LENGTH - 1; i >= 0; i--) { + final int thisByte = this.bytes[i] & 0xff; + final int otherByte = other.bytes[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } + } + return 0; + } +} + From 358b23025c6441b3841ee8e54afe2a647df61c50 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 7 Jan 2020 18:35:05 +0800 Subject: [PATCH 247/445] rebase local commit --- .../tron/common/utils/TransactionUtils.java | 160 ++-- .../java/org/tron/common/utils/Utils.java | 560 ++++++------- .../tron/demo/TransactionSignDemoForSM2.java | 36 +- .../java/org/tron/keystore/WalletUtils.java | 111 ++- .../java/org/tron/walletserver/WalletApi.java | 769 +++++++++--------- src/main/resources/config-back.conf | 20 + 6 files changed, 787 insertions(+), 869 deletions(-) create mode 100644 src/main/resources/config-back.conf diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index a1446fa0e..ccaf56222 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -16,14 +16,12 @@ package org.tron.common.utils; import com.google.protobuf.ByteString; -import com.typesafe.config.Config; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Hash; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.sm2.SM3; -import org.tron.core.config.Configuration; +import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -40,18 +38,9 @@ public class TransactionUtils { * @param transaction {@link Transaction} transaction * @return byte[] the hash of the transaction's data bytes which have no id */ - private static boolean isEckey = true; - - static { - Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant - if (config.hasPath("crypto.engine")) { - isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); - } - } - public static byte[] getHash(Transaction transaction) { Transaction.Builder tmp = transaction.toBuilder(); - // tmp.clearId(); + //tmp.clearId(); return Sha256Hash.hash(tmp.build().toByteArray()); } @@ -61,116 +50,69 @@ public static byte[] getOwner(Transaction.Contract contract) { try { switch (contract.getType()) { case AccountCreateContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.AccountCreateContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.AccountCreateContract.class).getOwnerAddress(); break; case TransferContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.TransferContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.TransferContract.class) + .getOwnerAddress(); break; case TransferAssetContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.TransferAssetContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.TransferAssetContract.class).getOwnerAddress(); break; case VoteAssetContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.VoteAssetContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteAssetContract.class) + .getOwnerAddress(); break; case VoteWitnessContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.VoteWitnessContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteWitnessContract.class) + .getOwnerAddress(); break; case WitnessCreateContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.WitnessCreateContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.WitnessCreateContract.class).getOwnerAddress(); break; case AssetIssueContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.AssetIssueContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.AssetIssueContract.class) + .getOwnerAddress(); break; case ParticipateAssetIssueContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) + .getOwnerAddress(); break; case CreateSmartContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.CreateSmartContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.CreateSmartContract.class) + .getOwnerAddress(); break; case TriggerSmartContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.TriggerSmartContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.TriggerSmartContract.class).getOwnerAddress(); break; case FreezeBalanceContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.FreezeBalanceContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.FreezeBalanceContract.class).getOwnerAddress(); break; case UnfreezeBalanceContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class).getOwnerAddress(); break; case UnfreezeAssetContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class).getOwnerAddress(); break; case WithdrawBalanceContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class) - .getOwnerAddress(); + owner = contract.getParameter() + .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class).getOwnerAddress(); break; case UpdateAssetContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.UpdateAssetContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.UpdateAssetContract.class) + .getOwnerAddress(); break; case AccountPermissionUpdateContract: - owner = - contract - .getParameter() - .unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) - .getOwnerAddress(); + owner = contract.getParameter().unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) + .getOwnerAddress(); break; default: return null; @@ -187,7 +129,7 @@ public static String getBase64FromByteString(ByteString sign) { byte[] s = sign.substring(32, 64).toByteArray(); byte v = sign.byteAt(64); if (v < 27) { - v += 27; // revId -> v + v += 27; //revId -> v } ECDSASignature signature = ECDSASignature.fromComponents(r, s, v); return signature.toBase64(); @@ -200,8 +142,8 @@ public static String getBase64FromByteString(ByteString sign) { * 4. check balance */ public static boolean validTransaction(Transaction signedTransaction) { - assert (signedTransaction.getSignatureCount() - == signedTransaction.getRawData().getContractCount()); + assert (signedTransaction.getSignatureCount() == + signedTransaction.getRawData().getContractCount()); List listContract = signedTransaction.getRawData().getContractList(); byte[] hash = Sha256Hash.hash(signedTransaction.getRawData().toByteArray()); int count = signedTransaction.getSignatureCount(); @@ -212,9 +154,8 @@ public static boolean validTransaction(Transaction signedTransaction) { try { Transaction.Contract contract = listContract.get(i); byte[] owner = getOwner(contract); - byte[] address = - ECKey.signatureToAddress( - hash, getBase64FromByteString(signedTransaction.getSignature(i))); + byte[] address = ECKey + .signatureToAddress(hash, getBase64FromByteString(signedTransaction.getSignature(i))); if (!Arrays.equals(owner, address)) { return false; } @@ -228,12 +169,7 @@ public static boolean validTransaction(Transaction signedTransaction) { public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); - byte[] hash; - if (isEckey) { - hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); - } else { - hash = SM3.hash(transaction.getRawData().toByteArray()); - } + byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); @@ -242,10 +178,10 @@ public static Transaction sign(Transaction transaction, SignInterface myKey) { } public static Transaction setTimestamp(Transaction transaction) { - long currentTime = System.currentTimeMillis(); // *1000000 + System.nanoTime()%1000000; + long currentTime = System.currentTimeMillis();//*1000000 + System.nanoTime()%1000000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = - transaction.getRawData().toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() + .toBuilder(); rowBuilder.setTimestamp(currentTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -255,8 +191,8 @@ public static Transaction setExpirationTime(Transaction transaction) { if (transaction.getSignatureCount() == 0) { long expirationTime = System.currentTimeMillis() + 6 * 60 * 60 * 1000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = - transaction.getRawData().toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() + .toBuilder(); rowBuilder.setExpiration(expirationTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -275,8 +211,8 @@ public static Transaction setPermissionId(Transaction transaction) throws Cancel } if (permission_id != 0) { Transaction.raw.Builder raw = transaction.getRawData().toBuilder(); - Transaction.Contract.Builder contract = - raw.getContract(0).toBuilder().setPermissionId(permission_id); + Transaction.Contract.Builder contract = raw.getContract(0).toBuilder() + .setPermissionId(permission_id); raw.clearContract(); raw.addContract(contract); transaction = transaction.toBuilder().setRawData(raw).build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index a22f42854..fbc1256ff 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Scanner; public class Utils { public static final String PERMISSION_ID = "Permission_id"; @@ -76,7 +77,9 @@ private char[] getChars(byte[] bytes) { return cb.array(); } - /** yyyy-MM-dd */ + /** + * yyyy-MM-dd + */ public static Date strToDateLong(String strDate) { if (strDate.length() == 10) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); @@ -117,22 +120,18 @@ public static String printTransaction(TransactionExtention transactionExtention) public static String printTransactionList(TransactionList transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); - transactions.stream() - .forEach( - transaction -> { - jsonArray.add(printTransactionToJSON(transaction, true)); - }); + transactions.stream().forEach(transaction -> { + jsonArray.add(printTransactionToJSON(transaction, true)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } public static String printTransactionList(TransactionListExtention transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); - transactions.stream() - .forEach( - transaction -> { - jsonArray.add(printTransactionExtentionToJSON(transaction)); - }); + transactions.stream().forEach(transaction -> { + jsonArray.add(printTransactionExtentionToJSON(transaction)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -149,22 +148,18 @@ public static String printBlockExtention(BlockExtention blockExtention) { public static String printBlockList(BlockList blockList) { JSONArray jsonArray = new JSONArray(); List blocks = blockList.getBlockList(); - blocks.stream() - .forEach( - block -> { - jsonArray.add(printBlockToJSON(block)); - }); + blocks.stream().forEach(block -> { + jsonArray.add(printBlockToJSON(block)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } public static String printBlockList(BlockListExtention blockList) { JSONArray jsonArray = new JSONArray(); List blocks = blockList.getBlockList(); - blocks.stream() - .forEach( - block -> { - jsonArray.add(printBlockExtentionToJSON(block)); - }); + blocks.stream().forEach(block -> { + jsonArray.add(printBlockExtentionToJSON(block)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -172,9 +167,9 @@ public static String printTransactionSignWeight(TransactionSignWeight transactio String string = JsonFormat.printToString(transactionSignWeight, true); JSONObject jsonObject = JSONObject.parseObject(string); JSONObject jsonObjectExt = jsonObject.getJSONObject(TRANSACTION); - jsonObjectExt.put( - TRANSACTION, - printTransactionToJSON(transactionSignWeight.getTransaction().getTransaction(), true)); + jsonObjectExt.put(TRANSACTION, + printTransactionToJSON(transactionSignWeight.getTransaction().getTransaction(), + true)); jsonObject.put(TRANSACTION, jsonObjectExt); return JsonFormatUtil.formatJson(jsonObject.toJSONString()); } @@ -184,9 +179,9 @@ public static String printTransactionApprovedList( String string = JsonFormat.printToString(transactionApprovedList, true); JSONObject jsonObject = JSONObject.parseObject(string); JSONObject jsonObjectExt = jsonObject.getJSONObject(TRANSACTION); - jsonObjectExt.put( - TRANSACTION, - printTransactionToJSON(transactionApprovedList.getTransaction().getTransaction(), true)); + jsonObjectExt.put(TRANSACTION, + printTransactionToJSON(transactionApprovedList.getTransaction().getTransaction(), + true)); jsonObject.put(TRANSACTION, jsonObjectExt); return JsonFormatUtil.formatJson(jsonObject.toJSONString()); } @@ -253,14 +248,12 @@ public static byte[] generateContractAddress(Transaction trx, byte[] ownerAddres public static JSONObject printBlockExtentionToJSON(BlockExtention blockExtention) { JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(blockExtention, true)); - if (blockExtention.getTransactionsCount() > 0) { + if (blockExtention.getTransactionsCount() > 0 ) { JSONArray jsonArray = new JSONArray(); List transactions = blockExtention.getTransactionsList(); - transactions.stream() - .forEach( - transaction -> { - jsonArray.add(printTransactionExtentionToJSON(transaction)); - }); + transactions.stream().forEach(transaction -> { + jsonArray.add(printTransactionExtentionToJSON(transaction)); + }); jsonObject.put(TRANSACTION, jsonArray); } return jsonObject; @@ -271,11 +264,9 @@ public static JSONObject printBlockToJSON(Block block) { if (block.getTransactionsCount() > 0) { JSONArray jsonArray = new JSONArray(); List transactions = block.getTransactionsList(); - transactions.stream() - .forEach( - transaction -> { - jsonArray.add(printTransactionToJSON(transaction, true)); - }); + transactions.stream().forEach(transaction -> { + jsonArray.add(printTransactionToJSON(transaction, true)); + }); jsonObject.put(TRANSACTION, jsonArray); } return jsonObject; @@ -283,265 +274,230 @@ public static JSONObject printBlockToJSON(Block block) { public static JSONObject printTransactionExtentionToJSON( TransactionExtention transactionExtention) { - JSONObject jsonObject = - JSONObject.parseObject(JsonFormat.printToString(transactionExtention, true)); + JSONObject jsonObject = JSONObject + .parseObject(JsonFormat.printToString(transactionExtention, true)); if (transactionExtention.getResult().getResult()) { - JSONObject transactionOjbect = - printTransactionToJSON(transactionExtention.getTransaction(), true); + JSONObject transactionOjbect = printTransactionToJSON(transactionExtention.getTransaction(), + true); jsonObject.put(TRANSACTION, transactionOjbect); } return jsonObject; } public static JSONObject printTransactionToJSON(Transaction transaction, boolean selfType) { - JSONObject jsonTransaction = - JSONObject.parseObject(JsonFormat.printToString(transaction, selfType)); + JSONObject jsonTransaction = JSONObject.parseObject(JsonFormat.printToString(transaction, + selfType)); JSONArray contracts = new JSONArray(); - transaction.getRawData().getContractList().stream() - .forEach( - contract -> { - try { - JSONObject contractJson = null; - Any contractParameter = contract.getParameter(); - switch (contract.getType()) { - case AccountCreateContract: - AccountCreateContract accountCreateContract = - contractParameter.unpack(AccountCreateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(accountCreateContract, selfType)); - break; - case TransferContract: - TransferContract transferContract = - contractParameter.unpack(TransferContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(transferContract, selfType)); - break; - case TransferAssetContract: - TransferAssetContract transferAssetContract = - contractParameter.unpack(TransferAssetContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(transferAssetContract, selfType)); - break; - case VoteAssetContract: - VoteAssetContract voteAssetContract = - contractParameter.unpack(VoteAssetContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(voteAssetContract, selfType)); - break; - case VoteWitnessContract: - VoteWitnessContract voteWitnessContract = - contractParameter.unpack(VoteWitnessContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(voteWitnessContract, selfType)); - break; - case WitnessCreateContract: - WitnessCreateContract witnessCreateContract = - contractParameter.unpack(WitnessCreateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(witnessCreateContract, selfType)); - break; - case AssetIssueContract: - AssetIssueContract assetIssueContract = - contractParameter.unpack(AssetIssueContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(assetIssueContract, selfType)); - break; - case WitnessUpdateContract: - WitnessUpdateContract witnessUpdateContract = - contractParameter.unpack(WitnessUpdateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(witnessUpdateContract, selfType)); - break; - case ParticipateAssetIssueContract: - ParticipateAssetIssueContract participateAssetIssueContract = - contractParameter.unpack(ParticipateAssetIssueContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(participateAssetIssueContract, selfType)); - break; - case AccountUpdateContract: - AccountUpdateContract accountUpdateContract = - contractParameter.unpack(AccountUpdateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(accountUpdateContract, selfType)); - break; - case FreezeBalanceContract: - FreezeBalanceContract freezeBalanceContract = - contractParameter.unpack(FreezeBalanceContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(freezeBalanceContract, selfType)); - break; - case UnfreezeBalanceContract: - UnfreezeBalanceContract unfreezeBalanceContract = - contractParameter.unpack(UnfreezeBalanceContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(unfreezeBalanceContract, selfType)); - break; - case WithdrawBalanceContract: - WithdrawBalanceContract withdrawBalanceContract = - contractParameter.unpack(WithdrawBalanceContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(withdrawBalanceContract, selfType)); - break; - case UnfreezeAssetContract: - UnfreezeAssetContract unfreezeAssetContract = - contractParameter.unpack(UnfreezeAssetContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(unfreezeAssetContract, selfType)); - break; - case UpdateAssetContract: - UpdateAssetContract updateAssetContract = - contractParameter.unpack(UpdateAssetContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(updateAssetContract, selfType)); - break; - case ProposalCreateContract: - ProposalCreateContract proposalCreateContract = - contractParameter.unpack(ProposalCreateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(proposalCreateContract, selfType)); - break; - case ProposalApproveContract: - ProposalApproveContract proposalApproveContract = - contractParameter.unpack(ProposalApproveContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(proposalApproveContract, selfType)); - break; - case ProposalDeleteContract: - ProposalDeleteContract proposalDeleteContract = - contractParameter.unpack(ProposalDeleteContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(proposalDeleteContract, selfType)); - break; - case SetAccountIdContract: - org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = - contractParameter.unpack( - org.tron.protos.Contract.SetAccountIdContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(setAccountIdContract, selfType)); - break; - case CreateSmartContract: - CreateSmartContract deployContract = - contractParameter.unpack(CreateSmartContract.class); - contractJson = - JSONObject.parseObject(JsonFormat.printToString(deployContract, selfType)); - byte[] ownerAddress = deployContract.getOwnerAddress().toByteArray(); - byte[] contractAddress = generateContractAddress(transaction, ownerAddress); - jsonTransaction.put("contract_address", ByteArray.toHexString(contractAddress)); - break; - case TriggerSmartContract: - TriggerSmartContract triggerSmartContract = - contractParameter.unpack(TriggerSmartContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(triggerSmartContract, selfType)); - break; - case UpdateSettingContract: - UpdateSettingContract updateSettingContract = - contractParameter.unpack(UpdateSettingContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(updateSettingContract, selfType)); - break; - case ExchangeCreateContract: - ExchangeCreateContract exchangeCreateContract = - contractParameter.unpack(ExchangeCreateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(exchangeCreateContract, selfType)); - break; - case ExchangeInjectContract: - ExchangeInjectContract exchangeInjectContract = - contractParameter.unpack(ExchangeInjectContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(exchangeInjectContract, selfType)); - break; - case ExchangeWithdrawContract: - ExchangeWithdrawContract exchangeWithdrawContract = - contractParameter.unpack(ExchangeWithdrawContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(exchangeWithdrawContract, selfType)); - break; - case ExchangeTransactionContract: - ExchangeTransactionContract exchangeTransactionContract = - contractParameter.unpack(ExchangeTransactionContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(exchangeTransactionContract, selfType)); - break; - case UpdateEnergyLimitContract: - UpdateEnergyLimitContract updateEnergyLimitContract = - contractParameter.unpack(UpdateEnergyLimitContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(updateEnergyLimitContract, selfType)); - break; - case AccountPermissionUpdateContract: - AccountPermissionUpdateContract accountPermissionUpdateContract = - contractParameter.unpack(AccountPermissionUpdateContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(accountPermissionUpdateContract, selfType)); - break; - case ClearABIContract: - org.tron.protos.Contract.ClearABIContract clearABIContract = - contractParameter.unpack(org.tron.protos.Contract.ClearABIContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(clearABIContract, selfType)); - break; - case ShieldedTransferContract: - ShieldedTransferContract shieldedTransferContract = - contractParameter.unpack(ShieldedTransferContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(shieldedTransferContract, selfType)); - break; - case UpdateBrokerageContract: - UpdateBrokerageContract updateBrokerageContract = - contract.getParameter().unpack(UpdateBrokerageContract.class); - contractJson = - JSONObject.parseObject( - JsonFormat.printToString(updateBrokerageContract, selfType)); - break; - // todo add other contract - default: - } - JSONObject parameter = new JSONObject(); - parameter.put(VALUE, contractJson); - parameter.put("type_url", contract.getParameterOrBuilder().getTypeUrl()); - JSONObject jsonContract = new JSONObject(); - jsonContract.put("parameter", parameter); - jsonContract.put("type", contract.getType()); - if (contract.getPermissionId() > 0) { - jsonContract.put(PERMISSION_ID, contract.getPermissionId()); - } - contracts.add(jsonContract); - } catch (InvalidProtocolBufferException e) { - e.printStackTrace(); - // System.out.println("InvalidProtocolBufferException: {}", e.getMessage()); - } - }); + transaction.getRawData().getContractList().stream().forEach(contract -> { + try { + JSONObject contractJson = null; + Any contractParameter = contract.getParameter(); + switch (contract.getType()) { + case AccountCreateContract: + AccountCreateContract accountCreateContract = contractParameter + .unpack(AccountCreateContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(accountCreateContract, + selfType)); + break; + case TransferContract: + TransferContract transferContract = contractParameter.unpack(TransferContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(transferContract, + selfType)); + break; + case TransferAssetContract: + TransferAssetContract transferAssetContract = contractParameter + .unpack(TransferAssetContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(transferAssetContract, + selfType)); + break; + case VoteAssetContract: + VoteAssetContract voteAssetContract = contractParameter.unpack(VoteAssetContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(voteAssetContract, + selfType)); + break; + case VoteWitnessContract: + VoteWitnessContract voteWitnessContract = contractParameter + .unpack(VoteWitnessContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(voteWitnessContract, + selfType)); + break; + case WitnessCreateContract: + WitnessCreateContract witnessCreateContract = contractParameter + .unpack(WitnessCreateContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(witnessCreateContract, + selfType)); + break; + case AssetIssueContract: + AssetIssueContract assetIssueContract = contractParameter + .unpack(AssetIssueContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(assetIssueContract, + selfType)); + break; + case WitnessUpdateContract: + WitnessUpdateContract witnessUpdateContract = contractParameter + .unpack(WitnessUpdateContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(witnessUpdateContract, + selfType)); + break; + case ParticipateAssetIssueContract: + ParticipateAssetIssueContract participateAssetIssueContract = contractParameter + .unpack(ParticipateAssetIssueContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(participateAssetIssueContract, selfType)); + break; + case AccountUpdateContract: + AccountUpdateContract accountUpdateContract = contractParameter + .unpack(AccountUpdateContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(accountUpdateContract, + selfType)); + break; + case FreezeBalanceContract: + FreezeBalanceContract freezeBalanceContract = contractParameter + .unpack(FreezeBalanceContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(freezeBalanceContract, + selfType)); + break; + case UnfreezeBalanceContract: + UnfreezeBalanceContract unfreezeBalanceContract = contractParameter + .unpack(UnfreezeBalanceContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(unfreezeBalanceContract, selfType)); + break; + case WithdrawBalanceContract: + WithdrawBalanceContract withdrawBalanceContract = contractParameter + .unpack(WithdrawBalanceContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(withdrawBalanceContract, selfType)); + break; + case UnfreezeAssetContract: + UnfreezeAssetContract unfreezeAssetContract = contractParameter + .unpack(UnfreezeAssetContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(unfreezeAssetContract, + selfType)); + break; + case UpdateAssetContract: + UpdateAssetContract updateAssetContract = contractParameter + .unpack(UpdateAssetContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(updateAssetContract, + selfType)); + break; + case ProposalCreateContract: + ProposalCreateContract proposalCreateContract = contractParameter + .unpack(ProposalCreateContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(proposalCreateContract, + selfType)); + break; + case ProposalApproveContract: + ProposalApproveContract proposalApproveContract = contractParameter + .unpack(ProposalApproveContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(proposalApproveContract, selfType)); + break; + case ProposalDeleteContract: + ProposalDeleteContract proposalDeleteContract = contractParameter + .unpack(ProposalDeleteContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(proposalDeleteContract, + selfType)); + break; + case SetAccountIdContract: + org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = + contractParameter.unpack(org.tron.protos.Contract.SetAccountIdContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(setAccountIdContract, + selfType)); + break; + case CreateSmartContract: + CreateSmartContract deployContract = contractParameter + .unpack(CreateSmartContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(deployContract, + selfType)); + byte[] ownerAddress = deployContract.getOwnerAddress().toByteArray(); + byte[] contractAddress = generateContractAddress(transaction, ownerAddress); + jsonTransaction.put("contract_address", ByteArray.toHexString(contractAddress)); + break; + case TriggerSmartContract: + TriggerSmartContract triggerSmartContract = contractParameter + .unpack(TriggerSmartContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(triggerSmartContract, + selfType)); + break; + case UpdateSettingContract: + UpdateSettingContract updateSettingContract = contractParameter + .unpack(UpdateSettingContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(updateSettingContract, + selfType)); + break; + case ExchangeCreateContract: + ExchangeCreateContract exchangeCreateContract = contractParameter + .unpack(ExchangeCreateContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(exchangeCreateContract, + selfType)); + break; + case ExchangeInjectContract: + ExchangeInjectContract exchangeInjectContract = contractParameter + .unpack(ExchangeInjectContract.class); + contractJson = JSONObject.parseObject(JsonFormat.printToString(exchangeInjectContract, + selfType)); + break; + case ExchangeWithdrawContract: + ExchangeWithdrawContract exchangeWithdrawContract = contractParameter + .unpack(ExchangeWithdrawContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(exchangeWithdrawContract, selfType)); + break; + case ExchangeTransactionContract: + ExchangeTransactionContract exchangeTransactionContract = contractParameter + .unpack(ExchangeTransactionContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(exchangeTransactionContract, selfType)); + break; + case UpdateEnergyLimitContract: + UpdateEnergyLimitContract updateEnergyLimitContract = contractParameter + .unpack(UpdateEnergyLimitContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(updateEnergyLimitContract, selfType)); + break; + case AccountPermissionUpdateContract: + AccountPermissionUpdateContract accountPermissionUpdateContract = contractParameter + .unpack(AccountPermissionUpdateContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(accountPermissionUpdateContract, selfType)); + break; + case ClearABIContract: + org.tron.protos.Contract.ClearABIContract clearABIContract = contractParameter + .unpack(org.tron.protos.Contract.ClearABIContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(clearABIContract, selfType)); + break; + case ShieldedTransferContract: + ShieldedTransferContract shieldedTransferContract = contractParameter + .unpack(ShieldedTransferContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(shieldedTransferContract, selfType)); + break; + case UpdateBrokerageContract: + UpdateBrokerageContract updateBrokerageContract = contract.getParameter() + .unpack(UpdateBrokerageContract.class); + contractJson = JSONObject + .parseObject(JsonFormat.printToString(updateBrokerageContract, selfType)); + break; + // todo add other contract + default: + } + JSONObject parameter = new JSONObject(); + parameter.put(VALUE, contractJson); + parameter.put("type_url", contract.getParameterOrBuilder().getTypeUrl()); + JSONObject jsonContract = new JSONObject(); + jsonContract.put("parameter", parameter); + jsonContract.put("type", contract.getType()); + if (contract.getPermissionId() > 0) { + jsonContract.put(PERMISSION_ID, contract.getPermissionId()); + } + contracts.add(jsonContract); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + //System.out.println("InvalidProtocolBufferException: {}", e.getMessage()); + } + }); JSONObject rawData = JSONObject.parseObject(jsonTransaction.get("raw_data").toString()); rawData.put("contract", contracts); @@ -553,22 +509,16 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean return jsonTransaction; } - public static boolean isNumericString(String str) { - for (int i = str.length(); --i >= 0; ) { - if (!Character.isDigit(str.charAt(i))) { - return false; - } + public static boolean confirmEncrption() { + System.out.println( + "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); + Scanner in = new Scanner(System.in); + String input = in.nextLine().trim(); + String str = input.split("\\s+")[0]; + if ("y".equalsIgnoreCase(str)) { + return true; } - return true; - } - - public static boolean isHexString(String str) { - boolean bRet = false; - try { - ByteArray.fromHexString(str); - bRet = true; - } catch (Exception e) { - } - return bRet; + return false; } } + diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 90ddc6ccd..fc57493a5 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -3,6 +3,9 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; +import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.TransactionExtention; + import org.tron.common.crypto.Sha256Hash; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; @@ -12,19 +15,18 @@ import org.tron.protos.Protocol.Transaction; import org.tron.walletserver.WalletApi; +import java.util.Arrays; + public class TransactionSignDemoForSM2 { public static Transaction setReference(Transaction transaction, Block newestBlock) { long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = - transaction - .getRawData() - .toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); + Transaction.raw rawData = transaction.getRawData().toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); return transaction.toBuilder().setRawData(rawData).build(); } @@ -37,13 +39,14 @@ public static String getTransactionHash(Transaction transaction) { return txid; } + public static Transaction createTransaction(byte[] from, byte[] to, long amount) { Transaction.Builder transactionBuilder = Transaction.newBuilder(); Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = - Contract.TransferContract.newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract + .newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); @@ -56,17 +59,15 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder - .getRawDataBuilder() - .addContract(contractBuilder) + transactionBuilder.getRawDataBuilder().addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration( - newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; } + private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { SM2 sm2 = SM2.fromPrivate(privateKey); @@ -109,14 +110,17 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); - // sign a transaction in byte format and return a Transaction in byte format + + //sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); boolean result = broadcast(transaction4); System.out.println(result); } + + } diff --git a/src/main/java/org/tron/keystore/WalletUtils.java b/src/main/java/org/tron/keystore/WalletUtils.java index 65b2fda65..8d206829f 100644 --- a/src/main/java/org/tron/keystore/WalletUtils.java +++ b/src/main/java/org/tron/keystore/WalletUtils.java @@ -20,14 +20,16 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -/** Utility functions for working with Wallet files. */ +/** + * Utility functions for working with Wallet files. + */ public class WalletUtils { private static final ObjectMapper objectMapper = new ObjectMapper(); private static boolean isEckey = true; static { - Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant + Config config = Configuration.getByPath("config.conf");//it is needs set to be a constant objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); if (config.hasPath("crypto.engine")) { @@ -37,15 +39,15 @@ public class WalletUtils { } public static String generateFullNewWalletFile(byte[] password, File destinationDirectory) - throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, - CipherException, IOException { + throws NoSuchAlgorithmException, NoSuchProviderException, + InvalidAlgorithmParameterException, CipherException, IOException { return generateNewWalletFile(password, destinationDirectory, true); } public static String generateLightNewWalletFile(byte[] password, File destinationDirectory) - throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, - CipherException, IOException { + throws NoSuchAlgorithmException, NoSuchProviderException, + InvalidAlgorithmParameterException, CipherException, IOException { return generateNewWalletFile(password, destinationDirectory, false); } @@ -53,7 +55,7 @@ public static String generateLightNewWalletFile(byte[] password, File destinatio public static String generateNewWalletFile( byte[] password, File destinationDirectory, boolean useFullScrypt) throws CipherException, IOException, InvalidAlgorithmParameterException, - NoSuchAlgorithmException, NoSuchProviderException { + NoSuchAlgorithmException, NoSuchProviderException { SignInterface ecKeySm2Pair = null; if (isEckey) { ecKeySm2Pair = new ECKey(Utils.getRandom()); @@ -106,38 +108,37 @@ public static String generateWalletFile(WalletFile walletFile, File destinationD } // /** - // * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can - // * be calculated using following algorithm: - // *
-  //     *     Key = SHA-256(BIP_39_SEED(mnemonic, password))
-  //     * 
- // * - // * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed - // * @param destinationDirectory The directory containing the wallet - // * @return A BIP-39 compatible Ethereum wallet - // * @throws CipherException if the underlying cipher is not available - // * @throws IOException if the destination cannot be written to - // */ - // public static Bip39Wallet generateBip39Wallet(String password, File destinationDirectory) - // throws CipherException, IOException { - // byte[] initialEntropy = new byte[16]; - // secureRandom.nextBytes(initialEntropy); - // - // String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); - // byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); - // ECKeyPair privateKey = ECKeyPair.create(sha256(seed)); - // - // String walletFile = generateWalletFile(password, privateKey, destinationDirectory, - // false); - // - // return new Bip39Wallet(walletFile, mnemonic); - // } - // - // public static Credentials loadCredentials(String password, String source) - // throws IOException, CipherException { - // return loadCredentials(password, new File(source)); - // } - // +// * Generates a BIP-39 compatible Ethereum wallet. The private key for the wallet can +// * be calculated using following algorithm: +// *
+//     *     Key = SHA-256(BIP_39_SEED(mnemonic, password))
+//     * 
+// * +// * @param password Will be used for both wallet encryption and passphrase for BIP-39 seed +// * @param destinationDirectory The directory containing the wallet +// * @return A BIP-39 compatible Ethereum wallet +// * @throws CipherException if the underlying cipher is not available +// * @throws IOException if the destination cannot be written to +// */ +// public static Bip39Wallet generateBip39Wallet(String password, File destinationDirectory) +// throws CipherException, IOException { +// byte[] initialEntropy = new byte[16]; +// secureRandom.nextBytes(initialEntropy); +// +// String mnemonic = MnemonicUtils.generateMnemonic(initialEntropy); +// byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); +// ECKeyPair privateKey = ECKeyPair.create(sha256(seed)); +// +// String walletFile = generateWalletFile(password, privateKey, destinationDirectory, false); +// +// return new Bip39Wallet(walletFile, mnemonic); +// } +// +// public static Credentials loadCredentials(String password, String source) +// throws IOException, CipherException { +// return loadCredentials(password, new File(source)); +// } +// public static Credentials loadCredentials(byte[] password, File source) throws IOException, CipherException { WalletFile walletFile = objectMapper.readValue(source, WalletFile.class); @@ -151,9 +152,15 @@ public static Credentials loadCredentials(byte[] password, File source) public static WalletFile loadWalletFile(File source) throws IOException { return objectMapper.readValue(source, WalletFile.class); } +// +// public static Credentials loadBip39Credentials(String password, String mnemonic) { +// byte[] seed = MnemonicUtils.generateSeed(mnemonic, password); +// return Credentials.create(ECKeyPair.create(sha256(seed))); +// } private static String getWalletFileName(WalletFile walletFile) { - DateTimeFormatter format = DateTimeFormatter.ofPattern("'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'"); + DateTimeFormatter format = DateTimeFormatter.ofPattern( + "'UTC--'yyyy-MM-dd'T'HH-mm-ss.nVV'--'"); ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); return now.format(format) + walletFile.getAddress() + ".json"; @@ -168,7 +175,8 @@ static String getDefaultKeyDirectory(String osName1) { if (osName.startsWith("mac")) { return String.format( - "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator, File.separator); + "%s%sLibrary%sEthereum", System.getProperty("user.home"), File.separator, + File.separator); } else if (osName.startsWith("win")) { return String.format("%s%sEthereum", System.getenv("APPDATA"), File.separator); } else { @@ -185,7 +193,26 @@ public static String getMainnetKeyDirectory() { return String.format("%s%skeystore", getDefaultKeyDirectory(), File.separator); } - public static void generateSkeyFile(SKeyCapsule skey, File file) throws IOException { + +// public static boolean isValidPrivateKey(String privateKey) { +// String cleanPrivateKey = Numeric.cleanHexPrefix(privateKey); +// return cleanPrivateKey.length() == PRIVATE_KEY_LENGTH_IN_HEX; +// } +// +// public static boolean isValidAddress(String input) { +// String cleanInput = Numeric.cleanHexPrefix(input); +// +// try { +// Numeric.toBigIntNoPrefix(cleanInput); +// } catch (NumberFormatException e) { +// return false; +// } +// +// return cleanInput.length() == ADDRESS_LENGTH_IN_HEX; +// } + + public static void generateSkeyFile(SKeyCapsule skey, File file) + throws IOException { objectMapper.writeValue(file, skey); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 3dddbefea..f1b4449d4 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -57,17 +57,17 @@ public class WalletApi { private static GrpcClient rpcCli = init(); - // static { - // new Timer().schedule(new TimerTask() { - // @Override - // public void run() { - // String fullnode = selectFullNode(); - // if(!"".equals(fullnode)) { - // rpcCli = new GrpcClient(fullnode); - // } - // } - // }, 3 * 60 * 1000, 3 * 60 * 1000); - // } +// static { +// new Timer().schedule(new TimerTask() { +// @Override +// public void run() { +// String fullnode = selectFullNode(); +// if(!"".equals(fullnode)) { +// rpcCli = new GrpcClient(fullnode); +// } +// } +// }, 3 * 60 * 1000, 3 * 60 * 1000); +// } public static GrpcClient init() { Config config = Configuration.getByPath("config.conf"); @@ -140,7 +140,9 @@ public static int getRpcVersion() { return rpcVersion; } - /** Creates a new WalletApi with a random ECKey or no ECKey. */ + /** + * Creates a new WalletApi with a random ECKey or no ECKey. + */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -155,7 +157,7 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - WalletFile walletFile = null; + WalletFile walletFile=null; if (isEckey) { ECKey ecKey = ECKey.fromPrivate(priKey); walletFile = Wallet.createStandard(password, ecKey); @@ -184,7 +186,9 @@ public boolean checkPassword(byte[] passwd) throws CipherException { return Wallet.validPassword(passwd, this.walletFile.get(0)); } - /** Creates a Wallet with an existing ECKey. */ + /** + * Creates a Wallet with an existing ECKey. + */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -260,12 +264,11 @@ public static File selcetWalletFile() { try { n = new Integer(num); } catch (NumberFormatException e) { - System.out.println("Invalid number of " + num); + System.out.println("Invaild number of " + num); System.out.println("Please choose again between 1 and " + wallets.length); continue; } if (n < 1 || n > wallets.length) { - System.out.println("Invalid number of " + num); System.out.println("Please choose again between 1 and " + wallets.length); continue; } @@ -310,6 +313,7 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso return true; } + private static WalletFile loadWalletFile() throws IOException { File wallet = selcetWalletFile(); if (wallet == null) { @@ -319,8 +323,11 @@ private static WalletFile loadWalletFile() throws IOException { return WalletUtils.loadWalletFile(wallet); } - /** load a Wallet from keystore */ - public static WalletApi loadWalletFromKeystore() throws IOException { + /** + * load a Wallet from keystore + */ + public static WalletApi loadWalletFromKeystore() + throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); return walletApi; @@ -331,7 +338,7 @@ public Account queryAccount() { } public static Account queryAccount(byte[] address) { - return rpcCli.queryAccount(address); // call rpc + return rpcCli.queryAccount(address);//call rpc } public static Account queryAccountById(String accountId) { @@ -373,9 +380,9 @@ private Transaction signTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } - // System.out - // .println("current transaction hex string is " + ByteArray - // .toHexString(transaction.toByteArray())); +// System.out +// .println("current transaction hex string is " + ByteArray +// .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -415,7 +422,9 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } - +// System.out +// .println("current transaction hex string is " + ByteArray +// .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -454,14 +463,13 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); - System.out.println( - "before sign transaction hex string is " - + ByteArray.toHexString(transaction.toByteArray())); + System.out.println("before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -469,24 +477,23 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { - System.out.println( - "after sign transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); - System.out.println( - "txid is " - + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println("after sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); + System.out.println("txid is " + + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { - CreateSmartContract createSmartContract = - transaction.getRawData().getContract(0).getParameter().unpack(CreateSmartContract.class); - byte[] contractAddress = - generateContractAddress(createSmartContract.getOwnerAddress().toByteArray(), transaction); + CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) + .getParameter().unpack(CreateSmartContract.class); + byte[] contractAddress = generateContractAddress( + createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); } } - private static boolean processShieldedTransaction( - TransactionExtention transactionExtention, WalletApi wallet) + private static boolean processShieldedTransaction(TransactionExtention transactionExtention, + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -504,7 +511,7 @@ private static boolean processShieldedTransaction( } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -520,11 +527,10 @@ private static boolean processShieldedTransaction( transaction = wallet.signOnlyForShieldedTransaction(transaction); } - System.out.println( - "transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); - System.out.println( - "txid is " - + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println("transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); + System.out.println("txid is " + + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -536,9 +542,8 @@ private boolean processTransaction(Transaction transaction) } System.out.println(Utils.printTransactionExceptId(transaction)); - System.out.println( - "before sign transaction hex string is " - + ByteArray.toHexString(transaction.toByteArray())); + System.out.println("before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -546,7 +551,7 @@ private boolean processTransaction(Transaction transaction) return rpcCli.broadcastTransaction(transaction); } - // Warning: do not invoke this interface provided by others. + //Warning: do not invoke this interface provided by others. public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); @@ -558,9 +563,9 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p return rpcCli.signTransaction(builder.build()); } - // Warning: do not invoke this interface provided by others. - public static TransactionExtention signTransactionByApi2( - Transaction transaction, byte[] privateKey) throws CancelException { + //Warning: do not invoke this interface provided by others. + public static TransactionExtention signTransactionByApi2(Transaction transaction, + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -570,9 +575,9 @@ public static TransactionExtention signTransactionByApi2( return rpcCli.signTransaction2(builder.build()); } - // Warning: do not invoke this interface provided by others. - public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) - throws CancelException { + //Warning: do not invoke this interface provided by others. + public static TransactionExtention addSignByApi(Transaction transaction, + byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -590,32 +595,32 @@ public static TransactionApprovedList getTransactionApprovedList(Transaction tra return rpcCli.getTransactionApprovedList(transaction); } - // Warning: do not invoke this interface provided by others. + //Warning: do not invoke this interface provided by others. public static byte[] createAdresss(byte[] passPhrase) { return rpcCli.createAdresss(passPhrase); } - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransfer( - byte[] passPhrase, byte[] toAddress, long amount) { + //Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, + long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferByPrivate( - byte[] privateKey, byte[] toAddress, long amount) { + //Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, + long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAsset( - byte[] passPhrase, byte[] toAddress, String assetId, long amount) { + //Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, + String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAssetByPrivate( - byte[] privateKey, byte[] toAddress, String assetId, long amount) { + //Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, + byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -666,15 +671,16 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) return processTransaction(transaction); } - public boolean updateAsset( - byte[] owner, byte[] description, byte[] url, long newLimit, long newPublicLimit) + + public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, + long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.UpdateAssetContract contract = - createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); + Contract.UpdateAssetContract contract + = createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -690,8 +696,8 @@ public boolean transferAsset(byte[] owner, byte[] to, byte[] assertName, long am owner = getAddress(); } - Contract.TransferAssetContract contract = - createTransferAssetContract(to, assertName, owner, amount); + Contract.TransferAssetContract contract = createTransferAssetContract(to, assertName, owner, + amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransferAssetTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -707,11 +713,11 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner = getAddress(); } - Contract.ParticipateAssetIssueContract contract = - participateAssetIssueContract(to, assertName, owner, amount); + Contract.ParticipateAssetIssueContract contract = participateAssetIssueContract(to, assertName, + owner, amount); if (rpcVersion == 2) { - TransactionExtention transactionExtention = - rpcCli.createParticipateAssetIssueTransaction2(contract); + TransactionExtention transactionExtention = rpcCli + .createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -756,7 +762,7 @@ public boolean createAccount(byte[] owner, byte[] address) } } - // Warning: do not invoke this interface provided by others. + //Warning: do not invoke this interface provided by others. public static AddressPrKeyPairMessage generateAddress() { EmptyMessage.Builder builder = EmptyMessage.newBuilder(); return rpcCli.generateAddress(builder.build()); @@ -822,8 +828,8 @@ public boolean voteWitness(byte[] owner, HashMap witness) } } - public static Contract.TransferContract createTransferContract( - byte[] to, byte[] owner, long amount) { + public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, + long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -834,8 +840,9 @@ public static Contract.TransferContract createTransferContract( return builder.build(); } - public static Contract.TransferAssetContract createTransferAssetContract( - byte[] to, byte[] assertName, byte[] owner, long amount) { + public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, + byte[] assertName, byte[] owner, + long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -848,10 +855,11 @@ public static Contract.TransferAssetContract createTransferAssetContract( return builder.build(); } - public static Contract.ParticipateAssetIssueContract participateAssetIssueContract( - byte[] to, byte[] assertName, byte[] owner, long amount) { - Contract.ParticipateAssetIssueContract.Builder builder = - Contract.ParticipateAssetIssueContract.newBuilder(); + public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, + byte[] assertName, byte[] owner, + long amount) { + Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract + .newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -863,8 +871,8 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra return builder.build(); } - public static Contract.AccountUpdateContract createAccountUpdateContract( - byte[] accountName, byte[] address) { + public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, + byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -874,8 +882,8 @@ public static Contract.AccountUpdateContract createAccountUpdateContract( return builder.build(); } - public static Contract.SetAccountIdContract createSetAccountIdContract( - byte[] accountId, byte[] address) { + public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, + byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -885,9 +893,16 @@ public static Contract.SetAccountIdContract createSetAccountIdContract( return builder.build(); } + public static Contract.UpdateAssetContract createUpdateAssetContract( - byte[] address, byte[] description, byte[] url, long newLimit, long newPublicLimit) { - Contract.UpdateAssetContract.Builder builder = Contract.UpdateAssetContract.newBuilder(); + byte[] address, + byte[] description, + byte[] url, + long newLimit, + long newPublicLimit + ) { + Contract.UpdateAssetContract.Builder builder = + Contract.UpdateAssetContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); builder.setDescription(ByteString.copyFrom(description)); builder.setUrl(ByteString.copyFrom(url)); @@ -898,8 +913,8 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( return builder.build(); } - public static Contract.AccountCreateContract createAccountCreateContract( - byte[] owner, byte[] address) { + public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, + byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -907,8 +922,8 @@ public static Contract.AccountCreateContract createAccountCreateContract( return builder.build(); } - public static Contract.WitnessCreateContract createWitnessCreateContract( - byte[] owner, byte[] url) { + public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, + byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -916,8 +931,8 @@ public static Contract.WitnessCreateContract createWitnessCreateContract( return builder.build(); } - public static Contract.WitnessUpdateContract createWitnessUpdateContract( - byte[] owner, byte[] url) { + public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, + byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -925,15 +940,15 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract( return builder.build(); } - public static Contract.VoteWitnessContract createVoteWitnessContract( - byte[] owner, HashMap witness) { + public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, + HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); - Contract.VoteWitnessContract.Vote.Builder voteBuilder = - Contract.VoteWitnessContract.Vote.newBuilder(); + Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote + .newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -954,7 +969,7 @@ public static boolean passwordValid(char[] password) { System.out.println("Warning: Password is too short !!"); return false; } - // Other rule; + //Other rule; int level = CheckStrength.checkPasswordStrength(password); if (level <= 4) { System.out.println("Your password is too weak!"); @@ -978,24 +993,18 @@ public static boolean addressValid(byte[] address) { } if (address.length != CommonConstant.ADDRESS_SIZE) { System.out.println( - "Warning: Address length need " - + CommonConstant.ADDRESS_SIZE - + " but " - + address.length + "Warning: Address length need " + CommonConstant.ADDRESS_SIZE + " but " + address.length + " !!"); return false; } byte preFixbyte = address[0]; if (preFixbyte != WalletApi.getAddressPreFixByte()) { - System.out.println( - "Warning: Address need prefix with " - + WalletApi.getAddressPreFixByte() - + " but " - + preFixbyte - + " !!"); + System.out + .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " + + preFixbyte + " !!"); return false; } - // Other rule; + //Other rule; return true; } @@ -1017,10 +1026,10 @@ private static byte[] decode58Check(String input) { System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length); byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); - if (hash1[0] == decodeCheck[decodeData.length] - && hash1[1] == decodeCheck[decodeData.length + 1] - && hash1[2] == decodeCheck[decodeData.length + 2] - && hash1[3] == decodeCheck[decodeData.length + 3]) { + if (hash1[0] == decodeCheck[decodeData.length] && + hash1[1] == decodeCheck[decodeData.length + 1] && + hash1[2] == decodeCheck[decodeData.length + 2] && + hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1047,10 +1056,25 @@ public static boolean priKeyValid(byte[] priKey) { System.out.println("Warning: PrivateKey length need 64 but " + priKey.length + " !!"); return false; } - // Other rule; + //Other rule; return true; } +// public static Optional listAccounts() { +// Optional result = rpcCli.listAccounts(); +// if (result.isPresent()) { +// AccountList accountList = result.get(); +// List list = accountList.getAccountsList(); +// List newList = new ArrayList(); +// newList.addAll(list); +// newList.sort(new AccountComparator()); +// AccountList.Builder builder = AccountList.newBuilder(); +// newList.forEach(account -> builder.addAccounts(account)); +// result = Optional.of(builder.build()); +// } +// return result; +// } + public static Optional listWitnesses() { Optional result = rpcCli.listWitnesses(); if (result.isPresent()) { @@ -1058,13 +1082,12 @@ public static Optional listWitnesses() { List list = witnessList.getWitnessesList(); List newList = new ArrayList<>(); newList.addAll(list); - newList.sort( - new Comparator() { - @Override - public int compare(Witness o1, Witness o2) { - return Long.compare(o2.getVoteCount(), o1.getVoteCount()); - } - }); + newList.sort(new Comparator() { + @Override + public int compare(Witness o1, Witness o2) { + return Long.compare(o2.getVoteCount(), o1.getVoteCount()); + } + }); WitnessList.Builder builder = WitnessList.newBuilder(); newList.forEach(witness -> builder.addWitnesses(witness)); result = Optional.of(builder.build()); @@ -1072,6 +1095,19 @@ public int compare(Witness o1, Witness o2) { return result; } +// public static Optional getAssetIssueListByTimestamp(long timestamp) { +// return rpcCli.getAssetIssueListByTimestamp(timestamp); +// } +// +// public static Optional getTransactionsByTimestamp(long start, long end, +// int offset, int limit) { +// return rpcCli.getTransactionsByTimestamp(start, end, offset, limit); +// } +// +// public static GrpcAPI.NumberMessage getTransactionsByTimestampCount(long start, long end) { +// return rpcCli.getTransactionsByTimestampCount(start, end); +// } + public static Optional getAssetIssueList() { return rpcCli.getAssetIssueList(); } @@ -1088,6 +1124,7 @@ public static Optional getExchangeListPaginated(long offset, long return rpcCli.getExchangeListPaginated(offset, limit); } + public static Optional listNodes() { return rpcCli.listNodes(); } @@ -1124,25 +1161,33 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { return rpcCli.getNextMaintenanceTime(); } - public static Optional getTransactionsFromThis( - byte[] address, int offset, int limit) { + public static Optional getTransactionsFromThis(byte[] address, int offset, + int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } - public static Optional getTransactionsFromThis2( - byte[] address, int offset, int limit) { + public static Optional getTransactionsFromThis2(byte[] address, + int offset, + int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } +// public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { +// return rpcCli.getTransactionsFromThisCount(address); +// } - public static Optional getTransactionsToThis( - byte[] address, int offset, int limit) { + public static Optional getTransactionsToThis(byte[] address, int offset, + int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } - public static Optional getTransactionsToThis2( - byte[] address, int offset, int limit) { + public static Optional getTransactionsToThis2(byte[] address, + int offset, + int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } +// public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { +// return rpcCli.getTransactionsToThisCount(address); +// } public static Optional getTransactionById(String txID) { return rpcCli.getTransactionById(txID); @@ -1152,16 +1197,12 @@ public static Optional getTransactionInfoById(String txID) { return rpcCli.getTransactionInfoById(txID); } - public boolean freezeBalance( - byte[] ownerAddress, - long frozen_balance, - long frozen_duration, - int resourceCode, - byte[] receiverAddress) + public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, + int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.FreezeBalanceContract contract = - createFreezeBalanceContract( - ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); + Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, + frozen_balance, + frozen_duration, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1190,29 +1231,23 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) Contract.SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); + } - private FreezeBalanceContract createFreezeBalanceContract( - byte[] address, - long frozen_balance, - long frozen_duration, - int resourceCode, - byte[] receiverAddress) { + private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, + long frozen_duration, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); - builder - .setOwnerAddress(byteAddress) - .setFrozenBalance(frozen_balance) - .setFrozenDuration(frozen_duration) - .setResourceValue(resourceCode); + builder.setOwnerAddress(byteAddress).setFrozenBalance(frozen_balance) + .setFrozenDuration(frozen_duration).setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = - ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = ByteString.copyFrom( + Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } return builder.build(); @@ -1235,8 +1270,8 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo address = getAddress(); } - Contract.BuyStorageBytesContract.Builder builder = - Contract.BuyStorageBytesContract.newBuilder(); + Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract + .newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1257,8 +1292,8 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeBalanceContract contract = - createUnfreezeBalanceContract(ownerAddress, resourceCode, receiverAddress); + Contract.UnfreezeBalanceContract contract = createUnfreezeBalanceContract(ownerAddress, + resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1268,20 +1303,21 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec } } - private UnfreezeBalanceContract createUnfreezeBalanceContract( - byte[] address, int resourceCode, byte[] receiverAddress) { + + private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, + byte[] receiverAddress) { if (address == null) { address = getAddress(); } - Contract.UnfreezeBalanceContract.Builder builder = - Contract.UnfreezeBalanceContract.newBuilder(); + Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = - ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = ByteString.copyFrom( + Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } @@ -1305,7 +1341,8 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { address = getAddress(); } - Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract.newBuilder(); + Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1328,8 +1365,8 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { address = getAddress(); } - Contract.WithdrawBalanceContract.Builder builder = - Contract.WithdrawBalanceContract.newBuilder(); + Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1375,8 +1412,8 @@ public static Optional getProposal(String id) { return rpcCli.getProposal(id); } - public static Optional getDelegatedResource( - String fromAddress, String toAddress) { + public static Optional getDelegatedResource(String fromAddress, + String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1397,8 +1434,9 @@ public static Optional getChainParameters() { return rpcCli.getChainParameters(); } - public static Contract.ProposalCreateContract createProposalCreateContract( - byte[] owner, HashMap parametersMap) { + + public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, + HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1411,16 +1449,16 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) owner = getAddress(); } - Contract.ProposalApproveContract contract = - createProposalApproveContract(owner, id, is_add_approval); + Contract.ProposalApproveContract contract = createProposalApproveContract(owner, id, + is_add_approval); TransactionExtention transactionExtention = rpcCli.proposalApprove(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalApproveContract createProposalApproveContract( - byte[] owner, long id, boolean is_add_approval) { - Contract.ProposalApproveContract.Builder builder = - Contract.ProposalApproveContract.newBuilder(); + public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, + long id, boolean is_add_approval) { + Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1438,38 +1476,30 @@ public boolean deleteProposal(byte[] owner, long id) return processTransactionExtention(transactionExtention); } - public static Contract.ProposalDeleteContract createProposalDeleteContract( - byte[] owner, long id) { + public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, + long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); return builder.build(); } - public boolean exchangeCreate( - byte[] owner, - byte[] firstTokenId, - long firstTokenBalance, - byte[] secondTokenId, - long secondTokenBalance) + public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, + byte[] secondTokenId, long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeCreateContract contract = - createExchangeCreateContract( - owner, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); + Contract.ExchangeCreateContract contract = createExchangeCreateContract(owner, firstTokenId, + firstTokenBalance, secondTokenId, secondTokenBalance); TransactionExtention transactionExtention = rpcCli.exchangeCreate(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeCreateContract createExchangeCreateContract( - byte[] owner, - byte[] firstTokenId, - long firstTokenBalance, - byte[] secondTokenId, - long secondTokenBalance) { + public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, + byte[] firstTokenId, long firstTokenBalance, + byte[] secondTokenId, long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1486,14 +1516,14 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon owner = getAddress(); } - Contract.ExchangeInjectContract contract = - createExchangeInjectContract(owner, exchangeId, tokenId, quant); + Contract.ExchangeInjectContract contract = createExchangeInjectContract(owner, exchangeId, + tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeInject(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeInjectContract createExchangeInjectContract( - byte[] owner, long exchangeId, byte[] tokenId, long quant) { + public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, + long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1509,16 +1539,16 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l owner = getAddress(); } - Contract.ExchangeWithdrawContract contract = - createExchangeWithdrawContract(owner, exchangeId, tokenId, quant); + Contract.ExchangeWithdrawContract contract = createExchangeWithdrawContract(owner, exchangeId, + tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeWithdraw(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( - byte[] owner, long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeWithdrawContract.Builder builder = - Contract.ExchangeWithdrawContract.newBuilder(); + public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, + long exchangeId, byte[] tokenId, long quant) { + Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1527,23 +1557,23 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( return builder.build(); } - public boolean exchangeTransaction( - byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) + public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeTransactionContract contract = - createExchangeTransactionContract(owner, exchangeId, tokenId, quant, expected); + Contract.ExchangeTransactionContract contract = createExchangeTransactionContract(owner, + exchangeId, tokenId, quant, expected); TransactionExtention transactionExtention = rpcCli.exchangeTransaction(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeTransactionContract createExchangeTransactionContract( - byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) { - Contract.ExchangeTransactionContract.Builder builder = - Contract.ExchangeTransactionContract.newBuilder(); + public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, + long exchangeId, byte[] tokenId, long quant, long expected) { + Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1553,6 +1583,7 @@ public static Contract.ExchangeTransactionContract createExchangeTransactionCont return builder.build(); } + public static SmartContract.ABI.Entry.EntryType getEntryType(String type) { switch (type) { case "constructor": @@ -1595,38 +1626,22 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { SmartContract.ABI.Builder abiBuilder = SmartContract.ABI.newBuilder(); for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); - boolean anonymous = - abiItem.getAsJsonObject().get("anonymous") != null - ? abiItem.getAsJsonObject().get("anonymous").getAsBoolean() - : false; - boolean constant = - abiItem.getAsJsonObject().get("constant") != null - ? abiItem.getAsJsonObject().get("constant").getAsBoolean() - : false; - String name = - abiItem.getAsJsonObject().get("name") != null - ? abiItem.getAsJsonObject().get("name").getAsString() - : null; - JsonArray inputs = - abiItem.getAsJsonObject().get("inputs") != null - ? abiItem.getAsJsonObject().get("inputs").getAsJsonArray() - : null; - JsonArray outputs = - abiItem.getAsJsonObject().get("outputs") != null - ? abiItem.getAsJsonObject().get("outputs").getAsJsonArray() - : null; - String type = - abiItem.getAsJsonObject().get("type") != null - ? abiItem.getAsJsonObject().get("type").getAsString() - : null; - boolean payable = - abiItem.getAsJsonObject().get("payable") != null - ? abiItem.getAsJsonObject().get("payable").getAsBoolean() - : false; - String stateMutability = - abiItem.getAsJsonObject().get("stateMutability") != null - ? abiItem.getAsJsonObject().get("stateMutability").getAsString() - : null; + boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? + abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; + boolean constant = abiItem.getAsJsonObject().get("constant") != null ? + abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; + String name = abiItem.getAsJsonObject().get("name") != null ? + abiItem.getAsJsonObject().get("name").getAsString() : null; + JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? + abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; + JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? + abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; + String type = abiItem.getAsJsonObject().get("type") != null ? + abiItem.getAsJsonObject().get("type").getAsString() : null; + boolean payable = abiItem.getAsJsonObject().get("payable") != null ? + abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; + String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? + abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; if (type == null) { System.out.println("No type!"); return null; @@ -1647,8 +1662,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (null != inputs) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); - if (inputItem.getAsJsonObject().get("name") == null - || inputItem.getAsJsonObject().get("type") == null) { + if (inputItem.getAsJsonObject().get("name") == null || + inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1656,11 +1671,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String inputType = inputItem.getAsJsonObject().get("type").getAsString(); Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { - inputIndexed = - Boolean.valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + inputIndexed = Boolean + .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = - SmartContract.ABI.Entry.Param.newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param + .newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1672,8 +1687,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (outputs != null) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); - if (outputItem.getAsJsonObject().get("name") == null - || outputItem.getAsJsonObject().get("type") == null) { + if (outputItem.getAsJsonObject().get("name") == null || + outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1681,11 +1696,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String outputType = outputItem.getAsJsonObject().get("type").getAsString(); Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { - outputIndexed = - Boolean.valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + outputIndexed = Boolean + .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = - SmartContract.ABI.Entry.Param.newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param + .newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1705,8 +1720,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { return abiBuilder.build(); } - public static Contract.UpdateSettingContract createUpdateSettingContract( - byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) { + public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, + byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1716,37 +1731,32 @@ public static Contract.UpdateSettingContract createUpdateSettingContract( } public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract( - byte[] owner, byte[] contractAddress, long originEnergyLimit) { + byte[] owner, + byte[] contractAddress, long originEnergyLimit) { - Contract.UpdateEnergyLimitContract.Builder builder = - Contract.UpdateEnergyLimitContract.newBuilder(); + Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); return builder.build(); } - public static Contract.ClearABIContract createClearABIContract( - byte[] owner, byte[] contractAddress) { + public static Contract.ClearABIContract createClearABIContract(byte[] owner, + byte[] contractAddress) { - Contract.ClearABIContract.Builder builder = Contract.ClearABIContract.newBuilder(); + Contract.ClearABIContract.Builder builder = Contract.ClearABIContract + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } - public static CreateSmartContract createContractDeployContract( - String contractName, - byte[] address, - String ABI, - String code, - long value, - long consumeUserResourcePercent, - long originEnergyLimit, - long tokenValue, - String tokenId, - String libraryAddressPair, - String compilerVersion) { + public static CreateSmartContract createContractDeployContract(String contractName, + byte[] address, + String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, + String libraryAddressPair, String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1757,8 +1767,7 @@ public static CreateSmartContract createContractDeployContract( builder.setName(contractName); builder.setOriginAddress(ByteString.copyFrom(address)); builder.setAbi(abi); - builder - .setConsumeUserResourcePercent(consumeUserResourcePercent) + builder.setConsumeUserResourcePercent(consumeUserResourcePercent) .setOriginEnergyLimit(originEnergyLimit); if (value != 0) { @@ -1774,17 +1783,16 @@ public static CreateSmartContract createContractDeployContract( builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); - createSmartContractBuilder - .setOwnerAddress(ByteString.copyFrom(address)) - .setNewContract(builder.build()); + createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). + setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } return createSmartContractBuilder.build(); } - private static byte[] replaceLibraryAddress( - String code, String libraryAddressPair, String compilerVersion) { + private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1799,22 +1807,21 @@ private static byte[] replaceLibraryAddress( String addr = cur.substring(lastPosition + 1); String libraryAddressHex; try { - libraryAddressHex = - (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), "US-ASCII")) - .substring(2); + libraryAddressHex = (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), + "US-ASCII")).substring(2); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // now ignore + throw new RuntimeException(e); // now ignore } String beReplaced; if (compilerVersion == null) { - // old version + //old version String repeated = new String(new char[40 - libraryName.length() - 2]).replace("\0", "_"); beReplaced = "__" + libraryName + repeated; } else if (compilerVersion.equalsIgnoreCase("v5")) { - // 0.5.4 version - String libraryNameKeccak256 = - ByteArray.toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + //0.5.4 version + String libraryNameKeccak256 = ByteArray + .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1827,13 +1834,9 @@ private static byte[] replaceLibraryAddress( return Hex.decode(code); } - public static Contract.TriggerSmartContract triggerCallContract( - byte[] address, - byte[] contractAddress, - long callValue, - byte[] data, - long tokenValue, - String tokenId) { + public static Contract.TriggerSmartContract triggerCallContract(byte[] address, + byte[] contractAddress, + long callValue, byte[] data, long tokenValue, String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1856,25 +1859,26 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { System.arraycopy(ownerAddress, 0, combined, txRawDataHash.length, ownerAddress.length); return Hash.sha3omit12(combined); + } - public boolean updateSetting( - byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) + public boolean updateSetting(byte[] owner, byte[] contractAddress, + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - UpdateSettingContract updateSettingContract = - createUpdateSettingContract(owner, contractAddress, consumeUserResourcePercent); + UpdateSettingContract updateSettingContract = createUpdateSettingContract(owner, + contractAddress, consumeUserResourcePercent); TransactionExtention transactionExtention = rpcCli.updateSetting(updateSettingContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1888,21 +1892,24 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig owner = getAddress(); } - UpdateEnergyLimitContract updateEnergyLimitContract = - createUpdateEnergyLimitContract(owner, contractAddress, originEnergyLimit); + UpdateEnergyLimitContract updateEnergyLimitContract = createUpdateEnergyLimitContract( + owner, + contractAddress, originEnergyLimit); - TransactionExtention transactionExtention = rpcCli.updateEnergyLimit(updateEnergyLimitContract); + TransactionExtention transactionExtention = rpcCli + .updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } return processTransactionExtention(transactionExtention); + } public boolean clearContractABI(byte[] owner, byte[] contractAddress) @@ -1917,8 +1924,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1926,53 +1933,33 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) return processTransactionExtention(transactionExtention); } - public boolean deployContract( - byte[] owner, - String contractName, - String ABI, - String code, - long feeLimit, - long value, - long consumeUserResourcePercent, - long originEnergyLimit, - long tokenValue, - String tokenId, - String libraryAddressPair, - String compilerVersion) + public boolean deployContract(byte[] owner, String contractName, String ABI, String code, + long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, + long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - CreateSmartContract contractDeployContract = - createContractDeployContract( - contractName, - owner, - ABI, - code, - value, - consumeUserResourcePercent, - originEnergyLimit, - tokenValue, - tokenId, - libraryAddressPair, - compilerVersion); + CreateSmartContract contractDeployContract = createContractDeployContract(contractName, owner, + ABI, code, value, consumeUserResourcePercent, originEnergyLimit, tokenValue, tokenId, + libraryAddressPair, compilerVersion); TransactionExtention transactionExtention = rpcCli.deployContract(contractDeployContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = - transactionExtention.getTransaction().getRawData().toBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1988,28 +1975,23 @@ public boolean deployContract( texBuilder.setTxid(transactionExtention.getTxid()); transactionExtention = texBuilder.build(); - // byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); - // System.out.println( - // "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); +// byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); +// System.out.println( +// "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); return processTransactionExtention(transactionExtention); + } - public boolean triggerContract( - byte[] owner, - byte[] contractAddress, - long callValue, - byte[] data, - long feeLimit, - long tokenValue, - String tokenId, - boolean isConstant) + public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, + long feeLimit, + long tokenValue, String tokenId, boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.TriggerSmartContract triggerContract = - triggerCallContract(owner, contractAddress, callValue, data, tokenValue, tokenId); + Contract.TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, + callValue, data, tokenValue, tokenId); TransactionExtention transactionExtention; if (isConstant) { transactionExtention = rpcCli.triggerConstantContract(triggerContract); @@ -2020,28 +2002,28 @@ public boolean triggerContract( if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create call trx failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); return false; } Transaction transaction = transactionExtention.getTransaction(); // for constant - if (transaction.getRetCount() != 0 - && transactionExtention.getConstantResult(0) != null - && transactionExtention.getResult() != null) { + if (transaction.getRetCount() != 0 && + transactionExtention.getConstantResult(0) != null && + transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); - System.out.println( - ":" + ByteArray.toStr(transactionExtention.getResult().getMessage().toByteArray())); + System.out.println(":" + ByteArray + .toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = - transactionExtention.getTransaction().getRawData().toBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2064,10 +2046,11 @@ public static SmartContract getContract(byte[] address) { return rpcCli.getContract(address); } + public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { - Contract.AccountPermissionUpdateContract contract = - createAccountPermissionContract(owner, permissionJson); + Contract.AccountPermissionUpdateContract contract = createAccountPermissionContract(owner, + permissionJson); TransactionExtention transactionExtention = rpcCli.accountPermissionUpdate(contract); return processTransactionExtention(transactionExtention); } @@ -2141,6 +2124,7 @@ public Contract.AccountPermissionUpdateContract createAccountPermissionContract( return builder.build(); } + public Transaction addTransactionSign(Transaction transaction) throws CipherException, IOException, CancelException { if (transaction.getRawData().getTimestamp() == 0) { @@ -2167,7 +2151,8 @@ public Transaction addTransactionSign(Transaction transaction) } public static Optional GetMerkleTreeVoucherInfo( - OutputPointInfo info, boolean showErrorMsg) { + OutputPointInfo info, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.GetMerkleTreeVoucherInfo(info)); @@ -2183,8 +2168,8 @@ public static Optional GetMerkleTreeVoucherInfo( return Optional.empty(); } - public static Optional scanNoteByIvk( - IvkDecryptParameters ivkDecryptParameters, boolean showErrorMsg) { + public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2200,8 +2185,8 @@ public static Optional scanNoteByIvk( return Optional.empty(); } - public static Optional scanNoteByOvk( - OvkDecryptParameters ovkDecryptParameters, boolean showErrorMsg) { + public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2285,8 +2270,8 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall return processShieldedTransaction(transactionExtention, wallet); } - public static boolean sendShieldedCoinWithoutAsk( - PrivateParametersWithoutAsk privateParameters, byte[] ask, WalletApi wallet) + public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2303,7 +2288,7 @@ public static boolean sendShieldedCoinWithoutAsk( Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2311,8 +2296,8 @@ public static boolean sendShieldedCoinWithoutAsk( Any any = transaction.getRawData().getContract(0).getParameter(); ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); - ShieldedTransferContract.Builder contractBuild = - shieldContract.toBuilder().clearSpendDescription(); + ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() + .clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2327,16 +2312,11 @@ public static boolean sendShieldedCoinWithoutAsk( contractBuild.addSpendDescription(spendDescription.build()); } - Transaction.raw.Builder rawBuilder = - transaction - .toBuilder() - .getRawDataBuilder() - .clearContract() - .addContract( - Transaction.Contract.newBuilder() - .setType(ContractType.ShieldedTransferContract) - .setParameter(Any.pack(contractBuild.build())) - .build()); + Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() + .addContract( + Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) + .setParameter( + Any.pack(contractBuild.build())).build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2345,8 +2325,8 @@ public static boolean sendShieldedCoinWithoutAsk( return processShieldedTransaction(transactionExtention, wallet); } - public static Optional isNoteSpend( - NoteParameters noteParameters, boolean showErrorMsg) { + public static Optional isNoteSpend(NoteParameters noteParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2412,14 +2392,14 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); - TransactionExtention transactionExtention = - rpcCli.updateBrokerage(updateBrokerageContract.build()); + TransactionExtention transactionExtention = rpcCli + .updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -2434,4 +2414,5 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + } diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf new file mode 100644 index 000000000..171520768 --- /dev/null +++ b/src/main/resources/config-back.conf @@ -0,0 +1,20 @@ +net { + type = mainnet +} + +fullnode = { + ip.list = [ + "47.89.189.124:50055", + "47.89.178.193:50055" + ] +} + +#soliditynode = { +# ip.list = [ +# "127.0.0.1:50052" +# ] +#} +crypto { + engine=sm2 +} +RPC_version = 2 From 260edff42d0e749b943c086b14f2268574640a71 Mon Sep 17 00:00:00 2001 From: zhenping Date: Tue, 7 Jan 2020 14:51:51 +0800 Subject: [PATCH 248/445] add the Hash interface Signed-off-by: zhenping --- .../java/org/tron/common/crypto/HashInterface.java | 11 +++++++++++ src/main/java/org/tron/common/crypto/SM3Hash.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/tron/common/crypto/HashInterface.java diff --git a/src/main/java/org/tron/common/crypto/HashInterface.java b/src/main/java/org/tron/common/crypto/HashInterface.java new file mode 100644 index 000000000..643eaf486 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/HashInterface.java @@ -0,0 +1,11 @@ +package org.tron.common.crypto; + +import com.google.protobuf.ByteString; + +public interface HashInterface { + + byte[] getBytes(); + + ByteString getByteString(); + +} diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index 485c90f0c..deb115daf 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -39,7 +39,7 @@ * used as keys in a map. It also checks that the length is correct and provides a bit more type * safety. */ -public class SM3Hash implements Serializable, Comparable { +public class SM3Hash implements Serializable, Comparable, HashInterface { public static final int LENGTH = 32; // bytes public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); From 4e2e41a6a0c2959436f596ea553f36f89eecdee3 Mon Sep 17 00:00:00 2001 From: zhenping Date: Tue, 7 Jan 2020 15:08:25 +0800 Subject: [PATCH 249/445] revise the SM3Hash comment Signed-off-by: zhenping --- .../java/org/tron/common/crypto/ECKey.java | 340 +++----- .../java/org/tron/common/crypto/SM3Hash.java | 12 +- .../org/tron/common/crypto/SignInterface.java | 4 +- .../java/org/tron/common/crypto/sm2/SM2.java | 2 +- .../tron/common/utils/TransactionUtils.java | 179 ++-- .../java/org/tron/common/utils/Utils.java | 27 +- .../tron/demo/TransactionSignDemoForSM2.java | 45 +- .../java/org/tron/walletserver/WalletApi.java | 810 ++++++++++-------- 8 files changed, 745 insertions(+), 674 deletions(-) diff --git a/src/main/java/org/tron/common/crypto/ECKey.java b/src/main/java/org/tron/common/crypto/ECKey.java index 2fc758ade..44a34a67a 100644 --- a/src/main/java/org/tron/common/crypto/ECKey.java +++ b/src/main/java/org/tron/common/crypto/ECKey.java @@ -17,29 +17,6 @@ * along with the ethereumJ library. If not, see . */ -import static org.tron.common.utils.BIUtil.isLessThan; -import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; -import static org.tron.common.utils.ByteUtil.byteArrayToInt; - -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import javax.annotation.Nullable; -import javax.crypto.KeyAgreement; import lombok.extern.slf4j.Slf4j; import org.spongycastle.asn1.ASN1InputStream; import org.spongycastle.asn1.ASN1Integer; @@ -51,11 +28,7 @@ import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.engines.AESEngine; import org.spongycastle.crypto.modes.SICBlockCipher; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.crypto.params.KeyParameter; -import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.crypto.params.*; import org.spongycastle.crypto.signers.ECDSASigner; import org.spongycastle.crypto.signers.HMacDSAKCalculator; import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; @@ -70,22 +43,29 @@ import org.spongycastle.util.encoders.Base64; import org.spongycastle.util.encoders.Hex; import org.tron.common.crypto.cryptohash.Keccak256; -import org.tron.common.crypto.jce.ECKeyAgreement; -import org.tron.common.crypto.jce.ECKeyFactory; -import org.tron.common.crypto.jce.ECKeyPairGenerator; -import org.tron.common.crypto.jce.ECSignatureFactory; -import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.crypto.jce.*; import org.tron.common.utils.BIUtil; import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Hash; +import javax.annotation.Nullable; +import javax.crypto.KeyAgreement; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.*; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + @Slf4j(topic = "crypto") public class ECKey implements Serializable, SignInterface { - /** - * The parameters of the secp256k1 curve. - */ + /** The parameters of the secp256k1 curve. */ public static final ECDomainParameters CURVE; + public static final ECParameterSpec CURVE_SPEC; /** @@ -96,8 +76,8 @@ public class ECKey implements Serializable, SignInterface { *

See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki * #Low_S_values_in_signatures */ - public static final BigInteger HALF_CURVE_ORDER; + private static final BigInteger SECP256K1N = new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); private static final SecureRandom secureRandom; @@ -106,10 +86,9 @@ public class ECKey implements Serializable, SignInterface { static { // All clients must agree on the curve to use by agreement. X9ECParameters params = SECNamedCurves.getByName("secp256k1"); - CURVE = new ECDomainParameters(params.getCurve(), params.getG(), - params.getN(), params.getH()); - CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), - params.getN(), params.getH()); + CURVE = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + CURVE_SPEC = + new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH()); HALF_CURVE_ORDER = params.getN().shiftRight(1); secureRandom = new SecureRandom(); } @@ -161,15 +140,17 @@ public ECKey(Provider provider, SecureRandom secureRandom) { pub = extractPublicKey((ECPublicKey) pubKey); } else { throw new AssertionError( - "Expected Provider " + provider.getName() + "Expected Provider " + + provider.getName() + " to produce a subtype of ECPublicKey, found " + pubKey.getClass()); } } /** - * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle - * will be used as the Java Security Provider + * Generates an entirely new keypair with the given {@link SecureRandom} object. + * + *

BouncyCastle will be used as the Java Security Provider * * @param secureRandom - */ @@ -203,10 +184,10 @@ public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { this.privKey = privKey; } else { throw new IllegalArgumentException( - "Expected EC private key, given a private key object with" + - " class " - + privKey.getClass().toString() + - " and algorithm " + "Expected EC private key, given a private key object with" + + " class " + + privKey.getClass().toString() + + " and algorithm " + privKey.getAlgorithm()); } @@ -218,15 +199,12 @@ public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { } /** - * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java - * Security Provider + * Pair a private key integer with a public EC point + * + *

BouncyCastle will be used as the Java Security Provider */ public ECKey(@Nullable BigInteger priv, ECPoint pub) { - this( - TronCastleProvider.getInstance(), - privateKeyFromBigInteger(priv), - pub - ); + this(TronCastleProvider.getInstance(), privateKeyFromBigInteger(priv), pub); } /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint @@ -246,8 +224,7 @@ private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { * a fallback that covers this case is to check the key algorithm */ private static boolean isECPrivateKey(PrivateKey privKey) { - return privKey instanceof ECPrivateKey || privKey.getAlgorithm() - .equals("EC"); + return privKey instanceof ECPrivateKey || privKey.getAlgorithm().equals("EC"); } /* Convert a BigInteger into a PrivateKey object @@ -257,10 +234,8 @@ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { return null; } else { try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - CURVE_SPEC)); + return ECKeyFactory.getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, CURVE_SPEC)); } catch (InvalidKeySpecException ex) { throw new AssertionError("Assumed correct key spec statically"); } @@ -319,8 +294,7 @@ public static ECKey fromPrivate(byte[] privKeyBytes) { * @param pub - * @return - */ - public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { + public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub) { return new ECKey(priv, pub); } @@ -333,12 +307,10 @@ public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, * @param pub - * @return - */ - public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] - pub) { + public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] pub) { check(priv != null, "Private key must not be null"); check(pub != null, "Public key must not be null"); - return new ECKey(new BigInteger(1, priv), CURVE.getCurve() - .decodePoint(pub)); + return new ECKey(new BigInteger(1, priv), CURVE.getCurve().decodePoint(pub)); } /** @@ -371,15 +343,15 @@ public static ECKey fromPublicOnly(byte[] pub) { * @param compressed - * @return - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { ECPoint point = CURVE.getG().multiply(privKey); return point.getEncoded(compressed); } /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. + * Compute the encoded X, Y coordinates of a public point. + * + *

This is the encoded public key without the leading byte. * * @param pubPoint a public point * @return 64-byte X,Y point pair @@ -402,8 +374,8 @@ public static ECKey fromNodeId(byte[] nodeId) { return ECKey.fromPublicOnly(pubBytes); } - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static byte[] signatureToKeyBytes(byte[] messageHash, String signatureBase64) + throws SignatureException { byte[] signatureEncoded; try { signatureEncoded = Base64.decode(signatureBase64); @@ -414,8 +386,8 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, String } // Parse the signature bytes into r/s and the selector value. if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); + throw new SignatureException( + "Signature truncated, expected 65 " + "bytes and got " + signatureEncoded.length); } return signatureToKeyBytes( @@ -426,11 +398,9 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, String (byte) (signatureEncoded[0] & 0xFF))); } - public static byte[] signatureToKeyBytes(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { - check(messageHash.length == 32, "messageHash argument has length " + - messageHash.length); + public static byte[] signatureToKeyBytes(byte[] messageHash, ECDSASignature sig) + throws SignatureException { + check(messageHash.length == 32, "messageHash argument has length " + messageHash.length); int header = sig.v; // The header byte: 0x1B = first key with even y, 0x1C = first key // with odd y, @@ -443,11 +413,9 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, header -= 4; } int recId = header - 27; - byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, - messageHash); + byte[] key = ECKey.recoverPubBytesFromSignature(recId, sig, messageHash); if (key == null) { - throw new SignatureException("Could not recover public key from " + - "signature"); + throw new SignatureException("Could not recover public key from " + "signature"); } return key; } @@ -459,10 +427,9 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, * @param signatureBase64 Base-64 encoded signature * @return 20-byte address */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { - return Hash.computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); + public static byte[] signatureToAddress(byte[] messageHash, String signatureBase64) + throws SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, signatureBase64)); } /** @@ -472,9 +439,8 @@ public static byte[] signatureToAddress(byte[] messageHash, String * @param sig - * @return 20-byte address */ - public static byte[] signatureToAddress(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { + public static byte[] signatureToAddress(byte[] messageHash, ECDSASignature sig) + throws SignatureException { return Hash.computeAddress(signatureToKeyBytes(messageHash, sig)); } @@ -485,10 +451,9 @@ public static byte[] signatureToAddress(byte[] messageHash, * @param signatureBase64 Base-64 encoded signature * @return ECKey */ - public static ECKey signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { - final byte[] keyBytes = signatureToKeyBytes(messageHash, - signatureBase64); + public static ECKey signatureToKey(byte[] messageHash, String signatureBase64) + throws SignatureException { + final byte[] keyBytes = signatureToKeyBytes(messageHash, signatureBase64); return ECKey.fromPublicOnly(keyBytes); } @@ -499,27 +464,29 @@ public static ECKey signatureToKey(byte[] messageHash, String * @param sig - * @return ECKey */ - public static ECKey signatureToKey(byte[] messageHash, ECDSASignature - sig) throws SignatureException { + public static ECKey signatureToKey(byte[] messageHash, ECDSASignature sig) + throws SignatureException { final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); return ECKey.fromPublicOnly(keyBytes); } /** - *

Verifies the given ECDSA signature against the message bytes using the public key bytes.

- *

When using native ECDSA verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

+ * Verifies the given ECDSA signature against the message bytes using the public key bytes. + * + *

+ * + *

When using native ECDSA verification, data must be 32 bytes, and no element may be larger + * than 520 bytes. * * @param data Hash of the data to verify. * @param signature signature. * @param pub The public key bytes to use. * @return - */ - public static boolean verify(byte[] data, ECDSASignature signature, - byte[] pub) { + public static boolean verify(byte[] data, ECDSASignature signature, byte[] pub) { ECDSASigner signer = new ECDSASigner(); - ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE - .getCurve().decodePoint(pub), CURVE); + ECPublicKeyParameters params = + new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE); signer.init(false, params); try { return signer.verifySignature(data, signature.r, signature.s); @@ -565,20 +532,25 @@ public static boolean isPubKeyCanonical(byte[] pubkey) { } /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6. + * + *

* - *

The recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the + *

The recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is the * correct one. Because the key recovery operation yields multiple potential allKeys, the correct * key must either be stored alongside the signature, or you must be willing to try each recId in - * turn until you find one that outputs the key you are expecting.

+ * turn until you find one that outputs the key you are expecting. * - *

If this method returns null it means recovery was not possible and recId should be - * iterated.

+ *

* - *

Given the above two points, a correct usage of this method is inside a for loop from 0 - * to 3, and if the output is null OR a key that is not the one you expect, you try again with the - * next recId.

+ *

If this method returns null it means recovery was not possible and recId should be iterated. + * + *

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, + * and if the output is null OR a key that is not the one you expect, you try again with the next + * recId. * * @param recId Which possible key to recover. * @param sig the R and S components of the signature, wrapped. @@ -586,9 +558,8 @@ public static boolean isPubKeyCanonical(byte[] pubkey) { * @return 65-byte encoded public key */ @Nullable - public static byte[] recoverPubBytesFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { + public static byte[] recoverPubBytesFromSignature( + int recId, ECDSASignature sig, byte[] messageHash) { check(recId >= 0, "recId must be positive"); check(sig.r.signum() >= 0, "r must be positive"); check(sig.s.signum() >= 0, "s must be positive"); @@ -596,7 +567,7 @@ public static byte[] recoverPubBytesFromSignature(int recId, // 1.0 For j from 0 to h (h == recId here and the loop is outside // this function) // 1.1 Let x = r + jn - BigInteger n = CURVE.getN(); // Curve order. + BigInteger n = CURVE.getN(); // Curve order. BigInteger i = BigInteger.valueOf((long) recId / 2); BigInteger x = sig.r.add(i.multiply(n)); // 1.2. Convert the integer x to an octet string X of length mlen @@ -612,7 +583,7 @@ public static byte[] recoverPubBytesFromSignature(int recId, // More concisely, what these points mean is to use X as a compressed // public key. ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); - BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent // about the letter it uses for the prime. if (x.compareTo(prime) >= 0) { // Cannot have point co-ordinates larger than this as everything @@ -652,8 +623,7 @@ public static byte[] recoverPubBytesFromSignature(int recId, BigInteger rInv = sig.r.modInverse(n); BigInteger srInv = rInv.multiply(sig.s).mod(n); BigInteger eInvrInv = rInv.multiply(eInv).mod(n); - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE - .getG(), eInvrInv, R, srInv); + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv); return q.getEncoded(/* compressed */ false); } @@ -664,11 +634,9 @@ public static byte[] recoverPubBytesFromSignature(int recId, * @return 20-byte address */ @Nullable - public static byte[] recoverAddressFromSignature(int recId, - ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); + public static byte[] recoverAddressFromSignature( + int recId, ECDSASignature sig, byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, messageHash); if (pubBytes == null) { return null; } else { @@ -683,10 +651,8 @@ public static byte[] recoverAddressFromSignature(int recId, * @return ECKey */ @Nullable - public static ECKey recoverFromSignature(int recId, ECDSASignature sig, - byte[] messageHash) { - final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, - messageHash); + public static ECKey recoverFromSignature(int recId, ECDSASignature sig, byte[] messageHash) { + final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, messageHash); if (pubBytes == null) { return null; } else { @@ -701,11 +667,9 @@ public static ECKey recoverFromSignature(int recId, ECDSASignature sig, * @param yBit - * @return - */ - private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE - .getCurve())); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve())); compEnc[0] = (byte) (yBit ? 0x03 : 0x02); return CURVE.getCurve().decodePoint(compEnc); } @@ -732,9 +696,7 @@ public ECKey decompress() { } } - /** - * @deprecated per-point compression property will be removed in Bouncy Castle - */ + /** @deprecated per-point compression property will be removed in Bouncy Castle */ public ECKey compress() { if (pub.isCompressed()) { return this; @@ -780,22 +742,20 @@ public String signHash(byte[] hash) { return sign(hash).toBase64(); } - public byte[] Base64toBytes (String signature) { + public byte[] Base64toBytes(String signature) { byte[] signData = Base64.decode(signature); - byte first = (byte)(signData[0] - 27); - byte[] temp = Arrays.copyOfRange(signData,1,65); - return ByteUtil.appendByte(temp,first); + byte first = (byte) (signData[0] - 27); + byte[] temp = Arrays.copyOfRange(signData, 1, 65); + return ByteUtil.appendByte(temp, first); } @Override - public byte[] signToAddress(byte[] messageHash, String signatureBase64) throws SignatureException { - return Hash.computeAddress(signatureToKeyBytes(messageHash, - signatureBase64)); + public byte[] signToAddress(byte[] messageHash, String signatureBase64) + throws SignatureException { + return Hash.computeAddress(signatureToKeyBytes(messageHash, signatureBase64)); } - /** - * Generates the NodeID based on this key, that is the public key without first format byte - */ + /** Generates the NodeID based on this key, that is the public key without first format byte */ public byte[] getNodeId() { if (nodeId == null) { nodeId = pubBytesWithoutFormat(this.pub); @@ -803,7 +763,7 @@ public byte[] getNodeId() { return nodeId; } - @Override + // @Override public byte[] hash(byte[] message) { Keccak256 hashFun = new Keccak256(); return hashFun.digest(message); @@ -875,8 +835,7 @@ public String toStringWithPrivate() { StringBuilder b = new StringBuilder(); b.append(toString()); if (privKey != null && privKey instanceof BCECPrivateKey) { - b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) - privKey).getD().toByteArray())); + b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey) privKey).getD().toByteArray())); } return b.toString(); } @@ -890,31 +849,27 @@ public String toStringWithPrivate() { */ public ECDSASignature doSign(byte[] input) { if (input.length != 32) { - throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + input.length); + throw new IllegalArgumentException( + "Expected 32 byte input to " + "ECDSA signature, not " + input.length); } // No decryption of private key required. if (privKey == null) { throw new MissingPrivateKeyException(); } if (privKey instanceof BCECPrivateKey) { - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new - SHA256Digest())); - ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters - (((BCECPrivateKey) privKey).getD(), CURVE); + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + ECPrivateKeyParameters privKeyParams = + new ECPrivateKeyParameters(((BCECPrivateKey) privKey).getD(), CURVE); signer.init(true, privKeyParams); BigInteger[] components = signer.generateSignature(input); - return new ECDSASignature(components[0], components[1]) - .toCanonicalised(); + return new ECDSASignature(components[0], components[1]).toCanonicalised(); } else { try { - final Signature ecSig = ECSignatureFactory.getRawInstance - (provider); + final Signature ecSig = ECSignatureFactory.getRawInstance(provider); ecSig.initSign(privKey); ecSig.update(input); final byte[] derSignature = ecSig.sign(); - return ECDSASignature.decodeFromDER(derSignature) - .toCanonicalised(); + return ECDSASignature.decodeFromDER(derSignature).toCanonicalised(); } catch (SignatureException | InvalidKeyException ex) { throw new RuntimeException("ECKey signing error", ex); } @@ -942,8 +897,8 @@ public ECDSASignature sign(byte[] messageHash) { } } if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); + throw new RuntimeException( + "Could not construct a recoverable key" + ". This should never happen."); } sig.v = (byte) (recId + 27); return sig; @@ -954,23 +909,18 @@ public BigInteger keyAgreement(ECPoint otherParty) { throw new MissingPrivateKeyException(); } else if (privKey instanceof BCECPrivateKey) { final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); - agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) - privKey).getD(), CURVE)); - return agreement.calculateAgreement(new ECPublicKeyParameters - (otherParty, CURVE)); + agreement.init(new ECPrivateKeyParameters(((BCECPrivateKey) privKey).getD(), CURVE)); + return agreement.calculateAgreement(new ECPublicKeyParameters(otherParty, CURVE)); } else { try { - final KeyAgreement agreement = ECKeyAgreement.getInstance - (this.provider); + final KeyAgreement agreement = ECKeyAgreement.getInstance(this.provider); agreement.init(this.privKey); agreement.doPhase( ECKeyFactory.getInstance(this.provider) - .generatePublic(new ECPublicKeySpec - (otherParty, CURVE_SPEC)), + .generatePublic(new ECPublicKeySpec(otherParty, CURVE_SPEC)), /* lastPhase */ true); return new BigInteger(1, agreement.generateSecret()); - } catch (IllegalStateException | InvalidKeyException | - InvalidKeySpecException ex) { + } catch (IllegalStateException | InvalidKeyException | InvalidKeySpecException ex) { throw new RuntimeException("ECDH key agreement failure", ex); } } @@ -989,15 +939,14 @@ public byte[] decryptAES(byte[] cipher) { throw new MissingPrivateKeyException(); } if (!(privKey instanceof BCECPrivateKey)) { - throw new UnsupportedOperationException("Cannot use the private " + - "key as an AES key"); + throw new UnsupportedOperationException("Cannot use the private " + "key as an AES key"); } AESEngine engine = new AESEngine(); SICBlockCipher ctrEngine = new SICBlockCipher(engine); - KeyParameter key = new KeyParameter(BigIntegers.asUnsignedByteArray(( - (BCECPrivateKey) privKey).getD())); + KeyParameter key = + new KeyParameter(BigIntegers.asUnsignedByteArray(((BCECPrivateKey) privKey).getD())); ParametersWithIV params = new ParametersWithIV(key, new byte[16]); ctrEngine.init(false, params); @@ -1097,10 +1046,9 @@ public int hashCode() { public static class ECDSASignature implements SignatureInterface { - /** - * The two components of the signature. - */ + /** The two components of the signature. */ public final BigInteger r, s; + public byte v; /** @@ -1127,8 +1075,7 @@ public ECDSASignature(byte[] r, byte[] s, byte v) { * @return - */ private static ECDSASignature fromComponents(byte[] r, byte[] s) { - return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, - s)); + return new ECDSASignature(new BigInteger(1, r), new BigInteger(1, s)); } /** @@ -1137,15 +1084,13 @@ private static ECDSASignature fromComponents(byte[] r, byte[] s) { * @param v - * @return - */ - public static ECDSASignature fromComponents(byte[] r, byte[] s, byte - v) { + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte v) { ECDSASignature signature = fromComponents(r, s); signature.v = v; return signature; } - public static boolean validateComponents(BigInteger r, BigInteger s, - byte v) { + public static boolean validateComponents(BigInteger r, BigInteger s, byte v) { if (v != 27 && v != 28) { return false; @@ -1170,8 +1115,7 @@ public static ECDSASignature decodeFromDER(byte[] bytes) { decoder = new ASN1InputStream(bytes); DLSequence seq = (DLSequence) decoder.readObject(); if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " + - "stream."); + throw new RuntimeException("Reached past end of ASN.1 " + "stream."); } ASN1Integer r, s; try { @@ -1184,8 +1128,7 @@ public static ECDSASignature decodeFromDER(byte[] bytes) { // values as unsigned, though they should not be // Thus, we always use the positive versions. See: // http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s - .getPositiveValue()); + return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue()); } catch (IOException e) { throw new RuntimeException(e); } finally { @@ -1220,11 +1163,9 @@ public ECDSASignature toCanonicalised() { } } - /** - * @return - - */ + /** @return - */ public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 // bytes for S sigData[0] = v; System.arraycopy(ByteUtil.bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); @@ -1232,17 +1173,13 @@ public String toBase64() { return new String(Base64.encode(sigData), Charset.forName("UTF-8")); } - - public byte[] toByteArray() { - final byte fixedV = this.v >= 27 - ? (byte) (this.v - 27) - : this.v; + final byte fixedV = this.v >= 27 ? (byte) (this.v - 27) : this.v; return ByteUtil.merge( ByteUtil.bigIntegerToBytes(this.r, 32), ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); + new byte[] {fixedV}); } public String toHex() { @@ -1275,8 +1212,5 @@ public int hashCode() { } @SuppressWarnings("serial") - public static class MissingPrivateKeyException extends RuntimeException { - - } - + public static class MissingPrivateKeyException extends RuntimeException {} } diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index deb115daf..91aeb34ee 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -136,17 +136,17 @@ public static SM3Hash twiceOf(byte[] contents) { } /** - * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the * checked exception that can never occur with a RuntimeException. * - * @return a new SHA-256 MessageDigest instance + * @return a new SM3 MessageDigest instance */ public static SM3Digest newDigest() { return new SM3Digest(); } /** - * Calculates the SHA-256 hash of the given bytes. + * Calculates the SM3 hash of the given bytes. * * @param input the bytes to hash * @return the hash (in big-endian order) @@ -156,7 +156,7 @@ public static byte[] hash(byte[] input) { } /** - * Calculates the SHA-256 hash of the given byte range. + * Calculates the SM3 hash of the given byte range. * * @param input the array containing the bytes to hash * @param offset the offset within the array of the bytes to hash @@ -173,7 +173,7 @@ public static byte[] hash(byte[] input, int offset, int length) { } /** - * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * Calculates the SM3 hash of the given bytes, and then hashes the resulting hash again. * * @param input the bytes to hash * @return the double-hash (in big-endian order) @@ -183,7 +183,7 @@ public static byte[] hashTwice(byte[] input) { } /** - * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * Calculates the SM3 hash of the given byte range, and then hashes the resulting hash again. * * @param input the array containing the bytes to hash * @param offset the offset within the array of the bytes to hash diff --git a/src/main/java/org/tron/common/crypto/SignInterface.java b/src/main/java/org/tron/common/crypto/SignInterface.java index b158c50b7..eb21a792e 100644 --- a/src/main/java/org/tron/common/crypto/SignInterface.java +++ b/src/main/java/org/tron/common/crypto/SignInterface.java @@ -4,7 +4,7 @@ public interface SignInterface { - byte[] hash(byte[] message); + // byte[] hash(byte[] message); byte[] getPrivateKey(); @@ -18,7 +18,7 @@ public interface SignInterface { byte[] getNodeId(); - byte[] Base64toBytes (String signature); + byte[] Base64toBytes(String signature); byte[] getPrivKeyBytes(); diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2.java b/src/main/java/org/tron/common/crypto/sm2/SM2.java index 3a323c579..729d2fdf1 100644 --- a/src/main/java/org/tron/common/crypto/sm2/SM2.java +++ b/src/main/java/org/tron/common/crypto/sm2/SM2.java @@ -383,7 +383,7 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, SM2Signature sig) return key; } - @Override + // @Override public byte[] hash(byte[] message) { SM2Signer signer = this.getSM2SignerForHash(); return signer.generateSM3Hash(message); diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index ccaf56222..04dbe3d10 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -16,12 +16,11 @@ package org.tron.common.utils; import com.google.protobuf.ByteString; -import org.tron.common.crypto.ECKey; +import com.typesafe.config.Config; +import org.tron.common.crypto.*; import org.tron.common.crypto.ECKey.ECDSASignature; -import org.tron.common.crypto.Sha256Hash; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.sm2.SM2; +import org.tron.common.crypto.sm2.SM3; +import org.tron.core.config.Configuration; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -38,11 +37,23 @@ public class TransactionUtils { * @param transaction {@link Transaction} transaction * @return byte[] the hash of the transaction's data bytes which have no id */ + private static boolean isEckey = true; + + static { + Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + } + } + public static byte[] getHash(Transaction transaction) { Transaction.Builder tmp = transaction.toBuilder(); - //tmp.clearId(); - - return Sha256Hash.hash(tmp.build().toByteArray()); + // tmp.clearId(); + if (isEckey) { + return Sha256Hash.hash(tmp.build().toByteArray()); + } else { + return SM3Hash.hash(tmp.build().toByteArray()); + } } public static byte[] getOwner(Transaction.Contract contract) { @@ -50,69 +61,116 @@ public static byte[] getOwner(Transaction.Contract contract) { try { switch (contract.getType()) { case AccountCreateContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.AccountCreateContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AccountCreateContract.class) + .getOwnerAddress(); break; case TransferContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.TransferContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TransferContract.class) + .getOwnerAddress(); break; case TransferAssetContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.TransferAssetContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TransferAssetContract.class) + .getOwnerAddress(); break; case VoteAssetContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteAssetContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.VoteAssetContract.class) + .getOwnerAddress(); break; case VoteWitnessContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteWitnessContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.VoteWitnessContract.class) + .getOwnerAddress(); break; case WitnessCreateContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.WitnessCreateContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.WitnessCreateContract.class) + .getOwnerAddress(); break; case AssetIssueContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.AssetIssueContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AssetIssueContract.class) + .getOwnerAddress(); break; case ParticipateAssetIssueContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) + .getOwnerAddress(); break; case CreateSmartContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.CreateSmartContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.CreateSmartContract.class) + .getOwnerAddress(); break; case TriggerSmartContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.TriggerSmartContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TriggerSmartContract.class) + .getOwnerAddress(); break; case FreezeBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.FreezeBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.FreezeBalanceContract.class) + .getOwnerAddress(); break; case UnfreezeBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class) + .getOwnerAddress(); break; case UnfreezeAssetContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class) + .getOwnerAddress(); break; case WithdrawBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class) + .getOwnerAddress(); break; case UpdateAssetContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.UpdateAssetContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UpdateAssetContract.class) + .getOwnerAddress(); break; case AccountPermissionUpdateContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) + .getOwnerAddress(); break; default: return null; @@ -129,7 +187,7 @@ public static String getBase64FromByteString(ByteString sign) { byte[] s = sign.substring(32, 64).toByteArray(); byte v = sign.byteAt(64); if (v < 27) { - v += 27; //revId -> v + v += 27; // revId -> v } ECDSASignature signature = ECDSASignature.fromComponents(r, s, v); return signature.toBase64(); @@ -142,10 +200,15 @@ public static String getBase64FromByteString(ByteString sign) { * 4. check balance */ public static boolean validTransaction(Transaction signedTransaction) { - assert (signedTransaction.getSignatureCount() == - signedTransaction.getRawData().getContractCount()); + assert (signedTransaction.getSignatureCount() + == signedTransaction.getRawData().getContractCount()); List listContract = signedTransaction.getRawData().getContractList(); - byte[] hash = Sha256Hash.hash(signedTransaction.getRawData().toByteArray()); + byte[] hash; + if (isEckey) { + hash = Sha256Hash.hash(signedTransaction.getRawData().toByteArray()); + } else { + hash = SM3Hash.hash(signedTransaction.getRawData().toByteArray()); + } int count = signedTransaction.getSignatureCount(); if (count == 0) { return false; @@ -154,8 +217,9 @@ public static boolean validTransaction(Transaction signedTransaction) { try { Transaction.Contract contract = listContract.get(i); byte[] owner = getOwner(contract); - byte[] address = ECKey - .signatureToAddress(hash, getBase64FromByteString(signedTransaction.getSignature(i))); + byte[] address = + ECKey.signatureToAddress( + hash, getBase64FromByteString(signedTransaction.getSignature(i))); if (!Arrays.equals(owner, address)) { return false; } @@ -169,7 +233,12 @@ public static boolean validTransaction(Transaction signedTransaction) { public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); - byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); + byte[] hash; + if (isEckey) { + hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); + } else { + hash = SM3.hash(transaction.getRawData().toByteArray()); + } SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); @@ -178,10 +247,10 @@ public static Transaction sign(Transaction transaction, SignInterface myKey) { } public static Transaction setTimestamp(Transaction transaction) { - long currentTime = System.currentTimeMillis();//*1000000 + System.nanoTime()%1000000; + long currentTime = System.currentTimeMillis(); // *1000000 + System.nanoTime()%1000000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() - .toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = + transaction.getRawData().toBuilder(); rowBuilder.setTimestamp(currentTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -191,8 +260,8 @@ public static Transaction setExpirationTime(Transaction transaction) { if (transaction.getSignatureCount() == 0) { long expirationTime = System.currentTimeMillis() + 6 * 60 * 60 * 1000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() - .toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = + transaction.getRawData().toBuilder(); rowBuilder.setExpiration(expirationTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -211,8 +280,8 @@ public static Transaction setPermissionId(Transaction transaction) throws Cancel } if (permission_id != 0) { Transaction.raw.Builder raw = transaction.getRawData().toBuilder(); - Transaction.Contract.Builder contract = raw.getContract(0).toBuilder() - .setPermissionId(permission_id); + Transaction.Contract.Builder contract = + raw.getContract(0).toBuilder().setPermissionId(permission_id); raw.clearContract(); raw.addContract(contract); transaction = transaction.toBuilder().setRawData(raw).build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index fbc1256ff..b38fcaa46 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -23,9 +23,12 @@ import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; +import com.typesafe.config.Config; import org.tron.api.GrpcAPI.*; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SM3Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.core.config.Configuration; import org.tron.keystore.StringUtils; import org.tron.protos.Contract.*; import org.tron.protos.Protocol.Block; @@ -52,6 +55,16 @@ public class Utils { public static final String VALUE = "value"; private static SecureRandom random = new SecureRandom(); + private static boolean isEckey = true; + + static { + Config config = Configuration.getByPath("config.conf"); + + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletApi getConfig isEckey: " + isEckey); + } + } public static SecureRandom getRandom() { return random; @@ -236,7 +249,12 @@ public static char[] inputPassword(boolean checkStrength) throws IOException { public static byte[] generateContractAddress(Transaction trx, byte[] ownerAddress) { // get tx hash - byte[] txRawDataHash = Sha256Hash.of(trx.getRawData().toByteArray()).getBytes(); + byte[] txRawDataHash; + if (isEckey) { + txRawDataHash = Sha256Hash.of(trx.getRawData().toByteArray()).getBytes(); + } else { + txRawDataHash = SM3Hash.of(trx.getRawData().toByteArray()).getBytes(); + } // combine byte[] combined = new byte[txRawDataHash.length + ownerAddress.length]; @@ -504,7 +522,12 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean jsonTransaction.put("raw_data", rawData); String rawDataHex = ByteArray.toHexString(transaction.getRawData().toByteArray()); jsonTransaction.put("raw_data_hex", rawDataHex); - String txID = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + String txID; + if (isEckey) { + txID = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + } else { + txID = ByteArray.toHexString(SM3Hash.hash(transaction.getRawData().toByteArray())); + } jsonTransaction.put("txID", txID); return jsonTransaction; } diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index fc57493a5..f3467329f 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -3,11 +3,9 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.TransactionExtention; - -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.SM3Hash; import org.tron.common.crypto.sm2.SM2; +import org.tron.common.crypto.sm2.SM3; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; import org.tron.protos.Contract; @@ -15,38 +13,38 @@ import org.tron.protos.Protocol.Transaction; import org.tron.walletserver.WalletApi; -import java.util.Arrays; - public class TransactionSignDemoForSM2 { public static Transaction setReference(Transaction transaction, Block newestBlock) { long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); + Transaction.raw rawData = + transaction + .getRawData() + .toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); return transaction.toBuilder().setRawData(rawData).build(); } - public static Sha256Hash getBlockHash(Block block) { - return Sha256Hash.of(block.getBlockHeader().getRawData().toByteArray()); + public static SM3Hash getBlockHash(Block block) { + return SM3Hash.of(block.getBlockHeader().getRawData().toByteArray()); } public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + String txid = ByteArray.toHexString(SM3.hash(transaction.getRawData().toByteArray())); return txid; } - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { Transaction.Builder transactionBuilder = Transaction.newBuilder(); Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract - .newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = + Contract.TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); @@ -59,15 +57,17 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) + transactionBuilder + .getRawDataBuilder() + .addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration( + newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; } - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { SM2 sm2 = SM2.fromPrivate(privateKey); @@ -110,17 +110,14 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); - long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); - - //sign a transaction in byte format and return a Transaction in byte format + // sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); boolean result = broadcast(transaction4); System.out.println(result); } - - } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index f1b4449d4..24812eb56 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -20,6 +20,7 @@ import org.tron.api.GrpcAPI.TransactionSignWeight.Result.response_code; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SM3Hash; import org.tron.common.crypto.Sha256Hash; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; @@ -57,17 +58,17 @@ public class WalletApi { private static GrpcClient rpcCli = init(); -// static { -// new Timer().schedule(new TimerTask() { -// @Override -// public void run() { -// String fullnode = selectFullNode(); -// if(!"".equals(fullnode)) { -// rpcCli = new GrpcClient(fullnode); -// } -// } -// }, 3 * 60 * 1000, 3 * 60 * 1000); -// } + // static { + // new Timer().schedule(new TimerTask() { + // @Override + // public void run() { + // String fullnode = selectFullNode(); + // if(!"".equals(fullnode)) { + // rpcCli = new GrpcClient(fullnode); + // } + // } + // }, 3 * 60 * 1000, 3 * 60 * 1000); + // } public static GrpcClient init() { Config config = Configuration.getByPath("config.conf"); @@ -140,9 +141,7 @@ public static int getRpcVersion() { return rpcVersion; } - /** - * Creates a new WalletApi with a random ECKey or no ECKey. - */ + /** Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -157,7 +156,7 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - WalletFile walletFile=null; + WalletFile walletFile = null; if (isEckey) { ECKey ecKey = ECKey.fromPrivate(priKey); walletFile = Wallet.createStandard(password, ecKey); @@ -186,9 +185,7 @@ public boolean checkPassword(byte[] passwd) throws CipherException { return Wallet.validPassword(passwd, this.walletFile.get(0)); } - /** - * Creates a Wallet with an existing ECKey. - */ + /** Creates a Wallet with an existing ECKey. */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -264,11 +261,12 @@ public static File selcetWalletFile() { try { n = new Integer(num); } catch (NumberFormatException e) { - System.out.println("Invaild number of " + num); + System.out.println("Invalid number of " + num); System.out.println("Please choose again between 1 and " + wallets.length); continue; } if (n < 1 || n > wallets.length) { + System.out.println("Invalid number of " + num); System.out.println("Please choose again between 1 and " + wallets.length); continue; } @@ -313,7 +311,6 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso return true; } - private static WalletFile loadWalletFile() throws IOException { File wallet = selcetWalletFile(); if (wallet == null) { @@ -323,11 +320,8 @@ private static WalletFile loadWalletFile() throws IOException { return WalletUtils.loadWalletFile(wallet); } - /** - * load a Wallet from keystore - */ - public static WalletApi loadWalletFromKeystore() - throws IOException { + /** load a Wallet from keystore */ + public static WalletApi loadWalletFromKeystore() throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); return walletApi; @@ -338,7 +332,7 @@ public Account queryAccount() { } public static Account queryAccount(byte[] address) { - return rpcCli.queryAccount(address);//call rpc + return rpcCli.queryAccount(address); // call rpc } public static Account queryAccountById(String accountId) { @@ -380,9 +374,9 @@ private Transaction signTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } -// System.out -// .println("current transaction hex string is " + ByteArray -// .toHexString(transaction.toByteArray())); + // System.out + // .println("current transaction hex string is " + ByteArray + // .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -422,9 +416,7 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } -// System.out -// .println("current transaction hex string is " + ByteArray -// .toHexString(transaction.toByteArray())); + org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -463,13 +455,14 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); - System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -477,23 +470,29 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { - System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); - System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println( + "after sign transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); + if (isEckey) { + System.out.println( + "txid is " + + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + } else { + System.out.println( + "txid is " + ByteArray.toHexString(SM3Hash.hash(transaction.getRawData().toByteArray()))); + } if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { - CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); - byte[] contractAddress = generateContractAddress( - createSmartContract.getOwnerAddress().toByteArray(), transaction); + CreateSmartContract createSmartContract = + transaction.getRawData().getContract(0).getParameter().unpack(CreateSmartContract.class); + byte[] contractAddress = + generateContractAddress(createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); } } - private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + private static boolean processShieldedTransaction( + TransactionExtention transactionExtention, WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -511,7 +510,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -527,11 +526,16 @@ private static boolean processShieldedTransaction(TransactionExtention transacti transaction = wallet.signOnlyForShieldedTransaction(transaction); } - System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); - System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); - + System.out.println( + "transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); + if (isEckey) { + System.out.println( + "txid is " + + ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + } else { + System.out.println( + "txid is " + ByteArray.toHexString(SM3Hash.hash(transaction.getRawData().toByteArray()))); + } return rpcCli.broadcastTransaction(transaction); } @@ -542,8 +546,9 @@ private boolean processTransaction(Transaction transaction) } System.out.println(Utils.printTransactionExceptId(transaction)); - System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -551,7 +556,7 @@ private boolean processTransaction(Transaction transaction) return rpcCli.broadcastTransaction(transaction); } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); @@ -563,9 +568,9 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p return rpcCli.signTransaction(builder.build()); } - //Warning: do not invoke this interface provided by others. - public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + // Warning: do not invoke this interface provided by others. + public static TransactionExtention signTransactionByApi2( + Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -575,9 +580,9 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction return rpcCli.signTransaction2(builder.build()); } - //Warning: do not invoke this interface provided by others. - public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + // Warning: do not invoke this interface provided by others. + public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) + throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -595,32 +600,32 @@ public static TransactionApprovedList getTransactionApprovedList(Transaction tra return rpcCli.getTransactionApprovedList(transaction); } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static byte[] createAdresss(byte[] passPhrase) { return rpcCli.createAdresss(passPhrase); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransfer( + byte[] passPhrase, byte[] toAddress, long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferByPrivate( + byte[] privateKey, byte[] toAddress, long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAsset( + byte[] passPhrase, byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAssetByPrivate( + byte[] privateKey, byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -671,16 +676,15 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) return processTransaction(transaction); } - - public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + public boolean updateAsset( + byte[] owner, byte[] description, byte[] url, long newLimit, long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.UpdateAssetContract contract - = createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); + Contract.UpdateAssetContract contract = + createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -696,8 +700,8 @@ public boolean transferAsset(byte[] owner, byte[] to, byte[] assertName, long am owner = getAddress(); } - Contract.TransferAssetContract contract = createTransferAssetContract(to, assertName, owner, - amount); + Contract.TransferAssetContract contract = + createTransferAssetContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransferAssetTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -713,11 +717,11 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner = getAddress(); } - Contract.ParticipateAssetIssueContract contract = participateAssetIssueContract(to, assertName, - owner, amount); + Contract.ParticipateAssetIssueContract contract = + participateAssetIssueContract(to, assertName, owner, amount); if (rpcVersion == 2) { - TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + TransactionExtention transactionExtention = + rpcCli.createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -762,7 +766,7 @@ public boolean createAccount(byte[] owner, byte[] address) } } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static AddressPrKeyPairMessage generateAddress() { EmptyMessage.Builder builder = EmptyMessage.newBuilder(); return rpcCli.generateAddress(builder.build()); @@ -828,8 +832,8 @@ public boolean voteWitness(byte[] owner, HashMap witness) } } - public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + public static Contract.TransferContract createTransferContract( + byte[] to, byte[] owner, long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -840,9 +844,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] return builder.build(); } - public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + public static Contract.TransferAssetContract createTransferAssetContract( + byte[] to, byte[] assertName, byte[] owner, long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -855,11 +858,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] return builder.build(); } - public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { - Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + public static Contract.ParticipateAssetIssueContract participateAssetIssueContract( + byte[] to, byte[] assertName, byte[] owner, long amount) { + Contract.ParticipateAssetIssueContract.Builder builder = + Contract.ParticipateAssetIssueContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -871,8 +873,8 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra return builder.build(); } - public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + public static Contract.AccountUpdateContract createAccountUpdateContract( + byte[] accountName, byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -882,8 +884,8 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] return builder.build(); } - public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + public static Contract.SetAccountIdContract createSetAccountIdContract( + byte[] accountId, byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -893,16 +895,9 @@ public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] ac return builder.build(); } - public static Contract.UpdateAssetContract createUpdateAssetContract( - byte[] address, - byte[] description, - byte[] url, - long newLimit, - long newPublicLimit - ) { - Contract.UpdateAssetContract.Builder builder = - Contract.UpdateAssetContract.newBuilder(); + byte[] address, byte[] description, byte[] url, long newLimit, long newPublicLimit) { + Contract.UpdateAssetContract.Builder builder = Contract.UpdateAssetContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); builder.setDescription(ByteString.copyFrom(description)); builder.setUrl(ByteString.copyFrom(url)); @@ -913,8 +908,8 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( return builder.build(); } - public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + public static Contract.AccountCreateContract createAccountCreateContract( + byte[] owner, byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -922,8 +917,8 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] return builder.build(); } - public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + public static Contract.WitnessCreateContract createWitnessCreateContract( + byte[] owner, byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -931,8 +926,8 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] return builder.build(); } - public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + public static Contract.WitnessUpdateContract createWitnessUpdateContract( + byte[] owner, byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -940,15 +935,15 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] return builder.build(); } - public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + public static Contract.VoteWitnessContract createVoteWitnessContract( + byte[] owner, HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); - Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + Contract.VoteWitnessContract.Vote.Builder voteBuilder = + Contract.VoteWitnessContract.Vote.newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -969,7 +964,7 @@ public static boolean passwordValid(char[] password) { System.out.println("Warning: Password is too short !!"); return false; } - //Other rule; + // Other rule; int level = CheckStrength.checkPasswordStrength(password); if (level <= 4) { System.out.println("Your password is too weak!"); @@ -993,24 +988,37 @@ public static boolean addressValid(byte[] address) { } if (address.length != CommonConstant.ADDRESS_SIZE) { System.out.println( - "Warning: Address length need " + CommonConstant.ADDRESS_SIZE + " but " + address.length + "Warning: Address length need " + + CommonConstant.ADDRESS_SIZE + + " but " + + address.length + " !!"); return false; } byte preFixbyte = address[0]; if (preFixbyte != WalletApi.getAddressPreFixByte()) { - System.out - .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + System.out.println( + "Warning: Address need prefix with " + + WalletApi.getAddressPreFixByte() + + " but " + + preFixbyte + + " !!"); return false; } - //Other rule; + // Other rule; return true; } public static String encode58Check(byte[] input) { - byte[] hash0 = Sha256Hash.hash(input); - byte[] hash1 = Sha256Hash.hash(hash0); + byte[] hash0; + byte[] hash1; + if (isEckey) { + hash0 = Sha256Hash.hash(input); + hash1 = Sha256Hash.hash(hash0); + } else { + hash0 = SM3Hash.hash(input); + hash1 = SM3Hash.hash(hash0); + } byte[] inputCheck = new byte[input.length + 4]; System.arraycopy(input, 0, inputCheck, 0, input.length); System.arraycopy(hash1, 0, inputCheck, input.length, 4); @@ -1024,12 +1032,19 @@ private static byte[] decode58Check(String input) { } byte[] decodeData = new byte[decodeCheck.length - 4]; System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length); - byte[] hash0 = Sha256Hash.hash(decodeData); - byte[] hash1 = Sha256Hash.hash(hash0); - if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + byte[] hash0; + byte[] hash1; + if (isEckey) { + hash0 = Sha256Hash.hash(decodeData); + hash1 = Sha256Hash.hash(hash0); + } else { + hash0 = SM3Hash.hash(decodeData); + hash1 = SM3Hash.hash(hash0); + } + if (hash1[0] == decodeCheck[decodeData.length] + && hash1[1] == decodeCheck[decodeData.length + 1] + && hash1[2] == decodeCheck[decodeData.length + 2] + && hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1056,25 +1071,10 @@ public static boolean priKeyValid(byte[] priKey) { System.out.println("Warning: PrivateKey length need 64 but " + priKey.length + " !!"); return false; } - //Other rule; + // Other rule; return true; } -// public static Optional listAccounts() { -// Optional result = rpcCli.listAccounts(); -// if (result.isPresent()) { -// AccountList accountList = result.get(); -// List list = accountList.getAccountsList(); -// List newList = new ArrayList(); -// newList.addAll(list); -// newList.sort(new AccountComparator()); -// AccountList.Builder builder = AccountList.newBuilder(); -// newList.forEach(account -> builder.addAccounts(account)); -// result = Optional.of(builder.build()); -// } -// return result; -// } - public static Optional listWitnesses() { Optional result = rpcCli.listWitnesses(); if (result.isPresent()) { @@ -1082,12 +1082,13 @@ public static Optional listWitnesses() { List list = witnessList.getWitnessesList(); List newList = new ArrayList<>(); newList.addAll(list); - newList.sort(new Comparator() { - @Override - public int compare(Witness o1, Witness o2) { - return Long.compare(o2.getVoteCount(), o1.getVoteCount()); - } - }); + newList.sort( + new Comparator() { + @Override + public int compare(Witness o1, Witness o2) { + return Long.compare(o2.getVoteCount(), o1.getVoteCount()); + } + }); WitnessList.Builder builder = WitnessList.newBuilder(); newList.forEach(witness -> builder.addWitnesses(witness)); result = Optional.of(builder.build()); @@ -1095,19 +1096,6 @@ public int compare(Witness o1, Witness o2) { return result; } -// public static Optional getAssetIssueListByTimestamp(long timestamp) { -// return rpcCli.getAssetIssueListByTimestamp(timestamp); -// } -// -// public static Optional getTransactionsByTimestamp(long start, long end, -// int offset, int limit) { -// return rpcCli.getTransactionsByTimestamp(start, end, offset, limit); -// } -// -// public static GrpcAPI.NumberMessage getTransactionsByTimestampCount(long start, long end) { -// return rpcCli.getTransactionsByTimestampCount(start, end); -// } - public static Optional getAssetIssueList() { return rpcCli.getAssetIssueList(); } @@ -1124,7 +1112,6 @@ public static Optional getExchangeListPaginated(long offset, long return rpcCli.getExchangeListPaginated(offset, limit); } - public static Optional listNodes() { return rpcCli.listNodes(); } @@ -1161,33 +1148,25 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { return rpcCli.getNextMaintenanceTime(); } - public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + public static Optional getTransactionsFromThis( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } - public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + public static Optional getTransactionsFromThis2( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } -// public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { -// return rpcCli.getTransactionsFromThisCount(address); -// } - public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + public static Optional getTransactionsToThis( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } - public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + public static Optional getTransactionsToThis2( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } -// public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { -// return rpcCli.getTransactionsToThisCount(address); -// } public static Optional getTransactionById(String txID) { return rpcCli.getTransactionById(txID); @@ -1197,12 +1176,16 @@ public static Optional getTransactionInfoById(String txID) { return rpcCli.getTransactionInfoById(txID); } - public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + public boolean freezeBalance( + byte[] ownerAddress, + long frozen_balance, + long frozen_duration, + int resourceCode, + byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, - frozen_balance, - frozen_duration, resourceCode, receiverAddress); + Contract.FreezeBalanceContract contract = + createFreezeBalanceContract( + ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1231,23 +1214,29 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) Contract.SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); - } - private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + private FreezeBalanceContract createFreezeBalanceContract( + byte[] address, + long frozen_balance, + long frozen_duration, + int resourceCode, + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); - builder.setOwnerAddress(byteAddress).setFrozenBalance(frozen_balance) - .setFrozenDuration(frozen_duration).setResourceValue(resourceCode); + builder + .setOwnerAddress(byteAddress) + .setFrozenBalance(frozen_balance) + .setFrozenDuration(frozen_duration) + .setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = ByteString.copyFrom( - Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = + ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } return builder.build(); @@ -1270,8 +1259,8 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo address = getAddress(); } - Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + Contract.BuyStorageBytesContract.Builder builder = + Contract.BuyStorageBytesContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1292,8 +1281,8 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeBalanceContract contract = createUnfreezeBalanceContract(ownerAddress, - resourceCode, receiverAddress); + Contract.UnfreezeBalanceContract contract = + createUnfreezeBalanceContract(ownerAddress, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1303,21 +1292,20 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec } } - - private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + private UnfreezeBalanceContract createUnfreezeBalanceContract( + byte[] address, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } - Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + Contract.UnfreezeBalanceContract.Builder builder = + Contract.UnfreezeBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = ByteString.copyFrom( - Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = + ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } @@ -1341,8 +1329,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { address = getAddress(); } - Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1365,8 +1352,8 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { address = getAddress(); } - Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + Contract.WithdrawBalanceContract.Builder builder = + Contract.WithdrawBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1412,8 +1399,8 @@ public static Optional getProposal(String id) { return rpcCli.getProposal(id); } - public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + public static Optional getDelegatedResource( + String fromAddress, String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1434,9 +1421,8 @@ public static Optional getChainParameters() { return rpcCli.getChainParameters(); } - - public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + public static Contract.ProposalCreateContract createProposalCreateContract( + byte[] owner, HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1449,16 +1435,16 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) owner = getAddress(); } - Contract.ProposalApproveContract contract = createProposalApproveContract(owner, id, - is_add_approval); + Contract.ProposalApproveContract contract = + createProposalApproveContract(owner, id, is_add_approval); TransactionExtention transactionExtention = rpcCli.proposalApprove(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { - Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + public static Contract.ProposalApproveContract createProposalApproveContract( + byte[] owner, long id, boolean is_add_approval) { + Contract.ProposalApproveContract.Builder builder = + Contract.ProposalApproveContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1476,30 +1462,38 @@ public boolean deleteProposal(byte[] owner, long id) return processTransactionExtention(transactionExtention); } - public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + public static Contract.ProposalDeleteContract createProposalDeleteContract( + byte[] owner, long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); return builder.build(); } - public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + public boolean exchangeCreate( + byte[] owner, + byte[] firstTokenId, + long firstTokenBalance, + byte[] secondTokenId, + long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeCreateContract contract = createExchangeCreateContract(owner, firstTokenId, - firstTokenBalance, secondTokenId, secondTokenBalance); + Contract.ExchangeCreateContract contract = + createExchangeCreateContract( + owner, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); TransactionExtention transactionExtention = rpcCli.exchangeCreate(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + public static Contract.ExchangeCreateContract createExchangeCreateContract( + byte[] owner, + byte[] firstTokenId, + long firstTokenBalance, + byte[] secondTokenId, + long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1516,14 +1510,14 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon owner = getAddress(); } - Contract.ExchangeInjectContract contract = createExchangeInjectContract(owner, exchangeId, - tokenId, quant); + Contract.ExchangeInjectContract contract = + createExchangeInjectContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeInject(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + public static Contract.ExchangeInjectContract createExchangeInjectContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1539,16 +1533,16 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l owner = getAddress(); } - Contract.ExchangeWithdrawContract contract = createExchangeWithdrawContract(owner, exchangeId, - tokenId, quant); + Contract.ExchangeWithdrawContract contract = + createExchangeWithdrawContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeWithdraw(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant) { + Contract.ExchangeWithdrawContract.Builder builder = + Contract.ExchangeWithdrawContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1557,23 +1551,23 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b return builder.build(); } - public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + public boolean exchangeTransaction( + byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeTransactionContract contract = createExchangeTransactionContract(owner, - exchangeId, tokenId, quant, expected); + Contract.ExchangeTransactionContract contract = + createExchangeTransactionContract(owner, exchangeId, tokenId, quant, expected); TransactionExtention transactionExtention = rpcCli.exchangeTransaction(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { - Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + public static Contract.ExchangeTransactionContract createExchangeTransactionContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) { + Contract.ExchangeTransactionContract.Builder builder = + Contract.ExchangeTransactionContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1583,7 +1577,6 @@ public static Contract.ExchangeTransactionContract createExchangeTransactionCont return builder.build(); } - public static SmartContract.ABI.Entry.EntryType getEntryType(String type) { switch (type) { case "constructor": @@ -1626,22 +1619,38 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { SmartContract.ABI.Builder abiBuilder = SmartContract.ABI.newBuilder(); for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); - boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; - boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; - String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; - JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; - JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; - String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; - boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; - String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + boolean anonymous = + abiItem.getAsJsonObject().get("anonymous") != null + ? abiItem.getAsJsonObject().get("anonymous").getAsBoolean() + : false; + boolean constant = + abiItem.getAsJsonObject().get("constant") != null + ? abiItem.getAsJsonObject().get("constant").getAsBoolean() + : false; + String name = + abiItem.getAsJsonObject().get("name") != null + ? abiItem.getAsJsonObject().get("name").getAsString() + : null; + JsonArray inputs = + abiItem.getAsJsonObject().get("inputs") != null + ? abiItem.getAsJsonObject().get("inputs").getAsJsonArray() + : null; + JsonArray outputs = + abiItem.getAsJsonObject().get("outputs") != null + ? abiItem.getAsJsonObject().get("outputs").getAsJsonArray() + : null; + String type = + abiItem.getAsJsonObject().get("type") != null + ? abiItem.getAsJsonObject().get("type").getAsString() + : null; + boolean payable = + abiItem.getAsJsonObject().get("payable") != null + ? abiItem.getAsJsonObject().get("payable").getAsBoolean() + : false; + String stateMutability = + abiItem.getAsJsonObject().get("stateMutability") != null + ? abiItem.getAsJsonObject().get("stateMutability").getAsString() + : null; if (type == null) { System.out.println("No type!"); return null; @@ -1662,8 +1671,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (null != inputs) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); - if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + if (inputItem.getAsJsonObject().get("name") == null + || inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1671,11 +1680,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String inputType = inputItem.getAsJsonObject().get("type").getAsString(); Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { - inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + inputIndexed = + Boolean.valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = + SmartContract.ABI.Entry.Param.newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1687,8 +1696,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (outputs != null) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); - if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + if (outputItem.getAsJsonObject().get("name") == null + || outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1696,11 +1705,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String outputType = outputItem.getAsJsonObject().get("type").getAsString(); Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { - outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + outputIndexed = + Boolean.valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = + SmartContract.ABI.Entry.Param.newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1720,8 +1729,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { return abiBuilder.build(); } - public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + public static Contract.UpdateSettingContract createUpdateSettingContract( + byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1731,32 +1740,37 @@ public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] } public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract( - byte[] owner, - byte[] contractAddress, long originEnergyLimit) { + byte[] owner, byte[] contractAddress, long originEnergyLimit) { - Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + Contract.UpdateEnergyLimitContract.Builder builder = + Contract.UpdateEnergyLimitContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); return builder.build(); } - public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + public static Contract.ClearABIContract createClearABIContract( + byte[] owner, byte[] contractAddress) { - Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + Contract.ClearABIContract.Builder builder = Contract.ClearABIContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } - public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + public static CreateSmartContract createContractDeployContract( + String contractName, + byte[] address, + String ABI, + String code, + long value, + long consumeUserResourcePercent, + long originEnergyLimit, + long tokenValue, + String tokenId, + String libraryAddressPair, + String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1767,7 +1781,8 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setName(contractName); builder.setOriginAddress(ByteString.copyFrom(address)); builder.setAbi(abi); - builder.setConsumeUserResourcePercent(consumeUserResourcePercent) + builder + .setConsumeUserResourcePercent(consumeUserResourcePercent) .setOriginEnergyLimit(originEnergyLimit); if (value != 0) { @@ -1783,16 +1798,17 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); - createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + createSmartContractBuilder + .setOwnerAddress(ByteString.copyFrom(address)) + .setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } return createSmartContractBuilder.build(); } - private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + private static byte[] replaceLibraryAddress( + String code, String libraryAddressPair, String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1807,21 +1823,22 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa String addr = cur.substring(lastPosition + 1); String libraryAddressHex; try { - libraryAddressHex = (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), - "US-ASCII")).substring(2); + libraryAddressHex = + (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), "US-ASCII")) + .substring(2); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // now ignore + throw new RuntimeException(e); // now ignore } String beReplaced; if (compilerVersion == null) { - //old version + // old version String repeated = new String(new char[40 - libraryName.length() - 2]).replace("\0", "_"); beReplaced = "__" + libraryName + repeated; } else if (compilerVersion.equalsIgnoreCase("v5")) { - //0.5.4 version - String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + // 0.5.4 version + String libraryNameKeccak256 = + ByteArray.toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1834,9 +1851,13 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa return Hex.decode(code); } - public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + public static Contract.TriggerSmartContract triggerCallContract( + byte[] address, + byte[] contractAddress, + long callValue, + byte[] data, + long tokenValue, + String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1851,7 +1872,12 @@ public static Contract.TriggerSmartContract triggerCallContract(byte[] address, public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { // get tx hash - byte[] txRawDataHash = Sha256Hash.of(trx.getRawData().toByteArray()).getBytes(); + byte[] txRawDataHash; + if (isEckey) { + txRawDataHash = Sha256Hash.of(trx.getRawData().toByteArray()).getBytes(); + } else { + txRawDataHash = SM3Hash.of(trx.getRawData().toByteArray()).getBytes(); + } // combine byte[] combined = new byte[txRawDataHash.length + ownerAddress.length]; @@ -1859,26 +1885,25 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { System.arraycopy(ownerAddress, 0, combined, txRawDataHash.length, ownerAddress.length); return Hash.sha3omit12(combined); - } - public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + public boolean updateSetting( + byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - UpdateSettingContract updateSettingContract = createUpdateSettingContract(owner, - contractAddress, consumeUserResourcePercent); + UpdateSettingContract updateSettingContract = + createUpdateSettingContract(owner, contractAddress, consumeUserResourcePercent); TransactionExtention transactionExtention = rpcCli.updateSetting(updateSettingContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1892,24 +1917,21 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig owner = getAddress(); } - UpdateEnergyLimitContract updateEnergyLimitContract = createUpdateEnergyLimitContract( - owner, - contractAddress, originEnergyLimit); + UpdateEnergyLimitContract updateEnergyLimitContract = + createUpdateEnergyLimitContract(owner, contractAddress, originEnergyLimit); - TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + TransactionExtention transactionExtention = rpcCli.updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } return processTransactionExtention(transactionExtention); - } public boolean clearContractABI(byte[] owner, byte[] contractAddress) @@ -1924,8 +1946,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1933,33 +1955,53 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) return processTransactionExtention(transactionExtention); } - public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + public boolean deployContract( + byte[] owner, + String contractName, + String ABI, + String code, + long feeLimit, + long value, + long consumeUserResourcePercent, + long originEnergyLimit, + long tokenValue, + String tokenId, + String libraryAddressPair, + String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - CreateSmartContract contractDeployContract = createContractDeployContract(contractName, owner, - ABI, code, value, consumeUserResourcePercent, originEnergyLimit, tokenValue, tokenId, - libraryAddressPair, compilerVersion); + CreateSmartContract contractDeployContract = + createContractDeployContract( + contractName, + owner, + ABI, + code, + value, + consumeUserResourcePercent, + originEnergyLimit, + tokenValue, + tokenId, + libraryAddressPair, + compilerVersion); TransactionExtention transactionExtention = rpcCli.deployContract(contractDeployContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + Transaction.raw.Builder rawBuilder = + transactionExtention.getTransaction().getRawData().toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1975,23 +2017,28 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str texBuilder.setTxid(transactionExtention.getTxid()); transactionExtention = texBuilder.build(); -// byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); -// System.out.println( -// "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); + // byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); + // System.out.println( + // "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); return processTransactionExtention(transactionExtention); - } - public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + public boolean triggerContract( + byte[] owner, + byte[] contractAddress, + long callValue, + byte[] data, + long feeLimit, + long tokenValue, + String tokenId, + boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, - callValue, data, tokenValue, tokenId); + Contract.TriggerSmartContract triggerContract = + triggerCallContract(owner, contractAddress, callValue, data, tokenValue, tokenId); TransactionExtention transactionExtention; if (isConstant) { transactionExtention = rpcCli.triggerConstantContract(triggerContract); @@ -2002,28 +2049,28 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create call trx failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); return false; } Transaction transaction = transactionExtention.getTransaction(); // for constant - if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + if (transaction.getRetCount() != 0 + && transactionExtention.getConstantResult(0) != null + && transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); - System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + System.out.println( + ":" + ByteArray.toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + Transaction.raw.Builder rawBuilder = + transactionExtention.getTransaction().getRawData().toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2046,11 +2093,10 @@ public static SmartContract getContract(byte[] address) { return rpcCli.getContract(address); } - public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { - Contract.AccountPermissionUpdateContract contract = createAccountPermissionContract(owner, - permissionJson); + Contract.AccountPermissionUpdateContract contract = + createAccountPermissionContract(owner, permissionJson); TransactionExtention transactionExtention = rpcCli.accountPermissionUpdate(contract); return processTransactionExtention(transactionExtention); } @@ -2124,7 +2170,6 @@ public Contract.AccountPermissionUpdateContract createAccountPermissionContract( return builder.build(); } - public Transaction addTransactionSign(Transaction transaction) throws CipherException, IOException, CancelException { if (transaction.getRawData().getTimestamp() == 0) { @@ -2151,8 +2196,7 @@ public Transaction addTransactionSign(Transaction transaction) } public static Optional GetMerkleTreeVoucherInfo( - OutputPointInfo info, - boolean showErrorMsg) { + OutputPointInfo info, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.GetMerkleTreeVoucherInfo(info)); @@ -2168,8 +2212,8 @@ public static Optional GetMerkleTreeVoucherInfo( return Optional.empty(); } - public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + public static Optional scanNoteByIvk( + IvkDecryptParameters ivkDecryptParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2185,8 +2229,8 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry return Optional.empty(); } - public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + public static Optional scanNoteByOvk( + OvkDecryptParameters ovkDecryptParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2270,8 +2314,8 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall return processShieldedTransaction(transactionExtention, wallet); } - public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + public static boolean sendShieldedCoinWithoutAsk( + PrivateParametersWithoutAsk privateParameters, byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2288,7 +2332,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2296,8 +2340,8 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Any any = transaction.getRawData().getContract(0).getParameter(); ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); - ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + ShieldedTransferContract.Builder contractBuild = + shieldContract.toBuilder().clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2312,11 +2356,16 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri contractBuild.addSpendDescription(spendDescription.build()); } - Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + Transaction.raw.Builder rawBuilder = + transaction + .toBuilder() + .getRawDataBuilder() + .clearContract() + .addContract( + Transaction.Contract.newBuilder() + .setType(ContractType.ShieldedTransferContract) + .setParameter(Any.pack(contractBuild.build())) + .build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2325,8 +2374,8 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri return processShieldedTransaction(transactionExtention, wallet); } - public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + public static Optional isNoteSpend( + NoteParameters noteParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2392,14 +2441,14 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); - TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + TransactionExtention transactionExtention = + rpcCli.updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -2414,5 +2463,4 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } - } From 40bbbb1db7772f0e4e4593ddf532395156304bc5 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 7 Jan 2020 19:26:35 +0800 Subject: [PATCH 250/445] utils add method --- .../java/org/tron/common/utils/Utils.java | 555 ++++++++++-------- 1 file changed, 309 insertions(+), 246 deletions(-) diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index b38fcaa46..d81861ad4 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -90,9 +90,7 @@ private char[] getChars(byte[] bytes) { return cb.array(); } - /** - * yyyy-MM-dd - */ + /** yyyy-MM-dd */ public static Date strToDateLong(String strDate) { if (strDate.length() == 10) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); @@ -133,18 +131,22 @@ public static String printTransaction(TransactionExtention transactionExtention) public static String printTransactionList(TransactionList transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionToJSON(transaction, true)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionToJSON(transaction, true)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } public static String printTransactionList(TransactionListExtention transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionExtentionToJSON(transaction)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionExtentionToJSON(transaction)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -161,18 +163,22 @@ public static String printBlockExtention(BlockExtention blockExtention) { public static String printBlockList(BlockList blockList) { JSONArray jsonArray = new JSONArray(); List blocks = blockList.getBlockList(); - blocks.stream().forEach(block -> { - jsonArray.add(printBlockToJSON(block)); - }); + blocks.stream() + .forEach( + block -> { + jsonArray.add(printBlockToJSON(block)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } public static String printBlockList(BlockListExtention blockList) { JSONArray jsonArray = new JSONArray(); List blocks = blockList.getBlockList(); - blocks.stream().forEach(block -> { - jsonArray.add(printBlockExtentionToJSON(block)); - }); + blocks.stream() + .forEach( + block -> { + jsonArray.add(printBlockExtentionToJSON(block)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -180,9 +186,9 @@ public static String printTransactionSignWeight(TransactionSignWeight transactio String string = JsonFormat.printToString(transactionSignWeight, true); JSONObject jsonObject = JSONObject.parseObject(string); JSONObject jsonObjectExt = jsonObject.getJSONObject(TRANSACTION); - jsonObjectExt.put(TRANSACTION, - printTransactionToJSON(transactionSignWeight.getTransaction().getTransaction(), - true)); + jsonObjectExt.put( + TRANSACTION, + printTransactionToJSON(transactionSignWeight.getTransaction().getTransaction(), true)); jsonObject.put(TRANSACTION, jsonObjectExt); return JsonFormatUtil.formatJson(jsonObject.toJSONString()); } @@ -192,9 +198,9 @@ public static String printTransactionApprovedList( String string = JsonFormat.printToString(transactionApprovedList, true); JSONObject jsonObject = JSONObject.parseObject(string); JSONObject jsonObjectExt = jsonObject.getJSONObject(TRANSACTION); - jsonObjectExt.put(TRANSACTION, - printTransactionToJSON(transactionApprovedList.getTransaction().getTransaction(), - true)); + jsonObjectExt.put( + TRANSACTION, + printTransactionToJSON(transactionApprovedList.getTransaction().getTransaction(), true)); jsonObject.put(TRANSACTION, jsonObjectExt); return JsonFormatUtil.formatJson(jsonObject.toJSONString()); } @@ -266,12 +272,14 @@ public static byte[] generateContractAddress(Transaction trx, byte[] ownerAddres public static JSONObject printBlockExtentionToJSON(BlockExtention blockExtention) { JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(blockExtention, true)); - if (blockExtention.getTransactionsCount() > 0 ) { + if (blockExtention.getTransactionsCount() > 0) { JSONArray jsonArray = new JSONArray(); List transactions = blockExtention.getTransactionsList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionExtentionToJSON(transaction)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionExtentionToJSON(transaction)); + }); jsonObject.put(TRANSACTION, jsonArray); } return jsonObject; @@ -282,9 +290,11 @@ public static JSONObject printBlockToJSON(Block block) { if (block.getTransactionsCount() > 0) { JSONArray jsonArray = new JSONArray(); List transactions = block.getTransactionsList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionToJSON(transaction, true)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionToJSON(transaction, true)); + }); jsonObject.put(TRANSACTION, jsonArray); } return jsonObject; @@ -292,230 +302,265 @@ public static JSONObject printBlockToJSON(Block block) { public static JSONObject printTransactionExtentionToJSON( TransactionExtention transactionExtention) { - JSONObject jsonObject = JSONObject - .parseObject(JsonFormat.printToString(transactionExtention, true)); + JSONObject jsonObject = + JSONObject.parseObject(JsonFormat.printToString(transactionExtention, true)); if (transactionExtention.getResult().getResult()) { - JSONObject transactionOjbect = printTransactionToJSON(transactionExtention.getTransaction(), - true); + JSONObject transactionOjbect = + printTransactionToJSON(transactionExtention.getTransaction(), true); jsonObject.put(TRANSACTION, transactionOjbect); } return jsonObject; } public static JSONObject printTransactionToJSON(Transaction transaction, boolean selfType) { - JSONObject jsonTransaction = JSONObject.parseObject(JsonFormat.printToString(transaction, - selfType)); + JSONObject jsonTransaction = + JSONObject.parseObject(JsonFormat.printToString(transaction, selfType)); JSONArray contracts = new JSONArray(); - transaction.getRawData().getContractList().stream().forEach(contract -> { - try { - JSONObject contractJson = null; - Any contractParameter = contract.getParameter(); - switch (contract.getType()) { - case AccountCreateContract: - AccountCreateContract accountCreateContract = contractParameter - .unpack(AccountCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(accountCreateContract, - selfType)); - break; - case TransferContract: - TransferContract transferContract = contractParameter.unpack(TransferContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(transferContract, - selfType)); - break; - case TransferAssetContract: - TransferAssetContract transferAssetContract = contractParameter - .unpack(TransferAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(transferAssetContract, - selfType)); - break; - case VoteAssetContract: - VoteAssetContract voteAssetContract = contractParameter.unpack(VoteAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(voteAssetContract, - selfType)); - break; - case VoteWitnessContract: - VoteWitnessContract voteWitnessContract = contractParameter - .unpack(VoteWitnessContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(voteWitnessContract, - selfType)); - break; - case WitnessCreateContract: - WitnessCreateContract witnessCreateContract = contractParameter - .unpack(WitnessCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(witnessCreateContract, - selfType)); - break; - case AssetIssueContract: - AssetIssueContract assetIssueContract = contractParameter - .unpack(AssetIssueContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(assetIssueContract, - selfType)); - break; - case WitnessUpdateContract: - WitnessUpdateContract witnessUpdateContract = contractParameter - .unpack(WitnessUpdateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(witnessUpdateContract, - selfType)); - break; - case ParticipateAssetIssueContract: - ParticipateAssetIssueContract participateAssetIssueContract = contractParameter - .unpack(ParticipateAssetIssueContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(participateAssetIssueContract, selfType)); - break; - case AccountUpdateContract: - AccountUpdateContract accountUpdateContract = contractParameter - .unpack(AccountUpdateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(accountUpdateContract, - selfType)); - break; - case FreezeBalanceContract: - FreezeBalanceContract freezeBalanceContract = contractParameter - .unpack(FreezeBalanceContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(freezeBalanceContract, - selfType)); - break; - case UnfreezeBalanceContract: - UnfreezeBalanceContract unfreezeBalanceContract = contractParameter - .unpack(UnfreezeBalanceContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(unfreezeBalanceContract, selfType)); - break; - case WithdrawBalanceContract: - WithdrawBalanceContract withdrawBalanceContract = contractParameter - .unpack(WithdrawBalanceContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(withdrawBalanceContract, selfType)); - break; - case UnfreezeAssetContract: - UnfreezeAssetContract unfreezeAssetContract = contractParameter - .unpack(UnfreezeAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(unfreezeAssetContract, - selfType)); - break; - case UpdateAssetContract: - UpdateAssetContract updateAssetContract = contractParameter - .unpack(UpdateAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(updateAssetContract, - selfType)); - break; - case ProposalCreateContract: - ProposalCreateContract proposalCreateContract = contractParameter - .unpack(ProposalCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(proposalCreateContract, - selfType)); - break; - case ProposalApproveContract: - ProposalApproveContract proposalApproveContract = contractParameter - .unpack(ProposalApproveContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(proposalApproveContract, selfType)); - break; - case ProposalDeleteContract: - ProposalDeleteContract proposalDeleteContract = contractParameter - .unpack(ProposalDeleteContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(proposalDeleteContract, - selfType)); - break; - case SetAccountIdContract: - org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = - contractParameter.unpack(org.tron.protos.Contract.SetAccountIdContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(setAccountIdContract, - selfType)); - break; - case CreateSmartContract: - CreateSmartContract deployContract = contractParameter - .unpack(CreateSmartContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(deployContract, - selfType)); - byte[] ownerAddress = deployContract.getOwnerAddress().toByteArray(); - byte[] contractAddress = generateContractAddress(transaction, ownerAddress); - jsonTransaction.put("contract_address", ByteArray.toHexString(contractAddress)); - break; - case TriggerSmartContract: - TriggerSmartContract triggerSmartContract = contractParameter - .unpack(TriggerSmartContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(triggerSmartContract, - selfType)); - break; - case UpdateSettingContract: - UpdateSettingContract updateSettingContract = contractParameter - .unpack(UpdateSettingContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(updateSettingContract, - selfType)); - break; - case ExchangeCreateContract: - ExchangeCreateContract exchangeCreateContract = contractParameter - .unpack(ExchangeCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(exchangeCreateContract, - selfType)); - break; - case ExchangeInjectContract: - ExchangeInjectContract exchangeInjectContract = contractParameter - .unpack(ExchangeInjectContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(exchangeInjectContract, - selfType)); - break; - case ExchangeWithdrawContract: - ExchangeWithdrawContract exchangeWithdrawContract = contractParameter - .unpack(ExchangeWithdrawContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(exchangeWithdrawContract, selfType)); - break; - case ExchangeTransactionContract: - ExchangeTransactionContract exchangeTransactionContract = contractParameter - .unpack(ExchangeTransactionContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(exchangeTransactionContract, selfType)); - break; - case UpdateEnergyLimitContract: - UpdateEnergyLimitContract updateEnergyLimitContract = contractParameter - .unpack(UpdateEnergyLimitContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(updateEnergyLimitContract, selfType)); - break; - case AccountPermissionUpdateContract: - AccountPermissionUpdateContract accountPermissionUpdateContract = contractParameter - .unpack(AccountPermissionUpdateContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(accountPermissionUpdateContract, selfType)); - break; - case ClearABIContract: - org.tron.protos.Contract.ClearABIContract clearABIContract = contractParameter - .unpack(org.tron.protos.Contract.ClearABIContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(clearABIContract, selfType)); - break; - case ShieldedTransferContract: - ShieldedTransferContract shieldedTransferContract = contractParameter - .unpack(ShieldedTransferContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(shieldedTransferContract, selfType)); - break; - case UpdateBrokerageContract: - UpdateBrokerageContract updateBrokerageContract = contract.getParameter() - .unpack(UpdateBrokerageContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(updateBrokerageContract, selfType)); - break; - // todo add other contract - default: - } - JSONObject parameter = new JSONObject(); - parameter.put(VALUE, contractJson); - parameter.put("type_url", contract.getParameterOrBuilder().getTypeUrl()); - JSONObject jsonContract = new JSONObject(); - jsonContract.put("parameter", parameter); - jsonContract.put("type", contract.getType()); - if (contract.getPermissionId() > 0) { - jsonContract.put(PERMISSION_ID, contract.getPermissionId()); - } - contracts.add(jsonContract); - } catch (InvalidProtocolBufferException e) { - e.printStackTrace(); - //System.out.println("InvalidProtocolBufferException: {}", e.getMessage()); - } - }); + transaction.getRawData().getContractList().stream() + .forEach( + contract -> { + try { + JSONObject contractJson = null; + Any contractParameter = contract.getParameter(); + switch (contract.getType()) { + case AccountCreateContract: + AccountCreateContract accountCreateContract = + contractParameter.unpack(AccountCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(accountCreateContract, selfType)); + break; + case TransferContract: + TransferContract transferContract = + contractParameter.unpack(TransferContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(transferContract, selfType)); + break; + case TransferAssetContract: + TransferAssetContract transferAssetContract = + contractParameter.unpack(TransferAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(transferAssetContract, selfType)); + break; + case VoteAssetContract: + VoteAssetContract voteAssetContract = + contractParameter.unpack(VoteAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(voteAssetContract, selfType)); + break; + case VoteWitnessContract: + VoteWitnessContract voteWitnessContract = + contractParameter.unpack(VoteWitnessContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(voteWitnessContract, selfType)); + break; + case WitnessCreateContract: + WitnessCreateContract witnessCreateContract = + contractParameter.unpack(WitnessCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(witnessCreateContract, selfType)); + break; + case AssetIssueContract: + AssetIssueContract assetIssueContract = + contractParameter.unpack(AssetIssueContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(assetIssueContract, selfType)); + break; + case WitnessUpdateContract: + WitnessUpdateContract witnessUpdateContract = + contractParameter.unpack(WitnessUpdateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(witnessUpdateContract, selfType)); + break; + case ParticipateAssetIssueContract: + ParticipateAssetIssueContract participateAssetIssueContract = + contractParameter.unpack(ParticipateAssetIssueContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(participateAssetIssueContract, selfType)); + break; + case AccountUpdateContract: + AccountUpdateContract accountUpdateContract = + contractParameter.unpack(AccountUpdateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(accountUpdateContract, selfType)); + break; + case FreezeBalanceContract: + FreezeBalanceContract freezeBalanceContract = + contractParameter.unpack(FreezeBalanceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(freezeBalanceContract, selfType)); + break; + case UnfreezeBalanceContract: + UnfreezeBalanceContract unfreezeBalanceContract = + contractParameter.unpack(UnfreezeBalanceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(unfreezeBalanceContract, selfType)); + break; + case WithdrawBalanceContract: + WithdrawBalanceContract withdrawBalanceContract = + contractParameter.unpack(WithdrawBalanceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(withdrawBalanceContract, selfType)); + break; + case UnfreezeAssetContract: + UnfreezeAssetContract unfreezeAssetContract = + contractParameter.unpack(UnfreezeAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(unfreezeAssetContract, selfType)); + break; + case UpdateAssetContract: + UpdateAssetContract updateAssetContract = + contractParameter.unpack(UpdateAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateAssetContract, selfType)); + break; + case ProposalCreateContract: + ProposalCreateContract proposalCreateContract = + contractParameter.unpack(ProposalCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(proposalCreateContract, selfType)); + break; + case ProposalApproveContract: + ProposalApproveContract proposalApproveContract = + contractParameter.unpack(ProposalApproveContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(proposalApproveContract, selfType)); + break; + case ProposalDeleteContract: + ProposalDeleteContract proposalDeleteContract = + contractParameter.unpack(ProposalDeleteContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(proposalDeleteContract, selfType)); + break; + case SetAccountIdContract: + org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = + contractParameter.unpack( + org.tron.protos.Contract.SetAccountIdContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(setAccountIdContract, selfType)); + break; + case CreateSmartContract: + CreateSmartContract deployContract = + contractParameter.unpack(CreateSmartContract.class); + contractJson = + JSONObject.parseObject(JsonFormat.printToString(deployContract, selfType)); + byte[] ownerAddress = deployContract.getOwnerAddress().toByteArray(); + byte[] contractAddress = generateContractAddress(transaction, ownerAddress); + jsonTransaction.put("contract_address", ByteArray.toHexString(contractAddress)); + break; + case TriggerSmartContract: + TriggerSmartContract triggerSmartContract = + contractParameter.unpack(TriggerSmartContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(triggerSmartContract, selfType)); + break; + case UpdateSettingContract: + UpdateSettingContract updateSettingContract = + contractParameter.unpack(UpdateSettingContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateSettingContract, selfType)); + break; + case ExchangeCreateContract: + ExchangeCreateContract exchangeCreateContract = + contractParameter.unpack(ExchangeCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeCreateContract, selfType)); + break; + case ExchangeInjectContract: + ExchangeInjectContract exchangeInjectContract = + contractParameter.unpack(ExchangeInjectContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeInjectContract, selfType)); + break; + case ExchangeWithdrawContract: + ExchangeWithdrawContract exchangeWithdrawContract = + contractParameter.unpack(ExchangeWithdrawContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeWithdrawContract, selfType)); + break; + case ExchangeTransactionContract: + ExchangeTransactionContract exchangeTransactionContract = + contractParameter.unpack(ExchangeTransactionContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeTransactionContract, selfType)); + break; + case UpdateEnergyLimitContract: + UpdateEnergyLimitContract updateEnergyLimitContract = + contractParameter.unpack(UpdateEnergyLimitContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateEnergyLimitContract, selfType)); + break; + case AccountPermissionUpdateContract: + AccountPermissionUpdateContract accountPermissionUpdateContract = + contractParameter.unpack(AccountPermissionUpdateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(accountPermissionUpdateContract, selfType)); + break; + case ClearABIContract: + org.tron.protos.Contract.ClearABIContract clearABIContract = + contractParameter.unpack(org.tron.protos.Contract.ClearABIContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(clearABIContract, selfType)); + break; + case ShieldedTransferContract: + ShieldedTransferContract shieldedTransferContract = + contractParameter.unpack(ShieldedTransferContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(shieldedTransferContract, selfType)); + break; + case UpdateBrokerageContract: + UpdateBrokerageContract updateBrokerageContract = + contract.getParameter().unpack(UpdateBrokerageContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateBrokerageContract, selfType)); + break; + // todo add other contract + default: + } + JSONObject parameter = new JSONObject(); + parameter.put(VALUE, contractJson); + parameter.put("type_url", contract.getParameterOrBuilder().getTypeUrl()); + JSONObject jsonContract = new JSONObject(); + jsonContract.put("parameter", parameter); + jsonContract.put("type", contract.getType()); + if (contract.getPermissionId() > 0) { + jsonContract.put(PERMISSION_ID, contract.getPermissionId()); + } + contracts.add(jsonContract); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + // System.out.println("InvalidProtocolBufferException: {}", e.getMessage()); + } + }); JSONObject rawData = JSONObject.parseObject(jsonTransaction.get("raw_data").toString()); rawData.put("contract", contracts); @@ -534,7 +579,7 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean public static boolean confirmEncrption() { System.out.println( - "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); + "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); Scanner in = new Scanner(System.in); String input = in.nextLine().trim(); String str = input.split("\\s+")[0]; @@ -543,5 +588,23 @@ public static boolean confirmEncrption() { } return false; } -} + public static boolean isNumericString(String str) { + for (int i = str.length(); --i >= 0; ) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + + public static boolean isHexString(String str) { + boolean bRet = false; + try { + ByteArray.fromHexString(str); + bRet = true; + } catch (Exception e) { + } + return bRet; + } +} From 8ced0a3bac7e716ff1d8552ca25919898abe7a33 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 7 Jan 2020 20:19:16 +0800 Subject: [PATCH 251/445] modify code after review --- .../java/org/tron/common/crypto/sm2/SM3.java | 22 ------------------- src/main/resources/config-back.conf | 20 ----------------- 2 files changed, 42 deletions(-) delete mode 100644 src/main/java/org/tron/common/crypto/sm2/SM3.java delete mode 100644 src/main/resources/config-back.conf diff --git a/src/main/java/org/tron/common/crypto/sm2/SM3.java b/src/main/java/org/tron/common/crypto/sm2/SM3.java deleted file mode 100644 index 6a72f85f2..000000000 --- a/src/main/java/org/tron/common/crypto/sm2/SM3.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.tron.common.crypto.sm2; - -import org.spongycastle.crypto.digests.SM3Digest; -import org.spongycastle.util.encoders.Hex; - -public class SM3 { - public static byte[] hash(String message) { - byte[] msg = Hex.decode(message); - return hash(msg); - } - - public static byte[] hash(byte[] message) { - SM3Digest digest = new SM3Digest(); - digest.update(message,0,message.length); - - byte[] eHash = new byte[digest.getDigestSize()]; - - digest.doFinal(eHash, 0); - - return eHash; - } -} diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf deleted file mode 100644 index 171520768..000000000 --- a/src/main/resources/config-back.conf +++ /dev/null @@ -1,20 +0,0 @@ -net { - type = mainnet -} - -fullnode = { - ip.list = [ - "47.89.189.124:50055", - "47.89.178.193:50055" - ] -} - -#soliditynode = { -# ip.list = [ -# "127.0.0.1:50052" -# ] -#} -crypto { - engine=sm2 -} -RPC_version = 2 From cbd8b0af5791c544c0d1c5afc3e59d11f29c8836 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 7 Jan 2020 20:27:22 +0800 Subject: [PATCH 252/445] delete import sm3 --- .../java/org/tron/common/crypto/SM3Hash.java | 498 +++++++++--------- .../tron/common/utils/TransactionUtils.java | 3 +- .../tron/demo/TransactionSignDemoForSM2.java | 3 +- 3 files changed, 244 insertions(+), 260 deletions(-) diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index 91aeb34ee..3f11900dd 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -33,268 +33,254 @@ import static com.google.common.base.Preconditions.checkArgument; - /** - * A SM3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be - * used as keys in a map. It also checks that the length is correct and provides a bit more type - * safety. + * A SM3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be used + * as keys in a map. It also checks that the length is correct and provides a bit more type safety. */ public class SM3Hash implements Serializable, Comparable, HashInterface { - public static final int LENGTH = 32; // bytes - public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); - - private final byte[] bytes; - - public SM3Hash(long num, byte[] hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; + public static final int LENGTH = 32; // bytes + public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); + + private final byte[] bytes; + + public SM3Hash(long num, byte[] hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + public SM3Hash(long num, SM3Hash hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** Use {@link #wrap(byte[])} instead. */ + @Deprecated + public SM3Hash(byte[] rawHashBytes) { + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Creates a new instance that wraps the given hash value. + * + * @param rawHashBytes the raw hash bytes to wrap + * @return a new instance + * @throws IllegalArgumentException if the given array length is not exactly 32 + */ + @SuppressWarnings("deprecation") // the constructor will be made private in the future + public static SM3Hash wrap(byte[] rawHashBytes) { + return new SM3Hash(rawHashBytes); + } + + public static SM3Hash wrap(ByteString rawHashByteString) { + return wrap(rawHashByteString.toByteArray()); + } + + /** Use {@link #of(byte[])} instead: this old name is ambiguous. */ + @Deprecated + public static SM3Hash create(byte[] contents) { + return of(contents); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + */ + public static SM3Hash of(byte[] contents) { + return wrap(hash(contents)); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given file's contents. + * The file contents are read fully into memory, so this method should only be used with small + * files. + * + * @param file the file on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + * @throws IOException if an error occurs while reading the file + */ + public static SM3Hash of(File file) throws IOException { + + try (FileInputStream in = new FileInputStream(file)) { + return of(ByteStreams.toByteArray(in)); } - - public SM3Hash(long num, SM3Hash hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; + } + + /** Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. */ + @Deprecated + public static SM3Hash createDouble(byte[] contents) { + return twiceOf(contents); + } + + /** + * Creates a new instance containing the hash of the calculated hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (two-time) hash + */ + public static SM3Hash twiceOf(byte[] contents) { + return wrap(hashTwice(contents)); + } + + /** + * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the checked + * exception that can never occur with a RuntimeException. + * + * @return a new SM3 MessageDigest instance + */ + public static SM3Digest newDigest() { + return new SM3Digest(); + } + + /** + * Calculates the SM3 hash of the given bytes. + * + * @param input the bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input) { + return hash(input, 0, input.length); + } + + /** + * Calculates the SM3 hash of the given byte range. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input, int offset, int length) { + SM3Digest digest = newDigest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * Calculates the SM3 hash of the given bytes, and then hashes the resulting hash again. + * + * @param input the bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input) { + return hashTwice(input, 0, input.length); + } + + /** + * Calculates the SM3 hash of the given byte range, and then hashes the resulting hash again. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input, int offset, int length) { + SM3Digest digest = newDigest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.reset(); + digest.update(eHash, 0, eHash.length); + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the + * two ranges and then passing the result to {@link #hashTwice(byte[])}. + */ + public static byte[] hashTwice( + byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) { + SM3Digest digest = newDigest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + return eHash; + } + + private byte[] generateBlockId(long blockNum, SM3Hash blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.getBytes().length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); + return hash; + } + + private byte[] generateBlockId(long blockNum, byte[] blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - /** - * Use {@link #wrap(byte[])} instead. - */ - @Deprecated - public SM3Hash(byte[] rawHashBytes) { - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; + if (o == null || !(o instanceof SM3Hash)) { + return false; } - - /** - * Creates a new instance that wraps the given hash value. - * - * @param rawHashBytes the raw hash bytes to wrap - * @return a new instance - * @throws IllegalArgumentException if the given array length is not exactly 32 - */ - @SuppressWarnings("deprecation") // the constructor will be made private in the future - public static SM3Hash wrap(byte[] rawHashBytes) { - return new SM3Hash(rawHashBytes); - } - - public static SM3Hash wrap(ByteString rawHashByteString) { - return wrap(rawHashByteString.toByteArray()); - } - - /** - * Use {@link #of(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static SM3Hash create(byte[] contents) { - return of(contents); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - */ - public static SM3Hash of(byte[] contents) { - return wrap(hash(contents)); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given file's contents. - * The file contents are read fully into memory, so this method should only be used with small - * files. - * - * @param file the file on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - * @throws IOException if an error occurs while reading the file - */ - public static SM3Hash of(File file) throws IOException { - - try (FileInputStream in = new FileInputStream(file)) { - return of(ByteStreams.toByteArray(in)); - } - } - - /** - * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static SM3Hash createDouble(byte[] contents) { - return twiceOf(contents); - } - - /** - * Creates a new instance containing the hash of the calculated hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (two-time) hash - */ - public static SM3Hash twiceOf(byte[] contents) { - return wrap(hashTwice(contents)); - } - - /** - * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the - * checked exception that can never occur with a RuntimeException. - * - * @return a new SM3 MessageDigest instance - */ - public static SM3Digest newDigest() { - return new SM3Digest(); - } - - /** - * Calculates the SM3 hash of the given bytes. - * - * @param input the bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input) { - return hash(input, 0, input.length); - } - - /** - * Calculates the SM3 hash of the given byte range. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input, int offset, int length) { - SM3Digest digest = newDigest(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - - digest.doFinal(eHash, 0); - return eHash; - } - - /** - * Calculates the SM3 hash of the given bytes, and then hashes the resulting hash again. - * - * @param input the bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input) { - return hashTwice(input, 0, input.length); - } - - /** - * Calculates the SM3 hash of the given byte range, and then hashes the resulting hash again. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input, int offset, int length) { - SM3Digest digest = newDigest(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - digest.reset(); - digest.update(eHash,0,eHash.length); - digest.doFinal(eHash,0); - return eHash; - } - - /** - * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the - * two ranges and then passing the result to {@link #hashTwice(byte[])}. - */ - public static byte[] hashTwice(byte[] input1, int offset1, int length1, - byte[] input2, int offset2, int length2) { - SM3Digest digest = newDigest(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - digest.doFinal(eHash,0); - return eHash; - } - - private byte[] generateBlockId(long blockNum, SM3Hash blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.getBytes().length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); - return hash; - } - - private byte[] generateBlockId(long blockNum, byte[] blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); - return hash; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !(o instanceof SM3Hash)) { - return false; - } - return Arrays.equals(bytes, ((SM3Hash) o).bytes); - } - - @Override - public String toString() { - return ByteArray.toHexString(bytes); - } - - /** - * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable - * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. - * the value as a big integer lower than the target value). - */ - @Override - public int hashCode() { - // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints - .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); - } - - /** - * Returns the bytes interpreted as a positive integer. - */ - public BigInteger toBigInteger() { - return new BigInteger(1, bytes); - } - - /** - * Returns the internal byte array, without defensively copying. Therefore do NOT modify the - * returned array. - */ - public byte[] getBytes() { - return bytes; - } - - /** - * For pb return ByteString. - */ - public ByteString getByteString() { - return ByteString.copyFrom(bytes); - } - - @Override - public int compareTo(final SM3Hash other) { - for (int i = LENGTH - 1; i >= 0; i--) { - final int thisByte = this.bytes[i] & 0xff; - final int otherByte = other.bytes[i] & 0xff; - if (thisByte > otherByte) { - return 1; - } - if (thisByte < otherByte) { - return -1; - } - } - return 0; + return Arrays.equals(bytes, ((SM3Hash) o).bytes); + } + + @Override + public String toString() { + return ByteArray.toHexString(bytes); + } + + /** + * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable + * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. + * the value as a big integer lower than the target value). + */ + @Override + public int hashCode() { + // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. + return Ints.fromBytes( + bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + } + + /** Returns the bytes interpreted as a positive integer. */ + public BigInteger toBigInteger() { + return new BigInteger(1, bytes); + } + + /** + * Returns the internal byte array, without defensively copying. Therefore do NOT modify the + * returned array. + */ + public byte[] getBytes() { + return bytes; + } + + /** For pb return ByteString. */ + public ByteString getByteString() { + return ByteString.copyFrom(bytes); + } + + @Override + public int compareTo(final SM3Hash other) { + for (int i = LENGTH - 1; i >= 0; i--) { + final int thisByte = this.bytes[i] & 0xff; + final int otherByte = other.bytes[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } } + return 0; + } } - diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index 04dbe3d10..0677fe748 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -19,7 +19,6 @@ import com.typesafe.config.Config; import org.tron.common.crypto.*; import org.tron.common.crypto.ECKey.ECDSASignature; -import org.tron.common.crypto.sm2.SM3; import org.tron.core.config.Configuration; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -237,7 +236,7 @@ public static Transaction sign(Transaction transaction, SignInterface myKey) { if (isEckey) { hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); } else { - hash = SM3.hash(transaction.getRawData().toByteArray()); + hash = SM3Hash.hash(transaction.getRawData().toByteArray()); } SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index f3467329f..5a42e86d5 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -5,7 +5,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.tron.common.crypto.SM3Hash; import org.tron.common.crypto.sm2.SM2; -import org.tron.common.crypto.sm2.SM3; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; import org.tron.protos.Contract; @@ -34,7 +33,7 @@ public static SM3Hash getBlockHash(Block block) { } public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(SM3.hash(transaction.getRawData().toByteArray())); + String txid = ByteArray.toHexString(SM3Hash.hash(transaction.getRawData().toByteArray())); return txid; } From e71b57b80fa6987637daed56a886fc117c77ee98 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 8 Jan 2020 16:22:08 +0800 Subject: [PATCH 253/445] Simplified market query interface --- src/main/java/org/tron/walletserver/GrpcClient.java | 2 +- src/main/protos/api/api.proto | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index e16425419..1e863ef4f 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -913,7 +913,7 @@ public TransactionExtention marketCancelOrder(Contract.MarketCancelOrderContract public Optional getMarketOrderByAccount(byte[] address) { ByteString addressBS = ByteString.copyFrom(address); - Account request = Account.newBuilder().setAddress(addressBS).build(); + BytesMessage request = BytesMessage.newBuilder().setValue(addressBS).build(); MarketOrderList marketOrderList = blockingStubFull.getMarketOrderByAccount(request); return Optional.ofNullable(marketOrderList); diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 9fc25d970..934385293 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -701,7 +701,7 @@ service Wallet { rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { } - rpc GetMarketOrderByAccount (Account) returns (MarketOrderList) { + rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { } rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { @@ -873,7 +873,7 @@ service WalletSolidity { rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { } - rpc GetMarketOrderByAccount (Account) returns (MarketOrderList) { + rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { } rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { From e26bec89d83d88515e5e4cafaf6c2f6c5d713385 Mon Sep 17 00:00:00 2001 From: zhenping Date: Sat, 11 Jan 2020 13:31:11 +0800 Subject: [PATCH 254/445] add the SM3Hash to the Sha256Hash class Signed-off-by: zhenping --- .../java/org/tron/common/crypto/SM3Hash.java | 1 - .../org/tron/common/crypto/Sha256Hash.java | 543 ++++++++++-------- 2 files changed, 288 insertions(+), 256 deletions(-) diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index 91aeb34ee..505d5c102 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -211,7 +211,6 @@ public static byte[] hashTwice(byte[] input1, int offset1, int length1, digest.update(input1, offset1, length1); digest.update(input2, offset2, length2); byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); digest.doFinal(eHash,0); return eHash; } diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java index a41b382e8..66f40fc5b 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Hash.java @@ -17,10 +17,13 @@ * limitations under the License. */ +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; +import org.spongycastle.crypto.digests.SM3Digest; import org.tron.common.utils.ByteArray; import java.io.File; @@ -32,270 +35,300 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import static com.google.common.base.Preconditions.checkArgument; - /** * A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be * used as keys in a map. It also checks that the length is correct and provides a bit more type * safety. */ -public class Sha256Hash implements Serializable, Comparable { - - - public static final int LENGTH = 32; // bytes - public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); - - private final byte[] bytes; - - private long blockNum; - - - private byte[] generateBlockId(long blockNum, Sha256Hash blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = blockHash.getBytes(); - System.arraycopy(numBytes, 0, hash, 0, 8); - return hash; - } - - private byte[] generateBlockId(long blockNum, byte[] blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = blockHash; - System.arraycopy(numBytes, 0, hash, 0, 8); - return hash; - } - - public long getBlockNum(){ - return blockNum; - } - - public Sha256Hash(long num, byte[] hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - this.blockNum = num; - } - - public Sha256Hash(long num, Sha256Hash hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - this.blockNum = num; - } - - /** - * Use {@link #wrap(byte[])} instead. - */ - @Deprecated - public Sha256Hash(byte[] rawHashBytes) { - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** - * Creates a new instance that wraps the given hash value. - * - * @param rawHashBytes the raw hash bytes to wrap - * @return a new instance - * @throws IllegalArgumentException if the given array length is not exactly 32 - */ - @SuppressWarnings("deprecation") // the constructor will be made private in the future - public static Sha256Hash wrap(byte[] rawHashBytes) { - return new Sha256Hash(rawHashBytes); - } - - public static Sha256Hash wrap(ByteString rawHashByteString) { - return wrap(rawHashByteString.toByteArray()); - } - - /** - * Use {@link #of(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static Sha256Hash create(byte[] contents) { - return of(contents); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - */ - public static Sha256Hash of(byte[] contents) { - return wrap(hash(contents)); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given file's contents. - * The file contents are read fully into memory, so this method should only be used with small - * files. - * - * @param file the file on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - * @throws IOException if an error occurs while reading the file - */ - public static Sha256Hash of(File file) throws IOException { - - try (FileInputStream in = new FileInputStream(file)) { - return of(ByteStreams.toByteArray(in)); +public class Sha256Hash implements Serializable, Comparable, HashInterface { + + public static final int LENGTH = 32; // bytes + public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); + + private final byte[] bytes; + + public Sha256Hash(long num, byte[] hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + public Sha256Hash(long num, Sha256Hash hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Use {@link #wrap(byte[])} instead. + */ + @Deprecated + public Sha256Hash(byte[] rawHashBytes) { + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Creates a new instance that wraps the given hash value. + * + * @param rawHashBytes the raw hash bytes to wrap + * @return a new instance + * @throws IllegalArgumentException if the given array length is not exactly 32 + */ + @SuppressWarnings("deprecation") // the constructor will be made private in the future + public static Sha256Hash wrap(byte[] rawHashBytes) { + return new Sha256Hash(rawHashBytes); + } + + public static Sha256Hash wrap(ByteString rawHashByteString) { + return wrap(rawHashByteString.toByteArray()); + } + + /** + * Use {@link #of(byte[])} instead: this old name is ambiguous. + */ + @Deprecated + public static Sha256Hash create(boolean isSha256, byte[] contents) { + return of(isSha256,contents); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + */ + public static Sha256Hash of(boolean isSha256, byte[] contents) { + return wrap(hash(isSha256,contents)); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given file's contents. + * The file contents are read fully into memory, so this method should only be used with small + * files. + * + * @param file the file on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + * @throws IOException if an error occurs while reading the file + */ + public static Sha256Hash of(boolean isSha256, File file) throws IOException { + + try (FileInputStream in = new FileInputStream(file)) { + return of(isSha256,ByteStreams.toByteArray(in)); + } } - } - - /** - * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static Sha256Hash createDouble(byte[] contents) { - return twiceOf(contents); - } - - /** - * Creates a new instance containing the hash of the calculated hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (two-time) hash - */ - public static Sha256Hash twiceOf(byte[] contents) { - return wrap(hashTwice(contents)); - } - - /** - * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the - * checked exception that can never occur with a RuntimeException. - * - * @return a new SHA-256 MessageDigest instance - */ - public static MessageDigest newDigest() { - try { - return MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Can't happen. + + /** + * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. + */ + @Deprecated + public static Sha256Hash createDouble(boolean isSha256, byte[] contents) { + return twiceOf(isSha256, contents); + } + + /** + * Creates a new instance containing the hash of the calculated hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (two-time) hash + */ + public static Sha256Hash twiceOf(boolean isSha256, byte[] contents) { + return wrap(hashTwice(isSha256, contents)); } - } - - /** - * Calculates the SHA-256 hash of the given bytes. - * - * @param input the bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input) { - return hash(input, 0, input.length); - } - - /** - * Calculates the SHA-256 hash of the given byte range. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input, int offset, int length) { - MessageDigest digest = newDigest(); - digest.update(input, offset, length); - return digest.digest(); - } - - /** - * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. - * - * @param input the bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input) { - return hashTwice(input, 0, input.length); - } - - /** - * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input, int offset, int length) { - MessageDigest digest = newDigest(); - digest.update(input, offset, length); - return digest.digest(digest.digest()); - } - - /** - * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the - * two ranges and then passing the result to {@link #hashTwice(byte[])}. - */ - public static byte[] hashTwice(byte[] input1, int offset1, int length1, - byte[] input2, int offset2, int length2) { - MessageDigest digest = newDigest(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - return digest.digest(digest.digest()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + + /** + * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SHA-256 MessageDigest instance + */ + public static MessageDigest newDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Can't happen. + } } - if (o == null || !(o instanceof Sha256Hash)) { - return false; + + /** + * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SM3 MessageDigest instance + */ + public static SM3Digest newSM3Digest() { + return new SM3Digest(); + } + + /** + * Calculates the SHA-256 hash of the given bytes. + * + * @param input the bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(boolean isSha256, byte[] input) { + return hash(isSha256, input, 0, input.length); } - return Arrays.equals(bytes, ((Sha256Hash) o).bytes); - } - - @Override - public String toString() { - return ByteArray.toHexString(bytes); - } - - /** - * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable - * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. - * the value as a big integer lower than the target value). - */ - @Override - public int hashCode() { - // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints - .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); - } - - /** - * Returns the bytes interpreted as a positive integer. - */ - public BigInteger toBigInteger() { - return new BigInteger(1, bytes); - } - - /** - * Returns the internal byte array, without defensively copying. Therefore do NOT modify the - * returned array. - */ - public byte[] getBytes() { - return bytes; - } - - /** - * For pb return ByteString. - */ - public ByteString getByteString() { - return ByteString.copyFrom(bytes); - } - - @Override - public int compareTo(final Sha256Hash other) { - for (int i = LENGTH - 1; i >= 0; i--) { - final int thisByte = this.bytes[i] & 0xff; - final int otherByte = other.bytes[i] & 0xff; - if (thisByte > otherByte) { - return 1; - } - if (thisByte < otherByte) { - return -1; - } + + /** + * Calculates the SHA-256 hash of the given byte range. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(boolean isSha256, byte[] input, int offset, int length) { + if (isSha256) { + MessageDigest digest = newDigest(); + digest.update(input, offset, length); + return digest.digest(); + } else { + SM3Digest digest = newSM3Digest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + return eHash; + } + + } + + /** + * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * + * @param input the bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(boolean isSha256, byte[] input) { + return hashTwice(isSha256, input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(boolean isSha256, byte[] input, int offset, int length) { + if (isSha256) { + MessageDigest digest = newDigest(); + digest.update(input, offset, length); + return digest.digest(digest.digest()); + } else { + SM3Digest digest = newSM3Digest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.reset(); + digest.update(eHash,0,eHash.length); + digest.doFinal(eHash,0); + return eHash; + } + + } + + /** + * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the + * two ranges and then passing the result to {@link #hashTwice(byte[])}. + */ + public static byte[] hashTwice(boolean isSha256, byte[] input1, int offset1, int length1, + byte[] input2, int offset2, int length2) { + if (isSha256) { + MessageDigest digest = newDigest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + return digest.digest(digest.digest()); + } else { + SM3Digest digest = newSM3Digest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash,0); + return eHash; + } + } + + private byte[] generateBlockId(long blockNum, Sha256Hash blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.getBytes().length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); + return hash; + } + + private byte[] generateBlockId(long blockNum, byte[] blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof Sha256Hash)) { + return false; + } + return Arrays.equals(bytes, ((Sha256Hash) o).bytes); + } + + @Override + public String toString() { + return ByteArray.toHexString(bytes); + } + + /** + * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable + * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. + * the value as a big integer lower than the target value). + */ + @Override + public int hashCode() { + // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. + return Ints + .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + } + + /** + * Returns the bytes interpreted as a positive integer. + */ + public BigInteger toBigInteger() { + return new BigInteger(1, bytes); + } + + /** + * Returns the internal byte array, without defensively copying. Therefore do NOT modify the + * returned array. + */ + public byte[] getBytes() { + return bytes; + } + + /** + * For pb return ByteString. + */ + public ByteString getByteString() { + return ByteString.copyFrom(bytes); + } + + @Override + public int compareTo(final Sha256Hash other) { + for (int i = LENGTH - 1; i >= 0; i--) { + final int thisByte = this.bytes[i] & 0xff; + final int otherByte = other.bytes[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } + } + return 0; } - return 0; - } } From 94f48df6700fff691b2e8a91a5df97a550206557 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Sat, 11 Jan 2020 19:26:16 +0800 Subject: [PATCH 255/445] 58check --- .../org/tron/common/crypto/Sha256Hash.java | 524 ++++++------ .../org/tron/common/crypto/Sha256Sm3Hash.java | 324 +++++++ .../tron/common/utils/TransactionUtils.java | 149 ++-- .../java/org/tron/common/utils/Utils.java | 542 ++++++------ src/main/java/org/tron/demo/Debug.java | 7 +- src/main/java/org/tron/demo/ECKeyDemo.java | 15 +- .../org/tron/demo/EasyTransferAssetDemo.java | 10 +- .../java/org/tron/demo/EasyTransferDemo.java | 9 +- .../org/tron/demo/TransactionSignDemo.java | 45 +- .../tron/demo/TransactionSignDemoForSM2.java | 44 +- .../java/org/tron/keystore/SKeyEncryptor.java | 39 +- src/main/java/org/tron/test/Test.java | 189 ++--- .../java/org/tron/walletserver/WalletApi.java | 801 ++++++++++-------- 13 files changed, 1544 insertions(+), 1154 deletions(-) create mode 100644 src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java index 66f40fc5b..8fd367e73 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Hash.java @@ -17,13 +17,10 @@ * limitations under the License. */ -import static com.google.common.base.Preconditions.checkArgument; - import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; -import org.spongycastle.crypto.digests.SM3Digest; import org.tron.common.utils.ByteArray; import java.io.File; @@ -35,300 +32,251 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import static com.google.common.base.Preconditions.checkArgument; /** * A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be * used as keys in a map. It also checks that the length is correct and provides a bit more type * safety. */ -public class Sha256Hash implements Serializable, Comparable, HashInterface { - - public static final int LENGTH = 32; // bytes - public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); - - private final byte[] bytes; - - public Sha256Hash(long num, byte[] hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - public Sha256Hash(long num, Sha256Hash hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** - * Use {@link #wrap(byte[])} instead. - */ - @Deprecated - public Sha256Hash(byte[] rawHashBytes) { - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** - * Creates a new instance that wraps the given hash value. - * - * @param rawHashBytes the raw hash bytes to wrap - * @return a new instance - * @throws IllegalArgumentException if the given array length is not exactly 32 - */ - @SuppressWarnings("deprecation") // the constructor will be made private in the future - public static Sha256Hash wrap(byte[] rawHashBytes) { - return new Sha256Hash(rawHashBytes); - } - - public static Sha256Hash wrap(ByteString rawHashByteString) { - return wrap(rawHashByteString.toByteArray()); - } - - /** - * Use {@link #of(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static Sha256Hash create(boolean isSha256, byte[] contents) { - return of(isSha256,contents); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - */ - public static Sha256Hash of(boolean isSha256, byte[] contents) { - return wrap(hash(isSha256,contents)); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given file's contents. - * The file contents are read fully into memory, so this method should only be used with small - * files. - * - * @param file the file on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - * @throws IOException if an error occurs while reading the file - */ - public static Sha256Hash of(boolean isSha256, File file) throws IOException { - - try (FileInputStream in = new FileInputStream(file)) { - return of(isSha256,ByteStreams.toByteArray(in)); - } - } - - /** - * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static Sha256Hash createDouble(boolean isSha256, byte[] contents) { - return twiceOf(isSha256, contents); - } - - /** - * Creates a new instance containing the hash of the calculated hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (two-time) hash - */ - public static Sha256Hash twiceOf(boolean isSha256, byte[] contents) { - return wrap(hashTwice(isSha256, contents)); - } - - /** - * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the - * checked exception that can never occur with a RuntimeException. - * - * @return a new SHA-256 MessageDigest instance - */ - public static MessageDigest newDigest() { - try { - return MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Can't happen. - } - } - - /** - * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the - * checked exception that can never occur with a RuntimeException. - * - * @return a new SM3 MessageDigest instance - */ - public static SM3Digest newSM3Digest() { - return new SM3Digest(); - } - - /** - * Calculates the SHA-256 hash of the given bytes. - * - * @param input the bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(boolean isSha256, byte[] input) { - return hash(isSha256, input, 0, input.length); - } - - /** - * Calculates the SHA-256 hash of the given byte range. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(boolean isSha256, byte[] input, int offset, int length) { - if (isSha256) { - MessageDigest digest = newDigest(); - digest.update(input, offset, length); - return digest.digest(); - } else { - SM3Digest digest = newSM3Digest(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - return eHash; - } - - } - - /** - * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. - * - * @param input the bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(boolean isSha256, byte[] input) { - return hashTwice(isSha256, input, 0, input.length); +public class Sha256Hash implements Serializable, Comparable { + + public static final int LENGTH = 32; // bytes + public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); + + private final byte[] bytes; + + public Sha256Hash(long num, byte[] hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + public Sha256Hash(long num, Sha256Hash hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** Use {@link #wrap(byte[])} instead. */ + @Deprecated + public Sha256Hash(byte[] rawHashBytes) { + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Creates a new instance that wraps the given hash value. + * + * @param rawHashBytes the raw hash bytes to wrap + * @return a new instance + * @throws IllegalArgumentException if the given array length is not exactly 32 + */ + @SuppressWarnings("deprecation") // the constructor will be made private in the future + public static Sha256Hash wrap(byte[] rawHashBytes) { + return new Sha256Hash(rawHashBytes); + } + + public static Sha256Hash wrap(ByteString rawHashByteString) { + return wrap(rawHashByteString.toByteArray()); + } + + /** Use {@link #of(byte[])} instead: this old name is ambiguous. */ + @Deprecated + public static Sha256Hash create(byte[] contents) { + return of(contents); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + */ + public static Sha256Hash of(byte[] contents) { + return wrap(hash(contents)); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given file's contents. + * The file contents are read fully into memory, so this method should only be used with small + * files. + * + * @param file the file on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + * @throws IOException if an error occurs while reading the file + */ + public static Sha256Hash of(File file) throws IOException { + + try (FileInputStream in = new FileInputStream(file)) { + return of(ByteStreams.toByteArray(in)); } - - /** - * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(boolean isSha256, byte[] input, int offset, int length) { - if (isSha256) { - MessageDigest digest = newDigest(); - digest.update(input, offset, length); - return digest.digest(digest.digest()); - } else { - SM3Digest digest = newSM3Digest(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - digest.reset(); - digest.update(eHash,0,eHash.length); - digest.doFinal(eHash,0); - return eHash; - } - + } + + /** Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. */ + @Deprecated + public static Sha256Hash createDouble(byte[] contents) { + return twiceOf(contents); + } + + /** + * Creates a new instance containing the hash of the calculated hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (two-time) hash + */ + public static Sha256Hash twiceOf(byte[] contents) { + return wrap(hashTwice(contents)); + } + + /** + * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SHA-256 MessageDigest instance + */ + public static MessageDigest newDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Can't happen. } - - /** - * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the - * two ranges and then passing the result to {@link #hashTwice(byte[])}. - */ - public static byte[] hashTwice(boolean isSha256, byte[] input1, int offset1, int length1, - byte[] input2, int offset2, int length2) { - if (isSha256) { - MessageDigest digest = newDigest(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - return digest.digest(digest.digest()); - } else { - SM3Digest digest = newSM3Digest(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash,0); - return eHash; - } + } + + /** + * Calculates the SHA-256 hash of the given bytes. + * + * @param input the bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input) { + return hash(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input, int offset, int length) { + MessageDigest digest = newDigest(); + digest.update(input, offset, length); + return digest.digest(); + } + + /** + * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * + * @param input the bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input) { + return hashTwice(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input, int offset, int length) { + MessageDigest digest = newDigest(); + digest.update(input, offset, length); + return digest.digest(digest.digest()); + } + + /** + * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the + * two ranges and then passing the result to {@link #hashTwice(byte[])}. + */ + public static byte[] hashTwice( + byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) { + MessageDigest digest = newDigest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + return digest.digest(digest.digest()); + } + + private byte[] generateBlockId(long blockNum, Sha256Hash blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.getBytes().length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); + return hash; + } + + private byte[] generateBlockId(long blockNum, byte[] blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - private byte[] generateBlockId(long blockNum, Sha256Hash blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.getBytes().length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); - return hash; - } - - private byte[] generateBlockId(long blockNum, byte[] blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); - return hash; + if (o == null || !(o instanceof Sha256Hash)) { + return false; } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !(o instanceof Sha256Hash)) { - return false; - } - return Arrays.equals(bytes, ((Sha256Hash) o).bytes); - } - - @Override - public String toString() { - return ByteArray.toHexString(bytes); - } - - /** - * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable - * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. - * the value as a big integer lower than the target value). - */ - @Override - public int hashCode() { - // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints - .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); - } - - /** - * Returns the bytes interpreted as a positive integer. - */ - public BigInteger toBigInteger() { - return new BigInteger(1, bytes); - } - - /** - * Returns the internal byte array, without defensively copying. Therefore do NOT modify the - * returned array. - */ - public byte[] getBytes() { - return bytes; - } - - /** - * For pb return ByteString. - */ - public ByteString getByteString() { - return ByteString.copyFrom(bytes); - } - - @Override - public int compareTo(final Sha256Hash other) { - for (int i = LENGTH - 1; i >= 0; i--) { - final int thisByte = this.bytes[i] & 0xff; - final int otherByte = other.bytes[i] & 0xff; - if (thisByte > otherByte) { - return 1; - } - if (thisByte < otherByte) { - return -1; - } - } - return 0; + return Arrays.equals(bytes, ((Sha256Hash) o).bytes); + } + + @Override + public String toString() { + return ByteArray.toHexString(bytes); + } + + /** + * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable + * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. + * the value as a big integer lower than the target value). + */ + @Override + public int hashCode() { + // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. + return Ints.fromBytes( + bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + } + + /** Returns the bytes interpreted as a positive integer. */ + public BigInteger toBigInteger() { + return new BigInteger(1, bytes); + } + + /** + * Returns the internal byte array, without defensively copying. Therefore do NOT modify the + * returned array. + */ + public byte[] getBytes() { + return bytes; + } + + /** For pb return ByteString. */ + public ByteString getByteString() { + return ByteString.copyFrom(bytes); + } + + @Override + public int compareTo(final Sha256Hash other) { + for (int i = LENGTH - 1; i >= 0; i--) { + final int thisByte = this.bytes[i] & 0xff; + final int otherByte = other.bytes[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } } + return 0; + } } diff --git a/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java b/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java new file mode 100644 index 000000000..c46e83cb9 --- /dev/null +++ b/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java @@ -0,0 +1,324 @@ +package org.tron.common.crypto; + +/* + * Copyright 2011 Google Inc. + * Copyright 2014 Andreas Schildbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.protobuf.ByteString; +import com.typesafe.config.Config; +import org.spongycastle.crypto.digests.SM3Digest; +import org.tron.common.utils.ByteArray; +import org.tron.core.config.Configuration; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Serializable; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A Sha256Sm3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be + * used as keys in a map. It also checks that the length is correct and provides a bit more type + * safety. + */ +public class Sha256Sm3Hash implements Serializable, Comparable, HashInterface { + + private static boolean isEckey = true; + + static { + Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant + if (config.hasPath("crypto.engine")) { + isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); + System.out.println("WalletUtils getConfig isEckey: " + isEckey); + } + } + + public static final int LENGTH = 32; // bytes + public static final Sha256Sm3Hash ZERO_HASH = wrap(new byte[LENGTH]); + + private final byte[] bytes; + + public Sha256Sm3Hash(long num, byte[] hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + public Sha256Sm3Hash(long num, Sha256Sm3Hash hash) { + byte[] rawHashBytes = this.generateBlockId(num, hash); + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** Use {@link #wrap(byte[])} instead. */ + @Deprecated + public Sha256Sm3Hash(byte[] rawHashBytes) { + checkArgument(rawHashBytes.length == LENGTH); + this.bytes = rawHashBytes; + } + + /** + * Creates a new instance that wraps the given hash value. + * + * @param rawHashBytes the raw hash bytes to wrap + * @return a new instance + * @throws IllegalArgumentException if the given array length is not exactly 32 + */ + @SuppressWarnings("deprecation") // the constructor will be made private in the future + public static Sha256Sm3Hash wrap(byte[] rawHashBytes) { + return new Sha256Sm3Hash(rawHashBytes); + } + + public static Sha256Sm3Hash wrap(ByteString rawHashByteString) { + return wrap(rawHashByteString.toByteArray()); + } + + /** Use {@link #of(byte[])} instead: this old name is ambiguous. */ + @Deprecated + public static Sha256Sm3Hash create(byte[] contents) { + return of(contents); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + */ + public static Sha256Sm3Hash of(byte[] contents) { + return wrap(hash(contents)); + } + + /** + * Creates a new instance containing the calculated (one-time) hash of the given file's contents. + * The file contents are read fully into memory, so this method should only be used with small + * files. + * + * @param file the file on which the hash value is calculated + * @return a new instance containing the calculated (one-time) hash + * @throws IOException if an error occurs while reading the file + */ + public static Sha256Sm3Hash of(File file) throws IOException { + + try (FileInputStream in = new FileInputStream(file)) { + return of(ByteStreams.toByteArray(in)); + } + } + + /** Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. */ + @Deprecated + public static Sha256Sm3Hash createDouble(byte[] contents) { + return twiceOf(contents); + } + + /** + * Creates a new instance containing the hash of the calculated hash of the given bytes. + * + * @param contents the bytes on which the hash value is calculated + * @return a new instance containing the calculated (two-time) hash + */ + public static Sha256Sm3Hash twiceOf(byte[] contents) { + return wrap(hashTwice(contents)); + } + + /** + * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SHA-256 MessageDigest instance + */ + public static MessageDigest newDigestEckey() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Can't happen. + } + } + + public static SM3Digest newDigestSM3() { + return new SM3Digest(); + } + + /** + * Calculates the SHA-256 hash of the given bytes. + * + * @param input the bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input) { + return hash(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the hash (in big-endian order) + */ + public static byte[] hash(byte[] input, int offset, int length) { + if (isEckey) { + MessageDigest digest = newDigestEckey(); + digest.update(input, offset, length); + return digest.digest(); + } + SM3Digest digest = newDigestSM3(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. + * + * @param input the bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input) { + return hashTwice(input, 0, input.length); + } + + /** + * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. + * + * @param input the array containing the bytes to hash + * @param offset the offset within the array of the bytes to hash + * @param length the number of bytes to hash + * @return the double-hash (in big-endian order) + */ + public static byte[] hashTwice(byte[] input, int offset, int length) { + if (isEckey) { + MessageDigest digest = newDigestEckey(); + digest.update(input, offset, length); + return digest.digest(digest.digest()); + } + SM3Digest digest = newDigestSM3(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.reset(); + digest.update(eHash, 0, eHash.length); + digest.doFinal(eHash, 0); + return eHash; + } + + /** + * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the + * two ranges and then passing the result to {@link #hashTwice(byte[])}. + */ + public static byte[] hashTwice( + byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) { + if (isEckey) { + MessageDigest digest = newDigestEckey(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + return digest.digest(digest.digest()); + } + SM3Digest digest = newDigestSM3(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + return eHash; + } + + private byte[] generateBlockId(long blockNum, Sha256Sm3Hash blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.getBytes().length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); + return hash; + } + + private byte[] generateBlockId(long blockNum, byte[] blockHash) { + byte[] numBytes = Longs.toByteArray(blockNum); + byte[] hash = new byte[blockHash.length]; + System.arraycopy(numBytes, 0, hash, 0, 8); + System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); + return hash; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof Sha256Sm3Hash)) { + return false; + } + return Arrays.equals(bytes, ((Sha256Sm3Hash) o).bytes); + } + + @Override + public String toString() { + return ByteArray.toHexString(bytes); + } + + /** + * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable + * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. + * the value as a big integer lower than the target value). + */ + @Override + public int hashCode() { + // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. + return Ints.fromBytes( + bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + } + + /** Returns the bytes interpreted as a positive integer. */ + public BigInteger toBigInteger() { + return new BigInteger(1, bytes); + } + + /** + * Returns the internal byte array, without defensively copying. Therefore do NOT modify the + * returned array. + */ + public byte[] getBytes() { + return bytes; + } + + /** For pb return ByteString. */ + public ByteString getByteString() { + return ByteString.copyFrom(bytes); + } + + @Override + public int compareTo(final Sha256Sm3Hash other) { + for (int i = LENGTH - 1; i >= 0; i--) { + final int thisByte = this.bytes[i] & 0xff; + final int otherByte = other.bytes[i] & 0xff; + if (thisByte > otherByte) { + return 1; + } + if (thisByte < otherByte) { + return -1; + } + } + return 0; + } +} diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index ccaf56222..ec994ff69 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -18,10 +18,9 @@ import com.google.protobuf.ByteString; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.sm2.SM2; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; @@ -40,9 +39,9 @@ public class TransactionUtils { */ public static byte[] getHash(Transaction transaction) { Transaction.Builder tmp = transaction.toBuilder(); - //tmp.clearId(); + // tmp.clearId(); - return Sha256Hash.hash(tmp.build().toByteArray()); + return Sha256Sm3Hash.hash(tmp.build().toByteArray()); } public static byte[] getOwner(Transaction.Contract contract) { @@ -50,69 +49,116 @@ public static byte[] getOwner(Transaction.Contract contract) { try { switch (contract.getType()) { case AccountCreateContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.AccountCreateContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AccountCreateContract.class) + .getOwnerAddress(); break; case TransferContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.TransferContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TransferContract.class) + .getOwnerAddress(); break; case TransferAssetContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.TransferAssetContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TransferAssetContract.class) + .getOwnerAddress(); break; case VoteAssetContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteAssetContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.VoteAssetContract.class) + .getOwnerAddress(); break; case VoteWitnessContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.VoteWitnessContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.VoteWitnessContract.class) + .getOwnerAddress(); break; case WitnessCreateContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.WitnessCreateContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.WitnessCreateContract.class) + .getOwnerAddress(); break; case AssetIssueContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.AssetIssueContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AssetIssueContract.class) + .getOwnerAddress(); break; case ParticipateAssetIssueContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) + .getOwnerAddress(); break; case CreateSmartContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.CreateSmartContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.CreateSmartContract.class) + .getOwnerAddress(); break; case TriggerSmartContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.TriggerSmartContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.TriggerSmartContract.class) + .getOwnerAddress(); break; case FreezeBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.FreezeBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.FreezeBalanceContract.class) + .getOwnerAddress(); break; case UnfreezeBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class) + .getOwnerAddress(); break; case UnfreezeAssetContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class) + .getOwnerAddress(); break; case WithdrawBalanceContract: - owner = contract.getParameter() - .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class).getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class) + .getOwnerAddress(); break; case UpdateAssetContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.UpdateAssetContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.UpdateAssetContract.class) + .getOwnerAddress(); break; case AccountPermissionUpdateContract: - owner = contract.getParameter().unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) - .getOwnerAddress(); + owner = + contract + .getParameter() + .unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) + .getOwnerAddress(); break; default: return null; @@ -129,7 +175,7 @@ public static String getBase64FromByteString(ByteString sign) { byte[] s = sign.substring(32, 64).toByteArray(); byte v = sign.byteAt(64); if (v < 27) { - v += 27; //revId -> v + v += 27; // revId -> v } ECDSASignature signature = ECDSASignature.fromComponents(r, s, v); return signature.toBase64(); @@ -142,10 +188,10 @@ public static String getBase64FromByteString(ByteString sign) { * 4. check balance */ public static boolean validTransaction(Transaction signedTransaction) { - assert (signedTransaction.getSignatureCount() == - signedTransaction.getRawData().getContractCount()); + assert (signedTransaction.getSignatureCount() + == signedTransaction.getRawData().getContractCount()); List listContract = signedTransaction.getRawData().getContractList(); - byte[] hash = Sha256Hash.hash(signedTransaction.getRawData().toByteArray()); + byte[] hash = Sha256Sm3Hash.hash(signedTransaction.getRawData().toByteArray()); int count = signedTransaction.getSignatureCount(); if (count == 0) { return false; @@ -154,8 +200,9 @@ public static boolean validTransaction(Transaction signedTransaction) { try { Transaction.Contract contract = listContract.get(i); byte[] owner = getOwner(contract); - byte[] address = ECKey - .signatureToAddress(hash, getBase64FromByteString(signedTransaction.getSignature(i))); + byte[] address = + ECKey.signatureToAddress( + hash, getBase64FromByteString(signedTransaction.getSignature(i))); if (!Arrays.equals(owner, address)) { return false; } @@ -169,7 +216,7 @@ public static boolean validTransaction(Transaction signedTransaction) { public static Transaction sign(Transaction transaction, SignInterface myKey) { Transaction.Builder transactionBuilderSigned = transaction.toBuilder(); - byte[] hash = Sha256Hash.hash(transaction.getRawData().toByteArray()); + byte[] hash = Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()); SignatureInterface signature = myKey.sign(hash); ByteString bsSign = ByteString.copyFrom(signature.toByteArray()); transactionBuilderSigned.addSignature(bsSign); @@ -178,10 +225,10 @@ public static Transaction sign(Transaction transaction, SignInterface myKey) { } public static Transaction setTimestamp(Transaction transaction) { - long currentTime = System.currentTimeMillis();//*1000000 + System.nanoTime()%1000000; + long currentTime = System.currentTimeMillis(); // *1000000 + System.nanoTime()%1000000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() - .toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = + transaction.getRawData().toBuilder(); rowBuilder.setTimestamp(currentTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -191,8 +238,8 @@ public static Transaction setExpirationTime(Transaction transaction) { if (transaction.getSignatureCount() == 0) { long expirationTime = System.currentTimeMillis() + 6 * 60 * 60 * 1000; Transaction.Builder builder = transaction.toBuilder(); - org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = transaction.getRawData() - .toBuilder(); + org.tron.protos.Protocol.Transaction.raw.Builder rowBuilder = + transaction.getRawData().toBuilder(); rowBuilder.setExpiration(expirationTime); builder.setRawData(rowBuilder.build()); return builder.build(); @@ -211,8 +258,8 @@ public static Transaction setPermissionId(Transaction transaction) throws Cancel } if (permission_id != 0) { Transaction.raw.Builder raw = transaction.getRawData().toBuilder(); - Transaction.Contract.Builder contract = raw.getContract(0).toBuilder() - .setPermissionId(permission_id); + Transaction.Contract.Builder contract = + raw.getContract(0).toBuilder().setPermissionId(permission_id); raw.clearContract(); raw.addContract(contract); transaction = transaction.toBuilder().setRawData(raw).build(); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index fbc1256ff..7479115e5 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -25,7 +25,7 @@ import com.google.protobuf.Message; import org.tron.api.GrpcAPI.*; import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.keystore.StringUtils; import org.tron.protos.Contract.*; import org.tron.protos.Protocol.Block; @@ -77,9 +77,7 @@ private char[] getChars(byte[] bytes) { return cb.array(); } - /** - * yyyy-MM-dd - */ + /** yyyy-MM-dd */ public static Date strToDateLong(String strDate) { if (strDate.length() == 10) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); @@ -120,18 +118,22 @@ public static String printTransaction(TransactionExtention transactionExtention) public static String printTransactionList(TransactionList transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionToJSON(transaction, true)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionToJSON(transaction, true)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } public static String printTransactionList(TransactionListExtention transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionExtentionToJSON(transaction)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionExtentionToJSON(transaction)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -148,18 +150,22 @@ public static String printBlockExtention(BlockExtention blockExtention) { public static String printBlockList(BlockList blockList) { JSONArray jsonArray = new JSONArray(); List blocks = blockList.getBlockList(); - blocks.stream().forEach(block -> { - jsonArray.add(printBlockToJSON(block)); - }); + blocks.stream() + .forEach( + block -> { + jsonArray.add(printBlockToJSON(block)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } public static String printBlockList(BlockListExtention blockList) { JSONArray jsonArray = new JSONArray(); List blocks = blockList.getBlockList(); - blocks.stream().forEach(block -> { - jsonArray.add(printBlockExtentionToJSON(block)); - }); + blocks.stream() + .forEach( + block -> { + jsonArray.add(printBlockExtentionToJSON(block)); + }); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -167,9 +173,9 @@ public static String printTransactionSignWeight(TransactionSignWeight transactio String string = JsonFormat.printToString(transactionSignWeight, true); JSONObject jsonObject = JSONObject.parseObject(string); JSONObject jsonObjectExt = jsonObject.getJSONObject(TRANSACTION); - jsonObjectExt.put(TRANSACTION, - printTransactionToJSON(transactionSignWeight.getTransaction().getTransaction(), - true)); + jsonObjectExt.put( + TRANSACTION, + printTransactionToJSON(transactionSignWeight.getTransaction().getTransaction(), true)); jsonObject.put(TRANSACTION, jsonObjectExt); return JsonFormatUtil.formatJson(jsonObject.toJSONString()); } @@ -179,9 +185,9 @@ public static String printTransactionApprovedList( String string = JsonFormat.printToString(transactionApprovedList, true); JSONObject jsonObject = JSONObject.parseObject(string); JSONObject jsonObjectExt = jsonObject.getJSONObject(TRANSACTION); - jsonObjectExt.put(TRANSACTION, - printTransactionToJSON(transactionApprovedList.getTransaction().getTransaction(), - true)); + jsonObjectExt.put( + TRANSACTION, + printTransactionToJSON(transactionApprovedList.getTransaction().getTransaction(), true)); jsonObject.put(TRANSACTION, jsonObjectExt); return JsonFormatUtil.formatJson(jsonObject.toJSONString()); } @@ -236,7 +242,7 @@ public static char[] inputPassword(boolean checkStrength) throws IOException { public static byte[] generateContractAddress(Transaction trx, byte[] ownerAddress) { // get tx hash - byte[] txRawDataHash = Sha256Hash.of(trx.getRawData().toByteArray()).getBytes(); + byte[] txRawDataHash = Sha256Sm3Hash.of(trx.getRawData().toByteArray()).getBytes(); // combine byte[] combined = new byte[txRawDataHash.length + ownerAddress.length]; @@ -248,12 +254,14 @@ public static byte[] generateContractAddress(Transaction trx, byte[] ownerAddres public static JSONObject printBlockExtentionToJSON(BlockExtention blockExtention) { JSONObject jsonObject = JSONObject.parseObject(JsonFormat.printToString(blockExtention, true)); - if (blockExtention.getTransactionsCount() > 0 ) { + if (blockExtention.getTransactionsCount() > 0) { JSONArray jsonArray = new JSONArray(); List transactions = blockExtention.getTransactionsList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionExtentionToJSON(transaction)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionExtentionToJSON(transaction)); + }); jsonObject.put(TRANSACTION, jsonArray); } return jsonObject; @@ -264,9 +272,11 @@ public static JSONObject printBlockToJSON(Block block) { if (block.getTransactionsCount() > 0) { JSONArray jsonArray = new JSONArray(); List transactions = block.getTransactionsList(); - transactions.stream().forEach(transaction -> { - jsonArray.add(printTransactionToJSON(transaction, true)); - }); + transactions.stream() + .forEach( + transaction -> { + jsonArray.add(printTransactionToJSON(transaction, true)); + }); jsonObject.put(TRANSACTION, jsonArray); } return jsonObject; @@ -274,244 +284,279 @@ public static JSONObject printBlockToJSON(Block block) { public static JSONObject printTransactionExtentionToJSON( TransactionExtention transactionExtention) { - JSONObject jsonObject = JSONObject - .parseObject(JsonFormat.printToString(transactionExtention, true)); + JSONObject jsonObject = + JSONObject.parseObject(JsonFormat.printToString(transactionExtention, true)); if (transactionExtention.getResult().getResult()) { - JSONObject transactionOjbect = printTransactionToJSON(transactionExtention.getTransaction(), - true); + JSONObject transactionOjbect = + printTransactionToJSON(transactionExtention.getTransaction(), true); jsonObject.put(TRANSACTION, transactionOjbect); } return jsonObject; } public static JSONObject printTransactionToJSON(Transaction transaction, boolean selfType) { - JSONObject jsonTransaction = JSONObject.parseObject(JsonFormat.printToString(transaction, - selfType)); + JSONObject jsonTransaction = + JSONObject.parseObject(JsonFormat.printToString(transaction, selfType)); JSONArray contracts = new JSONArray(); - transaction.getRawData().getContractList().stream().forEach(contract -> { - try { - JSONObject contractJson = null; - Any contractParameter = contract.getParameter(); - switch (contract.getType()) { - case AccountCreateContract: - AccountCreateContract accountCreateContract = contractParameter - .unpack(AccountCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(accountCreateContract, - selfType)); - break; - case TransferContract: - TransferContract transferContract = contractParameter.unpack(TransferContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(transferContract, - selfType)); - break; - case TransferAssetContract: - TransferAssetContract transferAssetContract = contractParameter - .unpack(TransferAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(transferAssetContract, - selfType)); - break; - case VoteAssetContract: - VoteAssetContract voteAssetContract = contractParameter.unpack(VoteAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(voteAssetContract, - selfType)); - break; - case VoteWitnessContract: - VoteWitnessContract voteWitnessContract = contractParameter - .unpack(VoteWitnessContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(voteWitnessContract, - selfType)); - break; - case WitnessCreateContract: - WitnessCreateContract witnessCreateContract = contractParameter - .unpack(WitnessCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(witnessCreateContract, - selfType)); - break; - case AssetIssueContract: - AssetIssueContract assetIssueContract = contractParameter - .unpack(AssetIssueContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(assetIssueContract, - selfType)); - break; - case WitnessUpdateContract: - WitnessUpdateContract witnessUpdateContract = contractParameter - .unpack(WitnessUpdateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(witnessUpdateContract, - selfType)); - break; - case ParticipateAssetIssueContract: - ParticipateAssetIssueContract participateAssetIssueContract = contractParameter - .unpack(ParticipateAssetIssueContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(participateAssetIssueContract, selfType)); - break; - case AccountUpdateContract: - AccountUpdateContract accountUpdateContract = contractParameter - .unpack(AccountUpdateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(accountUpdateContract, - selfType)); - break; - case FreezeBalanceContract: - FreezeBalanceContract freezeBalanceContract = contractParameter - .unpack(FreezeBalanceContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(freezeBalanceContract, - selfType)); - break; - case UnfreezeBalanceContract: - UnfreezeBalanceContract unfreezeBalanceContract = contractParameter - .unpack(UnfreezeBalanceContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(unfreezeBalanceContract, selfType)); - break; - case WithdrawBalanceContract: - WithdrawBalanceContract withdrawBalanceContract = contractParameter - .unpack(WithdrawBalanceContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(withdrawBalanceContract, selfType)); - break; - case UnfreezeAssetContract: - UnfreezeAssetContract unfreezeAssetContract = contractParameter - .unpack(UnfreezeAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(unfreezeAssetContract, - selfType)); - break; - case UpdateAssetContract: - UpdateAssetContract updateAssetContract = contractParameter - .unpack(UpdateAssetContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(updateAssetContract, - selfType)); - break; - case ProposalCreateContract: - ProposalCreateContract proposalCreateContract = contractParameter - .unpack(ProposalCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(proposalCreateContract, - selfType)); - break; - case ProposalApproveContract: - ProposalApproveContract proposalApproveContract = contractParameter - .unpack(ProposalApproveContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(proposalApproveContract, selfType)); - break; - case ProposalDeleteContract: - ProposalDeleteContract proposalDeleteContract = contractParameter - .unpack(ProposalDeleteContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(proposalDeleteContract, - selfType)); - break; - case SetAccountIdContract: - org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = - contractParameter.unpack(org.tron.protos.Contract.SetAccountIdContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(setAccountIdContract, - selfType)); - break; - case CreateSmartContract: - CreateSmartContract deployContract = contractParameter - .unpack(CreateSmartContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(deployContract, - selfType)); - byte[] ownerAddress = deployContract.getOwnerAddress().toByteArray(); - byte[] contractAddress = generateContractAddress(transaction, ownerAddress); - jsonTransaction.put("contract_address", ByteArray.toHexString(contractAddress)); - break; - case TriggerSmartContract: - TriggerSmartContract triggerSmartContract = contractParameter - .unpack(TriggerSmartContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(triggerSmartContract, - selfType)); - break; - case UpdateSettingContract: - UpdateSettingContract updateSettingContract = contractParameter - .unpack(UpdateSettingContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(updateSettingContract, - selfType)); - break; - case ExchangeCreateContract: - ExchangeCreateContract exchangeCreateContract = contractParameter - .unpack(ExchangeCreateContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(exchangeCreateContract, - selfType)); - break; - case ExchangeInjectContract: - ExchangeInjectContract exchangeInjectContract = contractParameter - .unpack(ExchangeInjectContract.class); - contractJson = JSONObject.parseObject(JsonFormat.printToString(exchangeInjectContract, - selfType)); - break; - case ExchangeWithdrawContract: - ExchangeWithdrawContract exchangeWithdrawContract = contractParameter - .unpack(ExchangeWithdrawContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(exchangeWithdrawContract, selfType)); - break; - case ExchangeTransactionContract: - ExchangeTransactionContract exchangeTransactionContract = contractParameter - .unpack(ExchangeTransactionContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(exchangeTransactionContract, selfType)); - break; - case UpdateEnergyLimitContract: - UpdateEnergyLimitContract updateEnergyLimitContract = contractParameter - .unpack(UpdateEnergyLimitContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(updateEnergyLimitContract, selfType)); - break; - case AccountPermissionUpdateContract: - AccountPermissionUpdateContract accountPermissionUpdateContract = contractParameter - .unpack(AccountPermissionUpdateContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(accountPermissionUpdateContract, selfType)); - break; - case ClearABIContract: - org.tron.protos.Contract.ClearABIContract clearABIContract = contractParameter - .unpack(org.tron.protos.Contract.ClearABIContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(clearABIContract, selfType)); - break; - case ShieldedTransferContract: - ShieldedTransferContract shieldedTransferContract = contractParameter - .unpack(ShieldedTransferContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(shieldedTransferContract, selfType)); - break; - case UpdateBrokerageContract: - UpdateBrokerageContract updateBrokerageContract = contract.getParameter() - .unpack(UpdateBrokerageContract.class); - contractJson = JSONObject - .parseObject(JsonFormat.printToString(updateBrokerageContract, selfType)); - break; - // todo add other contract - default: - } - JSONObject parameter = new JSONObject(); - parameter.put(VALUE, contractJson); - parameter.put("type_url", contract.getParameterOrBuilder().getTypeUrl()); - JSONObject jsonContract = new JSONObject(); - jsonContract.put("parameter", parameter); - jsonContract.put("type", contract.getType()); - if (contract.getPermissionId() > 0) { - jsonContract.put(PERMISSION_ID, contract.getPermissionId()); - } - contracts.add(jsonContract); - } catch (InvalidProtocolBufferException e) { - e.printStackTrace(); - //System.out.println("InvalidProtocolBufferException: {}", e.getMessage()); - } - }); + transaction.getRawData().getContractList().stream() + .forEach( + contract -> { + try { + JSONObject contractJson = null; + Any contractParameter = contract.getParameter(); + switch (contract.getType()) { + case AccountCreateContract: + AccountCreateContract accountCreateContract = + contractParameter.unpack(AccountCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(accountCreateContract, selfType)); + break; + case TransferContract: + TransferContract transferContract = + contractParameter.unpack(TransferContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(transferContract, selfType)); + break; + case TransferAssetContract: + TransferAssetContract transferAssetContract = + contractParameter.unpack(TransferAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(transferAssetContract, selfType)); + break; + case VoteAssetContract: + VoteAssetContract voteAssetContract = + contractParameter.unpack(VoteAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(voteAssetContract, selfType)); + break; + case VoteWitnessContract: + VoteWitnessContract voteWitnessContract = + contractParameter.unpack(VoteWitnessContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(voteWitnessContract, selfType)); + break; + case WitnessCreateContract: + WitnessCreateContract witnessCreateContract = + contractParameter.unpack(WitnessCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(witnessCreateContract, selfType)); + break; + case AssetIssueContract: + AssetIssueContract assetIssueContract = + contractParameter.unpack(AssetIssueContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(assetIssueContract, selfType)); + break; + case WitnessUpdateContract: + WitnessUpdateContract witnessUpdateContract = + contractParameter.unpack(WitnessUpdateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(witnessUpdateContract, selfType)); + break; + case ParticipateAssetIssueContract: + ParticipateAssetIssueContract participateAssetIssueContract = + contractParameter.unpack(ParticipateAssetIssueContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(participateAssetIssueContract, selfType)); + break; + case AccountUpdateContract: + AccountUpdateContract accountUpdateContract = + contractParameter.unpack(AccountUpdateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(accountUpdateContract, selfType)); + break; + case FreezeBalanceContract: + FreezeBalanceContract freezeBalanceContract = + contractParameter.unpack(FreezeBalanceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(freezeBalanceContract, selfType)); + break; + case UnfreezeBalanceContract: + UnfreezeBalanceContract unfreezeBalanceContract = + contractParameter.unpack(UnfreezeBalanceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(unfreezeBalanceContract, selfType)); + break; + case WithdrawBalanceContract: + WithdrawBalanceContract withdrawBalanceContract = + contractParameter.unpack(WithdrawBalanceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(withdrawBalanceContract, selfType)); + break; + case UnfreezeAssetContract: + UnfreezeAssetContract unfreezeAssetContract = + contractParameter.unpack(UnfreezeAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(unfreezeAssetContract, selfType)); + break; + case UpdateAssetContract: + UpdateAssetContract updateAssetContract = + contractParameter.unpack(UpdateAssetContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateAssetContract, selfType)); + break; + case ProposalCreateContract: + ProposalCreateContract proposalCreateContract = + contractParameter.unpack(ProposalCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(proposalCreateContract, selfType)); + break; + case ProposalApproveContract: + ProposalApproveContract proposalApproveContract = + contractParameter.unpack(ProposalApproveContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(proposalApproveContract, selfType)); + break; + case ProposalDeleteContract: + ProposalDeleteContract proposalDeleteContract = + contractParameter.unpack(ProposalDeleteContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(proposalDeleteContract, selfType)); + break; + case SetAccountIdContract: + org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = + contractParameter.unpack( + org.tron.protos.Contract.SetAccountIdContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(setAccountIdContract, selfType)); + break; + case CreateSmartContract: + CreateSmartContract deployContract = + contractParameter.unpack(CreateSmartContract.class); + contractJson = + JSONObject.parseObject(JsonFormat.printToString(deployContract, selfType)); + byte[] ownerAddress = deployContract.getOwnerAddress().toByteArray(); + byte[] contractAddress = generateContractAddress(transaction, ownerAddress); + jsonTransaction.put("contract_address", ByteArray.toHexString(contractAddress)); + break; + case TriggerSmartContract: + TriggerSmartContract triggerSmartContract = + contractParameter.unpack(TriggerSmartContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(triggerSmartContract, selfType)); + break; + case UpdateSettingContract: + UpdateSettingContract updateSettingContract = + contractParameter.unpack(UpdateSettingContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateSettingContract, selfType)); + break; + case ExchangeCreateContract: + ExchangeCreateContract exchangeCreateContract = + contractParameter.unpack(ExchangeCreateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeCreateContract, selfType)); + break; + case ExchangeInjectContract: + ExchangeInjectContract exchangeInjectContract = + contractParameter.unpack(ExchangeInjectContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeInjectContract, selfType)); + break; + case ExchangeWithdrawContract: + ExchangeWithdrawContract exchangeWithdrawContract = + contractParameter.unpack(ExchangeWithdrawContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeWithdrawContract, selfType)); + break; + case ExchangeTransactionContract: + ExchangeTransactionContract exchangeTransactionContract = + contractParameter.unpack(ExchangeTransactionContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(exchangeTransactionContract, selfType)); + break; + case UpdateEnergyLimitContract: + UpdateEnergyLimitContract updateEnergyLimitContract = + contractParameter.unpack(UpdateEnergyLimitContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateEnergyLimitContract, selfType)); + break; + case AccountPermissionUpdateContract: + AccountPermissionUpdateContract accountPermissionUpdateContract = + contractParameter.unpack(AccountPermissionUpdateContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(accountPermissionUpdateContract, selfType)); + break; + case ClearABIContract: + org.tron.protos.Contract.ClearABIContract clearABIContract = + contractParameter.unpack(org.tron.protos.Contract.ClearABIContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(clearABIContract, selfType)); + break; + case ShieldedTransferContract: + ShieldedTransferContract shieldedTransferContract = + contractParameter.unpack(ShieldedTransferContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(shieldedTransferContract, selfType)); + break; + case UpdateBrokerageContract: + UpdateBrokerageContract updateBrokerageContract = + contract.getParameter().unpack(UpdateBrokerageContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(updateBrokerageContract, selfType)); + break; + // todo add other contract + default: + } + JSONObject parameter = new JSONObject(); + parameter.put(VALUE, contractJson); + parameter.put("type_url", contract.getParameterOrBuilder().getTypeUrl()); + JSONObject jsonContract = new JSONObject(); + jsonContract.put("parameter", parameter); + jsonContract.put("type", contract.getType()); + if (contract.getPermissionId() > 0) { + jsonContract.put(PERMISSION_ID, contract.getPermissionId()); + } + contracts.add(jsonContract); + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + // System.out.println("InvalidProtocolBufferException: {}", e.getMessage()); + } + }); JSONObject rawData = JSONObject.parseObject(jsonTransaction.get("raw_data").toString()); rawData.put("contract", contracts); jsonTransaction.put("raw_data", rawData); String rawDataHex = ByteArray.toHexString(transaction.getRawData().toByteArray()); jsonTransaction.put("raw_data_hex", rawDataHex); - String txID = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + String txID = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray())); jsonTransaction.put("txID", txID); return jsonTransaction; } public static boolean confirmEncrption() { System.out.println( - "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); + "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); Scanner in = new Scanner(System.in); String input = in.nextLine().trim(); String str = input.split("\\s+")[0]; @@ -521,4 +566,3 @@ public static boolean confirmEncrption() { return false; } } - diff --git a/src/main/java/org/tron/demo/Debug.java b/src/main/java/org/tron/demo/Debug.java index ff5a0e24e..4fd7b23e3 100644 --- a/src/main/java/org/tron/demo/Debug.java +++ b/src/main/java/org/tron/demo/Debug.java @@ -3,7 +3,7 @@ import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.protos.Protocol.Transaction; @@ -14,7 +14,7 @@ public static void debug(String privateKey, String hexTransaction) byte[] privateBytes = ByteArray.fromHexString(privateKey); Transaction transaction = Transaction.parseFrom(ByteArray.fromHexString(hexTransaction)); byte[] rawData = transaction.getRawData().toByteArray(); - byte[] hash = Sha256Hash.hash(rawData); + byte[] hash = Sha256Sm3Hash.hash(rawData); ECKey ecKey = ECKey.fromPrivate(privateBytes); byte[] sign = ecKey.sign(hash).toByteArray(); transaction.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); @@ -22,7 +22,8 @@ public static void debug(String privateKey, String hexTransaction) public static void main(String[] args) { try { - debug("77bb18757dd1687574d9d54e32738856e84aefab3a7ae73541ca8ea1912283f9", + debug( + "77bb18757dd1687574d9d54e32738856e84aefab3a7ae73541ca8ea1912283f9", "0a7c0a02b7562208d3df68fd554637b940b8f7e7b6d52c5a65080112610a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412300a15412c20a3c84473df81ee23f84369711bf02cb81b64121541a1220729f9b1734adc923ae44f1dc0a107db0f1b1801"); } catch (Exception e) { System.out.println(e.getMessage()); diff --git a/src/main/java/org/tron/demo/ECKeyDemo.java b/src/main/java/org/tron/demo/ECKeyDemo.java index 7cdc72073..d0a23ea10 100644 --- a/src/main/java/org/tron/demo/ECKeyDemo.java +++ b/src/main/java/org/tron/demo/ECKeyDemo.java @@ -4,7 +4,7 @@ import org.springframework.util.StringUtils; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; @@ -33,10 +33,10 @@ private static byte[] public2AddressDemo(byte[] publicKey) { } public static String address2Encode58CheckDemo(byte[] input) { - byte[] hash0 = Sha256Hash.hash(input); + byte[] hash0 = Sha256Sm3Hash.hash(input); System.out.println("sha256_0: " + ByteArray.toHexString(hash0)); - byte[] hash1 = Sha256Hash.hash(hash0); + byte[] hash1 = Sha256Sm3Hash.hash(hash0); System.out.println("sha256_1: " + ByteArray.toHexString(hash1)); byte[] inputCheck = new byte[input.length + 4]; @@ -52,7 +52,7 @@ public static String address2Encode58CheckDemo(byte[] input) { private static String private2Address(byte[] privateKey) throws CipherException { ECKey eCkey; if (StringUtils.isEmpty(privateKey)) { - eCkey = new ECKey(Utils.getRandom()); //Gen new Keypair + eCkey = new ECKey(Utils.getRandom()); // Gen new Keypair } else { eCkey = ECKey.fromPrivate(privateKey); } @@ -60,21 +60,21 @@ private static String private2Address(byte[] privateKey) throws CipherException byte[] publicKey0 = eCkey.getPubKey(); byte[] publicKey1 = private2PublicDemo(eCkey.getPrivKeyBytes()); - if (!Arrays.equals(publicKey0, publicKey1)){ + if (!Arrays.equals(publicKey0, publicKey1)) { throw new CipherException("publickey error"); } System.out.println("Public Key: " + ByteArray.toHexString(publicKey0)); byte[] address0 = eCkey.getAddress(); byte[] address1 = public2AddressDemo(publicKey0); - if (!Arrays.equals(address0, address1)){ + if (!Arrays.equals(address0, address1)) { throw new CipherException("address error"); } System.out.println("Address: " + ByteArray.toHexString(address0)); String base58checkAddress0 = WalletApi.encode58Check(address0); String base58checkAddress1 = address2Encode58CheckDemo(address0); - if (!base58checkAddress0.equals(base58checkAddress1)){ + if (!base58checkAddress0.equals(base58checkAddress1)) { throw new CipherException("base58checkAddress error"); } @@ -90,6 +90,5 @@ public static void main(String[] args) throws CipherException { address = private2Address(null); System.out.println("base58Address: " + address); - } } diff --git a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java b/src/main/java/org/tron/demo/EasyTransferAssetDemo.java index b0fb8b911..772b0a79a 100644 --- a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java +++ b/src/main/java/org/tron/demo/EasyTransferAssetDemo.java @@ -2,7 +2,7 @@ import org.tron.api.GrpcAPI.EasyTransferResponse; import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; import org.tron.protos.Protocol.Transaction; @@ -13,7 +13,7 @@ public class EasyTransferAssetDemo { private static byte[] getAddressByPassphrase(String passPhrase) { - byte[] privateKey = Sha256Hash.hash(passPhrase.getBytes()); + byte[] privateKey = Sha256Sm3Hash.hash(passPhrase.getBytes()); ECKey ecKey = ECKey.fromPrivate(privateKey); byte[] address = ecKey.getAddress(); return address; @@ -28,9 +28,9 @@ public static void main(String[] args) { } System.out.println("address === " + WalletApi.encode58Check(address)); - EasyTransferResponse response = WalletApi - .easyTransferAsset(passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), - tokenId, 10000L); + EasyTransferResponse response = + WalletApi.easyTransferAsset( + passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), tokenId, 10000L); if (response.getResult().getResult() == true) { Transaction transaction = response.getTransaction(); System.out.println("Easy transfer successful!!!"); diff --git a/src/main/java/org/tron/demo/EasyTransferDemo.java b/src/main/java/org/tron/demo/EasyTransferDemo.java index 8b7827307..350d244a1 100644 --- a/src/main/java/org/tron/demo/EasyTransferDemo.java +++ b/src/main/java/org/tron/demo/EasyTransferDemo.java @@ -2,7 +2,7 @@ import org.tron.api.GrpcAPI.EasyTransferResponse; import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; import org.tron.protos.Protocol.Transaction; @@ -13,7 +13,7 @@ public class EasyTransferDemo { private static byte[] getAddressByPassphrase(String passPhrase) { - byte[] privateKey = Sha256Hash.hash(passPhrase.getBytes()); + byte[] privateKey = Sha256Sm3Hash.hash(passPhrase.getBytes()); ECKey ecKey = ECKey.fromPrivate(privateKey); byte[] address = ecKey.getAddress(); return address; @@ -27,8 +27,9 @@ public static void main(String[] args) { } System.out.println("address === " + WalletApi.encode58Check(address)); - EasyTransferResponse response = WalletApi - .easyTransfer(passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), 10000L); + EasyTransferResponse response = + WalletApi.easyTransfer( + passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), 10000L); if (response.getResult().getResult() == true) { Transaction transaction = response.getTransaction(); System.out.println("Easy transfer successful!!!"); diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 1ecbf5792..6a45bff54 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -6,7 +6,7 @@ import org.tron.api.GrpcAPI.Return; import org.tron.api.GrpcAPI.TransactionExtention; import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; import org.tron.protos.Contract; @@ -22,30 +22,32 @@ public static Transaction setReference(Transaction transaction, Block newestBloc long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); + Transaction.raw rawData = + transaction + .getRawData() + .toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); return transaction.toBuilder().setRawData(rawData).build(); } - public static Sha256Hash getBlockHash(Block block) { - return Sha256Hash.of(block.getBlockHeader().getRawData().toByteArray()); + public static Sha256Sm3Hash getBlockHash(Block block) { + return Sha256Sm3Hash.of(block.getBlockHeader().getRawData().toByteArray()); } public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + String txid = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray())); return txid; } - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { Transaction.Builder transactionBuilder = Transaction.newBuilder(); Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract - .newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = + Contract.TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); @@ -58,21 +60,23 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) + transactionBuilder + .getRawDataBuilder() + .addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration( + newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; } - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { ECKey ecKey = ECKey.fromPrivate(privateKey); Transaction transaction1 = Transaction.parseFrom(transaction); byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = Sha256Hash.hash(rawdata); + byte[] hash = Sha256Sm3Hash.hash(rawdata); byte[] sign = ecKey.sign(hash).toByteArray(); return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); } @@ -82,7 +86,7 @@ private static Transaction signTransaction2Object(byte[] transaction, byte[] pri ECKey ecKey = ECKey.fromPrivate(privateKey); Transaction transaction1 = Transaction.parseFrom(transaction); byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = Sha256Hash.hash(rawdata); + byte[] hash = Sha256Sm3Hash.hash(rawdata); byte[] sign = ecKey.sign(hash).toByteArray(); return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); } @@ -109,7 +113,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca ECKey ecKey = ECKey.fromPrivate(privateBytes); byte[] from = ecKey.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); @@ -125,12 +129,13 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca System.out.println("transaction3 ::::: " + ByteArray.toHexString(transaction3.toByteArray())); */ - //sign a transaction in byte format and return a Transaction in byte format + // sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); System.out.println("transaction4 ::::: " + ByteArray.toHexString(transaction4)); Transaction transactionSigned; if (WalletApi.getRpcVersion() == 2) { - TransactionExtention transactionExtention = WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); + TransactionExtention transactionExtention = + WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); if (transactionExtention == null) { System.out.println("transactionExtention is null"); return; @@ -149,7 +154,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca } byte[] transaction5 = transactionSigned.toByteArray(); System.out.println("transaction5 ::::: " + ByteArray.toHexString(transaction5)); - if (!Arrays.equals(transaction4, transaction5)){ + if (!Arrays.equals(transaction4, transaction5)) { System.out.println("transaction4 is not equals to transaction5 !!!!!"); } boolean result = broadcast(transaction4); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index fc57493a5..2c0ca5c89 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -3,10 +3,7 @@ import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.TransactionExtention; - -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; @@ -15,38 +12,38 @@ import org.tron.protos.Protocol.Transaction; import org.tron.walletserver.WalletApi; -import java.util.Arrays; - public class TransactionSignDemoForSM2 { public static Transaction setReference(Transaction transaction, Block newestBlock) { long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); + Transaction.raw rawData = + transaction + .getRawData() + .toBuilder() + .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) + .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) + .build(); return transaction.toBuilder().setRawData(rawData).build(); } - public static Sha256Hash getBlockHash(Block block) { - return Sha256Hash.of(block.getBlockHeader().getRawData().toByteArray()); + public static Sha256Sm3Hash getBlockHash(Block block) { + return Sha256Sm3Hash.of(block.getBlockHeader().getRawData().toByteArray()); } public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray())); + String txid = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray())); return txid; } - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { Transaction.Builder transactionBuilder = Transaction.newBuilder(); Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract - .newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = + Contract.TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); @@ -59,15 +56,17 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) + transactionBuilder + .getRawDataBuilder() + .addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration( + newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; } - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException { SM2 sm2 = SM2.fromPrivate(privateKey); @@ -110,17 +109,14 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); - long amount = 100_000_000L; //100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); - - //sign a transaction in byte format and return a Transaction in byte format + // sign a transaction in byte format and return a Transaction in byte format byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); boolean result = broadcast(transaction4); System.out.println(result); } - - } diff --git a/src/main/java/org/tron/keystore/SKeyEncryptor.java b/src/main/java/org/tron/keystore/SKeyEncryptor.java index ad56b1918..adf36a39e 100644 --- a/src/main/java/org/tron/keystore/SKeyEncryptor.java +++ b/src/main/java/org/tron/keystore/SKeyEncryptor.java @@ -5,7 +5,7 @@ import org.bouncycastle.crypto.generators.SCrypt; import org.bouncycastle.crypto.params.KeyParameter; import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CipherException; import org.tron.walletserver.WalletApi; @@ -50,29 +50,25 @@ public static SKeyCapsule create(byte[] password, byte[] skey, int n, int p) byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); - byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, - skey); + byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, skey); byte[] mac = generateMac(derivedKey, cipherText); - byte[] fp = Arrays.copyOfRange(Sha256Hash.hash(skey),0, 4); + byte[] fp = Arrays.copyOfRange(Sha256Sm3Hash.hash(skey), 0, 4); return createSkey(fp, cipherText, iv, salt, mac, n, p); } - public static SKeyCapsule createStandard(byte[] password, byte[] skey) - throws CipherException { + public static SKeyCapsule createStandard(byte[] password, byte[] skey) throws CipherException { return create(password, skey, N_STANDARD, P_STANDARD); } - public static SKeyCapsule createLight(byte[] password, byte[] skey) - throws CipherException { + public static SKeyCapsule createLight(byte[] password, byte[] skey) throws CipherException { return create(password, skey, N_LIGHT, P_LIGHT); } private static SKeyCapsule createSkey( - byte[] fp, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, - int n, int p) { + byte[] fp, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, int n, int p) { SKeyCapsule skey = new SKeyCapsule(); skey.setFp(WalletApi.encode58Check(fp)); @@ -108,11 +104,11 @@ private static byte[] generateDerivedScryptKey( return SCrypt.generate(password, salt, n, r, p, dkLen); } - private static byte[] generateAes128CtrDerivedKey( - byte[] password, byte[] salt, int c, String prf) throws CipherException { + private static byte[] generateAes128CtrDerivedKey(byte[] password, byte[] salt, int c, String prf) + throws CipherException { if (!prf.equals("hmac-sha256")) { - throw new CipherException("Unsupported prf:" + prf); + throw new CipherException("Unsupported prf:" + prf); } // Java 8 supports this, but you have to convert the password to a character array, see @@ -123,8 +119,8 @@ private static byte[] generateAes128CtrDerivedKey( return ((KeyParameter) gen.generateDerivedParameters(256)).getKey(); } - private static byte[] performCipherOperation( - int mode, byte[] iv, byte[] encryptKey, byte[] text) throws CipherException { + private static byte[] performCipherOperation(int mode, byte[] iv, byte[] encryptKey, byte[] text) + throws CipherException { try { IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); @@ -133,9 +129,12 @@ private static byte[] performCipherOperation( SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey, "AES"); cipher.init(mode, secretKeySpec, ivParameterSpec); return cipher.doFinal(text); - } catch (NoSuchPaddingException | NoSuchAlgorithmException - | InvalidAlgorithmParameterException | InvalidKeyException - | BadPaddingException | IllegalBlockSizeException e) { + } catch (NoSuchPaddingException + | NoSuchAlgorithmException + | InvalidAlgorithmParameterException + | InvalidKeyException + | BadPaddingException + | IllegalBlockSizeException e) { throw new CipherException("Error performing cipher operation", e); } } @@ -198,8 +197,7 @@ public static byte[] decrypt2PrivateBytes(byte[] password, SKeyCapsule skey) return privateKey; } - public static boolean validPassword (byte[] password, SKeyCapsule skey) - throws CipherException { + public static boolean validPassword(byte[] password, SKeyCapsule skey) throws CipherException { validate(skey); @@ -262,5 +260,4 @@ public static byte[] generateRandomBytes(int size) { new SecureRandom().nextBytes(bytes); return bytes; } - } diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 534a08a37..dd8daef29 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -6,7 +6,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; @@ -15,7 +15,6 @@ import org.tron.core.exception.CipherException; import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; -import org.tron.keystore.CredentialsEckey; import org.tron.keystore.WalletUtils; import org.tron.protos.Contract; import org.tron.protos.Contract.TransferContract; @@ -45,13 +44,12 @@ public static Transaction createTransactionEx(String toAddress, long amount) { for (int i = 0; i < 10; i++) { Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract - .newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = + Contract.TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); - ByteString bsTo = ByteString.copyFrom(ByteArray - .fromHexString(toAddress)); - ByteString bsOwner = ByteString.copyFrom(ByteArray - .fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); + ByteString bsTo = ByteString.copyFrom(ByteArray.fromHexString(toAddress)); + ByteString bsOwner = + ByteString.copyFrom(ByteArray.fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); transferContractBuilder.setToAddress(bsTo); transferContractBuilder.setOwnerAddress(bsOwner); try { @@ -71,33 +69,34 @@ public static Transaction createTransactionEx(String toAddress, long amount) { return transaction; } -// public static Transaction createTransactionAccount() { -// Transaction.Builder transactionBuilder = Transaction.newBuilder(); -// -// for (int i = 0; i < 10; i++) { -// Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); -// Contract.AccountCreateContract.Builder accountCreateContract = Contract.AccountCreateContract -// .newBuilder(); -// accountCreateContract.setAccountName(ByteString.copyFrom("zawtest".getBytes())); -// -// ByteString bsOwner = ByteString.copyFrom(ByteArray -// .fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); -// accountCreateContract.setOwnerAddress(bsOwner); -// try { -// Any anyTo = Any.pack(accountCreateContract.build()); -// contractBuilder.setParameter(anyTo); -// } catch (Exception e) { -// return null; -// } -// contractBuilder.setType(Transaction.Contract.ContractType.AccountCreateContract); -// -// transactionBuilder.getRawDataBuilder().addContract(contractBuilder); -// } -// transactionBuilder.getRawDataBuilder(); -// -// Transaction transaction = transactionBuilder.build(); -// return transaction; -// } + // public static Transaction createTransactionAccount() { + // Transaction.Builder transactionBuilder = Transaction.newBuilder(); + // + // for (int i = 0; i < 10; i++) { + // Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); + // Contract.AccountCreateContract.Builder accountCreateContract = + // Contract.AccountCreateContract + // .newBuilder(); + // accountCreateContract.setAccountName(ByteString.copyFrom("zawtest".getBytes())); + // + // ByteString bsOwner = ByteString.copyFrom(ByteArray + // .fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); + // accountCreateContract.setOwnerAddress(bsOwner); + // try { + // Any anyTo = Any.pack(accountCreateContract.build()); + // contractBuilder.setParameter(anyTo); + // } catch (Exception e) { + // return null; + // } + // contractBuilder.setType(Transaction.Contract.ContractType.AccountCreateContract); + // + // transactionBuilder.getRawDataBuilder().addContract(contractBuilder); + // } + // transactionBuilder.getRawDataBuilder(); + // + // Transaction transaction = transactionBuilder.build(); + // return transaction; + // } public static void test64() throws UnsupportedEncodingException { Encoder encoder = Base64.getEncoder(); @@ -116,7 +115,6 @@ public static void testDecode64() System.out.println("address::::" + ByteArray.toHexString(account.getAddress().toByteArray())); } - public static void testECKey() { Transaction transaction = createTransactionEx("e1a17255ccf15d6b12dcc074ca1152477ccf9b84", 10); byte[] bytes = transaction.toByteArray(); @@ -128,10 +126,12 @@ public static void testECKey() { System.out.println("prikey ::: " + ByteArray.toHexString(priKey)); System.out.println("pubKey ::: " + ByteArray.toHexString(pubKey)); System.out.println("addresss ::: " + ByteArray.toHexString(addresss)); - byte[] msg = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15}; + byte[] msg = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15 + }; - byte[] sha256 = Sha256Hash.hash(msg); + byte[] sha256 = Sha256Sm3Hash.hash(msg); ECDSASignature signature = eCkey.sign(sha256); System.out.println("hash:::" + ByteArray.toHexString(sha256)); @@ -140,44 +140,45 @@ public static void testECKey() { System.out.println("id:::" + signature.v); } -// public static void testTransaction() { -// Transaction transaction = createTransactionAccount(); -// String priKey = "8e812436a0e3323166e1f0e8ba79e19e217b2c4a53c970d4cca0cfb1078979df"; -// -// ECKey eCkey = null; -// try { -// BigInteger priK = new BigInteger(priKey, 16); -// eCkey = ECKey.fromPrivate(priK); -// } catch (Exception ex) { -// ex.printStackTrace(); -// } -// byte[] msg = transaction.getRawData().toByteArray(); -// byte[] sha256 = Hash.sha256(msg); -// eCkey.sign(sha256); -// -// ECDSASignature signature = eCkey.sign(sha256); -// -// System.out.println("msg:::" + ByteArray.toHexString(msg)); -// System.out.println("priKey:::" + ByteArray.toHexString(eCkey.getPrivKeyBytes())); -// System.out.println("pubKey::" + ByteArray.toHexString(eCkey.getPubKey())); -// System.out.println("hash:::" + ByteArray.toHexString(sha256)); -// System.out.println("r:::" + ByteArray.toHexString(signature.r.toByteArray())); -// System.out.println("s:::" + ByteArray.toHexString(signature.s.toByteArray())); -// System.out.println("id:::" + signature.v); -// } + // public static void testTransaction() { + // Transaction transaction = createTransactionAccount(); + // String priKey = "8e812436a0e3323166e1f0e8ba79e19e217b2c4a53c970d4cca0cfb1078979df"; + // + // ECKey eCkey = null; + // try { + // BigInteger priK = new BigInteger(priKey, 16); + // eCkey = ECKey.fromPrivate(priK); + // } catch (Exception ex) { + // ex.printStackTrace(); + // } + // byte[] msg = transaction.getRawData().toByteArray(); + // byte[] sha256 = Hash.sha256(msg); + // eCkey.sign(sha256); + // + // ECDSASignature signature = eCkey.sign(sha256); + // + // System.out.println("msg:::" + ByteArray.toHexString(msg)); + // System.out.println("priKey:::" + ByteArray.toHexString(eCkey.getPrivKeyBytes())); + // System.out.println("pubKey::" + ByteArray.toHexString(eCkey.getPubKey())); + // System.out.println("hash:::" + ByteArray.toHexString(sha256)); + // System.out.println("r:::" + ByteArray.toHexString(signature.r.toByteArray())); + // System.out.println("s:::" + ByteArray.toHexString(signature.s.toByteArray())); + // System.out.println("id:::" + signature.v); + // } public static void testVerify() { String hashBytes = "630211D6CA9440639F4965AA24831EB84815AB6BEF11E8BE6962A8540D861339"; String priKeyBytes = "8E812436A0E3323166E1F0E8BA79E19E217B2C4A53C970D4CCA0CFB1078979DF"; - String sign = "1D89243F93670AA2F209FD1E0BDACA67E327B78FA54D728628F4EBBF6B7917E5BB0642717EC2234D21BEFAA7577D5FC6B4D47C94F2C0618862CD4C9E3C839C464"; + String sign = + "1D89243F93670AA2F209FD1E0BDACA67E327B78FA54D728628F4EBBF6B7917E5BB0642717EC2234D21BEFAA7577D5FC6B4D47C94F2C0618862CD4C9E3C839C464"; ECKey eCkey = null; String signatureBase64 = ""; try { - signatureBase64 = new String(Base64.getEncoder().encode(ByteArray.fromHexString(sign)), - "UTF-8"); + signatureBase64 = + new String(Base64.getEncoder().encode(ByteArray.fromHexString(sign)), "UTF-8"); - byte[] pubKey = ECKey - .signatureToKeyBytes(ByteArray.fromHexString(hashBytes), signatureBase64); + byte[] pubKey = + ECKey.signatureToKeyBytes(ByteArray.fromHexString(hashBytes), signatureBase64); System.out.println("pubKey::" + ByteArray.toHexString(pubKey)); } catch (Exception ex) { ex.printStackTrace(); @@ -199,8 +200,8 @@ public static void testGenKey() { byte[] hash = Hash.sha3(Arrays.copyOfRange(pubKey, 1, pubKey.length)); byte[] hash_ = Hash.sha3(pubKey); byte[] address = eCkey.getAddress(); - byte[] hash0 = Sha256Hash.hash(address); - byte[] hash1 = Sha256Hash.hash(hash0); + byte[] hash0 = Sha256Sm3Hash.hash(address); + byte[] hash1 = Sha256Sm3Hash.hash(hash0); byte[] checkSum = Arrays.copyOfRange(hash1, 0, 4); byte[] addchecksum = new byte[address.length + 4]; System.arraycopy(address, 0, addchecksum, 0, address.length); @@ -224,38 +225,10 @@ public static void testGenKey() { public static void testSignEx() { byte[] priKey = { - 0, - 69, - 242 - 256, - 238 - 256, - 134 - 256, - 57, - 112, - 55, - 243 - 256, - 242 - 256, - 121, - 104, - 182 - 256, - 252 - 256, - 247 - 256, - 242 - 256, - 107, - 230 - 256, - 211 - 256, - 208 - 256, - 167 - 256, - 194 - 256, - 18, - 229 - 256, - 160 - 256, - 229 - 256, - 91, - 179 - 256, - 48, - 96, - 209 - 256, - 78}; + 0, 69, 242 - 256, 238 - 256, 134 - 256, 57, 112, 55, 243 - 256, 242 - 256, 121, 104, + 182 - 256, 252 - 256, 247 - 256, 242 - 256, 107, 230 - 256, 211 - 256, 208 - 256, 167 - 256, + 194 - 256, 18, 229 - 256, 160 - 256, 229 - 256, 91, 179 - 256, 48, 96, 209 - 256, 78 + }; ECKey eCkey = null; try { @@ -271,7 +244,6 @@ public static void testSignEx() { System.out.println("prikey ::: " + ByteArray.toHexString(priKey1)); System.out.println("pubKey ::: " + ByteArray.toHexString(pubKey)); System.out.println("addresss ::: " + ByteArray.toHexString(addresss)); - } public static void testBase58() { @@ -319,20 +291,20 @@ public static void testSha3() { public static void testGenerateWalletFile() throws CipherException, IOException { String PASSWORD = "Insecure Pa55w0rd"; String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; -// ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); + // ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(priKeyHex)); File file = new File("out"); String fileName = WalletUtils.generateWalletFile(PASSWORD.getBytes(), sm2, file, true); - Credentials credentials = WalletUtils.loadCredentials(PASSWORD.getBytes(), new File(file, fileName)); + Credentials credentials = + WalletUtils.loadCredentials(PASSWORD.getBytes(), new File(file, fileName)); String address = credentials.getAddress(); SignInterface pair = credentials.getPair(); String prikey = ByteArray.toHexString(pair.getPrivKeyBytes()); System.out.println("address = " + address); System.out.println("prikey = " + prikey); - } - public static void testPasswordStrength(){ + public static void testPasswordStrength() { List passwordList = new ArrayList(); passwordList.add("ZAQ!xsw2"); passwordList.add("3EDC4rfv"); @@ -371,6 +343,7 @@ public static void interfaceTest() { String address = WalletApi.encode58Check(sm2.getAddress()); System.out.println(address); } + public static void main(String[] args) throws Exception { testPasswordStrength(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index f1b4449d4..b3027f0e4 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -21,6 +21,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Hash; +import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; @@ -57,17 +58,17 @@ public class WalletApi { private static GrpcClient rpcCli = init(); -// static { -// new Timer().schedule(new TimerTask() { -// @Override -// public void run() { -// String fullnode = selectFullNode(); -// if(!"".equals(fullnode)) { -// rpcCli = new GrpcClient(fullnode); -// } -// } -// }, 3 * 60 * 1000, 3 * 60 * 1000); -// } + // static { + // new Timer().schedule(new TimerTask() { + // @Override + // public void run() { + // String fullnode = selectFullNode(); + // if(!"".equals(fullnode)) { + // rpcCli = new GrpcClient(fullnode); + // } + // } + // }, 3 * 60 * 1000, 3 * 60 * 1000); + // } public static GrpcClient init() { Config config = Configuration.getByPath("config.conf"); @@ -140,9 +141,7 @@ public static int getRpcVersion() { return rpcVersion; } - /** - * Creates a new WalletApi with a random ECKey or no ECKey. - */ + /** Creates a new WalletApi with a random ECKey or no ECKey. */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -157,7 +156,7 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio // Create Wallet with a pritKey public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { - WalletFile walletFile=null; + WalletFile walletFile = null; if (isEckey) { ECKey ecKey = ECKey.fromPrivate(priKey); walletFile = Wallet.createStandard(password, ecKey); @@ -186,9 +185,7 @@ public boolean checkPassword(byte[] passwd) throws CipherException { return Wallet.validPassword(passwd, this.walletFile.get(0)); } - /** - * Creates a Wallet with an existing ECKey. - */ + /** Creates a Wallet with an existing ECKey. */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -313,7 +310,6 @@ public static boolean changeKeystorePassword(byte[] oldPassword, byte[] newPasso return true; } - private static WalletFile loadWalletFile() throws IOException { File wallet = selcetWalletFile(); if (wallet == null) { @@ -323,11 +319,8 @@ private static WalletFile loadWalletFile() throws IOException { return WalletUtils.loadWalletFile(wallet); } - /** - * load a Wallet from keystore - */ - public static WalletApi loadWalletFromKeystore() - throws IOException { + /** load a Wallet from keystore */ + public static WalletApi loadWalletFromKeystore() throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); return walletApi; @@ -338,7 +331,7 @@ public Account queryAccount() { } public static Account queryAccount(byte[] address) { - return rpcCli.queryAccount(address);//call rpc + return rpcCli.queryAccount(address); // call rpc } public static Account queryAccountById(String accountId) { @@ -380,9 +373,9 @@ private Transaction signTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } -// System.out -// .println("current transaction hex string is " + ByteArray -// .toHexString(transaction.toByteArray())); + // System.out + // .println("current transaction hex string is " + ByteArray + // .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -422,9 +415,9 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } -// System.out -// .println("current transaction hex string is " + ByteArray -// .toHexString(transaction.toByteArray())); + // System.out + // .println("current transaction hex string is " + ByteArray + // .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -463,13 +456,14 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } if (transaction.getRawData().getContract(0).getType() - == ContractType.ShieldedTransferContract) { + == ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); - System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -477,23 +471,24 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { - System.out.println("after sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); - System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println( + "after sign transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "txid is " + + ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { - CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) - .getParameter().unpack(CreateSmartContract.class); - byte[] contractAddress = generateContractAddress( - createSmartContract.getOwnerAddress().toByteArray(), transaction); + CreateSmartContract createSmartContract = + transaction.getRawData().getContract(0).getParameter().unpack(CreateSmartContract.class); + byte[] contractAddress = + generateContractAddress(createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); } } - private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + private static boolean processShieldedTransaction( + TransactionExtention transactionExtention, WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -511,7 +506,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti } if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { return false; } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); @@ -527,10 +522,11 @@ private static boolean processShieldedTransaction(TransactionExtention transacti transaction = wallet.signOnlyForShieldedTransaction(transaction); } - System.out.println("transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); - System.out.println("txid is " + - ByteArray.toHexString(Sha256Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println( + "transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "txid is " + + ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); return rpcCli.broadcastTransaction(transaction); } @@ -542,8 +538,9 @@ private boolean processTransaction(Transaction transaction) } System.out.println(Utils.printTransactionExceptId(transaction)); - System.out.println("before sign transaction hex string is " + - ByteArray.toHexString(transaction.toByteArray())); + System.out.println( + "before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); @@ -551,7 +548,7 @@ private boolean processTransaction(Transaction transaction) return rpcCli.broadcastTransaction(transaction); } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); @@ -563,9 +560,9 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p return rpcCli.signTransaction(builder.build()); } - //Warning: do not invoke this interface provided by others. - public static TransactionExtention signTransactionByApi2(Transaction transaction, - byte[] privateKey) throws CancelException { + // Warning: do not invoke this interface provided by others. + public static TransactionExtention signTransactionByApi2( + Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -575,9 +572,9 @@ public static TransactionExtention signTransactionByApi2(Transaction transaction return rpcCli.signTransaction2(builder.build()); } - //Warning: do not invoke this interface provided by others. - public static TransactionExtention addSignByApi(Transaction transaction, - byte[] privateKey) throws CancelException { + // Warning: do not invoke this interface provided by others. + public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) + throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); System.out.println("Please input permission id."); transaction = TransactionUtils.setPermissionId(transaction); @@ -595,32 +592,32 @@ public static TransactionApprovedList getTransactionApprovedList(Transaction tra return rpcCli.getTransactionApprovedList(transaction); } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static byte[] createAdresss(byte[] passPhrase) { return rpcCli.createAdresss(passPhrase); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, - long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransfer( + byte[] passPhrase, byte[] toAddress, long amount) { return rpcCli.easyTransfer(passPhrase, toAddress, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferByPrivate( + byte[] privateKey, byte[] toAddress, long amount) { return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAsset( + byte[] passPhrase, byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); } - //Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, - byte[] toAddress, String assetId, long amount) { + // Warning: do not invoke this interface provided by others. + public static EasyTransferResponse easyTransferAssetByPrivate( + byte[] privateKey, byte[] toAddress, String assetId, long amount) { return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); } @@ -671,16 +668,15 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) return processTransaction(transaction); } - - public boolean updateAsset(byte[] owner, byte[] description, byte[] url, long newLimit, - long newPublicLimit) + public boolean updateAsset( + byte[] owner, byte[] description, byte[] url, long newLimit, long newPublicLimit) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.UpdateAssetContract contract - = createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); + Contract.UpdateAssetContract contract = + createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -696,8 +692,8 @@ public boolean transferAsset(byte[] owner, byte[] to, byte[] assertName, long am owner = getAddress(); } - Contract.TransferAssetContract contract = createTransferAssetContract(to, assertName, owner, - amount); + Contract.TransferAssetContract contract = + createTransferAssetContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransferAssetTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -713,11 +709,11 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner = getAddress(); } - Contract.ParticipateAssetIssueContract contract = participateAssetIssueContract(to, assertName, - owner, amount); + Contract.ParticipateAssetIssueContract contract = + participateAssetIssueContract(to, assertName, owner, amount); if (rpcVersion == 2) { - TransactionExtention transactionExtention = rpcCli - .createParticipateAssetIssueTransaction2(contract); + TransactionExtention transactionExtention = + rpcCli.createParticipateAssetIssueTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createParticipateAssetIssueTransaction(contract); @@ -762,7 +758,7 @@ public boolean createAccount(byte[] owner, byte[] address) } } - //Warning: do not invoke this interface provided by others. + // Warning: do not invoke this interface provided by others. public static AddressPrKeyPairMessage generateAddress() { EmptyMessage.Builder builder = EmptyMessage.newBuilder(); return rpcCli.generateAddress(builder.build()); @@ -828,8 +824,8 @@ public boolean voteWitness(byte[] owner, HashMap witness) } } - public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner, - long amount) { + public static Contract.TransferContract createTransferContract( + byte[] to, byte[] owner, long amount) { Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); @@ -840,9 +836,8 @@ public static Contract.TransferContract createTransferContract(byte[] to, byte[] return builder.build(); } - public static Contract.TransferAssetContract createTransferAssetContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { + public static Contract.TransferAssetContract createTransferAssetContract( + byte[] to, byte[] assertName, byte[] owner, long amount) { Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); @@ -855,11 +850,10 @@ public static Contract.TransferAssetContract createTransferAssetContract(byte[] return builder.build(); } - public static Contract.ParticipateAssetIssueContract participateAssetIssueContract(byte[] to, - byte[] assertName, byte[] owner, - long amount) { - Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract - .newBuilder(); + public static Contract.ParticipateAssetIssueContract participateAssetIssueContract( + byte[] to, byte[] assertName, byte[] owner, long amount) { + Contract.ParticipateAssetIssueContract.Builder builder = + Contract.ParticipateAssetIssueContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -871,8 +865,8 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra return builder.build(); } - public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName, - byte[] address) { + public static Contract.AccountUpdateContract createAccountUpdateContract( + byte[] accountName, byte[] address) { Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); @@ -882,8 +876,8 @@ public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] return builder.build(); } - public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] accountId, - byte[] address) { + public static Contract.SetAccountIdContract createSetAccountIdContract( + byte[] accountId, byte[] address) { Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); @@ -893,16 +887,9 @@ public static Contract.SetAccountIdContract createSetAccountIdContract(byte[] ac return builder.build(); } - public static Contract.UpdateAssetContract createUpdateAssetContract( - byte[] address, - byte[] description, - byte[] url, - long newLimit, - long newPublicLimit - ) { - Contract.UpdateAssetContract.Builder builder = - Contract.UpdateAssetContract.newBuilder(); + byte[] address, byte[] description, byte[] url, long newLimit, long newPublicLimit) { + Contract.UpdateAssetContract.Builder builder = Contract.UpdateAssetContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); builder.setDescription(ByteString.copyFrom(description)); builder.setUrl(ByteString.copyFrom(url)); @@ -913,8 +900,8 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( return builder.build(); } - public static Contract.AccountCreateContract createAccountCreateContract(byte[] owner, - byte[] address) { + public static Contract.AccountCreateContract createAccountCreateContract( + byte[] owner, byte[] address) { Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); @@ -922,8 +909,8 @@ public static Contract.AccountCreateContract createAccountCreateContract(byte[] return builder.build(); } - public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner, - byte[] url) { + public static Contract.WitnessCreateContract createWitnessCreateContract( + byte[] owner, byte[] url) { Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); @@ -931,8 +918,8 @@ public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] return builder.build(); } - public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner, - byte[] url) { + public static Contract.WitnessUpdateContract createWitnessUpdateContract( + byte[] owner, byte[] url) { Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); @@ -940,15 +927,15 @@ public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] return builder.build(); } - public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner, - HashMap witness) { + public static Contract.VoteWitnessContract createVoteWitnessContract( + byte[] owner, HashMap witness) { Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); - Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote - .newBuilder(); + Contract.VoteWitnessContract.Vote.Builder voteBuilder = + Contract.VoteWitnessContract.Vote.newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -969,7 +956,7 @@ public static boolean passwordValid(char[] password) { System.out.println("Warning: Password is too short !!"); return false; } - //Other rule; + // Other rule; int level = CheckStrength.checkPasswordStrength(password); if (level <= 4) { System.out.println("Your password is too weak!"); @@ -993,18 +980,24 @@ public static boolean addressValid(byte[] address) { } if (address.length != CommonConstant.ADDRESS_SIZE) { System.out.println( - "Warning: Address length need " + CommonConstant.ADDRESS_SIZE + " but " + address.length + "Warning: Address length need " + + CommonConstant.ADDRESS_SIZE + + " but " + + address.length + " !!"); return false; } byte preFixbyte = address[0]; if (preFixbyte != WalletApi.getAddressPreFixByte()) { - System.out - .println("Warning: Address need prefix with " + WalletApi.getAddressPreFixByte() + " but " - + preFixbyte + " !!"); + System.out.println( + "Warning: Address need prefix with " + + WalletApi.getAddressPreFixByte() + + " but " + + preFixbyte + + " !!"); return false; } - //Other rule; + // Other rule; return true; } @@ -1026,10 +1019,10 @@ private static byte[] decode58Check(String input) { System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length); byte[] hash0 = Sha256Hash.hash(decodeData); byte[] hash1 = Sha256Hash.hash(hash0); - if (hash1[0] == decodeCheck[decodeData.length] && - hash1[1] == decodeCheck[decodeData.length + 1] && - hash1[2] == decodeCheck[decodeData.length + 2] && - hash1[3] == decodeCheck[decodeData.length + 3]) { + if (hash1[0] == decodeCheck[decodeData.length] + && hash1[1] == decodeCheck[decodeData.length + 1] + && hash1[2] == decodeCheck[decodeData.length + 2] + && hash1[3] == decodeCheck[decodeData.length + 3]) { return decodeData; } return null; @@ -1056,24 +1049,24 @@ public static boolean priKeyValid(byte[] priKey) { System.out.println("Warning: PrivateKey length need 64 but " + priKey.length + " !!"); return false; } - //Other rule; + // Other rule; return true; } -// public static Optional listAccounts() { -// Optional result = rpcCli.listAccounts(); -// if (result.isPresent()) { -// AccountList accountList = result.get(); -// List list = accountList.getAccountsList(); -// List newList = new ArrayList(); -// newList.addAll(list); -// newList.sort(new AccountComparator()); -// AccountList.Builder builder = AccountList.newBuilder(); -// newList.forEach(account -> builder.addAccounts(account)); -// result = Optional.of(builder.build()); -// } -// return result; -// } + // public static Optional listAccounts() { + // Optional result = rpcCli.listAccounts(); + // if (result.isPresent()) { + // AccountList accountList = result.get(); + // List list = accountList.getAccountsList(); + // List newList = new ArrayList(); + // newList.addAll(list); + // newList.sort(new AccountComparator()); + // AccountList.Builder builder = AccountList.newBuilder(); + // newList.forEach(account -> builder.addAccounts(account)); + // result = Optional.of(builder.build()); + // } + // return result; + // } public static Optional listWitnesses() { Optional result = rpcCli.listWitnesses(); @@ -1082,12 +1075,13 @@ public static Optional listWitnesses() { List list = witnessList.getWitnessesList(); List newList = new ArrayList<>(); newList.addAll(list); - newList.sort(new Comparator() { - @Override - public int compare(Witness o1, Witness o2) { - return Long.compare(o2.getVoteCount(), o1.getVoteCount()); - } - }); + newList.sort( + new Comparator() { + @Override + public int compare(Witness o1, Witness o2) { + return Long.compare(o2.getVoteCount(), o1.getVoteCount()); + } + }); WitnessList.Builder builder = WitnessList.newBuilder(); newList.forEach(witness -> builder.addWitnesses(witness)); result = Optional.of(builder.build()); @@ -1095,18 +1089,18 @@ public int compare(Witness o1, Witness o2) { return result; } -// public static Optional getAssetIssueListByTimestamp(long timestamp) { -// return rpcCli.getAssetIssueListByTimestamp(timestamp); -// } -// -// public static Optional getTransactionsByTimestamp(long start, long end, -// int offset, int limit) { -// return rpcCli.getTransactionsByTimestamp(start, end, offset, limit); -// } -// -// public static GrpcAPI.NumberMessage getTransactionsByTimestampCount(long start, long end) { -// return rpcCli.getTransactionsByTimestampCount(start, end); -// } + // public static Optional getAssetIssueListByTimestamp(long timestamp) { + // return rpcCli.getAssetIssueListByTimestamp(timestamp); + // } + // + // public static Optional getTransactionsByTimestamp(long start, long end, + // int offset, int limit) { + // return rpcCli.getTransactionsByTimestamp(start, end, offset, limit); + // } + // + // public static GrpcAPI.NumberMessage getTransactionsByTimestampCount(long start, long end) { + // return rpcCli.getTransactionsByTimestampCount(start, end); + // } public static Optional getAssetIssueList() { return rpcCli.getAssetIssueList(); @@ -1124,7 +1118,6 @@ public static Optional getExchangeListPaginated(long offset, long return rpcCli.getExchangeListPaginated(offset, limit); } - public static Optional listNodes() { return rpcCli.listNodes(); } @@ -1161,33 +1154,31 @@ public static GrpcAPI.NumberMessage getNextMaintenanceTime() { return rpcCli.getNextMaintenanceTime(); } - public static Optional getTransactionsFromThis(byte[] address, int offset, - int limit) { + public static Optional getTransactionsFromThis( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsFromThis(address, offset, limit); } - public static Optional getTransactionsFromThis2(byte[] address, - int offset, - int limit) { + public static Optional getTransactionsFromThis2( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsFromThis2(address, offset, limit); } -// public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { -// return rpcCli.getTransactionsFromThisCount(address); -// } + // public static GrpcAPI.NumberMessage getTransactionsFromThisCount(byte[] address) { + // return rpcCli.getTransactionsFromThisCount(address); + // } - public static Optional getTransactionsToThis(byte[] address, int offset, - int limit) { + public static Optional getTransactionsToThis( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsToThis(address, offset, limit); } - public static Optional getTransactionsToThis2(byte[] address, - int offset, - int limit) { + public static Optional getTransactionsToThis2( + byte[] address, int offset, int limit) { return rpcCli.getTransactionsToThis2(address, offset, limit); } -// public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { -// return rpcCli.getTransactionsToThisCount(address); -// } + // public static GrpcAPI.NumberMessage getTransactionsToThisCount(byte[] address) { + // return rpcCli.getTransactionsToThisCount(address); + // } public static Optional getTransactionById(String txID) { return rpcCli.getTransactionById(txID); @@ -1197,12 +1188,16 @@ public static Optional getTransactionInfoById(String txID) { return rpcCli.getTransactionInfoById(txID); } - public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long frozen_duration, - int resourceCode, byte[] receiverAddress) + public boolean freezeBalance( + byte[] ownerAddress, + long frozen_balance, + long frozen_duration, + int resourceCode, + byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.FreezeBalanceContract contract = createFreezeBalanceContract(ownerAddress, - frozen_balance, - frozen_duration, resourceCode, receiverAddress); + Contract.FreezeBalanceContract contract = + createFreezeBalanceContract( + ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1231,23 +1226,29 @@ public boolean sellStorage(byte[] ownerAddress, long storageBytes) Contract.SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); - } - private FreezeBalanceContract createFreezeBalanceContract(byte[] address, long frozen_balance, - long frozen_duration, int resourceCode, byte[] receiverAddress) { + private FreezeBalanceContract createFreezeBalanceContract( + byte[] address, + long frozen_balance, + long frozen_duration, + int resourceCode, + byte[] receiverAddress) { if (address == null) { address = getAddress(); } Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); - builder.setOwnerAddress(byteAddress).setFrozenBalance(frozen_balance) - .setFrozenDuration(frozen_duration).setResourceValue(resourceCode); + builder + .setOwnerAddress(byteAddress) + .setFrozenBalance(frozen_balance) + .setFrozenDuration(frozen_duration) + .setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = ByteString.copyFrom( - Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = + ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } return builder.build(); @@ -1270,8 +1271,8 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo address = getAddress(); } - Contract.BuyStorageBytesContract.Builder builder = Contract.BuyStorageBytesContract - .newBuilder(); + Contract.BuyStorageBytesContract.Builder builder = + Contract.BuyStorageBytesContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1292,8 +1293,8 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeBalanceContract contract = createUnfreezeBalanceContract(ownerAddress, - resourceCode, receiverAddress); + Contract.UnfreezeBalanceContract contract = + createUnfreezeBalanceContract(ownerAddress, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1303,21 +1304,20 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec } } - - private UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] address, int resourceCode, - byte[] receiverAddress) { + private UnfreezeBalanceContract createUnfreezeBalanceContract( + byte[] address, int resourceCode, byte[] receiverAddress) { if (address == null) { address = getAddress(); } - Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract - .newBuilder(); + Contract.UnfreezeBalanceContract.Builder builder = + Contract.UnfreezeBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); if (receiverAddress != null) { - ByteString receiverAddressBytes = ByteString.copyFrom( - Objects.requireNonNull(receiverAddress)); + ByteString receiverAddressBytes = + ByteString.copyFrom(Objects.requireNonNull(receiverAddress)); builder.setReceiverAddress(receiverAddressBytes); } @@ -1341,8 +1341,7 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { address = getAddress(); } - Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract - .newBuilder(); + Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1365,8 +1364,8 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { address = getAddress(); } - Contract.WithdrawBalanceContract.Builder builder = Contract.WithdrawBalanceContract - .newBuilder(); + Contract.WithdrawBalanceContract.Builder builder = + Contract.WithdrawBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1412,8 +1411,8 @@ public static Optional getProposal(String id) { return rpcCli.getProposal(id); } - public static Optional getDelegatedResource(String fromAddress, - String toAddress) { + public static Optional getDelegatedResource( + String fromAddress, String toAddress) { return rpcCli.getDelegatedResource(fromAddress, toAddress); } @@ -1434,9 +1433,8 @@ public static Optional getChainParameters() { return rpcCli.getChainParameters(); } - - public static Contract.ProposalCreateContract createProposalCreateContract(byte[] owner, - HashMap parametersMap) { + public static Contract.ProposalCreateContract createProposalCreateContract( + byte[] owner, HashMap parametersMap) { Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); @@ -1449,16 +1447,16 @@ public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) owner = getAddress(); } - Contract.ProposalApproveContract contract = createProposalApproveContract(owner, id, - is_add_approval); + Contract.ProposalApproveContract contract = + createProposalApproveContract(owner, id, is_add_approval); TransactionExtention transactionExtention = rpcCli.proposalApprove(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalApproveContract createProposalApproveContract(byte[] owner, - long id, boolean is_add_approval) { - Contract.ProposalApproveContract.Builder builder = Contract.ProposalApproveContract - .newBuilder(); + public static Contract.ProposalApproveContract createProposalApproveContract( + byte[] owner, long id, boolean is_add_approval) { + Contract.ProposalApproveContract.Builder builder = + Contract.ProposalApproveContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1476,30 +1474,38 @@ public boolean deleteProposal(byte[] owner, long id) return processTransactionExtention(transactionExtention); } - public static Contract.ProposalDeleteContract createProposalDeleteContract(byte[] owner, - long id) { + public static Contract.ProposalDeleteContract createProposalDeleteContract( + byte[] owner, long id) { Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); return builder.build(); } - public boolean exchangeCreate(byte[] owner, byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) + public boolean exchangeCreate( + byte[] owner, + byte[] firstTokenId, + long firstTokenBalance, + byte[] secondTokenId, + long secondTokenBalance) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeCreateContract contract = createExchangeCreateContract(owner, firstTokenId, - firstTokenBalance, secondTokenId, secondTokenBalance); + Contract.ExchangeCreateContract contract = + createExchangeCreateContract( + owner, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); TransactionExtention transactionExtention = rpcCli.exchangeCreate(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeCreateContract createExchangeCreateContract(byte[] owner, - byte[] firstTokenId, long firstTokenBalance, - byte[] secondTokenId, long secondTokenBalance) { + public static Contract.ExchangeCreateContract createExchangeCreateContract( + byte[] owner, + byte[] firstTokenId, + long firstTokenBalance, + byte[] secondTokenId, + long secondTokenBalance) { Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1516,14 +1522,14 @@ public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, lon owner = getAddress(); } - Contract.ExchangeInjectContract contract = createExchangeInjectContract(owner, exchangeId, - tokenId, quant); + Contract.ExchangeInjectContract contract = + createExchangeInjectContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeInject(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeInjectContract createExchangeInjectContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { + public static Contract.ExchangeInjectContract createExchangeInjectContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant) { Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) @@ -1539,16 +1545,16 @@ public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, l owner = getAddress(); } - Contract.ExchangeWithdrawContract contract = createExchangeWithdrawContract(owner, exchangeId, - tokenId, quant); + Contract.ExchangeWithdrawContract contract = + createExchangeWithdrawContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeWithdraw(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeWithdrawContract.Builder builder = Contract.ExchangeWithdrawContract - .newBuilder(); + public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant) { + Contract.ExchangeWithdrawContract.Builder builder = + Contract.ExchangeWithdrawContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1557,23 +1563,23 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract(b return builder.build(); } - public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, - long expected) + public boolean exchangeTransaction( + byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeTransactionContract contract = createExchangeTransactionContract(owner, - exchangeId, tokenId, quant, expected); + Contract.ExchangeTransactionContract contract = + createExchangeTransactionContract(owner, exchangeId, tokenId, quant, expected); TransactionExtention transactionExtention = rpcCli.exchangeTransaction(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeTransactionContract createExchangeTransactionContract(byte[] owner, - long exchangeId, byte[] tokenId, long quant, long expected) { - Contract.ExchangeTransactionContract.Builder builder = Contract.ExchangeTransactionContract - .newBuilder(); + public static Contract.ExchangeTransactionContract createExchangeTransactionContract( + byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) { + Contract.ExchangeTransactionContract.Builder builder = + Contract.ExchangeTransactionContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1583,7 +1589,6 @@ public static Contract.ExchangeTransactionContract createExchangeTransactionCont return builder.build(); } - public static SmartContract.ABI.Entry.EntryType getEntryType(String type) { switch (type) { case "constructor": @@ -1626,22 +1631,38 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { SmartContract.ABI.Builder abiBuilder = SmartContract.ABI.newBuilder(); for (int index = 0; index < jsonRoot.size(); index++) { JsonElement abiItem = jsonRoot.get(index); - boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null ? - abiItem.getAsJsonObject().get("anonymous").getAsBoolean() : false; - boolean constant = abiItem.getAsJsonObject().get("constant") != null ? - abiItem.getAsJsonObject().get("constant").getAsBoolean() : false; - String name = abiItem.getAsJsonObject().get("name") != null ? - abiItem.getAsJsonObject().get("name").getAsString() : null; - JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null ? - abiItem.getAsJsonObject().get("inputs").getAsJsonArray() : null; - JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null ? - abiItem.getAsJsonObject().get("outputs").getAsJsonArray() : null; - String type = abiItem.getAsJsonObject().get("type") != null ? - abiItem.getAsJsonObject().get("type").getAsString() : null; - boolean payable = abiItem.getAsJsonObject().get("payable") != null ? - abiItem.getAsJsonObject().get("payable").getAsBoolean() : false; - String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null ? - abiItem.getAsJsonObject().get("stateMutability").getAsString() : null; + boolean anonymous = + abiItem.getAsJsonObject().get("anonymous") != null + ? abiItem.getAsJsonObject().get("anonymous").getAsBoolean() + : false; + boolean constant = + abiItem.getAsJsonObject().get("constant") != null + ? abiItem.getAsJsonObject().get("constant").getAsBoolean() + : false; + String name = + abiItem.getAsJsonObject().get("name") != null + ? abiItem.getAsJsonObject().get("name").getAsString() + : null; + JsonArray inputs = + abiItem.getAsJsonObject().get("inputs") != null + ? abiItem.getAsJsonObject().get("inputs").getAsJsonArray() + : null; + JsonArray outputs = + abiItem.getAsJsonObject().get("outputs") != null + ? abiItem.getAsJsonObject().get("outputs").getAsJsonArray() + : null; + String type = + abiItem.getAsJsonObject().get("type") != null + ? abiItem.getAsJsonObject().get("type").getAsString() + : null; + boolean payable = + abiItem.getAsJsonObject().get("payable") != null + ? abiItem.getAsJsonObject().get("payable").getAsBoolean() + : false; + String stateMutability = + abiItem.getAsJsonObject().get("stateMutability") != null + ? abiItem.getAsJsonObject().get("stateMutability").getAsString() + : null; if (type == null) { System.out.println("No type!"); return null; @@ -1662,8 +1683,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (null != inputs) { for (int j = 0; j < inputs.size(); j++) { JsonElement inputItem = inputs.get(j); - if (inputItem.getAsJsonObject().get("name") == null || - inputItem.getAsJsonObject().get("type") == null) { + if (inputItem.getAsJsonObject().get("name") == null + || inputItem.getAsJsonObject().get("type") == null) { System.out.println("Input argument invalid due to no name or no type!"); return null; } @@ -1671,11 +1692,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String inputType = inputItem.getAsJsonObject().get("type").getAsString(); Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { - inputIndexed = Boolean - .valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + inputIndexed = + Boolean.valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = + SmartContract.ABI.Entry.Param.newBuilder(); paramBuilder.setIndexed(inputIndexed); paramBuilder.setName(inputName); paramBuilder.setType(inputType); @@ -1687,8 +1708,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { if (outputs != null) { for (int k = 0; k < outputs.size(); k++) { JsonElement outputItem = outputs.get(k); - if (outputItem.getAsJsonObject().get("name") == null || - outputItem.getAsJsonObject().get("type") == null) { + if (outputItem.getAsJsonObject().get("name") == null + || outputItem.getAsJsonObject().get("type") == null) { System.out.println("Output argument invalid due to no name or no type!"); return null; } @@ -1696,11 +1717,11 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { String outputType = outputItem.getAsJsonObject().get("type").getAsString(); Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { - outputIndexed = Boolean - .valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + outputIndexed = + Boolean.valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); } - SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param - .newBuilder(); + SmartContract.ABI.Entry.Param.Builder paramBuilder = + SmartContract.ABI.Entry.Param.newBuilder(); paramBuilder.setIndexed(outputIndexed); paramBuilder.setName(outputName); paramBuilder.setType(outputType); @@ -1720,8 +1741,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { return abiBuilder.build(); } - public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] owner, - byte[] contractAddress, long consumeUserResourcePercent) { + public static Contract.UpdateSettingContract createUpdateSettingContract( + byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) { Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -1731,32 +1752,37 @@ public static Contract.UpdateSettingContract createUpdateSettingContract(byte[] } public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract( - byte[] owner, - byte[] contractAddress, long originEnergyLimit) { + byte[] owner, byte[] contractAddress, long originEnergyLimit) { - Contract.UpdateEnergyLimitContract.Builder builder = Contract.UpdateEnergyLimitContract - .newBuilder(); + Contract.UpdateEnergyLimitContract.Builder builder = + Contract.UpdateEnergyLimitContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); return builder.build(); } - public static Contract.ClearABIContract createClearABIContract(byte[] owner, - byte[] contractAddress) { + public static Contract.ClearABIContract createClearABIContract( + byte[] owner, byte[] contractAddress) { - Contract.ClearABIContract.Builder builder = Contract.ClearABIContract - .newBuilder(); + Contract.ClearABIContract.Builder builder = Contract.ClearABIContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); } - public static CreateSmartContract createContractDeployContract(String contractName, - byte[] address, - String ABI, String code, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, - String libraryAddressPair, String compilerVersion) { + public static CreateSmartContract createContractDeployContract( + String contractName, + byte[] address, + String ABI, + String code, + long value, + long consumeUserResourcePercent, + long originEnergyLimit, + long tokenValue, + String tokenId, + String libraryAddressPair, + String compilerVersion) { SmartContract.ABI abi = jsonStr2ABI(ABI); if (abi == null) { System.out.println("abi is null"); @@ -1767,7 +1793,8 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setName(contractName); builder.setOriginAddress(ByteString.copyFrom(address)); builder.setAbi(abi); - builder.setConsumeUserResourcePercent(consumeUserResourcePercent) + builder + .setConsumeUserResourcePercent(consumeUserResourcePercent) .setOriginEnergyLimit(originEnergyLimit); if (value != 0) { @@ -1783,16 +1810,17 @@ public static CreateSmartContract createContractDeployContract(String contractNa builder.setBytecode(ByteString.copyFrom(byteCode)); CreateSmartContract.Builder createSmartContractBuilder = CreateSmartContract.newBuilder(); - createSmartContractBuilder.setOwnerAddress(ByteString.copyFrom(address)). - setNewContract(builder.build()); + createSmartContractBuilder + .setOwnerAddress(ByteString.copyFrom(address)) + .setNewContract(builder.build()); if (tokenId != null && !tokenId.equalsIgnoreCase("") && !tokenId.equalsIgnoreCase("#")) { createSmartContractBuilder.setCallTokenValue(tokenValue).setTokenId(Long.parseLong(tokenId)); } return createSmartContractBuilder.build(); } - private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, - String compilerVersion) { + private static byte[] replaceLibraryAddress( + String code, String libraryAddressPair, String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1807,21 +1835,22 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa String addr = cur.substring(lastPosition + 1); String libraryAddressHex; try { - libraryAddressHex = (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), - "US-ASCII")).substring(2); + libraryAddressHex = + (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), "US-ASCII")) + .substring(2); } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // now ignore + throw new RuntimeException(e); // now ignore } String beReplaced; if (compilerVersion == null) { - //old version + // old version String repeated = new String(new char[40 - libraryName.length() - 2]).replace("\0", "_"); beReplaced = "__" + libraryName + repeated; } else if (compilerVersion.equalsIgnoreCase("v5")) { - //0.5.4 version - String libraryNameKeccak256 = ByteArray - .toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + // 0.5.4 version + String libraryNameKeccak256 = + ByteArray.toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1834,9 +1863,13 @@ private static byte[] replaceLibraryAddress(String code, String libraryAddressPa return Hex.decode(code); } - public static Contract.TriggerSmartContract triggerCallContract(byte[] address, - byte[] contractAddress, - long callValue, byte[] data, long tokenValue, String tokenId) { + public static Contract.TriggerSmartContract triggerCallContract( + byte[] address, + byte[] contractAddress, + long callValue, + byte[] data, + long tokenValue, + String tokenId) { Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); @@ -1851,7 +1884,7 @@ public static Contract.TriggerSmartContract triggerCallContract(byte[] address, public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { // get tx hash - byte[] txRawDataHash = Sha256Hash.of(trx.getRawData().toByteArray()).getBytes(); + byte[] txRawDataHash = Sha256Sm3Hash.of(trx.getRawData().toByteArray()).getBytes(); // combine byte[] combined = new byte[txRawDataHash.length + ownerAddress.length]; @@ -1859,26 +1892,25 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { System.arraycopy(ownerAddress, 0, combined, txRawDataHash.length, ownerAddress.length); return Hash.sha3omit12(combined); - } - public boolean updateSetting(byte[] owner, byte[] contractAddress, - long consumeUserResourcePercent) + public boolean updateSetting( + byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - UpdateSettingContract updateSettingContract = createUpdateSettingContract(owner, - contractAddress, consumeUserResourcePercent); + UpdateSettingContract updateSettingContract = + createUpdateSettingContract(owner, contractAddress, consumeUserResourcePercent); TransactionExtention transactionExtention = rpcCli.updateSetting(updateSettingContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1892,24 +1924,21 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig owner = getAddress(); } - UpdateEnergyLimitContract updateEnergyLimitContract = createUpdateEnergyLimitContract( - owner, - contractAddress, originEnergyLimit); + UpdateEnergyLimitContract updateEnergyLimitContract = + createUpdateEnergyLimitContract(owner, contractAddress, originEnergyLimit); - TransactionExtention transactionExtention = rpcCli - .updateEnergyLimit(updateEnergyLimitContract); + TransactionExtention transactionExtention = rpcCli.updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } return processTransactionExtention(transactionExtention); - } public boolean clearContractABI(byte[] owner, byte[] contractAddress) @@ -1924,8 +1953,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1933,33 +1962,53 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) return processTransactionExtention(transactionExtention); } - public boolean deployContract(byte[] owner, String contractName, String ABI, String code, - long feeLimit, long value, long consumeUserResourcePercent, long originEnergyLimit, - long tokenValue, String tokenId, String libraryAddressPair, String compilerVersion) + public boolean deployContract( + byte[] owner, + String contractName, + String ABI, + String code, + long feeLimit, + long value, + long consumeUserResourcePercent, + long originEnergyLimit, + long tokenValue, + String tokenId, + String libraryAddressPair, + String compilerVersion) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - CreateSmartContract contractDeployContract = createContractDeployContract(contractName, owner, - ABI, code, value, consumeUserResourcePercent, originEnergyLimit, tokenValue, tokenId, - libraryAddressPair, compilerVersion); + CreateSmartContract contractDeployContract = + createContractDeployContract( + contractName, + owner, + ABI, + code, + value, + consumeUserResourcePercent, + originEnergyLimit, + tokenValue, + tokenId, + libraryAddressPair, + compilerVersion); TransactionExtention transactionExtention = rpcCli.deployContract(contractDeployContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + Transaction.raw.Builder rawBuilder = + transactionExtention.getTransaction().getRawData().toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -1975,23 +2024,28 @@ public boolean deployContract(byte[] owner, String contractName, String ABI, Str texBuilder.setTxid(transactionExtention.getTxid()); transactionExtention = texBuilder.build(); -// byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); -// System.out.println( -// "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); + // byte[] contractAddress = generateContractAddress(transactionExtention.getTransaction()); + // System.out.println( + // "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); return processTransactionExtention(transactionExtention); - } - public boolean triggerContract(byte[] owner, byte[] contractAddress, long callValue, byte[] data, - long feeLimit, - long tokenValue, String tokenId, boolean isConstant) + public boolean triggerContract( + byte[] owner, + byte[] contractAddress, + long callValue, + byte[] data, + long feeLimit, + long tokenValue, + String tokenId, + boolean isConstant) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, - callValue, data, tokenValue, tokenId); + Contract.TriggerSmartContract triggerContract = + triggerCallContract(owner, contractAddress, callValue, data, tokenValue, tokenId); TransactionExtention transactionExtention; if (isConstant) { transactionExtention = rpcCli.triggerConstantContract(triggerContract); @@ -2002,28 +2056,28 @@ public boolean triggerContract(byte[] owner, byte[] contractAddress, long callVa if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create call trx failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); return false; } Transaction transaction = transactionExtention.getTransaction(); // for constant - if (transaction.getRetCount() != 0 && - transactionExtention.getConstantResult(0) != null && - transactionExtention.getResult() != null) { + if (transaction.getRetCount() != 0 + && transactionExtention.getConstantResult(0) != null + && transactionExtention.getResult() != null) { byte[] result = transactionExtention.getConstantResult(0).toByteArray(); System.out.println("message:" + transaction.getRet(0).getRet()); - System.out.println(":" + ByteArray - .toStr(transactionExtention.getResult().getMessage().toByteArray())); + System.out.println( + ":" + ByteArray.toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); return true; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() - .toBuilder(); + Transaction.raw.Builder rawBuilder = + transactionExtention.getTransaction().getRawData().toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2046,11 +2100,10 @@ public static SmartContract getContract(byte[] address) { return rpcCli.getContract(address); } - public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { - Contract.AccountPermissionUpdateContract contract = createAccountPermissionContract(owner, - permissionJson); + Contract.AccountPermissionUpdateContract contract = + createAccountPermissionContract(owner, permissionJson); TransactionExtention transactionExtention = rpcCli.accountPermissionUpdate(contract); return processTransactionExtention(transactionExtention); } @@ -2124,7 +2177,6 @@ public Contract.AccountPermissionUpdateContract createAccountPermissionContract( return builder.build(); } - public Transaction addTransactionSign(Transaction transaction) throws CipherException, IOException, CancelException { if (transaction.getRawData().getTimestamp() == 0) { @@ -2151,8 +2203,7 @@ public Transaction addTransactionSign(Transaction transaction) } public static Optional GetMerkleTreeVoucherInfo( - OutputPointInfo info, - boolean showErrorMsg) { + OutputPointInfo info, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.GetMerkleTreeVoucherInfo(info)); @@ -2168,8 +2219,8 @@ public static Optional GetMerkleTreeVoucherInfo( return Optional.empty(); } - public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, - boolean showErrorMsg) { + public static Optional scanNoteByIvk( + IvkDecryptParameters ivkDecryptParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2185,8 +2236,8 @@ public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecry return Optional.empty(); } - public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, - boolean showErrorMsg) { + public static Optional scanNoteByOvk( + OvkDecryptParameters ovkDecryptParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2270,8 +2321,8 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall return processShieldedTransaction(transactionExtention, wallet); } - public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, - byte[] ask, WalletApi wallet) + public static boolean sendShieldedCoinWithoutAsk( + PrivateParametersWithoutAsk privateParameters, byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); @@ -2288,7 +2339,7 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Transaction transaction = transactionExtention.getTransaction(); if (transaction.getRawData().getContract(0).getType() - != ContractType.ShieldedTransferContract) { + != ContractType.ShieldedTransferContract) { System.out.println("This method only for ShieldedTransferContract, please check!"); return false; } @@ -2296,8 +2347,8 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri Any any = transaction.getRawData().getContract(0).getParameter(); ShieldedTransferContract shieldContract = any.unpack(ShieldedTransferContract.class); List spendDescList = shieldContract.getSpendDescriptionList(); - ShieldedTransferContract.Builder contractBuild = shieldContract.toBuilder() - .clearSpendDescription(); + ShieldedTransferContract.Builder contractBuild = + shieldContract.toBuilder().clearSpendDescription(); for (int i = 0; i < spendDescList.size(); i++) { SpendDescription.Builder spendDescription = spendDescList.get(i).toBuilder(); SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); @@ -2312,11 +2363,16 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri contractBuild.addSpendDescription(spendDescription.build()); } - Transaction.raw.Builder rawBuilder = transaction.toBuilder().getRawDataBuilder().clearContract() - .addContract( - Transaction.Contract.newBuilder().setType(ContractType.ShieldedTransferContract) - .setParameter( - Any.pack(contractBuild.build())).build()); + Transaction.raw.Builder rawBuilder = + transaction + .toBuilder() + .getRawDataBuilder() + .clearContract() + .addContract( + Transaction.Contract.newBuilder() + .setType(ContractType.ShieldedTransferContract) + .setParameter(Any.pack(contractBuild.build())) + .build()); transaction = transaction.toBuilder().clearRawData().setRawData(rawBuilder).build(); @@ -2325,8 +2381,8 @@ public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk pri return processShieldedTransaction(transactionExtention, wallet); } - public static Optional isNoteSpend(NoteParameters noteParameters, - boolean showErrorMsg) { + public static Optional isNoteSpend( + NoteParameters noteParameters, boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.isNoteSpend(noteParameters)); @@ -2392,14 +2448,14 @@ public boolean updateBrokerage(byte[] owner, int brokerage) UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); - TransactionExtention transactionExtention = rpcCli - .updateBrokerage(updateBrokerageContract.build()); + TransactionExtention transactionExtention = + rpcCli.updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out - .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -2414,5 +2470,4 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } - } From 0e2cb38cf27ae03144f97b79557fc2e3511ded02 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 13 Jan 2020 12:47:51 +0800 Subject: [PATCH 256/445] typo: add command to help and command list --- src/main/java/org/tron/walletcli/Client.java | 6 ++++-- src/main/java/org/tron/walletcli/WalletApiWrapper.java | 3 ++- src/main/protos/core/Tron.proto | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index ac46044d6..dbd902957 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -96,9 +96,9 @@ public class Client { "GetExpandedSpendingKey", "GetIncomingViewingKey", "GetMarketOrderByAccount", + "GetMarketOrderListByPair", + "GetMarketPairList", "GetMarketPriceByPair", - "getMarketOrderListByPair", - "getMarketPairList", "GetNextMaintenanceTime", "GetNkFromNsk", "GetProposal", @@ -205,6 +205,8 @@ public class Client { "GetExpandedSpendingKey", "GetIncomingViewingKey", "GetMarketOrderByAccount", + "GetMarketOrderListByPair", + "GetMarketPairList", "GetMarketPriceByPair", "GetNextMaintenanceTime", "GetNkFromNsk", diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 95f153fe1..42079467a 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1191,7 +1191,8 @@ public boolean marketSellAsset( System.out.println("Warning: updateSetting failed, Please login first !!"); return false; } - return wallet.marketSellAsset(owner, sellTokenId, sellTokenQuantity, buyTokenId, buyTokenQuantity); + return wallet.marketSellAsset(owner, sellTokenId, sellTokenQuantity, + buyTokenId, buyTokenQuantity); } public boolean marketCancelOrder(byte[] owner, byte[] orderId) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 6ffb7e9e7..49ef4a083 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -682,7 +682,7 @@ message NodeInfo { } } -//market +// market message MarketOrder { bytes order_id = 1; bytes owner_address = 2; @@ -725,7 +725,7 @@ message MarketAccountOrder { } message MarketOrderPosition { - //if price list is empty or price exist, pre_price_key = null + // if price list is empty or price exist, pre_price_key = null bool price_list_not_empty = 1; bool price_exist = 2; bytes pre_price_key = 3; @@ -751,7 +751,7 @@ message MarketPriceList { } message MarketOrderIdList { - // repeated bytes orders = 1; + // repeated bytes orders = 1; bytes head = 2; bytes tail = 3; From 06f3c97e82d914efe71ef8b28ec7c14346cd1c42 Mon Sep 17 00:00:00 2001 From: ShuYu Wang Date: Mon, 13 Jan 2020 13:54:16 +0800 Subject: [PATCH 257/445] docs(README): update branch name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e06a42875..e409e0407 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The protocol is an independent project. You can use it for building other applic java-tron, wallet-cli and grpc-gateway -git subtree pull --prefix src/main/protos/ protocol develop +git subtree pull --prefix src/main/protos/ protocol master ## Run the included *.sh files to initialize the dependencies From ece823d3050f6f499b7cf833e2a63918ac3a2459 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 13 Jan 2020 14:37:09 +0800 Subject: [PATCH 258/445] typo: remove unused code --- src/main/protos/core/Tron.proto | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 49ef4a083..f29106b1b 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -751,8 +751,6 @@ message MarketPriceList { } message MarketOrderIdList { - // repeated bytes orders = 1; - - bytes head = 2; - bytes tail = 3; + bytes head = 1; + bytes tail = 2; } \ No newline at end of file From ed6fcdd8afdfc7ae9f0922aa210cf7a8f410d93a Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 13 Jan 2020 14:58:29 +0800 Subject: [PATCH 259/445] typo: modify proto --- src/main/protos/core/Tron.proto | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index f29106b1b..75ba65db8 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -688,9 +688,9 @@ message MarketOrder { bytes owner_address = 2; int64 create_time = 3; bytes sell_token_id = 4; - int64 sell_token_quantity = 6; - bytes buy_token_id = 5; - int64 buy_token_quantity = 7;//min to receive + int64 sell_token_quantity = 5; + bytes buy_token_id = 6; + int64 buy_token_quantity = 7; // min to receive int64 sell_token_quantity_remain = 9; enum State { ACTIVE = 0; @@ -713,8 +713,8 @@ message MarketOrderPairList { } message MarketOrderPair{ - bytes sell_token_id = 4; - bytes buy_token_id = 5; + bytes sell_token_id = 1; + bytes buy_token_id = 2; } message MarketAccountOrder { From 8601756b6bc46a439e733f6182835304269a538f Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 14 Jan 2020 15:37:42 +0800 Subject: [PATCH 260/445] doc: add doc for market --- README.md | 334 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 239 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 9d61771f7..dad730a7f 100644 --- a/README.md +++ b/README.md @@ -1046,106 +1046,250 @@ Import new shielded address is: ztron15y0cthwduz7mjpx3mc7cl9cxj8aqq9m8z08g6xns8q ImportShieldedAddress successful !! +How to use tron-dex to sell asset +----------------------------------- + +- create an order to sell asset +MarketSellAsset owner_address sell_token_id sell_token_quantity buy_token_id buy_token_quantity +*ownerAddress:The address of the account that initiated the transaction, optional, default is the address of the login account.* +*sell_token_id, sell_token_quantity:ID and amount of the token want to sell* +*buy_token_id, buy_token_quantity:ID and amount of the token want to buy* + +Example: +MarketSellAsset TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW _ 100 1000001 200 +Get the result of the contract execution with the getTransactionInfoById command: +getTransactionInfoById 10040f993cd9452b25bf367f38edadf11176355802baf61f3c49b96b4480d374 + +``` +{ + "id": "10040f993cd9452b25bf367f38edadf11176355802baf61f3c49b96b4480d374", + "blockNumber": 669, + "blockTimeStamp": 1578983493000, + "contractResult": [ + "" + ], + "receipt": { + "net_usage": 264 + } +} +``` + +- get the order created by account(include all status) +GetMarketOrderByAccount ownerAddress +Example: +GetMarketOrderByAccount TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW +``` +{ + "orders": [ + { + "order_id": "fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75db0", + "owner_address": "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW", + "create_time": 1578983490000, + "sell_token_id": "_", + "sell_token_quantity": 100, + "buy_token_id": "1000001", + "buy_token_quantity": 200, + "sell_token_quantity_remain": 100 + } + ] +} +``` + +- get market pair list +getMarketPairList +Example: +getMarketPairList +``` +{ + "orderPair": [ + { + "sell_token_id": "_", + "buy_token_id": "1000001" + } + ] +} +``` + +- get order list by pair +GetMarketOrderListByPair sell_token_id buy_token_id +*sell_token_id:ID of the token want to sell* +*buy_token_id:ID of the token want to buy* + +Example: +GetMarketOrderListByPair _ 1000001 +``` +{ + "orders": [ + { + "order_id": "fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75db0", + "owner_address": "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW", + "create_time": 1578983490000, + "sell_token_id": "_", + "sell_token_quantity": 100, + "buy_token_id": "1000001", + "buy_token_quantity": 200, + "sell_token_quantity_remain": 100 + } + ] +} +``` + +- get market price by pair +GetMarketPriceByPair sell_token_id buy_token_id +*sell_token_id:ID of the token want to sell* +*buy_token_id:ID of the token want to buy* + +Example: +GetMarketPriceByPair _ 1000001 +``` +{ + "sell_token_id": "_", + "buy_token_id": "1000001", + "prices": [ + { + "sell_token_quantity": 100, + "buy_token_quantity": 200 + } + ] +} +``` + +- cancel the order +MarketCancelOrder owner_address order_id +*owner_address: the account address who have created the order* +*order_id: the order id which want to cancel* + +Example: +MarketCancelOrder TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75db0 +Get the result of the contract execution with the getTransactionInfoById command: +getTransactionInfoById b375787a098498623403c755b1399e82910385251b643811936d914c9f37bd27 +``` +{ + "id": "b375787a098498623403c755b1399e82910385251b643811936d914c9f37bd27", + "blockNumber": 1582, + "blockTimeStamp": 1578986232000, + "contractResult": [ + "" + ], + "receipt": { + "net_usage": 283 + } +} +``` + ## Command List Following is a list of Tron Wallet-cli commands: For more information on a specific command, just type the command on terminal when you start your Wallet. - AddTransactionSign - ApproveProposal - AssetIssue - BackupShieldedAddress - BackupWallet - BackupWallet2Base64 - BroadcastTransaction - ChangePassword - ClearContractABI - Create2 - CreateAccount - CreateProposal - CreateWitness - DeleteProposal - DeployContract - ExchangeCreate - ExchangeInject - ExchangeTransaction - ExchangeWithdraw - FreezeBalance - GenerateAddress - GenerateShieldedAddress - GetAccount - GetAccountNet - GetAccountResource - GetAddress - GetAssetIssueByAccount - GetAssetIssueById - GetAssetIssueByName - GetAssetIssueListByName - GetBalance - GetBlock - GetBlockById - GetBlockByLatestNum - GetBlockByLimitNext - GetChainParameters - GetContract contractAddress - GetDelegatedResource - GetDelegatedResourceAccountIndex - GetDiversifier - GetExchange - GetExpandedSpendingKey - GetIncomingViewingKey - GetNextMaintenanceTime - GetShieldedNullifier - GetSpendingKey - GetProposal - GetTotalTransaction - GetTransactionApprovedList - GetTransactionById - GetTransactionCountByBlockNum - GetTransactionInfoById - GetTransactionsFromThis - GetTransactionsToThis - GetTransactionSignWeight - ImportShieldedAddress - ImportWallet - ImportWalletByBase64 - ListAssetIssue - ListAssetIssuePaginated - ListExchanges - ListExchangesPaginated - ListNodes - ListShieldedAddress - ListShieldedNote - ListProposals - ListProposalsPaginated - ListWitnesses - Login - Logout - LoadShieldedWallet - ParticipateAssetIssue - RegisterWallet - ResetShieldedNote - ScanAndMarkNotebyAddress - ScanNotebyIvk - ScanNotebyOvk - SendCoin - SendShieldedCoin - SendShieldedCoinWithoutAsk - SetAccountId - TransferAsset - TriggerContract - TriggerConstantContract - UnfreezeAsset - UnfreezeBalance - UpdateAccount - UpdateAsset - UpdateEnergyLimit - UpdateSetting - UpdateWitness - UpdateAccountPermission - VoteWitness - WithdrawBalance - Exit or Quit + AddTransactionSign + ApproveProposal + AssetIssue + BackupShieldedAddress + BackupWallet + BackupWallet2Base64 + BroadcastTransaction + ChangePassword + ClearContractABI + Create2 + CreateAccount + CreateProposal + CreateWitness + DeleteProposal + DeployContract + ExchangeCreate + ExchangeInject + ExchangeTransaction + ExchangeWithdraw + FreezeBalance + GenerateAddress + GenerateShieldedAddress + GetAccount + GetAccountNet + GetAccountResource + GetAddress + GetAkFromAsk + GetAssetIssueByAccount + GetAssetIssueById + GetAssetIssueByName + GetAssetIssueListByName + GetBalance + GetBlock + GetBlockById + GetBlockByLatestNum + GetBlockByLimitNext + GetBrokerage + GetChainParameters + GetContract + GetDelegatedResource + GetDelegatedResourceAccountIndex + GetDiversifier + GetExchange + GetExpandedSpendingKey + GetIncomingViewingKey + GetMarketOrderByAccount + GetMarketOrderListByPair + GetMarketPairList + GetMarketPriceByPair + GetNextMaintenanceTime + GetNkFromNsk + GetProposal + GetReward + GetShieldedNullifier + GetSpendingKey + GetTotalTransaction + GetTransactionApprovedList + GetTransactionById + GetTransactionCountByBlockNum + GetTransactionInfoById + GetTransactionSignWeight + GetTransactionsFromThis + GetTransactionsToThis + Help + ImportShieldedAddress + ImportWallet + ImportWalletByBase64 + ListAssetIssue + ListAssetIssuePaginated + ListExchanges + ListExchangesPaginated + ListNodes + ListProposals + ListProposalsPaginated + ListShieldedAddress + ListShieldedNote + ListWitnesses + LoadShieldedWallet + Login + Logout + MarketCancelOrder + MarketSellAsset + ParticipateAssetIssue + RegisterWallet + ResetShieldedNote + ScanAndMarkNotebyAddress + ScanNotebyIvk + ScanNotebyOvk + SendCoin + SendShieldedCoin + SendShieldedCoinWithoutAsk + SetAccountId + TransferAsset + TriggerConstantContract + TriggerContract + UnfreezeAsset + UnfreezeBalance + UpdateAccount + UpdateAccountPermission + UpdateAsset + UpdateBrokerage + UpdateEnergyLimit + UpdateSetting + UpdateWitness + VoteWitness + WithdrawBalance + Exit or Quit + Input any one of the listed commands, to display how-to tips. From c5105fc8289bbbccb751b23042ef1b7a8591e44c Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Mon, 13 Jan 2020 18:37:15 +0800 Subject: [PATCH 261/445] fix base58 --- .../java/org/tron/common/crypto/SM3Hash.java | 299 ------------------ .../org/tron/common/crypto/Sha256Hash.java | 282 ----------------- .../org/tron/common/crypto/Sha256Sm3Hash.java | 137 ++++---- .../java/org/tron/walletserver/WalletApi.java | 9 +- 4 files changed, 83 insertions(+), 644 deletions(-) delete mode 100644 src/main/java/org/tron/common/crypto/SM3Hash.java delete mode 100644 src/main/java/org/tron/common/crypto/Sha256Hash.java diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java deleted file mode 100644 index 505d5c102..000000000 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ /dev/null @@ -1,299 +0,0 @@ -package org.tron.common.crypto; - -/* - * Copyright 2011 Google Inc. - * Copyright 2014 Andreas Schildbach - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.google.common.io.ByteStreams; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import com.google.protobuf.ByteString; -import org.spongycastle.crypto.digests.SM3Digest; -import org.tron.common.utils.ByteArray; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.util.Arrays; - -import static com.google.common.base.Preconditions.checkArgument; - - -/** - * A SM3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be - * used as keys in a map. It also checks that the length is correct and provides a bit more type - * safety. - */ -public class SM3Hash implements Serializable, Comparable, HashInterface { - - public static final int LENGTH = 32; // bytes - public static final SM3Hash ZERO_HASH = wrap(new byte[LENGTH]); - - private final byte[] bytes; - - public SM3Hash(long num, byte[] hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - public SM3Hash(long num, SM3Hash hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** - * Use {@link #wrap(byte[])} instead. - */ - @Deprecated - public SM3Hash(byte[] rawHashBytes) { - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** - * Creates a new instance that wraps the given hash value. - * - * @param rawHashBytes the raw hash bytes to wrap - * @return a new instance - * @throws IllegalArgumentException if the given array length is not exactly 32 - */ - @SuppressWarnings("deprecation") // the constructor will be made private in the future - public static SM3Hash wrap(byte[] rawHashBytes) { - return new SM3Hash(rawHashBytes); - } - - public static SM3Hash wrap(ByteString rawHashByteString) { - return wrap(rawHashByteString.toByteArray()); - } - - /** - * Use {@link #of(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static SM3Hash create(byte[] contents) { - return of(contents); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - */ - public static SM3Hash of(byte[] contents) { - return wrap(hash(contents)); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given file's contents. - * The file contents are read fully into memory, so this method should only be used with small - * files. - * - * @param file the file on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - * @throws IOException if an error occurs while reading the file - */ - public static SM3Hash of(File file) throws IOException { - - try (FileInputStream in = new FileInputStream(file)) { - return of(ByteStreams.toByteArray(in)); - } - } - - /** - * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. - */ - @Deprecated - public static SM3Hash createDouble(byte[] contents) { - return twiceOf(contents); - } - - /** - * Creates a new instance containing the hash of the calculated hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (two-time) hash - */ - public static SM3Hash twiceOf(byte[] contents) { - return wrap(hashTwice(contents)); - } - - /** - * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the - * checked exception that can never occur with a RuntimeException. - * - * @return a new SM3 MessageDigest instance - */ - public static SM3Digest newDigest() { - return new SM3Digest(); - } - - /** - * Calculates the SM3 hash of the given bytes. - * - * @param input the bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input) { - return hash(input, 0, input.length); - } - - /** - * Calculates the SM3 hash of the given byte range. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input, int offset, int length) { - SM3Digest digest = newDigest(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - - digest.doFinal(eHash, 0); - return eHash; - } - - /** - * Calculates the SM3 hash of the given bytes, and then hashes the resulting hash again. - * - * @param input the bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input) { - return hashTwice(input, 0, input.length); - } - - /** - * Calculates the SM3 hash of the given byte range, and then hashes the resulting hash again. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input, int offset, int length) { - SM3Digest digest = newDigest(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - digest.reset(); - digest.update(eHash,0,eHash.length); - digest.doFinal(eHash,0); - return eHash; - } - - /** - * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the - * two ranges and then passing the result to {@link #hashTwice(byte[])}. - */ - public static byte[] hashTwice(byte[] input1, int offset1, int length1, - byte[] input2, int offset2, int length2) { - SM3Digest digest = newDigest(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash,0); - return eHash; - } - - private byte[] generateBlockId(long blockNum, SM3Hash blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.getBytes().length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); - return hash; - } - - private byte[] generateBlockId(long blockNum, byte[] blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); - return hash; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !(o instanceof SM3Hash)) { - return false; - } - return Arrays.equals(bytes, ((SM3Hash) o).bytes); - } - - @Override - public String toString() { - return ByteArray.toHexString(bytes); - } - - /** - * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable - * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. - * the value as a big integer lower than the target value). - */ - @Override - public int hashCode() { - // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints - .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); - } - - /** - * Returns the bytes interpreted as a positive integer. - */ - public BigInteger toBigInteger() { - return new BigInteger(1, bytes); - } - - /** - * Returns the internal byte array, without defensively copying. Therefore do NOT modify the - * returned array. - */ - public byte[] getBytes() { - return bytes; - } - - /** - * For pb return ByteString. - */ - public ByteString getByteString() { - return ByteString.copyFrom(bytes); - } - - @Override - public int compareTo(final SM3Hash other) { - for (int i = LENGTH - 1; i >= 0; i--) { - final int thisByte = this.bytes[i] & 0xff; - final int otherByte = other.bytes[i] & 0xff; - if (thisByte > otherByte) { - return 1; - } - if (thisByte < otherByte) { - return -1; - } - } - return 0; - } -} - diff --git a/src/main/java/org/tron/common/crypto/Sha256Hash.java b/src/main/java/org/tron/common/crypto/Sha256Hash.java deleted file mode 100644 index 8fd367e73..000000000 --- a/src/main/java/org/tron/common/crypto/Sha256Hash.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.tron.common.crypto; - -/* - * Copyright 2011 Google Inc. - * Copyright 2014 Andreas Schildbach - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.google.common.io.ByteStreams; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Longs; -import com.google.protobuf.ByteString; -import org.tron.common.utils.ByteArray; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Serializable; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import static com.google.common.base.Preconditions.checkArgument; - -/** - * A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be - * used as keys in a map. It also checks that the length is correct and provides a bit more type - * safety. - */ -public class Sha256Hash implements Serializable, Comparable { - - public static final int LENGTH = 32; // bytes - public static final Sha256Hash ZERO_HASH = wrap(new byte[LENGTH]); - - private final byte[] bytes; - - public Sha256Hash(long num, byte[] hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - public Sha256Hash(long num, Sha256Hash hash) { - byte[] rawHashBytes = this.generateBlockId(num, hash); - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** Use {@link #wrap(byte[])} instead. */ - @Deprecated - public Sha256Hash(byte[] rawHashBytes) { - checkArgument(rawHashBytes.length == LENGTH); - this.bytes = rawHashBytes; - } - - /** - * Creates a new instance that wraps the given hash value. - * - * @param rawHashBytes the raw hash bytes to wrap - * @return a new instance - * @throws IllegalArgumentException if the given array length is not exactly 32 - */ - @SuppressWarnings("deprecation") // the constructor will be made private in the future - public static Sha256Hash wrap(byte[] rawHashBytes) { - return new Sha256Hash(rawHashBytes); - } - - public static Sha256Hash wrap(ByteString rawHashByteString) { - return wrap(rawHashByteString.toByteArray()); - } - - /** Use {@link #of(byte[])} instead: this old name is ambiguous. */ - @Deprecated - public static Sha256Hash create(byte[] contents) { - return of(contents); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - */ - public static Sha256Hash of(byte[] contents) { - return wrap(hash(contents)); - } - - /** - * Creates a new instance containing the calculated (one-time) hash of the given file's contents. - * The file contents are read fully into memory, so this method should only be used with small - * files. - * - * @param file the file on which the hash value is calculated - * @return a new instance containing the calculated (one-time) hash - * @throws IOException if an error occurs while reading the file - */ - public static Sha256Hash of(File file) throws IOException { - - try (FileInputStream in = new FileInputStream(file)) { - return of(ByteStreams.toByteArray(in)); - } - } - - /** Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. */ - @Deprecated - public static Sha256Hash createDouble(byte[] contents) { - return twiceOf(contents); - } - - /** - * Creates a new instance containing the hash of the calculated hash of the given bytes. - * - * @param contents the bytes on which the hash value is calculated - * @return a new instance containing the calculated (two-time) hash - */ - public static Sha256Hash twiceOf(byte[] contents) { - return wrap(hashTwice(contents)); - } - - /** - * Returns a new SHA-256 MessageDigest instance. This is a convenience method which wraps the - * checked exception that can never occur with a RuntimeException. - * - * @return a new SHA-256 MessageDigest instance - */ - public static MessageDigest newDigest() { - try { - return MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Can't happen. - } - } - - /** - * Calculates the SHA-256 hash of the given bytes. - * - * @param input the bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input) { - return hash(input, 0, input.length); - } - - /** - * Calculates the SHA-256 hash of the given byte range. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the hash (in big-endian order) - */ - public static byte[] hash(byte[] input, int offset, int length) { - MessageDigest digest = newDigest(); - digest.update(input, offset, length); - return digest.digest(); - } - - /** - * Calculates the SHA-256 hash of the given bytes, and then hashes the resulting hash again. - * - * @param input the bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input) { - return hashTwice(input, 0, input.length); - } - - /** - * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. - * - * @param input the array containing the bytes to hash - * @param offset the offset within the array of the bytes to hash - * @param length the number of bytes to hash - * @return the double-hash (in big-endian order) - */ - public static byte[] hashTwice(byte[] input, int offset, int length) { - MessageDigest digest = newDigest(); - digest.update(input, offset, length); - return digest.digest(digest.digest()); - } - - /** - * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the - * two ranges and then passing the result to {@link #hashTwice(byte[])}. - */ - public static byte[] hashTwice( - byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) { - MessageDigest digest = newDigest(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - return digest.digest(digest.digest()); - } - - private byte[] generateBlockId(long blockNum, Sha256Hash blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.getBytes().length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash.getBytes(), 8, hash, 8, blockHash.getBytes().length - 8); - return hash; - } - - private byte[] generateBlockId(long blockNum, byte[] blockHash) { - byte[] numBytes = Longs.toByteArray(blockNum); - byte[] hash = new byte[blockHash.length]; - System.arraycopy(numBytes, 0, hash, 0, 8); - System.arraycopy(blockHash, 8, hash, 8, blockHash.length - 8); - return hash; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || !(o instanceof Sha256Hash)) { - return false; - } - return Arrays.equals(bytes, ((Sha256Hash) o).bytes); - } - - @Override - public String toString() { - return ByteArray.toHexString(bytes); - } - - /** - * Returns the last four bytes of the wrapped hash. This should be unique enough to be a suitable - * hash code even for blocks, where the goal is to try and get the first bytes to be zeros (i.e. - * the value as a big integer lower than the target value). - */ - @Override - public int hashCode() { - // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints.fromBytes( - bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); - } - - /** Returns the bytes interpreted as a positive integer. */ - public BigInteger toBigInteger() { - return new BigInteger(1, bytes); - } - - /** - * Returns the internal byte array, without defensively copying. Therefore do NOT modify the - * returned array. - */ - public byte[] getBytes() { - return bytes; - } - - /** For pb return ByteString. */ - public ByteString getByteString() { - return ByteString.copyFrom(bytes); - } - - @Override - public int compareTo(final Sha256Hash other) { - for (int i = LENGTH - 1; i >= 0; i--) { - final int thisByte = this.bytes[i] & 0xff; - final int otherByte = other.bytes[i] & 0xff; - if (thisByte > otherByte) { - return 1; - } - if (thisByte < otherByte) { - return -1; - } - } - return 0; - } -} diff --git a/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java b/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java index c46e83cb9..85bd46f86 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java @@ -17,14 +17,14 @@ * limitations under the License. */ +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; import com.typesafe.config.Config; import org.spongycastle.crypto.digests.SM3Digest; -import org.tron.common.utils.ByteArray; -import org.tron.core.config.Configuration; import java.io.File; import java.io.FileInputStream; @@ -34,31 +34,31 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import org.tron.common.utils.ByteArray; +import org.tron.core.config.Configuration; -import static com.google.common.base.Preconditions.checkArgument; /** * A Sha256Sm3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be * used as keys in a map. It also checks that the length is correct and provides a bit more type * safety. */ -public class Sha256Sm3Hash implements Serializable, Comparable, HashInterface { +public class Sha256Sm3Hash implements Serializable, Comparable { + + public static final int LENGTH = 32; // bytes + public static final Sha256Sm3Hash ZERO_HASH = wrap(new byte[LENGTH]); + private final byte[] bytes; private static boolean isEckey = true; static { Config config = Configuration.getByPath("config.conf"); // it is needs set to be a constant if (config.hasPath("crypto.engine")) { isEckey = config.getString("crypto.engine").equalsIgnoreCase("eckey"); - System.out.println("WalletUtils getConfig isEckey: " + isEckey); + System.out.println("Sha256Sm3Hash getConfig isEckey: " + isEckey); } } - - public static final int LENGTH = 32; // bytes - public static final Sha256Sm3Hash ZERO_HASH = wrap(new byte[LENGTH]); - - private final byte[] bytes; - + public Sha256Sm3Hash(long num, byte[] hash) { byte[] rawHashBytes = this.generateBlockId(num, hash); checkArgument(rawHashBytes.length == LENGTH); @@ -71,7 +71,9 @@ public Sha256Sm3Hash(long num, Sha256Sm3Hash hash) { this.bytes = rawHashBytes; } - /** Use {@link #wrap(byte[])} instead. */ + /** + * Use {@link #wrap(byte[])} instead. + */ @Deprecated public Sha256Sm3Hash(byte[] rawHashBytes) { checkArgument(rawHashBytes.length == LENGTH); @@ -94,9 +96,11 @@ public static Sha256Sm3Hash wrap(ByteString rawHashByteString) { return wrap(rawHashByteString.toByteArray()); } - /** Use {@link #of(byte[])} instead: this old name is ambiguous. */ + /** + * Use {@link #of(byte[])} instead: this old name is ambiguous. + */ @Deprecated - public static Sha256Sm3Hash create(byte[] contents) { + public static Sha256Sm3Hash create( byte[] contents) { return of(contents); } @@ -106,7 +110,7 @@ public static Sha256Sm3Hash create(byte[] contents) { * @param contents the bytes on which the hash value is calculated * @return a new instance containing the calculated (one-time) hash */ - public static Sha256Sm3Hash of(byte[] contents) { + public static Sha256Sm3Hash of( byte[] contents) { return wrap(hash(contents)); } @@ -119,17 +123,19 @@ public static Sha256Sm3Hash of(byte[] contents) { * @return a new instance containing the calculated (one-time) hash * @throws IOException if an error occurs while reading the file */ - public static Sha256Sm3Hash of(File file) throws IOException { + public static Sha256Sm3Hash of( File file) throws IOException { try (FileInputStream in = new FileInputStream(file)) { return of(ByteStreams.toByteArray(in)); } } - /** Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. */ + /** + * Use {@link #twiceOf(byte[])} instead: this old name is ambiguous. + */ @Deprecated - public static Sha256Sm3Hash createDouble(byte[] contents) { - return twiceOf(contents); + public static Sha256Sm3Hash createDouble( byte[] contents) { + return twiceOf( contents); } /** @@ -138,8 +144,8 @@ public static Sha256Sm3Hash createDouble(byte[] contents) { * @param contents the bytes on which the hash value is calculated * @return a new instance containing the calculated (two-time) hash */ - public static Sha256Sm3Hash twiceOf(byte[] contents) { - return wrap(hashTwice(contents)); + public static Sha256Sm3Hash twiceOf( byte[] contents) { + return wrap(hashTwice( contents)); } /** @@ -148,15 +154,21 @@ public static Sha256Sm3Hash twiceOf(byte[] contents) { * * @return a new SHA-256 MessageDigest instance */ - public static MessageDigest newDigestEckey() { + public static MessageDigest newDigest() { try { return MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); // Can't happen. + throw new RuntimeException(e); // Can't happen. } } - public static SM3Digest newDigestSM3() { + /** + * Returns a new SM3 MessageDigest instance. This is a convenience method which wraps the + * checked exception that can never occur with a RuntimeException. + * + * @return a new SM3 MessageDigest instance + */ + public static SM3Digest newSM3Digest() { return new SM3Digest(); } @@ -166,8 +178,8 @@ public static SM3Digest newDigestSM3() { * @param input the bytes to hash * @return the hash (in big-endian order) */ - public static byte[] hash(byte[] input) { - return hash(input, 0, input.length); + public static byte[] hash( byte[] input) { + return hash( input, 0, input.length); } /** @@ -178,17 +190,19 @@ public static byte[] hash(byte[] input) { * @param length the number of bytes to hash * @return the hash (in big-endian order) */ - public static byte[] hash(byte[] input, int offset, int length) { + public static byte[] hash( byte[] input, int offset, int length) { if (isEckey) { - MessageDigest digest = newDigestEckey(); + MessageDigest digest = newDigest(); digest.update(input, offset, length); return digest.digest(); + } else { + SM3Digest digest = newSM3Digest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + return eHash; } - SM3Digest digest = newDigestSM3(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - return eHash; + } /** @@ -197,8 +211,8 @@ public static byte[] hash(byte[] input, int offset, int length) { * @param input the bytes to hash * @return the double-hash (in big-endian order) */ - public static byte[] hashTwice(byte[] input) { - return hashTwice(input, 0, input.length); + public static byte[] hashTwice( byte[] input) { + return hashTwice( input, 0, input.length); } /** @@ -209,40 +223,43 @@ public static byte[] hashTwice(byte[] input) { * @param length the number of bytes to hash * @return the double-hash (in big-endian order) */ - public static byte[] hashTwice(byte[] input, int offset, int length) { + public static byte[] hashTwice( byte[] input, int offset, int length) { if (isEckey) { - MessageDigest digest = newDigestEckey(); + MessageDigest digest = newDigest(); digest.update(input, offset, length); return digest.digest(digest.digest()); + } else { + SM3Digest digest = newSM3Digest(); + digest.update(input, offset, length); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash, 0); + digest.reset(); + digest.update(eHash,0,eHash.length); + digest.doFinal(eHash,0); + return eHash; } - SM3Digest digest = newDigestSM3(); - digest.update(input, offset, length); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - digest.reset(); - digest.update(eHash, 0, eHash.length); - digest.doFinal(eHash, 0); - return eHash; + } /** * Calculates the hash of hash on the given byte ranges. This is equivalent to concatenating the * two ranges and then passing the result to {@link #hashTwice(byte[])}. */ - public static byte[] hashTwice( - byte[] input1, int offset1, int length1, byte[] input2, int offset2, int length2) { + public static byte[] hashTwice( byte[] input1, int offset1, int length1, + byte[] input2, int offset2, int length2) { if (isEckey) { - MessageDigest digest = newDigestEckey(); + MessageDigest digest = newDigest(); digest.update(input1, offset1, length1); digest.update(input2, offset2, length2); return digest.digest(digest.digest()); + } else { + SM3Digest digest = newSM3Digest(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[] eHash = new byte[digest.getDigestSize()]; + digest.doFinal(eHash,0); + return eHash; } - SM3Digest digest = newDigestSM3(); - digest.update(input1, offset1, length1); - digest.update(input2, offset2, length2); - byte[] eHash = new byte[digest.getDigestSize()]; - digest.doFinal(eHash, 0); - return eHash; } private byte[] generateBlockId(long blockNum, Sha256Sm3Hash blockHash) { @@ -285,11 +302,13 @@ public String toString() { @Override public int hashCode() { // Use the last 4 bytes, not the first 4 which are often zeros in Bitcoin. - return Ints.fromBytes( - bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); + return Ints + .fromBytes(bytes[LENGTH - 4], bytes[LENGTH - 3], bytes[LENGTH - 2], bytes[LENGTH - 1]); } - /** Returns the bytes interpreted as a positive integer. */ + /** + * Returns the bytes interpreted as a positive integer. + */ public BigInteger toBigInteger() { return new BigInteger(1, bytes); } @@ -302,7 +321,9 @@ public byte[] getBytes() { return bytes; } - /** For pb return ByteString. */ + /** + * For pb return ByteString. + */ public ByteString getByteString() { return ByteString.copyFrom(bytes); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index b3027f0e4..cc253b830 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -20,7 +20,6 @@ import org.tron.api.GrpcAPI.TransactionSignWeight.Result.response_code; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Hash; import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; @@ -1002,8 +1001,8 @@ public static boolean addressValid(byte[] address) { } public static String encode58Check(byte[] input) { - byte[] hash0 = Sha256Hash.hash(input); - byte[] hash1 = Sha256Hash.hash(hash0); + byte[] hash0 = Sha256Sm3Hash.hash(input); + byte[] hash1 = Sha256Sm3Hash.hash(hash0); byte[] inputCheck = new byte[input.length + 4]; System.arraycopy(input, 0, inputCheck, 0, input.length); System.arraycopy(hash1, 0, inputCheck, input.length, 4); @@ -1017,8 +1016,8 @@ private static byte[] decode58Check(String input) { } byte[] decodeData = new byte[decodeCheck.length - 4]; System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length); - byte[] hash0 = Sha256Hash.hash(decodeData); - byte[] hash1 = Sha256Hash.hash(hash0); + byte[] hash0 = Sha256Sm3Hash.hash(decodeData); + byte[] hash1 = Sha256Sm3Hash.hash(hash0); if (hash1[0] == decodeCheck[decodeData.length] && hash1[1] == decodeCheck[decodeData.length + 1] && hash1[2] == decodeCheck[decodeData.length + 2] From ed1787f61a0f025e5598dd545e400504452b6a6d Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 14 Jan 2020 19:30:51 +0800 Subject: [PATCH 262/445] modify code style --- .../org/tron/demo/EasyTransferAssetDemo.java | 7 +- .../java/org/tron/demo/EasyTransferDemo.java | 5 +- .../org/tron/demo/TransactionSignDemo.java | 12 +- .../tron/demo/TransactionSignDemoForSM2.java | 16 +-- .../java/org/tron/keystore/SKeyEncryptor.java | 35 +++--- src/main/java/org/tron/test/Test.java | 114 +++++++----------- .../java/org/tron/walletserver/WalletApi.java | 56 ++++----- 7 files changed, 98 insertions(+), 147 deletions(-) diff --git a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java b/src/main/java/org/tron/demo/EasyTransferAssetDemo.java index 772b0a79a..b64faf72f 100644 --- a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java +++ b/src/main/java/org/tron/demo/EasyTransferAssetDemo.java @@ -28,9 +28,10 @@ public static void main(String[] args) { } System.out.println("address === " + WalletApi.encode58Check(address)); - EasyTransferResponse response = - WalletApi.easyTransferAsset( - passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), tokenId, 10000L); + EasyTransferResponse response = WalletApi + .easyTransferAsset( + passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), + tokenId, 10000L); if (response.getResult().getResult() == true) { Transaction transaction = response.getTransaction(); System.out.println("Easy transfer successful!!!"); diff --git a/src/main/java/org/tron/demo/EasyTransferDemo.java b/src/main/java/org/tron/demo/EasyTransferDemo.java index 350d244a1..1a57ff642 100644 --- a/src/main/java/org/tron/demo/EasyTransferDemo.java +++ b/src/main/java/org/tron/demo/EasyTransferDemo.java @@ -27,9 +27,8 @@ public static void main(String[] args) { } System.out.println("address === " + WalletApi.encode58Check(address)); - EasyTransferResponse response = - WalletApi.easyTransfer( - passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), 10000L); + EasyTransferResponse response = WalletApi + .easyTransfer(passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), 10000L); if (response.getResult().getResult() == true) { Transaction transaction = response.getTransaction(); System.out.println("Easy transfer successful!!!"); diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 6a45bff54..6bb842758 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -22,10 +22,7 @@ public static Transaction setReference(Transaction transaction, Block newestBloc long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = - transaction - .getRawData() - .toBuilder() + Transaction.raw rawData = transaction.getRawData().toBuilder() .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) .build(); @@ -60,12 +57,9 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder - .getRawDataBuilder() - .addContract(contractBuilder) + transactionBuilder.getRawDataBuilder().addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration( - newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 2c0ca5c89..0277798aa 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -18,10 +18,7 @@ public static Transaction setReference(Transaction transaction, Block newestBloc long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); byte[] blockHash = getBlockHash(newestBlock).getBytes(); byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = - transaction - .getRawData() - .toBuilder() + Transaction.raw rawData = transaction.getRawData().toBuilder() .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) .build(); @@ -56,12 +53,9 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) return null; } contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder - .getRawDataBuilder() - .addContract(contractBuilder) + transactionBuilder.getRawDataBuilder().addContract(contractBuilder) .setTimestamp(System.currentTimeMillis()) - .setExpiration( - newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); + .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); Transaction transaction = transactionBuilder.build(); Transaction refTransaction = setReference(transaction, newestBlock); return refTransaction; @@ -104,11 +98,11 @@ private static void hexStringTobase58check() { } public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; + String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; byte[] privateBytes = ByteArray.fromHexString(privateStr); SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TWdVF6Bdg4vzVAbyqZodMhEWFwNYmSH8nE"); + byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/keystore/SKeyEncryptor.java b/src/main/java/org/tron/keystore/SKeyEncryptor.java index adf36a39e..c545eb8d4 100644 --- a/src/main/java/org/tron/keystore/SKeyEncryptor.java +++ b/src/main/java/org/tron/keystore/SKeyEncryptor.java @@ -50,7 +50,8 @@ public static SKeyCapsule create(byte[] password, byte[] skey, int n, int p) byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); - byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, skey); + byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, + skey); byte[] mac = generateMac(derivedKey, cipherText); @@ -59,16 +60,19 @@ public static SKeyCapsule create(byte[] password, byte[] skey, int n, int p) return createSkey(fp, cipherText, iv, salt, mac, n, p); } - public static SKeyCapsule createStandard(byte[] password, byte[] skey) throws CipherException { + public static SKeyCapsule createStandard(byte[] password, byte[] skey) + throws CipherException { return create(password, skey, N_STANDARD, P_STANDARD); } - public static SKeyCapsule createLight(byte[] password, byte[] skey) throws CipherException { + public static SKeyCapsule createLight(byte[] password, byte[] skey) + throws CipherException { return create(password, skey, N_LIGHT, P_LIGHT); } private static SKeyCapsule createSkey( - byte[] fp, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, int n, int p) { + byte[] fp, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, + int n, int p) { SKeyCapsule skey = new SKeyCapsule(); skey.setFp(WalletApi.encode58Check(fp)); @@ -104,11 +108,11 @@ private static byte[] generateDerivedScryptKey( return SCrypt.generate(password, salt, n, r, p, dkLen); } - private static byte[] generateAes128CtrDerivedKey(byte[] password, byte[] salt, int c, String prf) - throws CipherException { + private static byte[] generateAes128CtrDerivedKey( + byte[] password, byte[] salt, int c, String prf) throws CipherException { if (!prf.equals("hmac-sha256")) { - throw new CipherException("Unsupported prf:" + prf); + throw new CipherException("Unsupported prf:" + prf); } // Java 8 supports this, but you have to convert the password to a character array, see @@ -119,8 +123,8 @@ private static byte[] generateAes128CtrDerivedKey(byte[] password, byte[] salt, return ((KeyParameter) gen.generateDerivedParameters(256)).getKey(); } - private static byte[] performCipherOperation(int mode, byte[] iv, byte[] encryptKey, byte[] text) - throws CipherException { + private static byte[] performCipherOperation( + int mode, byte[] iv, byte[] encryptKey, byte[] text) throws CipherException { try { IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); @@ -129,12 +133,9 @@ private static byte[] performCipherOperation(int mode, byte[] iv, byte[] encrypt SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey, "AES"); cipher.init(mode, secretKeySpec, ivParameterSpec); return cipher.doFinal(text); - } catch (NoSuchPaddingException - | NoSuchAlgorithmException - | InvalidAlgorithmParameterException - | InvalidKeyException - | BadPaddingException - | IllegalBlockSizeException e) { + } catch (NoSuchPaddingException | NoSuchAlgorithmException + | InvalidAlgorithmParameterException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException e) { throw new CipherException("Error performing cipher operation", e); } } @@ -197,7 +198,8 @@ public static byte[] decrypt2PrivateBytes(byte[] password, SKeyCapsule skey) return privateKey; } - public static boolean validPassword(byte[] password, SKeyCapsule skey) throws CipherException { + public static boolean validPassword(byte[] password, SKeyCapsule skey) + throws CipherException { validate(skey); @@ -260,4 +262,5 @@ public static byte[] generateRandomBytes(int size) { new SecureRandom().nextBytes(bytes); return bytes; } + } diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index dd8daef29..470a434fe 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -44,12 +44,13 @@ public static Transaction createTransactionEx(String toAddress, long amount) { for (int i = 0; i < 10; i++) { Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = - Contract.TransferContract.newBuilder(); + Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract + .newBuilder(); transferContractBuilder.setAmount(amount); - ByteString bsTo = ByteString.copyFrom(ByteArray.fromHexString(toAddress)); - ByteString bsOwner = - ByteString.copyFrom(ByteArray.fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); + ByteString bsTo = ByteString.copyFrom(ByteArray + .fromHexString(toAddress)); + ByteString bsOwner = ByteString.copyFrom(ByteArray + .fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); transferContractBuilder.setToAddress(bsTo); transferContractBuilder.setOwnerAddress(bsOwner); try { @@ -69,35 +70,6 @@ public static Transaction createTransactionEx(String toAddress, long amount) { return transaction; } - // public static Transaction createTransactionAccount() { - // Transaction.Builder transactionBuilder = Transaction.newBuilder(); - // - // for (int i = 0; i < 10; i++) { - // Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - // Contract.AccountCreateContract.Builder accountCreateContract = - // Contract.AccountCreateContract - // .newBuilder(); - // accountCreateContract.setAccountName(ByteString.copyFrom("zawtest".getBytes())); - // - // ByteString bsOwner = ByteString.copyFrom(ByteArray - // .fromHexString("e1a17255ccf15d6b12dcc074ca1152477ccf9b84")); - // accountCreateContract.setOwnerAddress(bsOwner); - // try { - // Any anyTo = Any.pack(accountCreateContract.build()); - // contractBuilder.setParameter(anyTo); - // } catch (Exception e) { - // return null; - // } - // contractBuilder.setType(Transaction.Contract.ContractType.AccountCreateContract); - // - // transactionBuilder.getRawDataBuilder().addContract(contractBuilder); - // } - // transactionBuilder.getRawDataBuilder(); - // - // Transaction transaction = transactionBuilder.build(); - // return transaction; - // } - public static void test64() throws UnsupportedEncodingException { Encoder encoder = Base64.getEncoder(); byte[] encode = encoder.encode("test string ".getBytes()); @@ -127,9 +99,8 @@ public static void testECKey() { System.out.println("pubKey ::: " + ByteArray.toHexString(pubKey)); System.out.println("addresss ::: " + ByteArray.toHexString(addresss)); byte[] msg = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15 - }; + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15}; byte[] sha256 = Sha256Sm3Hash.hash(msg); ECDSASignature signature = eCkey.sign(sha256); @@ -140,31 +111,6 @@ public static void testECKey() { System.out.println("id:::" + signature.v); } - // public static void testTransaction() { - // Transaction transaction = createTransactionAccount(); - // String priKey = "8e812436a0e3323166e1f0e8ba79e19e217b2c4a53c970d4cca0cfb1078979df"; - // - // ECKey eCkey = null; - // try { - // BigInteger priK = new BigInteger(priKey, 16); - // eCkey = ECKey.fromPrivate(priK); - // } catch (Exception ex) { - // ex.printStackTrace(); - // } - // byte[] msg = transaction.getRawData().toByteArray(); - // byte[] sha256 = Hash.sha256(msg); - // eCkey.sign(sha256); - // - // ECDSASignature signature = eCkey.sign(sha256); - // - // System.out.println("msg:::" + ByteArray.toHexString(msg)); - // System.out.println("priKey:::" + ByteArray.toHexString(eCkey.getPrivKeyBytes())); - // System.out.println("pubKey::" + ByteArray.toHexString(eCkey.getPubKey())); - // System.out.println("hash:::" + ByteArray.toHexString(sha256)); - // System.out.println("r:::" + ByteArray.toHexString(signature.r.toByteArray())); - // System.out.println("s:::" + ByteArray.toHexString(signature.s.toByteArray())); - // System.out.println("id:::" + signature.v); - // } public static void testVerify() { String hashBytes = "630211D6CA9440639F4965AA24831EB84815AB6BEF11E8BE6962A8540D861339"; @@ -174,11 +120,11 @@ public static void testVerify() { ECKey eCkey = null; String signatureBase64 = ""; try { - signatureBase64 = - new String(Base64.getEncoder().encode(ByteArray.fromHexString(sign)), "UTF-8"); + signatureBase64 = new String(Base64.getEncoder().encode(ByteArray.fromHexString(sign)), + "UTF-8"); - byte[] pubKey = - ECKey.signatureToKeyBytes(ByteArray.fromHexString(hashBytes), signatureBase64); + byte[] pubKey = ECKey + .signatureToKeyBytes(ByteArray.fromHexString(hashBytes), signatureBase64); System.out.println("pubKey::" + ByteArray.toHexString(pubKey)); } catch (Exception ex) { ex.printStackTrace(); @@ -225,10 +171,38 @@ public static void testGenKey() { public static void testSignEx() { byte[] priKey = { - 0, 69, 242 - 256, 238 - 256, 134 - 256, 57, 112, 55, 243 - 256, 242 - 256, 121, 104, - 182 - 256, 252 - 256, 247 - 256, 242 - 256, 107, 230 - 256, 211 - 256, 208 - 256, 167 - 256, - 194 - 256, 18, 229 - 256, 160 - 256, 229 - 256, 91, 179 - 256, 48, 96, 209 - 256, 78 - }; + 0, + 69, + 242 - 256, + 238 - 256, + 134 - 256, + 57, + 112, + 55, + 243 - 256, + 242 - 256, + 121, + 104, + 182 - 256, + 252 - 256, + 247 - 256, + 242 - 256, + 107, + 230 - 256, + 211 - 256, + 208 - 256, + 167 - 256, + 194 - 256, + 18, + 229 - 256, + 160 - 256, + 229 - 256, + 91, + 179 - 256, + 48, + 96, + 209 - 256, + 78}; ECKey eCkey = null; try { diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index cc253b830..c984905b5 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -57,18 +57,6 @@ public class WalletApi { private static GrpcClient rpcCli = init(); - // static { - // new Timer().schedule(new TimerTask() { - // @Override - // public void run() { - // String fullnode = selectFullNode(); - // if(!"".equals(fullnode)) { - // rpcCli = new GrpcClient(fullnode); - // } - // } - // }, 3 * 60 * 1000, 3 * 60 * 1000); - // } - public static GrpcClient init() { Config config = Configuration.getByPath("config.conf"); @@ -140,7 +128,9 @@ public static int getRpcVersion() { return rpcVersion; } - /** Creates a new WalletApi with a random ECKey or no ECKey. */ + /** + * Creates a new WalletApi with a random ECKey or no ECKey. + * */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -184,7 +174,9 @@ public boolean checkPassword(byte[] passwd) throws CipherException { return Wallet.validPassword(passwd, this.walletFile.get(0)); } - /** Creates a Wallet with an existing ECKey. */ + /** + * Creates a Wallet with an existing ECKey. + * */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -318,7 +310,9 @@ private static WalletFile loadWalletFile() throws IOException { return WalletUtils.loadWalletFile(wallet); } - /** load a Wallet from keystore */ + /** + * load a Wallet from keystore + * */ public static WalletApi loadWalletFromKeystore() throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); @@ -372,9 +366,6 @@ private Transaction signTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } - // System.out - // .println("current transaction hex string is " + ByteArray - // .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -414,9 +405,6 @@ private Transaction signOnlyForShieldedTransaction(Transaction transaction) } else { transaction = TransactionUtils.sign(transaction, this.getSM2(walletFile, passwd)); } - // System.out - // .println("current transaction hex string is " + ByteArray - // .toHexString(transaction.toByteArray())); org.tron.keystore.StringUtils.clear(passwd); TransactionSignWeight weight = getTransactionSignWeight(transaction); @@ -460,9 +448,8 @@ private boolean processTransactionExtention(TransactionExtention transactionExte } System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); - System.out.println( - "before sign transaction hex string is " - + ByteArray.toHexString(transaction.toByteArray())); + System.out.println("before sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); transaction = signTransaction(transaction); showTransactionAfterSign(transaction); return rpcCli.broadcastTransaction(transaction); @@ -470,24 +457,23 @@ private boolean processTransactionExtention(TransactionExtention transactionExte private void showTransactionAfterSign(Transaction transaction) throws InvalidProtocolBufferException { - System.out.println( - "after sign transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); - System.out.println( - "txid is " - + ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); + System.out.println("after sign transaction hex string is " + + ByteArray.toHexString(transaction.toByteArray())); + System.out.println("txid is " + + ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); if (transaction.getRawData().getContract(0).getType() == ContractType.CreateSmartContract) { - CreateSmartContract createSmartContract = - transaction.getRawData().getContract(0).getParameter().unpack(CreateSmartContract.class); - byte[] contractAddress = - generateContractAddress(createSmartContract.getOwnerAddress().toByteArray(), transaction); + CreateSmartContract createSmartContract = transaction.getRawData().getContract(0) + .getParameter().unpack(CreateSmartContract.class); + byte[] contractAddress = generateContractAddress( + createSmartContract.getOwnerAddress().toByteArray(), transaction); System.out.println( "Your smart contract address will be: " + WalletApi.encode58Check(contractAddress)); } } - private static boolean processShieldedTransaction( - TransactionExtention transactionExtention, WalletApi wallet) + private static boolean processShieldedTransaction(TransactionExtention transactionExtention, + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; From d4e5a599a39c139ad3eb40412fed23350fc799f5 Mon Sep 17 00:00:00 2001 From: alberto-zhang Date: Tue, 14 Jan 2020 20:13:00 +0800 Subject: [PATCH 263/445] merge develop 1 --- src/main/java/org/tron/common/utils/Utils.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 7479115e5..15592373f 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -565,4 +565,21 @@ public static boolean confirmEncrption() { } return false; } + public static boolean isNumericString(String str) { + for (int i = str.length(); --i >= 0; ) { + if (!Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } + public static boolean isHexString(String str) { + boolean bRet = false; + try { + ByteArray.fromHexString(str); + bRet = true; + } catch (Exception e) { + } + return bRet; + } } From e637d2312551dc58b52b2e70784b1c5aadd9645f Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Wed, 15 Jan 2020 16:17:48 +0800 Subject: [PATCH 264/445] typo: use sysPrint to print msg --- src/main/java/org/tron/walletcli/Client.java | 959 +++++++++---------- 1 file changed, 477 insertions(+), 482 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index dbd902957..7f227d270 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -267,10 +267,15 @@ public class Client { "WithdrawBalance", }; + + private void sysPrint(String msg) { + sysPrint(msg); + } + private byte[] inputPrivateKey() throws IOException { byte[] temp = new byte[128]; byte[] result = null; - System.out.println("Please input private key. Max retry time:" + retryTime); + sysPrint("Please input private key. Max retry time:" + retryTime); int nTime = 0; while (nTime < retryTime) { int len = System.in.read(temp, 0, temp.length); @@ -283,7 +288,7 @@ private byte[] inputPrivateKey() throws IOException { } } StringUtils.clear(result); - System.out.println("Invalid private key, please input again."); + sysPrint("Invalid private key, please input again."); ++nTime; } StringUtils.clear(temp); @@ -294,7 +299,7 @@ private byte[] inputPrivateKey64() throws IOException { Decoder decoder = Base64.getDecoder(); byte[] temp = new byte[128]; byte[] result = null; - System.out.println("Please input private key by base64. Max retry time:" + retryTime); + sysPrint("Please input private key by base64. Max retry time:" + retryTime); int nTime = 0; while (nTime < retryTime) { int len = System.in.read(temp, 0, temp.length); @@ -306,7 +311,7 @@ private byte[] inputPrivateKey64() throws IOException { break; } } - System.out.println("Invalid base64 private key, please input again."); + sysPrint("Invalid base64 private key, please input again."); ++nTime; } StringUtils.clear(temp); @@ -319,10 +324,10 @@ private void registerWallet() throws CipherException, IOException { StringUtils.clear(password); if (null == fileName) { - System.out.println("Register wallet failed !!"); + sysPrint("Register wallet failed !!"); return; } - System.out.println("Register a wallet successful, keystore file name is " + fileName); + sysPrint("Register a wallet successful, keystore file name is " + fileName); } private void importWallet() throws CipherException, IOException { @@ -334,10 +339,10 @@ private void importWallet() throws CipherException, IOException { StringUtils.clear(priKey); if (null == fileName) { - System.out.println("Import wallet failed !!"); + sysPrint("Import wallet failed !!"); return; } - System.out.println("Import a wallet successful, keystore file name is " + fileName); + sysPrint("Import a wallet successful, keystore file name is " + fileName); } private void importWalletByBase64() throws CipherException, IOException { @@ -349,55 +354,55 @@ private void importWalletByBase64() throws CipherException, IOException { StringUtils.clear(priKey); if (null == fileName) { - System.out.println("Import wallet failed !!"); + sysPrint("Import wallet failed !!"); return; } - System.out.println("Import a wallet successful, keystore file name is " + fileName); + sysPrint("Import a wallet successful, keystore file name is " + fileName); } private void changePassword() throws IOException, CipherException { - System.out.println("Please input old password."); + sysPrint("Please input old password."); char[] oldPassword = Utils.inputPassword(false); - System.out.println("Please input new password."); + sysPrint("Please input new password."); char[] newPassword = Utils.inputPassword2Twice(); if (walletApiWrapper.changePassword(oldPassword, newPassword)) { - System.out.println("ChangePassword successful !!"); + sysPrint("ChangePassword successful !!"); } else { - System.out.println("ChangePassword failed !!"); + sysPrint("ChangePassword failed !!"); } } private void login() throws IOException, CipherException { boolean result = walletApiWrapper.login(); if (result) { - System.out.println("Login successful !!!"); + sysPrint("Login successful !!!"); } else { - System.out.println("Login failed !!!"); + sysPrint("Login failed !!!"); } } private void logout() { walletApiWrapper.logout(); - System.out.println("Logout successful !!!"); + sysPrint("Logout successful !!!"); } private void loadShieldedWallet() throws CipherException, IOException { boolean result = ShieldedWrapper.getInstance().loadShieldWallet(); if (result) { - System.out.println("LoadShieldedWallet successful !!!"); + sysPrint("LoadShieldedWallet successful !!!"); } else { - System.out.println("LoadShieldedWallet failed !!!"); + sysPrint("LoadShieldedWallet failed !!!"); } } private void backupWallet() throws IOException, CipherException { byte[] priKey = walletApiWrapper.backupWallet(); if (!ArrayUtils.isEmpty(priKey)) { - System.out.println("BackupWallet successful !!"); + sysPrint("BackupWallet successful !!"); for (int i = 0; i < priKey.length; i++) { StringUtils.printOneByte(priKey[i]); } - System.out.println(); + sysPrint("\n"); } StringUtils.clear(priKey); } @@ -409,11 +414,11 @@ private void backupWallet2Base64() throws IOException, CipherException { Encoder encoder = Base64.getEncoder(); byte[] priKey64 = encoder.encode(priKey); StringUtils.clear(priKey); - System.out.println("BackupWallet successful !!"); + sysPrint("BackupWallet successful !!"); for (int i = 0; i < priKey64.length; i++) { System.out.print((char) priKey64[i]); } - System.out.println(); + sysPrint("\n"); StringUtils.clear(priKey64); } } @@ -421,8 +426,8 @@ private void backupWallet2Base64() throws IOException, CipherException { private void getAddress() { String address = walletApiWrapper.getAddress(); if (address != null) { - System.out.println("GetAddress successful !!"); - System.out.println("address = " + address); + sysPrint("GetAddress successful !!"); + sysPrint("address = " + address); } } @@ -437,23 +442,23 @@ private void getBalance(String[] parameters) { } account = WalletApi.queryAccount(addressBytes); } else { - System.out.println("GetBalance needs no parameter or 1 parameter like the following: "); - System.out.println("GetBalance Address "); + sysPrint("GetBalance needs no parameter or 1 parameter like the following: "); + sysPrint("GetBalance Address "); return; } if (account == null) { - System.out.println("GetBalance failed !!!!"); + sysPrint("GetBalance failed !!!!"); } else { long balance = account.getBalance(); - System.out.println("Balance = " + balance); + sysPrint("Balance = " + balance); } } private void getAccount(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("GetAccount needs 1 parameter like the following: "); - System.out.println("GetAccount Address "); + sysPrint("GetAccount needs 1 parameter like the following: "); + sysPrint("GetAccount Address "); return; } String address = parameters[0]; @@ -464,33 +469,33 @@ private void getAccount(String[] parameters) { Account account = WalletApi.queryAccount(addressBytes); if (account == null) { - System.out.println("GetAccount failed !!!!"); + sysPrint("GetAccount failed !!!!"); } else { - System.out.println(Utils.formatMessageString(account)); + sysPrint(Utils.formatMessageString(account)); } } private void getAccountById(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("GetAccountById needs 1 parameter like the following: "); - System.out.println("GetAccountById accountId "); + sysPrint("GetAccountById needs 1 parameter like the following: "); + sysPrint("GetAccountById accountId "); return; } String accountId = parameters[0]; Account account = WalletApi.queryAccountById(accountId); if (account == null) { - System.out.println("GetAccountById failed !!!!"); + sysPrint("GetAccountById failed !!!!"); } else { - System.out.println(Utils.formatMessageString(account)); + sysPrint(Utils.formatMessageString(account)); } } private void updateAccount(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("UpdateAccount needs 1 parameter like the following: "); - System.out.println("UpdateAccount [OwnerAddress] AccountName "); + sysPrint("UpdateAccount needs 1 parameter like the following: "); + sysPrint("UpdateAccount [OwnerAddress] AccountName "); return; } @@ -499,7 +504,7 @@ private void updateAccount(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -508,17 +513,17 @@ private void updateAccount(String[] parameters) boolean ret = walletApiWrapper.updateAccount(ownerAddress, accountNameBytes); if (ret) { - System.out.println("Update Account successful !!!!"); + sysPrint("Update Account successful !!!!"); } else { - System.out.println("Update Account failed !!!!"); + sysPrint("Update Account failed !!!!"); } } private void setAccountId(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("SetAccountId needs 1 parameter like the following: "); - System.out.println("SetAccountId [OwnerAddress] AccountId "); + sysPrint("SetAccountId needs 1 parameter like the following: "); + sysPrint("SetAccountId [OwnerAddress] AccountId "); return; } @@ -527,7 +532,7 @@ private void setAccountId(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -536,17 +541,17 @@ private void setAccountId(String[] parameters) boolean ret = walletApiWrapper.setAccountId(ownerAddress, accountIdBytes); if (ret) { - System.out.println("Set AccountId successful !!!!"); + sysPrint("Set AccountId successful !!!!"); } else { - System.out.println("Set AccountId failed !!!!"); + sysPrint("Set AccountId failed !!!!"); } } private void updateAsset(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - System.out.println("UpdateAsset needs 4 parameters like the following: "); - System.out.println("UpdateAsset [OwnerAddress] newLimit newPublicLimit description url"); + sysPrint("UpdateAsset needs 4 parameters like the following: "); + sysPrint("UpdateAsset [OwnerAddress] newLimit newPublicLimit description url"); return; } @@ -555,7 +560,7 @@ private void updateAsset(String[] parameters) if (parameters.length == 5) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -572,16 +577,16 @@ private void updateAsset(String[] parameters) boolean ret = walletApiWrapper .updateAsset(ownerAddress, descriptionBytes, urlBytes, newLimit, newPublicLimit); if (ret) { - System.out.println("Update Asset successful !!!!"); + sysPrint("Update Asset successful !!!!"); } else { - System.out.println("Update Asset failed !!!!"); + sysPrint("Update Asset failed !!!!"); } } private void getAssetIssueByAccount(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("GetAssetIssueByAccount needs 1 parameter like following: "); - System.out.println("GetAssetIssueByAccount Address "); + sysPrint("GetAssetIssueByAccount needs 1 parameter like following: "); + sysPrint("GetAssetIssueByAccount Address "); return; } String address = parameters[0]; @@ -593,16 +598,16 @@ private void getAssetIssueByAccount(String[] parameters) { Optional result = WalletApi.getAssetIssueByAccount(addressBytes); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - System.out.println(Utils.formatMessageString(assetIssueList)); + sysPrint(Utils.formatMessageString(assetIssueList)); } else { - System.out.println("GetAssetIssueByAccount failed !!"); + sysPrint("GetAssetIssueByAccount failed !!"); } } private void getAccountNet(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("GetAccountNet needs 1 parameter like following: "); - System.out.println("GetAccountNet Address "); + sysPrint("GetAccountNet needs 1 parameter like following: "); + sysPrint("GetAccountNet Address "); return; } String address = parameters[0]; @@ -613,16 +618,16 @@ private void getAccountNet(String[] parameters) { AccountNetMessage result = WalletApi.getAccountNet(addressBytes); if (result == null) { - System.out.println("GetAccountNet failed !!"); + sysPrint("GetAccountNet failed !!"); } else { - System.out.println(Utils.formatMessageString(result)); + sysPrint(Utils.formatMessageString(result)); } } private void getAccountResource(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("getAccountResource needs 1 parameter like following: "); - System.out.println("getAccountResource Address "); + sysPrint("getAccountResource needs 1 parameter like following: "); + sysPrint("getAccountResource Address "); return; } String address = parameters[0]; @@ -633,9 +638,9 @@ private void getAccountResource(String[] parameters) { AccountResourceMessage result = WalletApi.getAccountResource(addressBytes); if (result == null) { - System.out.println("getAccountResource failed !!"); + sysPrint("getAccountResource failed !!"); } else { - System.out.println(Utils.formatMessageString(result)); + sysPrint(Utils.formatMessageString(result)); } } @@ -644,24 +649,24 @@ private void getAccountResource(String[] parameters) { // This function just remains for compatibility. private void getAssetIssueByName(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("GetAssetIssueByName needs 1 parameter like following: "); - System.out.println("GetAssetIssueByName AssetName "); + sysPrint("GetAssetIssueByName needs 1 parameter like following: "); + sysPrint("GetAssetIssueByName AssetName "); return; } String assetName = parameters[0]; AssetIssueContract assetIssueContract = WalletApi.getAssetIssueByName(assetName); if (assetIssueContract != null) { - System.out.println(Utils.formatMessageString(assetIssueContract)); + sysPrint(Utils.formatMessageString(assetIssueContract)); } else { - System.out.println("getAssetIssueByName failed !!"); + sysPrint("getAssetIssueByName failed !!"); } } private void getAssetIssueListByName(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("getAssetIssueListByName needs 1 parameter like following: "); - System.out.println("getAssetIssueListByName AssetName "); + sysPrint("getAssetIssueListByName needs 1 parameter like following: "); + sysPrint("getAssetIssueListByName AssetName "); return; } String assetName = parameters[0]; @@ -669,32 +674,32 @@ private void getAssetIssueListByName(String[] parameters) { Optional result = WalletApi.getAssetIssueListByName(assetName); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - System.out.println(Utils.formatMessageString(assetIssueList)); + sysPrint(Utils.formatMessageString(assetIssueList)); } else { - System.out.println("getAssetIssueListByName failed !!"); + sysPrint("getAssetIssueListByName failed !!"); } } private void getAssetIssueById(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("getAssetIssueById needs 1 parameter like following: "); - System.out.println("getAssetIssueById AssetId "); + sysPrint("getAssetIssueById needs 1 parameter like following: "); + sysPrint("getAssetIssueById AssetId "); return; } String assetId = parameters[0]; AssetIssueContract assetIssueContract = WalletApi.getAssetIssueById(assetId); if (assetIssueContract != null) { - System.out.println(Utils.formatMessageString(assetIssueContract)); + sysPrint(Utils.formatMessageString(assetIssueContract)); } else { - System.out.println("getAssetIssueById failed !!"); + sysPrint("getAssetIssueById failed !!"); } } private void sendCoin(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - System.out.println("SendCoin needs 2 parameters like following: "); - System.out.println("SendCoin [OwnerAddress] ToAddress Amount"); + sysPrint("SendCoin needs 2 parameters like following: "); + sysPrint("SendCoin [OwnerAddress] ToAddress Amount"); return; } @@ -703,7 +708,7 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -711,7 +716,7 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, String base58ToAddress = parameters[index++]; byte[] toAddress = WalletApi.decodeFromBase58Check(base58ToAddress); if (toAddress == null) { - System.out.println("Invalid toAddress."); + sysPrint("Invalid toAddress."); return; } @@ -720,17 +725,17 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, boolean result = walletApiWrapper.sendCoin(ownerAddress, toAddress, amount); if (result) { - System.out.println("Send " + amount + " drop to " + base58ToAddress + " successful !!"); + sysPrint("Send " + amount + " drop to " + base58ToAddress + " successful !!"); } else { - System.out.println("Send " + amount + " drop to " + base58ToAddress + " failed !!"); + sysPrint("Send " + amount + " drop to " + base58ToAddress + " failed !!"); } } private void transferAsset(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - System.out.println("TransferAsset needs 3 parameters using the following syntax: "); - System.out.println("TransferAsset [OwnerAddress] ToAddress AssertID Amount"); + sysPrint("TransferAsset needs 3 parameters using the following syntax: "); + sysPrint("TransferAsset [OwnerAddress] ToAddress AssertID Amount"); return; } @@ -739,7 +744,7 @@ private void transferAsset(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -747,7 +752,7 @@ private void transferAsset(String[] parameters) String base58Address = parameters[index++]; byte[] toAddress = WalletApi.decodeFromBase58Check(base58Address); if (toAddress == null) { - System.out.println("Invalid toAddress."); + sysPrint("Invalid toAddress."); return; } String assertName = parameters[index++]; @@ -756,17 +761,17 @@ private void transferAsset(String[] parameters) boolean result = walletApiWrapper.transferAsset(ownerAddress, toAddress, assertName, amount); if (result) { - System.out.println("TransferAsset " + amount + " to " + base58Address + " successful !!"); + sysPrint("TransferAsset " + amount + " to " + base58Address + " successful !!"); } else { - System.out.println("TransferAsset " + amount + " to " + base58Address + " failed !!"); + sysPrint("TransferAsset " + amount + " to " + base58Address + " failed !!"); } } private void participateAssetIssue(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - System.out.println("ParticipateAssetIssue needs 3 parameters using the following syntax: "); - System.out.println("ParticipateAssetIssue [OwnerAddress] ToAddress AssetID Amount"); + sysPrint("ParticipateAssetIssue needs 3 parameters using the following syntax: "); + sysPrint("ParticipateAssetIssue [OwnerAddress] ToAddress AssetID Amount"); return; } @@ -775,7 +780,7 @@ private void participateAssetIssue(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -783,7 +788,7 @@ private void participateAssetIssue(String[] parameters) String base58Address = parameters[index++]; byte[] toAddress = WalletApi.decodeFromBase58Check(base58Address); if (toAddress == null) { - System.out.println("Invalid toAddress."); + sysPrint("Invalid toAddress."); return; } @@ -794,12 +799,10 @@ private void participateAssetIssue(String[] parameters) boolean result = walletApiWrapper .participateAssetIssue(ownerAddress, toAddress, assertName, amount); if (result) { - System.out - .println("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + sysPrint("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + " successful !!"); } else { - System.out - .println("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + sysPrint("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + " failed !!"); } } @@ -807,17 +810,12 @@ private void participateAssetIssue(String[] parameters) private void assetIssue(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 12) { - System.out - .println("Use the assetIssue command for features that you require with below syntax: "); - System.out.println( - "AssetIssue [OwnerAddress] AssetName AbbrName TotalSupply TrxNum AssetNum Precision " + sysPrint("Use the assetIssue command for features that you require with below syntax: "); + sysPrint("AssetIssue [OwnerAddress] AssetName AbbrName TotalSupply TrxNum AssetNum Precision " + "StartDate EndDate Description Url FreeNetLimitPerAccount PublicFreeNetLimit " + "FrozenAmount0 FrozenDays0 ... FrozenAmountN FrozenDaysN"); - System.out - .println( - "TrxNum and AssetNum represents the conversion ratio of the tron to the asset."); - System.out - .println("The StartDate and EndDate format should look like 2018-03-01 2018-03-21 ."); + sysPrint("TrxNum and AssetNum represents the conversion ratio of the tron to the asset."); + sysPrint("The StartDate and EndDate format should look like 2018-03-01 2018-03-21 ."); return; } @@ -826,7 +824,7 @@ private void assetIssue(String[] parameters) if ((parameters.length & 1) == 1) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -858,7 +856,7 @@ private void assetIssue(String[] parameters) if (startDate == null || endDate == null) { System.out .println("The StartDate and EndDate format should look like 2018-03-01 2018-03-21 ."); - System.out.println("AssetIssue " + name + " failed !!"); + sysPrint("AssetIssue " + name + " failed !!"); return; } long startTime = startDate.getTime(); @@ -870,17 +868,17 @@ private void assetIssue(String[] parameters) trxNum, icoNum, precision, startTime, endTime, 0, description, url, freeAssetNetLimit, publicFreeNetLimit, frozenSupply); if (result) { - System.out.println("AssetIssue " + name + " successful !!"); + sysPrint("AssetIssue " + name + " successful !!"); } else { - System.out.println("AssetIssue " + name + " failed !!"); + sysPrint("AssetIssue " + name + " failed !!"); } } private void createAccount(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("CreateAccount needs 1 parameter using the following syntax: "); - System.out.println("CreateAccount [OwnerAddress] Address"); + sysPrint("CreateAccount needs 1 parameter using the following syntax: "); + sysPrint("CreateAccount [OwnerAddress] Address"); return; } @@ -889,30 +887,30 @@ private void createAccount(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } byte[] address = WalletApi.decodeFromBase58Check(parameters[index++]); if (address == null) { - System.out.println("Invalid Address."); + sysPrint("Invalid Address."); return; } boolean result = walletApiWrapper.createAccount(ownerAddress, address); if (result) { - System.out.println("CreateAccount successful !!"); + sysPrint("CreateAccount successful !!"); } else { - System.out.println("CreateAccount failed !!"); + sysPrint("CreateAccount failed !!"); } } private void createWitness(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("CreateWitness needs 1 parameter using the following syntax: "); - System.out.println("CreateWitness [OwnerAddress] Url"); + sysPrint("CreateWitness needs 1 parameter using the following syntax: "); + sysPrint("CreateWitness [OwnerAddress] Url"); return; } @@ -921,7 +919,7 @@ private void createWitness(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -930,17 +928,17 @@ private void createWitness(String[] parameters) boolean result = walletApiWrapper.createWitness(ownerAddress, url); if (result) { - System.out.println("CreateWitness successful !!"); + sysPrint("CreateWitness successful !!"); } else { - System.out.println("CreateWitness failed !!"); + sysPrint("CreateWitness failed !!"); } } private void updateWitness(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("updateWitness needs 1 parameter using the following syntax: "); - System.out.println("updateWitness [OwnerAddress] Url"); + sysPrint("updateWitness needs 1 parameter using the following syntax: "); + sysPrint("updateWitness [OwnerAddress] Url"); return; } @@ -949,7 +947,7 @@ private void updateWitness(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -957,9 +955,9 @@ private void updateWitness(String[] parameters) boolean result = walletApiWrapper.updateWitness(ownerAddress, url); if (result) { - System.out.println("updateWitness successful !!"); + sysPrint("updateWitness successful !!"); } else { - System.out.println("updateWitness failed !!"); + sysPrint("updateWitness failed !!"); } } @@ -967,9 +965,9 @@ private void listWitnesses() { Optional result = walletApiWrapper.listWitnesses(); if (result.isPresent()) { WitnessList witnessList = result.get(); - System.out.println(Utils.formatMessageString(witnessList)); + sysPrint(Utils.formatMessageString(witnessList)); } else { - System.out.println("List witnesses failed !!"); + sysPrint("List witnesses failed !!"); } } @@ -977,16 +975,16 @@ private void getAssetIssueList() { Optional result = walletApiWrapper.getAssetIssueList(); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - System.out.println(Utils.formatMessageString(assetIssueList)); + sysPrint(Utils.formatMessageString(assetIssueList)); } else { - System.out.println("GetAssetIssueList failed !!"); + sysPrint("GetAssetIssueList failed !!"); } } private void getAssetIssueList(String[] parameters) { if (parameters == null || parameters.length != 2) { - System.out.println("ListAssetIssuePaginated needs 2 parameters using the following syntax: "); - System.out.println("ListAssetIssuePaginated offset limit "); + sysPrint("ListAssetIssuePaginated needs 2 parameters using the following syntax: "); + sysPrint("ListAssetIssuePaginated offset limit "); return; } int offset = Integer.parseInt(parameters[0]); @@ -994,16 +992,16 @@ private void getAssetIssueList(String[] parameters) { Optional result = walletApiWrapper.getAssetIssueList(offset, limit); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - System.out.println(Utils.formatMessageString(assetIssueList)); + sysPrint(Utils.formatMessageString(assetIssueList)); } else { - System.out.println("GetAssetIssueListPaginated failed !!!"); + sysPrint("GetAssetIssueListPaginated failed !!!"); } } private void getProposalsListPaginated(String[] parameters) { if (parameters == null || parameters.length != 2) { - System.out.println("ListProposalsPaginated needs 2 parameters use the following syntax:"); - System.out.println("ListProposalsPaginated offset limit "); + sysPrint("ListProposalsPaginated needs 2 parameters use the following syntax:"); + sysPrint("ListProposalsPaginated offset limit "); return; } int offset = Integer.parseInt(parameters[0]); @@ -1011,9 +1009,9 @@ private void getProposalsListPaginated(String[] parameters) { Optional result = walletApiWrapper.getProposalListPaginated(offset, limit); if (result.isPresent()) { ProposalList proposalList = result.get(); - System.out.println(Utils.formatMessageString(proposalList)); + sysPrint(Utils.formatMessageString(proposalList)); } else { - System.out.println("ListProposalsPaginated failed !!!"); + sysPrint("ListProposalsPaginated failed !!!"); } } @@ -1021,7 +1019,7 @@ private void getExchangesListPaginated(String[] parameters) { if (parameters == null || parameters.length != 2) { System.out .println("ListExchangesPaginated command needs 2 parameters, use the following syntax:"); - System.out.println("ListExchangesPaginated offset limit "); + sysPrint("ListExchangesPaginated offset limit "); return; } int offset = Integer.parseInt(parameters[0]); @@ -1029,9 +1027,9 @@ private void getExchangesListPaginated(String[] parameters) { Optional result = walletApiWrapper.getExchangeListPaginated(offset, limit); if (result.isPresent()) { ExchangeList exchangeList = result.get(); - System.out.println(Utils.formatMessageString(exchangeList)); + sysPrint(Utils.formatMessageString(exchangeList)); } else { - System.out.println("ListExchangesPaginated failed !!!"); + sysPrint("ListExchangesPaginated failed !!!"); } } @@ -1042,11 +1040,11 @@ private void listNodes() { List list = nodeList.getNodesList(); for (int i = 0; i < list.size(); i++) { Node node = list.get(i); - System.out.println("IP::" + ByteArray.toStr(node.getAddress().getHost().toByteArray())); - System.out.println("Port::" + node.getAddress().getPort()); + sysPrint("IP::" + ByteArray.toStr(node.getAddress().getHost().toByteArray())); + sysPrint("Port::" + node.getAddress().getPort()); } } else { - System.out.println("GetAssetIssueList " + " failed !!!"); + sysPrint("GetAssetIssueList " + " failed !!!"); } } @@ -1054,14 +1052,14 @@ private void getBlock(String[] parameters) { long blockNum = -1; if (parameters == null || parameters.length == 0) { - System.out.println("Get current block !!!"); + sysPrint("Get current block !!!"); } else { if (parameters.length != 1) { - System.out.println("GetBlock has too many parameters !!!"); - System.out.println("You can get current block using the following command:"); - System.out.println("GetBlock"); - System.out.println("Or get block by number with the following syntax:"); - System.out.println("GetBlock BlockNum"); + sysPrint("GetBlock has too many parameters !!!"); + sysPrint("You can get current block using the following command:"); + sysPrint("GetBlock"); + sysPrint("Or get block by number with the following syntax:"); + sysPrint("GetBlock BlockNum"); } blockNum = Long.parseLong(parameters[0]); } @@ -1069,37 +1067,37 @@ private void getBlock(String[] parameters) { if (WalletApi.getRpcVersion() == 2) { BlockExtention blockExtention = walletApiWrapper.getBlock2(blockNum); if (blockExtention == null) { - System.out.println("No block for num : " + blockNum); + sysPrint("No block for num : " + blockNum); return; } - System.out.println(Utils.printBlockExtention(blockExtention)); + sysPrint(Utils.printBlockExtention(blockExtention)); } else { Block block = walletApiWrapper.getBlock(blockNum); if (block == null) { - System.out.println("No block for num : " + blockNum); + sysPrint("No block for num : " + blockNum); return; } - System.out.println(Utils.printBlock(block)); + sysPrint(Utils.printBlock(block)); } } private void getTransactionCountByBlockNum(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Use GetTransactionCountByBlockNum command with below syntax"); - System.out.println("GetTransactionCountByBlockNum number"); + sysPrint("Use GetTransactionCountByBlockNum command with below syntax"); + sysPrint("GetTransactionCountByBlockNum number"); return; } long blockNum = Long.parseLong(parameters[0]); long count = walletApiWrapper.getTransactionCountByBlockNum(blockNum); - System.out.println("The block contains " + count + " transactions"); + sysPrint("The block contains " + count + " transactions"); } private void voteWitness(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 2) { - System.out.println("Use VoteWitness command with below syntax: "); - System.out.println("VoteWitness [OwnerAddress] Address0 Count0 ... AddressN CountN"); + sysPrint("Use VoteWitness command with below syntax: "); + sysPrint("VoteWitness [OwnerAddress] Address0 Count0 ... AddressN CountN"); return; } @@ -1108,7 +1106,7 @@ private void voteWitness(String[] parameters) if ((parameters.length & 1) != 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1122,9 +1120,9 @@ private void voteWitness(String[] parameters) boolean result = walletApiWrapper.voteWitness(ownerAddress, witness); if (result) { - System.out.println("VoteWitness successful !!!"); + sysPrint("VoteWitness successful !!!"); } else { - System.out.println("VoteWitness failed !!!"); + sysPrint("VoteWitness failed !!!"); } } @@ -1141,8 +1139,8 @@ private void freezeBalance(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || !(parameters.length == 2 || parameters.length == 3 || parameters.length == 4 || parameters.length == 5)) { - System.out.println("Use freezeBalance command with below syntax: "); - System.out.println("freezeBalance [OwnerAddress] frozen_balance frozen_duration " + sysPrint("Use freezeBalance command with below syntax: "); + sysPrint("freezeBalance [OwnerAddress] frozen_balance frozen_duration " + "[ResourceCode:0 BANDWIDTH,1 ENERGY] [receiverAddress]"); return; } @@ -1175,17 +1173,17 @@ private void freezeBalance(String[] parameters) boolean result = walletApiWrapper.freezeBalance(ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (result) { - System.out.println("FreezeBalance successful !!!"); + sysPrint("FreezeBalance successful !!!"); } else { - System.out.println("FreezeBalance failed !!!"); + sysPrint("FreezeBalance failed !!!"); } } private void unfreezeBalance(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 1 || parameters.length > 3) { - System.out.println("Use unfreezeBalance command with below syntax: "); - System.out.println( + sysPrint("Use unfreezeBalance command with below syntax: "); + sysPrint( "unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH,1 CPU) [receiverAddress]"); return; } @@ -1211,39 +1209,39 @@ private void unfreezeBalance(String[] parameters) boolean result = walletApiWrapper.unfreezeBalance(ownerAddress, resourceCode, receiverAddress); if (result) { - System.out.println("UnfreezeBalance successful !!!"); + sysPrint("UnfreezeBalance successful !!!"); } else { - System.out.println("UnfreezeBalance failed !!!"); + sysPrint("UnfreezeBalance failed !!!"); } } private void unfreezeAsset(String[] parameters) throws IOException, CipherException, CancelException { - System.out.println("Use UnfreezeAsset command like: "); - System.out.println("UnfreezeAsset [OwnerAddress] "); + sysPrint("Use UnfreezeAsset command like: "); + sysPrint("UnfreezeAsset [OwnerAddress] "); byte[] ownerAddress = null; if (parameters != null && parameters.length > 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } boolean result = walletApiWrapper.unfreezeAsset(ownerAddress); if (result) { - System.out.println("UnfreezeAsset successful !!!"); + sysPrint("UnfreezeAsset successful !!!"); } else { - System.out.println("UnfreezeAsset failed !!!"); + sysPrint("UnfreezeAsset failed !!!"); } } private void createProposal(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 2) { - System.out.println("Use createProposal command with below syntax: "); - System.out.println("createProposal [OwnerAddress] id0 value0 ... idN valueN"); + sysPrint("Use createProposal command with below syntax: "); + sysPrint("createProposal [OwnerAddress] id0 value0 ... idN valueN"); return; } @@ -1252,7 +1250,7 @@ private void createProposal(String[] parameters) if ((parameters.length & 1) != 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1265,17 +1263,17 @@ private void createProposal(String[] parameters) } boolean result = walletApiWrapper.createProposal(ownerAddress, parametersMap); if (result) { - System.out.println("CreateProposal successful !!"); + sysPrint("CreateProposal successful !!"); } else { - System.out.println("CreateProposal failed !!"); + sysPrint("CreateProposal failed !!"); } } private void approveProposal(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - System.out.println("Use approveProposal command with below syntax: "); - System.out.println("approveProposal [OwnerAddress] id is_or_not_add_approval"); + sysPrint("Use approveProposal command with below syntax: "); + sysPrint("approveProposal [OwnerAddress] id is_or_not_add_approval"); return; } @@ -1284,7 +1282,7 @@ private void approveProposal(String[] parameters) if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1293,17 +1291,17 @@ private void approveProposal(String[] parameters) boolean is_add_approval = Boolean.valueOf(parameters[index++]); boolean result = walletApiWrapper.approveProposal(ownerAddress, id, is_add_approval); if (result) { - System.out.println("ApproveProposal successful !!!"); + sysPrint("ApproveProposal successful !!!"); } else { - System.out.println("ApproveProposal failed !!!"); + sysPrint("ApproveProposal failed !!!"); } } private void deleteProposal(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("Use deleteProposal command with below syntax: "); - System.out.println("deleteProposal [OwnerAddress] proposalId"); + sysPrint("Use deleteProposal command with below syntax: "); + sysPrint("deleteProposal [OwnerAddress] proposalId"); return; } @@ -1312,7 +1310,7 @@ private void deleteProposal(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1320,9 +1318,9 @@ private void deleteProposal(String[] parameters) long id = Long.valueOf(parameters[index++]); boolean result = walletApiWrapper.deleteProposal(ownerAddress, id); if (result) { - System.out.println("DeleteProposal successful !!!"); + sysPrint("DeleteProposal successful !!!"); } else { - System.out.println("DeleteProposal failed !!!"); + sysPrint("DeleteProposal failed !!!"); } } @@ -1331,16 +1329,16 @@ private void listProposals() { Optional result = walletApiWrapper.getProposalsList(); if (result.isPresent()) { ProposalList proposalList = result.get(); - System.out.println(Utils.formatMessageString(proposalList)); + sysPrint(Utils.formatMessageString(proposalList)); } else { - System.out.println("List witnesses failed !!!"); + sysPrint("List witnesses failed !!!"); } } private void getProposal(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getProposal command needs 1 parameter like: "); - System.out.println("getProposal id "); + sysPrint("Using getProposal command needs 1 parameter like: "); + sysPrint("getProposal id "); return; } String id = parameters[0]; @@ -1348,17 +1346,17 @@ private void getProposal(String[] parameters) { Optional result = WalletApi.getProposal(id); if (result.isPresent()) { Proposal proposal = result.get(); - System.out.println(Utils.formatMessageString(proposal)); + sysPrint(Utils.formatMessageString(proposal)); } else { - System.out.println("GetProposal failed !!!"); + sysPrint("GetProposal failed !!!"); } } private void getDelegatedResource(String[] parameters) { if (parameters == null || parameters.length != 2) { - System.out.println("Using getDelegatedResource command needs 2 parameters like: "); - System.out.println("getDelegatedResource fromAddress toAddress"); + sysPrint("Using getDelegatedResource command needs 2 parameters like: "); + sysPrint("getDelegatedResource fromAddress toAddress"); return; } String fromAddress = parameters[0]; @@ -1366,16 +1364,16 @@ private void getDelegatedResource(String[] parameters) { Optional result = WalletApi.getDelegatedResource(fromAddress, toAddress); if (result.isPresent()) { DelegatedResourceList delegatedResourceList = result.get(); - System.out.println(Utils.formatMessageString(delegatedResourceList)); + sysPrint(Utils.formatMessageString(delegatedResourceList)); } else { - System.out.println("GetDelegatedResource failed !!!"); + sysPrint("GetDelegatedResource failed !!!"); } } private void getDelegatedResourceAccountIndex(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getDelegatedResourceAccountIndex command needs 1 parameter like: "); - System.out.println("getDelegatedResourceAccountIndex address"); + sysPrint("Using getDelegatedResourceAccountIndex command needs 1 parameter like: "); + sysPrint("getDelegatedResourceAccountIndex address"); return; } String address = parameters[0]; @@ -1383,9 +1381,9 @@ private void getDelegatedResourceAccountIndex(String[] parameters) { .getDelegatedResourceAccountIndex(address); if (result.isPresent()) { DelegatedResourceAccountIndex delegatedResourceAccountIndex = result.get(); - System.out.println(Utils.formatMessageString(delegatedResourceAccountIndex)); + sysPrint(Utils.formatMessageString(delegatedResourceAccountIndex)); } else { - System.out.println("GetDelegatedResourceAccountIndex failed !!"); + sysPrint("GetDelegatedResourceAccountIndex failed !!"); } } @@ -1393,8 +1391,8 @@ private void getDelegatedResourceAccountIndex(String[] parameters) { private void exchangeCreate(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - System.out.println("Using exchangeCreate command needs 4 or 5 parameters like: "); - System.out.println("exchangeCreate [OwnerAddress] first_token_id first_token_balance " + sysPrint("Using exchangeCreate command needs 4 or 5 parameters like: "); + sysPrint("exchangeCreate [OwnerAddress] first_token_id first_token_balance " + "second_token_id second_token_balance"); return; } @@ -1404,7 +1402,7 @@ private void exchangeCreate(String[] parameters) if (parameters.length == 5) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1416,17 +1414,17 @@ private void exchangeCreate(String[] parameters) boolean result = walletApiWrapper.exchangeCreate(ownerAddress, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); if (result) { - System.out.println("ExchangeCreate successful !!!"); + sysPrint("ExchangeCreate successful !!!"); } else { - System.out.println("ExchangeCreate failed !!!"); + sysPrint("ExchangeCreate failed !!!"); } } private void exchangeInject(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - System.out.println("Using exchangeInject command needs 3 or 4 parameters like: "); - System.out.println("exchangeInject [OwnerAddress] exchange_id token_id quantity"); + sysPrint("Using exchangeInject command needs 3 or 4 parameters like: "); + sysPrint("exchangeInject [OwnerAddress] exchange_id token_id quantity"); return; } @@ -1435,7 +1433,7 @@ private void exchangeInject(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1445,17 +1443,17 @@ private void exchangeInject(String[] parameters) long quant = Long.valueOf(parameters[index++]); boolean result = walletApiWrapper.exchangeInject(ownerAddress, exchangeId, tokenId, quant); if (result) { - System.out.println("ExchangeInject successful !!!"); + sysPrint("ExchangeInject successful !!!"); } else { - System.out.println("ExchangeInject failed !!!"); + sysPrint("ExchangeInject failed !!!"); } } private void exchangeWithdraw(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - System.out.println("Using exchangeWithdraw command needs 3 or 4 parameters like: "); - System.out.println("exchangeWithdraw [OwnerAddress] exchange_id token_id quantity"); + sysPrint("Using exchangeWithdraw command needs 3 or 4 parameters like: "); + sysPrint("exchangeWithdraw [OwnerAddress] exchange_id token_id quantity"); return; } @@ -1464,7 +1462,7 @@ private void exchangeWithdraw(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1474,16 +1472,16 @@ private void exchangeWithdraw(String[] parameters) long quant = Long.valueOf(parameters[index++]); boolean result = walletApiWrapper.exchangeWithdraw(ownerAddress, exchangeId, tokenId, quant); if (result) { - System.out.println("ExchangeWithdraw successful !!!"); + sysPrint("ExchangeWithdraw successful !!!"); } else { - System.out.println("ExchangeWithdraw failed !!!"); + sysPrint("ExchangeWithdraw failed !!!"); } } private void exchangeTransaction(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - System.out.println("Using exchangeTransaction command needs 4 or 5 parameters like: "); + sysPrint("Using exchangeTransaction command needs 4 or 5 parameters like: "); System.out .println("exchangeTransaction [OwnerAddress] exchange_id token_id quantity expected"); return; @@ -1494,7 +1492,7 @@ private void exchangeTransaction(String[] parameters) if (parameters.length == 5) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1506,9 +1504,9 @@ private void exchangeTransaction(String[] parameters) boolean result = walletApiWrapper .exchangeTransaction(ownerAddress, exchangeId, tokenId, quant, expected); if (result) { - System.out.println("ExchangeTransaction successful !!!"); + sysPrint("ExchangeTransaction successful !!!"); } else { - System.out.println("ExchangeTransaction failed !!!"); + sysPrint("ExchangeTransaction failed !!!"); } } @@ -1516,16 +1514,16 @@ private void listExchanges() { Optional result = walletApiWrapper.getExchangeList(); if (result.isPresent()) { ExchangeList exchangeList = result.get(); - System.out.println(Utils.formatMessageString(exchangeList)); + sysPrint(Utils.formatMessageString(exchangeList)); } else { - System.out.println("ListExchanges failed !!!"); + sysPrint("ListExchanges failed !!!"); } } private void getExchange(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getExchange command needs 1 parameter like: "); - System.out.println("getExchange id"); + sysPrint("Using getExchange command needs 1 parameter like: "); + sysPrint("getExchange id"); return; } String id = parameters[0]; @@ -1533,49 +1531,49 @@ private void getExchange(String[] parameters) { Optional result = walletApiWrapper.getExchange(id); if (result.isPresent()) { Exchange exchange = result.get(); - System.out.println(Utils.formatMessageString(exchange)); + sysPrint(Utils.formatMessageString(exchange)); } else { - System.out.println("GetExchange failed !!!"); + sysPrint("GetExchange failed !!!"); } } private void withdrawBalance(String[] parameters) throws IOException, CipherException, CancelException { - System.out.println("Using withdrawBalance command like: "); - System.out.println("withdrawBalance [OwnerAddress] "); + sysPrint("Using withdrawBalance command like: "); + sysPrint("withdrawBalance [OwnerAddress] "); byte[] ownerAddress = null; if (parameters != null && parameters.length > 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } boolean result = walletApiWrapper.withdrawBalance(ownerAddress); if (result) { - System.out.println("WithdrawBalance successful !!!"); + sysPrint("WithdrawBalance successful !!!"); } else { - System.out.println("WithdrawBalance failed !!!"); + sysPrint("WithdrawBalance failed !!!"); } } private void getTotalTransaction() { NumberMessage totalTransition = walletApiWrapper.getTotalTransaction(); - System.out.println("The number of total transactions is : " + totalTransition.getNum()); + sysPrint("The number of total transactions is : " + totalTransition.getNum()); } private void getNextMaintenanceTime() { NumberMessage nextMaintenanceTime = walletApiWrapper.getNextMaintenanceTime(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = formatter.format(nextMaintenanceTime.getNum()); - System.out.println("Next maintenance time is : " + date); + sysPrint("Next maintenance time is : " + date); } private void getTransactionById(String[] parameters) { String txid = ""; if (parameters == null || parameters.length != 1) { - System.out.println("Using getTransactionById command needs 1 parameter, transaction id"); + sysPrint("Using getTransactionById command needs 1 parameter, transaction id"); return; } else { txid = parameters[0]; @@ -1583,16 +1581,16 @@ private void getTransactionById(String[] parameters) { Optional result = WalletApi.getTransactionById(txid); if (result.isPresent()) { Transaction transaction = result.get(); - System.out.println(Utils.printTransaction(transaction)); + sysPrint(Utils.printTransaction(transaction)); } else { - System.out.println("GetTransactionById failed !!"); + sysPrint("GetTransactionById failed !!"); } } private void getTransactionInfoById(String[] parameters) { String txid = ""; if (parameters == null || parameters.length != 1) { - System.out.println("Using getTransactionInfoById command needs 1 parameter, transaction id"); + sysPrint("Using getTransactionInfoById command needs 1 parameter, transaction id"); return; } else { txid = parameters[0]; @@ -1600,16 +1598,16 @@ private void getTransactionInfoById(String[] parameters) { Optional result = WalletApi.getTransactionInfoById(txid); if (result.isPresent() && !result.get().equals(TransactionInfo.getDefaultInstance())) { TransactionInfo transactionInfo = result.get(); - System.out.println(Utils.formatMessageString(transactionInfo)); + sysPrint(Utils.formatMessageString(transactionInfo)); } else { - System.out.println("GetTransactionInfoById failed !!!"); + sysPrint("GetTransactionInfoById failed !!!"); } } private void getTransactionsFromThis(String[] parameters) { if (parameters == null || parameters.length != 3) { - System.out.println("Using getTransactionsFromThis command needs 3 parameters like: "); - System.out.println("getTransactionsFromThis Address offset limit"); + sysPrint("Using getTransactionsFromThis command needs 3 parameters like: "); + sysPrint("getTransactionsFromThis Address offset limit"); return; } String address = parameters[0]; @@ -1626,12 +1624,12 @@ private void getTransactionsFromThis(String[] parameters) { if (result.isPresent()) { TransactionListExtention transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - System.out.println("No transaction from " + address); + sysPrint("No transaction from " + address); return; } - System.out.println(Utils.printTransactionList(transactionList)); + sysPrint(Utils.printTransactionList(transactionList)); } else { - System.out.println("GetTransactionsFromThis failed !!!"); + sysPrint("GetTransactionsFromThis failed !!!"); } } else { Optional result = WalletApi @@ -1639,20 +1637,20 @@ private void getTransactionsFromThis(String[] parameters) { if (result.isPresent()) { TransactionList transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - System.out.println("No transaction from " + address); + sysPrint("No transaction from " + address); return; } - System.out.println(Utils.printTransactionList(transactionList)); + sysPrint(Utils.printTransactionList(transactionList)); } else { - System.out.println("GetTransactionsFromThis failed !!!"); + sysPrint("GetTransactionsFromThis failed !!!"); } } } private void getTransactionsToThis(String[] parameters) { if (parameters == null || parameters.length != 3) { - System.out.println("Using getTransactionsToThis needs 3 parameters like: "); - System.out.println("getTransactionsToThis Address offset limit"); + sysPrint("Using getTransactionsToThis needs 3 parameters like: "); + sysPrint("getTransactionsToThis Address offset limit"); return; } String address = parameters[0]; @@ -1669,12 +1667,12 @@ private void getTransactionsToThis(String[] parameters) { if (result.isPresent()) { TransactionListExtention transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - System.out.println("No transaction to " + address); + sysPrint("No transaction to " + address); return; } - System.out.println(Utils.printTransactionList(transactionList)); + sysPrint(Utils.printTransactionList(transactionList)); } else { - System.out.println("getTransactionsToThis failed !!!"); + sysPrint("getTransactionsToThis failed !!!"); } } else { Optional result = WalletApi @@ -1682,20 +1680,20 @@ private void getTransactionsToThis(String[] parameters) { if (result.isPresent()) { TransactionList transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - System.out.println("No transaction to " + address); + sysPrint("No transaction to " + address); return; } - System.out.println(Utils.printTransactionList(transactionList)); + sysPrint(Utils.printTransactionList(transactionList)); } else { - System.out.println("getTransactionsToThis failed !!!"); + sysPrint("getTransactionsToThis failed !!!"); } } } // private void getTransactionsToThisCount(String[] parameters) { // if (parameters == null || parameters.length != 1) { -// System.out.println("getTransactionsToThisCount need 1 parameter like following: "); -// System.out.println("getTransactionsToThisCount Address"); +// sysPrint("getTransactionsToThisCount need 1 parameter like following: "); +// sysPrint("getTransactionsToThisCount Address"); // return; // } // String address = parameters[0]; @@ -1711,7 +1709,7 @@ private void getTransactionsToThis(String[] parameters) { private void getBlockById(String[] parameters) { String blockID = ""; if (parameters == null || parameters.length != 1) { - System.out.println("Using getBlockById command needs 1 parameter like: "); + sysPrint("Using getBlockById command needs 1 parameter like: "); return; } else { blockID = parameters[0]; @@ -1719,9 +1717,9 @@ private void getBlockById(String[] parameters) { Optional result = WalletApi.getBlockById(blockID); if (result.isPresent()) { Block block = result.get(); - System.out.println(Utils.printBlock(block)); + sysPrint(Utils.printBlock(block)); } else { - System.out.println("GetBlockById failed !!"); + sysPrint("GetBlockById failed !!"); } } @@ -1742,17 +1740,17 @@ private void getBlockByLimitNext(String[] parameters) { Optional result = WalletApi.getBlockByLimitNext2(start, end); if (result.isPresent()) { BlockListExtention blockList = result.get(); - System.out.println(Utils.printBlockList(blockList)); + sysPrint(Utils.printBlockList(blockList)); } else { - System.out.println("GetBlockByLimitNext failed !!"); + sysPrint("GetBlockByLimitNext failed !!"); } } else { Optional result = WalletApi.getBlockByLimitNext(start, end); if (result.isPresent()) { BlockList blockList = result.get(); - System.out.println(Utils.printBlockList(blockList)); + sysPrint(Utils.printBlockList(blockList)); } else { - System.out.println("GetBlockByLimitNext failed !!"); + sysPrint("GetBlockByLimitNext failed !!"); } } } @@ -1760,7 +1758,7 @@ private void getBlockByLimitNext(String[] parameters) { private void getBlockByLatestNum(String[] parameters) { long num = 0; if (parameters == null || parameters.length != 1) { - System.out.println("Using getBlockByLatestNum command needs 1 parameter, block_num"); + sysPrint("Using getBlockByLatestNum command needs 1 parameter, block_num"); return; } else { num = Long.parseLong(parameters[0]); @@ -1770,24 +1768,24 @@ private void getBlockByLatestNum(String[] parameters) { if (result.isPresent()) { BlockListExtention blockList = result.get(); if (blockList.getBlockCount() == 0) { - System.out.println("No block"); + sysPrint("No block"); return; } - System.out.println(Utils.printBlockList(blockList)); + sysPrint(Utils.printBlockList(blockList)); } else { - System.out.println("GetBlockByLimitNext failed !!"); + sysPrint("GetBlockByLimitNext failed !!"); } } else { Optional result = WalletApi.getBlockByLatestNum(num); if (result.isPresent()) { BlockList blockList = result.get(); if (blockList.getBlockCount() == 0) { - System.out.println("No block"); + sysPrint("No block"); return; } - System.out.println(Utils.printBlockList(blockList)); + sysPrint(Utils.printBlockList(blockList)); } else { - System.out.println("GetBlockByLimitNext failed !!"); + sysPrint("GetBlockByLimitNext failed !!"); } } } @@ -1795,9 +1793,8 @@ private void getBlockByLatestNum(String[] parameters) { private void updateSetting(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - System.out.println("Using updateSetting needs 2 parameters like: "); - System.out - .println("updateSetting [OwnerAddress] contract_address consume_user_resource_percent"); + sysPrint("Using updateSetting needs 2 parameters like: "); + sysPrint("updateSetting [OwnerAddress] contract_address consume_user_resource_percent"); return; } @@ -1806,36 +1803,36 @@ private void updateSetting(String[] parameters) if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (contractAddress == null) { - System.out.println("Invalid contractAddress."); + sysPrint("Invalid contractAddress."); return; } long consumeUserResourcePercent = Long.valueOf(parameters[index++]).longValue(); if (consumeUserResourcePercent > 100 || consumeUserResourcePercent < 0) { - System.out.println("consume_user_resource_percent must >= 0 and <= 100"); + sysPrint("consume_user_resource_percent must >= 0 and <= 100"); return; } boolean result = walletApiWrapper .updateSetting(ownerAddress, contractAddress, consumeUserResourcePercent); if (result) { - System.out.println("UpdateSetting successful !!!"); + sysPrint("UpdateSetting successful !!!"); } else { - System.out.println("UpdateSetting failed !!!"); + sysPrint("UpdateSetting failed !!!"); } } private void updateEnergyLimit(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - System.out.println("Using updateEnergyLimit command needs 2 parameters like: "); - System.out.println("updateEnergyLimit [OwnerAddress] contract_address energy_limit"); + sysPrint("Using updateEnergyLimit command needs 2 parameters like: "); + sysPrint("updateEnergyLimit [OwnerAddress] contract_address energy_limit"); return; } @@ -1844,36 +1841,36 @@ private void updateEnergyLimit(String[] parameters) if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (contractAddress == null) { - System.out.println("Invalid contractAddress."); + sysPrint("Invalid contractAddress."); return; } long originEnergyLimit = Long.valueOf(parameters[index++]).longValue(); if (originEnergyLimit < 0) { - System.out.println("origin_energy_limit need > 0 "); + sysPrint("origin_energy_limit need > 0 "); return; } boolean result = walletApiWrapper .updateEnergyLimit(ownerAddress, contractAddress, originEnergyLimit); if (result) { - System.out.println("UpdateSetting for origin_energy_limit successful !!!"); + sysPrint("UpdateSetting for origin_energy_limit successful !!!"); } else { - System.out.println("UpdateSetting for origin_energy_limit failed !!!"); + sysPrint("UpdateSetting for origin_energy_limit failed !!!"); } } private void clearContractABI(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - System.out.println("Using clearContractABI command needs 1 or 2 parameters like: "); - System.out.println("clearContractABI [OwnerAddress] contract_address"); + sysPrint("Using clearContractABI command needs 1 or 2 parameters like: "); + sysPrint("clearContractABI [OwnerAddress] contract_address"); return; } @@ -1882,7 +1879,7 @@ private void clearContractABI(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -1894,17 +1891,17 @@ private void clearContractABI(String[] parameters) boolean result = walletApiWrapper.clearContractABI(ownerAddress, contractAddress); if (result) { - System.out.println("ClearContractABI successful !!!"); + sysPrint("ClearContractABI successful !!!"); } else { - System.out.println("ClearContractABI failed !!!"); + sysPrint("ClearContractABI failed !!!"); } } private void updateBrokerage(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length != 2) { - System.out.println("Using updateBrokerage command needs 2 parameters like: "); - System.out.println("updateBrokerage OwnerAddress brokerage"); + sysPrint("Using updateBrokerage command needs 2 parameters like: "); + sysPrint("updateBrokerage OwnerAddress brokerage"); return; } @@ -1913,7 +1910,7 @@ private void updateBrokerage(String[] parameters) ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } @@ -1924,9 +1921,9 @@ private void updateBrokerage(String[] parameters) boolean result = walletApiWrapper.updateBrokerage(ownerAddress, brokerage); if (result) { - System.out.println("UpdateBrokerage successful !!!"); + sysPrint("UpdateBrokerage successful !!!"); } else { - System.out.println("UpdateBrokerage failed !!!"); + sysPrint("UpdateBrokerage failed !!!"); } } @@ -1936,16 +1933,16 @@ private void getReward(String[] parameters) { if (parameters.length == 1) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } else { - System.out.println("Using getReward command needs 1 parameter like: "); - System.out.println("getReward [OwnerAddress]"); + sysPrint("Using getReward command needs 1 parameter like: "); + sysPrint("getReward [OwnerAddress]"); return; } NumberMessage reward = walletApiWrapper.getReward(ownerAddress); - System.out.println("The reward is : " + reward.getNum()); + sysPrint("The reward is : " + reward.getNum()); } private void getBrokerage(String[] parameters) { @@ -1954,16 +1951,16 @@ private void getBrokerage(String[] parameters) { if (parameters.length == 1) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } else { - System.out.println("Using getBrokerage needs 1 parameter like following: "); - System.out.println("getBrokerage [OwnerAddress]"); + sysPrint("Using getBrokerage needs 1 parameter like following: "); + sysPrint("getBrokerage [OwnerAddress]"); return; } NumberMessage brokerage = walletApiWrapper.getBrokerage(ownerAddress); - System.out.println("The brokerage is : " + brokerage.getNum()); + sysPrint("The brokerage is : " + brokerage.getNum()); } private String[] getParas(String[] para) { @@ -2003,10 +2000,10 @@ private void deployContract(String[] parameter) String[] parameters = getParas(parameter); if (parameters == null || parameters.length < 11) { - System.out.println("Using deployContract needs at least 11 parameters like: "); - System.out.println( + sysPrint("Using deployContract needs at least 11 parameters like: "); + sysPrint( "DeployContract [ownerAddress] contractName ABI byteCode constructor params isHex fee_limit consume_user_resource_percent origin_energy_limit value token_value token_id(e.g: TRXTOKEN, use # if don't provided) "); -// System.out.println( +// sysPrint( // "Note: Please append the param for constructor tightly with byteCode without any space"); return; } @@ -2027,11 +2024,11 @@ private void deployContract(String[] parameter) long consumeUserResourcePercent = Long.parseLong(parameters[idx++]); long originEnergyLimit = Long.parseLong(parameters[idx++]); if (consumeUserResourcePercent > 100 || consumeUserResourcePercent < 0) { - System.out.println("consume_user_resource_percent should be >= 0 and <= 100"); + sysPrint("consume_user_resource_percent should be >= 0 and <= 100"); return; } if (originEnergyLimit <= 0) { - System.out.println("origin_energy_limit must > 0"); + sysPrint("origin_energy_limit must > 0"); return; } if (!constructorStr.equals("#")) { @@ -2067,10 +2064,10 @@ private void deployContract(String[] parameter) consumeUserResourcePercent, originEnergyLimit, tokenValue, tokenId, libraryAddressPair, compilerVersion); if (result) { - System.out.println("Broadcast the createSmartContract successful.\n" + sysPrint("Broadcast the createSmartContract successful.\n" + "Please check the given transaction id to confirm deploy status on blockchain using getTransactionInfoById command."); } else { - System.out.println("Broadcast the createSmartContract failed !!!"); + sysPrint("Broadcast the createSmartContract failed !!!"); } } @@ -2080,14 +2077,14 @@ private void triggerContract(String[] parameters, boolean isConstant) if (isConstant) { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - System.out.println(cmdMethodStr + " needs 4 or 5 parameters like: "); - System.out.println(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex"); + sysPrint(cmdMethodStr + " needs 4 or 5 parameters like: "); + sysPrint(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex"); return; } } else { if (parameters == null || (parameters.length != 8 && parameters.length != 9)) { - System.out.println(cmdMethodStr + " needs 8 or 9 parameters like: "); - System.out.println(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex" + sysPrint(cmdMethodStr + " needs 8 or 9 parameters like: "); + sysPrint(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex" + " fee_limit value token_value token_id(e.g: TRXTOKEN, use # if don't provided)"); return; } @@ -2098,7 +2095,7 @@ private void triggerContract(String[] parameters, boolean isConstant) if (parameters.length == 5 || parameters.length == 9) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } } @@ -2133,10 +2130,10 @@ private void triggerContract(String[] parameters, boolean isConstant) isConstant); if (!isConstant) { if (result) { - System.out.println("Broadcast the " + cmdMethodStr + " successful.\n" + sysPrint("Broadcast the " + cmdMethodStr + " successful.\n" + "Please check the given transaction id to get the result on blockchain using getTransactionInfoById command"); } else { - System.out.println("Broadcast the " + cmdMethodStr + " failed"); + sysPrint("Broadcast the " + cmdMethodStr + " failed"); } } } @@ -2144,60 +2141,60 @@ private void triggerContract(String[] parameters, boolean isConstant) private void getContract(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getContract needs 1 parameter like: "); - System.out.println("GetContract contractAddress"); + sysPrint("Using getContract needs 1 parameter like: "); + sysPrint("GetContract contractAddress"); return; } byte[] addressBytes = WalletApi.decodeFromBase58Check(parameters[0]); if (addressBytes == null) { - System.out.println("GetContract: invalid address !!!"); + sysPrint("GetContract: invalid address !!!"); return; } SmartContract contractDeployContract = WalletApi.getContract(addressBytes); if (contractDeployContract != null) { - System.out.println(Utils.formatMessageString(contractDeployContract)); + sysPrint(Utils.formatMessageString(contractDeployContract)); } else { - System.out.println("Query contract failed !!!"); + sysPrint("Query contract failed !!!"); } } private void generateAddress() { AddressPrKeyPairMessage result = walletApiWrapper.generateAddress(); if (null != result) { - System.out.println(Utils.formatMessageString(result)); + sysPrint(Utils.formatMessageString(result)); } else { - System.out.println("GenerateAddress failed !!!"); + sysPrint("GenerateAddress failed !!!"); } } private void updateAccountPermission(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 2) { - System.out.println( + sysPrint( "Using updateAccountPermission needs 2 parameters, like UpdateAccountPermission ownerAddress permissions, permissions is json format"); return; } byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (ownerAddress == null) { - System.out.println("GetContract: invalid address!"); + sysPrint("GetContract: invalid address!"); return; } boolean ret = walletApiWrapper.accountPermissionUpdate(ownerAddress, parameters[1]); if (ret) { - System.out.println("UpdateAccountPermission successful !!!"); + sysPrint("UpdateAccountPermission successful !!!"); } else { - System.out.println("UpdateAccountPermission failed !!!"); + sysPrint("UpdateAccountPermission failed !!!"); } } private void getTransactionSignWeight(String[] parameters) throws InvalidProtocolBufferException { if (parameters == null || parameters.length != 1) { - System.out.println( + sysPrint( "Using getTransactionSignWeight needs 1 parameter, like getTransactionSignWeight transaction which is hex string"); return; } @@ -2207,16 +2204,16 @@ private void getTransactionSignWeight(String[] parameters) throws InvalidProtoco TransactionSignWeight transactionSignWeight = WalletApi.getTransactionSignWeight(transaction); if (transactionSignWeight != null) { - System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); + sysPrint(Utils.printTransactionSignWeight(transactionSignWeight)); } else { - System.out.println("GetTransactionSignWeight failed !!!"); + sysPrint("GetTransactionSignWeight failed !!!"); } } private void getTransactionApprovedList(String[] parameters) throws InvalidProtocolBufferException { if (parameters == null || parameters.length != 1) { - System.out.println( + sysPrint( "Using getTransactionApprovedList needs 1 parameter, like getTransactionApprovedList transaction which is hex string"); return; } @@ -2227,16 +2224,16 @@ private void getTransactionApprovedList(String[] parameters) TransactionApprovedList transactionApprovedList = WalletApi .getTransactionApprovedList(transaction); if (transactionApprovedList != null) { - System.out.println(Utils.printTransactionApprovedList(transactionApprovedList)); + sysPrint(Utils.printTransactionApprovedList(transactionApprovedList)); } else { - System.out.println("GetTransactionApprovedList failed !!!"); + sysPrint("GetTransactionApprovedList failed !!!"); } } private void addTransactionSign(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 1) { - System.out.println( + sysPrint( "Using addTransactionSign needs 1 parameter, like addTransactionSign transaction which is hex string"); return; } @@ -2244,24 +2241,24 @@ private void addTransactionSign(String[] parameters) String transactionStr = parameters[0]; Transaction transaction = Transaction.parseFrom(ByteArray.fromHexString(transactionStr)); if (transaction == null || transaction.getRawData().getContractCount() == 0) { - System.out.println("Invalid transaction !!!"); + sysPrint("Invalid transaction !!!"); return; } transaction = walletApiWrapper.addTransactionSign(transaction); if (transaction != null) { - System.out.println(Utils.printTransaction(transaction)); - System.out.println("Transaction hex string is " + + sysPrint(Utils.printTransaction(transaction)); + sysPrint("Transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); } else { - System.out.println("AddTransactionSign failed !!!"); + sysPrint("AddTransactionSign failed !!!"); } } private void broadcastTransaction(String[] parameters) throws InvalidProtocolBufferException { if (parameters == null || parameters.length != 1) { - System.out.println( + sysPrint( "Using broadcastTransaction needs 1 parameter, like broadcastTransaction transaction which is hex string"); return; } @@ -2269,15 +2266,15 @@ private void broadcastTransaction(String[] parameters) throws InvalidProtocolBuf String transactionStr = parameters[0]; Transaction transaction = Transaction.parseFrom(ByteArray.fromHexString(transactionStr)); if (transaction == null || transaction.getRawData().getContractCount() == 0) { - System.out.println("Invalid transaction"); + sysPrint("Invalid transaction"); return; } boolean ret = WalletApi.broadcastTransaction(transaction); if (ret) { - System.out.println("BroadcastTransaction successful !!!"); + sysPrint("BroadcastTransaction successful !!!"); } else { - System.out.println("BroadcastTransaction failed !!!"); + sysPrint("BroadcastTransaction failed !!!"); } } @@ -2289,29 +2286,29 @@ private void generateShieldedAddress(String[] parameters) throws IOException, Ci ShieldedWrapper.getInstance().initShieldedWaletFile(); - System.out.println("ShieldedAddress list:"); + sysPrint("ShieldedAddress list:"); for (int i = 0; i < addressNum; ++i) { Optional addressInfo = walletApiWrapper.getNewShieldedAddress(); if (addressInfo.isPresent()) { if (ShieldedWrapper.getInstance().addNewShieldedAddress(addressInfo.get(), true)) { - System.out.println(addressInfo.get().getAddress()); + sysPrint(addressInfo.get().getAddress()); } } } - System.out.println("GenerateShieldedAddress successful !!!"); + sysPrint("GenerateShieldedAddress successful !!!"); } private void listShieldedAddress() { if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - System.out.println("ListShieldedAddress failed, please loadShieldedWallet first!"); + sysPrint("ListShieldedAddress failed, please loadShieldedWallet first!"); return; } List listAddress = ShieldedWrapper.getInstance().getShieldedAddressList(); - System.out.println("ShieldedAddress :"); + sysPrint("ShieldedAddress :"); for (String address : listAddress) { - System.out.println(address); + sysPrint(address); } } @@ -2342,7 +2339,7 @@ private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) long mapIndex = Long.valueOf(parameters[parameterIndex++]); ShieldedNoteInfo noteInfo = ShieldedWrapper.getInstance().getUtxoMapNote().get(mapIndex); if (noteInfo == null) { - System.out.println("Can't find index " + mapIndex + " note."); + sysPrint("Can't find index " + mapIndex + " note."); return false; } if (i == 0) { @@ -2374,7 +2371,7 @@ private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) shieldedOutputNum = Integer.valueOf(amountString); } if ((parameters.length - parameterIndex) % 3 != 0) { - System.out.println("Invalid parameter number!"); + sysPrint("Invalid parameter number!"); return false; } @@ -2425,8 +2422,8 @@ private boolean isFromShieldedNote(String shieldedStringInputNum) { private void sendShieldedCoin(String[] parameters) throws IOException, CipherException, CancelException, ZksnarkException { if (parameters == null || parameters.length < 6) { - System.out.println("Using SendShieldedCoin command needs more than 6 parameters like: "); - System.out.println("SendShieldedCoin publicFromAddress fromAmount shieldedInputNum " + sysPrint("Using SendShieldedCoin command needs more than 6 parameters like: "); + sysPrint("SendShieldedCoin publicFromAddress fromAmount shieldedInputNum " + "input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1" + " amount1 memo1 shieldedAddress2 amount2 memo2 ... "); return; @@ -2434,24 +2431,23 @@ private void sendShieldedCoin(String[] parameters) throws IOException, CipherExc if (isFromShieldedNote(parameters[2]) && !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - System.out.println("SendShieldedCoin failed, please loadShieldedWallet first !!!"); + sysPrint("SendShieldedCoin failed, please loadShieldedWallet first !!!"); return; } boolean result = sendShieldedCoinNormal(parameters, true); if (result) { - System.out.println("SendShieldedCoin successful !!!"); + sysPrint("SendShieldedCoin successful !!!"); } else { - System.out.println("SendShieldedCoin failed !!!"); + sysPrint("SendShieldedCoin failed !!!"); } } private void sendShieldedCoinWithoutAsk(String[] parameters) throws IOException, CipherException, CancelException, ZksnarkException { if (parameters == null || parameters.length < 6) { - System.out - .println("Using SendShieldedCoinWithoutAsk command needs more than 6 parameters like: "); - System.out.println("SendShieldedCoinWithoutAsk publicFromAddress fromAmount " + sysPrint("Using SendShieldedCoinWithoutAsk command needs more than 6 parameters like: "); + sysPrint("SendShieldedCoinWithoutAsk publicFromAddress fromAmount " + "shieldedInputNum input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum " + "shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 ... "); return; @@ -2459,28 +2455,28 @@ private void sendShieldedCoinWithoutAsk(String[] parameters) throws IOException, if (isFromShieldedNote(parameters[2]) && !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - System.out.println("SendShieldedCoin failed, please loadShieldedWallet first!"); + sysPrint("SendShieldedCoin failed, please loadShieldedWallet first!"); return; } boolean result = sendShieldedCoinNormal(parameters, false); if (result) { - System.out.println("SendShieldedCoinWithoutAsk successful !!!"); + sysPrint("SendShieldedCoinWithoutAsk successful !!!"); } else { - System.out.println("SendShieldedCoinWithoutAsk failed !!!"); + sysPrint("SendShieldedCoinWithoutAsk failed !!!"); } } private void listShieldedNote(String[] parameters) { if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - System.out.println("ListShieldedNote failed, please loadShieldedWallet first!"); + sysPrint("ListShieldedNote failed, please loadShieldedWallet first!"); return; } int showType = 0; if (parameters == null || parameters.length <= 0) { - System.out.println("This command will show all the unspent notes. "); - System.out.println( + sysPrint("This command will show all the unspent notes. "); + sysPrint( "If you want to query all the spent notes and unspent notes, please use command ListShieldedNote 1 "); } else { if (!StringUtil.isNullOrEmpty(parameters[0])) { @@ -2491,16 +2487,16 @@ private void listShieldedNote(String[] parameters) { if (showType == 0) { List utxoList = ShieldedWrapper.getInstance().getvalidateSortUtxoList(); if (utxoList.size() == 0) { - System.out.println("Unspent note is 0."); + sysPrint("Unspent note is 0."); } else { - System.out.println("Unspent note list like:"); + sysPrint("Unspent note list like:"); for (String string : utxoList) { - System.out.println(string); + sysPrint(string); } } } else { Map noteMap = ShieldedWrapper.getInstance().getUtxoMapNote(); - System.out.println("All notes list like:"); + sysPrint("All notes list like:"); for (Entry entry : noteMap.entrySet()) { String string = entry.getValue().getPaymentAddress() + " "; string += entry.getValue().getValue(); @@ -2512,7 +2508,7 @@ private void listShieldedNote(String[] parameters) { string += "UnSpent"; string += " "; string += ZenUtils.getMemo(entry.getValue().getMemo()); - System.out.println(string); + sysPrint(string); } List noteList = ShieldedWrapper.getInstance().getSpendUtxoList(); @@ -2527,14 +2523,14 @@ private void listShieldedNote(String[] parameters) { string += "Spent"; string += " "; string += ZenUtils.getMemo(noteInfo.getMemo()); - System.out.println(string); + sysPrint(string); } } } private void resetShieldedNote() { if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - System.out.println("ResetShieldedNote failed, please loadShieldedWallet first!"); + sysPrint("ResetShieldedNote failed, please loadShieldedWallet first!"); return; } @@ -2543,8 +2539,8 @@ private void resetShieldedNote() { private void scanNoteByIvk(String[] parameters) { if (parameters == null || parameters.length != 3) { - System.out.println("Using ScanNotebyIvk command needs 3 parameters like: "); - System.out.println("ScanNotebyIvk ivk startNum endNum "); + sysPrint("Using ScanNotebyIvk command needs 3 parameters like: "); + sysPrint("ScanNotebyIvk ivk startNum endNum "); return; } @@ -2553,7 +2549,7 @@ private void scanNoteByIvk(String[] parameters) { startNum = Long.parseLong(parameters[1]); endNum = Long.parseLong(parameters[2]); } catch (NumberFormatException e) { - System.out.println("Invalid parameter: startNum, endNum."); + sysPrint("Invalid parameter: startNum, endNum."); return; } @@ -2562,8 +2558,8 @@ private void scanNoteByIvk(String[] parameters) { private void scanAndMarkNoteByAddress(String[] parameters) { if (parameters == null || parameters.length != 3) { - System.out.println("Using scanAndMarkNotebyAddress needs 3 parameters like: "); - System.out.println("scanAndMarkNotebyAddress shieldedAddress startNum endNum "); + sysPrint("Using scanAndMarkNotebyAddress needs 3 parameters like: "); + sysPrint("scanAndMarkNotebyAddress shieldedAddress startNum endNum "); return; } long startNum, endNum; @@ -2571,7 +2567,7 @@ private void scanAndMarkNoteByAddress(String[] parameters) { startNum = Long.parseLong(parameters[1]); endNum = Long.parseLong(parameters[2]); } catch (NumberFormatException e) { - System.out.println("Invalid parameter: startNum, endNum."); + sysPrint("Invalid parameter: startNum, endNum."); return; } @@ -2580,8 +2576,8 @@ private void scanAndMarkNoteByAddress(String[] parameters) { private void ScanNoteByOvk(String[] parameters) { if (parameters == null || parameters.length != 3) { - System.out.println("Using scanNotebyOvk command needs 3 parameters like: "); - System.out.println("scanNotebyOvk ovk startNum endNum"); + sysPrint("Using scanNotebyOvk command needs 3 parameters like: "); + sysPrint("scanNotebyOvk ovk startNum endNum"); return; } long startNum, endNum; @@ -2589,7 +2585,7 @@ private void ScanNoteByOvk(String[] parameters) { startNum = Long.parseLong(parameters[1]); endNum = Long.parseLong(parameters[2]); } catch (NumberFormatException e) { - System.out.println("Invalid parameter: startNum, endNum."); + sysPrint("Invalid parameter: startNum, endNum."); return; } @@ -2598,32 +2594,32 @@ private void ScanNoteByOvk(String[] parameters) { private void getShieldedNullifier(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getShieldedNullifier needs 1 parameter like: "); - System.out.println("getShieldedNullifier index"); + sysPrint("Using getShieldedNullifier needs 1 parameter like: "); + sysPrint("getShieldedNullifier index"); return; } long index = Long.valueOf(parameters[0]); String hash = walletApiWrapper.getShieldedNulltifier(index); if (hash != null) { - System.out.println("ShieldedNullifier:" + hash); + sysPrint("ShieldedNullifier:" + hash); } else { - System.out.println("GetShieldedNullifier failed !!!"); + sysPrint("GetShieldedNullifier failed !!!"); } } private void getSpendingKey() { Optional sk = WalletApi.getSpendingKey(); if (!sk.isPresent()) { - System.out.println("GetSpendingKey failed !!!"); + sysPrint("GetSpendingKey failed !!!"); } else { - System.out.println(ByteArray.toHexString(sk.get().getValue().toByteArray())); + sysPrint(ByteArray.toHexString(sk.get().getValue().toByteArray())); } } private void getExpandedSpendingKey(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getExpandedSpendingKey command needs 1 parameter like: "); - System.out.println("getExpandedSpendingKey sk "); + sysPrint("Using getExpandedSpendingKey command needs 1 parameter like: "); + sysPrint("getExpandedSpendingKey sk "); return; } String spendingKey = parameters[0]; @@ -2632,18 +2628,18 @@ private void getExpandedSpendingKey(String[] parameters) { .setValue(ByteString.copyFrom(ByteArray.fromHexString(spendingKey))).build(); Optional esk = WalletApi.getExpandedSpendingKey(sk); if (!esk.isPresent()) { - System.out.println("GetExpandedSpendingKey failed !!!"); + sysPrint("GetExpandedSpendingKey failed !!!"); } else { - System.out.println("ask:" + ByteArray.toHexString(esk.get().getAsk().toByteArray())); - System.out.println("nsk:" + ByteArray.toHexString(esk.get().getNsk().toByteArray())); - System.out.println("ovk:" + ByteArray.toHexString(esk.get().getOvk().toByteArray())); + sysPrint("ask:" + ByteArray.toHexString(esk.get().getAsk().toByteArray())); + sysPrint("nsk:" + ByteArray.toHexString(esk.get().getNsk().toByteArray())); + sysPrint("ovk:" + ByteArray.toHexString(esk.get().getOvk().toByteArray())); } } private void getAkFromAsk(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getAkFromAsk needs 1 parameter like: "); - System.out.println("getAkFromAsk ask "); + sysPrint("Using getAkFromAsk needs 1 parameter like: "); + sysPrint("getAkFromAsk ask "); return; } String ask = parameters[0]; @@ -2652,16 +2648,16 @@ private void getAkFromAsk(String[] parameters) { .setValue(ByteString.copyFrom(ByteArray.fromHexString(ask))).build(); Optional ak = WalletApi.getAkFromAsk(ask1); if (!ak.isPresent()) { - System.out.println("GetAkFromAsk failed !!!"); + sysPrint("GetAkFromAsk failed !!!"); } else { - System.out.println("ak:" + ByteArray.toHexString(ak.get().getValue().toByteArray())); + sysPrint("ak:" + ByteArray.toHexString(ak.get().getValue().toByteArray())); } } private void getNkFromNsk(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using getNkFromNsk needs 1 parameter like: "); - System.out.println("getNkFromNsk nsk "); + sysPrint("Using getNkFromNsk needs 1 parameter like: "); + sysPrint("getNkFromNsk nsk "); return; } String nsk = parameters[0]; @@ -2670,17 +2666,17 @@ private void getNkFromNsk(String[] parameters) { .setValue(ByteString.copyFrom(ByteArray.fromHexString(nsk))).build(); Optional nk = WalletApi.getNkFromNsk(nsk1); if (!nk.isPresent()) { - System.out.println("GetNkFromNsk failed !!!"); + sysPrint("GetNkFromNsk failed !!!"); } else { - System.out.println("nk:" + ByteArray.toHexString(nk.get().getValue().toByteArray())); + sysPrint("nk:" + ByteArray.toHexString(nk.get().getValue().toByteArray())); } } private void getIncomingViewingKey(String[] parameters) { if (parameters == null || parameters.length != 2 || parameters[0].length() != 64 || parameters[1].length() != 64) { - System.out.println("Using getIncomingViewingKey needs 2 parameters like: "); - System.out.println("getIncomingViewingKey ak[64] nk[64] "); + sysPrint("Using getIncomingViewingKey needs 2 parameters like: "); + sysPrint("getIncomingViewingKey ak[64] nk[64] "); return; } String ak = parameters[0]; @@ -2692,25 +2688,25 @@ private void getIncomingViewingKey(String[] parameters) { Optional ivk = WalletApi.getIncomingViewingKey(vk); if (!ivk.isPresent()) { - System.out.println("GetIncomingViewingKey failed !!!"); + sysPrint("GetIncomingViewingKey failed !!!"); } else { - System.out.println("ivk:" + ByteArray.toHexString(ivk.get().getIvk().toByteArray())); + sysPrint("ivk:" + ByteArray.toHexString(ivk.get().getIvk().toByteArray())); } } private void getDiversifier(String[] parameters) { Optional diversifierMessage = WalletApi.getDiversifier(); if (!diversifierMessage.isPresent()) { - System.out.println("GetDiversifier failed !!!"); + sysPrint("GetDiversifier failed !!!"); } else { - System.out.println(ByteArray.toHexString(diversifierMessage.get().getD().toByteArray())); + sysPrint(ByteArray.toHexString(diversifierMessage.get().getD().toByteArray())); } } private void getShieldedPaymentAddress(String[] parameters) { if (parameters == null || parameters.length != 2 || parameters[1].length() != 22) { - System.out.println("Using getShieldedPaymentAddress command needs 2 parameters like: "); - System.out.println("getShieldedPaymentAddress ivk[64] d[22] "); + sysPrint("Using getShieldedPaymentAddress command needs 2 parameters like: "); + sysPrint("getShieldedPaymentAddress ivk[64] d[22] "); return; } String ivk = parameters[0]; @@ -2729,11 +2725,10 @@ private void getShieldedPaymentAddress(String[] parameters) { Optional paymentAddress = WalletApi.getZenPaymentAddress(ivk_d); if (!paymentAddress.isPresent()) { - System.out.println("GetShieldedPaymentAddress failed !!!"); + sysPrint("GetShieldedPaymentAddress failed !!!"); } else { - System.out - .println("pkd:" + ByteArray.toHexString(paymentAddress.get().getPkD().toByteArray())); - System.out.println("shieldedAddress:" + paymentAddress.get().getPaymentAddress()); + sysPrint("pkd:" + ByteArray.toHexString(paymentAddress.get().getPkD().toByteArray())); + sysPrint("shieldedAddress:" + paymentAddress.get().getPaymentAddress()); } } @@ -2743,11 +2738,11 @@ private void backupShieldedAddress() throws IOException, CipherException { for (int i = 0; i < priKey.length; i++) { StringUtils.printOneByte(priKey[i]); } - System.out.println(); + sysPrint("\n"); StringUtils.clear(priKey); - System.out.println("BackupShieldedAddress successful !!!"); + sysPrint("BackupShieldedAddress successful !!!"); } else { - System.out.println("BackupShieldedAddress failed !!!"); + sysPrint("BackupShieldedAddress failed !!!"); } } @@ -2762,21 +2757,21 @@ private void importShieldedAddress() throws CipherException, IOException { walletApiWrapper.getNewShieldedAddressBySkAndD(sk, d); if (addressInfo.isPresent() && ShieldedWrapper.getInstance().addNewShieldedAddress(addressInfo.get(), false)) { - System.out.println("Import new shielded address is: " + addressInfo.get().getAddress()); - System.out.println("ImportShieldedAddress successful !!!"); + sysPrint("Import new shielded address is: " + addressInfo.get().getAddress()); + sysPrint("ImportShieldedAddress successful !!!"); } else { - System.out.println("ImportShieldedAddress failed !!!"); + sysPrint("ImportShieldedAddress failed !!!"); } } else { - System.out.println("ImportShieldedAddress failed !!!"); + sysPrint("ImportShieldedAddress failed !!!"); } } private void marketSellAsset(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length != 5) { - System.out.println("Using MarketSellAsset command needs 5 parameters like: "); - System.out.println( + sysPrint("Using MarketSellAsset command needs 5 parameters like: "); + sysPrint( "MarketSellAsset ownerAddress sellTokenId sellTokenQuantity buyTokenId buyTokenQuantity"); return; } @@ -2784,7 +2779,7 @@ private void marketSellAsset(String[] parameters) int index = 0; byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } @@ -2797,9 +2792,9 @@ private void marketSellAsset(String[] parameters) .marketSellAsset(ownerAddress, sellTokenId, sellTokenQuantity, buyTokenId, buyTokenQuantity); if (result) { - System.out.println("MarketSellAsset successful !!!"); + sysPrint("MarketSellAsset successful !!!"); } else { - System.out.println("MarketSellAsset failed !!!"); + sysPrint("MarketSellAsset failed !!!"); } } @@ -2807,8 +2802,8 @@ private void marketSellAsset(String[] parameters) private void marketCancelOrder(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length != 2) { - System.out.println("Using MarketCancelOrder command needs 2 parameters like: "); - System.out.println( + sysPrint("Using MarketCancelOrder command needs 2 parameters like: "); + sysPrint( "MarketCancelOrder ownerAddress orderId"); return; } @@ -2816,7 +2811,7 @@ private void marketCancelOrder(String[] parameters) int index = 0; byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } @@ -2825,17 +2820,17 @@ private void marketCancelOrder(String[] parameters) boolean result = walletApiWrapper .marketCancelOrder(ownerAddress, orderId); if (result) { - System.out.println("MarketCancelOrder successful !!!"); + sysPrint("MarketCancelOrder successful !!!"); } else { - System.out.println("MarketCancelOrder failed !!!"); + sysPrint("MarketCancelOrder failed !!!"); } } private void getMarketOrderByAccount(String[] parameters) { if (parameters == null || parameters.length != 1) { - System.out.println("Using GetMarketOrderByAccount command needs 1 parameters like: "); - System.out.println( + sysPrint("Using GetMarketOrderByAccount command needs 1 parameters like: "); + sysPrint( "GetMarketOrderByAccount ownerAddress"); return; } @@ -2843,23 +2838,23 @@ private void getMarketOrderByAccount(String[] parameters) { int index = 0; byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - System.out.println("Invalid OwnerAddress."); + sysPrint("Invalid OwnerAddress."); return; } Optional marketOrderList = walletApiWrapper .getMarketOrderByAccount(ownerAddress); if (!marketOrderList.isPresent()) { - System.out.println("GetMarketOrderByAccount failed !!!"); + sysPrint("GetMarketOrderByAccount failed !!!"); } else { - System.out.println(Utils.formatMessageString(marketOrderList.get())); + sysPrint(Utils.formatMessageString(marketOrderList.get())); } } private void getMarketPriceByPair(String[] parameters) { if (parameters == null || parameters.length != 2) { - System.out.println("Using GetMarketPriceByPair command needs 2 parameters like: "); - System.out.println( + sysPrint("Using GetMarketPriceByPair command needs 2 parameters like: "); + sysPrint( "GetMarketPriceByPair sellTokenId buyTokenId"); return; } @@ -2871,17 +2866,17 @@ private void getMarketPriceByPair(String[] parameters) { Optional marketPriceList = walletApiWrapper .getMarketPriceByPair(sellTokenId, buyTokenId); if (!marketPriceList.isPresent()) { - System.out.println("GetMarketPriceByPair failed !!!"); + sysPrint("GetMarketPriceByPair failed !!!"); } else { - System.out.println(Utils.formatMessageString(marketPriceList.get())); + sysPrint(Utils.formatMessageString(marketPriceList.get())); } } private void getMarketOrderListByPair(String[] parameters) { if (parameters == null || parameters.length != 2) { - System.out.println("Using getMarketOrderListByPair command needs 2 parameters like: "); - System.out.println( + sysPrint("Using getMarketOrderListByPair command needs 2 parameters like: "); + sysPrint( "getMarketOrderListByPair sellTokenId buyTokenId"); return; } @@ -2893,17 +2888,17 @@ private void getMarketOrderListByPair(String[] parameters) { Optional orderListByPair = walletApiWrapper .getMarketOrderListByPair(sellTokenId, buyTokenId); if (!orderListByPair.isPresent()) { - System.out.println("getMarketOrderListByPair failed !!!"); + sysPrint("getMarketOrderListByPair failed !!!"); } else { - System.out.println(Utils.formatMessageString(orderListByPair.get())); + sysPrint(Utils.formatMessageString(orderListByPair.get())); } } private void getMarketPairList(String[] parameters) { if (parameters == null || parameters.length != 0) { - System.out.println("Using getMarketPairList command does not need any parameters, like: "); - System.out.println( + sysPrint("Using getMarketPairList command does not need any parameters, like: "); + sysPrint( "getMarketPairList"); return; } @@ -2911,30 +2906,30 @@ private void getMarketPairList(String[] parameters) { Optional pairList = walletApiWrapper .getMarketPairList(); if (!pairList.isPresent()) { - System.out.println("getMarketPairList failed !!!"); + sysPrint("getMarketPairList failed !!!"); } else { - System.out.println(Utils.formatMessageString(pairList.get())); + sysPrint(Utils.formatMessageString(pairList.get())); } } private void create2(String[] parameters) { if (parameters == null || parameters.length != 3) { - System.out.println("Using create2 command needs 3 parameters like: "); - System.out.println("create2 address code salt"); + sysPrint("Using create2 command needs 3 parameters like: "); + sysPrint("create2 address code salt"); return; } byte[] address = WalletApi.decodeFromBase58Check(parameters[0]); if (!WalletApi.addressValid(address)) { - System.out.println("The length of address must be 21 bytes."); + sysPrint("The length of address must be 21 bytes."); return; } byte[] code = Hex.decode(parameters[1]); byte[] temp = Longs.toByteArray(Long.parseLong(parameters[2])); if (temp.length != 8) { - System.out.println("Invalid salt!"); + sysPrint("Invalid salt!"); return; } byte[] salt = new byte[32]; @@ -2943,24 +2938,24 @@ private void create2(String[] parameters) { byte[] mergedData = ByteUtil.merge(address, salt, Hash.sha3(code)); String Address = WalletApi.encode58Check(Hash.sha3omit12(mergedData)); - System.out.println("Create2 Address: " + Address); + sysPrint("Create2 Address: " + Address); return; } private void help() { - System.out.println("Help: List of Tron Wallet-cli commands"); - System.out.println( + sysPrint("Help: List of Tron Wallet-cli commands"); + sysPrint( "For more information on a specific command, type the command and it will display tips"); - System.out.println(""); + sysPrint(""); for (String commandItem : commandHelp) { - System.out.println(commandItem); + sysPrint(commandItem); } - System.out.println("Exit or Quit"); + sysPrint("Exit or Quit"); - System.out.println("Input any one of the listed commands, to display how-to tips."); + sysPrint("Input any one of the listed commands, to display how-to tips."); } private String[] getCmd(String cmdLine) { @@ -3003,14 +2998,14 @@ private String[] getCmd(String cmdLine) { } private void run() { - System.out.println(" "); - System.out.println("Welcome to Tron Wallet-Cli"); - System.out.println("Please type one of the following commands to proceed."); - System.out.println("Login, RegisterWallet or ImportWallet"); - System.out.println(" "); - System.out.println( + sysPrint(" "); + sysPrint("Welcome to Tron Wallet-Cli"); + sysPrint("Please type one of the following commands to proceed."); + sysPrint("Login, RegisterWallet or ImportWallet"); + sysPrint(" "); + sysPrint( "You may also use the Help command at anytime to display a full list of commands."); - System.out.println(" "); + sysPrint(" "); try { Terminal terminal = TerminalBuilder.builder().system(true).dumb(true).build(); @@ -3469,34 +3464,34 @@ private void run() { } case "exit": case "quit": { - System.out.println("Exit !!!"); + sysPrint("Exit !!!"); return; } default: { - System.out.println("Invalid cmd: " + cmd); + sysPrint("Invalid cmd: " + cmd); help(); } } } catch (CipherException e) { - System.out.println(cmd + " failed!"); - System.out.println(e.getMessage()); + sysPrint(cmd + " failed!"); + sysPrint(e.getMessage()); } catch (IOException e) { - System.out.println(cmd + " failed!"); - System.out.println(e.getMessage()); + sysPrint(cmd + " failed!"); + sysPrint(e.getMessage()); } catch (CancelException e) { - System.out.println(cmd + " failed!"); - System.out.println(e.getMessage()); + sysPrint(cmd + " failed!"); + sysPrint(e.getMessage()); } catch (EndOfFileException e) { - System.out.println("\nBye."); + sysPrint("\nBye."); return; } catch (Exception e) { - System.out.println(cmd + " failed!"); - System.out.println(e.getMessage()); + sysPrint(cmd + " failed!"); + sysPrint(e.getMessage()); e.printStackTrace(); } } } catch (IOException e) { - System.out.println("\nBye."); + sysPrint("\nBye."); return; } } @@ -3505,9 +3500,9 @@ private void getChainParameters() { Optional result = walletApiWrapper.getChainParameters(); if (result.isPresent()) { ChainParameters chainParameters = result.get(); - System.out.println(Utils.formatMessageString(chainParameters)); + sysPrint(Utils.formatMessageString(chainParameters)); } else { - System.out.println("GetChainParameters failed !!"); + sysPrint("GetChainParameters failed !!"); } } From d05bc3b637b1f8e1942fe1eab1795624fc9a0053 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Wed, 15 Jan 2020 18:10:37 +0800 Subject: [PATCH 265/445] fix wrong relative uri --- doc/zh/nile_shielded_usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/zh/nile_shielded_usage.md b/doc/zh/nile_shielded_usage.md index 7456757cd..f91a2ebab 100644 --- a/doc/zh/nile_shielded_usage.md +++ b/doc/zh/nile_shielded_usage.md @@ -134,7 +134,7 @@ ImportShieldedWallet successful !!! **警告:导出匿名地址和导入匿名地址过程中的字符串`sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea d :8e52fc48c2a47509e7eb78`是重要的秘密信息,请不要泄露给其他人。** 如果你准备好了匿名钱包,就可以进行匿名转账了,当然在此之前,我们先通过普通钱包获取一些TRZ。你可以首先创建一个普通钱包,它包含了一个公开地址。我们使用已经注册好的一个普通钱包,它包含一个公开地址`TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq`,然后在[页面](http://nileex.io/join/getJoinPage)上请求获取一些TRZ用于测试。 -![](./images/nile_shielded_usage7.png) +![](./../images/nile_shielded_usage7.png) *在完成这些准备工作后,下面介绍如何基于wallet-cli的匿名钱包地址进行匿名转账* From f80a12b9c1688c168ba02a58525f918258bf264f Mon Sep 17 00:00:00 2001 From: Hou Date: Sun, 19 Jan 2020 18:06:43 +0800 Subject: [PATCH 266/445] Unified minimum currency unit is sun --- README.md | 8 ++++---- src/main/java/org/tron/demo/TransactionSignDemo.java | 2 +- .../java/org/tron/demo/TransactionSignDemoForSM2.java | 2 +- src/main/java/org/tron/walletcli/Client.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ac6fa8aa8..e2cf0a43f 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,8 @@ OwnerAddress > The address of the account that initiated the transaction, optional, default is the address of the login account. frozen_balance -> The amount of frozen funds, the unit is drop. -> The minimum value is **1000000 drop(1TRX)**. +> The amount of frozen funds, the unit is sun. +> The minimum value is **1000000 sun(1TRX)**. frozen_duration > Freeze time, this value is currently only allowed for **3 days**. @@ -148,7 +148,7 @@ The bandwidth calculation rule is: constant * FrozenFunds * days -Assuming freeze 1TRX(1_000_000 DROP), 3 days, bandwidth obtained = 1 * 1_000_000 * 3 = 3_000_000. +Assuming freeze 1TRX(1_000_000 SUN), 3 days, bandwidth obtained = 1 * 1_000_000 * 3 = 3_000_000. All contracts consume bandwidth, including transferring, transferring of assets, voting, freezing, etc. Querying does not consume bandwidth. Each contract needs to consume **100_000 bandwidth**. @@ -661,7 +661,7 @@ Select another account and enter the local password. i.e. TKwhcDup8L2PH5r6hxp5CQ need a private key of TKwhcDup8L2PH5r6hxp5CQvQzZqJLmKvZP to sign a transaction. The weight of each account is 1, threshold of access is 2. When the requirements are met, users -will be notified with “Send 10000000000000000 drop to TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW +will be notified with “Send 10000000000000000 sun to TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW successful !!”. This is how multiple accounts user multi-signature when using the same cli. diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 6bb842758..56509185d 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -107,7 +107,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca ECKey ecKey = ECKey.fromPrivate(privateBytes); byte[] from = ecKey.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; // 100 TRX, api only receive trx in sun, and 1 trx = 1000000 sun Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 0277798aa..43917cb41 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -103,7 +103,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in drop, and 1 trx = 1000000 drop + long amount = 100_000_000L; // 100 TRX, api only receive trx in sun, and 1 trx = 1000000 sun Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index a6ec39c31..283e4410f 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -712,9 +712,9 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, boolean result = walletApiWrapper.sendCoin(ownerAddress, toAddress, amount); if (result) { - System.out.println("Send " + amount + " drop to " + base58ToAddress + " successful !!"); + System.out.println("Send " + amount + " sun to " + base58ToAddress + " successful !!"); } else { - System.out.println("Send " + amount + " drop to " + base58ToAddress + " failed !!"); + System.out.println("Send " + amount + " sun to " + base58ToAddress + " failed !!"); } } From 87642b58c46c8d47711a7be321b4806d61b86ed4 Mon Sep 17 00:00:00 2001 From: Hou Date: Sun, 19 Jan 2020 18:35:38 +0800 Subject: [PATCH 267/445] update tips --- .../tron/common/utils/TransactionUtils.java | 5 +++- .../java/org/tron/walletserver/WalletApi.java | 26 +++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index ec994ff69..33fac80f8 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -247,11 +247,14 @@ public static Transaction setExpirationTime(Transaction transaction) { return transaction; } - public static Transaction setPermissionId(Transaction transaction) throws CancelException { + public static Transaction setPermissionId(Transaction transaction, String tipString) + throws CancelException { if (transaction.getSignatureCount() != 0 || transaction.getRawData().getContract(0).getPermissionId() != 0) { return transaction; } + + System.out.println(tipString); int permission_id = inputPermissionId(); if (permission_id < 0) { throw new CancelException("User cancelled"); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index c984905b5..aaf784540 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -351,9 +351,8 @@ private Transaction signTransaction(Transaction transaction) } transaction = TransactionUtils.setExpirationTime(transaction); - System.out.println( - "Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancell transaction."); - transaction = TransactionUtils.setPermissionId(transaction); + String tipsString = "Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancel transaction."; + transaction = TransactionUtils.setPermissionId(transaction, tipsString); while (true) { System.out.println("Please choose your key for sign."); WalletFile walletFile = selcetWalletFileE(); @@ -389,9 +388,8 @@ private Transaction signTransaction(Transaction transaction) private Transaction signOnlyForShieldedTransaction(Transaction transaction) throws CipherException, IOException, CancelException { - System.out.println( - "Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancell transaction."); - transaction = TransactionUtils.setPermissionId(transaction); + String tipsString = "Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancel transaction."; + transaction = TransactionUtils.setPermissionId(transaction, tipsString); while (true) { System.out.println("Please choose your key for sign."); WalletFile walletFile = selcetWalletFileE(); @@ -537,8 +535,8 @@ private boolean processTransaction(Transaction transaction) public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); - System.out.println("Please input permission id."); - transaction = TransactionUtils.setPermissionId(transaction); + String tipsString = "Please input permission id."; + transaction = TransactionUtils.setPermissionId(transaction, tipsString); TransactionSign.Builder builder = TransactionSign.newBuilder(); builder.setPrivateKey(ByteString.copyFrom(privateKey)); builder.setTransaction(transaction); @@ -549,8 +547,8 @@ public static Transaction signTransactionByApi(Transaction transaction, byte[] p public static TransactionExtention signTransactionByApi2( Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); - System.out.println("Please input permission id."); - transaction = TransactionUtils.setPermissionId(transaction); + String tipsString = "Please input permission id."; + transaction = TransactionUtils.setPermissionId(transaction, tipsString); TransactionSign.Builder builder = TransactionSign.newBuilder(); builder.setPrivateKey(ByteString.copyFrom(privateKey)); builder.setTransaction(transaction); @@ -561,8 +559,8 @@ public static TransactionExtention signTransactionByApi2( public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) throws CancelException { transaction = TransactionUtils.setExpirationTime(transaction); - System.out.println("Please input permission id."); - transaction = TransactionUtils.setPermissionId(transaction); + String tipsString = "Please input permission id."; + transaction = TransactionUtils.setPermissionId(transaction, tipsString); TransactionSign.Builder builder = TransactionSign.newBuilder(); builder.setPrivateKey(ByteString.copyFrom(privateKey)); builder.setTransaction(transaction); @@ -2168,8 +2166,8 @@ public Transaction addTransactionSign(Transaction transaction) transaction = TransactionUtils.setTimestamp(transaction); } transaction = TransactionUtils.setExpirationTime(transaction); - System.out.println("Please input permission id."); - transaction = TransactionUtils.setPermissionId(transaction); + String tipsString = "Please input permission id."; + transaction = TransactionUtils.setPermissionId(transaction, tipsString); System.out.println("Please choose your key for sign."); WalletFile walletFile = selcetWalletFileE(); From 249cad28b6d0710bb75680768e38d4b6af28c706 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 20 Jan 2020 14:14:29 +0800 Subject: [PATCH 268/445] Hex string output when local multi-signature is interrupted --- src/main/java/org/tron/walletserver/WalletApi.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index aaf784540..e74fd2ca8 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -376,6 +376,7 @@ private Transaction signTransaction(Transaction transaction) System.out.println(Utils.printTransactionSignWeight(weight)); System.out.println("Please confirm if continue add signature enter y or Y, else any other"); if (!confirm()) { + showTransactionAfterSign(transaction); throw new CancelException("User cancelled"); } continue; From 3facc8c5e362569508b6c1bae70e58cff83e855f Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 20 Jan 2020 15:04:04 +0800 Subject: [PATCH 269/445] typo: modify proto --- src/main/protos/core/Contract.proto | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/protos/core/Contract.proto b/src/main/protos/core/Contract.proto index 1df3dcf3c..10312278b 100644 --- a/src/main/protos/core/Contract.proto +++ b/src/main/protos/core/Contract.proto @@ -343,16 +343,14 @@ message ShieldedTransferContract { message MarketSellAssetContract { bytes owner_address = 1; - bytes sell_token_id = 4; - int64 sell_token_quantity = 6; - bytes buy_token_id = 5; - int64 buy_token_quantity = 7;//min to receive - bytes pre_price_key = 8 ;//order price position + bytes sell_token_id = 2; + int64 sell_token_quantity = 3; + bytes buy_token_id = 4; + int64 buy_token_quantity = 5; // min to receive + bytes pre_price_key = 6; // order price position } message MarketCancelOrderContract { bytes owner_address = 1; - // int64 sell_token_id = 2; - // bytes buy_token_id = 3; - bytes order_id = 4; + bytes order_id = 2; } From d1c73ffd60eac03e910ecd78a248e808a35b2f64 Mon Sep 17 00:00:00 2001 From: Hou Date: Mon, 20 Jan 2020 15:16:47 +0800 Subject: [PATCH 270/445] Adjust the code format --- README.md | 12 ++++++------ src/main/java/org/tron/demo/TransactionSignDemo.java | 2 +- .../org/tron/demo/TransactionSignDemoForSM2.java | 2 +- src/main/java/org/tron/walletcli/Client.java | 4 ++-- src/main/java/org/tron/walletserver/WalletApi.java | 6 ++++-- src/main/protos/core/Contract.proto | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e2cf0a43f..f97b29f78 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,8 @@ OwnerAddress > The address of the account that initiated the transaction, optional, default is the address of the login account. frozen_balance -> The amount of frozen funds, the unit is sun. -> The minimum value is **1000000 sun(1TRX)**. +> The amount of frozen funds, the unit is Sun. +> The minimum value is **1000000 Sun(1TRX)**. frozen_duration > Freeze time, this value is currently only allowed for **3 days**. @@ -148,7 +148,7 @@ The bandwidth calculation rule is: constant * FrozenFunds * days -Assuming freeze 1TRX(1_000_000 SUN), 3 days, bandwidth obtained = 1 * 1_000_000 * 3 = 3_000_000. +Assuming freeze 1TRX(1_000_000 Sun), 3 days, bandwidth obtained = 1 * 1_000_000 * 3 = 3_000_000. All contracts consume bandwidth, including transferring, transferring of assets, voting, freezing, etc. Querying does not consume bandwidth. Each contract needs to consume **100_000 bandwidth**. @@ -223,7 +223,7 @@ TotalSupply > Total issuing amount = account balance of the issuer at the time of issuance + all the frozen amount, before asset transfer and the issuance. TrxNum, AssetNum -> These two parameters determine the exchange rate between the issued token and the minimum unit of TRX (sun) when the token is issued. +> These two parameters determine the exchange rate between the issued token and the minimum unit of TRX (Sun) when the token is issued. FreeNetLimitPerAccount > The maximum amount of bandwidth an account is allowed to use. Token issuers can freeze TRX to obtain bandwidth (TransferAssetContract only) @@ -661,7 +661,7 @@ Select another account and enter the local password. i.e. TKwhcDup8L2PH5r6hxp5CQ need a private key of TKwhcDup8L2PH5r6hxp5CQvQzZqJLmKvZP to sign a transaction. The weight of each account is 1, threshold of access is 2. When the requirements are met, users -will be notified with “Send 10000000000000000 sun to TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW +will be notified with “Send 10000000000000000 Sun to TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW successful !!”. This is how multiple accounts user multi-signature when using the same cli. @@ -967,7 +967,7 @@ OwnerAddress > The address of the account that initiated the transaction, optional, default is the address of the login account. frozen_balance -> The amount of frozen TRX, the unit is the smallest unit (sun), the minimum is 1000000sun. +> The amount of frozen TRX, the unit is the smallest unit (Sun), the minimum is 1000000sun. frozen_duration > frezen duration, 3 days diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 56509185d..0aeeebe3b 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -107,7 +107,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca ECKey ecKey = ECKey.fromPrivate(privateBytes); byte[] from = ecKey.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in sun, and 1 trx = 1000000 sun + long amount = 100_000_000L; // 100 TRX, api only receive trx in Sun, and 1 trx = 1000000 Sun Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index 43917cb41..f31cab3aa 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -103,7 +103,7 @@ public static void main(String[] args) throws InvalidProtocolBufferException, Ca SM2 sm2 = SM2.fromPrivate(privateBytes); byte[] from = sm2.getAddress(); byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in sun, and 1 trx = 1000000 sun + long amount = 100_000_000L; // 100 TRX, api only receive trx in Sun, and 1 TRX = 1000000 Sun Transaction transaction = createTransaction(from, to, amount); byte[] transactionBytes = transaction.toByteArray(); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 283e4410f..7cf72daf4 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -712,9 +712,9 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, boolean result = walletApiWrapper.sendCoin(ownerAddress, toAddress, amount); if (result) { - System.out.println("Send " + amount + " sun to " + base58ToAddress + " successful !!"); + System.out.println("Send " + amount + " Sun to " + base58ToAddress + " successful !!"); } else { - System.out.println("Send " + amount + " sun to " + base58ToAddress + " failed !!"); + System.out.println("Send " + amount + " Sun to " + base58ToAddress + " failed !!"); } } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index e74fd2ca8..e81b0a0a6 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -351,7 +351,8 @@ private Transaction signTransaction(Transaction transaction) } transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancel transaction."; + String tipsString = "Please confirm and input your permission id, if input y or Y means " + + "default 0, other non-numeric characters will cancel transaction."; transaction = TransactionUtils.setPermissionId(transaction, tipsString); while (true) { System.out.println("Please choose your key for sign."); @@ -389,7 +390,8 @@ private Transaction signTransaction(Transaction transaction) private Transaction signOnlyForShieldedTransaction(Transaction transaction) throws CipherException, IOException, CancelException { - String tipsString = "Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancel transaction."; + String tipsString = "Please confirm and input your permission id, if input y or Y means " + + "default 0, other non-numeric characters will cancel transaction."; transaction = TransactionUtils.setPermissionId(transaction, tipsString); while (true) { System.out.println("Please choose your key for sign."); diff --git a/src/main/protos/core/Contract.proto b/src/main/protos/core/Contract.proto index f30597836..bcafc904e 100644 --- a/src/main/protos/core/Contract.proto +++ b/src/main/protos/core/Contract.proto @@ -211,7 +211,7 @@ message TriggerSmartContract { message BuyStorageContract { bytes owner_address = 1; - int64 quant = 2; // trx quantity for buy storage (sun) + int64 quant = 2; // trx quantity for buy storage (Sun) } message BuyStorageBytesContract { From bb08d73c929e5142aa7f5c07a9d97c23a7634208 Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 21 Jan 2020 17:35:14 +0800 Subject: [PATCH 271/445] add GetMarketOrderById --- src/main/java/org/tron/walletcli/Client.java | 21 +++++++++++++++++++ .../org/tron/walletcli/WalletApiWrapper.java | 4 ++++ .../org/tron/walletserver/GrpcClient.java | 12 +++++++++++ .../java/org/tron/walletserver/WalletApi.java | 4 ++++ src/main/protos/api/api.proto | 9 +++++--- 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index ac46044d6..457d69b1c 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2915,6 +2915,23 @@ private void getMarketPairList(String[] parameters) { } } + private void getMarketOrderById(String[] parameters) { + if (parameters == null || parameters.length != 0) { + System.out.println("Using getMarketOrderById command needs 1 parameters like:"); + System.out.println( + "getMarketOrderById orderId"); + return; + } + byte[] orderId = parameters[0].getBytes(); + Optional pairList = walletApiWrapper + .getMarketOrderById(orderId); + if (!pairList.isPresent()) { + System.out.println("getMarketOrderById failed !!!"); + } else { + System.out.println(Utils.formatMessageString(pairList.get())); + } + } + private void create2(String[] parameters) { if (parameters == null || parameters.length != 3) { @@ -3465,6 +3482,10 @@ private void run() { getMarketPairList(parameters); break; } + case "getmarketorderbyid": { + getMarketOrderById(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 95f153fe1..bbbff18ad 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1223,4 +1223,8 @@ public Optional getMarketPairList() { return WalletApi.getMarketPairList(); } + public Optional getMarketOrderById(byte[] order) { + return WalletApi.getMarketOrderById(order); + } + } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 1e863ef4f..6e4d2e4a5 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -964,4 +964,16 @@ public Optional getMarketPairList() { return Optional.ofNullable(orderPairList); } + public Optional getMarketOrderById(byte[] order) { + ByteString orderBytes = ByteString.copyFrom(order); + BytesMessage request = BytesMessage.newBuilder().setValue(orderBytes).build(); + MarketOrder orderPair; + if (blockingStubSolidity != null) { + orderPair =blockingStubSolidity.getMarketOrderById(request); + } else { + orderPair = blockingStubFull.getMarketOrderById(request); + } + return Optional.ofNullable(orderPair); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 645be1279..5de6e3fcc 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2437,4 +2437,8 @@ public static Optional getMarketPairList() { return rpcCli.getMarketPairList(); } + public static Optional getMarketOrderById(byte[] order) { + return rpcCli.getMarketOrderById(order); + } + } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 934385293..37e2b724f 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -704,6 +704,9 @@ service Wallet { rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { } + rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { + } + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } @@ -713,7 +716,6 @@ service Wallet { rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { } - }; service WalletSolidity { @@ -876,16 +878,17 @@ service WalletSolidity { rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { } - rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { } + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + } rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { } rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { } - }; service WalletExtension { From 051d6fde5528a488ae06764f827b47168e570479 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 10 Feb 2020 14:35:40 +0800 Subject: [PATCH 272/445] feat(market): fix GetMarketOrderById --- README.md | 21 +++++++++++++++++++- src/main/java/org/tron/walletcli/Client.java | 21 +++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dad730a7f..41bb5cf02 100644 --- a/README.md +++ b/README.md @@ -1074,7 +1074,7 @@ getTransactionInfoById 10040f993cd9452b25bf367f38edadf11176355802baf61f3c49b96b4 } ``` -- get the order created by account(include all status) +- get the order created by account(just include active status) GetMarketOrderByAccount ownerAddress Example: GetMarketOrderByAccount TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW @@ -1094,6 +1094,24 @@ GetMarketOrderByAccount TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW ] } ``` + +- get order by order_id +GetMarketOrderById orderId +Example: +GetMarketOrderById fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75db0 +``` +{ + "order_id": "fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75db0", + "owner_address": "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW", + "create_time": 1578983490000, + "sell_token_id": "_", + "sell_token_quantity": 100, + "buy_token_id": "1000001", + "buy_token_quantity": 200, + "state": "CANCELED" +} +``` + - get market pair list getMarketPairList @@ -1228,6 +1246,7 @@ For more information on a specific command, just type the command on terminal wh GetExpandedSpendingKey GetIncomingViewingKey GetMarketOrderByAccount + GetMarketOrderById GetMarketOrderListByPair GetMarketPairList GetMarketPriceByPair diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 87b70ddfa..62ba16d0a 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -96,6 +96,7 @@ public class Client { "GetExpandedSpendingKey", "GetIncomingViewingKey", "GetMarketOrderByAccount", + "GetMarketOrderById", "GetMarketOrderListByPair", "GetMarketPairList", "GetMarketPriceByPair", @@ -205,6 +206,7 @@ public class Client { "GetExpandedSpendingKey", "GetIncomingViewingKey", "GetMarketOrderByAccount", + "GetMarketOrderById", "GetMarketOrderListByPair", "GetMarketPairList", "GetMarketPriceByPair", @@ -269,7 +271,7 @@ public class Client { private void sysPrint(String msg) { - sysPrint(msg); + System.out.println(msg); } private byte[] inputPrivateKey() throws IOException { @@ -2913,19 +2915,20 @@ private void getMarketPairList(String[] parameters) { } private void getMarketOrderById(String[] parameters) { - if (parameters == null || parameters.length != 0) { - System.out.println("Using getMarketOrderById command needs 1 parameters like:"); - System.out.println( + if (parameters == null || parameters.length != 1) { + sysPrint("Using getMarketOrderById command needs 1 parameters like:"); + sysPrint( "getMarketOrderById orderId"); return; } - byte[] orderId = parameters[0].getBytes(); - Optional pairList = walletApiWrapper + + byte[] orderId = ByteArray.fromHexString(parameters[0]); + Optional order = walletApiWrapper .getMarketOrderById(orderId); - if (!pairList.isPresent()) { - System.out.println("getMarketOrderById failed !!!"); + if (!order.isPresent()) { + sysPrint("getMarketOrderById failed !!!"); } else { - System.out.println(Utils.formatMessageString(pairList.get())); + sysPrint(Utils.formatMessageString(order.get())); } } From a14e7e00b6068865896625adda08f064ead70b9b Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 10 Feb 2020 15:17:17 +0800 Subject: [PATCH 273/445] typo --- src/main/java/org/tron/walletcli/Client.java | 957 +++++++++---------- 1 file changed, 476 insertions(+), 481 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 62ba16d0a..a6ba46563 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -269,15 +269,10 @@ public class Client { "WithdrawBalance", }; - - private void sysPrint(String msg) { - System.out.println(msg); - } - private byte[] inputPrivateKey() throws IOException { byte[] temp = new byte[128]; byte[] result = null; - sysPrint("Please input private key. Max retry time:" + retryTime); + System.out.println("Please input private key. Max retry time:" + retryTime); int nTime = 0; while (nTime < retryTime) { int len = System.in.read(temp, 0, temp.length); @@ -290,7 +285,7 @@ private byte[] inputPrivateKey() throws IOException { } } StringUtils.clear(result); - sysPrint("Invalid private key, please input again."); + System.out.println("Invalid private key, please input again."); ++nTime; } StringUtils.clear(temp); @@ -301,7 +296,7 @@ private byte[] inputPrivateKey64() throws IOException { Decoder decoder = Base64.getDecoder(); byte[] temp = new byte[128]; byte[] result = null; - sysPrint("Please input private key by base64. Max retry time:" + retryTime); + System.out.println("Please input private key by base64. Max retry time:" + retryTime); int nTime = 0; while (nTime < retryTime) { int len = System.in.read(temp, 0, temp.length); @@ -313,7 +308,7 @@ private byte[] inputPrivateKey64() throws IOException { break; } } - sysPrint("Invalid base64 private key, please input again."); + System.out.println("Invalid base64 private key, please input again."); ++nTime; } StringUtils.clear(temp); @@ -326,10 +321,10 @@ private void registerWallet() throws CipherException, IOException { StringUtils.clear(password); if (null == fileName) { - sysPrint("Register wallet failed !!"); + System.out.println("Register wallet failed !!"); return; } - sysPrint("Register a wallet successful, keystore file name is " + fileName); + System.out.println("Register a wallet successful, keystore file name is " + fileName); } private void importWallet() throws CipherException, IOException { @@ -341,10 +336,10 @@ private void importWallet() throws CipherException, IOException { StringUtils.clear(priKey); if (null == fileName) { - sysPrint("Import wallet failed !!"); + System.out.println("Import wallet failed !!"); return; } - sysPrint("Import a wallet successful, keystore file name is " + fileName); + System.out.println("Import a wallet successful, keystore file name is " + fileName); } private void importWalletByBase64() throws CipherException, IOException { @@ -356,55 +351,55 @@ private void importWalletByBase64() throws CipherException, IOException { StringUtils.clear(priKey); if (null == fileName) { - sysPrint("Import wallet failed !!"); + System.out.println("Import wallet failed !!"); return; } - sysPrint("Import a wallet successful, keystore file name is " + fileName); + System.out.println("Import a wallet successful, keystore file name is " + fileName); } private void changePassword() throws IOException, CipherException { - sysPrint("Please input old password."); + System.out.println("Please input old password."); char[] oldPassword = Utils.inputPassword(false); - sysPrint("Please input new password."); + System.out.println("Please input new password."); char[] newPassword = Utils.inputPassword2Twice(); if (walletApiWrapper.changePassword(oldPassword, newPassword)) { - sysPrint("ChangePassword successful !!"); + System.out.println("ChangePassword successful !!"); } else { - sysPrint("ChangePassword failed !!"); + System.out.println("ChangePassword failed !!"); } } private void login() throws IOException, CipherException { boolean result = walletApiWrapper.login(); if (result) { - sysPrint("Login successful !!!"); + System.out.println("Login successful !!!"); } else { - sysPrint("Login failed !!!"); + System.out.println("Login failed !!!"); } } private void logout() { walletApiWrapper.logout(); - sysPrint("Logout successful !!!"); + System.out.println("Logout successful !!!"); } private void loadShieldedWallet() throws CipherException, IOException { boolean result = ShieldedWrapper.getInstance().loadShieldWallet(); if (result) { - sysPrint("LoadShieldedWallet successful !!!"); + System.out.println("LoadShieldedWallet successful !!!"); } else { - sysPrint("LoadShieldedWallet failed !!!"); + System.out.println("LoadShieldedWallet failed !!!"); } } private void backupWallet() throws IOException, CipherException { byte[] priKey = walletApiWrapper.backupWallet(); if (!ArrayUtils.isEmpty(priKey)) { - sysPrint("BackupWallet successful !!"); + System.out.println("BackupWallet successful !!"); for (int i = 0; i < priKey.length; i++) { StringUtils.printOneByte(priKey[i]); } - sysPrint("\n"); + System.out.println("\n"); } StringUtils.clear(priKey); } @@ -416,11 +411,11 @@ private void backupWallet2Base64() throws IOException, CipherException { Encoder encoder = Base64.getEncoder(); byte[] priKey64 = encoder.encode(priKey); StringUtils.clear(priKey); - sysPrint("BackupWallet successful !!"); + System.out.println("BackupWallet successful !!"); for (int i = 0; i < priKey64.length; i++) { System.out.print((char) priKey64[i]); } - sysPrint("\n"); + System.out.println("\n"); StringUtils.clear(priKey64); } } @@ -428,8 +423,8 @@ private void backupWallet2Base64() throws IOException, CipherException { private void getAddress() { String address = walletApiWrapper.getAddress(); if (address != null) { - sysPrint("GetAddress successful !!"); - sysPrint("address = " + address); + System.out.println("GetAddress successful !!"); + System.out.println("address = " + address); } } @@ -444,23 +439,23 @@ private void getBalance(String[] parameters) { } account = WalletApi.queryAccount(addressBytes); } else { - sysPrint("GetBalance needs no parameter or 1 parameter like the following: "); - sysPrint("GetBalance Address "); + System.out.println("GetBalance needs no parameter or 1 parameter like the following: "); + System.out.println("GetBalance Address "); return; } if (account == null) { - sysPrint("GetBalance failed !!!!"); + System.out.println("GetBalance failed !!!!"); } else { long balance = account.getBalance(); - sysPrint("Balance = " + balance); + System.out.println("Balance = " + balance); } } private void getAccount(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("GetAccount needs 1 parameter like the following: "); - sysPrint("GetAccount Address "); + System.out.println("GetAccount needs 1 parameter like the following: "); + System.out.println("GetAccount Address "); return; } String address = parameters[0]; @@ -471,33 +466,33 @@ private void getAccount(String[] parameters) { Account account = WalletApi.queryAccount(addressBytes); if (account == null) { - sysPrint("GetAccount failed !!!!"); + System.out.println("GetAccount failed !!!!"); } else { - sysPrint(Utils.formatMessageString(account)); + System.out.println(Utils.formatMessageString(account)); } } private void getAccountById(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("GetAccountById needs 1 parameter like the following: "); - sysPrint("GetAccountById accountId "); + System.out.println("GetAccountById needs 1 parameter like the following: "); + System.out.println("GetAccountById accountId "); return; } String accountId = parameters[0]; Account account = WalletApi.queryAccountById(accountId); if (account == null) { - sysPrint("GetAccountById failed !!!!"); + System.out.println("GetAccountById failed !!!!"); } else { - sysPrint(Utils.formatMessageString(account)); + System.out.println(Utils.formatMessageString(account)); } } private void updateAccount(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("UpdateAccount needs 1 parameter like the following: "); - sysPrint("UpdateAccount [OwnerAddress] AccountName "); + System.out.println("UpdateAccount needs 1 parameter like the following: "); + System.out.println("UpdateAccount [OwnerAddress] AccountName "); return; } @@ -506,7 +501,7 @@ private void updateAccount(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -515,17 +510,17 @@ private void updateAccount(String[] parameters) boolean ret = walletApiWrapper.updateAccount(ownerAddress, accountNameBytes); if (ret) { - sysPrint("Update Account successful !!!!"); + System.out.println("Update Account successful !!!!"); } else { - sysPrint("Update Account failed !!!!"); + System.out.println("Update Account failed !!!!"); } } private void setAccountId(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("SetAccountId needs 1 parameter like the following: "); - sysPrint("SetAccountId [OwnerAddress] AccountId "); + System.out.println("SetAccountId needs 1 parameter like the following: "); + System.out.println("SetAccountId [OwnerAddress] AccountId "); return; } @@ -534,7 +529,7 @@ private void setAccountId(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -543,17 +538,17 @@ private void setAccountId(String[] parameters) boolean ret = walletApiWrapper.setAccountId(ownerAddress, accountIdBytes); if (ret) { - sysPrint("Set AccountId successful !!!!"); + System.out.println("Set AccountId successful !!!!"); } else { - sysPrint("Set AccountId failed !!!!"); + System.out.println("Set AccountId failed !!!!"); } } private void updateAsset(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - sysPrint("UpdateAsset needs 4 parameters like the following: "); - sysPrint("UpdateAsset [OwnerAddress] newLimit newPublicLimit description url"); + System.out.println("UpdateAsset needs 4 parameters like the following: "); + System.out.println("UpdateAsset [OwnerAddress] newLimit newPublicLimit description url"); return; } @@ -562,7 +557,7 @@ private void updateAsset(String[] parameters) if (parameters.length == 5) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -579,16 +574,16 @@ private void updateAsset(String[] parameters) boolean ret = walletApiWrapper .updateAsset(ownerAddress, descriptionBytes, urlBytes, newLimit, newPublicLimit); if (ret) { - sysPrint("Update Asset successful !!!!"); + System.out.println("Update Asset successful !!!!"); } else { - sysPrint("Update Asset failed !!!!"); + System.out.println("Update Asset failed !!!!"); } } private void getAssetIssueByAccount(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("GetAssetIssueByAccount needs 1 parameter like following: "); - sysPrint("GetAssetIssueByAccount Address "); + System.out.println("GetAssetIssueByAccount needs 1 parameter like following: "); + System.out.println("GetAssetIssueByAccount Address "); return; } String address = parameters[0]; @@ -600,16 +595,16 @@ private void getAssetIssueByAccount(String[] parameters) { Optional result = WalletApi.getAssetIssueByAccount(addressBytes); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - sysPrint(Utils.formatMessageString(assetIssueList)); + System.out.println(Utils.formatMessageString(assetIssueList)); } else { - sysPrint("GetAssetIssueByAccount failed !!"); + System.out.println("GetAssetIssueByAccount failed !!"); } } private void getAccountNet(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("GetAccountNet needs 1 parameter like following: "); - sysPrint("GetAccountNet Address "); + System.out.println("GetAccountNet needs 1 parameter like following: "); + System.out.println("GetAccountNet Address "); return; } String address = parameters[0]; @@ -620,16 +615,16 @@ private void getAccountNet(String[] parameters) { AccountNetMessage result = WalletApi.getAccountNet(addressBytes); if (result == null) { - sysPrint("GetAccountNet failed !!"); + System.out.println("GetAccountNet failed !!"); } else { - sysPrint(Utils.formatMessageString(result)); + System.out.println(Utils.formatMessageString(result)); } } private void getAccountResource(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("getAccountResource needs 1 parameter like following: "); - sysPrint("getAccountResource Address "); + System.out.println("getAccountResource needs 1 parameter like following: "); + System.out.println("getAccountResource Address "); return; } String address = parameters[0]; @@ -640,9 +635,9 @@ private void getAccountResource(String[] parameters) { AccountResourceMessage result = WalletApi.getAccountResource(addressBytes); if (result == null) { - sysPrint("getAccountResource failed !!"); + System.out.println("getAccountResource failed !!"); } else { - sysPrint(Utils.formatMessageString(result)); + System.out.println(Utils.formatMessageString(result)); } } @@ -651,24 +646,24 @@ private void getAccountResource(String[] parameters) { // This function just remains for compatibility. private void getAssetIssueByName(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("GetAssetIssueByName needs 1 parameter like following: "); - sysPrint("GetAssetIssueByName AssetName "); + System.out.println("GetAssetIssueByName needs 1 parameter like following: "); + System.out.println("GetAssetIssueByName AssetName "); return; } String assetName = parameters[0]; AssetIssueContract assetIssueContract = WalletApi.getAssetIssueByName(assetName); if (assetIssueContract != null) { - sysPrint(Utils.formatMessageString(assetIssueContract)); + System.out.println(Utils.formatMessageString(assetIssueContract)); } else { - sysPrint("getAssetIssueByName failed !!"); + System.out.println("getAssetIssueByName failed !!"); } } private void getAssetIssueListByName(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("getAssetIssueListByName needs 1 parameter like following: "); - sysPrint("getAssetIssueListByName AssetName "); + System.out.println("getAssetIssueListByName needs 1 parameter like following: "); + System.out.println("getAssetIssueListByName AssetName "); return; } String assetName = parameters[0]; @@ -676,32 +671,32 @@ private void getAssetIssueListByName(String[] parameters) { Optional result = WalletApi.getAssetIssueListByName(assetName); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - sysPrint(Utils.formatMessageString(assetIssueList)); + System.out.println(Utils.formatMessageString(assetIssueList)); } else { - sysPrint("getAssetIssueListByName failed !!"); + System.out.println("getAssetIssueListByName failed !!"); } } private void getAssetIssueById(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("getAssetIssueById needs 1 parameter like following: "); - sysPrint("getAssetIssueById AssetId "); + System.out.println("getAssetIssueById needs 1 parameter like following: "); + System.out.println("getAssetIssueById AssetId "); return; } String assetId = parameters[0]; AssetIssueContract assetIssueContract = WalletApi.getAssetIssueById(assetId); if (assetIssueContract != null) { - sysPrint(Utils.formatMessageString(assetIssueContract)); + System.out.println(Utils.formatMessageString(assetIssueContract)); } else { - sysPrint("getAssetIssueById failed !!"); + System.out.println("getAssetIssueById failed !!"); } } private void sendCoin(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - sysPrint("SendCoin needs 2 parameters like following: "); - sysPrint("SendCoin [OwnerAddress] ToAddress Amount"); + System.out.println("SendCoin needs 2 parameters like following: "); + System.out.println("SendCoin [OwnerAddress] ToAddress Amount"); return; } @@ -710,7 +705,7 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -718,7 +713,7 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, String base58ToAddress = parameters[index++]; byte[] toAddress = WalletApi.decodeFromBase58Check(base58ToAddress); if (toAddress == null) { - sysPrint("Invalid toAddress."); + System.out.println("Invalid toAddress."); return; } @@ -727,17 +722,17 @@ private void sendCoin(String[] parameters) throws IOException, CipherException, boolean result = walletApiWrapper.sendCoin(ownerAddress, toAddress, amount); if (result) { - sysPrint("Send " + amount + " drop to " + base58ToAddress + " successful !!"); + System.out.println("Send " + amount + " drop to " + base58ToAddress + " successful !!"); } else { - sysPrint("Send " + amount + " drop to " + base58ToAddress + " failed !!"); + System.out.println("Send " + amount + " drop to " + base58ToAddress + " failed !!"); } } private void transferAsset(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - sysPrint("TransferAsset needs 3 parameters using the following syntax: "); - sysPrint("TransferAsset [OwnerAddress] ToAddress AssertID Amount"); + System.out.println("TransferAsset needs 3 parameters using the following syntax: "); + System.out.println("TransferAsset [OwnerAddress] ToAddress AssertID Amount"); return; } @@ -746,7 +741,7 @@ private void transferAsset(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -754,7 +749,7 @@ private void transferAsset(String[] parameters) String base58Address = parameters[index++]; byte[] toAddress = WalletApi.decodeFromBase58Check(base58Address); if (toAddress == null) { - sysPrint("Invalid toAddress."); + System.out.println("Invalid toAddress."); return; } String assertName = parameters[index++]; @@ -763,17 +758,17 @@ private void transferAsset(String[] parameters) boolean result = walletApiWrapper.transferAsset(ownerAddress, toAddress, assertName, amount); if (result) { - sysPrint("TransferAsset " + amount + " to " + base58Address + " successful !!"); + System.out.println("TransferAsset " + amount + " to " + base58Address + " successful !!"); } else { - sysPrint("TransferAsset " + amount + " to " + base58Address + " failed !!"); + System.out.println("TransferAsset " + amount + " to " + base58Address + " failed !!"); } } private void participateAssetIssue(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - sysPrint("ParticipateAssetIssue needs 3 parameters using the following syntax: "); - sysPrint("ParticipateAssetIssue [OwnerAddress] ToAddress AssetID Amount"); + System.out.println("ParticipateAssetIssue needs 3 parameters using the following syntax: "); + System.out.println("ParticipateAssetIssue [OwnerAddress] ToAddress AssetID Amount"); return; } @@ -782,7 +777,7 @@ private void participateAssetIssue(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -790,7 +785,7 @@ private void participateAssetIssue(String[] parameters) String base58Address = parameters[index++]; byte[] toAddress = WalletApi.decodeFromBase58Check(base58Address); if (toAddress == null) { - sysPrint("Invalid toAddress."); + System.out.println("Invalid toAddress."); return; } @@ -801,10 +796,10 @@ private void participateAssetIssue(String[] parameters) boolean result = walletApiWrapper .participateAssetIssue(ownerAddress, toAddress, assertName, amount); if (result) { - sysPrint("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + System.out.println("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + " successful !!"); } else { - sysPrint("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + System.out.println("ParticipateAssetIssue " + assertName + " " + amount + " from " + base58Address + " failed !!"); } } @@ -812,12 +807,12 @@ private void participateAssetIssue(String[] parameters) private void assetIssue(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 12) { - sysPrint("Use the assetIssue command for features that you require with below syntax: "); - sysPrint("AssetIssue [OwnerAddress] AssetName AbbrName TotalSupply TrxNum AssetNum Precision " + System.out.println("Use the assetIssue command for features that you require with below syntax: "); + System.out.println("AssetIssue [OwnerAddress] AssetName AbbrName TotalSupply TrxNum AssetNum Precision " + "StartDate EndDate Description Url FreeNetLimitPerAccount PublicFreeNetLimit " + "FrozenAmount0 FrozenDays0 ... FrozenAmountN FrozenDaysN"); - sysPrint("TrxNum and AssetNum represents the conversion ratio of the tron to the asset."); - sysPrint("The StartDate and EndDate format should look like 2018-03-01 2018-03-21 ."); + System.out.println("TrxNum and AssetNum represents the conversion ratio of the tron to the asset."); + System.out.println("The StartDate and EndDate format should look like 2018-03-01 2018-03-21 ."); return; } @@ -826,7 +821,7 @@ private void assetIssue(String[] parameters) if ((parameters.length & 1) == 1) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -858,7 +853,7 @@ private void assetIssue(String[] parameters) if (startDate == null || endDate == null) { System.out .println("The StartDate and EndDate format should look like 2018-03-01 2018-03-21 ."); - sysPrint("AssetIssue " + name + " failed !!"); + System.out.println("AssetIssue " + name + " failed !!"); return; } long startTime = startDate.getTime(); @@ -870,17 +865,17 @@ private void assetIssue(String[] parameters) trxNum, icoNum, precision, startTime, endTime, 0, description, url, freeAssetNetLimit, publicFreeNetLimit, frozenSupply); if (result) { - sysPrint("AssetIssue " + name + " successful !!"); + System.out.println("AssetIssue " + name + " successful !!"); } else { - sysPrint("AssetIssue " + name + " failed !!"); + System.out.println("AssetIssue " + name + " failed !!"); } } private void createAccount(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("CreateAccount needs 1 parameter using the following syntax: "); - sysPrint("CreateAccount [OwnerAddress] Address"); + System.out.println("CreateAccount needs 1 parameter using the following syntax: "); + System.out.println("CreateAccount [OwnerAddress] Address"); return; } @@ -889,30 +884,30 @@ private void createAccount(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } byte[] address = WalletApi.decodeFromBase58Check(parameters[index++]); if (address == null) { - sysPrint("Invalid Address."); + System.out.println("Invalid Address."); return; } boolean result = walletApiWrapper.createAccount(ownerAddress, address); if (result) { - sysPrint("CreateAccount successful !!"); + System.out.println("CreateAccount successful !!"); } else { - sysPrint("CreateAccount failed !!"); + System.out.println("CreateAccount failed !!"); } } private void createWitness(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("CreateWitness needs 1 parameter using the following syntax: "); - sysPrint("CreateWitness [OwnerAddress] Url"); + System.out.println("CreateWitness needs 1 parameter using the following syntax: "); + System.out.println("CreateWitness [OwnerAddress] Url"); return; } @@ -921,7 +916,7 @@ private void createWitness(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -930,17 +925,17 @@ private void createWitness(String[] parameters) boolean result = walletApiWrapper.createWitness(ownerAddress, url); if (result) { - sysPrint("CreateWitness successful !!"); + System.out.println("CreateWitness successful !!"); } else { - sysPrint("CreateWitness failed !!"); + System.out.println("CreateWitness failed !!"); } } private void updateWitness(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("updateWitness needs 1 parameter using the following syntax: "); - sysPrint("updateWitness [OwnerAddress] Url"); + System.out.println("updateWitness needs 1 parameter using the following syntax: "); + System.out.println("updateWitness [OwnerAddress] Url"); return; } @@ -949,7 +944,7 @@ private void updateWitness(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -957,9 +952,9 @@ private void updateWitness(String[] parameters) boolean result = walletApiWrapper.updateWitness(ownerAddress, url); if (result) { - sysPrint("updateWitness successful !!"); + System.out.println("updateWitness successful !!"); } else { - sysPrint("updateWitness failed !!"); + System.out.println("updateWitness failed !!"); } } @@ -967,9 +962,9 @@ private void listWitnesses() { Optional result = walletApiWrapper.listWitnesses(); if (result.isPresent()) { WitnessList witnessList = result.get(); - sysPrint(Utils.formatMessageString(witnessList)); + System.out.println(Utils.formatMessageString(witnessList)); } else { - sysPrint("List witnesses failed !!"); + System.out.println("List witnesses failed !!"); } } @@ -977,16 +972,16 @@ private void getAssetIssueList() { Optional result = walletApiWrapper.getAssetIssueList(); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - sysPrint(Utils.formatMessageString(assetIssueList)); + System.out.println(Utils.formatMessageString(assetIssueList)); } else { - sysPrint("GetAssetIssueList failed !!"); + System.out.println("GetAssetIssueList failed !!"); } } private void getAssetIssueList(String[] parameters) { if (parameters == null || parameters.length != 2) { - sysPrint("ListAssetIssuePaginated needs 2 parameters using the following syntax: "); - sysPrint("ListAssetIssuePaginated offset limit "); + System.out.println("ListAssetIssuePaginated needs 2 parameters using the following syntax: "); + System.out.println("ListAssetIssuePaginated offset limit "); return; } int offset = Integer.parseInt(parameters[0]); @@ -994,16 +989,16 @@ private void getAssetIssueList(String[] parameters) { Optional result = walletApiWrapper.getAssetIssueList(offset, limit); if (result.isPresent()) { AssetIssueList assetIssueList = result.get(); - sysPrint(Utils.formatMessageString(assetIssueList)); + System.out.println(Utils.formatMessageString(assetIssueList)); } else { - sysPrint("GetAssetIssueListPaginated failed !!!"); + System.out.println("GetAssetIssueListPaginated failed !!!"); } } private void getProposalsListPaginated(String[] parameters) { if (parameters == null || parameters.length != 2) { - sysPrint("ListProposalsPaginated needs 2 parameters use the following syntax:"); - sysPrint("ListProposalsPaginated offset limit "); + System.out.println("ListProposalsPaginated needs 2 parameters use the following syntax:"); + System.out.println("ListProposalsPaginated offset limit "); return; } int offset = Integer.parseInt(parameters[0]); @@ -1011,9 +1006,9 @@ private void getProposalsListPaginated(String[] parameters) { Optional result = walletApiWrapper.getProposalListPaginated(offset, limit); if (result.isPresent()) { ProposalList proposalList = result.get(); - sysPrint(Utils.formatMessageString(proposalList)); + System.out.println(Utils.formatMessageString(proposalList)); } else { - sysPrint("ListProposalsPaginated failed !!!"); + System.out.println("ListProposalsPaginated failed !!!"); } } @@ -1021,7 +1016,7 @@ private void getExchangesListPaginated(String[] parameters) { if (parameters == null || parameters.length != 2) { System.out .println("ListExchangesPaginated command needs 2 parameters, use the following syntax:"); - sysPrint("ListExchangesPaginated offset limit "); + System.out.println("ListExchangesPaginated offset limit "); return; } int offset = Integer.parseInt(parameters[0]); @@ -1029,9 +1024,9 @@ private void getExchangesListPaginated(String[] parameters) { Optional result = walletApiWrapper.getExchangeListPaginated(offset, limit); if (result.isPresent()) { ExchangeList exchangeList = result.get(); - sysPrint(Utils.formatMessageString(exchangeList)); + System.out.println(Utils.formatMessageString(exchangeList)); } else { - sysPrint("ListExchangesPaginated failed !!!"); + System.out.println("ListExchangesPaginated failed !!!"); } } @@ -1042,11 +1037,11 @@ private void listNodes() { List list = nodeList.getNodesList(); for (int i = 0; i < list.size(); i++) { Node node = list.get(i); - sysPrint("IP::" + ByteArray.toStr(node.getAddress().getHost().toByteArray())); - sysPrint("Port::" + node.getAddress().getPort()); + System.out.println("IP::" + ByteArray.toStr(node.getAddress().getHost().toByteArray())); + System.out.println("Port::" + node.getAddress().getPort()); } } else { - sysPrint("GetAssetIssueList " + " failed !!!"); + System.out.println("GetAssetIssueList " + " failed !!!"); } } @@ -1054,14 +1049,14 @@ private void getBlock(String[] parameters) { long blockNum = -1; if (parameters == null || parameters.length == 0) { - sysPrint("Get current block !!!"); + System.out.println("Get current block !!!"); } else { if (parameters.length != 1) { - sysPrint("GetBlock has too many parameters !!!"); - sysPrint("You can get current block using the following command:"); - sysPrint("GetBlock"); - sysPrint("Or get block by number with the following syntax:"); - sysPrint("GetBlock BlockNum"); + System.out.println("GetBlock has too many parameters !!!"); + System.out.println("You can get current block using the following command:"); + System.out.println("GetBlock"); + System.out.println("Or get block by number with the following syntax:"); + System.out.println("GetBlock BlockNum"); } blockNum = Long.parseLong(parameters[0]); } @@ -1069,37 +1064,37 @@ private void getBlock(String[] parameters) { if (WalletApi.getRpcVersion() == 2) { BlockExtention blockExtention = walletApiWrapper.getBlock2(blockNum); if (blockExtention == null) { - sysPrint("No block for num : " + blockNum); + System.out.println("No block for num : " + blockNum); return; } - sysPrint(Utils.printBlockExtention(blockExtention)); + System.out.println(Utils.printBlockExtention(blockExtention)); } else { Block block = walletApiWrapper.getBlock(blockNum); if (block == null) { - sysPrint("No block for num : " + blockNum); + System.out.println("No block for num : " + blockNum); return; } - sysPrint(Utils.printBlock(block)); + System.out.println(Utils.printBlock(block)); } } private void getTransactionCountByBlockNum(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Use GetTransactionCountByBlockNum command with below syntax"); - sysPrint("GetTransactionCountByBlockNum number"); + System.out.println("Use GetTransactionCountByBlockNum command with below syntax"); + System.out.println("GetTransactionCountByBlockNum number"); return; } long blockNum = Long.parseLong(parameters[0]); long count = walletApiWrapper.getTransactionCountByBlockNum(blockNum); - sysPrint("The block contains " + count + " transactions"); + System.out.println("The block contains " + count + " transactions"); } private void voteWitness(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 2) { - sysPrint("Use VoteWitness command with below syntax: "); - sysPrint("VoteWitness [OwnerAddress] Address0 Count0 ... AddressN CountN"); + System.out.println("Use VoteWitness command with below syntax: "); + System.out.println("VoteWitness [OwnerAddress] Address0 Count0 ... AddressN CountN"); return; } @@ -1108,7 +1103,7 @@ private void voteWitness(String[] parameters) if ((parameters.length & 1) != 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1122,9 +1117,9 @@ private void voteWitness(String[] parameters) boolean result = walletApiWrapper.voteWitness(ownerAddress, witness); if (result) { - sysPrint("VoteWitness successful !!!"); + System.out.println("VoteWitness successful !!!"); } else { - sysPrint("VoteWitness failed !!!"); + System.out.println("VoteWitness failed !!!"); } } @@ -1141,8 +1136,8 @@ private void freezeBalance(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || !(parameters.length == 2 || parameters.length == 3 || parameters.length == 4 || parameters.length == 5)) { - sysPrint("Use freezeBalance command with below syntax: "); - sysPrint("freezeBalance [OwnerAddress] frozen_balance frozen_duration " + System.out.println("Use freezeBalance command with below syntax: "); + System.out.println("freezeBalance [OwnerAddress] frozen_balance frozen_duration " + "[ResourceCode:0 BANDWIDTH,1 ENERGY] [receiverAddress]"); return; } @@ -1175,17 +1170,17 @@ private void freezeBalance(String[] parameters) boolean result = walletApiWrapper.freezeBalance(ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (result) { - sysPrint("FreezeBalance successful !!!"); + System.out.println("FreezeBalance successful !!!"); } else { - sysPrint("FreezeBalance failed !!!"); + System.out.println("FreezeBalance failed !!!"); } } private void unfreezeBalance(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 1 || parameters.length > 3) { - sysPrint("Use unfreezeBalance command with below syntax: "); - sysPrint( + System.out.println("Use unfreezeBalance command with below syntax: "); + System.out.println( "unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH,1 CPU) [receiverAddress]"); return; } @@ -1211,39 +1206,39 @@ private void unfreezeBalance(String[] parameters) boolean result = walletApiWrapper.unfreezeBalance(ownerAddress, resourceCode, receiverAddress); if (result) { - sysPrint("UnfreezeBalance successful !!!"); + System.out.println("UnfreezeBalance successful !!!"); } else { - sysPrint("UnfreezeBalance failed !!!"); + System.out.println("UnfreezeBalance failed !!!"); } } private void unfreezeAsset(String[] parameters) throws IOException, CipherException, CancelException { - sysPrint("Use UnfreezeAsset command like: "); - sysPrint("UnfreezeAsset [OwnerAddress] "); + System.out.println("Use UnfreezeAsset command like: "); + System.out.println("UnfreezeAsset [OwnerAddress] "); byte[] ownerAddress = null; if (parameters != null && parameters.length > 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } boolean result = walletApiWrapper.unfreezeAsset(ownerAddress); if (result) { - sysPrint("UnfreezeAsset successful !!!"); + System.out.println("UnfreezeAsset successful !!!"); } else { - sysPrint("UnfreezeAsset failed !!!"); + System.out.println("UnfreezeAsset failed !!!"); } } private void createProposal(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 2) { - sysPrint("Use createProposal command with below syntax: "); - sysPrint("createProposal [OwnerAddress] id0 value0 ... idN valueN"); + System.out.println("Use createProposal command with below syntax: "); + System.out.println("createProposal [OwnerAddress] id0 value0 ... idN valueN"); return; } @@ -1252,7 +1247,7 @@ private void createProposal(String[] parameters) if ((parameters.length & 1) != 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1265,17 +1260,17 @@ private void createProposal(String[] parameters) } boolean result = walletApiWrapper.createProposal(ownerAddress, parametersMap); if (result) { - sysPrint("CreateProposal successful !!"); + System.out.println("CreateProposal successful !!"); } else { - sysPrint("CreateProposal failed !!"); + System.out.println("CreateProposal failed !!"); } } private void approveProposal(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - sysPrint("Use approveProposal command with below syntax: "); - sysPrint("approveProposal [OwnerAddress] id is_or_not_add_approval"); + System.out.println("Use approveProposal command with below syntax: "); + System.out.println("approveProposal [OwnerAddress] id is_or_not_add_approval"); return; } @@ -1284,7 +1279,7 @@ private void approveProposal(String[] parameters) if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1293,17 +1288,17 @@ private void approveProposal(String[] parameters) boolean is_add_approval = Boolean.valueOf(parameters[index++]); boolean result = walletApiWrapper.approveProposal(ownerAddress, id, is_add_approval); if (result) { - sysPrint("ApproveProposal successful !!!"); + System.out.println("ApproveProposal successful !!!"); } else { - sysPrint("ApproveProposal failed !!!"); + System.out.println("ApproveProposal failed !!!"); } } private void deleteProposal(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("Use deleteProposal command with below syntax: "); - sysPrint("deleteProposal [OwnerAddress] proposalId"); + System.out.println("Use deleteProposal command with below syntax: "); + System.out.println("deleteProposal [OwnerAddress] proposalId"); return; } @@ -1312,7 +1307,7 @@ private void deleteProposal(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1320,9 +1315,9 @@ private void deleteProposal(String[] parameters) long id = Long.valueOf(parameters[index++]); boolean result = walletApiWrapper.deleteProposal(ownerAddress, id); if (result) { - sysPrint("DeleteProposal successful !!!"); + System.out.println("DeleteProposal successful !!!"); } else { - sysPrint("DeleteProposal failed !!!"); + System.out.println("DeleteProposal failed !!!"); } } @@ -1331,16 +1326,16 @@ private void listProposals() { Optional result = walletApiWrapper.getProposalsList(); if (result.isPresent()) { ProposalList proposalList = result.get(); - sysPrint(Utils.formatMessageString(proposalList)); + System.out.println(Utils.formatMessageString(proposalList)); } else { - sysPrint("List witnesses failed !!!"); + System.out.println("List witnesses failed !!!"); } } private void getProposal(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getProposal command needs 1 parameter like: "); - sysPrint("getProposal id "); + System.out.println("Using getProposal command needs 1 parameter like: "); + System.out.println("getProposal id "); return; } String id = parameters[0]; @@ -1348,17 +1343,17 @@ private void getProposal(String[] parameters) { Optional result = WalletApi.getProposal(id); if (result.isPresent()) { Proposal proposal = result.get(); - sysPrint(Utils.formatMessageString(proposal)); + System.out.println(Utils.formatMessageString(proposal)); } else { - sysPrint("GetProposal failed !!!"); + System.out.println("GetProposal failed !!!"); } } private void getDelegatedResource(String[] parameters) { if (parameters == null || parameters.length != 2) { - sysPrint("Using getDelegatedResource command needs 2 parameters like: "); - sysPrint("getDelegatedResource fromAddress toAddress"); + System.out.println("Using getDelegatedResource command needs 2 parameters like: "); + System.out.println("getDelegatedResource fromAddress toAddress"); return; } String fromAddress = parameters[0]; @@ -1366,16 +1361,16 @@ private void getDelegatedResource(String[] parameters) { Optional result = WalletApi.getDelegatedResource(fromAddress, toAddress); if (result.isPresent()) { DelegatedResourceList delegatedResourceList = result.get(); - sysPrint(Utils.formatMessageString(delegatedResourceList)); + System.out.println(Utils.formatMessageString(delegatedResourceList)); } else { - sysPrint("GetDelegatedResource failed !!!"); + System.out.println("GetDelegatedResource failed !!!"); } } private void getDelegatedResourceAccountIndex(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getDelegatedResourceAccountIndex command needs 1 parameter like: "); - sysPrint("getDelegatedResourceAccountIndex address"); + System.out.println("Using getDelegatedResourceAccountIndex command needs 1 parameter like: "); + System.out.println("getDelegatedResourceAccountIndex address"); return; } String address = parameters[0]; @@ -1383,9 +1378,9 @@ private void getDelegatedResourceAccountIndex(String[] parameters) { .getDelegatedResourceAccountIndex(address); if (result.isPresent()) { DelegatedResourceAccountIndex delegatedResourceAccountIndex = result.get(); - sysPrint(Utils.formatMessageString(delegatedResourceAccountIndex)); + System.out.println(Utils.formatMessageString(delegatedResourceAccountIndex)); } else { - sysPrint("GetDelegatedResourceAccountIndex failed !!"); + System.out.println("GetDelegatedResourceAccountIndex failed !!"); } } @@ -1393,8 +1388,8 @@ private void getDelegatedResourceAccountIndex(String[] parameters) { private void exchangeCreate(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - sysPrint("Using exchangeCreate command needs 4 or 5 parameters like: "); - sysPrint("exchangeCreate [OwnerAddress] first_token_id first_token_balance " + System.out.println("Using exchangeCreate command needs 4 or 5 parameters like: "); + System.out.println("exchangeCreate [OwnerAddress] first_token_id first_token_balance " + "second_token_id second_token_balance"); return; } @@ -1404,7 +1399,7 @@ private void exchangeCreate(String[] parameters) if (parameters.length == 5) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1416,17 +1411,17 @@ private void exchangeCreate(String[] parameters) boolean result = walletApiWrapper.exchangeCreate(ownerAddress, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); if (result) { - sysPrint("ExchangeCreate successful !!!"); + System.out.println("ExchangeCreate successful !!!"); } else { - sysPrint("ExchangeCreate failed !!!"); + System.out.println("ExchangeCreate failed !!!"); } } private void exchangeInject(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - sysPrint("Using exchangeInject command needs 3 or 4 parameters like: "); - sysPrint("exchangeInject [OwnerAddress] exchange_id token_id quantity"); + System.out.println("Using exchangeInject command needs 3 or 4 parameters like: "); + System.out.println("exchangeInject [OwnerAddress] exchange_id token_id quantity"); return; } @@ -1435,7 +1430,7 @@ private void exchangeInject(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1445,17 +1440,17 @@ private void exchangeInject(String[] parameters) long quant = Long.valueOf(parameters[index++]); boolean result = walletApiWrapper.exchangeInject(ownerAddress, exchangeId, tokenId, quant); if (result) { - sysPrint("ExchangeInject successful !!!"); + System.out.println("ExchangeInject successful !!!"); } else { - sysPrint("ExchangeInject failed !!!"); + System.out.println("ExchangeInject failed !!!"); } } private void exchangeWithdraw(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 3 && parameters.length != 4)) { - sysPrint("Using exchangeWithdraw command needs 3 or 4 parameters like: "); - sysPrint("exchangeWithdraw [OwnerAddress] exchange_id token_id quantity"); + System.out.println("Using exchangeWithdraw command needs 3 or 4 parameters like: "); + System.out.println("exchangeWithdraw [OwnerAddress] exchange_id token_id quantity"); return; } @@ -1464,7 +1459,7 @@ private void exchangeWithdraw(String[] parameters) if (parameters.length == 4) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1474,16 +1469,16 @@ private void exchangeWithdraw(String[] parameters) long quant = Long.valueOf(parameters[index++]); boolean result = walletApiWrapper.exchangeWithdraw(ownerAddress, exchangeId, tokenId, quant); if (result) { - sysPrint("ExchangeWithdraw successful !!!"); + System.out.println("ExchangeWithdraw successful !!!"); } else { - sysPrint("ExchangeWithdraw failed !!!"); + System.out.println("ExchangeWithdraw failed !!!"); } } private void exchangeTransaction(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - sysPrint("Using exchangeTransaction command needs 4 or 5 parameters like: "); + System.out.println("Using exchangeTransaction command needs 4 or 5 parameters like: "); System.out .println("exchangeTransaction [OwnerAddress] exchange_id token_id quantity expected"); return; @@ -1494,7 +1489,7 @@ private void exchangeTransaction(String[] parameters) if (parameters.length == 5) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1506,9 +1501,9 @@ private void exchangeTransaction(String[] parameters) boolean result = walletApiWrapper .exchangeTransaction(ownerAddress, exchangeId, tokenId, quant, expected); if (result) { - sysPrint("ExchangeTransaction successful !!!"); + System.out.println("ExchangeTransaction successful !!!"); } else { - sysPrint("ExchangeTransaction failed !!!"); + System.out.println("ExchangeTransaction failed !!!"); } } @@ -1516,16 +1511,16 @@ private void listExchanges() { Optional result = walletApiWrapper.getExchangeList(); if (result.isPresent()) { ExchangeList exchangeList = result.get(); - sysPrint(Utils.formatMessageString(exchangeList)); + System.out.println(Utils.formatMessageString(exchangeList)); } else { - sysPrint("ListExchanges failed !!!"); + System.out.println("ListExchanges failed !!!"); } } private void getExchange(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getExchange command needs 1 parameter like: "); - sysPrint("getExchange id"); + System.out.println("Using getExchange command needs 1 parameter like: "); + System.out.println("getExchange id"); return; } String id = parameters[0]; @@ -1533,49 +1528,49 @@ private void getExchange(String[] parameters) { Optional result = walletApiWrapper.getExchange(id); if (result.isPresent()) { Exchange exchange = result.get(); - sysPrint(Utils.formatMessageString(exchange)); + System.out.println(Utils.formatMessageString(exchange)); } else { - sysPrint("GetExchange failed !!!"); + System.out.println("GetExchange failed !!!"); } } private void withdrawBalance(String[] parameters) throws IOException, CipherException, CancelException { - sysPrint("Using withdrawBalance command like: "); - sysPrint("withdrawBalance [OwnerAddress] "); + System.out.println("Using withdrawBalance command like: "); + System.out.println("withdrawBalance [OwnerAddress] "); byte[] ownerAddress = null; if (parameters != null && parameters.length > 0) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } boolean result = walletApiWrapper.withdrawBalance(ownerAddress); if (result) { - sysPrint("WithdrawBalance successful !!!"); + System.out.println("WithdrawBalance successful !!!"); } else { - sysPrint("WithdrawBalance failed !!!"); + System.out.println("WithdrawBalance failed !!!"); } } private void getTotalTransaction() { NumberMessage totalTransition = walletApiWrapper.getTotalTransaction(); - sysPrint("The number of total transactions is : " + totalTransition.getNum()); + System.out.println("The number of total transactions is : " + totalTransition.getNum()); } private void getNextMaintenanceTime() { NumberMessage nextMaintenanceTime = walletApiWrapper.getNextMaintenanceTime(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = formatter.format(nextMaintenanceTime.getNum()); - sysPrint("Next maintenance time is : " + date); + System.out.println("Next maintenance time is : " + date); } private void getTransactionById(String[] parameters) { String txid = ""; if (parameters == null || parameters.length != 1) { - sysPrint("Using getTransactionById command needs 1 parameter, transaction id"); + System.out.println("Using getTransactionById command needs 1 parameter, transaction id"); return; } else { txid = parameters[0]; @@ -1583,16 +1578,16 @@ private void getTransactionById(String[] parameters) { Optional result = WalletApi.getTransactionById(txid); if (result.isPresent()) { Transaction transaction = result.get(); - sysPrint(Utils.printTransaction(transaction)); + System.out.println(Utils.printTransaction(transaction)); } else { - sysPrint("GetTransactionById failed !!"); + System.out.println("GetTransactionById failed !!"); } } private void getTransactionInfoById(String[] parameters) { String txid = ""; if (parameters == null || parameters.length != 1) { - sysPrint("Using getTransactionInfoById command needs 1 parameter, transaction id"); + System.out.println("Using getTransactionInfoById command needs 1 parameter, transaction id"); return; } else { txid = parameters[0]; @@ -1600,16 +1595,16 @@ private void getTransactionInfoById(String[] parameters) { Optional result = WalletApi.getTransactionInfoById(txid); if (result.isPresent() && !result.get().equals(TransactionInfo.getDefaultInstance())) { TransactionInfo transactionInfo = result.get(); - sysPrint(Utils.formatMessageString(transactionInfo)); + System.out.println(Utils.formatMessageString(transactionInfo)); } else { - sysPrint("GetTransactionInfoById failed !!!"); + System.out.println("GetTransactionInfoById failed !!!"); } } private void getTransactionsFromThis(String[] parameters) { if (parameters == null || parameters.length != 3) { - sysPrint("Using getTransactionsFromThis command needs 3 parameters like: "); - sysPrint("getTransactionsFromThis Address offset limit"); + System.out.println("Using getTransactionsFromThis command needs 3 parameters like: "); + System.out.println("getTransactionsFromThis Address offset limit"); return; } String address = parameters[0]; @@ -1626,12 +1621,12 @@ private void getTransactionsFromThis(String[] parameters) { if (result.isPresent()) { TransactionListExtention transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - sysPrint("No transaction from " + address); + System.out.println("No transaction from " + address); return; } - sysPrint(Utils.printTransactionList(transactionList)); + System.out.println(Utils.printTransactionList(transactionList)); } else { - sysPrint("GetTransactionsFromThis failed !!!"); + System.out.println("GetTransactionsFromThis failed !!!"); } } else { Optional result = WalletApi @@ -1639,20 +1634,20 @@ private void getTransactionsFromThis(String[] parameters) { if (result.isPresent()) { TransactionList transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - sysPrint("No transaction from " + address); + System.out.println("No transaction from " + address); return; } - sysPrint(Utils.printTransactionList(transactionList)); + System.out.println(Utils.printTransactionList(transactionList)); } else { - sysPrint("GetTransactionsFromThis failed !!!"); + System.out.println("GetTransactionsFromThis failed !!!"); } } } private void getTransactionsToThis(String[] parameters) { if (parameters == null || parameters.length != 3) { - sysPrint("Using getTransactionsToThis needs 3 parameters like: "); - sysPrint("getTransactionsToThis Address offset limit"); + System.out.println("Using getTransactionsToThis needs 3 parameters like: "); + System.out.println("getTransactionsToThis Address offset limit"); return; } String address = parameters[0]; @@ -1669,12 +1664,12 @@ private void getTransactionsToThis(String[] parameters) { if (result.isPresent()) { TransactionListExtention transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - sysPrint("No transaction to " + address); + System.out.println("No transaction to " + address); return; } - sysPrint(Utils.printTransactionList(transactionList)); + System.out.println(Utils.printTransactionList(transactionList)); } else { - sysPrint("getTransactionsToThis failed !!!"); + System.out.println("getTransactionsToThis failed !!!"); } } else { Optional result = WalletApi @@ -1682,20 +1677,20 @@ private void getTransactionsToThis(String[] parameters) { if (result.isPresent()) { TransactionList transactionList = result.get(); if (transactionList.getTransactionCount() == 0) { - sysPrint("No transaction to " + address); + System.out.println("No transaction to " + address); return; } - sysPrint(Utils.printTransactionList(transactionList)); + System.out.println(Utils.printTransactionList(transactionList)); } else { - sysPrint("getTransactionsToThis failed !!!"); + System.out.println("getTransactionsToThis failed !!!"); } } } // private void getTransactionsToThisCount(String[] parameters) { // if (parameters == null || parameters.length != 1) { -// sysPrint("getTransactionsToThisCount need 1 parameter like following: "); -// sysPrint("getTransactionsToThisCount Address"); +// System.out.println("getTransactionsToThisCount need 1 parameter like following: "); +// System.out.println("getTransactionsToThisCount Address"); // return; // } // String address = parameters[0]; @@ -1711,7 +1706,7 @@ private void getTransactionsToThis(String[] parameters) { private void getBlockById(String[] parameters) { String blockID = ""; if (parameters == null || parameters.length != 1) { - sysPrint("Using getBlockById command needs 1 parameter like: "); + System.out.println("Using getBlockById command needs 1 parameter like: "); return; } else { blockID = parameters[0]; @@ -1719,9 +1714,9 @@ private void getBlockById(String[] parameters) { Optional result = WalletApi.getBlockById(blockID); if (result.isPresent()) { Block block = result.get(); - sysPrint(Utils.printBlock(block)); + System.out.println(Utils.printBlock(block)); } else { - sysPrint("GetBlockById failed !!"); + System.out.println("GetBlockById failed !!"); } } @@ -1742,17 +1737,17 @@ private void getBlockByLimitNext(String[] parameters) { Optional result = WalletApi.getBlockByLimitNext2(start, end); if (result.isPresent()) { BlockListExtention blockList = result.get(); - sysPrint(Utils.printBlockList(blockList)); + System.out.println(Utils.printBlockList(blockList)); } else { - sysPrint("GetBlockByLimitNext failed !!"); + System.out.println("GetBlockByLimitNext failed !!"); } } else { Optional result = WalletApi.getBlockByLimitNext(start, end); if (result.isPresent()) { BlockList blockList = result.get(); - sysPrint(Utils.printBlockList(blockList)); + System.out.println(Utils.printBlockList(blockList)); } else { - sysPrint("GetBlockByLimitNext failed !!"); + System.out.println("GetBlockByLimitNext failed !!"); } } } @@ -1760,7 +1755,7 @@ private void getBlockByLimitNext(String[] parameters) { private void getBlockByLatestNum(String[] parameters) { long num = 0; if (parameters == null || parameters.length != 1) { - sysPrint("Using getBlockByLatestNum command needs 1 parameter, block_num"); + System.out.println("Using getBlockByLatestNum command needs 1 parameter, block_num"); return; } else { num = Long.parseLong(parameters[0]); @@ -1770,24 +1765,24 @@ private void getBlockByLatestNum(String[] parameters) { if (result.isPresent()) { BlockListExtention blockList = result.get(); if (blockList.getBlockCount() == 0) { - sysPrint("No block"); + System.out.println("No block"); return; } - sysPrint(Utils.printBlockList(blockList)); + System.out.println(Utils.printBlockList(blockList)); } else { - sysPrint("GetBlockByLimitNext failed !!"); + System.out.println("GetBlockByLimitNext failed !!"); } } else { Optional result = WalletApi.getBlockByLatestNum(num); if (result.isPresent()) { BlockList blockList = result.get(); if (blockList.getBlockCount() == 0) { - sysPrint("No block"); + System.out.println("No block"); return; } - sysPrint(Utils.printBlockList(blockList)); + System.out.println(Utils.printBlockList(blockList)); } else { - sysPrint("GetBlockByLimitNext failed !!"); + System.out.println("GetBlockByLimitNext failed !!"); } } } @@ -1795,8 +1790,8 @@ private void getBlockByLatestNum(String[] parameters) { private void updateSetting(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - sysPrint("Using updateSetting needs 2 parameters like: "); - sysPrint("updateSetting [OwnerAddress] contract_address consume_user_resource_percent"); + System.out.println("Using updateSetting needs 2 parameters like: "); + System.out.println("updateSetting [OwnerAddress] contract_address consume_user_resource_percent"); return; } @@ -1805,36 +1800,36 @@ private void updateSetting(String[] parameters) if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (contractAddress == null) { - sysPrint("Invalid contractAddress."); + System.out.println("Invalid contractAddress."); return; } long consumeUserResourcePercent = Long.valueOf(parameters[index++]).longValue(); if (consumeUserResourcePercent > 100 || consumeUserResourcePercent < 0) { - sysPrint("consume_user_resource_percent must >= 0 and <= 100"); + System.out.println("consume_user_resource_percent must >= 0 and <= 100"); return; } boolean result = walletApiWrapper .updateSetting(ownerAddress, contractAddress, consumeUserResourcePercent); if (result) { - sysPrint("UpdateSetting successful !!!"); + System.out.println("UpdateSetting successful !!!"); } else { - sysPrint("UpdateSetting failed !!!"); + System.out.println("UpdateSetting failed !!!"); } } private void updateEnergyLimit(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 2 && parameters.length != 3)) { - sysPrint("Using updateEnergyLimit command needs 2 parameters like: "); - sysPrint("updateEnergyLimit [OwnerAddress] contract_address energy_limit"); + System.out.println("Using updateEnergyLimit command needs 2 parameters like: "); + System.out.println("updateEnergyLimit [OwnerAddress] contract_address energy_limit"); return; } @@ -1843,36 +1838,36 @@ private void updateEnergyLimit(String[] parameters) if (parameters.length == 3) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (contractAddress == null) { - sysPrint("Invalid contractAddress."); + System.out.println("Invalid contractAddress."); return; } long originEnergyLimit = Long.valueOf(parameters[index++]).longValue(); if (originEnergyLimit < 0) { - sysPrint("origin_energy_limit need > 0 "); + System.out.println("origin_energy_limit need > 0 "); return; } boolean result = walletApiWrapper .updateEnergyLimit(ownerAddress, contractAddress, originEnergyLimit); if (result) { - sysPrint("UpdateSetting for origin_energy_limit successful !!!"); + System.out.println("UpdateSetting for origin_energy_limit successful !!!"); } else { - sysPrint("UpdateSetting for origin_energy_limit failed !!!"); + System.out.println("UpdateSetting for origin_energy_limit failed !!!"); } } private void clearContractABI(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || (parameters.length != 1 && parameters.length != 2)) { - sysPrint("Using clearContractABI command needs 1 or 2 parameters like: "); - sysPrint("clearContractABI [OwnerAddress] contract_address"); + System.out.println("Using clearContractABI command needs 1 or 2 parameters like: "); + System.out.println("clearContractABI [OwnerAddress] contract_address"); return; } @@ -1881,7 +1876,7 @@ private void clearContractABI(String[] parameters) if (parameters.length == 2) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -1893,17 +1888,17 @@ private void clearContractABI(String[] parameters) boolean result = walletApiWrapper.clearContractABI(ownerAddress, contractAddress); if (result) { - sysPrint("ClearContractABI successful !!!"); + System.out.println("ClearContractABI successful !!!"); } else { - sysPrint("ClearContractABI failed !!!"); + System.out.println("ClearContractABI failed !!!"); } } private void updateBrokerage(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length != 2) { - sysPrint("Using updateBrokerage command needs 2 parameters like: "); - sysPrint("updateBrokerage OwnerAddress brokerage"); + System.out.println("Using updateBrokerage command needs 2 parameters like: "); + System.out.println("updateBrokerage OwnerAddress brokerage"); return; } @@ -1912,7 +1907,7 @@ private void updateBrokerage(String[] parameters) ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } @@ -1923,9 +1918,9 @@ private void updateBrokerage(String[] parameters) boolean result = walletApiWrapper.updateBrokerage(ownerAddress, brokerage); if (result) { - sysPrint("UpdateBrokerage successful !!!"); + System.out.println("UpdateBrokerage successful !!!"); } else { - sysPrint("UpdateBrokerage failed !!!"); + System.out.println("UpdateBrokerage failed !!!"); } } @@ -1935,16 +1930,16 @@ private void getReward(String[] parameters) { if (parameters.length == 1) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } else { - sysPrint("Using getReward command needs 1 parameter like: "); - sysPrint("getReward [OwnerAddress]"); + System.out.println("Using getReward command needs 1 parameter like: "); + System.out.println("getReward [OwnerAddress]"); return; } NumberMessage reward = walletApiWrapper.getReward(ownerAddress); - sysPrint("The reward is : " + reward.getNum()); + System.out.println("The reward is : " + reward.getNum()); } private void getBrokerage(String[] parameters) { @@ -1953,16 +1948,16 @@ private void getBrokerage(String[] parameters) { if (parameters.length == 1) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } else { - sysPrint("Using getBrokerage needs 1 parameter like following: "); - sysPrint("getBrokerage [OwnerAddress]"); + System.out.println("Using getBrokerage needs 1 parameter like following: "); + System.out.println("getBrokerage [OwnerAddress]"); return; } NumberMessage brokerage = walletApiWrapper.getBrokerage(ownerAddress); - sysPrint("The brokerage is : " + brokerage.getNum()); + System.out.println("The brokerage is : " + brokerage.getNum()); } private String[] getParas(String[] para) { @@ -2002,10 +1997,10 @@ private void deployContract(String[] parameter) String[] parameters = getParas(parameter); if (parameters == null || parameters.length < 11) { - sysPrint("Using deployContract needs at least 11 parameters like: "); - sysPrint( + System.out.println("Using deployContract needs at least 11 parameters like: "); + System.out.println( "DeployContract [ownerAddress] contractName ABI byteCode constructor params isHex fee_limit consume_user_resource_percent origin_energy_limit value token_value token_id(e.g: TRXTOKEN, use # if don't provided) "); -// sysPrint( +// System.out.println( // "Note: Please append the param for constructor tightly with byteCode without any space"); return; } @@ -2026,11 +2021,11 @@ private void deployContract(String[] parameter) long consumeUserResourcePercent = Long.parseLong(parameters[idx++]); long originEnergyLimit = Long.parseLong(parameters[idx++]); if (consumeUserResourcePercent > 100 || consumeUserResourcePercent < 0) { - sysPrint("consume_user_resource_percent should be >= 0 and <= 100"); + System.out.println("consume_user_resource_percent should be >= 0 and <= 100"); return; } if (originEnergyLimit <= 0) { - sysPrint("origin_energy_limit must > 0"); + System.out.println("origin_energy_limit must > 0"); return; } if (!constructorStr.equals("#")) { @@ -2066,10 +2061,10 @@ private void deployContract(String[] parameter) consumeUserResourcePercent, originEnergyLimit, tokenValue, tokenId, libraryAddressPair, compilerVersion); if (result) { - sysPrint("Broadcast the createSmartContract successful.\n" + System.out.println("Broadcast the createSmartContract successful.\n" + "Please check the given transaction id to confirm deploy status on blockchain using getTransactionInfoById command."); } else { - sysPrint("Broadcast the createSmartContract failed !!!"); + System.out.println("Broadcast the createSmartContract failed !!!"); } } @@ -2079,14 +2074,14 @@ private void triggerContract(String[] parameters, boolean isConstant) if (isConstant) { if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - sysPrint(cmdMethodStr + " needs 4 or 5 parameters like: "); - sysPrint(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex"); + System.out.println(cmdMethodStr + " needs 4 or 5 parameters like: "); + System.out.println(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex"); return; } } else { if (parameters == null || (parameters.length != 8 && parameters.length != 9)) { - sysPrint(cmdMethodStr + " needs 8 or 9 parameters like: "); - sysPrint(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex" + System.out.println(cmdMethodStr + " needs 8 or 9 parameters like: "); + System.out.println(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex" + " fee_limit value token_value token_id(e.g: TRXTOKEN, use # if don't provided)"); return; } @@ -2097,7 +2092,7 @@ private void triggerContract(String[] parameters, boolean isConstant) if (parameters.length == 5 || parameters.length == 9) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } } @@ -2132,10 +2127,10 @@ private void triggerContract(String[] parameters, boolean isConstant) isConstant); if (!isConstant) { if (result) { - sysPrint("Broadcast the " + cmdMethodStr + " successful.\n" + System.out.println("Broadcast the " + cmdMethodStr + " successful.\n" + "Please check the given transaction id to get the result on blockchain using getTransactionInfoById command"); } else { - sysPrint("Broadcast the " + cmdMethodStr + " failed"); + System.out.println("Broadcast the " + cmdMethodStr + " failed"); } } } @@ -2143,60 +2138,60 @@ private void triggerContract(String[] parameters, boolean isConstant) private void getContract(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getContract needs 1 parameter like: "); - sysPrint("GetContract contractAddress"); + System.out.println("Using getContract needs 1 parameter like: "); + System.out.println("GetContract contractAddress"); return; } byte[] addressBytes = WalletApi.decodeFromBase58Check(parameters[0]); if (addressBytes == null) { - sysPrint("GetContract: invalid address !!!"); + System.out.println("GetContract: invalid address !!!"); return; } SmartContract contractDeployContract = WalletApi.getContract(addressBytes); if (contractDeployContract != null) { - sysPrint(Utils.formatMessageString(contractDeployContract)); + System.out.println(Utils.formatMessageString(contractDeployContract)); } else { - sysPrint("Query contract failed !!!"); + System.out.println("Query contract failed !!!"); } } private void generateAddress() { AddressPrKeyPairMessage result = walletApiWrapper.generateAddress(); if (null != result) { - sysPrint(Utils.formatMessageString(result)); + System.out.println(Utils.formatMessageString(result)); } else { - sysPrint("GenerateAddress failed !!!"); + System.out.println("GenerateAddress failed !!!"); } } private void updateAccountPermission(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 2) { - sysPrint( + System.out.println( "Using updateAccountPermission needs 2 parameters, like UpdateAccountPermission ownerAddress permissions, permissions is json format"); return; } byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (ownerAddress == null) { - sysPrint("GetContract: invalid address!"); + System.out.println("GetContract: invalid address!"); return; } boolean ret = walletApiWrapper.accountPermissionUpdate(ownerAddress, parameters[1]); if (ret) { - sysPrint("UpdateAccountPermission successful !!!"); + System.out.println("UpdateAccountPermission successful !!!"); } else { - sysPrint("UpdateAccountPermission failed !!!"); + System.out.println("UpdateAccountPermission failed !!!"); } } private void getTransactionSignWeight(String[] parameters) throws InvalidProtocolBufferException { if (parameters == null || parameters.length != 1) { - sysPrint( + System.out.println( "Using getTransactionSignWeight needs 1 parameter, like getTransactionSignWeight transaction which is hex string"); return; } @@ -2206,16 +2201,16 @@ private void getTransactionSignWeight(String[] parameters) throws InvalidProtoco TransactionSignWeight transactionSignWeight = WalletApi.getTransactionSignWeight(transaction); if (transactionSignWeight != null) { - sysPrint(Utils.printTransactionSignWeight(transactionSignWeight)); + System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); } else { - sysPrint("GetTransactionSignWeight failed !!!"); + System.out.println("GetTransactionSignWeight failed !!!"); } } private void getTransactionApprovedList(String[] parameters) throws InvalidProtocolBufferException { if (parameters == null || parameters.length != 1) { - sysPrint( + System.out.println( "Using getTransactionApprovedList needs 1 parameter, like getTransactionApprovedList transaction which is hex string"); return; } @@ -2226,16 +2221,16 @@ private void getTransactionApprovedList(String[] parameters) TransactionApprovedList transactionApprovedList = WalletApi .getTransactionApprovedList(transaction); if (transactionApprovedList != null) { - sysPrint(Utils.printTransactionApprovedList(transactionApprovedList)); + System.out.println(Utils.printTransactionApprovedList(transactionApprovedList)); } else { - sysPrint("GetTransactionApprovedList failed !!!"); + System.out.println("GetTransactionApprovedList failed !!!"); } } private void addTransactionSign(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 1) { - sysPrint( + System.out.println( "Using addTransactionSign needs 1 parameter, like addTransactionSign transaction which is hex string"); return; } @@ -2243,24 +2238,24 @@ private void addTransactionSign(String[] parameters) String transactionStr = parameters[0]; Transaction transaction = Transaction.parseFrom(ByteArray.fromHexString(transactionStr)); if (transaction == null || transaction.getRawData().getContractCount() == 0) { - sysPrint("Invalid transaction !!!"); + System.out.println("Invalid transaction !!!"); return; } transaction = walletApiWrapper.addTransactionSign(transaction); if (transaction != null) { - sysPrint(Utils.printTransaction(transaction)); - sysPrint("Transaction hex string is " + + System.out.println(Utils.printTransaction(transaction)); + System.out.println("Transaction hex string is " + ByteArray.toHexString(transaction.toByteArray())); } else { - sysPrint("AddTransactionSign failed !!!"); + System.out.println("AddTransactionSign failed !!!"); } } private void broadcastTransaction(String[] parameters) throws InvalidProtocolBufferException { if (parameters == null || parameters.length != 1) { - sysPrint( + System.out.println( "Using broadcastTransaction needs 1 parameter, like broadcastTransaction transaction which is hex string"); return; } @@ -2268,15 +2263,15 @@ private void broadcastTransaction(String[] parameters) throws InvalidProtocolBuf String transactionStr = parameters[0]; Transaction transaction = Transaction.parseFrom(ByteArray.fromHexString(transactionStr)); if (transaction == null || transaction.getRawData().getContractCount() == 0) { - sysPrint("Invalid transaction"); + System.out.println("Invalid transaction"); return; } boolean ret = WalletApi.broadcastTransaction(transaction); if (ret) { - sysPrint("BroadcastTransaction successful !!!"); + System.out.println("BroadcastTransaction successful !!!"); } else { - sysPrint("BroadcastTransaction failed !!!"); + System.out.println("BroadcastTransaction failed !!!"); } } @@ -2288,29 +2283,29 @@ private void generateShieldedAddress(String[] parameters) throws IOException, Ci ShieldedWrapper.getInstance().initShieldedWaletFile(); - sysPrint("ShieldedAddress list:"); + System.out.println("ShieldedAddress list:"); for (int i = 0; i < addressNum; ++i) { Optional addressInfo = walletApiWrapper.getNewShieldedAddress(); if (addressInfo.isPresent()) { if (ShieldedWrapper.getInstance().addNewShieldedAddress(addressInfo.get(), true)) { - sysPrint(addressInfo.get().getAddress()); + System.out.println(addressInfo.get().getAddress()); } } } - sysPrint("GenerateShieldedAddress successful !!!"); + System.out.println("GenerateShieldedAddress successful !!!"); } private void listShieldedAddress() { if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - sysPrint("ListShieldedAddress failed, please loadShieldedWallet first!"); + System.out.println("ListShieldedAddress failed, please loadShieldedWallet first!"); return; } List listAddress = ShieldedWrapper.getInstance().getShieldedAddressList(); - sysPrint("ShieldedAddress :"); + System.out.println("ShieldedAddress :"); for (String address : listAddress) { - sysPrint(address); + System.out.println(address); } } @@ -2341,7 +2336,7 @@ private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) long mapIndex = Long.valueOf(parameters[parameterIndex++]); ShieldedNoteInfo noteInfo = ShieldedWrapper.getInstance().getUtxoMapNote().get(mapIndex); if (noteInfo == null) { - sysPrint("Can't find index " + mapIndex + " note."); + System.out.println("Can't find index " + mapIndex + " note."); return false; } if (i == 0) { @@ -2373,7 +2368,7 @@ private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) shieldedOutputNum = Integer.valueOf(amountString); } if ((parameters.length - parameterIndex) % 3 != 0) { - sysPrint("Invalid parameter number!"); + System.out.println("Invalid parameter number!"); return false; } @@ -2424,8 +2419,8 @@ private boolean isFromShieldedNote(String shieldedStringInputNum) { private void sendShieldedCoin(String[] parameters) throws IOException, CipherException, CancelException, ZksnarkException { if (parameters == null || parameters.length < 6) { - sysPrint("Using SendShieldedCoin command needs more than 6 parameters like: "); - sysPrint("SendShieldedCoin publicFromAddress fromAmount shieldedInputNum " + System.out.println("Using SendShieldedCoin command needs more than 6 parameters like: "); + System.out.println("SendShieldedCoin publicFromAddress fromAmount shieldedInputNum " + "input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1" + " amount1 memo1 shieldedAddress2 amount2 memo2 ... "); return; @@ -2433,23 +2428,23 @@ private void sendShieldedCoin(String[] parameters) throws IOException, CipherExc if (isFromShieldedNote(parameters[2]) && !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - sysPrint("SendShieldedCoin failed, please loadShieldedWallet first !!!"); + System.out.println("SendShieldedCoin failed, please loadShieldedWallet first !!!"); return; } boolean result = sendShieldedCoinNormal(parameters, true); if (result) { - sysPrint("SendShieldedCoin successful !!!"); + System.out.println("SendShieldedCoin successful !!!"); } else { - sysPrint("SendShieldedCoin failed !!!"); + System.out.println("SendShieldedCoin failed !!!"); } } private void sendShieldedCoinWithoutAsk(String[] parameters) throws IOException, CipherException, CancelException, ZksnarkException { if (parameters == null || parameters.length < 6) { - sysPrint("Using SendShieldedCoinWithoutAsk command needs more than 6 parameters like: "); - sysPrint("SendShieldedCoinWithoutAsk publicFromAddress fromAmount " + System.out.println("Using SendShieldedCoinWithoutAsk command needs more than 6 parameters like: "); + System.out.println("SendShieldedCoinWithoutAsk publicFromAddress fromAmount " + "shieldedInputNum input1 input2 input3 ... publicToAddress toAmount shieldedOutputNum " + "shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 ... "); return; @@ -2457,28 +2452,28 @@ private void sendShieldedCoinWithoutAsk(String[] parameters) throws IOException, if (isFromShieldedNote(parameters[2]) && !ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - sysPrint("SendShieldedCoin failed, please loadShieldedWallet first!"); + System.out.println("SendShieldedCoin failed, please loadShieldedWallet first!"); return; } boolean result = sendShieldedCoinNormal(parameters, false); if (result) { - sysPrint("SendShieldedCoinWithoutAsk successful !!!"); + System.out.println("SendShieldedCoinWithoutAsk successful !!!"); } else { - sysPrint("SendShieldedCoinWithoutAsk failed !!!"); + System.out.println("SendShieldedCoinWithoutAsk failed !!!"); } } private void listShieldedNote(String[] parameters) { if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - sysPrint("ListShieldedNote failed, please loadShieldedWallet first!"); + System.out.println("ListShieldedNote failed, please loadShieldedWallet first!"); return; } int showType = 0; if (parameters == null || parameters.length <= 0) { - sysPrint("This command will show all the unspent notes. "); - sysPrint( + System.out.println("This command will show all the unspent notes. "); + System.out.println( "If you want to query all the spent notes and unspent notes, please use command ListShieldedNote 1 "); } else { if (!StringUtil.isNullOrEmpty(parameters[0])) { @@ -2489,16 +2484,16 @@ private void listShieldedNote(String[] parameters) { if (showType == 0) { List utxoList = ShieldedWrapper.getInstance().getvalidateSortUtxoList(); if (utxoList.size() == 0) { - sysPrint("Unspent note is 0."); + System.out.println("Unspent note is 0."); } else { - sysPrint("Unspent note list like:"); + System.out.println("Unspent note list like:"); for (String string : utxoList) { - sysPrint(string); + System.out.println(string); } } } else { Map noteMap = ShieldedWrapper.getInstance().getUtxoMapNote(); - sysPrint("All notes list like:"); + System.out.println("All notes list like:"); for (Entry entry : noteMap.entrySet()) { String string = entry.getValue().getPaymentAddress() + " "; string += entry.getValue().getValue(); @@ -2510,7 +2505,7 @@ private void listShieldedNote(String[] parameters) { string += "UnSpent"; string += " "; string += ZenUtils.getMemo(entry.getValue().getMemo()); - sysPrint(string); + System.out.println(string); } List noteList = ShieldedWrapper.getInstance().getSpendUtxoList(); @@ -2525,14 +2520,14 @@ private void listShieldedNote(String[] parameters) { string += "Spent"; string += " "; string += ZenUtils.getMemo(noteInfo.getMemo()); - sysPrint(string); + System.out.println(string); } } } private void resetShieldedNote() { if (!ShieldedWrapper.getInstance().ifShieldedWalletLoaded()) { - sysPrint("ResetShieldedNote failed, please loadShieldedWallet first!"); + System.out.println("ResetShieldedNote failed, please loadShieldedWallet first!"); return; } @@ -2541,8 +2536,8 @@ private void resetShieldedNote() { private void scanNoteByIvk(String[] parameters) { if (parameters == null || parameters.length != 3) { - sysPrint("Using ScanNotebyIvk command needs 3 parameters like: "); - sysPrint("ScanNotebyIvk ivk startNum endNum "); + System.out.println("Using ScanNotebyIvk command needs 3 parameters like: "); + System.out.println("ScanNotebyIvk ivk startNum endNum "); return; } @@ -2551,7 +2546,7 @@ private void scanNoteByIvk(String[] parameters) { startNum = Long.parseLong(parameters[1]); endNum = Long.parseLong(parameters[2]); } catch (NumberFormatException e) { - sysPrint("Invalid parameter: startNum, endNum."); + System.out.println("Invalid parameter: startNum, endNum."); return; } @@ -2560,8 +2555,8 @@ private void scanNoteByIvk(String[] parameters) { private void scanAndMarkNoteByAddress(String[] parameters) { if (parameters == null || parameters.length != 3) { - sysPrint("Using scanAndMarkNotebyAddress needs 3 parameters like: "); - sysPrint("scanAndMarkNotebyAddress shieldedAddress startNum endNum "); + System.out.println("Using scanAndMarkNotebyAddress needs 3 parameters like: "); + System.out.println("scanAndMarkNotebyAddress shieldedAddress startNum endNum "); return; } long startNum, endNum; @@ -2569,7 +2564,7 @@ private void scanAndMarkNoteByAddress(String[] parameters) { startNum = Long.parseLong(parameters[1]); endNum = Long.parseLong(parameters[2]); } catch (NumberFormatException e) { - sysPrint("Invalid parameter: startNum, endNum."); + System.out.println("Invalid parameter: startNum, endNum."); return; } @@ -2578,8 +2573,8 @@ private void scanAndMarkNoteByAddress(String[] parameters) { private void ScanNoteByOvk(String[] parameters) { if (parameters == null || parameters.length != 3) { - sysPrint("Using scanNotebyOvk command needs 3 parameters like: "); - sysPrint("scanNotebyOvk ovk startNum endNum"); + System.out.println("Using scanNotebyOvk command needs 3 parameters like: "); + System.out.println("scanNotebyOvk ovk startNum endNum"); return; } long startNum, endNum; @@ -2587,7 +2582,7 @@ private void ScanNoteByOvk(String[] parameters) { startNum = Long.parseLong(parameters[1]); endNum = Long.parseLong(parameters[2]); } catch (NumberFormatException e) { - sysPrint("Invalid parameter: startNum, endNum."); + System.out.println("Invalid parameter: startNum, endNum."); return; } @@ -2596,32 +2591,32 @@ private void ScanNoteByOvk(String[] parameters) { private void getShieldedNullifier(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getShieldedNullifier needs 1 parameter like: "); - sysPrint("getShieldedNullifier index"); + System.out.println("Using getShieldedNullifier needs 1 parameter like: "); + System.out.println("getShieldedNullifier index"); return; } long index = Long.valueOf(parameters[0]); String hash = walletApiWrapper.getShieldedNulltifier(index); if (hash != null) { - sysPrint("ShieldedNullifier:" + hash); + System.out.println("ShieldedNullifier:" + hash); } else { - sysPrint("GetShieldedNullifier failed !!!"); + System.out.println("GetShieldedNullifier failed !!!"); } } private void getSpendingKey() { Optional sk = WalletApi.getSpendingKey(); if (!sk.isPresent()) { - sysPrint("GetSpendingKey failed !!!"); + System.out.println("GetSpendingKey failed !!!"); } else { - sysPrint(ByteArray.toHexString(sk.get().getValue().toByteArray())); + System.out.println(ByteArray.toHexString(sk.get().getValue().toByteArray())); } } private void getExpandedSpendingKey(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getExpandedSpendingKey command needs 1 parameter like: "); - sysPrint("getExpandedSpendingKey sk "); + System.out.println("Using getExpandedSpendingKey command needs 1 parameter like: "); + System.out.println("getExpandedSpendingKey sk "); return; } String spendingKey = parameters[0]; @@ -2630,18 +2625,18 @@ private void getExpandedSpendingKey(String[] parameters) { .setValue(ByteString.copyFrom(ByteArray.fromHexString(spendingKey))).build(); Optional esk = WalletApi.getExpandedSpendingKey(sk); if (!esk.isPresent()) { - sysPrint("GetExpandedSpendingKey failed !!!"); + System.out.println("GetExpandedSpendingKey failed !!!"); } else { - sysPrint("ask:" + ByteArray.toHexString(esk.get().getAsk().toByteArray())); - sysPrint("nsk:" + ByteArray.toHexString(esk.get().getNsk().toByteArray())); - sysPrint("ovk:" + ByteArray.toHexString(esk.get().getOvk().toByteArray())); + System.out.println("ask:" + ByteArray.toHexString(esk.get().getAsk().toByteArray())); + System.out.println("nsk:" + ByteArray.toHexString(esk.get().getNsk().toByteArray())); + System.out.println("ovk:" + ByteArray.toHexString(esk.get().getOvk().toByteArray())); } } private void getAkFromAsk(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getAkFromAsk needs 1 parameter like: "); - sysPrint("getAkFromAsk ask "); + System.out.println("Using getAkFromAsk needs 1 parameter like: "); + System.out.println("getAkFromAsk ask "); return; } String ask = parameters[0]; @@ -2650,16 +2645,16 @@ private void getAkFromAsk(String[] parameters) { .setValue(ByteString.copyFrom(ByteArray.fromHexString(ask))).build(); Optional ak = WalletApi.getAkFromAsk(ask1); if (!ak.isPresent()) { - sysPrint("GetAkFromAsk failed !!!"); + System.out.println("GetAkFromAsk failed !!!"); } else { - sysPrint("ak:" + ByteArray.toHexString(ak.get().getValue().toByteArray())); + System.out.println("ak:" + ByteArray.toHexString(ak.get().getValue().toByteArray())); } } private void getNkFromNsk(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getNkFromNsk needs 1 parameter like: "); - sysPrint("getNkFromNsk nsk "); + System.out.println("Using getNkFromNsk needs 1 parameter like: "); + System.out.println("getNkFromNsk nsk "); return; } String nsk = parameters[0]; @@ -2668,17 +2663,17 @@ private void getNkFromNsk(String[] parameters) { .setValue(ByteString.copyFrom(ByteArray.fromHexString(nsk))).build(); Optional nk = WalletApi.getNkFromNsk(nsk1); if (!nk.isPresent()) { - sysPrint("GetNkFromNsk failed !!!"); + System.out.println("GetNkFromNsk failed !!!"); } else { - sysPrint("nk:" + ByteArray.toHexString(nk.get().getValue().toByteArray())); + System.out.println("nk:" + ByteArray.toHexString(nk.get().getValue().toByteArray())); } } private void getIncomingViewingKey(String[] parameters) { if (parameters == null || parameters.length != 2 || parameters[0].length() != 64 || parameters[1].length() != 64) { - sysPrint("Using getIncomingViewingKey needs 2 parameters like: "); - sysPrint("getIncomingViewingKey ak[64] nk[64] "); + System.out.println("Using getIncomingViewingKey needs 2 parameters like: "); + System.out.println("getIncomingViewingKey ak[64] nk[64] "); return; } String ak = parameters[0]; @@ -2690,25 +2685,25 @@ private void getIncomingViewingKey(String[] parameters) { Optional ivk = WalletApi.getIncomingViewingKey(vk); if (!ivk.isPresent()) { - sysPrint("GetIncomingViewingKey failed !!!"); + System.out.println("GetIncomingViewingKey failed !!!"); } else { - sysPrint("ivk:" + ByteArray.toHexString(ivk.get().getIvk().toByteArray())); + System.out.println("ivk:" + ByteArray.toHexString(ivk.get().getIvk().toByteArray())); } } private void getDiversifier(String[] parameters) { Optional diversifierMessage = WalletApi.getDiversifier(); if (!diversifierMessage.isPresent()) { - sysPrint("GetDiversifier failed !!!"); + System.out.println("GetDiversifier failed !!!"); } else { - sysPrint(ByteArray.toHexString(diversifierMessage.get().getD().toByteArray())); + System.out.println(ByteArray.toHexString(diversifierMessage.get().getD().toByteArray())); } } private void getShieldedPaymentAddress(String[] parameters) { if (parameters == null || parameters.length != 2 || parameters[1].length() != 22) { - sysPrint("Using getShieldedPaymentAddress command needs 2 parameters like: "); - sysPrint("getShieldedPaymentAddress ivk[64] d[22] "); + System.out.println("Using getShieldedPaymentAddress command needs 2 parameters like: "); + System.out.println("getShieldedPaymentAddress ivk[64] d[22] "); return; } String ivk = parameters[0]; @@ -2727,10 +2722,10 @@ private void getShieldedPaymentAddress(String[] parameters) { Optional paymentAddress = WalletApi.getZenPaymentAddress(ivk_d); if (!paymentAddress.isPresent()) { - sysPrint("GetShieldedPaymentAddress failed !!!"); + System.out.println("GetShieldedPaymentAddress failed !!!"); } else { - sysPrint("pkd:" + ByteArray.toHexString(paymentAddress.get().getPkD().toByteArray())); - sysPrint("shieldedAddress:" + paymentAddress.get().getPaymentAddress()); + System.out.println("pkd:" + ByteArray.toHexString(paymentAddress.get().getPkD().toByteArray())); + System.out.println("shieldedAddress:" + paymentAddress.get().getPaymentAddress()); } } @@ -2740,11 +2735,11 @@ private void backupShieldedAddress() throws IOException, CipherException { for (int i = 0; i < priKey.length; i++) { StringUtils.printOneByte(priKey[i]); } - sysPrint("\n"); + System.out.println("\n"); StringUtils.clear(priKey); - sysPrint("BackupShieldedAddress successful !!!"); + System.out.println("BackupShieldedAddress successful !!!"); } else { - sysPrint("BackupShieldedAddress failed !!!"); + System.out.println("BackupShieldedAddress failed !!!"); } } @@ -2759,21 +2754,21 @@ private void importShieldedAddress() throws CipherException, IOException { walletApiWrapper.getNewShieldedAddressBySkAndD(sk, d); if (addressInfo.isPresent() && ShieldedWrapper.getInstance().addNewShieldedAddress(addressInfo.get(), false)) { - sysPrint("Import new shielded address is: " + addressInfo.get().getAddress()); - sysPrint("ImportShieldedAddress successful !!!"); + System.out.println("Import new shielded address is: " + addressInfo.get().getAddress()); + System.out.println("ImportShieldedAddress successful !!!"); } else { - sysPrint("ImportShieldedAddress failed !!!"); + System.out.println("ImportShieldedAddress failed !!!"); } } else { - sysPrint("ImportShieldedAddress failed !!!"); + System.out.println("ImportShieldedAddress failed !!!"); } } private void marketSellAsset(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length != 5) { - sysPrint("Using MarketSellAsset command needs 5 parameters like: "); - sysPrint( + System.out.println("Using MarketSellAsset command needs 5 parameters like: "); + System.out.println( "MarketSellAsset ownerAddress sellTokenId sellTokenQuantity buyTokenId buyTokenQuantity"); return; } @@ -2781,7 +2776,7 @@ private void marketSellAsset(String[] parameters) int index = 0; byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } @@ -2794,9 +2789,9 @@ private void marketSellAsset(String[] parameters) .marketSellAsset(ownerAddress, sellTokenId, sellTokenQuantity, buyTokenId, buyTokenQuantity); if (result) { - sysPrint("MarketSellAsset successful !!!"); + System.out.println("MarketSellAsset successful !!!"); } else { - sysPrint("MarketSellAsset failed !!!"); + System.out.println("MarketSellAsset failed !!!"); } } @@ -2804,8 +2799,8 @@ private void marketSellAsset(String[] parameters) private void marketCancelOrder(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length != 2) { - sysPrint("Using MarketCancelOrder command needs 2 parameters like: "); - sysPrint( + System.out.println("Using MarketCancelOrder command needs 2 parameters like: "); + System.out.println( "MarketCancelOrder ownerAddress orderId"); return; } @@ -2813,7 +2808,7 @@ private void marketCancelOrder(String[] parameters) int index = 0; byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } @@ -2822,17 +2817,17 @@ private void marketCancelOrder(String[] parameters) boolean result = walletApiWrapper .marketCancelOrder(ownerAddress, orderId); if (result) { - sysPrint("MarketCancelOrder successful !!!"); + System.out.println("MarketCancelOrder successful !!!"); } else { - sysPrint("MarketCancelOrder failed !!!"); + System.out.println("MarketCancelOrder failed !!!"); } } private void getMarketOrderByAccount(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using GetMarketOrderByAccount command needs 1 parameters like: "); - sysPrint( + System.out.println("Using GetMarketOrderByAccount command needs 1 parameters like: "); + System.out.println( "GetMarketOrderByAccount ownerAddress"); return; } @@ -2840,23 +2835,23 @@ private void getMarketOrderByAccount(String[] parameters) { int index = 0; byte[] ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { - sysPrint("Invalid OwnerAddress."); + System.out.println("Invalid OwnerAddress."); return; } Optional marketOrderList = walletApiWrapper .getMarketOrderByAccount(ownerAddress); if (!marketOrderList.isPresent()) { - sysPrint("GetMarketOrderByAccount failed !!!"); + System.out.println("GetMarketOrderByAccount failed !!!"); } else { - sysPrint(Utils.formatMessageString(marketOrderList.get())); + System.out.println(Utils.formatMessageString(marketOrderList.get())); } } private void getMarketPriceByPair(String[] parameters) { if (parameters == null || parameters.length != 2) { - sysPrint("Using GetMarketPriceByPair command needs 2 parameters like: "); - sysPrint( + System.out.println("Using GetMarketPriceByPair command needs 2 parameters like: "); + System.out.println( "GetMarketPriceByPair sellTokenId buyTokenId"); return; } @@ -2868,17 +2863,17 @@ private void getMarketPriceByPair(String[] parameters) { Optional marketPriceList = walletApiWrapper .getMarketPriceByPair(sellTokenId, buyTokenId); if (!marketPriceList.isPresent()) { - sysPrint("GetMarketPriceByPair failed !!!"); + System.out.println("GetMarketPriceByPair failed !!!"); } else { - sysPrint(Utils.formatMessageString(marketPriceList.get())); + System.out.println(Utils.formatMessageString(marketPriceList.get())); } } private void getMarketOrderListByPair(String[] parameters) { if (parameters == null || parameters.length != 2) { - sysPrint("Using getMarketOrderListByPair command needs 2 parameters like: "); - sysPrint( + System.out.println("Using getMarketOrderListByPair command needs 2 parameters like: "); + System.out.println( "getMarketOrderListByPair sellTokenId buyTokenId"); return; } @@ -2890,17 +2885,17 @@ private void getMarketOrderListByPair(String[] parameters) { Optional orderListByPair = walletApiWrapper .getMarketOrderListByPair(sellTokenId, buyTokenId); if (!orderListByPair.isPresent()) { - sysPrint("getMarketOrderListByPair failed !!!"); + System.out.println("getMarketOrderListByPair failed !!!"); } else { - sysPrint(Utils.formatMessageString(orderListByPair.get())); + System.out.println(Utils.formatMessageString(orderListByPair.get())); } } private void getMarketPairList(String[] parameters) { if (parameters == null || parameters.length != 0) { - sysPrint("Using getMarketPairList command does not need any parameters, like: "); - sysPrint( + System.out.println("Using getMarketPairList command does not need any parameters, like: "); + System.out.println( "getMarketPairList"); return; } @@ -2908,16 +2903,16 @@ private void getMarketPairList(String[] parameters) { Optional pairList = walletApiWrapper .getMarketPairList(); if (!pairList.isPresent()) { - sysPrint("getMarketPairList failed !!!"); + System.out.println("getMarketPairList failed !!!"); } else { - sysPrint(Utils.formatMessageString(pairList.get())); + System.out.println(Utils.formatMessageString(pairList.get())); } } private void getMarketOrderById(String[] parameters) { if (parameters == null || parameters.length != 1) { - sysPrint("Using getMarketOrderById command needs 1 parameters like:"); - sysPrint( + System.out.println("Using getMarketOrderById command needs 1 parameters like:"); + System.out.println( "getMarketOrderById orderId"); return; } @@ -2926,30 +2921,30 @@ private void getMarketOrderById(String[] parameters) { Optional order = walletApiWrapper .getMarketOrderById(orderId); if (!order.isPresent()) { - sysPrint("getMarketOrderById failed !!!"); + System.out.println("getMarketOrderById failed !!!"); } else { - sysPrint(Utils.formatMessageString(order.get())); + System.out.println(Utils.formatMessageString(order.get())); } } private void create2(String[] parameters) { if (parameters == null || parameters.length != 3) { - sysPrint("Using create2 command needs 3 parameters like: "); - sysPrint("create2 address code salt"); + System.out.println("Using create2 command needs 3 parameters like: "); + System.out.println("create2 address code salt"); return; } byte[] address = WalletApi.decodeFromBase58Check(parameters[0]); if (!WalletApi.addressValid(address)) { - sysPrint("The length of address must be 21 bytes."); + System.out.println("The length of address must be 21 bytes."); return; } byte[] code = Hex.decode(parameters[1]); byte[] temp = Longs.toByteArray(Long.parseLong(parameters[2])); if (temp.length != 8) { - sysPrint("Invalid salt!"); + System.out.println("Invalid salt!"); return; } byte[] salt = new byte[32]; @@ -2958,24 +2953,24 @@ private void create2(String[] parameters) { byte[] mergedData = ByteUtil.merge(address, salt, Hash.sha3(code)); String Address = WalletApi.encode58Check(Hash.sha3omit12(mergedData)); - sysPrint("Create2 Address: " + Address); + System.out.println("Create2 Address: " + Address); return; } private void help() { - sysPrint("Help: List of Tron Wallet-cli commands"); - sysPrint( + System.out.println("Help: List of Tron Wallet-cli commands"); + System.out.println( "For more information on a specific command, type the command and it will display tips"); - sysPrint(""); + System.out.println(""); for (String commandItem : commandHelp) { - sysPrint(commandItem); + System.out.println(commandItem); } - sysPrint("Exit or Quit"); + System.out.println("Exit or Quit"); - sysPrint("Input any one of the listed commands, to display how-to tips."); + System.out.println("Input any one of the listed commands, to display how-to tips."); } private String[] getCmd(String cmdLine) { @@ -3018,14 +3013,14 @@ private String[] getCmd(String cmdLine) { } private void run() { - sysPrint(" "); - sysPrint("Welcome to Tron Wallet-Cli"); - sysPrint("Please type one of the following commands to proceed."); - sysPrint("Login, RegisterWallet or ImportWallet"); - sysPrint(" "); - sysPrint( + System.out.println(" "); + System.out.println("Welcome to Tron Wallet-Cli"); + System.out.println("Please type one of the following commands to proceed."); + System.out.println("Login, RegisterWallet or ImportWallet"); + System.out.println(" "); + System.out.println( "You may also use the Help command at anytime to display a full list of commands."); - sysPrint(" "); + System.out.println(" "); try { Terminal terminal = TerminalBuilder.builder().system(true).dumb(true).build(); @@ -3488,34 +3483,34 @@ private void run() { } case "exit": case "quit": { - sysPrint("Exit !!!"); + System.out.println("Exit !!!"); return; } default: { - sysPrint("Invalid cmd: " + cmd); + System.out.println("Invalid cmd: " + cmd); help(); } } } catch (CipherException e) { - sysPrint(cmd + " failed!"); - sysPrint(e.getMessage()); + System.out.println(cmd + " failed!"); + System.out.println(e.getMessage()); } catch (IOException e) { - sysPrint(cmd + " failed!"); - sysPrint(e.getMessage()); + System.out.println(cmd + " failed!"); + System.out.println(e.getMessage()); } catch (CancelException e) { - sysPrint(cmd + " failed!"); - sysPrint(e.getMessage()); + System.out.println(cmd + " failed!"); + System.out.println(e.getMessage()); } catch (EndOfFileException e) { - sysPrint("\nBye."); + System.out.println("\nBye."); return; } catch (Exception e) { - sysPrint(cmd + " failed!"); - sysPrint(e.getMessage()); + System.out.println(cmd + " failed!"); + System.out.println(e.getMessage()); e.printStackTrace(); } } } catch (IOException e) { - sysPrint("\nBye."); + System.out.println("\nBye."); return; } } @@ -3524,9 +3519,9 @@ private void getChainParameters() { Optional result = walletApiWrapper.getChainParameters(); if (result.isPresent()) { ChainParameters chainParameters = result.get(); - sysPrint(Utils.formatMessageString(chainParameters)); + System.out.println(Utils.formatMessageString(chainParameters)); } else { - sysPrint("GetChainParameters failed !!"); + System.out.println("GetChainParameters failed !!"); } } From 04cf342e6875b23fc21826eae4680a7e36148166 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 10 Feb 2020 22:49:36 +0800 Subject: [PATCH 274/445] add MarketOrderDetail --- src/main/protos/core/Tron.proto | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 75ba65db8..93b71dddc 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -236,6 +236,14 @@ message ResourceReceipt { Transaction.Result.contractResult result = 7; } + +message MarketOrderDetail { + bytes makerOrderId = 1; + bytes takerOrderId = 2; + int64 fillSellQuantity = 3; + int64 fillBuyQuantity = 4; +} + message Transaction { message Contract { enum ContractType { @@ -373,6 +381,9 @@ message TransactionInfo { int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; int64 shielded_transaction_fee = 22; + + bytes orderId = 25; + repeated MarketOrderDetail orderDetails = 26; } message TransactionRet { From a34e2bc3473a80e1b6498657ba8d5bd49fadc951 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 10 Feb 2020 23:41:42 +0800 Subject: [PATCH 275/445] fix print state error --- src/main/protos/core/Tron.proto | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 93b71dddc..0201bfb76 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -703,15 +703,19 @@ message MarketOrder { bytes buy_token_id = 6; int64 buy_token_quantity = 7; // min to receive int64 sell_token_quantity_remain = 9; + // When state != ACTIVE and sell_token_quantity_return !=0, + //it means that some sell tokens are returned to the account due to insufficient remaining amount + int64 sell_token_quantity_return = 10; + enum State { ACTIVE = 0; INACTIVE = 1; CANCELED = 2; } - State state = 10; + State state = 11; - bytes prev = 11; - bytes next = 12; + bytes prev = 12; + bytes next = 13; } message MarketOrderList { From 859a0a056475efac33c3b991341f7e9e9ca90039 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 11 Feb 2020 15:02:01 +0800 Subject: [PATCH 276/445] feat: sync proto --- README.md | 3 +-- src/main/protos/core/Tron.proto | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 720cd8d2a..29cbfa9d2 100644 --- a/README.md +++ b/README.md @@ -1451,7 +1451,7 @@ MarketSellAsset owner_address sell_token_id sell_token_quantity buy_token_id buy *buy_token_id, buy_token_quantity:ID and amount of the token want to buy* Example: -MarketSellAsset TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW _ 100 1000001 200 +MarketSellAsset TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW 1000001 200 _ 100 Get the result of the contract execution with the getTransactionInfoById command: getTransactionInfoById 10040f993cd9452b25bf367f38edadf11176355802baf61f3c49b96b4480d374 @@ -1503,7 +1503,6 @@ GetMarketOrderById fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75 "sell_token_quantity": 100, "buy_token_id": "1000001", "buy_token_quantity": 200, - "state": "CANCELED" } ``` diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 75ba65db8..8f21c567f 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -720,8 +720,8 @@ message MarketOrderPair{ message MarketAccountOrder { bytes owner_address = 1; repeated bytes orders = 2; // order_id list - int64 count = 3; - + int64 count = 3; // active count + int64 total_count = 4; } message MarketOrderPosition { From f375c96cf2b9398b1141fa72fd1e54051d4c3f1a Mon Sep 17 00:00:00 2001 From: Hou Date: Wed, 12 Feb 2020 14:17:51 +0800 Subject: [PATCH 277/445] set random ovk if send shielded coin from public address --- .../org/tron/walletcli/WalletApiWrapper.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 1c20953e1..44692740c 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -804,9 +804,7 @@ public boolean sendShieldedCoin(String fromAddress, long fromAmount, List builder.addShieldedSpends(spendNoteBuilder.build()); } } else { - byte[] ovk = ByteArray - .fromHexString("030c8c2bc59fb3eb8afb047a8ea4b028743d23e7d38c6fa30908358431e2314d"); - builder.setOvk(ByteString.copyFrom(ovk)); + builder.setOvk(ByteString.copyFrom(getRandomOvk())); } if (shieldedOutputList.size() > 0) { @@ -900,9 +898,7 @@ public boolean sendShieldedCoinWithoutAsk(String fromAddress, long fromAmount, builder.addShieldedSpends(spendNoteBuilder.build()); } } else { - byte[] ovk = ByteArray - .fromHexString("030c8c2bc59fb3eb8afb047a8ea4b028743d23e7d38c6fa30908358431e2314d"); - builder.setOvk(ByteString.copyFrom(ovk)); + builder.setOvk(ByteString.copyFrom(getRandomOvk())); } if (shieldedOutputList.size() > 0) { @@ -1067,6 +1063,18 @@ public Optional getNewShieldedAddress() { return Optional.empty(); } + public byte[] getRandomOvk() { + try { + Optional sk = WalletApi.getSpendingKey(); + Optional expandedSpendingKeyMessage = WalletApi + .getExpandedSpendingKey(sk.get()); + return expandedSpendingKeyMessage.get().getOvk().toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + public Optional getNewShieldedAddressBySkAndD(byte[] sk, byte[] d) { ShieldedAddressInfo addressInfo = new ShieldedAddressInfo(); try { From ac63348a5e6914fc7238fc5856eae631a2158e71 Mon Sep 17 00:00:00 2001 From: Hou Date: Thu, 13 Feb 2020 16:23:50 +0800 Subject: [PATCH 278/445] show error message if ovk is null --- .../org/tron/walletcli/WalletApiWrapper.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 44692740c..c844e9345 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -804,7 +804,13 @@ public boolean sendShieldedCoin(String fromAddress, long fromAmount, List builder.addShieldedSpends(spendNoteBuilder.build()); } } else { - builder.setOvk(ByteString.copyFrom(getRandomOvk())); + byte[] ovk = getRandomOvk(); + if (ovk != null) { + builder.setOvk(ByteString.copyFrom(ovk)); + } else { + System.out.println("Get random ovk from Rpc failure,please check config"); + return false; + } } if (shieldedOutputList.size() > 0) { @@ -898,7 +904,13 @@ public boolean sendShieldedCoinWithoutAsk(String fromAddress, long fromAmount, builder.addShieldedSpends(spendNoteBuilder.build()); } } else { - builder.setOvk(ByteString.copyFrom(getRandomOvk())); + byte[] ovk = getRandomOvk(); + if (ovk != null) { + builder.setOvk(ByteString.copyFrom(ovk)); + } else { + System.out.println("Get random ovk from Rpc failure,please check config"); + return false; + } } if (shieldedOutputList.size() > 0) { From 44ea9859175552d91670a873d7b0dfce91c742e6 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 20 Feb 2020 10:03:17 +0800 Subject: [PATCH 279/445] Fixed the terminal can no longer be used when an exception occurs --- src/main/java/org/tron/walletcli/Client.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 7cf72daf4..80d320bc2 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3368,7 +3368,8 @@ private void run() { } catch (Exception e) { System.out.println(cmd + " failed!"); System.out.println(e.getMessage()); - e.printStackTrace(); + System.out.println(e.getCause().getMessage()); +// e.printStackTrace(); } } } catch (IOException e) { From 66adf74a2d829805bed72676d5386337f46ad38a Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Thu, 20 Feb 2020 16:19:57 +0800 Subject: [PATCH 280/445] fix the missing solidity interface Signed-off-by: federico.zhen --- .../org/tron/walletserver/GrpcClient.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 3dd7d4ae6..c9a455d08 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -45,6 +45,7 @@ import org.tron.api.WalletSolidityGrpc; import org.tron.common.utils.ByteArray; import org.tron.protos.Contract; +import org.tron.protos.Protocol; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.ChainParameters; @@ -331,8 +332,12 @@ public Optional getDelegatedResource(String fromAddress, .setFromAddress(fromAddressBS) .setToAddress(toAddressBS) .build(); - DelegatedResourceList delegatedResource = blockingStubFull - .getDelegatedResource(request); + DelegatedResourceList delegatedResource; + if (blockingStubSolidity != null) { + delegatedResource = blockingStubSolidity.getDelegatedResource(request); + } else { + delegatedResource = blockingStubFull.getDelegatedResource(request); + } return Optional.ofNullable(delegatedResource); } @@ -342,9 +347,12 @@ public Optional getDelegatedResourceAccountIndex( Objects.requireNonNull(WalletApi.decodeFromBase58Check(address))); BytesMessage bytesMessage = BytesMessage.newBuilder().setValue(addressBS).build(); - - DelegatedResourceAccountIndex accountIndex = blockingStubFull - .getDelegatedResourceAccountIndex(bytesMessage); + DelegatedResourceAccountIndex accountIndex; + if (blockingStubSolidity != null) { + accountIndex = blockingStubSolidity.getDelegatedResourceAccountIndex(bytesMessage); + } else { + accountIndex = blockingStubFull.getDelegatedResourceAccountIndex(bytesMessage); + } return Optional.ofNullable(accountIndex); } @@ -724,7 +732,12 @@ public Optional getTransactionsToThis2(byte[] address, public Optional getTransactionById(String txID) { ByteString bsTxid = ByteString.copyFrom(ByteArray.fromHexString(txID)); BytesMessage request = BytesMessage.newBuilder().setValue(bsTxid).build(); - Transaction transaction = blockingStubFull.getTransactionById(request); + Transaction transaction; + if (blockingStubSolidity != null) { + transaction = blockingStubSolidity.getTransactionById(request); + } else { + transaction = blockingStubFull.getTransactionById(request); + } return Optional.ofNullable(transaction); } From 3f8749098e14411fb14fcaa215fce79bf6fc61ee Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 21 Feb 2020 16:49:03 +0800 Subject: [PATCH 281/445] feat: add getTransactionInfoByBlockNum --- README.md | 1 + .../java/org/tron/common/utils/Utils.java | 20 +++++++++++ src/main/java/org/tron/walletcli/Client.java | 34 ++++++++++++++++++- .../org/tron/walletcli/WalletApiWrapper.java | 5 +++ .../org/tron/walletserver/GrpcClient.java | 15 ++++++++ .../java/org/tron/walletserver/WalletApi.java | 5 +++ src/main/protos/api/api.proto | 12 ++++++- 7 files changed, 90 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6d42426a..03ecdf7ba 100644 --- a/README.md +++ b/README.md @@ -819,6 +819,7 @@ GetTransactionCountByBlockNum: Get the number of transactions in the block base block height GetTransactionInfoById: Get transaction-info based on transaction id,generally used to check the result of a smart contract trigger +GetTransactioninfoByBlockNum: Get transaction-info list in the block based on the block height How to get block information ---------------------------- diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 865108392..f3a0b8579 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -43,6 +43,7 @@ import org.tron.api.GrpcAPI.ProposalList; import org.tron.api.GrpcAPI.TransactionApprovedList; import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; import org.tron.api.GrpcAPI.TransactionList; import org.tron.api.GrpcAPI.TransactionListExtention; import org.tron.api.GrpcAPI.TransactionSignWeight; @@ -1344,6 +1345,25 @@ public static String printTransaction(TransactionExtention transactionExtention) return result; } + public static String printTransactionInfoList(TransactionInfoList transactionInfoList) { + List infoList = transactionInfoList.getTransactionInfoList(); + + String result = "\n"; + int i = 0; + for (TransactionInfo transactioninfo : infoList) { + result += "transactionInfo " + i + " :::"; + result += "\n"; + result += "["; + result += "\n"; + result += printTransactionInfo(transactioninfo); + result += "]"; + result += "\n"; + result += "\n"; + i++; + } + return result; + } + public static String printTransactionInfo(TransactionInfo transactionInfo) { String result = ""; result += "txid: "; diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 0b1206e82..2954ec702 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -36,6 +36,7 @@ import org.tron.api.GrpcAPI.NumberMessage; import org.tron.api.GrpcAPI.ProposalList; import org.tron.api.GrpcAPI.TransactionApprovedList; +import org.tron.api.GrpcAPI.TransactionInfoList; import org.tron.api.GrpcAPI.TransactionList; import org.tron.api.GrpcAPI.TransactionListExtention; import org.tron.api.GrpcAPI.TransactionSignWeight; @@ -822,7 +823,9 @@ private void getTransactionCountByBlockNum(String[] parameters) { System.out.println("Too many parameters !!!"); System.out.println("You need input number with the following syntax:"); System.out.println("GetTransactionCountByBlockNum number"); + return; } + long blockNum = Long.parseLong(parameters[0]); long count = walletApiWrapper.getTransactionCountByBlockNum(blockNum); System.out.println("The block contain " + count + " transactions"); @@ -1651,6 +1654,30 @@ private void getBrokerage(String[] parameters) { System.out.println("The brokerage is : " + brokerage.getNum()); } + private void getTransactionInfoByBlockNum(String[] parameters) { + if (parameters.length != 1) { + System.out.println("Too many parameters !!!"); + System.out.println("You need input number with the following syntax:"); + System.out.println("GetTransactionInfoByBlockNum number"); + return; + } + + long blockNum = Long.parseLong(parameters[0]); + Optional result = walletApiWrapper.getTransactionInfoByBlockNum(blockNum); + + if (result.isPresent()) { + TransactionInfoList transactionInfoList = result.get(); + if (transactionInfoList.getTransactionInfoCount() == 0) { + System.out.println("[]"); + } else { + System.out.println(Utils.printTransactionInfoList(transactionInfoList)); + } + } else { + System.out.println("GetTransactionInfoByBlockNum failed !!!"); + } + + } + private String[] getParas(String[] para) { String paras = String.join(" ", para); Pattern pattern = Pattern.compile(" (\\[.*?\\]) "); @@ -1998,6 +2025,7 @@ private void help() { System.out.println("BackupWallet"); System.out.println("BackupWallet2Base64"); System.out.println("BroadcastTransaction"); + System.out.println("Create2"); System.out.println("ChangePassword"); System.out.println("ClearContractABI"); System.out.println("CreateAccount"); @@ -2035,6 +2063,7 @@ private void help() { System.out.println("GetTransactionApprovedList"); System.out.println("GetTransactionById"); System.out.println("GetTransactionCountByBlockNum"); + System.out.println("GetTransactionInfoByBlockNum"); System.out.println("GetTransactionInfoById"); System.out.println("GetTransactionsFromThis"); System.out.println("GetTransactionsToThis"); @@ -2067,7 +2096,6 @@ private void help() { System.out.println("UpdateAccountPermission"); System.out.println("VoteWitness"); System.out.println("WithdrawBalance"); - System.out.println("Create2"); System.out.println("UpdateBrokerage"); System.out.println("GetReward"); System.out.println("GetBrokerage"); @@ -2512,6 +2540,10 @@ private void run() { getBrokerage(parameters); break; } + case "gettransactioninfobyblocknum": { + getTransactionInfoByBlockNum(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index b6d827087..f8bfe464d 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -13,6 +13,7 @@ import org.tron.api.GrpcAPI.ExchangeList; import org.tron.api.GrpcAPI.NodeList; import org.tron.api.GrpcAPI.ProposalList; +import org.tron.api.GrpcAPI.TransactionInfoList; import org.tron.api.GrpcAPI.WitnessList; import org.tron.common.utils.Utils; import org.tron.core.exception.CancelException; @@ -715,4 +716,8 @@ public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } + public static Optional getTransactionInfoByBlockNum(long blockNum) { + return WalletApi.getTransactionInfoByBlockNum(blockNum); + } + } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index c9a455d08..4403647d9 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -36,6 +36,7 @@ import org.tron.api.GrpcAPI.Return.response_code; import org.tron.api.GrpcAPI.TransactionApprovedList; import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; import org.tron.api.GrpcAPI.TransactionList; import org.tron.api.GrpcAPI.TransactionListExtention; import org.tron.api.GrpcAPI.TransactionSignWeight; @@ -849,4 +850,18 @@ public NumberMessage getBrokerage(byte[] address) { } } + public Optional getTransactionInfoByBlockNum(long blockNum) { + TransactionInfoList transactionInfoList; + NumberMessage.Builder builder = NumberMessage.newBuilder(); + builder.setNum(blockNum); + + if (blockingStubSolidity != null) { + transactionInfoList = blockingStubSolidity.getTransactionInfoByBlockNum(builder.build()); + } else { + transactionInfoList = blockingStubFull.getTransactionInfoByBlockNum(builder.build()); + } + + return Optional.ofNullable(transactionInfoList); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 38b0331fe..e5421269e 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -44,6 +44,7 @@ import org.tron.api.GrpcAPI.Return; import org.tron.api.GrpcAPI.TransactionApprovedList; import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; import org.tron.api.GrpcAPI.TransactionList; import org.tron.api.GrpcAPI.TransactionListExtention; import org.tron.api.GrpcAPI.TransactionSignWeight; @@ -2005,4 +2006,8 @@ public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + public static Optional getTransactionInfoByBlockNum(long blockNum) { + return rpcCli.getTransactionInfoByBlockNum(blockNum); + } + } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 932e1c47a..0420dbd64 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -640,6 +640,9 @@ service Wallet { }; + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } + }; @@ -780,6 +783,9 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } + }; service WalletExtension { @@ -1061,4 +1067,8 @@ message TransactionApprovedList { repeated bytes approved_list = 2; Result result = 4; TransactionExtention transaction = 5; -} \ No newline at end of file +} + +message TransactionInfoList { + repeated TransactionInfo transactionInfo = 1; +} From c98e91e507747fcf7f4838012a8408307b662780 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Sat, 22 Feb 2020 02:49:58 +0800 Subject: [PATCH 282/445] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03ecdf7ba..c08177b74 100644 --- a/README.md +++ b/README.md @@ -819,7 +819,7 @@ GetTransactionCountByBlockNum: Get the number of transactions in the block base block height GetTransactionInfoById: Get transaction-info based on transaction id,generally used to check the result of a smart contract trigger -GetTransactioninfoByBlockNum: Get transaction-info list in the block based on the block height +GetTransactionInfoByBlockNum: Get transaction-info list in the block based on the block height How to get block information ---------------------------- From f296d5f75c655be92d2060ea0cf17b83141cf219 Mon Sep 17 00:00:00 2001 From: Aiden Date: Wed, 26 Feb 2020 20:13:46 +0800 Subject: [PATCH 283/445] update file:1.add Brokerage,including introduction and commands 2.modify the position of Command List add a link to each command --- README.md | 107 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index b6d42426a..5c4b50a1a 100644 --- a/README.md +++ b/README.md @@ -66,36 +66,33 @@ Derive a AES Key by password and then use the AES algorithm to encrypt and save The account address is calculated by the public key sha3-256, and taking the last 20 bytes. All subsequent operations that require the use of a private key must enter the password. -ImportWallet -ImportwalletByBase64 -ChangePassword -Login -Logout -BackupWallet -BackupWallet2Base64 -Getaddress -GetBalance -GetAccount -GetAssetissueByAccount -GetAssetIssueByName -SendCoin -TransferAsset -ParticipateAssetissue -Assetissue -CreateWitness -VoteWitness -FreezeBalance -UnfreezeBalance -WithdrawBalance -Listaccounts -Listwitnesses -Listassetissue -listNodes -GetAssetIssueByName -Getblock -UpdateAccount  -Exit or Quit -help +**Following is a list of Tron Wallet-cli commands:** + +For more information on a specific command, just type the command on terminal when you start your Wallet. + +| [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli?) | [ApproveProposal](#How-to-initiate-a-proposal) | [AssetIssue](#How-to-issue-TRC10-tokens) | +| :---------:|:---------:|:--------: | +| [BackupWallet](#Wallet-related-commands)| [BackupWallet2Base64](#Wallet-related-commands) | [BroadcastTransaction](#Some-others) | +|[ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) |[DeleteProposal](#How-to-initiate-a-proposal) | +| [DeployContract](#How-to-use-smart-contracts) | [ExchangeCreate](#How-to-trade-on-the-exchange) | [ExchangeInject](#How-to-trade-on-the-exchange) | +| [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | [FreezeBalance](#How-to-freeze/unfreeze-balance) | +| [GenerateAddress](#Account-related-commands) | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | +|[GetAccountResource](#Account-related-commands) | [GetAddress](#Account-related-commands) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | +|[GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | +|[GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | +|[GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) |[GetContract contractAddress](#How-to-use-smart-contracts) | +|[GetDelegatedResource](#How-to-delegate-resource) | [GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | [GetNextMaintenanceTime](#Some-others) | +[GetProposal](#How-to-initiate-a-proposal) |[GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli?) |[GetTransactionById](#How-to-get-transaction-information) | +| [GetTransactionCountByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoById](#How-to-get-transaction-information) | [GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli?) | +|[ImportWallet](#Wallet-related-commands) | [ImportWalletByBase64](#Wallet-related-commands) |[ListAssetIssue](#How-to-issue-TRC10-tokens) | +|[ListExchanges](#How-to-trade-on-the-exchange) | [ListExchangesPaginated](#How-to-trade-on-the-exchange) |[ListNodes](#Some-others) | +| [ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | +| [Login](#Command-line-operation-flow-example) |[ParticipateAssetIssue](#How-to-issue-TRC10-tokens) |[RegisterWallet](#Wallet-related-commands) | +| [SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli?) |[TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | +|[UnfreezeAsset](#How-to-issue-TRC10-tokens) |[UnfreezeBalance](#How-to-freeze/unfreeze-balance) | [UpdateAsset](#How-to-issue-TRC10-tokens) | +|[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli?) | +|[VoteWitness](#How-to-vote) |[getbrokerage](#getbrokerage) | [getreward](#getreward) | +|[updateBrokerage](#updateBrokerage) | Input any one of them, you will get more tips. @@ -167,6 +164,54 @@ votewitness 123455 witness1 10 // Voted 10 votes for witness1. The final result of the above command was 10 votes for witness1 and 0 votes for witness2. +Brokerage +---------------------------------- + +After voting for the witness, you will receive the rewards. The witness has the right to decide the ratio of brokerage. The default ratio is 20%, and the witness can adjust it. + +By default, if a witness is rewarded, he will receive 20% of the whole rewards, and 80% of the rewards will be distributed to his voters. + +**getbrokerage** + +View the ratio of brokerage of the witness. + + > getbrokerage OwnerAddress + +OwnerAddress +> The address of the witness's account, it is a base58check type address. + +**getreward** + +Query unclaimed reward. + + > getreward OwnerAddress + +OwnerAddress +> The address of the voter's account, it is a base58check type address. + +**updateBrokerage** + +Update the ratio of brokerage, this command is usually used by a witness account. + + > updateBrokerage OwnerAddress brokerage + +OwnerAddress +> The address of the witness's account, it is a base58check type address. + +brokerage +> The ratio of brokerage you want to update to, the limit of it: 0-100. + +For example: + +```console +> getbrokerage TZ7U1WVBRLZ2umjizxqz3XfearEHhXKX7h + +> getreward TNfu3u8jo1LDWerHGbzs2Pv88Biqd85wEY + +> updateBrokerage TZ7U1WVBRLZ2umjizxqz3XfearEHhXKX7h 30 +``` + + How to calculate bandwidth ---------------------------------- @@ -837,4 +882,4 @@ ListNodes: Get other peer information ListWitnesses: Get all miner node information BroadcastTransaction: Broadcast the transaction, where the transaction is in hex string format. - \ No newline at end of file + From 0255410c015a2d848641c5c350f924c16ec3010b Mon Sep 17 00:00:00 2001 From: Aiden Date: Thu, 27 Feb 2020 12:21:05 +0800 Subject: [PATCH 284/445] update file:1.add Brokerage,including introduction and commands 2.modify the position of Command List add a link to each command --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5c4b50a1a..e92ec3cd8 100644 --- a/README.md +++ b/README.md @@ -70,29 +70,29 @@ All subsequent operations that require the use of a private key must enter the p For more information on a specific command, just type the command on terminal when you start your Wallet. -| [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli?) | [ApproveProposal](#How-to-initiate-a-proposal) | [AssetIssue](#How-to-issue-TRC10-tokens) | +| [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [ApproveProposal](#How-to-initiate-a-proposal) | [AssetIssue](#How-to-issue-TRC10-tokens) | | :---------:|:---------:|:--------: | | [BackupWallet](#Wallet-related-commands)| [BackupWallet2Base64](#Wallet-related-commands) | [BroadcastTransaction](#Some-others) | |[ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) |[DeleteProposal](#How-to-initiate-a-proposal) | | [DeployContract](#How-to-use-smart-contracts) | [ExchangeCreate](#How-to-trade-on-the-exchange) | [ExchangeInject](#How-to-trade-on-the-exchange) | -| [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | [FreezeBalance](#How-to-freeze/unfreeze-balance) | +| [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | [FreezeBalance](#How-to-freezeunfreeze-balance) | | [GenerateAddress](#Account-related-commands) | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | |[GetAccountResource](#Account-related-commands) | [GetAddress](#Account-related-commands) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | |[GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | |[GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | -|[GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) |[GetContract contractAddress](#How-to-use-smart-contracts) | -|[GetDelegatedResource](#How-to-delegate-resource) | [GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | [GetNextMaintenanceTime](#Some-others) | -[GetProposal](#How-to-initiate-a-proposal) |[GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli?) |[GetTransactionById](#How-to-get-transaction-information) | -| [GetTransactionCountByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoById](#How-to-get-transaction-information) | [GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli?) | -|[ImportWallet](#Wallet-related-commands) | [ImportWalletByBase64](#Wallet-related-commands) |[ListAssetIssue](#How-to-issue-TRC10-tokens) | -|[ListExchanges](#How-to-trade-on-the-exchange) | [ListExchangesPaginated](#How-to-trade-on-the-exchange) |[ListNodes](#Some-others) | -| [ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | -| [Login](#Command-line-operation-flow-example) |[ParticipateAssetIssue](#How-to-issue-TRC10-tokens) |[RegisterWallet](#Wallet-related-commands) | -| [SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli?) |[TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | -|[UnfreezeAsset](#How-to-issue-TRC10-tokens) |[UnfreezeBalance](#How-to-freeze/unfreeze-balance) | [UpdateAsset](#How-to-issue-TRC10-tokens) | -|[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli?) | -|[VoteWitness](#How-to-vote) |[getbrokerage](#getbrokerage) | [getreward](#getreward) | -|[updateBrokerage](#updateBrokerage) | +|[GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) |[GetBrokerage](#Brokerage) | +|[GetContract contractAddress](#How-to-use-smart-contracts) |[GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | +[GetNextMaintenanceTime](#Some-others) |[GetProposal](#How-to-initiate-a-proposal) |[GetReward](#Brokerage) | +|[GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[GetTransactionById](#How-to-get-transaction-information) |[GetTransactionCountByBlockNum](#How-to-get-transaction-information) | +|[GetTransactionInfoById](#How-to-get-transaction-information) | [GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[ImportWallet](#Wallet-related-commands) | +|[ImportWalletByBase64](#Wallet-related-commands) |[ListAssetIssue](#How-to-issue-TRC10-tokens) |[ListExchanges](#How-to-trade-on-the-exchange) | +|[ListExchangesPaginated](#How-to-trade-on-the-exchange) |[ListNodes](#Some-others) |[ListProposals](#How-to-initiate-a-proposal) | +|[ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | [Login](#Command-line-operation-flow-example) | +|[ParticipateAssetIssue](#How-to-issue-TRC10-tokens) |[RegisterWallet](#Wallet-related-commands) |[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) | +|[TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) |[UnfreezeAsset](#How-to-issue-TRC10-tokens) | +|[UnfreezeBalance](#How-to-freezeunfreeze-balance) | [UpdateAsset](#How-to-issue-TRC10-tokens) |[UpdateBrokerage](#Brokerage) | +|[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | +|[VoteWitness](#How-to-vote) | Input any one of them, you will get more tips. @@ -171,7 +171,7 @@ After voting for the witness, you will receive the rewards. The witness has the By default, if a witness is rewarded, he will receive 20% of the whole rewards, and 80% of the rewards will be distributed to his voters. -**getbrokerage** +**GetBrokerage** View the ratio of brokerage of the witness. @@ -180,7 +180,7 @@ View the ratio of brokerage of the witness. OwnerAddress > The address of the witness's account, it is a base58check type address. -**getreward** +**GetReward** Query unclaimed reward. @@ -189,7 +189,7 @@ Query unclaimed reward. OwnerAddress > The address of the voter's account, it is a base58check type address. -**updateBrokerage** +**UpdateBrokerage** Update the ratio of brokerage, this command is usually used by a witness account. From e85193c56e67a64d910b805d81e336e2b02209ea Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 13 Mar 2020 16:40:41 +0800 Subject: [PATCH 285/445] for java-tron multimodule --- api/api.proto | 51 +++- api/zksnark.proto | 1 - core/Contract.proto | 346 ----------------------- core/Tron.proto | 58 +--- core/contract/account_contract.proto | 50 ++++ core/contract/asset_issue_contract.proto | 60 ++++ core/contract/balance_contract.proto | 36 +++ core/contract/common.proto | 12 + core/contract/exchange_contract.proto | 37 +++ core/contract/proposal_contract.proto | 23 ++ core/contract/shield_contract.proto | 81 ++++++ core/contract/smart_contract.proto | 89 ++++++ core/contract/storage_contract.proto | 27 ++ core/contract/vote_asset_contract.proto | 14 + core/contract/witness_contract.proto | 27 ++ core/tron/account.proto | 0 core/tron/block.proto | 0 core/tron/delegated_resource.proto | 0 core/tron/p2p.proto | 0 core/tron/proposal.proto | 0 core/tron/transaction.proto | 0 core/tron/vote.proto | 0 core/tron/witness.proto | 0 23 files changed, 496 insertions(+), 416 deletions(-) delete mode 100644 core/Contract.proto create mode 100644 core/contract/account_contract.proto create mode 100644 core/contract/asset_issue_contract.proto create mode 100644 core/contract/balance_contract.proto create mode 100644 core/contract/common.proto create mode 100644 core/contract/exchange_contract.proto create mode 100644 core/contract/proposal_contract.proto create mode 100644 core/contract/shield_contract.proto create mode 100644 core/contract/smart_contract.proto create mode 100644 core/contract/storage_contract.proto create mode 100644 core/contract/vote_asset_contract.proto create mode 100644 core/contract/witness_contract.proto create mode 100644 core/tron/account.proto create mode 100644 core/tron/block.proto create mode 100644 core/tron/delegated_resource.proto create mode 100644 core/tron/p2p.proto create mode 100644 core/tron/proposal.proto create mode 100644 core/tron/transaction.proto create mode 100644 core/tron/vote.proto create mode 100644 core/tron/witness.proto diff --git a/api/api.proto b/api/api.proto index b62f6cdd1..90feaf0fc 100644 --- a/api/api.proto +++ b/api/api.proto @@ -2,9 +2,17 @@ syntax = "proto3"; package protocol; import "core/Tron.proto"; -import "core/Contract.proto"; import "google/api/annotations.proto"; +import "core/contract/asset_issue_contract.proto"; +import "core/contract/account_contract.proto"; +import "core/contract/witness_contract.proto"; +import "core/contract/balance_contract.proto"; +import "core/contract/proposal_contract.proto"; +import "core/contract/storage_contract.proto"; +import "core/contract/exchange_contract.proto"; +import "core/contract/smart_contract.proto"; +import "core/contract/shield_contract.proto"; option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file @@ -630,6 +638,16 @@ service Wallet { rpc GetNodeInfo (EmptyMessage) returns (NodeInfo) { }; + rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { + }; + + rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { + + }; + // for shiededTransaction rpc CreateShieldedTransaction (PrivateParameters) returns (TransactionExtention) { }; @@ -689,14 +707,11 @@ service Wallet { }; // end for shiededTransaction - rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { - }; - - rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { + rpc CreateCommonTransaction (Transaction) returns (TransactionExtention) { }; - rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { - }; + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } }; service WalletSolidity { @@ -829,7 +844,6 @@ service WalletSolidity { }; } - rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { } @@ -845,15 +859,18 @@ service WalletSolidity { rpc IsSpend (NoteParameters) returns (SpendResult) { } - rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { - } - rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { }; rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; + rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { + } + + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } + }; service WalletExtension { @@ -1070,7 +1087,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.raw_data) + bytes txid = 3; //transaction id = sha256(transaction.rowdata) } message AddressPrKeyPairMessage { @@ -1080,7 +1097,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) repeated bytes constant_result = 3; Return result = 4; } @@ -1165,7 +1182,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1174,7 +1191,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) int32 index = 3; //the index of note in receive bool is_spend = 4; } @@ -1291,3 +1308,7 @@ message SpendResult { bool result = 1; string message = 2; } + +message TransactionInfoList { + repeated TransactionInfo transactionInfo = 1; +} diff --git a/api/zksnark.proto b/api/zksnark.proto index 66538d6ad..91b985651 100644 --- a/api/zksnark.proto +++ b/api/zksnark.proto @@ -2,7 +2,6 @@ syntax = "proto3"; package protocol; import "core/Tron.proto"; -import "core/Contract.proto"; option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file option java_outer_classname = "ZksnarkGrpcAPI"; //Specify the class name of the generated Java file diff --git a/core/Contract.proto b/core/Contract.proto deleted file mode 100644 index aa33cfe33..000000000 --- a/core/Contract.proto +++ /dev/null @@ -1,346 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -syntax = "proto3"; - -package protocol; - -option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file -option java_outer_classname = "Contract"; //Specify the class name of the generated Java file -option go_package = "github.com/tronprotocol/grpc-gateway/core"; - -import "core/Tron.proto"; - -message AccountCreateContract { - bytes owner_address = 1; - bytes account_address = 2; - AccountType type = 3; -} - -// Update account name. Account name is not unique now. -message AccountUpdateContract { - bytes account_name = 1; - bytes owner_address = 2; -} - -// Set account id if the account has no id. Account id is unique and case insensitive. -message SetAccountIdContract { - bytes account_id = 1; - bytes owner_address = 2; -} - -message TransferContract { - bytes owner_address = 1; - bytes to_address = 2; - int64 amount = 3; -} - - -message ShieldAddress { - bytes private_address = 1; - bytes public_address = 2; -} - -message TransferAssetContract { - // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME's activation, - // otherwise it is token id and token should be in string format. - bytes asset_name = 1; - bytes owner_address = 2; - bytes to_address = 3; - int64 amount = 4; -} - - -message VoteAssetContract { - bytes owner_address = 1; - repeated bytes vote_address = 2; - bool support = 3; - int32 count = 5; -} - -message VoteWitnessContract { - message Vote { - bytes vote_address = 1; - int64 vote_count = 2; - } - bytes owner_address = 1; - repeated Vote votes = 2; - bool support = 3; -} - -message UpdateSettingContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 consume_user_resource_percent = 3; -} - -message UpdateEnergyLimitContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 origin_energy_limit = 3; -} - -message ClearABIContract { - bytes owner_address = 1; - bytes contract_address = 2; -} - -message WitnessCreateContract { - bytes owner_address = 1; - bytes url = 2; -} - -message WitnessUpdateContract { - bytes owner_address = 1; - bytes update_url = 12; -} - -message AssetIssueContract { - string id = 41; - - message FrozenSupply { - int64 frozen_amount = 1; - int64 frozen_days = 2; - } - bytes owner_address = 1; - bytes name = 2; - bytes abbr = 3; - int64 total_supply = 4; - repeated FrozenSupply frozen_supply = 5; - int32 trx_num = 6; - int32 precision = 7; - int32 num = 8; - int64 start_time = 9; - int64 end_time = 10; - int64 order = 11; // useless - int32 vote_score = 16; - bytes description = 20; - bytes url = 21; - int64 free_asset_net_limit = 22; - int64 public_free_asset_net_limit = 23; - int64 public_free_asset_net_usage = 24; - int64 public_latest_free_net_time = 25; -} - -message ParticipateAssetIssueContract { - bytes owner_address = 1; - bytes to_address = 2; - // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME's activation, - // otherwise it is token id and token should be in string format. - bytes asset_name = 3; - int64 amount = 4; // the amount of drops -} - - -enum ResourceCode { - BANDWIDTH = 0x00; - ENERGY = 0x01; -} - -message FreezeBalanceContract { - bytes owner_address = 1; - int64 frozen_balance = 2; - int64 frozen_duration = 3; - - ResourceCode resource = 10; - bytes receiver_address = 15; -} - -message UnfreezeBalanceContract { - bytes owner_address = 1; - - ResourceCode resource = 10; - bytes receiver_address = 15; -} - -message UnfreezeAssetContract { - bytes owner_address = 1; -} - -message WithdrawBalanceContract { - bytes owner_address = 1; -} - -message UpdateAssetContract { - bytes owner_address = 1; - bytes description = 2; - bytes url = 3; - int64 new_limit = 4; - int64 new_public_limit = 5; -} - -message ProposalCreateContract { - bytes owner_address = 1; - map parameters = 2; -} - -message ProposalApproveContract { - bytes owner_address = 1; - int64 proposal_id = 2; - bool is_add_approval = 3; // add or remove approval -} - -message ProposalDeleteContract { - bytes owner_address = 1; - int64 proposal_id = 2; -} - -message CreateSmartContract { - bytes owner_address = 1; - SmartContract new_contract = 2; - int64 call_token_value = 3; - int64 token_id = 4; -} - -message TriggerSmartContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 call_value = 3; - bytes data = 4; - int64 call_token_value = 5; - int64 token_id = 6; -} - -message BuyStorageContract { - bytes owner_address = 1; - int64 quant = 2; // trx quantity for buy storage (in sun) -} - -message BuyStorageBytesContract { - bytes owner_address = 1; - int64 bytes = 2; // storage bytes for buy -} - -message SellStorageContract { - bytes owner_address = 1; - int64 storage_bytes = 2; -} - -message ExchangeCreateContract { - bytes owner_address = 1; - bytes first_token_id = 2; - int64 first_token_balance = 3; - bytes second_token_id = 4; - int64 second_token_balance = 5; -} - -message ExchangeInjectContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; -} - -message ExchangeWithdrawContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; -} - -message ExchangeTransactionContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; - int64 expected = 5; -} - -message AccountPermissionUpdateContract { - bytes owner_address = 1; - Permission owner = 2; // Empty means invalidate - Permission witness = 3; // Can be empty - repeated Permission actives = 4; // Empty means invalidate -} - -// for shielded transaction -message AuthenticationPath { - repeated bool value = 1; -} - -message MerklePath { - repeated AuthenticationPath authentication_paths = 1; - repeated bool index = 2; - bytes rt = 3; -} - -message OutputPoint { - bytes hash = 1; - int32 index = 2; -} - -message OutputPointInfo { - repeated OutputPoint out_points = 1; - int32 block_num = 2; -} - -message PedersenHash { - bytes content = 1; -} - -message IncrementalMerkleTree { - PedersenHash left = 1; - PedersenHash right = 2; - repeated PedersenHash parents = 3; -} - -message IncrementalMerkleVoucher { - IncrementalMerkleTree tree = 1; - repeated PedersenHash filled = 2; - IncrementalMerkleTree cursor = 3; - int64 cursor_depth = 4; - bytes rt = 5; - OutputPoint output_point = 10; -} - -message IncrementalMerkleVoucherInfo { - repeated IncrementalMerkleVoucher vouchers = 1; - repeated bytes paths = 2; -} - -message SpendDescription { - bytes value_commitment = 1; - bytes anchor = 2; // merkle root - bytes nullifier = 3; // used for check double spend - bytes rk = 4; // used for check spend authority signature - bytes zkproof = 5; - bytes spend_authority_signature = 6; -} - -message ReceiveDescription { - bytes value_commitment = 1; - bytes note_commitment = 2; - bytes epk = 3; // for Encryption - bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk - bytes c_out = 5; // Encryption for audit, decrypt it with ovk - bytes zkproof = 6; -} - -message ShieldedTransferContract { - bytes transparent_from_address = 1; // transparent address - int64 from_amount = 2; - repeated SpendDescription spend_description = 3; - repeated ReceiveDescription receive_description = 4; - bytes binding_signature = 5; - bytes transparent_to_address = 6; // transparent address - int64 to_amount = 7; // the amount to transparent to_address -} -// end shielded transaction - -message UpdateBrokerageContract { - bytes owner_address = 1; - int32 brokerage = 2; // 1 mean 1% -} - diff --git a/core/Tron.proto b/core/Tron.proto index db1355863..d794d3083 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -259,9 +259,6 @@ message Transaction { ProposalDeleteContract = 18; SetAccountIdContract = 19; CustomContract = 20; - // BuyStorageContract = 21; - // BuyStorageBytesContract = 22; - // SellStorageContract = 23; CreateSmartContract = 30; TriggerSmartContract = 31; GetContract = 32; @@ -325,9 +322,9 @@ message Transaction { bytes ref_block_hash = 4; int64 expiration = 8; repeated authority auths = 9; - // transaction note + // data not used bytes data = 10; - //only support size = 1, repeated list here for extension + //only support size = 1, repeated list here for extension repeated Contract contract = 11; // scripts not used bytes scripts = 12; @@ -336,7 +333,7 @@ message Transaction { } raw raw_data = 1; - // only support size = 1, repeated list here for muti-sig extension + // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; repeated Result ret = 5; } @@ -509,53 +506,6 @@ message HelloMessage { BlockId headBlockId = 6; } -message SmartContract { - message ABI { - message Entry { - enum EntryType { - UnknownEntryType = 0; - Constructor = 1; - Function = 2; - Event = 3; - Fallback = 4; - } - message Param { - bool indexed = 1; - string name = 2; - string type = 3; - // SolidityType type = 3; - } - enum StateMutabilityType { - UnknownMutabilityType = 0; - Pure = 1; - View = 2; - Nonpayable = 3; - Payable = 4; - } - - bool anonymous = 1; - bool constant = 2; - string name = 3; - repeated Param inputs = 4; - repeated Param outputs = 5; - EntryType type = 6; - bool payable = 7; - StateMutabilityType stateMutability = 8; - } - repeated Entry entrys = 1; - } - bytes origin_address = 1; - bytes contract_address = 2; - ABI abi = 3; - bytes bytecode = 4; - int64 call_value = 5; - int64 consume_user_resource_percent = 6; - string name = 7; - int64 origin_energy_limit = 8; - bytes code_hash = 9; - bytes trx_hash = 10; -} - message InternalTransaction { // internalTransaction identity, the root InternalTransaction hash // should equals to root transaction id. @@ -678,4 +628,4 @@ message NodeInfo { string stackTrace = 7; } } -} +} \ No newline at end of file diff --git a/core/contract/account_contract.proto b/core/contract/account_contract.proto new file mode 100644 index 000000000..d3180048f --- /dev/null +++ b/core/contract/account_contract.proto @@ -0,0 +1,50 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "Contract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message AccountCreateContract { + bytes owner_address = 1; + bytes account_address = 2; + AccountType type = 3; +} + +// Update account name. Account name is not unique now. +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} + +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + +message AccountPermissionUpdateContract { + bytes owner_address = 1; + Permission owner = 2; //Empty is invalidate + Permission witness = 3; //Can be empty + repeated Permission actives = 4; //Empty is invalidate +} + diff --git a/core/contract/asset_issue_contract.proto b/core/contract/asset_issue_contract.proto new file mode 100644 index 000000000..f2e515e57 --- /dev/null +++ b/core/contract/asset_issue_contract.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "AssetIssueContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message AssetIssueContract { + string id = 41; + + message FrozenSupply { + int64 frozen_amount = 1; + int64 frozen_days = 2; + } + bytes owner_address = 1; + bytes name = 2; + bytes abbr = 3; + int64 total_supply = 4; + repeated FrozenSupply frozen_supply = 5; + int32 trx_num = 6; + int32 precision = 7; + int32 num = 8; + int64 start_time = 9; + int64 end_time = 10; + int64 order = 11; // useless + int32 vote_score = 16; + bytes description = 20; + bytes url = 21; + int64 free_asset_net_limit = 22; + int64 public_free_asset_net_limit = 23; + int64 public_free_asset_net_usage = 24; + int64 public_latest_free_net_time = 25; +} + +message TransferAssetContract { + bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. + bytes owner_address = 2; + bytes to_address = 3; + int64 amount = 4; +} + +message UnfreezeAssetContract { + bytes owner_address = 1; +} + +message UpdateAssetContract { + bytes owner_address = 1; + bytes description = 2; + bytes url = 3; + int64 new_limit = 4; + int64 new_public_limit = 5; +} + +message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. + int64 amount = 4; // the amount of drops +} \ No newline at end of file diff --git a/core/contract/balance_contract.proto b/core/contract/balance_contract.proto new file mode 100644 index 000000000..1ce29a0cd --- /dev/null +++ b/core/contract/balance_contract.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "FreezeBalanceContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/contract/common.proto"; + +message FreezeBalanceContract { + bytes owner_address = 1; + int64 frozen_balance = 2; + int64 frozen_duration = 3; + + ResourceCode resource = 10; + bytes receiver_address = 15; +} + + +message UnfreezeBalanceContract { + bytes owner_address = 1; + + ResourceCode resource = 10; + bytes receiver_address = 15; +} + +message WithdrawBalanceContract { + bytes owner_address = 1; +} + +message TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 amount = 3; +} \ No newline at end of file diff --git a/core/contract/common.proto b/core/contract/common.proto new file mode 100644 index 000000000..561767186 --- /dev/null +++ b/core/contract/common.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "common"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +enum ResourceCode { + BANDWIDTH = 0x00; + ENERGY = 0x01; +} \ No newline at end of file diff --git a/core/contract/exchange_contract.proto b/core/contract/exchange_contract.proto new file mode 100644 index 000000000..a2f878c57 --- /dev/null +++ b/core/contract/exchange_contract.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ExchangeCreateContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message ExchangeCreateContract { + bytes owner_address = 1; + bytes first_token_id = 2; + int64 first_token_balance = 3; + bytes second_token_id = 4; + int64 second_token_balance = 5; +} + +message ExchangeInjectContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeWithdrawContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeTransactionContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; + int64 expected = 5; +} \ No newline at end of file diff --git a/core/contract/proposal_contract.proto b/core/contract/proposal_contract.proto new file mode 100644 index 000000000..6cd25fab0 --- /dev/null +++ b/core/contract/proposal_contract.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ProposalApproveContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} \ No newline at end of file diff --git a/core/contract/shield_contract.proto b/core/contract/shield_contract.proto new file mode 100644 index 000000000..9119a7aef --- /dev/null +++ b/core/contract/shield_contract.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ShieldedTransferContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +// for shielded transaction + +message AuthenticationPath { + repeated bool value = 1; +} + +message MerklePath { + repeated AuthenticationPath authentication_paths = 1; + repeated bool index = 2; + bytes rt = 3; +} + +message OutputPoint { + bytes hash = 1; + int32 index = 2; +} + +message OutputPointInfo { + repeated OutputPoint out_points = 1; + int32 block_num = 2; +} + +message PedersenHash { + bytes content = 1; +} + +message IncrementalMerkleTree { + PedersenHash left = 1; + PedersenHash right = 2; + repeated PedersenHash parents = 3; +} + +message IncrementalMerkleVoucher { + IncrementalMerkleTree tree = 1; + repeated PedersenHash filled = 2; + IncrementalMerkleTree cursor = 3; + int64 cursor_depth = 4; + bytes rt = 5; + OutputPoint output_point = 10; +} + +message IncrementalMerkleVoucherInfo { + repeated IncrementalMerkleVoucher vouchers = 1; + repeated bytes paths = 2; +} + +message SpendDescription { + bytes value_commitment = 1; + bytes anchor = 2; // merkle root + bytes nullifier = 3; // used for check double spend + bytes rk = 4; // used for check spend authority signature + bytes zkproof = 5; + bytes spend_authority_signature = 6; +} + +message ReceiveDescription { + bytes value_commitment = 1; + bytes note_commitment = 2; + bytes epk = 3; // for Encryption + bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk + bytes c_out = 5; // Encryption for audit, decrypt it with ovk + bytes zkproof = 6; +} + +message ShieldedTransferContract { + bytes transparent_from_address = 1; // transparent address + int64 from_amount = 2; + repeated SpendDescription spend_description = 3; + repeated ReceiveDescription receive_description = 4; + bytes binding_signature = 5; + bytes transparent_to_address = 6; // transparent address + int64 to_amount = 7; // the amount to transparent to_address +} diff --git a/core/contract/smart_contract.proto b/core/contract/smart_contract.proto new file mode 100644 index 000000000..642264679 --- /dev/null +++ b/core/contract/smart_contract.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "CreateSmartContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + int64 consume_user_resource_percent = 6; + string name = 7; + int64 origin_energy_limit = 8; + bytes code_hash = 9; + bytes trx_hash = 10; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; + int64 call_token_value = 3; + int64 token_id = 4; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; + int64 call_token_value = 5; + int64 token_id = 6; +} + +message ClearABIContract { + bytes owner_address = 1; + bytes contract_address = 2; +} + +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + +message UpdateEnergyLimitContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 origin_energy_limit = 3; +} \ No newline at end of file diff --git a/core/contract/storage_contract.proto b/core/contract/storage_contract.proto new file mode 100644 index 000000000..666e6b11f --- /dev/null +++ b/core/contract/storage_contract.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "BuyStorageBytesContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} + +message UpdateBrokerageContract { + bytes owner_address = 1; + int32 brokerage = 2; // 1 mean 1% +} diff --git a/core/contract/vote_asset_contract.proto b/core/contract/vote_asset_contract.proto new file mode 100644 index 000000000..0ca124773 --- /dev/null +++ b/core/contract/vote_asset_contract.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "VoteAssetContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message VoteAssetContract { + bytes owner_address = 1; + repeated bytes vote_address = 2; + bool support = 3; + int32 count = 5; +} \ No newline at end of file diff --git a/core/contract/witness_contract.proto b/core/contract/witness_contract.proto new file mode 100644 index 000000000..acd4292a4 --- /dev/null +++ b/core/contract/witness_contract.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "WitnessCreateContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message WitnessCreateContract { + bytes owner_address = 1; + bytes url = 2; +} + +message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; +} + +message VoteWitnessContract { + message Vote { + bytes vote_address = 1; + int64 vote_count = 2; + } + bytes owner_address = 1; + repeated Vote votes = 2; + bool support = 3; +} \ No newline at end of file diff --git a/core/tron/account.proto b/core/tron/account.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/block.proto b/core/tron/block.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/delegated_resource.proto b/core/tron/delegated_resource.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/p2p.proto b/core/tron/p2p.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/proposal.proto b/core/tron/proposal.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/transaction.proto b/core/tron/transaction.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/vote.proto b/core/tron/vote.proto new file mode 100644 index 000000000..e69de29bb diff --git a/core/tron/witness.proto b/core/tron/witness.proto new file mode 100644 index 000000000..e69de29bb From 53b796d5881a97c4dcbc15e324c8e73b67b91806 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 13 Mar 2020 16:43:43 +0800 Subject: [PATCH 286/445] spell check --- api/api.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index 90feaf0fc..14f59ef74 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1087,7 +1087,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) + bytes txid = 3; //transaction id = sha256(transaction.rawdata) } message AddressPrKeyPairMessage { @@ -1097,7 +1097,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.rawdata) repeated bytes constant_result = 3; Return result = 4; } @@ -1182,7 +1182,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.rawdata) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1191,7 +1191,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.rawdata) int32 index = 3; //the index of note in receive bool is_spend = 4; } From ccdb97fbccf55140d4a85a21834ce91dc31532b2 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 13 Mar 2020 16:45:18 +0800 Subject: [PATCH 287/445] spell check --- api/api.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index 14f59ef74..ad86cad37 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1087,7 +1087,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rawdata) + bytes txid = 3; //transaction id = sha256(transaction.raw_data) } message AddressPrKeyPairMessage { @@ -1097,7 +1097,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rawdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; } @@ -1182,7 +1182,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rawdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1191,7 +1191,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rawdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive bool is_spend = 4; } From 3cd35af457fdc0e1d674944fc20d3c1069bb3868 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 13 Mar 2020 16:46:33 +0800 Subject: [PATCH 288/445] spell check --- api/api.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/api.proto b/api/api.proto index ad86cad37..945fc7730 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1087,7 +1087,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.raw_data) + bytes txid = 3; //transaction id = sha256(transaction.raw_data) } message AddressPrKeyPairMessage { @@ -1097,7 +1097,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; } @@ -1182,7 +1182,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1191,7 +1191,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive bool is_spend = 4; } From 22dedea16378103c2883771cafde3ce7438da60e Mon Sep 17 00:00:00 2001 From: wubinTron Date: Sat, 14 Mar 2020 11:42:19 +0800 Subject: [PATCH 289/445] fix following comment --- core/Tron.proto | 8 ++++---- core/contract/asset_issue_contract.proto | 2 +- core/contract/balance_contract.proto | 2 +- core/contract/exchange_contract.proto | 2 +- core/contract/proposal_contract.proto | 2 +- core/contract/smart_contract.proto | 2 +- core/contract/vote_asset_contract.proto | 2 +- core/contract/witness_contract.proto | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index d794d3083..797273ce6 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -322,9 +322,9 @@ message Transaction { bytes ref_block_hash = 4; int64 expiration = 8; repeated authority auths = 9; - // data not used + // transaction note bytes data = 10; - //only support size = 1, repeated list here for extension + //only support size = 1, repeated list here for extension repeated Contract contract = 11; // scripts not used bytes scripts = 12; @@ -333,7 +333,7 @@ message Transaction { } raw raw_data = 1; - // only support size = 1, repeated list here for muti-sig extension + // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; repeated Result ret = 5; } @@ -628,4 +628,4 @@ message NodeInfo { string stackTrace = 7; } } -} \ No newline at end of file +} diff --git a/core/contract/asset_issue_contract.proto b/core/contract/asset_issue_contract.proto index f2e515e57..cdd086a2b 100644 --- a/core/contract/asset_issue_contract.proto +++ b/core/contract/asset_issue_contract.proto @@ -57,4 +57,4 @@ message ParticipateAssetIssueContract { bytes to_address = 2; bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. int64 amount = 4; // the amount of drops -} \ No newline at end of file +} diff --git a/core/contract/balance_contract.proto b/core/contract/balance_contract.proto index 1ce29a0cd..07f7141f6 100644 --- a/core/contract/balance_contract.proto +++ b/core/contract/balance_contract.proto @@ -33,4 +33,4 @@ message TransferContract { bytes owner_address = 1; bytes to_address = 2; int64 amount = 3; -} \ No newline at end of file +} diff --git a/core/contract/exchange_contract.proto b/core/contract/exchange_contract.proto index a2f878c57..58d69cc2a 100644 --- a/core/contract/exchange_contract.proto +++ b/core/contract/exchange_contract.proto @@ -34,4 +34,4 @@ message ExchangeTransactionContract { bytes token_id = 3; int64 quant = 4; int64 expected = 5; -} \ No newline at end of file +} diff --git a/core/contract/proposal_contract.proto b/core/contract/proposal_contract.proto index 6cd25fab0..1dc74d8f7 100644 --- a/core/contract/proposal_contract.proto +++ b/core/contract/proposal_contract.proto @@ -20,4 +20,4 @@ message ProposalCreateContract { message ProposalDeleteContract { bytes owner_address = 1; int64 proposal_id = 2; -} \ No newline at end of file +} diff --git a/core/contract/smart_contract.proto b/core/contract/smart_contract.proto index 642264679..739abdfba 100644 --- a/core/contract/smart_contract.proto +++ b/core/contract/smart_contract.proto @@ -86,4 +86,4 @@ message UpdateEnergyLimitContract { bytes owner_address = 1; bytes contract_address = 2; int64 origin_energy_limit = 3; -} \ No newline at end of file +} diff --git a/core/contract/vote_asset_contract.proto b/core/contract/vote_asset_contract.proto index 0ca124773..522666e47 100644 --- a/core/contract/vote_asset_contract.proto +++ b/core/contract/vote_asset_contract.proto @@ -11,4 +11,4 @@ message VoteAssetContract { repeated bytes vote_address = 2; bool support = 3; int32 count = 5; -} \ No newline at end of file +} diff --git a/core/contract/witness_contract.proto b/core/contract/witness_contract.proto index acd4292a4..115a2128e 100644 --- a/core/contract/witness_contract.proto +++ b/core/contract/witness_contract.proto @@ -24,4 +24,4 @@ message VoteWitnessContract { bytes owner_address = 1; repeated Vote votes = 2; bool support = 3; -} \ No newline at end of file +} From d9451e02f6aa8d3721c1ea31f9ad8f42f26b1b9d Mon Sep 17 00:00:00 2001 From: wubinTron Date: Sat, 14 Mar 2020 11:48:01 +0800 Subject: [PATCH 290/445] fix format wrong --- core/contract/asset_issue_contract.proto | 1 + core/contract/common.proto | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/contract/asset_issue_contract.proto b/core/contract/asset_issue_contract.proto index cdd086a2b..5ad372887 100644 --- a/core/contract/asset_issue_contract.proto +++ b/core/contract/asset_issue_contract.proto @@ -58,3 +58,4 @@ message ParticipateAssetIssueContract { bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. int64 amount = 4; // the amount of drops } + \ No newline at end of file diff --git a/core/contract/common.proto b/core/contract/common.proto index 561767186..933c5f5d0 100644 --- a/core/contract/common.proto +++ b/core/contract/common.proto @@ -9,4 +9,4 @@ option go_package = "github.com/tronprotocol/grpc-gateway/core"; enum ResourceCode { BANDWIDTH = 0x00; ENERGY = 0x01; -} \ No newline at end of file +} From 797cd4a1f33f8fde9144ffbb3932b95220cccd5a Mon Sep 17 00:00:00 2001 From: wubinTron Date: Sat, 14 Mar 2020 11:48:58 +0800 Subject: [PATCH 291/445] fix format wrong --- core/contract/asset_issue_contract.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/core/contract/asset_issue_contract.proto b/core/contract/asset_issue_contract.proto index 5ad372887..cdd086a2b 100644 --- a/core/contract/asset_issue_contract.proto +++ b/core/contract/asset_issue_contract.proto @@ -58,4 +58,3 @@ message ParticipateAssetIssueContract { bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. int64 amount = 4; // the amount of drops } - \ No newline at end of file From 430bceb3defcdd7fd0d3b68d02e2ad995197022e Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 19 Mar 2020 01:56:26 +0800 Subject: [PATCH 292/445] feat: merge master to develop --- README.md | 4 + .../tron/common/utils/TransactionUtils.java | 59 ++- .../java/org/tron/common/utils/Utils.java | 66 ++- .../org/tron/demo/TransactionSignDemo.java | 5 +- src/main/java/org/tron/test/Test.java | 5 +- src/main/java/org/tron/walletcli/Client.java | 58 ++- .../org/tron/walletcli/WalletApiWrapper.java | 27 +- .../org/tron/walletserver/GrpcClient.java | 201 +++++--- .../java/org/tron/walletserver/WalletApi.java | 460 +++++++++++------- src/main/protos/.travis.yml | 23 +- ...glish version of TRON Protocol document.md | 196 ++++---- src/main/protos/README.md | 2 + src/main/protos/api/api.proto | 43 +- src/main/protos/api/zksnark.proto | 33 ++ src/main/protos/core/Contract.proto | 342 ------------- src/main/protos/core/Tron.proto | 6 +- .../core/contract/account_contract.proto | 50 ++ .../core/contract/asset_issue_contract.proto | 60 +++ .../core/contract/balance_contract.proto | 36 ++ src/main/protos/core/contract/common.proto | 12 + .../core/contract/exchange_contract.proto | 37 ++ .../core/contract/proposal_contract.proto | 23 + .../core/contract/shield_contract.proto | 81 +++ .../protos/core/contract/smart_contract.proto | 89 ++++ .../core/contract/storage_contract.proto | 27 + .../core/contract/vote_asset_contract.proto | 14 + .../core/contract/witness_contract.proto | 27 + src/main/protos/core/tron/account.proto | 0 src/main/protos/core/tron/block.proto | 0 .../protos/core/tron/delegated_resource.proto | 0 src/main/protos/core/tron/p2p.proto | 0 src/main/protos/core/tron/proposal.proto | 0 src/main/protos/core/tron/transaction.proto | 0 src/main/protos/core/tron/vote.proto | 0 src/main/protos/core/tron/witness.proto | 0 35 files changed, 1223 insertions(+), 763 deletions(-) create mode 100644 src/main/protos/api/zksnark.proto delete mode 100644 src/main/protos/core/Contract.proto create mode 100644 src/main/protos/core/contract/account_contract.proto create mode 100644 src/main/protos/core/contract/asset_issue_contract.proto create mode 100644 src/main/protos/core/contract/balance_contract.proto create mode 100644 src/main/protos/core/contract/common.proto create mode 100644 src/main/protos/core/contract/exchange_contract.proto create mode 100644 src/main/protos/core/contract/proposal_contract.proto create mode 100644 src/main/protos/core/contract/shield_contract.proto create mode 100644 src/main/protos/core/contract/smart_contract.proto create mode 100644 src/main/protos/core/contract/storage_contract.proto create mode 100644 src/main/protos/core/contract/vote_asset_contract.proto create mode 100644 src/main/protos/core/contract/witness_contract.proto create mode 100644 src/main/protos/core/tron/account.proto create mode 100644 src/main/protos/core/tron/block.proto create mode 100644 src/main/protos/core/tron/delegated_resource.proto create mode 100644 src/main/protos/core/tron/p2p.proto create mode 100644 src/main/protos/core/tron/proposal.proto create mode 100644 src/main/protos/core/tron/transaction.proto create mode 100644 src/main/protos/core/tron/vote.proto create mode 100644 src/main/protos/core/tron/witness.proto diff --git a/README.md b/README.md index f97b29f78..6e976086f 100644 --- a/README.md +++ b/README.md @@ -1045,6 +1045,9 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 **GetTransactionInfoById** > Get transaction-info based on transaction id, generally used to check the result of a smart contract trigger +**GetTransactionInfoByBlockNum** +> Get the list of transaction information in the block based on the block height + ## How to get block information **GetBlock** @@ -1496,6 +1499,7 @@ For more information on a specific command, just type the command on terminal wh GetTransactionApprovedList GetTransactionById GetTransactionCountByBlockNum + GetTransactionInfoByBlockNum GetTransactionInfoById GetTransactionsFromThis GetTransactionsToThis diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index 33fac80f8..fd77fe2d4 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -16,6 +16,10 @@ package org.tron.common.utils; import com.google.protobuf.ByteString; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Sm3Hash; @@ -23,11 +27,22 @@ import org.tron.common.crypto.SignatureInterface; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; - -import java.security.SignatureException; -import java.util.Arrays; -import java.util.List; -import java.util.Scanner; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.VoteAssetContractOuterClass.VoteAssetContract; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; public class TransactionUtils { @@ -52,112 +67,110 @@ public static byte[] getOwner(Transaction.Contract contract) { owner = contract .getParameter() - .unpack(org.tron.protos.Contract.AccountCreateContract.class) - .getOwnerAddress(); + .unpack(AccountCreateContract.class).getOwnerAddress(); break; case TransferContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.TransferContract.class) + .unpack(TransferContract.class) .getOwnerAddress(); break; case TransferAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.TransferAssetContract.class) + .unpack(TransferAssetContract.class) .getOwnerAddress(); break; case VoteAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.VoteAssetContract.class) + .unpack(VoteAssetContract.class) .getOwnerAddress(); break; case VoteWitnessContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.VoteWitnessContract.class) + .unpack(VoteWitnessContract.class) .getOwnerAddress(); break; case WitnessCreateContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.WitnessCreateContract.class) - .getOwnerAddress(); + .unpack(WitnessCreateContract.class).getOwnerAddress(); break; case AssetIssueContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.AssetIssueContract.class) + .unpack(AssetIssueContract.class) .getOwnerAddress(); break; case ParticipateAssetIssueContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) + .unpack(ParticipateAssetIssueContract.class) .getOwnerAddress(); break; case CreateSmartContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.CreateSmartContract.class) + .unpack(CreateSmartContract.class) .getOwnerAddress(); break; case TriggerSmartContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.TriggerSmartContract.class) + .unpack(TriggerSmartContract.class) .getOwnerAddress(); break; case FreezeBalanceContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.FreezeBalanceContract.class) + .unpack(FreezeBalanceContract.class) .getOwnerAddress(); break; case UnfreezeBalanceContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class) + .unpack(UnfreezeBalanceContract.class) .getOwnerAddress(); break; case UnfreezeAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class) + .unpack(UnfreezeAssetContract.class) .getOwnerAddress(); break; case WithdrawBalanceContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class) + .unpack(WithdrawBalanceContract.class) .getOwnerAddress(); break; case UpdateAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.UpdateAssetContract.class) + .unpack(UpdateAssetContract.class) .getOwnerAddress(); break; case AccountPermissionUpdateContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) + .unpack(AccountPermissionUpdateContract.class) .getOwnerAddress(); break; default: diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 15592373f..160ce0893 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -23,14 +23,6 @@ import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import org.tron.api.GrpcAPI.*; -import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.keystore.StringUtils; -import org.tron.protos.Contract.*; -import org.tron.protos.Protocol.Block; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; import java.io.Console; import java.io.IOException; @@ -45,6 +37,46 @@ import java.util.List; import java.util.Scanner; +import org.tron.api.GrpcAPI.*; +import org.tron.common.crypto.Hash; +import org.tron.common.crypto.Sha256Sm3Hash; +import org.tron.keystore.StringUtils; +import org.tron.walletserver.WalletApi; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AccountContract.AccountUpdateContract; +import org.tron.protos.contract.AccountContract.SetAccountIdContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; +import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; +import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; +import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.ProposalContract.ProposalApproveContract; +import org.tron.protos.contract.ProposalContract.ProposalCreateContract; +import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; +import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; +import org.tron.protos.contract.VoteAssetContractOuterClass.VoteAssetContract; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; +import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; +import org.tron.protos.contract.ShieldContract.ShieldedTransferContract; + public class Utils { public static final String PERMISSION_ID = "Permission_id"; public static final String VISIBLE = "visible"; @@ -126,6 +158,16 @@ public static String printTransactionList(TransactionList transactionList) { return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } + public static String printTransactionInfoList(TransactionInfoList transactionInfoList) { + JSONArray jsonArray = new JSONArray(); + List infoList = transactionInfoList.getTransactionInfoList(); + infoList.stream() + .forEach( + transactionInfo -> jsonArray.add(formatMessageString(transactionInfo)) + ); + return JsonFormatUtil.formatJson(jsonArray.toJSONString()); + } + public static String printTransactionList(TransactionListExtention transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); @@ -432,9 +474,9 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean JsonFormat.printToString(proposalDeleteContract, selfType)); break; case SetAccountIdContract: - org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = + SetAccountIdContract setAccountIdContract = contractParameter.unpack( - org.tron.protos.Contract.SetAccountIdContract.class); + SetAccountIdContract.class); contractJson = JSONObject.parseObject( JsonFormat.printToString(setAccountIdContract, selfType)); @@ -505,8 +547,8 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean JsonFormat.printToString(accountPermissionUpdateContract, selfType)); break; case ClearABIContract: - org.tron.protos.Contract.ClearABIContract clearABIContract = - contractParameter.unpack(org.tron.protos.Contract.ClearABIContract.class); + ClearABIContract clearABIContract = + contractParameter.unpack(ClearABIContract.class); contractJson = JSONObject.parseObject( JsonFormat.printToString(clearABIContract, selfType)); diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 0aeeebe3b..b787981c7 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -9,9 +9,9 @@ import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; -import org.tron.protos.Contract; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.walletserver.WalletApi; import java.util.Arrays; @@ -43,8 +43,7 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = - Contract.TransferContract.newBuilder(); + TransferContract.Builder transferContractBuilder = TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 470a434fe..946c20a79 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -16,10 +16,9 @@ import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; import org.tron.keystore.WalletUtils; -import org.tron.protos.Contract; -import org.tron.protos.Contract.TransferContract; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.walletserver.WalletApi; import java.io.File; @@ -44,7 +43,7 @@ public static Transaction createTransactionEx(String toAddress, long amount) { for (int i = 0; i < 10; i++) { Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract + TransferContract.Builder transferContractBuilder = TransferContract .newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(ByteArray diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 80d320bc2..fc52a6df0 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1,5 +1,13 @@ package org.tron.walletcli; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Base64.Decoder; +import java.util.Base64.Encoder; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.beust.jcommander.JCommander; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; @@ -31,18 +39,18 @@ import org.tron.core.zen.address.KeyIo; import org.tron.core.zen.address.PaymentAddress; import org.tron.keystore.StringUtils; -import org.tron.protos.Contract.AssetIssueContract; -import org.tron.protos.Protocol.*; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.DelegatedResourceAccountIndex; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; import org.tron.walletserver.WalletApi; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Base64.Decoder; -import java.util.Base64.Encoder; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class Client { @@ -104,6 +112,7 @@ public class Client { "GetTransactionApprovedList", "GetTransactionById", "GetTransactionCountByBlockNum", + "GetTransactionInfoByBlockNum", "GetTransactionInfoById", "GetTransactionsFromThis", "GetTransactionsToThis", @@ -207,6 +216,7 @@ public class Client { "GetTransactionApprovedList", "GetTransactionById", "GetTransactionCountByBlockNum", + "GetTransactionInfoByBlockNum", "GetTransactionInfoById", "GetTransactionsFromThis", "GetTransactionsToThis", @@ -1957,6 +1967,30 @@ private void getBrokerage(String[] parameters) { System.out.println("The brokerage is : " + brokerage.getNum()); } + private void getTransactionInfoByBlockNum(String[] parameters) { + if (parameters.length != 1) { + System.out.println("Too many parameters !!!"); + System.out.println("You need input number with the following syntax:"); + System.out.println("GetTransactionInfoByBlockNum number"); + return; + } + + long blockNum = Long.parseLong(parameters[0]); + Optional result = walletApiWrapper.getTransactionInfoByBlockNum(blockNum); + + if (result.isPresent()) { + TransactionInfoList transactionInfoList = result.get(); + if (transactionInfoList.getTransactionInfoCount() == 0) { + System.out.println("[]"); + } else { + System.out.println(Utils.printTransactionInfoList(transactionInfoList)); + } + } else { + System.out.println("GetTransactionInfoByBlockNum failed !!!"); + } + + } + private String[] getParas(String[] para) { String paras = String.join(" ", para); Pattern pattern = Pattern.compile(" (\\[.*?\\]) "); @@ -3343,6 +3377,10 @@ private void run() { create2(parameters); break; } + case "gettransactioninfobyblocknum": { + getTransactionInfoByBlockNum(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index c844e9345..23ddc33a5 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -20,12 +20,16 @@ import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; import org.tron.keystore.WalletFile; -import org.tron.protos.Contract; -import org.tron.protos.Contract.AssetIssueContract; -import org.tron.protos.Contract.IncrementalMerkleVoucherInfo; -import org.tron.protos.Contract.OutputPoint; -import org.tron.protos.Contract.OutputPointInfo; -import org.tron.protos.Protocol.*; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo; +import org.tron.protos.contract.ShieldContract.OutputPoint; +import org.tron.protos.contract.ShieldContract.OutputPointInfo; import org.tron.walletserver.WalletApi; import java.io.IOException; @@ -205,7 +209,7 @@ public boolean assetIssue(byte[] ownerAddress, String name, String abbrName, lon return false; } - Contract.AssetIssueContract.Builder builder = Contract.AssetIssueContract.newBuilder(); + AssetIssueContract.Builder builder = AssetIssueContract.newBuilder(); if (ownerAddress == null) { ownerAddress = wallet.getAddress(); } @@ -272,8 +276,8 @@ public boolean assetIssue(byte[] ownerAddress, String name, String abbrName, lon String amountStr = frozenSupply.get(daysStr); long amount = Long.parseLong(amountStr); long days = Long.parseLong(daysStr); - Contract.AssetIssueContract.FrozenSupply.Builder frozenSupplyBuilder - = Contract.AssetIssueContract.FrozenSupply.newBuilder(); + AssetIssueContract.FrozenSupply.Builder frozenSupplyBuilder + = AssetIssueContract.FrozenSupply.newBuilder(); frozenSupplyBuilder.setFrozenAmount(amount); frozenSupplyBuilder.setFrozenDays(days); builder.addFrozenSupply(frozenSupplyBuilder.build()); @@ -1209,4 +1213,9 @@ public GrpcAPI.NumberMessage getReward(byte[] ownerAddress) { public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } + + public static Optional getTransactionInfoByBlockNum(long blockNum) { + return WalletApi.getTransactionInfoByBlockNum(blockNum); + } + } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index f04066b8c..32726a127 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -1,5 +1,9 @@ package org.tron.walletserver; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -8,18 +12,61 @@ import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.*; import org.tron.api.GrpcAPI.Return.response_code; +import org.tron.api.GrpcAPI.TransactionApprovedList; +import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; +import org.tron.api.GrpcAPI.TransactionList; +import org.tron.api.GrpcAPI.TransactionListExtention; +import org.tron.api.GrpcAPI.TransactionSignWeight; +import org.tron.api.GrpcAPI.WitnessList; import org.tron.api.WalletExtensionGrpc; import org.tron.api.WalletGrpc; import org.tron.api.WalletSolidityGrpc; import org.tron.common.utils.ByteArray; -import org.tron.protos.Contract; -import org.tron.protos.Contract.IncrementalMerkleVoucherInfo; -import org.tron.protos.Contract.OutputPointInfo; -import org.tron.protos.Protocol.*; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.DelegatedResourceAccountIndex; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AccountContract.AccountUpdateContract; +import org.tron.protos.contract.AccountContract.SetAccountIdContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; +import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; +import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; +import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.ProposalContract.ProposalApproveContract; +import org.tron.protos.contract.ProposalContract.ProposalCreateContract; +import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; +import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo; +import org.tron.protos.contract.ShieldContract.OutputPointInfo; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.Protocol.TransactionSign; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; +import org.tron.protos.contract.StorageContract.BuyStorageBytesContract; +import org.tron.protos.contract.StorageContract.BuyStorageContract; +import org.tron.protos.contract.StorageContract.SellStorageContract; +import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; +import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; @Slf4j public class GrpcClient { @@ -160,114 +207,114 @@ public EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, byte[] return blockingStubFull.easyTransferAssetByPrivate(builder.build()); } - public Transaction createTransaction(Contract.AccountUpdateContract contract) { + public Transaction createTransaction(AccountUpdateContract contract) { return blockingStubFull.updateAccount(contract); } - public TransactionExtention createTransaction2(Contract.AccountUpdateContract contract) { + public TransactionExtention createTransaction2(AccountUpdateContract contract) { return blockingStubFull.updateAccount2(contract); } - public Transaction createTransaction(Contract.SetAccountIdContract contract) { + public Transaction createTransaction(SetAccountIdContract contract) { return blockingStubFull.setAccountId(contract); } - public Transaction createTransaction(Contract.UpdateAssetContract contract) { + public Transaction createTransaction(UpdateAssetContract contract) { return blockingStubFull.updateAsset(contract); } - public TransactionExtention createTransaction2(Contract.UpdateAssetContract contract) { + public TransactionExtention createTransaction2(UpdateAssetContract contract) { return blockingStubFull.updateAsset2(contract); } - public Transaction createTransaction(Contract.TransferContract contract) { + public Transaction createTransaction(TransferContract contract) { return blockingStubFull.createTransaction(contract); } - public TransactionExtention createTransaction2(Contract.TransferContract contract) { + public TransactionExtention createTransaction2(TransferContract contract) { return blockingStubFull.createTransaction2(contract); } - public Transaction createTransaction(Contract.FreezeBalanceContract contract) { + public Transaction createTransaction(FreezeBalanceContract contract) { return blockingStubFull.freezeBalance(contract); } - public TransactionExtention createTransaction(Contract.BuyStorageContract contract) { + public TransactionExtention createTransaction(BuyStorageContract contract) { return blockingStubFull.buyStorage(contract); } - public TransactionExtention createTransaction(Contract.BuyStorageBytesContract contract) { + public TransactionExtention createTransaction(BuyStorageBytesContract contract) { return blockingStubFull.buyStorageBytes(contract); } - public TransactionExtention createTransaction(Contract.SellStorageContract contract) { + public TransactionExtention createTransaction(SellStorageContract contract) { return blockingStubFull.sellStorage(contract); } - public TransactionExtention createTransaction2(Contract.FreezeBalanceContract contract) { + public TransactionExtention createTransaction2(FreezeBalanceContract contract) { return blockingStubFull.freezeBalance2(contract); } - public Transaction createTransaction(Contract.WithdrawBalanceContract contract) { + public Transaction createTransaction(WithdrawBalanceContract contract) { return blockingStubFull.withdrawBalance(contract); } - public TransactionExtention createTransaction2(Contract.WithdrawBalanceContract contract) { + public TransactionExtention createTransaction2(WithdrawBalanceContract contract) { return blockingStubFull.withdrawBalance2(contract); } - public Transaction createTransaction(Contract.UnfreezeBalanceContract contract) { + public Transaction createTransaction(UnfreezeBalanceContract contract) { return blockingStubFull.unfreezeBalance(contract); } - public TransactionExtention createTransaction2(Contract.UnfreezeBalanceContract contract) { + public TransactionExtention createTransaction2(UnfreezeBalanceContract contract) { return blockingStubFull.unfreezeBalance2(contract); } - public Transaction createTransaction(Contract.UnfreezeAssetContract contract) { + public Transaction createTransaction(UnfreezeAssetContract contract) { return blockingStubFull.unfreezeAsset(contract); } - public TransactionExtention createTransaction2(Contract.UnfreezeAssetContract contract) { + public TransactionExtention createTransaction2(UnfreezeAssetContract contract) { return blockingStubFull.unfreezeAsset2(contract); } - public Transaction createTransferAssetTransaction(Contract.TransferAssetContract contract) { + public Transaction createTransferAssetTransaction(TransferAssetContract contract) { return blockingStubFull.transferAsset(contract); } public TransactionExtention createTransferAssetTransaction2( - Contract.TransferAssetContract contract) { + TransferAssetContract contract) { return blockingStubFull.transferAsset2(contract); } public Transaction createParticipateAssetIssueTransaction( - Contract.ParticipateAssetIssueContract contract) { + ParticipateAssetIssueContract contract) { return blockingStubFull.participateAssetIssue(contract); } public TransactionExtention createParticipateAssetIssueTransaction2( - Contract.ParticipateAssetIssueContract contract) { + ParticipateAssetIssueContract contract) { return blockingStubFull.participateAssetIssue2(contract); } - public Transaction createAssetIssue(Contract.AssetIssueContract contract) { + public Transaction createAssetIssue(AssetIssueContract contract) { return blockingStubFull.createAssetIssue(contract); } - public TransactionExtention createAssetIssue2(Contract.AssetIssueContract contract) { + public TransactionExtention createAssetIssue2(AssetIssueContract contract) { return blockingStubFull.createAssetIssue2(contract); } - public Transaction voteWitnessAccount(Contract.VoteWitnessContract contract) { + public Transaction voteWitnessAccount(VoteWitnessContract contract) { return blockingStubFull.voteWitnessAccount(contract); } - public TransactionExtention voteWitnessAccount2(Contract.VoteWitnessContract contract) { + public TransactionExtention voteWitnessAccount2(VoteWitnessContract contract) { return blockingStubFull.voteWitnessAccount2(contract); } - public TransactionExtention proposalCreate(Contract.ProposalCreateContract contract) { + public TransactionExtention proposalCreate(ProposalCreateContract contract) { return blockingStubFull.proposalCreate(contract); } @@ -296,8 +343,12 @@ public Optional getDelegatedResource(String fromAddress, .setFromAddress(fromAddressBS) .setToAddress(toAddressBS) .build(); - DelegatedResourceList delegatedResource = blockingStubFull - .getDelegatedResource(request); + DelegatedResourceList delegatedResource; + if (blockingStubSolidity != null) { + delegatedResource = blockingStubSolidity.getDelegatedResource(request); + } else { + delegatedResource = blockingStubFull.getDelegatedResource(request); + } return Optional.ofNullable(delegatedResource); } @@ -307,9 +358,12 @@ public Optional getDelegatedResourceAccountIndex( Objects.requireNonNull(WalletApi.decodeFromBase58Check(address))); BytesMessage bytesMessage = BytesMessage.newBuilder().setValue(addressBS).build(); - - DelegatedResourceAccountIndex accountIndex = blockingStubFull - .getDelegatedResourceAccountIndex(bytesMessage); + DelegatedResourceAccountIndex accountIndex; + if (blockingStubSolidity != null) { + accountIndex = blockingStubSolidity.getDelegatedResourceAccountIndex(bytesMessage); + } else { + accountIndex = blockingStubFull.getDelegatedResourceAccountIndex(bytesMessage); + } return Optional.ofNullable(accountIndex); } @@ -346,35 +400,35 @@ public Optional getChainParameters() { return Optional.ofNullable(chainParameters); } - public TransactionExtention proposalApprove(Contract.ProposalApproveContract contract) { + public TransactionExtention proposalApprove(ProposalApproveContract contract) { return blockingStubFull.proposalApprove(contract); } - public TransactionExtention proposalDelete(Contract.ProposalDeleteContract contract) { + public TransactionExtention proposalDelete(ProposalDeleteContract contract) { return blockingStubFull.proposalDelete(contract); } - public TransactionExtention exchangeCreate(Contract.ExchangeCreateContract contract) { + public TransactionExtention exchangeCreate(ExchangeCreateContract contract) { return blockingStubFull.exchangeCreate(contract); } - public TransactionExtention exchangeInject(Contract.ExchangeInjectContract contract) { + public TransactionExtention exchangeInject(ExchangeInjectContract contract) { return blockingStubFull.exchangeInject(contract); } - public TransactionExtention exchangeWithdraw(Contract.ExchangeWithdrawContract contract) { + public TransactionExtention exchangeWithdraw(ExchangeWithdrawContract contract) { return blockingStubFull.exchangeWithdraw(contract); } - public TransactionExtention exchangeTransaction(Contract.ExchangeTransactionContract contract) { + public TransactionExtention exchangeTransaction(ExchangeTransactionContract contract) { return blockingStubFull.exchangeTransaction(contract); } - public Transaction createAccount(Contract.AccountCreateContract contract) { + public Transaction createAccount(AccountCreateContract contract) { return blockingStubFull.createAccount(contract); } - public TransactionExtention createAccount2(Contract.AccountCreateContract contract) { + public TransactionExtention createAccount2(AccountCreateContract contract) { return blockingStubFull.createAccount2(contract); } @@ -386,19 +440,19 @@ public AddressPrKeyPairMessage generateAddress(EmptyMessage emptyMessage) { } } - public Transaction createWitness(Contract.WitnessCreateContract contract) { + public Transaction createWitness(WitnessCreateContract contract) { return blockingStubFull.createWitness(contract); } - public TransactionExtention createWitness2(Contract.WitnessCreateContract contract) { + public TransactionExtention createWitness2(WitnessCreateContract contract) { return blockingStubFull.createWitness2(contract); } - public Transaction updateWitness(Contract.WitnessUpdateContract contract) { + public Transaction updateWitness(WitnessUpdateContract contract) { return blockingStubFull.updateWitness(contract); } - public TransactionExtention updateWitness2(Contract.WitnessUpdateContract contract) { + public TransactionExtention updateWitness2(WitnessUpdateContract contract) { return blockingStubFull.updateWitness2(contract); } @@ -556,7 +610,7 @@ public AccountResourceMessage getAccountResource(byte[] address) { return blockingStubFull.getAccountResource(request); } - public Contract.AssetIssueContract getAssetIssueByName(String assetName) { + public AssetIssueContract getAssetIssueByName(String assetName) { ByteString assetNameBs = ByteString.copyFrom(assetName.getBytes()); BytesMessage request = BytesMessage.newBuilder().setValue(assetNameBs).build(); if (blockingStubSolidity != null) { @@ -578,7 +632,7 @@ public Optional getAssetIssueListByName(String assetName) { } } - public Contract.AssetIssueContract getAssetIssueById(String assetId) { + public AssetIssueContract getAssetIssueById(String assetId) { ByteString assetIdBs = ByteString.copyFrom(assetId.getBytes()); BytesMessage request = BytesMessage.newBuilder().setValue(assetIdBs).build(); if (blockingStubSolidity != null) { @@ -689,7 +743,12 @@ public Optional getTransactionsToThis2(byte[] address, public Optional getTransactionById(String txID) { ByteString bsTxid = ByteString.copyFrom(ByteArray.fromHexString(txID)); BytesMessage request = BytesMessage.newBuilder().setValue(bsTxid).build(); - Transaction transaction = blockingStubFull.getTransactionById(request); + Transaction transaction; + if (blockingStubSolidity != null) { + transaction = blockingStubSolidity.getTransactionById(request); + } else { + transaction = blockingStubFull.getTransactionById(request); + } return Optional.ofNullable(transaction); } @@ -740,29 +799,29 @@ public Optional getBlockByLatestNum2(long num) { return Optional.ofNullable(blockList); } - public TransactionExtention updateSetting(Contract.UpdateSettingContract request) { + public TransactionExtention updateSetting(UpdateSettingContract request) { return blockingStubFull.updateSetting(request); } public TransactionExtention updateEnergyLimit( - Contract.UpdateEnergyLimitContract request) { + UpdateEnergyLimitContract request) { return blockingStubFull.updateEnergyLimit(request); } public TransactionExtention clearContractABI( - Contract.ClearABIContract request) { + ClearABIContract request) { return blockingStubFull.clearContractABI(request); } - public TransactionExtention deployContract(Contract.CreateSmartContract request) { + public TransactionExtention deployContract(CreateSmartContract request) { return blockingStubFull.deployContract(request); } - public TransactionExtention triggerContract(Contract.TriggerSmartContract request) { + public TransactionExtention triggerContract(TriggerSmartContract request) { return blockingStubFull.triggerContract(request); } - public TransactionExtention triggerConstantContract(Contract.TriggerSmartContract request) { + public TransactionExtention triggerConstantContract(TriggerSmartContract request) { return blockingStubFull.triggerConstantContract(request); } @@ -773,11 +832,10 @@ public SmartContract getContract(byte[] address) { } public TransactionExtention accountPermissionUpdate( - Contract.AccountPermissionUpdateContract request) { + AccountPermissionUpdateContract request) { return blockingStubFull.accountPermissionUpdate(request); } - public TransactionExtention createShieldedTransaction(PrivateParameters privateParameters) { return blockingStubFull.createShieldedTransaction(privateParameters); } @@ -871,7 +929,7 @@ public DecryptNotesMarked scanAndMarkNoteByIvk(IvkDecryptAndMarkParameters param } } - public TransactionExtention updateBrokerage(Contract.UpdateBrokerageContract request) { + public TransactionExtention updateBrokerage(UpdateBrokerageContract request) { return blockingStubFull.updateBrokerage(request); } @@ -894,4 +952,19 @@ public NumberMessage getBrokerage(byte[] address) { return blockingStubFull.getBrokerageInfo(bytesMessage); } } + + public Optional getTransactionInfoByBlockNum(long blockNum) { + TransactionInfoList transactionInfoList; + NumberMessage.Builder builder = NumberMessage.newBuilder(); + builder.setNum(blockNum); + + if (blockingStubSolidity != null) { + transactionInfoList = blockingStubSolidity.getTransactionInfoByBlockNum(builder.build()); + } else { + transactionInfoList = blockingStubFull.getTransactionInfoByBlockNum(builder.build()); + } + + return Optional.ofNullable(transactionInfoList); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index e81b0a0a6..642146dae 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -16,8 +16,46 @@ import org.apache.commons.lang3.StringUtils; import org.spongycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; -import org.tron.api.GrpcAPI.*; +import org.tron.api.GrpcAPI.AccountNetMessage; +import org.tron.api.GrpcAPI.AccountResourceMessage; +import org.tron.api.GrpcAPI.AddressPrKeyPairMessage; +import org.tron.api.GrpcAPI.AssetIssueList; +import org.tron.api.GrpcAPI.BlockExtention; +import org.tron.api.GrpcAPI.BlockList; +import org.tron.api.GrpcAPI.BlockListExtention; +import org.tron.api.GrpcAPI.BytesMessage; +import org.tron.api.GrpcAPI.DecryptNotes; +import org.tron.api.GrpcAPI.DecryptNotesMarked; +import org.tron.api.GrpcAPI.DelegatedResourceList; +import org.tron.api.GrpcAPI.DiversifierMessage; +import org.tron.api.GrpcAPI.EasyTransferResponse; +import org.tron.api.GrpcAPI.EmptyMessage; +import org.tron.api.GrpcAPI.ExchangeList; +import org.tron.api.GrpcAPI.ExpandedSpendingKeyMessage; +import org.tron.api.GrpcAPI.IncomingViewingKeyDiversifierMessage; +import org.tron.api.GrpcAPI.IncomingViewingKeyMessage; +import org.tron.api.GrpcAPI.IvkDecryptAndMarkParameters; +import org.tron.api.GrpcAPI.IvkDecryptParameters; +import org.tron.api.GrpcAPI.NfParameters; +import org.tron.api.GrpcAPI.NodeList; +import org.tron.api.GrpcAPI.NoteParameters; +import org.tron.api.GrpcAPI.OvkDecryptParameters; +import org.tron.api.GrpcAPI.PaymentAddressMessage; +import org.tron.api.GrpcAPI.PrivateParameters; +import org.tron.api.GrpcAPI.PrivateParametersWithoutAsk; +import org.tron.api.GrpcAPI.ProposalList; +import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.SpendAuthSigParameters; +import org.tron.api.GrpcAPI.SpendResult; +import org.tron.api.GrpcAPI.TransactionApprovedList; +import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; +import org.tron.api.GrpcAPI.TransactionList; +import org.tron.api.GrpcAPI.TransactionListExtention; +import org.tron.api.GrpcAPI.TransactionSignWeight; import org.tron.api.GrpcAPI.TransactionSignWeight.Result.response_code; +import org.tron.api.GrpcAPI.ViewingKeyMessage; +import org.tron.api.GrpcAPI.WitnessList; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; @@ -30,12 +68,62 @@ import org.tron.core.config.Parameter.CommonConstant; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; -import org.tron.keystore.*; -import org.tron.protos.Contract; -import org.tron.protos.Contract.*; -import org.tron.protos.Protocol.*; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.Protocol.Transaction.Result; +import org.tron.keystore.CheckStrength; +import org.tron.keystore.Credentials; +import org.tron.keystore.Wallet; +import org.tron.keystore.WalletFile; +import org.tron.keystore.WalletUtils; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AccountContract.AccountUpdateContract; +import org.tron.protos.contract.AccountContract.SetAccountIdContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; +import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; +import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; +import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.ProposalContract.ProposalApproveContract; +import org.tron.protos.contract.ProposalContract.ProposalCreateContract; +import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; +import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo; +import org.tron.protos.contract.ShieldContract.OutputPointInfo; +import org.tron.protos.contract.ShieldContract.ShieldedTransferContract; +import org.tron.protos.contract.ShieldContract.SpendDescription; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.StorageContract.BuyStorageBytesContract; +import org.tron.protos.contract.StorageContract.BuyStorageContract; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.StorageContract.SellStorageContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.DelegatedResourceAccountIndex; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Key; +import org.tron.protos.Protocol.Permission; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.Protocol.TransactionSign; +import org.tron.protos.Protocol.Witness; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; +import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; import java.io.File; import java.io.IOException; @@ -130,7 +218,7 @@ public static int getRpcVersion() { /** * Creates a new WalletApi with a random ECKey or no ECKey. - * */ + */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -176,7 +264,7 @@ public boolean checkPassword(byte[] passwd) throws CipherException { /** * Creates a Wallet with an existing ECKey. - * */ + */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -312,7 +400,7 @@ private static WalletFile loadWalletFile() throws IOException { /** * load a Wallet from keystore - * */ + */ public static WalletApi loadWalletFromKeystore() throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); @@ -474,7 +562,7 @@ private void showTransactionAfterSign(Transaction transaction) } private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -498,8 +586,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); Any any = transaction.getRawData().getContract(0).getParameter(); - Contract.ShieldedTransferContract shieldedTransferContract = - any.unpack(ShieldedTransferContract.class); + ShieldedTransferContract shieldedTransferContract = any.unpack(ShieldedTransferContract.class); if (shieldedTransferContract.getFromAmount() > 0) { if (wallet == null || !wallet.isLoginState()) { System.out.println("Warning: processShieldedTransaction failed, Please login first !!"); @@ -613,7 +700,7 @@ public boolean sendCoin(byte[] owner, byte[] to, long amount) owner = getAddress(); } - Contract.TransferContract contract = createTransferContract(to, owner, amount); + TransferContract contract = createTransferContract(to, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -629,7 +716,7 @@ public boolean updateAccount(byte[] owner, byte[] accountNameBytes) owner = getAddress(); } - Contract.AccountUpdateContract contract = createAccountUpdateContract(accountNameBytes, owner); + AccountUpdateContract contract = createAccountUpdateContract(accountNameBytes, owner); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -645,7 +732,7 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) owner = getAddress(); } - Contract.SetAccountIdContract contract = createSetAccountIdContract(accountIdBytes, owner); + SetAccountIdContract contract = createSetAccountIdContract(accountIdBytes, owner); Transaction transaction = rpcCli.createTransaction(contract); if (transaction == null || transaction.getRawData().getContractCount() == 0) { return false; @@ -661,7 +748,7 @@ public boolean updateAsset( owner = getAddress(); } - Contract.UpdateAssetContract contract = + UpdateAssetContract contract = createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); @@ -678,8 +765,7 @@ public boolean transferAsset(byte[] owner, byte[] to, byte[] assertName, long am owner = getAddress(); } - Contract.TransferAssetContract contract = - createTransferAssetContract(to, assertName, owner, amount); + TransferAssetContract contract = createTransferAssetContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransferAssetTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -695,7 +781,7 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner = getAddress(); } - Contract.ParticipateAssetIssueContract contract = + ParticipateAssetIssueContract contract = participateAssetIssueContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = @@ -717,7 +803,7 @@ public static boolean broadcastTransaction(Transaction transaction) { return rpcCli.broadcastTransaction(transaction); } - public boolean createAssetIssue(Contract.AssetIssueContract contract) + public boolean createAssetIssue(AssetIssueContract contract) throws CipherException, IOException, CancelException { if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createAssetIssue2(contract); @@ -734,7 +820,7 @@ public boolean createAccount(byte[] owner, byte[] address) owner = getAddress(); } - Contract.AccountCreateContract contract = createAccountCreateContract(owner, address); + AccountCreateContract contract = createAccountCreateContract(owner, address); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createAccount2(contract); return processTransactionExtention(transactionExtention); @@ -756,7 +842,7 @@ public boolean createWitness(byte[] owner, byte[] url) owner = getAddress(); } - Contract.WitnessCreateContract contract = createWitnessCreateContract(owner, url); + WitnessCreateContract contract = createWitnessCreateContract(owner, url); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createWitness2(contract); return processTransactionExtention(transactionExtention); @@ -772,7 +858,7 @@ public boolean updateWitness(byte[] owner, byte[] url) owner = getAddress(); } - Contract.WitnessUpdateContract contract = createWitnessUpdateContract(owner, url); + WitnessUpdateContract contract = createWitnessUpdateContract(owner, url); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.updateWitness2(contract); return processTransactionExtention(transactionExtention); @@ -800,7 +886,7 @@ public boolean voteWitness(byte[] owner, HashMap witness) owner = getAddress(); } - Contract.VoteWitnessContract contract = createVoteWitnessContract(owner, witness); + VoteWitnessContract contract = createVoteWitnessContract(owner, witness); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.voteWitnessAccount2(contract); return processTransactionExtention(transactionExtention); @@ -810,9 +896,8 @@ public boolean voteWitness(byte[] owner, HashMap witness) } } - public static Contract.TransferContract createTransferContract( - byte[] to, byte[] owner, long amount) { - Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); + public static TransferContract createTransferContract(byte[] to, byte[] owner, long amount) { + TransferContract.Builder builder = TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); builder.setToAddress(bsTo); @@ -822,9 +907,9 @@ public static Contract.TransferContract createTransferContract( return builder.build(); } - public static Contract.TransferAssetContract createTransferAssetContract( + public static TransferAssetContract createTransferAssetContract( byte[] to, byte[] assertName, byte[] owner, long amount) { - Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); + TransferAssetContract.Builder builder = TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -836,10 +921,9 @@ public static Contract.TransferAssetContract createTransferAssetContract( return builder.build(); } - public static Contract.ParticipateAssetIssueContract participateAssetIssueContract( + public static ParticipateAssetIssueContract participateAssetIssueContract( byte[] to, byte[] assertName, byte[] owner, long amount) { - Contract.ParticipateAssetIssueContract.Builder builder = - Contract.ParticipateAssetIssueContract.newBuilder(); + ParticipateAssetIssueContract.Builder builder = ParticipateAssetIssueContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -851,9 +935,9 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra return builder.build(); } - public static Contract.AccountUpdateContract createAccountUpdateContract( + public static AccountUpdateContract createAccountUpdateContract( byte[] accountName, byte[] address) { - Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); + AccountUpdateContract.Builder builder = AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); builder.setAccountName(bsAccountName); @@ -862,9 +946,8 @@ public static Contract.AccountUpdateContract createAccountUpdateContract( return builder.build(); } - public static Contract.SetAccountIdContract createSetAccountIdContract( - byte[] accountId, byte[] address) { - Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); + public static SetAccountIdContract createSetAccountIdContract(byte[] accountId, byte[] address) { + SetAccountIdContract.Builder builder = SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); builder.setAccountId(bsAccountId); @@ -873,9 +956,9 @@ public static Contract.SetAccountIdContract createSetAccountIdContract( return builder.build(); } - public static Contract.UpdateAssetContract createUpdateAssetContract( + public static UpdateAssetContract createUpdateAssetContract( byte[] address, byte[] description, byte[] url, long newLimit, long newPublicLimit) { - Contract.UpdateAssetContract.Builder builder = Contract.UpdateAssetContract.newBuilder(); + UpdateAssetContract.Builder builder = UpdateAssetContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); builder.setDescription(ByteString.copyFrom(description)); builder.setUrl(ByteString.copyFrom(url)); @@ -886,42 +969,38 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( return builder.build(); } - public static Contract.AccountCreateContract createAccountCreateContract( - byte[] owner, byte[] address) { - Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); + public static AccountCreateContract createAccountCreateContract(byte[] owner, byte[] address) { + AccountCreateContract.Builder builder = AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); return builder.build(); } - public static Contract.WitnessCreateContract createWitnessCreateContract( - byte[] owner, byte[] url) { - Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); + public static WitnessCreateContract createWitnessCreateContract(byte[] owner, byte[] url) { + WitnessCreateContract.Builder builder = WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); return builder.build(); } - public static Contract.WitnessUpdateContract createWitnessUpdateContract( - byte[] owner, byte[] url) { - Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); + public static WitnessUpdateContract createWitnessUpdateContract(byte[] owner, byte[] url) { + WitnessUpdateContract.Builder builder = WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); return builder.build(); } - public static Contract.VoteWitnessContract createVoteWitnessContract( - byte[] owner, HashMap witness) { - Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); + public static VoteWitnessContract createVoteWitnessContract(byte[] owner, + HashMap witness) { + VoteWitnessContract.Builder builder = VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); - Contract.VoteWitnessContract.Vote.Builder voteBuilder = - Contract.VoteWitnessContract.Vote.newBuilder(); + VoteWitnessContract.Vote.Builder voteBuilder = VoteWitnessContract.Vote.newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -1181,7 +1260,7 @@ public boolean freezeBalance( int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.FreezeBalanceContract contract = + FreezeBalanceContract contract = createFreezeBalanceContract( ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (rpcVersion == 2) { @@ -1195,21 +1274,21 @@ public boolean freezeBalance( public boolean buyStorage(byte[] ownerAddress, long quantity) throws CipherException, IOException, CancelException { - Contract.BuyStorageContract contract = createBuyStorageContract(ownerAddress, quantity); + BuyStorageContract contract = createBuyStorageContract(ownerAddress, quantity); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); } public boolean buyStorageBytes(byte[] ownerAddress, long bytes) throws CipherException, IOException, CancelException { - Contract.BuyStorageBytesContract contract = createBuyStorageBytesContract(ownerAddress, bytes); + BuyStorageBytesContract contract = createBuyStorageBytesContract(ownerAddress, bytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); } public boolean sellStorage(byte[] ownerAddress, long storageBytes) throws CipherException, IOException, CancelException { - Contract.SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); + SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); } @@ -1224,7 +1303,7 @@ private FreezeBalanceContract createFreezeBalanceContract( address = getAddress(); } - Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder(); + FreezeBalanceContract.Builder builder = FreezeBalanceContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder .setOwnerAddress(byteAddress) @@ -1245,7 +1324,7 @@ private BuyStorageContract createBuyStorageContract(byte[] address, long quantit address = getAddress(); } - Contract.BuyStorageContract.Builder builder = Contract.BuyStorageContract.newBuilder(); + BuyStorageContract.Builder builder = BuyStorageContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setQuant(quantity); @@ -1257,8 +1336,7 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo address = getAddress(); } - Contract.BuyStorageBytesContract.Builder builder = - Contract.BuyStorageBytesContract.newBuilder(); + BuyStorageBytesContract.Builder builder = BuyStorageBytesContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1270,7 +1348,7 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora address = getAddress(); } - Contract.SellStorageContract.Builder builder = Contract.SellStorageContract.newBuilder(); + SellStorageContract.Builder builder = SellStorageContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setStorageBytes(storageBytes); @@ -1279,7 +1357,7 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeBalanceContract contract = + UnfreezeBalanceContract contract = createUnfreezeBalanceContract(ownerAddress, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); @@ -1296,8 +1374,8 @@ private UnfreezeBalanceContract createUnfreezeBalanceContract( address = getAddress(); } - Contract.UnfreezeBalanceContract.Builder builder = - Contract.UnfreezeBalanceContract.newBuilder(); + UnfreezeBalanceContract.Builder builder = + UnfreezeBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); @@ -1312,7 +1390,7 @@ private UnfreezeBalanceContract createUnfreezeBalanceContract( public boolean unfreezeAsset(byte[] ownerAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeAssetContract contract = createUnfreezeAssetContract(ownerAddress); + UnfreezeAssetContract contract = createUnfreezeAssetContract(ownerAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1327,7 +1405,8 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { address = getAddress(); } - Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract.newBuilder(); + UnfreezeAssetContract.Builder builder = UnfreezeAssetContract + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1335,9 +1414,11 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { public boolean withdrawBalance(byte[] ownerAddress) throws CipherException, IOException, CancelException { - Contract.WithdrawBalanceContract contract = createWithdrawBalanceContract(ownerAddress); + WithdrawBalanceContract contract = createWithdrawBalanceContract( + ownerAddress); if (rpcVersion == 2) { - TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); + TransactionExtention transactionExtention = rpcCli + .createTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createTransaction(contract); @@ -1350,8 +1431,8 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { address = getAddress(); } - Contract.WithdrawBalanceContract.Builder builder = - Contract.WithdrawBalanceContract.newBuilder(); + WithdrawBalanceContract.Builder builder = + WithdrawBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1366,7 +1447,8 @@ public static Optional getBlockByLimitNext(long start, long end) { return rpcCli.getBlockByLimitNext(start, end); } - public static Optional getBlockByLimitNext2(long start, long end) { + public static Optional getBlockByLimitNext2(long start, + long end) { return rpcCli.getBlockByLimitNext2(start, end); } @@ -1384,7 +1466,8 @@ public boolean createProposal(byte[] owner, HashMap parametersMap) owner = getAddress(); } - Contract.ProposalCreateContract contract = createProposalCreateContract(owner, parametersMap); + ProposalCreateContract contract = createProposalCreateContract(owner, + parametersMap); TransactionExtention transactionExtention = rpcCli.proposalCreate(contract); return processTransactionExtention(transactionExtention); } @@ -1402,8 +1485,9 @@ public static Optional getDelegatedResource( return rpcCli.getDelegatedResource(fromAddress, toAddress); } - public static Optional getDelegatedResourceAccountIndex( - String address) { + public static Optional getDelegatedResourceAccountIndex + ( + String address) { return rpcCli.getDelegatedResourceAccountIndex(address); } @@ -1419,30 +1503,32 @@ public static Optional getChainParameters() { return rpcCli.getChainParameters(); } - public static Contract.ProposalCreateContract createProposalCreateContract( + public static ProposalCreateContract createProposalCreateContract( byte[] owner, HashMap parametersMap) { - Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); + ProposalCreateContract.Builder builder = ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); return builder.build(); } - public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) + public boolean approveProposal(byte[] owner, long id, + boolean is_add_approval) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ProposalApproveContract contract = + ProposalApproveContract contract = createProposalApproveContract(owner, id, is_add_approval); - TransactionExtention transactionExtention = rpcCli.proposalApprove(contract); + TransactionExtention transactionExtention = rpcCli + .proposalApprove(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalApproveContract createProposalApproveContract( + public static ProposalApproveContract createProposalApproveContract( byte[] owner, long id, boolean is_add_approval) { - Contract.ProposalApproveContract.Builder builder = - Contract.ProposalApproveContract.newBuilder(); + ProposalApproveContract.Builder builder = + ProposalApproveContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1455,14 +1541,15 @@ public boolean deleteProposal(byte[] owner, long id) owner = getAddress(); } - Contract.ProposalDeleteContract contract = createProposalDeleteContract(owner, id); - TransactionExtention transactionExtention = rpcCli.proposalDelete(contract); + ProposalDeleteContract contract = createProposalDeleteContract(owner, id); + TransactionExtention transactionExtention = rpcCli + .proposalDelete(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalDeleteContract createProposalDeleteContract( - byte[] owner, long id) { - Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); + public static ProposalDeleteContract createProposalDeleteContract(byte[] owner, long id) { + ProposalDeleteContract.Builder builder = ProposalDeleteContract + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); return builder.build(); @@ -1479,20 +1566,24 @@ public boolean exchangeCreate( owner = getAddress(); } - Contract.ExchangeCreateContract contract = + ExchangeCreateContract contract = createExchangeCreateContract( - owner, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); - TransactionExtention transactionExtention = rpcCli.exchangeCreate(contract); + owner, firstTokenId, firstTokenBalance, secondTokenId, + secondTokenBalance); + TransactionExtention transactionExtention = rpcCli + .exchangeCreate(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeCreateContract createExchangeCreateContract( + + public static ExchangeCreateContract createExchangeCreateContract( byte[] owner, byte[] firstTokenId, long firstTokenBalance, byte[] secondTokenId, long secondTokenBalance) { - Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); + ExchangeCreateContract.Builder builder = ExchangeCreateContract + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setFirstTokenId(ByteString.copyFrom(firstTokenId)) @@ -1502,21 +1593,24 @@ public static Contract.ExchangeCreateContract createExchangeCreateContract( return builder.build(); } - public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, long quant) + public boolean exchangeInject(byte[] owner, long exchangeId, + byte[] tokenId, long quant) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeInjectContract contract = + ExchangeInjectContract contract = createExchangeInjectContract(owner, exchangeId, tokenId, quant); - TransactionExtention transactionExtention = rpcCli.exchangeInject(contract); + TransactionExtention transactionExtention = rpcCli + .exchangeInject(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeInjectContract createExchangeInjectContract( + public static ExchangeInjectContract createExchangeInjectContract( byte[] owner, long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); + ExchangeInjectContract.Builder builder = ExchangeInjectContract + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1525,22 +1619,23 @@ public static Contract.ExchangeInjectContract createExchangeInjectContract( return builder.build(); } - public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, long quant) + public boolean exchangeWithdraw(byte[] owner, long exchangeId, + byte[] tokenId, long quant) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeWithdrawContract contract = + ExchangeWithdrawContract contract = createExchangeWithdrawContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeWithdraw(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( + + public static ExchangeWithdrawContract createExchangeWithdrawContract( byte[] owner, long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeWithdrawContract.Builder builder = - Contract.ExchangeWithdrawContract.newBuilder(); + ExchangeWithdrawContract.Builder builder = ExchangeWithdrawContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1549,23 +1644,21 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( return builder.build(); } - public boolean exchangeTransaction( - byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) - throws CipherException, IOException, CancelException { + public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeTransactionContract contract = + ExchangeTransactionContract contract = createExchangeTransactionContract(owner, exchangeId, tokenId, quant, expected); TransactionExtention transactionExtention = rpcCli.exchangeTransaction(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeTransactionContract createExchangeTransactionContract( + public static ExchangeTransactionContract createExchangeTransactionContract( byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) { - Contract.ExchangeTransactionContract.Builder builder = - Contract.ExchangeTransactionContract.newBuilder(); + ExchangeTransactionContract.Builder builder = ExchangeTransactionContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1619,11 +1712,13 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { JsonElement abiItem = jsonRoot.get(index); boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null - ? abiItem.getAsJsonObject().get("anonymous").getAsBoolean() + ? abiItem.getAsJsonObject().get("anonymous") + .getAsBoolean() : false; boolean constant = abiItem.getAsJsonObject().get("constant") != null - ? abiItem.getAsJsonObject().get("constant").getAsBoolean() + ? abiItem.getAsJsonObject().get("constant") + .getAsBoolean() : false; String name = abiItem.getAsJsonObject().get("name") != null @@ -1631,11 +1726,13 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { : null; JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null - ? abiItem.getAsJsonObject().get("inputs").getAsJsonArray() + ? abiItem.getAsJsonObject().get("inputs") + .getAsJsonArray() : null; JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null - ? abiItem.getAsJsonObject().get("outputs").getAsJsonArray() + ? abiItem.getAsJsonObject().get("outputs") + .getAsJsonArray() : null; String type = abiItem.getAsJsonObject().get("type") != null @@ -1643,11 +1740,13 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { : null; boolean payable = abiItem.getAsJsonObject().get("payable") != null - ? abiItem.getAsJsonObject().get("payable").getAsBoolean() + ? abiItem.getAsJsonObject().get("payable") + .getAsBoolean() : false; String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null - ? abiItem.getAsJsonObject().get("stateMutability").getAsString() + ? abiItem.getAsJsonObject().get("stateMutability") + .getAsString() : null; if (type == null) { System.out.println("No type!"); @@ -1679,7 +1778,9 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { inputIndexed = - Boolean.valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + Boolean.valueOf( + inputItem.getAsJsonObject().get("indexed") + .getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param.newBuilder(); @@ -1704,7 +1805,9 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { outputIndexed = - Boolean.valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + Boolean.valueOf( + outputItem.getAsJsonObject().get("indexed") + .getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param.newBuilder(); @@ -1718,7 +1821,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { entryBuilder.setType(getEntryType(type)); entryBuilder.setPayable(payable); if (stateMutability != null) { - entryBuilder.setStateMutability(getStateMutability(stateMutability)); + entryBuilder.setStateMutability( + getStateMutability(stateMutability)); } abiBuilder.addEntrys(entryBuilder.build()); @@ -1727,31 +1831,29 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { return abiBuilder.build(); } - public static Contract.UpdateSettingContract createUpdateSettingContract( + public static UpdateSettingContract createUpdateSettingContract( byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) { - Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); + UpdateSettingContract.Builder builder = UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setConsumeUserResourcePercent(consumeUserResourcePercent); return builder.build(); } - public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract( + public static UpdateEnergyLimitContract createUpdateEnergyLimitContract( byte[] owner, byte[] contractAddress, long originEnergyLimit) { - Contract.UpdateEnergyLimitContract.Builder builder = - Contract.UpdateEnergyLimitContract.newBuilder(); + UpdateEnergyLimitContract.Builder builder = UpdateEnergyLimitContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); return builder.build(); } - public static Contract.ClearABIContract createClearABIContract( - byte[] owner, byte[] contractAddress) { + public static ClearABIContract createClearABIContract(byte[] owner, byte[] contractAddress) { - Contract.ClearABIContract.Builder builder = Contract.ClearABIContract.newBuilder(); + ClearABIContract.Builder builder = ClearABIContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); @@ -1805,8 +1907,8 @@ public static CreateSmartContract createContractDeployContract( return createSmartContractBuilder.build(); } - private static byte[] replaceLibraryAddress( - String code, String libraryAddressPair, String compilerVersion) { + private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1821,9 +1923,8 @@ private static byte[] replaceLibraryAddress( String addr = cur.substring(lastPosition + 1); String libraryAddressHex; try { - libraryAddressHex = - (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), "US-ASCII")) - .substring(2); + libraryAddressHex = (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), + "US-ASCII")).substring(2); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); // now ignore } @@ -1831,12 +1932,16 @@ private static byte[] replaceLibraryAddress( String beReplaced; if (compilerVersion == null) { // old version - String repeated = new String(new char[40 - libraryName.length() - 2]).replace("\0", "_"); + String repeated = new String( + new char[40 - libraryName.length() - 2]) + .replace("\0", "_"); beReplaced = "__" + libraryName + repeated; } else if (compilerVersion.equalsIgnoreCase("v5")) { // 0.5.4 version String libraryNameKeccak256 = - ByteArray.toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + ByteArray.toHexString( + Hash.sha3(ByteArray.fromString(libraryName))) + .substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1849,14 +1954,14 @@ private static byte[] replaceLibraryAddress( return Hex.decode(code); } - public static Contract.TriggerSmartContract triggerCallContract( + public static TriggerSmartContract triggerCallContract( byte[] address, byte[] contractAddress, long callValue, byte[] data, long tokenValue, String tokenId) { - Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); + TriggerSmartContract.Builder builder = TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setData(ByteString.copyFrom(data)); @@ -1880,28 +1985,28 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { return Hash.sha3omit12(combined); } - public boolean updateSetting( - byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) - throws IOException, CipherException, CancelException { + public boolean updateSetting(byte[] owner, byte[] contractAddress, + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - UpdateSettingContract updateSettingContract = - createUpdateSettingContract(owner, contractAddress, consumeUserResourcePercent); + UpdateSettingContract updateSettingContract = createUpdateSettingContract(owner, + contractAddress, consumeUserResourcePercent); TransactionExtention transactionExtention = rpcCli.updateSetting(updateSettingContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } - return processTransactionExtention(transactionExtention); + return processTransactionExtention( + transactionExtention); } public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long originEnergyLimit) @@ -1910,21 +2015,22 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig owner = getAddress(); } - UpdateEnergyLimitContract updateEnergyLimitContract = - createUpdateEnergyLimitContract(owner, contractAddress, originEnergyLimit); + UpdateEnergyLimitContract updateEnergyLimitContract = createUpdateEnergyLimitContract(owner, + contractAddress, originEnergyLimit); TransactionExtention transactionExtention = rpcCli.updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } - return processTransactionExtention(transactionExtention); + return processTransactionExtention( + transactionExtention); } public boolean clearContractABI(byte[] owner, byte[] contractAddress) @@ -1939,8 +2045,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1985,16 +2091,16 @@ public boolean deployContract( System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = - transactionExtention.getTransaction().getRawData().toBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2030,8 +2136,8 @@ public boolean triggerContract( owner = getAddress(); } - Contract.TriggerSmartContract triggerContract = - triggerCallContract(owner, contractAddress, callValue, data, tokenValue, tokenId); + TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, callValue, + data, tokenValue, tokenId); TransactionExtention transactionExtention; if (isConstant) { transactionExtention = rpcCli.triggerConstantContract(triggerContract); @@ -2042,12 +2148,13 @@ public boolean triggerContract( if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create call trx failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); return false; } - Transaction transaction = transactionExtention.getTransaction(); + Transaction transaction = transactionExtention + .getTransaction(); // for constant if (transaction.getRetCount() != 0 && transactionExtention.getConstantResult(0) != null @@ -2062,8 +2169,8 @@ public boolean triggerContract( TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = - transactionExtention.getTransaction().getRawData().toBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2088,7 +2195,7 @@ public static SmartContract getContract(byte[] address) { public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { - Contract.AccountPermissionUpdateContract contract = + AccountPermissionUpdateContract contract = createAccountPermissionContract(owner, permissionJson); TransactionExtention transactionExtention = rpcCli.accountPermissionUpdate(contract); return processTransactionExtention(transactionExtention); @@ -2133,10 +2240,9 @@ private Permission json2Permission(JSONObject json) { return permissionBuilder.build(); } - public Contract.AccountPermissionUpdateContract createAccountPermissionContract( - byte[] owner, String permissionJson) { - Contract.AccountPermissionUpdateContract.Builder builder = - Contract.AccountPermissionUpdateContract.newBuilder(); + public AccountPermissionUpdateContract createAccountPermissionContract(byte[] owner, + String permissionJson) { + AccountPermissionUpdateContract.Builder builder = AccountPermissionUpdateContract.newBuilder(); JSONObject permissions = JSONObject.parseObject(permissionJson); JSONObject owner_permission = permissions.getJSONObject("owner_permission"); @@ -2205,8 +2311,8 @@ public static Optional GetMerkleTreeVoucherInfo( return Optional.empty(); } - public static Optional scanNoteByIvk( - IvkDecryptParameters ivkDecryptParameters, boolean showErrorMsg) { + public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2222,8 +2328,8 @@ public static Optional scanNoteByIvk( return Optional.empty(); } - public static Optional scanNoteByOvk( - OvkDecryptParameters ovkDecryptParameters, boolean showErrorMsg) { + public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2307,9 +2413,8 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall return processShieldedTransaction(transactionExtention, wallet); } - public static boolean sendShieldedCoinWithoutAsk( - PrivateParametersWithoutAsk privateParameters, byte[] ask, WalletApi wallet) - throws CipherException, IOException, CancelException { + public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); if (transactionExtention == null) { @@ -2433,7 +2538,9 @@ public boolean updateBrokerage(byte[] owner, int brokerage) } UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); - updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); + updateBrokerageContract + .setOwnerAddress(ByteString.copyFrom(owner)) + .setBrokerage(brokerage); TransactionExtention transactionExtention = rpcCli.updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { @@ -2456,4 +2563,9 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + + public static Optional getTransactionInfoByBlockNum(long blockNum) { + return rpcCli.getTransactionInfoByBlockNum(blockNum); + } + } diff --git a/src/main/protos/.travis.yml b/src/main/protos/.travis.yml index 34fa71b3d..c77079aeb 100644 --- a/src/main/protos/.travis.yml +++ b/src/main/protos/.travis.yml @@ -1,24 +1,13 @@ language: ruby - cache: directories: - - $HOME/protobuf - + - "$HOME/protobuf" sudo: false - before_install: - - bash install-protobuf.sh - - bash install-googleapis.sh - -# check what has been installed by listing contents of protobuf folder before_script: - - ls -R $HOME/protobuf - -# let's use protobuf script: - - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto - - - ls -l \ No newline at end of file + - ls -l +notifications: + slack: + secure: lCDOgsHefxu0Sd7J4N9wyo6Y/dvAentdf+q+R6R+SpJUym+tI7h4lPVwDFl9iQabFahLC6bV4lgfv/US0aqjL4QE/c3ZsWBDo2dtkfCzGmkCiU6mGd/bPWLFP9e28OHEFBzUT8ukc5O3GDJREnzscIqGk7Knv3QYQGsoZw563grzVG9VVfqKhsVgrAjhzDwiCoUNSiND09ZNifGrHr7JBQ2u0wrZRR+E9yOQ1K3pNnfq5wAJjbJ0LiuxRlLZoajR7z8VeP8ytet+u2XQE1+fd089XEa/thYxtt0ZvhnyOYk1vmEldDdVakqMqVaa9MKiyLBNnLby1xdSosrM55ZB3tFmXQo6EMIaNKyO07xMUJ/hK0gEppQhHMDlbOW31YmOshVutp2yN4tRF8D3ot1OcYPXsVeXFG+kMUR0PYM71YiqZy8UVgoslKgRgEehvxjuAhbMYXMG878bznco9L64TzubFcjOx0+owR8H0kCXDNf5Xwm+tlc2onWlUDerEWIAignVOT1iR8KK0U+EnhTfw2BWfDV+BkSBvaeFbmnsFgz00d7qSvzbtCJ9KdDj3f4FUjUuORNv5ykMGCv1MrvAvttAZPTNIc/wOFJ9yRC2NhoUfbNILYg7bYhmqKiicnflln03Bh7Ml2mzldzPaRMm7eUBUwYTFbgC83YusX4fxBg= + if: branch = master \ No newline at end of file diff --git a/src/main/protos/English version of TRON Protocol document.md b/src/main/protos/English version of TRON Protocol document.md index 94498fb1c..e282f4b0a 100644 --- a/src/main/protos/English version of TRON Protocol document.md +++ b/src/main/protos/English version of TRON Protocol document.md @@ -7,12 +7,14 @@ + A basic account is able to apply to be a validation node, which has serval parameters, including extra attributes, public key, URL, voting statistics, history performance, etc. There are three different `Account types`: `Normal`, `AssetIssue`, `Contract`. - - enum AccountType {
 - Normal = 0;
 - AssetIssue = 1;
 - Contract = 2; - 
} +```protobuf +enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2;
 +
} +``` + An `Account` contains 7 parameters: `account_name`: the name for this account – e.g. “_BillsAccount_”. @@ -21,21 +23,21 @@ `votes`: received votes on this account – e.g. _{(“0x1b7w…9xj3”,323), (“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_. `asset`: other assets expect TRX in this account – e.g. _{<“WishToken”,66666>,<”Dogie”,233>}_. `latest_operation_time`: the latest operation time of this account. - - // Account
 - message Account {
 - message Vote {
 - bytes vote_address = 1;
 - int64 vote_count = 2;
 }
 - bytes accout_name = 1;
 - AccountType type = 2;
 - bytes address = 3;
 - int64 balance = 4;
 - repeated Vote votes = 5;
 - map asset = 6; - int64 latest_operation_time = 10;
 - } - +```protobuf +// Account
 +message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6; + int64 latest_operation_time = 10;
 +} +``` A `Witness` contains 8 parameters: `address`: the address of this witness – e.g. “_0xu82h…7237_”. `voteCount`: number of received votes on this witness – e.g. _234234_. @@ -45,31 +47,31 @@ `totalMissed`: the number of blocks this witness missed – e.g. _7_. `latestBlockNum`: the latest height of block – e.g. _4522_. `isjobs`: a bool flag. - - // Witness
 - message Witness{
 - bytes address = 1;
 - int64 voteCount = 2;
 - bytes pubKey = 3;
 - string url = 4;
 - int64 totalProduced = 5;
 - int64 totalMissed = 6;
 - int64 latestBlockNum = 7;
 - bool isJobs = 9; - } - +```protobuf +// Witness
 +message Witness{
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7;
 + bool isJobs = 9; +} +``` + A block typically contains transaction data and a blockheader, which is a list of basic block information, including timestamp, signature, parent hash, root of Merkle tree and so on. A block contains `transactions` and a `block_header`. `transactions`: transaction data of this block. `block_header`: one part of a block. - - // block - 
message Block {
 - repeated Transaction transactions = 1;
 - BlockHeader block_header = 2;
 - } - + ```protobuf + // block +message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2;
 +} +``` A `BlockHeader` contains `raw_data` and `witness_signature`. `raw_data`: a `raw` message. `witness_signature`: signature for this block header from witness node. @@ -81,22 +83,22 @@ `number`: the height of this block – e.g. _13534657_. `witness_id`: the id of witness which packed this block – e.g. “_0xu82h…7237_”. `witness_address`: the adresss of the witness packed this block – e.g. “_0xu82h…7237_”. - - message BlockHeader {
 - message raw {
 - int64 timestamp = 1;
 - bytes txTrieRoot = 2;
 - bytes parentHash = 3;
 - //bytes nonce = 5;
 - //bytes difficulty = 6;
 - uint64 number = 7;
 - uint64 witness_id = 8;
 - bytes witness_address = 9;
 - }
 - raw raw_data = 1;
 - bytes witness_signature = 2;
 - } - +```protobuf +message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2;
 +} +``` message `ChainInventory` contains `BlockId` and `remain_num`. `BlockId`: the identification of block. `remain_num`:the remain number of blocks in the synchronizing process. @@ -104,75 +106,75 @@ A `BlockId` contains 2 parameters: `hash`: the hash of block. `number`: the hash and height of block. - - message ChainInventory { - message BlockId { - bytes hash = 1; - int64 number = 2; - } - repeated BlockId ids = 1; - int64 remain_num = 2; - } - +```protobuf +message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; +} +``` + Transaction contracts mainly includes account create contract, account update contract transfer contract, transfer asset contract, vote asset contract, vote witness contract, witness creation contract, witness update contract, asset issue contract, participate asset issue contract and deploy contract. An `AccountCreateContract` contains 3 parameters: `type`: What type this account is – e.g. _0_ stands for `Normal`. `account_name`: the name for this account – e.g.”_Billsaccount_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message AccountCreateContract {
 - AccountType type = 1;
 - bytes account_name = 2;
 - bytes owner_address = 3;
 - } - +```protobuf +message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 +} +``` A `AccountUpdateContract` contains 2 paremeters: `account_name`: the name for this account – e.g.”_Billsaccount_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message AccountUpdateContract { - bytes account_name = 1; - bytes owner_address = 2; - } - +```protobuf +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} +``` A `TransferContract` contains 3 parameters: `amount`: the amount of TRX – e.g. _12534_. `to_address`: the receiver address – e.g. “_0xu82h…7237_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message TransferContract {
 - bytes owner_address = 1;
 - bytes to_address = 2;
 - int64 amount = 3; - } - +```protobuf +message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; +} +``` A `TransferAssetContract` contains 4 parameters: `asset_name`: the name for asset – e.g.”_Billsaccount_”. `to_address`: the receiver address – e.g. “_0xu82h…7237_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. `amount`: the amount of target asset - e.g._12353_. - - message TransferAssetContract {
 - bytes asset_name = 1;
 - bytes owner_address = 2;
 - bytes to_address = 3;
 - int64 amount = 4;
 - } - +```protobuf +message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 +} +``` A `VoteAssetContract` contains 4 parameters: `vote_address`: the voted address of the asset. `support`: is the votes supportive or not – e.g. _true_. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. `count`: the count number of votes- e.g. _2324234_. - +```protobuf message VoteAssetContract {
 bytes owner_address = 1;
 repeated bytes vote_address = 2;
 bool support = 3;
 int32 count = 5;
 } - +``` A `VoteWitnessContract` contains 4 parameters: `vote_address`: the addresses of those who voted. `support`: is the votes supportive or not - e.g. _true_. diff --git a/src/main/protos/README.md b/src/main/protos/README.md index 6a2811911..e409e0407 100644 --- a/src/main/protos/README.md +++ b/src/main/protos/README.md @@ -3,6 +3,8 @@ # The protocol of Tron including api and message. +The protocol is an independent project. You can use it for building other application. + java-tron, wallet-cli and grpc-gateway git subtree pull --prefix src/main/protos/ protocol master diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6172ffcfb..1909f45ba 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -2,9 +2,17 @@ syntax = "proto3"; package protocol; import "core/Tron.proto"; -import "core/Contract.proto"; import "google/api/annotations.proto"; +import "core/contract/asset_issue_contract.proto"; +import "core/contract/account_contract.proto"; +import "core/contract/witness_contract.proto"; +import "core/contract/balance_contract.proto"; +import "core/contract/proposal_contract.proto"; +import "core/contract/storage_contract.proto"; +import "core/contract/exchange_contract.proto"; +import "core/contract/smart_contract.proto"; +import "core/contract/shield_contract.proto"; option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file @@ -687,6 +695,7 @@ service Wallet { rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; + // end for shiededTransaction rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { }; @@ -697,7 +706,12 @@ service Wallet { rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { }; - // end for shiededTransaction + + rpc CreateCommonTransaction (Transaction) returns (TransactionExtention) { + }; + + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } }; service WalletSolidity { @@ -850,6 +864,13 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; + + rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { + } + + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } + }; service WalletExtension { @@ -1066,7 +1087,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) + bytes txid = 3; //transaction id = sha256(transaction.raw_data) } message AddressPrKeyPairMessage { @@ -1076,7 +1097,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; } @@ -1161,7 +1182,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1170,7 +1191,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive bool is_spend = 4; } @@ -1275,7 +1296,10 @@ message ShieldedAddressInfo{ string payment_address = 10; } +<<<<<<< HEAD +======= +>>>>>>> master message NoteParameters { bytes ak = 1; bytes nk = 2; @@ -1288,3 +1312,10 @@ message SpendResult { bool result = 1; string message = 2; } +<<<<<<< HEAD +======= + +message TransactionInfoList { + repeated TransactionInfo transactionInfo = 1; +} +>>>>>>> master diff --git a/src/main/protos/api/zksnark.proto b/src/main/protos/api/zksnark.proto new file mode 100644 index 000000000..91b985651 --- /dev/null +++ b/src/main/protos/api/zksnark.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +package protocol; + +import "core/Tron.proto"; + +option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file +option java_outer_classname = "ZksnarkGrpcAPI"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/api"; + +service TronZksnark { + rpc CheckZksnarkProof (ZksnarkRequest) returns (ZksnarkResponse) { + } +}; + +message ZksnarkRequest { + Transaction transaction = 1; + bytes sighash = 2; + int64 valueBalance = 3; + string txId = 4; +} + +message ZksnarkResponse { + enum Code { + SUCCESS = 0; + FAILED = 1; + } + + Code code = 1; +} + + + + diff --git a/src/main/protos/core/Contract.proto b/src/main/protos/core/Contract.proto deleted file mode 100644 index bcafc904e..000000000 --- a/src/main/protos/core/Contract.proto +++ /dev/null @@ -1,342 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -syntax = "proto3"; - -package protocol; - -option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file -option java_outer_classname = "Contract"; //Specify the class name of the generated Java file -option go_package = "github.com/tronprotocol/grpc-gateway/core"; - -import "core/Tron.proto"; - -message AccountCreateContract { - bytes owner_address = 1; - bytes account_address = 2; - AccountType type = 3; -} - -// Update account name. Account name is not unique now. -message AccountUpdateContract { - bytes account_name = 1; - bytes owner_address = 2; -} - -// Set account id if the account has no id. Account id is unique and case insensitive. -message SetAccountIdContract { - bytes account_id = 1; - bytes owner_address = 2; -} - -message TransferContract { - bytes owner_address = 1; - bytes to_address = 2; - int64 amount = 3; -} - - -message ShieldAddress { - bytes private_address = 1; - bytes public_address = 2; -} - -message TransferAssetContract { - bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. - bytes owner_address = 2; - bytes to_address = 3; - int64 amount = 4; -} - - -message VoteAssetContract { - bytes owner_address = 1; - repeated bytes vote_address = 2; - bool support = 3; - int32 count = 5; -} - -message VoteWitnessContract { - message Vote { - bytes vote_address = 1; - int64 vote_count = 2; - } - bytes owner_address = 1; - repeated Vote votes = 2; - bool support = 3; -} - -message UpdateSettingContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 consume_user_resource_percent = 3; -} - -message UpdateEnergyLimitContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 origin_energy_limit = 3; -} - -message ClearABIContract { - bytes owner_address = 1; - bytes contract_address = 2; -} - -message WitnessCreateContract { - bytes owner_address = 1; - bytes url = 2; -} - -message WitnessUpdateContract { - bytes owner_address = 1; - bytes update_url = 12; -} - -message AssetIssueContract { - string id = 41; - - message FrozenSupply { - int64 frozen_amount = 1; - int64 frozen_days = 2; - } - bytes owner_address = 1; - bytes name = 2; - bytes abbr = 3; - int64 total_supply = 4; - repeated FrozenSupply frozen_supply = 5; - int32 trx_num = 6; - int32 precision = 7; - int32 num = 8; - int64 start_time = 9; - int64 end_time = 10; - int64 order = 11; // useless - int32 vote_score = 16; - bytes description = 20; - bytes url = 21; - int64 free_asset_net_limit = 22; - int64 public_free_asset_net_limit = 23; - int64 public_free_asset_net_usage = 24; - int64 public_latest_free_net_time = 25; -} - -message ParticipateAssetIssueContract { - bytes owner_address = 1; - bytes to_address = 2; - bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. - int64 amount = 4; // the amount of drops -} - - -enum ResourceCode { - BANDWIDTH = 0x00; - ENERGY = 0x01; -} - -message FreezeBalanceContract { - bytes owner_address = 1; - int64 frozen_balance = 2; - int64 frozen_duration = 3; - - ResourceCode resource = 10; - bytes receiver_address = 15; -} - -message UnfreezeBalanceContract { - bytes owner_address = 1; - - ResourceCode resource = 10; - bytes receiver_address = 15; -} - -message UnfreezeAssetContract { - bytes owner_address = 1; -} - -message WithdrawBalanceContract { - bytes owner_address = 1; -} - -message UpdateAssetContract { - bytes owner_address = 1; - bytes description = 2; - bytes url = 3; - int64 new_limit = 4; - int64 new_public_limit = 5; -} - -message ProposalCreateContract { - bytes owner_address = 1; - map parameters = 2; -} - -message ProposalApproveContract { - bytes owner_address = 1; - int64 proposal_id = 2; - bool is_add_approval = 3; // add or remove approval -} - -message ProposalDeleteContract { - bytes owner_address = 1; - int64 proposal_id = 2; -} - -message CreateSmartContract { - bytes owner_address = 1; - SmartContract new_contract = 2; - int64 call_token_value = 3; - int64 token_id = 4; -} - -message TriggerSmartContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 call_value = 3; - bytes data = 4; - int64 call_token_value = 5; - int64 token_id = 6; -} - -message BuyStorageContract { - bytes owner_address = 1; - int64 quant = 2; // trx quantity for buy storage (Sun) -} - -message BuyStorageBytesContract { - bytes owner_address = 1; - int64 bytes = 2; // storage bytes for buy -} - -message SellStorageContract { - bytes owner_address = 1; - int64 storage_bytes = 2; -} - -message ExchangeCreateContract { - bytes owner_address = 1; - bytes first_token_id = 2; - int64 first_token_balance = 3; - bytes second_token_id = 4; - int64 second_token_balance = 5; -} - -message ExchangeInjectContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; -} - -message ExchangeWithdrawContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; -} - -message ExchangeTransactionContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; - int64 expected = 5; -} - -message AccountPermissionUpdateContract { - bytes owner_address = 1; - Permission owner = 2; //Empty is invalidate - Permission witness = 3; //Can be empty - repeated Permission actives = 4; //Empty is invalidate -} - -message UpdateBrokerageContract { - bytes owner_address = 1; - int32 brokerage = 2; // 1 mean 1% -} - -// for shielded transaction - -message AuthenticationPath { - repeated bool value = 1; -} - -message MerklePath { - repeated AuthenticationPath authentication_paths = 1; - repeated bool index = 2; - bytes rt = 3; -} - -message OutputPoint { - bytes hash = 1; - int32 index = 2; -} - -message OutputPointInfo { - repeated OutputPoint out_points = 1; - int32 block_num = 2; -} - -message PedersenHash { - bytes content = 1; -} - -message IncrementalMerkleTree { - PedersenHash left = 1; - PedersenHash right = 2; - repeated PedersenHash parents = 3; -} - -message IncrementalMerkleVoucher { - IncrementalMerkleTree tree = 1; - repeated PedersenHash filled = 2; - IncrementalMerkleTree cursor = 3; - int64 cursor_depth = 4; - bytes rt = 5; - OutputPoint output_point = 10; -} - -message IncrementalMerkleVoucherInfo { - repeated IncrementalMerkleVoucher vouchers = 1; - repeated bytes paths = 2; -} - -message SpendDescription { - bytes value_commitment = 1; - bytes anchor = 2; // merkle root - bytes nullifier = 3; // used for check double spend - bytes rk = 4; // used for check spend authority signature - bytes zkproof = 5; - bytes spend_authority_signature = 6; -} - -message ReceiveDescription { - bytes value_commitment = 1; - bytes note_commitment = 2; - bytes epk = 3; // for Encryption - bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk - bytes c_out = 5; // Encryption for audit, decrypt it with ovk - bytes zkproof = 6; -} - -message ShieldedTransferContract { - bytes transparent_from_address = 1; // transparent address - int64 from_amount = 2; - repeated SpendDescription spend_description = 3; - repeated ReceiveDescription receive_description = 4; - bytes binding_signature = 5; - bytes transparent_to_address = 6; // transparent address - int64 to_amount = 7; // the amount to transparent to_address -} -// end shielded transaction diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index d70672297..0a1b13908 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -259,9 +259,6 @@ message Transaction { ProposalDeleteContract = 18; SetAccountIdContract = 19; CustomContract = 20; - // BuyStorageContract = 21; - // BuyStorageBytesContract = 22; - // SellStorageContract = 23; CreateSmartContract = 30; TriggerSmartContract = 31; GetContract = 32; @@ -509,6 +506,7 @@ message HelloMessage { BlockId headBlockId = 6; } +<<<<<<< HEAD message SmartContract { message ABI { message Entry { @@ -556,6 +554,8 @@ message SmartContract { bytes trx_hash = 10; } +======= +>>>>>>> master message InternalTransaction { // internalTransaction identity, the root InternalTransaction hash // should equals to root transaction id. diff --git a/src/main/protos/core/contract/account_contract.proto b/src/main/protos/core/contract/account_contract.proto new file mode 100644 index 000000000..d3180048f --- /dev/null +++ b/src/main/protos/core/contract/account_contract.proto @@ -0,0 +1,50 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "Contract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message AccountCreateContract { + bytes owner_address = 1; + bytes account_address = 2; + AccountType type = 3; +} + +// Update account name. Account name is not unique now. +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} + +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + +message AccountPermissionUpdateContract { + bytes owner_address = 1; + Permission owner = 2; //Empty is invalidate + Permission witness = 3; //Can be empty + repeated Permission actives = 4; //Empty is invalidate +} + diff --git a/src/main/protos/core/contract/asset_issue_contract.proto b/src/main/protos/core/contract/asset_issue_contract.proto new file mode 100644 index 000000000..f2e515e57 --- /dev/null +++ b/src/main/protos/core/contract/asset_issue_contract.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "AssetIssueContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message AssetIssueContract { + string id = 41; + + message FrozenSupply { + int64 frozen_amount = 1; + int64 frozen_days = 2; + } + bytes owner_address = 1; + bytes name = 2; + bytes abbr = 3; + int64 total_supply = 4; + repeated FrozenSupply frozen_supply = 5; + int32 trx_num = 6; + int32 precision = 7; + int32 num = 8; + int64 start_time = 9; + int64 end_time = 10; + int64 order = 11; // useless + int32 vote_score = 16; + bytes description = 20; + bytes url = 21; + int64 free_asset_net_limit = 22; + int64 public_free_asset_net_limit = 23; + int64 public_free_asset_net_usage = 24; + int64 public_latest_free_net_time = 25; +} + +message TransferAssetContract { + bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. + bytes owner_address = 2; + bytes to_address = 3; + int64 amount = 4; +} + +message UnfreezeAssetContract { + bytes owner_address = 1; +} + +message UpdateAssetContract { + bytes owner_address = 1; + bytes description = 2; + bytes url = 3; + int64 new_limit = 4; + int64 new_public_limit = 5; +} + +message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. + int64 amount = 4; // the amount of drops +} \ No newline at end of file diff --git a/src/main/protos/core/contract/balance_contract.proto b/src/main/protos/core/contract/balance_contract.proto new file mode 100644 index 000000000..1ce29a0cd --- /dev/null +++ b/src/main/protos/core/contract/balance_contract.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "FreezeBalanceContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/contract/common.proto"; + +message FreezeBalanceContract { + bytes owner_address = 1; + int64 frozen_balance = 2; + int64 frozen_duration = 3; + + ResourceCode resource = 10; + bytes receiver_address = 15; +} + + +message UnfreezeBalanceContract { + bytes owner_address = 1; + + ResourceCode resource = 10; + bytes receiver_address = 15; +} + +message WithdrawBalanceContract { + bytes owner_address = 1; +} + +message TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 amount = 3; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/common.proto b/src/main/protos/core/contract/common.proto new file mode 100644 index 000000000..561767186 --- /dev/null +++ b/src/main/protos/core/contract/common.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "common"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +enum ResourceCode { + BANDWIDTH = 0x00; + ENERGY = 0x01; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/exchange_contract.proto b/src/main/protos/core/contract/exchange_contract.proto new file mode 100644 index 000000000..a2f878c57 --- /dev/null +++ b/src/main/protos/core/contract/exchange_contract.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ExchangeCreateContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message ExchangeCreateContract { + bytes owner_address = 1; + bytes first_token_id = 2; + int64 first_token_balance = 3; + bytes second_token_id = 4; + int64 second_token_balance = 5; +} + +message ExchangeInjectContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeWithdrawContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeTransactionContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; + int64 expected = 5; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/proposal_contract.proto b/src/main/protos/core/contract/proposal_contract.proto new file mode 100644 index 000000000..6cd25fab0 --- /dev/null +++ b/src/main/protos/core/contract/proposal_contract.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ProposalApproveContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/shield_contract.proto b/src/main/protos/core/contract/shield_contract.proto new file mode 100644 index 000000000..9119a7aef --- /dev/null +++ b/src/main/protos/core/contract/shield_contract.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ShieldedTransferContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +// for shielded transaction + +message AuthenticationPath { + repeated bool value = 1; +} + +message MerklePath { + repeated AuthenticationPath authentication_paths = 1; + repeated bool index = 2; + bytes rt = 3; +} + +message OutputPoint { + bytes hash = 1; + int32 index = 2; +} + +message OutputPointInfo { + repeated OutputPoint out_points = 1; + int32 block_num = 2; +} + +message PedersenHash { + bytes content = 1; +} + +message IncrementalMerkleTree { + PedersenHash left = 1; + PedersenHash right = 2; + repeated PedersenHash parents = 3; +} + +message IncrementalMerkleVoucher { + IncrementalMerkleTree tree = 1; + repeated PedersenHash filled = 2; + IncrementalMerkleTree cursor = 3; + int64 cursor_depth = 4; + bytes rt = 5; + OutputPoint output_point = 10; +} + +message IncrementalMerkleVoucherInfo { + repeated IncrementalMerkleVoucher vouchers = 1; + repeated bytes paths = 2; +} + +message SpendDescription { + bytes value_commitment = 1; + bytes anchor = 2; // merkle root + bytes nullifier = 3; // used for check double spend + bytes rk = 4; // used for check spend authority signature + bytes zkproof = 5; + bytes spend_authority_signature = 6; +} + +message ReceiveDescription { + bytes value_commitment = 1; + bytes note_commitment = 2; + bytes epk = 3; // for Encryption + bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk + bytes c_out = 5; // Encryption for audit, decrypt it with ovk + bytes zkproof = 6; +} + +message ShieldedTransferContract { + bytes transparent_from_address = 1; // transparent address + int64 from_amount = 2; + repeated SpendDescription spend_description = 3; + repeated ReceiveDescription receive_description = 4; + bytes binding_signature = 5; + bytes transparent_to_address = 6; // transparent address + int64 to_amount = 7; // the amount to transparent to_address +} diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto new file mode 100644 index 000000000..642264679 --- /dev/null +++ b/src/main/protos/core/contract/smart_contract.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "CreateSmartContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + int64 consume_user_resource_percent = 6; + string name = 7; + int64 origin_energy_limit = 8; + bytes code_hash = 9; + bytes trx_hash = 10; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; + int64 call_token_value = 3; + int64 token_id = 4; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; + int64 call_token_value = 5; + int64 token_id = 6; +} + +message ClearABIContract { + bytes owner_address = 1; + bytes contract_address = 2; +} + +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + +message UpdateEnergyLimitContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 origin_energy_limit = 3; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/storage_contract.proto b/src/main/protos/core/contract/storage_contract.proto new file mode 100644 index 000000000..666e6b11f --- /dev/null +++ b/src/main/protos/core/contract/storage_contract.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "BuyStorageBytesContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} + +message UpdateBrokerageContract { + bytes owner_address = 1; + int32 brokerage = 2; // 1 mean 1% +} diff --git a/src/main/protos/core/contract/vote_asset_contract.proto b/src/main/protos/core/contract/vote_asset_contract.proto new file mode 100644 index 000000000..0ca124773 --- /dev/null +++ b/src/main/protos/core/contract/vote_asset_contract.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "VoteAssetContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message VoteAssetContract { + bytes owner_address = 1; + repeated bytes vote_address = 2; + bool support = 3; + int32 count = 5; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/witness_contract.proto b/src/main/protos/core/contract/witness_contract.proto new file mode 100644 index 000000000..acd4292a4 --- /dev/null +++ b/src/main/protos/core/contract/witness_contract.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "WitnessCreateContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message WitnessCreateContract { + bytes owner_address = 1; + bytes url = 2; +} + +message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; +} + +message VoteWitnessContract { + message Vote { + bytes vote_address = 1; + int64 vote_count = 2; + } + bytes owner_address = 1; + repeated Vote votes = 2; + bool support = 3; +} \ No newline at end of file diff --git a/src/main/protos/core/tron/account.proto b/src/main/protos/core/tron/account.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/block.proto b/src/main/protos/core/tron/block.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/delegated_resource.proto b/src/main/protos/core/tron/delegated_resource.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/p2p.proto b/src/main/protos/core/tron/p2p.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/proposal.proto b/src/main/protos/core/tron/proposal.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/transaction.proto b/src/main/protos/core/tron/transaction.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/vote.proto b/src/main/protos/core/tron/vote.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/witness.proto b/src/main/protos/core/tron/witness.proto new file mode 100644 index 000000000..e69de29bb From 2e97b29148fb973f5cab972342a4d9e68c41d034 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 19 Mar 2020 02:00:03 +0800 Subject: [PATCH 293/445] feat: remove conflict --- src/main/protos/api/api.proto | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 1909f45ba..39f76c09d 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1296,10 +1296,6 @@ message ShieldedAddressInfo{ string payment_address = 10; } -<<<<<<< HEAD - -======= ->>>>>>> master message NoteParameters { bytes ak = 1; bytes nk = 2; @@ -1312,10 +1308,7 @@ message SpendResult { bool result = 1; string message = 2; } -<<<<<<< HEAD -======= message TransactionInfoList { repeated TransactionInfo transactionInfo = 1; } ->>>>>>> master From f8997f07b35fc2d670a6244dd127c6787439e887 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 19 Mar 2020 02:05:38 +0800 Subject: [PATCH 294/445] feat: remove conflict --- .../tron/demo/TransactionSignDemoForSM2.java | 6 +-- src/main/protos/core/Tron.proto | 50 ------------------- 2 files changed, 3 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index f31cab3aa..26780c1bb 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -7,9 +7,9 @@ import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; -import org.tron.protos.Contract; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.walletserver.WalletApi; public class TransactionSignDemoForSM2 { @@ -39,8 +39,8 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = - Contract.TransferContract.newBuilder(); + TransferContract.Builder transferContractBuilder = + TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 0a1b13908..d794d3083 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -506,56 +506,6 @@ message HelloMessage { BlockId headBlockId = 6; } -<<<<<<< HEAD -message SmartContract { - message ABI { - message Entry { - enum EntryType { - UnknownEntryType = 0; - Constructor = 1; - Function = 2; - Event = 3; - Fallback = 4; - } - message Param { - bool indexed = 1; - string name = 2; - string type = 3; - // SolidityType type = 3; - } - enum StateMutabilityType { - UnknownMutabilityType = 0; - Pure = 1; - View = 2; - Nonpayable = 3; - Payable = 4; - } - - bool anonymous = 1; - bool constant = 2; - string name = 3; - repeated Param inputs = 4; - repeated Param outputs = 5; - EntryType type = 6; - bool payable = 7; - StateMutabilityType stateMutability = 8; - } - repeated Entry entrys = 1; - } - bytes origin_address = 1; - bytes contract_address = 2; - ABI abi = 3; - bytes bytecode = 4; - int64 call_value = 5; - int64 consume_user_resource_percent = 6; - string name = 7; - int64 origin_energy_limit = 8; - bytes code_hash = 9; - bytes trx_hash = 10; -} - -======= ->>>>>>> master message InternalTransaction { // internalTransaction identity, the root InternalTransaction hash // should equals to root transaction id. From fca2c07731b2599c835124f1d70d58a7f95395f5 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 19 Mar 2020 01:56:26 +0800 Subject: [PATCH 295/445] feat: merge master to develop --- README.md | 4 + .../tron/common/utils/TransactionUtils.java | 59 ++- .../java/org/tron/common/utils/Utils.java | 66 ++- .../org/tron/demo/TransactionSignDemo.java | 5 +- .../tron/demo/TransactionSignDemoForSM2.java | 6 +- src/main/java/org/tron/test/Test.java | 5 +- src/main/java/org/tron/walletcli/Client.java | 58 ++- .../org/tron/walletcli/WalletApiWrapper.java | 27 +- .../org/tron/walletserver/GrpcClient.java | 201 +++++--- .../java/org/tron/walletserver/WalletApi.java | 460 +++++++++++------- src/main/protos/.travis.yml | 23 +- ...glish version of TRON Protocol document.md | 196 ++++---- src/main/protos/README.md | 2 + src/main/protos/api/api.proto | 38 +- src/main/protos/api/zksnark.proto | 33 ++ src/main/protos/core/Contract.proto | 342 ------------- src/main/protos/core/Tron.proto | 50 -- .../core/contract/account_contract.proto | 50 ++ .../core/contract/asset_issue_contract.proto | 60 +++ .../core/contract/balance_contract.proto | 36 ++ src/main/protos/core/contract/common.proto | 12 + .../core/contract/exchange_contract.proto | 37 ++ .../core/contract/proposal_contract.proto | 23 + .../core/contract/shield_contract.proto | 81 +++ .../protos/core/contract/smart_contract.proto | 89 ++++ .../core/contract/storage_contract.proto | 27 + .../core/contract/vote_asset_contract.proto | 14 + .../core/contract/witness_contract.proto | 27 + src/main/protos/core/tron/account.proto | 0 src/main/protos/core/tron/block.proto | 0 .../protos/core/tron/delegated_resource.proto | 0 src/main/protos/core/tron/p2p.proto | 0 src/main/protos/core/tron/proposal.proto | 0 src/main/protos/core/tron/transaction.proto | 0 src/main/protos/core/tron/vote.proto | 0 src/main/protos/core/tron/witness.proto | 0 36 files changed, 1217 insertions(+), 814 deletions(-) create mode 100644 src/main/protos/api/zksnark.proto delete mode 100644 src/main/protos/core/Contract.proto create mode 100644 src/main/protos/core/contract/account_contract.proto create mode 100644 src/main/protos/core/contract/asset_issue_contract.proto create mode 100644 src/main/protos/core/contract/balance_contract.proto create mode 100644 src/main/protos/core/contract/common.proto create mode 100644 src/main/protos/core/contract/exchange_contract.proto create mode 100644 src/main/protos/core/contract/proposal_contract.proto create mode 100644 src/main/protos/core/contract/shield_contract.proto create mode 100644 src/main/protos/core/contract/smart_contract.proto create mode 100644 src/main/protos/core/contract/storage_contract.proto create mode 100644 src/main/protos/core/contract/vote_asset_contract.proto create mode 100644 src/main/protos/core/contract/witness_contract.proto create mode 100644 src/main/protos/core/tron/account.proto create mode 100644 src/main/protos/core/tron/block.proto create mode 100644 src/main/protos/core/tron/delegated_resource.proto create mode 100644 src/main/protos/core/tron/p2p.proto create mode 100644 src/main/protos/core/tron/proposal.proto create mode 100644 src/main/protos/core/tron/transaction.proto create mode 100644 src/main/protos/core/tron/vote.proto create mode 100644 src/main/protos/core/tron/witness.proto diff --git a/README.md b/README.md index f97b29f78..6e976086f 100644 --- a/README.md +++ b/README.md @@ -1045,6 +1045,9 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 **GetTransactionInfoById** > Get transaction-info based on transaction id, generally used to check the result of a smart contract trigger +**GetTransactionInfoByBlockNum** +> Get the list of transaction information in the block based on the block height + ## How to get block information **GetBlock** @@ -1496,6 +1499,7 @@ For more information on a specific command, just type the command on terminal wh GetTransactionApprovedList GetTransactionById GetTransactionCountByBlockNum + GetTransactionInfoByBlockNum GetTransactionInfoById GetTransactionsFromThis GetTransactionsToThis diff --git a/src/main/java/org/tron/common/utils/TransactionUtils.java b/src/main/java/org/tron/common/utils/TransactionUtils.java index 33fac80f8..fd77fe2d4 100644 --- a/src/main/java/org/tron/common/utils/TransactionUtils.java +++ b/src/main/java/org/tron/common/utils/TransactionUtils.java @@ -16,6 +16,10 @@ package org.tron.common.utils; import com.google.protobuf.ByteString; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.ECKey.ECDSASignature; import org.tron.common.crypto.Sha256Sm3Hash; @@ -23,11 +27,22 @@ import org.tron.common.crypto.SignatureInterface; import org.tron.core.exception.CancelException; import org.tron.protos.Protocol.Transaction; - -import java.security.SignatureException; -import java.util.Arrays; -import java.util.List; -import java.util.Scanner; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.VoteAssetContractOuterClass.VoteAssetContract; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; public class TransactionUtils { @@ -52,112 +67,110 @@ public static byte[] getOwner(Transaction.Contract contract) { owner = contract .getParameter() - .unpack(org.tron.protos.Contract.AccountCreateContract.class) - .getOwnerAddress(); + .unpack(AccountCreateContract.class).getOwnerAddress(); break; case TransferContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.TransferContract.class) + .unpack(TransferContract.class) .getOwnerAddress(); break; case TransferAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.TransferAssetContract.class) + .unpack(TransferAssetContract.class) .getOwnerAddress(); break; case VoteAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.VoteAssetContract.class) + .unpack(VoteAssetContract.class) .getOwnerAddress(); break; case VoteWitnessContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.VoteWitnessContract.class) + .unpack(VoteWitnessContract.class) .getOwnerAddress(); break; case WitnessCreateContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.WitnessCreateContract.class) - .getOwnerAddress(); + .unpack(WitnessCreateContract.class).getOwnerAddress(); break; case AssetIssueContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.AssetIssueContract.class) + .unpack(AssetIssueContract.class) .getOwnerAddress(); break; case ParticipateAssetIssueContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.ParticipateAssetIssueContract.class) + .unpack(ParticipateAssetIssueContract.class) .getOwnerAddress(); break; case CreateSmartContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.CreateSmartContract.class) + .unpack(CreateSmartContract.class) .getOwnerAddress(); break; case TriggerSmartContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.TriggerSmartContract.class) + .unpack(TriggerSmartContract.class) .getOwnerAddress(); break; case FreezeBalanceContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.FreezeBalanceContract.class) + .unpack(FreezeBalanceContract.class) .getOwnerAddress(); break; case UnfreezeBalanceContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.UnfreezeBalanceContract.class) + .unpack(UnfreezeBalanceContract.class) .getOwnerAddress(); break; case UnfreezeAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.UnfreezeAssetContract.class) + .unpack(UnfreezeAssetContract.class) .getOwnerAddress(); break; case WithdrawBalanceContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.WithdrawBalanceContract.class) + .unpack(WithdrawBalanceContract.class) .getOwnerAddress(); break; case UpdateAssetContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.UpdateAssetContract.class) + .unpack(UpdateAssetContract.class) .getOwnerAddress(); break; case AccountPermissionUpdateContract: owner = contract .getParameter() - .unpack(org.tron.protos.Contract.AccountPermissionUpdateContract.class) + .unpack(AccountPermissionUpdateContract.class) .getOwnerAddress(); break; default: diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 15592373f..160ce0893 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -23,14 +23,6 @@ import com.google.protobuf.Any; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import org.tron.api.GrpcAPI.*; -import org.tron.common.crypto.Hash; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.keystore.StringUtils; -import org.tron.protos.Contract.*; -import org.tron.protos.Protocol.Block; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; import java.io.Console; import java.io.IOException; @@ -45,6 +37,46 @@ import java.util.List; import java.util.Scanner; +import org.tron.api.GrpcAPI.*; +import org.tron.common.crypto.Hash; +import org.tron.common.crypto.Sha256Sm3Hash; +import org.tron.keystore.StringUtils; +import org.tron.walletserver.WalletApi; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AccountContract.AccountUpdateContract; +import org.tron.protos.contract.AccountContract.SetAccountIdContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; +import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; +import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; +import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.ProposalContract.ProposalApproveContract; +import org.tron.protos.contract.ProposalContract.ProposalCreateContract; +import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; +import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; +import org.tron.protos.contract.VoteAssetContractOuterClass.VoteAssetContract; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; +import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; +import org.tron.protos.contract.ShieldContract.ShieldedTransferContract; + public class Utils { public static final String PERMISSION_ID = "Permission_id"; public static final String VISIBLE = "visible"; @@ -126,6 +158,16 @@ public static String printTransactionList(TransactionList transactionList) { return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } + public static String printTransactionInfoList(TransactionInfoList transactionInfoList) { + JSONArray jsonArray = new JSONArray(); + List infoList = transactionInfoList.getTransactionInfoList(); + infoList.stream() + .forEach( + transactionInfo -> jsonArray.add(formatMessageString(transactionInfo)) + ); + return JsonFormatUtil.formatJson(jsonArray.toJSONString()); + } + public static String printTransactionList(TransactionListExtention transactionList) { JSONArray jsonArray = new JSONArray(); List transactions = transactionList.getTransactionList(); @@ -432,9 +474,9 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean JsonFormat.printToString(proposalDeleteContract, selfType)); break; case SetAccountIdContract: - org.tron.protos.Contract.SetAccountIdContract setAccountIdContract = + SetAccountIdContract setAccountIdContract = contractParameter.unpack( - org.tron.protos.Contract.SetAccountIdContract.class); + SetAccountIdContract.class); contractJson = JSONObject.parseObject( JsonFormat.printToString(setAccountIdContract, selfType)); @@ -505,8 +547,8 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean JsonFormat.printToString(accountPermissionUpdateContract, selfType)); break; case ClearABIContract: - org.tron.protos.Contract.ClearABIContract clearABIContract = - contractParameter.unpack(org.tron.protos.Contract.ClearABIContract.class); + ClearABIContract clearABIContract = + contractParameter.unpack(ClearABIContract.class); contractJson = JSONObject.parseObject( JsonFormat.printToString(clearABIContract, selfType)); diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java index 0aeeebe3b..b787981c7 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ b/src/main/java/org/tron/demo/TransactionSignDemo.java @@ -9,9 +9,9 @@ import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; -import org.tron.protos.Contract; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.walletserver.WalletApi; import java.util.Arrays; @@ -43,8 +43,7 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = - Contract.TransferContract.newBuilder(); + TransferContract.Builder transferContractBuilder = TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); diff --git a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java index f31cab3aa..26780c1bb 100644 --- a/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java +++ b/src/main/java/org/tron/demo/TransactionSignDemoForSM2.java @@ -7,9 +7,9 @@ import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.ByteArray; import org.tron.core.exception.CancelException; -import org.tron.protos.Contract; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.walletserver.WalletApi; public class TransactionSignDemoForSM2 { @@ -39,8 +39,8 @@ public static Transaction createTransaction(byte[] from, byte[] to, long amount) Block newestBlock = WalletApi.getBlock(-1); Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = - Contract.TransferContract.newBuilder(); + TransferContract.Builder transferContractBuilder = + TransferContract.newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(from); diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 470a434fe..946c20a79 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -16,10 +16,9 @@ import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; import org.tron.keystore.WalletUtils; -import org.tron.protos.Contract; -import org.tron.protos.Contract.TransferContract; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.walletserver.WalletApi; import java.io.File; @@ -44,7 +43,7 @@ public static Transaction createTransactionEx(String toAddress, long amount) { for (int i = 0; i < 10; i++) { Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - Contract.TransferContract.Builder transferContractBuilder = Contract.TransferContract + TransferContract.Builder transferContractBuilder = TransferContract .newBuilder(); transferContractBuilder.setAmount(amount); ByteString bsTo = ByteString.copyFrom(ByteArray diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 80d320bc2..fc52a6df0 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1,5 +1,13 @@ package org.tron.walletcli; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Base64.Decoder; +import java.util.Base64.Encoder; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.beust.jcommander.JCommander; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; @@ -31,18 +39,18 @@ import org.tron.core.zen.address.KeyIo; import org.tron.core.zen.address.PaymentAddress; import org.tron.keystore.StringUtils; -import org.tron.protos.Contract.AssetIssueContract; -import org.tron.protos.Protocol.*; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.DelegatedResourceAccountIndex; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; import org.tron.walletserver.WalletApi; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Base64.Decoder; -import java.util.Base64.Encoder; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class Client { @@ -104,6 +112,7 @@ public class Client { "GetTransactionApprovedList", "GetTransactionById", "GetTransactionCountByBlockNum", + "GetTransactionInfoByBlockNum", "GetTransactionInfoById", "GetTransactionsFromThis", "GetTransactionsToThis", @@ -207,6 +216,7 @@ public class Client { "GetTransactionApprovedList", "GetTransactionById", "GetTransactionCountByBlockNum", + "GetTransactionInfoByBlockNum", "GetTransactionInfoById", "GetTransactionsFromThis", "GetTransactionsToThis", @@ -1957,6 +1967,30 @@ private void getBrokerage(String[] parameters) { System.out.println("The brokerage is : " + brokerage.getNum()); } + private void getTransactionInfoByBlockNum(String[] parameters) { + if (parameters.length != 1) { + System.out.println("Too many parameters !!!"); + System.out.println("You need input number with the following syntax:"); + System.out.println("GetTransactionInfoByBlockNum number"); + return; + } + + long blockNum = Long.parseLong(parameters[0]); + Optional result = walletApiWrapper.getTransactionInfoByBlockNum(blockNum); + + if (result.isPresent()) { + TransactionInfoList transactionInfoList = result.get(); + if (transactionInfoList.getTransactionInfoCount() == 0) { + System.out.println("[]"); + } else { + System.out.println(Utils.printTransactionInfoList(transactionInfoList)); + } + } else { + System.out.println("GetTransactionInfoByBlockNum failed !!!"); + } + + } + private String[] getParas(String[] para) { String paras = String.join(" ", para); Pattern pattern = Pattern.compile(" (\\[.*?\\]) "); @@ -3343,6 +3377,10 @@ private void run() { create2(parameters); break; } + case "gettransactioninfobyblocknum": { + getTransactionInfoByBlockNum(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index c844e9345..23ddc33a5 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -20,12 +20,16 @@ import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; import org.tron.keystore.WalletFile; -import org.tron.protos.Contract; -import org.tron.protos.Contract.AssetIssueContract; -import org.tron.protos.Contract.IncrementalMerkleVoucherInfo; -import org.tron.protos.Contract.OutputPoint; -import org.tron.protos.Contract.OutputPointInfo; -import org.tron.protos.Protocol.*; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo; +import org.tron.protos.contract.ShieldContract.OutputPoint; +import org.tron.protos.contract.ShieldContract.OutputPointInfo; import org.tron.walletserver.WalletApi; import java.io.IOException; @@ -205,7 +209,7 @@ public boolean assetIssue(byte[] ownerAddress, String name, String abbrName, lon return false; } - Contract.AssetIssueContract.Builder builder = Contract.AssetIssueContract.newBuilder(); + AssetIssueContract.Builder builder = AssetIssueContract.newBuilder(); if (ownerAddress == null) { ownerAddress = wallet.getAddress(); } @@ -272,8 +276,8 @@ public boolean assetIssue(byte[] ownerAddress, String name, String abbrName, lon String amountStr = frozenSupply.get(daysStr); long amount = Long.parseLong(amountStr); long days = Long.parseLong(daysStr); - Contract.AssetIssueContract.FrozenSupply.Builder frozenSupplyBuilder - = Contract.AssetIssueContract.FrozenSupply.newBuilder(); + AssetIssueContract.FrozenSupply.Builder frozenSupplyBuilder + = AssetIssueContract.FrozenSupply.newBuilder(); frozenSupplyBuilder.setFrozenAmount(amount); frozenSupplyBuilder.setFrozenDays(days); builder.addFrozenSupply(frozenSupplyBuilder.build()); @@ -1209,4 +1213,9 @@ public GrpcAPI.NumberMessage getReward(byte[] ownerAddress) { public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } + + public static Optional getTransactionInfoByBlockNum(long blockNum) { + return WalletApi.getTransactionInfoByBlockNum(blockNum); + } + } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index f04066b8c..32726a127 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -1,5 +1,9 @@ package org.tron.walletserver; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -8,18 +12,61 @@ import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.*; import org.tron.api.GrpcAPI.Return.response_code; +import org.tron.api.GrpcAPI.TransactionApprovedList; +import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; +import org.tron.api.GrpcAPI.TransactionList; +import org.tron.api.GrpcAPI.TransactionListExtention; +import org.tron.api.GrpcAPI.TransactionSignWeight; +import org.tron.api.GrpcAPI.WitnessList; import org.tron.api.WalletExtensionGrpc; import org.tron.api.WalletGrpc; import org.tron.api.WalletSolidityGrpc; import org.tron.common.utils.ByteArray; -import org.tron.protos.Contract; -import org.tron.protos.Contract.IncrementalMerkleVoucherInfo; -import org.tron.protos.Contract.OutputPointInfo; -import org.tron.protos.Protocol.*; - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.DelegatedResourceAccountIndex; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AccountContract.AccountUpdateContract; +import org.tron.protos.contract.AccountContract.SetAccountIdContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; +import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; +import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; +import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.ProposalContract.ProposalApproveContract; +import org.tron.protos.contract.ProposalContract.ProposalCreateContract; +import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; +import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo; +import org.tron.protos.contract.ShieldContract.OutputPointInfo; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.Protocol.TransactionSign; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; +import org.tron.protos.contract.StorageContract.BuyStorageBytesContract; +import org.tron.protos.contract.StorageContract.BuyStorageContract; +import org.tron.protos.contract.StorageContract.SellStorageContract; +import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; +import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; @Slf4j public class GrpcClient { @@ -160,114 +207,114 @@ public EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, byte[] return blockingStubFull.easyTransferAssetByPrivate(builder.build()); } - public Transaction createTransaction(Contract.AccountUpdateContract contract) { + public Transaction createTransaction(AccountUpdateContract contract) { return blockingStubFull.updateAccount(contract); } - public TransactionExtention createTransaction2(Contract.AccountUpdateContract contract) { + public TransactionExtention createTransaction2(AccountUpdateContract contract) { return blockingStubFull.updateAccount2(contract); } - public Transaction createTransaction(Contract.SetAccountIdContract contract) { + public Transaction createTransaction(SetAccountIdContract contract) { return blockingStubFull.setAccountId(contract); } - public Transaction createTransaction(Contract.UpdateAssetContract contract) { + public Transaction createTransaction(UpdateAssetContract contract) { return blockingStubFull.updateAsset(contract); } - public TransactionExtention createTransaction2(Contract.UpdateAssetContract contract) { + public TransactionExtention createTransaction2(UpdateAssetContract contract) { return blockingStubFull.updateAsset2(contract); } - public Transaction createTransaction(Contract.TransferContract contract) { + public Transaction createTransaction(TransferContract contract) { return blockingStubFull.createTransaction(contract); } - public TransactionExtention createTransaction2(Contract.TransferContract contract) { + public TransactionExtention createTransaction2(TransferContract contract) { return blockingStubFull.createTransaction2(contract); } - public Transaction createTransaction(Contract.FreezeBalanceContract contract) { + public Transaction createTransaction(FreezeBalanceContract contract) { return blockingStubFull.freezeBalance(contract); } - public TransactionExtention createTransaction(Contract.BuyStorageContract contract) { + public TransactionExtention createTransaction(BuyStorageContract contract) { return blockingStubFull.buyStorage(contract); } - public TransactionExtention createTransaction(Contract.BuyStorageBytesContract contract) { + public TransactionExtention createTransaction(BuyStorageBytesContract contract) { return blockingStubFull.buyStorageBytes(contract); } - public TransactionExtention createTransaction(Contract.SellStorageContract contract) { + public TransactionExtention createTransaction(SellStorageContract contract) { return blockingStubFull.sellStorage(contract); } - public TransactionExtention createTransaction2(Contract.FreezeBalanceContract contract) { + public TransactionExtention createTransaction2(FreezeBalanceContract contract) { return blockingStubFull.freezeBalance2(contract); } - public Transaction createTransaction(Contract.WithdrawBalanceContract contract) { + public Transaction createTransaction(WithdrawBalanceContract contract) { return blockingStubFull.withdrawBalance(contract); } - public TransactionExtention createTransaction2(Contract.WithdrawBalanceContract contract) { + public TransactionExtention createTransaction2(WithdrawBalanceContract contract) { return blockingStubFull.withdrawBalance2(contract); } - public Transaction createTransaction(Contract.UnfreezeBalanceContract contract) { + public Transaction createTransaction(UnfreezeBalanceContract contract) { return blockingStubFull.unfreezeBalance(contract); } - public TransactionExtention createTransaction2(Contract.UnfreezeBalanceContract contract) { + public TransactionExtention createTransaction2(UnfreezeBalanceContract contract) { return blockingStubFull.unfreezeBalance2(contract); } - public Transaction createTransaction(Contract.UnfreezeAssetContract contract) { + public Transaction createTransaction(UnfreezeAssetContract contract) { return blockingStubFull.unfreezeAsset(contract); } - public TransactionExtention createTransaction2(Contract.UnfreezeAssetContract contract) { + public TransactionExtention createTransaction2(UnfreezeAssetContract contract) { return blockingStubFull.unfreezeAsset2(contract); } - public Transaction createTransferAssetTransaction(Contract.TransferAssetContract contract) { + public Transaction createTransferAssetTransaction(TransferAssetContract contract) { return blockingStubFull.transferAsset(contract); } public TransactionExtention createTransferAssetTransaction2( - Contract.TransferAssetContract contract) { + TransferAssetContract contract) { return blockingStubFull.transferAsset2(contract); } public Transaction createParticipateAssetIssueTransaction( - Contract.ParticipateAssetIssueContract contract) { + ParticipateAssetIssueContract contract) { return blockingStubFull.participateAssetIssue(contract); } public TransactionExtention createParticipateAssetIssueTransaction2( - Contract.ParticipateAssetIssueContract contract) { + ParticipateAssetIssueContract contract) { return blockingStubFull.participateAssetIssue2(contract); } - public Transaction createAssetIssue(Contract.AssetIssueContract contract) { + public Transaction createAssetIssue(AssetIssueContract contract) { return blockingStubFull.createAssetIssue(contract); } - public TransactionExtention createAssetIssue2(Contract.AssetIssueContract contract) { + public TransactionExtention createAssetIssue2(AssetIssueContract contract) { return blockingStubFull.createAssetIssue2(contract); } - public Transaction voteWitnessAccount(Contract.VoteWitnessContract contract) { + public Transaction voteWitnessAccount(VoteWitnessContract contract) { return blockingStubFull.voteWitnessAccount(contract); } - public TransactionExtention voteWitnessAccount2(Contract.VoteWitnessContract contract) { + public TransactionExtention voteWitnessAccount2(VoteWitnessContract contract) { return blockingStubFull.voteWitnessAccount2(contract); } - public TransactionExtention proposalCreate(Contract.ProposalCreateContract contract) { + public TransactionExtention proposalCreate(ProposalCreateContract contract) { return blockingStubFull.proposalCreate(contract); } @@ -296,8 +343,12 @@ public Optional getDelegatedResource(String fromAddress, .setFromAddress(fromAddressBS) .setToAddress(toAddressBS) .build(); - DelegatedResourceList delegatedResource = blockingStubFull - .getDelegatedResource(request); + DelegatedResourceList delegatedResource; + if (blockingStubSolidity != null) { + delegatedResource = blockingStubSolidity.getDelegatedResource(request); + } else { + delegatedResource = blockingStubFull.getDelegatedResource(request); + } return Optional.ofNullable(delegatedResource); } @@ -307,9 +358,12 @@ public Optional getDelegatedResourceAccountIndex( Objects.requireNonNull(WalletApi.decodeFromBase58Check(address))); BytesMessage bytesMessage = BytesMessage.newBuilder().setValue(addressBS).build(); - - DelegatedResourceAccountIndex accountIndex = blockingStubFull - .getDelegatedResourceAccountIndex(bytesMessage); + DelegatedResourceAccountIndex accountIndex; + if (blockingStubSolidity != null) { + accountIndex = blockingStubSolidity.getDelegatedResourceAccountIndex(bytesMessage); + } else { + accountIndex = blockingStubFull.getDelegatedResourceAccountIndex(bytesMessage); + } return Optional.ofNullable(accountIndex); } @@ -346,35 +400,35 @@ public Optional getChainParameters() { return Optional.ofNullable(chainParameters); } - public TransactionExtention proposalApprove(Contract.ProposalApproveContract contract) { + public TransactionExtention proposalApprove(ProposalApproveContract contract) { return blockingStubFull.proposalApprove(contract); } - public TransactionExtention proposalDelete(Contract.ProposalDeleteContract contract) { + public TransactionExtention proposalDelete(ProposalDeleteContract contract) { return blockingStubFull.proposalDelete(contract); } - public TransactionExtention exchangeCreate(Contract.ExchangeCreateContract contract) { + public TransactionExtention exchangeCreate(ExchangeCreateContract contract) { return blockingStubFull.exchangeCreate(contract); } - public TransactionExtention exchangeInject(Contract.ExchangeInjectContract contract) { + public TransactionExtention exchangeInject(ExchangeInjectContract contract) { return blockingStubFull.exchangeInject(contract); } - public TransactionExtention exchangeWithdraw(Contract.ExchangeWithdrawContract contract) { + public TransactionExtention exchangeWithdraw(ExchangeWithdrawContract contract) { return blockingStubFull.exchangeWithdraw(contract); } - public TransactionExtention exchangeTransaction(Contract.ExchangeTransactionContract contract) { + public TransactionExtention exchangeTransaction(ExchangeTransactionContract contract) { return blockingStubFull.exchangeTransaction(contract); } - public Transaction createAccount(Contract.AccountCreateContract contract) { + public Transaction createAccount(AccountCreateContract contract) { return blockingStubFull.createAccount(contract); } - public TransactionExtention createAccount2(Contract.AccountCreateContract contract) { + public TransactionExtention createAccount2(AccountCreateContract contract) { return blockingStubFull.createAccount2(contract); } @@ -386,19 +440,19 @@ public AddressPrKeyPairMessage generateAddress(EmptyMessage emptyMessage) { } } - public Transaction createWitness(Contract.WitnessCreateContract contract) { + public Transaction createWitness(WitnessCreateContract contract) { return blockingStubFull.createWitness(contract); } - public TransactionExtention createWitness2(Contract.WitnessCreateContract contract) { + public TransactionExtention createWitness2(WitnessCreateContract contract) { return blockingStubFull.createWitness2(contract); } - public Transaction updateWitness(Contract.WitnessUpdateContract contract) { + public Transaction updateWitness(WitnessUpdateContract contract) { return blockingStubFull.updateWitness(contract); } - public TransactionExtention updateWitness2(Contract.WitnessUpdateContract contract) { + public TransactionExtention updateWitness2(WitnessUpdateContract contract) { return blockingStubFull.updateWitness2(contract); } @@ -556,7 +610,7 @@ public AccountResourceMessage getAccountResource(byte[] address) { return blockingStubFull.getAccountResource(request); } - public Contract.AssetIssueContract getAssetIssueByName(String assetName) { + public AssetIssueContract getAssetIssueByName(String assetName) { ByteString assetNameBs = ByteString.copyFrom(assetName.getBytes()); BytesMessage request = BytesMessage.newBuilder().setValue(assetNameBs).build(); if (blockingStubSolidity != null) { @@ -578,7 +632,7 @@ public Optional getAssetIssueListByName(String assetName) { } } - public Contract.AssetIssueContract getAssetIssueById(String assetId) { + public AssetIssueContract getAssetIssueById(String assetId) { ByteString assetIdBs = ByteString.copyFrom(assetId.getBytes()); BytesMessage request = BytesMessage.newBuilder().setValue(assetIdBs).build(); if (blockingStubSolidity != null) { @@ -689,7 +743,12 @@ public Optional getTransactionsToThis2(byte[] address, public Optional getTransactionById(String txID) { ByteString bsTxid = ByteString.copyFrom(ByteArray.fromHexString(txID)); BytesMessage request = BytesMessage.newBuilder().setValue(bsTxid).build(); - Transaction transaction = blockingStubFull.getTransactionById(request); + Transaction transaction; + if (blockingStubSolidity != null) { + transaction = blockingStubSolidity.getTransactionById(request); + } else { + transaction = blockingStubFull.getTransactionById(request); + } return Optional.ofNullable(transaction); } @@ -740,29 +799,29 @@ public Optional getBlockByLatestNum2(long num) { return Optional.ofNullable(blockList); } - public TransactionExtention updateSetting(Contract.UpdateSettingContract request) { + public TransactionExtention updateSetting(UpdateSettingContract request) { return blockingStubFull.updateSetting(request); } public TransactionExtention updateEnergyLimit( - Contract.UpdateEnergyLimitContract request) { + UpdateEnergyLimitContract request) { return blockingStubFull.updateEnergyLimit(request); } public TransactionExtention clearContractABI( - Contract.ClearABIContract request) { + ClearABIContract request) { return blockingStubFull.clearContractABI(request); } - public TransactionExtention deployContract(Contract.CreateSmartContract request) { + public TransactionExtention deployContract(CreateSmartContract request) { return blockingStubFull.deployContract(request); } - public TransactionExtention triggerContract(Contract.TriggerSmartContract request) { + public TransactionExtention triggerContract(TriggerSmartContract request) { return blockingStubFull.triggerContract(request); } - public TransactionExtention triggerConstantContract(Contract.TriggerSmartContract request) { + public TransactionExtention triggerConstantContract(TriggerSmartContract request) { return blockingStubFull.triggerConstantContract(request); } @@ -773,11 +832,10 @@ public SmartContract getContract(byte[] address) { } public TransactionExtention accountPermissionUpdate( - Contract.AccountPermissionUpdateContract request) { + AccountPermissionUpdateContract request) { return blockingStubFull.accountPermissionUpdate(request); } - public TransactionExtention createShieldedTransaction(PrivateParameters privateParameters) { return blockingStubFull.createShieldedTransaction(privateParameters); } @@ -871,7 +929,7 @@ public DecryptNotesMarked scanAndMarkNoteByIvk(IvkDecryptAndMarkParameters param } } - public TransactionExtention updateBrokerage(Contract.UpdateBrokerageContract request) { + public TransactionExtention updateBrokerage(UpdateBrokerageContract request) { return blockingStubFull.updateBrokerage(request); } @@ -894,4 +952,19 @@ public NumberMessage getBrokerage(byte[] address) { return blockingStubFull.getBrokerageInfo(bytesMessage); } } + + public Optional getTransactionInfoByBlockNum(long blockNum) { + TransactionInfoList transactionInfoList; + NumberMessage.Builder builder = NumberMessage.newBuilder(); + builder.setNum(blockNum); + + if (blockingStubSolidity != null) { + transactionInfoList = blockingStubSolidity.getTransactionInfoByBlockNum(builder.build()); + } else { + transactionInfoList = blockingStubFull.getTransactionInfoByBlockNum(builder.build()); + } + + return Optional.ofNullable(transactionInfoList); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index e81b0a0a6..642146dae 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -16,8 +16,46 @@ import org.apache.commons.lang3.StringUtils; import org.spongycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; -import org.tron.api.GrpcAPI.*; +import org.tron.api.GrpcAPI.AccountNetMessage; +import org.tron.api.GrpcAPI.AccountResourceMessage; +import org.tron.api.GrpcAPI.AddressPrKeyPairMessage; +import org.tron.api.GrpcAPI.AssetIssueList; +import org.tron.api.GrpcAPI.BlockExtention; +import org.tron.api.GrpcAPI.BlockList; +import org.tron.api.GrpcAPI.BlockListExtention; +import org.tron.api.GrpcAPI.BytesMessage; +import org.tron.api.GrpcAPI.DecryptNotes; +import org.tron.api.GrpcAPI.DecryptNotesMarked; +import org.tron.api.GrpcAPI.DelegatedResourceList; +import org.tron.api.GrpcAPI.DiversifierMessage; +import org.tron.api.GrpcAPI.EasyTransferResponse; +import org.tron.api.GrpcAPI.EmptyMessage; +import org.tron.api.GrpcAPI.ExchangeList; +import org.tron.api.GrpcAPI.ExpandedSpendingKeyMessage; +import org.tron.api.GrpcAPI.IncomingViewingKeyDiversifierMessage; +import org.tron.api.GrpcAPI.IncomingViewingKeyMessage; +import org.tron.api.GrpcAPI.IvkDecryptAndMarkParameters; +import org.tron.api.GrpcAPI.IvkDecryptParameters; +import org.tron.api.GrpcAPI.NfParameters; +import org.tron.api.GrpcAPI.NodeList; +import org.tron.api.GrpcAPI.NoteParameters; +import org.tron.api.GrpcAPI.OvkDecryptParameters; +import org.tron.api.GrpcAPI.PaymentAddressMessage; +import org.tron.api.GrpcAPI.PrivateParameters; +import org.tron.api.GrpcAPI.PrivateParametersWithoutAsk; +import org.tron.api.GrpcAPI.ProposalList; +import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.SpendAuthSigParameters; +import org.tron.api.GrpcAPI.SpendResult; +import org.tron.api.GrpcAPI.TransactionApprovedList; +import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.api.GrpcAPI.TransactionInfoList; +import org.tron.api.GrpcAPI.TransactionList; +import org.tron.api.GrpcAPI.TransactionListExtention; +import org.tron.api.GrpcAPI.TransactionSignWeight; import org.tron.api.GrpcAPI.TransactionSignWeight.Result.response_code; +import org.tron.api.GrpcAPI.ViewingKeyMessage; +import org.tron.api.GrpcAPI.WitnessList; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; @@ -30,12 +68,62 @@ import org.tron.core.config.Parameter.CommonConstant; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; -import org.tron.keystore.*; -import org.tron.protos.Contract; -import org.tron.protos.Contract.*; -import org.tron.protos.Protocol.*; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.Protocol.Transaction.Result; +import org.tron.keystore.CheckStrength; +import org.tron.keystore.Credentials; +import org.tron.keystore.Wallet; +import org.tron.keystore.WalletFile; +import org.tron.keystore.WalletUtils; +import org.tron.protos.contract.AccountContract.AccountCreateContract; +import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; +import org.tron.protos.contract.AccountContract.AccountUpdateContract; +import org.tron.protos.contract.AccountContract.SetAccountIdContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; +import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; +import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; +import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.ProposalContract.ProposalApproveContract; +import org.tron.protos.contract.ProposalContract.ProposalCreateContract; +import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; +import org.tron.protos.contract.ShieldContract.IncrementalMerkleVoucherInfo; +import org.tron.protos.contract.ShieldContract.OutputPointInfo; +import org.tron.protos.contract.ShieldContract.ShieldedTransferContract; +import org.tron.protos.contract.ShieldContract.SpendDescription; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.StorageContract.BuyStorageBytesContract; +import org.tron.protos.contract.StorageContract.BuyStorageContract; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; +import org.tron.protos.contract.StorageContract.SellStorageContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.DelegatedResourceAccountIndex; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Key; +import org.tron.protos.Protocol.Permission; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.Protocol.TransactionSign; +import org.tron.protos.Protocol.Witness; +import org.tron.protos.contract.WitnessContract.VoteWitnessContract; +import org.tron.protos.contract.WitnessContract.WitnessCreateContract; +import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; import java.io.File; import java.io.IOException; @@ -130,7 +218,7 @@ public static int getRpcVersion() { /** * Creates a new WalletApi with a random ECKey or no ECKey. - * */ + */ public static WalletFile CreateWalletFile(byte[] password) throws CipherException { WalletFile walletFile = null; if (isEckey) { @@ -176,7 +264,7 @@ public boolean checkPassword(byte[] passwd) throws CipherException { /** * Creates a Wallet with an existing ECKey. - * */ + */ public WalletApi(WalletFile walletFile) { if (this.walletFile.isEmpty()) { this.walletFile.add(walletFile); @@ -312,7 +400,7 @@ private static WalletFile loadWalletFile() throws IOException { /** * load a Wallet from keystore - * */ + */ public static WalletApi loadWalletFromKeystore() throws IOException { WalletFile walletFile = loadWalletFile(); WalletApi walletApi = new WalletApi(walletFile); @@ -474,7 +562,7 @@ private void showTransactionAfterSign(Transaction transaction) } private static boolean processShieldedTransaction(TransactionExtention transactionExtention, - WalletApi wallet) + WalletApi wallet) throws IOException, CipherException, CancelException { if (transactionExtention == null) { return false; @@ -498,8 +586,7 @@ private static boolean processShieldedTransaction(TransactionExtention transacti System.out.println(Utils.printTransactionExceptId(transactionExtention.getTransaction())); Any any = transaction.getRawData().getContract(0).getParameter(); - Contract.ShieldedTransferContract shieldedTransferContract = - any.unpack(ShieldedTransferContract.class); + ShieldedTransferContract shieldedTransferContract = any.unpack(ShieldedTransferContract.class); if (shieldedTransferContract.getFromAmount() > 0) { if (wallet == null || !wallet.isLoginState()) { System.out.println("Warning: processShieldedTransaction failed, Please login first !!"); @@ -613,7 +700,7 @@ public boolean sendCoin(byte[] owner, byte[] to, long amount) owner = getAddress(); } - Contract.TransferContract contract = createTransferContract(to, owner, amount); + TransferContract contract = createTransferContract(to, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -629,7 +716,7 @@ public boolean updateAccount(byte[] owner, byte[] accountNameBytes) owner = getAddress(); } - Contract.AccountUpdateContract contract = createAccountUpdateContract(accountNameBytes, owner); + AccountUpdateContract contract = createAccountUpdateContract(accountNameBytes, owner); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -645,7 +732,7 @@ public boolean setAccountId(byte[] owner, byte[] accountIdBytes) owner = getAddress(); } - Contract.SetAccountIdContract contract = createSetAccountIdContract(accountIdBytes, owner); + SetAccountIdContract contract = createSetAccountIdContract(accountIdBytes, owner); Transaction transaction = rpcCli.createTransaction(contract); if (transaction == null || transaction.getRawData().getContractCount() == 0) { return false; @@ -661,7 +748,7 @@ public boolean updateAsset( owner = getAddress(); } - Contract.UpdateAssetContract contract = + UpdateAssetContract contract = createUpdateAssetContract(owner, description, url, newLimit, newPublicLimit); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); @@ -678,8 +765,7 @@ public boolean transferAsset(byte[] owner, byte[] to, byte[] assertName, long am owner = getAddress(); } - Contract.TransferAssetContract contract = - createTransferAssetContract(to, assertName, owner, amount); + TransferAssetContract contract = createTransferAssetContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransferAssetTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -695,7 +781,7 @@ public boolean participateAssetIssue(byte[] owner, byte[] to, byte[] assertName, owner = getAddress(); } - Contract.ParticipateAssetIssueContract contract = + ParticipateAssetIssueContract contract = participateAssetIssueContract(to, assertName, owner, amount); if (rpcVersion == 2) { TransactionExtention transactionExtention = @@ -717,7 +803,7 @@ public static boolean broadcastTransaction(Transaction transaction) { return rpcCli.broadcastTransaction(transaction); } - public boolean createAssetIssue(Contract.AssetIssueContract contract) + public boolean createAssetIssue(AssetIssueContract contract) throws CipherException, IOException, CancelException { if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createAssetIssue2(contract); @@ -734,7 +820,7 @@ public boolean createAccount(byte[] owner, byte[] address) owner = getAddress(); } - Contract.AccountCreateContract contract = createAccountCreateContract(owner, address); + AccountCreateContract contract = createAccountCreateContract(owner, address); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createAccount2(contract); return processTransactionExtention(transactionExtention); @@ -756,7 +842,7 @@ public boolean createWitness(byte[] owner, byte[] url) owner = getAddress(); } - Contract.WitnessCreateContract contract = createWitnessCreateContract(owner, url); + WitnessCreateContract contract = createWitnessCreateContract(owner, url); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createWitness2(contract); return processTransactionExtention(transactionExtention); @@ -772,7 +858,7 @@ public boolean updateWitness(byte[] owner, byte[] url) owner = getAddress(); } - Contract.WitnessUpdateContract contract = createWitnessUpdateContract(owner, url); + WitnessUpdateContract contract = createWitnessUpdateContract(owner, url); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.updateWitness2(contract); return processTransactionExtention(transactionExtention); @@ -800,7 +886,7 @@ public boolean voteWitness(byte[] owner, HashMap witness) owner = getAddress(); } - Contract.VoteWitnessContract contract = createVoteWitnessContract(owner, witness); + VoteWitnessContract contract = createVoteWitnessContract(owner, witness); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.voteWitnessAccount2(contract); return processTransactionExtention(transactionExtention); @@ -810,9 +896,8 @@ public boolean voteWitness(byte[] owner, HashMap witness) } } - public static Contract.TransferContract createTransferContract( - byte[] to, byte[] owner, long amount) { - Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder(); + public static TransferContract createTransferContract(byte[] to, byte[] owner, long amount) { + TransferContract.Builder builder = TransferContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsOwner = ByteString.copyFrom(owner); builder.setToAddress(bsTo); @@ -822,9 +907,9 @@ public static Contract.TransferContract createTransferContract( return builder.build(); } - public static Contract.TransferAssetContract createTransferAssetContract( + public static TransferAssetContract createTransferAssetContract( byte[] to, byte[] assertName, byte[] owner, long amount) { - Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder(); + TransferAssetContract.Builder builder = TransferAssetContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -836,10 +921,9 @@ public static Contract.TransferAssetContract createTransferAssetContract( return builder.build(); } - public static Contract.ParticipateAssetIssueContract participateAssetIssueContract( + public static ParticipateAssetIssueContract participateAssetIssueContract( byte[] to, byte[] assertName, byte[] owner, long amount) { - Contract.ParticipateAssetIssueContract.Builder builder = - Contract.ParticipateAssetIssueContract.newBuilder(); + ParticipateAssetIssueContract.Builder builder = ParticipateAssetIssueContract.newBuilder(); ByteString bsTo = ByteString.copyFrom(to); ByteString bsName = ByteString.copyFrom(assertName); ByteString bsOwner = ByteString.copyFrom(owner); @@ -851,9 +935,9 @@ public static Contract.ParticipateAssetIssueContract participateAssetIssueContra return builder.build(); } - public static Contract.AccountUpdateContract createAccountUpdateContract( + public static AccountUpdateContract createAccountUpdateContract( byte[] accountName, byte[] address) { - Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder(); + AccountUpdateContract.Builder builder = AccountUpdateContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); ByteString bsAccountName = ByteString.copyFrom(accountName); builder.setAccountName(bsAccountName); @@ -862,9 +946,8 @@ public static Contract.AccountUpdateContract createAccountUpdateContract( return builder.build(); } - public static Contract.SetAccountIdContract createSetAccountIdContract( - byte[] accountId, byte[] address) { - Contract.SetAccountIdContract.Builder builder = Contract.SetAccountIdContract.newBuilder(); + public static SetAccountIdContract createSetAccountIdContract(byte[] accountId, byte[] address) { + SetAccountIdContract.Builder builder = SetAccountIdContract.newBuilder(); ByteString bsAddress = ByteString.copyFrom(address); ByteString bsAccountId = ByteString.copyFrom(accountId); builder.setAccountId(bsAccountId); @@ -873,9 +956,9 @@ public static Contract.SetAccountIdContract createSetAccountIdContract( return builder.build(); } - public static Contract.UpdateAssetContract createUpdateAssetContract( + public static UpdateAssetContract createUpdateAssetContract( byte[] address, byte[] description, byte[] url, long newLimit, long newPublicLimit) { - Contract.UpdateAssetContract.Builder builder = Contract.UpdateAssetContract.newBuilder(); + UpdateAssetContract.Builder builder = UpdateAssetContract.newBuilder(); ByteString basAddreess = ByteString.copyFrom(address); builder.setDescription(ByteString.copyFrom(description)); builder.setUrl(ByteString.copyFrom(url)); @@ -886,42 +969,38 @@ public static Contract.UpdateAssetContract createUpdateAssetContract( return builder.build(); } - public static Contract.AccountCreateContract createAccountCreateContract( - byte[] owner, byte[] address) { - Contract.AccountCreateContract.Builder builder = Contract.AccountCreateContract.newBuilder(); + public static AccountCreateContract createAccountCreateContract(byte[] owner, byte[] address) { + AccountCreateContract.Builder builder = AccountCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setAccountAddress(ByteString.copyFrom(address)); return builder.build(); } - public static Contract.WitnessCreateContract createWitnessCreateContract( - byte[] owner, byte[] url) { - Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder(); + public static WitnessCreateContract createWitnessCreateContract(byte[] owner, byte[] url) { + WitnessCreateContract.Builder builder = WitnessCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUrl(ByteString.copyFrom(url)); return builder.build(); } - public static Contract.WitnessUpdateContract createWitnessUpdateContract( - byte[] owner, byte[] url) { - Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder(); + public static WitnessUpdateContract createWitnessUpdateContract(byte[] owner, byte[] url) { + WitnessUpdateContract.Builder builder = WitnessUpdateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setUpdateUrl(ByteString.copyFrom(url)); return builder.build(); } - public static Contract.VoteWitnessContract createVoteWitnessContract( - byte[] owner, HashMap witness) { - Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder(); + public static VoteWitnessContract createVoteWitnessContract(byte[] owner, + HashMap witness) { + VoteWitnessContract.Builder builder = VoteWitnessContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); for (String addressBase58 : witness.keySet()) { String value = witness.get(addressBase58); long count = Long.parseLong(value); - Contract.VoteWitnessContract.Vote.Builder voteBuilder = - Contract.VoteWitnessContract.Vote.newBuilder(); + VoteWitnessContract.Vote.Builder voteBuilder = VoteWitnessContract.Vote.newBuilder(); byte[] address = WalletApi.decodeFromBase58Check(addressBase58); if (address == null) { continue; @@ -1181,7 +1260,7 @@ public boolean freezeBalance( int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.FreezeBalanceContract contract = + FreezeBalanceContract contract = createFreezeBalanceContract( ownerAddress, frozen_balance, frozen_duration, resourceCode, receiverAddress); if (rpcVersion == 2) { @@ -1195,21 +1274,21 @@ public boolean freezeBalance( public boolean buyStorage(byte[] ownerAddress, long quantity) throws CipherException, IOException, CancelException { - Contract.BuyStorageContract contract = createBuyStorageContract(ownerAddress, quantity); + BuyStorageContract contract = createBuyStorageContract(ownerAddress, quantity); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); } public boolean buyStorageBytes(byte[] ownerAddress, long bytes) throws CipherException, IOException, CancelException { - Contract.BuyStorageBytesContract contract = createBuyStorageBytesContract(ownerAddress, bytes); + BuyStorageBytesContract contract = createBuyStorageBytesContract(ownerAddress, bytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); } public boolean sellStorage(byte[] ownerAddress, long storageBytes) throws CipherException, IOException, CancelException { - Contract.SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); + SellStorageContract contract = createSellStorageContract(ownerAddress, storageBytes); TransactionExtention transactionExtention = rpcCli.createTransaction(contract); return processTransactionExtention(transactionExtention); } @@ -1224,7 +1303,7 @@ private FreezeBalanceContract createFreezeBalanceContract( address = getAddress(); } - Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder(); + FreezeBalanceContract.Builder builder = FreezeBalanceContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder .setOwnerAddress(byteAddress) @@ -1245,7 +1324,7 @@ private BuyStorageContract createBuyStorageContract(byte[] address, long quantit address = getAddress(); } - Contract.BuyStorageContract.Builder builder = Contract.BuyStorageContract.newBuilder(); + BuyStorageContract.Builder builder = BuyStorageContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setQuant(quantity); @@ -1257,8 +1336,7 @@ private BuyStorageBytesContract createBuyStorageBytesContract(byte[] address, lo address = getAddress(); } - Contract.BuyStorageBytesContract.Builder builder = - Contract.BuyStorageBytesContract.newBuilder(); + BuyStorageBytesContract.Builder builder = BuyStorageBytesContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setBytes(bytes); @@ -1270,7 +1348,7 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora address = getAddress(); } - Contract.SellStorageContract.Builder builder = Contract.SellStorageContract.newBuilder(); + SellStorageContract.Builder builder = SellStorageContract.newBuilder(); ByteString byteAddress = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddress).setStorageBytes(storageBytes); @@ -1279,7 +1357,7 @@ private SellStorageContract createSellStorageContract(byte[] address, long stora public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] receiverAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeBalanceContract contract = + UnfreezeBalanceContract contract = createUnfreezeBalanceContract(ownerAddress, resourceCode, receiverAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); @@ -1296,8 +1374,8 @@ private UnfreezeBalanceContract createUnfreezeBalanceContract( address = getAddress(); } - Contract.UnfreezeBalanceContract.Builder builder = - Contract.UnfreezeBalanceContract.newBuilder(); + UnfreezeBalanceContract.Builder builder = + UnfreezeBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); @@ -1312,7 +1390,7 @@ private UnfreezeBalanceContract createUnfreezeBalanceContract( public boolean unfreezeAsset(byte[] ownerAddress) throws CipherException, IOException, CancelException { - Contract.UnfreezeAssetContract contract = createUnfreezeAssetContract(ownerAddress); + UnfreezeAssetContract contract = createUnfreezeAssetContract(ownerAddress); if (rpcVersion == 2) { TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); return processTransactionExtention(transactionExtention); @@ -1327,7 +1405,8 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { address = getAddress(); } - Contract.UnfreezeAssetContract.Builder builder = Contract.UnfreezeAssetContract.newBuilder(); + UnfreezeAssetContract.Builder builder = UnfreezeAssetContract + .newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); return builder.build(); @@ -1335,9 +1414,11 @@ private UnfreezeAssetContract createUnfreezeAssetContract(byte[] address) { public boolean withdrawBalance(byte[] ownerAddress) throws CipherException, IOException, CancelException { - Contract.WithdrawBalanceContract contract = createWithdrawBalanceContract(ownerAddress); + WithdrawBalanceContract contract = createWithdrawBalanceContract( + ownerAddress); if (rpcVersion == 2) { - TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); + TransactionExtention transactionExtention = rpcCli + .createTransaction2(contract); return processTransactionExtention(transactionExtention); } else { Transaction transaction = rpcCli.createTransaction(contract); @@ -1350,8 +1431,8 @@ private WithdrawBalanceContract createWithdrawBalanceContract(byte[] address) { address = getAddress(); } - Contract.WithdrawBalanceContract.Builder builder = - Contract.WithdrawBalanceContract.newBuilder(); + WithdrawBalanceContract.Builder builder = + WithdrawBalanceContract.newBuilder(); ByteString byteAddreess = ByteString.copyFrom(address); builder.setOwnerAddress(byteAddreess); @@ -1366,7 +1447,8 @@ public static Optional getBlockByLimitNext(long start, long end) { return rpcCli.getBlockByLimitNext(start, end); } - public static Optional getBlockByLimitNext2(long start, long end) { + public static Optional getBlockByLimitNext2(long start, + long end) { return rpcCli.getBlockByLimitNext2(start, end); } @@ -1384,7 +1466,8 @@ public boolean createProposal(byte[] owner, HashMap parametersMap) owner = getAddress(); } - Contract.ProposalCreateContract contract = createProposalCreateContract(owner, parametersMap); + ProposalCreateContract contract = createProposalCreateContract(owner, + parametersMap); TransactionExtention transactionExtention = rpcCli.proposalCreate(contract); return processTransactionExtention(transactionExtention); } @@ -1402,8 +1485,9 @@ public static Optional getDelegatedResource( return rpcCli.getDelegatedResource(fromAddress, toAddress); } - public static Optional getDelegatedResourceAccountIndex( - String address) { + public static Optional getDelegatedResourceAccountIndex + ( + String address) { return rpcCli.getDelegatedResourceAccountIndex(address); } @@ -1419,30 +1503,32 @@ public static Optional getChainParameters() { return rpcCli.getChainParameters(); } - public static Contract.ProposalCreateContract createProposalCreateContract( + public static ProposalCreateContract createProposalCreateContract( byte[] owner, HashMap parametersMap) { - Contract.ProposalCreateContract.Builder builder = Contract.ProposalCreateContract.newBuilder(); + ProposalCreateContract.Builder builder = ProposalCreateContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.putAllParameters(parametersMap); return builder.build(); } - public boolean approveProposal(byte[] owner, long id, boolean is_add_approval) + public boolean approveProposal(byte[] owner, long id, + boolean is_add_approval) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ProposalApproveContract contract = + ProposalApproveContract contract = createProposalApproveContract(owner, id, is_add_approval); - TransactionExtention transactionExtention = rpcCli.proposalApprove(contract); + TransactionExtention transactionExtention = rpcCli + .proposalApprove(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalApproveContract createProposalApproveContract( + public static ProposalApproveContract createProposalApproveContract( byte[] owner, long id, boolean is_add_approval) { - Contract.ProposalApproveContract.Builder builder = - Contract.ProposalApproveContract.newBuilder(); + ProposalApproveContract.Builder builder = + ProposalApproveContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); builder.setIsAddApproval(is_add_approval); @@ -1455,14 +1541,15 @@ public boolean deleteProposal(byte[] owner, long id) owner = getAddress(); } - Contract.ProposalDeleteContract contract = createProposalDeleteContract(owner, id); - TransactionExtention transactionExtention = rpcCli.proposalDelete(contract); + ProposalDeleteContract contract = createProposalDeleteContract(owner, id); + TransactionExtention transactionExtention = rpcCli + .proposalDelete(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ProposalDeleteContract createProposalDeleteContract( - byte[] owner, long id) { - Contract.ProposalDeleteContract.Builder builder = Contract.ProposalDeleteContract.newBuilder(); + public static ProposalDeleteContract createProposalDeleteContract(byte[] owner, long id) { + ProposalDeleteContract.Builder builder = ProposalDeleteContract + .newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setProposalId(id); return builder.build(); @@ -1479,20 +1566,24 @@ public boolean exchangeCreate( owner = getAddress(); } - Contract.ExchangeCreateContract contract = + ExchangeCreateContract contract = createExchangeCreateContract( - owner, firstTokenId, firstTokenBalance, secondTokenId, secondTokenBalance); - TransactionExtention transactionExtention = rpcCli.exchangeCreate(contract); + owner, firstTokenId, firstTokenBalance, secondTokenId, + secondTokenBalance); + TransactionExtention transactionExtention = rpcCli + .exchangeCreate(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeCreateContract createExchangeCreateContract( + + public static ExchangeCreateContract createExchangeCreateContract( byte[] owner, byte[] firstTokenId, long firstTokenBalance, byte[] secondTokenId, long secondTokenBalance) { - Contract.ExchangeCreateContract.Builder builder = Contract.ExchangeCreateContract.newBuilder(); + ExchangeCreateContract.Builder builder = ExchangeCreateContract + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setFirstTokenId(ByteString.copyFrom(firstTokenId)) @@ -1502,21 +1593,24 @@ public static Contract.ExchangeCreateContract createExchangeCreateContract( return builder.build(); } - public boolean exchangeInject(byte[] owner, long exchangeId, byte[] tokenId, long quant) + public boolean exchangeInject(byte[] owner, long exchangeId, + byte[] tokenId, long quant) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeInjectContract contract = + ExchangeInjectContract contract = createExchangeInjectContract(owner, exchangeId, tokenId, quant); - TransactionExtention transactionExtention = rpcCli.exchangeInject(contract); + TransactionExtention transactionExtention = rpcCli + .exchangeInject(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeInjectContract createExchangeInjectContract( + public static ExchangeInjectContract createExchangeInjectContract( byte[] owner, long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeInjectContract.Builder builder = Contract.ExchangeInjectContract.newBuilder(); + ExchangeInjectContract.Builder builder = ExchangeInjectContract + .newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1525,22 +1619,23 @@ public static Contract.ExchangeInjectContract createExchangeInjectContract( return builder.build(); } - public boolean exchangeWithdraw(byte[] owner, long exchangeId, byte[] tokenId, long quant) + public boolean exchangeWithdraw(byte[] owner, long exchangeId, + byte[] tokenId, long quant) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeWithdrawContract contract = + ExchangeWithdrawContract contract = createExchangeWithdrawContract(owner, exchangeId, tokenId, quant); TransactionExtention transactionExtention = rpcCli.exchangeWithdraw(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( + + public static ExchangeWithdrawContract createExchangeWithdrawContract( byte[] owner, long exchangeId, byte[] tokenId, long quant) { - Contract.ExchangeWithdrawContract.Builder builder = - Contract.ExchangeWithdrawContract.newBuilder(); + ExchangeWithdrawContract.Builder builder = ExchangeWithdrawContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1549,23 +1644,21 @@ public static Contract.ExchangeWithdrawContract createExchangeWithdrawContract( return builder.build(); } - public boolean exchangeTransaction( - byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) - throws CipherException, IOException, CancelException { + public boolean exchangeTransaction(byte[] owner, long exchangeId, byte[] tokenId, long quant, + long expected) throws CipherException, IOException, CancelException { if (owner == null) { owner = getAddress(); } - Contract.ExchangeTransactionContract contract = + ExchangeTransactionContract contract = createExchangeTransactionContract(owner, exchangeId, tokenId, quant, expected); TransactionExtention transactionExtention = rpcCli.exchangeTransaction(contract); return processTransactionExtention(transactionExtention); } - public static Contract.ExchangeTransactionContract createExchangeTransactionContract( + public static ExchangeTransactionContract createExchangeTransactionContract( byte[] owner, long exchangeId, byte[] tokenId, long quant, long expected) { - Contract.ExchangeTransactionContract.Builder builder = - Contract.ExchangeTransactionContract.newBuilder(); + ExchangeTransactionContract.Builder builder = ExchangeTransactionContract.newBuilder(); builder .setOwnerAddress(ByteString.copyFrom(owner)) .setExchangeId(exchangeId) @@ -1619,11 +1712,13 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { JsonElement abiItem = jsonRoot.get(index); boolean anonymous = abiItem.getAsJsonObject().get("anonymous") != null - ? abiItem.getAsJsonObject().get("anonymous").getAsBoolean() + ? abiItem.getAsJsonObject().get("anonymous") + .getAsBoolean() : false; boolean constant = abiItem.getAsJsonObject().get("constant") != null - ? abiItem.getAsJsonObject().get("constant").getAsBoolean() + ? abiItem.getAsJsonObject().get("constant") + .getAsBoolean() : false; String name = abiItem.getAsJsonObject().get("name") != null @@ -1631,11 +1726,13 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { : null; JsonArray inputs = abiItem.getAsJsonObject().get("inputs") != null - ? abiItem.getAsJsonObject().get("inputs").getAsJsonArray() + ? abiItem.getAsJsonObject().get("inputs") + .getAsJsonArray() : null; JsonArray outputs = abiItem.getAsJsonObject().get("outputs") != null - ? abiItem.getAsJsonObject().get("outputs").getAsJsonArray() + ? abiItem.getAsJsonObject().get("outputs") + .getAsJsonArray() : null; String type = abiItem.getAsJsonObject().get("type") != null @@ -1643,11 +1740,13 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { : null; boolean payable = abiItem.getAsJsonObject().get("payable") != null - ? abiItem.getAsJsonObject().get("payable").getAsBoolean() + ? abiItem.getAsJsonObject().get("payable") + .getAsBoolean() : false; String stateMutability = abiItem.getAsJsonObject().get("stateMutability") != null - ? abiItem.getAsJsonObject().get("stateMutability").getAsString() + ? abiItem.getAsJsonObject().get("stateMutability") + .getAsString() : null; if (type == null) { System.out.println("No type!"); @@ -1679,7 +1778,9 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean inputIndexed = false; if (inputItem.getAsJsonObject().get("indexed") != null) { inputIndexed = - Boolean.valueOf(inputItem.getAsJsonObject().get("indexed").getAsString()); + Boolean.valueOf( + inputItem.getAsJsonObject().get("indexed") + .getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param.newBuilder(); @@ -1704,7 +1805,9 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { Boolean outputIndexed = false; if (outputItem.getAsJsonObject().get("indexed") != null) { outputIndexed = - Boolean.valueOf(outputItem.getAsJsonObject().get("indexed").getAsString()); + Boolean.valueOf( + outputItem.getAsJsonObject().get("indexed") + .getAsString()); } SmartContract.ABI.Entry.Param.Builder paramBuilder = SmartContract.ABI.Entry.Param.newBuilder(); @@ -1718,7 +1821,8 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { entryBuilder.setType(getEntryType(type)); entryBuilder.setPayable(payable); if (stateMutability != null) { - entryBuilder.setStateMutability(getStateMutability(stateMutability)); + entryBuilder.setStateMutability( + getStateMutability(stateMutability)); } abiBuilder.addEntrys(entryBuilder.build()); @@ -1727,31 +1831,29 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { return abiBuilder.build(); } - public static Contract.UpdateSettingContract createUpdateSettingContract( + public static UpdateSettingContract createUpdateSettingContract( byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) { - Contract.UpdateSettingContract.Builder builder = Contract.UpdateSettingContract.newBuilder(); + UpdateSettingContract.Builder builder = UpdateSettingContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setConsumeUserResourcePercent(consumeUserResourcePercent); return builder.build(); } - public static Contract.UpdateEnergyLimitContract createUpdateEnergyLimitContract( + public static UpdateEnergyLimitContract createUpdateEnergyLimitContract( byte[] owner, byte[] contractAddress, long originEnergyLimit) { - Contract.UpdateEnergyLimitContract.Builder builder = - Contract.UpdateEnergyLimitContract.newBuilder(); + UpdateEnergyLimitContract.Builder builder = UpdateEnergyLimitContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setOriginEnergyLimit(originEnergyLimit); return builder.build(); } - public static Contract.ClearABIContract createClearABIContract( - byte[] owner, byte[] contractAddress) { + public static ClearABIContract createClearABIContract(byte[] owner, byte[] contractAddress) { - Contract.ClearABIContract.Builder builder = Contract.ClearABIContract.newBuilder(); + ClearABIContract.Builder builder = ClearABIContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); return builder.build(); @@ -1805,8 +1907,8 @@ public static CreateSmartContract createContractDeployContract( return createSmartContractBuilder.build(); } - private static byte[] replaceLibraryAddress( - String code, String libraryAddressPair, String compilerVersion) { + private static byte[] replaceLibraryAddress(String code, String libraryAddressPair, + String compilerVersion) { String[] libraryAddressList = libraryAddressPair.split("[,]"); @@ -1821,9 +1923,8 @@ private static byte[] replaceLibraryAddress( String addr = cur.substring(lastPosition + 1); String libraryAddressHex; try { - libraryAddressHex = - (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), "US-ASCII")) - .substring(2); + libraryAddressHex = (new String(Hex.encode(WalletApi.decodeFromBase58Check(addr)), + "US-ASCII")).substring(2); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); // now ignore } @@ -1831,12 +1932,16 @@ private static byte[] replaceLibraryAddress( String beReplaced; if (compilerVersion == null) { // old version - String repeated = new String(new char[40 - libraryName.length() - 2]).replace("\0", "_"); + String repeated = new String( + new char[40 - libraryName.length() - 2]) + .replace("\0", "_"); beReplaced = "__" + libraryName + repeated; } else if (compilerVersion.equalsIgnoreCase("v5")) { // 0.5.4 version String libraryNameKeccak256 = - ByteArray.toHexString(Hash.sha3(ByteArray.fromString(libraryName))).substring(0, 34); + ByteArray.toHexString( + Hash.sha3(ByteArray.fromString(libraryName))) + .substring(0, 34); beReplaced = "__\\$" + libraryNameKeccak256 + "\\$__"; } else { throw new RuntimeException("unknown compiler version."); @@ -1849,14 +1954,14 @@ private static byte[] replaceLibraryAddress( return Hex.decode(code); } - public static Contract.TriggerSmartContract triggerCallContract( + public static TriggerSmartContract triggerCallContract( byte[] address, byte[] contractAddress, long callValue, byte[] data, long tokenValue, String tokenId) { - Contract.TriggerSmartContract.Builder builder = Contract.TriggerSmartContract.newBuilder(); + TriggerSmartContract.Builder builder = TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); builder.setContractAddress(ByteString.copyFrom(contractAddress)); builder.setData(ByteString.copyFrom(data)); @@ -1880,28 +1985,28 @@ public byte[] generateContractAddress(byte[] ownerAddress, Transaction trx) { return Hash.sha3omit12(combined); } - public boolean updateSetting( - byte[] owner, byte[] contractAddress, long consumeUserResourcePercent) - throws IOException, CipherException, CancelException { + public boolean updateSetting(byte[] owner, byte[] contractAddress, + long consumeUserResourcePercent) throws IOException, CipherException, CancelException { if (owner == null) { owner = getAddress(); } - UpdateSettingContract updateSettingContract = - createUpdateSettingContract(owner, contractAddress, consumeUserResourcePercent); + UpdateSettingContract updateSettingContract = createUpdateSettingContract(owner, + contractAddress, consumeUserResourcePercent); TransactionExtention transactionExtention = rpcCli.updateSetting(updateSettingContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } - return processTransactionExtention(transactionExtention); + return processTransactionExtention( + transactionExtention); } public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long originEnergyLimit) @@ -1910,21 +2015,22 @@ public boolean updateEnergyLimit(byte[] owner, byte[] contractAddress, long orig owner = getAddress(); } - UpdateEnergyLimitContract updateEnergyLimitContract = - createUpdateEnergyLimitContract(owner, contractAddress, originEnergyLimit); + UpdateEnergyLimitContract updateEnergyLimitContract = createUpdateEnergyLimitContract(owner, + contractAddress, originEnergyLimit); TransactionExtention transactionExtention = rpcCli.updateEnergyLimit(updateEnergyLimitContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } - return processTransactionExtention(transactionExtention); + return processTransactionExtention( + transactionExtention); } public boolean clearContractABI(byte[] owner, byte[] contractAddress) @@ -1939,8 +2045,8 @@ public boolean clearContractABI(byte[] owner, byte[] contractAddress) System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } @@ -1985,16 +2091,16 @@ public boolean deployContract( System.out.println("RPC create trx failed!"); if (transactionExtention != null) { System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); } return false; } TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = - transactionExtention.getTransaction().getRawData().toBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2030,8 +2136,8 @@ public boolean triggerContract( owner = getAddress(); } - Contract.TriggerSmartContract triggerContract = - triggerCallContract(owner, contractAddress, callValue, data, tokenValue, tokenId); + TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, callValue, + data, tokenValue, tokenId); TransactionExtention transactionExtention; if (isConstant) { transactionExtention = rpcCli.triggerConstantContract(triggerContract); @@ -2042,12 +2148,13 @@ public boolean triggerContract( if (transactionExtention == null || !transactionExtention.getResult().getResult()) { System.out.println("RPC create call trx failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); - System.out.println( - "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); return false; } - Transaction transaction = transactionExtention.getTransaction(); + Transaction transaction = transactionExtention + .getTransaction(); // for constant if (transaction.getRetCount() != 0 && transactionExtention.getConstantResult(0) != null @@ -2062,8 +2169,8 @@ public boolean triggerContract( TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); Transaction.Builder transBuilder = Transaction.newBuilder(); - Transaction.raw.Builder rawBuilder = - transactionExtention.getTransaction().getRawData().toBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); rawBuilder.setFeeLimit(feeLimit); transBuilder.setRawData(rawBuilder); for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { @@ -2088,7 +2195,7 @@ public static SmartContract getContract(byte[] address) { public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { - Contract.AccountPermissionUpdateContract contract = + AccountPermissionUpdateContract contract = createAccountPermissionContract(owner, permissionJson); TransactionExtention transactionExtention = rpcCli.accountPermissionUpdate(contract); return processTransactionExtention(transactionExtention); @@ -2133,10 +2240,9 @@ private Permission json2Permission(JSONObject json) { return permissionBuilder.build(); } - public Contract.AccountPermissionUpdateContract createAccountPermissionContract( - byte[] owner, String permissionJson) { - Contract.AccountPermissionUpdateContract.Builder builder = - Contract.AccountPermissionUpdateContract.newBuilder(); + public AccountPermissionUpdateContract createAccountPermissionContract(byte[] owner, + String permissionJson) { + AccountPermissionUpdateContract.Builder builder = AccountPermissionUpdateContract.newBuilder(); JSONObject permissions = JSONObject.parseObject(permissionJson); JSONObject owner_permission = permissions.getJSONObject("owner_permission"); @@ -2205,8 +2311,8 @@ public static Optional GetMerkleTreeVoucherInfo( return Optional.empty(); } - public static Optional scanNoteByIvk( - IvkDecryptParameters ivkDecryptParameters, boolean showErrorMsg) { + public static Optional scanNoteByIvk(IvkDecryptParameters ivkDecryptParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByIvk(ivkDecryptParameters)); @@ -2222,8 +2328,8 @@ public static Optional scanNoteByIvk( return Optional.empty(); } - public static Optional scanNoteByOvk( - OvkDecryptParameters ovkDecryptParameters, boolean showErrorMsg) { + public static Optional scanNoteByOvk(OvkDecryptParameters ovkDecryptParameters, + boolean showErrorMsg) { if (showErrorMsg) { try { return Optional.of(rpcCli.scanNoteByOvk(ovkDecryptParameters)); @@ -2307,9 +2413,8 @@ public static boolean sendShieldedCoin(PrivateParameters privateParameters, Wall return processShieldedTransaction(transactionExtention, wallet); } - public static boolean sendShieldedCoinWithoutAsk( - PrivateParametersWithoutAsk privateParameters, byte[] ask, WalletApi wallet) - throws CipherException, IOException, CancelException { + public static boolean sendShieldedCoinWithoutAsk(PrivateParametersWithoutAsk privateParameters, + byte[] ask, WalletApi wallet) throws CipherException, IOException, CancelException { TransactionExtention transactionExtention = rpcCli.createShieldedTransactionWithoutSpendAuthSig(privateParameters); if (transactionExtention == null) { @@ -2433,7 +2538,9 @@ public boolean updateBrokerage(byte[] owner, int brokerage) } UpdateBrokerageContract.Builder updateBrokerageContract = UpdateBrokerageContract.newBuilder(); - updateBrokerageContract.setOwnerAddress(ByteString.copyFrom(owner)).setBrokerage(brokerage); + updateBrokerageContract + .setOwnerAddress(ByteString.copyFrom(owner)) + .setBrokerage(brokerage); TransactionExtention transactionExtention = rpcCli.updateBrokerage(updateBrokerageContract.build()); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { @@ -2456,4 +2563,9 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + + public static Optional getTransactionInfoByBlockNum(long blockNum) { + return rpcCli.getTransactionInfoByBlockNum(blockNum); + } + } diff --git a/src/main/protos/.travis.yml b/src/main/protos/.travis.yml index 34fa71b3d..c77079aeb 100644 --- a/src/main/protos/.travis.yml +++ b/src/main/protos/.travis.yml @@ -1,24 +1,13 @@ language: ruby - cache: directories: - - $HOME/protobuf - + - "$HOME/protobuf" sudo: false - before_install: - - bash install-protobuf.sh - - bash install-googleapis.sh - -# check what has been installed by listing contents of protobuf folder before_script: - - ls -R $HOME/protobuf - -# let's use protobuf script: - - $HOME/protobuf/bin/protoc --java_out=./ ./core/*.proto ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./core/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=./ ./api/*.proto - - $HOME/protobuf/bin/protoc -I. -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true:./ ./api/*.proto - - - ls -l \ No newline at end of file + - ls -l +notifications: + slack: + secure: lCDOgsHefxu0Sd7J4N9wyo6Y/dvAentdf+q+R6R+SpJUym+tI7h4lPVwDFl9iQabFahLC6bV4lgfv/US0aqjL4QE/c3ZsWBDo2dtkfCzGmkCiU6mGd/bPWLFP9e28OHEFBzUT8ukc5O3GDJREnzscIqGk7Knv3QYQGsoZw563grzVG9VVfqKhsVgrAjhzDwiCoUNSiND09ZNifGrHr7JBQ2u0wrZRR+E9yOQ1K3pNnfq5wAJjbJ0LiuxRlLZoajR7z8VeP8ytet+u2XQE1+fd089XEa/thYxtt0ZvhnyOYk1vmEldDdVakqMqVaa9MKiyLBNnLby1xdSosrM55ZB3tFmXQo6EMIaNKyO07xMUJ/hK0gEppQhHMDlbOW31YmOshVutp2yN4tRF8D3ot1OcYPXsVeXFG+kMUR0PYM71YiqZy8UVgoslKgRgEehvxjuAhbMYXMG878bznco9L64TzubFcjOx0+owR8H0kCXDNf5Xwm+tlc2onWlUDerEWIAignVOT1iR8KK0U+EnhTfw2BWfDV+BkSBvaeFbmnsFgz00d7qSvzbtCJ9KdDj3f4FUjUuORNv5ykMGCv1MrvAvttAZPTNIc/wOFJ9yRC2NhoUfbNILYg7bYhmqKiicnflln03Bh7Ml2mzldzPaRMm7eUBUwYTFbgC83YusX4fxBg= + if: branch = master \ No newline at end of file diff --git a/src/main/protos/English version of TRON Protocol document.md b/src/main/protos/English version of TRON Protocol document.md index 94498fb1c..e282f4b0a 100644 --- a/src/main/protos/English version of TRON Protocol document.md +++ b/src/main/protos/English version of TRON Protocol document.md @@ -7,12 +7,14 @@ + A basic account is able to apply to be a validation node, which has serval parameters, including extra attributes, public key, URL, voting statistics, history performance, etc. There are three different `Account types`: `Normal`, `AssetIssue`, `Contract`. - - enum AccountType {
 - Normal = 0;
 - AssetIssue = 1;
 - Contract = 2; - 
} +```protobuf +enum AccountType {
 + Normal = 0;
 + AssetIssue = 1;
 + Contract = 2;
 +
} +``` + An `Account` contains 7 parameters: `account_name`: the name for this account – e.g. “_BillsAccount_”. @@ -21,21 +23,21 @@ `votes`: received votes on this account – e.g. _{(“0x1b7w…9xj3”,323), (“0x8djq…j12m”,88),…,(“0x82nd…mx6i”,10001)}_. `asset`: other assets expect TRX in this account – e.g. _{<“WishToken”,66666>,<”Dogie”,233>}_. `latest_operation_time`: the latest operation time of this account. - - // Account
 - message Account {
 - message Vote {
 - bytes vote_address = 1;
 - int64 vote_count = 2;
 }
 - bytes accout_name = 1;
 - AccountType type = 2;
 - bytes address = 3;
 - int64 balance = 4;
 - repeated Vote votes = 5;
 - map asset = 6; - int64 latest_operation_time = 10;
 - } - +```protobuf +// Account
 +message Account {
 + message Vote {
 + bytes vote_address = 1;
 + int64 vote_count = 2;
 }
 + bytes accout_name = 1;
 + AccountType type = 2;
 + bytes address = 3;
 + int64 balance = 4;
 + repeated Vote votes = 5;
 + map asset = 6; + int64 latest_operation_time = 10;
 +} +``` A `Witness` contains 8 parameters: `address`: the address of this witness – e.g. “_0xu82h…7237_”. `voteCount`: number of received votes on this witness – e.g. _234234_. @@ -45,31 +47,31 @@ `totalMissed`: the number of blocks this witness missed – e.g. _7_. `latestBlockNum`: the latest height of block – e.g. _4522_. `isjobs`: a bool flag. - - // Witness
 - message Witness{
 - bytes address = 1;
 - int64 voteCount = 2;
 - bytes pubKey = 3;
 - string url = 4;
 - int64 totalProduced = 5;
 - int64 totalMissed = 6;
 - int64 latestBlockNum = 7;
 - bool isJobs = 9; - } - +```protobuf +// Witness
 +message Witness{
 + bytes address = 1;
 + int64 voteCount = 2;
 + bytes pubKey = 3;
 + string url = 4;
 + int64 totalProduced = 5;
 + int64 totalMissed = 6;
 + int64 latestBlockNum = 7;
 + bool isJobs = 9; +} +``` + A block typically contains transaction data and a blockheader, which is a list of basic block information, including timestamp, signature, parent hash, root of Merkle tree and so on. A block contains `transactions` and a `block_header`. `transactions`: transaction data of this block. `block_header`: one part of a block. - - // block - 
message Block {
 - repeated Transaction transactions = 1;
 - BlockHeader block_header = 2;
 - } - + ```protobuf + // block +message Block {
 + repeated Transaction transactions = 1;
 + BlockHeader block_header = 2;
 +} +``` A `BlockHeader` contains `raw_data` and `witness_signature`. `raw_data`: a `raw` message. `witness_signature`: signature for this block header from witness node. @@ -81,22 +83,22 @@ `number`: the height of this block – e.g. _13534657_. `witness_id`: the id of witness which packed this block – e.g. “_0xu82h…7237_”. `witness_address`: the adresss of the witness packed this block – e.g. “_0xu82h…7237_”. - - message BlockHeader {
 - message raw {
 - int64 timestamp = 1;
 - bytes txTrieRoot = 2;
 - bytes parentHash = 3;
 - //bytes nonce = 5;
 - //bytes difficulty = 6;
 - uint64 number = 7;
 - uint64 witness_id = 8;
 - bytes witness_address = 9;
 - }
 - raw raw_data = 1;
 - bytes witness_signature = 2;
 - } - +```protobuf +message BlockHeader {
 + message raw {
 + int64 timestamp = 1;
 + bytes txTrieRoot = 2;
 + bytes parentHash = 3;
 + //bytes nonce = 5;
 + //bytes difficulty = 6;
 + uint64 number = 7;
 + uint64 witness_id = 8;
 + bytes witness_address = 9;
 + }
 + raw raw_data = 1;
 + bytes witness_signature = 2;
 +} +``` message `ChainInventory` contains `BlockId` and `remain_num`. `BlockId`: the identification of block. `remain_num`:the remain number of blocks in the synchronizing process. @@ -104,75 +106,75 @@ A `BlockId` contains 2 parameters: `hash`: the hash of block. `number`: the hash and height of block. - - message ChainInventory { - message BlockId { - bytes hash = 1; - int64 number = 2; - } - repeated BlockId ids = 1; - int64 remain_num = 2; - } - +```protobuf +message ChainInventory { + message BlockId { + bytes hash = 1; + int64 number = 2; + } + repeated BlockId ids = 1; + int64 remain_num = 2; +} +``` + Transaction contracts mainly includes account create contract, account update contract transfer contract, transfer asset contract, vote asset contract, vote witness contract, witness creation contract, witness update contract, asset issue contract, participate asset issue contract and deploy contract. An `AccountCreateContract` contains 3 parameters: `type`: What type this account is – e.g. _0_ stands for `Normal`. `account_name`: the name for this account – e.g.”_Billsaccount_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message AccountCreateContract {
 - AccountType type = 1;
 - bytes account_name = 2;
 - bytes owner_address = 3;
 - } - +```protobuf +message AccountCreateContract {
 + AccountType type = 1;
 + bytes account_name = 2;
 + bytes owner_address = 3;
 +} +``` A `AccountUpdateContract` contains 2 paremeters: `account_name`: the name for this account – e.g.”_Billsaccount_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message AccountUpdateContract { - bytes account_name = 1; - bytes owner_address = 2; - } - +```protobuf +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} +``` A `TransferContract` contains 3 parameters: `amount`: the amount of TRX – e.g. _12534_. `to_address`: the receiver address – e.g. “_0xu82h…7237_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. - - message TransferContract {
 - bytes owner_address = 1;
 - bytes to_address = 2;
 - int64 amount = 3; - } - +```protobuf +message TransferContract {
 + bytes owner_address = 1;
 + bytes to_address = 2;
 + int64 amount = 3; +} +``` A `TransferAssetContract` contains 4 parameters: `asset_name`: the name for asset – e.g.”_Billsaccount_”. `to_address`: the receiver address – e.g. “_0xu82h…7237_”. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. `amount`: the amount of target asset - e.g._12353_. - - message TransferAssetContract {
 - bytes asset_name = 1;
 - bytes owner_address = 2;
 - bytes to_address = 3;
 - int64 amount = 4;
 - } - +```protobuf +message TransferAssetContract {
 + bytes asset_name = 1;
 + bytes owner_address = 2;
 + bytes to_address = 3;
 + int64 amount = 4;
 +} +``` A `VoteAssetContract` contains 4 parameters: `vote_address`: the voted address of the asset. `support`: is the votes supportive or not – e.g. _true_. `owner_address`: the address of contract owner – e.g. “_0xu82h…7237_”. `count`: the count number of votes- e.g. _2324234_. - +```protobuf message VoteAssetContract {
 bytes owner_address = 1;
 repeated bytes vote_address = 2;
 bool support = 3;
 int32 count = 5;
 } - +``` A `VoteWitnessContract` contains 4 parameters: `vote_address`: the addresses of those who voted. `support`: is the votes supportive or not - e.g. _true_. diff --git a/src/main/protos/README.md b/src/main/protos/README.md index 6a2811911..e409e0407 100644 --- a/src/main/protos/README.md +++ b/src/main/protos/README.md @@ -3,6 +3,8 @@ # The protocol of Tron including api and message. +The protocol is an independent project. You can use it for building other application. + java-tron, wallet-cli and grpc-gateway git subtree pull --prefix src/main/protos/ protocol master diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6172ffcfb..39f76c09d 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -2,9 +2,17 @@ syntax = "proto3"; package protocol; import "core/Tron.proto"; -import "core/Contract.proto"; import "google/api/annotations.proto"; +import "core/contract/asset_issue_contract.proto"; +import "core/contract/account_contract.proto"; +import "core/contract/witness_contract.proto"; +import "core/contract/balance_contract.proto"; +import "core/contract/proposal_contract.proto"; +import "core/contract/storage_contract.proto"; +import "core/contract/exchange_contract.proto"; +import "core/contract/smart_contract.proto"; +import "core/contract/shield_contract.proto"; option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file @@ -687,6 +695,7 @@ service Wallet { rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; + // end for shiededTransaction rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { }; @@ -697,7 +706,12 @@ service Wallet { rpc UpdateBrokerage (UpdateBrokerageContract) returns (TransactionExtention) { }; - // end for shiededTransaction + + rpc CreateCommonTransaction (Transaction) returns (TransactionExtention) { + }; + + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } }; service WalletSolidity { @@ -850,6 +864,13 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; + + rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { + } + + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + } + }; service WalletExtension { @@ -1066,7 +1087,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) + bytes txid = 3; //transaction id = sha256(transaction.raw_data) } message AddressPrKeyPairMessage { @@ -1076,7 +1097,7 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; } @@ -1161,7 +1182,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1170,7 +1191,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.rowdata) + bytes txid = 2; //transaction id = sha256(transaction.raw_data) int32 index = 3; //the index of note in receive bool is_spend = 4; } @@ -1275,7 +1296,6 @@ message ShieldedAddressInfo{ string payment_address = 10; } - message NoteParameters { bytes ak = 1; bytes nk = 2; @@ -1288,3 +1308,7 @@ message SpendResult { bool result = 1; string message = 2; } + +message TransactionInfoList { + repeated TransactionInfo transactionInfo = 1; +} diff --git a/src/main/protos/api/zksnark.proto b/src/main/protos/api/zksnark.proto new file mode 100644 index 000000000..91b985651 --- /dev/null +++ b/src/main/protos/api/zksnark.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +package protocol; + +import "core/Tron.proto"; + +option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file +option java_outer_classname = "ZksnarkGrpcAPI"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/api"; + +service TronZksnark { + rpc CheckZksnarkProof (ZksnarkRequest) returns (ZksnarkResponse) { + } +}; + +message ZksnarkRequest { + Transaction transaction = 1; + bytes sighash = 2; + int64 valueBalance = 3; + string txId = 4; +} + +message ZksnarkResponse { + enum Code { + SUCCESS = 0; + FAILED = 1; + } + + Code code = 1; +} + + + + diff --git a/src/main/protos/core/Contract.proto b/src/main/protos/core/Contract.proto deleted file mode 100644 index bcafc904e..000000000 --- a/src/main/protos/core/Contract.proto +++ /dev/null @@ -1,342 +0,0 @@ -/* - * java-tron is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * java-tron is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -syntax = "proto3"; - -package protocol; - -option java_package = "org.tron.protos"; //Specify the name of the package that generated the Java file -option java_outer_classname = "Contract"; //Specify the class name of the generated Java file -option go_package = "github.com/tronprotocol/grpc-gateway/core"; - -import "core/Tron.proto"; - -message AccountCreateContract { - bytes owner_address = 1; - bytes account_address = 2; - AccountType type = 3; -} - -// Update account name. Account name is not unique now. -message AccountUpdateContract { - bytes account_name = 1; - bytes owner_address = 2; -} - -// Set account id if the account has no id. Account id is unique and case insensitive. -message SetAccountIdContract { - bytes account_id = 1; - bytes owner_address = 2; -} - -message TransferContract { - bytes owner_address = 1; - bytes to_address = 2; - int64 amount = 3; -} - - -message ShieldAddress { - bytes private_address = 1; - bytes public_address = 2; -} - -message TransferAssetContract { - bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. - bytes owner_address = 2; - bytes to_address = 3; - int64 amount = 4; -} - - -message VoteAssetContract { - bytes owner_address = 1; - repeated bytes vote_address = 2; - bool support = 3; - int32 count = 5; -} - -message VoteWitnessContract { - message Vote { - bytes vote_address = 1; - int64 vote_count = 2; - } - bytes owner_address = 1; - repeated Vote votes = 2; - bool support = 3; -} - -message UpdateSettingContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 consume_user_resource_percent = 3; -} - -message UpdateEnergyLimitContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 origin_energy_limit = 3; -} - -message ClearABIContract { - bytes owner_address = 1; - bytes contract_address = 2; -} - -message WitnessCreateContract { - bytes owner_address = 1; - bytes url = 2; -} - -message WitnessUpdateContract { - bytes owner_address = 1; - bytes update_url = 12; -} - -message AssetIssueContract { - string id = 41; - - message FrozenSupply { - int64 frozen_amount = 1; - int64 frozen_days = 2; - } - bytes owner_address = 1; - bytes name = 2; - bytes abbr = 3; - int64 total_supply = 4; - repeated FrozenSupply frozen_supply = 5; - int32 trx_num = 6; - int32 precision = 7; - int32 num = 8; - int64 start_time = 9; - int64 end_time = 10; - int64 order = 11; // useless - int32 vote_score = 16; - bytes description = 20; - bytes url = 21; - int64 free_asset_net_limit = 22; - int64 public_free_asset_net_limit = 23; - int64 public_free_asset_net_usage = 24; - int64 public_latest_free_net_time = 25; -} - -message ParticipateAssetIssueContract { - bytes owner_address = 1; - bytes to_address = 2; - bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. - int64 amount = 4; // the amount of drops -} - - -enum ResourceCode { - BANDWIDTH = 0x00; - ENERGY = 0x01; -} - -message FreezeBalanceContract { - bytes owner_address = 1; - int64 frozen_balance = 2; - int64 frozen_duration = 3; - - ResourceCode resource = 10; - bytes receiver_address = 15; -} - -message UnfreezeBalanceContract { - bytes owner_address = 1; - - ResourceCode resource = 10; - bytes receiver_address = 15; -} - -message UnfreezeAssetContract { - bytes owner_address = 1; -} - -message WithdrawBalanceContract { - bytes owner_address = 1; -} - -message UpdateAssetContract { - bytes owner_address = 1; - bytes description = 2; - bytes url = 3; - int64 new_limit = 4; - int64 new_public_limit = 5; -} - -message ProposalCreateContract { - bytes owner_address = 1; - map parameters = 2; -} - -message ProposalApproveContract { - bytes owner_address = 1; - int64 proposal_id = 2; - bool is_add_approval = 3; // add or remove approval -} - -message ProposalDeleteContract { - bytes owner_address = 1; - int64 proposal_id = 2; -} - -message CreateSmartContract { - bytes owner_address = 1; - SmartContract new_contract = 2; - int64 call_token_value = 3; - int64 token_id = 4; -} - -message TriggerSmartContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 call_value = 3; - bytes data = 4; - int64 call_token_value = 5; - int64 token_id = 6; -} - -message BuyStorageContract { - bytes owner_address = 1; - int64 quant = 2; // trx quantity for buy storage (Sun) -} - -message BuyStorageBytesContract { - bytes owner_address = 1; - int64 bytes = 2; // storage bytes for buy -} - -message SellStorageContract { - bytes owner_address = 1; - int64 storage_bytes = 2; -} - -message ExchangeCreateContract { - bytes owner_address = 1; - bytes first_token_id = 2; - int64 first_token_balance = 3; - bytes second_token_id = 4; - int64 second_token_balance = 5; -} - -message ExchangeInjectContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; -} - -message ExchangeWithdrawContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; -} - -message ExchangeTransactionContract { - bytes owner_address = 1; - int64 exchange_id = 2; - bytes token_id = 3; - int64 quant = 4; - int64 expected = 5; -} - -message AccountPermissionUpdateContract { - bytes owner_address = 1; - Permission owner = 2; //Empty is invalidate - Permission witness = 3; //Can be empty - repeated Permission actives = 4; //Empty is invalidate -} - -message UpdateBrokerageContract { - bytes owner_address = 1; - int32 brokerage = 2; // 1 mean 1% -} - -// for shielded transaction - -message AuthenticationPath { - repeated bool value = 1; -} - -message MerklePath { - repeated AuthenticationPath authentication_paths = 1; - repeated bool index = 2; - bytes rt = 3; -} - -message OutputPoint { - bytes hash = 1; - int32 index = 2; -} - -message OutputPointInfo { - repeated OutputPoint out_points = 1; - int32 block_num = 2; -} - -message PedersenHash { - bytes content = 1; -} - -message IncrementalMerkleTree { - PedersenHash left = 1; - PedersenHash right = 2; - repeated PedersenHash parents = 3; -} - -message IncrementalMerkleVoucher { - IncrementalMerkleTree tree = 1; - repeated PedersenHash filled = 2; - IncrementalMerkleTree cursor = 3; - int64 cursor_depth = 4; - bytes rt = 5; - OutputPoint output_point = 10; -} - -message IncrementalMerkleVoucherInfo { - repeated IncrementalMerkleVoucher vouchers = 1; - repeated bytes paths = 2; -} - -message SpendDescription { - bytes value_commitment = 1; - bytes anchor = 2; // merkle root - bytes nullifier = 3; // used for check double spend - bytes rk = 4; // used for check spend authority signature - bytes zkproof = 5; - bytes spend_authority_signature = 6; -} - -message ReceiveDescription { - bytes value_commitment = 1; - bytes note_commitment = 2; - bytes epk = 3; // for Encryption - bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk - bytes c_out = 5; // Encryption for audit, decrypt it with ovk - bytes zkproof = 6; -} - -message ShieldedTransferContract { - bytes transparent_from_address = 1; // transparent address - int64 from_amount = 2; - repeated SpendDescription spend_description = 3; - repeated ReceiveDescription receive_description = 4; - bytes binding_signature = 5; - bytes transparent_to_address = 6; // transparent address - int64 to_amount = 7; // the amount to transparent to_address -} -// end shielded transaction diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index d70672297..d794d3083 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -259,9 +259,6 @@ message Transaction { ProposalDeleteContract = 18; SetAccountIdContract = 19; CustomContract = 20; - // BuyStorageContract = 21; - // BuyStorageBytesContract = 22; - // SellStorageContract = 23; CreateSmartContract = 30; TriggerSmartContract = 31; GetContract = 32; @@ -509,53 +506,6 @@ message HelloMessage { BlockId headBlockId = 6; } -message SmartContract { - message ABI { - message Entry { - enum EntryType { - UnknownEntryType = 0; - Constructor = 1; - Function = 2; - Event = 3; - Fallback = 4; - } - message Param { - bool indexed = 1; - string name = 2; - string type = 3; - // SolidityType type = 3; - } - enum StateMutabilityType { - UnknownMutabilityType = 0; - Pure = 1; - View = 2; - Nonpayable = 3; - Payable = 4; - } - - bool anonymous = 1; - bool constant = 2; - string name = 3; - repeated Param inputs = 4; - repeated Param outputs = 5; - EntryType type = 6; - bool payable = 7; - StateMutabilityType stateMutability = 8; - } - repeated Entry entrys = 1; - } - bytes origin_address = 1; - bytes contract_address = 2; - ABI abi = 3; - bytes bytecode = 4; - int64 call_value = 5; - int64 consume_user_resource_percent = 6; - string name = 7; - int64 origin_energy_limit = 8; - bytes code_hash = 9; - bytes trx_hash = 10; -} - message InternalTransaction { // internalTransaction identity, the root InternalTransaction hash // should equals to root transaction id. diff --git a/src/main/protos/core/contract/account_contract.proto b/src/main/protos/core/contract/account_contract.proto new file mode 100644 index 000000000..d3180048f --- /dev/null +++ b/src/main/protos/core/contract/account_contract.proto @@ -0,0 +1,50 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "Contract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message AccountCreateContract { + bytes owner_address = 1; + bytes account_address = 2; + AccountType type = 3; +} + +// Update account name. Account name is not unique now. +message AccountUpdateContract { + bytes account_name = 1; + bytes owner_address = 2; +} + +// Set account id if the account has no id. Account id is unique and case insensitive. +message SetAccountIdContract { + bytes account_id = 1; + bytes owner_address = 2; +} + +message AccountPermissionUpdateContract { + bytes owner_address = 1; + Permission owner = 2; //Empty is invalidate + Permission witness = 3; //Can be empty + repeated Permission actives = 4; //Empty is invalidate +} + diff --git a/src/main/protos/core/contract/asset_issue_contract.proto b/src/main/protos/core/contract/asset_issue_contract.proto new file mode 100644 index 000000000..f2e515e57 --- /dev/null +++ b/src/main/protos/core/contract/asset_issue_contract.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "AssetIssueContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message AssetIssueContract { + string id = 41; + + message FrozenSupply { + int64 frozen_amount = 1; + int64 frozen_days = 2; + } + bytes owner_address = 1; + bytes name = 2; + bytes abbr = 3; + int64 total_supply = 4; + repeated FrozenSupply frozen_supply = 5; + int32 trx_num = 6; + int32 precision = 7; + int32 num = 8; + int64 start_time = 9; + int64 end_time = 10; + int64 order = 11; // useless + int32 vote_score = 16; + bytes description = 20; + bytes url = 21; + int64 free_asset_net_limit = 22; + int64 public_free_asset_net_limit = 23; + int64 public_free_asset_net_usage = 24; + int64 public_latest_free_net_time = 25; +} + +message TransferAssetContract { + bytes asset_name = 1; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. + bytes owner_address = 2; + bytes to_address = 3; + int64 amount = 4; +} + +message UnfreezeAssetContract { + bytes owner_address = 1; +} + +message UpdateAssetContract { + bytes owner_address = 1; + bytes description = 2; + bytes url = 3; + int64 new_limit = 4; + int64 new_public_limit = 5; +} + +message ParticipateAssetIssueContract { + bytes owner_address = 1; + bytes to_address = 2; + bytes asset_name = 3; // this field is token name before the proposal ALLOW_SAME_TOKEN_NAME is active, otherwise it is token id and token is should be in string format. + int64 amount = 4; // the amount of drops +} \ No newline at end of file diff --git a/src/main/protos/core/contract/balance_contract.proto b/src/main/protos/core/contract/balance_contract.proto new file mode 100644 index 000000000..1ce29a0cd --- /dev/null +++ b/src/main/protos/core/contract/balance_contract.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "FreezeBalanceContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/contract/common.proto"; + +message FreezeBalanceContract { + bytes owner_address = 1; + int64 frozen_balance = 2; + int64 frozen_duration = 3; + + ResourceCode resource = 10; + bytes receiver_address = 15; +} + + +message UnfreezeBalanceContract { + bytes owner_address = 1; + + ResourceCode resource = 10; + bytes receiver_address = 15; +} + +message WithdrawBalanceContract { + bytes owner_address = 1; +} + +message TransferContract { + bytes owner_address = 1; + bytes to_address = 2; + int64 amount = 3; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/common.proto b/src/main/protos/core/contract/common.proto new file mode 100644 index 000000000..561767186 --- /dev/null +++ b/src/main/protos/core/contract/common.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "common"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +enum ResourceCode { + BANDWIDTH = 0x00; + ENERGY = 0x01; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/exchange_contract.proto b/src/main/protos/core/contract/exchange_contract.proto new file mode 100644 index 000000000..a2f878c57 --- /dev/null +++ b/src/main/protos/core/contract/exchange_contract.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ExchangeCreateContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message ExchangeCreateContract { + bytes owner_address = 1; + bytes first_token_id = 2; + int64 first_token_balance = 3; + bytes second_token_id = 4; + int64 second_token_balance = 5; +} + +message ExchangeInjectContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeWithdrawContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; +} + +message ExchangeTransactionContract { + bytes owner_address = 1; + int64 exchange_id = 2; + bytes token_id = 3; + int64 quant = 4; + int64 expected = 5; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/proposal_contract.proto b/src/main/protos/core/contract/proposal_contract.proto new file mode 100644 index 000000000..6cd25fab0 --- /dev/null +++ b/src/main/protos/core/contract/proposal_contract.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ProposalApproveContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message ProposalApproveContract { + bytes owner_address = 1; + int64 proposal_id = 2; + bool is_add_approval = 3; // add or remove approval +} + +message ProposalCreateContract { + bytes owner_address = 1; + map parameters = 2; +} + +message ProposalDeleteContract { + bytes owner_address = 1; + int64 proposal_id = 2; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/shield_contract.proto b/src/main/protos/core/contract/shield_contract.proto new file mode 100644 index 000000000..9119a7aef --- /dev/null +++ b/src/main/protos/core/contract/shield_contract.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "ShieldedTransferContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +// for shielded transaction + +message AuthenticationPath { + repeated bool value = 1; +} + +message MerklePath { + repeated AuthenticationPath authentication_paths = 1; + repeated bool index = 2; + bytes rt = 3; +} + +message OutputPoint { + bytes hash = 1; + int32 index = 2; +} + +message OutputPointInfo { + repeated OutputPoint out_points = 1; + int32 block_num = 2; +} + +message PedersenHash { + bytes content = 1; +} + +message IncrementalMerkleTree { + PedersenHash left = 1; + PedersenHash right = 2; + repeated PedersenHash parents = 3; +} + +message IncrementalMerkleVoucher { + IncrementalMerkleTree tree = 1; + repeated PedersenHash filled = 2; + IncrementalMerkleTree cursor = 3; + int64 cursor_depth = 4; + bytes rt = 5; + OutputPoint output_point = 10; +} + +message IncrementalMerkleVoucherInfo { + repeated IncrementalMerkleVoucher vouchers = 1; + repeated bytes paths = 2; +} + +message SpendDescription { + bytes value_commitment = 1; + bytes anchor = 2; // merkle root + bytes nullifier = 3; // used for check double spend + bytes rk = 4; // used for check spend authority signature + bytes zkproof = 5; + bytes spend_authority_signature = 6; +} + +message ReceiveDescription { + bytes value_commitment = 1; + bytes note_commitment = 2; + bytes epk = 3; // for Encryption + bytes c_enc = 4; // Encryption for incoming, decrypt it with ivk + bytes c_out = 5; // Encryption for audit, decrypt it with ovk + bytes zkproof = 6; +} + +message ShieldedTransferContract { + bytes transparent_from_address = 1; // transparent address + int64 from_amount = 2; + repeated SpendDescription spend_description = 3; + repeated ReceiveDescription receive_description = 4; + bytes binding_signature = 5; + bytes transparent_to_address = 6; // transparent address + int64 to_amount = 7; // the amount to transparent to_address +} diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto new file mode 100644 index 000000000..642264679 --- /dev/null +++ b/src/main/protos/core/contract/smart_contract.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "CreateSmartContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +import "core/Tron.proto"; + +message SmartContract { + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } + + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; + } + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + int64 consume_user_resource_percent = 6; + string name = 7; + int64 origin_energy_limit = 8; + bytes code_hash = 9; + bytes trx_hash = 10; +} + +message CreateSmartContract { + bytes owner_address = 1; + SmartContract new_contract = 2; + int64 call_token_value = 3; + int64 token_id = 4; +} + +message TriggerSmartContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; + int64 call_token_value = 5; + int64 token_id = 6; +} + +message ClearABIContract { + bytes owner_address = 1; + bytes contract_address = 2; +} + +message UpdateSettingContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; +} + +message UpdateEnergyLimitContract { + bytes owner_address = 1; + bytes contract_address = 2; + int64 origin_energy_limit = 3; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/storage_contract.proto b/src/main/protos/core/contract/storage_contract.proto new file mode 100644 index 000000000..666e6b11f --- /dev/null +++ b/src/main/protos/core/contract/storage_contract.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "BuyStorageBytesContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message BuyStorageBytesContract { + bytes owner_address = 1; + int64 bytes = 2; // storage bytes for buy +} + +message BuyStorageContract { + bytes owner_address = 1; + int64 quant = 2; // trx quantity for buy storage (sun) +} + +message SellStorageContract { + bytes owner_address = 1; + int64 storage_bytes = 2; +} + +message UpdateBrokerageContract { + bytes owner_address = 1; + int32 brokerage = 2; // 1 mean 1% +} diff --git a/src/main/protos/core/contract/vote_asset_contract.proto b/src/main/protos/core/contract/vote_asset_contract.proto new file mode 100644 index 000000000..0ca124773 --- /dev/null +++ b/src/main/protos/core/contract/vote_asset_contract.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "VoteAssetContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message VoteAssetContract { + bytes owner_address = 1; + repeated bytes vote_address = 2; + bool support = 3; + int32 count = 5; +} \ No newline at end of file diff --git a/src/main/protos/core/contract/witness_contract.proto b/src/main/protos/core/contract/witness_contract.proto new file mode 100644 index 000000000..acd4292a4 --- /dev/null +++ b/src/main/protos/core/contract/witness_contract.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +//option java_outer_classname = "WitnessCreateContract"; //Specify the class name of the generated Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message WitnessCreateContract { + bytes owner_address = 1; + bytes url = 2; +} + +message WitnessUpdateContract { + bytes owner_address = 1; + bytes update_url = 12; +} + +message VoteWitnessContract { + message Vote { + bytes vote_address = 1; + int64 vote_count = 2; + } + bytes owner_address = 1; + repeated Vote votes = 2; + bool support = 3; +} \ No newline at end of file diff --git a/src/main/protos/core/tron/account.proto b/src/main/protos/core/tron/account.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/block.proto b/src/main/protos/core/tron/block.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/delegated_resource.proto b/src/main/protos/core/tron/delegated_resource.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/p2p.proto b/src/main/protos/core/tron/p2p.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/proposal.proto b/src/main/protos/core/tron/proposal.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/transaction.proto b/src/main/protos/core/tron/transaction.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/vote.proto b/src/main/protos/core/tron/vote.proto new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/protos/core/tron/witness.proto b/src/main/protos/core/tron/witness.proto new file mode 100644 index 000000000..e69de29bb From 802962359dbff6d4abbc47363d32ce45a36a0b04 Mon Sep 17 00:00:00 2001 From: "jason.jiang" Date: Fri, 27 Mar 2020 10:39:06 +0800 Subject: [PATCH 296/445] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e976086f..338e02f0e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Wallet CLI -[gitter](https://gitter.im/tronprotocol/wallet-cli) +[Telegram](https://t.me/troncoredevscommunity) ## Get started From 3496a972c03d047a294c184d3a3c1eb53eb29fd4 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 27 Mar 2020 15:25:38 +0800 Subject: [PATCH 297/445] feat: remove create and cancel order from solidity --- .../org/tron/walletserver/GrpcClient.java | 28 ++++++++----------- src/main/protos/api/api.proto | 6 ---- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 6e4d2e4a5..53f7ee052 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -896,26 +896,23 @@ public NumberMessage getBrokerage(byte[] address) { } public TransactionExtention marketSellAsset(Contract.MarketSellAssetContract request) { - if (blockingStubSolidity != null) { - return blockingStubSolidity.marketSellAsset(request); - } else { - return blockingStubFull.marketSellAsset(request); - } + return blockingStubFull.marketSellAsset(request); } public TransactionExtention marketCancelOrder(Contract.MarketCancelOrderContract request) { - if (blockingStubSolidity != null) { - return blockingStubSolidity.marketCancelOrder(request); - } else { - return blockingStubFull.marketCancelOrder(request); - } + return blockingStubFull.marketCancelOrder(request); } public Optional getMarketOrderByAccount(byte[] address) { ByteString addressBS = ByteString.copyFrom(address); BytesMessage request = BytesMessage.newBuilder().setValue(addressBS).build(); - MarketOrderList marketOrderList = blockingStubFull.getMarketOrderByAccount(request); + MarketOrderList marketOrderList; + if (blockingStubSolidity != null) { + marketOrderList = blockingStubSolidity.getMarketOrderByAccount(request); + } else { + marketOrderList = blockingStubFull.getMarketOrderByAccount(request); + } return Optional.ofNullable(marketOrderList); } @@ -928,7 +925,7 @@ public Optional getMarketPriceByPair(byte[] sellTokenId, byte[] MarketPriceList marketPriceList; if (blockingStubSolidity != null) { - marketPriceList =blockingStubSolidity.getMarketPriceByPair(request); + marketPriceList = blockingStubSolidity.getMarketPriceByPair(request); } else { marketPriceList = blockingStubFull.getMarketPriceByPair(request); } @@ -945,7 +942,7 @@ public Optional getMarketOrderListByPair(byte[] sellTokenId, by MarketOrderList marketOrderList; if (blockingStubSolidity != null) { - marketOrderList =blockingStubSolidity.getMarketOrderListByPair(request); + marketOrderList = blockingStubSolidity.getMarketOrderListByPair(request); } else { marketOrderList = blockingStubFull.getMarketOrderListByPair(request); } @@ -954,10 +951,9 @@ public Optional getMarketOrderListByPair(byte[] sellTokenId, by public Optional getMarketPairList() { - MarketOrderPairList orderPairList; if (blockingStubSolidity != null) { - orderPairList =blockingStubSolidity.getMarketPairList(EmptyMessage.newBuilder().build()); + orderPairList = blockingStubSolidity.getMarketPairList(EmptyMessage.newBuilder().build()); } else { orderPairList = blockingStubFull.getMarketPairList(EmptyMessage.newBuilder().build()); } @@ -969,7 +965,7 @@ public Optional getMarketOrderById(byte[] order) { BytesMessage request = BytesMessage.newBuilder().setValue(orderBytes).build(); MarketOrder orderPair; if (blockingStubSolidity != null) { - orderPair =blockingStubSolidity.getMarketOrderById(request); + orderPair = blockingStubSolidity.getMarketOrderById(request); } else { orderPair = blockingStubFull.getMarketOrderById(request); } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 2fa38959c..cbf5bcb1b 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -872,12 +872,6 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; - rpc MarketSellAsset (MarketSellAssetContract) returns (TransactionExtention) { - } - - rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { - } - rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { } From e30f308aa6cabbf8c59f05a4d333fd9478b85709 Mon Sep 17 00:00:00 2001 From: cathy-lishipu <59823661+cathy-lishipu@users.noreply.github.com> Date: Fri, 27 Mar 2020 16:21:36 +0800 Subject: [PATCH 298/445] modify the link:Wallet-cli Document Summary--Freeze/unfreeze Balance (#349) * modify the link:Wallet-cli Document Summary--Freeze/unfreeze Balance * update file: Adjust structure and increase Brokerage Co-authored-by: Aiden Co-authored-by: ShuYu Wang --- README.md | 100 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 338e02f0e..752013c9b 100644 --- a/README.md +++ b/README.md @@ -53,30 +53,36 @@ soliditynode = { Wallet-cli connect to java-tron via gRPC protocol, which can be deployed locally or remotely. Check **Run a web Wallet** section. We can configure java-tron node IP and port in ``src/main/resources/config.conf``, so that wallet-cli server can successfully talk to java-tron nodes. -## Wallet-cli Document Summary - -The following are a overview of documents including some command explanations and usage examples. Check following links to find your interesting commands: - -- [Freeze/unfreeze Balance](#How-to-freeze/unfreeze-balance) -- [Vote](#How-to-vote) -- [Bandwidth](#How-to-calculate-bandwidth) -- [IssueToke](#How-to-issue-TRC10-tokens) -- [Proposal](#How-to-initiate-a-proposal) -- [HowToTrade](#How-to-trade-on-the-exchange) -- [Multi-signature ](#How-to-use-the-multi-signature-feature-of-wallet-cli) -- [SmartContract](#How-to-use-smart-contract) -- [DelegateResource](#How-to-delegate-resource) -- [TransferToShieldedAddress](#How-to-transfer-to-shielded-address) -- [GetBlock](#How-to-get-block-information) -- [GetTransaction](#How-to-get-transaction-information) -- [GetToken10](#Get-Token10) -- [GetProposal](#Get-proposal-information) -- [AccountCommand](#Account-related-commands) - - [createAccount](#How-to-create-account) - - [createWitness](#How-to-create-witness) -- [WalletCommands](#Wallet-related-commands) -- [CommandLineFlow](#Command-line-operation-flow-example) -- [CommandList](#Command-list) +## Wallet-cli supported command list + +Following is a list of Tron Wallet-cli commands: +For more information on a specific command, just type the command on terminal when you start your Wallet. + +| [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [ApproveProposal](#Approvecancel-the-proposal) | [AssetIssue](#How-to-issue-TRC10-tokens) | +| :---------:|:---------:|:--------: | +| [BackupWallet](#Wallet-related-commands)| [BackupWallet2Base64](#Wallet-related-commands) | [BroadcastTransaction](#Some-others) | +|[ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) |[DeleteProposal](#Cancel-the-created-proposal) | +| [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | [ExchangeInject](#How-to-trade-on-the-exchange) | +| [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | [FreezeBalance](#How-to-delegate-resourcee) | +| [GenerateAddress](#Account-related-commands) | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | +|[GetAccountResource](#Account-related-commands) | [GetAddress](#Account-related-commands) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | +|[GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | +|[GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | +|[GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) |[GetBrokerage](#Brokerage) | +|[GetContract](#How-to-use-smart-contracts) |[GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | +|[GetNextMaintenanceTime](#Some-others) |[GetProposal](#Get-proposal-information) |[GetReward](#Brokerage) | +|[GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[GetTransactionById](#How-to-get-transaction-information) |[GetTransactionCountByBlockNum](#How-to-get-transaction-information) | +|[GetTransactionInfoByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoById](#How-to-get-transaction-information) |[GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli) | +|[ImportWallet](#Wallet-related-commands) |[ImportWalletByBase64](#Wallet-related-commands) |[ListAssetIssue](#Get-Token10) | +|[ListExchanges](#How-to-trade-on-the-exchange) |[ListExchangesPaginated](#How-to-trade-on-the-exchange) |[ListNodes](#Some-others) | +|[ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | +|[Login](#Command-line-operation-flow-example) |[ParticipateAssetIssue](#How-to-issue-TRC10-tokens) |[RegisterWallet](#Wallet-related-commands) | +|[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | +|[UnfreezeAsset](#How-to-issue-TRC10-tokens) |[UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#How-to-issue-TRC10-tokens) | +|[UpdateBrokerage](#Brokerage) |[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | +|[UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | + +Type any one of the listed commands, to display how-to tips. ## How to freeze/unfreeze balance @@ -142,6 +148,52 @@ For example: The final result of the above command was 10 votes for witness1 and 0 vote for witness2. +## Brokerage + +After voting for the witness, you will receive the rewards. The witness has the right to decide the ratio of brokerage. The default ratio is 20%, and the witness can adjust it. + +By default, if a witness is rewarded, he will receive 20% of the whole rewards, and 80% of the rewards will be distributed to his voters. + +### GetBrokerage + +View the ratio of brokerage of the witness. + + > getbrokerage OwnerAddress + +OwnerAddress +> The address of the witness's account, it is a base58check type address. + +### GetReward + +Query unclaimed reward. + + > getreward OwnerAddress + +OwnerAddress +> The address of the voter's account, it is a base58check type address. + +### UpdateBrokerage + +Update the ratio of brokerage, this command is usually used by a witness account. + + > updateBrokerage OwnerAddress brokerage + +OwnerAddress +> The address of the witness's account, it is a base58check type address. + +brokerage +> The ratio of brokerage you want to update to, the limit of it: 0-100. + +For example: + +```console +> getbrokerage TZ7U1WVBRLZ2umjizxqz3XfearEHhXKX7h + +> getreward TNfu3u8jo1LDWerHGbzs2Pv88Biqd85wEY + +> updateBrokerage TZ7U1WVBRLZ2umjizxqz3XfearEHhXKX7h 30 +``` + ## How to calculate bandwidth The bandwidth calculation rule is: From 43bc877e811ec4e346027ea57db9cf0a139062ba Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 27 Mar 2020 16:32:47 +0800 Subject: [PATCH 299/445] typo --- src/main/java/org/tron/walletcli/WalletApiWrapper.java | 2 +- src/main/java/org/tron/walletserver/WalletApi.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 16bdbe2f1..be1658d2d 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1220,7 +1220,7 @@ public Optional getMarketOrderByAccount(byte[] address) { public Optional getMarketPriceByPair( byte[] sellTokenId, byte[] buyTokenId) { - return WalletApi.GetMarketPriceByPair(sellTokenId, buyTokenId); + return WalletApi.getMarketPriceByPair(sellTokenId, buyTokenId); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 35400dfa3..cd6a6e1ee 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2499,7 +2499,7 @@ public static Optional getMarketOrderByAccount(byte[] address) return rpcCli.getMarketOrderByAccount(address); } - public static Optional GetMarketPriceByPair( + public static Optional getMarketPriceByPair( byte[] sellTokenId, byte[] buyTokenId) { return rpcCli.getMarketPriceByPair(sellTokenId, buyTokenId); } From 8eeb260918c52f067c092e3018d7fed29ac30646 Mon Sep 17 00:00:00 2001 From: quan Date: Mon, 30 Mar 2020 17:45:47 +0800 Subject: [PATCH 300/445] add the logic to avoid the thread exits when detecting RuntimeException --- src/main/java/org/tron/walletcli/Client.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index fc52a6df0..bb988370d 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3403,6 +3403,9 @@ private void run() { } catch (EndOfFileException e) { System.out.println("\nBye."); return; + } catch (RuntimeException e) { + System.out.println(cmd + " failed!"); + System.out.println(e.getMessage()); } catch (Exception e) { System.out.println(cmd + " failed!"); System.out.println(e.getMessage()); From 6559135b0f70fbfacc5a84abc7a3ae6bf53b58d2 Mon Sep 17 00:00:00 2001 From: quan Date: Tue, 31 Mar 2020 13:59:07 +0800 Subject: [PATCH 301/445] Fix: getCause throw NPE --- src/main/java/org/tron/walletcli/Client.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index bb988370d..f0f95a864 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3403,13 +3403,12 @@ private void run() { } catch (EndOfFileException e) { System.out.println("\nBye."); return; - } catch (RuntimeException e) { - System.out.println(cmd + " failed!"); - System.out.println(e.getMessage()); } catch (Exception e) { System.out.println(cmd + " failed!"); System.out.println(e.getMessage()); - System.out.println(e.getCause().getMessage()); + if (e.getCause() != null) { + System.out.println(e.getCause().getMessage()); + } // e.printStackTrace(); } } From ff118973761aeb37f849142a32ab9df21b0b2352 Mon Sep 17 00:00:00 2001 From: mattopolitan Date: Thu, 9 Apr 2020 10:39:47 +0800 Subject: [PATCH 302/445] revise TRC10 token related documents --- README.md | 101 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 752013c9b..b6e160895 100644 --- a/README.md +++ b/README.md @@ -254,40 +254,46 @@ Balance = 0 > TransferAsset TWzrEZYtwzkAxXJ8PatVrGuoSNsexejRiM 1000001 10000 ``` -## How to issue TRC10 tokens +## How to issue a TRC10 token -Each account can only issue one TRC10 token. +Each account can only issue **ONE** TRC10 token. ### Issue TRC10 tokens > AssetIssue [OwnerAddress] AssetName AbbrName TotalSupply TrxNum AssetNum Precision StartDate EndDate Description Url FreeNetLimitPerAccount PublicFreeNetLimit FrozenAmount0 FrozenDays0 [...] FrozenAmountN FrozenDaysN -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account.* +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. AssetName > The name of the issued TRC10 token AbbrName -> The Abbreviation of TRC10 tokens +> The abbreviation of TRC10 token TotalSupply -> Total issuing amount = account balance of the issuer at the time of issuance + all the frozen amount, before asset transfer and the issuance. +> ​![Total Supply = Account Balance Of Issuer + AllFrozenTokenAmount](http://latex.codecogs.com/gif.latex?Total%20Supply%20%3D%20Account%20Balance%20Of%20Issuer%20+%20AllFrozenTokenAmount) +> Total Supply: Total Issuing Amount +> Account Balance Of Issuer: At the time of issuance +> All Frozen Token Amount: Before asset transfer and the issuance TrxNum, AssetNum -> These two parameters determine the exchange rate between the issued token and the minimum unit of TRX (Sun) when the token is issued. +> These two parameters determine the exchange rate between the issued token and TRX token in the unit of SUN (0.0000001 TRX), when the token is issued. FreeNetLimitPerAccount -> The maximum amount of bandwidth an account is allowed to use. Token issuers can freeze TRX to obtain bandwidth (TransferAssetContract only) +> The maximum amount of bandwidth each account is allowed to use. Token issuers can freeze TRX to obtain bandwidth (TransferAssetContract only) PublicFreeNetLimit -> The maximum amount of bandwidth issuing accounts are allowed user. Token issuers can freeze REX to obtain bandwidth (TransferAssetContract only) +> The maximum total amount of bandwidth which is allowed to use for all accounts. Token issuers can freeze TRX to obtain bandwidth (TransferAssetContract only) StartDate, EndDate -> The start and end date of token issuance. Within this period time, other users can participate in token issuance.* +> The start and end date of token issuance. Within this period time, other users can participate in token issuance. FrozenAmount0 FrozenDays0 -> Amount and time of token freeze. FrozenAmount0 must be bigger than 0, FrozenDays0 must be bigger than 1 and smaller than 3653. +> Amount and days of token freeze. +> FrozenAmount0: Must be bigger than 0 +> FrozenDays0: Must between 1 and 3653. Example: @@ -330,7 +336,7 @@ Example: > UpdateAsset [OwnerAddress] newLimit newPublicLimit description url -Specific meaning of the parameters is the same with that of AssetIssue. +Specific meaning of the parameters is the same as that of AssetIssue. Example: @@ -369,18 +375,20 @@ Example: } ``` -### TRC10 transfer +### TRC10 token transfer > TransferAsset [OwnerAddress] ToAddress AssertID Amount -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. ToAddress > Address of the target account AssertName -> TRC10 id, 1000001 in the example +> TRC10 token ID +> Example: 1000001 Amount > The number of TRC10 token to transfer @@ -400,23 +408,25 @@ address: TN3zfjYUmMFK3ZsHSsrdJoNRtGkQmZLBLz } ``` -### Participating in the issue of TRC10 +### Participating in the issue of TRC10 token > ParticipateAssetIssue [OwnerAddress] ToAddress AssetID Amount -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. ToAddress -> Account address of Token 10 issuers +> Account address of TRC10 issuers AssertName -> TRC10 ID, 1000001 in the example +> TRC10 token ID +> Example: 1000001 Amount > The number of TRC10 token to transfers -It must happen during the release of Token 10, otherwise an error may occur. +The participation process must happen during the release of TRC10, otherwise an error may occur. Example: @@ -433,43 +443,43 @@ assetV2 } ``` -### unfreeze TRC10 token +### Unfreeze TRC10 token -It must be unfrozen after the freezing period, unfreeze Token10, which has stopped being frozen. +To unfreeze all TRC10 token which are supposed to be unfrozen after the freezing period. > unfreezeasset [OwnerAddress] -## Get Token10 +## How to obtain TRC10 token information ListAssetIssue -> Obtain all of the published Token 10 information +> Obtain all of the published TRC10 token information GetAssetIssueByAccount -> Obtain Token10 information according to the issuing address +> Obtain TRC10 token information based on issuing address GetAssetIssueById -> Obtain Token10 Information based on ID +> Obtain TRC10 token Information based on ID GetAssetIssueByName -> Obtain Token10 Information based on names +> Obtain TRC10 token Information based on names GetAssetIssueListByName -> Get list information on Token10 based on names +> Obtain a list of TRC10 token information based on names -## How to initiate a proposal +## How to operate with proposal -Any proposal-related operations, except for viewing operations, must be performed by committee -members. +Any proposal-related operations, except for viewing operations, must be performed by committee members. ### Initiate a proposal > createProposal [OwnerAddress] id0 value0 ... idN valueN -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. id0 -> The serial number of the parameter. Every parameter of TRON network has a serial number. Go to "http://tronscan.org/#/sr/committee" to see the specifics +> The serial number of the parameter. Every parameter of TRON network has a serial number. Please refer to "http://tronscan.org/#/sr/committee" Value0 > The modified value @@ -499,15 +509,17 @@ In the example, modification No.4 (modifying token issuance fee) costs 1000TRX a The corresponding id is 1. -### Approve/cancel the proposal +### Approve / Disapprove a proposal > approveProposal [OwnerAddress] id is_or_not_add_approval -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. id -> ID of the initiated proposal, 1 in the example +> ID of the initiated proposal +> Example: 1 is_or_not_add_approval > true for approve; false for disapprove @@ -519,12 +531,13 @@ Example: > ApproveProposal 1 false # Cancel the approved proposal ``` -### Cancel the created proposal +### Delete an existed proposal > deleteProposal [OwnerAddress] proposalId proposalId -> ID of the initiated proposal, 1 in the example +> ID of the initiated proposal +> Example: 1 The proposal must be canceled by the supernode that initiated the proposal. @@ -532,10 +545,10 @@ Example: > DeleteProposal 1 -## Get proposal information +### Obtain proposal information ListProposals -> Obtain initiated proposals +> Obtain a list of initiated proposals ListProposalsPaginated > Use the paging mode to obtain the initiated proposal From 4d25981d250e6d44708bc8a6644c5a606a999091 Mon Sep 17 00:00:00 2001 From: mattopolitan Date: Thu, 9 Apr 2020 15:51:48 +0800 Subject: [PATCH 303/445] revise TRC10 token related documents --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b6e160895..26e7488ed 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,14 @@ TotalSupply > All Frozen Token Amount: Before asset transfer and the issuance TrxNum, AssetNum -> These two parameters determine the exchange rate between the issued token and TRX token in the unit of SUN (0.0000001 TRX), when the token is issued. +> ![Exchange Rate = AssetNum / TrxNum](http://latex.codecogs.com/gif.latex?ExchangeRate%20=%20AssetNum%20/%20TrxNum) +> These two parameters determine the exchange rate when the token is issued. +> AssetNum: +> TrxNum: Unit in SUN (0.000001 TRX) + +Precision +> Precision to how many decimal places +> Example: 2 FreeNetLimitPerAccount > The maximum amount of bandwidth each account is allowed to use. Token issuers can freeze TRX to obtain bandwidth (TransferAssetContract only) From 0e91ddc2ec36671a9712fe20e915a7f4dcca3c85 Mon Sep 17 00:00:00 2001 From: mattopolitan Date: Thu, 9 Apr 2020 16:55:00 +0800 Subject: [PATCH 304/445] revise trc10 token related documents --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26e7488ed..dd518f1fe 100644 --- a/README.md +++ b/README.md @@ -279,14 +279,13 @@ TotalSupply > All Frozen Token Amount: Before asset transfer and the issuance TrxNum, AssetNum -> ![Exchange Rate = AssetNum / TrxNum](http://latex.codecogs.com/gif.latex?ExchangeRate%20=%20AssetNum%20/%20TrxNum) +> ![Exchange Rate = TrxNum / AssetNum](http://latex.codecogs.com/gif.latex?ExchangeRate%20=%20TrxNum%20/%20AssetNum) > These two parameters determine the exchange rate when the token is issued. -> AssetNum: +> AssetNum: Unit in base unit of the issued token > TrxNum: Unit in SUN (0.000001 TRX) Precision > Precision to how many decimal places -> Example: 2 FreeNetLimitPerAccount > The maximum amount of bandwidth each account is allowed to use. Token issuers can freeze TRX to obtain bandwidth (TransferAssetContract only) From 357df48e4405d0f5902b96923cf035a35c7f46f8 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Tue, 14 Apr 2020 02:57:10 +0800 Subject: [PATCH 305/445] add command for shieldedTRC20 contract --- .../tron/core/zen/ShieldedTRC20NoteInfo.java | 101 +++ .../tron/core/zen/ShieldedTRC20Wrapper.java | 792 ++++++++++++++++++ src/main/java/org/tron/walletcli/Client.java | 523 ++++++++++++ .../org/tron/walletcli/WalletApiWrapper.java | 541 ++++++++++++ .../org/tron/walletserver/GrpcClient.java | 35 + .../java/org/tron/walletserver/WalletApi.java | 128 +++ src/main/protos/api/api.proto | 103 +++ 7 files changed, 2223 insertions(+) create mode 100644 src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java create mode 100644 src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java b/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java new file mode 100644 index 000000000..48055dcd9 --- /dev/null +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java @@ -0,0 +1,101 @@ +package org.tron.core.zen; + +import io.netty.util.internal.StringUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.tron.common.utils.Base58; +import org.tron.common.utils.ByteArray; +import org.tron.core.exception.CipherException; + +@AllArgsConstructor +public class ShieldedTRC20NoteInfo { + @Setter + @Getter + public long value = 0; + @Setter + @Getter + public String paymentAddress; + @Setter + @Getter + public byte[] r; // 256 + @Setter + @Getter + public String trxId; + @Setter + @Getter + public int index; + @Setter + @Getter + public long noteIndex; + @Setter + @Getter + public long position; + @Setter + @Getter + public byte[] memo; + + public ShieldedTRC20NoteInfo() { + } + + /** + * format shieldedTRC20 note to a string + * + * @return + */ + public String encode(byte[] encryptKey) throws CipherException { + String encodeString = noteIndex + ";"; + encodeString += paymentAddress; + encodeString += ";"; + encodeString += ByteArray.toHexString(r); + encodeString += ";"; + encodeString += trxId; + encodeString += ";"; + encodeString += String.valueOf(value); + encodeString += ";"; + encodeString += String.valueOf(index); + encodeString += ";"; + encodeString += String.valueOf(position); + encodeString += ";"; + String stringMemo = ByteArray.toHexString(memo); + if (StringUtil.isNullOrEmpty(stringMemo)) { + encodeString += "null"; + } else { + encodeString += stringMemo; + } + byte[] chipherText = ZenUtils.aesCtrEncrypt(encodeString.getBytes(), encryptKey); + encodeString = Base58.encode(chipherText); + return encodeString; + } + + /** + * parse string to get shieldedTRC20 note + * + * @param data + * @return + */ + public boolean decode(String data, byte[] encryptKey) throws CipherException { + byte[] chipherText = Base58.decode(data); + byte[] text = ZenUtils.aesCtrDecrypt(chipherText, encryptKey); + data = new String(text); + + String[] sourceStrArray = data.split(";"); + if (sourceStrArray.length != 8) { + System.out.println("len is not right."); + return false; + } + noteIndex = Long.valueOf(sourceStrArray[0]); + paymentAddress = sourceStrArray[1]; + r = ByteArray.fromHexString(sourceStrArray[2]); + trxId = sourceStrArray[3]; + value = Long.valueOf(sourceStrArray[4]); + index = Integer.valueOf(sourceStrArray[5]); + position = Long.valueOf(sourceStrArray[6]); + if (sourceStrArray[7].equals("null")) { + memo = ByteArray.fromHexString(""); + } else { + memo = ByteArray.fromHexString(sourceStrArray[7]); + } + return true; + } +} diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java new file mode 100644 index 000000000..658590a0f --- /dev/null +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -0,0 +1,792 @@ +package org.tron.core.zen; + +import com.google.protobuf.ByteString; +import io.netty.util.internal.StringUtil; +import java.lang.reflect.Field; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.ArrayUtils; +import org.tron.api.GrpcAPI.*; +import org.tron.api.GrpcAPI.DecryptNotes.NoteTx; +import org.tron.api.GrpcAPI.DecryptNotesTRC20; +import org.tron.common.utils.Base58; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.Utils; +import org.tron.core.exception.CipherException; +import org.tron.core.exception.ZksnarkException; +import org.tron.keystore.SKeyCapsule; +import org.tron.keystore.SKeyEncryptor; +import org.tron.keystore.StringUtils; +import org.tron.keystore.WalletUtils; +import org.tron.protos.Protocol.Block; +import org.tron.walletcli.Client; +import org.tron.walletserver.WalletApi; +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +public class ShieldedTRC20Wrapper { + + private static String prefixFolder;// = "WalletShieldedTRC20Contract/"; + private static String trc20ContractAddress; + private static String shieldedTRC20ContarctAddress; + private static String ivkAndNumFileName;// = "/scanblocknumber"; + private static String unspendNoteFileName;// = "/unspendnote"; + private static String spendNoteFileName;// = "/spendnote"; + private static String shieldedAddressFileName;// = "/shieldedaddress"; + private static String shieldedSkeyFileName;// = "/shieldedskey.json"; + private static AtomicLong nodeIndex = new AtomicLong(0L); + private Thread thread; + + private byte[] shieldedSkey; + private static ShieldedTRC20Wrapper instance; + + @Setter + @Getter + Map shieldedAddressInfoMap = new ConcurrentHashMap(); + @Setter + private boolean resetNote = false; + @Getter + @Setter + public Map ivkMapScanBlockNum = new ConcurrentHashMap(); + @Getter + @Setter + public Map utxoMapNote = new ConcurrentHashMap(); + @Getter + @Setter + public List spendUtxoList = new ArrayList<>(); + + private boolean loadShieldedStatus = false; + + private ShieldedTRC20Wrapper() { + thread = new Thread(new scanIvkRunable()); + } + + public static ShieldedTRC20Wrapper getInstance() { + if (instance == null) { + instance = new ShieldedTRC20Wrapper(); + } + return instance; + } + + public static boolean isSetShieldedTRC20WalletPath() { + return !(prefixFolder == null || trc20ContractAddress == null + || shieldedTRC20ContarctAddress == null || ivkAndNumFileName == null + || unspendNoteFileName == null || spendNoteFileName == null + || shieldedAddressFileName == null || shieldedSkeyFileName == null); + } + + public static void setShieldedTRC20WalletPath(String contractAddress, + String shieldedContractAddress) { + trc20ContractAddress = contractAddress; + shieldedTRC20ContarctAddress = shieldedContractAddress; + prefixFolder = "WalletShieldedTRC20Contract/" + + trc20ContractAddress + "_" + shieldedTRC20ContarctAddress; + ivkAndNumFileName = prefixFolder + "/scanblocknumber"; + unspendNoteFileName = prefixFolder + "/unspendnote"; + spendNoteFileName = prefixFolder + "/spendnote"; + shieldedAddressFileName = prefixFolder + "/shieldedaddress"; + shieldedSkeyFileName = prefixFolder + "/shieldedskey.json"; + } + + public String getShieldedTRC20ContractAddress() { + return shieldedTRC20ContarctAddress; + } + + public String getTRC20ContractAddress() { + return trc20ContractAddress; + } + + public boolean ifShieldedTRC20WalletLoaded() { + return loadShieldedStatus; + } + + private void loadWalletFile() throws CipherException { + loadAddressFromFile(); + loadIvkFromFile(); + loadUnSpendNoteFromFile(); + loadSpendNoteFromFile(); + } + + public boolean loadShieldTRC20Wallet() throws CipherException, IOException { + if (ifShieldedTRC20WalletLoaded()) { + return true; + } + + if (!shieldedSkeyFileExist()) { + System.out.println("shieldedTRC20 wallet does not exist."); + return false; + } + + if (ArrayUtils.isEmpty(shieldedSkey)) { + shieldedSkey = loadSkey(); + } + + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + loadWalletFile(); + + if (!thread.isAlive()) { + thread.start(); + } + loadShieldedStatus = true; + + return true; + } + + public class scanIvkRunable implements Runnable { + public void run() { + int count = 24; + for (; ; ) { + try { + scanBlockByIvk(); + updateNoteWhetherSpend(); + } catch (Exception e) { + ++count; + if (count >= 24) { + if (e.getMessage() != null) { + System.out.println(e.getMessage()); + } + System.out.println("Please user command resetShieldedTRC20Note to reset notes!!"); + count = 0; + } + } finally { + try { + //wait for 2.5 seconds + for (int i = 0; i < 5; ++i) { + Thread.sleep(500); + if (resetNote) { + resetShieldedTRC20Note(); + resetNote = false; + count = 0; + System.out.println("Reset shieldedTRC20 note success!"); + } + } + } catch (Exception e) { + } + } + } + } + } + + private void resetShieldedTRC20Note() throws ZksnarkException { + ivkMapScanBlockNum.clear(); + for (Entry entry : getShieldedAddressInfoMap().entrySet()) { + Ivk ivk = new Ivk(); + ivk.setIvk(entry.getValue().getIvk()); + ivk.setAk(entry.getValue().getFullViewingKey().getAk()); + ivk.setNk(entry.getValue().getFullViewingKey().getNk()); + ivkMapScanBlockNum.put(ivk, 0L); + } + + utxoMapNote.clear(); + spendUtxoList.clear(); + + ZenUtils.clearFile(ivkAndNumFileName); + ZenUtils.clearFile(unspendNoteFileName); + ZenUtils.clearFile(spendNoteFileName); + nodeIndex.set(0L); + + updateIvkAndBlockNumFile(); + } + + private void scanBlockByIvk() throws CipherException { + Block block = WalletApi.getBlock(-1); + if (block != null) { + long blockNum = block.getBlockHeader().toBuilder().getRawData().getNumber(); + for (Entry entry : ivkMapScanBlockNum.entrySet()) { + long start = entry.getValue(); + long end = start; + while (end < blockNum) { + if (blockNum - start > 1000) { + end = start + 1000; + } else { + end = blockNum; + } + + IvkDecryptTRC20Parameters.Builder builder = IvkDecryptTRC20Parameters.newBuilder(); + builder.setStartBlockIndex(start); + builder.setEndBlockIndex(end); + builder.setShieldedTRC20ContractAddress( + ByteString.copyFrom( + WalletApi.decodeFromBase58Check( + getShieldedTRC20ContractAddress()))); + builder.setIvk(ByteString.copyFrom(entry.getKey().getIvk())); + builder.setAk(ByteString.copyFrom(entry.getKey().getAk())); + builder.setNk(ByteString.copyFrom(entry.getKey().getNk())); + Optional notes = WalletApi.scanShieldedTRC20NoteByIvk( + builder.build(), false); + if (notes.isPresent()) { + int startNum = utxoMapNote.size(); + for (int i = 0; i < notes.get().getNoteTxsList().size(); ++i) { + DecryptNotesTRC20.NoteTx noteTx = notes.get().getNoteTxsList().get(i); + ShieldedTRC20NoteInfo noteInfo = new ShieldedTRC20NoteInfo(); + noteInfo.setPaymentAddress(noteTx.getNote().getPaymentAddress()); + noteInfo.setR(noteTx.getNote().getRcm().toByteArray()); + noteInfo.setValue(noteTx.getNote().getValue()); + noteInfo.setTrxId(ByteArray.toHexString(noteTx.getTxid().toByteArray())); + noteInfo.setIndex(noteTx.getIndex()); + noteInfo.setNoteIndex(nodeIndex.getAndIncrement()); + noteInfo.setPosition(noteTx.getPosition()); + noteInfo.setMemo(noteTx.getNote().getMemo().toByteArray()); + boolean isSpent = noteTx.getIsSpent(); + if (!isSpent) { + utxoMapNote.put(noteInfo.getNoteIndex(), noteInfo); + } else { + spendUtxoList.add(noteInfo); + saveSpendNoteToFile(noteInfo); + } + } + int endNum = utxoMapNote.size(); + if (endNum > startNum) { + saveUnspendNoteToFile(); + } + } + start = end; + } + ivkMapScanBlockNum.put(entry.getKey(), blockNum); + } + updateIvkAndBlockNumFile(); + } + } + + private void updateNoteWhetherSpend() throws Exception { + for (Entry entry : utxoMapNote.entrySet()) { + ShieldedTRC20NoteInfo noteInfo = entry.getValue(); + + ShieldedAddressInfo addressInfo = + getShieldedAddressInfoMap().get(noteInfo.getPaymentAddress()); + NfTRC20Parameters.Builder builder = NfTRC20Parameters.newBuilder(); + builder.setAk(ByteString.copyFrom(addressInfo.getFullViewingKey().getAk())); + builder.setNk(ByteString.copyFrom(addressInfo.getFullViewingKey().getNk())); + builder.setPosition(noteInfo.getPosition()); + builder.setShieldedTRC20ContractAddress( + ByteString.copyFrom( + WalletApi.decodeFromBase58Check( + getShieldedTRC20ContractAddress()))); + + Note.Builder noteBuild = Note.newBuilder(); + noteBuild.setPaymentAddress(noteInfo.getPaymentAddress()); + noteBuild.setValue(noteInfo.getValue()); + noteBuild.setRcm(ByteString.copyFrom(noteInfo.getR())); + noteBuild.setMemo(ByteString.copyFrom(noteInfo.getMemo())); + builder.setNote(noteBuild.build()); + + Optional result = WalletApi.IsShieldedTRC20ContractNoteSpent( + builder.build(), false); + if (result.isPresent() && result.get().getIsSpent()) { + spendNote(entry.getKey()); + } + } + } + + /** + * set some index note is spend + * + * @param noteIndex + * @return + */ + public boolean spendNote(long noteIndex) throws CipherException { + ShieldedTRC20NoteInfo noteInfo = utxoMapNote.get(noteIndex); + if (noteInfo != null) { + utxoMapNote.remove(noteIndex); + spendUtxoList.add(noteInfo); + + saveUnspendNoteToFile(); + saveSpendNoteToFile(noteInfo); + } else { + System.err.println("Find note failure. index:" + noteIndex); + } + return true; + } + + /** + * save new shieldedTRC20 address and scan block num + * + * @param addressInfo new shieldedTRC20 address + * @return + */ + public boolean addNewShieldedTRC20Address(final ShieldedAddressInfo addressInfo, + boolean newAddress) + throws CipherException, ZksnarkException { + appendAddressInfoToFile(addressInfo); + long blockNum = 0; + if (newAddress) { + try { + Block block = WalletApi.getBlock(-1); + if (block != null) { + blockNum = block.getBlockHeader().toBuilder().getRawData().getNumber(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Ivk ivk = new Ivk(); + ivk.setIvk(addressInfo.getIvk()); + ivk.setAk(addressInfo.getFullViewingKey().getAk()); + ivk.setNk(addressInfo.getFullViewingKey().getNk()); + if (!ivkMapScanBlockNum.containsKey(ivk)) { + ivkMapScanBlockNum.put(ivk, blockNum); + updateIvkAndBlockNum(ivk, blockNum); + } + return true; + } + + /** + * append ivk and block num relationship to file tail + * + * @param ivk + * @param blockNum + * @return + */ + private boolean updateIvkAndBlockNum(Ivk ivk, long blockNum) { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + synchronized (ivkAndNumFileName) { + byte[] key = ByteUtil.merge(ivk.getIvk(), ivk.getAk(), ivk.getNk()); + byte[] value = ByteArray.fromLong(blockNum); + byte[] text = new byte[key.length + value.length]; + System.arraycopy(key, 0, text, 0, key.length); + System.arraycopy(value, 0, text, key.length, value.length); + try { + byte[] cipherText = ZenUtils.aesCtrEncrypt(text, shieldedSkey); + String data = Base58.encode(cipherText); + ZenUtils.appendToFileTail(ivkAndNumFileName, data); + } catch (CipherException e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * update ivk and block num + * + * @return + */ + private boolean updateIvkAndBlockNumFile() { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + synchronized (ivkAndNumFileName) { + ZenUtils.clearFile(ivkAndNumFileName); + for (Entry entry : ivkMapScanBlockNum.entrySet()) { + byte[] key = ByteUtil.merge(entry.getKey().getIvk(), entry.getKey().getAk(), + entry.getKey().getNk()); + byte[] value = ByteArray.fromLong(entry.getValue()); + byte[] text = new byte[key.length + value.length]; + System.arraycopy(key, 0, text, 0, key.length); + System.arraycopy(value, 0, text, key.length, value.length); + try { + byte[] cipherText = ZenUtils.aesCtrEncrypt(text, shieldedSkey); + String date = Base58.encode(cipherText); + ZenUtils.appendToFileTail(ivkAndNumFileName, date); + } catch (CipherException e) { + e.printStackTrace(); + } + } + } + return true; + } + + /** + * load ivk and block num relationship from file + * + * @return + */ + private boolean loadIvkFromFile() { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + ivkMapScanBlockNum.clear(); + if (ZenUtils.checkFileExist(ivkAndNumFileName)) { + List list = ZenUtils.getListFromFile(ivkAndNumFileName); + for (int i = 0; i < list.size(); ++i) { + byte[] cipherText = Base58.decode(list.get(i)); + try { + byte[] text = ZenUtils.aesCtrDecrypt(cipherText, shieldedSkey); + Ivk ivk = new Ivk(); + ivk.setIvk(Arrays.copyOfRange(text, 0, 32)); + ivk.setAk(Arrays.copyOfRange(text, 32, 64)); + ivk.setNk(Arrays.copyOfRange(text, 64, 96)); + byte[] value = Arrays.copyOfRange(text, 96, text.length); + + ivkMapScanBlockNum.put(ivk, ByteArray.toLong(value)); + } catch (CipherException e) { + e.printStackTrace(); + } + } + } + return true; + } + + /** + * get shielded address list + * + * @return + */ + public List getShieldedTRC20AddressList() { + List addressList = new ArrayList<>(); + for (Entry entry : shieldedAddressInfoMap.entrySet()) { + addressList.add(entry.getKey()); + } + return addressList; + } + + /** + * sort by value of UTXO + * + * @return + */ + public List getvalidateSortUtxoList() { + List> list = new ArrayList<>(utxoMapNote.entrySet()); + Collections.sort(list, (Entry o1, + Entry o2) -> { + if (o1.getValue().getValue() < o2.getValue().getValue()) { + return 1; + } else { + return -1; + } + }); + + List utxoList = new ArrayList<>(); + for (Map.Entry entry : list) { + String string = entry.getKey() + " " + entry.getValue().getPaymentAddress() + " "; + string += entry.getValue().getValue(); + string += " "; + string += entry.getValue().getTrxId(); + string += " "; + string += entry.getValue().getIndex(); + string += " "; + string += "UnSpend"; + string += " "; + string += ZenUtils.getMemo(entry.getValue().getMemo()); + utxoList.add(string); + } + return utxoList; + } + + /** + * update unspend note + * + * @return + */ + private boolean saveUnspendNoteToFile() throws CipherException { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + ZenUtils.clearFile(unspendNoteFileName); + for (Entry entry : utxoMapNote.entrySet()) { + String date = entry.getValue().encode(shieldedSkey); + ZenUtils.appendToFileTail(unspendNoteFileName, date); + } + return true; + } + + /** + * load unspend note from file + * + * @return + */ + private boolean loadUnSpendNoteFromFile() throws CipherException { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + utxoMapNote.clear(); + + if (ZenUtils.checkFileExist(unspendNoteFileName)) { + List list = ZenUtils.getListFromFile(unspendNoteFileName); + for (int i = 0; i < list.size(); ++i) { + ShieldedTRC20NoteInfo noteInfo = new ShieldedTRC20NoteInfo(); + noteInfo.decode(list.get(i), shieldedSkey); + utxoMapNote.put(noteInfo.getNoteIndex(), noteInfo); + + if (noteInfo.getNoteIndex() >= nodeIndex.get()) { + nodeIndex.set(noteInfo.getNoteIndex() + 1); + } + } + } + return true; + } + + + /** + * append spend note to file tail + * + * @return + */ + private boolean saveSpendNoteToFile(ShieldedTRC20NoteInfo noteInfo) throws CipherException { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + String data = noteInfo.encode(shieldedSkey); + ZenUtils.appendToFileTail(spendNoteFileName, data); + return true; + } + + /** + * load spend note from file + * + * @return + */ + private boolean loadSpendNoteFromFile() throws CipherException { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + spendUtxoList.clear(); + if (ZenUtils.checkFileExist(spendNoteFileName)) { + List list = ZenUtils.getListFromFile(spendNoteFileName); + for (int i = 0; i < list.size(); ++i) { + ShieldedTRC20NoteInfo noteInfo = new ShieldedTRC20NoteInfo(); + noteInfo.decode(list.get(i), shieldedSkey); + spendUtxoList.add(noteInfo); + + if (noteInfo.getNoteIndex() >= nodeIndex.get()) { + nodeIndex.set(noteInfo.getNoteIndex() + 1); + } + } + } + return true; + } + + /** + * load shielded address from file + * + * @return + */ + private boolean loadAddressFromFile() throws CipherException { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + shieldedAddressInfoMap.clear(); + if (ZenUtils.checkFileExist(shieldedAddressFileName)) { + List addressList = ZenUtils.getListFromFile(shieldedAddressFileName); + for (String addressString : addressList) { + ShieldedAddressInfo addressInfo = new ShieldedAddressInfo(); + if (addressInfo.decode(addressString, shieldedSkey)) { + shieldedAddressInfoMap.put(addressInfo.getAddress(), addressInfo); + } else { + System.out.println("*******************"); + } + } + } + return true; + } + + /** + * put new shielded address to address list + * + * @param addressInfo + * @return + */ + private boolean appendAddressInfoToFile(final ShieldedAddressInfo addressInfo) + throws CipherException { + if (ArrayUtils.isEmpty(shieldedSkey)) { + return false; + } + + String shieldedAddress = addressInfo.getAddress(); + if (!StringUtil.isNullOrEmpty(shieldedAddress) && + !shieldedAddressInfoMap.containsKey(shieldedAddress)) { + String addressString = addressInfo.encode(shieldedSkey); + ZenUtils.appendToFileTail(shieldedAddressFileName, addressString); + + shieldedAddressInfoMap.put(shieldedAddress, addressInfo); + } + return true; + } + + private boolean shieldedSkeyFileExist() { + File file = new File(shieldedSkeyFileName); + return file != null && file.exists(); + } + + private byte[] loadSkey() throws IOException, CipherException { + File file = new File(shieldedSkeyFileName); + SKeyCapsule skey = WalletUtils.loadSkeyFile(file); + + byte[] passwd = null; + System.out.println("Please input your password for shieldedTRC20 wallet."); + for (int i = 6; i > 0; i--) { + char[] password = Utils.inputPassword(false); + passwd = StringUtils.char2Byte(password); + try { + SKeyEncryptor.validPassword(passwd, skey); + break; + } catch (CipherException e) { + passwd = null; + System.out.println(e.getMessage()); + System.out.printf("Left times : %d, please try again.\n", i - 1); + continue; + } + } + if (passwd == null) { + System.out.println("Load skey failed, you can not use operation for shieldedTRC20 " + + "transaction."); + return null; + } + return SKeyEncryptor.decrypt2PrivateBytes(passwd, skey); + } + + private byte[] generateSkey() throws IOException, CipherException { + File file = new File(shieldedSkeyFileName); + byte[] skey = new byte[16]; + new SecureRandom().nextBytes(skey); + + System.out.println("ShieldedTRC20 wallet does not exist, will build it."); + char[] password = Utils.inputPassword2Twice(); + byte[] passwd = StringUtils.char2Byte(password); + + SKeyCapsule sKeyCapsule = SKeyEncryptor.createStandard(passwd, skey); + WalletUtils.generateSkeyFile(sKeyCapsule, file); + return skey; + } + + public void initShieldedTRC20WalletFile() throws IOException, CipherException { + ZenUtils.checkFolderExist(prefixFolder); + + if (ArrayUtils.isEmpty(shieldedSkey)) { + if (shieldedSkeyFileExist()) { + shieldedSkey = loadSkey(); + } else { + shieldedSkey = generateSkey(); + } + loadShieldTRC20Wallet(); + } + } + + public ShieldedAddressInfo backupShieldedTRC20Wallet() throws IOException, + CipherException { + ZenUtils.checkFolderExist(prefixFolder); + + if (shieldedSkeyFileExist()) { + byte[] tempShieldedKey = loadSkey(); + if (!ArrayUtils.isEmpty(tempShieldedKey)) { + + if (ArrayUtils.isEmpty(shieldedSkey)) { + shieldedSkey = tempShieldedKey; + loadShieldTRC20Wallet(); + } + } else { + System.out.println("Invalid password."); + return null; + } + } else { + System.out.println("ShieldedTRC20 wallet does not exist, please build it first."); + return null; + } + + if (shieldedAddressInfoMap.size() <= 0) { + System.out.println("ShieldedTRC20 addresses is empty, please use command to generate " + + "ShieldedTRC20 address."); + return null; + } + + List shieldedAddressInfoList = new ArrayList( + shieldedAddressInfoMap.values()); + for (int i = 0; i < shieldedAddressInfoList.size(); i++) { + System.out.println("The " + (i + 1) + "th shieldedTRC20 address is " + + shieldedAddressInfoList.get(i).getAddress()); + } + + if (shieldedAddressInfoList.size() == 1) { + return shieldedAddressInfoList.get(0); + } else { + System.out.println("Please choose between 1 and " + shieldedAddressInfoList.size()); + Scanner in = new Scanner(System.in); + while (true) { + String input = in.nextLine().trim(); + String num = input.split("\\s+")[0]; + int n; + try { + n = new Integer(num); + } catch (NumberFormatException e) { + System.out.println("Invalid number of " + num); + System.out.println("Please choose again between 1 and " + shieldedAddressInfoList.size()); + continue; + } + if (n < 1 || n > shieldedAddressInfoList.size()) { + System.out.println("Invalid number of " + num); + System.out.println("Please choose again between 1 and " + shieldedAddressInfoList.size()); + continue; + } + return shieldedAddressInfoList.get(n - 1); + } + } + } + + public byte[] importShieldedTRC20Wallet() throws IOException, CipherException { + ZenUtils.checkFolderExist(prefixFolder); + + if (shieldedSkeyFileExist()) { + byte[] tempShieldedKey = loadSkey(); + if (ArrayUtils.isEmpty(tempShieldedKey)) { + System.out.println("Invalid password."); + return null; + } else { + shieldedSkey = tempShieldedKey; + } + } else { + shieldedSkey = generateSkey(); + } + loadShieldTRC20Wallet(); + + byte[] result = null; + System.out.println("Please input shieldedTRC20 wallet hex string. " + + "such as 'sk d',Max retry time:" + 3); + int nTime = 0; + + Scanner in = new Scanner(System.in); + while (nTime < 3) { + String input = in.nextLine().trim(); + String[] array = Client.getCmd(input.trim()); + if (array.length == 2 && Utils.isHexString(array[0]) && Utils.isHexString(array[1])) { + System.out.println("Import shieldedTRC20 wallet hex string is : "); + System.out.println("sk:" + array[0]); + System.out.println("d :" + array[1]); + + byte[] sk = ByteArray.fromHexString(array[0]); + byte[] d = ByteArray.fromHexString(array[1]); + result = new byte[sk.length + d.length]; + System.arraycopy(sk, 0, result, 0, sk.length); + System.arraycopy(d, 0, result, sk.length, d.length); + break; + } + + StringUtils.clear(result); + System.out.println("Invalid shieldedTRC20 wallet hex string, please input again."); + ++nTime; + } + return result; + } + + public class Ivk { + @Setter + @Getter + public byte[] ivk; + @Setter + @Getter + public byte[] ak; + @Setter + @Getter + public byte[] nk; + + public Ivk() { + } + } +} diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 80d320bc2..7239d6a49 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -26,6 +26,8 @@ import org.tron.core.exception.ZksnarkException; import org.tron.core.zen.ShieldedAddressInfo; import org.tron.core.zen.ShieldedNoteInfo; +import org.tron.core.zen.ShieldedTRC20NoteInfo; +import org.tron.core.zen.ShieldedTRC20Wrapper; import org.tron.core.zen.ShieldedWrapper; import org.tron.core.zen.ZenUtils; import org.tron.core.zen.address.KeyIo; @@ -55,6 +57,7 @@ public class Client { "ApproveProposal", "AssetIssue", "BackupShieldedWallet", + "BackupShieldedTRC20Wallet", "BackupWallet", "BackupWallet2Base64", "BroadcastTransaction", @@ -73,6 +76,7 @@ public class Client { "FreezeBalance", "GenerateAddress", "GenerateShieldedAddress", + "GenerateShieldedTRC20Address", "GetAccount", "GetAccountNet", "GetAccountResource", @@ -109,6 +113,7 @@ public class Client { "GetTransactionsToThis", "GetTransactionSignWeight", "ImportShieldedWallet", + "ImportShieldedTRC20Wallet", "ImportWallet", "ImportWalletByBase64", "ListAssetIssue", @@ -118,22 +123,31 @@ public class Client { "ListNodes", "ListShieldedAddress", "ListShieldedNote", + "ListShieldedTRC20Address", + "ListShieldedTRC20Note", "ListProposals", "ListProposalsPaginated", "ListWitnesses", "Login", "Logout", "LoadShieldedWallet", + "LoadShieldedTRC20Wallet", "ParticipateAssetIssue", "RegisterWallet", "ResetShieldedNote", + "ResetShieldedTRC20Note", "ScanAndMarkNotebyAddress", "ScanNotebyIvk", "ScanNotebyOvk", + "ScanShieldedTRC20NoteByIvk", + "ScanShieldedTRC20NoteByOvk", "SendCoin", "SendShieldedCoin", "SendShieldedCoinWithoutAsk", + "SendShieldedTRC20Coin", + "SendShieldedTRC20CoinWithoutAsk", "SetAccountId", + "SetShieldedTRC20ContractAddress", "ShowShieldedAddressInfo", "TransferAsset", "TriggerContract contractAddress method args isHex fee_limit value", @@ -158,6 +172,7 @@ public class Client { "ApproveProposal", "AssetIssue", "BackupShieldedWallet", + "BackupShieldedTRC20Wallet", "BackupWallet", "BackupWallet2Base64", "BroadcastTransaction", @@ -176,6 +191,7 @@ public class Client { "FreezeBalance", "GenerateAddress", "GenerateShieldedAddress", + "GenerateShieldedTRC20Address", "GetAccount", "GetAccountNet", "GetAccountResource", @@ -213,6 +229,7 @@ public class Client { "GetTransactionSignWeight", "Help", "ImportShieldedWallet", + "ImportShieldedTRC20Wallet", "ImportWallet", "ImportWalletByBase64", "ListAssetIssue", @@ -222,22 +239,31 @@ public class Client { "ListNodes", "ListShieldedAddress", "ListShieldedNote", + "ListShieldedTRC20Address", + "ListShieldedTRC20Note", "ListProposals", "ListProposalsPaginated", "ListWitnesses", "Login", "Logout", "LoadShieldedWallet", + "LoadShieldedTRC20Wallet", "ParticipateAssetIssue", "RegisterWallet", "ResetShieldedNote", + "ResetShieldedTRC20Note", "ScanAndMarkNotebyAddress", "ScanNotebyIvk", "ScanNotebyOvk", + "ScanShieldedTRC20NoteByIvk", + "ScanShieldedTRC20NoteByOvk", "SendCoin", "SendShieldedCoin", "SendShieldedCoinWithoutAsk", + "SendShieldedTRC20Coin", + "SendShieldedTRC20CoinWithoutAsk", "SetAccountId", + "SetShieldedTRC20ContractAddress", "ShowShieldedAddressInfo", "TransferAsset", "TriggerContract", @@ -2844,6 +2870,451 @@ private void create2(String[] parameters) { return; } + private void setShieldedTRC20ContractAddress(String[] parameters) { + if (parameters.length == 2) { + byte[] trc20ContractAddress = WalletApi.decodeFromBase58Check(parameters[0]); + byte[] shieldedContractAddress = WalletApi.decodeFromBase58Check(parameters[1]); + if (!(trc20ContractAddress == null || shieldedContractAddress == null)) { + ShieldedTRC20Wrapper.setShieldedTRC20WalletPath(parameters[0], parameters[1]); + } else { + System.out.println("SetShieldedTRC20ContractAddress failed !!! Invalid Address !!!"); + } + } else { + System.out.println("SetShieldedTRC20ContractAddress command needs 2 parameters like:"); + System.out.println("SetShieldedTRC20ContractAddress TRC20ContractAddress" + + " ShieldedContractAddress"); + } + } + + private void backupShieldedTRC20Wallet() throws IOException, CipherException { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + System.out.println("BackupShieldedTRC20Wallet failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + return; + } + + ShieldedAddressInfo addressInfo = ShieldedTRC20Wrapper.getInstance() + .backupShieldedTRC20Wallet(); + if (addressInfo != null) { + System.out.println("sk:" + ByteArray.toHexString(addressInfo.getSk())); + System.out.println("d :" + ByteArray.toHexString(addressInfo.getD().getData())); + System.out.println("BackupShieldedTRC20Wallet successful !!!"); + } else { + System.out.println("BackupShieldedTRC20Wallet failed !!!"); + } + } + + private void generateShieldedTRC20Address(String[] parameters) throws IOException, + CipherException, ZksnarkException { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + System.out.println("GenerateShieldedTRC20Address failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + return; + } + + int addressNum = 1; + if (parameters.length > 0 && !StringUtil.isNullOrEmpty(parameters[0])) { + addressNum = Integer.valueOf(parameters[0]); + } + + ShieldedTRC20Wrapper.getInstance().initShieldedTRC20WalletFile(); + + System.out.println("ShieldedTRC20Address list:"); + for (int i = 0; i < addressNum; ++i) { + Optional addressInfo = walletApiWrapper.getNewShieldedAddress(); + if (addressInfo.isPresent()) { + if (ShieldedTRC20Wrapper.getInstance().addNewShieldedTRC20Address( + addressInfo.get(), true)) { + System.out.println(addressInfo.get().getAddress()); + } + } + } + System.out.println("GenerateShieldedTRC20Address successful !!!"); + } + + private void importShieldedTRC20Wallet() throws CipherException, IOException, ZksnarkException { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + System.out.println("ImportShieldedTRC20Wallet failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + return; + } + + byte[] priKey = ShieldedTRC20Wrapper.getInstance().importShieldedTRC20Wallet(); + if (!ArrayUtils.isEmpty(priKey) && priKey.length == 43) { + byte[] sk = new byte[32]; + byte[] d = new byte[11]; + System.arraycopy(priKey, 0, sk, 0, sk.length); + System.arraycopy(priKey, sk.length, d, 0, d.length); + Optional addressInfo = + walletApiWrapper.getNewShieldedAddressBySkAndD(sk, d); + if (addressInfo.isPresent() && ShieldedTRC20Wrapper.getInstance().addNewShieldedTRC20Address( + addressInfo.get(), false)) { + System.out.println("Import new shieldedTRC20 wallet address is: " + + addressInfo.get().getAddress()); + System.out.println("ImportShieldedTRC20Wallet successfully !!!"); + } else { + System.out.println("ImportShieldedTRC20Wallet failed !!!"); + } + } else { + System.out.println("ImportShieldedTRC20Wallet failed !!!"); + } + } + + private void listShieldedTRC20Address() { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("ListShieldedTRC20Address failed, " + + "please LoadShieldedTRC20Wallet first!"); + return; + } + + List listAddress = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20AddressList(); + System.out.println("ShieldedTRC20Address :"); + for (String address : listAddress) { + System.out.println(address); + } + } else { + System.out.println("ListShieldedTRC20Address failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + } + } + + private void listShieldedTRC20Note(String[] parameters) { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + System.out.println("ListShieldedTRC20Note failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + return; + } + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("ListShieldedNote failed, please LoadShieldedWallet first!"); + return; + } + + int showType = 0; + if (parameters == null || parameters.length <= 0) { + System.out.println("This command will show all the unspent notes. "); + System.out.println( + "If you want to display all notes, including spent notes and unspent notes, " + + "please use command ListShieldedTRC20Note 1 "); + } else { + if (!StringUtil.isNullOrEmpty(parameters[0])) { + showType = Integer.valueOf(parameters[0]); + } + } + + if (showType == 0) { + List utxoList = ShieldedTRC20Wrapper.getInstance().getvalidateSortUtxoList(); + if (utxoList.size() == 0) { + System.out.println("The count of unspent note is 0."); + } else { + System.out.println("The unspent note list is shown below:"); + for (String string : utxoList) { + System.out.println(string); + } + } + } else { + Map noteMap = + ShieldedTRC20Wrapper.getInstance().getUtxoMapNote(); + System.out.println("All notes are shown below:"); + for (Entry entry : noteMap.entrySet()) { + String string = entry.getValue().getPaymentAddress() + " "; + string += entry.getValue().getValue(); + string += " "; + string += entry.getValue().getTrxId(); + string += " "; + string += entry.getValue().getIndex(); + string += " "; + string += entry.getValue().getPosition(); + string += " "; + string += "UnSpent"; + string += " "; + string += ZenUtils.getMemo(entry.getValue().getMemo()); + System.out.println(string); + } + + List noteList = ShieldedTRC20Wrapper.getInstance().getSpendUtxoList(); + for (ShieldedTRC20NoteInfo noteInfo : noteList) { + String string = noteInfo.getPaymentAddress() + " "; + string += noteInfo.getValue(); + string += " "; + string += noteInfo.getTrxId(); + string += " "; + string += noteInfo.getIndex(); + string += " "; + string += noteInfo.getPosition(); + string += " "; + string += "Spent"; + string += " "; + string += ZenUtils.getMemo(noteInfo.getMemo()); + System.out.println(string); + } + } + } + + private void loadShieldedTRC20Wallet() throws CipherException, IOException { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + boolean result = ShieldedTRC20Wrapper.getInstance().loadShieldTRC20Wallet(); + if (result) { + System.out.println("LoadShieldedTRC20Wallet successful !!!"); + } else { + System.out.println("LoadShieldedTRC20Wallet failed !!!"); + } + } else { + System.out.println("LoadShieldedTRC20Wallet failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + } + } + + private void resetShieldedTRC20Note() { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("ResetShieldedTRC20Note failed, please LoadShieldedTRC20Wallet first!"); + return; + } else { + System.out.println("Start to reset shieldedTRC20 notes, please wait ..."); + ShieldedTRC20Wrapper.getInstance().setResetNote(true); + } + } else { + System.out.println("ResetShieldedTRC20Note failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + } + } + + private void scanShieldedTRC20NoteByIvk(String[] parameters) { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + System.out.println("ScanShieldedTRC20NoteByIvk failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + return; + } + if (parameters == null || parameters.length != 5) { + System.out.println("ScanShieldedTRC20NoteByIvk command needs 5 parameters like: "); + System.out.println("ScanShieldedTRC20NoteByIvk ivk ak nk startNum endNum "); + return; + } + + long startNum, endNum; + try { + startNum = Long.parseLong(parameters[3]); + endNum = Long.parseLong(parameters[4]); + } catch (NumberFormatException e) { + System.out.println("Invalid parameter: startNum, endNum."); + return; + } + String addressStr = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress(); + byte[] address = WalletApi.decodeFromBase58Check(addressStr); + walletApiWrapper.scanShieldedTRC20NoteByIvk(parameters[0], + parameters[1], parameters[2], address, startNum, endNum); + } + + private void scanShieldedTRC20NoteByOvk(String[] parameters) { + if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + System.out.println("ScanShieldedTRC20NoteByOvk failed !!!" + + " Please SetShieldedTRC20ContractAddress first !!!"); + return; + } + if (parameters == null || parameters.length != 3) { + System.out.println("ScanShieldedTRC20NoteByOvk command needs 3 parameters like: "); + System.out.println("ScanShieldedTRC20NoteByOvk ovk startNum endNum"); + return; + } + long startNum, endNum; + try { + startNum = Long.parseLong(parameters[1]); + endNum = Long.parseLong(parameters[2]); + } catch (NumberFormatException e) { + System.out.println("Invalid parameter: startNum, endNum."); + return; + } + String addressStr = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress(); + byte[] address = WalletApi.decodeFromBase58Check(addressStr); + walletApiWrapper.scanShieldedTRC20NoteByOvk(parameters[0], startNum, endNum, address); + } + + private void sendShieldedTRC20Coin(String[] parameters) throws IOException, CipherException, + CancelException, ZksnarkException { + if (firstCheck(parameters, "SendShieldedTRC20Coin")) { + String contractAddress = + ShieldedTRC20Wrapper.getInstance().getTRC20ContractAddress(); + String shieldedContractAddress = + ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress(); + boolean result = sendShieldedTRC20CoinNormal(parameters, true, + contractAddress, shieldedContractAddress); + if (result) { + System.out.println("SendShieldedTRC20Coin successfully !!!"); + } else { + System.out.println("SendShieldedTRC20Coin failed !!!"); + } + } + } + + private void sendShieldedTRC20CoinWithoutAsk(String[] parameters) throws IOException, + CipherException, CancelException, ZksnarkException { + if (firstCheck(parameters, "SendShieldedTRC20CoinWithoutAsk")) { + String contractAddress = + ShieldedTRC20Wrapper.getInstance().getTRC20ContractAddress(); + String shieldedContractAddress = + ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress(); + boolean result = sendShieldedTRC20CoinNormal(parameters, false, + contractAddress, shieldedContractAddress); + if (result) { + System.out.println("SendShieldedTRC20CoinWithoutAsk successfully !!!"); + } else { + System.out.println("SendShieldedTRC20CoinWithoutAsk failed !!!"); + } + } + } + + private boolean firstCheck(String[] parameters, String sendCoinType) { + if (parameters == null || parameters.length < 8) { + System.out.println(sendCoinType + " command needs more than 8 parameters like: "); + System.out.println(sendCoinType + " fromAmount shieldedInputNum input1 input2 ... " + + "publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 " + + "shieldedAddress2 amount2 memo2 ... "); + return false; + } + + if (!walletApiWrapper.isLoginState()) { + System.out.println(sendCoinType + " failed, please login wallet first !!"); + return false; + } + + if (isFromShieldedTRC20Note(parameters) + && !ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("SendShieldedTRC20Coin failed, please LoadShieldedTRC20Wallet first !!!"); + return false; + } + return true; + } + + private boolean isFromShieldedTRC20Note(String[] parameters) { + if (Long.valueOf(parameters[1]) > 0) { + return true; + } else { + return false; + } + } + + private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk, + String contractAddress, + String shieldedContractAddress) + throws IOException, CipherException, CancelException, ZksnarkException { + int parameterIndex = 0; + + long fromPublicAmount = Long.valueOf(parameters[parameterIndex++]); + if (fromPublicAmount < 0) { + System.out.println("fromPublicAmount must be non-negative. "); + return false; + } + + int shieldedInputNum = 0; + String amountString = parameters[parameterIndex++]; + if (!StringUtil.isNullOrEmpty(amountString)) { + shieldedInputNum = Integer.valueOf(amountString); + } + + List shieldedInputList = new ArrayList<>(); + String shieldedInputAddress = ""; + for (int i = 0; i < shieldedInputNum; ++i) { + long mapIndex = Long.valueOf(parameters[parameterIndex++]); + ShieldedTRC20NoteInfo noteInfo = + ShieldedTRC20Wrapper.getInstance().getUtxoMapNote().get(mapIndex); + if (noteInfo == null) { + System.out.println("Can't find index " + mapIndex + " note."); + return false; + } + if (i == 0) { + shieldedInputAddress = noteInfo.getPaymentAddress(); + } else { + if (!noteInfo.getPaymentAddress().equals(shieldedInputAddress)) { + System.err.println("All the input notes should be the same address!"); + return false; + } + } + shieldedInputList.add(mapIndex); + } + + String toPublicAddress = parameters[parameterIndex++]; + long toPublicAmount = 0; + if (toPublicAddress.equals("null")) { + toPublicAddress = null; + ++parameterIndex; + } else { + amountString = parameters[parameterIndex++]; + if (!StringUtil.isNullOrEmpty(amountString)) { + toPublicAmount = Long.valueOf(amountString); + if (toPublicAmount <= 0) { + System.out.println("toPublicAmount must be positive. "); + return false; + } + } + } + + int shieldedOutputNum = 0; + amountString = parameters[parameterIndex++]; + if (!StringUtil.isNullOrEmpty(amountString)) { + shieldedOutputNum = Integer.valueOf(amountString); + } + if ((parameters.length - parameterIndex) % 3 != 0) { + System.out.println("Invalid parameter number!"); + return false; + } + + List shieldedOutList = new ArrayList<>(); + for (int i = 0; i < shieldedOutputNum; ++i) { + String shieldedAddress = parameters[parameterIndex++]; + amountString = parameters[parameterIndex++]; + String memoString = parameters[parameterIndex++]; + if (memoString.equals("null")) { + memoString = ""; + } + long shieldedAmount = 0; + if (!StringUtil.isNullOrEmpty(amountString)) { + shieldedAmount = Long.valueOf(amountString); + if (shieldedAmount < 0) { + System.out.println("shieldedAmount must be non-negative. "); + return false; + } + } + + Note.Builder noteBuild = Note.newBuilder(); + noteBuild.setPaymentAddress(shieldedAddress); + noteBuild.setPaymentAddress(shieldedAddress); + noteBuild.setValue(shieldedAmount); + noteBuild.setRcm(ByteString.copyFrom(walletApiWrapper.getRcm())); + noteBuild.setMemo(ByteString.copyFrom(memoString.getBytes())); + shieldedOutList.add(noteBuild.build()); + } + + int shieldedContractType = -1; + if (fromPublicAmount > 0 && shieldedOutList.size() == 1 + && shieldedInputList.size() == 0 && toPublicAmount == 0) { + System.out.println("This is an MINT. "); + shieldedContractType = 0; + } else if (fromPublicAmount == 0 && toPublicAmount == 0 + && shieldedOutList.size() > 0 && shieldedOutList.size() < 3 + && shieldedInputList.size() > 0 && shieldedInputList.size() < 3) { + System.out.println("This is an TRANSFER. "); + shieldedContractType = 1; + } else if (fromPublicAmount == 0 && shieldedOutList.size() == 0 + && shieldedInputList.size() == 1 && toPublicAmount > 0) { + System.out.println("This is an BURN. "); + shieldedContractType = 2; + } else { + System.out.println("The shieldedContractType is not MINT, TRANSFER or BURN"); + return false; + } + + if (withAsk) { + return walletApiWrapper.sendShieldedTRC20Coin(shieldedContractType, fromPublicAmount, + shieldedInputList, shieldedOutList, toPublicAddress, toPublicAmount, contractAddress, + shieldedContractAddress); + } else { + return walletApiWrapper.sendShieldedTRC20CoinWithoutAsk(shieldedContractType, + fromPublicAmount, shieldedInputList, shieldedOutList, toPublicAddress, toPublicAmount, + contractAddress, shieldedContractAddress); + } + } + private void help() { System.out.println("Help: List of Tron Wallet-cli commands"); System.out.println( @@ -3343,6 +3814,58 @@ private void run() { create2(parameters); break; } + case "setshieldedtrc20contractaddress": { + setShieldedTRC20ContractAddress(parameters); + break; + } + case "backupshieldedtrc20wallet": { + backupShieldedTRC20Wallet(); + break; + } + case "generateshieldedtrc20address": { + generateShieldedTRC20Address(parameters); + break; + } + case "importshieldedtrc20wallet": { + importShieldedTRC20Wallet(); + break; + } + case "listshieldedtrc20address": { + listShieldedTRC20Address(); + break; + } + case "listshieldedtrc20note": { + listShieldedTRC20Note(parameters); + break; + } + case "loadshieldedtrc20wallet": { + loadShieldedTRC20Wallet(); + break; + } + // case "markshieldedtrc20note": { + // markShieldedTRC20Note(parameters); + // break; + // } + case "resettrc20contractshieldednote": { + resetShieldedTRC20Note(); + break; + } + case "scanshieldedtrc20notebyivk": { + scanShieldedTRC20NoteByIvk(parameters); + break; + } + case "scanshieldedtrc20notebyovk": { + scanShieldedTRC20NoteByOvk(parameters); + break; + } + case "sendshieldedtrc20coin": { + sendShieldedTRC20Coin(parameters); + break; + } + case "sendshieldedtrc20coinwithoutask": { + sendShieldedTRC20CoinWithoutAsk(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index c844e9345..919396869 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -2,16 +2,23 @@ import com.google.protobuf.ByteString; import io.netty.util.internal.StringUtil; +import java.util.ArrayList; +import java.util.Arrays; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.*; +import org.tron.common.utils.AbiUtil; import org.tron.common.utils.ByteArray; +import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Utils; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; import org.tron.core.exception.ZksnarkException; import org.tron.core.zen.ShieldedAddressInfo; import org.tron.core.zen.ShieldedNoteInfo; +import org.tron.core.zen.ShieldedTRC20NoteInfo; +import org.tron.core.zen.ShieldedTRC20Wrapper; import org.tron.core.zen.ShieldedWrapper; import org.tron.core.zen.ZenUtils; import org.tron.core.zen.address.DiversifierT; @@ -1209,4 +1216,538 @@ public GrpcAPI.NumberMessage getReward(byte[] ownerAddress) { public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } + + public boolean scanShieldedTRC20NoteByIvk(final String ivk, final String ak, final String nk, + byte[] contractAddress, long start, long end) { + GrpcAPI.IvkDecryptTRC20Parameters parameters = IvkDecryptTRC20Parameters + .newBuilder() + .setStartBlockIndex(start) + .setEndBlockIndex(end) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)) + .setIvk(ByteString.copyFrom(ByteArray.fromHexString(ivk))) + .setAk(ByteString.copyFrom(ByteArray.fromHexString(ak))) + .setNk(ByteString.copyFrom(ByteArray.fromHexString(nk))) + .build(); + + Optional notes = WalletApi.scanShieldedTRC20NoteByIvk( + parameters, true); + if (!notes.isPresent()) { + System.out.println("ScanShieldedTRC20NoteByIvk failed !!!"); + } else { + System.out.println(Utils.formatMessageString(notes.get())); +// for (int i = 0; i < decryptNotes.get().getNoteTxsList().size(); i++) { +// NoteTx noteTx = decryptNotes.get().getNoteTxs(i); +// Note note = noteTx.getNote(); +// System.out.println("\ntxid:{}\nindex:{}\naddress:{}\nrcm:{}\nvalue:{}\nmemo:{}", +// ByteArray.toHexString(noteTx.getTxid().toByteArray()), +// noteTx.getIndex(), +// note.getPaymentAddress(), +// ByteArray.toHexString(note.getRcm().toByteArray()), +// note.getValue(), +// ZenUtils.getMemo(note.getMemo().toByteArray())); +// } +// System.out.println("complete."); + } + return true; + } + + public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end, + byte[] contractAddress) { + GrpcAPI.OvkDecryptTRC20Parameters parameters = OvkDecryptTRC20Parameters.newBuilder() + .setStartBlockIndex(start) + .setEndBlockIndex(end) + .setOvk(ByteString.copyFrom(ByteArray.fromHexString(ovk))) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)) + .build(); + + Optional notes = WalletApi.scanShieldedTRC20NoteByOvk(parameters, true); + if (!notes.isPresent()) { + System.out.println("ScanShieldedTRC20NoteByovk failed !!!"); + } else { + System.out.println(Utils.formatMessageString(notes.get())); +// for (int i = 0; i < decryptNotes.get().getNoteTxsList().size(); i++) { +// NoteTx noteTx = decryptNotes.get().getNoteTxs(i); +// Note note = noteTx.getNote(); +// System.out.println("\ntxid:{}\nindex:{}\npaymentAddress:{}\nrcm:{}\nmemo +// :{}\nvalue:{}", +// ByteArray.toHexString(noteTx.getTxid().toByteArray()), +// noteTx.getIndex(), +// note.getPaymentAddress(), +// ByteArray.toHexString(note.getRcm().toByteArray()), +// ZenUtils.getMemo(note.getMemo().toByteArray()), +// note.getValue()); +// } + System.out.println("complete."); + } + return true; + } + + public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, + List shieldedInputList, + List shieldedOutputList, + String toAddress, long toAmount, + String contractAddress, String shieldedContractAddress) + throws CipherException, IOException, CancelException, ZksnarkException { + if (shieldedContractType == 0 && fromAmount != shieldedOutputList.get(0).getValue()) { + System.out.println("MINT: fromPublicAmount must be equal to note value"); + return false; + } + if (shieldedContractType == 2) { + ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() + .get(shieldedInputList.get(0)); + if (toAmount != noteInfo.getValue()) { + System.out.println("BURN: toPublicAmount must be equal to note value"); + return false; + } + } + + PrivateShieldedTRC20Parameters.Builder builder = PrivateShieldedTRC20Parameters.newBuilder(); + builder.setFromAmount(fromAmount); + byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); + builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedContractAddressBytes)); + + if (!StringUtil.isNullOrEmpty(toAddress)) { + byte[] to = WalletApi.decodeFromBase58Check(toAddress); + if (to == null) { + return false; + } + builder.setTransparentToAddress(ByteString.copyFrom(to)); + builder.setToAmount(toAmount); + } + + if (shieldedInputList.size() > 0) { + List rootAndPath = new ArrayList<>(); + for (int i = 0; i < shieldedInputList.size(); i++) { + ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() + .get(shieldedInputList.get(i)); + long position = noteInfo.getPosition(); + rootAndPath.add(getRootAndPath(shieldedContractAddress, position)); + } + if (rootAndPath.isEmpty() || rootAndPath.size() != shieldedInputList.size()) { + System.out.println("Can't get all merkle tree, please check the notes."); + return false; + } + + for (int i = 0; i < shieldedInputList.size(); ++i) { + ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() + .get(shieldedInputList.get(i)); + if (i == 0) { + String shieldedAddress = noteInfo.getPaymentAddress(); + ShieldedAddressInfo addressInfo = + ShieldedTRC20Wrapper.getInstance().getShieldedAddressInfoMap().get(shieldedAddress); + SpendingKey spendingKey = new SpendingKey(addressInfo.getSk()); + ExpandedSpendingKey expandedSpendingKey = spendingKey.expandedSpendingKey(); + + builder.setAsk(ByteString.copyFrom(expandedSpendingKey.getAsk())); + builder.setNsk(ByteString.copyFrom(expandedSpendingKey.getNsk())); + builder.setOvk(ByteString.copyFrom(expandedSpendingKey.getOvk())); + } + + Note.Builder noteBuild = Note.newBuilder(); + noteBuild.setPaymentAddress(noteInfo.getPaymentAddress()); + noteBuild.setValue(noteInfo.getValue()); + noteBuild.setRcm(ByteString.copyFrom(noteInfo.getR())); + noteBuild.setMemo(ByteString.copyFrom(noteInfo.getMemo())); + + System.out.println("address " + noteInfo.getPaymentAddress()); + System.out.println("value " + noteInfo.getValue()); + System.out.println("rcm " + ByteArray.toHexString(noteInfo.getR())); + System.out.println("trxId " + noteInfo.getTrxId()); + System.out.println("index " + noteInfo.getIndex()); + System.out.println("position " + noteInfo.getPosition()); + System.out.println("memo " + ZenUtils.getMemo(noteInfo.getMemo())); + + byte[] eachRootAndPath = rootAndPath.get(i).getBytes(); + byte[] root = Arrays.copyOfRange(eachRootAndPath, 0, 32); + byte[] path = Arrays.copyOfRange(eachRootAndPath, 32, 1056); + SpendNoteTRC20.Builder spendTRC20NoteBuilder = SpendNoteTRC20.newBuilder(); + spendTRC20NoteBuilder.setNote(noteBuild.build()); + spendTRC20NoteBuilder.setAlpha(ByteString.copyFrom(getRcm())); + spendTRC20NoteBuilder.setRoot(ByteString.copyFrom(root)); + spendTRC20NoteBuilder.setPath(ByteString.copyFrom(path)); + spendTRC20NoteBuilder.setPos(noteInfo.getPosition()); + + builder.addShieldedSpends(spendTRC20NoteBuilder.build()); + } + } else { + //@TODO remove randomOvk by sha256.of(privateKey) + byte[] ovk = getRandomOvk(); + if (ovk != null) { + builder.setOvk(ByteString.copyFrom(ovk)); + } else { + System.out.println("Get random ovk from Rpc failure,please check config"); + return false; + } + } + + if (shieldedOutputList.size() > 0) { + for (int i = 0; i < shieldedOutputList.size(); i++) { + builder.addShieldedReceives( + ReceiveNote.newBuilder().setNote(shieldedOutputList.get(i)).build()); + } + } + ShieldedTRC20Parameters parameters = + WalletApi.createShieldedContractParameters(builder.build()); + if (parameters == null) { + System.out.println("CreateShieldedContractParameters failed, please check input data!"); + return false; + } + + if (shieldedContractType == 0) {//MINT + boolean setAllowanceResult = setAllowance(contractAddress, shieldedContractAddress, + fromAmount); + if (!setAllowanceResult) { + System.out.println("SetAllowance failed, please check wallet account!"); + return false; + } + String inputData = encodeMintParamsToHexString(parameters, fromAmount); + boolean mintResult = triggerShieldedContract(shieldedContractAddress, inputData, 0); + if (mintResult) { + System.out.println("MINT succeed!"); + return true; + } else { + System.out.println("Trigger shielded contract MINT failed!!"); + return false; + } + } else if (shieldedContractType == 1) { //TRANSFER + String inputData = encodeTransferParamsToHexString(parameters); + boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 1); + if (transferResult) { + System.out.println("TRANSFER succeed!"); + return true; + } else { + System.out.println("Trigger shielded contract TRANSFER failed!"); + return false; + } + } else if (shieldedContractType == 2) {//BURN + String inputData = encodeBurnParamsToHexString(parameters, toAmount, toAddress); + boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 1); + if (transferResult) { + System.out.println("TRANSFER succeed!"); + return true; + } else { + System.out.println("Trigger shielded contract TRANSFER failed!"); + return false; + } + } else { + System.out.println("Error shieldedContractType!"); + return false; + } + } + + public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fromAmount, + List shieldedInputList, + List shieldedOutputList, + String toAddress, long toAmount, + String contractAddress, + String shieldedContractAddress) + throws CipherException, IOException, CancelException, ZksnarkException { + if (shieldedContractType == 0 && fromAmount != shieldedOutputList.get(0).getValue()) { + System.out.println("MINT: fromPublicAmount must be equal to note value"); + return false; + } + if (shieldedContractType == 2) { + ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() + .get(shieldedInputList.get(0)); + if (toAmount != noteInfo.getValue()) { + System.out.println("BURN: toPublicAmount must be equal to note value"); + return false; + } + } + + PrivateShieldedTRC20ParametersWithoutAsk.Builder builder = + PrivateShieldedTRC20ParametersWithoutAsk.newBuilder(); + builder.setFromAmount(fromAmount); + byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); + builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedContractAddressBytes)); + + if (!StringUtil.isNullOrEmpty(toAddress)) { + byte[] to = WalletApi.decodeFromBase58Check(toAddress); + if (to == null) { + return false; + } + builder.setTransparentToAddress(ByteString.copyFrom(to)); + builder.setToAmount(toAmount); + } + + byte[] ask = new byte[32]; + if (shieldedInputList.size() > 0) { + List rootAndPath = new ArrayList<>(); + for (int i = 0; i < shieldedInputList.size(); i++) { + ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() + .get(shieldedInputList.get(i)); + long position = noteInfo.getPosition(); + rootAndPath.add(getRootAndPath(shieldedContractAddress, position)); + } + if (rootAndPath.isEmpty() || rootAndPath.size() != shieldedInputList.size()) { + System.out.println("Can't get all merkle tree, please check the notes."); + return false; + } + + for (int i = 0; i < shieldedInputList.size(); i++) { + ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() + .get(shieldedInputList.get(i)); + if (i == 0) { + String shieldAddress = noteInfo.getPaymentAddress(); + ShieldedAddressInfo addressInfo = + ShieldedTRC20Wrapper.getInstance().getShieldedAddressInfoMap().get(shieldAddress); + SpendingKey spendingKey = new SpendingKey(addressInfo.getSk()); + ExpandedSpendingKey expandedSpendingKey = spendingKey.expandedSpendingKey(); + + System.arraycopy(expandedSpendingKey.getAsk(), 0, ask, 0, 32); + builder.setAk(ByteString.copyFrom( + ExpandedSpendingKey.getAkFromAsk(expandedSpendingKey.getAsk()))); + builder.setNsk(ByteString.copyFrom(expandedSpendingKey.getNsk())); + builder.setOvk(ByteString.copyFrom(expandedSpendingKey.getOvk())); + } + + Note.Builder noteBuild = Note.newBuilder(); + noteBuild.setPaymentAddress(noteInfo.getPaymentAddress()); + noteBuild.setValue(noteInfo.getValue()); + noteBuild.setRcm(ByteString.copyFrom(noteInfo.getR())); + noteBuild.setMemo(ByteString.copyFrom(noteInfo.getMemo())); + + System.out.println("address " + noteInfo.getPaymentAddress()); + System.out.println("value " + noteInfo.getValue()); + System.out.println("rcm " + ByteArray.toHexString(noteInfo.getR())); + System.out.println("trxId " + noteInfo.getTrxId()); + System.out.println("index " + noteInfo.getIndex()); + System.out.println("position " + noteInfo.getPosition()); + System.out.println("memo " + ZenUtils.getMemo(noteInfo.getMemo())); + + byte[] eachRootAndPath = rootAndPath.get(i).getBytes(); + byte[] root = Arrays.copyOfRange(eachRootAndPath, 0, 32); + byte[] path = Arrays.copyOfRange(eachRootAndPath, 32, 1056); + SpendNoteTRC20.Builder spendTRC20NoteBuilder = SpendNoteTRC20.newBuilder(); + spendTRC20NoteBuilder.setNote(noteBuild.build()); + spendTRC20NoteBuilder.setAlpha(ByteString.copyFrom(getRcm())); + spendTRC20NoteBuilder.setRoot(ByteString.copyFrom(root)); + spendTRC20NoteBuilder.setPath(ByteString.copyFrom(path)); + spendTRC20NoteBuilder.setPos(noteInfo.getPosition()); + + builder.addShieldedSpends(spendTRC20NoteBuilder.build()); + } + } else { + //@TODO remove randomOvk by sha256.of(privateKey) + byte[] ovk = getRandomOvk(); + if (ovk != null) { + builder.setOvk(ByteString.copyFrom(ovk)); + } else { + System.out.println("Get random ovk from Rpc failure,please check config"); + return false; + } + } + + if (shieldedOutputList.size() > 0) { + for (int i = 0; i < shieldedOutputList.size(); ++i) { + builder.addShieldedReceives( + ReceiveNote.newBuilder().setNote(shieldedOutputList.get(i)).build()); + } + } + + ShieldedTRC20Parameters parameters = + WalletApi.createShieldedContractParametersWithoutAsk(builder.build(), ask); + if (parameters == null) { + System.out.println("CreateShieldedContractParametersWithoutAsk failed," + + " please check input data!"); + return false; + } + + if (shieldedContractType == 0) {//MINT + boolean setAllowanceResult = setAllowance(contractAddress, shieldedContractAddress, + fromAmount); + if (!setAllowanceResult) { + System.out.println("SetAllowance failed, please check wallet account!"); + return false; + } + String inputData = encodeMintParamsToHexString(parameters, fromAmount); + boolean mintResult = triggerShieldedContract(shieldedContractAddress, inputData, 0); + if (mintResult) { + System.out.println("MINT succeed!"); + return true; + } else { + System.out.println("Trigger shielded contract MINT failed!!"); + return false; + } + } else if (shieldedContractType == 1) { //TRANSFER + String inputData = encodeTransferParamsToHexString(parameters); + boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 1); + if (transferResult) { + System.out.println("TRANSFER succeed!"); + return true; + } else { + System.out.println("Trigger shielded contract TRANSFER failed!"); + return false; + } + } else if (shieldedContractType == 2) {//BURN + String inputData = encodeBurnParamsToHexString(parameters, toAmount, toAddress); + boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); + if (transferResult) { + System.out.println("TRANSFER succeed!"); + return true; + } else { + System.out.println("Trigger shielded contract TRANSFER failed!"); + return false; + } + } else { + System.out.println("Error shieldedContractType!"); + return false; + } + } + + public String getRootAndPath(String address, long position) { + byte[] shieldedContractAddress = WalletApi.decodeFromBase58Check(address); + String methodStr = "getPath(uint256)"; + byte[] indexBytes = ByteArray.fromLong(position); + String argsStr = ByteArray.toHexString(indexBytes); + argsStr = "000000000000000000000000000000000000000000000000" + argsStr; + byte[] input = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, true)); + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: getRootAndPath failed, Please login wallet first !!"); + return null; + } + return wallet.getRootAndPath(shieldedContractAddress, input); + } + + public boolean setAllowance(String contractAddress, String shieldedContractAddress, long value) + throws CipherException, IOException, CancelException { + byte[] contractAddressBytes = WalletApi.decodeFromBase58Check(contractAddress); + byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); + String methodStr = "approve(address,uint256)"; + byte[] mergedBytes = ByteUtil.merge(new byte[11], shieldedContractAddressBytes, + longTo32Bytes(value)); + String argsStr = ByteArray.toHexString(mergedBytes); + byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, true)); + byte[] ownerAddress = wallet.getAddress(); + + return callContract(ownerAddress, contractAddressBytes, 0, inputData, 1000_000_000L, + 0, "", false); + } + + public boolean triggerShieldedContract(String contractAddress, String data, + int shieldedContractType) + throws CipherException, IOException, CancelException { + byte[] contractAddressBytes = WalletApi.decodeFromBase58Check(contractAddress); + String methodStr; + if (shieldedContractType == 0) { + methodStr = "mint(uint64,bytes32[9],bytes32[2],bytes32[21])"; + } else if (shieldedContractType == 1) { + methodStr = "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])"; + } else if (shieldedContractType == 2) { + methodStr = "burn(bytes32[10],bytes32[2],uint64,bytes32[2],uint256)"; + } else { + System.out.println("shieldedContractType should be 0, 1 or 2. "); + return false; + } + byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, data, true)); + byte[] ownerAddress = wallet.getAddress(); + + return callContract(ownerAddress, contractAddressBytes, 0, inputData, 1000_000_000L, + 0, "", false); + } + + public String encodeMintParamsToHexString(ShieldedTRC20Parameters parameters, + long value) { + byte[] mergedBytes; + Contract.ReceiveDescription revDesc = parameters.getReceiveDescription(0); + mergedBytes = ByteUtil.merge( + longTo32Bytes(value), + revDesc.getNoteCommitment().toByteArray(), + revDesc.getValueCommitment().toByteArray(), + revDesc.getEpk().toByteArray(), + revDesc.getZkproof().toByteArray(), + parameters.getBindingSignature().toByteArray(), + revDesc.getCEnc().toByteArray(), + revDesc.getCOut().toByteArray(), + new byte[12] + ); + return ByteArray.toHexString(mergedBytes); + } + + public String encodeTransferParamsToHexString(ShieldedTRC20Parameters parameters) { + byte[] input = new byte[0]; + byte[] spendAuthSig = new byte[0]; + byte[] output = new byte[0]; + byte[] c = new byte[0]; + byte[] bindingSig; + byte[] mergedBytes; + List spendDescs = parameters.getSpendDescriptionList(); + for (Contract.SpendDescription spendDesc : spendDescs) { + input = ByteUtil.merge(input, + spendDesc.getNullifier().toByteArray(), + spendDesc.getAnchor().toByteArray(), + spendDesc.getValueCommitment().toByteArray(), + spendDesc.getRk().toByteArray(), + spendDesc.getZkproof().toByteArray() + ); + spendAuthSig = ByteUtil.merge( + spendAuthSig, spendDesc.getSpendAuthoritySignature().toByteArray()); + } + byte[] inputOffsetbytes = longTo32Bytes(192); + long spendCount = spendDescs.size(); + byte[] spendCountBytes = longTo32Bytes(spendCount); + byte[] authOffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount); + List recvDescs = parameters.getReceiveDescriptionList(); + for (Contract.ReceiveDescription recvDesc : recvDescs) { + output = ByteUtil.merge(output, + recvDesc.getNoteCommitment().toByteArray(), + recvDesc.getValueCommitment().toByteArray(), + recvDesc.getEpk().toByteArray(), + recvDesc.getZkproof().toByteArray() + ); + c = ByteUtil.merge(c, + recvDesc.getCEnc().toByteArray(), + recvDesc.getCOut().toByteArray(), + new byte[12] + ); + } + long recvCount = recvDescs.size(); + byte[] recvCountBytes = longTo32Bytes(recvCount); + byte[] outputOffsetbytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount); + byte[] coffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount + 32 + + 288 * recvCount); + bindingSig = parameters.getBindingSignature().toByteArray(); + mergedBytes = ByteUtil.merge(inputOffsetbytes, + authOffsetBytes, + outputOffsetbytes, + bindingSig, + coffsetBytes, + spendCountBytes, + input, + spendCountBytes, + spendAuthSig, + recvCountBytes, + output, + recvCountBytes, + c + ); + return ByteArray.toHexString(mergedBytes); + } + + public String encodeBurnParamsToHexString(ShieldedTRC20Parameters parameters, long value, + String transparentToAddress) { + byte[] mergedBytes; + byte[] payTo = new byte[32]; + byte[] transparentToAddressBytes = WalletApi.decodeFromBase58Check(transparentToAddress); + System.arraycopy(transparentToAddressBytes, 0, payTo, 11, 21); + Contract.SpendDescription spendDesc = parameters.getSpendDescription(0); + mergedBytes = ByteUtil.merge( + spendDesc.getNullifier().toByteArray(), + spendDesc.getAnchor().toByteArray(), + spendDesc.getValueCommitment().toByteArray(), + spendDesc.getRk().toByteArray(), + spendDesc.getZkproof().toByteArray(), + spendDesc.getSpendAuthoritySignature().toByteArray(), + longTo32Bytes(value), + parameters.getBindingSignature().toByteArray(), + payTo + ); + return ByteArray.toHexString(mergedBytes); + } + + public byte[] longTo32Bytes(long value) { + byte[] longBytes = ByteArray.fromLong(value); + byte[] zeroBytes = new byte[24]; + return ByteUtil.merge(zeroBytes, longBytes); + } } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index f04066b8c..a4c8c2fd1 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -894,4 +894,39 @@ public NumberMessage getBrokerage(byte[] address) { return blockingStubFull.getBrokerageInfo(bytesMessage); } } + + public DecryptNotesTRC20 scanShieldedTRC20NoteByIvk(IvkDecryptTRC20Parameters parameters) { + if (blockingStubSolidity != null) { + return blockingStubSolidity.scanShieldedTRC20NotesbyIvk(parameters); + } else { + return blockingStubFull.scanShieldedTRC20NotesbyIvk(parameters); + } + } + + public DecryptNotesTRC20 scanShieldedTRC20NoteByOvk(OvkDecryptTRC20Parameters parameters) { + if (blockingStubSolidity != null) { + return blockingStubSolidity.scanShieldedTRC20NotesbyOvk(parameters); + } else { + return blockingStubFull.scanShieldedTRC20NotesbyOvk(parameters); + } + } + + public ShieldedTRC20Parameters createShieldedContractParameters( + PrivateShieldedTRC20Parameters parameters) { + return blockingStubFull.createShieldedContractParameters(parameters); + } + + public ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( + PrivateShieldedTRC20ParametersWithoutAsk parameters) { + return blockingStubFull.createShieldedContractParametersWithoutAsk(parameters); + } + + public NullifierResult IsShieldedTRC20ContractNoteSpent(NfTRC20Parameters prameters) { + if (blockingStubSolidity != null) { + return blockingStubSolidity.isShieldedTRC20ContractNoteSpent(prameters); + } else { + return blockingStubFull.isShieldedTRC20ContractNoteSpent(prameters); + } + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index e81b0a0a6..8e024962c 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -24,6 +24,7 @@ import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; +import org.tron.common.utils.ByteUtil; import org.tron.common.utils.TransactionUtils; import org.tron.common.utils.Utils; import org.tron.core.config.Configuration; @@ -2456,4 +2457,131 @@ public static GrpcAPI.NumberMessage getReward(byte[] owner) { public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + + public static Optional scanShieldedTRC20NoteByIvk( + IvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { + if (showErrorMsg) { + try { + return Optional.of(rpcCli.scanShieldedTRC20NoteByIvk(parameters)); + } catch (Exception e) { + if (showErrorMsg) { + Status status = Status.fromThrowable(e); + System.out.println("ScanShieldedTRC20NoteByIvk failed,error " + status.getDescription()); + } + } + } else { + return Optional.of(rpcCli.scanShieldedTRC20NoteByIvk(parameters)); + } + return Optional.empty(); + } + + public static Optional scanShieldedTRC20NoteByOvk( + OvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { + if (showErrorMsg) { + try { + return Optional.of(rpcCli.scanShieldedTRC20NoteByOvk(parameters)); + } catch (Exception e) { + if (showErrorMsg) { + Status status = Status.fromThrowable(e); + System.out.println("scanNoteByOvk failed,error " + status.getDescription()); + } + } + } else { + return Optional.of(rpcCli.scanShieldedTRC20NoteByOvk(parameters)); + } + return Optional.empty(); + } + + public String getRootAndPath(byte[] contractAddress, byte[] data) { + byte[] address = getAddress(); + Contract.TriggerSmartContract triggerContract = + triggerCallContract(address, contractAddress, 0, data, 0, ""); + TransactionExtention transactionExtention = rpcCli.triggerConstantContract(triggerContract); + + if (transactionExtention == null || !transactionExtention.getResult().getResult()) { + System.out.println("getPath failed!"); + System.out.println("Code = " + transactionExtention.getResult().getCode()); + System.out.println( + "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + return null; + } + + Transaction transaction = transactionExtention.getTransaction(); + if (transaction.getRetCount() != 0 + && transactionExtention.getConstantResult(0) != null + && transactionExtention.getResult() != null) { + byte[] contractResult = transactionExtention.getConstantResult(0).toByteArray(); + byte[] result = ByteArray.subArray(contractResult, 0, 1056); + return ByteArray.toHexString(result); + } else { + return null; + } + } + + // public static boolean createShieldedContractParameters( + // PrivateShieldedTRC20Parameters parameters) { + // return rpcCli.createShieldedContractParameters(parameters); + // } + // + // public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( + // PrivateShieldedTRC20ParametersWithoutAsk parameters) { + // return rpcCli.createShieldedContractParametersWithoutAsk(parameters); + // } + + public static ShieldedTRC20Parameters createShieldedContractParameters( + PrivateShieldedTRC20Parameters privateParameters) { + try { + return rpcCli.createShieldedContractParameters(privateParameters); + } catch (Exception e) { + Status status = Status.fromThrowable(e); + System.out.println("createShieldedContractParameters failed,error " + status.getDescription()); + } + return null; + } + + public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( + PrivateShieldedTRC20ParametersWithoutAsk privateParameters, byte[] ask) { + ShieldedTRC20Parameters parameters = + rpcCli.createShieldedContractParametersWithoutAsk(privateParameters); + if (parameters == null) { + System.out.println("createShieldedContractParametersWithoutAsk failed!"); + return null; + } + + ByteString messageHash = parameters.getMessageHash(); + List spendDescList = parameters.getSpendDescriptionList(); + ShieldedTRC20Parameters.Builder newBuilder = + ShieldedTRC20Parameters.newBuilder().mergeFrom(parameters); + for (int i = 0; i < spendDescList.size(); i++) { + SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); + builder.setAsk(ByteString.copyFrom(ask)); + builder.setTxHash(messageHash); + builder.setAlpha(privateParameters.getShieldedSpends(i).getAlpha()); + + BytesMessage authSig = rpcCli.createSpendAuthSig(builder.build()); + newBuilder.getSpendDescriptionBuilder(i) + .setSpendAuthoritySignature( + ByteString.copyFrom( + authSig.getValue().toByteArray())); + } + return newBuilder.build(); + } + + public static Optional IsShieldedTRC20ContractNoteSpent( + NfTRC20Parameters parameters, boolean showErrorMsg) { + if (showErrorMsg) { + try { + return Optional.of(rpcCli.IsShieldedTRC20ContractNoteSpent(parameters)); + } catch (Exception e) { + if (showErrorMsg) { + Status status = Status.fromThrowable(e); + System.out.println("IsShieldedTRC20ContractNoteSpent failed,error " + + status.getDescription()); + } + } + } else { + return Optional.of(rpcCli.IsShieldedTRC20ContractNoteSpent(parameters)); + } + return Optional.empty(); + } } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6172ffcfb..c1f2f32a3 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -698,6 +698,22 @@ service Wallet { }; // end for shiededTransaction + // for shieldedTRC20Contract + rpc CreateShieldedContractParameters (PrivateShieldedTRC20Parameters) returns (ShieldedTRC20Parameters) { + }; + + rpc CreateShieldedContractParametersWithoutAsk (PrivateShieldedTRC20ParametersWithoutAsk) returns (ShieldedTRC20Parameters) { + }; + + rpc ScanShieldedTRC20NotesbyIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc ScanShieldedTRC20NotesbyOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { + }; + // end for shieldedTRC20Contract }; service WalletSolidity { @@ -850,6 +866,15 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; + + rpc ScanShieldedTRC20NotesbyIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc ScanShieldedTRC20NotesbyOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { + }; }; service WalletExtension { @@ -1288,3 +1313,81 @@ message SpendResult { bool result = 1; string message = 2; } + +message SpendNoteTRC20 { + Note note = 1; + bytes alpha = 2; + bytes root = 3; + bytes path = 4; + int64 pos = 5; +} + +message PrivateShieldedTRC20Parameters { + bytes ask = 1; + bytes nsk = 2; + bytes ovk = 3; + int64 from_amount = 4; + repeated SpendNoteTRC20 shielded_spends = 5; + repeated ReceiveNote shielded_receives = 6; + bytes transparent_to_address = 7; + int64 to_amount = 8; + bytes shielded_TRC20_contract_address = 9; +} + +message PrivateShieldedTRC20ParametersWithoutAsk { + bytes ak = 1; + bytes nsk = 2; + bytes ovk = 3; + int64 from_amount = 4; + repeated SpendNoteTRC20 shielded_spends = 5; + repeated ReceiveNote shielded_receives = 6; + bytes transparent_to_address = 7; + int64 to_amount = 8; + bytes shielded_TRC20_contract_address = 9; +} + +message ShieldedTRC20Parameters { + repeated SpendDescription spend_description = 1; + repeated ReceiveDescription receive_description = 2; + bytes binding_signature = 3; + bytes message_hash = 4; +} + +message IvkDecryptTRC20Parameters { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes shielded_TRC20_contract_address = 3; + bytes ivk = 4; + bytes ak = 5; + bytes nk = 6; +} + +message OvkDecryptTRC20Parameters { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes ovk = 3; + bytes shielded_TRC20_contract_address = 4; +} + +message DecryptNotesTRC20 { + message NoteTx { + Note note = 1; + int64 position = 2; + bool isSpent = 3; + bytes txid = 4; + int32 index = 5; //the index of note in txid + } + repeated NoteTx noteTxs = 1; +} + +message NfTRC20Parameters { + Note note = 1; + bytes ak = 2; + bytes nk = 3; + int64 position = 4; + bytes shielded_TRC20_contract_address = 5; +} + +message NullifierResult { + bool isSpent = 1; +} \ No newline at end of file From a2a74b69b1bee0b2cd146cb8ce2750a8c8e3fb42 Mon Sep 17 00:00:00 2001 From: mattopolitan Date: Tue, 14 Apr 2020 14:32:13 +0800 Subject: [PATCH 306/445] revise trc10 token related documents --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dd518f1fe..4db864f7e 100644 --- a/README.md +++ b/README.md @@ -273,14 +273,14 @@ AbbrName > The abbreviation of TRC10 token TotalSupply -> ​![Total Supply = Account Balance Of Issuer + AllFrozenTokenAmount](http://latex.codecogs.com/gif.latex?Total%20Supply%20%3D%20Account%20Balance%20Of%20Issuer%20+%20AllFrozenTokenAmount) -> Total Supply: Total Issuing Amount +> ​TotalSupply = Account Balance of Issuer + All Frozen Token Amount +> TotalSupply: Total Issuing Amount > Account Balance Of Issuer: At the time of issuance > All Frozen Token Amount: Before asset transfer and the issuance TrxNum, AssetNum -> ![Exchange Rate = TrxNum / AssetNum](http://latex.codecogs.com/gif.latex?ExchangeRate%20=%20TrxNum%20/%20AssetNum) -> These two parameters determine the exchange rate when the token is issued. +> These two parameters determine the exchange rate when the token is issued. +> Exchange Rate = TrxNum / AssetNum > AssetNum: Unit in base unit of the issued token > TrxNum: Unit in SUN (0.000001 TRX) From 9b7e3a935d38875a3c205e8d7a4a224f05f6fffd Mon Sep 17 00:00:00 2001 From: mattopolitan Date: Tue, 14 Apr 2020 16:08:42 +0800 Subject: [PATCH 307/445] revise trade on exchange documents --- README.md | 82 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 752013c9b..bfb357522 100644 --- a/README.md +++ b/README.md @@ -65,23 +65,23 @@ For more information on a specific command, just type the command on terminal wh | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | [ExchangeInject](#How-to-trade-on-the-exchange) | | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | [FreezeBalance](#How-to-delegate-resourcee) | | [GenerateAddress](#Account-related-commands) | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | -|[GetAccountResource](#Account-related-commands) | [GetAddress](#Account-related-commands) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | -|[GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | -|[GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | +|[GetAccountResource](#Account-related-commands) | [GetAddress](#Account-related-commands) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | +|[GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | +|[GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | |[GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) |[GetBrokerage](#Brokerage) | -|[GetContract](#How-to-use-smart-contracts) |[GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | +|[GetContract](#How-to-use-smart-contracts) |[GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | |[GetNextMaintenanceTime](#Some-others) |[GetProposal](#Get-proposal-information) |[GetReward](#Brokerage) | |[GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[GetTransactionById](#How-to-get-transaction-information) |[GetTransactionCountByBlockNum](#How-to-get-transaction-information) | |[GetTransactionInfoByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoById](#How-to-get-transaction-information) |[GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli) | |[ImportWallet](#Wallet-related-commands) |[ImportWalletByBase64](#Wallet-related-commands) |[ListAssetIssue](#Get-Token10) | |[ListExchanges](#How-to-trade-on-the-exchange) |[ListExchangesPaginated](#How-to-trade-on-the-exchange) |[ListNodes](#Some-others) | -|[ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | +|[ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | |[Login](#Command-line-operation-flow-example) |[ParticipateAssetIssue](#How-to-issue-TRC10-tokens) |[RegisterWallet](#Wallet-related-commands) | |[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | |[UnfreezeAsset](#How-to-issue-TRC10-tokens) |[UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#How-to-issue-TRC10-tokens) | -|[UpdateBrokerage](#Brokerage) |[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | +|[UpdateBrokerage](#Brokerage) |[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | |[UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | - + Type any one of the listed commands, to display how-to tips. ## How to freeze/unfreeze balance @@ -168,7 +168,7 @@ OwnerAddress Query unclaimed reward. > getreward OwnerAddress - + OwnerAddress > The address of the voter's account, it is a base58check type address. @@ -177,7 +177,7 @@ OwnerAddress Update the ratio of brokerage, this command is usually used by a witness account. > updateBrokerage OwnerAddress brokerage - + OwnerAddress > The address of the witness's account, it is a base58check type address. @@ -545,55 +545,61 @@ GetProposal ## How to trade on the exchange -The trading and price fluctuations of trading pairs are in accordance with the Bancor Agreement, -which can be found in TRON's related documents. +The trading and price fluctuations of trading pairs are in accordance with the [Bancor Agreement](https://storage.googleapis.com/website-bancor/2018/04/01ba8253-bancor_protocol_whitepaper_en.pdf), +which can be found in TRON's [related documents](https://developers.tron.network/docs/tronscan-dex). ### Create a trading pair > exchangeCreate [OwnerAddress] first_token_id first_token_balance second_token_id second_token_balance -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. First_token_id, first_token_balance > ID and amount of the first token second_token_id, second_token_balance > ID and amount of the second token -> The ID is the ID of the issued TRC10 token. If it is TRX, the ID is "_", the amount must be greater than 0, and less than 1,000,000,000,000,000. +> +> The ID is the ID of the issued TRC10 token. +> If it is TRX, the ID is "_". +> The amount must be greater than 0, and less than 1,000,000,000,000,000. Example: > exchangeCreate 1000001 10000 _ 10000 - # Create trading pairs with the IDs of 1000001 and TRX, the amount is 10000 for both. + # Create trading pairs with the IDs of 1000001 and TRX, with amount 10000 for both. ### Capital injection > exchangeInject [OwnerAddress] exchange_id token_id quant -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. exchange_id -> The ID of the transaction pair to be funded +> The ID of the trading pair to be funded token_id, quant > TokenId and quantity of capital injection -When conducting capital injection, depending on the amount of capital injection, the proportion -of each token in the transaction pair is deducted from the account and added to the transaction +When conducting a capital injection, depending on its quantity (quant), a proportion +of each token in the trading pair will be withdrawn from the account, and injected into the trading pair. Depending on the difference in the balance of the transaction, the same amount of money for -the same token is different. +the same token would vary. ### Transactions > exchangeTransaction [OwnerAddress] exchange_id token_id quant expected -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. exchange_id -> ID of the transaction pair +> ID of the trading pair token_id, quant > The ID and quantity of tokens being exchanged, equivalent to selling @@ -601,37 +607,39 @@ token_id, quant expected > Expected quantity of another token -The expected must be less than exchanged, otherwise, an error will be reported. +expected must be less than quant, or an error will be reported. Example: > ExchangeTransaction 1 1000001 100 80 -It is expected to acquire the 80 TRX by exchanging 1000001 from the transaction pair ID of 1, and the amount is 100 (equivalent to selling token10, the ID is 1000001, the amount is 100). +It is expected to acquire the 80 TRX by exchanging 1000001 from the trading pair ID of 1, and the amount is 100.(Equivalent to selling an amount of 100 tokenID - 1000001, at a price of 80 TRX, in trading pair ID - 1). -### Divestment +### Capital Withdrawal > exchangeWithdraw [OwnerAddress] exchange_id token_id quant -OwnerAddress -> The address of the account that initiated the transaction, optional, default is the address of the login account. +OwnerAddress (optional) +> The address of the account which initiated the transaction. +> Default: the address of the login account. + +Exchange_id -> Exchange_id -The ID of the transaction pair to be divested +> +The ID of the trading pair to be withdrawn Token_id, quant -> TokenId and quantity of divestment +> TokenId and quantity of capital withdrawal -When conducting divestment, depending on the amount of divestment, the proportion of each token -in the transaction pair is deducted from the account and added to the transaction pair. Depending -on the difference in the balance of the transaction, the same amount of money for the same token is different. +When conducting a capital withdrawal, depending on its quantity (quant), a proportion of each token +in the transaction pair is withdrawn from the trading pair, and injected into the account. Depending on the difference in the balance of the transaction, the same amount of money for the same token would vary. ### Obtain information on trading pairs ListExchanges -> lists trading pairs +> List trading pairs -ListexchangesPaginated +ListExchangesPaginated > List trading pairs by page ## How to use the multi-signature feature of wallet-cli? From e4c3c98987e3f97743c9fd7afe21036957c98699 Mon Sep 17 00:00:00 2001 From: mattopolitan Date: Tue, 14 Apr 2020 17:36:05 +0800 Subject: [PATCH 308/445] revise trade on exchange documents --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bfb357522..bac4931fe 100644 --- a/README.md +++ b/README.md @@ -583,7 +583,7 @@ exchange_id > The ID of the trading pair to be funded token_id, quant -> TokenId and quantity of capital injection +> TokenId and quantity (unit in base unit) of capital injection When conducting a capital injection, depending on its quantity (quant), a proportion of each token in the trading pair will be withdrawn from the account, and injected into the trading @@ -629,7 +629,7 @@ Exchange_id The ID of the trading pair to be withdrawn Token_id, quant -> TokenId and quantity of capital withdrawal +> TokenId and quantity (unit in base unit) of capital withdrawal When conducting a capital withdrawal, depending on its quantity (quant), a proportion of each token in the transaction pair is withdrawn from the trading pair, and injected into the account. Depending on the difference in the balance of the transaction, the same amount of money for the same token would vary. From acc49297d0e0b6186a6b91744a2aed059d2db03f Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 24 Apr 2020 14:55:49 +0800 Subject: [PATCH 309/445] fix typos --- .../tron/core/zen/ShieldedTRC20Wrapper.java | 21 +++++++------ src/main/java/org/tron/core/zen/ZenUtils.java | 16 ++++++++++ src/main/java/org/tron/walletcli/Client.java | 24 +++++++-------- .../org/tron/walletcli/WalletApiWrapper.java | 30 ++++++++++++++----- .../java/org/tron/walletserver/WalletApi.java | 3 +- src/main/protos/api/api.proto | 12 ++++++++ 6 files changed, 71 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java index 658590a0f..8b6d292fe 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -32,14 +32,14 @@ public class ShieldedTRC20Wrapper { - private static String prefixFolder;// = "WalletShieldedTRC20Contract/"; + private static String prefixFolder; private static String trc20ContractAddress; private static String shieldedTRC20ContarctAddress; - private static String ivkAndNumFileName;// = "/scanblocknumber"; - private static String unspendNoteFileName;// = "/unspendnote"; - private static String spendNoteFileName;// = "/spendnote"; - private static String shieldedAddressFileName;// = "/shieldedaddress"; - private static String shieldedSkeyFileName;// = "/shieldedskey.json"; + private static String ivkAndNumFileName; + private static String unspendNoteFileName; + private static String spendNoteFileName; + private static String shieldedAddressFileName; + private static String shieldedSkeyFileName; private static AtomicLong nodeIndex = new AtomicLong(0L); private Thread thread; @@ -146,8 +146,8 @@ public void run() { int count = 24; for (; ; ) { try { - scanBlockByIvk(); updateNoteWhetherSpend(); + scanBlockByIvk(); } catch (Exception e) { ++count; if (count >= 24) { @@ -658,7 +658,7 @@ private byte[] generateSkey() throws IOException, CipherException { } public void initShieldedTRC20WalletFile() throws IOException, CipherException { - ZenUtils.checkFolderExist(prefixFolder); + ZenUtils.checkFoldersExist(prefixFolder); if (ArrayUtils.isEmpty(shieldedSkey)) { if (shieldedSkeyFileExist()) { @@ -672,12 +672,11 @@ public void initShieldedTRC20WalletFile() throws IOException, CipherException { public ShieldedAddressInfo backupShieldedTRC20Wallet() throws IOException, CipherException { - ZenUtils.checkFolderExist(prefixFolder); + ZenUtils.checkFoldersExist(prefixFolder); if (shieldedSkeyFileExist()) { byte[] tempShieldedKey = loadSkey(); if (!ArrayUtils.isEmpty(tempShieldedKey)) { - if (ArrayUtils.isEmpty(shieldedSkey)) { shieldedSkey = tempShieldedKey; loadShieldTRC20Wallet(); @@ -731,7 +730,7 @@ public ShieldedAddressInfo backupShieldedTRC20Wallet() throws IOException, } public byte[] importShieldedTRC20Wallet() throws IOException, CipherException { - ZenUtils.checkFolderExist(prefixFolder); + ZenUtils.checkFoldersExist(prefixFolder); if (shieldedSkeyFileExist()) { byte[] tempShieldedKey = loadSkey(); diff --git a/src/main/java/org/tron/core/zen/ZenUtils.java b/src/main/java/org/tron/core/zen/ZenUtils.java index f2d7cdecc..792e57e73 100644 --- a/src/main/java/org/tron/core/zen/ZenUtils.java +++ b/src/main/java/org/tron/core/zen/ZenUtils.java @@ -92,6 +92,22 @@ public static void checkFolderExist(final String filePath) { } } + public static void checkFoldersExist(final String filePath) { + try { + File file = new File(filePath); + if (file.exists()) { + if (file.isDirectory()) { + return; + } else { + file.delete(); + } + } + file.mkdirs(); + } catch (Exception e) { + e.printStackTrace(); + } + } + public static boolean checkFileExist(final String filePath) { try { File file = new File(filePath); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 7239d6a49..6e1b52077 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2887,7 +2887,7 @@ private void setShieldedTRC20ContractAddress(String[] parameters) { } private void backupShieldedTRC20Wallet() throws IOException, CipherException { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { System.out.println("BackupShieldedTRC20Wallet failed !!!" + " Please SetShieldedTRC20ContractAddress first !!!"); return; @@ -2906,7 +2906,7 @@ private void backupShieldedTRC20Wallet() throws IOException, CipherException { private void generateShieldedTRC20Address(String[] parameters) throws IOException, CipherException, ZksnarkException { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { System.out.println("GenerateShieldedTRC20Address failed !!!" + " Please SetShieldedTRC20ContractAddress first !!!"); return; @@ -2933,7 +2933,7 @@ private void generateShieldedTRC20Address(String[] parameters) throws IOExceptio } private void importShieldedTRC20Wallet() throws CipherException, IOException, ZksnarkException { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { System.out.println("ImportShieldedTRC20Wallet failed !!!" + " Please SetShieldedTRC20ContractAddress first !!!"); return; @@ -2980,13 +2980,13 @@ private void listShieldedTRC20Address() { } private void listShieldedTRC20Note(String[] parameters) { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { System.out.println("ListShieldedTRC20Note failed !!!" + " Please SetShieldedTRC20ContractAddress first !!!"); return; } if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { - System.out.println("ListShieldedNote failed, please LoadShieldedWallet first!"); + System.out.println("ListShieldedTRC20Note failed, please LoadShieldedTRC20Wallet first!"); return; } @@ -3081,7 +3081,7 @@ private void resetShieldedTRC20Note() { } private void scanShieldedTRC20NoteByIvk(String[] parameters) { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { System.out.println("ScanShieldedTRC20NoteByIvk failed !!!" + " Please SetShieldedTRC20ContractAddress first !!!"); return; @@ -3107,7 +3107,7 @@ private void scanShieldedTRC20NoteByIvk(String[] parameters) { } private void scanShieldedTRC20NoteByOvk(String[] parameters) { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { + if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { System.out.println("ScanShieldedTRC20NoteByOvk failed !!!" + " Please SetShieldedTRC20ContractAddress first !!!"); return; @@ -3165,8 +3165,8 @@ private void sendShieldedTRC20CoinWithoutAsk(String[] parameters) throws IOExcep } private boolean firstCheck(String[] parameters, String sendCoinType) { - if (parameters == null || parameters.length < 8) { - System.out.println(sendCoinType + " command needs more than 8 parameters like: "); + if (parameters == null || parameters.length < 6) { + System.out.println(sendCoinType + " command needs more than 6 parameters like: "); System.out.println(sendCoinType + " fromAmount shieldedInputNum input1 input2 ... " + "publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 " + "shieldedAddress2 amount2 memo2 ... "); @@ -3842,11 +3842,7 @@ private void run() { loadShieldedTRC20Wallet(); break; } - // case "markshieldedtrc20note": { - // markShieldedTRC20Note(parameters); - // break; - // } - case "resettrc20contractshieldednote": { + case "resetshieldedtrc20note": { resetShieldedTRC20Note(); break; } diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 919396869..c130fcac5 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1328,6 +1328,13 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, return false; } + for(int i = 0; i < rootAndPath.size(); i++) { + if (rootAndPath.get(i) == null) { + System.out.println("Can't get merkle path, please check the note " + i + "."); + return false; + } + } + for (int i = 0; i < shieldedInputList.size(); ++i) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(i)); @@ -1357,7 +1364,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, System.out.println("position " + noteInfo.getPosition()); System.out.println("memo " + ZenUtils.getMemo(noteInfo.getMemo())); - byte[] eachRootAndPath = rootAndPath.get(i).getBytes(); + byte[] eachRootAndPath = ByteArray.fromHexString(rootAndPath.get(i)); byte[] root = Arrays.copyOfRange(eachRootAndPath, 0, 32); byte[] path = Arrays.copyOfRange(eachRootAndPath, 32, 1056); SpendNoteTRC20.Builder spendTRC20NoteBuilder = SpendNoteTRC20.newBuilder(); @@ -1421,12 +1428,12 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, } } else if (shieldedContractType == 2) {//BURN String inputData = encodeBurnParamsToHexString(parameters, toAmount, toAddress); - boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 1); + boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); if (transferResult) { - System.out.println("TRANSFER succeed!"); + System.out.println("BURN succeed!"); return true; } else { - System.out.println("Trigger shielded contract TRANSFER failed!"); + System.out.println("Trigger shielded contract BURN failed!"); return false; } } else { @@ -1484,6 +1491,13 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fr return false; } + for(int i = 0; i < rootAndPath.size(); i++) { + if (rootAndPath.get(i) == null) { + System.out.println("Can't get merkle path, please check the note " + i + "."); + return false; + } + } + for (int i = 0; i < shieldedInputList.size(); i++) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(i)); @@ -1515,7 +1529,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fr System.out.println("position " + noteInfo.getPosition()); System.out.println("memo " + ZenUtils.getMemo(noteInfo.getMemo())); - byte[] eachRootAndPath = rootAndPath.get(i).getBytes(); + byte[] eachRootAndPath = ByteArray.fromHexString(rootAndPath.get(i)); byte[] root = Arrays.copyOfRange(eachRootAndPath, 0, 32); byte[] path = Arrays.copyOfRange(eachRootAndPath, 32, 1056); SpendNoteTRC20.Builder spendTRC20NoteBuilder = SpendNoteTRC20.newBuilder(); @@ -1583,10 +1597,10 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fr String inputData = encodeBurnParamsToHexString(parameters, toAmount, toAddress); boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); if (transferResult) { - System.out.println("TRANSFER succeed!"); + System.out.println("BURN succeed!"); return true; } else { - System.out.println("Trigger shielded contract TRANSFER failed!"); + System.out.println("Trigger shielded contract BURN failed!"); return false; } } else { @@ -1634,7 +1648,7 @@ public boolean triggerShieldedContract(String contractAddress, String data, } else if (shieldedContractType == 1) { methodStr = "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])"; } else if (shieldedContractType == 2) { - methodStr = "burn(bytes32[10],bytes32[2],uint64,bytes32[2],uint256)"; + methodStr = "burn(bytes32[10],bytes32[2],uint64,bytes32[2],address)"; } else { System.out.println("shieldedContractType should be 0, 1 or 2. "); return false; diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 8e024962c..05654f784 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2510,8 +2510,7 @@ public String getRootAndPath(byte[] contractAddress, byte[] data) { if (transaction.getRetCount() != 0 && transactionExtention.getConstantResult(0) != null && transactionExtention.getResult() != null) { - byte[] contractResult = transactionExtention.getConstantResult(0).toByteArray(); - byte[] result = ByteArray.subArray(contractResult, 0, 1056); + byte[] result = transactionExtention.getConstantResult(0).toByteArray(); return ByteArray.toHexString(result); } else { return null; diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index c1f2f32a3..6a3a113dc 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -713,6 +713,9 @@ service Wallet { rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { }; + + rpc GetTriggerInputForShieldedTRC20Contract (ShieldedTRC20TriggerContractParameters) returns (BytesMessage) { + }; // end for shieldedTRC20Contract }; @@ -1351,6 +1354,8 @@ message ShieldedTRC20Parameters { repeated ReceiveDescription receive_description = 2; bytes binding_signature = 3; bytes message_hash = 4; + string trigger_contract_input = 5; + string parameter_type = 6; } message IvkDecryptTRC20Parameters { @@ -1390,4 +1395,11 @@ message NfTRC20Parameters { message NullifierResult { bool isSpent = 1; +} + +message ShieldedTRC20TriggerContractParameters { + ShieldedTRC20Parameters shielded_TRC20_Parameters = 1; + repeated BytesMessage spend_authority_signature = 2; + int64 amount = 3; + bytes transparent_to_address = 4; } \ No newline at end of file From 84c045c5d4958e1d236db4f2c887b76b6f972f73 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 27 Apr 2020 16:44:01 +0800 Subject: [PATCH 310/445] feat: update proto --- src/main/protos/core/Tron.proto | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 9aa958738..dc51af497 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -722,7 +722,6 @@ message MarketOrderList { repeated MarketOrder orders = 1; } - message MarketOrderPairList { repeated MarketOrderPair orderPair = 1; } @@ -739,24 +738,9 @@ message MarketAccountOrder { int64 total_count = 4; } -message MarketOrderPosition { - // if price list is empty or price exist, pre_price_key = null - bool price_list_not_empty = 1; - bool price_exist = 2; - bytes pre_price_key = 3; -} - message MarketPrice { int64 sell_token_quantity = 1; int64 buy_token_quantity = 2; - bytes prev = 3; - bytes next = 4; -} - -message MarketPriceLinkedList { - bytes sell_token_id = 1; - bytes buy_token_id = 2; - MarketPrice bestPrice = 3; } message MarketPriceList { From e72e5b67154790a43fc8cf5f6bbdc4eb89f7aa06 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Thu, 7 May 2020 15:22:18 +0800 Subject: [PATCH 311/445] add showshieldedtrc20addressinfo, add instructions for shielded trc20 transaction, and remove unnecessary code --- README.md | 349 +++++++++++++++++- .../tron/core/zen/ShieldedTRC20Wrapper.java | 37 +- src/main/java/org/tron/walletcli/Client.java | 131 ++++--- .../org/tron/walletcli/WalletApiWrapper.java | 42 +-- .../java/org/tron/walletserver/WalletApi.java | 36 +- 5 files changed, 477 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index f97b29f78..448247928 100644 --- a/README.md +++ b/README.md @@ -1440,6 +1440,340 @@ pkd:3a7406c13767c7d08f2883f4884ec6aafdfcdeacebde45f1f4db98df5bf0a1ca d :2fd028965d3b455579ab28 ``` +## How to transfer TRC20 token to shielded address + +### SetShieldedTRC20ContractAddress TRC20ContractAddress ShieldedContractAddress + +Set TRC20 contract address and shielded contract address. Please execute this command before you perform all operations related to the shielded transaction of TRC20. + +TRC20ContractAddress +> TRC20 contract address + +ShieldedContractAddress +> Shielded contract address + +Example: + +```console +> SetShieldedTRC20ContractAddress TLDxNTzNvEPd4gHox8V1zK2w82LFnideKE TKERuAmhJh8vZi1dzJtx8926xeCT74747e +``` + +### LoadShieldedTRC20Wallet + +Load TRC20 shielded address, shielded note and start to scan by ivk. + +Example: + +```console +> LoadShieldedTRC20Wallet +Please input your password for shieldedTRC20 wallet. +> ******* +LoadShieldedTRC20Wallet successful !!! +``` + +### GenerateShieldedTRC20Address number + +Generate TRC20 shielded addresses. + +number +> The number of TRC20 shielded addresses, the default is 1. + +Example: + +```console +> GenerateShieldedTRC20Address 3 +ShieldedTRC20Address list: +ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf +ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq +GenerateShieldedTRC20Address successful !!! +``` + +### ListShieldedTRC20Address + +Display cached local TRC20 shielded address list. + +Example: + +```console +> ListShieldedTRC20Address +ShieldedTRC20Address : +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf +ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq +ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc +ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 +``` + +### SendShieldedTRC20Coin + + > SendShieldedTRC20Coin fromAmount shieldedInputNum input1 input2 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 .... + +Shielded transfer, support three types: + +- MINT: one public address to one shielded address, fromAmount shoule be equal to shielded output amount. +- TRANSFER: one or two shielded address(es) to one or two shielded address(es), the sum of shielded input amount should be equal to the sum of shielded output amount. +- BURN: one shielded address to one public address, shielded input amount should be equal to toAmount. + +fromAmount +> The amount transfer from public address. If the transfer type is MINT, this variable must be positive, or it must be 0. + +shieldedInputNum +> The number of shielded input note, should be 0, 1 or 2. If the transfer type is MINT, this variable must be 0; if BURN, it must be 1. + +input1/input2 +> The index of shielded input note, get from execute command ListShieldedTRC20Note. If shieldedInputNum set to 0, no need to set. + +publicToAddress +> Public to address. If the transfer type is BURN, this variable must be a valid address, otherwise it should be set null. + +toAmount +> The amount transfer to public address. If publicToAddress set to null, this variable must be 0. + +shieldedOutputNum +> The amount of shielded output note. That is the number of (shieldedAddress amount meno) pairs, should be 0, 1 or 2. + +shieldedAddress1/shieldedAddress2 +> Output shielded address + +amount1/amount2 +> The amount transfer to shieldedAddress1/shieldedAddress2 + +memo1/memo2 +> The memo of this note, up to 512 bytes, can be set to null if not needed. + +Example: + +1. MINT + + **When in this mode, Some variables must be set as follows, shieldedInputNum=0, publicToAddress=null, toAmount=0** + + ```console + > SendShieldedTRC20Coin 1000000000 0 null 0 1 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 null + ``` + +2. TRANSFER + + **When in this mode, Some variables must be set as follows, fromAmount=0, publicToAddress=null,toAmount=0** + + Transfer from one shielded address to one shielded address. + ```console + > ListShieldedTRC20Note + This command will show all the unspent notes. + If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 + The unspent note list is shown below: + 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend + 8 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 UnSpend + + > SendShieldedTRC20Coin 0 1 8 null 0 1 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 null + ``` + + Transfer from one shielded address to two shielded addresses. + ```console + > ListShieldedTRC20Note + This command will show all the unspent notes. + If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 + The unspent note list is shown below: + 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend + 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend + + > SendShieldedTRC20Coin 0 1 9 null 0 2 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 test1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 null + ``` + + Transfer from two shielded addresses to one shielded address. + ```console + > ListShieldedTRC20Note + This command will show all the unspent notes. + If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 + The unspent note list is shown below: + 11 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 UnSpend test1 + 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend + 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend + + > SendShieldedTRC20Coin 0 2 10 11 null 0 1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 null + ``` + + Transfer from two shielded addresses to two shielded addresses. + ```console + > ListShieldedTRC20Note + This command will show all the unspent notes. + If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 + The unspent note list is shown below: + 13 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 UnSpend + 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend + + > SendShieldedTRC20Coin 0 2 12 13 null 0 2 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 null ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 null + ``` + +3. BURN + + **When in this mode, Some variables must be set as follows, fromAmount=0, shieldedInputNum=1, shieldedOutputNum=0** + + > ListShieldedTRC20Note + This command will show all the unspent notes. + If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 + The unspent note list is shown below: + 15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend + 14 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 UnSpend + + > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 1300000000 0 + ``` + +### SendShieldedTRC20CoinWithoutAsk + +Usage and parameters are consistent with the command SendShieldedTRC20Coin, the only difference is that SendShieldedTRC20Coin uses ask for signature, but SendShieldedTRC20CoinWithoutAsk uses ak. + +### ListShieldedTRC20Note type + +List the note scanned by the local cache address. + +type +> Shows the type of note. If the variable is omitted or set to 0, it shows all unspent notes; For other values, it shows all the notes, including spent notes and unspent notes. + +Example: + +```console +> ListShieldedTRC20Note +This command will show all the unspent notes. +If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 +The unspent note list is shown below: +15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend + +> ListShieldedTRC20Note 1 +All notes are shown below: +ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 15 UnSpent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 dc02678b0cf1c93c557dc805edb776fe79201c77f210f08f60cea5d687b14f2e 0 0 Spent +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 1000000000 e4d35d147762020078d7d197c98fffde181250e4a637d4bdd9ca809116d74131 0 2 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 1594e1ee06c8420a4f1d80670000cd9268a2ff4e97e3f630909feeb51a9de993 0 3 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 2000000000 3f035e966b3ef636ae9c0a0f64bff781b1d1a8b52bab5d8124c0f9162f71f68f 0 1 Spent +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 300000000 6929757cb86cb6cf3e89df19f3212c3e62070b12d8b36de48e663fed214a4082 0 4 Spent test1 +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2000000000 e39a1e1d5af7dcbab0d55a63a0c62ec9cc7c0aaf8ce98733802674c3ec1f3a06 0 6 Spent +ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq 700000000 6929757cb86cb6cf3e89df19f3212c3e62070b12d8b36de48e663fed214a4082 1 5 Spent +ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq 2300000000 4ce1ce9f6377ee3cd936757b696ac43ecc39ee6e8a0eab1b8f8ef093e15010f8 0 7 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 8 Spent +ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 9 Spent +ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 10 Spent +ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 11 Spent test1 +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 12 Spent +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 13 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 14 Spent +``` + +### ResetShieldedTRC20Note + +Clean all the notes scanned, and rescan all blocks. Generally used when there is a problem with the notes or when switching environments. + +### ScanShieldedTRC20NoteByIvk shieldedTRC20ContarctAddress ivk ak nk startNum endNum + +Scan notes by ivk, ak and nk. + +shieldedTRC20ContarctAddress +> The address of shielded contract + +ivk +> The ivk of shielded address + +ak +> The ak of shielded address + +nk +> The nk of shielded address + +startNum +> The starting block number of the scan + +endNum +> The end block number of the scan + +Example: + +> ScanShieldedTRC20NoteByIvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e fe8203f3dc5feb2497986512f94a3b9631bffec02aee0bca735639742d2cef07 a150fa887ed45b6d9eca73ec94e1dd53dddf945cc947fbc6a2c5cc334c940233 696aca6b5db2f42041850423d63d95574ad1256519716507c5c074a924e47b0c 500 1499 + +### ScanShieldedTRC20NoteByOvk shieldedTRC20ContarctAddress ovk startNum endNum + +Scan notes by ovk + +shieldedTRC20ContarctAddress +> The address of shielded contract + +ovk +> the ovk of shielded address + +startNum +> The starting block number of the scan + +endNum +> The end block number of the scan + +Example: + +> ScanShieldedTRC20NoteByOvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e 00bad26a4f1d2380345fd2ab28008a593ba9a2e19c75cba28333afa73c89e6d8 500 1499 + + +### BackupShieldedTRC20Wallet + +Back up one shielded address. + +Example: + +```console +wallet> BackupShieldedTRC20Wallet +Please input your password for shieldedTRC20 wallet. +password: +The 1th shieldedTRC20 address is ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z +The 2th shieldedTRC20 address is ztron1mnkdjl0802dqha9ufh4m80f2ua9cff2hct8geeh77llrz4ywgtu0ct8ygy6k5xavdkd278jyttj +The 3th shieldedTRC20 address is ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 +Please choose between 1 and 3 +1 +sk:01ef2d71f8eef668e12db7aef1267c7d6a8f43c84dffa66fc09e2c749464190e +d :da5fd7e087d48e3dcebff3 +BackupShieldedTRC20Wallet successful !!! +``` + +### ImportShieldedTRC20Wallet + +Import one shielded address to local wallet. + +Example: + +```console +wallet> ImportShieldedTRC20Wallet +ShieldedTRC20 wallet does not exist, will build it. +Please input password. +password: +Please input password again. +password: +Please input shieldedTRC20 wallet hex string. such as 'sk d',Max retry time:3 +0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb000a 11db4baf6bd5d5afd3a8b5 +Import shieldedTRC20 wallet hex string is : +sk:0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb000a +d :11db4baf6bd5d5afd3a8b5 +Import new shieldedTRC20 wallet address is: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 +ImportShieldedTRC20Wallet successfully !!! +``` + +### ShowShieldedTRC20AddressInfo + +Display information about shielded addresses + +Example: + +```console +wallet> ListShieldedTRC20Address +ShieldedTRC20Address : +ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z +ztron1mnkdjl0802dqha9ufh4m80f2ua9cff2hct8geeh77llrz4ywgtu0ct8ygy6k5xavdkd278jyttj +ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + +wallet> ShowShieldedTRC20AddressInfo ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z +The following variables are secret information, please don't show to other people!!! +sk :01ef2d71f8eef668e12db7aef1267c7d6a8f43c84dffa66fc09e2c749464190e +ivk:7d2e9c14ff1d82843f39cb69e8bcc228370e4ea8750669bba79e90c485d94c03 +ovk:2c3d164fffa63b41a34f495e0c9d8af79d595cfb07db1539545ddcecf046d66e +pkd:70d84ee492ad5d0f3ba80ce902f513ccad717f6b57bd8e89b51b8b896ab87922 +d :da5fd7e087d48e3dcebff3 +``` + ## Command List Following is a list of Tron Wallet-cli commands: @@ -1448,6 +1782,7 @@ For more information on a specific command, just type the command on terminal wh AddTransactionSign ApproveProposal AssetIssue + BackupShieldedTRC20Wallet BackupShieldedWallet BackupWallet BackupWallet2Base64 @@ -1467,6 +1802,7 @@ For more information on a specific command, just type the command on terminal wh FreezeBalance GenerateAddress GenerateShieldedAddress + GenerateShieldedTRC20Address GetAccount GetAccountNet GetAccountResource @@ -1500,6 +1836,7 @@ For more information on a specific command, just type the command on terminal wh GetTransactionsFromThis GetTransactionsToThis GetTransactionSignWeight + ImportShieldedTRC20Wallet ImportShieldedWallet ImportWallet ImportWalletByBase64 @@ -1510,23 +1847,33 @@ For more information on a specific command, just type the command on terminal wh ListNodes ListShieldedAddress ListShieldedNote + ListShieldedTRC20Address + ListShieldedTRC20Note ListProposals ListProposalsPaginated ListWitnesses + LoadShieldedWallet + LoadShieldedTRC20Wallet Login Logout - LoadShieldedWallet ParticipateAssetIssue RegisterWallet ResetShieldedNote + ResetShieldedTRC20Note ScanAndMarkNotebyAddress ScanNotebyIvk ScanNotebyOvk + ScanShieldedTRC20NoteByIvk + ScanShieldedTRC20NoteByOvk SendCoin SendShieldedCoin SendShieldedCoinWithoutAsk + SendShieldedTRC20Coin + SendShieldedTRC20CoinWithoutAsk SetAccountId + SetShieldedTRC20ContractAddress ShowShieldedAddressInfo + ShowShieldedTRC20AddressInfo TransferAsset TriggerContract TriggerConstantContract diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java index 8b6d292fe..4b21d787b 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -2,12 +2,10 @@ import com.google.protobuf.ByteString; import io.netty.util.internal.StringUtil; -import java.lang.reflect.Field; import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.ArrayUtils; import org.tron.api.GrpcAPI.*; -import org.tron.api.GrpcAPI.DecryptNotes.NoteTx; import org.tron.api.GrpcAPI.DecryptNotesTRC20; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; @@ -81,17 +79,23 @@ public static boolean isSetShieldedTRC20WalletPath() { || shieldedAddressFileName == null || shieldedSkeyFileName == null); } - public static void setShieldedTRC20WalletPath(String contractAddress, - String shieldedContractAddress) { - trc20ContractAddress = contractAddress; - shieldedTRC20ContarctAddress = shieldedContractAddress; - prefixFolder = "WalletShieldedTRC20Contract/" - + trc20ContractAddress + "_" + shieldedTRC20ContarctAddress; - ivkAndNumFileName = prefixFolder + "/scanblocknumber"; - unspendNoteFileName = prefixFolder + "/unspendnote"; - spendNoteFileName = prefixFolder + "/spendnote"; - shieldedAddressFileName = prefixFolder + "/shieldedaddress"; - shieldedSkeyFileName = prefixFolder + "/shieldedskey.json"; + public void setShieldedTRC20WalletPath(String contractAddress, + String shieldedContractAddress) { + if (contractAddress == null || shieldedContractAddress == null + || !contractAddress.equals(trc20ContractAddress) + || !shieldedContractAddress.equals(shieldedTRC20ContarctAddress)) { + loadShieldedStatus = false; + shieldedSkey = null; + trc20ContractAddress = contractAddress; + shieldedTRC20ContarctAddress = shieldedContractAddress; + prefixFolder = "WalletShieldedTRC20Contract/" + + trc20ContractAddress + "_" + shieldedTRC20ContarctAddress; + ivkAndNumFileName = prefixFolder + "/scanblocknumber"; + unspendNoteFileName = prefixFolder + "/unspendnote"; + spendNoteFileName = prefixFolder + "/spendnote"; + shieldedAddressFileName = prefixFolder + "/shieldedaddress"; + shieldedSkeyFileName = prefixFolder + "/shieldedskey.json"; + } } public String getShieldedTRC20ContractAddress() { @@ -145,6 +149,13 @@ public class scanIvkRunable implements Runnable { public void run() { int count = 24; for (; ; ) { + if (!ifShieldedTRC20WalletLoaded()) { + try { + Thread.sleep(500); + } catch (Exception e) { + } + continue; + } try { updateNoteWhetherSpend(); scanBlockByIvk(); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 6e1b52077..d944aa34a 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -149,6 +149,7 @@ public class Client { "SetAccountId", "SetShieldedTRC20ContractAddress", "ShowShieldedAddressInfo", + "ShowShieldedTRC20AddressInfo", "TransferAsset", "TriggerContract contractAddress method args isHex fee_limit value", "TriggerConstantContract contractAddress method args isHex", @@ -265,6 +266,7 @@ public class Client { "SetAccountId", "SetShieldedTRC20ContractAddress", "ShowShieldedAddressInfo", + "ShowShieldedTRC20AddressInfo", "TransferAsset", "TriggerContract", "TriggerConstantContract", @@ -2875,7 +2877,7 @@ private void setShieldedTRC20ContractAddress(String[] parameters) { byte[] trc20ContractAddress = WalletApi.decodeFromBase58Check(parameters[0]); byte[] shieldedContractAddress = WalletApi.decodeFromBase58Check(parameters[1]); if (!(trc20ContractAddress == null || shieldedContractAddress == null)) { - ShieldedTRC20Wrapper.setShieldedTRC20WalletPath(parameters[0], parameters[1]); + ShieldedTRC20Wrapper.getInstance().setShieldedTRC20WalletPath(parameters[0], parameters[1]); } else { System.out.println("SetShieldedTRC20ContractAddress failed !!! Invalid Address !!!"); } @@ -2961,30 +2963,20 @@ private void importShieldedTRC20Wallet() throws CipherException, IOException, Zk } private void listShieldedTRC20Address() { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { - if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { - System.out.println("ListShieldedTRC20Address failed, " - + "please LoadShieldedTRC20Wallet first!"); - return; - } + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("ListShieldedTRC20Address failed, please LoadShieldedTRC20Wallet " + + "first!"); + return; + } - List listAddress = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20AddressList(); - System.out.println("ShieldedTRC20Address :"); - for (String address : listAddress) { - System.out.println(address); - } - } else { - System.out.println("ListShieldedTRC20Address failed !!!" - + " Please SetShieldedTRC20ContractAddress first !!!"); + List listAddress = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20AddressList(); + System.out.println("ShieldedTRC20Address :"); + for (String address : listAddress) { + System.out.println(address); } } private void listShieldedTRC20Note(String[] parameters) { - if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { - System.out.println("ListShieldedTRC20Note failed !!!" - + " Please SetShieldedTRC20ContractAddress first !!!"); - return; - } if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { System.out.println("ListShieldedTRC20Note failed, please LoadShieldedTRC20Wallet first!"); return; @@ -3066,68 +3058,60 @@ private void loadShieldedTRC20Wallet() throws CipherException, IOException { } private void resetShieldedTRC20Note() { - if (ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { - if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { - System.out.println("ResetShieldedTRC20Note failed, please LoadShieldedTRC20Wallet first!"); - return; - } else { - System.out.println("Start to reset shieldedTRC20 notes, please wait ..."); - ShieldedTRC20Wrapper.getInstance().setResetNote(true); - } + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("ResetShieldedTRC20Note failed, please LoadShieldedTRC20Wallet first!"); + return; } else { - System.out.println("ResetShieldedTRC20Note failed !!!" - + " Please SetShieldedTRC20ContractAddress first !!!"); + System.out.println("Start to reset shieldedTRC20 notes, please wait ..."); + ShieldedTRC20Wrapper.getInstance().setResetNote(true); } } private void scanShieldedTRC20NoteByIvk(String[] parameters) { - if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { - System.out.println("ScanShieldedTRC20NoteByIvk failed !!!" - + " Please SetShieldedTRC20ContractAddress first !!!"); + if (parameters == null || parameters.length != 6) { + System.out.println("ScanShieldedTRC20NoteByIvk command needs 6 parameters like: "); + System.out.println("ScanShieldedTRC20NoteByIvk shieldedTRC20ContarctAddress ivk ak nk " + + "startNum endNum "); return; } - if (parameters == null || parameters.length != 5) { - System.out.println("ScanShieldedTRC20NoteByIvk command needs 5 parameters like: "); - System.out.println("ScanShieldedTRC20NoteByIvk ivk ak nk startNum endNum "); + byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[0]); + if (contractAddress == null) { + System.out.println("ScanShieldedTRC20NoteByIvk failed! Invalid shieldedTRC20ContarctAddress"); return; } - long startNum, endNum; try { - startNum = Long.parseLong(parameters[3]); - endNum = Long.parseLong(parameters[4]); + startNum = Long.parseLong(parameters[4]); + endNum = Long.parseLong(parameters[5]); } catch (NumberFormatException e) { System.out.println("Invalid parameter: startNum, endNum."); return; } - String addressStr = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress(); - byte[] address = WalletApi.decodeFromBase58Check(addressStr); - walletApiWrapper.scanShieldedTRC20NoteByIvk(parameters[0], - parameters[1], parameters[2], address, startNum, endNum); + walletApiWrapper.scanShieldedTRC20NoteByIvk(contractAddress, + parameters[1], parameters[2], parameters[3], startNum, endNum); } private void scanShieldedTRC20NoteByOvk(String[] parameters) { - if (!ShieldedTRC20Wrapper.isSetShieldedTRC20WalletPath()) { - System.out.println("ScanShieldedTRC20NoteByOvk failed !!!" - + " Please SetShieldedTRC20ContractAddress first !!!"); + if (parameters == null || parameters.length != 4) { + System.out.println("ScanShieldedTRC20NoteByOvk command needs 4 parameters like: "); + System.out.println("ScanShieldedTRC20NoteByOvk shieldedTRC20ContarctAddress ovk startNum " + + "endNum"); return; } - if (parameters == null || parameters.length != 3) { - System.out.println("ScanShieldedTRC20NoteByOvk command needs 3 parameters like: "); - System.out.println("ScanShieldedTRC20NoteByOvk ovk startNum endNum"); + byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[0]); + if (contractAddress == null) { + System.out.println("ScanShieldedTRC20NoteByOvk failed! Invalid shieldedTRC20ContarctAddress"); return; } long startNum, endNum; try { - startNum = Long.parseLong(parameters[1]); - endNum = Long.parseLong(parameters[2]); + startNum = Long.parseLong(parameters[2]); + endNum = Long.parseLong(parameters[3]); } catch (NumberFormatException e) { System.out.println("Invalid parameter: startNum, endNum."); return; } - String addressStr = ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress(); - byte[] address = WalletApi.decodeFromBase58Check(addressStr); - walletApiWrapper.scanShieldedTRC20NoteByOvk(parameters[0], startNum, endNum, address); + walletApiWrapper.scanShieldedTRC20NoteByOvk(parameters[1], startNum, endNum, contractAddress); } private void sendShieldedTRC20Coin(String[] parameters) throws IOException, CipherException, @@ -3315,6 +3299,41 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk } } + private void showShieldedTRC20AddressInfo(String[] parameters) { + if (parameters == null || parameters.length < 1) { + System.out.println("Using ShowShieldedTRC20AddressInfo needs 1 parameter like: "); + System.out.println("ShowShieldedTRC20AddressInfo shieldedTRC20Address"); + return; + } + + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + System.out.println("ShowShieldedTRC20AddressInfo failed, " + + "please loadShieldedTRC20Wallet first!"); + return; + } + + String shieldedAddress = parameters[0]; + ShieldedAddressInfo addressInfo = + ShieldedTRC20Wrapper.getInstance().getShieldedAddressInfoMap().get(shieldedAddress); + if (addressInfo != null) { + System.out.println("The following variables are secret information, " + + "please don't show to other people!!!"); + System.out.println("sk :" + ByteArray.toHexString(addressInfo.getSk())); + System.out.println("ivk:" + ByteArray.toHexString(addressInfo.getIvk())); + System.out.println("ovk:" + ByteArray.toHexString(addressInfo.getOvk())); + System.out.println("pkd:" + ByteArray.toHexString(addressInfo.getPkD())); + System.out.println("d :" + ByteArray.toHexString(addressInfo.getD().getData())); + } else { + PaymentAddress decodePaymentAddress = KeyIo.decodePaymentAddress(shieldedAddress); + if (decodePaymentAddress != null) { + System.out.println("pkd:" + ByteArray.toHexString(decodePaymentAddress.getPkD())); + System.out.println("d :" + ByteArray.toHexString(decodePaymentAddress.getD().getData())); + } else { + System.out.println("Shielded address " + shieldedAddress + " is invalid, please check!"); + } + } + } + private void help() { System.out.println("Help: List of Tron Wallet-cli commands"); System.out.println( @@ -3862,6 +3881,10 @@ private void run() { sendShieldedTRC20CoinWithoutAsk(parameters); break; } + case "showshieldedtrc20addressinfo": { + showShieldedTRC20AddressInfo(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index c130fcac5..cfec9500d 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1217,13 +1217,14 @@ public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } - public boolean scanShieldedTRC20NoteByIvk(final String ivk, final String ak, final String nk, - byte[] contractAddress, long start, long end) { + public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, + final String ak, final String nk, + long start, long end) { GrpcAPI.IvkDecryptTRC20Parameters parameters = IvkDecryptTRC20Parameters .newBuilder() .setStartBlockIndex(start) .setEndBlockIndex(end) - .setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(address)) .setIvk(ByteString.copyFrom(ByteArray.fromHexString(ivk))) .setAk(ByteString.copyFrom(ByteArray.fromHexString(ak))) .setNk(ByteString.copyFrom(ByteArray.fromHexString(nk))) @@ -1235,18 +1236,6 @@ public boolean scanShieldedTRC20NoteByIvk(final String ivk, final String ak, fin System.out.println("ScanShieldedTRC20NoteByIvk failed !!!"); } else { System.out.println(Utils.formatMessageString(notes.get())); -// for (int i = 0; i < decryptNotes.get().getNoteTxsList().size(); i++) { -// NoteTx noteTx = decryptNotes.get().getNoteTxs(i); -// Note note = noteTx.getNote(); -// System.out.println("\ntxid:{}\nindex:{}\naddress:{}\nrcm:{}\nvalue:{}\nmemo:{}", -// ByteArray.toHexString(noteTx.getTxid().toByteArray()), -// noteTx.getIndex(), -// note.getPaymentAddress(), -// ByteArray.toHexString(note.getRcm().toByteArray()), -// note.getValue(), -// ZenUtils.getMemo(note.getMemo().toByteArray())); -// } -// System.out.println("complete."); } return true; } @@ -1265,18 +1254,6 @@ public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end System.out.println("ScanShieldedTRC20NoteByovk failed !!!"); } else { System.out.println(Utils.formatMessageString(notes.get())); -// for (int i = 0; i < decryptNotes.get().getNoteTxsList().size(); i++) { -// NoteTx noteTx = decryptNotes.get().getNoteTxs(i); -// Note note = noteTx.getNote(); -// System.out.println("\ntxid:{}\nindex:{}\npaymentAddress:{}\nrcm:{}\nmemo -// :{}\nvalue:{}", -// ByteArray.toHexString(noteTx.getTxid().toByteArray()), -// noteTx.getIndex(), -// note.getPaymentAddress(), -// ByteArray.toHexString(note.getRcm().toByteArray()), -// ZenUtils.getMemo(note.getMemo().toByteArray()), -// note.getValue()); -// } System.out.println("complete."); } return true; @@ -1315,6 +1292,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, builder.setToAmount(toAmount); } + long valueBalance = 0; if (shieldedInputList.size() > 0) { List rootAndPath = new ArrayList<>(); for (int i = 0; i < shieldedInputList.size(); i++) { @@ -1374,6 +1352,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, spendTRC20NoteBuilder.setPath(ByteString.copyFrom(path)); spendTRC20NoteBuilder.setPos(noteInfo.getPosition()); + valueBalance += noteInfo.getValue(); builder.addShieldedSpends(spendTRC20NoteBuilder.build()); } } else { @@ -1389,10 +1368,17 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, if (shieldedOutputList.size() > 0) { for (int i = 0; i < shieldedOutputList.size(); i++) { + GrpcAPI.Note note = shieldedOutputList.get(i); + valueBalance -= note.getValue(); builder.addShieldedReceives( - ReceiveNote.newBuilder().setNote(shieldedOutputList.get(i)).build()); + ReceiveNote.newBuilder().setNote(note).build()); } } + if (shieldedContractType == 1 && valueBalance != 0) { + System.out.println("TRANSFER: the sum of shielded input amount should be equal to the " + + "sum of shielded output amount"); + return false; + } ShieldedTRC20Parameters parameters = WalletApi.createShieldedContractParameters(builder.build()); if (parameters == null) { diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 05654f784..9da731d4c 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2459,35 +2459,27 @@ public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { } public static Optional scanShieldedTRC20NoteByIvk( - IvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { - if (showErrorMsg) { - try { - return Optional.of(rpcCli.scanShieldedTRC20NoteByIvk(parameters)); - } catch (Exception e) { - if (showErrorMsg) { - Status status = Status.fromThrowable(e); - System.out.println("ScanShieldedTRC20NoteByIvk failed,error " + status.getDescription()); - } - } - } else { + IvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { + try { return Optional.of(rpcCli.scanShieldedTRC20NoteByIvk(parameters)); + } catch (Exception e) { + if (showErrorMsg) { + Status status = Status.fromThrowable(e); + System.out.println("ScanShieldedTRC20NoteByIvk failed,error " + status.getDescription()); + } } return Optional.empty(); } public static Optional scanShieldedTRC20NoteByOvk( - OvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { - if (showErrorMsg) { - try { - return Optional.of(rpcCli.scanShieldedTRC20NoteByOvk(parameters)); - } catch (Exception e) { - if (showErrorMsg) { - Status status = Status.fromThrowable(e); - System.out.println("scanNoteByOvk failed,error " + status.getDescription()); - } - } - } else { + OvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { + try { return Optional.of(rpcCli.scanShieldedTRC20NoteByOvk(parameters)); + } catch (Exception e) { + if (showErrorMsg) { + Status status = Status.fromThrowable(e); + System.out.println("scanNoteByOvk failed,error " + status.getDescription()); + } } return Optional.empty(); } From 2502a87bac5aabdb8dff3304b60da98ff1071bca Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Tue, 12 May 2020 15:48:59 +0800 Subject: [PATCH 312/445] add scaling factor for shielded contract --- .../tron/core/zen/ShieldedTRC20Wrapper.java | 82 +++++++--------- src/main/java/org/tron/walletcli/Client.java | 41 ++++++-- .../org/tron/walletcli/WalletApiWrapper.java | 93 +++++++++++++------ .../java/org/tron/walletserver/WalletApi.java | 14 +-- src/main/protos/api/api.proto | 10 +- 5 files changed, 136 insertions(+), 104 deletions(-) diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java index 4b21d787b..606fffe6e 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -22,6 +22,7 @@ import org.tron.walletserver.WalletApi; import java.io.File; import java.io.IOException; +import java.math.BigInteger; import java.security.SecureRandom; import java.util.*; import java.util.Map.Entry; @@ -38,6 +39,7 @@ public class ShieldedTRC20Wrapper { private static String spendNoteFileName; private static String shieldedAddressFileName; private static String shieldedSkeyFileName; + private static BigInteger scalingFactor; private static AtomicLong nodeIndex = new AtomicLong(0L); private Thread thread; @@ -51,7 +53,7 @@ public class ShieldedTRC20Wrapper { private boolean resetNote = false; @Getter @Setter - public Map ivkMapScanBlockNum = new ConcurrentHashMap(); + public Map ivkMapScanBlockNum = new ConcurrentHashMap(); @Getter @Setter public Map utxoMapNote = new ConcurrentHashMap(); @@ -98,6 +100,13 @@ public void setShieldedTRC20WalletPath(String contractAddress, } } + public void setScalingFactor(BigInteger factor) { + scalingFactor = factor; + } + public BigInteger getScalingFactor() { + return scalingFactor; + } + public String getShieldedTRC20ContractAddress() { return shieldedTRC20ContarctAddress; } @@ -190,11 +199,10 @@ public void run() { private void resetShieldedTRC20Note() throws ZksnarkException { ivkMapScanBlockNum.clear(); for (Entry entry : getShieldedAddressInfoMap().entrySet()) { - Ivk ivk = new Ivk(); - ivk.setIvk(entry.getValue().getIvk()); - ivk.setAk(entry.getValue().getFullViewingKey().getAk()); - ivk.setNk(entry.getValue().getFullViewingKey().getNk()); - ivkMapScanBlockNum.put(ivk, 0L); + byte[] key = ByteUtil.merge(entry.getValue().getIvk(), + entry.getValue().getFullViewingKey().getAk(), + entry.getValue().getFullViewingKey().getNk()); + ivkMapScanBlockNum.put(ByteArray.toHexString(key), 0L); } utxoMapNote.clear(); @@ -212,7 +220,7 @@ private void scanBlockByIvk() throws CipherException { Block block = WalletApi.getBlock(-1); if (block != null) { long blockNum = block.getBlockHeader().toBuilder().getRawData().getNumber(); - for (Entry entry : ivkMapScanBlockNum.entrySet()) { + for (Entry entry : ivkMapScanBlockNum.entrySet()) { long start = entry.getValue(); long end = start; while (end < blockNum) { @@ -229,9 +237,10 @@ private void scanBlockByIvk() throws CipherException { ByteString.copyFrom( WalletApi.decodeFromBase58Check( getShieldedTRC20ContractAddress()))); - builder.setIvk(ByteString.copyFrom(entry.getKey().getIvk())); - builder.setAk(ByteString.copyFrom(entry.getKey().getAk())); - builder.setNk(ByteString.copyFrom(entry.getKey().getNk())); + byte[] key = ByteArray.fromHexString(entry.getKey()); + builder.setIvk(ByteString.copyFrom(ByteArray.subArray(key, 0, 32))); + builder.setAk(ByteString.copyFrom(ByteArray.subArray(key, 32, 64))); + builder.setNk(ByteString.copyFrom(ByteArray.subArray(key, 64, 96))); Optional notes = WalletApi.scanShieldedTRC20NoteByIvk( builder.build(), false); if (notes.isPresent()) { @@ -339,13 +348,13 @@ public boolean addNewShieldedTRC20Address(final ShieldedAddressInfo addressInfo, e.printStackTrace(); } } - Ivk ivk = new Ivk(); - ivk.setIvk(addressInfo.getIvk()); - ivk.setAk(addressInfo.getFullViewingKey().getAk()); - ivk.setNk(addressInfo.getFullViewingKey().getNk()); - if (!ivkMapScanBlockNum.containsKey(ivk)) { - ivkMapScanBlockNum.put(ivk, blockNum); - updateIvkAndBlockNum(ivk, blockNum); + String key = ByteArray.toHexString(ByteUtil.merge( + addressInfo.getIvk(), + addressInfo.getFullViewingKey().getAk(), + addressInfo.getFullViewingKey().getNk())); + if (!ivkMapScanBlockNum.containsKey(key)) { + ivkMapScanBlockNum.put(key, blockNum); + updateIvkAndBlockNum(key, blockNum); } return true; } @@ -357,17 +366,15 @@ public boolean addNewShieldedTRC20Address(final ShieldedAddressInfo addressInfo, * @param blockNum * @return */ - private boolean updateIvkAndBlockNum(Ivk ivk, long blockNum) { + private boolean updateIvkAndBlockNum(String ivk, long blockNum) { if (ArrayUtils.isEmpty(shieldedSkey)) { return false; } synchronized (ivkAndNumFileName) { - byte[] key = ByteUtil.merge(ivk.getIvk(), ivk.getAk(), ivk.getNk()); + byte[] key = ByteArray.fromHexString(ivk); byte[] value = ByteArray.fromLong(blockNum); - byte[] text = new byte[key.length + value.length]; - System.arraycopy(key, 0, text, 0, key.length); - System.arraycopy(value, 0, text, key.length, value.length); + byte[] text = ByteUtil.merge(key, value); try { byte[] cipherText = ZenUtils.aesCtrEncrypt(text, shieldedSkey); String data = Base58.encode(cipherText); @@ -391,13 +398,10 @@ private boolean updateIvkAndBlockNumFile() { synchronized (ivkAndNumFileName) { ZenUtils.clearFile(ivkAndNumFileName); - for (Entry entry : ivkMapScanBlockNum.entrySet()) { - byte[] key = ByteUtil.merge(entry.getKey().getIvk(), entry.getKey().getAk(), - entry.getKey().getNk()); + for (Entry entry : ivkMapScanBlockNum.entrySet()) { + byte[] key = ByteArray.fromHexString(entry.getKey()); byte[] value = ByteArray.fromLong(entry.getValue()); - byte[] text = new byte[key.length + value.length]; - System.arraycopy(key, 0, text, 0, key.length); - System.arraycopy(value, 0, text, key.length, value.length); + byte[] text = ByteUtil.merge(key, value); try { byte[] cipherText = ZenUtils.aesCtrEncrypt(text, shieldedSkey); String date = Base58.encode(cipherText); @@ -427,13 +431,10 @@ private boolean loadIvkFromFile() { byte[] cipherText = Base58.decode(list.get(i)); try { byte[] text = ZenUtils.aesCtrDecrypt(cipherText, shieldedSkey); - Ivk ivk = new Ivk(); - ivk.setIvk(Arrays.copyOfRange(text, 0, 32)); - ivk.setAk(Arrays.copyOfRange(text, 32, 64)); - ivk.setNk(Arrays.copyOfRange(text, 64, 96)); + byte[] key = Arrays.copyOfRange(text, 0, 96); byte[] value = Arrays.copyOfRange(text, 96, text.length); - ivkMapScanBlockNum.put(ivk, ByteArray.toLong(value)); + ivkMapScanBlockNum.put(ByteArray.toHexString(key), ByteArray.toLong(value)); } catch (CipherException e) { e.printStackTrace(); } @@ -784,19 +785,4 @@ public byte[] importShieldedTRC20Wallet() throws IOException, CipherException { } return result; } - - public class Ivk { - @Setter - @Getter - public byte[] ivk; - @Setter - @Getter - public byte[] ak; - @Setter - @Getter - public byte[] nk; - - public Ivk() { - } - } } diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index d944aa34a..a280d68dc 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -38,6 +38,7 @@ import org.tron.walletserver.WalletApi; import java.io.IOException; +import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.*; import java.util.Base64.Decoder; @@ -2878,6 +2879,21 @@ private void setShieldedTRC20ContractAddress(String[] parameters) { byte[] shieldedContractAddress = WalletApi.decodeFromBase58Check(parameters[1]); if (!(trc20ContractAddress == null || shieldedContractAddress == null)) { ShieldedTRC20Wrapper.getInstance().setShieldedTRC20WalletPath(parameters[0], parameters[1]); + //set scaling factor + String scalingFactorHexStr = walletApiWrapper.getScalingFactor(shieldedContractAddress); + if (scalingFactorHexStr != null) { + BigInteger scalingFactor = new BigInteger(scalingFactorHexStr, 16); + ShieldedTRC20Wrapper.getInstance().setScalingFactor(scalingFactor); + System.out.println("SetShieldedTRC20ContractAddress succeed!"); + System.out.println("The scalingFactor is " + scalingFactor.toString()); + System.out.println("That means:"); + System.out.println("If you mint " + scalingFactor.toString() + " token into " + + "shielded contract, you can get 1 shielded token."); + System.out.println("If you burn 1 shielded token into toPublicAddress, " + + "you can get " + scalingFactor.toString() + " token."); + } else { + System.out.println("Get scalingFactor failed!! Please check shielded contract!"); + } } else { System.out.println("SetShieldedTRC20ContractAddress failed !!! Invalid Address !!!"); } @@ -3041,6 +3057,10 @@ private void listShieldedTRC20Note(String[] parameters) { System.out.println(string); } } + BigInteger scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); + System.out.println("The Scaling Factor is " + scalingFactor.toString()); + System.out.println("That means 1 shielded token is equal to " + scalingFactor.toString() + + " TRC20 token"); } private void loadShieldedTRC20Wallet() throws CipherException, IOException { @@ -3184,8 +3204,8 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk throws IOException, CipherException, CancelException, ZksnarkException { int parameterIndex = 0; - long fromPublicAmount = Long.valueOf(parameters[parameterIndex++]); - if (fromPublicAmount < 0) { + BigInteger fromPublicAmount = new BigInteger(parameters[parameterIndex++]); + if (fromPublicAmount.compareTo(BigInteger.ZERO) < 0) { System.out.println("fromPublicAmount must be non-negative. "); return false; } @@ -3218,15 +3238,15 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk } String toPublicAddress = parameters[parameterIndex++]; - long toPublicAmount = 0; + BigInteger toPublicAmount = BigInteger.ZERO; if (toPublicAddress.equals("null")) { toPublicAddress = null; ++parameterIndex; } else { amountString = parameters[parameterIndex++]; if (!StringUtil.isNullOrEmpty(amountString)) { - toPublicAmount = Long.valueOf(amountString); - if (toPublicAmount <= 0) { + toPublicAmount = new BigInteger(amountString); + if (toPublicAmount.compareTo(BigInteger.ZERO) <= 0) { System.out.println("toPublicAmount must be positive. "); return false; } @@ -3270,17 +3290,18 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk } int shieldedContractType = -1; - if (fromPublicAmount > 0 && shieldedOutList.size() == 1 - && shieldedInputList.size() == 0 && toPublicAmount == 0) { + if (fromPublicAmount.compareTo(BigInteger.ZERO) > 0 && shieldedOutList.size() == 1 + && shieldedInputList.size() == 0 && toPublicAmount.compareTo(BigInteger.ZERO) == 0) { System.out.println("This is an MINT. "); shieldedContractType = 0; - } else if (fromPublicAmount == 0 && toPublicAmount == 0 + } else if (fromPublicAmount.compareTo(BigInteger.ZERO) == 0 + && toPublicAmount.compareTo(BigInteger.ZERO) == 0 && shieldedOutList.size() > 0 && shieldedOutList.size() < 3 && shieldedInputList.size() > 0 && shieldedInputList.size() < 3) { System.out.println("This is an TRANSFER. "); shieldedContractType = 1; - } else if (fromPublicAmount == 0 && shieldedOutList.size() == 0 - && shieldedInputList.size() == 1 && toPublicAmount > 0) { + } else if (fromPublicAmount.compareTo(BigInteger.ZERO) == 0 && shieldedOutList.size() == 0 + && shieldedInputList.size() == 1 && toPublicAmount.compareTo(BigInteger.ZERO) > 0) { System.out.println("This is an BURN. "); shieldedContractType = 2; } else { diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index cfec9500d..4e8eefcf5 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -2,6 +2,7 @@ import com.google.protobuf.ByteString; import io.netty.util.internal.StringUtil; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import lombok.extern.slf4j.Slf4j; @@ -1259,27 +1260,35 @@ public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end return true; } - public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, + public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAmount, List shieldedInputList, List shieldedOutputList, - String toAddress, long toAmount, + String toAddress, BigInteger toAmount, String contractAddress, String shieldedContractAddress) throws CipherException, IOException, CancelException, ZksnarkException { - if (shieldedContractType == 0 && fromAmount != shieldedOutputList.get(0).getValue()) { - System.out.println("MINT: fromPublicAmount must be equal to note value"); + BigInteger scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); + if (shieldedContractType == 0 + && BigInteger.valueOf(shieldedOutputList.get(0).getValue()) + .multiply(scalingFactor) + .compareTo(fromAmount) != 0) { + System.out.println("MINT: fromPublicAmount must be equal to noteValue * scalingFactor."); + System.out.println("The scalingFactor is " + scalingFactor.toString()); return false; } if (shieldedContractType == 2) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(0)); - if (toAmount != noteInfo.getValue()) { - System.out.println("BURN: toPublicAmount must be equal to note value"); + if (BigInteger.valueOf(noteInfo.getValue()) + .multiply(scalingFactor) + .compareTo(toAmount) != 0) { + System.out.println("BURN: toPublicAmount must be equal to noteValue * scalingFactor"); + System.out.println("The scalingFactor is " + scalingFactor.toString()); return false; } } PrivateShieldedTRC20Parameters.Builder builder = PrivateShieldedTRC20Parameters.newBuilder(); - builder.setFromAmount(fromAmount); + builder.setFromAmount(fromAmount.toString()); byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedContractAddressBytes)); @@ -1289,7 +1298,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, return false; } builder.setTransparentToAddress(ByteString.copyFrom(to)); - builder.setToAmount(toAmount); + builder.setToAmount(toAmount.toString()); } long valueBalance = 0; @@ -1352,7 +1361,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, spendTRC20NoteBuilder.setPath(ByteString.copyFrom(path)); spendTRC20NoteBuilder.setPos(noteInfo.getPosition()); - valueBalance += noteInfo.getValue(); + valueBalance = Math.addExact(valueBalance, noteInfo.getValue()); builder.addShieldedSpends(spendTRC20NoteBuilder.build()); } } else { @@ -1369,7 +1378,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, if (shieldedOutputList.size() > 0) { for (int i = 0; i < shieldedOutputList.size(); i++) { GrpcAPI.Note note = shieldedOutputList.get(i); - valueBalance -= note.getValue(); + valueBalance = Math.subtractExact(valueBalance, note.getValue()); builder.addShieldedReceives( ReceiveNote.newBuilder().setNote(note).build()); } @@ -1428,29 +1437,35 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, long fromAmount, } } - public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fromAmount, + public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInteger fromAmount, List shieldedInputList, List shieldedOutputList, - String toAddress, long toAmount, + String toAddress, BigInteger toAmount, String contractAddress, String shieldedContractAddress) throws CipherException, IOException, CancelException, ZksnarkException { - if (shieldedContractType == 0 && fromAmount != shieldedOutputList.get(0).getValue()) { - System.out.println("MINT: fromPublicAmount must be equal to note value"); + BigInteger scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); + if (shieldedContractType == 0 + && BigInteger.valueOf(shieldedOutputList.get(0).getValue()) + .multiply(scalingFactor) + .compareTo(fromAmount) != 0) { + System.out.println("MINT: fromPublicAmount must be equal to noteValue * scalingFactor"); return false; } if (shieldedContractType == 2) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(0)); - if (toAmount != noteInfo.getValue()) { - System.out.println("BURN: toPublicAmount must be equal to note value"); + if (BigInteger.valueOf(noteInfo.getValue()) + .multiply(scalingFactor) + .compareTo(toAmount) != 0) { + System.out.println("BURN: toPublicAmount must be equal to noteValue * scalingFactor"); return false; } } PrivateShieldedTRC20ParametersWithoutAsk.Builder builder = PrivateShieldedTRC20ParametersWithoutAsk.newBuilder(); - builder.setFromAmount(fromAmount); + builder.setFromAmount(fromAmount.toString()); byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedContractAddressBytes)); @@ -1460,10 +1475,11 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fr return false; } builder.setTransparentToAddress(ByteString.copyFrom(to)); - builder.setToAmount(toAmount); + builder.setToAmount(toAmount.toString()); } byte[] ask = new byte[32]; + long valueBalance = 0; if (shieldedInputList.size() > 0) { List rootAndPath = new ArrayList<>(); for (int i = 0; i < shieldedInputList.size(); i++) { @@ -1526,6 +1542,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fr spendTRC20NoteBuilder.setPos(noteInfo.getPosition()); builder.addShieldedSpends(spendTRC20NoteBuilder.build()); + valueBalance = Math.addExact(valueBalance, noteInfo.getValue()); } } else { //@TODO remove randomOvk by sha256.of(privateKey) @@ -1540,11 +1557,19 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, long fr if (shieldedOutputList.size() > 0) { for (int i = 0; i < shieldedOutputList.size(); ++i) { + GrpcAPI.Note note = shieldedOutputList.get(i); + valueBalance = Math.subtractExact(valueBalance, note.getValue()); builder.addShieldedReceives( - ReceiveNote.newBuilder().setNote(shieldedOutputList.get(i)).build()); + ReceiveNote.newBuilder().setNote(note).build()); } } + if (shieldedContractType == 1 && valueBalance != 0) { + System.out.println("TRANSFER: the sum of shielded input amount should be equal to the " + + "sum of shielded output amount"); + return false; + } + ShieldedTRC20Parameters parameters = WalletApi.createShieldedContractParametersWithoutAsk(builder.build(), ask); if (parameters == null) { @@ -1606,16 +1631,26 @@ public String getRootAndPath(String address, long position) { System.out.println("Warning: getRootAndPath failed, Please login wallet first !!"); return null; } - return wallet.getRootAndPath(shieldedContractAddress, input); + return wallet.constantCallShieldedContract(shieldedContractAddress, input, methodStr); } - public boolean setAllowance(String contractAddress, String shieldedContractAddress, long value) - throws CipherException, IOException, CancelException { + public String getScalingFactor(byte[] address) { + String methodStr = "scalingFactor()"; + byte[] input = Hex.decode(AbiUtil.parseMethod(methodStr, "", false)); + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: scalingFactor failed, Please login wallet first !!"); + return null; + } + return wallet.constantCallShieldedContract(address, input, methodStr); + } + + public boolean setAllowance(String contractAddress, String shieldedContractAddress, + BigInteger value) throws CipherException, IOException, CancelException { byte[] contractAddressBytes = WalletApi.decodeFromBase58Check(contractAddress); byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); String methodStr = "approve(address,uint256)"; byte[] mergedBytes = ByteUtil.merge(new byte[11], shieldedContractAddressBytes, - longTo32Bytes(value)); + ByteUtil.bigIntegerToBytes(value, 32)); String argsStr = ByteArray.toHexString(mergedBytes); byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, true)); byte[] ownerAddress = wallet.getAddress(); @@ -1630,11 +1665,11 @@ public boolean triggerShieldedContract(String contractAddress, String data, byte[] contractAddressBytes = WalletApi.decodeFromBase58Check(contractAddress); String methodStr; if (shieldedContractType == 0) { - methodStr = "mint(uint64,bytes32[9],bytes32[2],bytes32[21])"; + methodStr = "mint(uint256,bytes32[9],bytes32[2],bytes32[21])"; } else if (shieldedContractType == 1) { methodStr = "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])"; } else if (shieldedContractType == 2) { - methodStr = "burn(bytes32[10],bytes32[2],uint64,bytes32[2],address)"; + methodStr = "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address)"; } else { System.out.println("shieldedContractType should be 0, 1 or 2. "); return false; @@ -1647,11 +1682,11 @@ public boolean triggerShieldedContract(String contractAddress, String data, } public String encodeMintParamsToHexString(ShieldedTRC20Parameters parameters, - long value) { + BigInteger value) { byte[] mergedBytes; Contract.ReceiveDescription revDesc = parameters.getReceiveDescription(0); mergedBytes = ByteUtil.merge( - longTo32Bytes(value), + ByteUtil.bigIntegerToBytes(value, 32), revDesc.getNoteCommitment().toByteArray(), revDesc.getValueCommitment().toByteArray(), revDesc.getEpk().toByteArray(), @@ -1724,7 +1759,7 @@ public String encodeTransferParamsToHexString(ShieldedTRC20Parameters parameters return ByteArray.toHexString(mergedBytes); } - public String encodeBurnParamsToHexString(ShieldedTRC20Parameters parameters, long value, + public String encodeBurnParamsToHexString(ShieldedTRC20Parameters parameters, BigInteger value, String transparentToAddress) { byte[] mergedBytes; byte[] payTo = new byte[32]; @@ -1738,7 +1773,7 @@ public String encodeBurnParamsToHexString(ShieldedTRC20Parameters parameters, lo spendDesc.getRk().toByteArray(), spendDesc.getZkproof().toByteArray(), spendDesc.getSpendAuthoritySignature().toByteArray(), - longTo32Bytes(value), + ByteUtil.bigIntegerToBytes(value, 32), parameters.getBindingSignature().toByteArray(), payTo ); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 9da731d4c..aa17d8e59 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2484,14 +2484,14 @@ public static Optional scanShieldedTRC20NoteByOvk( return Optional.empty(); } - public String getRootAndPath(byte[] contractAddress, byte[] data) { + public String constantCallShieldedContract(byte[] contractAddress, byte[] data, String functionName) { byte[] address = getAddress(); Contract.TriggerSmartContract triggerContract = triggerCallContract(address, contractAddress, 0, data, 0, ""); TransactionExtention transactionExtention = rpcCli.triggerConstantContract(triggerContract); if (transactionExtention == null || !transactionExtention.getResult().getResult()) { - System.out.println("getPath failed!"); + System.out.println("Get " + functionName + " failed!"); System.out.println("Code = " + transactionExtention.getResult().getCode()); System.out.println( "Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); @@ -2509,16 +2509,6 @@ public String getRootAndPath(byte[] contractAddress, byte[] data) { } } - // public static boolean createShieldedContractParameters( - // PrivateShieldedTRC20Parameters parameters) { - // return rpcCli.createShieldedContractParameters(parameters); - // } - // - // public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( - // PrivateShieldedTRC20ParametersWithoutAsk parameters) { - // return rpcCli.createShieldedContractParametersWithoutAsk(parameters); - // } - public static ShieldedTRC20Parameters createShieldedContractParameters( PrivateShieldedTRC20Parameters privateParameters) { try { diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6a3a113dc..9ea00cfdd 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1329,11 +1329,11 @@ message PrivateShieldedTRC20Parameters { bytes ask = 1; bytes nsk = 2; bytes ovk = 3; - int64 from_amount = 4; + string from_amount = 4; repeated SpendNoteTRC20 shielded_spends = 5; repeated ReceiveNote shielded_receives = 6; bytes transparent_to_address = 7; - int64 to_amount = 8; + string to_amount = 8; bytes shielded_TRC20_contract_address = 9; } @@ -1341,11 +1341,11 @@ message PrivateShieldedTRC20ParametersWithoutAsk { bytes ak = 1; bytes nsk = 2; bytes ovk = 3; - int64 from_amount = 4; + string from_amount = 4; repeated SpendNoteTRC20 shielded_spends = 5; repeated ReceiveNote shielded_receives = 6; bytes transparent_to_address = 7; - int64 to_amount = 8; + string to_amount = 8; bytes shielded_TRC20_contract_address = 9; } @@ -1400,6 +1400,6 @@ message NullifierResult { message ShieldedTRC20TriggerContractParameters { ShieldedTRC20Parameters shielded_TRC20_Parameters = 1; repeated BytesMessage spend_authority_signature = 2; - int64 amount = 3; + string amount = 3; bytes transparent_to_address = 4; } \ No newline at end of file From a583206fb5d5faebc9efd0e450e27476285999d9 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 15 May 2020 20:30:03 +0800 Subject: [PATCH 313/445] add scaling factor, fix typos --- README.md | 50 ++++++++++++++----- src/main/java/org/tron/walletcli/Client.java | 4 +- .../org/tron/walletcli/WalletApiWrapper.java | 6 +-- .../java/org/tron/walletserver/WalletApi.java | 2 +- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 448247928..175da452f 100644 --- a/README.md +++ b/README.md @@ -1444,7 +1444,9 @@ d :2fd028965d3b455579ab28 ### SetShieldedTRC20ContractAddress TRC20ContractAddress ShieldedContractAddress -Set TRC20 contract address and shielded contract address. Please execute this command before you perform all operations related to the shielded transaction of TRC20. +Set TRC20 contract address and shielded contract address. Please execute this command before you perform all operations related to the shielded transaction of TRC20 token. + +When you execute this command, the `Scaling Factor` will be shown. The `Scaling Factor` is set in the shileded contract, and defines the ratio of public TRC20 token to shielded token. For example, the `Scaling Factor` is 1000, if you mint 1 shielded token, you need 1000 TRC20 tokens; if you burn 1 shielded token, you can get 1000 TRC20 tokens. TRC20ContractAddress > TRC20 contract address @@ -1456,6 +1458,12 @@ Example: ```console > SetShieldedTRC20ContractAddress TLDxNTzNvEPd4gHox8V1zK2w82LFnideKE TKERuAmhJh8vZi1dzJtx8926xeCT74747e +scalingFactor():ed3437f8 +SetShieldedTRC20ContractAddress succeed! +The Scaling Factor is 1000 +That means: +If you mint 1000 token into shielded contract, you can get 1 shielded token. +If you burn 1 shielded token into toPublicAddress, you can get 1000 token. ``` ### LoadShieldedTRC20Wallet @@ -1511,12 +1519,12 @@ ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f4 Shielded transfer, support three types: -- MINT: one public address to one shielded address, fromAmount shoule be equal to shielded output amount. -- TRANSFER: one or two shielded address(es) to one or two shielded address(es), the sum of shielded input amount should be equal to the sum of shielded output amount. -- BURN: one shielded address to one public address, shielded input amount should be equal to toAmount. +- MINT: transfer from one public address to one shielded address, fromAmount shoule be equal to the product of Scaling Factor and shielded output amount. +- TRANSFER: transfer from one or two shielded address(es) to one or two shielded address(es), the sum of shielded input amount should be equal to the sum of shielded output amount. +- BURN: transfer from one shielded address to one public address, toAmount shoule be equal to the product of Scaling Factor and shielded input amount. fromAmount -> The amount transfer from public address. If the transfer type is MINT, this variable must be positive, or it must be 0. +> The amount transfer from public address. If the transfer type is MINT, this variable must be the product of Scaling Factor and shielded output amount, otherwise it must be 0. shieldedInputNum > The number of shielded input note, should be 0, 1 or 2. If the transfer type is MINT, this variable must be 0; if BURN, it must be 1. @@ -1528,10 +1536,10 @@ publicToAddress > Public to address. If the transfer type is BURN, this variable must be a valid address, otherwise it should be set null. toAmount -> The amount transfer to public address. If publicToAddress set to null, this variable must be 0. +> The amount transfer to public address. If the transfer type is BURN, this variable must be the product of Scaling Factor and shielded input amount, otherwise it should be 0. shieldedOutputNum -> The amount of shielded output note. That is the number of (shieldedAddress amount meno) pairs, should be 0, 1 or 2. +> The amount of shielded output note. That is the number of (shieldedAddress amount memo) pairs, should be 0, 1 or 2. shieldedAddress1/shieldedAddress2 > Output shielded address @@ -1544,17 +1552,19 @@ memo1/memo2 Example: +In this example, the scalingFactor is 1000. + 1. MINT - **When in this mode, Some variables must be set as follows, shieldedInputNum=0, publicToAddress=null, toAmount=0** + **In this mode, some variables must be set as follows, shieldedInputNum = 0, publicToAddress = null, toAmount = 0.** ```console - > SendShieldedTRC20Coin 1000000000 0 null 0 1 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 null + > SendShieldedTRC20Coin 1000000000000 0 null 0 1 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 null ``` 2. TRANSFER - **When in this mode, Some variables must be set as follows, fromAmount=0, publicToAddress=null,toAmount=0** + **In this mode, some variables must be set as follows, fromAmount = 0, publicToAddress = null,toAmount = 0.** Transfer from one shielded address to one shielded address. ```console @@ -1564,6 +1574,8 @@ Example: The unspent note list is shown below: 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend 8 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 UnSpend + The Scaling Factor is 1000 + That means 1 shielded token is equal to 1000 TRC20 token > SendShieldedTRC20Coin 0 1 8 null 0 1 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 null ``` @@ -1576,6 +1588,8 @@ Example: The unspent note list is shown below: 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend + The Scaling Factor is 1000 + That means 1 shielded token is equal to 1000 TRC20 token > SendShieldedTRC20Coin 0 1 9 null 0 2 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 test1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 null ``` @@ -1589,6 +1603,8 @@ Example: 11 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 UnSpend test1 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend + The Scaling Factor is 1000 + That means 1 shielded token is equal to 1000 TRC20 token > SendShieldedTRC20Coin 0 2 10 11 null 0 1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 null ``` @@ -1601,13 +1617,15 @@ Example: The unspent note list is shown below: 13 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 UnSpend 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend + The Scaling Factor is 1000 + That means 1 shielded token is equal to 1000 TRC20 token > SendShieldedTRC20Coin 0 2 12 13 null 0 2 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 null ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 null ``` 3. BURN - **When in this mode, Some variables must be set as follows, fromAmount=0, shieldedInputNum=1, shieldedOutputNum=0** + **In this mode, some variables must be set as follows, fromAmount = 0, shieldedInputNum = 1, shieldedOutputNum = 0.** > ListShieldedTRC20Note This command will show all the unspent notes. @@ -1615,8 +1633,10 @@ Example: The unspent note list is shown below: 15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend 14 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 UnSpend + The Scaling Factor is 1000 + That means 1 shielded token is equal to 1000 TRC20 token - > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 1300000000 0 + > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 1300000000000 0 ``` ### SendShieldedTRC20CoinWithoutAsk @@ -1625,7 +1645,7 @@ Usage and parameters are consistent with the command SendShieldedTRC20Coin, the ### ListShieldedTRC20Note type -List the note scanned by the local cache address. +List the note scanned by the local cache address, and the `Scaling Factor`. type > Shows the type of note. If the variable is omitted or set to 0, it shows all unspent notes; For other values, it shows all the notes, including spent notes and unspent notes. @@ -1638,6 +1658,8 @@ This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: 15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend +The Scaling Factor is 1000 +That means 1 shielded token is equal to 1000 TRC20 token > ListShieldedTRC20Note 1 All notes are shown below: @@ -1657,6 +1679,8 @@ ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hz ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 12 Spent ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 13 Spent ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 14 Spent +The Scaling Factor is 1000 +That means 1 shielded token is equal to 1000 TRC20 token ``` ### ResetShieldedTRC20Note diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index a280d68dc..2f181f661 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2885,14 +2885,14 @@ private void setShieldedTRC20ContractAddress(String[] parameters) { BigInteger scalingFactor = new BigInteger(scalingFactorHexStr, 16); ShieldedTRC20Wrapper.getInstance().setScalingFactor(scalingFactor); System.out.println("SetShieldedTRC20ContractAddress succeed!"); - System.out.println("The scalingFactor is " + scalingFactor.toString()); + System.out.println("The Scaling Factor is " + scalingFactor.toString()); System.out.println("That means:"); System.out.println("If you mint " + scalingFactor.toString() + " token into " + "shielded contract, you can get 1 shielded token."); System.out.println("If you burn 1 shielded token into toPublicAddress, " + "you can get " + scalingFactor.toString() + " token."); } else { - System.out.println("Get scalingFactor failed!! Please check shielded contract!"); + System.out.println("Get Scaling Factor failed!! Please check shielded contract!"); } } else { System.out.println("SetShieldedTRC20ContractAddress failed !!! Invalid Address !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 4e8eefcf5..b10732021 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1272,7 +1272,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm .multiply(scalingFactor) .compareTo(fromAmount) != 0) { System.out.println("MINT: fromPublicAmount must be equal to noteValue * scalingFactor."); - System.out.println("The scalingFactor is " + scalingFactor.toString()); + System.out.println("The Scaling Factor is " + scalingFactor.toString()); return false; } if (shieldedContractType == 2) { @@ -1282,7 +1282,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm .multiply(scalingFactor) .compareTo(toAmount) != 0) { System.out.println("BURN: toPublicAmount must be equal to noteValue * scalingFactor"); - System.out.println("The scalingFactor is " + scalingFactor.toString()); + System.out.println("The Scaling Factor is " + scalingFactor.toString()); return false; } } @@ -1638,7 +1638,7 @@ public String getScalingFactor(byte[] address) { String methodStr = "scalingFactor()"; byte[] input = Hex.decode(AbiUtil.parseMethod(methodStr, "", false)); if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: scalingFactor failed, Please login wallet first !!"); + System.out.println("Warning: get Scaling Factor failed, Please login wallet first !!"); return null; } return wallet.constantCallShieldedContract(address, input, methodStr); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index aa17d8e59..0fc66306b 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2478,7 +2478,7 @@ public static Optional scanShieldedTRC20NoteByOvk( } catch (Exception e) { if (showErrorMsg) { Status status = Status.fromThrowable(e); - System.out.println("scanNoteByOvk failed,error " + status.getDescription()); + System.out.println("ScanShieldedTRC20NoteByOvk failed,error " + status.getDescription()); } } return Optional.empty(); From 9fcec5418788ff85248efa05dc30ba5fd164edd6 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 29 May 2020 15:32:54 +0800 Subject: [PATCH 314/445] fix code style --- src/main/protos/api/api.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 9ea00cfdd..60987b9be 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1378,7 +1378,7 @@ message DecryptNotesTRC20 { message NoteTx { Note note = 1; int64 position = 2; - bool isSpent = 3; + bool is_spent = 3; bytes txid = 4; int32 index = 5; //the index of note in txid } @@ -1394,7 +1394,7 @@ message NfTRC20Parameters { } message NullifierResult { - bool isSpent = 1; + bool is_spent = 1; } message ShieldedTRC20TriggerContractParameters { From e869c5c138ad832716b9c9d9fc1c86dc0dc02b2d Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 29 May 2020 21:37:43 +0800 Subject: [PATCH 315/445] fix the display of shielded amount --- README.md | 223 ++++++++++++------ .../tron/core/zen/ShieldedTRC20NoteInfo.java | 17 +- .../tron/core/zen/ShieldedTRC20Wrapper.java | 54 ++++- src/main/java/org/tron/walletcli/Client.java | 147 ++++++------ .../org/tron/walletcli/WalletApiWrapper.java | 105 +++++++-- 5 files changed, 369 insertions(+), 177 deletions(-) diff --git a/README.md b/README.md index 175da452f..8a843cd47 100644 --- a/README.md +++ b/README.md @@ -1073,6 +1073,7 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 **BroadcastTransaction** > Broadcast the transaction, where the transaction is in hex string format. + ## How to transfer TRC20 token to shielded address +### GetSpendingKey + +Generate a sk + +Example: + +```console +> GetSpendingKey +0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a +``` + +### getExpandedSpendingKey sk + +Generate ask, nsk, ovk from sk + +Example: + +```console +> getExpandedSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a +ask:252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e +nsk:5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 +ovk:892a10c1d3e8ea22242849e13f177d69e1180d1d5bba118c586765241ba2d3d6 +``` + +### getAkFromAsk ask +Generate ak from ask + +Example: + +```console +> GetAkFromAsk 252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e +ak:f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 +``` + +### getNkFromNsk nsk + +Generate nk from nsk + +Example: + +```console +> GetNkFromNsk 5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 +nk:ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a +``` + +### getIncomingViewingKey ak[64] nk[64] + +Generate ivk from ak and nk + +Example: + +```console +> getincomingviewingkey f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a +ivk:148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 +``` + +### GetDiversifier + +Generate a diversifier + +Example: + +```console +> GetDiversifier +11db4baf6bd5d5afd3a8b5 +``` + +### getshieldedpaymentaddress ivk[64] d[22] + +Generate a shielded address from sk and d + +Example: + +```console +GetShieldedPaymentAddress 148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 11db4baf6bd5d5afd3a8b5 +pkd:65c11642115d386ed716b9cc06a3498e86e303d7f20d0869c9de90e31322ac15 +shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z +``` + ### SetShieldedTRC20ContractAddress TRC20ContractAddress ShieldedContractAddress -Set TRC20 contract address and shielded contract address. Please execute this command before you perform all operations related to the shielded transaction of TRC20 token. +Set TRC20 contract address and shielded contract address. Please execute this command before you perform all the following operations related to the shielded transaction of TRC20 token except `ScanShieldedTRC20NoteByIvk` and `ScanShieldedTRC20NoteByOvk`. -When you execute this command, the `Scaling Factor` will be shown. The `Scaling Factor` is set in the shileded contract, and defines the ratio of public TRC20 token to shielded token. For example, the `Scaling Factor` is 1000, if you mint 1 shielded token, you need 1000 TRC20 tokens; if you burn 1 shielded token, you can get 1000 TRC20 tokens. +When you execute this command, the `Scaling Factor` will be shown. The `Scaling Factor` is set in the shileded contract. TRC20ContractAddress > TRC20 contract address @@ -1462,8 +1543,7 @@ scalingFactor():ed3437f8 SetShieldedTRC20ContractAddress succeed! The Scaling Factor is 1000 That means: -If you mint 1000 token into shielded contract, you can get 1 shielded token. -If you burn 1 shielded token into toPublicAddress, you can get 1000 token. +No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 ``` ### LoadShieldedTRC20Wallet @@ -1519,24 +1599,24 @@ ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f4 Shielded transfer, support three types: -- MINT: transfer from one public address to one shielded address, fromAmount shoule be equal to the product of Scaling Factor and shielded output amount. +- MINT: transfer from one public address to one shielded address, fromAmount shoule be equal to the shielded output amount. - TRANSFER: transfer from one or two shielded address(es) to one or two shielded address(es), the sum of shielded input amount should be equal to the sum of shielded output amount. -- BURN: transfer from one shielded address to one public address, toAmount shoule be equal to the product of Scaling Factor and shielded input amount. +- BURN: transfer from one shielded address to one public address, toAmount shoule be equal to the shielded input amount. fromAmount -> The amount transfer from public address. If the transfer type is MINT, this variable must be the product of Scaling Factor and shielded output amount, otherwise it must be 0. +> The amount transfer from public address. If the transfer type is MINT, this variable must be equal to the shielded output amount, otherwise it must be 0. shieldedInputNum > The number of shielded input note, should be 0, 1 or 2. If the transfer type is MINT, this variable must be 0; if BURN, it must be 1. input1/input2 -> The index of shielded input note, get from execute command ListShieldedTRC20Note. If shieldedInputNum set to 0, no need to set. +> The index of shielded input note, get from executing command ListShieldedTRC20Note. If shieldedInputNum set to 0, no need to set. publicToAddress > Public to address. If the transfer type is BURN, this variable must be a valid address, otherwise it should be set null. toAmount -> The amount transfer to public address. If the transfer type is BURN, this variable must be the product of Scaling Factor and shielded input amount, otherwise it should be 0. +> The amount transfer to public address. If the transfer type is BURN, this variable must be equal to the shielded input amount, otherwise it should be 0. shieldedOutputNum > The amount of shielded output note. That is the number of (shieldedAddress amount memo) pairs, should be 0, 1 or 2. @@ -1552,14 +1632,14 @@ memo1/memo2 Example: -In this example, the scalingFactor is 1000. +In this example, the scalingFactor is 1000. 1. MINT **In this mode, some variables must be set as follows, shieldedInputNum = 0, publicToAddress = null, toAmount = 0.** ```console - > SendShieldedTRC20Coin 1000000000000 0 null 0 1 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 null + > SendShieldedTRC20Coin 1000000000000 0 null 0 1 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000000 null ``` 2. TRANSFER @@ -1572,12 +1652,12 @@ In this example, the scalingFactor is 1000. This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: - 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend - 8 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 UnSpend + 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend + 8 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 UnSpend The Scaling Factor is 1000 - That means 1 shielded token is equal to 1000 TRC20 token + No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 - > SendShieldedTRC20Coin 0 1 8 null 0 1 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 null + > SendShieldedTRC20Coin 0 1 8 null 0 1 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000000 null ``` Transfer from one shielded address to two shielded addresses. @@ -1586,12 +1666,12 @@ In this example, the scalingFactor is 1000. This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: - 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend - 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend + 9 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 UnSpend + 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend The Scaling Factor is 1000 - That means 1 shielded token is equal to 1000 TRC20 token + No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 - > SendShieldedTRC20Coin 0 1 9 null 0 2 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 test1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 null + > SendShieldedTRC20Coin 0 1 9 null 0 2 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000000 test1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000000 null ``` Transfer from two shielded addresses to one shielded address. @@ -1600,13 +1680,13 @@ In this example, the scalingFactor is 1000. This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: - 11 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 UnSpend test1 - 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend - 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend + 11 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 UnSpend test1 + 10 ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 UnSpend + 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend The Scaling Factor is 1000 - That means 1 shielded token is equal to 1000 TRC20 token + No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 - > SendShieldedTRC20Coin 0 2 10 11 null 0 1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 null + > SendShieldedTRC20Coin 0 2 10 11 null 0 1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000000 null ``` Transfer from two shielded addresses to two shielded addresses. @@ -1615,28 +1695,28 @@ In this example, the scalingFactor is 1000. This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: - 13 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 UnSpend - 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend + 13 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 UnSpend + 12 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 UnSpend The Scaling Factor is 1000 - That means 1 shielded token is equal to 1000 TRC20 token + No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 - > SendShieldedTRC20Coin 0 2 12 13 null 0 2 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 null ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 null + > SendShieldedTRC20Coin 0 2 12 13 null 0 2 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000000 null ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000000 null ``` 3. BURN **In this mode, some variables must be set as follows, fromAmount = 0, shieldedInputNum = 1, shieldedOutputNum = 0.** - + ```console > ListShieldedTRC20Note This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: - 15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend - 14 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 UnSpend + 15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend + 14 ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 UnSpend The Scaling Factor is 1000 - That means 1 shielded token is equal to 1000 TRC20 token + No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 - > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 1300000000000 0 + > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 1300000000000000 0 ``` ### SendShieldedTRC20CoinWithoutAsk @@ -1657,30 +1737,30 @@ Example: This command will show all the unspent notes. If you want to display all notes, including spent notes and unspent notes, please use command ListShieldedTRC20Note 1 The unspent note list is shown below: -15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend +15 ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 UnSpend The Scaling Factor is 1000 -That means 1 shielded token is equal to 1000 TRC20 token +No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 > ListShieldedTRC20Note 1 All notes are shown below: -ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 15 UnSpent -ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 dc02678b0cf1c93c557dc805edb776fe79201c77f210f08f60cea5d687b14f2e 0 0 Spent -ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 1000000000 e4d35d147762020078d7d197c98fffde181250e4a637d4bdd9ca809116d74131 0 2 Spent -ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 1594e1ee06c8420a4f1d80670000cd9268a2ff4e97e3f630909feeb51a9de993 0 3 Spent -ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 2000000000 3f035e966b3ef636ae9c0a0f64bff781b1d1a8b52bab5d8124c0f9162f71f68f 0 1 Spent -ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 300000000 6929757cb86cb6cf3e89df19f3212c3e62070b12d8b36de48e663fed214a4082 0 4 Spent test1 -ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2000000000 e39a1e1d5af7dcbab0d55a63a0c62ec9cc7c0aaf8ce98733802674c3ec1f3a06 0 6 Spent -ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq 700000000 6929757cb86cb6cf3e89df19f3212c3e62070b12d8b36de48e663fed214a4082 1 5 Spent -ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq 2300000000 4ce1ce9f6377ee3cd936757b696ac43ecc39ee6e8a0eab1b8f8ef093e15010f8 0 7 Spent -ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 8 Spent -ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 9 Spent -ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 10 Spent -ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 11 Spent test1 -ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 12 Spent -ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 13 Spent -ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 14 Spent +ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 1700000000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 1 15 UnSpent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000000 dc02678b0cf1c93c557dc805edb776fe79201c77f210f08f60cea5d687b14f2e 0 0 Spent +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 1000000000000 e4d35d147762020078d7d197c98fffde181250e4a637d4bdd9ca809116d74131 0 2 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000000 1594e1ee06c8420a4f1d80670000cd9268a2ff4e97e3f630909feeb51a9de993 0 3 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 2000000000000 3f035e966b3ef636ae9c0a0f64bff781b1d1a8b52bab5d8124c0f9162f71f68f 0 1 Spent +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 300000000000 6929757cb86cb6cf3e89df19f3212c3e62070b12d8b36de48e663fed214a4082 0 4 Spent test1 +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2000000000000 e39a1e1d5af7dcbab0d55a63a0c62ec9cc7c0aaf8ce98733802674c3ec1f3a06 0 6 Spent +ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq 700000000000 6929757cb86cb6cf3e89df19f3212c3e62070b12d8b36de48e663fed214a4082 1 5 Spent +ztron109r3w5gpm0qcf67r67a9ftjt3zy9wmzux4fqgtgcql8gwhcmauv5dm6t9t9x9ht7h3lvs8shxhq 2300000000000 4ce1ce9f6377ee3cd936757b696ac43ecc39ee6e8a0eab1b8f8ef093e15010f8 0 7 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1000000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 0 8 Spent +ztron1tjgkfk9hgrl0u6d07w3hq0s9jtgq9q64vek3e5l447dmnzhe27yy0ftpee45h07sa092wkrgrjl 2000000000000 23f171f6552680b553707715bead8de807a70255c0b091f7e788bf3b59fe3bea 1 9 Spent +ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1000000000000 81a06080f2be3f795c506826e066b9bb5327ca234eb31a0ef2446e11339a3935 0 10 Spent +ztron1da9rnkmnzl89kqq87gzh534xmkdhq9cnm0j39lackskrhflfe9d26chnq3adl86es0jm2098hzc 1500000000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 0 11 Spent test1 +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 500000000000 35901973a96369618e5e3f7f4dcede2b5ddb5bc99bf6feac29f2706420ea99c0 1 12 Spent +ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 2500000000000 6ec74435e32261a6dfe10f9498b3ab5a5cfede7c4e31299752b449b9506efc11 0 13 Spent +ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f46 1300000000000 7291b2c58cafb4dede626388f12e846470441f9bb05581221fd742bdd8909a24 0 14 Spent The Scaling Factor is 1000 -That means 1 shielded token is equal to 1000 TRC20 token +No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 ``` ### ResetShieldedTRC20Note @@ -1711,7 +1791,7 @@ endNum Example: -> ScanShieldedTRC20NoteByIvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e fe8203f3dc5feb2497986512f94a3b9631bffec02aee0bca735639742d2cef07 a150fa887ed45b6d9eca73ec94e1dd53dddf945cc947fbc6a2c5cc334c940233 696aca6b5db2f42041850423d63d95574ad1256519716507c5c074a924e47b0c 500 1499 +> ScanShieldedTRC20NoteByIvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e fe8203f3dc5feb2497986512f94a3b9631bffec02aee0bca735639742d2cef07 a150fa887ed45b6d9eca73ec94e1dd53dddf945cc947fbc6a2c5cc334c940233 696aca6b5db2f42041850423d63d95574ad1256519716507c5c074a924e47b0c 0 1000 ### ScanShieldedTRC20NoteByOvk shieldedTRC20ContarctAddress ovk startNum endNum @@ -1731,7 +1811,7 @@ endNum Example: -> ScanShieldedTRC20NoteByOvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e 00bad26a4f1d2380345fd2ab28008a593ba9a2e19c75cba28333afa73c89e6d8 500 1499 +> ScanShieldedTRC20NoteByOvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e 00bad26a4f1d2380345fd2ab28008a593ba9a2e19c75cba28333afa73c89e6d8 0 1000 ### BackupShieldedTRC20Wallet @@ -1778,7 +1858,7 @@ ImportShieldedTRC20Wallet successfully !!! ### ShowShieldedTRC20AddressInfo -Display information about shielded addresses +Display information about shielded addresses. If this address is not in the wallet, it will only display `d` and `pkd` Example: @@ -1796,6 +1876,10 @@ ivk:7d2e9c14ff1d82843f39cb69e8bcc228370e4ea8750669bba79e90c485d94c03 ovk:2c3d164fffa63b41a34f495e0c9d8af79d595cfb07db1539545ddcecf046d66e pkd:70d84ee492ad5d0f3ba80ce902f513ccad717f6b57bd8e89b51b8b896ab87922 d :da5fd7e087d48e3dcebff3 + +wallet> ShowShieldedTRC20AddressInfo ztron1z8d5htmt6h26l5agk8r7wxw9pyhc0a78hl5thva4k9kcn7fsqvygchyt3n2ncy0r4xv4j5mywnu +pkd:c7e719c5092f87f7c7bfe8bbb3b5b16d89f93003088c5c8b8cd53c11e3a99959 +d :11db4baf6bd5d5afd3a8b1 ``` ## Command List @@ -1807,7 +1891,7 @@ For more information on a specific command, just type the command on terminal wh ApproveProposal AssetIssue BackupShieldedTRC20Wallet - BackupShieldedWallet + BackupWallet BackupWallet2Base64 BroadcastTransaction @@ -1825,7 +1909,7 @@ For more information on a specific command, just type the command on terminal wh ExchangeWithdraw FreezeBalance GenerateAddress - GenerateShieldedAddress + GenerateShieldedTRC20Address GetAccount GetAccountNet @@ -1849,7 +1933,8 @@ For more information on a specific command, just type the command on terminal wh GetExpandedSpendingKey GetIncomingViewingKey GetNextMaintenanceTime - GetShieldedNullifier + + GetShieldedPaymentAddress GetSpendingKey GetProposal GetTotalTransaction @@ -1861,7 +1946,7 @@ For more information on a specific command, just type the command on terminal wh GetTransactionsToThis GetTransactionSignWeight ImportShieldedTRC20Wallet - ImportShieldedWallet + ImportWallet ImportWalletByBase64 ListAssetIssue @@ -1869,34 +1954,34 @@ For more information on a specific command, just type the command on terminal wh ListExchanges ListExchangesPaginated ListNodes - ListShieldedAddress - ListShieldedNote + + ListShieldedTRC20Address ListShieldedTRC20Note ListProposals ListProposalsPaginated ListWitnesses - LoadShieldedWallet + LoadShieldedTRC20Wallet Login Logout ParticipateAssetIssue RegisterWallet - ResetShieldedNote + ResetShieldedTRC20Note - ScanAndMarkNotebyAddress - ScanNotebyIvk - ScanNotebyOvk + + + ScanShieldedTRC20NoteByIvk ScanShieldedTRC20NoteByOvk SendCoin - SendShieldedCoin - SendShieldedCoinWithoutAsk + + SendShieldedTRC20Coin SendShieldedTRC20CoinWithoutAsk SetAccountId SetShieldedTRC20ContractAddress - ShowShieldedAddressInfo + ShowShieldedTRC20AddressInfo TransferAsset TriggerContract diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java b/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java index 48055dcd9..2c12715a4 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20NoteInfo.java @@ -1,6 +1,7 @@ package org.tron.core.zen; import io.netty.util.internal.StringUtil; +import java.math.BigInteger; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -15,6 +16,9 @@ public class ShieldedTRC20NoteInfo { public long value = 0; @Setter @Getter + public BigInteger rawValue = BigInteger.ZERO;//rawValue = value * scalingFactor + @Setter + @Getter public String paymentAddress; @Setter @Getter @@ -53,6 +57,8 @@ public String encode(byte[] encryptKey) throws CipherException { encodeString += ";"; encodeString += String.valueOf(value); encodeString += ";"; + encodeString += rawValue.toString(); + encodeString += ";"; encodeString += String.valueOf(index); encodeString += ";"; encodeString += String.valueOf(position); @@ -80,7 +86,7 @@ public boolean decode(String data, byte[] encryptKey) throws CipherException { data = new String(text); String[] sourceStrArray = data.split(";"); - if (sourceStrArray.length != 8) { + if (sourceStrArray.length != 9) { System.out.println("len is not right."); return false; } @@ -89,12 +95,13 @@ public boolean decode(String data, byte[] encryptKey) throws CipherException { r = ByteArray.fromHexString(sourceStrArray[2]); trxId = sourceStrArray[3]; value = Long.valueOf(sourceStrArray[4]); - index = Integer.valueOf(sourceStrArray[5]); - position = Long.valueOf(sourceStrArray[6]); - if (sourceStrArray[7].equals("null")) { + rawValue = new BigInteger(sourceStrArray[5]); + index = Integer.valueOf(sourceStrArray[6]); + position = Long.valueOf(sourceStrArray[7]); + if (sourceStrArray[8].equals("null")) { memo = ByteArray.fromHexString(""); } else { - memo = ByteArray.fromHexString(sourceStrArray[7]); + memo = ByteArray.fromHexString(sourceStrArray[8]); } return true; } diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java index 606fffe6e..44ce9958a 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -13,6 +13,8 @@ import org.tron.common.utils.Utils; import org.tron.core.exception.CipherException; import org.tron.core.exception.ZksnarkException; +import org.tron.core.zen.address.KeyIo; +import org.tron.core.zen.address.PaymentAddress; import org.tron.keystore.SKeyCapsule; import org.tron.keystore.SKeyEncryptor; import org.tron.keystore.StringUtils; @@ -33,7 +35,7 @@ public class ShieldedTRC20Wrapper { private static String prefixFolder; private static String trc20ContractAddress; - private static String shieldedTRC20ContarctAddress; + private static String shieldedTRC20ContractAddress; private static String ivkAndNumFileName; private static String unspendNoteFileName; private static String spendNoteFileName; @@ -76,7 +78,7 @@ public static ShieldedTRC20Wrapper getInstance() { public static boolean isSetShieldedTRC20WalletPath() { return !(prefixFolder == null || trc20ContractAddress == null - || shieldedTRC20ContarctAddress == null || ivkAndNumFileName == null + || shieldedTRC20ContractAddress == null || ivkAndNumFileName == null || unspendNoteFileName == null || spendNoteFileName == null || shieldedAddressFileName == null || shieldedSkeyFileName == null); } @@ -85,13 +87,13 @@ public void setShieldedTRC20WalletPath(String contractAddress, String shieldedContractAddress) { if (contractAddress == null || shieldedContractAddress == null || !contractAddress.equals(trc20ContractAddress) - || !shieldedContractAddress.equals(shieldedTRC20ContarctAddress)) { + || !shieldedContractAddress.equals(shieldedTRC20ContractAddress)) { loadShieldedStatus = false; shieldedSkey = null; trc20ContractAddress = contractAddress; - shieldedTRC20ContarctAddress = shieldedContractAddress; + shieldedTRC20ContractAddress = shieldedContractAddress; prefixFolder = "WalletShieldedTRC20Contract/" - + trc20ContractAddress + "_" + shieldedTRC20ContarctAddress; + + trc20ContractAddress + "_" + shieldedTRC20ContractAddress; ivkAndNumFileName = prefixFolder + "/scanblocknumber"; unspendNoteFileName = prefixFolder + "/unspendnote"; spendNoteFileName = prefixFolder + "/spendnote"; @@ -108,7 +110,7 @@ public BigInteger getScalingFactor() { } public String getShieldedTRC20ContractAddress() { - return shieldedTRC20ContarctAddress; + return shieldedTRC20ContractAddress; } public String getTRC20ContractAddress() { @@ -221,6 +223,12 @@ private void scanBlockByIvk() throws CipherException { if (block != null) { long blockNum = block.getBlockHeader().toBuilder().getRawData().getNumber(); for (Entry entry : ivkMapScanBlockNum.entrySet()) { + byte[] key = ByteArray.fromHexString(entry.getKey()); + byte[] ivk = ByteArray.subArray(key, 0, 32); + byte[] ak = ByteArray.subArray(key, 32, 64); + byte[] nk = ByteArray.subArray(key, 64, 96); + //find a shieldedAddressInfo whose ivk is equal to this ivk + ShieldedAddressInfo sampleAdressInfo = getShieldedAddressInfoFromIvk(ivk); long start = entry.getValue(); long end = start; while (end < blockNum) { @@ -237,10 +245,9 @@ private void scanBlockByIvk() throws CipherException { ByteString.copyFrom( WalletApi.decodeFromBase58Check( getShieldedTRC20ContractAddress()))); - byte[] key = ByteArray.fromHexString(entry.getKey()); - builder.setIvk(ByteString.copyFrom(ByteArray.subArray(key, 0, 32))); - builder.setAk(ByteString.copyFrom(ByteArray.subArray(key, 32, 64))); - builder.setNk(ByteString.copyFrom(ByteArray.subArray(key, 64, 96))); + builder.setIvk(ByteString.copyFrom(ivk)); + builder.setAk(ByteString.copyFrom(ak)); + builder.setNk(ByteString.copyFrom(nk)); Optional notes = WalletApi.scanShieldedTRC20NoteByIvk( builder.build(), false); if (notes.isPresent()) { @@ -250,7 +257,9 @@ private void scanBlockByIvk() throws CipherException { ShieldedTRC20NoteInfo noteInfo = new ShieldedTRC20NoteInfo(); noteInfo.setPaymentAddress(noteTx.getNote().getPaymentAddress()); noteInfo.setR(noteTx.getNote().getRcm().toByteArray()); - noteInfo.setValue(noteTx.getNote().getValue()); + long noteValue = noteTx.getNote().getValue(); + noteInfo.setValue(noteValue); + noteInfo.setRawValue(BigInteger.valueOf(noteValue).multiply(scalingFactor)); noteInfo.setTrxId(ByteArray.toHexString(noteTx.getTxid().toByteArray())); noteInfo.setIndex(noteTx.getIndex()); noteInfo.setNoteIndex(nodeIndex.getAndIncrement()); @@ -263,6 +272,18 @@ private void scanBlockByIvk() throws CipherException { spendUtxoList.add(noteInfo); saveSpendNoteToFile(noteInfo); } + //put note payment address into shieldedAddressInfoMap + if (!shieldedAddressInfoMap.containsKey(noteInfo.getPaymentAddress())) { + PaymentAddress paymentAddress = + KeyIo.decodePaymentAddress(noteInfo.getPaymentAddress()); + ShieldedAddressInfo addressInfo = new ShieldedAddressInfo(); + addressInfo.setD(paymentAddress.getD()); + addressInfo.setPkD(paymentAddress.getPkD()); + addressInfo.setSk(sampleAdressInfo.getSk()); + addressInfo.setIvk(sampleAdressInfo.getIvk()); + addressInfo.setOvk(sampleAdressInfo.getOvk()); + appendAddressInfoToFile(addressInfo); + } } int endNum = utxoMapNote.size(); if (endNum > startNum) { @@ -277,6 +298,15 @@ private void scanBlockByIvk() throws CipherException { } } + private ShieldedAddressInfo getShieldedAddressInfoFromIvk(byte[] ivk) { + for (Entry entry : shieldedAddressInfoMap.entrySet()) { + if (ByteUtil.equals(ivk, entry.getValue().getIvk())) { + return entry.getValue(); + } + } + return null;//It should not return null. + } + private void updateNoteWhetherSpend() throws Exception { for (Entry entry : utxoMapNote.entrySet()) { ShieldedTRC20NoteInfo noteInfo = entry.getValue(); @@ -475,7 +505,7 @@ public List getvalidateSortUtxoList() { List utxoList = new ArrayList<>(); for (Map.Entry entry : list) { String string = entry.getKey() + " " + entry.getValue().getPaymentAddress() + " "; - string += entry.getValue().getValue(); + string += entry.getValue().getRawValue().toString(); string += " "; string += entry.getValue().getTrxId(); string += " "; diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 2f181f661..c206390f8 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -57,7 +57,7 @@ public class Client { "AddTransactionSign", "ApproveProposal", "AssetIssue", - "BackupShieldedWallet", + // "BackupShieldedWallet", "BackupShieldedTRC20Wallet", "BackupWallet", "BackupWallet2Base64", @@ -76,7 +76,7 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "GenerateAddress", - "GenerateShieldedAddress", + // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", "GetAccountNet", @@ -102,7 +102,8 @@ public class Client { "GetIncomingViewingKey", "GetNkFromNsk", "GetNextMaintenanceTime", - "GetShieldedNullifier", + // "GetShieldedNullifier", + "GetShieldedPaymentAddress", "GetSpendingKey", "GetProposal", "GetTotalTransaction", @@ -113,7 +114,7 @@ public class Client { "GetTransactionsFromThis", "GetTransactionsToThis", "GetTransactionSignWeight", - "ImportShieldedWallet", + // "ImportShieldedWallet", "ImportShieldedTRC20Wallet", "ImportWallet", "ImportWalletByBase64", @@ -122,8 +123,8 @@ public class Client { "ListExchanges", "ListExchangesPaginated", "ListNodes", - "ListShieldedAddress", - "ListShieldedNote", + // "ListShieldedAddress", + // "ListShieldedNote", "ListShieldedTRC20Address", "ListShieldedTRC20Note", "ListProposals", @@ -131,25 +132,25 @@ public class Client { "ListWitnesses", "Login", "Logout", - "LoadShieldedWallet", + // "LoadShieldedWallet", "LoadShieldedTRC20Wallet", "ParticipateAssetIssue", "RegisterWallet", - "ResetShieldedNote", + // "ResetShieldedNote", "ResetShieldedTRC20Note", - "ScanAndMarkNotebyAddress", - "ScanNotebyIvk", - "ScanNotebyOvk", + // "ScanAndMarkNotebyAddress", + // "ScanNotebyIvk", + // "ScanNotebyOvk", "ScanShieldedTRC20NoteByIvk", "ScanShieldedTRC20NoteByOvk", "SendCoin", - "SendShieldedCoin", - "SendShieldedCoinWithoutAsk", + // "SendShieldedCoin", + // "SendShieldedCoinWithoutAsk", "SendShieldedTRC20Coin", "SendShieldedTRC20CoinWithoutAsk", "SetAccountId", "SetShieldedTRC20ContractAddress", - "ShowShieldedAddressInfo", + // "ShowShieldedAddressInfo", "ShowShieldedTRC20AddressInfo", "TransferAsset", "TriggerContract contractAddress method args isHex fee_limit value", @@ -173,7 +174,7 @@ public class Client { "AddTransactionSign", "ApproveProposal", "AssetIssue", - "BackupShieldedWallet", + // "BackupShieldedWallet", "BackupShieldedTRC20Wallet", "BackupWallet", "BackupWallet2Base64", @@ -192,7 +193,7 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "GenerateAddress", - "GenerateShieldedAddress", + // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", "GetAccountNet", @@ -218,7 +219,8 @@ public class Client { "GetIncomingViewingKey", "GetNkFromNsk", "GetNextMaintenanceTime", - "GetShieldedNullifier", + // "GetShieldedNullifier", + "GetShieldedPaymentAddress", "GetSpendingKey", "GetProposal", "GetTotalTransaction", @@ -230,7 +232,7 @@ public class Client { "GetTransactionsToThis", "GetTransactionSignWeight", "Help", - "ImportShieldedWallet", + // "ImportShieldedWallet", "ImportShieldedTRC20Wallet", "ImportWallet", "ImportWalletByBase64", @@ -239,8 +241,8 @@ public class Client { "ListExchanges", "ListExchangesPaginated", "ListNodes", - "ListShieldedAddress", - "ListShieldedNote", + // "ListShieldedAddress", + // "ListShieldedNote", "ListShieldedTRC20Address", "ListShieldedTRC20Note", "ListProposals", @@ -248,25 +250,25 @@ public class Client { "ListWitnesses", "Login", "Logout", - "LoadShieldedWallet", + // "LoadShieldedWallet", "LoadShieldedTRC20Wallet", "ParticipateAssetIssue", "RegisterWallet", - "ResetShieldedNote", + // "ResetShieldedNote", "ResetShieldedTRC20Note", - "ScanAndMarkNotebyAddress", - "ScanNotebyIvk", - "ScanNotebyOvk", + // "ScanAndMarkNotebyAddress", + // "ScanNotebyIvk", + // "ScanNotebyOvk", "ScanShieldedTRC20NoteByIvk", "ScanShieldedTRC20NoteByOvk", "SendCoin", - "SendShieldedCoin", - "SendShieldedCoinWithoutAsk", + // "SendShieldedCoin", + // "SendShieldedCoinWithoutAsk", "SendShieldedTRC20Coin", "SendShieldedTRC20CoinWithoutAsk", "SetAccountId", "SetShieldedTRC20ContractAddress", - "ShowShieldedAddressInfo", + // "ShowShieldedAddressInfo", "ShowShieldedTRC20AddressInfo", "TransferAsset", "TriggerContract", @@ -2887,12 +2889,8 @@ private void setShieldedTRC20ContractAddress(String[] parameters) { System.out.println("SetShieldedTRC20ContractAddress succeed!"); System.out.println("The Scaling Factor is " + scalingFactor.toString()); System.out.println("That means:"); - System.out.println("If you mint " + scalingFactor.toString() + " token into " + - "shielded contract, you can get 1 shielded token."); - System.out.println("If you burn 1 shielded token into toPublicAddress, " + - "you can get " + scalingFactor.toString() + " token."); - } else { - System.out.println("Get Scaling Factor failed!! Please check shielded contract!"); + System.out.println("No matter you MINT, TRANSFER or BURN, the value must be an integer " + + "multiple of " + scalingFactor.toString()); } } else { System.out.println("SetShieldedTRC20ContractAddress failed !!! Invalid Address !!!"); @@ -3026,7 +3024,7 @@ private void listShieldedTRC20Note(String[] parameters) { System.out.println("All notes are shown below:"); for (Entry entry : noteMap.entrySet()) { String string = entry.getValue().getPaymentAddress() + " "; - string += entry.getValue().getValue(); + string += entry.getValue().getRawValue().toString(); string += " "; string += entry.getValue().getTrxId(); string += " "; @@ -3043,7 +3041,7 @@ private void listShieldedTRC20Note(String[] parameters) { List noteList = ShieldedTRC20Wrapper.getInstance().getSpendUtxoList(); for (ShieldedTRC20NoteInfo noteInfo : noteList) { String string = noteInfo.getPaymentAddress() + " "; - string += noteInfo.getValue(); + string += noteInfo.getRawValue().toString(); string += " "; string += noteInfo.getTrxId(); string += " "; @@ -3059,8 +3057,8 @@ private void listShieldedTRC20Note(String[] parameters) { } BigInteger scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); System.out.println("The Scaling Factor is " + scalingFactor.toString()); - System.out.println("That means 1 shielded token is equal to " + scalingFactor.toString() + - " TRC20 token"); + System.out.println("No matter you MINT, TRANSFER or BURN, the value must be an integer " + + "multiple of " + scalingFactor.toString()); } private void loadShieldedTRC20Wallet() throws CipherException, IOException { @@ -3090,13 +3088,13 @@ private void resetShieldedTRC20Note() { private void scanShieldedTRC20NoteByIvk(String[] parameters) { if (parameters == null || parameters.length != 6) { System.out.println("ScanShieldedTRC20NoteByIvk command needs 6 parameters like: "); - System.out.println("ScanShieldedTRC20NoteByIvk shieldedTRC20ContarctAddress ivk ak nk " + + System.out.println("ScanShieldedTRC20NoteByIvk shieldedTRC20ContractAddress ivk ak nk " + "startNum endNum "); return; } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (contractAddress == null) { - System.out.println("ScanShieldedTRC20NoteByIvk failed! Invalid shieldedTRC20ContarctAddress"); + System.out.println("ScanShieldedTRC20NoteByIvk failed! Invalid shieldedTRC20ContractAddress"); return; } long startNum, endNum; @@ -3114,13 +3112,13 @@ private void scanShieldedTRC20NoteByIvk(String[] parameters) { private void scanShieldedTRC20NoteByOvk(String[] parameters) { if (parameters == null || parameters.length != 4) { System.out.println("ScanShieldedTRC20NoteByOvk command needs 4 parameters like: "); - System.out.println("ScanShieldedTRC20NoteByOvk shieldedTRC20ContarctAddress ovk startNum " + + System.out.println("ScanShieldedTRC20NoteByOvk shieldedTRC20ContractAddress ovk startNum " + "endNum"); return; } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[0]); if (contractAddress == null) { - System.out.println("ScanShieldedTRC20NoteByOvk failed! Invalid shieldedTRC20ContarctAddress"); + System.out.println("ScanShieldedTRC20NoteByOvk failed! Invalid shieldedTRC20ContractAddress"); return; } long startNum, endNum; @@ -3171,8 +3169,8 @@ private void sendShieldedTRC20CoinWithoutAsk(String[] parameters) throws IOExcep private boolean firstCheck(String[] parameters, String sendCoinType) { if (parameters == null || parameters.length < 6) { System.out.println(sendCoinType + " command needs more than 6 parameters like: "); - System.out.println(sendCoinType + " fromAmount shieldedInputNum input1 input2 ... " - + "publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 " + System.out.println(sendCoinType + " fromPublicAmount shieldedInputNum input1 input2 ... " + + "publicToAddress toPublicAmount shieldedOutputNum shieldedAddress1 amount1 memo1 " + "shieldedAddress2 amount2 memo2 ... "); return false; } @@ -3182,31 +3180,23 @@ private boolean firstCheck(String[] parameters, String sendCoinType) { return false; } - if (isFromShieldedTRC20Note(parameters) - && !ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { + if (!ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded()) { System.out.println("SendShieldedTRC20Coin failed, please LoadShieldedTRC20Wallet first !!!"); return false; } return true; } - private boolean isFromShieldedTRC20Note(String[] parameters) { - if (Long.valueOf(parameters[1]) > 0) { - return true; - } else { - return false; - } - } - private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk, String contractAddress, String shieldedContractAddress) throws IOException, CipherException, CancelException, ZksnarkException { + BigInteger scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); int parameterIndex = 0; - BigInteger fromPublicAmount = new BigInteger(parameters[parameterIndex++]); - if (fromPublicAmount.compareTo(BigInteger.ZERO) < 0) { - System.out.println("fromPublicAmount must be non-negative. "); + if (!checkAmountValid(fromPublicAmount, scalingFactor)) { + System.out.println("fromPublicAmount must be 0 or positive integer multiple of " + + scalingFactor.toString()); return false; } @@ -3246,8 +3236,9 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk amountString = parameters[parameterIndex++]; if (!StringUtil.isNullOrEmpty(amountString)) { toPublicAmount = new BigInteger(amountString); - if (toPublicAmount.compareTo(BigInteger.ZERO) <= 0) { - System.out.println("toPublicAmount must be positive. "); + if (!checkAmountValid(toPublicAmount, scalingFactor)) { + System.out.println("toPublicAmount must be positive integer multiple of " + + scalingFactor.toString()); return false; } } @@ -3271,15 +3262,15 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk if (memoString.equals("null")) { memoString = ""; } - long shieldedAmount = 0; + BigInteger shieldedAmountBi = BigInteger.ZERO; if (!StringUtil.isNullOrEmpty(amountString)) { - shieldedAmount = Long.valueOf(amountString); - if (shieldedAmount < 0) { - System.out.println("shieldedAmount must be non-negative. "); - return false; + shieldedAmountBi = new BigInteger(amountString); + if (!checkAmountValid(shieldedAmountBi, scalingFactor)) { + System.out.println("shielded amount must be an integer multiple of " + + scalingFactor.toString()); } } - + long shieldedAmount = shieldedAmountBi.divide(scalingFactor).longValueExact(); Note.Builder noteBuild = Note.newBuilder(); noteBuild.setPaymentAddress(shieldedAddress); noteBuild.setPaymentAddress(shieldedAddress); @@ -3320,8 +3311,22 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk } } + private boolean checkAmountValid(BigInteger amount, BigInteger scalingFactor) { + if (amount.compareTo(BigInteger.ZERO) < 0) { + return false; + } + BigInteger[] quotientAndReminder = amount.divideAndRemainder(scalingFactor); + if (quotientAndReminder[1].compareTo(BigInteger.ZERO) != 0) { + return false; + } + if (quotientAndReminder[0].compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + return false; + } + return true; + } + private void showShieldedTRC20AddressInfo(String[] parameters) { - if (parameters == null || parameters.length < 1) { + if (parameters == null || parameters.length != 1) { System.out.println("Using ShowShieldedTRC20AddressInfo needs 1 parameter like: "); System.out.println("ShowShieldedTRC20AddressInfo shieldedTRC20Address"); return; @@ -3486,10 +3491,10 @@ private void run() { logout(); break; } - case "loadshieldedwallet": { - loadShieldedWallet(); - break; - } + // case "loadshieldedwallet": { + // loadShieldedWallet(); + // break; + // } case "backupwallet": { backupWallet(); break; @@ -3798,6 +3803,7 @@ private void run() { broadcastTransaction(parameters); break; } + /* case "generateshieldedaddress": { generateShieldedAddress(parameters); break; @@ -3850,6 +3856,7 @@ private void run() { backupShieldedWallet(); break; } + */ case "create2": { create2(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index b10732021..382e4a759 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -28,6 +28,7 @@ import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; import org.tron.keystore.WalletFile; +import org.tron.keystore.WalletUtils; import org.tron.protos.Contract; import org.tron.protos.Contract.AssetIssueContract; import org.tron.protos.Contract.IncrementalMerkleVoucherInfo; @@ -1234,9 +1235,41 @@ public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, Optional notes = WalletApi.scanShieldedTRC20NoteByIvk( parameters, true); if (!notes.isPresent()) { - System.out.println("ScanShieldedTRC20NoteByIvk failed !!!"); + return false; + } + if (notes.get().getNoteTxsList().size() > 0) { + BigInteger scalingFactor; + if (ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded() + && ByteUtil.equals(address, WalletApi.decodeFromBase58Check( + ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress()))) { + scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); + } else { + try { + String scalingFactorHexStr = getScalingFactor(address); + scalingFactor = new BigInteger(scalingFactorHexStr, 16); + } catch (Exception e) { + return false; + } + } + // System.out.println(Utils.formatMessageString(notes.get())); + System.out.println("["); + for(DecryptNotesTRC20.NoteTx noteTx : notes.get().getNoteTxsList()) { + System.out.println("\t{"); + System.out.println("\t\t note: {"); + BigInteger showValue = + BigInteger.valueOf(noteTx.getNote().getValue()).multiply(scalingFactor); + System.out.println("\t\t\t value: " + showValue.toString()); + System.out.println("\t\t\t payment_address: " + noteTx.getNote().getPaymentAddress()); + System.out.println("\t\t\t rcm: " + + ByteArray.toHexString(noteTx.getNote().getRcm().toByteArray())); + System.out.println("\t\t }\n\t\t position: " + noteTx.getPosition()); + System.out.println("\t\t is_spent: " + noteTx.getIsSpent()); + System.out.println("\t\t tx_id: " + ByteArray.toHexString(noteTx.getTxid().toByteArray())); + System.out.println("\t}"); + } + System.out.println("]"); } else { - System.out.println(Utils.formatMessageString(notes.get())); + System.out.println("No notes found!"); } return true; } @@ -1252,10 +1285,40 @@ public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end Optional notes = WalletApi.scanShieldedTRC20NoteByOvk(parameters, true); if (!notes.isPresent()) { - System.out.println("ScanShieldedTRC20NoteByovk failed !!!"); + return false; + } + if (notes.get().getNoteTxsList().size() > 0) { + BigInteger scalingFactor; + if (ShieldedTRC20Wrapper.getInstance().ifShieldedTRC20WalletLoaded() + && ByteUtil.equals(contractAddress, WalletApi.decodeFromBase58Check( + ShieldedTRC20Wrapper.getInstance().getShieldedTRC20ContractAddress()))) { + scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); + } else { + try { + String scalingFactorHexStr = getScalingFactor(contractAddress); + scalingFactor = new BigInteger(scalingFactorHexStr, 16); + } catch (Exception e) { + return false; + } + } + // System.out.println(Utils.formatMessageString(notes.get())); + System.out.println("["); + for(DecryptNotesTRC20.NoteTx noteTx : notes.get().getNoteTxsList()) { + System.out.println("\t{"); + System.out.println("\t\t note: {"); + BigInteger showValue = + BigInteger.valueOf(noteTx.getNote().getValue()).multiply(scalingFactor); + System.out.println("\t\t\t value: " + showValue.toString()); + System.out.println("\t\t\t payment_address: " + noteTx.getNote().getPaymentAddress()); + System.out.println("\t\t\t rcm: " + + ByteArray.toHexString(noteTx.getNote().getRcm().toByteArray())); + System.out.println("\t\t }"); + System.out.println("\t\t tx_id: " + ByteArray.toHexString(noteTx.getTxid().toByteArray())); + System.out.println("\t}"); + } + System.out.println("]"); } else { - System.out.println(Utils.formatMessageString(notes.get())); - System.out.println("complete."); + System.out.println("No notes found!"); } return true; } @@ -1271,18 +1334,14 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm && BigInteger.valueOf(shieldedOutputList.get(0).getValue()) .multiply(scalingFactor) .compareTo(fromAmount) != 0) { - System.out.println("MINT: fromPublicAmount must be equal to noteValue * scalingFactor."); - System.out.println("The Scaling Factor is " + scalingFactor.toString()); + System.out.println("MINT: fromPublicAmount must be equal to note amount."); return false; } if (shieldedContractType == 2) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(0)); - if (BigInteger.valueOf(noteInfo.getValue()) - .multiply(scalingFactor) - .compareTo(toAmount) != 0) { - System.out.println("BURN: toPublicAmount must be equal to noteValue * scalingFactor"); - System.out.println("The Scaling Factor is " + scalingFactor.toString()); + if (noteInfo.getRawValue().compareTo(toAmount) != 0) { + System.out.println("BURN: toPublicAmount must be equal to note amount."); return false; } } @@ -1344,7 +1403,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm noteBuild.setMemo(ByteString.copyFrom(noteInfo.getMemo())); System.out.println("address " + noteInfo.getPaymentAddress()); - System.out.println("value " + noteInfo.getValue()); + System.out.println("value " + noteInfo.getRawValue().toString()); System.out.println("rcm " + ByteArray.toHexString(noteInfo.getR())); System.out.println("trxId " + noteInfo.getTrxId()); System.out.println("index " + noteInfo.getIndex()); @@ -1449,16 +1508,14 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte && BigInteger.valueOf(shieldedOutputList.get(0).getValue()) .multiply(scalingFactor) .compareTo(fromAmount) != 0) { - System.out.println("MINT: fromPublicAmount must be equal to noteValue * scalingFactor"); + System.out.println("MINT: fromPublicAmount must be equal to note amount."); return false; } if (shieldedContractType == 2) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(0)); - if (BigInteger.valueOf(noteInfo.getValue()) - .multiply(scalingFactor) - .compareTo(toAmount) != 0) { - System.out.println("BURN: toPublicAmount must be equal to noteValue * scalingFactor"); + if (noteInfo.getRawValue().compareTo(toAmount) != 0) { + System.out.println("BURN: toPublicAmount must be equal to note amount."); return false; } } @@ -1524,7 +1581,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte noteBuild.setMemo(ByteString.copyFrom(noteInfo.getMemo())); System.out.println("address " + noteInfo.getPaymentAddress()); - System.out.println("value " + noteInfo.getValue()); + System.out.println("value " + noteInfo.getRawValue().toString()); System.out.println("rcm " + ByteArray.toHexString(noteInfo.getR())); System.out.println("trxId " + noteInfo.getTrxId()); System.out.println("index " + noteInfo.getIndex()); @@ -1566,7 +1623,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte if (shieldedContractType == 1 && valueBalance != 0) { System.out.println("TRANSFER: the sum of shielded input amount should be equal to the " + - "sum of shielded output amount"); + "sum of shielded output amount."); return false; } @@ -1641,7 +1698,13 @@ public String getScalingFactor(byte[] address) { System.out.println("Warning: get Scaling Factor failed, Please login wallet first !!"); return null; } - return wallet.constantCallShieldedContract(address, input, methodStr); + String scalingFactorHexStr = wallet.constantCallShieldedContract(address, input, methodStr); + if (scalingFactorHexStr != null) { + return scalingFactorHexStr; + } else { + System.out.println("Get Scaling Factor failed!! Please check shielded contract!"); + return null; + } } public boolean setAllowance(String contractAddress, String shieldedContractAddress, From e2e269acd19a122769d35767e93a8966b348a494 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 29 May 2020 22:15:18 +0800 Subject: [PATCH 316/445] remove unused command --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index 8a843cd47..a5dc45718 100644 --- a/README.md +++ b/README.md @@ -1891,7 +1891,6 @@ For more information on a specific command, just type the command on terminal wh ApproveProposal AssetIssue BackupShieldedTRC20Wallet - BackupWallet BackupWallet2Base64 BroadcastTransaction @@ -1909,7 +1908,6 @@ For more information on a specific command, just type the command on terminal wh ExchangeWithdraw FreezeBalance GenerateAddress - GenerateShieldedTRC20Address GetAccount GetAccountNet @@ -1933,7 +1931,6 @@ For more information on a specific command, just type the command on terminal wh GetExpandedSpendingKey GetIncomingViewingKey GetNextMaintenanceTime - GetShieldedPaymentAddress GetSpendingKey GetProposal @@ -1946,7 +1943,6 @@ For more information on a specific command, just type the command on terminal wh GetTransactionsToThis GetTransactionSignWeight ImportShieldedTRC20Wallet - ImportWallet ImportWalletByBase64 ListAssetIssue @@ -1954,34 +1950,24 @@ For more information on a specific command, just type the command on terminal wh ListExchanges ListExchangesPaginated ListNodes - - ListShieldedTRC20Address ListShieldedTRC20Note ListProposals ListProposalsPaginated ListWitnesses - LoadShieldedTRC20Wallet Login Logout ParticipateAssetIssue RegisterWallet - ResetShieldedTRC20Note - - - ScanShieldedTRC20NoteByIvk ScanShieldedTRC20NoteByOvk SendCoin - - SendShieldedTRC20Coin SendShieldedTRC20CoinWithoutAsk SetAccountId SetShieldedTRC20ContractAddress - ShowShieldedTRC20AddressInfo TransferAsset TriggerContract From ee17e38954073e2ba8ef919e738c8a07220eb9cb Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Wed, 10 Jun 2020 18:24:26 +0800 Subject: [PATCH 317/445] update scanShilededTRC20noteByIvk and scanShilededTRC20noteByOvk command --- .../tron/core/zen/ShieldedTRC20Wrapper.java | 6 +- src/main/java/org/tron/walletcli/Client.java | 91 ++++-- .../org/tron/walletcli/WalletApiWrapper.java | 262 ++++++++++-------- .../org/tron/walletserver/GrpcClient.java | 15 +- .../java/org/tron/walletserver/WalletApi.java | 60 +++- src/main/protos/api/api.proto | 12 +- src/main/resources/config.conf | 4 +- 7 files changed, 301 insertions(+), 149 deletions(-) diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java index 44ce9958a..2839ed2e5 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -134,7 +134,9 @@ public boolean loadShieldTRC20Wallet() throws CipherException, IOException { } if (!shieldedSkeyFileExist()) { - System.out.println("shieldedTRC20 wallet does not exist."); + System.out.println("shieldedTRC20 wallet does not exist. Please use " + + "ImportShieldedTRC20Wallet command to import a shielded TRC20 wallet, or " + + "GenerateShieldedTRC20Address to generate one."); return false; } @@ -329,7 +331,7 @@ private void updateNoteWhetherSpend() throws Exception { noteBuild.setMemo(ByteString.copyFrom(noteInfo.getMemo())); builder.setNote(noteBuild.build()); - Optional result = WalletApi.IsShieldedTRC20ContractNoteSpent( + Optional result = WalletApi.isShieldedTRC20ContractNoteSpent( builder.build(), false); if (result.isPresent() && result.get().getIsSpent()) { spendNote(entry.getKey()); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index c206390f8..18d6e62ac 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3004,7 +3004,12 @@ private void listShieldedTRC20Note(String[] parameters) { + "please use command ListShieldedTRC20Note 1 "); } else { if (!StringUtil.isNullOrEmpty(parameters[0])) { - showType = Integer.valueOf(parameters[0]); + try { + showType = Integer.valueOf(parameters[0]); + } catch (NumberFormatException e) { + System.out.println("Invalid parameter!"); + return; + } } } @@ -3086,10 +3091,10 @@ private void resetShieldedTRC20Note() { } private void scanShieldedTRC20NoteByIvk(String[] parameters) { - if (parameters == null || parameters.length != 6) { - System.out.println("ScanShieldedTRC20NoteByIvk command needs 6 parameters like: "); + if (parameters == null || parameters.length < 6) { + System.out.println("ScanShieldedTRC20NoteByIvk command needs at least 6 parameters like: "); System.out.println("ScanShieldedTRC20NoteByIvk shieldedTRC20ContractAddress ivk ak nk " + - "startNum endNum "); + "startNum endNum [event1] [event2]"); return; } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[0]); @@ -3105,15 +3110,25 @@ private void scanShieldedTRC20NoteByIvk(String[] parameters) { System.out.println("Invalid parameter: startNum, endNum."); return; } - walletApiWrapper.scanShieldedTRC20NoteByIvk(contractAddress, - parameters[1], parameters[2], parameters[3], startNum, endNum); + int eventNum = parameters.length - 6; + if (eventNum > 0) { + String[] eventArray = new String[eventNum]; + for (int i = 0; i < eventNum; i++) { + eventArray[i] = parameters[i + 6]; + } + walletApiWrapper.scanShieldedTRC20NoteByIvk(contractAddress, + parameters[1], parameters[2], parameters[3], startNum, endNum, eventArray); + } else { + walletApiWrapper.scanShieldedTRC20NoteByIvk(contractAddress, + parameters[1], parameters[2], parameters[3], startNum, endNum, null); + } } private void scanShieldedTRC20NoteByOvk(String[] parameters) { - if (parameters == null || parameters.length != 4) { - System.out.println("ScanShieldedTRC20NoteByOvk command needs 4 parameters like: "); + if (parameters == null || parameters.length < 4) { + System.out.println("ScanShieldedTRC20NoteByOvk command needs at lease 4 parameters like: "); System.out.println("ScanShieldedTRC20NoteByOvk shieldedTRC20ContractAddress ovk startNum " + - "endNum"); + "endNum [event1] [event2] "); return; } byte[] contractAddress = WalletApi.decodeFromBase58Check(parameters[0]); @@ -3129,7 +3144,19 @@ private void scanShieldedTRC20NoteByOvk(String[] parameters) { System.out.println("Invalid parameter: startNum, endNum."); return; } - walletApiWrapper.scanShieldedTRC20NoteByOvk(parameters[1], startNum, endNum, contractAddress); + + int eventNum = parameters.length - 4; + if (eventNum > 0) { + String[] eventArray = new String[eventNum]; + for (int i = 0; i < eventNum; i++) { + eventArray[i] = parameters[i + 4]; + } + walletApiWrapper.scanShieldedTRC20NoteByOvk(parameters[1], startNum, endNum, + contractAddress, eventArray); + } else { + walletApiWrapper.scanShieldedTRC20NoteByOvk(parameters[1], startNum, endNum, + contractAddress, null); + } } private void sendShieldedTRC20Coin(String[] parameters) throws IOException, CipherException, @@ -3193,7 +3220,13 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk throws IOException, CipherException, CancelException, ZksnarkException { BigInteger scalingFactor = ShieldedTRC20Wrapper.getInstance().getScalingFactor(); int parameterIndex = 0; - BigInteger fromPublicAmount = new BigInteger(parameters[parameterIndex++]); + BigInteger fromPublicAmount; + try { + fromPublicAmount = new BigInteger(parameters[parameterIndex++]); + } catch (NumberFormatException e) { + System.out.println("Invalid fromPublicAmount!"); + return false; + } if (!checkAmountValid(fromPublicAmount, scalingFactor)) { System.out.println("fromPublicAmount must be 0 or positive integer multiple of " + scalingFactor.toString()); @@ -3203,13 +3236,24 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk int shieldedInputNum = 0; String amountString = parameters[parameterIndex++]; if (!StringUtil.isNullOrEmpty(amountString)) { - shieldedInputNum = Integer.valueOf(amountString); + try { + shieldedInputNum = Integer.valueOf(amountString); + } catch (NumberFormatException e) { + System.out.println("Invalid shieldedInputNum!"); + return false; + } } List shieldedInputList = new ArrayList<>(); String shieldedInputAddress = ""; for (int i = 0; i < shieldedInputNum; ++i) { - long mapIndex = Long.valueOf(parameters[parameterIndex++]); + long mapIndex; + try { + mapIndex = Long.valueOf(parameters[parameterIndex++]); + } catch (NumberFormatException e) { + System.out.println("Invalid the " + (i + 1) + "shielded input"); + return false; + } ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote().get(mapIndex); if (noteInfo == null) { @@ -3235,7 +3279,12 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk } else { amountString = parameters[parameterIndex++]; if (!StringUtil.isNullOrEmpty(amountString)) { - toPublicAmount = new BigInteger(amountString); + try { + toPublicAmount = new BigInteger(amountString); + } catch (NumberFormatException e) { + System.out.println("Invalid toPublicAmount!"); + return false; + } if (!checkAmountValid(toPublicAmount, scalingFactor)) { System.out.println("toPublicAmount must be positive integer multiple of " + scalingFactor.toString()); @@ -3247,7 +3296,12 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk int shieldedOutputNum = 0; amountString = parameters[parameterIndex++]; if (!StringUtil.isNullOrEmpty(amountString)) { - shieldedOutputNum = Integer.valueOf(amountString); + try { + shieldedOutputNum = Integer.valueOf(amountString); + } catch (NumberFormatException e) { + System.out.println("Invalid shieldedOutputNum!"); + return false; + } } if ((parameters.length - parameterIndex) % 3 != 0) { System.out.println("Invalid parameter number!"); @@ -3264,7 +3318,12 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk } BigInteger shieldedAmountBi = BigInteger.ZERO; if (!StringUtil.isNullOrEmpty(amountString)) { - shieldedAmountBi = new BigInteger(amountString); + try { + shieldedAmountBi = new BigInteger(amountString); + } catch (NumberFormatException e) { + System.out.println("Invalid shielded output amount"); + return false; + } if (!checkAmountValid(shieldedAmountBi, scalingFactor)) { System.out.println("shielded amount must be an integer multiple of " + scalingFactor.toString()); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 382e4a759..7f0681591 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1221,16 +1221,21 @@ public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, final String ak, final String nk, - long start, long end) { - GrpcAPI.IvkDecryptTRC20Parameters parameters = IvkDecryptTRC20Parameters - .newBuilder() - .setStartBlockIndex(start) - .setEndBlockIndex(end) - .setShieldedTRC20ContractAddress(ByteString.copyFrom(address)) - .setIvk(ByteString.copyFrom(ByteArray.fromHexString(ivk))) - .setAk(ByteString.copyFrom(ByteArray.fromHexString(ak))) - .setNk(ByteString.copyFrom(ByteArray.fromHexString(nk))) - .build(); + long start, long end, String[] events) { + GrpcAPI.IvkDecryptTRC20Parameters.Builder builder = IvkDecryptTRC20Parameters + .newBuilder(); + builder.setStartBlockIndex(start) + .setEndBlockIndex(end) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(address)) + .setIvk(ByteString.copyFrom(ByteArray.fromHexString(ivk))) + .setAk(ByteString.copyFrom(ByteArray.fromHexString(ak))) + .setNk(ByteString.copyFrom(ByteArray.fromHexString(nk))); + if (events != null ) { + for (String event : events) { + builder.addEvents(event); + } + } + GrpcAPI.IvkDecryptTRC20Parameters parameters = builder.build(); Optional notes = WalletApi.scanShieldedTRC20NoteByIvk( parameters, true); @@ -1262,6 +1267,7 @@ public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, System.out.println("\t\t\t payment_address: " + noteTx.getNote().getPaymentAddress()); System.out.println("\t\t\t rcm: " + ByteArray.toHexString(noteTx.getNote().getRcm().toByteArray())); + System.out.println("\t\t\t memo: " + noteTx.getNote().getMemo().toStringUtf8()); System.out.println("\t\t }\n\t\t position: " + noteTx.getPosition()); System.out.println("\t\t is_spent: " + noteTx.getIsSpent()); System.out.println("\t\t tx_id: " + ByteArray.toHexString(noteTx.getTxid().toByteArray())); @@ -1275,14 +1281,18 @@ public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, } public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end, - byte[] contractAddress) { - GrpcAPI.OvkDecryptTRC20Parameters parameters = OvkDecryptTRC20Parameters.newBuilder() - .setStartBlockIndex(start) - .setEndBlockIndex(end) - .setOvk(ByteString.copyFrom(ByteArray.fromHexString(ovk))) - .setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)) - .build(); - + byte[] contractAddress, String[] events) { + GrpcAPI.OvkDecryptTRC20Parameters.Builder builder = OvkDecryptTRC20Parameters.newBuilder(); + builder.setStartBlockIndex(start) + .setEndBlockIndex(end) + .setOvk(ByteString.copyFrom(ByteArray.fromHexString(ovk))) + .setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + if (events != null ) { + for (String event : events) { + builder.addEvents(event); + } + } + GrpcAPI.OvkDecryptTRC20Parameters parameters = builder.build(); Optional notes = WalletApi.scanShieldedTRC20NoteByOvk(parameters, true); if (!notes.isPresent()) { return false; @@ -1305,14 +1315,23 @@ public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end System.out.println("["); for(DecryptNotesTRC20.NoteTx noteTx : notes.get().getNoteTxsList()) { System.out.println("\t{"); - System.out.println("\t\t note: {"); - BigInteger showValue = - BigInteger.valueOf(noteTx.getNote().getValue()).multiply(scalingFactor); - System.out.println("\t\t\t value: " + showValue.toString()); - System.out.println("\t\t\t payment_address: " + noteTx.getNote().getPaymentAddress()); - System.out.println("\t\t\t rcm: " - + ByteArray.toHexString(noteTx.getNote().getRcm().toByteArray())); - System.out.println("\t\t }"); + //note + if (noteTx.hasNote()) { + System.out.println("\t\t note: {"); + BigInteger showValue = + BigInteger.valueOf(noteTx.getNote().getValue()).multiply(scalingFactor); + System.out.println("\t\t\t value: " + showValue.toString()); + System.out.println("\t\t\t payment_address: " + noteTx.getNote().getPaymentAddress()); + System.out.println("\t\t\t rcm: " + + ByteArray.toHexString(noteTx.getNote().getRcm().toByteArray())); + System.out.println("\t\t\t memo: " + noteTx.getNote().getMemo().toStringUtf8()); + System.out.println("\t\t }"); + } else { + //This is specific for BURN. + System.out.println("\t\t transparent_to_address: " + + ByteArray.toHexString(noteTx.getTransparentToAddress().toByteArray())); + System.out.println("\t\t transparent_amount: " + noteTx.getToAmount()); + } System.out.println("\t\t tx_id: " + ByteArray.toHexString(noteTx.getTxid().toByteArray())); System.out.println("\t}"); } @@ -1453,6 +1472,11 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm System.out.println("CreateShieldedContractParameters failed, please check input data!"); return false; } + String inputData = parameters.getTriggerContractInput(); + if (inputData == null) { + System.out.println("CreateShieldedContractParameters failed, please check input data!"); + return false; + } if (shieldedContractType == 0) {//MINT boolean setAllowanceResult = setAllowance(contractAddress, shieldedContractAddress, @@ -1461,7 +1485,6 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm System.out.println("SetAllowance failed, please check wallet account!"); return false; } - String inputData = encodeMintParamsToHexString(parameters, fromAmount); boolean mintResult = triggerShieldedContract(shieldedContractAddress, inputData, 0); if (mintResult) { System.out.println("MINT succeed!"); @@ -1471,7 +1494,6 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm return false; } } else if (shieldedContractType == 1) { //TRANSFER - String inputData = encodeTransferParamsToHexString(parameters); boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 1); if (transferResult) { System.out.println("TRANSFER succeed!"); @@ -1481,7 +1503,6 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm return false; } } else if (shieldedContractType == 2) {//BURN - String inputData = encodeBurnParamsToHexString(parameters, toAmount, toAddress); boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); if (transferResult) { System.out.println("BURN succeed!"); @@ -1491,7 +1512,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm return false; } } else { - System.out.println("Error shieldedContractType!"); + System.out.println("Unsupported shieldedContractType!"); return false; } } @@ -1635,6 +1656,12 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte return false; } + String inputData = parameters.getTriggerContractInput(); + if (inputData == null) { + System.out.println("CreateShieldedContractParametersWithoutAsk failed, " + + "please check input data!"); + return false; + } if (shieldedContractType == 0) {//MINT boolean setAllowanceResult = setAllowance(contractAddress, shieldedContractAddress, fromAmount); @@ -1642,7 +1669,6 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte System.out.println("SetAllowance failed, please check wallet account!"); return false; } - String inputData = encodeMintParamsToHexString(parameters, fromAmount); boolean mintResult = triggerShieldedContract(shieldedContractAddress, inputData, 0); if (mintResult) { System.out.println("MINT succeed!"); @@ -1652,7 +1678,6 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte return false; } } else if (shieldedContractType == 1) { //TRANSFER - String inputData = encodeTransferParamsToHexString(parameters); boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 1); if (transferResult) { System.out.println("TRANSFER succeed!"); @@ -1662,7 +1687,6 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte return false; } } else if (shieldedContractType == 2) {//BURN - String inputData = encodeBurnParamsToHexString(parameters, toAmount, toAddress); boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); if (transferResult) { System.out.println("BURN succeed!"); @@ -1732,9 +1756,9 @@ public boolean triggerShieldedContract(String contractAddress, String data, } else if (shieldedContractType == 1) { methodStr = "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])"; } else if (shieldedContractType == 2) { - methodStr = "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address)"; + methodStr = "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address,bytes32[3])"; } else { - System.out.println("shieldedContractType should be 0, 1 or 2. "); + System.out.println("unsupported shieldedContractType! "); return false; } byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, data, true)); @@ -1747,19 +1771,24 @@ public boolean triggerShieldedContract(String contractAddress, String data, public String encodeMintParamsToHexString(ShieldedTRC20Parameters parameters, BigInteger value) { byte[] mergedBytes; - Contract.ReceiveDescription revDesc = parameters.getReceiveDescription(0); - mergedBytes = ByteUtil.merge( - ByteUtil.bigIntegerToBytes(value, 32), - revDesc.getNoteCommitment().toByteArray(), - revDesc.getValueCommitment().toByteArray(), - revDesc.getEpk().toByteArray(), - revDesc.getZkproof().toByteArray(), - parameters.getBindingSignature().toByteArray(), - revDesc.getCEnc().toByteArray(), - revDesc.getCOut().toByteArray(), - new byte[12] - ); - return ByteArray.toHexString(mergedBytes); + try { + Contract.ReceiveDescription revDesc = parameters.getReceiveDescription(0); + mergedBytes = ByteUtil.merge( + ByteUtil.bigIntegerToBytes(value, 32), + revDesc.getNoteCommitment().toByteArray(), + revDesc.getValueCommitment().toByteArray(), + revDesc.getEpk().toByteArray(), + revDesc.getZkproof().toByteArray(), + parameters.getBindingSignature().toByteArray(), + revDesc.getCEnc().toByteArray(), + revDesc.getCOut().toByteArray(), + new byte[12] + ); + return ByteArray.toHexString(mergedBytes); + } catch (Exception e) { + } + + return null; } public String encodeTransferParamsToHexString(ShieldedTRC20Parameters parameters) { @@ -1769,78 +1798,87 @@ public String encodeTransferParamsToHexString(ShieldedTRC20Parameters parameters byte[] c = new byte[0]; byte[] bindingSig; byte[] mergedBytes; - List spendDescs = parameters.getSpendDescriptionList(); - for (Contract.SpendDescription spendDesc : spendDescs) { - input = ByteUtil.merge(input, - spendDesc.getNullifier().toByteArray(), - spendDesc.getAnchor().toByteArray(), - spendDesc.getValueCommitment().toByteArray(), - spendDesc.getRk().toByteArray(), - spendDesc.getZkproof().toByteArray() - ); - spendAuthSig = ByteUtil.merge( - spendAuthSig, spendDesc.getSpendAuthoritySignature().toByteArray()); - } - byte[] inputOffsetbytes = longTo32Bytes(192); - long spendCount = spendDescs.size(); - byte[] spendCountBytes = longTo32Bytes(spendCount); - byte[] authOffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount); - List recvDescs = parameters.getReceiveDescriptionList(); - for (Contract.ReceiveDescription recvDesc : recvDescs) { - output = ByteUtil.merge(output, - recvDesc.getNoteCommitment().toByteArray(), - recvDesc.getValueCommitment().toByteArray(), - recvDesc.getEpk().toByteArray(), - recvDesc.getZkproof().toByteArray() - ); - c = ByteUtil.merge(c, - recvDesc.getCEnc().toByteArray(), - recvDesc.getCOut().toByteArray(), - new byte[12] + try { + List spendDescs = parameters.getSpendDescriptionList(); + for (Contract.SpendDescription spendDesc : spendDescs) { + input = ByteUtil.merge(input, + spendDesc.getNullifier().toByteArray(), + spendDesc.getAnchor().toByteArray(), + spendDesc.getValueCommitment().toByteArray(), + spendDesc.getRk().toByteArray(), + spendDesc.getZkproof().toByteArray() + ); + spendAuthSig = ByteUtil.merge( + spendAuthSig, spendDesc.getSpendAuthoritySignature().toByteArray()); + } + byte[] inputOffsetbytes = longTo32Bytes(192); + long spendCount = spendDescs.size(); + byte[] spendCountBytes = longTo32Bytes(spendCount); + byte[] authOffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount); + List recvDescs = parameters.getReceiveDescriptionList(); + for (Contract.ReceiveDescription recvDesc : recvDescs) { + output = ByteUtil.merge(output, + recvDesc.getNoteCommitment().toByteArray(), + recvDesc.getValueCommitment().toByteArray(), + recvDesc.getEpk().toByteArray(), + recvDesc.getZkproof().toByteArray() + ); + c = ByteUtil.merge(c, + recvDesc.getCEnc().toByteArray(), + recvDesc.getCOut().toByteArray(), + new byte[12] + ); + } + long recvCount = recvDescs.size(); + byte[] recvCountBytes = longTo32Bytes(recvCount); + byte[] outputOffsetbytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount); + byte[] coffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount + 32 + + 288 * recvCount); + bindingSig = parameters.getBindingSignature().toByteArray(); + mergedBytes = ByteUtil.merge(inputOffsetbytes, + authOffsetBytes, + outputOffsetbytes, + bindingSig, + coffsetBytes, + spendCountBytes, + input, + spendCountBytes, + spendAuthSig, + recvCountBytes, + output, + recvCountBytes, + c ); + return ByteArray.toHexString(mergedBytes); + } catch (Exception e) { } - long recvCount = recvDescs.size(); - byte[] recvCountBytes = longTo32Bytes(recvCount); - byte[] outputOffsetbytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount); - byte[] coffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount + 32 - + 288 * recvCount); - bindingSig = parameters.getBindingSignature().toByteArray(); - mergedBytes = ByteUtil.merge(inputOffsetbytes, - authOffsetBytes, - outputOffsetbytes, - bindingSig, - coffsetBytes, - spendCountBytes, - input, - spendCountBytes, - spendAuthSig, - recvCountBytes, - output, - recvCountBytes, - c - ); - return ByteArray.toHexString(mergedBytes); + return null; } public String encodeBurnParamsToHexString(ShieldedTRC20Parameters parameters, BigInteger value, String transparentToAddress) { byte[] mergedBytes; byte[] payTo = new byte[32]; - byte[] transparentToAddressBytes = WalletApi.decodeFromBase58Check(transparentToAddress); - System.arraycopy(transparentToAddressBytes, 0, payTo, 11, 21); - Contract.SpendDescription spendDesc = parameters.getSpendDescription(0); - mergedBytes = ByteUtil.merge( - spendDesc.getNullifier().toByteArray(), - spendDesc.getAnchor().toByteArray(), - spendDesc.getValueCommitment().toByteArray(), - spendDesc.getRk().toByteArray(), - spendDesc.getZkproof().toByteArray(), - spendDesc.getSpendAuthoritySignature().toByteArray(), - ByteUtil.bigIntegerToBytes(value, 32), - parameters.getBindingSignature().toByteArray(), - payTo - ); - return ByteArray.toHexString(mergedBytes); + try { + byte[] transparentToAddressBytes = WalletApi.decodeFromBase58Check(transparentToAddress); + System.arraycopy(transparentToAddressBytes, 0, payTo, 11, 21); + Contract.SpendDescription spendDesc = parameters.getSpendDescription(0); + mergedBytes = ByteUtil.merge( + spendDesc.getNullifier().toByteArray(), + spendDesc.getAnchor().toByteArray(), + spendDesc.getValueCommitment().toByteArray(), + spendDesc.getRk().toByteArray(), + spendDesc.getZkproof().toByteArray(), + spendDesc.getSpendAuthoritySignature().toByteArray(), + ByteUtil.bigIntegerToBytes(value, 32), + parameters.getBindingSignature().toByteArray(), + payTo + ); + return ByteArray.toHexString(mergedBytes); + } catch (Exception e) { + } + + return null; } public byte[] longTo32Bytes(long value) { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index a4c8c2fd1..98b5340d2 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -897,17 +897,17 @@ public NumberMessage getBrokerage(byte[] address) { public DecryptNotesTRC20 scanShieldedTRC20NoteByIvk(IvkDecryptTRC20Parameters parameters) { if (blockingStubSolidity != null) { - return blockingStubSolidity.scanShieldedTRC20NotesbyIvk(parameters); + return blockingStubSolidity.scanShieldedTRC20NotesByIvk(parameters); } else { - return blockingStubFull.scanShieldedTRC20NotesbyIvk(parameters); + return blockingStubFull.scanShieldedTRC20NotesByIvk(parameters); } } public DecryptNotesTRC20 scanShieldedTRC20NoteByOvk(OvkDecryptTRC20Parameters parameters) { if (blockingStubSolidity != null) { - return blockingStubSolidity.scanShieldedTRC20NotesbyOvk(parameters); + return blockingStubSolidity.scanShieldedTRC20NotesByOvk(parameters); } else { - return blockingStubFull.scanShieldedTRC20NotesbyOvk(parameters); + return blockingStubFull.scanShieldedTRC20NotesByOvk(parameters); } } @@ -921,7 +921,7 @@ public ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( return blockingStubFull.createShieldedContractParametersWithoutAsk(parameters); } - public NullifierResult IsShieldedTRC20ContractNoteSpent(NfTRC20Parameters prameters) { + public NullifierResult isShieldedTRC20ContractNoteSpent(NfTRC20Parameters prameters) { if (blockingStubSolidity != null) { return blockingStubSolidity.isShieldedTRC20ContractNoteSpent(prameters); } else { @@ -929,4 +929,9 @@ public NullifierResult IsShieldedTRC20ContractNoteSpent(NfTRC20Parameters pramet } } + public BytesMessage getTriggerInputForShieldedTRC20Contract( + ShieldedTRC20TriggerContractParameters parameters) { + return blockingStubFull.getTriggerInputForShieldedTRC20Contract(parameters); + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 0fc66306b..beafb5ab2 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2522,13 +2522,32 @@ public static ShieldedTRC20Parameters createShieldedContractParameters( public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( PrivateShieldedTRC20ParametersWithoutAsk privateParameters, byte[] ask) { - ShieldedTRC20Parameters parameters = - rpcCli.createShieldedContractParametersWithoutAsk(privateParameters); + ShieldedTRC20Parameters parameters; + try { + parameters = rpcCli.createShieldedContractParametersWithoutAsk(privateParameters); + } catch (Exception e) { + Status status = Status.fromThrowable(e); + System.out.println("createShieldedContractParametersWithoutAsk failed,error " + + status.getDescription()); + parameters = null; + } + if (parameters == null) { System.out.println("createShieldedContractParametersWithoutAsk failed!"); return null; } + if (parameters.getParameterType().equals("mint")) { + return parameters; + } + //generate spendAuthority signature and trigger input data + ShieldedTRC20TriggerContractParameters.Builder stBuilder = + ShieldedTRC20TriggerContractParameters.newBuilder(); + stBuilder.setShieldedTRC20Parameters(parameters); + if (parameters.getParameterType().equals("burn")) { + stBuilder.setAmount(privateParameters.getToAmount()); + stBuilder.setTransparentToAddress(privateParameters.getTransparentToAddress()); + } ByteString messageHash = parameters.getMessageHash(); List spendDescList = parameters.getSpendDescriptionList(); ShieldedTRC20Parameters.Builder newBuilder = @@ -2539,20 +2558,43 @@ public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk builder.setTxHash(messageHash); builder.setAlpha(privateParameters.getShieldedSpends(i).getAlpha()); - BytesMessage authSig = rpcCli.createSpendAuthSig(builder.build()); + BytesMessage authSig; + try { + authSig = rpcCli.createSpendAuthSig(builder.build()); + } catch (Exception e) { + Status status = Status.fromThrowable(e); + System.out.println("createSpendAuthSig failed,error " + + status.getDescription()); + authSig = null; + } + if (authSig == null) { + return null; + } newBuilder.getSpendDescriptionBuilder(i) - .setSpendAuthoritySignature( - ByteString.copyFrom( - authSig.getValue().toByteArray())); + .setSpendAuthoritySignature( + ByteString.copyFrom( + authSig.getValue().toByteArray())); + stBuilder.addSpendAuthoritySignature(authSig); + } + BytesMessage triggerInputData; + try { + triggerInputData = rpcCli.getTriggerInputForShieldedTRC20Contract(stBuilder.build()); + } catch (Exception e) { + triggerInputData = null; + System.out.println("getTriggerInputForShieldedTRC20Contract error, please retry!"); + } + if (triggerInputData == null) { + return null; } + newBuilder.setTriggerContractInputBytes(triggerInputData.toByteString()); return newBuilder.build(); } - public static Optional IsShieldedTRC20ContractNoteSpent( + public static Optional isShieldedTRC20ContractNoteSpent( NfTRC20Parameters parameters, boolean showErrorMsg) { if (showErrorMsg) { try { - return Optional.of(rpcCli.IsShieldedTRC20ContractNoteSpent(parameters)); + return Optional.of(rpcCli.isShieldedTRC20ContractNoteSpent(parameters)); } catch (Exception e) { if (showErrorMsg) { Status status = Status.fromThrowable(e); @@ -2561,7 +2603,7 @@ public static Optional IsShieldedTRC20ContractNoteSpent( } } } else { - return Optional.of(rpcCli.IsShieldedTRC20ContractNoteSpent(parameters)); + return Optional.of(rpcCli.isShieldedTRC20ContractNoteSpent(parameters)); } return Optional.empty(); } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 60987b9be..ba7d92193 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -705,10 +705,10 @@ service Wallet { rpc CreateShieldedContractParametersWithoutAsk (PrivateShieldedTRC20ParametersWithoutAsk) returns (ShieldedTRC20Parameters) { }; - rpc ScanShieldedTRC20NotesbyIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + rpc ScanShieldedTRC20NotesByIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { }; - rpc ScanShieldedTRC20NotesbyOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + rpc ScanShieldedTRC20NotesByOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { }; rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { @@ -870,10 +870,10 @@ service WalletSolidity { rpc GetBrokerageInfo (BytesMessage) returns (NumberMessage) { }; - rpc ScanShieldedTRC20NotesbyIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + rpc ScanShieldedTRC20NotesByIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { }; - rpc ScanShieldedTRC20NotesbyOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + rpc ScanShieldedTRC20NotesByOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { }; rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { @@ -1365,6 +1365,7 @@ message IvkDecryptTRC20Parameters { bytes ivk = 4; bytes ak = 5; bytes nk = 6; + repeated string events = 7; } message OvkDecryptTRC20Parameters { @@ -1372,6 +1373,7 @@ message OvkDecryptTRC20Parameters { int64 end_block_index = 2; bytes ovk = 3; bytes shielded_TRC20_contract_address = 4; + repeated string events = 5; } message DecryptNotesTRC20 { @@ -1381,6 +1383,8 @@ message DecryptNotesTRC20 { bool is_spent = 3; bytes txid = 4; int32 index = 5; //the index of note in txid + string to_amount = 6; + bytes transparent_to_address = 7; } repeated NoteTx noteTxs = 1; } diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index fcf33ca1d..c35c2310b 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,7 +4,9 @@ net { fullnode = { ip.list = [ - "127.0.0.1:50051" + #"127.0.0.1:50051" + "127.0.0.1:50056" + #"47.252.84.138:15056" ] } From ccb394dbea12c4a2ca4a44b0122ae55db069839b Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Thu, 11 Jun 2020 12:13:12 +0800 Subject: [PATCH 318/445] process unexpected input parameters --- README.md | 165 +++++++++++++++--- src/main/java/org/tron/walletcli/Client.java | 29 ++- .../org/tron/walletcli/WalletApiWrapper.java | 10 +- .../java/org/tron/walletserver/WalletApi.java | 5 +- src/main/resources/config.conf | 4 +- 5 files changed, 177 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index a5dc45718..5aa554633 100644 --- a/README.md +++ b/README.md @@ -1442,7 +1442,10 @@ d :2fd028965d3b455579ab28 ``` --> -## How to transfer TRC20 token to shielded address +## How to transfer shielded TRC20 token + +when you begin to transfer TRC20 token to shielded address, you must have a shielded address. The + following commands help to generate shielded account. ### GetSpendingKey @@ -1455,20 +1458,20 @@ Example: 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a ``` -### getExpandedSpendingKey sk +### GetExpandedSpendingKey sk Generate ask, nsk, ovk from sk Example: ```console -> getExpandedSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a +> GetExpandedSpendingKey 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a ask:252a0f6f6f0bac114a13e1e663d51943f1df9309649400218437586dea78260e nsk:5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 ovk:892a10c1d3e8ea22242849e13f177d69e1180d1d5bba118c586765241ba2d3d6 ``` -### getAkFromAsk ask +### GetAkFromAsk ask Generate ak from ask Example: @@ -1478,7 +1481,7 @@ Example: ak:f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ``` -### getNkFromNsk nsk +### GetNkFromNsk nsk Generate nk from nsk @@ -1489,14 +1492,15 @@ Example: nk:ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a ``` -### getIncomingViewingKey ak[64] nk[64] +### GetIncomingViewingKey ak[64] nk[64] Generate ivk from ak and nk Example: ```console -> getincomingviewingkey f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a +> Getincomingviewingkey f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 + ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a ivk:148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 ``` @@ -1511,14 +1515,15 @@ Example: 11db4baf6bd5d5afd3a8b5 ``` -### getshieldedpaymentaddress ivk[64] d[22] +### GetShieldedPaymentAddress ivk[64] d[22] -Generate a shielded address from sk and d +Generate a shielded address from ivk and d Example: ```console -GetShieldedPaymentAddress 148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 11db4baf6bd5d5afd3a8b5 +> GetShieldedPaymentAddress 148cf9e91f1e6656a41dc9b6c6ee4e52ff7a25b25c2d4a3a3182d0a2cd851205 + 11db4baf6bd5d5afd3a8b5 pkd:65c11642115d386ed716b9cc06a3498e86e303d7f20d0869c9de90e31322ac15 shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z ``` @@ -1527,7 +1532,8 @@ shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe8 Set TRC20 contract address and shielded contract address. Please execute this command before you perform all the following operations related to the shielded transaction of TRC20 token except `ScanShieldedTRC20NoteByIvk` and `ScanShieldedTRC20NoteByOvk`. -When you execute this command, the `Scaling Factor` will be shown. The `Scaling Factor` is set in the shileded contract. +When you execute this command, the `Scaling Factor` will be shown. The `Scaling Factor` is set in + the shielded contract. TRC20ContractAddress > TRC20 contract address @@ -1595,13 +1601,25 @@ ztron15t3c27a5ve43ssflqepa8dke36vzvccxrren4ma2lghu3hle8rtwltufnvvzrm76w042s9p5f4 ### SendShieldedTRC20Coin - > SendShieldedTRC20Coin fromAmount shieldedInputNum input1 input2 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 .... +> SendShieldedTRC20Coin fromAmount shieldedInputNum input1 input2 ... publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 shieldedAddress2 amount2 memo2 .... Shielded transfer, support three types: -- MINT: transfer from one public address to one shielded address, fromAmount shoule be equal to the shielded output amount. -- TRANSFER: transfer from one or two shielded address(es) to one or two shielded address(es), the sum of shielded input amount should be equal to the sum of shielded output amount. -- BURN: transfer from one shielded address to one public address, toAmount shoule be equal to the shielded input amount. +- MINT: transfer from one public address to one shielded address, fromAmount should be equal to + the shielded output amount. When you MINT, you need to enter password twice as prompted, one + time is for triggering `approve` method of TRC20 contract that allows the shielded contract can + transfer form your account, and other one is for triggering `mint` method of shielded contract + that executes MINT. It's important to remember that you must use the same public address to + trigger these two methods. +- TRANSFER: transfer from one or two shielded address(es) to one or two shielded address(es), the + sum of shielded input amount should be equal to the sum of shielded output amount. When you + TRANSFER, you need to enter password of public account as prompted, and this is used to trigger + 'transfer' method of shielded contract that executes TRANSFER. +- BURN: transfer from one shielded address to one public address, toAmount should be equal to the + shielded input amount.When you BURN, you need to enter password of public account as prompted, and + this is used to trigger 'burn' method of shielded contract that executes BURN. + +It's better to use different accounts to trigger BURN, TRANSFER and MINT. fromAmount > The amount transfer from public address. If the transfer type is MINT, this variable must be equal to the shielded output amount, otherwise it must be 0. @@ -1767,11 +1785,11 @@ No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1 Clean all the notes scanned, and rescan all blocks. Generally used when there is a problem with the notes or when switching environments. -### ScanShieldedTRC20NoteByIvk shieldedTRC20ContarctAddress ivk ak nk startNum endNum +### ScanShieldedTRC20NoteByIvk shieldedTRC20ContractAddress ivk ak nk startNum endNum [event1] [event2] ... Scan notes by ivk, ak and nk. -shieldedTRC20ContarctAddress +shieldedTRC20ContractAddress > The address of shielded contract ivk @@ -1789,15 +1807,51 @@ startNum endNum > The end block number of the scan +event1/event2 +> The events you want to scan. These events must be compatible with standard events, that is, NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21 +>]) and TokenBurn(address,uint256,bytes32[3]). If you ignore this field, the command will scan the standard events. +>In most cases, you can ignore these parameters. + Example: -> ScanShieldedTRC20NoteByIvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e fe8203f3dc5feb2497986512f94a3b9631bffec02aee0bca735639742d2cef07 a150fa887ed45b6d9eca73ec94e1dd53dddf945cc947fbc6a2c5cc334c940233 696aca6b5db2f42041850423d63d95574ad1256519716507c5c074a924e47b0c 0 1000 +```console +> ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 5400 +[ + { + note: { + value: 100000 + payment_address: ztron12dq4ktrydrxzxrsgpmusp4pe0xawqyz4qfxzsgjdauw99n4n3efnw4kmrptlw8jcrrydx5694mw + rcm: a45878a4e0d53f5cac79370fea1bf4aa82c67d3b2f647ac89c2b1e7061ea740a + memo: without ask 2v1 + } + position: 10 + is_spent: true + tx_id: 5891fd3a8e860b336b7f7d31f64ec52ec5dc76f81b9bb4e4d0fa8a5756a61dd6 + } +] + +> ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 5400 NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) TokenBurn(address,uint256,bytes32[3]) +[ + { + note: { + value: 100000 + payment_address: ztron12dq4ktrydrxzxrsgpmusp4pe0xawqyz4qfxzsgjdauw99n4n3efnw4kmrptlw8jcrrydx5694mw + rcm: a45878a4e0d53f5cac79370fea1bf4aa82c67d3b2f647ac89c2b1e7061ea740a + memo: without ask 2v1 + } + position: 10 + is_spent: true + tx_id: 5891fd3a8e860b336b7f7d31f64ec52ec5dc76f81b9bb4e4d0fa8a5756a61dd6 + } +] -### ScanShieldedTRC20NoteByOvk shieldedTRC20ContarctAddress ovk startNum endNum +``` + +## ScanShieldedTRC20NoteByOvk shieldedTRC20ContractAddress ovk startNum endNum [event1] [event2] ... Scan notes by ovk -shieldedTRC20ContarctAddress +shieldedTRC20ContractAddress > The address of shielded contract ovk @@ -1809,10 +1863,67 @@ startNum endNum > The end block number of the scan -Example: +event1/event2 +> The event you want to scan. These events must be compatible with standard events, that is, NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) and TokenBurn(address,uint256,bytes32[3 +>]). If you ignore this field, the command will scan the standard events. -> ScanShieldedTRC20NoteByOvk TKERuAmhJh8vZi1dzJtx8926xeCT74747e 00bad26a4f1d2380345fd2ab28008a593ba9a2e19c75cba28333afa73c89e6d8 0 1000 +Example: +```console +> ScanShieldedTRC20NoteByOvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu 4b33fc947a53a5e2a1d1636b323f7f6cecff8c34c9fc511ccc7cfaf0dd6f4c03 5000 6000 +[ + { + note: { + value: 60000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 50698dc3c97fb4d2c818b62de2265a271eb9a58b5dd65074122ddf4d794c6b03 + memo: 1 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + note: { + value: 40000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 94afb02c6fd4b19ada89b6b85e2cc23f2fb76c5188ede646c5046b2539a3bf00 + memo: 2 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + transparent_to_address: TV7ceN4tHDNPB47DMStcUFC3Y8QQ7KzN32 + transparent_amount: 130000 + tx_id: d45da3394be6c15220d31ac17c13e02130aab0c3edf97750620538f4efae366b + } +] + +> ScanShieldedTRC20NoteByOvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu 4b33fc947a53a5e2a1d1636b323f7f6cecff8c34c9fc511ccc7cfaf0dd6f4c03 5000 6000 NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) TokenBurn(address,uint256,bytes32[3]) +[ + { + note: { + value: 60000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 50698dc3c97fb4d2c818b62de2265a271eb9a58b5dd65074122ddf4d794c6b03 + memo: 1 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + note: { + value: 40000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 94afb02c6fd4b19ada89b6b85e2cc23f2fb76c5188ede646c5046b2539a3bf00 + memo: 2 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + transparent_to_address: TV7ceN4tHDNPB47DMStcUFC3Y8QQ7KzN32 + transparent_amount: 130000 + tx_id: d45da3394be6c15220d31ac17c13e02130aab0c3edf97750620538f4efae366b + } +] +``` ### BackupShieldedTRC20Wallet @@ -1821,7 +1932,7 @@ Back up one shielded address. Example: ```console -wallet> BackupShieldedTRC20Wallet +> BackupShieldedTRC20Wallet Please input your password for shieldedTRC20 wallet. password: The 1th shieldedTRC20 address is ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z @@ -1841,7 +1952,7 @@ Import one shielded address to local wallet. Example: ```console -wallet> ImportShieldedTRC20Wallet +> ImportShieldedTRC20Wallet ShieldedTRC20 wallet does not exist, will build it. Please input password. password: @@ -1863,13 +1974,13 @@ Display information about shielded addresses. If this address is not in the wall Example: ```console -wallet> ListShieldedTRC20Address +> ListShieldedTRC20Address ShieldedTRC20Address : ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z ztron1mnkdjl0802dqha9ufh4m80f2ua9cff2hct8geeh77llrz4ywgtu0ct8ygy6k5xavdkd278jyttj ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 -wallet> ShowShieldedTRC20AddressInfo ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z +> ShowShieldedTRC20AddressInfo ztron1mf0a0cy86j8rmn4l7dcdsnhyj2k46rem4qxwjqh4z0x26utlddtmmr5fk5dchzt2hpujyvgk69z The following variables are secret information, please don't show to other people!!! sk :01ef2d71f8eef668e12db7aef1267c7d6a8f43c84dffa66fc09e2c749464190e ivk:7d2e9c14ff1d82843f39cb69e8bcc228370e4ea8750669bba79e90c485d94c03 @@ -1877,7 +1988,7 @@ ovk:2c3d164fffa63b41a34f495e0c9d8af79d595cfb07db1539545ddcecf046d66e pkd:70d84ee492ad5d0f3ba80ce902f513ccad717f6b57bd8e89b51b8b896ab87922 d :da5fd7e087d48e3dcebff3 -wallet> ShowShieldedTRC20AddressInfo ztron1z8d5htmt6h26l5agk8r7wxw9pyhc0a78hl5thva4k9kcn7fsqvygchyt3n2ncy0r4xv4j5mywnu +> ShowShieldedTRC20AddressInfo ztron1z8d5htmt6h26l5agk8r7wxw9pyhc0a78hl5thva4k9kcn7fsqvygchyt3n2ncy0r4xv4j5mywnu pkd:c7e719c5092f87f7c7bfe8bbb3b5b16d89f93003088c5c8b8cd53c11e3a99959 d :11db4baf6bd5d5afd3a8b1 ``` diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 18d6e62ac..d2c7de1c9 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2930,7 +2930,16 @@ private void generateShieldedTRC20Address(String[] parameters) throws IOExceptio int addressNum = 1; if (parameters.length > 0 && !StringUtil.isNullOrEmpty(parameters[0])) { - addressNum = Integer.valueOf(parameters[0]); + try { + addressNum = Integer.valueOf(parameters[0]); + if (addressNum == 0) { + System.out.println("Parameter must be positive!"); + return; + } + } catch (NumberFormatException e) { + System.out.println("Invalid parameter!"); + return; + } } ShieldedTRC20Wrapper.getInstance().initShieldedTRC20WalletFile(); @@ -3303,7 +3312,15 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk return false; } } - if ((parameters.length - parameterIndex) % 3 != 0) { + int parameterNum; + try { + parameterNum = shieldedOutputNum * 3 + parameterIndex; + } catch (Exception e) { + System.out.println("Invalid parameter number!"); + return false; + } + + if (parameters.length != parameterNum) { System.out.println("Invalid parameter number!"); return false; } @@ -3409,7 +3426,13 @@ private void showShieldedTRC20AddressInfo(String[] parameters) { System.out.println("pkd:" + ByteArray.toHexString(addressInfo.getPkD())); System.out.println("d :" + ByteArray.toHexString(addressInfo.getD().getData())); } else { - PaymentAddress decodePaymentAddress = KeyIo.decodePaymentAddress(shieldedAddress); + PaymentAddress decodePaymentAddress; + try { + decodePaymentAddress = KeyIo.decodePaymentAddress(shieldedAddress); + } catch (IllegalArgumentException e) { + System.out.println("Shielded address " + shieldedAddress + " is invalid, please check!"); + return; + } if (decodePaymentAddress != null) { System.out.println("pkd:" + ByteArray.toHexString(decodePaymentAddress.getPkD())); System.out.println("d :" + ByteArray.toHexString(decodePaymentAddress.getD().getData())); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 7f0681591..7e8aea360 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1328,8 +1328,14 @@ public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end System.out.println("\t\t }"); } else { //This is specific for BURN. - System.out.println("\t\t transparent_to_address: " - + ByteArray.toHexString(noteTx.getTransparentToAddress().toByteArray())); + try { + String toAddress = + WalletApi.encode58Check(noteTx.getTransparentToAddress().toByteArray()); + System.out.println("\t\t transparent_to_address: " + toAddress); + } catch (Exception e) { + System.out.println("\t\t transparent_to_address: " + + ByteArray.toHexString(noteTx.getTransparentToAddress().toByteArray())); + } System.out.println("\t\t transparent_amount: " + noteTx.getToAmount()); } System.out.println("\t\t tx_id: " + ByteArray.toHexString(noteTx.getTxid().toByteArray())); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index beafb5ab2..d571d423b 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2586,7 +2586,8 @@ public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk if (triggerInputData == null) { return null; } - newBuilder.setTriggerContractInputBytes(triggerInputData.toByteString()); + newBuilder.setTriggerContractInput( + ByteArray.toHexString(triggerInputData.getValue().toByteArray())); return newBuilder.build(); } @@ -2598,7 +2599,7 @@ public static Optional isShieldedTRC20ContractNoteSpent( } catch (Exception e) { if (showErrorMsg) { Status status = Status.fromThrowable(e); - System.out.println("IsShieldedTRC20ContractNoteSpent failed,error " + System.out.println("IsShieldedTRC20ContractNoteSpent failed, error " + status.getDescription()); } } diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index c35c2310b..c4cd68d9b 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,8 +4,8 @@ net { fullnode = { ip.list = [ - #"127.0.0.1:50051" - "127.0.0.1:50056" + "127.0.0.1:50051" + # "127.0.0.1:50056" #"47.252.84.138:15056" ] } From cada8d94babb98ed5842ece2c054a485ca055e78 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Tue, 16 Jun 2020 14:26:46 +0800 Subject: [PATCH 319/445] add output cm for burn --- src/main/java/org/tron/walletcli/Client.java | 8 +- .../org/tron/walletcli/WalletApiWrapper.java | 147 +++--------------- 2 files changed, 23 insertions(+), 132 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index d2c7de1c9..fa488820a 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3359,17 +3359,17 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk int shieldedContractType = -1; if (fromPublicAmount.compareTo(BigInteger.ZERO) > 0 && shieldedOutList.size() == 1 && shieldedInputList.size() == 0 && toPublicAmount.compareTo(BigInteger.ZERO) == 0) { - System.out.println("This is an MINT. "); + System.out.println("This is MINT."); shieldedContractType = 0; } else if (fromPublicAmount.compareTo(BigInteger.ZERO) == 0 && toPublicAmount.compareTo(BigInteger.ZERO) == 0 && shieldedOutList.size() > 0 && shieldedOutList.size() < 3 && shieldedInputList.size() > 0 && shieldedInputList.size() < 3) { - System.out.println("This is an TRANSFER. "); + System.out.println("This is TRANSFER."); shieldedContractType = 1; - } else if (fromPublicAmount.compareTo(BigInteger.ZERO) == 0 && shieldedOutList.size() == 0 + } else if (fromPublicAmount.compareTo(BigInteger.ZERO) == 0 && shieldedOutList.size() < 2 && shieldedInputList.size() == 1 && toPublicAmount.compareTo(BigInteger.ZERO) > 0) { - System.out.println("This is an BURN. "); + System.out.println("This is BURN."); shieldedContractType = 2; } else { System.out.println("The shieldedContractType is not MINT, TRANSFER or BURN"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 7e8aea360..a0ee48714 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1365,8 +1365,13 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm if (shieldedContractType == 2) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(0)); - if (noteInfo.getRawValue().compareTo(toAmount) != 0) { - System.out.println("BURN: toPublicAmount must be equal to note amount."); + BigInteger valueBalanceBi = noteInfo.getRawValue(); + if (shieldedOutputList.size() > 0) { + valueBalanceBi = valueBalanceBi.subtract(BigInteger.valueOf( + shieldedOutputList.get(0).getValue()).multiply(scalingFactor)); + } + if (valueBalanceBi.compareTo(toAmount) != 0) { + System.out.println("BURN: shielded input amount must be equal to output amount."); return false; } } @@ -1509,8 +1514,8 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm return false; } } else if (shieldedContractType == 2) {//BURN - boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); - if (transferResult) { + boolean burnResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); + if (burnResult) { System.out.println("BURN succeed!"); return true; } else { @@ -1541,8 +1546,13 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte if (shieldedContractType == 2) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() .get(shieldedInputList.get(0)); - if (noteInfo.getRawValue().compareTo(toAmount) != 0) { - System.out.println("BURN: toPublicAmount must be equal to note amount."); + BigInteger valueBalanceBi = noteInfo.getRawValue(); + if (shieldedOutputList.size() > 0) { + valueBalanceBi = valueBalanceBi.subtract(BigInteger.valueOf( + shieldedOutputList.get(0).getValue()).multiply(scalingFactor)); + } + if (valueBalanceBi.compareTo(toAmount) != 0) { + System.out.println("BURN: shielded input amount must be equal to output amount."); return false; } } @@ -1693,8 +1703,8 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte return false; } } else if (shieldedContractType == 2) {//BURN - boolean transferResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); - if (transferResult) { + boolean burnResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); + if (burnResult) { System.out.println("BURN succeed!"); return true; } else { @@ -1762,7 +1772,7 @@ public boolean triggerShieldedContract(String contractAddress, String data, } else if (shieldedContractType == 1) { methodStr = "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])"; } else if (shieldedContractType == 2) { - methodStr = "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address,bytes32[3])"; + methodStr = "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address,bytes32[3],bytes32[9][],bytes32[21][])"; } else { System.out.println("unsupported shieldedContractType! "); return false; @@ -1773,123 +1783,4 @@ public boolean triggerShieldedContract(String contractAddress, String data, return callContract(ownerAddress, contractAddressBytes, 0, inputData, 1000_000_000L, 0, "", false); } - - public String encodeMintParamsToHexString(ShieldedTRC20Parameters parameters, - BigInteger value) { - byte[] mergedBytes; - try { - Contract.ReceiveDescription revDesc = parameters.getReceiveDescription(0); - mergedBytes = ByteUtil.merge( - ByteUtil.bigIntegerToBytes(value, 32), - revDesc.getNoteCommitment().toByteArray(), - revDesc.getValueCommitment().toByteArray(), - revDesc.getEpk().toByteArray(), - revDesc.getZkproof().toByteArray(), - parameters.getBindingSignature().toByteArray(), - revDesc.getCEnc().toByteArray(), - revDesc.getCOut().toByteArray(), - new byte[12] - ); - return ByteArray.toHexString(mergedBytes); - } catch (Exception e) { - } - - return null; - } - - public String encodeTransferParamsToHexString(ShieldedTRC20Parameters parameters) { - byte[] input = new byte[0]; - byte[] spendAuthSig = new byte[0]; - byte[] output = new byte[0]; - byte[] c = new byte[0]; - byte[] bindingSig; - byte[] mergedBytes; - try { - List spendDescs = parameters.getSpendDescriptionList(); - for (Contract.SpendDescription spendDesc : spendDescs) { - input = ByteUtil.merge(input, - spendDesc.getNullifier().toByteArray(), - spendDesc.getAnchor().toByteArray(), - spendDesc.getValueCommitment().toByteArray(), - spendDesc.getRk().toByteArray(), - spendDesc.getZkproof().toByteArray() - ); - spendAuthSig = ByteUtil.merge( - spendAuthSig, spendDesc.getSpendAuthoritySignature().toByteArray()); - } - byte[] inputOffsetbytes = longTo32Bytes(192); - long spendCount = spendDescs.size(); - byte[] spendCountBytes = longTo32Bytes(spendCount); - byte[] authOffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount); - List recvDescs = parameters.getReceiveDescriptionList(); - for (Contract.ReceiveDescription recvDesc : recvDescs) { - output = ByteUtil.merge(output, - recvDesc.getNoteCommitment().toByteArray(), - recvDesc.getValueCommitment().toByteArray(), - recvDesc.getEpk().toByteArray(), - recvDesc.getZkproof().toByteArray() - ); - c = ByteUtil.merge(c, - recvDesc.getCEnc().toByteArray(), - recvDesc.getCOut().toByteArray(), - new byte[12] - ); - } - long recvCount = recvDescs.size(); - byte[] recvCountBytes = longTo32Bytes(recvCount); - byte[] outputOffsetbytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount); - byte[] coffsetBytes = longTo32Bytes(192 + 32 + 320 * spendCount + 32 + 64 * spendCount + 32 - + 288 * recvCount); - bindingSig = parameters.getBindingSignature().toByteArray(); - mergedBytes = ByteUtil.merge(inputOffsetbytes, - authOffsetBytes, - outputOffsetbytes, - bindingSig, - coffsetBytes, - spendCountBytes, - input, - spendCountBytes, - spendAuthSig, - recvCountBytes, - output, - recvCountBytes, - c - ); - return ByteArray.toHexString(mergedBytes); - } catch (Exception e) { - } - return null; - } - - public String encodeBurnParamsToHexString(ShieldedTRC20Parameters parameters, BigInteger value, - String transparentToAddress) { - byte[] mergedBytes; - byte[] payTo = new byte[32]; - try { - byte[] transparentToAddressBytes = WalletApi.decodeFromBase58Check(transparentToAddress); - System.arraycopy(transparentToAddressBytes, 0, payTo, 11, 21); - Contract.SpendDescription spendDesc = parameters.getSpendDescription(0); - mergedBytes = ByteUtil.merge( - spendDesc.getNullifier().toByteArray(), - spendDesc.getAnchor().toByteArray(), - spendDesc.getValueCommitment().toByteArray(), - spendDesc.getRk().toByteArray(), - spendDesc.getZkproof().toByteArray(), - spendDesc.getSpendAuthoritySignature().toByteArray(), - ByteUtil.bigIntegerToBytes(value, 32), - parameters.getBindingSignature().toByteArray(), - payTo - ); - return ByteArray.toHexString(mergedBytes); - } catch (Exception e) { - } - - return null; - } - - public byte[] longTo32Bytes(long value) { - byte[] longBytes = ByteArray.fromLong(value); - byte[] zeroBytes = new byte[24]; - return ByteUtil.merge(zeroBytes, longBytes); - } } From 03c7469987b47cb0c12d484e910b6e7e41738ea2 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Thu, 18 Jun 2020 10:26:33 +0800 Subject: [PATCH 320/445] add APIs for shielded trc20 transaction --- api/api.proto | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/api/api.proto b/api/api.proto index 945fc7730..05f50196a 100644 --- a/api/api.proto +++ b/api/api.proto @@ -705,6 +705,25 @@ service Wallet { rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; + + //for shielded contract + rpc CreateShieldedContractParameters (PrivateShieldedTRC20Parameters) returns (ShieldedTRC20Parameters) { + }; + + rpc CreateShieldedContractParametersWithoutAsk (PrivateShieldedTRC20ParametersWithoutAsk) returns (ShieldedTRC20Parameters) { + }; + + rpc ScanShieldedTRC20NotesByIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc ScanShieldedTRC20NotesByOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { + }; + + rpc GetTriggerInputForShieldedTRC20Contract (ShieldedTRC20TriggerContractParameters) returns (BytesMessage) { + }; // end for shiededTransaction rpc CreateCommonTransaction (Transaction) returns (TransactionExtention) { @@ -859,6 +878,15 @@ service WalletSolidity { rpc IsSpend (NoteParameters) returns (SpendResult) { } + rpc ScanShieldedTRC20NotesByIvk (IvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc ScanShieldedTRC20NotesByOvk (OvkDecryptTRC20Parameters) returns (DecryptNotesTRC20) { + }; + + rpc IsShieldedTRC20ContractNoteSpent (NfTRC20Parameters) returns (NullifierResult) { + }; + rpc GetRewardInfo (BytesMessage) returns (NumberMessage) { }; @@ -1226,6 +1254,7 @@ message PrivateParameters { repeated ReceiveNote shielded_receives = 7; bytes transparent_to_address = 8; int64 to_amount = 9; + int64 timeout = 10; // timeout in seconds, it works only when it bigger than 0 } message PrivateParametersWithoutAsk { @@ -1238,6 +1267,7 @@ message PrivateParametersWithoutAsk { repeated ReceiveNote shielded_receives = 7; bytes transparent_to_address = 8; int64 to_amount = 9; + int64 timeout = 10; // timeout in seconds, it works only when it bigger than 0 } message SpendAuthSigParameters { @@ -1312,3 +1342,94 @@ message SpendResult { message TransactionInfoList { repeated TransactionInfo transactionInfo = 1; } + +message SpendNoteTRC20 { + Note note = 1; + bytes alpha = 2; + bytes root = 3; + bytes path = 4; + int64 pos = 5; +} + +message PrivateShieldedTRC20Parameters { + bytes ask = 1; + bytes nsk = 2; + bytes ovk = 3; + string from_amount = 4; + repeated SpendNoteTRC20 shielded_spends = 5; + repeated ReceiveNote shielded_receives = 6; + bytes transparent_to_address = 7; + string to_amount = 8; + bytes shielded_TRC20_contract_address = 9; +} + +message PrivateShieldedTRC20ParametersWithoutAsk { + bytes ak = 1; + bytes nsk = 2; + bytes ovk = 3; + string from_amount = 4; + repeated SpendNoteTRC20 shielded_spends = 5; + repeated ReceiveNote shielded_receives = 6; + bytes transparent_to_address = 7; + string to_amount = 8; + bytes shielded_TRC20_contract_address = 9; +} + +message ShieldedTRC20Parameters { + repeated SpendDescription spend_description = 1; + repeated ReceiveDescription receive_description = 2; + bytes binding_signature = 3; + bytes message_hash = 4; + string trigger_contract_input = 5; + string parameter_type = 6; +} + +message IvkDecryptTRC20Parameters { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes shielded_TRC20_contract_address = 3; + bytes ivk = 4; + bytes ak = 5; + bytes nk = 6; + repeated string events = 7; +} + +message OvkDecryptTRC20Parameters { + int64 start_block_index = 1; + int64 end_block_index = 2; + bytes ovk = 3; + bytes shielded_TRC20_contract_address = 4; + repeated string events = 5; +} + +message DecryptNotesTRC20 { + message NoteTx { + Note note = 1; + int64 position = 2; + bool is_spent = 3; + bytes txid = 4; + int32 index = 5; //the index of note in txid + string to_amount = 6; + bytes transparent_to_address = 7; + } + repeated NoteTx noteTxs = 1; +} + +message NfTRC20Parameters { + Note note = 1; + bytes ak = 2; + bytes nk = 3; + int64 position = 4; + bytes shielded_TRC20_contract_address = 5; +} + +message NullifierResult { + bool is_spent = 1; +} + +message ShieldedTRC20TriggerContractParameters { + ShieldedTRC20Parameters shielded_TRC20_Parameters = 1; + repeated BytesMessage spend_authority_signature = 2; + string amount = 3; + bytes transparent_to_address = 4; +} \ No newline at end of file From e9f97e19056651fdde0acc6c689720f363cdb5e1 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Thu, 18 Jun 2020 10:55:40 +0800 Subject: [PATCH 321/445] reset config.conf --- src/main/resources/config.conf | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index c4cd68d9b..fcf33ca1d 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -5,8 +5,6 @@ net { fullnode = { ip.list = [ "127.0.0.1:50051" - # "127.0.0.1:50056" - #"47.252.84.138:15056" ] } From 339df9d225ec5a86c59ce71a9b90725740866e93 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Thu, 18 Jun 2020 16:40:20 +0800 Subject: [PATCH 322/445] import necessary packages --- src/main/java/org/tron/walletserver/WalletApi.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 59adda95e..ed85af551 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -26,6 +26,7 @@ import org.tron.api.GrpcAPI.BytesMessage; import org.tron.api.GrpcAPI.DecryptNotes; import org.tron.api.GrpcAPI.DecryptNotesMarked; +import org.tron.api.GrpcAPI.DecryptNotesTRC20; import org.tron.api.GrpcAPI.DelegatedResourceList; import org.tron.api.GrpcAPI.DiversifierMessage; import org.tron.api.GrpcAPI.EasyTransferResponse; @@ -37,16 +38,24 @@ import org.tron.api.GrpcAPI.IvkDecryptAndMarkParameters; import org.tron.api.GrpcAPI.IvkDecryptParameters; import org.tron.api.GrpcAPI.NfParameters; +import org.tron.api.GrpcAPI.NfTRC20Parameters; import org.tron.api.GrpcAPI.NodeList; import org.tron.api.GrpcAPI.NoteParameters; +import org.tron.api.GrpcAPI.NullifierResult; +import org.tron.api.GrpcAPI.IvkDecryptTRC20Parameters; import org.tron.api.GrpcAPI.OvkDecryptParameters; +import org.tron.api.GrpcAPI.OvkDecryptTRC20Parameters; import org.tron.api.GrpcAPI.PaymentAddressMessage; import org.tron.api.GrpcAPI.PrivateParameters; import org.tron.api.GrpcAPI.PrivateParametersWithoutAsk; +import org.tron.api.GrpcAPI.PrivateShieldedTRC20Parameters; +import org.tron.api.GrpcAPI.PrivateShieldedTRC20ParametersWithoutAsk; import org.tron.api.GrpcAPI.ProposalList; import org.tron.api.GrpcAPI.Return; import org.tron.api.GrpcAPI.SpendAuthSigParameters; import org.tron.api.GrpcAPI.SpendResult; +import org.tron.api.GrpcAPI.ShieldedTRC20Parameters; +import org.tron.api.GrpcAPI.ShieldedTRC20TriggerContractParameters; import org.tron.api.GrpcAPI.TransactionApprovedList; import org.tron.api.GrpcAPI.TransactionExtention; import org.tron.api.GrpcAPI.TransactionInfoList; @@ -2593,7 +2602,7 @@ public static Optional scanShieldedTRC20NoteByOvk( public String constantCallShieldedContract(byte[] contractAddress, byte[] data, String functionName) { byte[] address = getAddress(); - Contract.TriggerSmartContract triggerContract = + TriggerSmartContract triggerContract = triggerCallContract(address, contractAddress, 0, data, 0, ""); TransactionExtention transactionExtention = rpcCli.triggerConstantContract(triggerContract); From 7b232406d73a9ab1615c0d0a6b9f4b8823c8aa8e Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 18 Jun 2020 17:06:31 +0800 Subject: [PATCH 323/445] typo --- src/main/java/org/tron/walletserver/WalletApi.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index ed85af551..a1f97735b 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -71,7 +71,6 @@ import org.tron.common.crypto.sm2.SM2; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; -import org.tron.common.utils.ByteUtil; import org.tron.common.utils.TransactionUtils; import org.tron.common.utils.Utils; import org.tron.core.config.Configuration; From 890cd03b0d748f5c8da4d87cd546064683788b04 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 19 Jun 2020 15:06:36 +0800 Subject: [PATCH 324/445] update command list --- README.md | 53 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c1b78fba5..bca75816d 100644 --- a/README.md +++ b/README.md @@ -60,27 +60,34 @@ For more information on a specific command, just type the command on terminal wh | [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [ApproveProposal](#Approvecancel-the-proposal) | [AssetIssue](#How-to-issue-TRC10-tokens) | | :---------:|:---------:|:--------: | -| [BackupWallet](#Wallet-related-commands)| [BackupWallet2Base64](#Wallet-related-commands) | [BroadcastTransaction](#Some-others) | -|[ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) |[DeleteProposal](#Cancel-the-created-proposal) | -| [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | [ExchangeInject](#How-to-trade-on-the-exchange) | -| [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | [FreezeBalance](#How-to-delegate-resourcee) | -| [GenerateAddress](#Account-related-commands) | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | -|[GetAccountResource](#Account-related-commands) | [GetAddress](#Account-related-commands) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | -|[GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | -|[GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | -|[GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) |[GetBrokerage](#Brokerage) | -|[GetContract](#How-to-use-smart-contracts) |[GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | -|[GetNextMaintenanceTime](#Some-others) |[GetProposal](#Get-proposal-information) |[GetReward](#Brokerage) | -|[GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[GetTransactionById](#How-to-get-transaction-information) |[GetTransactionCountByBlockNum](#How-to-get-transaction-information) | -|[GetTransactionInfoByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoById](#How-to-get-transaction-information) |[GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli) | -|[ImportWallet](#Wallet-related-commands) |[ImportWalletByBase64](#Wallet-related-commands) |[ListAssetIssue](#Get-Token10) | -|[ListExchanges](#How-to-trade-on-the-exchange) |[ListExchangesPaginated](#How-to-trade-on-the-exchange) |[ListNodes](#Some-others) | -|[ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | -|[Login](#Command-line-operation-flow-example) |[ParticipateAssetIssue](#How-to-issue-TRC10-tokens) |[RegisterWallet](#Wallet-related-commands) | -|[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | -|[UnfreezeAsset](#How-to-issue-TRC10-tokens) |[UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#How-to-issue-TRC10-tokens) | -|[UpdateBrokerage](#Brokerage) |[UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | -|[UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | +| [BackupShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | [BackupWallet](#Wallet-related-commands)| [BackupWallet2Base64](#Wallet-related-commands) | +| [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) +| [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | +| [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | +| [FreezeBalance](#How-to-delegate-resourcee) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | +| [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | +| [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | +| [GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | +| [GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) | [GetBrokerage](#Brokerage) | +| [GetContract](#How-to-use-smart-contracts) | [GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | +| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token)| +| [GetNextMaintenanceTime](#Some-others) | [GetNkFromNsk](#How-to-transfer-shielded-TRC20-token) | [GetProposal](#Get-proposal-information) | +| [GetShieldedPaymentAddress](#How-to-transfer-shielded-TRC20-token)| [GetSpendingKey](#How-to-transfer-shielded-TRC20-token) | [GetReward](#Brokerage) | +| [GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) |[GetTransactionById](#How-to-get-transaction-information) | [GetTransactionCountByBlockNum](#How-to-get-transaction-information) | +| [GetTransactionInfoByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoById](#How-to-get-transaction-information) | [GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli) | +| [ImportShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | [ImportWallet](#Wallet-related-commands) | [ImportWalletByBase64](#Wallet-related-commands) | +| [ListAssetIssue](#Get-Token10) | [ListExchanges](#How-to-trade-on-the-exchange) | [ListExchangesPaginated](#How-to-trade-on-the-exchange) | +| [ListNodes](#Some-others) | [ListShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token) | [ListShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | +| [ListProposals](#How-to-initiate-a-proposal) | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | +| [LoadShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | [Login](#Command-line-operation-flow-example) | [ParticipateAssetIssue](#How-to-issue-TRC10-tokens) | +| [RegisterWallet](#Wallet-related-commands) | [ResetShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | [ScanShieldedTRC20NoteByIvk](#How-to-transfer-shielded-TRC20-token) | +| [ScanShieldedTRC20NoteByOvk](#How-to-transfer-shielded-TRC20-token) |[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [SendShieldedTRC20Coin](#How-to-transfer-shielded-TRC20-token) | +| [SendShieldedTRC20CoinWithoutAsk](#How-to-transfer-shielded-TRC20-token) | [SetShieldedTRC20ContractAddress](#How-to-transfer-shielded-TRC20-token) | [ShowShieldedTRC20AddressInfo](#How-to-transfer-shielded-TRC20-token) | +| [TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) |[UnfreezeAsset](#How-to-issue-TRC10-tokens) | +| [UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#How-to-issue-TRC10-tokens) | [UpdateBrokerage](#Brokerage) | +| [UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | +| [VoteWitness](#How-to-vote) | Type any one of the listed commands, to display how-to tips. @@ -2087,6 +2094,7 @@ For more information on a specific command, just type the command on terminal wh GetAccountNet GetAccountResource GetAddress + GetAkFromAsk GetAssetIssueByAccount GetAssetIssueById GetAssetIssueByName @@ -2105,9 +2113,10 @@ For more information on a specific command, just type the command on terminal wh GetExpandedSpendingKey GetIncomingViewingKey GetNextMaintenanceTime + GetNkFromNsk + GetProposal GetShieldedPaymentAddress GetSpendingKey - GetProposal GetTotalTransaction GetTransactionApprovedList GetTransactionById From 21ba6c38e04e1bc1bc039b4bc6638a54e95dd138 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 19 Jun 2020 15:27:07 +0800 Subject: [PATCH 325/445] add an example of BURN --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bca75816d..b829b05a9 100644 --- a/README.md +++ b/README.md @@ -1685,8 +1685,10 @@ Shielded transfer, support three types: sum of shielded input amount should be equal to the sum of shielded output amount. When you TRANSFER, you need to enter password of public account as prompted, and this is used to trigger 'transfer' method of shielded contract that executes TRANSFER. -- BURN: transfer from one shielded address to one public address, toAmount should be equal to the - shielded input amount.When you BURN, you need to enter password of public account as prompted, and +- BURN: transfer from one shielded address to one public address and one optional shielded + address. If there is no shielded output, toAmount should be equal to the shielded input amount + , otherwise, the sum of toAmount and shielded output amount should be equal to the shielded + input amount. When you BURN, you need to enter password of public account as prompted, and this is used to trigger 'burn' method of shielded contract that executes BURN. It's better to use different accounts to trigger BURN, TRANSFER and MINT. @@ -1793,7 +1795,7 @@ In this example, the scalingFactor is 1000. 3. BURN - **In this mode, some variables must be set as follows, fromAmount = 0, shieldedInputNum = 1, shieldedOutputNum = 0.** + **In this mode, some variables must be set as follows, fromAmount = 0, shieldedInputNum = 1.** ```console > ListShieldedTRC20Note This command will show all the unspent notes. @@ -1805,6 +1807,7 @@ In this example, the scalingFactor is 1000. No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1000 > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 1300000000000000 0 + > SendShieldedTRC20Coin 0 1 14 TDVr15jvAx6maR28tP7RRpxuKZ38tgsyNE 300000000000000 1 ztron1mm20lkcpj6tx6jfd6ek5fxkgmpk9f2hda6vxdtkwlzr45ez32wa7dt8uka9xwfqamr7zyk7jpzf 1000000000000000 null ``` ### SendShieldedTRC20CoinWithoutAsk From f9f79601715a699d995bed94653a1176590696c5 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Fri, 19 Jun 2020 16:59:54 +0800 Subject: [PATCH 326/445] typo --- src/main/java/org/tron/walletcli/Client.java | 14 ++++++---- .../org/tron/walletcli/WalletApiWrapper.java | 28 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 4f8a45135..a7141f7c5 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2438,7 +2438,7 @@ private boolean sendShieldedCoinNormal(String[] parameters, boolean withAsk) shieldedInputAddress = noteInfo.getPaymentAddress(); } else { if (!noteInfo.getPaymentAddress().equals(shieldedInputAddress)) { - System.err.println("All the input notes should be the same address!"); + System.out.println("All the input notes should be the same address!"); return false; } } @@ -3058,7 +3058,7 @@ private void listShieldedTRC20Note(String[] parameters) { if (showType == 0) { List utxoList = ShieldedTRC20Wrapper.getInstance().getvalidateSortUtxoList(); - if (utxoList.size() == 0) { + if (utxoList.isEmpty()) { System.out.println("The count of unspent note is 0."); } else { System.out.println("The unspent note list is shown below:"); @@ -3145,7 +3145,8 @@ private void scanShieldedTRC20NoteByIvk(String[] parameters) { System.out.println("ScanShieldedTRC20NoteByIvk failed! Invalid shieldedTRC20ContractAddress"); return; } - long startNum, endNum; + long startNum; + long endNum; try { startNum = Long.parseLong(parameters[4]); endNum = Long.parseLong(parameters[5]); @@ -3179,7 +3180,8 @@ private void scanShieldedTRC20NoteByOvk(String[] parameters) { System.out.println("ScanShieldedTRC20NoteByOvk failed! Invalid shieldedTRC20ContractAddress"); return; } - long startNum, endNum; + long startNum; + long endNum; try { startNum = Long.parseLong(parameters[2]); endNum = Long.parseLong(parameters[3]); @@ -3307,7 +3309,7 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk shieldedInputAddress = noteInfo.getPaymentAddress(); } else { if (!noteInfo.getPaymentAddress().equals(shieldedInputAddress)) { - System.err.println("All the input notes should be the same address!"); + System.out.println("All the input notes should be the same address!"); return false; } } @@ -3392,7 +3394,7 @@ private boolean sendShieldedTRC20CoinNormal(String[] parameters, boolean withAsk int shieldedContractType = -1; if (fromPublicAmount.compareTo(BigInteger.ZERO) > 0 && shieldedOutList.size() == 1 - && shieldedInputList.size() == 0 && toPublicAmount.compareTo(BigInteger.ZERO) == 0) { + && shieldedInputList.isEmpty() && toPublicAmount.compareTo(BigInteger.ZERO) == 0) { System.out.println("This is MINT."); shieldedContractType = 0; } else if (fromPublicAmount.compareTo(BigInteger.ZERO) == 0 diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 595b707f7..ffb97aa02 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1260,7 +1260,7 @@ public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, return false; } } - // System.out.println(Utils.formatMessageString(notes.get())); + System.out.println("["); for(DecryptNotesTRC20.NoteTx noteTx : notes.get().getNoteTxsList()) { System.out.println("\t{"); @@ -1315,7 +1315,7 @@ public boolean scanShieldedTRC20NoteByOvk(final String ovk, long start, long end return false; } } - // System.out.println(Utils.formatMessageString(notes.get())); + System.out.println("["); for(DecryptNotesTRC20.NoteTx noteTx : notes.get().getNoteTxsList()) { System.out.println("\t{"); @@ -1383,6 +1383,10 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm PrivateShieldedTRC20Parameters.Builder builder = PrivateShieldedTRC20Parameters.newBuilder(); builder.setFromAmount(fromAmount.toString()); byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); + if (shieldedContractAddressBytes == null) { + System.out.println("Invalid shieldedContractAddress."); + return false; + } builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedContractAddressBytes)); if (!StringUtil.isNullOrEmpty(toAddress)) { @@ -1395,7 +1399,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm } long valueBalance = 0; - if (shieldedInputList.size() > 0) { + if (!shieldedInputList.isEmpty()) { List rootAndPath = new ArrayList<>(); for (int i = 0; i < shieldedInputList.size(); i++) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() @@ -1458,12 +1462,11 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm builder.addShieldedSpends(spendTRC20NoteBuilder.build()); } } else { - //@TODO remove randomOvk by sha256.of(privateKey) byte[] ovk = getRandomOvk(); if (ovk != null) { builder.setOvk(ByteString.copyFrom(ovk)); } else { - System.out.println("Get random ovk from Rpc failure,please check config"); + System.out.println("Get random ovk from Rpc failure, please check config"); return false; } } @@ -1493,7 +1496,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm return false; } - if (shieldedContractType == 0) {//MINT + if (shieldedContractType == 0) { //MINT boolean setAllowanceResult = setAllowance(contractAddress, shieldedContractAddress, fromAmount); if (!setAllowanceResult) { @@ -1517,7 +1520,7 @@ public boolean sendShieldedTRC20Coin(int shieldedContractType, BigInteger fromAm System.out.println("Trigger shielded contract TRANSFER failed!"); return false; } - } else if (shieldedContractType == 2) {//BURN + } else if (shieldedContractType == 2) { //BURN boolean burnResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); if (burnResult) { System.out.println("BURN succeed!"); @@ -1565,6 +1568,10 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte PrivateShieldedTRC20ParametersWithoutAsk.newBuilder(); builder.setFromAmount(fromAmount.toString()); byte[] shieldedContractAddressBytes = WalletApi.decodeFromBase58Check(shieldedContractAddress); + if (shieldedContractAddressBytes == null) { + System.out.println("Invalid shieldedContractAddress."); + return false; + } builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedContractAddressBytes)); if (!StringUtil.isNullOrEmpty(toAddress)) { @@ -1578,7 +1585,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte byte[] ask = new byte[32]; long valueBalance = 0; - if (shieldedInputList.size() > 0) { + if (!shieldedInputList.isEmpty()) { List rootAndPath = new ArrayList<>(); for (int i = 0; i < shieldedInputList.size(); i++) { ShieldedTRC20NoteInfo noteInfo = ShieldedTRC20Wrapper.getInstance().getUtxoMapNote() @@ -1643,7 +1650,6 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte valueBalance = Math.addExact(valueBalance, noteInfo.getValue()); } } else { - //@TODO remove randomOvk by sha256.of(privateKey) byte[] ovk = getRandomOvk(); if (ovk != null) { builder.setOvk(ByteString.copyFrom(ovk)); @@ -1682,7 +1688,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte "please check input data!"); return false; } - if (shieldedContractType == 0) {//MINT + if (shieldedContractType == 0) { //MINT boolean setAllowanceResult = setAllowance(contractAddress, shieldedContractAddress, fromAmount); if (!setAllowanceResult) { @@ -1706,7 +1712,7 @@ public boolean sendShieldedTRC20CoinWithoutAsk(int shieldedContractType, BigInte System.out.println("Trigger shielded contract TRANSFER failed!"); return false; } - } else if (shieldedContractType == 2) {//BURN + } else if (shieldedContractType == 2) { //BURN boolean burnResult = triggerShieldedContract(shieldedContractAddress, inputData, 2); if (burnResult) { System.out.println("BURN succeed!"); From f9c2548dd0cf1de242efbc16c025d335fe57c6cc Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Mon, 22 Jun 2020 12:52:47 +0800 Subject: [PATCH 327/445] add a demo for shielded trc20 transfer --- README.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b829b05a9..eab04ce8c 100644 --- a/README.md +++ b/README.md @@ -1881,8 +1881,10 @@ endNum > The end block number of the scan event1/event2 -> The events you want to scan. These events must be compatible with standard events, that is, NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21 ->]) and TokenBurn(address,uint256,bytes32[3]). If you ignore this field, the command will scan the standard events. +> The events you want to scan. These events must be compatible with standard events, that is +>,MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), TransferNewLeaf(uint256,bytes32 +>,bytes32,bytes32,bytes32[21]) and BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]). If +> you ignore this field, the command will scan the standard events. >In most cases, you can ignore these parameters. Example: @@ -1903,7 +1905,22 @@ Example: } ] -> ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 5400 NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) TokenBurn(address,uint256,bytes32[3]) +> ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 6000 MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) + [ + { + note: { + value: 100000 + payment_address: ztron1z8d5htmt6h26l5agk4ywv86xv3shuv4gjc2rzufyz4s2g5x0035nwrcqmxj4a49n2dy5sq28s5p + rcm: 07604b4a8018d353c08f93044df0fc04ef988c2f65f9222eacc8d41f0e095404 + memo: mint + } + position: 16 + is_spent: false + tx_id: 38d759216f62503c2b8bf7fc9777e6e25f5f77ec22dd760cc03057c4704277a2 + } + ] + +> ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 5400 BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) [ { note: { @@ -1937,8 +1954,8 @@ endNum > The end block number of the scan event1/event2 -> The event you want to scan. These events must be compatible with standard events, that is, NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) and TokenBurn(address,uint256,bytes32[3 ->]). If you ignore this field, the command will scan the standard events. +> The event you want to scan. These events must be compatible with standard events, that is, MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), TransferNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) and TokenBurn(address,uint256,bytes32[3]). +If you ignore this field, the command will scan the standard events. Example: @@ -1970,7 +1987,7 @@ Example: } ] -> ScanShieldedTRC20NoteByOvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu 4b33fc947a53a5e2a1d1636b323f7f6cecff8c34c9fc511ccc7cfaf0dd6f4c03 5000 6000 NewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) TokenBurn(address,uint256,bytes32[3]) +> ScanShieldedTRC20NoteByOvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu 4b33fc947a53a5e2a1d1636b323f7f6cecff8c34c9fc511ccc7cfaf0dd6f4c03 5000 6000 BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) TokenBurn(address,uint256,bytes32[3]) [ { note: { From de07bf92d3457dd9d4dc5e962087116b776dc967 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Mon, 22 Jun 2020 12:56:21 +0800 Subject: [PATCH 328/445] fix typos --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index eab04ce8c..c46be0a65 100644 --- a/README.md +++ b/README.md @@ -1881,11 +1881,7 @@ endNum > The end block number of the scan event1/event2 -> The events you want to scan. These events must be compatible with standard events, that is ->,MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), TransferNewLeaf(uint256,bytes32 ->,bytes32,bytes32,bytes32[21]) and BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]). If -> you ignore this field, the command will scan the standard events. ->In most cases, you can ignore these parameters. +> The events you want to scan. These events must be compatible with standard events, that is, MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), TransferNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) and BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]). If you ignore this field, the command will scan the standard events. In most cases, you can ignore these parameters. Example: From 14ba86f6d1a8422e0fa9d742b0420ad9c96146be Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 2 Jul 2020 16:26:12 +0800 Subject: [PATCH 329/445] feat: update jline version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6d00c8c17..0b7f9b13a 100644 --- a/build.gradle +++ b/build.gradle @@ -88,7 +88,7 @@ dependencies { compile group: 'commons-io', name: 'commons-io', version: '2.6' compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - compile group: 'org.jline', name: 'jline', version: '3.1.3' + compile group: 'org.jline', name: 'jline', version: '3.15.0' } protobuf { From 5d3c2620e5de471303f47bbf232df9d549e93721 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Sun, 5 Jul 2020 22:00:46 +0800 Subject: [PATCH 330/445] remove unused documents --- doc/en/nile_shielded_usage.md | 278 --------------------------- doc/images/nile_config_conf.png | Bin 114418 -> 0 bytes doc/images/nile_public_fullnode.png | Bin 25344 -> 0 bytes doc/images/nile_shielded_usage1.png | Bin 102368 -> 0 bytes doc/images/nile_shielded_usage10.png | Bin 63301 -> 0 bytes doc/images/nile_shielded_usage11.png | Bin 64296 -> 0 bytes doc/images/nile_shielded_usage12.png | Bin 31032 -> 0 bytes doc/images/nile_shielded_usage13.png | Bin 92096 -> 0 bytes doc/images/nile_shielded_usage14.png | Bin 66810 -> 0 bytes doc/images/nile_shielded_usage15.png | Bin 38182 -> 0 bytes doc/images/nile_shielded_usage16.png | Bin 93056 -> 0 bytes doc/images/nile_shielded_usage2.png | Bin 275417 -> 0 bytes doc/images/nile_shielded_usage3.png | Bin 60145 -> 0 bytes doc/images/nile_shielded_usage4.png | Bin 136095 -> 0 bytes doc/images/nile_shielded_usage5.png | Bin 121678 -> 0 bytes doc/images/nile_shielded_usage6.png | Bin 114502 -> 0 bytes doc/images/nile_shielded_usage7.png | Bin 62802 -> 0 bytes doc/images/nile_shielded_usage8.png | Bin 42535 -> 0 bytes doc/images/nile_shielded_usage9.png | Bin 30744 -> 0 bytes doc/zh/nile_shielded_usage.md | 271 -------------------------- 20 files changed, 549 deletions(-) delete mode 100644 doc/en/nile_shielded_usage.md delete mode 100644 doc/images/nile_config_conf.png delete mode 100644 doc/images/nile_public_fullnode.png delete mode 100644 doc/images/nile_shielded_usage1.png delete mode 100644 doc/images/nile_shielded_usage10.png delete mode 100644 doc/images/nile_shielded_usage11.png delete mode 100644 doc/images/nile_shielded_usage12.png delete mode 100644 doc/images/nile_shielded_usage13.png delete mode 100644 doc/images/nile_shielded_usage14.png delete mode 100644 doc/images/nile_shielded_usage15.png delete mode 100644 doc/images/nile_shielded_usage16.png delete mode 100644 doc/images/nile_shielded_usage2.png delete mode 100644 doc/images/nile_shielded_usage3.png delete mode 100644 doc/images/nile_shielded_usage4.png delete mode 100644 doc/images/nile_shielded_usage5.png delete mode 100644 doc/images/nile_shielded_usage6.png delete mode 100644 doc/images/nile_shielded_usage7.png delete mode 100644 doc/images/nile_shielded_usage8.png delete mode 100644 doc/images/nile_shielded_usage9.png delete mode 100644 doc/zh/nile_shielded_usage.md diff --git a/doc/en/nile_shielded_usage.md b/doc/en/nile_shielded_usage.md deleted file mode 100644 index ee4c5d72a..000000000 --- a/doc/en/nile_shielded_usage.md +++ /dev/null @@ -1,278 +0,0 @@ -# Abstract -This document offers an explanation for wallet-cli users and TRON developers on how to use shielded transaction feature with wallet-cli on Nile Testnet. - -## 1 Shielded transaction on Nile Testnet -Currently, a TRC-10 token named TRONZ (TRZ, Token id: 1000016) supports shielded transaction on Nile Testnet. - -## 2 Use wallet-cli -### 2.1 Build wallet-cli -Wallet-cli project code is hosted at: `https://github.com/tronprotocol/wallet-cli` -Wallet-cli is a command-line-based wallet application. We will quickly go through the building of a wallet-cli. Feel free to skip it if you are familiar with the process. Simply put, there are a few steps you need to go through before you can use the wallet-cli software: - -1) **Download the source code of the project** -```test -git clone https://github.com/tronprotocol/wallet-cli.git -``` - -2) **Change configuration files** - -You can find publicly available [nodes](http://nileex.io/status/getStatusPage) on [Nile Testnet](http://nileex.io). -```text -Public Fullnode List -47.252.19.181 -47.252.3.238 -``` -And you can make changes to ip.list in the fullnode in this configuration file src/main/resources/config.conf. - -```text -net { - type = mainnet -} - -fullnode = { - ip.list = [ - "47.252.19.181:50051" - ] -} - -#soliditynode = { -# ip.list = [ -# "127.0.0.1:50052" -# ] -#} - -RPC_version = 2 -``` -3) **Build the source code of the project** -```test -$ cd wallet-cli -$ ./gradlew build -``` - -4) **Use the built wallet-cli.jar** -```test -$ cd build/libs -$ java -jar wallet-cli.jar -``` - -Please refer to [wallet-cli get started](https://github.com/tronprotocol/wallet-cli#get-started) if you encounter any problems during the process of building wallet-cli software. Your wallet-cli software is running properly if you see the output below. -```text -Welcome to Tron Wallet-Cli -Please type one of the following commands to proceed. -Login, RegisterWallet or ImportWallet - -You may also use the Help command at anytime to display a full list of commands. - -wallet> -``` - -*All of the following command demos are run in wallet-cli* -### 2.2 Shielded wallet and shielded address -You may create multiple shielded addresses in one shielded wallet. Shielded addresses can be exported from or imported to a shielded wallet. Here are some related commands. -#### 2.2.1 Create a shielded address -> Execute the command `GenerateShieldedAddress` to generate a shielded address. If there is no shielded wallet locally, this command will create a shielded wallet first and then a shielded address. -```text -wallet> GenerateShieldedAddress -ShieldedAddress list: -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -``` - -Now a shielded address is generated: -`ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx` -**Please note: the command `GenerateShieldedAddress n` also supports generating n shielded addresses. The default value for n is 1. If you want to generate 5 shielded addresses at one time** -```test -GenerateShieldedAddress 5 -``` -Now you can check out the shielded addresses you have created. -#### 2.2.2 View the shielded address -> Execute the command `ListShieldedAddress` to view the shielded addresses created in the shielded wallet. -```test -wallet> ListShieldedAddress -ShieldedAddress : -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -``` - -If you re-run wallet-cli, you can log into the local shielded wallet you have created using the command below. -#### 2.2.3 Log into the shielded wallet -> Execute the command `LoadShieldedWallet` to log into the local shielded wallet. -```test -wallet> LoadShieldedWallet -Please input your password for shielded wallet. -password: -user defined config file doesn't exists, use default config file in jar -LoadShieldedWallet successful !!! -``` - -Surely you may need to back up the local shielded address to other shielded wallets. You can do so by executing the two commands below: -#### 2.2.4 Export the shielded address -> Execute the command `BackupShieldedWallet` in the local wallet to export the shielded address: - -```test -wallet> BackupShieldedWallet -Please input your password for shielded wallet. -password: -The 1th shielded address is ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea -d :8e52fc48c2a47509e7eb78 -BackupShieldedWallet successful !!! -``` - -#### 2.2.5 Import the shielded wallet address -> Execute the command `ImportShieldedWallet` in other shielded wallets to import the shielded address: -```test -wallet> ImportShieldedWallet -Please input your password for shielded wallet. -password: -Please input shielded wallet hex string. such as 'sk d',Max retry time:3 -00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea 8e52fc48c2a47509e7eb78 -Import shielded wallet hex string is : -sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea -d :8e52fc48c2a47509e7eb78 -Import new shielded wallet address is: ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -ImportShieldedWallet successful !!! -``` - -**Warning: The string (`sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea d :8e52fc48c2a47509e7eb78`) used for exporting and importing the shielded address is confidential. Please do not disclose the information to anyone else.** - -After the shielded wallet is all set up, you can maket a shielded transaction. Before proceeding to the next step, let's first acquire some TRZ via a wallet that is not shielded. The first step is to create a wallet that is not shielded which contains an public address. Here, we use a registered wallet with the public address `TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq` and request some TRZ on the [page](http://nileex.io/join/getJoinPage) for further tests. -![](./../images/nile_shielded_usage7.png) - -*After making these preparations, here are some steps to start a shielded transaction based on the shielded wallet address in wallet-cli* - -### 2.3 Shielded transaction - -#### 2.3.1 Overview -There are three types of transfer that involve shielded addresses. They are: - -| sender address | ---> | receiving address | -| ---------|---------|-------- | -| `public address` | ---> | `shielded address` | -| `shielded address` | ---> | `shielded address` | -| `shielded address` | ---> | `public address` | - -The above three types of shielded transactions can all be executed using the command `SendShieldedCoin`. **Each type of transfer costs a flat fee of 10 TRZ**. Please keep this number in mind as it affects the settings of command parameters. In addition, the TRZ amount in the command `SendShieldedCoin` is always `1,000,000` times the actual input, meaning you need to set the parameter as `1000000` to put `1 TRZ` in the command . - -#### 2.3.2 Use SendShieldedCoin command -Here are the full description and explanation of relevant parameters of the command `SendShieldedCoin`: -```test -SendShieldedCoin [publicFromAddress] fromAmount shieldedInputNum input1 publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 ... -``` -`publicFromAddress` The public sender address. The parameter is used when transferring from a public address to a shielded address. Otherwise, please set to `null`. Optional, If this variable is not configured, it is the address of the current login account. -`fromAmount` Amount of transfer from the public sender address. If `publicFromAddress` is set to `null`, this parameter needs to be set to `0`. -`shieldedInputNum` Number of shielded notes to be sent. This can be set to `0` or `1`. -`input1` Local serial numbers of shielded notes. The number of notes should equal the value of `shieldedInputNum`. No need to set these parameters if `shieldedInputNum` is `0`. -`publicToAddress` Public address to receive the transfer. This parameter is used when transferring from a shielded address to a public address. -`toAmount` The amount sent to the public address. -`shieldedOutputNum` Number of shielded notes that receive the transfer. This can be set to 0, 1 or 2. -`shieldedAddress1` Shielded address to receive the transfer. -`amount1` The amount sent to the shielded address `shieldedAddress1`. -`memo1` Remarks of note (512 bytes at most). This parameter can be set to `null` when not in use. - -**Note: A legitimate `SendShieldedCoin` command needs to make sure the TRZ transferred from a sender address equals the sum of TRZ received by all receiving addresses plus the fees paid. We'll touch upon it in the following examples.** - -*Below are examples of the three types of transactions:* -##### 2.3.2.1 Transfer from a public address to a shielded address - -```test -SendShieldedCoin TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq 210000000 0 null 0 2 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 first ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 second -``` - -Please note that additional signature is needed when transferring from a public address. You will see the result below if the transfer is successful. - -```test -wallet> SendShieldedCoin TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq 210000000 0 null 0 2 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 first ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 second - -... ... -Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancell transaction. -y -Please choose your key for sign. -The 1th keystore file name is UTC--2019-12-31T17-01-51.940000000Z--TEKqBNRDPRW7MGS4SHvNwHAcgkhH6jzH87.json -The 2th keystore file name is UTC--2019-12-31T09-22-43.363000000Z--TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq.json - -Please choose between 1 and 2 -2 -Please input your password. -password: - -... ... - -txid is ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b -SendShieldedCoin successful !!! -``` - -*Command Interpretation:* -Transfer 210 TRZ from the public address `TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq. Out of the total 210 TRZ, send 120TRZ to the shielded address `ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym` with the note "first" -and send 80 TRZ to the shielded address `ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx` with the note "second". -The transfer fee is 10TRZ. -> *It is verified that 210 TRZ = 120 TRZ + 80 TRZ + 10 TRZ* - -2 notes will be added to the local wallet after the command is successfully executed. Run the command `listshieldednote 0` to obtain all notes in Unspend state within the local wallet. Results are followed: - -```test -wallet> listshieldednote 0 -Unspent note list like: -0 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 UnSpend first -1 ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 1 UnSpend second -``` - -**Reminder: All notes in Unspend state are designated with a specific number - the one each note starts with. This number is critical when transferring from a shielded address, which we will address in details later.** - -When transferring from a shielded address to other addresses, only notes marked UnSpend can be sent via shielded transaction. As one shielded address may contain multiple notes, the note number must be entered to identity the note to be sent. - -##### 2.3.2.2 Transfer between shielded addresses -We will only talk about transfer between two shielded addresses in the local wallet for the sake of simplicity. - -```test -wallet> SendShieldedCoin null 0 1 1 null 0 1 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 third -``` - -*Command Interpretation:* -80 TRZ are sent from the No.1 note of the shielded address `ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx`. 70 TRZ are received by the shielded address `ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym`. -The transfer fee is 10 TRZ. -> *It is verified that 80 TRZ = 70 TRZ + 10 TRZ* - -After executing the command, you can check the notes in your local wallet where you'll find that the note with 80 TRZ is now in Spent state and there is a new UnSpend note with 70 TRZ. - -```test -wallet> listshieldednote 1 -All notes list like: -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 UnSpent first -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 f95185394430b49a865488a7ffc8c7e4306eecd6fcd94d9bdc3018a810879a49 0 UnSpent third -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 1 Spent second -``` - -##### 2.3.2.3 Transfer from a shielded address to a public address -Execute the command `listshieldednote 0` to get the note associated with the local shielded address. - -```test -wallet> listshieldednote 0 -Unspent note list like: -0 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 UnSpend first -2 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 f95185394430b49a865488a7ffc8c7e4306eecd6fcd94d9bdc3018a810879a49 0 UnSpend third -``` - -Then execute the command below: - -```test -sendshieldedcoin null 0 1 0 TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq 110000000 0 -``` - -*Command Interpretation:* -120 TRZ are sent from the No.0 note of the shielded address `ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym`. 110 TRZ are received by the public address `TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq` -Transfer fee is 10 TRZ. -> *It is verified that 120 TRZ = 110 TRZ + 10 TRZ。* - -After the command is executed, execute the command `listshieldednote 1` and you'll see the note with 120 TRZ has changed from UnSpend to Spent state. - - -```test -wallet> listshieldednote 1 -All notes list like: -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 f95185394430b49a865488a7ffc8c7e4306eecd6fcd94d9bdc3018a810879a49 0 UnSpent third -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 1 Spent second -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 Spent first -``` - -## Support -This document is dedicated to wallet-cli software with a focus on the basic commands to make shielded transactions through wallet-cli. If you have any problems or find any mistakes, please join our discussion [Gitter](https://gitter.im/tronprotocol/wallet-cli). - diff --git a/doc/images/nile_config_conf.png b/doc/images/nile_config_conf.png deleted file mode 100644 index 566ea64ba19bebf44ccbff02df6d12314b3b7505..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114418 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

Rg%6?djmKBi2RinGm5^5!k5C>$NcRrW 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%0D3s%Q$$mS`E(w8sij5jymhozg}E=z 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

4ZEcO*ESF| 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%0Dm3qaSDV`NN!Ha3l)kS^f`yW2e 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 diff --git a/doc/images/nile_public_fullnode.png b/doc/images/nile_public_fullnode.png deleted file mode 100644 index 31882fd7b995b44adab9233892a41a02ea5dcc83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25344 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 diff --git a/doc/images/nile_shielded_usage1.png b/doc/images/nile_shielded_usage1.png deleted file mode 100644 index 7516829cc254163f7152f1d45b76b7e3fb3c7646..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102368 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

YacKnJiRAQFZawsv+HkWzN<9%&2h;vo3?j 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 diff --git a/doc/images/nile_shielded_usage10.png b/doc/images/nile_shielded_usage10.png deleted file mode 100644 index 98dfff3c4da330a8412adc7c6e38ede1d750c628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63301 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 diff --git a/doc/images/nile_shielded_usage11.png b/doc/images/nile_shielded_usage11.png deleted file mode 100644 index ce4fe76a3f76a163329148841e543242a5bfd22c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64296 zcmZ_0V{j&0`#n69WMVsaY}?iyYhv5BH4|rI+qSKVZQFKo$NHb=!~5lT&ReS*ySjGO z?!CLOwXUmIb@)$t2?SUG?6+^<5Tqo5O5eVL8~k&w3=R3u{w2|b=byuOXC(=dZ`D(H zr~foy93(ZJzkS0({onT8JZJ8oE<)d=fWj&s-!FWjJTP05AJ3zsEQ_DEc!Tsr5n_d) zW4{r@NI{beL3ah7(u3l`^3c3~A$uvDbh$H48h{tKbADXMnOz|DZFad|U-dj)v4Z&* z#6?eWdr1_`rWM3(po4$X2bR+NUw!qjQO5UVJUj`~jTv4O?feTj{($c-{`%k-Um#aU zn&W?6-yyp3NB;d~`84|_Jd#Pn4bAl*cwa_-4uxl0Uj2jG@}(ozG1prB)!?Y!&GR`n zAeBb(JPoz{N;a5q5EfaOzzO>n5p7>cf(6&n6PJ~EF~&%J>(6!fboMA%8PdNc{P+K0 z=Zg&NEB5=*pI;dnxChq{Hz9?~QlF>J^y(3qfmCsj2*_AyPdTd+{v{jBXNhD`VKE9q zU|*lvHxxD(PG5@Ia-Se`F&2fHj{s74FqL^9S#HWHn+W?P9c=sY z8MCMIV=|t8MUuH+)P#&;z-S;=c%cr%Ppgq3JlaG_+t=^?hQUYByzZ##VPP&fE)Ut4 zjL+8tXnt!wus~IVWT6Oh-B@Oif1l0F`;kQ5PcXynHagl&4JAxa7pid+K z3mfDB=TJch?tG*AfM|r*)8^Amv!QP0nr|NqH0sYLGX$5HMtb>6{^(Kn){PO# zPRZA4geOKFp)bSz)L|WZ(G{-rdKs7>&J!r8Q+S7Q}qOSYEekt>Dw~C5?x?=*AUEScSYeXi?BTC70g`*?5LC z(r(dV>wdj%`DAB1;`?f~u{V3S{Fz+V zAa`f1NMJitel*RL8aNFx>>CmxF+(wAyqr^3jfYV+1`f-n4xu}c4^g~ks~=^YCBFyv z7JakukwnUbszpQB{<{_zwpVj+^-{UNy~K zELPDwn(YQUe~g#4^r`FnM_FNv@80AUwEDggAYQD8E_4e}=@!H98D|^rah-W&@hnxv z)RY<5oBau!AIHLp&a!SXp6JYp-JC{X?C-x_AF6t8zBeC2UR>_4j&yeUSNw56ey${G33+<93BrXUHD7(@!>j+TDlten$>`jTRqw(>K^i zou$$v;@s%$bokuKS#dp~Yj?X0$SLuQ!Odg5b^B;<^}Aw|&uYtia8SO*o-N@e81y1? zIB=*;ed2d}_f$F_oHGb-a6l>IsL~7gi^6&{N~xb~dLMFg=QJGGwa(=DY6QY32%FGj zMCNkix%#mQe4FkTc^PY(-9KlHqfmwALqHVIVbFCv?3MAkJsnQvwZx1j5d4$f6`Fsk zu3w-#e)L?dt&Yd}RSKiDtfUh6d|EuW+lQ^Kr>&7KQIAm%;y7dPba{>W(higlG_j)JJV!nvdC?}YdgF)|t*K1VLjG>`A=YIdcXjETRNSnRw z?m$TA87`C06aRrb%i*J(eI%2Y)XhdS<}-mZ-|Rt}Dov2kXS1sjoikm_?yh%{_V+gI zsKutVpbrS!E;M-m?v%CFfgYMspHD0rjGW2u=38QgT5xvVD)v7QwUtU$w5zrMATNT| z-nxDJe!^px&&E{_)lKsVMH=Lm&47C3pH11NR$RXtQ zZpCAjV!5p4^DG(`Az?)PVY%U4YMjMJEx}#!?eHi2`p@^OIgc7WS2oKR;y+k4VYwyY zsceRHU3!O1_ru(&+?IS`e0z0{%f#~Lvbn-tKL|ivTW!t~=cUX|Mg*O9ep`PtPZN-1 zsiC@`Pw)o@oXYcmwCkeDUA_OO;7LKb0UD8j)te2@Q$F3$^+jzXy8=_1_4A!;^co$E z;;4xV%({)Q)yN8K%pe?rS68Qq>v;3xC|yQ+c6v9Dtl|kJDV+6(G?6ju&A~0^B;BXn zzaV3f`t-y}-e;Zp8(W|r8MDQRxwz27LG`BN*$C8~zLEWdEJg#lDEsoA+D7}HaqN0P3q>W^uEO?J2$Ehk@MOI$D1oeQV38zCDN^r3ukt%7bqx5Je=DLZ|m z`>D5^WIoTMFg}9ij4X$f;UJ^9Qt0Qxr^8Cg{qP8Kdq#j5>V{?H>uZVVKEurMW?lM<`jlG0O)m{pUDFp6%(?w6*WqZ*|_wPhmoYKkFVQz#iMU-_2@9#=RYXi)g zTO%(~@Ez`U#U(e~J%Y}nk0PGCX6j56ehj+oPe}~lB&U{`Fu$gCe&{zQz-DMxU*Y~IyZkc_G45)X* z#YK`i+~7(xxTU@(g;)wDzFmYoIk=6$S(81KxGK2Y<(`piaCN~J*Xj86i7}DN z0Uib56t8MIC!GfoGI=lApwlZMr!a2z=jMIH5xd(3{m2?l#9@sc8^=0jH66+%G|v@T z1ZXrmpxhmeRGWsy5k<&I)*{e{kCz9%A=wUh2CvR!&R@^Hy~Rb(Ln;sq1pJ7YE!M@g z(Qktup4I`jovFq$%ebw@4Ou5wco@o_aYEilnB+Z8ASL}d>Wy!M`+1thGm=cnS)JKk z5S0)X2F>~scUNF!WKjSppci03JYQxI1^5TQx?1hbFI^i>70!lz-pS~2Ie&LeZx+fb zFKS)MXbIx{pmWuek>qNrcePd@Zw#y?0Fk{ode=?GgN(_pIiUn{+nBlOo8xRWbHrEoBrfyHjU zaPXv}`Lo=_vGYMqvs1T5AU}s+v|EV{9`aH2QztE!*PX%RtwgAO;`G!L3>Jei=$FbT z+ny4MakEQU5hb1b?|_jcdMLn>FD_?b_5SI+d8u9l1uXgmj6Z{);%U`gtJ4GQT=8hY z9L89#W(4MQsirc&>d$1emO$HI;Sa>0j4wGHMBmE5l=`x^(oo5z{|?V5=w&6|$cPcf zb#O{2(*34Onc`bIvRtOm$OvL@(&fgU2eiu_L6VU`ZzdTg5 zT+L)85q!Koi8RxtaaS>8-Ju|9mN6?IRZX>%m$dS%_X_+6QFCCMu>^gvfIoHZ<29R284w_QHD+CM51M7EIsAonKi-IKN<@YWnl;#VRw|1nD?6!F5;%g zM%DI2iUwtsQSQuoG(|MOx+T|`Lnb-(225QpF|}8YblAzsY?lm=z1GRD5fh9$&vf?L zso<98^%t%1n2Kyy9m_awO(&pvwR%|#nSx^~!HkPPZ zGF^%?&fU1;T%t|w1Z%=n@EJY9P-+4i0F%wEQT8$oMLLBj-vZeJd|(rO$!Ab9b`iCS zE_q4QNIDfEgxfggRWi#Zz6edBaf{E2d0||DGI%LR)98kLj@80tPArx7G$4tt+5VX+ z+%zZ-TQa0%3d;y)0+pjI)ff#Lo&m^w6|Cr+A(XsIV`yP(&2P=M0*8ZJVc> z!;>P-kUH<02U3P>3o~lvPyavy#2hes`*i^#!fx)YY@D%%s#s)Krbf~3=gho%9OAz*(n|XWq{=Q9r92K-SKk&Od6t!RE z5gCpD69$J3Z*O#188^DQ4y4|&A2r6LC`;=&wfa8;3yz`!~J>pl2YM z^k{EgJRQz*BX0)jc8spqZsN-=V!|Ta4||%t)-+H9V|uwvKB*tqtR)cFM9GK!f0cMl zBzi2cK@vnp)R9xfFxBC7t(KB}H=Ruw_HVu@B#A#o9hi}INzoV9FSXqgF&U-~%@<3@ z?ee(W)Irt9(vx5KSK$AL;srstMEF z%-@xbTz)p(- zPqJ;Xk~Fum&_`0DEBO|Qs6VX)j++}=R}L?kv7W*{*rzgH}$`# z2@&i|9Qs5IMpXv_lTIg~0;wMa8zvER)t9Zy3v!oo0k>3$yi9qyuELE%u2)x_ZS}eY z7(LJseMLg-np4PzY_&!0)KW@)7Jg+`EF`>T=yv}7=+bibZ%AICP8=M78eE$Pe~^nI z&}cLJ*fuaVxxeRz(CrB^ubj^avd#e<(&hAgtJ3oMW$zu;cmfQ;z1r#uJet(r-7TQx za~-~?G{k1Hz$Pg$Zd82c4B{RA&l`Y*cJPIjx7io}>+JVTAWV$ZROkBsmvE)twd7V} zmSHx$MwNnDST_8N>Ybk=VMoARurvLkkN= z&Bwp9e;r|azO(~_ki&nzv@7gEG9;TBCc+KIWVT?T)9DnL6JGQ;mbn1g^O!|mA7L;n zg3|G>PYiUz3ApAc4HA5^`CCbdNy@N<&;|7ir)NVl^PpRBpBcWzuV?rj6 zz~l(Qe~IWKQ6}+KDg-|AamS5rBfZPYgr0<@q_P@gUIh#Emf*KL&F{PLeC(weWAhq9 z8tog$QTq!=;;#{=$m8vcPd_<&920uDvBJToPzCF3Y-rk0Xhta}@21rBD3!=XY^<^n zH>I>Zf=MGVX7fZ3j;sF_ROJ9&JXN8{Gp3ecac0Qx2A&_s?1cuO&)l!;dECB34#i{y zP%;|bYKGR#)LWv>br>#{qHy_rF(3H}Cg|UC|6B6?^XBWrd}rt*E{5`_Cj}-t?`Nxa z<2~oBH6ZxDM)h^r3QEM$QP+Bm)^K@(h?42Hf6by%8uqQkjbsyl!(zx3@98awEl|;H zv;%R3Kp_r9RUFS&95eZBbzyZ|tRwrrAr6rOm=d56`)(_cm@J!-J32%EChZaJ`K#zrBCQBIM+<}KI_f-RcXq`g zci;!UFvtiQ-rPOr4VPnx1laHG1ru4Be##NB!i)*xn@wd$)vWpVvCMMa{j(&`7YxyO zTcK6!&3lgp!)m%CVejw~lbA3tcUdWA1s?lON8_Qz*Nel^w(H3FrrqplcuF&pylvjN zYt5e!gX4+f?F)7%cdF_Oc1w``Z|;2_@`{}puaUBwaTKBelhMpUXa~Q4i?M$+O%d|V zLc=PmDI-0=FnO4AwqGkFkaf-L@ieeF>a3st0?S2DE!g?HNxQ{h`nRCtmnB*B6>m^#3Q}ur2W6jS1^WCBH`gm?}% z`cy0|&fMY9$*!maKO0^`iNf)e_7E=lV=QlqYl>#DS(=-Ca$%3%N~vP0_M7bnUn5jr zN`S-Fvs9#JJKoFPq40IL?~Lt%i7Wv-CTFyMh{DUC=k`Nyzzt4<1Stsz3h#-jH#X{@ z=VwXa@L=z@#2G{HA{z^}QM$CllkD&JA8^F_1OSd^?Z{I?p9Oeh3c3E}P49f~V|gmw zR;F$o^ss~y#$8;my|KfovhfJVLt#;AjuJQTO-9oTM!6Ikxo=~M3?VL-%8t~Cq?_aX zKSZv7TC=dLj1KGX{E;+$FhE#WFi{RDSrvb*a5E z+1waF2>ub;`sQykdRbuYVzd37#|tc$-WXE;a6C9VOP~a57(bBT7uo5BZO(LBq(Fj> z|LO~4C)DH05d#M+yd)-vBwe1+-iqv;@y{W7PouU)SUCGGOif~09I1rb(D$6i%yz6{ z%LHoWozs3SA>tj&5#ML8Fc_!-V5g9XWNY+zj9^sad$4pa8UlG;-p0w>ye~ev$P|r5xOI z(O%=2EceXk{o_db8q($~yb!F{DRBDa1bSeIoWzyp9wCiq>w3iSgF!v%hhB;U+=FDXM2>;&bz@6Uwj654-XE>KwrUAO=lLb71r~Jl}ksWy< z70>=;giPQiA<^F(CNwkbKeC-*#Z)p9!BeBl37yn#78#^8(kQV@6>w%CK>-*1QUN7~ z^Ph*mzcd)$r3%TW31DQjTrWoiU>nCTCD*oqKRva3J>U&VX4!Adm0dL=iV{K??matZ zisQ%{D-uVC+(H|zmwE~KtQ;wujAX(!ZPA4N6%MR5I;4TW6oe&?(L1w>e0|jJe3~r@ zW2RY;&nyW1^)LMG`36QH8j6IJSm)K0zL;l3g4|bhBKT5IYNb{t$tYtc&rKKE$i|>l zBo`GB3|pX*79WNXGtgfW^RrG`PG0yOf)ep!aP=JNXO$6-%hf8lYq@$5R8*We0`3lu zI#*;-ol=J|`2UJNFR)lb(V1tN_ZpLv`BYY3}Gq)0wCBT(LBCj#q%u4ktAvi0s+4O%p7-MMDg_uO5!h) zqA?=xWRS-lh+I0Q>=#Ke+-{ba>&*^96tCAlfXyz*Mw4}htDc^O`EjCnp`N41Cnkv^ zixI5;5SXpA+WJeJuij)lxUo!`#CZvCM0AtY2_;Hmfty5pH)2Xx@xE$KYg)Rgyq8}9 z((yLCRHuz)uK2-R_gO4sAvl}krCF}~^6o(Gc%O~O+)#o$+A(OZR zMk;0@&h!7r-YpR1w89Ri0^{Sxv?(K}HWZUKfQzBS#lfHRtdC@SsAjT&Kx<}dCTB_e zEE?0zft?u&A(lFi-;IhwG4f#iyab?c)Lt>MCsid7YjmbB2)F2?0ved2YX&e2p#&7g z4Q%@$3T83~3?Ndu_5tLzM} znlqrl8o4WIVrepeVSK9NynW`zD1Vm$_gibSd30B|Pkc5AW9-ROh50vRZYYc6q&jvR z<}rblSCix$Pxq+^PH?9FzrK=whx+|R4zZy^g1{7gbBIO-vqlWu4?jvtODaL2wpITd zl})9|_0xiXKo1%2*9(&96|tJ<{C;BaOL%6Gc`?G+mWojjPBsi1@SjSDRDxUtzvW6; zBA4_7uyas(9Z3skmmXj#%*G%&?y_!yvK3zw#XPAVRs=s!u5kW0aAbZc>_ILqQ>Hr> zx{HCdB)Qq>jd_*Mm$A)=0jDZ>Emc8DBT@p5EeczKtsa$HJ)qFCt#4a8p_Hh?TG%P= zd)kDQQE{adhFB2Vp@wU`laf}zr)igornj!ra4_#ZSC{fJu9DpKe;3WKNrk{8tI=?( z*8^SoPq0V_fF^_t_?7o$N%TAk<`x4}igxcD5s-_ttLc&~QF6p|rV4b%bT|cppSa~j zm@usLapK9({R2N)m;TJkK-(w}+$7fqS-Z<(fDNk+b!{r8=~gdw=C&BF%;DSa3Et+=6X7F{{gf5p4W-}<@8{yM>rdO zm*5}&y)LJcCo$AP>>2;`hL>4L2(-Zj@@!VaY^oy+dQN6gV;Q;3^tp;=xhOoMvE&NR ztWo1#8711`^v|&Bkg;M6{z48C`TJyeUlN#j90iz4)&w~s3{U9rsb%isrTjAVFrUvlG6Tx^SD% zkekKK!agaKgdCm6BqiDALZ;RHg+hvKGNog!7tEsK<;D=>yK7vuDjVMEn|L7aAaW=5 zW)cavVA|Gz9(Rgcfi*?w@;G!6t-|uX`uw(vQfb1N^_>ZX{wOOY!=04Pr#ANy&l*eo z^hJQv0@y?^g^QulA;r9=*rm&xMf_yi`&nv|<-r+b1KG38ZkvIm5#h+F4WB#X zvl6L+7mRFG)@kZx3DfbZET@x>8LCccJ^0Rt2G4E9k{bf%_}EG5sp#jW^Fcj@pU#%e zbp6{mjfdnV z@5#(Udy*TSFA8+mj?7Bd<2|P6)NSLb$;?t(_T8!zYuB@~C>HK6>P1lXcXwa;8e!s3 z$&xDrtv|?zA3QBibS#7Wf;dmDibjQHW>E0mlPtzEwoMpDnK7G0>JLn;c&`zL)wAi9 zIKpRf!x9ocoy&WSNFJDlqpRcN_ocWaJM*I$(i1ZVMuD>JN7o`HrlG%aQoY&Xb46=5JjzV8H&1Sxcl_FUu zHg2;NM?-5dT}$ki8>X^5>#sJa+WGi0mudmI@+bx?YfBgz3K{8G5f%zR=NY;eQXdz- zaM>vx<@kN4ZELG6kXIWSB;A$PkbKMOWkI2~BXP@T;Yo({x(-NjenrKpai1gUCP^Vl zw!IRe7viG;&9$C^awrvBu`(5pyA`WJ6dvx9cP~8cgd!y778J%zj?QzFi-03VEI29J z$=RoU7X%kP9)LwLdoy!qB25ezWAa)Lued-l1L^Bx2VZe+)GmS2$8ne zUYRB$RV|xfV*7kMpd+Nr$($g|9w{##HiGemHqNX~VFw%6ebL^TxYs5NvoH@<6iT7KyCl{6d^HzJ7dY`EKre-&dFIX9C3l^OR#L0k5oMN;BIkAx_*mflba~jaqXQaDjk>7{4kenilaH` zTY5E@hGSH>!?e$`@ksZv%!#*{b5VI_A`z9hq--afKJ>ECiqLD3vg}V6X(b<@HIyys{XmbA#_z)@TP#AaKAch z%LB4VM{V`8|6PIQp~RUX6fWy7Im<*BO`BA!l4Lygrhsi-pG1qwk&Mo^qY{XbxQx$P zc0&iBd)^7;%ebuM^x)Ny-Z0CisjvKpO$we~fQS0`N5&1)-UB!(bDK@QQe{c9HT0o6 zD5a4T(ooU{)q`!rh9`l+{I294JFbA?4$>?nx~JWiEQL8g3Q~krSca4|%jz_C!#oVV zgMh-URVqEselU`5m&B;beO3z} z6~KpGtfZDx+f7I9nou7Chg7WQB#S8ZgH1ngvHs;J27e_g-JX^>h}n}z{|=Pn!2W

CHKMNpA)Vuj`IM7KU#96viW`5 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

`P{EK1Sh+|_cy{Vntr~j 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 bl50Y8h1b^Jyc)M3-;b1-yl9QELE!%YxhgH$ diff --git a/doc/images/nile_shielded_usage12.png b/doc/images/nile_shielded_usage12.png deleted file mode 100644 index df7248895de695d6a8f7a7b0d640f864d1d4003a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31032 zcmV(%K;plNP)Px#32;bRa{vGr5&!@f5&>tQ(oz5bKmbWZK~#7F%v}Y1RXMZ&*S$c2BE^ckI}0qX z%i`|t?zZUS?(XjHT8cZglp>{t7N@1|eg8@Bt*|UB-}ip+1@67)=bTg*$h=(a*%M)Ik#Ii4>9d9Kkd6Kr-)jvK+UdV7Q#n>VU4lp%AoT<=- zeGq!_I)QB_kjNyN6fnTl_^GU9UvVsLCO9WpAXaF}JM#p=RSKEHG@kGj`~(O@vu`jL zTqONn+;!>y{9Ex+y!4rTWKKTfKfxh^e3TGpCYU3Z{3V??H_$~|RoxP1O1vR}#6HFF z+JY3(6c$?OaWdY^C<jdFo}ROOooR^v_sVYMKbj z-%V;hv8;)-Pndd&{F5Gi7wiu+4VV%up{VrD1j2zbZA~cL;!VwougE5O#=azulhicU zmb$EvV8vf4s@Dz;Pi6gsFM-9R+LOjKUU9rMQ*D^DRpF;3$)L(7ehE(BA3Is65-3|F zscuM7b)mBU{g;6Z!s7yvqMAQR^!(d^Sx;6g8GjR5zl47TCc7gNFOGvE{9}}*-{mA} zo9U0ZNbVmwPbeiMNRp>g&PWvFuW?MIl;9*S%Xl+Co^!`6nl(t*MENCauhfm>H}wev zPBI_lMxMz$QAy2xO)EVgWey2e^06c}q_lhlci}_+%Ey$vsd8m*3dc$*%^SKrv-AWq zbVth@DU*~0f5SXBmHrTC)-kf4vG+;k|AkQa;vbK@GIv5`JY*Nt)}sg!>H?YAB)RY_gNL5N=n8lb8HLXL* zd^F{t3Og8knF54DRLc~3uGrL|hova@cWwj$Nr}8JVYAum|$vPv@L{3Qmgj$>}-{y<%NEkX_cw0duU8PN#pMsMl znHNF`89ULx_)IAbTBnz1Qch4eWqtzZp9yumc*2Y4wboMfI?N&}r67fF#Q5lkzcS|& zC|fjcYS38zyCe5QIR6_DlS_|(4PUw<3cV&S%JW3Qp{QUaf@b7IVlZ0~ zi_J;BQh=Y|9BINnNWe;;JSanUTO{ zdg;Ef{x={JZxb&RfdBO`#YZA&6b%fGs~`47@t0PT1arYz)Y=p%8g5eN3mpWqP%wE{ zrEpSh<1+|$WSY9b~0-7JSKjEcXK$xM+i^9g1RTWW=q_0Vkc%xDhMB_6(!W8|A?xIl10wyCX zi-EC}vHp*K1(K0&vXDt-qw1JckC}-;rMzsejqM2ac&$h;(nokoI%OOSsc0aAlB=Q| z{dtmSCaNxFhAMA_^XGN`8&8b0!U&6I19pKuB`zX^Ol0vVuc$Fi?192oS5X73=_mcw zBlM%58e96_uk=}HXn4j;Q7{zO7_Q~<-=hpGDmV%N0Sf(DIWeh+iFg0eg=$J+DT7E* z{{psBVhwebLZ+|vS}lDxV6Q;`2?Y$zN=bG37r5vaj6K(PLLre1qG$;ps!bpjL(P*^ zjMTL}V?m;!HeYfh_ezn|9FS6e$ulV+m=QOn4IAc*D3Xfb6#zpy-9KGJ>Pq4r%juLf zca3ljCHRw!HU^MJ*{iGT9;$VjJT7fZj?6tFS7H;op+ET+z^tX<%g7iSO4hb9=T89q zkNidVQV$A&> zgg?)dS_b}Ah5kMiDXDN#Yp-rW3>wDq*E!#O>q@EzswI9Yx&BSDe|D>Zs*MckDMmeI z^d)~7d0pvu(w4|s0#_qJ229Lnt}$PrNxt4Fi*cJ{CH$&b(3r0!M2dXMaarTktW)JR z&n7Tm>B6d00$q}{rmtl+GZI8{)O?j+-e?tq#ty=eGBi6&Cv4kYC{~y>uG;tT`?jbXCvzrRM^lCWEP;X{Z{S##t#P#)|*o zH#vT~8G4i0ptLPm%7wIVA{o3_-wGjJ`p=q@eY{S(*GAaNYN!OiJe6F7rBE0CTi=3% zU?<3k@}>RN0&9vSeQPq^Vhfji8Gn7npA<>zs@25stNA8LbKvY$b@}+eMPoLpdn;}8O36V(yUh8! z$p}N{h+dJUCSeMmiLyjWY3}+nxEhYofLDKki_9-g292+-D|s^ZV$@(%jLcn`7WBC& zj+SmJUeRT=%rMq8V3qt<%BwntXNr%MYAP5inDQ%l=wgDq9z%U^tSouF=KtjKUT^f2 zlu`AKrFA(eE6D&;A`Ha4p|nWBU!=A0wLyoGq6VzgpYuUuf+?D7c!lHLXdli${HeO5n z#@_Lomr~lOp=dg5d<1X()L8ege-+Ktmz3p&x;FIh57qw+K3f)#CdXW1Yn<4cqJdrbJCQilIBM#r=RM2a;xQ^-dO9h zN(!rdK9@;jfFZDS`ET+SLIOwf`7ySsZUm-oQy!}%x1@ozDVNej$|QZ^d?73~XQkc$ zS~B!pwW*LbK>lyG@$ZpMntgv8hN31*lb8lZl&%PCDvPn?QAi75VRH!?(2hyo7?KQ; z8hRF$!H*??I5jF923kKQ>`o@PJkyL~toegq8DL>{Ny%udMWN3AC*RTo%`VbkVoUfU zl9kO_I>rtvACsb>4pm5mFI7l`#7QByl9wW>Ld@Z>;i0DEAL%7!1e~H78>T~?RRsR` zQ#=`10xC2eSeBQ9rrMK<)k0Nc4h5sw-xUrPusZ|I)95tlBp6kpLF# z;>FWvz(a7=4g!QWP>afB@u7UTdKD@OL|CHnqCI}kfN%*T*y^Yd3m5=7z@a+ zp_sx+mluH{w_0ErNUe+hgKvRl{OD?eiS$@a2i1VG9Wh4~cH_b!b-vSo!jffQi+g|q^?dHf8)L83zetf7)yQcm+cs;$;MvhubwNE0-U>|+{$Ccp++>m2j;tZ zQqurju6U`HV^i@>scx&lDH&@;{NzAnOxUV~v3!?4No7?l1fCFYpci>gD4;BIsvUum zghx`%A6%*8f}^I7mVXk`A)_iE;X@5m$fCzuzBS%5dYW8@U)d~>o{}dDNfMvRwL#|m z35qfvLJDn%5#Avz$@pJSf{^Z+>Z|H6DM3U`<8+vp3V#;El~P(YLaH0azUU4sI7Y*U zP{H{4-*iFxZUn0+E;q`Wt44)ig#M@Wks2w-PT?Rh)GDAZR2h~3AAji;(>U^qp9s^~ z%HIVq^cVit6_Y&SV%+&lS(?~L65SLa;ce35uW*sEQtihX>a&7G6__%VOrD7cRFreg zr^1&4m2@`pOJWP$I&xg}ywPEQ8Z{X&fh63f`AgPjX+wBksmgL;NCI26rN-EhuM|Cj z!A)Ueds0E>qu^s~*8oI*6dxt0iG){?o z=P+1IYVyZ87KsF4-gn0C2cW)vq$$rn$VO0sO@jSxS@wik#*cogS4Mj66AS;C9%7K! zEyy=NNtXOX2>@dlrwAqcN&=DWJJqmP^SO>{SjQ zscXWPqcpl#QeNjP{{LRSnnPNGsKSQw@k;-exxoxW(i?cW5$cgHLRu!-Y>D0={Gp7f z#6jkaa==J+eq_ETCh5OwSLeyMfh37VzIUPKyn-t={#tkb9*qCZJhQG{yH0X(aeC>lU_pY6I>ir(2%9ezg65P@T z>#J)GM+QQM!7L^Qp^?!rHMbDfQjDbCqsHM>1*LGxl$gzdq!M-O+EpxBwhSjvoyNPM zAQUN7NO|9+S=DyR+CR8syR75mbqCvao27CAK#kJe_P`G$e_-Pj}H||F9Qe|Nw+{$MH zq4SRITd{oUGF-WM3k8amfujQ#HqNlcixy(@#!a|$`zeZ-C4Z?%GquzbW#Vqcn>-|N zBEv(mf&93A=Wcxb5`=;UIsT?vgi4FXa4u2mwE}O7r=E}D>Gd3$vbexpXu*lD-0rHI zsuo+3^1P-nb-#QC!QoLzpFXY1mtsjV@2b+@_!77pw*20^dmED{O~M)St&L-L=TN%*Dmi7m+DfQDjW#K)Oh;IY=q`Po@(A#X6^sAH&MkYw_T*hvGduTU)`9 zbp-#UcGYH$3uWM^4{*D39jVf#hpn|0!D`;pW&hX}jkW&PvE!syR3yS;;$g}8n@B6w zLtXBVQ2r{1_1GnKHiS5Sc;k=j*KQ$grcAK5BolBPWMjQ|?;ei#CS16137K57Abq-Y z>X{C``;#^dC<&f2+_&7XV9~PWxO(L#vgXZ&v<{Y7wP+>GoHHR)npA%R6uk7cQ>3ktUTR=^l&SJK3Mj8}Zc3 z6M2dhBfneV-c>i;ynCOr^ckLeJ;ifhUzk|g!zq=WjHqEuRRf7%dJIhA{pcEY9XyB9 zrAx4^%n_+!AV6a2|JFrH*lKW#yBRA4I zX&zCEgre_%O3!$__IrW*kG$cMB@5RABfP{5W5y4E^;joh=Z4h?i?_pxL;J99-D`RAzy)lu;CbK`6V(> z+SRfyIx-R$&z^y&hbNvq^TIRE4{ zMOm84f%PPMfn)uB2&(8-F#*M>?qsI{%ZHGbya_M9{*2rDZq#6(A8>!yv^ynPq^-+drG z3vv%(j*y_wSiNc$cJDiY@Th3y%O`Z!eb-~Buf^da>k%TO5K6tl!h$nRNJ`S4JGc+G zpZTG1zC6i@qlgW!Cm!h7xd&?2stJ30!}^!3)VG(ym!b>2e(?w!w;n*rQl()=-C6VW zza5irTlx3*!byLTL^yme2|-Lyrc4>+&z&7xR&PZ_gtoBDX0CGODsZx~#MTXag;hBT z#M>gt$iSMQV%regT-2w}=+nI~UVRSa?m-Bn?fr^G#u+5lzmuel4DRh~ZfMcG1uUs> z=F6WSU&F!>Co@+i1<7!&rcdH^$_rgymo`Cg&>I}we+*IF9CmKo8keYimnd2kZXHg-v_EK&tZ#0HDpjf?m9;sxC8L+3o1&K%U6J8m zuxIxH#F3D~RLO;r#ySaiVX&ueym}kwDsf|Rg^dz~;tP=^I1@_>i-CRnVfD(b$jyF^9XS$n=B%Y?ktE!wuT+ct zij0iF+!<3*r%oLVA3YY)gsI@MUHR3pRg-Ks0e-$d=-R$L>esK2EjxB=Xe^tElQi>> zlC&V$02wnnqb_;n`ne0ZbJJ7h35{HH=0vS(Rd9rxXP+1ERK9qf1Ua!5X^aJq(8dfq zw{1q3F1?UFdv2UPa~y42_d!&&EbQ#R!Q&v+m0;rUu|xY&zivIWYTXt-ul+SP(tj2* z_MgSRNqne*sJUy(c5fU%g^`n{sqeQ=)wq3g5 zP2gu;f|i@fgGXTExMiH{MPSQK)4TWY)SQrcVU#ukAw^O!4XtnvJ-%}bojdj9<}E8; z-E~K=?tKs)n}9P^uwK6ZkfQ2e-YD3zl<7tSUU=Xj7w;rGQkfxcuYG+G9P$-KiWY;% zt*dC+su#i{W3YJo6wI8n5V>>Zq=L5vefkeU9C`ZMg;UtPd6W8V-?0ttTDQlgEBDw6 z+hHG4CSz+l|I-Iw95{GVDL-0XXx#qxoHl|>*~kx=Dx1n&M5-{hY{5dcPpSkJTyZZ<^Dyh7&-nYNsw5}gZ zH7d6UxLJ?pCbt0BhW*^MkDE$<EK^Xx_9dJ_Lmu#zGZLs`|IC{)lm8_*Zmp-xfoLjzElhEMxiiVJD3(IfQfb z;<*#>@bEz?Zea80%E7gI8^R+fI|$OaJStvFfv?6ZHaY@*dUZq3UVRY}BjIR&fs3GN zKvn1!9ukaBZQA2~Xf)C$#Gps()_C|jfMv{h!pL9{FtNpnV+Ros5QrU{*08OCaCOa# z!w2?pZVrNS;t8*)aS|lu+6>V#kvPaP-MV=LHgDNM#cw&2s~aaAPMcx1y~krvc8lKY|GVb3=M$ z?@sjZ(;r#0xo~6o2rXK)LSQiYOK?dxU%rL#pWi27+~BeBeIJX(OO~KVhnBDuYp2p> z2%T$@F(qCRXG!uS^VjSCeYEe`9qA}b-`&53Zk)?s`BD$*KOy}f8T(H~+2#;;CM+elVZs6; zo1V?vux{;Yv~1Z8-YKK;J~5F$jQxjpB}xi zZv6pe1j=F3Na_8icy#FsE?>W$k}O;YrJ|FQ6Lo>Q z2BVA=`73&A+J}XHz@EL_peiU@zmeMi*U4N$1mjgCJl~kdL4NA(g&cVbA^5XDHyH8A zlOqRwo;^Xv9Jye{zFs(g0k7Y`M;`8MbLTIFa^))_tD_ZmEjwb+?-&qZVTo$htHVC{ z3*0XGDiTR(QVbP-IoI;&;XPcwe2q$S0o1HhO$`JRBH;Hd0I?*rkW{RRAz#13fnFLJ zFr{L1@`)!+3dlmBF4dPPnC9@fdluOW7Q>W@6BFqgFK;%Z(0u3r9EXmbgM*_Jnlx_2 z{gW9zAJ5pYjJR{z4L;n(*Z-vjGN+}*h7AAWl{ao)b%!^Fpt+?3y;*PZjFy?*CpV*L zp6rS6Uoniro(jeL53h0ZzzJlfGFYo>d3^BogSYQ9{8Y6nGNj8)e2KL3Hc_c~?~B7n zP9omM3C$YUhohY>=~WZffgf@Giob$wO1R=(kV*dLjW68YuEFQ|W760L7NiFqxU|?` z!rlAg87hN{e3PXRNj6pY=}md$@fg?dcp$B79Xz;t0b7q>z~QS`Q7*5mf=_Hzdj9k= zPM>vygHswbY4j@`>?~tesuZuznqUBAq^m#Gp{F9IR&oKkpYX zW!puIl|sI(nXrEPT1rg8P6VzADpah5EOzGDv-X4xi^?}uarV-dgs`YAmNvu7hu3iG z>}4Fhcoijbq{XJCv$2p?&7OTaBAugbB$JCwZmz$EASg7P{4yGAcN|lIM0FQN7OQCT z(z5u!q_8W<&9@W#97QEPF&WA$`0TvAC5WbcdNH0LgR3jidflcv8%Ca{#3HU7 zcG5*GUU3m$v2wv844yI*1A2A97oWSBG=D8F-yV5l-Gxy36z`%Lih;|F+gF^Z?Rz>;#!!ps`!U2`BiH|ybFKf~YeqhQ2DC5LowrKj)m9Lr^uY$l^c%J z2>2L=I*l760~cZM=iV4MZYXBWUrpgJEhON{gPYj6`xH*PUqZp0=~b)pQu6_Cp2~vy?F_6KXVdZzW%7ku}e=Ldg%2G`SKQk9X%u>BmJp-=gpl9=H!p3PafjxwLA2T zX5!|mIOU){9PMrJ@zYzJ*?$xnbLB(L>XlHUTve3jM^Yt@`_D~#zM~F=nvp=&z)ViRkU@&e<@6z7?SLw^en#PJSy;vl z%NNeZBd^y;ohH4amB@luKHjjlPJ@r1UvtB92YK_BLdHz?oXa^875ovOzeXT$o}Bb# zcq1w#l3YGBHg-vyfqj-dkNb9QVs2! zHALYO6|wu^5&TSk&7Lzm=hgx@!kpFX(Xw_GJUDegMU@O0Hx|WQ z9XXHRAv#j?z~~s{bZLM5AW9~RIE5Yyn2pPr_UjpA&U7+mO_p4 zCGpJjArfrUWB8!{@F<-fZTe18vXlxGJ;wn!ckT*3Cnm^Uq=53I*m4o^effkNqMLA} zJSjlW`fXH2Kxk{lHZ&!;|&0&qY)*HN>W zP#iyX5&24%Mb$E;=ppiiW$FwF_I(L=Mh{e}RS)@dX5qY~mLd@pObIdJxX#U821Ysr z1%9Cs>?`N51=nROMZ^3B3s9#13QyA9S6MyDWt^))K_Bsmc*)!SOu1>W=7ODuo}}!V zGb4g};;7-1kcRy+r3{Fp{(1NAJ><{FHN$}(8|qSDITlY|yrg$9HDbeq;pP7Y6&dw( z^7t9lY0v-}(xevVk?F@Du2sCYwsXXSm1`8<^0j>K@32Un1>r9r!P`HO{WuG&kk^L492VPEk2 zmDYKU>V_q~$<_|GT*DqKo+&|@SEguwy!3d&sJ8d0Ri_?o$mgPCh&&2pWLh9)jy-jS zqZ}&-M`tu_)PQZ0zxvK(bLrld|q z(L!aB5UmtM*#?UWe0cW;jF~zg_3G5ex)m$nHDCyp!@~G&_#PK2rP2i=R6F=G5u^dB)1lSU7tYn35(rrc;f zypDDqdZ1L*pYi7LJ)Asy7Hd~5!2H4e@QRC`X+$(4xlrsmbsHyluEd$$ThVj)1TMyP z@yXwpF4FXHN}B-%s2q0b)QRB~3-MFg;tB~fhH<=keh)1>bU}8fbT~I{Hdb!kh8(7m z*v}pJp`+(;;P4p~DV$lx9eywFqIsLnC|s%%f+@sK(n_*%$41z3Ll_$^ak#OHNd>s5 z!EPrHp+oN>s8*v2B0|0(Jkk^vmho6TcOusBK7-mnRl~}K^D$}R3N&w68&k%N#D?9+ z(Wp@khSm9?Ms5q7y>JD2%hptzFY%ek1d!S7e(D%H^%;gbjT*w=^By*?*?=8~4&dRv zyEu0G6z+Kk!tLToIM`C5jt#}+A^q{$HVrIEukhet?4>|GxMdBj;@L9_BH4%;EkpiP zru+9D1QTw+ob2tX+&#pHISaTce}Q!uAK>(swaR<4=(lmm-Lx(GwrorE;K*{zyFL6ZzH*Jt`7GLhjH-i9a`$k;r1g>HOEAORRSmx4lr`m z7%FRDv510L3$$bp6DcN*nIORbEsh*MN(IvnejazR{m=<)K7In10v=$+vTb3xSvdcJZ6D7^3E@Qzz>$(92x z+O)*6UE8t$z8{WnT1w`iHFxY7l^Xz#% zV2H@t)f>>He-Aiia6q_eG*a8^aJH{85eSM6!AqZ4C|5L>8kDy_p6Jqf9u6 z(?@n=$I;6;ec~vV&iD;?pMGMv;0ml?J{Nw0@z}Fz9r||b0?TYAv7~SwjOp707r1e_ z=J5`0XZNE){l@6ltpgYSsgz4kk%3_`X3;^o=>7^7OF5!K%}Tg?gS5H(5IOS|MAK%C zxO9kIG>0|i|E2{qF>4hqz+CW8ZeEI$^z6h?K4eJkh&ju*;DY;EeD%7D4qXP}rz6FR zOm6xcHE&9fWe7L-A2D|H2;8~ihWj)`Y}~jR4Lf#4o@~xIyze}&yWi!;-5lqRp2nls zLAd<*IxIij#;=X~B7LST$d)S`+%DW=xa%5>8{HKvcN|ii?Z`0BlMd}LY3W+LxOWSO zj=N(P=g-pB>r}|)^2G}o@_wE3`y*^JyU@B7hQNq4IDf1ly-UsDm@PL-7RZHLN1vf& zg;L055s%KTJK@Q@kNEkgGTc;uWayyw>WM5hWr&nH<(U#8+%O*AydM20&qs&$t*9J6 zLA;eM3U|nkYiCYi_4>n@J8KT+E?Pz}kJO39#?`a2=in`Fy0XL0EEES#-ML0YW5D>) zxUqjTwx7NUx67w7W9%f1p7A?6)hmTg&3{3zY#EUMr^YB%AUDFlyk)dXdxXRSrOOnA z|NAdAVsi8L*#z}|u7PvM_Tt1nZ*JntWA*Aa`1JBItZ3N!M1xtTELmw}q^!@B6ZNZ? z;)t5zGoRSTbuW;G74m7x0&z-?wc&*6lux z2n%bZPHnHshen1mG%^;K+5Yie4B1@22ra1xv>!Vizm&?0X#>WP4rx%WQW@;txD2;m zhagA(TsU!hJC;+?yXo-)zYQITWohkEx>9Y_tx+27dJn~!^QVx-)&i%uZ^oq$p;-3& z1hi|~0!ZV+{^!G0ZlX(-EQWC6>)_<5a0%zUh>efM^Jh;H6H^gATerdUkJ0$K)=v!2 zy+H#;IvU6CnSM+!;pkyQd0fH?1BMR8g@fDiz&{fEwy&kJ%@q5# zZo!UYH?e%)c+_vu3gv3oLgv(#c=q}oDwi#R>a}Vj553$iTD8Py@?IPEJ1wK2o_M;oV^E`39R$<(5*VBg6^mzsy^|gb(e)zurxM=# z`{Tgwoj8Br6Gs@HW=mc2)RBEyyx|}^HLQlUj1GyVK`dMPbogz?VjQA;-?L;M&fa+q z2a71!GsJb~>@~P_?G#KY*UhQ3#YTi;aF5P-&NXYr@)fvn<_tb@6DH?WSFT)zQgvJ5 z(7ts_M?q|)C}TR2Z$8q%bmYhpSeV=JSP137wtbkreg}qkX+>E&jG^NdQ0Axdh=>iN z>@wkIXAE+es)c#KjU?ZhVKL{#mB%me<;!QJwlkw)!56NDO2U&~!?%HE7(J*h*J9Bx zBs`c&ed*@q+wJTDEL_L=ap@FJEuN3Uc`G9wd8<$y*dCo(sG8AEwhFH!2m&jRn6?LXdeHZa5>bXa8~J%UK@T)7oKR zk3PtjzcgO>hr<7JF!E$d!+n70?xH7(xi}izIe%6%TFLWyAeuL6%`;%p7(Qb<^#v2$ zpzOJR_9(J3gmco|WjJ;26y7|#kA2ijZ`}69Ww+A|+m}Hxk|S=9V^a;)diAt)0(& z-cy)Ae6iBL%X)(`G-iHCjp#!_TY{CZRMg=olqO2?{`NmmBy|{g@454Ni)!- zV_yvE-jShY=`nBK2DEB*45LR5z#B43|KVg-#?+^E&J3$M6|kL~nE}1JqhPU;=-sC; ziWiZM7~3-FyV9yUNV+(7sJLO=p!v2sqoS^k8;@61GRKYk9m6Iqz<{oe@$lwx44b@(e}=*u zYqo5|_=)4tlvavn?R#Lxj4_N|rN`**J1jc15bf&xgg#9g;M%1}XgskyivLs!o{#I% zS}B`cCVnlRyM&e*S4{hDGQI?OqDJ+W43F@^PbG5mF*h3Qhw2*3M$$?;lJ40q{YT-q z;l1Gd)Ezzg&1G!wE6ka<4pWz`L&KlTv*2f}S+xU&Gdp7C#=Y1@rMPm5d|1C|3cTNj zBEp2R$gWuh17+A1Tudn(B>b*fw?3FPZV+O^Lr}iLPuO?#82WbXjPjKVp>5|0$}6J0 zcV-C)dlLjRZiM&jU5;1R&!bh>aa7jgVQoXdiXd$?Very|obJUl3hi6Kp`yMLh^4zbH*Rl4xQ;-6b+*! z2pC|?uOb3}X%gU~|Ct*53ShK9!@!k_2R&#FVNeTUb`q zDm*`t5-qLr(B4o!nxxvm5<-Jp<}0V zX3dE3k8kjip0NR)M_|;ro)|x{A3ZDYF@OF7Or6hgp*IsG zX>6$=B^iX`$X{Y0s!Ij1Zi8R&>ggS9+QPXrbsCBhM%Y*XBwwcmn)6!q?h;kqv$kZ0bad+ zg9(%7V(hGC4EL&s8y9zA;W9TWK51wTzlXN^*|)@zDtKmUx>*jPf#Gy+Ygh2x z&ljAzauj(}TcJcT8rZx8ux$G8$Wx{o&u@%}$IT0r)nhPx%xL6vNkawWI$E^riLR3; zqiMscDx9WX!xlXE)e5(-oI(5HGhsX_V-smLnVXhEnbP5DKok|NP>h;55M8_d%CMZiRJe=K z>-#fi&zy&Cv)#SInj{B&!owX1b(h+Y}uh0W0TxSTlVz+IJa) zeMb&31ZXkWXGUc_e}UeuI-`I0c5r0KyQFPv7BdX)6F0sm;F2yCqQ1OGWE?$AO#CZ@ z?1D&MNm?LyJ%7rueQPvn(Hy0VXz#~o|Cbm-L+0pdGf}!!Mim`n#f^$ejFdr)HRsU= z1o}UsLUx0Z4KMMCo}$OzezZn^#?2cy@GdG1oVZ!anI|i}cORvP?H+z#(o~%*TFi5F z@4lMi!o@>8YhsHpuN#w|L7ZDQ2>IZL36rPecPd^R7p=yd*Maa4PQamqYiX!!iynQ3 zRI(|={V99+OA>s5oXopFZ8uv_n7m zjTwTBP8L|YVJ8NUnS~FJ&tpXIKJe<@fn#xyFwNndHVZy{dV4 zNHXWzA;tY4pLD781|>Q+9zRzWZ!GC}cq=xqGQ$#{y|)ea!NPk-c-GsEo@0AlID3^IurE9V zWQiHS&Bv4_tI?UV_uS@{n6=s+sTux0Y}^>!_Z@_uefywPvAoFQnhSX|+o4?5M!3lN zUaM3AtlhF7-A4RI59U|+eRzwpjF4%`jhs0>K(d0FJEf)9)*1WGoGAcm z4{p;ClR0}qbm`L#Yb#ZwB7PN3YE{CDO`AEE{gEN|Js#hCj!hR%qJHTjXj!E)9^djo zDCKep=j+~sdyp%wIXAcEd5-ZO>`m>-U(e8|Q+xP+iNdyB+mN1TpoWbZrEpz6Zz3G% z?VLGfEICm&3=+-3StyrGqBG=S=;`d)bD0-~mJRBmKAi z&r|V=t3;hjhto5Bof+@=9)RAxMq%2bRmkO>9svwbwWcTS@UBgG6&Q*+GbhoDI}k6z z9Z=peJyy=2g*3VHp#$~cAp_^IJEpLw0d?BA(Wud^8M4}2auaj}A<;H4Ay1B*GzR@? zz<7M=60Tf-fk?B|_`(RRX}{0GjAg5_a@st2K6?*S8)t^kU&jV+e%Ef_MvrxWc)t3= zXqSRKx4xfr@WGmu%XnO)6W15^i5oCkFKJ=tCyxALh8lI7z~pNbmLI!>ag)b!*2N)b zjvROz$q0@|ud)BiNtCDOyL#c$c=*7Np_Xk>x=L}%sR8O3j*N{YGtQ~Nz*sC_JA($N zoEY410v!bOM2=zP$cW!DvR_*|VJ^}DxmfY3Z2$Dgs_OrzUxraO;$VtGMe=hKvkO_d zBd=UKANDR^4cA;*>3YA9mhD=r%@=5=iJ)sWQJbTML2;-MI9ZZT%1ID2#k``_gy2n3 z6rMeMNd}&cdtR@2?xhl~0QM+Ws1)q!zAjR<2*c5e!i9^MQ(6}c95f0iPcm$QZpQNE zD&TiAo<7$_Rh{dHgeuB1Vmu8~xpsZHxnD+~HVx3HLpSdFz2F@XgwU`M%%_sDWB*As z_=PS^vk;gtba2?Hfv{(2fUT1?`{sz(#3?*gDl}_Nmp@ZzSTIb<5Yn@BF4?e>?%kt1 zH{$5lm6*YB0V0E+>QzP?ZoVXCruW6tupy11i(B6Gq_?1gWhA^_zM*i6z@qu{v3~15 z)M`)%4mOHFq?NS7@=}!PW58kXw1(NGv!?}u)_V3|y}m}uh|nLBCndfoBNQT1H^PgfYmKvkaQouR$JoN3Wn>uvKVeQuU-Ofx~e#dXkf9 zlO!JWW|d(m^e8rM-WCz*3ea-6P`wZ%V`f+6;RZjIQ$`f!{4LJ;>u6)mmubZ?ge44EB;co zi3|T5`(Q~=?AMSudQujnSFfH}F=HAo?mLA5h6GA@p{A!P!YJ%x4fE0Or5ER0Tm0I6 z2!_(@L^j5vsZ;UbvppU>c!=R`>!TyjkopFEf+and1I7-3BVU4H>*$CK>8TORc?KTf zHU5bQ`2(G%X6EpI>H(*mc~Os>FG=CS{3Fbjh3xh6`i^Hl9VY|2A0=zy-9~I zn7?Qdu3bBer(9@u9dg6WrK>P(U>m$==~mkGlxZ5jUO03U{cBxKBhW=-o#lL!i!M+rsdL#ie-~}JfFaq zj%0F4#my7X$ymmr{m>3D&PBoLRV`Vt#7pZ0Oys6Yg@Is8O#K(%9Ql9GKJNS%hsk@Jve8%b@c8m|n;BXx?`;26k_+Bt$fg zE3SpJp=qb)1Viih%Ef%CKxzya&>xSkU%=Hn_pxj90d()y6X)m+f5vuqQJES!b^;#L zL)*1SPdujV?$o|L?70CbQ>!lW=FExcIC@KSxMKa9ML4&2BT5!6fsv!fA~GU^;Yg2A zuR}An`4ZfN!?@w4gp(vMACfg8MuZ3ZB&^hfVt(}+E-;kmf!8CP zKed-UIT~IJHSIdzTz&RQ+e{Sh^0b zIi2Bo^#p!fei+9uy5r!cwV1*AEfE2 z4Xah;`s)wtGz<-`RRbmRxgbNK{3u_e5=8wlquk`dL5vzZ7PXpoWckVpx3X0m$+5w;!WK@gi#TC2tCp)zFf%Nam^AxzlLbbu1>*lT*8D zX?j|-B8Xw1m)&k)*oa|pKgbtSy?#p!B6y_335^@n5QGrU$P9_Buwuw<{*py_Hq#0@ z3*<%dqQ&?kB71JaicoQ{h9&bB(z|;RK@mVBhPW0iSsIIGOv3!hV^O3?Nz7ZaoKqWW$CD{Ez!AUbB3%xfj2kGo%?iBZAMU^ilY(B&xhfPeBo2`Rt&b=tUt($09~bJh*!mZ~Q{hn$ZSfG=kJ=-BfM9 zWQ}ym}Cslo%Qg%;=Fjd*T>&@i@vz8ro&^C2!f3w0Zq9 zjG40y8+PnM$wIjlZ%ICvSSGC-s0l+E5Kma0+jqo^FiULNu$smXQ!Ha7MKmM6h79V1 zgGUZw*Ov7dJMni^tW*;f^Jc-?HJfmZ(H7_};vgdL-VYI1`V&+tb% zPpwj7bE0+7Fw1$G5Fdl_uEDe!vtY+LZEI(N0$J^G z`t(|C*?S2yrZW_jVXEoUJCYveShrz19y|-i_~C;Q`sqEgWJya;W;mwL90_yBTh)v0~i{Ixl!u zm!bD<+csA9Lc_vn2$6x52r&6W@Bcp%kCkw6l`z&;_}sa3;na!4a3wLCG_Au8%MRG2 z&Q2>w94+1N71kFH?_-?)L;W@~=9t7=tMe(=HZ;{FO9Ix++?0!-&a_Q!hYWPDH~zH+ zHx6?cwzLBC7R}~a2@84!q7cNm_pr~Ai4fvr!f^KNaUT5ZgU?)rSFT@=9_?G|NJJ7+ z78e-=xiaM>W$?ACYrznT5!1#a2i@&{T+ni4&48i|`I|L+CKmFodRutVH%k^5)~4l# z_(?M5!75X^X81u2!%gDDqban5uwdaTz5$1C%94$SY6&P?qZWE_U_EZ$Pz$)JSgI8i z&UH(9QK+V+2uB7MGG5-2Pi4q0kv(f#zL~GM!e##aCD^)c3kva#Gm=E8N~DO5Mv2M* zt?k@6#)Q*?5Fg1fu|@b29M72L)JRL~Ue7-LFo%|y6>HXEBoAySP!S3Z`lMjJ_4Q)- z#AY^Yjp}vk!iSdNYY)A6Pv$2oru1TFqzCdI&xxrN8xF6>50EWWR((&wESiXottjUF zM?OhlY;L3upC&(0@u%Wu+%&4`p*9b?x(H2E#>~rxQIdr~CvRyY1yTibH}n$gl~ zuy9NN6|s^CCbKL$A{>KycEg82bL`u>8IFvl*Mg8!j|=eImwrZ&`j>Pu=f@;gJW}K# zTHhS9rh^qX%1=F>sWL$UzIe|~zpFE2`>ClI=c9!=ZS_2ZWzdb)ILaTNv>~pNT=_;w z8*9>u9%xAcfx)mzlM1OB_nkmxOQ`;so0|*g-5D=#i$GdzJ2h#^H+wa~{E5R=9r-$X z<|^v7ZOb>jWybiqGhh=Rir3Fy5v(oZEwtb=vmhPR*zuR4r`G1?j7kZ?vW=SwnK4X83`T~vMCGM&aHglro^Ma1_s`mf-kEOr zonaUbkstAp=PgR`+)hGV1orPahF${(sIx#aIx^MFI3Hsb&kAgs1Y}1^`4vs2B%W^O zsIVX`pT7c8ahAxLKR3@>oQ6}@JZROnA@=hPj0xuHxv3DGSWo6VlZhw++yKY$1vNv5 zjYR#H9T4jG2y+&$vnt<*FC&_@WGx2t>4P;h$HVR3 z6T%XRoTG7+v*Zt*bUW$^$5SO!jw9z^zTEQGNA`{LOVYnG_Bn9T24zjE+j7I}V4sF-R~!Q0 z`%zhKjT*mpz-(@cjmd<6C(4q_M!bX;6O1`GncVkLNiK%=&1;~0m+tWV7|AfjnzW?1 zr1E)`FV1O(I<;#sj5RyjcW4h6XD7-_ZVtKWO5i#b%Q>&+V{jO1Hfn`)m)&t-+eYkW zSmw6Fm$@#RtCv~{4%gfc;l#-c%+vEm)_ZxOfvpYQ%3_WqGFFz3^foQwS(V#pRHYcY z^caZz)f?a}y~6F=wB?&{8*;Or0qr`rp(5-;n}}{-;3t@p{)CsaKn|Rv7b3lDK1Q@O z#7^o6mUbC%hn}O%1xuqmWpn+u?f62WvT)~lGb?JfU-^O+Nhc5Q!Fg^L(&f#;^VC*& z`Qjsun=>gFJb3MovL%ai(?gyeX>viq zG8Hgo>U4DJ&=$M4?m@8%6%>#3@7oJ&x$ce_ITamy_2xMdN(u{xSv75j-{;IiflO&} z{?c_+s{bp`p_t&rnG2}Zq7kwcNQ2FLkD`3lhTQXo;%%4(U*a+v&+lF5OQZM}xZ+&H zsf31p;<@6tx?h5j84ZMHQ4xGQv;}2@A2w~<%ri@td{cBJs?q~+=A0Xj9oUMq+$1gD zyq6WMP@Q4Ab7uXHN<6l3oad}cR;h*@eAD8M+t=X7H~Y$A4z9kWy(x7NcXXqndHj?) z=-#ZpqJdM^oO}bY5AMBsht{3iA~g6l+!&o8FPRAq{zzpxnx3c+e38Re#8Sq|%#5cI zR}2xcT$@xPpUdnVICvnQ-MWbG{YTR)PA{vS6(Xn`b?Gw@x9{BHMruWnsvfZ2({B`YeFdi7?AI(edF_x`Bes0qWhDO)LX&A4u9Ny4$D z&LMM7A`>k6mOM%KFJHhzhR>BKT$E=eL(rscYfKz96zw~8#LZ{EENjYRC)*f>HIp)C zJF1n^>!rN8QIdFA%!N;wLYavDb5H{`w6J&zy-i^wJh8P?(#7 zvOM!#hdQu5Mvoefy{j2cKYJ55qdj3sL!7)(RHUI8s!T4h+RSck&!+^G()9Dn z*Qrel*io4Y<#PubzhA%b=DDJ=$WyK&U9}O|apD4FXctm&1u;ZK|3N%~6Hd2u90iUf z8Rjrr45m*Vr(&)sO zTjNe#UNL-Q;NW3=LtYD1D_e+fxuoORps2-&(#b`{luC*G6ULY6r4$(ciZU8a%YH!j!q0qXwk4P7tU?yP5i6!_5BOxu0Y3b-TCqY zo_|W^MB@srvy129;=@PCSf~U#@f>GVoH@pDajN%AEv#5L367xw@bb9Dc*ktWmCqHS zp$tzJB~NhSm_*Twkb!5}a?`Ujx=Uy7a~RwHDV&GGW4YKe)aFY7TJuatG{djVY@Mj! z@&z%hAHhwlk%rOn5!_UoV8@oNICJp^E!w$h0Y8U0`;4eh3#hxh8xPr^rT5SZJ6Ekm z*Zw1shsLA0$N+kVLXpcQox&%M8zzz4(Gg<7PA0!1LKq%X0Q3W)U8^QMm>%{&#hnS5 zRmGLZ4`39<5H_&^MFiPZkfot%WK+b%#3YV!OZ+C2I5XKyG%<_LBx4pc<4l$>lYP?| z)Udd+i7bk&BCVhxq5=j1QBV{__U@kF|D0R*-S=pcnQy)stNYzsb?VfqQ>RYt%jJ;# zadiF;Teluv{X&ePL;KsnfqmHq-G+`%Pv{+_Ge|i)l<5#kuMgz@6SJP{cO13_i*HAd zrrAYTe8GD6>|_t#cAK5lyFaT`zp_95;z#WD|BO{l`JLVTm9N|QD=ug2%}v(+x@+wd zS6pq6Jv!66b?)d_7PBwZd6zComkux}Z(zpTjjxW)Sm5dwa(Q zJCPP{-TG~O7UjFPbk%eA%7K~o$&Jg|N_xoJb?Hyt{53m~K@Ht3*pe(6)KbTFtUAp; z_nE)9>puN=w(P1aZ9ivT`>?WcN|#gY0cOyL4IW_6ELm#X>9}5T)%)!Sw|vuF{5B^R4y^&Q#V-pKP_Gxv*wHZ_ZM4n<#1A$0~qU3JxARK)F0fhx#q_nj0+H zy^^UG=ehUVzjGN;TXt+bxnK@@(ZW7|%{A^n=0g`*tDC>irAPR-Qk1KXp|XN9%}6IP$H6Q~0|^NA~M54U-~S$~4{WYS~< ztFd3b_68d`W-OB`e7c88h%3&bx7hvhK6_u+eyoCawq6W8 z*1g6mK@Ho64}F_~|0OIs?zQ?>Z8$%xTh+V0M$53;K-A7{7x zau%z{m$|%V-}Ohk^MRRGJ!-V=BuyPoK9y_!JJP2ZZ9o3$zgg>p+im^x&(L@5U_+{h z*dY32H+<&PHnCS1+s@@)RbAUz*E2Y)$kCR+yX;~nKA8ZezHe;7S!uRf|K#UCW>TOP zGF)V9x9+nkztP~j(b`t^w0~r~;+MYp8@uD)PdG0s{)g#@sFhWj+Rw_=A=01;(m8ie zvcnsn=KR=XyM|TVUZ;1qX{?OtlBFBI^=){0-7Y8Z?wfd` z+ML|ozW2SGRV00abN<|WSqXZR8#+I0?{LQI`#=4)jUID8tE<F;?0sV%| z%zE&CYtjBx8$NU}yfs)gtNgVCJKK_%4%&w~!?*Q?XZ&Vu4I}n5p`dMs^=vub&qTY@ zT2DRfLky}7wed20&~<%&=u~!!2uHh8sz;q?m(aIf`NRV1xYz9V-``>1|HIujf$f;X zfB$=1IA@w|Sux8VS-!$1az8;;<379N!5KDv>YX+mBdv-;?Z-r?0m= zfA=%n@Wdl_Qul5){%*_}P=O6yce*VM%@H2rmY{wq`fiX+Ula zZ|```?tO5YZ=0#5QR%=kefzE*nQ5XQrr)?xRUA?w>gUy`rkX4=0 z*G6(McI|qu@I9wLSHw=?8$4SWteK4%c^+6QJoMCbfHHgFI(7;WG3SnI$00ToMUTV++c0mSMgnhaoliJ zWve(Bvv|cS&Tx%k>(8;wGB&U!tTz%k%=e)lvL{!qX7->pGp--y+{Y>QOx<#u&o`i2 zwQj?K_m8s0K?i`j!H&W{Z{EYUYxe;TWY#h}&7tf^AGhvwJgV4|_0GQSR<~k}jT}Cf ziAYs{7)nQU*$+`ky~zz}>uu<$8dmfgZ8KXqcI>EUurk2r&Yi`rY#Z#n3*K+Gd!IG0 zYHPi^bn!vP!Z~wn+payZ%#_&i^=?e#ZIWsnkhJ8QsJ{BF@hyM2H(Ubfb z4vs2{G++t++PwMCwqU_RW@M<98IT>MlF;V^PV0KA<7<$n&4uc+v){oUf2_{BRu8bw zZ0FdwcPFz-tHB@37Tnc*pXO2P)$c4jiy7lhZ*I3C=$Q`2KecSJt$XQZW=UK61raAP zi`PH}{rKDkwviR+W7$G-(FGIebTD&CS<+ljsz>~`N_c*B{=>FpX`Qv}aI#(X@hjMZ zd197kFcY%ZL=7C~USOLxv(1NTOb#<~@ZiP$zu z%cpB)&mJ(;&Sru}GkEJ(uCzIn?Gsw^34=>6q5QtXHmx-_wDw#MiEE`rIUu>VpkbJ8 zb!*mH$1dI3F2^l(Ee@k!%dH0uR!7c+?By1|y1KPiJ7N?a`G;-Wj_us?1&=g#t=qNd zM$HRt?^|!!q)Ahl5ZYHf2g<>2vQR>oR$)@u*vW!7zY(YFPv zE|=zvA2-1|Rkg9{_fN4^tQ4N0GVrmFv#qX;J-*;EJLT-b)>$`R9@xdD6;D|$+q1T8 zdBJ8qG}}(=(aQ#(HNall$_)LG0bJm)-X2-HjPC&swRwCSW#yafZU5d^Z0*KP+J=acUU^wi| ze)c^2G=y1Q)#c~ZjIj2s$S+;GfbaA$$=3NeTeRRw>&-sLQ!SY+ptC=H#%v~j zhH&vhXC@7nTl)^DGP%V?M9|md$5U)0YSo&~Vb2~S<3{i|w!LDLrcSokxz%d)*bCUx zKZG+>JN?$N+H-4JQ9W$Smpn#C{5b2#xutrxuk>S}(1K3n;$;iDnRh7L8e7}*tDdyk zl-1@fT3StQE#Il>%Z*xdY!?IM`g)(fv9@hHaV7J4`K_am%(|Zr!)^A+d=8msdb*Uj z>gu>DlU2F%#+^s{j^!-UeSY)d`#N^A%Q!bx)uA0YD;Vr>A;>;1QKD{Psft?)xi0yH zw@K?pZnPX}$7xz#iKUFnY<;PYThIR1CeEB|=bYY&0T#8C>NN8F_1AaUTE2s&&$+Bz z^|YVWYt5FPwJ&d_9UX{1uHv@3GwhkAOIRs=ks60jc8nNlHFVaMy1BEak?&i%+4sYI zN@W{$0JpSt>&R`TyD59~Y&(;Pr}XS;=M5chTQ{w@c?;&-8*jbCZHFDWLF{6-X0?GL zyz;+m*Q@aG9NQDeAcuOsr}hMIF4XAX-OIDKfbZiXA8mOXQ8UW7yf1rfp>^XDm8wpi zsM8+d5{f18_#W!~3*m7fW0iU?__~*ya^GTL{SmI9-@wI)y{(E1Am(!9;PJXu+#K1J zoeWplTf25p?o>ziv?Wijv4IS7H*&Gidr$6U9l2tA`HDIlK}YmBR;G1xtn_cvU3c@H zByQ|Hxija%F3;OFRe<^q(jJZ#467OHE1=uAZDi7GD(9K^Tc2~PxefUJtVA5PjW4a| z0)agYz6PcERXWKXYt+TNgU+|Oj627vE5rj32z zBNwtQx{kWM3pX6{d5!h!?2VnfxOugP+X0`q*>mSu&prdZEnfB1la%Lk{o6UH>DRxZ`Xtl#nqyF6Ob_n2V)`t)FBZJ#|f z{Q+)0<(AA7>3>l+RQ~tyJ=FW|nQF}$@QoR94wt=r&hEUAZMz3vAx-Aw;y8-s`;1I|K5 z+*(R{-gi=0yXBw1W*c`Owktk3-oFJ}J7T!krTUiQ0VakA3^Q)esLv9XVp?C}%EqZ^0#PTC^sVA{b0`ZR;F zLVp&Y(HVe^2^IiPuUW&Y*|FBYPaj|^_Xq7`7hhn5CR}4TavSH{yWg^>Rg7zF^t1ijciT?d)`5My(f!b>j`}PR^9d>@_xHYL%a)_-Bggw>huTv0Id;Dm=dj`9RAxiy zNV2jd!-7U6&RwO4M(&!?D>IPMwh?W&il!!fb@)_WFfI8Ojn2y?Wm4Qn8Y2HI($ZdL zEj?BmWwK@<(zjlcez2mBLL{3=6g=vwq|stey#`wv(<|d; z8srDfB1^(i$FDN|QOJ-Z9n-ROs;9`ntYktCIs@ltLn`E`Ei&;@l09)XAdlt_SICml)+X(AE7M`I%UT9JYS#S)R z3$0QDJpJ-0C|!`y+Lvs?4gBcXE51^A1(k8~o`)KKW%x(YE#Yg@RNFoxy@JR%cM%$Y z8Fem|m!cS53I}_HDm}gsK|I2XWWlvpriHYpAWgwx+$;4a(v$`JZ@tukj`~7rAYWBC zz_iM5F^x&0%a_czAN~3+`yh?YD_dXY496esC%1EX!}%J>E6*i=)fLi*rj7Gm$V*{G zJ}7EznV;%m7@>m%J+`j8oF$7)zm8siNv2T>#E-V2$!MN2HvF;9M>@0Ic3g3wEov(3 zsb2FyJXF8wX2L9hgO$ztz;5uX`As2~>RFw&P>bL?mD1{gJQc1ny3W67yc`k6OR?$$ zO*$}%Rx-*VWWg+GsC`TfL|t0IDv7WJ#I}Fm9-H*1J6KUUh7ZC2(k5JTx&7O>Z&U)k z-xCuMWkHT`9IfpsB9?A7<63k&S?4D2gGiQm8aQ@2bKww9P7KLEN zC$BsK<@5ui{8^+O^{2wHt&0eX-H8;RmrIpR?+=k<$oJ5JgADej)A3$rGiEKbd#62M zJvp+_sNEK%r;>{Xd1z>aR?#U0@+5K77CBIXBp>Wc))VKQhrEHrWb+P^6i~o=lD)JKiDl;qcJ;zpl z@JG7TCKuD&WS^ICqOTsL&NroY^(`h>r?V)qh&E!wdTx$p%VL`o)nAJ8Th#YFKeQrA z8Pg($;_`l*24s?JEnWxJO+NHjkq(}dV`bn7Hr&gQf{G!#;G38gTVo1WvX$Ej6 z2#4IF&zK{Mn#V7BYkOz#U-ZwI`hR}uh(^g|6>G2x*c&=2L6-SSMEqyT0VicXaP6{i zVIwgA%PPb;v@YREkwNkZL|2xSEe!gY0hFiD0wPQe;M{m7h4pAp6_TI^J-6r~nJ}FNGb>+>{TarN` z9c)$6U^wJ1CX$wlPSAB4PA`*#M@Z3ke>GD4pb-sju`-HyoF?hjiU?x`JH$31rOrEJ z&}g1*F@j-OR~V&6{7ZOBuLc+jfj^H(FNK7`l(rYD(vWWH%6Ttbu&M8)IVq8m;jQ$$ zQKpC=fPzRT3E?*PmraE_SivEG5ej0e_>vuq8*gw0mZ`uOA!WD6NA@2UZ4Jz`&Zt*x z9#(jYLwXT$X>YSCi)ctBL(vy4;W|a=Ft-OxGvlg|qg+s$qQHqGH=h11o=9BCqfjyu zYQ_gnk53Q%0$-dtn0GSbJMre?kxa38DCwi~C>^ECMkiT3E0sY}1Shj&&)2boGfUIu_mSCqfr>CECt9Yf#i4V!_ZcgW9a z38)`Rq!KB+?(%9N-{h}mPx6lZ3SJ{(;Yy;BZ?MdP`=K1>f7QP!)bhCFZ3^dKhO=TW z92wU_Rzw;!c%VG80OLG+xlmdZo6FZrsq;x-aKcCy!ciJE=s83oYrz)E=*Uv{XdvXW z;DJ%RaXFD>j(p+QCrI?xpU8Q|MM{|sY((~|e*|vu^F>rVu&#aej_k4$ZT-Z&1`EhQ zI4TSBmyhgfLtIb&1$|Xw>gp1gqv;`C5-sfUpS?p-)wkgZSn0dK~3GnT`Umb6NNvpQI0vh+(2#FONvi%0fyvCPXa@APZ>uVltO%A|B! zc>J7~;K4iDipS-EH~wCysUzUb0`mI7eZT|m@n&Th?|1g@Vte-{`@)xRVg-$Da2`|x zR^_iIxUvC~;z=|~2h#O2@8yGJ>f21ZGF>{MBq=VX*z-nj+>sZ+DO2LhKZYuqL)#@o zrU>RDQPz1Zq9dIE0Cn*dI)y|D8*-t3M#3J4>wtI`6XZ1=c`rY>LzZg)usd_4Q8a>6 zo=O$r_-P^!qS!QuSKJC9Kk<#1bPg983~t0wkNzIx`i+nCtMo)jS)6a+rB+KZiVAIx z%LF>;kLp*MXk)?22Kj1p#EZC;5m(~f+i-^{pvspVikmobxYV#WkHlt0gmkGrmW;sA zQxc5CMarZBo);b?M4=V5g~z`@3X_Z{R$;}NWD=6oYFH!bi#7p&0%fdtUMPfqZjg`i zThdYl%6FChO`HGB&WQGWMExE+b$Y=By--9x66k(p)cM=vzXW7J@Pu9C${Qn z-~5*w`4*d|eLP0Jh!eXKCmN!ORsF$eGaC3_;1;%;wY16i+{3EUY2qYeBgyK^IsfpZ zx?F9~XRp1^PUv(R^dOInoDM`?cST%3$xIA8)4T-2FjRGi=c3Xp83UCLH1qlqi#JLN z00VhA5D7u)EV5R^AE1-SCa;Tg=m)g}?ZwI;!p7vkUhQv@o&Rc@kQ zD8hjWDT^}=`jz8?yPsr@t!Pr+Ch{rYyziyD6cZByvZ;(B3y(6>6Y3nF=vUp3Je4;w zX{vw`k6KMUORBS7$3=&WREq36Qhsa=Q}aHG~q7lBf)m zuw?`bv3q8@n~GIn8HIJkQXJutJ@J;F2~GV=8NH5z?fyqRoQ8M`uKbJ;FqgUBnCQFW zpCZfCBHlx3RK!y!U$9_Ifl*I&g49tI7$g;yFe#yO0dsj#cod#E91CS~zZ68MnGsfi zi*gK4HD)r=0S~Mau6#o7uJGc^A;MRBQhv!J;*T5U)Yz#Ii&9X-;>HW1!sUcA4@V3B zB8(77p$Z9Bbubl!r#F+Y%hVe@!G-T396uhCkxWy1+YL@L8U| z6hiS<`ZNXmYxwe4UqlUp+m+v*ez{WRO73`mxI~CoG3i%IzpxWLX_oV&^n)vsnPwls zx55U!pjomjg_WsvWRJaRpYU(;>AZ*}3C{90az^p0!zDj;gVlsYSFJ3fEH9Nlhl8cb z4V5=(K$Ly?dexlE(aV6RKZG27D>9Ezk@CYN{$zKM9+G+Hln~|O}t=+I0kb zG{Vw($wbhozg$t2KhB}@N&ZmtXn0EoV$dVN&*O^Dq1Oq>P%?@7L3AW6h0#jB%AZoM z#!+w46)&;!5r3&|%MnYK6rGm= zflK-CD|^nf^qu|*>650%)QfzRZn}^?N>NDVIWJ=ZW^`qXI#8k{u3Ti&k7UGya#AsY z1&&L?2@5B9^uV4@!Ty3tX%vc=-^3#l%;HZdt!u%;n=$K+lbtIuukld;qfgj+-Ri2QZ{It6!wzg|6pL>Tz z4dlqlh^MKi(ij?A@{M!^Kam3WQ@%J~;slwwmmWz+Bb?)UdBCP1o}W=qrvZUVa7=^< zMKOhLMZJkUmESV{5?xvJR+*BKTY8z{IV^hw0w_t>vdnWh59dd?O~FMkqbYpHbxQ?2 z(XPZ#@T4$uC%fqAm)uw@Okk@$6%P6(bn%n1RH~1HlGocRyKZQkNwc(4Kd;FX>rN*~ zxe{S5wF?pY-F`Cvm$~8C9L| za|vS?QH7Lxsgr>f~YZa^s=id?^>Y!orYRfZ8RpGhT-l8LaMAKsx+XAjB(XL5-dC&iZH ziSSZ98Mz$a3n2y7<(8$y>3}C?btJ)Ydn83ZoD>faps^-cN;TCUGaQkJ!l?+8$y$|( zZlDp}2Ji(|flA(@r2oj$;9G7t@D>enNsp9PZ^)5}WT|+gfVwrpxqM^||08&FtO|Y| zmPhQa5+C6#eCV6+Uc|i7M`t zer2D>kw}8+LjB*SA1Y2Nrz=3wBu1qRRDkY;6W2GDH*rPjRtH&mFD}V=pLr{?D3ZjX z+%82W{9Lk<9B%n)pdk;rWb>2@f=l&}VEl6Vx|f5TAkL6@fTB(S;$=V_sNoZaWE}O5 z=&Fv3@|I{To|Jd8Qu38N&Az#pxC2yUIs*1@=_UBIDfcB4ov8P2)O+4 zm(BG7n0MfjfkMQ9RdkAagq+IgO0Jpg1c!;}z$Ci7k%e~-B|gdV40F!1y4=Ex}wo-3xv)nc|G}lJ`CXEUZjco&~g_Hh7$6q0pw<+xrS3J?w3@m?_Jn8dUc4tfvw>WyxkTvqxfwFsf z3Ee4SLl_6XfmSB7#7lZBX33PF%U5AMa27VotC!7;j%TTpE>D@4Glg}YR2F4Qa*eVe zsD6r*xWSDiNKS&oS7HrUnF)e`a*KOMxF~;Gb9Z0Lu~q_7nbT^x!g=13fT%YVJ^BJK zL8F|mh==yc7Wx=?@UA)rbg?QuR^6b_k(9~M{q)EZbR_v4Bex`)USWm)D_C@A6M^)O zybB(bCQ-?F5Oe+?9D6?j#q?Df_;HOTK`E&H*Q#;4Sx@18dQ5e+M z5`F2Y@=iV}9;`*<{K(HG!dqdpymeR#_t)SGUyMib62|McL>o|6J|a(=fD7fnX}o_& zoLbQeO%vbZQ*`y#FZh>qqKt_r$xv>zQ*skZ5))QD+$eMma@9V8=WVb9-T&Quu)C}% zydqO6!6o)`NR}{FB$3EOL&6BLg$)M)!Q7pP%Pl41Zi7zdbu^5M07p8H63_=Do{!=L zq#Q3SdBvG4azaUnp#<*A*~At91wJH;6sFRqYOKsV-1$@CGJ?`C!P6rvn7P}PG7ng? zIKCKkOCk_-LKA~T9wOo8fXpH|l5%fJtjK#4VL%M1(jO+raz9-0U*HYA97$p1h@YN8 zDYNkkAK_u-$W568XwhA?=K;zrIYw?Nr%;Amq~0Wc2`HPV0gLOGdqtIzSags-gi~r3 z1kz8~PKB3jGU*+zrXeM)Be6^2!d8Y+=BUI>?ktZTw=#fIk#qoi(Yt5|OHTdNz^T}| zhUGZw5iV ztVOy-FOS-zlq-Yy3%nviV$aq+7CwI|{*0=32HcrJgcRur4D4CBGLpGNp3!!N z+#;0hzHK#yEmPh+a1#~j;5cqfTPeVedp zBeVLqP_pn;c@9lhxIz>`wCpM0qTU2AC6th#Q*l1R$0y`u&5KbAm%WqIV-H8Et?I#Vm?!h9gM-)->Eb^NJ3zifnf#g0~n7;&XijH8F@I>Qi@FE|{ezbQ+pE4L( z+Z4?rjJPu$NPK0g9?l|a&bj!_Jp5IGbTrofkKz9X)Jm!_c85w>00000NkvXXu0mjf DYaiV| diff --git a/doc/images/nile_shielded_usage13.png b/doc/images/nile_shielded_usage13.png deleted file mode 100644 index 15e9f266e36aaee87eba8601b2e9895ce88d4d55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92096 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

{u*tC6ZwSfY3jCvxZ#ul~f3)YTre5bO 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 diff --git a/doc/images/nile_shielded_usage14.png b/doc/images/nile_shielded_usage14.png deleted file mode 100644 index 6b4ef3aec66fde820ec979bb43c7c22bd67513a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66810 zcmZ_0Wl$YW7cC3~m!QGj-QC?Cf(3U7dT=#jKbTrV@w$D}IJVGHz6zX3?33|HS`8jhJGQQv--0A59qAQuT$z20o*^_&2mN&YIrm8pExg!g6VCB z`Q8o_*(YbgoI^XA(WV)!qR8LQVBs?3p6j+ml9}}Q=1(TEK^mwd?XH{}m|Q#Snf}si zB&W~Fk@{z{nb-B1?(14>O~CI27NoEl;6+tIg)++ZaR0PEyCRW$!BngGS<{ahA&jLt z<{jdnxlGWE|G@_lG^pkeXWpMW%0*{VJyAM2XPqu@F6CE)0INUZwEC

`NN;HV59% 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 diff --git a/doc/images/nile_shielded_usage15.png b/doc/images/nile_shielded_usage15.png deleted file mode 100644 index 2c4c700c0f477777c6416c9f09bd6d71f4512e28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38182 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

C9@QXy33Kwt*4wj{~#UFARLPDZ;oDDi1J; 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≫eBNKe`yhxf=4TEyugl_#Zov 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 diff --git a/doc/images/nile_shielded_usage2.png b/doc/images/nile_shielded_usage2.png deleted file mode 100644 index e75228245f143a638494404325a432336d10ab11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275417 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

Sd_pvkp@loLe!Lkw>Hy6!X7v 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 Q%EP0ktff?@2z~qi0AJI5wg3PC diff --git a/doc/images/nile_shielded_usage3.png b/doc/images/nile_shielded_usage3.png deleted file mode 100644 index d18c029da1792c0c59112e2c1e29dac81f0fb33f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60145 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

#=Lo@CuT7eXFxF7sSz(I?Yg2WE&ce~khHuM6Ms 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 diff --git a/doc/images/nile_shielded_usage4.png b/doc/images/nile_shielded_usage4.png deleted file mode 100644 index bac098e7bb83a0859cf93a733b9feab34b1e855c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136095 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

Hx@cMY-36`pgxrrjyw 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 DdRfA> diff --git a/doc/images/nile_shielded_usage5.png b/doc/images/nile_shielded_usage5.png deleted file mode 100644 index cb491c1a94d7f547d9856d05dbf4f2ad1aafb79b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121678 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 UgmtUful+53h>3Qs=98%Z0jzkukpKVy diff --git a/doc/images/nile_shielded_usage6.png b/doc/images/nile_shielded_usage6.png deleted file mode 100644 index d222ab0aec1a819d8de394b9606849b8a3a782d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114502 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

  • Auq&UnRu|gds!hS*N^hkuJM#Hb z6?|$p;}!)QB-o@9Kdn0RUS4LzKxqw8Sz-8>JHiknNG=dp6pN1`appQD5ck64PB{Ff z!NFmQfkCDHuM+B(U^F5^i|(91kV9z0gWd7I*ZrLD_m7I!yKdY)aWFgNOa#{oY|mHA z=6=&$7u-hCnZLCGN)B$=lJKuv<&Mtcy6r)PixmTz-mlG~^;8D48@L0_{j=gzyMvLq z&)R=%mxrz&=+|5a`e#UyanQP3aQ!aYd`Nr; zTa6n6u}8!ASK7Oe5gryZmMTJyCNX`fXqxz&PqX&V5g)C3`v2BIu7m{}VcskD)rCJ{ z$X}u(7*SYgugaHv%OF05OgoUm#iP%6Zgw^qI+|mTUK0CP!(9YsKGm^7KCKl-&WHQ2 zonH5wtZpCgc%N-$0)6k%#h)_(Ae!DrmjIL1M@Uw}JBk8z{C+0w%PylApUwEX7lGK> z)ad6oowBh1+IC&=f4UC7zA7ZH3G~fZ4D*L|@){gBCmOnCzP?NW-#8CW=1<7apG)$( zh*?IWc-Tvw>~Tf7nP|5CWyaQ>OcU>`ZYEcg@X;U1c+WYw`oEy<3AeNPChle}jg8)3 z{d$(Ts7(s}u66REw2~#2)?lliI?G_4qAQ@u;2tflwZ>nbksv^4aQv>clJ}(b{J;Ed z{qG-1Umq0u?tJrDXepva_3BwgTbR87?iarYzK_G-ffbBy74~ivW7k|M~vtt9;~>*;Pc%{Wv#aV|a6*lV$2-O1xKT9F;P^{;XAcsxmTF?&)i}_*Q4*AJ3+-NGr6vDnOAueY|wgWQ1!v9o#F%= z`wDYX8)5TE_l^&vG5Bsvn15fap7K1K2>=e~C%7kRDNJ5YO|`cYKE7I;EqYYH=T)-m zB(wS_(sJf8?e&xI`Y%s;%gnEX2ygg?%d{##@Tee6KFvipPA zeS)Cb8AzdFROGaUIVKlsy^>=`%UvrY`?G`#QeoC4x1LJmkw;X^ zD`Q-%jI0bcY6^(Ki7tUdnKHg9&Hto9vYTb8c{u&ckW}#xe0rkrIm!N=-f=#i{K|-; zoU2?KEN6Ks{2i+DjX1EXLCQ-|)pj}!z|_MkYIajE@|~!&QoS&xO_4>b-_5UWKertR zmpGr?yhg^XM^WKfbyQ~L2_=Q?D*-(3GsjOjnG+BJF(w1c!ZF(vaZG6ecyOYyzas?det^<8HR8;QquEHYf>fGrVuM*1IGsYbA z5T?Gwd|CeT5=bV@O5wBrJ7fO;)!hf_?d zF?LodTs8z-Kof6!K1yAQ`Uw@5u-N`Y>b~@&AmUrINnQBQ0r*i`LsnV(xhTF}7)xfA z<(2!EWbM^;qgZ#s6ku1elj$|aB@|n!&R@uQxiT;1@CLqsXsWQ>h!*Qp=~R%HlrI8$ z>~IFl)V-60seWF0%{3)wghJvD&Kn5g!q^HaeEHPQGB3YKKX^v->PeAE>tx8dL)Oa~<3=r( zfdV59zw(y{DOe+ar@ugpo;rP;f%MEb30vvA_8&zKOX_DrZ&ijpStLI48F^wm9pcH% zgWB3;&2Ph((hc4ojvSOSlhg8|TsfAGR^T?+ZJp5<^KB*Q~?KzI{k| z(XPUkUvv|a)4@dKw`VvK?q`C{EEaSWrJC!9dy!X$_vw5?(^I_M5EdRH1VkjIqzKnM zbWA0)`d#`{RBQ|Uz#{C?m`Ou&ji7|Btm5sELorCznvP--ZDJOyxJs+yR-Mor3YvNc z4D@94H)VmotCesTaWC{mOm5nSnntefGd9}KxsGWe`iz5WN!uCB+YBB?6^!NL0QhO$J{mt&^_#*swU0@e@?p@V;}T!aVi4&SJGZv|Bm25}P6- z5*ahr+1%Mq`$zTm$IW0a-y3+ZzOe0yXtNxxg`ZAy$JF*z$FWN+;0jx9JCU^zqSiL7 zJ~MqOfq-yY$8z!(4^x%~y#1+~Y85-~$yWy`r^KqDhl#V;rg}^smN=)u7Qq)|@#y{m z+!4)2i$?d7mWUJ|w~S;imaRazNTr*{eS@c#x#X7~Ivj=eBM4{XerHmpa>?SP9xOBx zkdT&1bO-vMX#ULb&36M~aPP`UGRv?RG2b(K_OsQ~v3e@}l5o>HV_1v>R1g%a*7z>&L8WHcQu67J^oi+kLmOX>1jPu>9%1Qs;Y5{O$xGlk-rmFYZ0C?FHkuGLCU}YhEN2y-`BV{BjmQUEnOC z3T*#&NcWbC6#l-AiY#d?vNN`lWq5U|qJ1(KhJw!UUYf~i8Z$-=Vbog6Y!;UAkU%MeIPrAkKR z0|KDp8*&(1jl!5n1lj-{M-A$!zzV4_;sSve>03orYB78?q<~xvX?WU`aIc76+V%n9 zj*B+WCc!Oir(Rp&Xmzn+KB?>z3Z(WvCwoC8!vScRIC;h)p8xE$c3MV!pkg+q==L8X z2P-Ur_(+~Z|BkW}kX4T!l?%lEAjs9N&4>U|km~CF{*9l61+&6xd8J)Q9uxYeTzV3I zqo5ULfcKy-sMs2Xe%?m!|Z?0hLbQRA+GWMu#C z(-hL<*&N2)I6vyEj_}KjHDbvn4DR6!XfQAKxAy5DFs|YRtQdb0WGozT+Cnisyfh_B zrHo6eHq!81ZZH`WIg+W`Go9P@9~VH&?#IQIr^tILB+`5rWOKHbnlp+&V8KgX5o~|| zsV&5Cb2aZfuug%6z)ZIwGF`^N6kz*gyF}sDv}+z4M0k=n?l6gotusmZ=uPyjZN(~u z;ENDThid|-4tg%W!k?YRw%Iv2itO$d)aCN|;=Gv^olk}>7W=0#uZi>R88-j#jOoQ3x`j*S}If&zU{GFIZQTsV2Y}`Rk zMWy;b)-plVm!+|gI3afb%((q#F2McWuXbi1cst#$AV*{U!Qz>{nPzvD2p>}t_6gN= z{knMi^|5ayzZJoAVPQJA$OmZzKTiu)w~AB4lnXVpq*H+f@QlwC26i9SmTpl>y)MMo zQaT-O;e$CytS%-0N^izWXzV4ozh<9vLssRoR$b1&Js-c5BP#V5n>cFa%F(LNnk!W$ zQ>CdZm*9^%(RI{Im6ez&vpffX&c z;7nPtLUAmsnU-O^I|OGFX<0(|g+qSMz@1)SQ|MtsEVVl{QL-wD4QR#`WcTVs!=1H= zk}&0y?Twh)#KD`U5FQF+WLdULirgB@L_$;rq(%IRjk<|%g+2gRhcER2uk%%TFY;L| z!-VK$Fa8z1-GpV09%X-S%Bw%bFei3e5OYI#3vZthjuX2sp!EqG6S07G4;j%N3lJ=R zEiKYJR8QnyCFMzG|Kljl*(sPI0S&sXYpoF4kd%CF^odKI<>UlPnD(&ymt`qXBspZv z!VxHz@zCgKLX52QST)H`7P_h|!W|Z{z?PTdA&EgLlh|hx{Q4aTk2h60mM`T+XQ`gul-B3+%-g@zgC~; zjv(ZPzdz#?43_7sKI=A2)!|n)X)kstFn;Ewoo>;ithd>~+0%DNH(=b7yqE}jN?rqk z#&FLT>?2<8PxIs+36{NIzO_ru1&5uC7`K7XRroa3krWWTzk*0Bm5QABxR*v1%-lp* zP)VtGZ^&gcxxU)VCJP=oAzlus2sNpoKWxvv1yFg77U)7xW%VtEV`MA&zZ@^;aXw_W z3kLGw!yS4Bq3b;U8k)kLE)hemw_Jd-=DZ-&L$cYd#$XjNi1@D5I6M{`=NHIoX0!xm zl~f25v5`enwxA;)+7hv`p(Dw=BH|(b(^2F^4U3CD8-X-X_=@bG*70J@~p2@d^xC7?$K3uGoy}E<%9NW1Q6>hKkPpq+n<+kYi zmbR)Ejh%MJ`DL>n_!%*TdvTR7=GuZsrFeBC)_&HXCoO{HdIT zMO=t|uSkLrMglGmM)~=(*)uN%sAo$R1MzyTx$fcWSbQGv7<4b0L2(GwFpo^;4>oKT z(+L+i*=lVM%gtw;D3KY?zur>`XBurK=F9#MLCe{bUX`@>P=TBL+`~4C9?aWRWjMNK zzjNF&X5_j3O784W+5nemq zB(V_?8K;TBM}N82k&lPXGQzbUO2hV34%eWoQpcZD-yJfZqBE2cWWTt70-G_8}c&jO+Mh#E=g?yyVzqz?!mv6mMNKb)AUStAl% zm(RXo8X1RFE8YBJ;RP6!vu9~8$d z#?82O;Yei3QpYtHAa>2W0~Q>ef7o{|!JPS|C`)9|aY(r4_4AP=#Y}Ts6Z7l)ip4_0 z4PXYnO3nk7_aS|kjzcznZr5F&RC>x*OPbb)H9jtmOZ>&HZ$2p)UmhpQ=_&(IgDL_w zY%d4F5bs5x@vKAIMd|34hvLonRi1xYDx@Wl*UiW=NdR!?MYVD>+$ z+afxh&BFgIX zql{x_2co}odDD3+61wn3JK}+NkxPR)=2MwouzKG!cwD=YWI)?>^BWlP|fAv!qh8xcZJg--m`pgj+!b(KEF;8#fC*6b7?*LSv-sG z=lBL|*cQ0G>i@^h=8 zGF!fhrgtF!p&dfZb)QRW{!Izfmx&v;zt&VT%{lZCZ;s-!2fLZdf`HrhKCkOuON zv|qHD#^kKZ91L)*s*X-L>qb$n(oTBaZ6qRAdboh$jE``l2`gr%L%LdtTCyZ-I(Z?R z+=vod!CSdLg@a&rJBiLe3w5vC6nS;UkFi1$Te8!uzAZ|8+TJ5y9lPO+jEiOL`Wr;A zy%%UWwF4faokMQTGv#gd#cXvw2|ecy+kQTh$d*ep3khkL)1g3MT{I!i{$mjlbd2xHiJ&m=R{5rN@kkLU;cr%3g+$6oe)qY z|8j(yJ1#H&E7bb4X##uso6ch%gx5^#JF3gy8)vDo&HeUy_M|8TSt~3i~V~Sp?x!zZU(y1?ft<* z3fXoUFBY0r%#ClY;ZL!h$PT`DKiWiV5dUX+9Z5uxdlpSZiq?G8cA;Gy`+j#=>GTe~ zcbaE8m*6WH;&B=XrWxoF6Z5jka=l6|&8qVAc+9YOAIRVj`;6p2e`_-fRJNV{cFI&`K(Hv1NTZ>!5vDMA5x;9! zMspxt(#aU1*v3(%@ux+)v;wrT%soF(*;c|JqOT0^S@~f1BKqtORZPH_sUk&9eY#(A zQR=mFdU{#_Oroepvc!&Y+=>m!pDTA8qHVKxWk0VtF$;)CRh$eZ+ zo-s?t&dfU%jy28YD2FZusthgQ8tW5%s4YDBOU&o=y)N9RajL2Iqp10@M7dkb2LaAB z7*#fXI!}viyZ!-oDy2>XMx_=xR;T?ltmX0LsT?Ia7!zy#&O$shzes@bm{X;0v+!)?xel$Ae#jC#8h1wq@Z8Stosu$dpwh67M?%ILR!KKkh_KKX7 znd&WX!VAkwX2&eg9=^Rq3$=5%hpWW3HBjUxu|qeOt8NmkaNAFoTJL}sqU^VT@#r}Yz~wVS{nWKw8hR>A)%NBy54Aap+*SVOF%VY0S8 zUV(-qy8}-rLkrh?$~mF-U7(IBjQe9bEUshA z-&S*Y1QF^@N5iHJkD-0H{!q6tb@4|^>k{Ak`^DqrnT-`r-+7&+b6?hBC5=WU8yCeey2;Pe83f&6 zyYtS)znp+@9WT`BnC}F$yP)+w-2f2MPd&8u?&Sw89l9;&Se>pAr>@6gX6&>z72|SX z3@6UmW+}1}!D*emqv*SZ1zH70do)@V#Amquy^ihZ(L@>|#h{pz>(UF!ZI=4Miqm9x zpQYWKNkEI!-cyQ$h_bJtc{LX2PUeH%7w_y>&3i(v zuKSJ0>FE~uv7n(;B1_F?vx(6Mg(&PiDQ5SPOSuH`t`EfE!&O`I@~=J0(U5vDZg~+- zJt<-NG_8zj&B!!j*yr=wr@M4`KIRPX?0!(&>W#m>w^C*M8!pXIldD7_Hcy{Lrqjj+;a$e3I!>caCjgk6txl2gy9HH*gsII|TMi%r+hu>y zEK)_#TX#x?KPY6o^Y!@IehL!m^w5&o8ztcC>3Ds%;s`PjMhAL+&$BrRqcSK&)@dD5Xm`77 zJ=wxO!G0|{iocus51`p4;pfpR^IRyL+FxJ~k>!a})yb6*6eZx*TrTx9OWxX44<4J4 z=I8|8ggBH38l9ctdMC{YE>U8r_V)k&3xFoL##o{!-m_nH0O$Edhbo;CEu0@G7_Wyr zc0;1qvRcjv5M>bQ_mACsX;m+)TvnJsn7^!F{y`Wz-{Oq z%L{hk){aZ+S>wfZLZ2LJ%smc?Bd-mqB_%vmDsU>}r*IE)DNSsB7i9;$EY`yvR7naH7VGiiCZmzjdzzzz+?_SIDf=NSq zJaAX)ah~>@tI+FjzMBd6bRc_crMlH8(4IhZaC|rw%Lie24M#F&-QN|~?AQ;^EQJpm z_TYJ}*HcJSvx&{LHjtF3IX;6>g<6DnWcp-`Q)F;VR}-^^Dect zt3LhdN^G;Zfc<>J4&E7@R!&Tp2`Und$Ccs2wXTECxPY$o1y%wSkuGKsh`>Oo4hD-m zwr4#!Qx3Eif+7}Z2*hTjaK-%GOP$4@a-r313(^vviWFNta5L~nifaAfsC0rHP$Z=1?f#5&{f&esop8YW)ZGQ}@ zqqb+0PW>~9Jmt<~P%GSF=phL%pZUv?H=%T}u+2~m#;{xIA**JT_tO(bj5kV(Gnr>R zZa;xoGbhdw+~}pumn_*nX6${ZvFn9X4n^r7+6pkueRH(OrQKnTu^nOSH0}I+6 z3zBLCqExZWli$s|d4h&N5}ic|67vyO_tpm2Jt3QCb^rP9CG#LM8t=SU zDSH5bzAjM_cH0R_HS0)fgfB>ya|P~8VBDab>84io!$+R4i}s+6vGQK zf*Oext7f~~C$a?WVZ)p54Tec??3Rl0$K78%a!#ON?W8sgc8+h*M9zkD_l z&ftkjXklJ3UqQ~M)$l)z;O+O*d8@g8XF;acg)l|`S0H}6N3PvKf5ui3>;(QqSfgjG zTsTa3r^(KGwNYG%%%SG}0psuQuR!kt%lm^~G??q6mHfe=MuNw!dOez^{ZU&sG_2<) zS&WGC3fI%DMRHEGt*5UlVvV+#6oXorhwT$t_ney7szeb{pQC<1#Af-VOwn-r)v3k% zgEXp>absTelXxJgtW6tSFP2H+X>xK1ii3eM{O576on%;hM0xQv5w#e>gWF#)Z=Mb~ zYs{sdJY%X>7s(D~Y^kechnOVvF?(ZwNeH^w*iQ<&TTcnb`!3N(${Q!r-KEC#64ge^ zvc-~c$h}#pk*l~6`&)1Le1)yAVx+_7`H=>)V*TZzOHQi2cYRSIoM~`+!gid=w57gE zs${eG*L+Gh<@{Q0*((p5Pj6R~{uiWTj3GXMMRxSp51xuoV{niUf94^^$H@(ceOhu~ z1XQ7yD_Q_HuXp%w1)PNn)nqsX#al~PusgI$UwF|1?lHGme(L?}tv*X4fPj%8bkyHm z(>Qjvmoisge)`tk?3t$x8k1#e9Tv2?rmp)fy>T5Y|e%`y`g5yV|YE-gV*Tmt!$(aUyA;x%vy4l&m6X&on%~+D3@g)nSq@?GCv8?WWP2NBf z(ZTKBE`ZMv0;PX_%L^Y@vioA0CU~`6R#-77P}uQVg53R2znNaqtWZJi9@4!@X1XCZ zQ3DTN+HbmIGQj(JSSntp;n?Q;07PCWVNrY*m;!o$V7S4>P4Ud}DjS32`x|pr33r`m zPtESLiIZ4^DSt?G<*ClAH-5~84}NPoh&82%h<#22lx(XaJ6ga%r32J9u^{bs8K zc+>xcW47XVJqHOhFm1VvPZw3mI0%JLP|1vK4!cmMs`Q3{dCQJ+FMoCGU!qw|kaf27 zXPQxPv#J*q!Do+L)n+Bog)cQk9y1*cTcTxn2RiG)K-}k~c}|r0;`3xBx|d&6R(Lpi z6T$6@9wZ#~xNgof(jsM|Ee;F|4VIQJVS<1GoSjiSTEZ}W{EvtiTE3*B_|zOa+m#Bm z&mqhJ>4q?Fm(=;Wo5Sq&si}OoJ%u}e%>G|{#B4TnC5aBAqEhk?`>Q>%jYr{shb`2S zIsV(###Bbt%6|^cW{B-!|8Dwx4?#i~i-{K$R0KhsKMk8z4An>(>k$6E`j6MlG8GOU zd!^PICCg1TS0MiAWWJthOtq}Phq061Q_lu_iwT{>-e{Q4Q%&auvy$O>Uu`8RrO<7KR3ZoL6ZrkOektb-d*&`u9W{@IRI+t)dog}(}*62QHNhr3Tk>p)4< zfe7KiMz*o55X!=EhMqwVzrvN{(z1X$^)5zX7UA;u2{KyP<^%*7J% z{&$l1mZD#nELOgMbvTK_haQfiJhNBo2F)hWoI~z<Vx!hHEFZgS-c@}lW9 zKUJ7>3!YB?Q^b*UGU(=(hZ@LanXa!{E96n!;NepR@_+GL>tEC6=q|k-w;G|LG1sDd93(XyfV+j1fu6hLcRBoAi=^ zG^NwY_s0s$8a`k=eV67fO7ni#wt)~S3cHX8!nQMX-i=m0qG%|a(SZ7A7-bdR=M=|m5m8kMTnfkDQ{AFFX*4ton4Tfm1Tr^{Km zOs*#WE`K+LbU2m2^Ci%eI$%sD(*lK(B=Yz+`pOmZNqh4y1+_V@v=_f%$$0#>wA(bQ zw2d3gmi~1!K?Xq`h=j$F$TvI7B$O&+xY>sgu)flsv6@f6dj7xmzk4?dsh<#S=!%lGG63vBR%;+U&v#--B(7GqW?RvqFzQ8=^}zN;8s# z(9{9{twVsX7k^^tJ{`&}oDBXTjR>MCD-w%xxP-2!Twg*So|eW!(dDUrHo}XR-(x~^ znB_&=f0y*g1th6-vmZoH1axMcb_wdrmmcXdhDJ^UoW>XeY|kXKAjzdm31>&rOlVbFV!%&5uqNi;1Y z@#Oe|!5Gdci(O%O9LESfeknz1w026C-w%{J# z^S!sc0^h@}0QXHaO(PyUk-Ayw0egh8HiX2@uRi{FZETQN;$wjU+DR6q{w=?FX~S}) zE57m5w?@|Ozd+%#y_i7_tXUu)?DVBRItFsX(8!WRANL>Z&dG}E)-v%&tSjvoQHlk} z<|7FcJB$i;41~@$5l$OzSZy3X;TsQS8d@qGoYom4BE>N%LZn`-6VA=iJiP$xRV~`- z^+O!#5pVm^iFAiL_WCLxYKUtDniRW=j*cF2qTi~4C$pyuGJ1Mk;M z<*wNN{ae@ACkI!ji9L{$o!}txNT3ic z;cs1-NxmMOrAk%EpUhAK;wFi-DGsZcjHkdEr9fyZYMpFw8lu3+1j8sJouY`l`bNCT z97c(P;$lb-AvY#v%;76HCUgm1)GLXoBjaj;9qQMB;wG#+x_oD5U;t9LAOb*U)KP-v zM&tg)gXV9a%jb+)g{4YUY+6N|UF9MicGvhk)qo};p?fW9ESsRk%4+n`Olr9xfXO_k zk4B|5T9eoX(bPATNvCddvrICFr6Xj)Fp5YY5RI1od3;xFkTt|3MvV{zc%^)gMEFM$ z5s7UYd6YjK__(o8AsUedZU9}&l{Wy%ipl3a-u*ekJws9yjiIzOcbK3sjg(|;PAI}i zXT5h9J0N%(rO?IkIR~h0!CGZ}mK2g05Rb(9V@2@Jm`ne=LA>CeSx%uu$ESkMs4y`} z?ucnRsLPy)Q6iU0Zdyr5;7Cr0a{!$?09&fVWnc;P*GM3H!3C@ZPqxc5r_ESQd**=t0RV5);uCGqC3b z2QXi3v-V-iIP8=fir(PUF5?d^ZdhqEkykCupuu| z=T}f3aJD`{8H8McLahoXhX^H?DQJe5ggE7KJNC)qcq0|~)u}6R4ZQZdX!jVK$T-f} z%;Yd$66+D$`3^I6{S0Ph0gW62Ph!^xg*FhZah&@-If-Apd(GE+IUdQIZfr|*q^PK#cEyL(dl*#50qguG0Ke4$s4Q&uVtRezCm^Bn);8RR&Cpm%|Ehumym2 zl)U#5#vq0pz^CAvo$%kT;TzNQ_2c_5IAV!Q|0p#BVqY1%@UgkLUhZXB`gIopu|tLA zi~99_KUp5{TVElb{~O*DA)eD3bLHZPQ*p1v_al1UlpT(Jo@Qn2QpH%c0zxgRc?yU# zu-+^|IzN61%{)UAB1HmyyudvF2R_L`W}ZvU((CQmLq&UZiF=znPh``UO(0#tnV=_F z8N?RFh5Rqk?ilwNQ^-!{(lpcuTFqb)si);Y2j>2TDZg|QeI92IL!xC7M~YGJXIAtg zle(HfA^C4h)Va6TX(~ouqxvD$tqprf1O=Wbt||1}(9b~I6^XYBAWFX>`Bd_D7r3aq zea2;?-COo0NZnbMf|DGQ{r4aIAD`bTL7rv_m8$e3Z<3klGr|}gh8)BZ=;feOaCV#R zZE<~3_LUv513Z%9#e5JQ^>$BO-zR!{(HdK-ODE_BF?j{0_d{6a|D5JwcYC9p9UdRs zW^#!3`q$IvL^^~AOy|`RS5Z)yb74+df|bs7-R<3QnI6S6{_Rt?@krd4kM;N&V zyB)FJNfp6~23={f`R&&|9LM+0ITs5T&Gf=~#ogAuvl^Q`dVKOf?!_A)C_h`%vYu@m zXbSHK<x3ndQ6xrEfwiOG9!KX#567^pq=X3B|?To{ypTI zcM_F*`8+!mQ+tRkT?GWJ_YK1T26j(~6|`)tqJERQ4NqzD z63M@!!8=+VS+_#+i*>Bf5vh?`Bx$VTIzAE$9ynM5R8E)~e6a_Bf| zh8l+M?rBCwMiK}neb1|gm^S=~u2CSTLeUm88j2 z+#IbMaj~gXgN0MvL_C;2g@u%ZBqE~Kioz{4lGLJ~@aPVMZb6DYZD8hkp?LrEKK_qy zV9`Kwf1(RDs61V)kfTMms`I{E#71^L2%2vwXldRrAhOI;deVJ;9DPr&7ii0=!6v;W%{LfZUqy zPbWmfFkDJ;z^mlpq79nr=}+W^{(~zno4B_k9s?#DsN*iuX`Xvx=wiWEpE=>vwc7ND zi!G(#Y-VZIicf@hGj-!J>;0;PWPrB{ZmUn)1Hbz*?B&4=+VSj0JR!PK3?H+`>*FPq z8`tQ8n2Dr#A$ftGGi;IeRo0~ao$n{(!9r|Y9Kf~Z=JE7@Qp0~dDG3Ddm>~OMx%hNa zpC7Xx_Prcv<8{`hjOKGVaq%6hw4HmLqhd*eP`q*a{UK7$0xjGx4~`g4d;D&uM^trW z<>GN{l?f4a?ErE?nDF6K5z3dkcm&M)fP~0vbmk%40vy)@{ zgC$&rOWqs2D?MLVP2@ z^BEUsc#cujSp$LtgHT5EY`PMlrn=>`e2kN%yN+?q;gM*yl%BM_uDV_{TaS*~4+E8^ z5>BVNiH0AqX+;LL*C5_tNC7`1t8^%|jDS@-irUInx1YV=@0b6H9a}1qQc^8ZaiBJEj5+z6CD4}E3`eG7&}QyT9IeJ zrx5aawhJ>Cg(vioYj#@YTa81AN32zx2`dq=J-mOG1UP-DsANH{_zC~!~=W7x+ zVl~A9yI&P0`>3S)EmWwt`2IKce1^c51#7_=J4p@}Ht5w9SUl<|P~CAzO#bTGTPgoc@IrOaE&IxNCAYpA#2n*mZe!9(b(RtCU*M zxLK`v#npS{KU{8!xWl0P8;96PN#Nt-a5W`%rYj!IyWx%gIgU)Cl}Q^5sXr3;#+Shy zQi+em@hG%RRy8s;G5D^+7b*z)TWz!%AP-ry9XbiD+EZm zYWqjBd$FGhgDMpk1j_QYD8Vr4(f6QZZ5K2$|4li;c2f`c`QNzg2H~5{^gb#3N~pA_ z#f0~&-IXz#jiI{Z`PD0w%TY;wve$){Yx62ST+R*Fl@sId(t8RLQH>s^lxlunGNw9D zyRJU&8*rGPNZc)kN2y<-kq$?qlYEDp;+kvT=?U`0MYElXxF26V5VvcoGVHD5)c|ku z?oV8*zv0da@VehRDgd#BlbTJvS(Wso(^T|sW~51|hy8Y#L%9%F6Fx)j-eJ-gZT0MZ#>ahu{q-V7?*+p(dqN zLdBPUs+D@DqQz1*9hzSE@xU~_nZ)qwbT?lF zxr0+blsv!6nVGT2{|FV}C2>5NEy@pNut@KnE60agJRUaG%f<4HIvU3?(sL;_TgR>s zZ4tIMA|5;|5EW`_KlX>z$*uk`eAgrP10vAO9N~RNY-GuaPam}`Cc|#`aIW4dk6F@+ zI3Su!G>@=G7$M%kZ_hLJ{s}9tgGwcu^UU>hp|Nn{>%_}^y*6f{Qbi_-TAO^E4nV*$ zD5BV?$%Em2M{kY}_|`N4Z_Q54u6OrXBU97{I|!Rrsf|R!A9d5 zXr>YZjxZei74wCV-D4Nvct8Ia`1ObKhobHa>FpBKT^6IvbuII|J=N108!bN#&9#>G z97k_9%=#JVeI*CZoB8#EDxD_(R7KVKz z1HuorcLrWFEx+DC4lzK?v$pe%3|fh869d*EeF%&?N_|s-&z!`XK~dVK+aSgCWrCyv%+(rh-U$6krpemi+GkDz)y*b&mtwMB@z9S%hp6BwePdsL({%=dnL zfz2y?DJ_2t6Lr6(&3Ozv-|ZpU@u{@rar%oBLo;MCNOrVAinukpX@!{f)fVzf$8h%7b(IFoqUx+3O2C#SuVaDZ`zk@ zuY!#p5LJU8El6+lg>PO1wcb7@S>#f#tF{g5`pjGvX7ce*1R&$N(y6;Wbr6yPD^nY@ zvOeDQs`PUh)2#YEK{QMs)oVe5wQ_MqVrZtQuvpXh0bdWvmtoyMexuEJ|3v@PzoE9^ zZYp00<}J0_i(%~mUkJ|K`OqZx z)8U{@>-%NYvpjsR4SuDN(}|!`FKYkApFVtGH${ikXeh zvpZ#Qcmw6o{l+<;{J#pm)Y{v7Zl428$0(XNLN{gTgczo5AJ*w`?$3D#gAl$gi)V)I zd{ig$i%%t79|7;BUbrtY_!`)kE5=Q)BBCdQlP-#%BUR4xzo{c@pD#S6)y5U zQ!VRV^!50T{?7L1Ju~k^leGbcSL5&HX;NIDEw$w|fa=C28gy&v0L9J}@3qqV#-(ch ztGcds^2Ss~t@dN4saNYHRA`?em|dIZ>S&W=KFALx-P{S3|BBZhYQ!3zwB>F$bo`v@ z;Xl>b^E5p9a*nj=V6Qh0LW|e{31GmJp?w)&3E884Ea|1S!Y6_VFuPUW?$7-Xhe=$F zlHHWJJ7JIS$Y!iHgzFZVv|@{T8ndJ%K*ZlhifgVEYsc+f&(jK=-oF&h2*3$Cd~vDS zabiHfo3g!s){w#}+vq*{wcnXa_m7+nVsiow2+wF+x}81FH1Y=b4nn*9x5v+VhkFKp zs@3lW6!~j`u)BM^&7-0sBsnuD^DF)69m%r4jlKo2cd7Fg#{DU+DDd(roz^W%*KmO~HzxHTh9zc-x9{q1XFFb8UZUQ_MHENO%07v}k((pOXz$oMT%`z=N}f z-s$nS3N@|ko?+eNN(7D31+odn#U9`v+O0tU_>ZyPV9TEC^=juZ78(U5gRd{yqr6Y@ z)CkO@SRF^XN3&N+Far~M4CM?%Src{?E_ER(vCr2pAMtk_djrPKOg?sSq0Zh9Pag*n zf*?TwN^xq-UeU7Eh)=T{h*(a;o7cXqv)vjO8q3v@Q!* zwGTA*cNfMF&C?$pL7&by%FYiWO_ntv#+PsFV4n-v9m|xTv933tP($D|5*5`6b|z%I z+uKB7hum><=yGtN9$706_tiFX%efmuG1-{AN@G{?@URfUQoe5n1VxsHK84!V( z@((>YIvT0F?n?GIm{`Hd)f1(>eX9mp2MgzHh}rAl-pQi)g)c#pm0OXuK#p*Ce_xU1 z78K;{RRcP>=gru5JUu2J|Ik3anw><@$}xE0{Ler(siFdTbds`8J43@>5$w%3et#JP zIngL!T9<;xpnK00Bn9o5HQ(jNzFTyBu$8gZt@I>b1D(m&!sL!V(Fr1PFOK?VWB(R< zis6He8|RqMyI`V_e#$`p3keO?M<3AyI!wuDF^SiEE6|8;FjUEWN$4dATx2N0xgFf+GcU%;&x822a z*FVM*YG)HdM3H=3cZ{7aepOFIBI2g#Yl=NYS8+|Tw$Agq65QJzz|$Sw^1v(fpC<;t zZDC(5cWJvndQFF^WO(sf!`sCB!<)p-8}1R@z<21;(b4$4K57miUVfq4_10Xw3tj5t z*|EGs>C8J$0GoeI^22v@fpW9`F`@CC)lG7LH!(T<{5!6xUr>aTbM}EyrA%c8?Zf*9 z(|WTfMT|5io)RUmG`oX$ZXQK;-Eq%)wyM$vc-gmEKpLV*z*+=peLWkWN$Q*};yK(E z)9r1!t8O%vZ~c?VMHU?&g6BCJX^&}38ZbGe01M}kkqBPyXSLCyD4Jm(eIYyAdjZEQ z)zcwRhmz2BXCJ}|g$f-DcRUpbHH}4e(j@;rnIRw$GPz{E<94@WY`2G-&NNjY zlUQ`pdmj$om_9zr! zYHB*3M58qmvq~2uJu*7VkTp=j?FyvN;y@ym{?{8I7x#g&>MWLc%)w~N{3dvlK}k6h zcgv?)O~|#(vuS1p4l8Vc~np1=52Iv-}+xF_`Mk-FnG<3NF^S` z?eT@oXzRTYQHw`*sal&PS({ySs?(t3drV>i1)OXmS}`B#tOsyBtMe3ho3H~q*ZY1@ zP4m`x%7p+KvLF#ZmZV9dXrT`mf``a|B z6zVL6=a%|7bf_C}@Ukp|l+BjYj)=ojm*YeVKT^)sM77=%bQ5x^KNtH8J>A^$?Y15n zkig>6sRJ)^SkSOp^vP!5&C|az@#5ROorgii#Zvc0VL34SBX7?QqoFZs$>6k`Kj&x0 z?AHU^jhCe4#6R*tR&Ryactv_7{c1r|-UW&2NJN@0r5ZhRpQ6}jazVg?qPDH- z;Y^Uw7V1~N^*eh*fqc{7Eo!%>3`UT6y$W!nVYqXnM={kVD!ztGcm~!XJZ)cVh66z^ zCDLj6$cd2zn)POhpZfmzs<`&_!umrFZ$&h5){@r5^aAI5Z}uj1?r;70rHz>^!CdMZ zxa7R;zt>9$-8&4l?(=u5Ko2ZI+MVy%iR313{8 z6Y(~@Sjs&)C7=9*L`WqPa3lJn(G;Fc`YSR_?ej5XgZ*#bMb89nwi)#GLWW8k5-5uyyJ)c^gz#g0hNH}xY8xd zJ>qxK`=s@8DrUc?kvyK|2_1T)YgDCpar=OI@P0oXD~8)8SEd@RFR~eOVW-LfpRb2c z_OyM6em7X3Qdh%-?3VIfHp>y`%R_qN zQW9&*fOFu+`!Gr8|4~4G2JrW|QTY~N+d=pH4=Bm;=?q16t{M59rVbzUVUdlO-Li>q zvs%|KRaPwwgrp@x-O~i-V*~E-kx(g9SKo51$fO$28i{M|Ky;%X-i|6KLh9-r)@8|o zB8v6SIAIVmaU&xT6D5IC`ue;kl1uAWqK#>dBwgNKGx_eLZmIGTo0-(@PZx=tw|^9? zRdG6O%#Ch?u5{0o3D_~m5Ve~S$te;&sre#pQpk^E%m;ydpW%_HgOhXvY$=o>l14fF zOO@-i3Mysd1_na%elKy~inTOOI3#HF3Fe#x4iF)0kp|{Wps1KZ6|~|5kAOk4+k=lR zsc7dNov_162@=jv5?j%Mtz_a4A%H(WE-k8?63Ok4l{7UwuTN}tS3a-B`cAB&4vVfq zqIpI}Dj7xI5+bGufxJ$BGrrO+^<7Agf0faaoRoaFb+Y?&7DvKz&hl>-4RCNdOh~HT z)lXuhOb=x|-c%Wv8A}v}#U`|_?B|C}N)xY1P?@_>iW}iMWt&Tb`&(OsGH_f$!0%(ReW6I!i=kLaa8c7MFNh#aRPOr9=$>l20Ku zmi0{vb87ev$`<3?|G@Zyz7^#L0y2Dg-x3$m3j1b?HHsk`tYw#YSDOskUd;_l6)MA# z{qe5I4@MJV&f`gnTlmu&#p5hu6AN1KweBRtXVfg~(qp;KbjVLR3K4%{Q|4N4+$je1 z&9xdWUi~3&AjnmS>#`{(Cc7TCMxnI3y;W=%bvkz`NXpKJ!zE?6IBrDRlLCEFOhEg0 zl0tc6=~m~|kNPL3j5;g|5ufS`JQC{xmtL807VtcK{{woHej~eZ@aJf-f-(mBvFTMn zZHLy`o_B>#J|?ujBjP(a+C*J}1mIW=3RyV ztn`1^4uh^H%yn(DS_}35t<~fV`oQ2>M@LMlnAc|u*ug-CeIe2#M>QBKVXmlq456o{ zD4w|RN?7C-h1*!Q>BpBdan23g$LQ<9Eq0wsL`KbpJCG>;>s25IcirwwpRt1a>%=D( zK_M=_7Vu;OUf{Oxx?1m$e4zI)-`oHPez#uJc)00Nolz(B*hi^UAs_N=w5TXIgk<;B zEzbYgRWQQDwF~ncz?1L;3wBbuR6*>@eJ8h$`y)PW9E%R_AD&uuJkpy}p!Kl#80Rg{ zgO*o8WTemDe@c^^Wg=e;1ZYn|7Mj&+Q1^xzt}CyZDmV3Z&o0%N%F1jSfT119>8QKo zhj8XY4|Otle;WGBfLqv<*B6QPa#JDe-#Ov)rP5-TO+FbZNhtw!7PoN|9@7B|Oh9`V ztIe!7m-AVkT8zhlS%vmp(9`*>$~lYpOcG68gRTKrCX-EW;V((3hgx%_MveI3wE{aN z>xGiAuG=H7Bvnju)-6)L1N?l0_(JvNYW1T2VBF%5=(GHQXq-|HASxl9F~!n*L#F%I zXus2tmdy!@Cknm9+wQmP$<$(dw>K-a+Xs zULNc4Ecf2SHna`;@%%v!4ENg`ea`dyYRCE4#n&ULX>-N+BKI{7wUcy*tvNMaAD!+O zXAX{Zqvuk1$p(m)SlOqoPWN5Dsin3?KY{TlO}?&NUFY?WBw(cvZ;97$m<3al?lNmk zmW8z1+O^Y@+%|o+wjwLda-tYMU2q(yhNp9%@V}dh0gNRgzKt+u`k%a=yKM|>^b7(> zSfpm{&Nxo9dp0tt{gv!WVh!g7$=X+qZWNT}>Sa}0kNL2C=Aop-LzeTTVVJm>Mai4* zEu#${Z!zEbIz1Wj&}Db0vy<9>*?Alau1c$%0(Crh+wdlvdC_!c^MYGDJ58FV#;UE@ zusRa-6|z5Y2sQQ26byB3?vHSs4=el3wW?m$vz!uK&Q~Sz*FJ-Y4or%V!Bli&0JB^f zP3Fb1#Qa977$)YJA4y3kOfkF#IO|PI!VleRje8CDji_w~QbnjHA0?()^vZZ%H`(rM zrKV;9zhN3A3)Q`Dqew)7A%)pFUrziyNR6_UZSJ#&%Vs9tKj%Q&Y`5CiI=ws=>RVZD zwxrJ+PYUp8bgZ9AY&0s1Gw9XN7bIp-X|yalpGDoT=b5mZDs=zA_RKee1r2l%noqeY zyft0teZA^cHJL9;TP|0W(s04d7+W@}rx#vtRvc&ZNbe&dGI#0nTyojg{9Wmgsh@K> z8&Q%I{%RLy3Kyr#0miiJzTx=<|6T8#hG#Kzqgqj6n4wi%61KzZdYzc>slr$*sARQX z6JCG9Eg1EfcLGMkye^&B+g<}_bpvGQR~@!ymQ6a#vM0X6Nu0*z0H?K};3p5ox-&mN zUM)Y`8XH6(!9Mh@%et8EHF)%U@GqA-i*=p$k;^@5A5Ro@YW1?R+|D4pyskXfa;*`s z>eJR*Wbi(K;n_2Tw$-QyCj(l47gTA|1+tQ!t@FuX+Qpd|LH?z8Ca-WzROpgU_QDPR zN_Cg6u0qFxT>fr&L1C7aA5YG`Aan{Y}TW=D>wdcw~8DlizE;Q z1aGSzf`ZSh7}VWmF{b-{Xs!40XP&QPd?WI7fa8bb)f?ySD;0B-36s|zfXV6Ug2U_{ z=xC$s=N;X|X}-gMxmcf^<xa3rQK=-12ZbEk{tVhic(ZgY8M!R|EZ{o3I5DFwLxnb_NZI$W>?!05U5b$9&Y zyf{ld95UZ?m?9;^BOc}=rWW0!G|!$sG)NUChGco#1c=DL+b8*$BQ+nxFPdykX}X^G zV4ICDFUw^_Hxrp;;ntVyRRI89Kw#ity1yC`gE@ox@Uwo`1CdaWQG zmuVpDDYN|iUBqKSFDMIVQ?xUeNs0I4DVqE8BAOvYOWYKoOZK|Mx}xcVM|fVwo;-Y& z>oJ8i>hTnKe7fOca;Cu3W~RM(mNrunkNeBG(X?9kbthjAy${wH`5^40SG!=(H$qM~ z%TM5J?DZ8KikXT_Yq?6JG@HhLY<%PNYeBTp(oBc^Wtto4?|r{$iixJAL&ozIjEZt_ zx|Ue}P>CVe<7!z}-i(B1imkZo^PXkdKzVjYnsAyqy8bY+A2av&A0JmXC2d0D9yPjN z=40GAHz>3y=$p{hC(c^Cjutv<=Hq>J*KDQM@@W`o+j+|Q44Wk6V@CXMv-7FWhxJD+ zr~WdMwOTu&&WBg7NZ|VP7m`!h0@kpcNGr{2Z^fx*w=R}W=VW6l3xX>bdQHhcn$U=k z_GXK9RhdSakgYboUB*oi&|*siB%ZH0@W`u{aWP9_BNnft7d9uXGyCPh@kWPbrHT(e z&qIv~qQLVgwymo4=D&-;?bO`IGm_KrNes<@joMsfECJGqjtd-yj+5cnjfVZgnY{N> zlR`d!x65L2`OZK#VL|P8L#iz@Q^sBwq^>ci=fcPzr8&sW1}0TH?S`|oS${}gu2!{1qkC1RvH+&Ma2#Cn_DMI&9k$(N z%6lL(c^#T&X?}a{_XK3#c#{v9^WQ7Fm_@k5JQ~eNtY??DyI9WydA}5a$A@P?D8As` zIU%ft$VE*UEK30TeW&~5!ua3T{t+wn`>LV7oKVQps_%kUwfEcNTbg`+)fAvkNXg}T zZjq{cc&C@1$K<4tG(=72jyG&&pF83Sm$ z<;MVLn_e$rCi5<7RA~58Y0Rfo^YuzDFsfbkeCZ_5qfVt;>%CSr0(udO7NZ>C$*E?k zw{!R@mc6T!H`p_OQ0hywUm3IyLYO^GJ?DZ71Wq0`EzA*bm?{}SU8>CvE zUgCg8yL51Q{&4V<=i$UqcCq1M)BRJUzd@=V6;p{tw*BF}*z2YPo!zmeN~RZHPOli( zM;mP>iXSKV9FEVknC$6q@&1+PPit1WenLXWp|@-dI9%Lx&va7Ozs2p0_LZC1;Zwea zH}A>WvC#XU(AIGI&GwEk-xUS&!7N=I+xM_G1F>+Eg3G>-T0xukD@5ygua-RqKfO$e zY0ekdsM0=Odfk}III#R$f-O)a2J$7L?v!rP>`YF9@|^DtVynw--=P(rP2GC z?}2CjL8jvoOG?r>8`GsD(FYEP-9qoH!sgX2EqAr9#^q!MM=fk43}CFiIWcOtP4NS& zyZLaiYw!*JXPbEE+MmBgOR--K>uE|@c;xt=HV-&UIu;rYN^Vz@$A)O5%~%Y>GLzY>Q`jZub3WxYNqiSH7iT!|b^V(jVC&P{+wA#y4* zm$&~T^>&X5G2)Pi40uhJ?a3$v=@3ziTqJW$`lRf`sMu6%S=!Jw?fTnJ)Hv^sTGrc3pD zbEva)DQYm&?w_r;JzymXf9_qk83&##8uiv0&vgnZjn6g+@LV+aA?C}JA8f{SCs2{{ z606i)lrq;r8j1T~K#`C!DdDz!>0g+)YT|ZWi{`7OtqYck{wD4SwAPDQzZnlwxI&*Y zcK>1el#y&~Y0*Zl`;^#Vx$&d4b&efYs*kYWc=;%b(QBxk@pjkrAP!0tU#7q-LiBEy zjEk7BoxjNUxR!`Ewp58sLsMJaNgdXYILY~lc=)~adBpe&omQo6Xyht;X$&0C3%_5EUE%lRwc&$X z31*P7^)HOO(Kj2(ONIOb%VgRlledK$M_OMNwF*OYg_^NCvK>laV~aP3!=XRTBbCdU z0WAin5rrx*>Q$|LBpSLN_d*;mW9h`Sm{LIbDu-lP#^ZRp8tf|JXk1Q3+{`-Fp)I>+ z?l5n*qu};~z1b7f9(X9=4utt&FKSSSDFR~SHGW~F zHjTUihuhoGah7s?N^@zB|B~@C%m|@)>yrxKvGXDT`jeK$rkE_-Esy*0x&H2G0^{yB z?$LfLCAaP^_Sk4YY4Ps)o^A{W$V1Jh%QQsZC?}q9hESrfQ0jQfA)MuYA<%l!_UG=% za{e&kzA8Sw%5Ou!YStxSrA?#_U%+>!g+P5wyWAwNgVi+YJ1Q#*ImarM;~Z>cTr?K( zl}0p-?Gw&ijzQJxRyfxQplf~mWFw*J4xey+?!5*4nK zfJtSwJ7Ive(MesVsT`&tg-A{gJr*cs``O>wbsp}57mc2hK_^JMaUHHEn~aV`R3*AE z*4jV;3u*2T`Ue9C5M?%?63dp8Fsdh#Y>qkzk7YfzkGxssEc91Nvo9-<-sB%!+~xNWvs;-#{?f)5#n)+QLw~IDXxe6*vwxT@}zCPq%+H$aIpQq#Z3oWWSF0R|!=D_{2-*8|(?C zWT8_D)94e9=((ky7%mlI$6@uW$Q4hd^Jf%C+D-PlP}Jz<2|CZ!+id!46jLNd4|uK< zLLisSRJ3bNEilFxrQ8Qvd$$={N%@b`O~^}8WQ0*haZ||4^Ao{_L{J_J3>jhCJnSvr z0-Z3Ux^PT}^$mwHPX{?{Bf8jESHKS&{!kGG_XZDR!h}kMNvT~4?U_^B5*2m$H942p z=TE;d`DZtonua^!NBJu5ZkPHMivCUXu~UHa)2qkYm%uIfx}}_OR^TU6A@mj3_q)PA zov$j}{x+UllmB*GZChdGFV zEFbfJG})0TrhC7GlhXhD(=L*aZfKXK-XN*C)HV&D>31EDtJ6;>>LChZc+22Q7%0TJ zw2uBngi!t_ry=Fj&8D6|Z2Qr1xIhR57I8y?xkvge@?vdQY+l=Zjoi29a-1mBGIMBi zo`c~I`4M!my~NU?O@o2$K;80dDbn!XXJJBySil>h@Aw_xC(xD}<~uc4Ka)6bV3w5T zA#=EBW`&iR(C0*`g5|-l&Fn($8JDt1B0kBnWs3x%-u>hPjv*1=@a|?oVbtVFAvJIW zF{y)Mn^=$g@2_g~OH;K{r4Sn0E5a()$2B$^0NGdT0h=hw8?@DGXYNH%{N)ytEK5}X zL3qi8+598@iHulMP8RJ5-iAl*AEk2l@Ma}6BAF@3v)$~QXHAUr? zuyF!Tmobp71&Qns4@xT{Lh~On_|C<>pQC<8#U2i9j!qFjiOHGn&dL5H5RW{jWsSrp z4|3ext+j;nKJ+H9+O&Y`AkvOGG8G4du+9Rf`BYOB4yxylN1GIQN$)l6l1vo%7h1VF z_1e|I3U8vG(l@9h_^QRo=PPU|PeJ#iBPnwn1G7UG@w3tZ?yi4@ z%-!8$!jTu4mR+n-vYg>v6A0!x&tw7l;`#`%1ce9_-s1p#zY_YeA_j<{UIop~0T+|w$(wfP-wJaIl5Wk$-Gr0p zf)B^9Bd}!7+5TkO&dSB|=`wg^UYD%-8YK@RW$VRiN|9+pLKKfZeDX2`Y>I_aUWzg# z!XRH$pRCp{gE*X$r~hm%y*q>;Ct9ijmb!g==KnNT+tk5!UBmFy{-8y|X19eLKG*rV`tsiiq%DG)> z<7z+EvK8k_=N$u-Bjui%eoGTsc(|~0UC-m1EuKUlOw3@820v|+Tf}o40BSW6O^_>H zRXNd!NMczQqY6cgvcbWa0t->XUc>v5qu_Q?z>YzYsl3nHFvkvTK0zo`nF{BAhr|<1 z3PNxyQ5ZtSU`5nHV9D;L2QqI!`RN z9_md-Y_p&Gtz{4rGb;EC8t$WZxY^ex{c`j*LmHo6Vbcw*-Y zUE{eofzzx2HfphAJ5RKT@|tj;xIyu)df8)g;4t{USqzGjXw2t7ta=0DP(V-YC()J8 za={L8ZB3>NKXi2qY?={D?P zYwU3q+>#O07^objnw*IUpC|#$J2t%yPW}1vESK9IR>^b_Mkw|NK4uP6PQzF zghU#T-1(+GzLogC7*=@J#jK&1ko_iZG^`+)&o&DP21EiuUig>Prh?JKpF(*sb##2m z+M|{68q)k%$Cm%d+s}byj9|{*WeoiS|5;ex<&b+T*%v%+euwM@_>0}>4-1w+OnhCI zs7r%E+6%SFLdf{SCheZW5#CPiW($?a^p?N+(aCuqMUH?6_oW9?0bcTaXeKml!^5km z?-nYr5BA&g#@f{;4mzXj0CL_7WGvj$ca2VF%YUpIT|X!)mhuC2J*Hgezgmk-ot;<= z?w#h^NDrfb86+X`f8fD7r<Fz@c7zC@>#tRV9Q4BRvL(Pl zP^!%{OY6X5E?_l=vILBK!2Y}!6SRUUYXloYb*4>%LbBrWglM!y1{CV?t9v2h5bH5F zDKSz=E3eR{<(&UAfjlAQy3WipCG4btyu|3Um0qGDzMDF}?#nXDJuqCG98b-%J=g$% zD>0KMJUdI28!N7EXWTMs-C>_&(FZHwP+%H(MHX9_8LkoOshH4V!NPy|^pW)=OCzL` z7af-0^5V&$IxR`+fOMbMIQq(`%I~3 zKs@&oN0>W0bPF4@tq0qV$E_^llK7tE)crsZ&~M&7zg(=@o`pfgvP|IMMDwFy(NR7c zf3m_L^c^pL@*Unvp_*tQKQTCnPqq^~(C_pyqy z*p%1~<}T;a9~p}7>z=D{6kSjVVuadc`*rxiAc;PPxFipI>(b1;4BapD@IK2%Wt$HB za(74X*FXqn@j~X=`yub9!)EH4@j*($#9z0QV_IKbx^8QlD*rsMKk;;oOLgO8d78ip zu#7Hs+}#L_J^jOFbi0OG?k1B>UsEFQ7S{6&1w;C1PvkQmUL}&E4kO^dtt9CU&Z#~v zF4!?ZV=W~6aGj_ADw5n0EG;1e=axuz+E;jQM=HpC;r_}AxfKIO+MajSBfoCV+SWZ| zR*%)~Nt>5T$$`Al{ZY4mtrG2qq}012I)i5j-Wf>K{nFURSQX1YW!sKD+p)n(C+^(2 zTDEQx%VlOre_+9Ku0X-iiFXN#Mn}UtSkpG6fCnQ#uoLG2K9S8wH3qyHMtzl3@@*i3w7tQ<2D&k~E>w|S->%jWtZo_;O75pKe+>R$xKY;o61 zvDRoc0h$NR&$0;pHtpuZ_5&%eI&^H7%WLs@To&UlchzczWIX=8DmyPIyDHA)I+QA@ zo#$_ouWT1<=y8Zc5jeLW^=+)INjxlzdJtc%|NU-q%?jD{1@EEG%y5)}i9wutO`Wj* z^?K0s=sYi~(P3LCKGJiYs+^)I+VGg#T7MS_m{ccB%b&pXFK=L;yV+Y&4$tNkcD{4| zLu8WnXtOM8@P6W~0Xb4~Z_&qNq9G?WHQxvJYULuE-F!W}#*2u^d*z|mLOpcW=J{Z3 zhhadN`-Sr7cg?!jzFvz>OY!6DtT!Pp{i|Kp*Yt!#rjmh9%W1yddh?R=Oiv$2gfnU< zSTu^TUEt`yV(|pRPP1x`0QZ6E-WrLg-$%?>7jU)9#?@Qi`;pY^9Wz8*Lo_Tr+9v<7 z!Cc>;DLsvhww??uKdk3&1lM$-@SKB6&1ZMT60bIz*ezce$MvP2d_4w5Xx=2Z|U@m3nx-bln znuq0y?kE7VhdfJv?l=FKK#bvA?smEEbzUsRZ9NFXeRdIzHbMBX=R7?xotXN^C}*Af zBt7|Gv$y>sLsvL%^Nawmm`Pz_FDt?JzodVOl%-Y+#cu7^CKTBY)r)RZ=jYCKJR>-! z2HO}QzBZ5d-;fTwt;$%pt9db0Ysxde1%2fU*LhT)=B2?`gul4iRW6nb+WY6fmNKr} zo4Y;9@cMfPOhy=T{^EXG7Yb~;jC5&-aQx+$B&+H2=vU#^Rr+w6rd>yFwGC$SXG$XSE4Ci_8=cm*!vpubLDw2u-+#kdpTqOin)`Aa_m5Kv%?)r(`a1mpuifYE zmS?@dDyeoWZFhW^^92C+Z*T!v4Q9u5TRFi78Ao>Zs~paP{lkUjs-#rP*VVhUN=)f@ z7{Nwqs1`h-Pe)SG8dixo9(JPYt<4QuP?S^(K0-p75~$lk@ni7 z)8{OOm3_!~K`nKJ!$iXtj#_u}x)q@hFH8J?r{A-(57jsb@x;IKN~Wc(^2p+HiMb4Z zh%YnX@v3qvvt`boQ7=BJ!b>-ggZuY9S~lq{GsFoXfT;^y+nz>hDLMXk*<~C-Ew+y$ zAsN%q*Z`{!G`M^e1NXja>;P*x0bLgRdExyL&rA>*^ZAQ6I>W zLb|4>C(Q0a4E*^zuz!cfptsg+mLsZG$LQMd6-gS0cX)8e4|$|y%dyk`yUMg* z3F6O>3ay?%wrU+XPiF0CGin>ZSD2luwSem*QEBD#xTFNd8S%Y^r^^xBRVPmNa$-Gi zmjQSCByxIoByXck8OPsY)%smpZr2JUvSKjVq<{Kb(lZT96o?CY4lT*69 z$q{9S*NwmhIGVYK`KU1g*{d;8Qlo*|xDYLT=ved&qcUkzSuHXk6!s+bDyy3SN3xv_i1Kn`rfOJxby-%o!`ZNj#CSd$mGyvEkkb$C6 zRqArG2(i=C-%SX_30jM_>m>nWwVORH_PT7JCnK0hXIe<@w!k#MRyV59vpb#p+f;n9 z8I|~nWu;h00iX$!!Z2q7d}F!oTdBKEmL%8z+67EAJ{98olS?-E`kEfMcIm9{a zR_S7K$&F4)UQ-Z8(&C4qY{$b`=~r<>)&f&)Rti5|OGwS}o-Wn`o2eV^VbM*l7u};V ziwq)M&KEEz%$iJh7&uX_0G4;P?^il+SDG90_{9u2pH}L|$Kl05bds!x$G^@}%t=0<-U_2yO=!;N`^s7kTv^T@DvWS+K+gUWX zC&#;%GTc>bS&a})Y|P361q|xtq0+z2+kKiYbjBvW6Lud^i)BXQlAtKraG9!RJLPOD zK=KQeOknay;|2Eo2A;ArIjjyIn71)G4oxQp`W+Q^j{qO7P#QIQlxtjEk2N*&TV7H* zoSepi)!AGmIH&1bZ*G@32XP7N&sPS#nK-XayK8;7mlL(PQYr+YgT}Q z%oYQW3QHd&kz*?+&Qcxi3)d=UN)ipsRF=tU=6TDTn|*3$5S7(zee=|4t@bB<_V<}I z=leD;(xQ-XdQG1qw&P3u7XT>Os6q9qO(010DOn`DA% zwS#jxIREUY^UAQ+Zc?IY;5+a+08?u$8caqW0q0hJ!hA^CMq<_;U}sf}JHQU7q*1g(dF8@^C5nm>O( z(DjASsDIw@J!091I~S8w7}RzDQc@fa`V_o$97yZ2^XDw zR9qyPUct{6b9AbANSuNTO=e@qq9G#uqOS|T)EV-Pc)rH#nLJZ>U)%)P`ytHcL>yuy zuHXCyOGoSsO*X_X3rYzZi5CD8S(w)N^+mG1D#SIYkBxO`_6t6mNHc2<<~#@$_H52U zPOU)M(w*q&wK6Td6r}?*)%P*ckO`g-0*7xB&%Bj9yo4zgZ#5m0S$+DVUV80LfE7cD z0vwD?0O{Ki32S=F@sVOSFd5DVMHTfI!%T!GE1up)_j%Jn+d+$qVGjO~Z#W=m6G^-+ z$fH)fLGUW`u`}EMDs-Nvpp;JgrcKvviGEe(m)2Dm+?&GObIA{qNkMRgfw}UJ*FYrj zfdrHf-q!xZ0Rnl3;YP&Tr#4>I_kjj~7&)X*2TMzvP6kBUEUQOhb`ZL2Ut@tYW-+s)VXo(7^pq-Bo%a-UHMP`v zQ~nCphW+#ydCR_u9Y+E%57q z{5~H)do`QNux`boo-lu1Wm((7BIp% zSN?2_%2EDs$u#O{j)gpy%&rvNkFLZ}MyY&J#28N*FvKT<9NL{Tbt1`#;0=ID zb*N5j#BmZIqOOYoqgTFFPDq=Uuv}KACZ-^=;%usotJTW{3gz}jRNuyTL*kDkms6XW zbu{?SwL9?Xfnrk$Jmp}0TH-nv25e&Rio}Wky)A(_8VjUSpcoVq$9bhlK`&MGX^K4UYL1)RQ!wx3rjhe_PZzs3h$N z>K&!VJ77#aI}yZ{!gnPl-yS~c$<#saHRkTh8x_$vgo|rInD$qS?_!7%PpIKA zxsdh1*o0h~9;65%y>3P0rppSL(|#&uKH56orLp&kqP`K+y_TV?*jXK96)6`>tNKC4 zmI0@7x*`w?%@L7K5p!C$&CQUDf5O2OKd`R<*%)7s+(Y3VMaKq>qY|wD%<5-h!?Uf+ z6WVC~o~h70C-Dk!l6D^&-<@Udoal*$#d~%R#>SZLVz`k9ga8xHbpE-a(Pcp0YOAY? z_i3|;sk;?*+{EY~q92oEEWu%Y80*V(apPHX0_Fv_AU4pDR5CA^NMQuforg3cZ^87_EfGUrv|f3D43 zpwGPU7=gD#@lWX$>ZNSA=+PQPDWGtQ4&w&Byv~fjPnbM@0n!euX1f-(FkaFFVcS!O z$)ht1hjo31?oRM3!~mt~m71r7)C& zAq|VygTkv4Rj>s|Q3sUv0#gEwJ=QL`J#Olq#}S!}CnsgbRoOJ@${p%Crm;u>0%e{* z;PlhqZ)71eHt8tGGg(9t4-Vn!reEhEcrCPT0de9WK5D2UPA=m#4GwoHtZr<&8&GD& zy~B8v5wxgLmDuOT*_XdOeyc_n{B^8{NJJ*Lbz6{bWcqGDqVr%hb2Ab6Uw{!9(3%`4 z%+x#g)PDRNa;i<>30HyO&Z@_T9oUijmW^Y!E&urpBvZl3Ajgsg03rN@a{1vq^-ob` z`6Wis9JXafC4)mhVgtdz+~RjPuc)2ObLJ$ZjnYG)JU_Dym~urCGsFB0eK#NMA9c^I zw!21~&1OoQ>{)fI>4}hDArQBHk771~_)}5sZ-A6l#zev$QqtdUG7fGrmp|4Tpj7Yb zZ8>0fK&x@Ev+azxdNF5UbogQP@xX~jM4;5M&_geVM_5!K$KimE*NR8^7q$L(*v1W# z&I(Wy#*VE6L0F%#e&$q7OeaO+oF+Zs^o(`4e>Km?+{6>{qzLqQXQ;G7+-LUDGP0Qu z9Lg>xqd9t2v7ox{0(FpcHFTroOiy;ghIeL$*tFdYX<6{O9F&(^I#(NgCI=0lU=)Io z53Z@0mc^P3P;_uip|M)`V{&hSe;GihnrJq&hMR;SmJff@!&auC`2)3V zE&KYgZ8Q#=9B#&;oS%FChf{zHp5N1^QK}MJ06R++=%Grs$up%N%z12s90lpux)|rl ziQH=d=_fIaLQiR#Dcr|-RIn@9GU#JOTGR!6*`olVQ!3u=jfrp{Ci2{Ky+SS{xlq-o zL{24e7|dfHVg%cF&=W#-ABOcLVYF0G;mJME)LVHo70 z62@y*IGM_V#xpLn5yEEm9D0w-V-E9O_h5rWWyc!%)-}P_R-qm`Ju6{T_~nDyqFq&s z2-oAnuW>1ym{X0>w-!%Q`%%Gvaz34j0R}UFuU?|DS6RlSqfF85^=GWWu! zHgdizf{=MwN|Y+yHsAzgV56BcHSuYNu>Klcud1THtc_td=4IFleBRCEOzzeQdGWAb zljSq}E{YCvMYne2uq|h-J#fuKxxXvUx%kGBQ{}NPYwj2tkn4@KcIMT$D2e1#WzLaSv#HHmgxqC>=FO8H#7v?9e z639AX#tPB{lz`Wif?PqNzz`xvScakQyrv`AtR^JQz3Lgu!(MaVoW#5a88r)oC(+9E z-SKFp3@?f&wbZl;47mrKa3+wZrE1eeEnvlly}f@2t;pKehvTh15#vJq$zWT)e}X7H zvWCnZ&Z|MWh)>BoE;gf&F)$~Q0RM;0Dqtk((8k>01|tg(X=)x2;6uWR#c4muLFOxZ zt2JaxK)BMNH99NWA~GbDEtARxX3g4sAsNA1DIWYH6?98w(R%;sYF4Q}vdwq1o4Y1_ z5Da5}X<=td?(dl|DL(BWrWFLA5gGoM?KK@idu_7vP~2?R-GOwqiE%YPfLN*dtGWqf z=-rmQZl)CvW274h3a>UgDb`egwOKEZBKx|==v{q|enOwGpoFZ{Ox8x%!KX9Ml``~3 z`9Bld8GO?XwfpMEm|ZQF(MN@Vic7v6Z~st&Jh4MdQRqM@LZdGNZIxr!nk@k537{ugAaO=3sW0($z(cyP%g(CO#3Rlv7xEQkXtN z{id3f9$(=z$Erx}#0_F1D^Upf+tq@PcWByA3A87U{tl}qKc&+FHdu*cuh`@(aTvOk zDNhBDbHNk~MSyWhzEi}ZkMeJVA=g8UX(cP7_xQ;o#_|}bzbpr(NzQ3;F=x zxB9IlE{@_n-k06yFI&)gq40jJJu+R52vil~C)?=8{{;-(w4~iGq8n*;ra#t>82|fi zzMU`>U^BroDXC6O-`TRz4U_StkH2-L;Wl%4gW`Ha7yw~4j>2YHjy##?{BtE1$7%FQ z1e!95ML-nRQJy|w=2W6f<5)0S>vu5ZjEIIsk)Bh)iY0A}rNdJIJq})hsxz>{qa8)5 znNYb8#7CIOKOl&+Wn%>QCZ@G$I1n>ekNe0v{x+rtN-?FcjnFg7gl;g!%;DF5p!7~U|@dx!|2p1~cQ zc67WoEB9<{JvYP#IQSBdNjiy83k=kg@JQp+>G~;S94C#l;3=|rDCJ6Pjn~oSYHv4^ zxyNhjN1OBv!^}+FX~KN{Uy71ISO9_pt%R@RuZ0&-FoRDYM@OsTG;ZP12=jP~Q|v zpH29A0>JK|(5Z)&L6ebL#f6539$RMB@Wvs|`<>(NoSQ+YC5!e#o!{F_=<%k^k?U$B z+~e(7%JAqy4Aq8hU>;0h?{US1dua;rs;t+H7WYsvM0>K2;-bOXv-Ej zC(F~`$TWg;h8)G<={;8DFU|PJPfbgdCNXIcFpPogA%6!6Eum7`=8ItE zDysvsUIw_p;R9K)!scw}O%av(1HviMxKb9%Y$3DX3i4~43h1=z_XyZUIl16vi#vRS zYTba~T-X+kT2_y9h7Lb)ejUK|COAoxr%7Uy;%Pdco5j>Se{yC%lQaM0ns@&Ic?v4= zUg2y3fmmJuC-+CYo8GDmr>;;T+mskgV6B-H&x1h3HT?mgKs}#nF0~B{XC6W~+ntoH zNB?Q>cyPLuGYtQ!rB_qeaNXJtgtD8~ALs~iXWGcTBVPRgCKT(wliHJjmyVvolNA#M z9X`wO|Aq{0Y>)@6!R}9*D^nFM4bHDxJU7^%k4!3zu-=?llu?vttvIvR)_%$xOJ926 z_$!(~4}+sS8)M_?3PBLHpUW(dqe1wi>*O@fjhXY^@gR6uCP19_UXS|@g{qOi$Y)?fXwc}(bSXj- zZw@SoS%YYFp|DpO5C5%WtPE!cfFV(a6&Xy|cj_NH9_$Vx0*vAj5S5*tliUM@g7*46 z;)S>}8d>kCg8O)!XGn{#ZyR-?ZV&**wRuICd!K~f_V{^+bAJ#Rf07Zy5KE$=NJ3wI ze~2ZPI#i*`n|jg(irIiY?M>oV9&i~@A>o)-@3`x9zB=03?I&md>$gu=fgVCQt~wPr z3-|9~;bvh$76u-C;S)u9eK6+3{#(HJm-JvJG$DzvhPyPYEOiz$(s*4by7g^daAMXV zb4yIckJlHKQ-R$*6{|*-lST2jjNeUt(s-mb2gJK7Ub{929n@i5naF(+mSitZA3=eP zSkDGQf!Wxlk#Y<6C`4LjOaB6b^^z&qAb=q@Oc28V4sZBj&{45sNMe;7Kv%3iva1nD z#_VG%C~by0pg!d9nB=nWK2gV5uNv#}P%-p~#EF3)RQ6^`~QTVR0Jk8u=8%qZo*wP>809 zZ)bu+@Bm2PYBqniD4P&;?$-&XQLDsEB~w!}65Hr`S%lk@F$N=8$R06@B?ts>?fStc z5@2AVwfXjS^qfe`Pn|Dc2uW$+^eK&ZK~PG}fAjx}gJGg2P{~Sv%}%lz zz+%MQ8Uo3@c_q;RBI^clv23D+Qbo@~d#r(5P6iZ0ISVMw96% zB|;cxU-lJid%~UiCqKj~X&4jTHF%!zF9@VgGl9gW)F8Y9Sn*GmBluv+Fs}wB@jfom znk*QzcPu_p!*nM$Gg^W99y7#*b{kSfvdZ=ytg5hfv?S>PV;@gqv3XxTOxb#pgktlf zov+Qvkg$w3muc~#Rg3clkFr&%NVf&N|rK9?c#HK z*WHUg?}{dZmdIb%yG(qO>%=vM5{2fi> zpOTHeNyz|TR|B>sp?Im`R54S8z%jE=v-5`bBTs23^Z;%ZPea4)7#qK$p7AEr8AF`G)pY<_rOFS=(SA)O@DPh}g}o6?SpS}i~IWcuS-2vs|$LZ9`Qq1no~taygN{d@;T zj1k5jSw?ernMO-2%mL~rxO;mqx4#h-M5f~*{zji66nyq+wg+=5m$3Qz3}u8uI>%^F zig-F)5o9&bsO71iLSOvi8W}JhjQ=$t;r#wrtLL0M&DfPmDGzihaow)94lSb@AXli84h|O$n0J*%y~&)~VZq zZ!k4E_gBm&c10g} zODJKC06t)Er5GnY`kt8#LJB1)n9H*MCEF%P0!xo1J5#E5e1g&Fe8c@LMv|I}soKnN zhheX`9faeL;dLcQLRyfF6R$nOJE#gPGRFB+0ZQC2$h0L_tak7IrHqSp@AN zT$e8UH5OEDCHWrSE_q7+v+;ss>}2JTRk~OJMsxc_Ib&Ttmpt}%#$&#&k&yyV3YRsu zjhd`95I={Pr#VIE%zQEOISC3tnyk)SLWsk4V_i$Q1JjkDEbYHYy0D1{Dh>rg@^_*4 zA1+vtxB_zeaGq}Ldu#@AN7HQ=h-#W#)i4e(u2nv6^dz)K2Q+b%>7T;h`awqbFFY zwEljn8r2S#>!PJ{>Hf6@JxK7%4T;Pydl@BnM-yd~u*9~RZOq7KHS0tf){t5e4`rvC zHxy*L_9!30e@2T*X*2V5EK;72h{%+xej*2P;O78}gFtu6(~DJ8XV&F;x>9)$ea1)X z715CiN|Kb5Q)2%joc>AR3lzk1viHJ>l=&PxtHDc0J!0kO)`ghM_|3gkHds)(mQnvx zQG7`#50i!YEwkSG=9DI6`Zp;mN@{9Njk@>(zSBfJeRp=Y4s`7GI~n2UY65@ zhpli!ZqwKTqx-A2T2XWQ&Ubh67>>F%F1^Rj!6R|9M1XtlF}Gl>_>Hou%Sv& zH@D2Wm79`3lZt{?h|;;PE~OS*+uhz(h*w*6wev!DwVR$Pq&aPVut`0^i{Dk`|YC@$;)@*|rFvLkhZcKR-Niik< zR{loVV>Brr2c&P#++ChJIy$&ZNA5-Yxb?-Hdy{WVRljKJkjnp4d$%>JG9J1GP{ zRRbatjT;zfQ{$p`!YiLsRL#sxYOteHiaj3o(9f;RO6Z)U4+ZZR>6t#uh<1v1P&L** z05@C##YkgL!9bz+F|!hi%HI4I8V0T$&w1*<0j7G5Csow4!y=-^((*D#9zUUWWQXob z&vEMO>MI%Z1eC+wua%z&_)K)rl*QmM$#T;LdaU_v;XY(d zioV&bl`}z*{FtbC%{%I@x4yz*kDs4E5`*==inrYW7g=B=@1O(%`Vm?WbT~R5QKfJ4S1g>|UV#3-V475# zBCO%K;>C936Aa=r<*mytcoIWGn+vztqu7l#G=1KkY{W}>kVx=}E?=6PXw7aqLsFH{ z-;f~5833h~L;-c)Ot9DTVgtFDwl=7dxC8ItSwnXCecUW{ksgFRBKqTJ4foB8k?ZN1 z9fz6x;l;&ZOEm!_w?=cgUqN1s`)hS}ZRT#2(70(IK4~OnEpDOk+OdH6TtY=?RN}dJ>OlupABZe*bBdrEdl5i;Lv38AG3ouyH+3;evMQfu;WzM}{`;Te;+3#| zUx7P?FLKY|ehcssDJs(D?Pqbl7F`bHFEIFTyrZBMicF8h8D%e0<7JF2ZsahSd^?cf z<)ds=Jh-&oCe+Xx{9k!@>|Bz#!JuF#gZyF(j;|;zw=xuHKKjLsPXDIr9kx#I2l%eHfBnn8P!Df(M;%VLJ}y_A z5cu3Kf^Ts-bIp1@F?lTb)O1*(oThgQn29~uonzxH<6dWVJ+s|%Iyto_;g2Wt-Sq8l z=V3}F=A_K@5QIJk`OeV*2j`~{mAS$b9tZILqWyGxl+t)y&=%t%;I0-5BBB_R!>DG6 zC^-uun47c@FJLAwG}rg7@7Rur)r{qX8g7H5THFe0d8P9xuCAJP zVsx+Z!f7Jp9pU9?K)2rdz|s=B{E+T!t&RbB=UeYH{JY=ruo|1?=8vH*n2r!0>&438 zgi)-0QquRo-k7i7a+8m;>C7k+PKY?d)kx)_YZP+e#YK#P-v$x+Y-^#4*>;Idwv;EQ zQbPqR^pvFc*Rn60-8|ON=4Qq=s#@iuxTY#ykifH%U|k%QRwKBymq*{&HUVi5Zg|AA zs3d|GkN)9s?@HVQ+-$3rdviu-^4Sexk>uOyxRlSfj5f@19ARKl6C4eoE+*_kqU#NQ zDc{8vr%u7MYzE!SBhw$o0d^D~1z; zI>>(bJz(%amG`MFxUe>t({)B-VDZu-sLmjrL0P@16Ee4Cvi;^eXz6^p$Ra+s)!{8S zE~j>(Y;S3R$8w_{W^=74JLC99Y5!+(!ld<}(PYAvm#nu3Ku{?zykJL8!V(lAht;ms zFxa8RUKfhzEvQ_li~DS`I(ly3Z|TP158d`5OEA<$Q6r2zaiIVRkADB5TskH^&;=TC5kWF8AnxK(x?PV@g~ z0j$n=*q2hTFz6X|yGpV0MwFqHZ`E@ZDl6aK1^%)IFbutV7N&R5j;B-wbcJ`a>lVeX zV!C9V5JFGKa!3uYFUcsjIeDhg90nT8%a?0bz)tV9)+}UZ$-yd={R@io$92&;XT%Zu ziZJQic%G2`W6e5xveW)8ceHX$XLH0G{$k5uQ#!LElaq-YsQXsCxz~Mq?AiZG^{fu{ zgYN77{8%+-xVfQ1z@*gB?e^Um&G`dn8+wz_g2h=+sBZr*vLXV8fy3Bp3SSZRZr$1F zaau8^$z(#ZDfzZ3(_Y~7z|HBq+aBh*zlrVgdjmswfamXZ#N+$rdI>IMY((O5RwX9!l` zdFT%LU9*wz+=1xzX}Qrx@8xnH(mg;y#KfXMfTnvY5cKN4d(WJnLwO8H-e@VxE=#-e zXF!}@y%}-a!VJN{F;NGe>F33EOvbQH=CMQMa3Z-~%ueSU*{vJB2BBw~6KuB;xY}rl zjVYI~Bg&-y>!04oboD|=hUj!WWs}Bm4o%arV*5RhgU|ga6!p`zFFtO}Q!qbcBf_4& zWI|d@*p~BK%pLb4qkh6%YNcb%yF8f}om7kW9RpmVS=NMEXYGKEvjVE~8a($SbGlyt zo0P$1e4^pinYNeiPNfRn^@=EH=n+!UDQ?B)1QB z#M|PI$^FMFc6wey(th3DcBEkkVMi`Kcs8CG>+6!rdd?f7gkrf;?ktkRT#bqin#GQbqnl`!{MHQzmFkm`3A_w`sJvu2 z(Er8k=E*bY>OhOzVfu7*XZk?E%k?VY_lNlI*tsFT1Z5&yklV6;*;I_gL}@IO^)d=R zW%3`jJLZM_CJDqZoo@3%cUI&XM>JXfD(@$OkR;WUL%I8irRf%$pt!L(_a^x9@8vstkvb zcWRl$+3;4BUMO3ndRnVgE<881t0C_Vg*)k0CUn3 zi?`+SoqS>pI^Bph=VOzjYOQDAx8KDu_=%0!rd&MTxqDjM9U66puPI~cEUq-AaydaS z&gO+qLyc4_EvPWsu;R+A)M_Q<1aIAa6_>b5MD`5T8*YNA{U1Kv?)UuLJ^r!5JnDVi zW%Nfi>lODmZGQN!v>L*F{Sby$DnEbay=L7__s7zfwNB@N?Zo8-uGoX#Y}E&jS}FZl zxlYG2r%!IrDv!yir994FdXITZwl7q{0M(>uVXa4S>l0zgEL{SAo!am zm4Mq<%4BK~(QM@Fo#J^&gcE}%=rz`Rmm1=5rOv0p{)?aGP|D+FkNHS0FQNm9Ac#k8 z_20ei!I(*O7v;27D@&?B!J}=qP+$XG(m%S_I+8f%ITZ(^!hegLi(}ZDG;WhTjzFYJmfnVIsGlUelYsj-`~q*m>eI0?Z0opG9-I;{fRYee`BBY#|xha=tBm{)vFTT$0nG1 zUKXN*UZ8gMikhArK6g!n@;#`@gX8!x3pP! z`5b1JFjnb__;tlH?Ax~&@81RB-gR%(s9XUJJ9I;$RG*~c-!cOgMoJZA>Z zoIQ)cz&G$dz8|iRMKNmXECX-<4u2+u2EDirC-%Lmv)3UY;0-49>cTd2;{sC*UO#z` z*^?(@>eR{1a}wvBnP}Xg77Dso!-sgm;D2fr;=xqEfBuB|-$5~JS4^0;ig~`pjKTd- zq^Jw)5vJ3a>6JNMHXQbqX7y{MRjamGw16~huF$Yq*nZ%MUMkzM={IcIv>v{1-^1tO zeROVG3!XLVAv9TNZ7Q2P5ly*p(6DVE#Kcg5WQgHnqfEO(SN~lwnnwCbI+ib-q1&@_ z?;)m|XX5&)J;-h0h)sKr!S}^;bgfklHS0D*634!gHoNqnfzL$Hdq2#aJ_R>!+`xzT zZ?Nx=wcw6AmjAw0$1{Z0&ytvKjbaMSOx(P50)-1$axtnnUPVOf7`HE-$IQu-FoS($ zD(CyTb7!D5X;1a0JrNNdj?!Fg={M*r%v-Psb7#-N4E~+Iat9e{iRjR*K8hAAgPZpr z;_|uUu+D3VuSQKlT6_Y!w`hRMHR|Kxqer-T^&Cn$I-ql}uZ;y@=4^}_Qz7-UTv*S5 z*O)uaD2tS5&a9c}(Wwn`+7!d1_p%80@9@LdnQ;`RYoOn>MSqK-^C8^(*RN^5W-9p_ zfxBSNEL5vf0iHG6kyfW-bkEN4sL&J-U%tVa-G9K&G7o;+{3ihuw4RWN$^Rj19qG&E z3kJSm;J*d~RJjl$4m`Ykg3L-@>^^x(Kfix)3yvJzcFq+@^dF;i30I7mVyxPv$3>u3 zm2xE7mN;5EpFqd)|(ISZF% zKbvndwJ;-3DY$p%2KMaPi#v~=7{p&@mVZ#0kT?D)#nt`a_6y^ff;wf(VDRX90)X3h zp{VTPjwP$t>lH=grRka;fx51xG3>|1`o%%6l8*af9K25-N=|iEHE!Ge5yiOUBg;-YoBKoFc;+85s-zN7rFvl@Duo9@elTU%fzS z3RF3*OW@JFaLoR82v-biA~e=$3scE-75$$*!Qg>?asAo_)UH+)i`V>N`kaLcefn}I zvbC{lDQ{CkP`5%kwCXyT#M(@Sv+#_Q_Q~TX@$j(^-h2o{dPX{Cj~)t_;+64%JAa=C zu46M_b6Vp|?AyC2YVU$A2aap3%h#-oR6HazcjnA#O#PuZYS-<91f5tI^h8{_cmccj z?Z#(UE>)5MSmy`%6>LGaY+vq<8ukc7Lpjw4@@;&~g8MN#|;MTeKNAp8b#$`^ED(dE_Lbje=(Ppx_U$ zndg2SKYb4IiIS)6{>U!er;ru?8m`twF?;1M(@W#)+t+?9>nZH{V>vvWO5x^qMyMrTkA3lXQFYhCN;bJ)S>=hzI z0&)28A>4al2yUO3#qZfOoH%iU3nlmP=3NMqxVv%t${CFOdH`--zQ7&mQrKz|z=T~& zObUf(u@V?RZM{*p$hRoP-O`Oa_I&EMrdlEH1IpVM!=!nu&2RAj^+WDHxZ>tZ?goDr zeqTHb1+1O$z%S&pw_2{i(`()+n9B<5cb)s&H+j$uJAc!XqH*r@N$h7odHqgcbIBl6 zL}g=q@kWQ;zb!-|_lgLJmep{k5p>o+g`VEOT-GvWH@nPFMb^J4KAHKK`jS&T&Cb`ge;uiy$O~%?Qs6qGt)cc z$Drf7usF773tYLgQ@C&;j2b^#_aD9cAyK*GcE$S~ak~#M{9ftA=?USeTeU2@3>c|} z38W`s0JSVcAX~lJ*r70TF|M)U!Khre6sG^W$w1HN9}$b@WM2D!KSg?q#$P%Yx2|3C z#sTiEKKAp=j(z>gUtAn>#`TA<^}B2Pcc5rdH@yAu_c=;FCWnWkYK01zy7aefS{lze z?)Ztr_-p@P2nsWe>1^nLA9q0X7<|M(uKhp5`Z;6aTDBH~l7vH)X%f z;^NH1+qZD)_-QQ$|*wF63U*X>QXXn{yD=W09!ae<^5cjtaT z;*EraNL1tw>6JVGI)0vT(Ze!te*AIFTPFXjUJy#%^)A_Bks;!xQ0Z{m#{4VY!}w44Hv)$jh+bejGC4bk8|fw;=tYm zT)cT}6g(>$b?er^;JM3@nVN!2r%&VLsq=`_yOn>hqa+BU5L>#02UhM9t)VnDs8j*{ zM$R-~C4EHo(xtIrg+Z$g0Zd9Qqr><$B7V7k!N3;`{5N7i$)J|7YvFu2+PmWeRWQ<0 zlhC$7eRyzJp+FG|HH(&^aDF=+zI;c=oj>+F6yfTWoxKz0{PH_S4eO7VeTN~5lh9M2 zXL#iEn8Y}R6E2AW|G#*62}KGP!MgQZxxL;3Kh8A-IRVy}ssydLBD`(aPCS0-gAC0m zFcBI2l$4JcGpa8fs2WhCdKDBZV233u*Xc5dkRUWr$V_Td7+O&^s$<^~NYw-~85^hl zfFia9v6-rSKTjTqVh&z-K&HIz!&JYc3Y0l}}IQH`P@oGD0DuU45%cz)EZKR~M>#zwwDw~kFI+_|C(%WKbFwjKKq>2%&? zc&Gj_6a@?9N24~qFnjhw%wyjQkBHLW5AWZI;?9LpsY+$4jTS+hc3lw5Rm%H!&%l-| z$>V1&LIpCE&UOW0ZRBd`Z5yR2LTd3Ak1QjY*BxC7-W-U7*h-#R^A7@gPH7-09!GZ7bGS z`@wT&m@s-23fmG##BI3>rBU3iChk0^It_&g>Awk)AK+1F_1RFtC7lVy_9!ruF$YB4AD8T2Dn6peP> z_vdO9uq}!sM~+dA*aHX8-(b2-G_O+|UrjbI=CBW^end64YsK0PxZ{*TfiDbGh7Lku zI|tONRR@*H%#^QM8?UIMB)jLcf8K(m#CY82&hIndSB6R?JA%-$l=vt_Mup?f-J57s zxhh)s9E}*#5ec`i9z%t)#Zk6Q1*%KBpmOcH@Ou-;5@nj|qNCydm6LGDZHd`Sf9H!V zq$NjS!uJD-Z%0(EQ5}VB3t-Nom4=`~(Pr!BC1i{Xp)wa{(Pw4W`rRS#mAss!&>OjeGqg|x38YSiGu$P zwu=$cF=_N5RBO~33B>)2XU}n;yT38qh1a!Y`oLEYQG)$^_PiD7-?s<8rdqNDX_ct( zx9HKfAza*wQO&X<1vDq@g0`E{NaN*o=Eq0jdV^gBa6i!Dfj zvPY{HXwbS78B+FK0@j)kroM1^+iK*`WrKYuuIO~Jkpbx4wHchKB3Fs+Y+tA_w(m1& zO?;Fenw9sUc12TMx_ALi%a=pXfnzBsQGJ2s62 zs})ePv=`OQsXkk#B5pr2)LfGiV({IFA#kv9q-tqpl&k1TRn0nh`Zf-!vG38cYH8G} z(->DSoJC9SGauhaU3&#Tj_h-=apjlKAHPNtifsJlgl_oh^WUcy3=-RF=ic_<|l5`0v61&(9kLnm^?yp37+n z(P~)#+Y%HZgLd-T4cObcW5|Fpu*z2!_Z|ho_x=qODByx^T(zl1HHe{uC!lwi7UV%xB`olWSy9 zoT!2*j3^Kn^%gFLOJMfG9aN)>*G$i_>5DOo0-S?O6U6b{*yzC>c~;0Bx2QrU(|lxP z7#==(j5SN#v~{2mrePW%v%XO;cA`C2-|=E1@=_k@TgdYJ2~-~ z{NqU2P)OXf|1^c!6zpBU94`62@RBYNDOilcq+zIYoQL-H>y zkulxPBayeXrSN^u*s;n?PR0NVennl%;quK#NaZTt+QmPjdcE#4ZD9WR!Ekc(z$2<_ z$z(8T^kBG^s)1M{`0(b<=-a(JY$$*=Y|<9}2Y!XoW5ysnCJw;?k5H^oe*8FVuCDQ$ z=l94c+hO(kgLwYnE*u^tlYX4q1+{FQKuS8R;kAo$t?W5U?$HfxFerMPFUv3 zhX!?8;=wa%w={e`uqW~sAhXk{FMM9U#PMyL(c4SYiWyhn|5(`FbP+AR>~ol18zMw?LcujM1=U7}UKP%2clhf2tT|q{U-c*XC%_peJP0Ge!43 zATr`T>eM2WTdx5^!VMw)!)sp@$#09f+^vX>3Z($*ibLKafB{oR4dfZh$`pQr+4s`0 z?$?>Hva&;^dd*p%RBTu=gQ~Rou;ai9?gDeMg=6I2-8%?+bDJvFF8Frb_o!I0JZjXa z2@h9S^!j=ff~k@#tI2;*HLQYHDek!CM=mRC(#EHJOLi*q9cq%>*umSib{=ePo!~?I_V)Qz z6tuR+j1}v3T)$iAP=sy2<$yO;N>kCD=S7RTRl@B%&o~B?v24ajj;a1S?&>*H3C9`l z!oskAE5w9s?=_SR4xkj?b;SYzMCWQK!wWhhay214Qth!6H|p*pQ$3!!AWTHNhV(J3>@ z%*xKDsNbzx_!A0pclU;`)RE&eC6dBW6*Oqoo@ZW@kd_dIwsotcQ_pW$FJSTXACbqp zAU1A2fn?Ic^E-b>k=!m^+%x8$M@P1Sjz#o1a`LJHzeqV-%rn4TaBz0D!?)9B$*@9* z|7{d6ToAKY80urgdUr!{&Vw|=f&BSw(52sSgoZ~lEvc~xU;-oXB`Q=Xi&`vOB-L@H zHIMJwhJidAT#$=WH5xa?zyU)sbkw(aLqQ-VHInC`>%g;gWjyl_AgoM;hP>6nq@*1; zWB}YqQ)3e1FnQc4wCgcYmsQFqVfTV5x*kIN8g%T>g%;y|$onVoaIr_no^t+3)W@^n z|H2>7sk$rl#H?z$>9^&u$Y+HOrn6X?X$k1rvpov)Oz1K1TPz>XNY45J?oM^V^+KH3 zw*$Fy*y0j*vaXyu0#}|v42YC^O7Aevq#`{T-w*GDlAaWH!evLCb8}<}S~sfCbBnH; zw%hXPz-XR(OyL5_@>x^4s8t_Pq$@kt{>+8rGB`yVRLIT)Gbb!X@gk0xy23v zIkHb1cvh%_*WBI9-W&9QFZhg zF;>VSs#V}tdfy@F*So1^Mn`-{V-_U*fxdpI$-}?{hEBoKSu^11;>HOegoKFa%o^0d z_mfv>1}KFSOFVaJ5@X}=_Vp{Q`eg}hEXhE({|X7oDd^Ck3f#(9$DR|%aOBt_tXwvi zgeNzSoHh|UV)NAgt;o%@Imf*XqL`&u-Y9HrU4R5rSo!-F+Y4quNz5-0|_%Z5A8;j=L5fSiF2Iep@_| zt7xV1F(Hw<`r}d5u>pL^+?RB=$1fWVVy$OuQ)_QrvJiy}R>N~L<`vwEVD9SQsG}c- zLev+ZJ8d$V%+h2O?J;-B&(z_M#S@-yE6$yxsmlylLR2%7!cn_Qc`RPL*?`Jb;pkvL zw5eGQ?o_jC(6}+&xntR_dk5}}ROh*so7}-Gg!vn`n@RzA?^q2_TTklrzeIy-r6^R} zVCMWKoXlh4bNeDn7IDDDd8@MHFPJe6)^$G?FyQkOAQP9bYZAXoBH}YY}ra7op zuPb7C?w}$Ci2AKM<0w_+j>!(s_)%=L{CM`#Q2i10)iN|=+}aqQ48IC9sk-+-U+oI-yNsr zzDBD$Rnewfe=cTZAb=__4n-Ys?%EUG=S6T{zk<82Ja6<>|G_9kX1>e7L0kb&;=%3* z$VtI%(k}*6CcqEvS)~mk_&>jiiri%xKEv2GkZ(TTN8p*;@sj|?cTu75xv=1dJx5PT z6f#HBp?MBx%^Wyzr*hDM0VqVqzFqIZJP#A6$3t3j6nv@H_~70H%$xQT`=u?$O<$=Q zMS=1D?E@~(6z92v0C_ZpH|W&5JjcBiMtwgH!4VW1UOq>alAb&_Isq#deotm7H=4F= z&D|tFs_2Dbc(2YVQ?)5^O7)@qHt5)YDAl}<6ZeNObYNH5JE>ljL4hj7|1la=F2fxOFRscrA_G(z z9l1+axfTVG_-M4R%~kW^W6c%F9_z8;SGj292rKTGFI@F2Vo8tgoj=EOnH-b98NzrZ zRo8sE%XjkRQM7B>6orbEq3UHIZe2Pi7S!?m?7294`~;33+lPAGJ*if$6L+zVtX}%u zqwwW|9$$ZhD3e;Ph?$*RH9|2OiP&?D>DcGj|0?t?2UTJ3VEyu0u*hYJRXjU$_}CGi zsojBsT=^b1c{!e4yM{dcw#VDhNRV9q{AChaxA+QouW-?f=R();ysLy?dA_GYwR(t3 zOs5c<&K(1zPyx>=5O7!HLbeKG1V#;M#=7K2uK{0Etu7Q{Z{MM9ojNoaGzuG6tUy81 zq#O7A*r!s^qhmAl9PuM)B@u~X0Vqx3zCqjWIHt$_5lkFAlw~Z8BfC$+Cbu&VQRR4e zpN?qMx+9WVhOVue)5ytJCUKBPh%Ow1HS_*skD*2<=glM^31?3pge~hnVcyd0_zCf0 zC|AM-wHi0W;ghFHYmSnk9zpzB|U+kQyrBNyL?*N`t)4h;H!JWd=x#x^~SjxC$PxkOD0K(A1ym;=6F@QZnxz=MMa z;buuSTz@VkB*)U}_GAfgE7-#&yV>&l)SY?J<9% zD*gr1P#bJIaxVMbz9rL9sYHGF-1p-yTT%Qpbunv}g_D2%$z5?zybOxeD)Z)kdwAzM z(rgTx&s9^4%Z^K9!L z8sG@vLW~nOZ9az0%NI~1qaYeIY``;R<eSmqIky@(EYx4{c{XZP`AuYsMxr@il+#T#KT%))>^UABq($ zhPF^3NJZfP4vGFz^Kf|5X^U$YI!ZRwW8rtAs>9HFDCA zs#K-sYIvU>>ebU*G`F0pYI&AW*K!|LbAO(pe1am?<-L5AsE;*yt!VVsKxTGd)Hu7uUn-W zR;x~0sg#?ojT$pxs@ufejGPWS>?{5DmG}SI@YSEuAINFGA0Z% z87CIMCNwuJYorWfez<^BDRtz+Id%EQZT0$Huxi)7v$}TZceT`Szlx51PnRrEwd&S1 z%EN0~dZwb=WtzgarFC#i$0~ zU9VP+i4r=f&5=jFkNBtxIJ>J_bt*_H_?J_~#-z~fZZ4IQn5ZI>GL$9pcmBd9l`B1k zDy|l4+_>*lek%i!i7AOZcbKJ8*q#y$GwBv8>|?abZE2@oynLa8e0_LABvWTS;jcVyMa>q>)OF zrwMJEV;(+WpsL%rvKlq8r}}B~ay4$@3N>f^aCLHHJ=J;SD7CO;bv3eoBV}o0tC}>b zV}i?i(qw!-a`^FaAu2vLS=DRQ!1OIkrDmk4L=hpid1Pq|F-J{<_r$Nc--^lq$9Jx( z?!Y7e{;~rm)hit6^5zO6AFCOPo8Z8Iz_dr$Y8h@oz|%PHNK`ALTW;wr<-W zCQMT0yh>@Wb9zOF^Q8d+`l$@Y0cwbOi8R^Q}&P9zHTS4W`ou$%ZBh@$k z`m2a6OLgkVU#fT!8LM=G@&3I!?R`<%xOk|xty`%ymNSB8vL}ohsdiSarnYV0q|RQu zsxDr>rGi6ZRJ+a{)ZUGY)Q;mfRTkSkGB8+G=}=OaMO^xdI|ms}xpUjoSCF)IB{gpJ z_bMN|YW3zVRO>3W)YS|3mHKX=vUhRPe_oX;s=77GsG43i)S-iCRiiSjw}qt&rR$Ch zEVr0-uU4;-a&<4O%~wYS_^RHWda0_-JE<*eS1IefG(k^$+#t<~q)G9dmR9QW)hjAr zQaXKMrK@koj8iVodDK1cNM&Jbsh&K2tX>oMVy1e)ph2o(9WUko{*@}^QBqZ@Sytmk z+C7{m+S4s^sWYd}tEj{zwQ2P-wP)=z6~sRKCOBF(saIEhHGG8n;k)5X!*M|8)j~te zj^7m>!soRn?IvxUGIgeMb9K;olKwh!)^-)@cU#59#;OTpMktHSIQ7imPbH>5Rt+BA zRAa^rQPIju70ygkm%P1IMn)RpTdD7U8m>wd$*r6$)76^Ii4qiMpvR{*yKfiua4emQa4WBeqEtodWL<`yX6C+e? zVyZStfBxb{m51XbGiP2kc0BuR!CY$NFDumnZ(nun${E$HQVBJA$Otv_#~&2c|5S&j zHB?4wteQA+x@y|7zxujQ7jYQC95XpreN^eBa}iMq>iqdj^p%&Q+{=_#HJZ86ZOL;L zlb))YHma-JR=h?gBS|H*7Wwk#QJdHOqIRCVuZ~_kuUb_vr)G~HrshtVtQu5qsAA(1 zNZ<0Q*DqhHNdMP*Y_#dtSyiZ5oir&+ySR{cv@cao{Wx=xiVk?Iwx2z(l3A}DDY0sB z&tA&X$x|KJyHnFL?$t52%!A-OoIsZgVya#o12BjgdEzc2@T1~(rE+(1G2Zh^DNvXF z*pZ_umhMFs%$}qc&B;(7*iR#)pz76ape9cptF9e8ru^gbsOzT>sG{^tFt}@H_3iK> zs$R_p>f^gt$}XS1ap@o*(B4BsgI>`r ze|}ZBO2zE*B+<=5RxZx**Kep-bUShK_#SoZ;vp3k5vD={gH)%MEmhkNE!8iJ7pu7N z4{G9!rHW?{Rjc|{n6qGvf9ymB=*mMvfy$NDvh}N#pU-o3g?({UD=(Fq^jeMm=||;W zuD<$){rSD`b-F^xQdX{oRKKB9RfBdN)uqci2n%uWik0aAmZq6xB zA3jk2nHh99kfjC>8>(v8EUxp)LH8Z=X3bHBJ*%q;Y>K*b_^@i-r;qw+>O8f5%dgB^Fk+NJe2W_W!{8T;jJ{mI zVBiY|{+lqsSi z#ZkSueOCK&s#(8@dV7`rZE#EFq>meOh>D9;1>GIhip9UEB>F{3OV6Og<4iSu=4|Cz zv6dRtx0~{GcQ;4puffcx=dz#|mn;&#Jj$i8wJKWBORZYE{Ihs6?a2a{QV*_PRPRD# z)u!*h(u9~?4XJT%#T_0MotjU5JxZ7jrRw$|6LIOdI(72~{lSpckXfI3h|dnWEnKiD zolT{Z5zHp(WXd>E(P5JXeV^!1z(!T5SzfK%b4c|i5$x5owaQG5P}jU~sH#o-s#2cC z$bS~3;Bi@XZ`q6!prq0lRib)Dm7Wo!?mhUR_8&c|{i&S2aZ&qRs$RQ^y0mAfdd&QD z39?Ofi>5!OA|>f(iEm`e%$19NU+8ztfIMxZ3gOE|r+mM&+|AtlWz_tI*_dm6_Wpqr{0wO;%1GrPK$% zCu+*%Icol|zZ=4reB{I`fvlU#AXAW@DlAJ*6%*m7wr<{`VBb{bqmW}yrm=Q|ChFH2 z<8*xUMLe=dtBLoZ9GpVgaDH<#{d@oVdHRdasnW<` z>OkD=+jBxWc~(|7;!DL84EV$#9iyZQ)^gFUHvO69WS=rp=2UmsKKEb8t7(oV=7p&JYFqt22j)gh*Au#a?-NmSY?-0BK?}7$KVe*p!{E zaw3ysWl3STLw7Y{NKbRp?B6X5m#XUzUaR8|?wEux`IJKyb#+q*&m30?^qrPTA4`dG z!D{k^nQ9OhG#b^brt;;_NB?QIT!`pkz={i1JO4bOYIN+aYzr1v&R(AC!ZnUN^*t8} zSZNcQEv?;^d*Pz$Bc&V;dsCnfB>E9_a4BMb!P*!}3eeY{E$KpXYKkiAY)79~7HZ_k z@v2#Muk5&T!=-t(aLQzrr=Yv~WAhqiEe?AaL}1QgVWSFhp(>Y!vs$}kv3~#g<%WYj z>tnCx&s(Y-@|*g-DZ*bn*Ku6ar&d|s$A{l@BE>-Xfoy7%Ip+PQKo1uBCA%9=uIe6;$30&&w;{nRoF zU)fE=R95suXG5A?t5#FBdhyi1( zHmZ~?Yn6|STURe%R~72DR4r>&(g|iPT|(jisPcLGP?d4B=UA|za5GnxW}o>yaPxw_ z@l);OTQV1xGIC^SUz_Wf%~#Lg#i&b<&X^XGqz@TBxilOqJ!&6P+LxHTF<=!dSzKA< z%cIU+xvFYcHMhM^p}w8?6Bi|Wa}kGZIt8F@8`i4#q%D(w8m}sOxKePOp<4C(M!B;e z3YA{EW`R0ir-kx=8>~uKt4E)6Th*h-KB`6i8oCYjgv6Lpfv?mLV}DZLj-9E6FUgG* zN`AP$l|)~a$tr<Fu-&P3%_yA~I&BGs$svFacf zPxF$mdC0zzheA+!E@DV1U8bV`lly~f7OM}5P^D{DRqvnPRt54GQHvHWQU#@Beino> zMg_iBr_WqgKQ39X?DFT)dEyRWJ?8>pj+q61Bhk@g?s>zvyw91t` zHzJ9L}$2#A;dX?tGi=$2V5lALw3=jQ)XHTBsz@NXP z0M%YL?K+G{PkpG59}e$>yGhn_VlwL$9TSOdOD5~*Ll`b^{}JkW7NaqUcKVR{xviTCCkK|UT2Ix)Sp2nV z1Gu|@9cM4{oJs=A^9T>FQAM!03r7Ah4Ns}U0_a zxNzq=V#8n2=!7lWcA*7Dngs4m)fksT_VjyJ4evgtu&pGNCr7tp3rZJLy}=b4Ei zTH#^S(wWFd)tlXa9ik688ZEee19N9C$J>y2_OeXGeteD6#oe&&kf9=%E-lUe6%`V| zgVZi))0w8ydFJFp;9LB;aWl42h3WCVOUO-YRU3|8z>DY4FlJyM8PS=iYR0bh zt8nAieV%U!#d^xU}hU?j4w9GVd99s^zG)v^IK){_}Qle-O|Z}{GOwTlLIc($ccp02e%1_ppB_SL`C85 zy~{M*;DE2bp8&s?4`IoA{<8N3%kvbIX@Dkg4yq6xy-Ky&FjVm@j_P%q={!g9uzK+# zMPX;_j=N7n5zoFX{r1x7<0xWZ2(uRb#5J=Uf%SU(XJQw`B4iU7<`H*T>p6<@* zJ8}#@Qe}AKdYV`dc&o?5wXz@yD|uDKWVH@Fx8NMIe-Z_r`QE0M>l0FGU|iOc7`spfW$e)*zU)==% z_Be9mG0$Vgpi7e)s93cQZqNWxP)G<4?%j=5t9BudG|kh_hW-@Cv92S zF5Tieuq>QExf|BG^J3A@YiS8C1TTFaV##cwZ|~_h(2s_L3ZdtKVR#h~K-V6xv2oQJ z96Ejt_pY4NXN7*6xPW*&j)t|W@hp@*TDKpm>-qU*&9a54Shu0+>>{nIF&`fv9~|AY znP&HGv1aQ&8c%sj`WlXq7mwhaKR=CC3_&Qz?vq=W*~jQJZpPBjLF)THo!a8-F%z@j zuU#^UYU(tQaqzfKdzZ9t!SrQ_ib=uhIn(JE*plaly%7}=j){Z1!n!~a+~Zk2373v- zrICUnR5vruGMdf$``x=r0|hxSkt$zTFJGnwzcV;b)zFJKACr>stTGL~} zO%$e#83IchraDbEP@$=NcWk8^X>M#ebR0=>p{P;D9WB~){oxTUQKr(zEu5t&va{%mzMhmj;GrPs%thVR|a((b%*zrE9^sO;qC2>)90@tI4(ug z+?1FwRG?d!^;---XK7T+OPA`<^~;d1ZFmmLmexr3;q}`9;_nQtAsPlzjEWoAiQf-L zt3HE_2O0`$R-bKYx6r`tTeY)3D-s)!Q!u5Fh!5D!au{qEvYtWxE7$F4|8^ zf0u6E#l>U0IX~paufOfV<;$0Ffpf>%bLVi^hptXu+=8t|E({p?9WGzJKvmvGw60qa zWxQI@<;v%!0WKch2N$X{hsGyofB1dPOn7*eM>Kg3QMSv{DwtV#{roYuY}^Dt(lDXn z%NEV#IL}9w^*uaOnt~=3%EO~lT|5p7#yg)!sN~{+HZ*LJ%ue(w;3?bVrBH=174+&3ePG&AZTUNWR_i^D39bEgB@P}p=#9V6SvVE8#f2M&Asg+7OZpL0 z&!IYO0Xs(=rTTvG$B(#9T+E*R3;RVR8rG#@jln;_pXJ^C#|9M1mk<4h{GfROsqDcm zKhrOx8{Tn#kRuZ`AEa^c@YZSM%9#t}=gucz6Ar%@_h<;l3r#!rG-$QPhl~s(NJ+kg zFBtfOf&X3%aMwrX9b>3Iy?=>*i@bQYhE9f^UFi3t5njC&i$daH@hu$=wuOU}6H2(d zp@drnoYL0ESUCb&vuHN00@`UO%uYN@G+_8-o{=&p2r)e%0+Jj~?bER>jU24j7)go# zi1yX1(wd$#&oQ~tz=j<<^%#bLU>>;FbS#C3`KM}Q7#?(jD$bJ|aU$~fmop(eAG2>6 zYEz#&Hr_ZZAje%eU-3LhGGqcRyo7nkDjqN7(w zTCwZUy&ql$QjeP90QI~}mSFvPX2ZF#16*87X&5{9$=QU=|Bt=zfbXhU+Fn8np#;(! z2}wvJB=jP^_udg~6cxd1xhmI=4GSXnUaqK!NK-%%kq**(@1ceeT0(kxzGr6tCqypT z?tQ=ae!njZ`JW#+dNBY1KmbWZK~!^gXJ%(-XJ_~9&hEj)3FA2!4dTanYwGp!=eeJa zzSpw(IFp0m@ni3XpN}s>BjlsD{^&n=v=-$UeZ}Rpvf%1rJasP&b!#`nHaWdWsB%xy z!*|`mm(QxC8_Dv9;NfTARkM5az*aP*e}6=14bt_Y{6lz>RV40vWCHgi=dcrN!vjdd zdD31u54(utsfvr0rU-EAhdh*n`&fMa0@0HnJ6^PCF6zhD=Eql0P)Jws^q9Mtr#GTm zmH?iJ_Ry1WDz6{BHHD{`F)t2~s|N&da1_k41mNLkzF?4HfivUkgZm18ysImXR zyYIcklPrfrwoB(`q+Trld4*_?~Tp->_+~(c*u+J62YU{1GyJ#@bKHXx6wI19N4^iAsW=K#pAkp z+8N~#6WM^@p<9Hc1KSZ!yfB{5C*|;CS#P^@98SAVPyBh#4DNdg)Kd^!w(5Xc+{e_a zaSIMutR6|nDf*Jc!B%4JL=MhvU1xDG-`Kn5B))1qURZeYMa#BBvH$pKe)noC^^$rQ z63yf5Yj9vv3!i>6-Bq95q(19w=I6tM3jFvI*$+Q1;|GEhYGd`rz1Y5CHDalAHL8cO zzP)*R9zO<4J$e4s_jtNoHXa-|f~Ul>eTK(#kDMR(4EDvoqo=s9WjCS&s&kK07-HBS zgns{9M)Nc;?lZAw0Y^A6uU)$~*6p<~VDgOzA8c}PS*>^yv2$zDo1iF^2MJb&tDO&)F$8XSRvD!c#l0{2z6y{RE(XFuQ^ z8JEcYR5nd^M#ypBZ@%&pKge30lSkFLHzb&cOtj$swaeTan1Kgwy%kltUo?Wc7Zkt) zcV7OC837+moWT0>LO5T#!+AQT9P|I?$8&@e{X4vS6S_2O%wyfFbAMwbB13EAg;%B` zl`m_tu~94wKLE=ET7vi@9>DfgE3q3-;S?F=;zRDy3Ss%exfe{@aEs=>x!2Bqpndrq zo-`K~iBRgi@Dv-@6tm{7r;W?U)8p>ZQ(^u2;xguur||ZaXZgv6F={i!)=ImQ@Ge|BD{1uPpgdQskCyC1$oaCiu^Rnh85Ihe|Y-@A(VDcPF)QM2tvmm z{H`AvlFt}C$xnFr@g*oyY)w!kKW01@=lNoC?C>76Y1vfMr0x58`(xw-FCsnLP9AJn zF^306#pBp%PMB~)!5(yBg>)uqHwJd$>52itv_Uo57sT*{z1NlB8Ph(64^O1@BVX-% z-Hh+~LR63T;}^bspG?||uC1H0U-0805fRMO7o+ZZ9+&ldUBI^0i_w%X10mGEXv#b) zvJO7{l%Lz+K`yPhr&JD+2MiR0>dN2 zX#;#XSvDI#&7Y}#hY$CR1qFp6nDyn)&lZflhkLu2->c6)t}p&RoX{CD=24!?It>jH z>+?nTyf$k+Sc_~e|6wNTHfn<7+*j;`nV-MF69k)ce>hLEWTe$aVBf|Sh#?;#)%gK- zjuCumQy+eOvVMGAaQEV8hV3CFxF&|*`ZTWa<#AA-ZsakDZJPGqKNxq8{Ttf}drzqkhIzB!M3aV1xF4J^ zpNw1{*Zuh}B81{Oc<=`p<_k5K#QPDA`0^Koanam}|ojQ}O<=`n~CYo+z zPRJv@4DurM-1tZPr+YR$JSv%TE_bS9M~^B$RjYX$9(bUrR~^-iIh&kp(zz#$C$5=* zzyP{4a(#oU-9du0(O`O#$()kPo_w z(LGHZbl8!ZvUK~Bx;b@AoQZ^%E!@5;V*|cCr@*aRC+q$E1JlfHBA|H=cFr7Ru*~7G8x<#QYP-ltXlE9A)9Ab)CU-@>E6c zdy_o9OhwAHc;E_*riWAyQyj^&yt_N~mHWh~C+gnD{K>wSp}f0?>b=l7a)kPI@e*ZU z-bBa5k!Lyy(rx#A>Und9d#R+}J*#?}nr!#QgXo~}bZr?Nn3E@tv)!N5_8Al7w0kRI z&hDEerb~Us#??|eIe4PW$CHkmEb5w|y9)Q>@bSXpkAMa80e)f5&| z>a{%EK^gI*B*LkaC(XI@=PAoV6HznDRP(&XwUme#+5W|zNE_fln&qlsb%C-teE6uj z#Jx^FKK>>wEJU&~`IO5E(v^6TZ}renx*LmYmgP?LsDSQ)UosAsUq!Wh}bt*yrgDQr@1;t8&a0GB)F(9CG7JqrpQe7gfbO97d|UcZ_B$}50jbSY5s{`)v8si zsGTSQ*H0GPjO@|kwhEJ@p;58YF}yso+pCMTaE%^V|bungVQuh7nwcdwxKOzMFj z^42`7k{^C1;a7xnXpcmfn5hZy52PMQy%cgE?PYC)Vz0zzhKGk+JkcR(50r(}lQ=_b zU!(1z5_OnhY$5_9_D%LoyAXM~D$7&cqbRi9$)`WE;>1br`O7!_9v|DUFDG>KxwkV*0c@Q~`z^)3usms7T{&_5 zgtlr4zoc)2%iqm%2>}<+e)>ppq zrHv1!juj8GfAJ{C18x*p1jiT^>F%!WlX)L!U#8;^PwowmiYlV*;9p5gGpE_7m!R}wvAv$L_$_*BJpU}xZxQ^R zG|5jw@lO7CQ2ryyHeYt0T2`m)_%-SO4f*{i7UO8TFN z7M^8r^zSr5OW{Ay+h3vhN9y3Z?TnE+)xfC0zQU2tpY7BC3eKPBtOTP(0woeCk-&{5 zaCOSb1tW$@f?S<@S5ndC`f0z9S46-?zqrx7K23|^@ZnsG)A_CY;xvonI`>Yx^ZZ-1 zu1#}rZxsKpG_T6@`nXr+cP-6ppB;Qhe$Ml?aPs_HX@c*h%k|F$2k&|~PMUMQ9?Xs6 z96p?Dak%32oO?$G0_VgP$8qj|FYQ`<2mactL(hRPel4E%d-uiZUJL7_T^sK_OZs04 z#pzz9@vnM$HSG`N@*DQTp;J8H;uL=`y*TV|-8=LhU2vYSha>M6PP&tS(Hp3W)ym^q z5f{fP!Yh`66X#ruCH?NLgX3I_BRFx+_1d)S#TSS9tM@|p&(d~qoa^=S{;jm@!T(;I zL(jQh3-83s_0I$c-nm|jd!u-PlQie=MtH^Xi%-ej2=7`tf9BbtSv*g{zxMfBn#J+1 z2U|Sd!FA%C=j&aIVE)Y)eTRBiu-YCx-#fvO{ zEA6*r<={Ej--7*5r4`Ss_`O5_dT~zL?_GZj_qWo7?(e1lJ9+;eZt--7k87_Ej1%uX zU+?-yU~Ysb@i($>*Q0yAxa-mWD{=o0?SBXNukb23|5JJXzee}B@$*W&(O`nB+X_WAedOS*&`wY3}N_j`2z(|G^4=-nuvlK2t{lt|!zRs#R=7qI_X zcK=_aZVf$6{WXl=DE=C-lE)GWlt|!zPy)Zz4khi{cKbibN(t!_36w~nL;@ueD3L&k z1pdE~z`wX4{(n>Ql7f~m!R8RCD|1#$o$Vxz&OI|z5F-{f=4Xj zgZ~_SA&zZ@;qT!=<=_C@j^?2luBS-54^~;07`HnNVPhLkV2Xg1s!bL0Ha8(6yvq~iu0$nH{v_5_Uf#>IChJ9V6+Bx4Nz$M)6=;|em6#G2~H8N>&}%8Q-qF7_k{*!E|;tDe7H8a60g!AKvA6KHD4s8 z_0J?ZOB&ha$ClJ)Ao&XKOk;*t6qbn8kCm3BI^4*Wp$JxJN%@>sAbCig@tL>M{za-n zo*(;BolsKwk}hdXRa`OGH7=WU+QbCt`+lhXq7LIU};3ARm1 zo`Noj!lSiC%4u<2$wK}lfBBOK%}X@U)-Pi$ZyF8@XY#5@{9z-KvI?G-SL3C=RO-x!cv48-3CmW4lu!{%njo1= z27C}`%N6~P2NTsIvQD9;N_`O4mC$NI1NS2=P zDMhw=ZMl?W%dy-Et^#Pj(q`mIauQyI58>WMGENc|fU!)I{4{91(33wGhyq$Tp;1Jq z2#1=+yxCHdff+%mUyd)ptm;^7grv;VV1&@D>xA_bIR?b38 zfBAa+SWY?&krSA@p#NAZP%&=h$HqyuB2 zg}TjGc#%I<2q_&O1V{5{-3W);CKOIeAR0<~N>cBLYw2k|O3Rkbrjn+_F$juK)V!Dp z?=()*Y>-mwXvOA1y%iiwNAh;yZP^{dnofGcnb-p@1Mk`6@t3)SBFl^}oR zMtK!v+qSibBSEpRGCmc_S$F{yI7Q}XX9)AgORRJZAG>L0TUFA<=Y@F~e@mk$N#l~wn1;#=-CDvzK zG*d`nfWjy(xNc<$$?xt^DK!_ONqI#srZg3`(2a2X=9(m`QV|YJCzh6obmR*#g;E}+ zn9pB+M~m>51No+tm7BxSRb~Z7C=p&|Abe3(`7IjBRV3mf!mG$xf<2DgB}G$4i3&f$ zjmS&{AaH^!H{_3HkjFymgv1GTn`G-&WT?-C7Ht!1_6uoRGUAaNmZ22O3bz8L+q#ir zNxt@oaOujVtXA?OF23h#zNNI`vy7rInvs-2_~XU0sJYrWi4ejLk3vaki=2cXCkV1b z!rm)A@}ne6OA{(bh^rX(p0MQ6!bRYRlQR3|HhA{ta1=ONn4~US&Mva{;KhM zYvY6qF;!CYB6D^g$_uH8u#1?qJX^_Yq_*!u63^qetjZEr@+Bb=BXhI3pbn{)^4n&T zt}qr|JEKz6J=Xh*As!QUP z61nlu7LULB-#%%HT+eQX-#`*UkeCc!rS&Q~BDj?Clp>Li%3~;$fLAFH~vX| ziY&+($%zfIoGQD-mo8Imm9;HGSojbri7kOe>_EC57qI-?8omQ{kyYG5~fX zGo=d4X}6$&@_>H`a@#Ak2w9RC$o!@`RHYn$j$j)VIn^>W@s9CJ#l2 zO$Z_OLt;n}lXOifvD)NPh0I4OgYqi6LLO+lMYf{R zHn1H@n^(pJQ@#f#a} zuC^Io#G;pB-XyLtA|26du`ML(;)1sfB6*v;qqgV44mF3Mlwg;$=yHYI>!23&MSHA+c^ zNqzF4SOgRjUNaSXA_!Nz)%=*33Pj|gnUc2URj!QW12IG&h-X`g#7a(5R$fGg3d|q= zx8<|CBlgsm&z41Pu8iZQm~0KAah5lmm(-WEOG`uI{x#SdBRy%uge-K8%}UXlS~kk5 z9h5#>auJ;q+hjA6K167;c9@CWN-b!69^WKxM~SNS8nkP#$am4DiC4QzK( zHsM?P69H0ts`*=xfB6;m{>wt?Cnr2L!B#FmddZI>=jLYXM^Ll!`0-VKz&4-Pvhu-J z7F7hJ!WD(pf3}m5jzcS0D%vGIFIO#VfaKu z!Yi!MlsFE$MEFX}N>S=Xf+(rbk`BTu0pnEIQu$JY^5?2Rfn+0=XN_mNVoN%Cx5imu zZM@tGU-E1Xjqt-K7rtJFlnX-%YhF@*DIJ?7KMuKa$qG~8UeQ$J$EIxqwB>VnlUc?O zKKRIN*^^`%)QKkc{4FD@@AS9DZEYmpCFX(F@Y%U{G!)6X^t0*Z5%nDF>P+xw*jj%p9ezBEu}0O49|ayve=jf@&vW zs1b}8zJ;_KJ6B%pgIuo-nrKP8ZuLQ^N_q58Wg>j>k8M!vp2;F>D=5mrRSu~uxs@6c zQ!NAaQiVAdml)DMb@og$lY3re0OIJAlwIWNrn)KmD0%RM$Z40ULmYI;mtj>tnI`$v zakFjn9<%@G5q@kngC8RG)4WNU`AB_oh(Td!y)qM>nvk-v?p3GwQP)L_=9`yae%Z`j z^ph!DzM6@t8Or>#%`{%t^C;{d#lk=s}jJ&{XyD z()?uzAbPIllI-+e_>y{YX)3RlOGy^K`B!A$1V=28$eT6D)sJOAuP~doZZLcJQRKXQ zcN6GaP30jvYKuf& zVBtyd1zGR(zJLHyANlzicuPZj042fN9Wh-OyDX%ir+so(AnjPD=m^}xM zne+TGdNp5vQ%;Pv$Y1QBlvBBt)+d~Ckfm`_cOoB(UicP12`DY0H2J25CO_-V?tObq zZebbY=Uq*0w&;W%(3hgFoi?vLGtrdw4KN}6*s{neFDKjV-m$~%*}dDGVL8iD|0?t2 z*=h%c57vhc{9Spf{xBZemouIBl7k&M7myb>b71>Uv-{8q6T}adm$3rZyov@@)SuLp zlV;ueU(AWq=ZvSfx2eF7L`x|)tY2r6Q!ksKKz?+c@^Z>9aygia;S~zP#ph zERw~K6X$c4GLLOOPd--650B?^8Lf=e4dF|(PEQ>>Y~FwSb@TCu|1>-IoicH?<4vUs z(zA)4O8tvoP_VXOE=_V}M<{D2C2Qf9L2Qx0^C>OkvMJzTu{0-dBuyaH{@FXy;s94^ zvn2}qj1j$78S`F3=~CHd+nNpL=;bU^-QSn+%v0$|1&|)w#*u@E&DNbe%+aKyrXa`7 zc(aX)rhuzenHgehIPS{L<5-O!U(d@n*&Jt-qp^{fPvJ}G3CC978AH7*V44KsS;h~d zXd>qV*UPl_X(DrhkaR)QC&}4`E6GYhbVf^ObyZ&c^jXR(Wwv@Q?M(DZrN?yT$3?i1 z^(Y^r&gBQu#b)MnjFic-r;J0&QN}7a=H`^em9_GsI;s(*%yL-0WBby9CKLEy%gCG3 z+P_HKa_N^AU8Eocq)+D6scYT~Es2xzxyj2O%iz3}2~UR(?Khh@Z#GBy@opK1cvkgf zUeX^5mgFPtn*2*!XP>5YBr8dmK94w(pG2t4c@?`Y@kwhVVn) zjy@{~lEGD0^nv^_U+s^iJ+K@bfBD6{#(sJFj9KPNdMOi~7)$#nT-q9w1sh?UQM@QJ z)A*0JQ_3VkCOO3>u3x#vq-NzCe?K4Xhjq*-07NTbUUnfu^h$VOoy*um1D~WlOC8w? z;JxUp1e=Gw|1bXKj55bt`N+-5#Qk@UM!VK6(6DYj)Nj-VZQ8fTGZSCo0vSUg=LbRO z3c}Onj4#^!&-*;a@rLOZMiS&!e+ei_xdKy|sWj;RAgByEgv+@~ik;tE1fcygkJ5|{E7M)(y_x+CyP$U<`90<+li&6+1T7vPK;f-bRilyYK)lhFjVyn$DyP&2bHP${3p3cusrTq z_dUWxYT|HGI^&8Y!aG8$OtfY-Kb1)lY7RyYYJY{X%3m0vHms?2@l&mYK(YRIwKKpuwL^75-LiBuv$;0SdJ7d&{r!`GvA@r5c0+BI! zP#pPT8vj%W1X?mzJ}58YMR9W^#qz4W353X#53V|}^aNM_Y$(b@pKGvjRyjj>ZTc|54W+z67v9+cv54UgK4MB5}ba5h1}pNtJe@@{%^qkap<;#@}}@ z>eWreoS&9!ocx?h+K8JHqYzuGHsTZG;90p6ZXfd`uH*=#%1x4bk$6JM14BmISq$vc z6}94PAwDh+wc-*G8xx0ry!tj#@-g<-o`?>MK%+Vh$g?NvHS2_3hf_37nEd9m4^TZg z0x=?|_&Ca{9?qP)%?w8S%2{`5}x;KS8AByTQ$Er+)BB7yw8blf#^ zBpNlSkBa5WVeEaANyVXKuQ{|;|NM9=4xA*9q$3iv^$pCO@j1c*0}#tHR`c;ihc10_ z@=U6>E|sg=P>Un^Y5x4Nced>a&l%}Ss2>}NufAQY`8yoiLJ5LkNI67Tgr3Mi7?pS% zC)Pk)bOHMX%7C$qk$%SFtB#R|t5=~MDJ1?$-AcVKo$)1VhJ>=-65w00B6{{3gyaii z7lB0!rXj3)bwo$kgKvNznz!#sJxO6lmVvHy67cxDpDLUvt=Jux`M}TfrlEFBH1!|> zb>iaTK^fmV<}vELv=#Pugq1s~JJlcIg%7q=qNlb^F@o&VK5H5=FC}B#;2{VO3`Ja2 zP581+eDd`y0kil@kZtbB4g~u8;N{8hN($TH9&~8g7&U7~pibTT@UBz|4Vt&bzT>AP zQ_G0Rl=n(P;-n&_O#G+%C5aN`ns06B%lF@l4&8=8q$Ooi8$?;y8qUZ1ALbxFJQ8uW z5)kO=i8k#z;?&u5N@h^MUbyGJf9QRlwsy%+b8^+8Bnda#Tj*GT0GR&4>qv->MeSNK z+ScM~C8Bm>9dsKs3hCJbUx=dzwj-iOBx2(0;o%4FN0%-`v2*W9r6u}g+qRIm8Yfs< zE@ar!svIO=rckE}q-^|?@YN^pBOx&Xi&w9+q7!*Ie+nUTA|B(UG}3Myt&|av&{Nez zHty-%8Mi(1td=)lbXVwzS~6@{IR~+!fk;S5KukPSpTL{wB1+IQ-PY%*%|BD~@WlH5BjNEoz#N>h?tbd^`h$;F?|Px>I+U%N_W z@g)$@@u*cRj`E8|Jjd-nZr@`$FL2d~D>F+G*sa;#dlJmz~3G(>g7U*^%a-oRw^Kk6YVeHzv5s{%0Xx(8L_Uzrqj^_j+Y^4jV4L2&v zHSq;vVs*IUAQoKF6maqW-;Sn%%ah^XBJ$$UY)Dp7LcQfjFHTM5L^V;9WF z99NuHcoBU;XF9>OaTkx*q^qUlQfN`C%dFUEVNvoG1<-$TM+i|SLAgr8!n=NRFP%cS z$SAz~-Qr)x3j$d#3YRXXAm!2}Wm`$9f&4X$0&5xaSx(y_u9)^YLMujK&vE+#rZ1fw z;1=j0w}?EB?p%!M&{!PQ7ck56)l_?@138x8Y3NS+J-2klsC!p`ke757q)oqfbWhwd>RAWaUW>^~BghLg zzov1CDW!gqpF$Pz)%tXZi4JJJu@u(^n`SFhe(jx@0vT+H@ULQ=OG(AKj0{FGO?hG3 zzmil0m+H9kwNxcRWO<_?N%F!jhU-SbVduc=g$M|a!scUV6jWFhqm*B0^+Y1Cr6RiS z8utB)ywqu~7;z_DhsT3Ex1oMaB*JUfL?sUHKA*L~LKF8=>QVf%atXK!fQ-!Z_~Nx^ zxPI+}C9Ah-U1bYz4sF*pU+j=eco)tbL_k#^On80jRVX6Np7E*70pg8MXCgBz7mts> zhtC!9j)Z?rQ=T#|pXb0Z z3gaJr65q|4g_*Oy!Hk(Rux|H$$}blimM_HF)5*xo$-<6x3lLPv5BL1z9VN7V`8>G0 zmB)y26LC2`16Qt`$GXiMaQTXTsrc92IFuuAQWOb=9L)VHNS!h5diG5s#a`&JjFVYC z_>wSYoE^AnJ9ZWE(xIIQ_lv=Izie01wq7}cC;ccI@}*?N-4k#nGYhL`PerBjWik8bHAugZjPT$4X%f0dzfLEYW9VHxIs_dPCMzJkp3bWDBqF;wuZ zhF|s^P_A#pue@Lv?{j`doDcKAd=nn-eps<-KhiFy;;!x;QKxAuWNAhJT5ie7Cy}0& zrDZ?E38I}5N+z~1`3^pn-0|TzvlV*ifSwrlz>|8Pf1P$J33gJ66Eq5C ze-7{3jA>s^#W!D0(oNaUbm~x3)G8GJOuynT1Yx?U!)~2pEj(ZnEjKu zqEM8flx3lW&Jc%`Dt1QLqrXc0}`{JJiSQ?yXydUVF zV(%&Q6GwMo%f@xIZJD@y`5d0Ob2zGa*2U43bmU}TMBSJOJUo6p=Fa&J-_DweZ)Sgk zbvyPj&tl}HB8r8pkH3x=ko@BPiq`p6#Nus`dv34W2Gy=!MY-;s^5%MRZ~zYP*?|CW zKfL+Q2U?diX3fBK*6H43C#|h^0B@ip)DngGb%~_S(*`=YMORu47oc8TG^J)_($5C5 zTvzp=2-S5n_mj6+{}u7Vlo`m%%E9^b7qEWoCgjp46$KKMvGlb;#~DTW3x7_YQi|)q zGd8CPL|$S4H7uF;Edm4l@zu01F`KfRIddjv%>5q6FI={wxegyPj&R0|yMO)>)yoEA z^;Y`=Cgbs6E77m>lzv6zT9|hpt?DPB?=25dUar7|A|CU^mWj=m)FKGp|7$rdb_PZ@ zZ-$3oe(P5diml^Ur%tXN_y4uwTV z@FtXQGG4~pJH*7r#^~BzO7aPF@>GiP^QmOMoiWFBAAGYZOuuC2&R=AP4I6IqGt$gC zx<%9q^*4*Y{>tn>d*1Z!JKWT&6~@I_TCMzS^ZmR9X5;1^#>cCw=|5z!2?+{fN*R-U zdbc@|lws=Dt7m>(@}t?hd8-Kwi6hb&I(xy?Y1G)1$vAKJ?mlW_>ee$J<;BNZ@|Rz6 zaTBv_m+8d?>Bl8MnP2EmByJ(03H3~$UY$&}(%h^_zml@<@VyH{ANc)Hhk7{6s4D4dc(kJ=K#bh&! z%i)Jloi~XsTA03_I0z4Jt3!!_yk?lW-_JLD z_UxrAOF0wYsHy4FslBO4w;>u0mQ%d;l{@}eM*)%Vmh~IW#mkqCJKahHqvK39kMb6d zOXAX&&Yn4IX3U;tlFytm9_}8dS(mP+b;G(urLIs<&!0YKzWDMR)1X6l)4YD7RO5An zN|v&wfiWA`{bJHHGZbH(JKW3C_nP@9CMK9F?&avBNB4vB6-{|@$zhOc78e9@wb-|P zwL$p+6Xfr2zM1irNj`tcG-%M?bnjfBx>jh`u3kpnUB!JN?xtt|0j7R@O~&V%otw8C z_rPG|RR(6-)Gth?TY1xO@DLLf97y+<0(0xg(WY0wUZz|3KHS`IOCipLRlLJY)0Q=a z4Tg%QN&SXw?fE91ZiOPx@&rvjc|hCXxr;fbUB~XGb&JN_>M8Q0-lk_5H+Of_rcE=Y zAV0{zV!rz5ebcJ<0CU$J{Y`ng`lTLDVoCZpvL5+2<^p9tZT57NeD<;l@b)#W zJ9ja)V`@?lj!};f(Q`eYF1Te)1rLTw{BDn-Kvbqq^sR8`wkio z@=V(0RCDm~G18`U2dmG$Y#EbF5!Z}~H$D~0bDvE))4o%8)3HsHB3_kW?h|U%x=T@9 zLY)RCz_X&s%1UD#%o}gMYic&^VBUS}QQZ||j0ff9MZFUVl`@wvUNUp$&N7FN95Ll+ z4C}RSXF9jO$z)x;Xm;%0Yic*GXZEaFX%?;c#ni0b*!1kx*?4e2nYdz|PdQ{}eLK@6 zrDU4stvZ@+?OT$f-OC`G|8++T-8&?xQ=GbWaAyPE)5=t+KpmnW8ElOjvuwsplapV@ zyfFEBQ>Ai6bB=aQ$}sQyh32Uz#+h>Y=ghvnCrn-1+%>CKnKf(H(se4_483)b@nYK$ zFO*GN)|s^{R?}4`%bcJang{m?UPwM^4xdOdwHm~kpJ&Z5TUkF%+q5_BS~oY_StoAg z%F`aR-AJH8E0vpW)>9|F0>VvHcnDq7GR-RL?#3;ZkL zbItl~`%Mt*>gIucO=ap-F5N}XWn`Pc&=6Cxg1d>X6>U6NFXi1!nKcXMn3VJ^Gi}NQ zQ;oKH%dw+u?S*FH!Ug8|*}u_Nl{OyKol~ci%-3IhX-a$ent_7`nh-xv_7!bR9kOX_ z&zSIxJ1+Yz(HSQJN zO!b%wCV=Ji52$YX551jGHblmTayvmeQ-NvOl-ruMt7wksQpdewZe`t=MrWR?ekLJ4 zo-MYNIk1;~LH4 zTv{)kI%YQPJ!lf^HZ>9P_4Usw%ev)1n3HsG{N$Z`+2`BXl=GKOlbf2FP_IfFA+}R& zU@7)18EI)|*@~rR59w784Kp>PVok7rph-?XV*>n*nK6BV={(>zQ!3}2nLT%r89I2F z38#J)sD3UBS%$>?@N(moFK|)nR=mAK!0|r)IMsi7^XjeQ9>= zJ8oKZ>}*=y)YxpL8)>bJ%0+XJ4&aBTS7gsU;2i$Ce{A_(W1>*FV zIeKurNzZUMG0`>6%vs->LrF(XVr&yrzhQ#8n0bk>LKn>Zh3ifK5u?q?eVfgyRol(2 zqwk<=uqVegm(36J=9%@|ce5>UTr;SjDWomfNmtys`b~{TLAu$vb-SsN&_KsuySJ}1 zUV$}DpkIJFe<9WUyz~ci{Ky%$!9)`o8EN7p{mi7--!{F5j5d$mKTz3pXMfw+s{!lf zvRS!qohe^A$TVO-T!!_K&2p~Uwbumr1yb+Mn<(1-%7vMt8YyKrRJjVKLEQxM;EdOtUd&b4|E7z+D02o*S9;ir3-U8ipYAe!20d2vemH;5 zOeAutvva%Vw7X@{xpz;xDaK1bgYi$jg(n{!kC=K*(6()7#D#?;)W;Lm{Ay$Mo?|G? zO2gdiL&zLAQ^>5xO(T`?S8?%8%agqEjvT*tL8y8pcLp#6x2d=TP^NN*?whIaqfwJKIuG*V_;IM@SrvQE zCS&(6Kca8jHn@~!XPjjwK;1<+dtzq`#hvO39;Pc+<%+%-H1IauI{aoNH1B|Q`wnuN z{49DV#^Bb6{)U>-F=*AS1*%r5h)%=*hHTRKaPmtS(62jPl)NzR;Ww2>nf0~B$;5My zjzLB5%IG;{5b8vRp>CbVIB_mj;bdlE#TQc%S}6ki)df+6LFtfg^63+3)ua*pef-e3 zUq2+)OF&hxcqE<5Mn=Xd4C>n!{?)@Vbm&locvYdR*9he1+8Kv`e)vABRj!PtjhdiO z-#+lDPzj?RdQ0=vrE`_LtORmChk4n5bbC2*95*#;0I#ZE=+|!$8rN?CZ|_>zd*TY& zO2HKdgR@31{;1~Rfe9~Jx4=`!b|JoI zZO&je;%eFmbm`gy{YF2mvpIQO1sT%24}82sFl_i}3?4K9jobFX;d9mnjMKTy+s;wh z`4#@k>{)&$o_X{U^zGFZl|8E9y-$Bo9(9JBt9WNl?nmv|+K8%I7b8d9jUL^*px5p9 zyVfhLe7}5cqPCwNcmA!|yp59i)sM{i%R+o!W(FSq+r5-&Zwwkd5dC}ggh!b&2##rp zW9QCb;-mMYeT!x&UDg9F+jT)7wv`@z`(fFxeWaI%`$qMkyGJ4#)oqNX)V~NnFZ3Ar zh&G)mPd~vr@gV$QbZ*%io>hD>b;cax<>Qvl?J;=NFw}2c7fl;9pc_;G65IB{an1rd z4KL%uany>efzMnkU^$eLtazkRNB3;nj75tUqDj35Xxr}&oa9JR@V0R^C^9Sn4V$+> zuWp^`qF4q0oNn9a`_D{3w>DCivZ&vn1Nu_`#*BLi=c!wD0)p|_^B<4~@a5ZYA|Nmt z4VyMWlMZ)qb`RLIZV779x1C~`Xezs91#uL z;b=Bxn|lTK-!&Xn0|Ky;E*!FkeoM!8s9moUF4CRzn`vJVuNV3c?2lV-z8R7J0krH@ z@$I5j#LYkgR{=Y9?T&~4{ty-~S&OtRTgRd+d9)b^wr$78b!#wU_%H<5Y=9Np4^p*K z(6m-8o_psr95{RcUo($63w}cKB|CecL07=`&0BFrGY+@jHWK~&_D1tw!*MY$8;3S7 zM-$G3-}%tJ;2;w1TQx(eveoeHhhK?UaCFZq#7Bi9wo!BR>(dL~6)IxV+wW_dlnvDT zUa@s?_unRIoVA-*@Xn+s(6wWGlqv6lhRs@0?)}iKS6?joWh*9(7)2MCj_P4>;=pc% z285wqr|ziaABP=BPhsP#ui@wAkItRDAcCt9ZJIVlRgW6jcarTsFOBZ)qfnt@C3NcC z6`k4cOSx6XoF%*P^Ry4ptZoAg8hty0LxRwvG3~uaHO$(u3vWz%9$tPy*mlCM=nAt3 z*8c?GYQ9*wY75fO9Yj5Kbb0cTdwzqCSl&*qwGrH$aL`Swn8eM9VPw%0QMxZTi zMkSv}tlqdC!`e2%@Vmw!woYv{Y0?0GTor8EeHhMXldep_$|^X+*^`ISAgnt24jPLL zmrb4f_}wok{GlzY5mz%3!E~V;IB*DE4I@yies^S$-1(&K zXhz%Ds#Qn4{rbzO?iq^rr!COzveHxVz_`0HkUHJ7O)GE$2Jih~S82pfa~HNQUw}Z`#U@SZ`7m-M znzwF)hhBMCd42EUv1rq=4Z05MOB+`U4d`NA*)IZX_9ijC5K|{T3Geci@Z9qgaa&K0 z6&iHKsSDOSW9We1Ts3`K>Fi&>6mjg&dUPLzq@#z}UI)S_I1HQja>cOVGWxLJu76Wk zoViFHVmY+mA?DeWdr>ne5D!kY`g?fWdW@tkj;aya$hGMJ06+jqL_t)-KHC?)dJaI3 zo;@)4hb5YixVtr|T?>JM)!BvzAu+KwqT^d(`WN%qha}LIsyW@U%A$R*UT9Q5QSI`W z2VYm*DHF!SpKfQX*RDX#s7TEGajC}3&(90z(*@lZv9a-5XTe-K?c8^)rZ4>VP568J zqf^HYh>c2QpIC?eOgvIA72xj!dZ7Q!gVDHkbJVL>7d5CSQFUA6@cA^Yt8>RsVE2~I z_~xt6I8KSg9S=W;Oxn}zw96dVM5AMuu6XdlN3rOqUyzw8qY()O_<7NL)TtnJ@6Z{M zHDl4Taea7sMj-ioJ{}%60D}hgN0au=QKxntgolTr2J<|dk%^ffOhyZ~*MYYWM?^>% z8aJ$m@>P7XXxAaU^vt9152=AY$e0J7Zt=s9Yd4a=ER1H~6&}?B$5SpK>EJffsEQ#s--@T6n1D5G@49HB zW4SE6I&my&ghrul^OmSvpRS5gHPGs&A$VuX2WZr&5t=tjM1|62(0j;WL^Dm^Dqj0| zERm7xpI<;p{<)@ z-G zeeRk2;lXjscR$&7cR%y39?=LZ>8>i?5i7PHz`jjC!=3&0q>s4z%oW+;?VDf?qs?h6X z^g_2DgE5lt0f|l9V#boyEN3q6 z>d+GXN8g6JjT+KDzA0txi@GhkAvK-vC0WPOqEQT5w``BN@Mvy}YJq}HBSB+a*zNZzE79pI7J+>UFYANJ@&kx@wWw0&|8*LSy%;OVq^KBFG#M6(1vn1$$$2~|scL7Is zZh#M8YM!3_I)42AbCfS#5#KJfgX~Mm$5FR7jY#i7Dwuh*U*f#EJDN4@z=8BhtX?`D zks)<(^!RCf@!8uL!ww>_dK4y1dKoXh`U>88|6`m_OIMLz6{y_t&((qtu3m(Q3LXfE zYJ_E*wlXaj!&DCO5+IIzQp@!%0zn%W%o$$kq?-54FiXChGl1Z{1HFER7yrZbnM#WqkADi>Mdh zo&&-H+&^LI~5lJ=nzU)G+Q zXkRQP`2x$E$$ds5L%XEyENsimm(?@-(#iDPvuNF*E+)M{Pbu5<94x1Ce}4eqFR}aI07XE$zZ|%0uhu7;?Ni(-sk`E?Bu3@^iBEAI zvNT?K>l3;wV;-C&*FXR4ux{;?!_}e}IKqmF_ z*|EdXumAlxba)3UyL;f-S3cCN3$wE@a%gV^N7mwC;T*>GY)`zZ828*1eK}t-_e*%Y z2Vlis85D~=fb^6Th~+@)vsvHibDm31tLD!^1N!xc^2J6bYo2)ZQ*hOc{m%t-Z{7$k zdi2x5o*+GP*D$_7cf;jOVENn`cxL>4d>Qw|oqvBEQ(k!;Q>T8-JuQdeRVf&+zBwBg zlas(zbWHi|TRi>vKM)wv9_P}|qkHSdXxz9pI2*3KFF;rqQ; zo`F|;W4v~D>9xUZ!{*IQ_Z zvXFfx9iusLY1z6NLbxxAYY2QX>5XHjxMzs>gZi2(gO0s#!SQo+QMskn}W%&OlHS@5<@weQm@GniYgDohx9jpV)Svosoiu zaZ#AQXdBji`zd_b$tSezh5g5pIMI-TCgIgF?x_#yZkK|fKyQqD%yHMs!}xnfA+d31 zwZSjnH;V3A;rNj+V-j|6{~4hnQS9)qV9qD(@T&x1*^a%MwsXZ?R4rQ-3zqKI=gbq^ z5a;EgbyCK|n}ehy8b4;}VD883OKAXOMhruv*1fQ5<0?)R1z`T1l^EHn4X2JPVZs|9 zYMY!r^&NPY3&ggA7ZhK9QrX8fYg`X8OnL%rzKdU+@6sR)O`gc&+V+b7eoAPB6&}`9+NG(*>2PdE`y8YklTLVk{J_Yy87A46T*kp}f!aUgg%2hnt= z-g!vVWO7T(!~uU_4%nKs?Syl>w~jAlNk{SNlu2mctT87|Zlc|rf|n<~h^@Q!vro#v z$lje1Qlkzw^I5{mMPG2=ObfbaE>s@xf9g5R`1n z))OCdK$n$v8sVW~n6=~=rEw+o80tkvqGiXSNI7erF<*J=F0}44Lem$|ox;AZJet;N zhhs89v3wS)2gUPcvH(vG>kIlTV8r-mxPL7RTb3-7iimkeX8J*7bYuj((m6hau{xE*^@Z}?@fA~`*c0=^J;m?l2Cv%$!F*;nTf27 zRJ3i{2sI)T@Z95%aPl_yI ziIbe-3Y><2zBK_&TMWd9Z-1n9_SR=#Ve;dnxFFzxne!KMP(KDwJo^#mOnnQLs`_Kq zo`cHGmSrKvw`_N-sVgK#}=)pcIzCn8& zb-D0={@w&MZrT@nwy#BKuongly$wMj;am*phA8f}?8=FfvmC4BW?e?_jxAB0lYtYT zd_BCwQGdo{zvTOzSPm~pMF4iSzoNt7cf~!;OmNvpQV4+ zJ~9vQz3@17%ndKU^O?52EvuH})t8<|^#EV=x%qZXn({hcfAbxrUb&1t+m>-(aRt2g z`L~*c8P+crhrW*uNHAGL@@yeu!UoEW;lYiriO2kv|6ElxNUVA3;ae3zShAOnCAI#75M>y$_7n zK2k?nvVx)GEo(a@?!PV2drsJry%#nV=4EjOtRY{(9xvi`{&$?+42oexzJ!(YKH{5h zD_o|cOn&|~+%jT39{$IJ@Tt)lNf%wc1umgy>qPwh{s-~+ZG$*t(Y8n^LiB@oC&HgI zahEc$;QMc1WT)YS9lH)|ekmspV!_<`R79KoQoeP^u~FE0VH>BvI>;;bEGi-vnqmK% z1qdx)1)qLzjio5ubGP=P!rqOuXU?#}yW=L#)V}uGJD4(gGWz%F#94?CteO7<;$rJy z*7x6|Qz2gZsp2@iTk(VuTW+E?LHhW$vIN7@*Gbs;`yq%qXWt~Zz`t5hR zS3;2T($1kn^%@xU_h;2;N!-e5A0x_#g9WZeS;n(*_h9;lk8|b2L7#+IpLv8clcjP0 zQ_ten*Cw$6On{e1Mcg&+8BLd;6<^5<7-yS{R>0VqojHVHA8$M|#SVG|Zr|3qh^bK< z$4_#0rCSGh1qS2US6{;v{yqBWeN<#OOrN=gMs)%_X}FJGbX5XpRQ@)sGe+F&@Fs$m z7ch~ma4*`!LB_Ey@Z*fr zX3W`yPh^I~JA$iE9D!>nz~4spz^GAnhWNe7W4Uq~$Ndbpvp#fi3x4|HXI!~(7BQSb zjBD5olU{p+gT_g?^Oj*OOBK#`9mUB*+mIL;g21pExbNX7uztgK7kxSSiZ2d!`^t;D%PehNA_u7j7~|6U^mULYdvpcQ!div9#7?sVS>)Ht(l_PLGnX}ytXOngzC^!f! z_t-&`#HF7~LMUhdC%y5xu8ucr-4Qty%wrFZMQBVX?A);t!Br~Zi|-Z+xVB5Z;g8l? zQN;o8zWyw{`2u!0n*~!F$kmk{n-Ll4N8K@)^116}lJwbEXh`A8X4Cj+)NR=TXD`}; zsra7Tz?ZnxRDAOG6dlM7896}*@!$aP%7s*4iObACqm>yH+#2l60}FLecKLJVdv*ox(RIScXRi_)%>HmkyP3~gEmqwlhl zJrke5mj`}?;Lw@#xN`m&!h^!_)+aML^PY-u{~(Ng@?D2T9NxVIQQ>hoax4ua`g3*K zCkPW>c@>jieHBkW{&(`~hPOVN$Lh|*10x4;Kpcq|xUoL0T2&6{d*Dp!1w3@e2(C(W z!`%7basXTvYd7t{z8z~&x|A=zpSv1E8{LH7L+>uqiOox9A);~!w(YZn@C)2mD2EF) z;Ee5o6dNZDe)9IqaHsC>J(E_%@8e_p;Fgik;Lx_6@F-gapZu_@m z+PVx80oCY1G7_V29*T+_aJ|GA1<^}=$9CDK)Z|0(4hY1X3zjM!odIT9AGv=lJbi;O zUsoceU5NFTAi7(KiO)QM3LHFr%N1BBcFhl8a|Oo}QSlA%?8Miwci&-6lb`n=dH|6% zYGMCb+Xm#|mS(hdQ{J7=)I2=%(4Cx_4!{c&UqCch4icN(#8pyTzjqAlk7hg=BtFa! zUAek?iTh_IIQ!#vZ&*eT8ZYgO-ko9jkqI3;(aAyKOV6Q_doVWbck!cTR}y$++$i)N zHrhV8eoyY-h-ww8;I)4)a>W#2-IC8ZdDjNhzL?L|ok$!_y@<6xevX*hH{n87KHhot zEp+cTk^{enXy0`Z4-mP4ecM;@WhM~seEco<|6Ihp8B@91SOu$go^a94!@JMlgLd6U z>GMxBUgBz$AJ%U_q|fJ0AL9PNd7OYM#C<(_qD71DxF{Re8P4okgIbk+@xxO40+yMX zhRd{@`}gj_19#rR32$%wxO9WHVXjt`Mjk$Y_YEHOQWk^m_=nP2@xvQjiQ**cW=rRM z@)69R`yF^%Bp&HMfGf5gk;)Z+3CV{xB9^O#OO_qL!jIlVuy-id9+XKnYD8{2`f`8W zBaeQf&yPPk5`KXZIKdUVi>LM>#4i*dPGg_P$(Y_P8)5K>dlXJeW5)u#y3AtIfi0*T z9-=n&sduI;9H$JZCc=kc-w$Di>8c$!nJY;`A|o0*Ge4tHud zw2PO1&kS)!jhW|ke)s_=l2QTVxM??4{eGl2Zhf7lZ>)4WaFgnVDKng|UAjA6x;#!} zLiad3c6{J8ZPC&BaN8y>=AxZoVvB!IEN2?YWSLbG+?%C@0SL??|1KX za)sw~rZ2T~-US-GsZpkwsprs%i%c9RJ;FFvF8@tg!veaVUz)4f!Bv==hf{-Lb8Wqoh~0@@1Sk%U5OQWnU7VF5H-ltx(+< z6r;|NfA?JN}!9C-}F-A|kR{e(TLyLIGanVhm;&iK9*J<3UlaXeU=g}{Al4s1v z4bXEOsE#-_D;A=(hRg4=z8`K~?|lC~`490Kv|TZj(iNJsE7faDNd%WK6#{9kQB>NJ1Zr|JZeLBHynQyLQ1&_?m>Me-!<7DnTvS*XD25L*Pw?Yk6m_0* z7NTr9U9SAa(V|UDga!yE#!2F3f;0!3M44slB;m?G$+2cas1V6{+5()^0L0|Yi)yvX zFoyhmk*jzHrmC02y=ou75WGBlCRQ$)jd1G6J^gSy)UDe9 zpC9~=dQkzCuMb1*CUrCAX0sQh$;~j``=xpab{Q}wtlV=j&iDg2;*t2L_|w-O@_1?(+Er#3iwPsi$-`AsI0R ziy}NUgr>s-kta_qX3UvGeT#fJN4<=S)K|(C6-Z;*%Gtr`v$t4R!O1W4kf~LbCj46X zZ{9dcxm!QVYL_?h)E?L3Zr~;blhbK-_S&sP)N6RRRD9@sk$C$SPTja|WOc7zzKKdz zDIc9PgrzCZWSlu2M|ofyTY7{!b4Ma75A_M?iWRSnCe0hsjO{JlqDkI-MT%3ehj&v^ zN27eT8kqUUE6A5U1o?|q!j6x3p>NkGuyx%EG^ke_PxTszYe`863XR6nch-WUPPlOR z5Zbr64^5igkHcrqn=*{3wCX0oYEwr&J#xi-BO+u@-E;MU>Pty|eX1X>B=DkbW~y|ityPLauhoIm=dYc`K25Q*Xkp@uomh_TKlSwngxcM_+^NoBF3Kz{o9I?NXa4J^48`W!+M?7`Gd`W9Q0k+>O zpHS+>kSLJGek^^cnoVjT!hq-?8%@3k<;cd?6J#F8{z!d9YjBu;1{`V`1%^^}B?s~s zDuSV-M&g0Gm9TEZd$@j^7u~Wp1&S169QB<5G;H1yGiQ#bo}kS$fpX?NQA7q!70apj zhfo&1Xf&{7$rQY^?F+m#X);X~1DDU8Mc(51dEG-i<^K~A84+R9n3iH58ZU%#>nz+V zU+~&mTl`0c`y$-!emCM%Omxp(T(_^CNBj1jaO`#%7QML)G1SGOac5RL#i-G82}3=Z zD0J;T0G+8r_~Tc5b-yheEfitVIgle~PPBdSalA0JKfc7Qf%G+2@-v>;eiM4L)^{FNN3#|Hmq*q>F3&o>(qrJ-H^Vo#it@y!Geh7T;or@ znOwOFVdm`Dsb^RQ+do)?*B5WWEAy9PC--5{;8Ez* zWdLedt$;JEXJAAy^5mn*e)g5>)PJyK;vi=}nplqtH+?!S={9;l)(O|0Y*@Q)4N6eI z!{QIr$-7QH*wV+%#~TyIVeaBpc;|x; z(6V+l!pWJP__5Qg59Usrgpp%sV8paVXxE~NokH1F>NIbMPd@n&_uo?)b6=l;S~aTU z#mUo232a@GM%gM=kUu6HztX8A6pV<1IjGm;$E!m=#kgt9@!Zt87&&4j)~;BIbKf7t z_!p)LB+eZH;MxMWXu3KL+4)EQf&RM6KrT9*V+O}?xm&k2nsO*&430)1lZ$av-oTI- zC!lJ{Sd&iSHYLrnr;(2Nr?J8_H{OiDPW`QD)T+s=U5umN*`+HWc`zPzZVKi`LH64u znzheUx;S-dIlI%``#D~-@Z*VdIP}dPGk!nV`cd}Tix@Se7dmu!7~lPTmOM`x#FotC z=If7BXKw1|DD$n0*TW)0k+(1v%;<9FDTbE!x1v6~#x)038rDS=_3p%bg^^A|VxoBu zk{zRhLc&2&e3UF*29u^vM8)VZY~J*#$(ct1+B8R4_H5|B>a3H0jGi@I?av2giH>iuswdXKGJkycV~;MVo49O#T{ITovo1=Vl956k#+-=G9>#WZ z5L>zm8c-iNJ>xctk^dBp(0~RC5^hloMVpA%G{e{AE>IBJSg!b`T)AQ~eajXOcz#suCM!v#ObDfWqm z4M3F+!w!@ok5QK6%fSQRo464iXF|w-$laixwon1uEvQhrBtF{w9uCqbLGNdtrv7je zR?k_?Yb3HEBrE^}$B)N`cNZf!>8@Y*Zm3zS7B+vfhiQ3UNrUpWn$&akgNaLC-4GrX zYRua!dp0;J*dIkpmd2!4#vnI`_wNrMrdfE0d1p5~Z~({15Nb;e8~HrC_ikq{HaBTG zzPfCK`BXoa`FD=U)YIK;c#L}=?u+L~4rN^!re4NMC2Q^`&7|-OAvy69rUUZ4ci_Nh zaQ5i8*zo>`czewz6f9j4^=gs-B){2b*jQ}Z@D7URjKZ)!y-}U`Tes~a=0HA@_omfs zd^bW}9wRyzX)}8$QvGbtR{D}<3fc1bO9>N{;kV(x+4#A_ZJ$d_%pebB`;fv6%w}9M zoPbIHXZta0B7tWv9%VhGzHh>{8GLur4r57hA&w|Yl zUvB&edk>vOwZ=_Rf{NnDkNt#m*Qj`I`5b)p*{8^!o%fNDXtlT!^ODZ{AcQbYo?O?m zC|kP2ONy_YIZI`0J0V*(RD+}kx;rR!lQ69>l9*dE^YlQa!IpX{h@vuJel~Faf~C>7 zPY?9#-xvM+^utq~A4A^!`5Equ54LUK0;DEt-_sN+J~y%P}Kc;{=Z;9ZiND$&8;2 zX=t#t&N54-OBUr!k%o>PI$=QHzUVu!FM9Rvg?sAX%^Vb$5-}uDiX$M1_X}AkKi*1` zn24)CpEF+ZH_zjhS0?eU4c-|-V<07|_#NOIi~-N|M4th@(XU@W^y%3hrHhv)z5C+Q ziHp*;<`Ce6^)%YBY3Juu$P6`k8PvP@H?3eQMJi%vPbK4a?b2lv@A~C)m_G9jQ-2K= zx^VQw@t<8XNF&2D1t(8jfghDSoxoJy%X1D1;=veW)4Ju@xP2D_v>2e%{TV!`<)F*K zKsY08>S|$BERM}fqcM@W(3g1W+ixKM`l0vJ&rmTwH&S?4My`Uf7%}E~96WRgQ^yU% zLJr6ue|6a0+Y}U@3!S_6#3z6G3wCW>i?6@@9P{2>Zx+^C99zbeu(P7JW%z4f3|zWE z(=)-y07(ejB87L>kMrXoxQ(wY9Lmw;5W`` zQK{dTj8!NVu&pEO#{`1T(1&@`;OLlpqw?m8L0%d-C|s-z+l>9M-vIRK+m8zBPjc~O zY0S1RbC97KI)C2_iQ z64@us)Xnxee&sZZmdj7_NZ}n>dC;@>Abft{0N!6Y8{0Q+z{)M3(%1^`%;V}Vg?Hz; zBe5AXd5VB+R6;M9JDSWyaXY^CzBFFaESS~`* zhR=y(XRSZkscBOtRDAWRCVOY&apQDn^!KQ!Kd%BjK(>rdt%ah3`CHCX$0mHQpqIf-kJ|L zzfEI8I}sKXjtoY+aq9{fkXKBaGp7#VwYl@SffmX>6OThbok0EjS|E}Po)3+r^cypf z0y`YrskAO!c=uS(cFpnPgjaFmdZIDdk)BTaV+u2l5C?&rqXOC2 zj-NP9zB3Zdn>RK2mAj`Ao_wegPXGL^`Iga?`)TZH<%%WveCylj+H)Y{M2o~pV*E99 z=b|FIXa)3o`VmYR)C0S}I80d8k^HcC_h(2Z$I|}cW+pF7E$Y5@bdnJ+-34d-4f)XD zCkXM}0Jut{I&xPpoyM!L&Nefyp?NJx#bA;lI}d$MQU6gs;$WR@`;FsQCKJ9ay?z4I(+$%cXHsDP0?*#EX_OhP1`k zunb)?oI;~YcGlxN{HrOGb>x_>KBh7iM`fmwZ%JcYbDE<+!w8;cX*u4sX5#5RU*U3is>>so zH|1r#IB7Z_e&QkIiC{egv!Q&|irBV!11J$-#!9h@T5p;A_t;kk3>b+k{&_KUNPo;6 zI|v)Le8SpUqck+>U^fu>ynW*$&fUHa|7b1h)kBCs&i9&#{5Ai$!;12!Y#-zB$qjYd z*|zUY<4?mT<%@Dt&o_*V4e@sD_X9Zo_3VjKrOF_G;TU|e?;`|pL#%w|TBuaB6c)X| z0iiid@GjB-+_-TWS1;c%`JMc64`$6P%7Xj)0(CekIsTZD$@ zgwYYtj=-s#p%^o47#6=Y6mPEELVp@!i!F@5?4waO@);E?S3!+B1+nP8jmVa*B*#8h ziuBz0-iK)HW)lr_e2yIcDHtZY-}IS1Geg>)l7S3PQL6G<^p?`bouHr!$#L0qEi+IkD}E_UJ}OkNhmH?6#EZ`j!mOpM(X~e>L~@fM zgY%o3vR`y;n!pPBR((Vy7X;>SVV#D#m>x($h_QJ4%z3{__oAAcs4H!0Q7D7nLDLhe2A~WjG ze8hO`k~J+JEaOC*jNI)=yLt?-O@0+^TeiTSz58+Ry^T=5dXrU_}Uv7J8CQ< zxd5HNay?IV@{{mI1H!hRf|*VXvf6;C&Rd`gEs;gzg&_lQ>r{8VG5-z3@sdS9S`tiS zN3U9@I3_QejcHYi;>XXoVas2>2MvRIUtDxc4NwnX;?L99De%w z3hsZb11)ey)B4|a1cYWsL0a+qg1f@AXTO05xbsvnwh)e8J7;L70K=>k{TKpszqMm& zr$?2ocMThR_iRJ1=rmNRQ3J6B^I*aFaX5JL4B9^Sn7Ksr?A2>zO0pq_JDtXnU^cB< zhz?!*q4VI;nETQwH~-&w5Wm87dddw>C@JYl#|XS}cir6-HSa1-CbuhIC1ad& z<`|wG`XXAl>5L+U3X!2DV{JA^)Z(l&Cp=yHD5Ae|`3&~#+)JkBJWifFjcpq~AR`u! z5=Dw2{yHseZrw_YPHoMJ+RBCVvGFh8V(!`rh|HcH1N%RP!O!)l^&$riIf?Jv#|7{g zKk<}e5-%&))J!IjwBs4>IajbH{LtyKPME*q1Kc9RdVp4~mQ5Rv^Kt3im2QC$PIBG5 zK8?XIzQTo`FCJ*!5+{xv!E7Ka zTQnz*?Awng9(jTn$L_!21|r3|O#xe+7#pi|co$e%48O&Zn1;cpIN^R{m=dDcX3N^pUj&Pj}u|F_?LhSh5e zAr~)Zzo%v;wJsMXIe01%g*mUkgrZR)ICu6(_=o4ky-nMo8m;yvB*)>qL*L@!{#|%$ zHOKL33lPSIx1=PJIj;C}^F}SDvqc~FYnpaNC0Z_>zhoY*#~Y zaGHzg+cxW5v?<@fgSbinM?lfBAV#8b1n;)1vI* zFZW`@wy)89Y$sH%TL<;4mdClY>p0AloBcn0hXsq@LGSTX5S=3&aaw?pA=I#~-C|k% zOg8hOgRMMiPa(td@%yW=heuHv+z8}V4s8ENS!Pw79y1A}Ilje4`CvBbBrmNJYAz{U zpaeoQk}-PdKs@nKOH7~fI?}mO7#5s?kkC++uT%`H7EDBwnk7&;XEscnFp26hF~}3E zaY{5`oo4bh_DgqUrnT99IF)!4Kht97%-Lr-$8!uw^F@(z)lt7(A#~|G7=b)_$wy{r z#>{DE6VyMdD4u+z4aPs&89@b$pg#LhBpKVYw8R*dGX{~N++<_>Bx+H>Vr3;(?0QOi zobfANvKWn$p2v&N4>$A5iNgo5WbID0={~>!naX+iD%KH3={ezp-MNcY#TU*HB!Fmh$r2{#!q4hmXy8Sam~ z3DrkfM_!~&A5BZbO`g035fkc%u_K4z!qXkGVEP=y`-Rb>VJJ73cur;LObZf@gRk&p4iTg#SV^PX?;+KSPJ%b~uo*{F6nFKBbfN-5a>m@age!%7R|*;O0M;ul*|NX1!(!k}BEu_2);fRm=+OsDm%W42HzR4;^fA{zkXCSe zVDZ~+(7sh`3>Y?q8xm>Qx$R>-H+mS}n)@17Z2An_cJD^LN=0z|s{?ra(XQCD??c=; zb_knz0&;K5Ht^+X$}5v6;6g$G9)03L=B*8Ro@5%B2tWBm)0_$+nCV=U$|+7d7aDRJ z|B^T+_wU^sZ!W8cPAq3YudZecerw5l=-PiUs+X+*pZEk+uU&-)3_)mGw?39GUC)I> z2^1}oAAU(UF>>f|v~O7xbEi&6a86#L9_ELTY{BT;w(k;na5r@bu$dP;B3BR488(e({N@U85>DC_+GEH{^@f zp~Br2aP8a)^m(i+8g=MGE8yig*IvT)_;gg@T%mSI;TWI9vBo-%HNJ-DH4>rZ?JVDJFnT@AxnVs)sfEre~CCL zP_h6Q%UsO|!+U5q5ESdlC)h;~#SRXt-csRz48-a3# zqtWiE{wQ6hG=>f4sjV3sJT%O#^yl zOyG`VXVjypQWctYl*^vI5UP|HGr%2knrTfA3P#Kit zBqF#U?b4gO83~xZa1ItPdIQ0M;dtQTr)a|4A2sf&hoa$kCu4kCRxFQL1VUn#893lmMU$ ziONM0uqs9*Ovm=(KuTAtgoA5$;gyLmVc?JvW&su!l^-kLT7p5NCZInjr~n>K#}+Dw zP8CYojQATKQwU|*VODYh=^K$BrOH%5?vjNubJ1K39sDdNQgozo%XS#|NE1wbWi}ix zaEg_$i`6R^V#N4q=>GIm6vy;OwJJ@Kzd$t2qCbKc4(!Lubt@5-JvU}9Sc?7oHX_)E zA`67y+~6)0n7U|pgx!S9R(NC)%ztApdJP_dsZ*z+H5cI>9&3r`2aZHW;&lueHwou1 z-om`;ucJ6mE8cl)9p=w^iA;urrcEBgOV56VWi-1T5tSV?W-Y~`@3wLP(4xgb?!u)} zqgt?GqSXc*YhBU5WHfv5Q7o93hKbW>VC|Z>xT_X{RxKVUBOi#n?`cTJeiEJ~9yBZa zP-bn?%vq>PF^$WqG#{Tk8^(=$fpy??MP{kbfPr|lZBvmVo!Oc&PctvEOgrva+Vt@a zqM6~Eymnv@7tVVzY5Ghq!17X5p)o$Dc-+t-gPAXxQnuOaG-F%6Tx=#iMq~>`A)4@| z%)UXR(D_@=^iRgnq#B;DFlyHi#OCem%+9DzPyGUOqgC?<;N%WQbb(lkDb2)luTDhI z9z78n9>qmfZD#9BQJJd9ky6+cA^d4Eqg~LDh!`w?_boizZ!m`S??t~1wBU*0%cDk{ zle(gnDX#_a8GFA^8e`#(MDC z_H9O7qILU@oWqG>)26D3Hcen|;{u;YZ9rnP$MVQRHhmy0*I)saMb8 z1ey4O6k)dPx`U^Xs{k6KS^)xPBu> zX#<6+Gq_Xu98ZAmqe#c?pAMlyjhY7644P7_Y(URg3=Uq*}=udtFOF-CT%;J1(6A=*|jTP@KN6(&v(T$51v5kG79gD#|Ix`jfel)KgsCzd>NHuoLsi%lJBJ(?b zEKe%0Vb;vaC{d~c-g$csX21Rl!lD8Yo*sj0G;vI$4x~MfEo`S^+>FVf*pKOh>_555 z@K(R8Eb@cbS(GVavJMK5W?Q_5p#z3t;-sl)c<=of*{%VeesT~+FjKh;T!R}`i!p>a z>)E{rcgMNOQ@I|BQsk^8$C9a&$B~Yf;{8=`n?+E!UL){q@6HGg4a35B-bD8v1JL8q z$5>AXop}oR+<<4u5M=N=ld9Ye&!N%ZxZsdzR3&p2!f_>r(gP(*{*1SmEur`f#}4+T zjEq2=5F*PcQXa&cS)GaLF>1D3r(aB{TOCpg^7+hH^m|Z3Zh|myk0L zYH(BN(~sW6$2)m0#*IKKc~D9t9CbOr#d5y!p*i_w%NKFJXBfHL0wrsq^CJ(TVT*e) zm2|nD=J$2V7nLIhvuDjf4vMhlC{zSxf}+iGLXGWkd@EM1HgeLOcm;0W&0)N0GiT!A z9Xtgf&RRd(6Qf2vYv`gxnR1BEm)E?vrrGyJadCKk-U=*PoC9C~a01h2%|of8`G{MG zjQu+l7k`G=DXhgXE~4{re5+ZlD#Cc$ZfQ%gG!u(a12;)aAu@NOX`6K7z>jcerx@P+ zMYFg8^CBisnZbq3W2jZF5Tz65azULNRd~&W@`#LzMYXbVCZ~K9Eh`siTUcje5;1qk z^wRns@^IW%f-3c!V)m3Nn9lj`hi}3#`o&53`r{4QciQq8Imjo~uGzryHOyZugL;H! zl!Ba}eaJfrYSUCf5h!1?7UzO6-1<2YOXttP(gpKKKdI!!3R9$O9cq`R$nVd$@Xa^h zVEu=?m={$OLc>Wn%h+Bm4NQ?yg?Yl#kf%8zqynBCu}m#cRZfA@6_Asneg;#7-c z!^X|)@zP6^@!W_Jrk+J(OY^k%96tQ?OT09BGU`(dPqm-P1>=+5`e5D4H_@_Q4Q$=K z1@Erk#Go|7RsgHktwqynWe7w%C6vk$zFbs6P)l100o5db{-V4lfl>r4Js(f`%X1+i zIEr!c8^lxRd?m_b^@=w!Y{EG9`|dog^+%;D4N-_v3*l6?Xwakynlx=>(lo3?vF=7q zQG=VWg$kF!8*^u4;;WPJ`mFuv)^8YMg06E6{RG_K!$aMBV(iJ&y!K!Q7Lm4N@)yQC z@2p35_PI6NcVH}ew)>1ocq*nYnui-t^`g1|H}UL%Cs3Cn=`HVVAdpe7dLu5b8=_{J z0uEsLHuFGMr+C zly+LQd?w!DSfkCflI&OWUV071^F|u_e`Ee4j2ihoh7TD^$t-_Lv&3S+koG2w=t58w z%dSxg@2_6Kt860hQqf|F%@vM_{P|F)ULEY%vfeCowTb2v9E0Yq+XC$0KJ4q2t5!t; zZioo)KHTu(`6OG$_LmA3E23nvf~ZO}@r80^BOOvoiK4Ex>sK>0y=d-2j)`d~Ual%e zbCX76QqhW)5XFNEjkTK0{J2?Nxh74^Q$&+uRa8x>L%ynzDNn^P3TY+*Z}Xa;d+u$8 z@goMA&h=22J{WgCp0IDk)7?5DXEga$s-47?eUGMH>l& zEkux{>fL_QN@1LHoiL?w>rBpCHZ-p$udJ653qQuXt^UFqueV zfXWa|o(mRbV*9ph$uKh=6L^%KWhL(B%^Nh^>@m0@JP3viUQ&7oH);!krVIAWMU%v_ zX(ReEH-mGvw_F?zxg@$)R9r71o*~MYRt71gY+e=Fyco=wuf#Z7lr##u=F@~{ZEN9A z7Dm|(W+XUljvH`a+`!9sk921LL4ycwrf~{ zzQO^EV&0+}y$E)eXG*hv*6N|u*{w>uYG^C~Dk@!kLV|Pa=1nJ&RtMz-mBFnFrv@kAE^V;-81DUBUVU}f1(?{rQpjN@Z<5Z_aUM6~lrsywSxJ(>a z90_OElA{GEkrt$G+`2(y8a5-fb*jc99)ZY-w(M9TnsxCmB#yr|&t@G#UTy*1ZPr%2Vwvmyt}|xK>k- zeaq`b3Knw?9=q-)<~Tvimp3^cCnmV6>;_LZ|2SITyTmrT%(kzZ>{ z@lsB=;S((=?Dxirw_;lTQ>{Jpr7*=XIB{v&&sOtyuu^Vxk4+U7iqjG*~PDTEHh@f zfawx#bCEW2WGxfh;%VZEFT$zDb_=V1dBSQUr1Khs+t;s=-*nr7zP4Vr{S2KNdJ>h3 z=4`suc+J4ISvmn?&6lihp_0Wx_q=Y%VlFF03eL`H2bm&A^d zMr+g_cbjwTt+=>M7|r~qSvjq(&eY#XGL*NNkJa)Z2yg_iYn#{f?~#bbCb$0jwZVKM=+V~^-l^AqX9 zr5TeMqiUS+IK#hXmSR9LDEEZ_S)SY^;Xku2qGNZS+;Fj|1*Wz>9Lh%+F%m{d<4%g* zYfwDxN@z)`ElZw8P|n%UmkRq_u<|NTvx}lPN?nW9iL$7~%bmU7@zStx{X@{$?uS`kLcchDI> zlP~3>O~E#PC@-xIr3kE6@De4Fo{XGiPbFUB!zW3M{#xJjNMGAn#F3FTH!)cT^VQCb z;+Yc(9&HC?qyC4&S-k2ZWe|xV_!${(5585VcAHeXcJu^2!Zz0Qd47SUT{`J1Nt8f43Thp;BvbN@#y`TPq^F~7?pDatmu03gZ3gS>!(E|l zJSmbteObP#jmaa7I}s)mWn@H6tmv6`JwzXZPX7(;e3~|4F~Pjh*iUE6Lci#PIPmAG zpy(jOCzuMNHi%S3Kjx)+7{cS}B^A2W;ylUks5MdTPZQ%(=2v0vvL9CZK;S0V(_H4GEoSCVg-kU(53+Y1Q2PQD4;(>NbA(ZcoLx3FO0G{tboD+J*YR!qtQ#=0<_XjGSx1Yq;+k* zQ+y>+wC>yKj?uA`PRHulwr$(Cv0~d?vDL9{tfa&4aK*Nro4wDu`|h*PeXDw?hx*Mq zYK}2#eE+XiFF3e?DGq;H-2}B5bt~m3O9KOKh7iG{w0e&JQA#v+4Wz_Xkb83a3U_R0 zw>71jN|UGv$=Xv&T+r40IX?rycQ3V~P5v9Xg?#P+&xL;g?i#u34}Mz_f#7dxpb5zt z5F#67UpjX^v?VfyDug%HrvqyK5%?Dk&)s}my#3%~)rRe1R>KXKS0U{z>5eLOSmX%r zUP!1#(RCM$dKl`1wbK?0XGr?VwZfYaEULd8&+lpfE>R82%Th3t&hXRTqj)ehWL`4| z;9WGoTuQRkiWJY^+@63zcGWTF8Z!ur>t;?2#GGaduL4=h)a2gem z>Ysu)e8~J%-u1pzRqr$M4LoAmdsbw&yq`u?pwg?dd{KvkB-IQBU9N7CQ+hTANJ-V6 zBZ1;8wK{NeNYy9F$tctR(Q}jnIA^~rGs9inJ+7UakHfip~gp)1fbmaV~ zW+Esx#VPn{zjy%^Ybn}Pb_Je#fcTYsM&W&FvC5f3|3@mqnZqQ_I-g09F!_<}a$;$7 zG+%}2c7o9a&yUspYD*!Du?s=rT!gMNvCmyzep{EUXJ~3NFhr>NCtvmyMxskd-Ad4p zTa3z648xaA3nGW%J~)*>uRZ%zg5BA2+3Z`!g$&+&8$t=py@LA+){5FECO66w38dBA z3as{ca5(-aL(+S*%rqaj^m8QgvN|@pCAAcAbV=So_v$T?r{4sDnL?gWq!NPOo!+_E zj7TRVW|^hvm!@g5R3_HGYR-2CAku-FT&1~k0B5-0<$;R8_E{@f%%U=pwNkIYk4#^9 zGi&)TN?R~RMr9(*A5Y*`VJCY;!ASOY zj|i_nM-6^da*38a@R(^|Wn_~x&2d9%zC2D>78Np9Ocun;#*W}I0KEG1DvKj#23?oK z!b)T1TWDot;viShvztCJc!pz^RQW0kO!PFhW+Qg7VBA@4w189=;191NW$+xTnsg1v z9gReg`ef7n4|6)x{Q7@ea z&_h`ptwD(yAcq#}IY$#hUJu)f!LP@;Mg?pF77U>$4rWvoSsTw~J+h}!%I(i5_v)m} zdSV6|4aCz;Gp80hA%yzgS&+0}+_SLgLdw?}>`DAR8V05s zPv?=r`G#v#S-wXLrWtQD^S%9na6gwFTB05ryw0EC^=k~-u8eU1e>$MSV)vVd0z?&9 z8P>$q>78l`bG;)!%DB~*slleyDD^hViojKWEo;U~?aqrI5VLCW#Vfgy>lF%X}q;X|m3F0ox3u>({KN zELFgnZKC}~i!6FHoNaW_`l5VEKLV>Q-;OvdqGtk+?S0Xp%!c?rTxv_&gC0 zz`{caW=?DEPw{h-aTmbZ57cG2PHp^iMLRFzcDL(Q2B4{7A3{s6BMnslVvxffUTAWL zLbg9E3!aMfdd0hGr_SPw!;*7JtY}0~XM&~4j6)2sz>Q{6y|`*Ecr6JJT%31c{+1E` zb1v^>Otf^0bpKB>(~rgPIH+uqga*Z8D88>7<@BUofK@nOlRVK<-1t>Xpv zmh*|heG``1PIfe%X1_nSkABz72W^6bWQ3h=5-%{T1(FbW)_CnKphuz22VSsOb1!($>w+V+7kbsY}TNDl(;8%mmME<0yA;e{I-((6}}oK)@}NE zGHaLF$ecdbeCI^diM>`YW|y5h1FsBQu_6y+T+LsWF+bDc5-FKXe100rrG$@5^#6EW zPSFC;@!IQT)f2Puh`ac7N0Mp!kF#V6XLJV6z5dGqnbR7uTpd6x_U*(WP0i=we{pJM za1p|dgL0@VtdFNSC0+AnY?|tbP#Bxykm0J%fEeG?Qd3X~vGV9QglASbF*~WNmgUX< z2NJnj4riI1^}u@YdDkj@HRy6?>Hih@vDF|JC4D@(E@&^3b=yQ`i~UWv{@wfMQ#isU z#iemyGz0_myBK0dveh1O=~!+Cua>pN_4MxH)gAvw9aFxSm_M^5Rlh-g3mt z&ho;P9Cyu@Rb=iqra=!N=lr8`qfn{eyfO_gBy^GSW&j2|!9$bg47RiR33ISEN4JNJ zh4Nz3!KBYn50=87Zt|t|C`4IqO4hZUmyh5m_cgT)PlA#r4ObNikniaHc4yH0R^@kD z=kj`S$KLGy-V1<_t9?=te>o3<52x9$|_j?tx-Z zuqqBS@~ouHIc|wL-*;qwjqV$XU>LY-b)UyG89ridEkzXSOs!%7D03a}$d#mX8}OLK z=PdXC6?(j}L|gA!;}i-&A1dHj4~t1zgo!U>egyJAQdUn3KqcADW30lkw6d-7KPv1! z_NG%M%af@~Ehw6-Zp!GO0Bow?!Gj|L`b9jGO8~~2QUu!3nqMLIq-(~b*X*+3{Yai*(4;1eYCW3aRL+}4&z0>2u{dUrM-}~0J zsu`28XvA$34=lw0-a3%I2m5}Y3z=tq{#PJYOf_Uxl2vkt2Ys!ph{l4|PL*B=GYS*H z>a5cM^%Ytr$Bqxa+^pg-YBs~RpfWpE4bwSgK%MnY_iyHIY17aa_Q<(nU;KC@euy~z zJN{wQqee-})aQNlMsyVX!It`pAE1pktGmT5LdhNdxAHdrXefCX@Rlh>DHn+Ku-5s5%6k6?m@1vB3?|*krxH_)-`DGKFJNaQcB+EUUVKPGaFx>T_p#B8bt^jDmoK9C8!jQaF5sx-->A!yH ztd3`AVt>;|d}YBIGmX04b|T1Se&h4sILJfzc5ga5MOyo>pn2)ObqV|4c7VP~jWm+J zvi`$=U%TSHms2E|d)66St);1t9OTpuC&#gB!u-bLIzXtfZ z0jVlS>%DHR*gy6T{K0VdqUt>F9PNM>Y?Hx;HH)UA187!RLKa*}`IQO2Xu9U|gFs%hGZ3K+ zB8y!iVgcTeWQ}qlG94ZJ*#u#Ez=ZW zP83Lo-DG|r^mtt5*M^oCRVaqAwK87h>GGsArcp^i668q)gPDG|u0JI+vBp~GeYD2K z`~Ghpc3kO8Nff4|)o}O~Cnym=O}lBc`9gN!q2{7yc&=4rr;)GI>DZTKB8|=-u{+~M zHHN+Eyw6ZO6T|$O!NY!ZnlD_jLD}HS zQzm($D?0p=l5BlsW_0>zKsp5!Muaf*p-Jx)DmCDPzLu5SQbzk>Ix45vOIDcT3%q(IF8Yt?PvRW|ASG9e=^wkg_V8b8 zeY@oTuX1OPs_sE5$Zs8r*^d(%Q%Pf@?eItw5`cXTvuu>TevV`TaD)vtU_xQD98u*f zc>buM%J|WGRmcj5A=C*nZ*GP6&IT82z1qmAW#Xl@y0-{8@%P-YjSEdMn1~;gOKMf2 zd9{zm%TX?1K4TE2g1xO&W{)(lF_kM-oo8L(6KWcNv6h&}Xfk5+_}IAq=nE#spjL)X zL_J?bj(WhCzq{R(JM+CeHp}662|L684w#F~DPRa2rX1Qi$cw+qegK9}sJ0Hn@g+!W zD&%m+g|wN9nnp9)8KZst{rJ6YOZhhQ%2IbscYWI-KnN|{SnkFewPwY$8Uk1|mp0p} zPzScTSHHO|B_oI;1wgAJ?^Xw2T`vqncHB8RZC7MdM#8DR3RvlVn2uqW{yO%LT@`>{ zNS=_(kus*JGiVA$YG}8Bd)ztw;4Of5MCQ91tO@eV=o3J${vej*b-UxWV|u>6ua`~1 zSHAoM=Lc#H*IkRB8rw)WFzH(}&F^HH8xN4nWk%xjz7-7izH>5|hnX!?0Q}OsZ{bSi zz%E4V?C_O~0qW%^M3#yD1PnUP#jllP5&5TanAK5jts)duHrxYAPZX$I>$WT6khf>$6%6N>&NO@72`OeW zVKyw|swn-3Y#4fiT%6#hR2d;J2e|g?hM^}n`Cwb^*LH($sO(1!INWB1MF=YB50l3Y z^i<1+1JjhXGY;p*KB32(kBJxcpXL`+na^ojs#@Y%PFCC6jwf>#GiPKk{Hp<6jbK)v z-|kz5Vr6Lt#_O4MP9PuaZ{gPM)Q{c@6r<8;$hWa`GDR4YC>9NcMQWk=BsMmg3s6L~WqS+*P zLGOuaDt)4f-S*P#)${Q{IsIux%+%q8pg@GdP=@^oC(E`%AYH`RUUf?UZSoHGR za|C6RCWi4wfmNhZq9HkEP~a_skEdn(slTXgthb*Nu$h4r%}Qp>0Q1lfbI}vs(Tuueq-D+25YB0R$WxJ0?x6mDrpCtdKL-zbn*`>u2lG31=k z9fnLL#|5GBSnOCY`_`&~h6zP`L0H_c7=P>FaYnzcY4~i9NAt7HXVAXN{&KOl(%9l< zTTqrHw}u#^+`;EWOu|?f94#yP*6O))F*r{`rwO{NFD3BCca#;=d({cpt$_+_$K5b3 zM8hnFO8V-CXYDk@z}@maxLNSM^SFYnH1>Q!MaEa)xuk4uD4SM2U*^LogWu@4vAL7A zi$A>J(i*rV+}Ld56G+;6xpTnt=MiXM3e-Y-yRVX*nGv+L{K-Zhhz}J)%$bc3rVmte zpd^LT@Uo)%T4al}DiX~S`s4`VwIfcToD%`2A3ZtwAN$3V>C&M;K&uePJYmXHQ;o$00CHPWse&x@4+22}&{;Br)t2>|rY9=4H_pLv4vyG}5l&{n6B{l}p zD_kr-$8ZC=%;mC1UXkZkrKkTXEF@GJX(mAj7LFC1lqAOAsre|wD9nxhNIh-IFV~o3 z2P6Y9TqK2b3Nod0TZ!7xxYJ@Tr6xBmdQ=8g5DK^6B+Ng*Re^OQqa&Rzk>ErcDUkCu zjY@Z@Be$Z)*YX5>5M3-l1N|2O(W7fNMo&Wl(SwLDS^#l^Z$u2x4CECR-7JT~mRw7=>mp2`u0!wv?m}A?35y$w#|{A&gI*M} zhrgA4;&p0mNP)<6kz*VcJ#W(Gdl;MM)jb|r*$cYE?BPZ)(Xr|;3E#w74I4Q29$0{O z)3Mk|1`B1J$;g@(=j$LfJBD=J>AABtF`L>BKopbNc>Drn`k zr<8Z5*mHgFB!dG0&HpwD@%RFjFCYAT`1%ih><6Kc(xPd#UN>IVQk{^u_?L+k+IY$i zSD|s^P%3ZiaF28b((~2<4(70?WrRCxRi@$u&*;K@#MQ1Hmd2z>y=ufTMBIpNFJ}@` zI$RxgzX0+{4HBkdHxo_pk6#ts?J5D8_|MUIeY z$r?sAB)ZwV^I%1L*vIEk5;Y3fy?<%$aMMWPh5*RWu*4CT)*rS67j^%~H~2p*_Fl|y zKj>q5e@`pAJ{yUxFt?RowvmQ^HxvXz9P)Uw-k+`nTDb3H;BgMHIxdhu>#$rZC6w1~54--H z%HqHsky!BMMKL)NS!NO9BhUSM-sX{;GKmoRN>yOmxoMyU!YQ zN|4}rD@MxfyWiNY0jC!ZD4T86hv%WtIraG8<-V?~|FzXnS%g+J?6Iv(rr{(1E$cs*jBbYt}X!x-~} z2ia`$uc%y<7?Ag!MLw6$VPM101@(u27f-_;O$_wiiBo-6P#yA*CA?_BK)z7c)7*BS zF1vJHh)(*I7S9B0;Gk2hXk&=|8#?%LJKP6fk{<=jZi& zdQJJ}8J!IQx45soQ7Kg+yC;-ahLcN=*QQf%bU|r_pR9w9A5YdHMc`rWs7#u*K=x~Kd1|4L#0>*6(>akJQId>CN$ z9qMXV89mn;vx?h}JMQ|Cb5+1|^>Ri9tJblln+*i_9o|{pVfpA&R5b2&eZ@2#T_n!Q zZ`7U>xKqkaJ$3Q?R3sm$t2CVf$G}yUChH+Aa>MO&pU1jfMsDDu&0cY$>M%E#^3ovjCslO@~0d4=TRb_o5zkmfPIT@FZCcE<#DwR<#|L3mpheHmX z+``aFE{SP!P7eArw+W2#gOo9Y{^gHFqmkaDg=)nWt;W3o7sJ2elnZ%wT_$x>2V^Qp!(173lqYx!?AK1(2wMn)EUH|T3@jxf{cg|BTC|u!EhJG+}ttfiFWk-eLJO(?Q z6Lpp=x2~7RI`}C&DeIW5tdz&#RP!Vx6p|Yuy>0?g#*CJ%w3rioy0IGq^T-tq2AdDg z)p4dqr#pIo_Z{}-s8m|6n7xEJFgKPqU%}9Uvv~ajA+MpVHP-g)f-qL-Vvd3W!N zWwHv{&-g~4-jaivPa&p6t@=4|1K)Ib=DWhUE&pHlrtZW;D9b za8OVxWM~gab?q(G>4J_vj$aMZx8LsnSG)9o^$|at;axb79L9a?U($gQqnUSz0=zu8 zg3{J_Q5aQW^zn)hR5Y+9RjfjjKKOkM>+zIyM-dA0!ae`4aoBBtmP?0~^Li<@3;)_? z&>b*nITa0^FjH)HCbl~pWMIT2{9zToBLEy;3|YODiAoF0bC#1wdI2q*2 z?8MG6!oR8H24Jv6p=BU11!6@7Y+611QGETdiV8ZT*w(U|ni)pMh}$cX3!Zi&lgB~H)n}Vn*4qu^ymA94 zxs1MV_y!v{*sV|vX^&2h(&cMK*K3ex66JaqhHt$I3?92pbmv5xA*{leP>+dV=xb4n zkJ|WzRzamEuitd@%*{+V+u@r5Up|Qnv_nccx7~|XjO7jjz-#{SMh=Iq?NCyRa}@>N zsATY&a67ZvAHX5(>Bs%2>_$yt^Ig40@fQV-Ta}vNDE<0Xe;l$_ujoA-5eAhC)&rYE z6an!TmEIFQtNjwB93F?LqhUTZSQBJ=ml}V(vtvVNw9-iRd^4O*`G*_sB|FY76{n2N z?Cb6rR;XbVs*iJPTT8nrWH>2vOq;2fGmud$I0S&=tmHczUN)+K@P8LSd4mB}W1=ib z%6KG{qwCH^85hY$xZap&38XROm{FGW2}#UosA^OkcgYxf9h6PmBd%Q0d*TsFM8pWP zE|=Kyy23TkUQWp*E+dus6~SgqNx}xK*KG2yC;9FM6Ph+GLmTskPQHTYrS=1Bo`xk{JhS4*AXR0|y+)$woc-T^v4OdEfhuK&m3%jnWtO;%DiAyQ2>iE(8< zw-QoWP1wmcX}Gu*l2^%Z-0C9ChVT9nGF@>&q44>(J*T&r!6(7r!sV3+7=yTlzNd@v zm;bFK*Iw7d#?}49-upou5*|isaqo!7FD0~2QxA=X%hT?9kLcf#A*t7jateNdn%3`$ zYIdmYdwAg=o(ccHJ$F=hwfq-M!b_gX?Z`x>7r-r5i4N#}Kj)mAYuk-f2s9`sqEX75 z+==@{fMKJz?UIdIa~vI*#n(gQnl^OFhQh}j+r!p{(}_-;{p$Br5un$K>vJ z&$ZOkN#H=QDU3QsekhANXO%okoReRy7|)O^&hObYmXtwwb3?xU7WvIMfS;Z~s@3rr zI(8J&QAxpImZJQOijZEQ=;x?{N!Jkl2(Jw@xX>z&E8bS=6CFQ(anX z^`Z(HgHB!j9jIf_lD`@HPc zS7WNvo30x_UDp?zvtHcI7m&k#uE&O#Ip=|2ug8AwFVi@j(1jG$rl_dGiDT7WSZ~uT z#-*MZ#SZ_+PkCk+?#1#mw3z|nrVo0xme8>8QLm*gyO^r1jqXx2>BIx!UB2BFx(b2=4d^Y-rY}xhyE(b0aw!A=!Fc;HK4RM#4gy%qq-n z7HBpbk++=Er!J#4(NZD*arlF>;ineW>e(YVxa4Xz((Z!c_d;1vTKUzEq=+#{NQt?w z2M@P``q1A>1K7_KNeY8pAd@c2dwJ;VtA9-mO`#ukU}ZaA&NJ27LQ+)Z%HbItrsKE+Z*C<+1FJbm6^rG?SZ9PPy1OC z*Zt>N4NB%!(}?gxK>V*C-rG{ZW?43S)5BzmKGIFpOIS}x-mgC(;`{0XzR=;6{XG8h zI~`kKM$#2rP+bif9;aq9O{BBUj0dNzTZMzzzreep`pR-cQ)QacDh;g_@jbe)CwKMg zHhIR-Knpdk>l)8|DkpmPJ(6>&mJ3b)8HPfkCr#65l&kBXadV(og>9IkgnAG7wVhzhf;2xuT^ja-DVW37In<;B8 zjIHXDa1WP_385_hybR_Lk`Go{O^@5H<11x*^Ph^#6<17g@=k)fS33y)jX6TD*0lhc zmDb^@OpmSgl*BRPKOs5P=tr^KA&Zk7#v20`iQHuU8EW%YS5F5|o{rN5qN8r+D2ez^ zUU?9=Sh;2$4y8$-h1zwM54br$zsCpd&i(@1OJSdb@{jI?D>!#}vqr zs0~{sQ&4r5ce`Ot4vj2RA}R6LRL2r8LmnDBP0Zl3p&&Wp(z=>aI}|wPp27h@)z09> z`Y-RyX1^*B@CTsPX68uETP5W6>m}EUCCgt)O5+CO0(OEjoumz zS0?{M1d3}{w)Ybg(wHNp?X%NcBZ2!Bf~3J)%AKN};_Bd10f zY5t)$_JosmIemOwT;*NmY)Oq_Oq(*W>UL{WbeVdnHrN)Om$<2FskiPOY==Q>U;Mnu zHg8)EHh~rMf4c6eTGCVU#i}g5g`_6y%gyB6IO8;k`hqNogu>-#GdR8AksdvlIv8XS z8~dc$b+o;_v7k<}>#nvByb$U$l&HdyamTP^)v<#V1gQJi&`W=MEQvh6t+!Vi@vSC~ z&*t@5d`V)8I8F;G0T@i>lVp+R{bz&){8zwDb9^;&Ihe||N!%;vt<&j*9xm~j?eri? z-Q*14qiN{HH6E1udTFz)uRb;PGlRzTE8a{yJc~#VSY!l*9t`&&0zyX1O!*23dB5zYwjk8ofFU;SslVw1kIg8Qv z<$PNi5hpA3NdY9n0{nK8=7>9!o!fuMvG+4g34U)oVWyC+bxbXnY81zNa4!72@bbAH zFS>f{DA4yi?ooP?*JG%pOefknYtVPU7s8%rZvp$U?re({`x0Wk(1lx8UBU24RQ<7> zBz6L)dU_h*xxl=7w<0iiVhX3fy?O*jhrQ@Dnpqs1AR-69PQ(H)Eb;dD*+1ZnWnky; zgQ~X-=Rw!0EZ{P3mMjH|PX=0@sZRD%VRb=;m;Vq;#i5m$v4T znlzf7G7iB&GsF|9a+>xJy}NS=9YOziDk3|z%e^26dS79e$??*#$p+novwgCBuX|4+ zSBREH#)caMd;yN0>wZb(^ZwO6wZ>ezs1PHj-OrZXyF*Hb+zs#Nb7va+zJ6F^vX1Lz zbkj=Q;=)^JE?HExjBgk+N*VwE1s(6#144*n^R@DlVDf!97w8d&`$D^j(u||mC_fMT z;s>QC{f|8-TfI(1`bRfG!lqzO)f?NH>>X?AyoUW;G08=<>Wnl^x6M|a=}E7CMp#|Cd{8oKMYk*`gIYuw(yJrK zFEx8lgeu;c?ma?t8LY-qtDIaV{|tZjtlYY}qlJ=LaoSUwm`HEgtwCAjI#=I_*(18O zL1#IcP})qVjVdw8hTd>OSE|u~)5Y^y+}$lF9@$eW>v_Fdc)z~g3`?&)D{jQU zrpuS#1<#Ruy58lgB@1erk&>gxOQ0@L{v2n2G~(6MxNKX7I*F{kvgWWp(e`-^UHW_(Zuo5)C*}sOs=BRRxu%Mi zo$!uK2T30o8Bg0jmUa_`kfv`%qP-ncOBqmK3Yjl{n(tAhvTT)omxX6aDwXQe!o>ol> zE`m@dx$43qjFj4SqvaDl{si?r<#GzcFZ6C2Rg2Ob>VNhG6Il|W3ZUpI!mUlwq)fE2 zO8q46w2@IMeNY$V)@l9pmtv(;@hY_fwfy2~v7b9KmWc~7T7T2Wt)WHo6}~Ci2=9;q z*cBNNJM8U{ysj&@iYq_md<1JaT7ur0-Qsq_JXXWO9Hci2<;qwmDrJy_0JSP7$DE{% z=3P#?(hoLy+>>WzVP&hPS~*8Tyn!|Is=Lwo$m#>J?bqE|_Boa^CODs3hOO;qgu;m> z8pKOX2J#mMre(~+VktvAn;~udN@PRv;K?3N*k2RR+;`L_F%~%AY?QAjNcaD*d@@)t z;{1#AbJ~Is_PLH;=9LLe1%@hhiYh_HPiexmqzdNlqXU7GJZ5fV6*5L$j}TtGdnCS8 z2|S5HB~}h7lmug{Z=Wwzk&myyU!9|yn?zNjYo^Mvms<4c@K|GH={6gh#U$RNF2SDy z$A)?uJwa zdF*{ymjB>m$KE^3fg0FdksX=xsks{q0trkI&SNgIXwr5?}1G=Hm)S@u^ z*M(NF$WFR;AjzE-y#(wc3o53pVw@JCViFi+;UQMN1R<(=itNaB1YFU~gp(j2qnU5n z6dZ;YIKg`d3@uW>WkIk&K4Em5=O{*|1 zjH;G3fPm`1zDu7>NZdgJxXHB+6q1otml01&g1i`u)@`zEMwN2+*!lgy1}Kf+LEZ_N zljLPfh+K<*E<`h}g)LDU*6iTOw9!&ldC38=PZb8CBdt!(qHJCEeIo;N&rV67*mRR| zF7O&B8^zUZ<^24{H=^7528HROCCCGrlR-QdSh)zhw_WQ5h>?~7Ro#%r76R=g6oSSL zRUL^UASXZW4o$09VX^%is>7gcL&p=M5xC_$b*E)$tJh@PNV9v$@0P+2RLz<9!T%BO zyd-^e5zjyaDddXS{^AG2Oj=}=B$^Bsm%s__U@~`c`V#JZS$zC5&ra#Gs801t*01~} zve)VH;(QH#xQ_k)Id}#VBII)`5wS@ogE2cNht;LEQ{7N6NJ~(8%&(@R^(Ajn@_@iZ zn#tYW6zR46wHW%vr3Y(_uxQ%kj_R|heva8CQsMpksyH!9n)iw+yo5QkuNmckjBNG- z-%p;jR8EAH+k2*K-S*<`dgW{B|Y=!tof#2*-5=6NZZ}PrW|jrG`xQh zlzB#Jys2>A$plB`;RTOdn;5l^?|UP+)GtBco^ZA2XmVET(QDSV)E4s}JYFXMYvcBV z_SsVDJy=*&p*i@Uk@h5#9|+<8p5ZP3S;zhN1Mh#|I?{g?YVB)h>lyk#m*W4e|E6ib pZhYaXbM!X<|9gHj9rckx2svO}Z!q30;cPL21DtQUcP%LQ$##0qHG(1dt9A0*Leg(ovA!q=%A( zA|2^9^iJrZU4HK!ciem4_xu6(!}*Z0$Mad(AypB3|l1Xm8)YedERrS`BrT zS2u2un%}rV0-+?oeuK>sBfTz2++IPR-ze?{u3bM+IjbAF-MGPW=RY43>x{wcD$j0c zs3^VhBH5g#m@`q0SKQSQ49ps1(CaU)FRt6Z9DU0;s+L3bHL$U$KC3vqv1Q_!s=Z*O z-TQkU_w;U+WpeTSxJ$wqBieYJa)at==UJci%uetI{^Fgwp9W5D*L5e&b-Bm0w+AI9 zb$pq3_$WR{g^N-B)}4U=TJRxT4){!;GsjRqxEb+`=f4*H4C)lZ_qhL?Y7d`tv6nu) zDIclvf3&D_{mtmT6 z|F5S(5>591b4DuWU$2L4|Mg4nhySL*>j`=McfZ5`Oz7Gu|6`&5S#tmP-u(aT9%7~n zFfB2tQ`Axr(B=pbbqrWr{9}n+A_<63q~Lyy`L7^h#S~yWlqZrJC}RD}!^M5p1D8!}^p%>xe*rxj$lMq-T14?;O}D#CsKhu=H^X%1V%sy(W7NdcYk{Vb ztzWsmDwB-)uOSX9! zW{0>@B9?JBwbB*BVrsbDmtkf~9864rC=v%WjYH2~lSF&>K)cvIr}XIL7Guf>UkJrh zi`M>t@A%9LMSHdLZtITp)VVX@JX_?=7ajgzq!vD-T%1qKom897cD*|GzcLX0v-pkB zLIEySSl5ewH0jjrJ4yAv?vAKgZb^C9qopk;e;>EnIj;d#mV34Q)BcRtV0p*EuBB!@ zj`ZrmvsCj}NupH7*?;*3F~M;n51$&olGUpHkSxw<7{F+F2ikv|X63_g;<$B&3D9jH zbUfuY+1vd=gezJ5K8eoQT7HHpo$TC~^7UVZ380;Z3j4AS=B1s>?VSW19BfOHSV5!f zM~Lg@PqlxwUAIvjx+3O(+U?paw<2s^rS2l|^%;lKCYKdqt9aem?{sOYa!5yLCBxtE z&%#%suD1pzZ?~Jywh3P-6n>0}n)+;2bt71%x`xt^D$6g48&_{<=qC9-vym&~o}R$X zsOkNRdp&rW<}xl1@}*lYFJ`ZNy*o8cB{M(lt`bhW%`RUdLEE*`Eyl2ME81qP;^p2_ zgf9GpcduLeYZ^86^nzJ)CG_hCPV8z?MejSD0o&~iy~#BV-OsPQk18^zi%r1zy+pGV zsa{zqw;6G+2;{xTCnn6m`f_fBL;h+mWY@!(boRQ{Xj%$+eopVJdCtqsy(h_;j!n!l zSJ&>g#aGR|^okxlyK*zZjl8?77Jrd6d%fHofbe;0GsC9yB{8uToaog;>c-K8U5V?) z?;7c_c{IJBv-kDrn@T?nBm(sob2_GK#nHaC61q*BR$@ZY@I_uW7ORV+nq~(O1i2ff zw(#$r-S3YTW%#;N-snGaf(IGfF6V#GB7=ZK#@1;b+a2p8C4L@q2mQ*a4$k|3Wn|#& zJ2dNE%og4zU`AZDGfib`FRYyr^4;uwfo19YYn_KRQ?8mDl=Agvf{Jb9<~;UMPyPw& z`mVo@OyoD)4P+74l4|)Jp@6xua{pNGCGLJ-^Pd+Luv{KsyQ8ey}GbpwHR z`4i`_O(z{<2~eb5-V|)ofmT_TCyDrURf+ylpU=#G*V)W*Q~Av2ji@fgb&+OX2A|C` zh}%cU=8Gd3QeK?0Y2s6Ml=Pz=-yNTBDCgOj-YJY1WhWq(&tzuB+8)mi*H)O|FteV` zzBX0RF}WLDSAH!AcEr}F`(1_Je--9gnHP?cjJ9(l%>nW6fwR8o?E47Kq2zA zY1^a3j(RLoSzu4vY`bRGZ|R|~ch~dz+0$OnYR>}UZ_)ZXi&fC$-7v+5^X}$z&G`cb zJZqmJF5ibq{wjbT{$nimokP_~-9TIuw$E@si1Yl1eJPs!xJyjusjxdJVRc0?>vO2^ zao2Z}ZoZpB=Zq@1*)5Ni1olA$9B-PQY5Wa@jag7jSnjqAilNw+jQ)Q6M>k;jpzmjX`oy4XdeuGCF6t%%TWQrinu2qx zw*WLK$#HSo=kExQmR~QxutXO(=#i(LTrabclL?7?U}l$P)NwUwcJbGSU{;O#kK?;hN zKV#~_F&>Pd@X?A*TfJK1leL{6<_6|{ld;94eg2pGA&0<3w0fW@C-w+@kV#*KN9h;H z8X=t~zIq~b-_<_{verir;`U>s;W=%r z-f941qY?XAZ^e1J8^&gaWvILL+F^~)5YC=K>Fvu5EBsyk$$BccqTKo|BF@Hqt6?9-Gfa!Ph;3^n)r=#$L!cODGgY0@NM&yV}Vz?E!OF*RQMdH5LnYOZkHxt-dY{M)rUQdpe6JJlt64EEWC8CL(?6K!soB;lKA{aNXia$dpg6(zL_v zQbr`0xGD{nBa3stQZipialiVxZ#EL#Dh1mzT|>zKrfXC8?+`K_kT3Og4)u@^q6*Vq zDc5w_sv4~(!T2Zzk+zxLs{?QrfsME&!fE1M^u9`tag6~i6;C^Lt2LgY7+lDEB3{x6 zk`Mf6@!BHk?Jew8M|$a2p5T0RfsAycp3P^|(kIRdz>%|^tIHjq_(Ixt)|Y#4>C*KB z>b-8wmiTVjS?x3)J9@b!G#%uMRi(8#`r>-L^wLQf%$6E9Om;pME>2e>$zn#!B7Kj> z^Y@liJ}aVseI7Qum|}zft{jMu868NuI*GnIDXctk<+3=!b6(ZvE^WqMF=d>n1Qm9J zcDwDa_na18fKkOq5#T zO-y*u^?vC1(@m-p#!_AkkHBjkg}wGX=xSQJpSgu9CY?euuq1GM2`y{x0kz zfH0tEjGwV=mxCsOvh6~!zoMYwWxrfYZnC(2nU)kDf%~okImHB%j%;R! z3o3Vne2;2sj^#Z5@A%$L8CC^Q`KpkP$*a)l4=1XPa(Gl7;BkFJ{NI4+5|&Q4llick z=`vBX<8_N~R89QJGEI9?zfDcj2U8d>H<~XC6|PSEoCt%uy-g22Kxynb z?ZL#+$KU6>?wV8&1U;5+H!PQZ;h8fNX!MGwUzH2FH1x!++WS+?>VlWs+tu|SNLnyG zfnz6`X*q3c07o~Z1KN5svl}WSJSCEF*+NQhZ}hylKv^NuU2#TNJ~EHlmG`&YbSE2K zLTOoPfVD7>W;aZ|&Y!!qRo?vW{jImnb5Nla3CEiiK1(L~=;mh}?0oY0ANP9-%zmG3 zS}RWKzuA2jYc1}e0Le$J)FPQV_NEI*G;{#iXWn`xjwzipc(lgctLB{Yn&7ZzUTX@IF@$QOPZpk%_X%?|4{OdFyh4*~{Ufvgcd;QCr^Lfq`bb zEWNh+Fq$vZBr=5gs1m9hW^||52L_e_+7(eJMt-i|U)jt!7g7&3?!rR!^vLauw@PB- z6tsl9bUtv}sRg-7{o5?@-`NQ?1b~pvL^$V$JWh&0OU)@H+y&5LnlFaN!Zz~Q8#ohW z2Ho*I_PZ0>QWF>E>0aAi{~C7)vC@JBk2aoO4{?Qb{Dd}W)3ok{78A`pqs_9UGU2tI zw>Co`8Ql;7$%)>rRr4|q+xuD@VJZi}#HeQOb?tOL)t^w8d6(jNZexp})e#1OC>|2% zL!%FYONPGDXO2V*i)pW8JxAlP4*F0Ly04Jw=sMmwISLp)zmoZXaykH6HpXy1O9I4jP&^&K7m`-ZjaxVU=V6_@i) zEb;Pnjh@{HhRpNoAMEUwi?^XFlZS-f(X7GJPN&DF1Ku5zjeFGNm<<$qsI0Si???8m zfn=@UCY00eq$OAwRTk}kj{mEd*3m^kt& zxC9CZcFb*1Yqfzls2ArRFPDQa{K#LLX zF7eeqeK#b=V`LU4{yw;*@@w-_owC`i^A#Md%kqMWP5(f}>cC$vu9@;WHrUDN?rxh# zvBdK4cDzoi4zOsCe*4e-Ddq(Tz;bTVDV|)WPK>GVwk}2Nr7x+vL>-u4vN#(L95(&7`k&6i2*zbJ#{xchjzM#EU@(9v@CI~5VGSy43blj80y>rATA3zbU;%Fars9|6w?V=)CUal7yjcgq&bQgkuh^kl)dPs1o1>W2JCNd}rlq5%(1=rvtE(gdLFD`ccXHQ&r*;%HX+i2g5 z!mC)rW`CSdt$;mg9FGA4=tN}*Cmh2$YpB5%*ZJ&$@)x&=#F8h2Ag^%?{Hn|Cd0DL; z9QxVbi1wC-!F3pa)wuMwr&AzfO8gMbu$5(I)br9GqO&%r67IH^Es26LHCA)!YN-z7;4RdH$ zv%srPk6K4A#0N)x0~Id$v&%33P#lg#BjSl8DiIuQrY47cOW)Fpjck#h1+jo1Kzt9^ z&-%Bvk+PX3>A6*34z7;}(GCqMdWV`5O09~mor{h4eTPsCP&V-LEnlW)^=KBks51$c z)!Bu$<~a5}bh!U560iR>jict2Mqsmg?WWx}2ML z-UKSDW?gYdIxPb}XVN4My6y0uK|7}V^6Bv+2r%(OsU+^B{V;v!(beVbm7TmqtSWZk zMC_An(P~O{7z+hC^L9P)Pr-RU52u{+OecaJ-x*q>`fC3H{GbE!bX{+Y_g&mWigfqj z%Q;(AXgy4~&i@+pSeGvKN?hzLZVqUC=r?bW{ScP*8rg06jViDNo#X~Be1DGcb}#b%iTV7IO0@LwwgH}!a`WCV zR89EKM;vCMO)%P~`7aMibsK1Ldm)&J-6yLlUaZU$C`6n`3t-GWjEODuoq4VUARXb4 z0p9?_3hDHo&radQw(UF8yg1BoH?9<$v2)_79M$Xr?j&8Z= z^=zu>AKR@GLr&N%h0+kVu8EnynPI6c1)YIK;lIL6jESvW*?nErUE>`w=;b+hXkw^Gm!Ulu_(nXOHWrO~jB=;=Eul zT-(b7Plwfea+TdSbV&!{U?LZCuREzlLk(>vuk(d|s$bI!rokCrHaPlb_UiJu=91;S z-nRajnAca?HsaV^9U<|Z?kt2xysW?F52AS|%-}nE_o(AFMgknGdmnQLKOT9xev~KE zXlo(2P;XoH=Mrs^e?0BA)uQyJB>Ik*XTpFsxqM-B67@l2KPmCIzq>{wdvk(6AeKVc zVNKI{nF${2$3;g+)6G@hThM#M1X#`W9DDo=doH3zc2!VA#;`Nz`n@va*hWr(u>{c~ z*OiKk*J_&Ba8j{lN|J5N%ph}u%LPs#9S2B<;|FG~A3M7EHI7bSw3_9_K_<`4zn9Av z5equVeBWd+(~@(n9UGS4BrlePoh^AwWycyBUPUQ z1Ka}|wQk;6lQSFt&sjRC2l#-qf*mi9jSm)DGTUsRO1Vo+9&1&GR>I<1SN}9gDgquS zA~U2*ds7HS%KXAkCPY%I#r-Zh=9{Tee=Hyv^++f}KA+$qHTa*&jBlJ#^u0>Fiz?mV^d0^;Cy$>SV;;!qjL=!TM2tEvT^69-=qlvgY z*OX4=<=RR3<8b`qh9N@8H$o4?uzl2Mu_P3|2Lw66%7&?9JG1D4^KGR|W35^sHv(W- zf0y+ZhD*BwcCApk|H46@!U3fcLScXM@cJiPm%_rCcsvtGc_i!r!}HJmY6#p;m?p<&?`a z1CHes^SOUnZGd+OEic*E1ZIeN4Mfp4c2Sf|a7`1#g~UmhfC+AWecb8sH}cqNprvPw z!i3}kkN+?$xK+WE>f^eW*@(aax|R)!lxG!)dFw|PLI1&sl5KHx>FK|x<7o7i~Pk)4ZJ1DSEV~9@3(#^~)57HW?=CfuSKRcfD#y(|1|&L=Ls_K#tbE z5#2*8>f7!d;KjS!uh&dkvCqG&UVCFR&azdNn%$)XR;J+~(IigPT=MO}>?Rh6U%n%Z z9xlnZjhG><6Qc{+p8=YvTbZCtF?>AZ+OUa#e=&ydvsLEbR=-MZnJq(jZd>0NGTPe9 z4Wri{-NB1b@V&L2OqJkk#1a`S@$97~X_Ah$cx9lJ%`M@Cn;@<4owZU7LZd2+(~lrc ztv3A@?A;dUt_<2;+s(w0l4>OYh|_TnIFA;$gs-BS)`QHncIAJPO#NEOy2BypoGQ$D zs=c_#OD$lVCFDW4Pucc^o7D8AHd>=as!fsK_!JYDNOST+QyR_)ZO!5L-+jWZ`(3M+ zf20^exIea*T235G&oFd3eHQIV$PIx$`GsA(%a8(mwb z91#j56EUyYk_y`A zmbk#$clP{xGV2h|z0jx(7pC#00ezUfEUM|v{oZZEB|GJM?R5$thY~Y%%-+uHAw3rO zYL04;^IXsJ#9YWOi>`bbNg9<04^y&7S%x-ASQ2|&A<=PjCzUL z)~mFK7lM`oY{tqpi&jZ?P--oV)P?Gq+#PwuoMRJm^hWjg$5hNvQufmx30S_b1gotw zz~TBG^`Sl>KN`vj4J#W-+`qggkN&JNkIOC*tB?M1*4MkWS-wYE9B?g5I#>pT^$JH@ z>R7%v!u30?X*W1B`r0>q)?lgoWGs#inK=`WNVB<+8bn}q;olv$+agh|!r=qdb)jZC zsg!+M6`R*#zMT2Mt+IW_v@B2oGIuzZBV%AtLuyPer%LkUh^~N)`S|#KcO=a)LI+bI zMwF0eWwD^aZ#1Tyyrf*|{t~|H4!3t1wDCaMum-5+$Ruffx6X-7qwEVhSKzHBQ*H9v zHswX?g*YwB{Os^Oo>-b(lA35#Qdo>9Hw(5tS7@`4e9$-@5hHS<6C6f0F~D0de-H#6 zijOVZmd$R~#Zb!pVOwN(Vf{-&4P-T-p$uh$2!;=A3ett$&?Rrs2uF9IDIuDl1%;u2+!r);AmBWqz7tO$l4Fv^UH&D3&h!OuHj{A1FCNg%_69-{_MuoW_PJ zqvyj_v(?3l-|Ga6tsmlue!{G*J?k5~lI~RC^-YNN2$W|@vyGAR1B0CmzMvyQb^*8- zWKh?G?iaNjf*how%UZpPM1)Z%&x?;LhJK398@uqx>0}CE;GkTS!JVbdW|Z32ot!?0 zy0yHm0pw3!|Me{0_m9I}c`m#R#$u#qpgb9u_k5#75!GXib$u>+eFbrTAAqY!dg487 zd+jZ^i*Kfd8H82dX%waEAD@x5c|%-(f)}>z-(0pIQF?mN^dQo-V}l}AXB>u-%|glc zrodJU zS&I~V@}sQ%gPUj)WZAC7ebLu^@4qY4-NkzbdzLVh%Z*JW6yKkWY6j;#G0HMv)&s8m zb3Ax)Cf~&d3w<2N2L<{C6o%Zm$N_EZCRpd0!5EUpC~G!x^S4?D0kb5$)N2I~G20UnZwT=I>y8vZc>qh(O zE!Nu&>q~;MmV0F{EwG4Q7jPPJ4(@U4zEu7Nn)u>Thy`gyMHOajunn#=W{!X9sMz2*DrFZCj`2$Npw18YsBgP`~Ww9S~uWU22 z{vB{0Zfjt>mU&ucS081d)1DeFQr_Ong+L4}r?v18imty+TUD*e5PMY8Q)AC14%dK< z$2dg1u*ix+DwTP(M!a;l$3_<2^yYXF^y{sj+WYSo6p`HRUeH_b#?^vO>-+dAgnoCG z{aWqPYp+OkEIqecOD-zqFY$@%wbTGNi(>+HuW6()%b%$Ni(fS)cB3A zARAs10(Y@R_L&P}Hgm%UUYQtO+1~QS9h`3_=AluyQV7j-0 zJ!xByQ%!r@^)nT#zS_#M<@nDMpNJGYe_KImkHrIe>aIDM;_NiRY@8^Y(fio5XZDsN6v8_`s6^T+=}4CB!719TMgnF zb2u#T7IRF*W-_m}dh<*Y4^KXLCRV(M;cZI6*?U8x z9pWZ$ENd!_7dysv2zzf~Yv4uj! z_Q*m2S!%xsHD{3PJg;!;p@7>Tdb2U3qsuLEh>;Nnuok9=%Mu%8g^0&X)NclvAJXvi8WAmqlv+I-fXFu2`&h0)jaWL{{tQ7Q5f?R(zRi-H8P!bte)-}zy#k3*z2M{y&2X&t1dO+ z@$v{4rrX{Tk6`S3mcJpmP;Z6fq+2dMp``Xh3$#8RvQvACS_8FV9x&TZYOiGAHgHiU zY#;?^3&W0^ssg92lM!94=T@6DKCm|fKE_Q*HB-7VJF~Ni<-00A-EHPy4n7k4eZnYj3-cJ?b;8ZzxJ@`Fh&dTYUu@|(ehY)ahI z1!g$5?iX*GMeX}c{tWsQujY6?ycbX*?;GwGC;1+q2Maa%!3bT6xFPJYK`0fz8wUY- zVtwmbVCw7!BgHdoWo;iQ!nnoyxM=P2)ur@}bBw+j>=gXu{B!t6woA6Cr??1sM|O-h zFuNnP1uk9!cYfSANYD9cfEgu*L`56ia-5djMUTpo*nM7t%v@hMP|$CM^-O|wq*6#j zuSqIx+=BeS6DP(O-s|}~O>^p;2;Kvc6G7?@H=m?He{;~pK-13ztD^O9s_)%h0_)4- zF2_vV?{xBz7z=1-N->8uU@4iF%65PPoOT?k_C|w3^hZI^tE}BFg>Lej#~jm%w0{z6 z?@m*pK6nar?A;zM^SH$nVn!n`|HFsnhDomxqF^-XXraX@I3svs}c7}OQ!5Q`=-`xj%(wTXvJi=j5wy@%?S(8FgaAj zo|o{8hR(^eH$d^@(gl0DYSvr2_4sg|-XhQgBzD?h9VxJ`$Q*UgiBSOgQbUHZA-l*+ zfPSVl^z@r^OO^tpZPQWQi&ZKg<@(}G9Wkv4@j)>-14kC9(%1`U&~ADq0|MqO7yVvN zOCT_h1hW%9P})68m3D27F@JFVPZ_2ms~0v71XX?8ZsTHR%Ze_r#!2ZXzh-5Tv=wrT zulqIW#d$%=n!6NyN*U7t3z^GtpwM`?20y^wq?tMZUBpymnJ7@05-3df%J}+D&R%M? z(=U?b?Q2jQ?Wpz9 zn7z=^GG{h3dsF`;JI12uoVCWD_dmB~^6dBkMo06iK(MS0y`B7v66Ld7)x5XJxzgH1 z<13aYdOBlYEyVc=s!me2bCEy#20}xvqQRYzy;FX$!g+ zqE&RFmda|ZjpSd6U|?-!plu?=y6kR*TL83!#h#XSKT{*LpTb?|3x$)!Z#_Ti+>JK? z7kWm%fQfAi%6`Q|WZI~LWN5xi+`E;{mDFL$Y7sQ*m_cQqi_Y)16Q5o1(m(21f0X+^-KjTu~uOYMvBm<-f%Gr0{8L)C4;%(mB-R>&h=?vP<6w(a*uB1l(C))}pyZR6@ z|EZ`tydx<^0;&dDSQ|4!NNGOLYe6V2a(I+cCPReY!#6+C9GQ?m!J;WhdLCkKpG?p$ z*qoFGd(Hs^s;KXTntoS&cWrHs^8Sbx$Wy>N6JPDEUK9{O!)wI~6-jy2X53mfGOLii z8Xc>6@@_MBcHa^sM}1>2+gflPXeEra!>y%h9wV(G?JPAF|eq%eIzgJOsttSR8%ARq~7E>bKpqt&;39iXvb`hs>a>Yej z%OcX1O^>C1HJFOae37h_-H=G8$B)zEOGBU(kOqFut|N-qjgeWGKxWqg|G0a90Ib=p z;5ZQ{p0)Vd+&yCOK`ejq^tmQ+=GN+kc1C?@;!+_pEMZlnsgrxuD(= zi0JY4LlprDY=f-1g>#35pvMLwUhN&-m`W)(DI_8;q$a+MD^3>G;R2Uk29`$bR=w)e z8(eNXd8Ax>OdB4j6SOg0%vphIcs=&I={`tlxay~+}-jqY+^nzzX>;2{cK^3Lpgvq@_ zq^n94^~@PTDY`?0`NLeHu>m#xf2a9sSC-3=$o>)17w+J zuu5&>c8rG^ExkVqr*rw(ScBk6Tu7Cq4zn9^6lUaXACksmQv_pK zbO{S~RSsDP*GjRhi!J^&5oz&{jWr9(k~HA@Kc$xHMBv$*Z&ZVo+LJ#h?} zFAXP#n}8^0>$L2~s5Ixp6*<{=e#GB<=tqyUyPPz>l?4{ZM$8!Dl(_L;R+=)x4yy_0 zG7xwrfMK6;M@H z{xeEee46EB+GJ>JFuN{0}`eW8IM;Dj?*qIyanDkYUl1DLrk;&$RX(<=0vyIxc z;W*cfd1*>4?ke!mVS+3c;1S3&b}TZU75`$PVGhw+YnCz;Sj*!KIX(%W$A%Ac!gmFw_XQ%T! zu!b%fCc(O|N%^48rTg35*d>?VVmRh>w1fPc!O$0O?!zlfCVckdb#YebuzVKqZt$@YyTb;MSTm%fj zA4$KH6n&@-%|9I*y~`qVsWWTH6;yI*i3;)=yr2lUH%PJ}oe@v%e|I4Gm$F0_&5Q*O z61KIxHUr78_$sOYE^YV=UnA9bJ;WlbNQQIVxy_!5+=8E^EpOkqrf;2*Ki5B$@8$}F z#BGGVk7s;#s-HiMZkTCY%k$*47&KIxw$AXy{6;o&E^T_^l&eLltPgAolZ>+(>(F27VLGRKHRYHt zkiK!^@j|UYTr@+H`4s5IvW?M8bA~<_s}s{#(axVd37FZ=P`VIU|gGG_50rd@j=@)@nMYU?Rb~zRJ zt)w{XwMsmfs&O#ACX-8~>)YbXP_?-DASZz@bcW&O{T<=JgeIXN#kH0%iM=lrFH!`5 z){ueyRSf)=-l0xgS(^Hpse{Ro%%3)o;^@JbHjk}g!bLX?;O7YsR9T_%Ode%PEXUAX zvpa+dvgHTZ=fU1$u%JX0Z_Pk~Np#sqox5rCD*uu8|JLcEwy(Ehqd2}6yh|?^)_0I& z1J>*-O=K>7|J0mWNswGCyGey_{6_>>do1X8^>Z@#(}$Hd*kj(Vu41IklSyy$LeDmN z3vnbJw&n2n{enquc(f1)LNcRsWITQ;#C0bYp{j4C#Q;`f1BnN%LFYU?LTB2`a~^iR z(K+@r{KL2duhSTOA&>AsY2y?n$?AJg7(@{%p)r6qMPk(d3< z>hpL|2ldc_tM8htI4GYFWt$N?y-sczG%b#vexeg|EcUz!O*8RLDx^f7sO5Z2|0|Tk z+hwXfL=1`nQKS6s8hY%=MIsY}8^D^h*tt)nmFdb*=AMiRA;GCoV9Nz?eQ2 z=$!}M;(?6VZjgg|Mpeq1`rMmu)%zhLp=`-GyBfVKsKWYA_x%t*ho;lzt=fl07LKF- zk9mzaK&2S2&kyK%nd$Fd-yy*iD=F+7bTvn_dRkhgPyj6C-|>>LAPw)@hs4)rtcy(E z!gVdCAn`z1%ec&IonrQuu!r~O<^rEZhv=tAfPHpAIePaxae~mQn98tJ=csfx2Y7c@bocoJfQzk(B>xdXH+AAHO1~~t6s}v|hug3o zvA&+2jYmw0P_!q{xyB8DA5yj5$y^SI%lE&xS(*rQ-Ldn=ybd;+W-}IQT&M9GA5RJ^ zJZUS#OdjlL&f7i5$Xs&VYdUD(3+)e!G5Cx%`))fIDl&s)X^633_%Fc7%e{_K* z!nVrfsLw91cB9flTVO>((7$~;NrWv1N#{BUVC1iP-!n_fUp_bm=ADjv6k@(X+&Llu zPGWlV{D&t`k<0^Z7p>iWG)}TDOCnC*-K`}WI=NH)Dj*94w7i7{MuWCWE;4OHfZ5z+ z&4_#rB0$+LjgZS>s1RY<%`hFJ>#TUESHb#N%m zCw}(Zct1NagN?=R$(?wMS%36svQXwcaFTpyzh5(-6BKoR!V7I|NE}urqh>5!nuB#3 zE*GXc4;uW1Oqb-}7D((+A=HK))-)admOc_1Qdkmd{Evc|xtDcXQmuaU_`Ju%Yx3t{ zFG3$#LdFvby}VKG8Vj2XwzbAfDMa;&SDZcQsiiZ+VtiwJBbej`bl&TJL`1sAJ52rP zW3EE*%`hzr$H^V^U~7bfSu5gKUWA#Km=eXmlHG6!%hdxL`Vq9|Ke#0H(N%fY?m*uC;8|>dS4`6yVdK zLSAQnO8;)mEw0}7sFH|?Ac2w$SJqjj)|hJwe z@(p=?>lUgzjxBG^+MoGs*_qYHl(=dG$%RPYBx((#M8)nD3UI9V&A;+&7)hPyp=&~* z|0Dm_CbU_yd}QgKy-gg$g8%Vr!+}&m$_<AJI$dmu=X;4XR%%~m(eCtRn zl;!}45%XQsbm*^{=sEd#W037|;nrE~-+JwtLso}F4@oOs{@?UMV^m*ApDu~Jy;^&E z!N=xH_a}=}()6$AP zeM5}dd-1GLkv#WPx_VCq6z>6T`km)zq zbXFvWX^ED%w}^~wsh?Cp0nx2-EOwo9Ubto3KNvyd+7ybH4Q?*WByIoldnl=;E=`0S z6q*=+m+23>zf0|!dY{1A9S6iMjz#P6_vGwgSHlllJ#?7ZRyxmm=oh{duB@rmtvkV3ZQSE6B5l{XhQ&z+|hXGf4I@ap4&grIJbb&%`5cdJs?3`=g?OtL;;wJ)BHD}?w8c= zqyX#``+fP_6skAe7SCN=q{NdteRIpPBN0p;RFk2w9x&ym4ws1YQ!zY#Bo>4~}eODfQsl=zVtr{nta4#2FqsW4I9qT!b z6SeE~`2>)aLY5^c6aXHSV(zrG=me1Y)8!Dutsk@@M9KOvUMPRy240Izrbokl+Av(e z4+DW3Bn}&#$vynxIFX{|9M94jNpV_PGwtgpaED{8>I+f#fW#&H(Aov190>!udJU?E zmZup5H1MB_=bnMKb#z09eAo1F5W2c8%Lsg=c#O3A?1)UR=N3ja1#O4W9)xWKhLz2r z>{edNdzH}!mOQo`On35XW4rIjC~esL%};t}z(55|SJSbHu1*aH9&kCoaq5fXGaQsE z*QylBsS39*3p}4ljN4ENZCXs0(#2YU)gxbx4iG2}oQ?z<2X&(qnQIt~Al)EA1F+cH ze=vXShon><07m}49;A<<@uT^&I>s-}gt*Ri#UMf^M`!}d1iQ+=+RkAoxw-+;Ke^eF zyy&r`neQVn)kwacZ&@sIl8WJjfcgsB%jDdJR^Wi%XG?}z7N?LgnnrO~cB$~U8JQ8} zH)$D4_t2H@36F} z(PLkXY8v{>WcWvNx2j&cav%L=cqitPxv;pD_sk|ZQTjE7t8D0Fjx{M}y}2D0NGzr+ zpZ>iD1je-p(aS&TKMfP6JUV#m9|H9ZtB-@vi?CPJlPe!X8A(7 zpw21z-m(JFa!Q>&6(~T%Lu=2zG1`PN-+u~;B`Ja%x^a!@nLSx1n`gwc!Lyoww%It` zT{96u)%{<@oo84RUDxmJ4G<9p=^_Zydxsz(O`3poLhrpuC!jQ`BE1`>N=I5~N$3cn zw@3?8LWe*?3n6g0-}gP&bDr~jI3Lf)Q;Z^P< z-9<#Q3i?}^*2gvlmH+iu`*{^n0^=gvM{(5g)m1ozlFUF%(wdiE$v0<7#n&6WQac+Y zC{DVPV~JqAG2v8zs!Ofg>4&JR+1AqR! zTfiwlqp3!VH5ni&wB9)bBPQjI_rqBTg?@F5M&m#S0Jt=aF=Si*g#c-=1m>M!?a4wHK1wAswI zMn38S6#S&^i*m%UE1!Ogi>H&78>r3W`wXA0#2t7Dhr?2AH(a0XdKjA;ZpQ5ktaY$& z2}O6k`{eZfd_T#(Mt0>>n0?T*6|kZz--w0z7S#`}2?N1)XpV2qmn64Z?86l1t;e+= z0yyuk(f1p+Zw*T%*_Ty+`dmi1$^1Y5E!mTbB_M{N8aojSE==K^`JAD$GYjIs=lybW)9xW*Lx zFvPn5vEyo9e1-P3VeKu9;u76CFg%eUXSN`IK>0(Cu>gG^6}}lsPO^=qiBD{@3}E4{ z(sq+h?B!a%J3+$4mzc<9D3>^;Z@yb^{iIC^!E~VQYs#Jtz$Q-fu>ek69^|N;$XXlqfWkjA?K}gW7Z9uxpuRA-OGzbii+ZvehZ~PU}a&aP5US zdzgIV-<4*QjImCZ#lJrM&~;vT$6@%$Rj!u_FXPu&)X|m#Vq9gyGvJN{p2hJ~akh;w zTdR(zysSzBR3pT0ghDG?f|0f{DohL>!fVSc8Ydz3hZKyCEbbAl4|~bfCN!7=q9Sis zcQjoIr-c4;4}2F&5@bo(ey*6iZ+Q^D**cQsS07Blh;&0)o^)u?e3`gZjW zP`9;iQlkN5x8<=63Jy5gABuj!5)P_^_~jrJb51JGo9{ zlYi)l^%rCJum8<_`R4qukIFceaHd;Zh(sm|XGmR$`afEn|{`XMO2tHJsOQ54-3Vs2U_N%t;@tJ5&tPi(MpaNuu*02ATeI{!-Y|*y< zTgrI$U^(1g3^=bmU*lXDfhy$JR&rA-eN zwA9uYOJsP1SqV&>)cj>|qwO*=pVxDD4M9Lte_HzdE@SbmaP_}XPTZcif@?Ym{JwB{ zO1f?9=W3>z-2cr!DvJJCtt=U~8yDUCCQb(>cewX{y!+8UGpm#-imRc0y^9X@6alP` zS%OFx53ocdtjmiAuT0iVKH_ISvoblqzI&B>Y=lpwmZzvys@DF!;;tCR-XryX+y-xG z^81o*^)$nOtZP4vYoOUSrVIZ5!Ec6|q8mX}*;iZ?1l zeC|hY11SH2Mm6zWnRzsh&!*4s+NX`$UTw15vZeVnf88YAgxyx{iZ*3?=aWZG?$`z`j-_pX{!`J60oO8q}*T1AOheNlsx>4g0|+vXpS%l*T-y6w18VfQZ@%>TH78+7$@ zG&ytq`uJad`5(Gh`-8A6lUyna_9Fk~@&A*cWwFWf_2Hj!_wdyJWMt_-`p2Xktz>!D zRh$34gKA7yXs`4Ty24xk{{KJz4|{%P^1=A1(a?YP;Ot)E&BnvI=P&=yM*R1spWeDM zssA-<*op1`x>!v6xbhWatZ4BLhunX3yyE#kCjUPSovF^fLr!Zr;AqRWTC6(qaNwNw zRr@8;d6VZSd5HWPiR&bn2={osR6r3Ii|HD&1dy1n(|h~dL9~bInPq+rJlw3ct^H3lvwIu)HTozYWACEh}HmG?}JrhA&De7_r) z?tY7;>HP(>7F=n!wCiDbQeQTFX(9Rat{brlKt*D_U?Ne;{fmdKaFb-x@Js5%vwQpV zT660r+o+3_@012|J#ceuG-(2OGOsqGVIc80%D?_Ksq&9jXMa~{uU2wk!SInR`pAUu z3aB9l+E8N`UEQJvQQrY|7GEHB8h;19*bAVKNql7>cXP73C=mHOX)rWT-6ZALg>i7& z>`<6-VqhL${Ki!+;0BTmVSJKZbx?U(pN%|aYYk3d4mP%6Z$pH;uwbWJ0S$u(S8Icg z=3n{Y*uzc6hqKn-HI)5R;NXXQA~YbnBsK9YnHCKDqs12yhmK2i<6KT@eK%TOnyy#2 zjQWf5>ZQ{jA`RcK8XH0M9pt=SL`3@`0smFGL3^8PRu9y`s{*7 zha^WjV3<;43vxYZR$wdEJe1qn3nI-@ArQ0pyVCyg1f%xB{2q1G+evrI`)7fyJohnW zFYmErNS&%IlBa~cx?cs?SH4t&m*MMGzix>E-O!1X5K2CC>-Q6SyIXOS13Md|zr9x^ z!ps$ZVYQP}K{NNC+A2FB(*BG7;}!kSYs#{q=r@A)23s#hw*El9_tI9xiK|ZZlh?g=s=7@iVLcAt(9Oz+#U`9Mvp=IDG{Y!#diK08VNBHn>JQo? zAcZ{Dt!L7e%e|w|1OlnBjxz*Dr?9ur^d?-474I<~d0zjwy2{oy7kFjvEq(0 zF=vNor2dLXB$g)2c)9M?mpAx6fXi-IzBtYKTaskcvNrChxK%qR8R_fS~$jwRaTCGcwDdcMmx5BImuf8PW_W8?y^4p`3~O&@*ztF6_m&@mye0 ztm+*z%i)wWY$PSiQ~+F@;FzvI@fng6&<_n{$&1-mdPI~7Ww0} zf}X{c;>%ZxD51Zav89EW-ChXT$K(tGr36O_Gf*01#`hhwzo&O2Hyvdv)xdm{*=jz z*;Hcd(fuM-=5pESmrm!NZNfg!@3$pv$|7M)Wa?MO>WN}`3hhjnfh*-pQ^oU6!u1@G z;m)P{q4JZV`_3sId7WLaD)IX(A8xO&6;GWyx)!A;30v_Suviy4eXL~VVd0~Pvyt+$ zBxe!J55%D~-$g3=pZ)?BlwZC_yJF*4or5pVz?&@EVhhr(RrgH#E0fC>e-HfHNH0vg z&Z4!|dA)SWL5xh1fx=B}}UQo5d zUug=ODnhS-XQ6|oUlbM(N|maD8+45GfBj4xoDLxv9ZkzOMA+d|3|fCuUu>bxv5=hf z>V92JlTg+zf)fJE{1L0YcH}|dHS}2vv zS~G4PAgA#s`eLdb@CB56)(}@uR3|Yu-=k@N5-f7$){ge3nJ!Sl;xj_}5Hy12T zyxOb&PeGgG(+SWqs{H)$dsj`>duKhNLuXUJmt6ZnAviTOIeXqLU#sVaHA9o8^XZp* zmTx>7)>HnX++6NUm5sNysEg}EI@h|Bq&TjE!gS?ZgMzA8@eRM@EvwEdU72!e#`1#? zf3C~&eWnlam%t{nA2lEESk=!=!i%xKyPuEP_r5({icG1gCsy_UaZ!#r%T*#xfR#et z4t+9L-{K*^>of}#iT>68{mJ4*sexxP4putJw{%@ZPQ##Ri#Ck-VCpX$k9V<5%s9kM=&tuO8s(IuEAfew|Pbmy^d*0(%gfIbZIiZ<-!UvtDuys5iL#Z zkF>3-&icC*$H))|A;B#4Q)ZVfa`G2R0Y%2^`d4YfUndqi3^{f~2{x(jg;5|}>+Qo! z$H4C2Z?-VT4Pn-^dj!i^$Ha@ufNZMCmix|L!t~A#;`NnY{M@!_n>Yk#xq3Mg@R`am zVbadn$wW~2adjb#V~(5-Z;-6-w%FlvTg>YLQp9sM zpr%2a&FJnrK0f!rmFN}r5Bqa;gxBBdhg>+f9P@9-^JECS99J{_JTtPy+kP3=wEAR&#U+mSb z-Ityq3X%)hziGgCMGps@SfO^UM&Z#_&GcD%`-EW3MJg>xtT8gf~jBsvh z^FVDQoFzHe$ecq8HsXj82>Z1zhPcn3X8*9r7 z^G1{j;sg%&3z%P&`6YcRL6`36w`lZG>Ppz%M5)QaCA-0l?%@ki#jlr0JHBaw;f0o` zQxUxI8hX%RzinLX2n*A}sMnBQMmq7BV3Z}}>^t*!W`sM({b0G)spoH$GcR8WI4Hy@ zXjeHcJk&>}*=KmccCB}$+6z!F|NcaH>!7(S@f0X=5s~72rZ=E_rTM8H>!nK*(KiPb z&fDGBXUNpo6u z-&qOa)w=u)uckFNz?8cnrzH0-SC}%&?H7%|H7?a5)F6KmK@yABPR0@Le9-{xB2&?!PDLYY&otvd9XB-v*x;LKElOn1{$lz$E$_! z3+3bs!6Oi~4tY;^v#V(ZQHeydw^ z<4!Avd|<{N`0m^OYDucA8BAk0g=FBzUB>s{YL~nbXNjN>+jl3ZCINmMmS~D?K$Udm zmah5dvrClEx@&{hd2Mb;uY@hMPrA5m&8M1!JoJx(6Aj70m@>WIblyRe@yQAzz&_8M z(E#Uu)MAj{Fl`E@_de}FBkwY5SHW%Nns??Y5#IK0W_HrPd0q=52C10K50_pzk%qky zfBl)AmpK_zW$I$4AUz5E4q`y}+v#a7hCI|z-+V;??RtqH!2T7{=PR!s_Zz?TpZ7#t zl83&R*gh8;*ksSsnGQUiwar1cwMegxIg0?HKEt_Q?bq+7;rL+n6~t%}1PkNQPeJ#y zn*(Z)`L?ZXr>7mTF}TGA@Crja%&p826r2Sj`k*=VKAY7KaUq<1u3Mwa`XCFE<HN?9aEo5hP*@KP7dDGM9AKtrh1M@G@ty1;cNI3bFwp zj_UnM*#rH4*P9d5M!K?8HrS zE+Mgn3YTAFb%=QryEO1rhzF_&nR*01O~uX{xab~KY65-UXq`>;qrWZ^8{1DY7Z*$e zB%(oCzm_`Y1NRBnG=&m zPWMn;D*L>OplZ0+3h8hFvaqHVRumk9to1vyK_~P{;(b_q0kVwAl`m2RJzf^Bc znIAi+)4K>W+1ZjScyIy2QJFyxby^(CClwNmbn*v$m$ta5M0a6=+$Gi`0&VQhIInIX z%~zNB-d%((H4r!r^xMUzIK2-2OKeSThUl%koH5vrxPw}0wL)o(a(K{dGX@!#glTHT z{2ZnV^9-nsy4)jAcXrP-VT7Op(QV!%T=rLmiKF#S{`2pv;CtD}f&>l7ylFyqPWe)s zj>L{VA&ne!K32PxP0mA7cD#3vPcsxb9RqCxuNAk?G^?4u!ZoXGyZ zE6BIm@wkGmFQLwz6Ec`JN9y96h^?x`SPbTQt2GCq7Hp?0jHWoslqt$tS_$j2dKZN4 zAbo`Z=$6(n=jG8bBsKiYxwU!wR+Dj*nQfXLSJZmO33MHbPA;$JA`jRPA4sFlGe8}$ z?II#GfF_K%KMey&zeQ;9CS6cNXBBj789SB-Fb34(+-w`B7)|v}h|}04a*SaJF{i+J zPJ@p?{40gtHYb<`92QJ-l*>98`xugQK^7?PUVjQqxY%;Q5W1jwdM02!F)f_@QrA`tn(Hy?K0sauGudehn4UsU zNMTHUPyhCnlQ-!Y;h4+QfPQ$J8ORnqFZya)DQ&^6m5s2c`yfpq=T?_Ysd>H&m9#@g z?=~2`5t>r4>YWpW!az%6P_RUiWNXkKv7mO#iKUfB6ZT9HRZIl` z8TZR@L||f&iw}ROIg0otF?|Lyc~V!rsCT@0aydY^i2dTFvw${shx%;Y=ng@%NP%kS zLy+}e_@~Iptwo?)D@apD-p@PB?_>d;&Be!onRg?2btoE4U*I!q*A0$ewT1>%Z>d!n zyES2Ru?-`8#whrT@^-6p-2i8Kwei|WjxG$CsPoMSug9tX@T@1%Il!Ig^W|Oy_Ky#f z$rq^$KlB2I_^vr%Z&weqmZ25uvW{(4Am^i#CT^l*5nB&&)V^Han4iM|QzI7?@ZREi zyo)dj6flMQZZUfQEvNCPbABGwRd<@LBtJ;pY zYAvn;42af_>BV~aA|JW&%98EykXU*gFSKb{4E_)C_mvc;MLA$pVY+O1>=4^=0xj9W z;lgQ~GLSu~GO$xPx^Zr=D)Tr=<|2AA-JJ0-C-$=5YwG3IbBNjR!1il_Dl2~VW{V6l z*nXZJR-V2c(+ru{vzt0Or5ZGUN3H3`DK^D|aJ{Q8vem?WSy2`)d)|Rc!-Zj^m8C0o zj!S_9Fj)d=hQ2{u(fR>AE3X3RPdMova0TKtcMLgzEWX z$fca}y$z7nXNExV`8je9I=P77a)a`ipS7TNOfU|XI5%iBWN3|VkBF_2Jzg7u7o40` zLN-D8@}R^*%+avqt`>A%#@-Gy653o7wJ4s`Pf_Qgg3zKS$5pV+X_Q0><6O{jRL=NPT8?MAH$kEzx&SLBS zIsDM6OK2yN}F{c(b+j$;4n4pm?d@|~&&V4g4 zs5c2p_D!VDZxB2}8)H67IMig?*sWQypT#LJ64FuZ09;RGz{1xlI5d`wm?^Zqg^Cjm z2*KQym3nIVpByk$5b2bm4~q&8_&su`?(t6L^&$t`a1?z7XQ=US4JZmpuGK z73xQXF!sBgl+c4zFf${8zL@U1{0ox6px|>BsOuu^rfQ%$aYqxniy|(iPs^4t2037w zbK08xX2?L$7wp;ID`;q?=tAL1SES_RHgRdx{7^Kz*%p((?Y21tyBJ3eX3-rTpY?a} zKtmtOT3}FiCa^ZYm0YPnKa@l{qz8c-ZT&kA`!-pP)0-xSP|yJIKgdsfFD5S=`-t#X z6O7E|RK|80P74G5yE(GOa7qXvvYz&4_+g-U8L7tlyTKS|r~!2Csmzg*0np3$st+?2 zert*b$C@%{?N_j7sk)O4MAfG9@!l3Rh#+><7>bPk(q4Mu;Ou^(0o`o`0WlycPzbAQ zwI3c|ax9A4XojYYK!5t}B;7qUYwAG+A{Qt7i0z=09q1~$dUqkXBze8z(&ilJdH2Yy z<{&N*Iq$EwB7|u)f>OI)jUb>Bw6_D@)u`TsI%g~x=Y|eT;hIo`>sKc6X=2C{JQM;$ z%LXdv`S+@vhoPut38g4s_c_be=Ekvu&&S^y`lq34Zn+b!8;1%(bO53w6mz-5IFHE= zGQ!B3)oEa&bw}I+08L%3LDfMBLXh499nfzo5~^Wdqm7Bj@oo-ffqdMz*WKr();raV z=YbHjxw);P>MbIEUTscDCclb=YOwrtJQD_pJ|DVHI59h5!Z>V(T;ecS&#X;33N7}t zOXkB`jPl_(gCk6R9pSv^uNSe%{`i|YLS{8Hg^KNBDiMyHTaDx^4+I5a26Yp+Tx$6uNih8+d6F*cyBsYJ48R&NLA;rSI-w zd;fVToVNksqt?i$s5X0npIwKK1|bQs(Y=$oZ8c~H%x0Uj54ejzmJmBOgZ0=!M?g5j zZoml;g$&v?K>-BG4+D{Ao`J6L^^*|noVD>e1aP^D5(3u+V9w(d)&rs8Cj?DQ5nm_> z)0iaMbTEJmqI0eH#Ed*WVG1RbU)?+B8qhiAW!jXp5$(IX6LT(gJb?!DUO~5xl;vEl zf-B+05G#yhdZZ)npN~DI^W4JTTlU%zF8Qs-bltgTx96he#^R;?^dUB5LX$VN`7aim z>OI1}2wzGLtasOx*xg_mNWlDQl+I_oURI(%KzXs$kN)A~!&F&6%V}Hr8L)F%=lArQ zj-D$aD{I%b)gtH5_g1OY_qU$PQINFGjf`|&?CiZ(=O*QTRMdKf%d!lzvtJ8EdE5Va zc}O^z@QFNWAD$OK>GmFm;sEOj&iV&&4|f%R2$Ao$Hi)g;BthJWEh>p|-Dg8NV-;o^ zjF18j4dgjTqY}M{kQ|$p`P{>WM`xXLnJN=$*JWlc=MyO%zKt(t_BGGDSqo-9VzA{U zdqDno!0CmP{rYLF7YmC-r%RlB=h`k|%zLBR&O9|p;Go}O;#D5k9-V&$%bZ$TuJCBa zys!zSkW?ZfW^ku>164#(#UL8b=F1@PcSr7Lj>%`Pp=FYnhB4Hi8D8>MH3|j$P#8>y zM|XYN4XSZ>XAHTpy`evnsZLpVt-`eNPS2R5FetvGhGe)sl(FfiY=cYUCwub3ej}2D zrq_<8DYSxgu`{_&lW%@MsH^xIx0JyODUYVT2EVxb=e1C+O}6wjqSSU6p)8d>oIuaR zg;u;*$X2^E`f&ckLPAZSULS+sqWp>^o4Y`x%ZyUx4Ufy{&od0{4PpVWcxDq70wR-d z-WQ%KOG|V7nxtRI%KG;&)qVbJhb8^%GAxXJ@3KL8pfw5c-9xu~O2tk85EEku~k=&F?&U>)o^Nv#Gh7>W{QJ1g6)eT?yk0 zLD6cZV~+}ZB5|}=HP2i#Es?mHFT=eqQ=DZ+Ir_pv z=@{mx3HF^dC5AmLIg+#aI{56szIJV)xWW0Wa>AbX2t59gQx1dhAL&fU%~nW^Qta8A z#vhI;Z1ase>K|@cg^Q=R*|T=H=6v~HqzP_J&2;?{gNa&jljdJ$cJgWSpxSwJe~1-V zx^N%uqM@RxaI%*dT=a{qd$_CihQlnIs{GixsLNa2TQ0R*B1%&$VMgSS)P;J!*IeTU zLa8Jg1b=pzY4Lagh!6$NxdDJju#!Xlvp@^RpJG}Lzo)X49=ePuXpLjzu2|OG4b1o--jq!BVnN4 zCzFa~V8@R~mZKuiem%MWo+;;RtfbGl1`(?*^zyz*X;aFoO${|i4)H+VEdZv zoKk32^37A*bJoFJcy>{S)k0gdN)>XD@^y0{YHOq?XANLH!jY? ztY4HSVtYyu0QBG}!rUR*|KZso<42*p$8txf&x9TQXlvnF(M#*3g#mVa*-7BY_`?s2 zKWjMhhlF_2T`!|TxNgM0)>CZ%O{Lf1Im@f~z3hbr5q6NWrk(yBEwaWRGt(^mX;v*X z7a0BhGQz&!OFcgT+0M9&SJ;%Zaf#o297^DMVIA@Mf#nm6!JJ_!wza}l5v z>nE8nZu0yv!FnuB-1_w0mMpAHu|PRP|8%JjJXU5W9T~RJggzWjPRNhRxDfgAIW(nt z7BPU=e6Aa}YVnd=Aq1x3!)kbaCxg|oj-C=j!~5GSZW#0Af#Pc{dyJKR$=6pmTs<6~ zG5YT?UtRhUI7E^bX(st31LegR^=$%~0UX$mDOt{016*-N;3I{6Adv` zlA`O4l|`ST!Q#i`L&8}m;N{8Ko9bVIbu<*H}@5EZVz;LGr9Ui)EH8c7_dhX#$p#1-i~R3)I$!=XsM5 z8_pt`FriW4-drURcdVL3kAj{X?4+_*rlm$2t5trElCn$gfgD1kcNVz>nm5gM1S@Cn76KiA~M4LM}*J)0V^ZQ zYw~;7mEYacl&xeL8iOh1%x@V&MVJf@x0p37<3LT+L0R+5?ISO5D|y)XyA}mFs4N?wEa? ze(hV+iQM`5 z0z|w%h-;X^7f>s-v946RUeuLhgnBNHfuGR>=m}g=4_mMK)Ya8k9CM%7Ta>3^O%&WC!t-La&A_SOW7G=OhWpYt{S=ZANopU!CIFM z!MXxktr9^udDcmcGNcM=>YG;B$!@iof*bt@*`xPt7cR7^xRX`=Zknp&78Md z>Wt)lwM^fTvI9n)xU8;D%9-krqWryC*|eT1n-7i%oW+gock6292Xt{upLF6f1$+d- zm0-&a#l=yZpj&4j+(+Juo8M*;eZ7S{nR{Qx6#MbTyO$I-R{%wuiF)nW*Hen^=j{i| zx*?ly+85#048_BS5kcUP>kPC=nfZNcrZy(?P=P384rU8=n7INs{V4jMJzCQ5zl zn)kh9^W9uc^%ut2qHA{QPZnR&ZlP&xN2AA`pAPO9sDLAmVB!!t)%AjRHdE~`Zb|@a z{yU$wzQUo34=JeSqUD1w#Sto{ba!faMod3xo?l(OI5TL-LfNb@u+ zL|y*GH_mLf4S&%?=ud#hhCY%mofq518Z!%-N}FUt$VmJzzS%D-tt8pGPIN%z3xtB;ha4o)XcUMtD54)WdUp3ZCCz&$}&o89r=Nc;tnjrUt5 z-V@mtwJhwg$pX>q4*M8c{PlIRV@PFu_Bkqn_JyLb{8LI|T?D;R2Oh+JD$P%PVmHhK zY5fS(rFvKs`J(8Kh{q(A-)``Zv`qvF_NI~g&*%ibFTvTYE}ERS$&JQ-MTdW0kOmnZ zZ$g8Xvz_^hrrDEEA|q2U!qsR!mz7mj-OPPS85a=eu! zEi%7bBJ&q56BABWgtD<<Rc06v_IEmh$OWCpwA zsI5>p=b_; z`-oKdkv3gu&7*Qr7ey14R&xCDh!|rCz0LI3-ZL zyK#Jy_^I(PQ{2b%ZW>RL#Za;IPj?O}r!Ba-adc>Jo&AC*Tzao;sQ6*x}Vo&Md0JOU0Oews&;jtj&p zNxccXakXJjcQCe%`4K`$cln;e7FqYMy419fwY*;Gd455VeEt3IgU&j>d_5f7#$>Y$ z?=$H~e9~UBjqc*$BxZTeN4(uZVKIf5i3DT4r*0Kbe4z5iE1hx zQB#QhUT62`Vi16&uY&?}OrnVSqV9O@vu75hN&=(ub_=E-9L68#Wh1-cRq$`0?(z1* zfOH2-QsAZKIkysB(6!?hwV2YJH~FONrhzZ-9|+C|AZ7fwr>H%a6f-PO8<5LqIT-m9 z-bxxn3p#UQm%bq}V9%KE=%A{w(zKz9U!E=2KH;Q<2^N=kH*WN&8E-nD;p>Th{z5i< zgTA0iTePZrhXgU`?-B(|jVr;HG<5hQfP?!LwyPQ!7~zSi*U=;+q`35I>#R`26zvtsE$hPH{z?n;# z#E!hqDf{*JZ1D`?Z?Y?GwGp`fpC@0lX0GvU*=7l-mFLCX@p-)8PhB9eT#6i&|JXzE zhFk@wZME2@;V1n2;z1W5l$83RM22^FLJya|)kaZQ5<>A>#x3h5LnE!n`iQ`|Khz5O zr}5FTvE4tB=AXCQ0m3fhPu9)+nXB5?0!=+vb~h9%XJImQzt+Ded=jfFX^ftI3ZSf5 zPZlw=b4%U{?C$;AzsZ>-W}D-R|0jWiIMDiWXc3&oZpB_exzInG39kkce!!B^z z>(;h^j&qo96EJRYj*GSsn6Z67`cQ_7T%pz%)szC9DJQM%@7;Q6Us9@tWP@6`=G%IkeM)%EHeXE_zW zbFx?v#|cC6=>o<;x~Q2duwI<}gGk!CKw#9*h&NvB)K-)!H2RXaejSz!3=gEzukj@= zMR?PRzSgmluJ!WXWdHgJ|K!?qWx?8aYwGzAD-HKj_h%*>83v_3&MRqT8t5-_m?m*Q z(eZGaZF|{i>zE#*8xXJ36jA>Bs+^QX#L2W1o#>@%$=sR0JoepFhNb?k3L)Prs@Z+! z#@LdjOAHDAwxY@pclFBn4`=*^1&VQOq;t|t<@%MyrSF0Bc#M>G_?lNOcA-X<<-xfd zx1lf{;ng5HooFuX9*4tw2y=cXyUGY zn`S~RM16I;{Fl%#ewy6yzb|6+*s;nMSA5TI16RzGZ9UPe7ymZv%bg>~Y~(MV=_2h9qq;5akrs;f$0`cDPTlY{JpfDj@%p+hM%53_Iv^WGwzU76H~P~|S1(4hRX(G= z7!*SC2V(y>3n1xrUE|=S%@bVF*nz6Z;5I2~zb(@-4&CZQC$elBvyz!Y>fvbmTbDzF zD7Z;L#uBqdZC|MF%b5oEh-@Hrg9*~n8gE5^oEcODKa0>4!sB^oek!U74_3PBBa4F7 zbgdL9hr>_*yc)C-KNvD;%+~_fsRZ)AzGn2fZ{^K958+`(4>r5u%m|Tf$h&ecswi{8 z2xBciP>=lBN20mvi&{sVX1xRQ(@|s5$e18EyVA@InZV(BM|5n0lIAYy<79<<394)K zyu7xvj-!Nj zLQ9SrYFxJiVOUEE4KYgoo#ePjl$3q^TZgjCM5{v=dU8Ckjm-$N!kw{oXCA7g3S0DK z7lR*apyx9`DzuQ->WjYzB~{gYc#d1^r&-1Whvl@+#Z#^Q$90p#KuO(|?LzZ8>Evyw zimKXLQ=={6ray*ZwVaW5l$>jLlCMnyY`JZ6HJiRtXc1clHF$OzXLq|5-VytZD`m#S zJ^a)Cs*Gz>w_*J?z*x+27%@q>ANKtT4qct$26ppG;y#{h@U(@;vc&|L~q@k zAbxT8ct=26li|md$B`cLIWHS68Gh^D*xwJuQEuDCLx#VcZexb5aAhBDlq`2An<*b` zA{!iLbd>73Uw2Uri6>5s<}Mu;bTVX$1TprxN!bi+YN!^B1b8jBkPyNGE@Y@@4~)HoJrO!d~t; z1Hd4j=I`z6xBA<{F*{D9P0R(PRUO#0U|8XF`^yaTsEz1Xyjzok%!hOPM$I4kahYn!CR+?BCuQ3+SN_)`kjju4NqnGkqw|=y^G>xlotV#LJlM5&1 zidiqNH2t_a#YDX?!YVgT{sXhqh~MQ{t-=MOGD%c+*A1(S>EL*94KI~9fIf{+JlvwN~mz`pb>uk(iA+Lyj~pBpe) z@lX8+bTgc$o;z#=7Fs@4!Ro@7YduqdyY%Iz<%~1O1S{1G>&HT;;f3_x!}R+i@3w@A zOR#FU(2#!S6|05~aggYGk8+~5`BHtgO{jo!h(V^5-GqZSf`a`~B04o670k z(AWZ>RaFj?^NB*6?JsAQZP>S_+r)SIGB8~4wnbcdoHEAxFBJ#)NR6`aNmqAa{i4Ij zlKrPu(dVZa+vabZ{_XUb%zopL%Ia4W59z2iR&D#d)ax^_VRm(ng8g+Wq7^|0k;<5^ z_fl|^krPD5_mz~!&%z9PdfT_;R!jICz9gXb?zm;=oy-reG)yAGbzo0)O~ z#kTi{eLOGJKleE%o@@6m&7OElZJzL#o|Y3$R@ENxTfOPw^dEfuxLH)aL;n0%vHG{c zjAvSXor>lAYYC7nDkJ&tFI`k6><}ss&|xVr#os%uYajjgL2vqLomsr%Ydx2A)BF0a z!k0d)x-CuF6g?*q0_~#|9`L?Uis@r9*CF9cbtWQn+%LaWKLy+($Kb^{r)_|K9}r(S zez&A%o{ktI@oLhaFb{5R?6*nM6WftY<}&)grzSz~Wd=Q(;tMp5T4B?SRTB_%=4yHl zj9w(=Q_)2BeMS?V9+H6(N)z(7DPNU%&?Uz&Y97 z{V63Ddpox?oBb)x0&wQj#FyY=dX71`Wty*z>$I6-TV)7++R0C#_te3ctl0srOr8+K z#c86eYsSf@A@`>bPI0ghqHg@fs(0a(dLLW%)qW)2UL0!T?o`sL0t)GXUl*?o&>)WJsAu8_&3?vsYT+O1 zx6cji=1`&gxXgg4i&*7LL}T;s7A!gN(mWP9ag__(M?U{fTLZsiz@tZTy8TF;`}}t{ zC(+Wu?C`v9WW2ukW5@qu?;RT}>6Z85m=l{5>~Lb+wr!ge+qP{@>|~P3j?IZRv27>+ z+{fp4-aH@RdAWCYudY?K)~c(E1au;^W-KVXqm?M+At6V#*ZS<3TGvz|{%f9vvkr_J zqta8rU;^lMj)%^h=~B%gKR(2UbYbN#*d4!~U8-TsVMQF~n5s=DchKUK{ojx%Pf8-Y z6JRt3T>!-U_doInIA9;cgJrQi$pR!|<%TZ)T*VRkRG)u(3j9g(Veq56>*ZIBZw^sD zKhMjBV8WMnBb5yRPk6$5B0|2^Q{_~TK}8v1WEmVbvx_E+WF)hhVgxPM~`<==Ue z{R5^PcDsV>dd2>4lljM-{y#iM3CgW1uJ`g6A^$lbC0I(VKWd#+n9es9i2oezFI}UI z0ZL|2c06ni#P~b!{(VFK?@zOd+EKi9$bXOa`OD!4Nj}7>3W3L2>;KTN!u82~Y67Qf zg-=QRd$hmgBB%N}>zp5?wQp+w=L&j&l(+z_Mu~6@2KE0TKC-(a@{@dJm)+)#{~<)- z2!;~0^80YJ(Esp?L-mt<*B`N+_uBtM2sOdytWyx%)yn^eS7p?nf|~Q)&Z~)S#6{;Meljv3c%PAJ1hm*)8h-A+-QMHZV4g{xSNvcV+i<)H=WW z&KEDODb4c~oJo5mimsT`G5)KMTqw)x{}vg=H?W3jKOTZK@z|T-bvLD5#VnXoa)JxE zjSAqC^yh)b1M<;y%q!Hgbjb9++`hG+w|){ZWX(SG_C_qqjPpxq+T?vq^!g6^BBbvj zQFZ*nHxuZobRw zq9I3i0aG2<4C522u^yF6_9pW?(p?eLF_8uZ)45;ySFJFakq#oUlLnERcS2^k>Gh>3h?LJ)I7f%Q$rNrdR2XZn4Kl=BNsufeAo2fnz>j!s%KOrD- zNxSe4%V3*Zp)%HzzzUR?BIiH)b*iryepUp)IQfCbNL5jm_m}&xJtrdjHBAC1j~6=% zj6)IIE`>W+;)5b51ckr!1y;2K`i1e3LA+Y*>+&4wL+rVl>Df2XN}6K%K+j8>Zk1JE=JC<{Nz zRY5i+uwP9xh26(w#Enc^kQvsoR5usgmtipJ>Pu#-CnFnSTU5%8_J5KLmN~a7PF=h< zNk*>YqcO7VGf5JCuGB7lhM$9VO8)Q59*bbYze`pqbx~@;A))*>r8sipIQ<@m(kpsG zgTf7tF9wcjQ#@U?tFUqB*M#A>;NlnVFIgrnEnV++p_iPTtZ+h{i9}}Vw~((g;Z*)- zuj>ByIYYN@ppZ7JaOeJBNcSFHqY5z8L$f;ViO4Pd6M_E6QS#Rgc)frL?}$QXluAcu z1ef?J6a*HJixu!Qf*7WW(_;YM)su%d>p@+P!y61me_mfO1VWyygxy0ofvhF!i7%*`R^1;D08+1A{Gj z8$Kr5`(24`UH7!Vq57r%O#o(PA3&TM833YA7N!lsnN9R%fx74Q_^fZ_4wfr$cz@T|5`?ywL${H%2@=4f{0C%6|wr5 zX9A8+tPoI|D5$h2v|^%FMo^j*Yxq}BaImlim(|CyKr1@cbtlvDC-F}s6v?;);vrA@ z&!Q4q<^Rx8o4aE9I+(IGV%9X`Ad%8IS^iExvTDVdBEi870(%whs;PelqAmDWbo)5T z?`)jp_?^-o%QlLP&=AJ|6O{Z@6$Xro`5U5$qGIUR8eD}ISdAQ|wm~=rqkh{{k+w?- zRVB!{3uZM)iK$#rXGMu0p$R^{PV+Vm$6)UpHfR*{JYv{)6)5$v-k(lB7vZJ!>YiNMR*be zkaMrCv0cETd5Ju6Xa7LNsa*QeH!+LwU$6bgV-Yaecc83>sf1O>cb{s19uLhn;lEVv z-}VO%3^vKVIr{BEN&&>_RG*Tx(tk+gFFb)B07LDR{fEEXPb~s8YdayRMIq<_qsa_)Ymfykxc2^ic?p*!u8Xu zMCX=XYp_H^B&2S7cD;?-=v3oVbWce}-(EJ)8%Qh}LqSG1SRtuyuxh?a2iNy`BIA2| zg;#EMF%<<+-&b~4V>oM6-{@0$DRq7JY^2t+->b@=inM+EnA2eVm|4Ymh@(4sm`ow{ z`7BvD`hKC})2X~rPdb&{p1lOj(*nt(kDjL+F@7V+cxl!0dDRgoY#!w<3@$pTuDBbn z9KHEixVpxt=yK^~inA$8*>QiWTJe1uztXxn*t_588LHg-@H*|+ayF^hTTHc=j7>~5 znk?%iW&T?4X7WxdN6@%4_VV#lgoz+)hRf;n%J|1`@PA2CGTc8;^|e{|W@XDCUQZkb z?#6ZJ>4PV9*OS)qtXaKxhgKwi@zehRqn8!7q313!&I3at=|J4C3s)jyJp+5e&cDYopC`{Bk)^@hvg5VlQVCwoCA=UN)qF#n#t&?wB*d*nV;4c#wb~?{$L6Mie%ywl;QCUlbt2Y4PrJ?sJlFjZ zg~i5=oQ{AG8I*AUhAVx@c;$;Pw?XypNd&r~{KeHa;OLU&}*x zi|mqllqn7<&%RhjpYiqb(gO(n3w8RXvIK@S0CjiP28o`*_Mt&!#E{^dF(ICmO+bHs z^kT>6cNOEW#d4@j_dV8^EFhYTS2uq0zX`!(b z)Wmr3ms(s2)T*?n*t99fG;2Qt8dqDyRGJlLv1S}bc|dQ*2AITuWfL)dsL|^A-*F?R zx05h4Q}e-cBAk$t0EVNdVkhwG&0gr5tmfeY5Sd9gX}E$VmCcLHTzP}N5Vy9<^aefy zNrmo$DV<|7o~mM%R|Ssa;W&@2)v9|EO{E>e`}t`)aerGai1dlYpjdqyxYdML(9keh zftbYck^ro@S_EB!v?rOoOL=MLMnjD;KOh0&9}bMkU#81NzX4R>#c{3@U%oFi9bE>C z5Y<&dwA3N9*v}&x3*y$KJq@@r~Pn7wuhhW;S zfDAmP15H@JnPh3`Cv> z&VUJ*77jZ7LfAMsx|8s9I`AWex!zG7bjaVsLvAuObg)48#mu4Z196g_e${*u4K%}; zDgHb$D4oj(6E0hTq4uu9mM##mI%3J1piLijW6YXby&>_aNj()38d_-gYfvIQZXbX1 zCVJ6kxf9cQpSC-olLSDnBHj^ei-JWkK$m88Ud6fOc1UzF4WitVbB3_(Ohe6xAd9zW zaPyp^(QXHyr+Dq1a7FCfGe6y?X;1S-oRM9x8wmn z@r%LwnI*WNtjKVkhA_#T=7OL-Ec_P__ylv#(ujt;54@UE2l+ph<)(?yCOw4#y7?xX zP;6Fj3$!%WR3E50Rirv?UyH6+LMD0u)4Rgz9&~!Igu6c;868$g5R(*70Xn>d0VM|% zw+I|n(AdBZBHZ+xM2g}g?52z0sc1!#P*_$!Njey>Bot3_ZaV(8Fr5$)Jyt;<%r*M= z;NQz3{SH~c2bcnU1tl>OmHlAaV%#Yx;`}P)o&Zu^DLJUocS41=T&W!lKKC_`XEXUk#TM21E|#0OTuSdol#C_16Bw;%jKFVS;w2?fM=N+g<@KPuzCNJ}B%Jav= z3*BnoHZ*z;`}J{;0{df(X3g#6EPU6$>-v;gqs0&!m)$n-QUIFI{Y6zKhl`-~k0X3< z5E!G+wmXfcdU$Dduz!0@H`T8+zncS@!K@;`e1)7S0hH5a&@5gU*-RD^PYZlwcrHOCWeeaTO8_Ok1jQ2DpeR&6EA2A;I9r#C5q3t`L$a@P>kuhWnQo->FF~ZoYGo8su`qg8{8KjBAG&>Fa zmcrIM*th2w_4?l8%t?t!vd8dTqX};K`0(H0?S8(n;jcAg*z>&*5PDwo4@=1>Lm53M z!@3_;&$OLCTSwf8BqxK_B$8D9sYAhGwXR1fpd{Oxs?bK#UZCr{p$E!(J0bVKBH!gGN-oXToW>ejslMw>MWv^J9biIifcrz6n9u|K%u$awddc^hR1p2bM zym=f>AFJRHszW_pPBxQ}HpHXsp4v<#{AS-9euH(sELbnqA-PkrLrd1rFYSWZXf&hK z18;E&KND{!Cr~H*NE(;WX~1blZu8&~0nLQk3@9l00L_gsexERk_eTV^uV41x=7zN# zNVYsr>p!{)QleW3xD6gsy-!z~=GVSKKCNDkOBXq8^vM?}%KfBr>dDr0-+y9v9P)Wc zcg-qGOSWBSDK-0O3Hj-5^)DbT#|QE51V#?*YzREdhdFS;G#E`jI{e*8-%_O^W;@6; z$J5B2Z92Do8MY)?J>AM5+h+nd!sZ6si>BnbMV3~iCd68EUE6Q2?-TUv`y}Spc2n-J z>0jFFHELKpVHbYgbDB}@)cVnn$E?!_*~H};S~-EvIKHpX+8nl9hi-#M04jjR99uL+ z!DyL<%RP=yy-T0g>+2YOxn?7qZw?RSWN%Qoq*Px<(4@GW6ARzdF6p>-8#XcHi^IhV zB>Eq&?c4J+MJ;k%4itzGY{f_su z^sb#`YOYlrdd=~qD8130)w9JuxDg7Wr|$7MJgxvLqC$Ag6Z;Asaz5u}n&=l-poW|Q zQL`owg)!BQ%Ak;xTRaPe`9hCy9AB`(`AgC+YbKBDuqbm6uh)Ze+Z|CZgi^5O%;+VE zv&T%Ks`Q%24Z{BKYCN@_E@;BR^snSQp;1r{aE-Y{iKel%6y#!E?{Jv48zC>^YeC7y z?lxK-IX7v>gE!EKr-GLCXejkpFlG0XxS1f)A6DoG{pg>uKocCllvn#G35L1DDHB(G zwAF}tc<@^{ypXIY1|K^3ah=_|e-Y`Kgo86OM2=Ga21@~}7FKK8I8ypAQ zUtUr0Z;onTh*>8h+P~qCXYfK_bg`o%t$R_x-P;d*7PkltI6x4Ewu0quvK?SrzF+5d zILY}Klx5!t%(NT{Tdgyk2b+_S9RNq-4`wjhk`KgRgT%+8b8_gCqAoG33wF*LV_73L zB)Tuq0l@D6ve^&1@H`5cvy2Hi<(&SZ>$T>)p0Xe9tzoPiTKEbUEEZf)9$xCv|BZzM z`^vGt6TV`i5bB}}<-xJCQ+JiUrkCJ>FZTYd;+HWh?RPCP@M!l#!C(g%zNye*3nOmQ1>j&QKK7F6RUA4p8MC)~%pD(yEKS4yy3okZH ze(eZ-X8wxH@y^yzF#$#rR!oJY?!bXzMk_y-qW@XeVk46197&qtz$mMP)nYW3d!5;U z6kvW>L`wSe{Vbd)%ohh zcEZqkSP=^nsd%hNYa?`lN8e9?55D|@vW|lPFOR3Wwb0M70jLns5d2kzSQ2it^P)(f zMcaSjpk0A{RG4(rmEF29gOlTurM4ZS$&OCug|L;Yw`fa_Bq3*pW+pZ`ZT+}+fSer9Z~z7dJ->gPK0UL< zv{KFZqDSfb;Sxr`fJZvFEvn~tJ2~Ra%GEi&UoU^SHJV(Zq4XKB3()C?V&Bj^T+hsa zl3*5ytS-M*I=QV_CXd~qobXJHsbzneP0y7}hUK!n8QF?LrZd};PxPu4d4-vkC*+0% zr%Ti{DZ}5sTil-bvHhmn^!>QQ((&+v{CNI^aKP_WVt!W`&q!U^BUkfvDesncaEh)i z2eU}xg~;;^Cc}`FbaXu{E_iK^GQxv>KYs9=2Wmg#rjjXT4fprfnl@^;Rgdbol$cj|TiVO{ql9TvnHldo;6{xAjrp!FxNDym(IS*!# zZjdNdO{?mBi5ukv2VdQ@&%kGvl=(sxU8CQQT>I+{tHLCJU9X9j-R42evx+$knlrn7&U}+>fT`PHR3JZ{@0_BiUG6%)`oo z2^U=vw0La!pACPJQG>>6+fZeXO_Kz6x5B=b7ODe54<)bn5;Dt2kY_uvGutIcr}#wz zQNzm*B`Re;C@w+@g`qDGgOKuFTHiU!J}mdg)4u+StB`Kl40t&=${IjV$~31#rAg-| zsO`E#nFPLm%5i)+n7AE%VJ872 zH8KJu3WU!2d>F7p%pwt4KmCyakGMlVC0v&-o9K8tLEi(}S0i#UNjo7xz-dZ*mPX9p zuRp%@<+oDRt7O6)BJk$=rcQLo7+e+>X&)=aIT0#<(uhggWF*^Jm_1PR zPD``D3lZJ`zWv8G`Wb~JT)87sT)61Te$3@wFvi?CIBVkUlX*jCT=}6ztT1*&F)O?2 zfF#TOHU4s^Cko&5DfgGISFSm!hzH$%q1eE2E<<6o%rrS4dlp%2_ylx}STcAl5^_%P zn8aR2;{}@U0%>vt z?MBkdgvN*xJ<)p}lp;_HFj-=~$=aH^_A))jg z!MR*)WY2Q(9P5eQcY6*Z5GTeh_&8jyRamT+b8&;6DXTo=bvwvMcEoax$!t=BZ0Sm< z3~Tw!4Gi*Xl8LGEXVgt!<@pO$TYcv|bApgFWlEL292ZID4g2=1SwO!PKT*g&TP=ze zaamf-bV9;a*wTEXGT^^PNz08r^+Z4>#EfOLt(Y%jn|Z7a@AvjTiW~Zf1|*i;MqM+! zQ0F?5M*wVxQN$%7=HP7OzrBvsrX4l$Zn7A*qBjfsoU0LHwp2NOjx6sVQ`%pOU4D z1_|xzB)BPBY{uGe>$WB+bkfgFD!z4OuHT;&#}|HRH5XAw?>fo*fuNQQjN;?PQ)m@c zR}M+LksrJrq8HzZx**?*^*R@S(>m%GvIu(wUpbs@h9;9kI{$T{x5OF7?|vZ**Hzna zKBU;>_RSTe!%?%tRn4PcdN>U-JUWlysZcjRZnB*HPjNMUgD^bl{fS=nuQ5H{#A$^WmS>#o6$GZN zgz_MXznvLvE!P%FM0HGHijJx? zAOrf@TT2<;k_!$9%$^sD^!=KS{=}6IBFa2JiF-eu0gBB_FSApEbor?@cPO35uB}bp zSolA4gP3fj-+R{6Tfg(ZyUgCrv6wn#n{Umh=?fcKk^lTHF2~Yb>iuWKtF?A$S2Kzs zx?iAaCb9EYvpLF=@gSl&@Rc{6%b4%?thKXX$bp9prZ>%?cwTo(WnJ7&oueG>$YhdQ zY{PG1_mhc)^5t@jXO{~~ggwqo09syeFAt4QQ!=UKhkPI|Do&7RHfeKX1M(&{Kyx{= zcu+H)+n)HC?IhTYiqZY)O+G>WSO^*RdGM@*(L_L3IVZ=&5s6iwmJ6`8@-4I|-4uAa z7Ug~x^KkX(!#?X*`{mOm8mMfeC%a7`m^TKF5xd;JsXExw`7$U$qgPLW#B&dkxGj0^ z^HAN<1mpGawYX(nY9`{S&16ZTvmcV!UIG97=L@2UbAa?Tr-0MLD10qZnXI@D&v%B% zw)m^(Z}LX-$JK*hw60Y;KYT=w)Bfq>a8M%JOhfwUom4mxN$Lp<;%11;1ju`H@x2?M zgXwx)#>2=M#`~vE92XJU)2k;3DP)=*Ab?OCcN=vYJ>7SOomU~e}hdb;OMHH2x z2ex_UNT4y9Q{|{pDugIemW_Q0a#zR_DkL>Zj@eJx29gEFV}Jy$kiZ&r&tJcJu^C-$ zlZ8(W4?-7-^~zo!7V8g=FW)Ev^=RQ3Uks6Ai!qBavjLoJEzEpunDZ7bq_or}uAi1i z!6u6hf0uU6IOzGq#$^1jVLWsj@QMx%vcxDTpNZ$hj6{ls*(z!}(L!9;Q+fPf-D))@ zNi<~mvDhOiWeA4Hgt;>#S+i0?%)k1;aL#3ZzgWuX!NDc*FSUQ0#1C)fd&GBJzoT!m zU5EP&!8QX4aJPEE*6C@A{OTvjWJK|eVFNwv*BtUQB<%_Iw-hJ|!Y8&YF`E|GamfvG z;3>|i{5Kjag(b0HeIO+%54c!nqt>}w0UR6d5QN>3@= zpWn|#8XFiALqKSP5ZAp7XCn7*(L50YvR?>1b3*8D!y0Rl1T8IL zyB=*=4m61)MUw^qQP@I>rdL8rJEeC9UJsrGv^lgR`kOil(b)Y0yjuzVXjORdJd zfkjM?oUCheLk+Gix-|!B@iA%oJ_H2Z4?=cgxKm@%X*`Hl3xfu4EpOP4v+Ly8QIDP@ z&DY+mBao#D7ttn9rj(_n9LyaUPPM;a>RHg_3L>r=Fe+y#X`xnKPX(t!5#m$OVUdEe zs@Cfu83#HfQUl|Kov0*X)Qr60u-u`DQ&*_^2RD0frh1B|fI+$dO~L*(CSI21YU|=! z8|WJ=5Sc^-ADd|yq?5}2w`+y3p>=Qu)s9D_}tL9S0Vg+DNg zl{IgtXH1DjCQ7Q5WFjf3hss(@n@16J^dQ*S>VN_P$TX-Y=0MFCkg{b@?1VFFP zA}D?Y+CZXA_Yy1X-mnR#+iUz!0re9(0v3;TyRyAk-T#GI9{B2EB<0tP+hf zE7wo!A;|_bqc;gU7meSVh*eaEj@h^25G+>pL`(8--5unW#`9tyeBlGX({AYlZ^pyD zY0w}12tS%7-#Nzn2wB02sFEWgediq6wB$n^bL-QPmWQ1IVD zR@g?!y1pFuZh|Xi?mdyciz*>80$^k1CO@-K`46M8DQ}dtz^&lmkR|;JB%YB++=oR` z7_THE;Kd2wGyxt=^4AfMc{obMSUv+>juYm73_q zP&aiPlM0$ViW5J7XYYD@$71QcV$wtNkD|j5{7eJM0p&(@;H&>8UozJKWjK8K4Oy`P zqqg(ybDtFA*pWW7DDId?Gjpo*KW$nU&yN~rwQPksqA}E|yTu|6sX|3ZJey6LTIRC4 zzF)5czAMhpGHlO?BlmQ37J??N>gtUcFzhX*`##ZKIwX z@G|T;X=e5tB%9u(INq4LWGHMd8wN=#s_b!nD{=PPqNA+?K(a8s=vguZ36ZnRKF@`< z)emOtg#psISM5X2lRZZq=PXsd*Dx(sF{##p2N z(gm@*C74<9<7s3aTBsQos=EfCYne#$2zYa%H1EvsgHYN##)R$rpY=*zwTce8yF~+! zXILFy&K3>kD(SaM%yEkM3ObN+Oqw-w%TU|Z7!nmXp}JmA!Gjlv{maT>Hl`20FSVOr zy5q3Mzo1|w^?QZGV|(8_HA6503LCK317}!_^7Sr#CrTZc! zm5qxvV%l1Wxzo}CkEL{6=2hFSkGBTJt?si$!zIG^E?Wdx{$WHnl4voWPSVt@!)a`fLf z4Vj}^qRdw_&Zt*3)M)o)m4#a7hbz`g%;)uz+!r}d^h0M8#ooqR1)QOsiGz@k6jB{x z73o%DT5Pl+tTfxksa0!}YW3N!#3gx>-#>X`pZ^JH}*+tb2c*W}35S1+(i$*Gg ziX&L+0FUE)1}U_f8;tqljv^Ik6v%)L8V`}RZ%a%}Jc#sz;zf&>4AOeRj%g1rG^{Uc z42ASHl7WgtxyqcExJU2{42lG^s&6BWBr!4Q?cUZ25={y60sLKRw2``2zcnSk^fgz` zuxWokw;k})YvcqkDS#y0^$J!Ue^8!oF-Pp!W%1|$;DEdQZEatq(gvOs4+lGOflks5 zSjSqWDHvq>Fy2;1Avo-JHW{gX9DfUp(i+L&aD~282FBR z(n8qpl9nTXPSUTNcu2GNes_RY-DGh#a4{$5YP_g#xl{8Dlkge4hrCTGNCwJYzCx|tmL$*+4a!p zRgrS!yJ<#=*1(at#u{#?odT7-!<`!-q~nRsrt9B zuZt4c;3bRuLeCm%gkf;AWo(S6lYJ9QMEvVCZSxF>r74aAZq#NGYB4{|xr-=367*^^ zUlUYW^9#(2jlL)BtKUR+SAA-W2LP_xWdN=VYBS6-%5eapbrX)yYtZM3Sj}d+=N0r+ z9@~Z`;XZt%s8$XsBK=Cx6mPGq35K=N`rqyzCWzS7#P{2CNQV-)ou8~PkfQlQz^kENFqaY1dR6k-@UJA$~2lBKJ$CclVRuW{@{uA zh?NIM70U=+c~MpzCzru?3pE-@@*`3rs} znuzSulaM1SCTyj1oQuQx2#JamLvDO!L&|bJ%{LA9oec4gY$krShhj=8hUaE~mynCi9{cB%G(8v#X!AQSQQXlSuETNFIX z%^Bp6{!Mf8-C%hQa>=zW-By})nP84ES>c#fOp9hn7$N$0l3r!heUuUFBCKv$M8|o3 zUIS=B&iA%2?(Bmcvb7z!{spA*+TkwhsiyW#bdzI%G<>B|&Nj>FR9)ZjJ`p21V9egM zM}|vJMZ@4Y*|s1B@(G$D4<4*J<^#RXassvUsYl~Wk^T1N&CBLnt6neEGs06+XIe$@ zLjdw@lq4!CHqF7=;zoXG*t=xdJ(I;aMf5W#^3gonbcvU`Nx!(kuK3rqut|1E)&41# zF?@_dbb$s_7QJ`4iPqa7zs_;Lz2}shtEVM>hqJrIOgDYlZPadIi*#hH*cMfx5bND{ z^p&Gsj$v%<9z7Q4#H70w$vP|=jd8EYK)x6CIzRRz^BNJZH)Vv0)Gn;k9wh#^$k?(w zAsdbj@gc`u5GhzQ{G88JktB3!To!kR>y}#M?g&X5Zji8h+acwTCnT^WCx;859b@SX zIq52Gqs>~YzR^yqPuuqKH;L0~Q6lF*w_2WZRwG`&yjAvSSa-UfG^D*pus*XSBTs+h7&R_HlEd`x%LNt@#3~ z^>7&W^H$q>xdt59q-6We?GNT2(+_5Ux%b>^4LM~Izn!#84^<7BUh&gv6GNXihxLDD zZOEZQRF!N4>zGOC!}--_Q*9Nq_5}zN2^<};z;;VB3=iaV>vy^}xa_|g5q(B2$3zZ% zc^;$L(ejh9VH$}Ak9e&I-nC>g?q_|y+;P)au4>q6((@Vja`Vh%1QDDkVN99?CBLnb6UfflTOB)J}T{|aYzbDKs+MmKYNQSH5>d!v7~?`rU)5QoBKTLW}CkH z8Bso8u%qJPy&XEmEKp?Prk<^~-1rEeSbmlgpr<}}UdWi^YQu3S{W4oRRZ#nzB-R%P zD#wj5lg-XxxI8`*$P-~RU5TB1-Kk{V7`JN( z`}6)h40CQ&VW8jb>#qRKsKtC!2N#~FM>KM-tSkU4XBi{Z?P;$+HWJ?Tw;5D;96N8e zq8d|%ONowD1X&lMY zaJJWms7Jjr{M(ad#M-V$o>ck^sY1Qz3j71RxjIu*^7IDN1i_|i%tRqZ(@K1GSXUyx zFmF+gIA=XJi=eSk#9nFAjndEpOr|~#B+iA6IGh;UleAfxsi=!VS-}HBUPX!lPtXfZ z;ZVbynzYN?Akuj0ula!IJA6O_A_79+1p7vKp5J>$k0H5puB1bT!1#f5fN=O(#uq1L z2Mhk(Pro@7q-D@~&Z>eF9dS=i5LTX7NRQ#Wsg|ma3~D!M0;XD2O~8qLC+tVp0blfc z6Qb z1k;NCYYDJq+-#w5I8vCt4JLz1v9oXBRE^HTXpZ{==j@^Psj=RIO}|3GuXEp4t5DWC zwC@0j1AOflz^M$j&rmpx=ya>eZ_m5hxQz*@=;5z@qWuiX^cH*&M&b|s+3r#fn=N;; zD*=UjG%giFxXSppm{)f|bYa}}vq!1{nIyEQD)L4qP);JANRTo?Q4;$8A_9pIKP2|n zlHg&U+Z*;;ip3T!2uq@W4X~=$wIg3>h{(~%%=fa*O?Ml-y^}y%Q8&TP+&A#TUnIvC zq?B!azTLIsR3@wS>sn+J2nfrpq==wOpplqg4u>Od5R$H$9wH&CI{0$N z{ME7ZRDpCH-S7@r-;a{H@S7)VTEfd5JACn;0;vdfR4CGzk=S%7q!_33sEhK2`&hualp3QAB?t0kCt`s=1*&qyDLzpsJc-M z;KxN3lH2Csf5z()NgO(&O=cgAqCt|h2v;TdLrh8zF`MHEXEd%!;@vnJR+{vO=3=L% z{T9VzaUK;3X6!ZH@#H+M#4iaIfXexHzuIcQ1@9*)qw_l_q$5g&q>S}q@mDpxn^;Cr*MWv7 zZ^lP1t<4e5nN$uLkzrpSFQc(%*7OdGc6%F>+l=yx)A>q@K0D?r|5k__91~M2R%9kU z&X}xx)v?l2+iWxRLyEoukC_%1WoCI9pb6%PU+zYo9jjXPa=2ZY3k7Js%mH9}tnCU7 z89&FLTr&B@QHvesp2@IVL&BVtfw2=?h@ z5yv!%tQFD@8cx%1N>u>AVkOPWM5h!q^&hC5id1r@RO%(vk||YyR2t2i@>4)+dl1Bf z52PeWKglTsC~~*K{kd=L4fn%c);Sad_P9go%=#-~#L;5wMaP)ipR9Hu-$U-py5Eym z%k%7mb>nwiw}K5z@JI)b4=ldga$nTe`3*&S{+r^M!F4zjr?}(0lbpnr3MJRBo6<90 zmMZIW3@pFxMmA1YB|HayJ3RaM;sr3mdSPnzg+}eG7tN^xzk~*(fjz^xCh@PUHln=t zraqQvn;o?CwPUq9rg^{*y|b;m)L+jF9Zn09=T8?fDKkY!V|g#5X||TNz8^=0ZhBtJ zh=gAg5cm&(X5ZZ}m{2RGmJ+TsdY_q}-wyOiOwYRii_ajOd+C>IFhrI`ecO+UDcfw}X*J>nbD*ZM=S@sC5PUJLia zo&tIMS%Y5a$Atb=X@7}8BCMx&k~i3RlLV+S-0_!u4NZ7l1q{|p1bYD6n)7~89K-I6 zz(^Tt$Sd`1km}>+BCm}dl>X7y-c$2)tlrbBXABfe`W0x(!QCc7ea#K1Q+VuQ!Y`KY zJDaUAb9L>TXYxNcFPo6ft@8pmgh&jxTC0zgerq^b?0KU4Gr602|edez@75$_rt`kZ@rfJ zZ{C)5+;cA+{G$A>64qRdu+PgH1s|X5P53JZe|HYpStCj3;>)Q_MfkNV?Cnwgsa5z9 zY?htSszId*n;-4xsNe<>_~TnD*yStLtU}!ROI2>bZpD3`>YoC-gW08XjofK)Xrz@I zw~dC6tG*B0k5m1Rto~I^b^SL>fL=R}dr6jQ*emKD;TMt3IoSGC_m5WK!<6lgd6&(G z0_A%s>Dt#(YQCc!tOePK3~^CaD%g)(tTqVA@ZBT*A1vtKbJ^cTgK|f3;vhaa`R{oz z0*}_tRlT>U5)gh+SDkY;PXMcCummfbPxxfyLW-++Jk*t$52UWk<9yvS0^%weu);0X zg@j+-ws9nX+IJ%S94TuIH78o@I2u?{@SM`V@Ax?8UvUFIyL)iuK zi-Xwx?to{$DnGytr_%Ixj<$66AW@WPNhOiU)WSUVB$_90GZ}a!R<~Wkw(N&fQjhik zVSeX(mht#2pLR1c^1Q{#Uj*;;BLz1Kc8O!PvSB#6dX+QEzhYoX=_hy*a`*QDrsQ`N z9&maKI&q0iE&IAUhk@#2%Q8eiuIdPpCM2i!Ke7i}fO`rZ)6 zrQwBL2n=vsN}k6qq0=NoI!#;h1gc0^>%e6h&e46ekl;F+M&EGHJh#zeRjrZ+xHdY) zYn{-$vuZl}u`P{h?=vBDMCKUVTv}J?5>8gF9MxQ#RwiBxw3`{Dk9=W@YUv{N;<2wU zy%4+TBE{n|FC}6tIpa3XGOu9ocTI2TJ1&?wev?jefOy{sowMCa8;d5j{eZvq8-{~%%z|Yqi%+Y!yMCRx$Bo&y zuS@1Q)xtC*Yze0c7-@+-w)LHbT5Fc3IpQ5H+G15>^8B_lUaKZZ^}b^Nuf6XKYpUD2 z1w@)6f}#QnC<4-v5(G&QP=ty@2WN;MUxrF<#I8&)@}b^D ztlT#<$G26&|2DzHN|C2<3Cm$zj%R-SX`(w=wB=y|&3BP&+ z4u|Swvz8}x2AY(@Q>Z-IKm%gZ%89%k9$BUo?P(lyrP&cGb+{tvMCI_>VILea*6p-1 zpNN!AD@G-rti~UyOtTX+6A+R;`x4<5E)ZqA047x(qfMxIoC)*A(HrmI{&aFL!}IHU z#-#S97lz?!$&>ZECJQE$v+7{Ge;a1nt*IcCJ=E~@bZ1A@& zv~MQ8K9a=zLbCZcAmuN!Ev^j86oG+Nn7~Nj?-ufNjP5BLp@?6b|m+Q{%*EDWU4I7 zQQ^{H(t*GiKyvItD+1L_qK!bBP}&Ki;ovrO0YrC(zrY(GxdT4iT6-%a1!=1o9$DlO zgTL3e8<%;?8j-hQU%B19yc(TOSrg^B(98hcA7vM29?EVH_7OQs>J}EZky~z%5t0iJ zOF?FdOj@!~+|h`*L;oAuCc|2o4kU%j;aiOQ2|N$288pdorNhQ$TVqcl3)#z%73CnJ z%cgX7Yb1&8%UWFZ8#U+>$xCm7gb?y~duuZDpf>1%c?-x!LIt1#43A-S?>rFBDs^`* zR_2WBXw2vG>E!@zlu#UqW)OSny>1k*Cq?(k^^))ecQ=)OBDe-&({ZhKW2_%(>2L1bnG)6IrqWMAM;K@^tV&I>S)z9Kp(8S z!Hw={osEOG@@z?P;X5}o7=y96C^9d*ym5A-U)& zz(X@)l4Quxw6va*ZWus%yj(o=brT54C-gYg0%PtimOgfwIVjSDic&t8t+eSUa15>Rfd$eY93m{bmU4By~}vDa+9)=eQ&i~^0F*PY%|nu&jV8pDD1hbQ%hQmGLptcD$5yMg%~i2 z7nyE-p)70|%82eDsYQ4;p-Zxw7Q*fgH8>`V2N(iMm9aW!xoe)W-VP|JfG5{MGOQDY zxp;Db14#~(v|Kz~e4nw4v^bwSY3p*vp!K@lN_sU>Acb6CR&L5SMjDUHE(0Otn(GD5|mo zdg+0C*J>!U!|uUF zN%FZ@7epBp&&Dr7$hMf37Oh=?4>D*np-iazOz*qgds85*R{zOevArJ5{UPBt@V?yQ zq8&WI9uvVR{`AzP4@GB`ZHz5Wb4PhZ+Z!^Xr-~)P`7ikK;+@QyhfeyH@O*Rq*a@cfBhcHR9hiO;s6W{U2JP#WXE8*|ma&Q2B-YdTt65x`P|e=?<&D%IBcInWU2!STKPYV-N-rMfxQNQvoGJz2Ll;vfm-Bkop8)TkTVFq;<$jLoke-WJj%$eCR)x4^Tda+B7ht&hx@lV_N%vN z0-3~R>-V?T9F-4;UMD6{S^+*>bHMvj{%+2rytyKSVSh7t>BIgriAB%I6a%+3KwJov zDCqUGe@>YsqYx^TtT`9fYjQuuMzBvC8fiNJ{=!*sS%@XR}1?r zVX|bRRaXB|$$hyWH5^1KK2BPTE&pZ!nLT!B%vTr1=@d})<9&%KgtHc1GsL3TUP(jcPnzvLbyAB=Tw0CcMSCC~uv#C=aN2V-uIOt! zukv2DpXsiBPsH}1F&NL#k9v^hw{^^#R|OJ_unAb%l>^Clqr3-`92SjQ;}=s3egV>+ zWU&no59T2=XLkT{(0L~9eiS{ey6J$Qy$y8VlNuToCpkFK`Y?u#qhcF3I%Uuk3Lm!g z%?!}1>EFS!RCL)pdzi^@xn8^Whg=)45+6O)I{vm%tkE>dbxQQ)h>+ZPdP({?G#;df z<-0-zK|f03zU3V-1`80OY;%wf?1M(V?^lJ0{&Wxin$aaAvH%wi z+1+Jsy!E|3n((jjKi4&|CkndV$*Q|V3Fg_vc#(a#{bdv7X!*=;R(q};@#vHL&|Ye! zeK@C~32xwRLwfwdFISOcB_7s$|GvbR%jAC3bE6)JUUi-jj^&KP2lwZZK;>@S5=`LI z5AKfpnfJtY_`swpVfr>gCda@Io^Gpwe8r>uZ3kPlaDzIQwK0rI!n&!c z2_PgS)Sy?Lze@Jj!PjDLlUwsa5Wlfl+*bq5QeG~-`*87_SZAT=_1}3=QZEweKi$gn zz#*>@Qa;9+B)uw?&WPF_sBGG8+tJZ2zDv9XyF85%tkAQ4nE!a^H2JHh!Z$y4?ju z8Q9Z_fSbN^-I=}Ok;i9R86uW-Z5``=AaGl|E7ueR+i>j4V(QAxpexL6BJW&{D_F(a z8E5gNg+>2ZDeiiWLBuHnaiyXToz}|-cSkZ(n(;j*4j!PL)S+@QoTy6uvXgC0sToRC zUw8lv-?nnp)}APaj0)g=95|v>`|JpZtXk?W>lQfp?Fxl^g&@kJwdQ;j6$x%H)7-+rbk*VJxRsHQFplHyfDJsosTSE zPr!RL`8FJ0BkVg}D0R7nQ!0hvUT#1b8}vb8WZ;+do!76rDiHfRS1Htr`{z&zvEp1j zjx8d$=_(}u9dh@#L~YJ?u9~lCb?9TPeskbi{W0IB%jn@X*9!vJ$ttoRtgwif5MFua z#lzgG!p8{#PKmf49Q%4EGsj>Gn%t=~w{gKdoU-4Qx?4~$%~T;5_@z3j7;4vIcv1I>35tpM-IHo(3QCvlqto`OzG-K zF-hUTeUB*ZVY$?*D^WXMW}PBx>95OTP4q>Lxqkd`9>4_k*BB zPke|QD-pyN?+9636#9YmmGn)kP`_Iz^=}t(3wRIo$7s)L(2U)G#7x9pai{nL`%CsJ z&iZTIe)*|xj}874xxDGfGfle%=WnR6r$(${l2|z6;~cuF%nM!AKxa;*(5CSJcZ|<; z!!5r?WS`#WK3>=R#M4y^Y+nffXs*9&r12B6UL#o8bZmapoAlcBU!n0Yu=QrU{>)35 zP(bTn|NJ@cJrNoI+ND2W{jbX@Kky;|(^%&JMfQJA@gkUI{9kr^9}Ed%x7&c2G?KEpASp8z)~C z?DA7@bvLzLK6y#~0aRCC%~(Eo^-069cWfs-L zt*ydmU+IHIzufxEaWvFVAnerkM?66A|v1op*t18+fxp3uT#TcJ5gA!hwn(l}t~v?}`cC6O7MY zIMz=4rxJYez>5?T(WuUSUKYl(GQ%Ry9(H)ErZX79tfo$mbwKQfi%sQLtt%37^?|iH z1&uihbm+M9J=>yxAfqOx>EnfBbpC)!qZG{<9zIlVKj*w&5@G{O{~hhEtNy~8bX0YZ zr}A7^uMcWjknTaM7lM(^9VnCfTb*u8Q8XIHIuB4s53&X(24cQ*{LxCUO~}qPWB^On zxi2gkh!IsHihyB>d$It|Islu1jCXvS^oI)g5cP+6zRJU4Yo? z3j)F+quun1bL_q4@=fD|?rPS{RkJ`Tk7H^!rPwVM%9V{}1xNAc>6ZHcb``dowI-f)v6IZTGl7S|V2Gc(q*?L~jw&3}f z>7{P`QTM{+#HE>`pOnA1^Z?VRqA>{FFlfGRS>B^EyG&r^c6Es0q(wnw*OH+;mX2%r zH>)1a;rP!eg}!GF;nzbxF*<;<{<=L8e@{L&Syy+O1@_;z@B1 z)L7^Td93=HlBiCo`vUEUzbVcuaOSWV0D{5u^=#=t^sR&S8wOPTWG!+e7`hr+2-~^A zwW6PjYOi<2(bvw+2c z&z6kjHinf9r*%8dt?9{yV+e>4QeLn|y`MD5^Lpvol^dUGzS z`EtjyJ7bYWN!u^mJhpo~c5nCGE6Mv!({bAsI@_*@D9(|EE9pr}G*-TPQ1EI(Bq)*3fYQp3a5m1RU953Cb)!db;_szE3)1MM1{14&loP$zW|}j=ieQg zktBy(_X<{+JZz(&F3_*XuW#?u+ivp^(9yZj(r_ybGm}n>!EBwzZp#s?D5IU#9PF#FM?s>T%M~Q0I!^~; z^y2xUzUPX@JDmC~8CqIL>}k&nC3muD!@KfiJKJ-s%T6}PV^Yw6i%>%Q;(4L9c`V$_%gK0E0X!MP%9716@Fc%~@=e2Bj_{gd20;SvRM%D3b z@wk<7&7uOA!r<}dy8E}vHIK=Y4Zg{WLr}#x4_w$_<=ZdAt~0GhW0jNjrwfcvIc0}y z9N?qH)_U(`J+=~Haa<$o9%r|Vl&zOQYu2o4XZi?39+EJ5Oal)`uhc?+lVEUL!td7b zoIN>X2ABTmD&3nzg@pPy3vW`KbA~UOoEPm}X^nfy>)}G(H$AB89+|9nHFIX|@?H*@ zGDp|4xB8X$Dt2^uD=?QtL*`&h-4-ihiNolQVw-X%;8u;HUEAm5ldlqB9ZHAM-PPKa zg}kWUlfazS9!6MGip!xF8;9d42F_cN(V{XE&gA@y5w`aQhAB^6QT$r?vN4=PzdWe&se-x&K?wPn# zxwdxpti{&jQ5HThENJZYHUM6S@_!ttqxnrr&-(GaPa;;+h z_@_3P;%C(6$+F{eMF+GA(@h*0U7BF0<$G}uIZo(fqw_MVnC`q** zwOS89+uW0p!~@0BcQ5xBaO4KSX9Cw*V~=nY$EpcN1+qUG7Eb?xMxN(q6f4#Ih2xxJ z^&-3iy~FKXo_e^pr%Z0vj~6_nb1G`nGoOimz@jqZmrixkS7WyOlR;;!BZ0=*3SuyU zD8o2(3B&gimBaM7?^k%dnuy)G4OtJIr3m?T!>37>Mvn1~hgF$nBEZN*BVl5HTw?T% zgv(|L%5Edm%&mqyha-r$z@4{Lz1u=cMgpQQq1er) zp=~X4cp33^4_%33LT_r+i^L7N$w#BAuzup^di>cAmNqLOUK{jt-AnW7g_*6NidRAA zfdM(uu__zATxjsLHHpGS7sNYUi8+IV;pp8#>m82sYW!I(^@i~=-P1?KgEpsusbJhp zmg_npm$}4)7u6eb{>S4q>ga7}cdPd7;0Uw1#rDh78H0wL^@YO59(^n}I>t^DGDv{Z zuN7h8$JDYBpgk~&TTRnIlN=W7T$oUnnZ#*1(<8@$y0cxzqM_ZM2))HnKBXzdo=Jb^lT$JnI5bqz1z zj0%5N66{>)A`K$i@O#CxbvHO3CPu1&ewZ{*~&j+}I`&LMK$T^i&a=v_Xy0cK{El)uQr{jA{?<#`|Jq&8w)4rq!kE&v!)$0&ovX{! zQ9dNKuf$MS*J1t_Rn()95aXx97o%@%i6F3blnB$#={Yx*PmFA_dk{n))7lacz9%6X zdE->HT!Oc&gX?dZadNgvfwO3dxwvlRvzb7v?dQVv;127q;$MSdImfc@T~sVO7jtX$ zKWC07Tz2Q3qy;jfeif#5WnlpmX+7EPEh(`GVhZv*QoGSooGrYWSGWH9a$bgj5FlbS zciv;}Jt5BIQ_q`DWMId>Q#f?8sbm~j*N;s-;aW)VI5zG!5wopK-@p4e-7JWRr{e?# zc+W%FOHNF~*V8-Yg?MEf1eFl58b`sfWD&o}VF=@4R$GkP?TpqfwKi+r2DZi5y~Y*f zADNg$(R906w=d=u7KuT9bQ zvA_}+Dy|fZq1L_^Khg2}Fayjg!(e&e;T9FY6{LWKucn_E@ z+ufMP{GVd-Cz}_6KID5e)w$UDNmdoBfP_Dny(=GrflhAtLB~F{25bEMKoL=`5LLY- zEGslDc9d|XfIZ$kK4zPa7>KMtmI z+~}arC02#2R&{8$%P$LnSYRNUT#T60G-YrHjcN5~rcHc*IX_5cA_wT-{>bL?6oqJy}A}kVS6{guM1A^k*SD&z=Ql zqI=@g4ZelX|46TM#5~Id0`D+pFM?$lm>UkJ`V@-I}L7fHSL`5(+5 B2Ydhk diff --git a/doc/images/nile_shielded_usage8.png b/doc/images/nile_shielded_usage8.png deleted file mode 100644 index 4ffd991cf802930ea0dc04f9eaf6b0863029f329..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42535 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 TRF1VX00000NkvXXu0mjf)=pRm diff --git a/doc/images/nile_shielded_usage9.png b/doc/images/nile_shielded_usage9.png deleted file mode 100644 index 918c4cd1888505adbe41e0894be67fc7a7f0497e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30744 zcmV(+K;6HIP)Px#32;bRa{vGr5&!@f5&>tQ(oz5bKmbWZK~#7F?0p4bR>#uz#1ldY7Lt$zNN|D& zcXusCixerP6fZ3;#a)XPC&7xl6)5gj9D~1QGGl-wslNQaC~A{Xq}7 zj5Ih?N_u}WV(+i{-y8euB#Bd(w(*+0(5(3PV)?$>Al4M*O*z6iysdF4D4BG|l9vCo{MTjv8 zf>WyoWxs?dpnn-FF-GiL3M_HLH=X|WfG)L+Q4;ygpn3gPyos7b%Jct9fE+AV;2h{S zNf#E&`~P1dwSB2UyP{77{p-TkEJ#~UIP&iM`?&OO9)VYL`R?@MXZA1EzcgwsF zt^#8uyshmixi!)$uYdMI4f8}F@Q*PmYsEIu7a6`v_=_w1v&jBU%KwsUsj^Q=6Fo_J z`D291q=Pm5I5iGX_EzAC3{l^og#E`b`fn~J4AD$Ud({{l%!2N#R;LW4DInazTPrb` zDoXN7+s2C-r-A$juc`W#iiG&DnEX+CA-F2!HQxXA^rTYpXP*qo8ertjj4>vhB*kQn zJe$e#{1*i^2lxwEsv=z8TOlGvaAr`?B4l(V=E|9w_P4nUmkmW!^rUuhy3q+Cl1FHi z|3bgej5t{}lO!{wVT~E}@4hP=0i-mZ8aX|y15DJbiGNyT|9k2Ack}OGUZ2PyrQM`8 zkcK=&r<86Pn4}fc`;b#I6*%%so7s?QT3Av(Z5Da|z2Tp%N2-w(2UVSZLBFJc{CbTB z>6(^z>5*claTI_?`dc8=yqK_lmDhlwc&)$+@ITHQUHWf#Roucw?t1-I1_UbhZ%50o zA#A7~^bmBJ6Nbnvx~WN(c%fV6Olcmesw(`DU+PW2{4-b4RWr!SSDLnIM*oAmiG{{Q z@cq}yN^?WkCi`4;Q;`{d`Tbw`LmV)LsN|nkCk$wc$;hPHu2u36RHWA_p&_t+Y8_X> zm0d9Z7Ze8+e}(_@6HPaQtQdm%x|c~4FZonIBCQUVGQ~I`k0~p;r}Q;|laj{-{$F&L zf%seWn7I8-=`qp&XYNW~?<*j3m!FZrl<)hp{>eB&J@qI@LbX$Ek5cUuGHXKxphBNc zS2pz5sxdkr&1=c8!#mZQ(SPXvchGkw30#C{N;8B3%65pYH`w}zsQ>RPB=%5%YMx?R*I$e09^h&b*Mv&Yp7+^&QL{D`v1}{dM#F^_!Muy+MDIHIf z1#tcqI+g-c;42)3Tbgid(G4Rm4JQBh_|I}cn3yKKZTk&-Wbi*tw%`8?hISFI*OR>}_+n_^5%D7ey2-l+gN z{@p=ij#`a}6ng}__clG!PmAvX8vcvv`K$PsV&eqhJ7Q9=!V5`IzSA%BNU^^nSexpm z+a+8aW7MwVbtKRbl;F(6aZ<~zP(Eqg%(9QvR65BlNdFy!qE;cEDi~>4La)%If6`gv zn>k^H`pMUGT1e_aUJz29IsN`0xT<~Mn6nf-vCmhGE@KH@;g(7&F+2mIS7pORI|@R} zUr&$JlgeAc>wh?yGLXCQ#qiM7t3)Z*yeosW@9*%=gokL$J2FjHJ?^_@|DcZk)AgXk z;-pkP6bHaGZ10ee@@m#0cZ9U`NR-Uz3eObFR36wYv0_TuB|_$YRRW$<+bUjAES1-i zMFw>5=e6YibK!5H@8E%0HsONYZRKhW+pf5xIiuK$N3c%u57$4(nEz1e6g?Ds4qK?3 zg{dcU7u_^z6Wb3hO6eViJMxo!(%+f+@c7<UUigQ z({M&?b=vndNx%6hLuSZL^iLE`8nTt0>8<@{$$wSif0a!%07i-)cjLE zui_svgO^sx(i()Na+OA*x8~~as?5ICOhfX2I0!A;9vS)|s0oGQ>;Hlt@e?&Yq`njT zZF>HzWcoY#{1(nikSGXNSS(_f#l|AwqTMniq_jVIHu)i${(P_*fuP{4k)DNOV`3;K zIs(5Kic7MPqk}y<5NmpP{~qn!wTo`vx=mq`iR9(wDH>>WE~118Kw2TM-~L8d!P>xF zH7Ze5qR2O|X-QBJ1qLmqyCKi0NRc9BZ*N0;c5S0wdrwfwQpHhDoEM|K8oZ*cYLx$3 z@}pv2(BUH|$vs<6va=UaiSkI}N}&{}16PP{+vbgQ_{0S&QM@RqNe1?k4>T#A`1ACL z!fRbIc&xDa-@S+=BnUSYYaoNi4?=0`^f{E@zbHAUb2Kyx35sQ66y)Lk{1bR7DQfV| zw0y}Dx_R#z6)KPiCIjCCD4j$ur3aEfr36a19 z^+*j?(4iA24^mR1GiA)29!B5PulLuPmNzPgFbI$rEnGnNUq(=Y+`dptI4;sa@n3Ay zy*tHDqg!8CjBVk#OCKn`}Yw_nnQ*HYJC zr0;-ajls-+bC98gG;y&F$%#o6{`w8EqdnPJ3s1lY@Hh-{qARv`^0}~%6K?&jg2){x z-wC-$H?BV+AMfl+5B21lmcF7#GASB~;$k9ch#%P9EnL-H?>4?JRuiF!jjf80)& zgRj#)^l={EImq5t+J%KKUpPyL4jrePcW=V2a-l4lGb$0K+7!SQ8T=K<{s|aq>yLO7 zMnC@eBVE0AjUGM;hdqLC2v<+&Isfx%I&$=cqQ}NQ6J^en0lFh;Mt?3iyhD%3)U*?S z0>yu8QKg5dQT5HBCq)my1MxE~$w@SK_8bb2W%A4AZTK|WZ`S^>a8!POO^_uWKr+#= zMW?fnDpsmNoj>kMwX0U8TD5A?)te#o^2G~UyJigy8_VgNp)P}+^c8FgnH#)Y5)U_IEmHe%~T!pzR{;!R#Nut+0k)dr?}Wy6tSj#`wr0l{RaR@ToaigL^Qyq z)-J9n+#A3WC&!FW&7<>cJh60*dANv+j*mpE0IN;GKG zv}$UFVkwG$f)M^oBaL|Wn5NJ8o?e7Urlw0G@@PnM)e=-M6 z+PZZ!{rvLPx74Pe(GQn8|Bag1t;1=RUcQ`qjHgDcS7tUQ# z^~j(IlYr^}bF00JCF#V^f6fhhk4mXrz(z6yBTFv%3B25=H*5eccv zO}Pr53K`s`yu_PPl2_6tPB^23Y1{=bvLw;cMT_Xv-yw1Xo=~B8*JxjsZt(40UbShK39or(&ez-M)DP z)oRd)p1ypgSq@p6rG&@wCK#wlxk#c4TBr1@Q9>olGjNb+{1o-Zi=>!zNR-J{olojO zZYhU+`X^V-7fDFLMN;u!b58NfFIu}w^mR zCe0>K&+K&f_FW2(h*Wi`yehk{M@s+^N*@F#V1j2EvOItG9DU|m8u`T+G-B9v^qHb( zxS|gEojrVz7Oz|{=&B$zDC9M0g_BSqutZ1i-wmc3HR@2G0V84Cdr(8j=Ft<#M&;2Q zz~gTOokw^pPond}QysJ|v>-y-$;o~DXxX~WfQ^5K?RjwHDh(Piklw`0&KC)QqHxuC z3rvN-x=I_72g#%Jsdq$a>1e%`EGmz{(glE*#K}+Ou60SZPsw4#Ysuk7{WmSDQA`yV zXqr-;N9t3B4d8->ZVzT1k}M@uK9yCT%zrwsL@WGoSK#E1kg5mzOBN}y`;S$o7NM_FMbg2@~Dg9IhiI5 z{)P^mIfvd}<0{D}f2FY5S1Uj|QNxjZYRj&sTJzB0iwHx>N7Jcfqw)(k%%HGQ`X2W9 z0nL~(m%<~X%|zU#GJ#_}s#*mhyreb7rcgN1OcHIHh zwq*~2Drl6~L?8-P2tPCyhI~xi6c5cjI+s#%qm1Z_Dld2SC{WCQ8m5M;Q>7*YiYliH zsTeDYjg6))n>Nslv16!bmw^<6L58NsiY{I{Ppeiep`PtJ(bnxJK&24z9(ojBx_+@? z_b`~7H7Ae~5_MZswIffeF6el{BUN`K-N=Nykfv}pqNIcb$)d_g|EHdHyteyBX32p^ z#UrD=%ezELg1|L{ijx=Vn->Jq>B~2aBqJ9{PDqHS4XZaozBg1`lzve8!iwj@6A8Kk z!A;X7F&etMD};2uRO(g1UtSpNuh^cUZ+MzEvM1N5-H>0tc*W<>oaPFpigT3)9g#za zIRpA|ojRYX7Yp`IC8Bt8vcQmF;p9a9l)wpWNs{c6Vk_2v2D&CTw0DONfSOi@pCMtYlKpWr)EW`<{ zBw>nyM_L$f*8t9fCJ9o`Vu@GJ=HA_h8|&=8n89S0w)pm zm~WUp(Lg1^2#}`vh`Y{d=1(oDv&5kek-g}Y1^i1+(IMzqqL#J55J{Oqc#~kfYIr7e z3&#uZkS6qt_M0IoS=x%ums*FJDiSmT9u&POFDfY-D;zVFCnZw?_#ycc1%Ld~KtP@( z{-{M=o;Gi*&M&etO1|%=)81cCjL1OBgU_O?66Q`C$)zgQ1AS?Olm{gBU#LM_6~Jk1 zOFZ~1P!zAlh`kl$-H3Z9N%BibwNr+?1UDn79I!`?%U9;_H~wTsYm5~l5+%8`cBpnB ze3gLoB+zKuA&C+hNwvv}e&j}0Q9+BUQ6m0i zNQBKw+kSMcz8b7ei4vWXkPLY8PrraSrotmmVPMcAc0=g};5MtN0QxXhp)$@Wu1G!f9 zZYoZ~1oNNW>SIYIiZp!Fmr3JjJ0DZwZ&QcE<7DBiro1MyX) zCY@cwN&?fA9|d+LL4Oks-bx)pTMBzqDoyhMU;;&36T=S@dQJM0Be+%h@;q?UDzP?XkWtwo?1rgGuy1%=tmi(aJ$o&WTPAGlbKD*Qa$OfmDsbE{$jJZ`$tS1UfJ(xZ5a zSJfXWikQ!DSvNQWicoCw)r2lQ5tJm0CB;uqO_iqHw0-ws za`nhUd2<$`&-?W!_e?~yX3e6FTlXLzQ>I)6XvVY&loLz2;V(mJ?AUR1{O4fGmc@-m zjTwtAAca!Lm_S*^o3d$e@G($&k*w2@=z!K*0h-uEl&)T#-+co<5TuLYAWmh4|Y zyh~SJ+md&7Z;F2QgyzkeO;MPl@-JPNy0)(;NMN?Wh8{h*K#P~{psH0W(ENo#*s>Tx zt=je@xAZZTC5r>in;S@F>Ng{AM+?mfT1QhBETl?B^Qker%s9%*n#|YRs2LV3@bMMR zoHmR0A3Z|ZJl!c@?h-U$z^C-%ngx`tL^-;AU>|MRvWs#Tt3abB4kO=;87Ly`1@N0g zdk!6=1gzjadl5~xHhRuWGf&TJBw^JBqM_SYFVdn#O9>qum8jT^+PADuNl*p5q(oXe zeLDG+FGnZ#?W7%B_fnoxRcYFU;S_S^6zw??OaX=R(7Z_#=#9mOz8E!`>Q*XFQ$|mw z3QZbPm7)bfOEN8;JDakXs!RdCUUW6|E+xfV=nnWwuQC(%a+0raE;Vzc$XC>>zp{bJ zu2`BfWg2-FEKMIZsf&q>Sh7Vu^Jh#VkAkJBR;2(MJ8C2yJa!gaWNqlP&p)G z%4~rS>X}}V0cJvC!fEPvGpKB}y0mx0dOC3AAXRPDj=uT2H@Tu6gx)z%BgRak+qWN( zUv7UIH(@k+dAQNli-&2)?vqrqcoCXCX8}Eb{(?U5Gn!&vUng&`3^aNALi(sj9}2&D znl|pgKnoWK(t!;t=+1+uUOu@?C&aAUj4tKCiSY*xqaK|;{8|T3H)Y^ol1!g8R)CAan7RAASPa7eWC*NRFp_p&=9!@_^DgJJQV? zw_yvC$j8r@oUv_M<&i6J60~FmFd;gOW=)+z&%(lCiwe-dzCFmn34aL%;ErwINFh=2 zlrFs$eLrCq*}A&Zh)I*k-^-b%%~(X$>eQ!=bHAfA!8fUS=YG`x^Y*l5^?G^+a=W+t z5VEqS)BAVO+52H=N6qNTy_*ynpF}T0?$gbiHwpa)dHLp~j1G<|1cO3p?LrOh+_sKR zTzN!a59&wh@XoNqM}FK&$FAL@zI{I@M`faAehn)UFnKm&e6dyV{@q(NeB?;F9dZv_ zJTuYPUw=y#%ax_*=qIqJOQ>?CnzVkyDmrua5(SiPK+~rUhRqiE_+e&IW^?76CQl=`r73W65LIZW7Isqd#9$vPpL<_FHFwJSG)ZfkPPo|7g` zolHf%+_9pixT02W^o*{oGhr{Pl|5ZPcb4YQpGVj4hGH9i2<0hQPYz(F$cV=@Ve(iy zasX{1VC$ClJ12^M`i!nyUQFv|ZR;yayz5p8JoK|;i>Eh3)Xwun^#Y1#mWuj;OIsRVE25zTm>oB)@`7JqSw+O9$Y_5>vy7W zu3Ukpj~Gjj!Xl~LfI-xyMFVA*r%awfJ9qCPYwVwD-?=+={kR=H2|h!ccO9dWl}pmJ z5fkX?n@H;P<+s$KSv|6ceu$5wB!Iq7ZOfDX;qrOficbkjR4PW_jTlYOfOnrShEkiR zHE8n02~@OZeX1K!40@GBYvwPZ4EYPv(>s^R-Q9)e&0I+3ns=Z~R&lg!=?^q#;bJOL zI3I0VJ(uP$T7xZ2$<#r7jqcE0n?wqlKbtaV%Rvv(Ue|8iO3s<{(z3;~Y0sK@bpF;o zV&_cMyZ=DSnI%2!n6xh|iiZs5%$PY1t z$9_IbZa(gmKW_jH{Bi)j3_V4gHyokjWy{k1xwEj^`joo%8iSSc8uaYxBbq&F3LVAn z2R9!N%Ada&4d~yOGCEt)seOlN!n7&$Iy#mzq;sL3y*{V10mbQh=zVSs z!A5-#fA1+xL?3nHG*+xYTb_#O|3(iZS9Dgg3q$ttNRU2MY~1#ZYbeUf8M3WM<4295 z@--V$)$;!IL(n|PGe7O!vxlN#SL)PnOHFD8sIszxB63Jf5IYH=zXvA!3s(idi?TH$B%oEUGy#b zIrJ%c!8Uw1VJeY>3w?tXtQz2_l~n@m-?E9O%w9w>iE)&{H4F6_I9T~dH!dHg(c@-N zD7GpWD^?sn^cc#F{z0fnfPbeR@cdRlpDOgck5b8}=z*?TqeGQdA=zSm>&avICJ}V` z>UG#4j2)a@C`XR$bo|g3I&(GCDs$07*_2|)=Uc#1! zC&$tA&|B!6Jt!8or+}|7o%rcT+O~HO?2$dS`sg#N7?4-lBy0Fi53vhk*r-u-EA%e8 zX39!~z8ON5%ax>kYk#1ZHtEPJ@eSJQ0!ruZMZ+dep>p|rp?UCUl@2K_c#G93FXph( z9HdPf)ssJH*;)hL>~te$EYQ_fW-*K`)=&x0*e&W##V{F5;hl zT+R8sv+1XLfesX)D%n@Xv=hzk4RmRXBi0 zkDI`w2KDD`Ig4{>WGv76dLS1rkdN&h({qWE6}fboGQ9i1A&z(*#yd7|!i;_cZt%e% zgGU;l;PV?fgS{j8kc%fyn!%l#)#lEfhjXjuE!o>ICwFVzjJ-X*xo-U?T&_$x{&xIQ zT^{pRGCd$8ZJ|g)TucPlt5+K^W#lgqa)v2dG`@7{0z5E0#!YHh;gS{V@cu(bc*>B$oZcxT z@4Ik?HwR7U9PXJpe?UbJTDX8eYSx@f)@s91N@w&;s+#Ff(a~hScRQFjZ&=6WOM<=$ zYg9e5awD=^IeRkOJGk(e@zZ(lo?Tq3Xi=WI$kYqXUyazWT+`GOa`P(cS<7XL7UuC& zHmCvuAui$tcln?h`+8;P^qI4BnX*;5Mva<$=l;VqK!pHP+c8TW7s2~>@8ogA2XgK_ zwfJ?UV5@a-*@PjS%RMU>Dp!q{E?vgWYgOl3t$V0f<>^;~d-d(dlLq%^E9-Rp z)A67A%if=H&Cb2F-k{c{HeLDrr4wAOYyfA1eao4zFqgyZbA`$^_}tA~3OA*0 zs9CAKDsAD5XAiSe`V72b-!YAUQVevoJogX z@JG(z&$JeqyCG=8r?}vPb5OT&Y%7Zdtu7dwJ#H_8r@DjvS@= z{N+#{(5oXCE?u7Y9X!Zmzy5^t!!|v76~ouBo?}lB4{qMNJM?2ecdTENYgF&d&j7Dz zE;r8MU!1@AY6yQnbu_zV_TtkIU-0QYJK4!03kR*-%oi@4=LJElIQZTpAf)lp^P<2J zB&nHJJUqB^o_*XrxJm0auob)bvu-W8Zj;`M?oT>4;*#a6@S#ITd03zBoUdR>ei0eR zOD2uxQvLUtxeEN@hAO2vUj3wQbQ>7$&>H$M*@GnUIwQM!PkWL0)K!fH zzJL21`Xe9i+;b2gflY1Gpf2YtT#WBOxW!%CHRn8NSDCVUaz)s!(xuDr{Ixqd^ujSN zpOxdpW*aqq76+1A>D zYgTW>KP;QiIoy2s=c`XNdZs?fT@%h%4sBuotXWY{2_7?H91j`Pn>}(B<0nzk{B5sK zI7gnMymjYZUbS!@XLL%>v)69oODFem-fS7!w@4`-GiegzQwjF+EyZ`=M5krLThHhA zt>=PSGqPXNGCTo&**E?Auy@`v{4gwxOQBtjTCvf{AIqQAuf-$259DTz8=_y#&z)O+ z02?Z(GG#vwL#jnhm^Z^%C~V>c$&($Sivdcl@w2+uJyC!}_gw zY2YmO&gRFrL&Ny=vBSK0{xtS#GV|*J-fBx+_?+zmaW_Q@R1`tZrpf& z^yu+hH0WLn(#8Lzr#nsA{=Ay+)^R{g}dGe^ioXxcmKYyLTrw=UTbZ{YadKctbbLMlys-?L9 zkooXW!ns13Qe3=5dET~tJ5L$*HD}9IfFDI9@`dADIGb}O{_2~teCqTGUKF$h`0wDL z8RNJ_VSjc^mzj%0pURXi%^!8|tL)K=dE+>{j~B1nzKvG~&SuXnS^2<;i=gur*RNKD z9qpXCb?a_C|NDuY&)uIRVYkN(>BpWqi}C&=C*kiM=XvP=-o#1Ysd_5#QCk7SRPppN z!?=0dF8uW2eNKnbS%*CCCip_@w~n~ zciBFE{v?F`a^>QhwVUxzyZ7=JAGhJ6{`L9Q<7Zr{pbz`NhwR?7A1|3don11x@s5i( z`R=82?BbA_r_5f;moA>;pyjLiV(=Z*lguNBe#PlCX5;B|7xGT5#$|WP!izT^=eX#{ zTqB?eSFM6^1p1#JckSS5GZ*mbgFCn;`3Ca?Hm3D>Gv znd^W2g)~QSLI0B;>em8R8+4MXY_lfx<%0f&dGzSfT&H?9_R5)`_n$haz)8;8{d#fz z+MlXIS`ZdELUp+~9H4WTgZVl-y~Z^wag(N>^6681I5Q0C@ypurcy;#z7s`^4&%@B{ zSoZ@vVK6jq+AO|%Kh>yQz}7!UCp(_|0|rS~FY~swi`m-7g^ykkhHDj&t8$bv=n(Xu zJ-f}ha%AHrTg;_Xm6fBPUgH9u=m=Kt(h<0hMW<1=W<9=e>=1j|XW~iArITQOeElrv zar1?f^bCu9iM(&;M)ofnzz<(VaZGFsmLz2(gbqR959Ow)!?sO}ITH-| zm3!KmymxjVItqWji@{WnT2(lpLQ{SfD^3`5kERW|ProUA^6*~H2!%a;<*tekdln4m ztPBT-gsFGUIb$29EU%ixAa&N*FZiR6CmQ)ck0pjXz%Z4o`XRqCw)=cquR4D-Vj*u@ zyPWMadh*tz=T-iYYezYU2kJd@0t4leJZ1ZC15Pr3R;wm|w{$H!6Kt6|cZmIqmE{Zf zA8}l497n_@XlKyKt!Ot`ARLVhd&qfxy?E%f`6|bh;e)tJ-4FQQwVyd>=FGhN_~n$6 zpLA@^t-JQ*d1$o$XxJ}|lrvjabM>;d)ceUj^Eiu3X5PB>uu5HmLBg^%d+-cCV!MYl z5)p$LV#1%WU+&!eVb5<;f>Z+Y9AIqc%%!khLV;;X@zdHVR#oZi)s!y*#+ z@J`f|!Gm}0Jf%P_nmLKrt-v6pWH}x_eJ+Qb-Of2MA#v~NOLVYb^1L8DFnMzCBnGL~ zIpndp$QqNUcW-fyESb1t*U$OX#b6UPbsq_*&eJCsFTrHOtJiv4r!2>tVja|@Ee{^P zDy7U-Y#;K-l!?y-hZu1dZqcwVfBMz8yd!8KXGss4p4Ef9Ln{N>KX(;|Uxgt0AX`J= z;>xxdpeAO;41K$_;-M4Pt5=DQ2z$)=bLQgVleOGrpwOXp9sX{v(Kf}=Qu?Q1q;6T! z5Y?8e_tf)w`ww``6x~*p?k2@?azY%BL3?i2t`o<_C%~DA;dp>Gu@i}6V-RpwNT=X^ z?c7c-RiY_BS36;J`~6}3NOsGVgAW<}**bRumo3?lZ{NPlu5Q_Q!1%cuL5||8CG+!4 z4B`h5?9DZM^h?p>brVPPhpj&5Xdsvv@syiZslqeYZAZr&&yg{49D{fhH-)9V;HRZ# z;qIN9bGh101!KN);wLyIdHG7{GrS4@dTK=SYE8B9fwc=@{Bv-~OL!y_V7E$_EY8EH z&gI0It6b18I}iLqPpn)zv!4flI~lkNEOEt+ZUL|IB$HT@B=xjzRG0Jn*F+l?-Wcsd za0jji%If~~-Yw3S#e=u*JIdRZjAs9UY8-(N3+mPG%xhO~W%takJaonkzINp@AKANs z)7!f8&OIl&SNAqtqtS;(N6tL&o8DZn)~Eb3B$V?zXW#*&<_I+$|LQgu_sGFJPu%8H z2Y0e_Iwu}HdNNttkw5&^BlgMV!%H`8NBG=k`tNaOrB*`NGw!9DL;x*QijDo3!Yy^7ZT5 zm`fI|&97cXgYGx{W#0jO^T8{mNgpQC5)DNEs>Q_D4{zh_7ywWE!RVW>oMC?qP);5? z%>}))^AB5PP@}`}FZ*#NOxB(|w2wUTd)jLnPECvXZR_pqOU%i_);xTiA zxIyDa+`i9Hetqv0`+4W&GuJ~f5S_xK$1dWgn24zUK?}YFzvb%X3$W+KV7rF`b`p2~ zusT<%(3~Tq;yF6v5fAJ=fFC{6gTt6Ncez}V%6##v@I;4uH_l*C=E}Q{7!xvP!Y}R+ zAcQWgyLtUIyJJ#$=ZTYV=dwg|i!!CTVyzC)F)i?qt5@eSBNp?9b&J`}FAoOs+E2K; ze+w6KFUij^06TSPK4*4u<1L%^DR|50PQ~Qz9$vS6HfP7=Xvm9j6@PKhHZFt#%F8#2 z+^b__ZrG~b+x6ou3AXz7$-`V2TUc*BddSgnF&u?$)A5*WE?p=$x9ZfLF9!!R4!7Vu zIehu+p<-{NxKXtd+_*s(#y3WA-tX|2y}n_Xd7e15Kf8J6#02IVe)?MUUjo`cd_rmN zdO`*d!We{|YZh|NdTn_X`nCWJuFF-f!w2{8=Q>S3Raz){2KR1{0oXEmM6uMe=XR{; zqMmBMA=48ZR$}l`m!C#NLM}0wRF^uEdF8@yI4f+!c?0h$BfjM$$1m`N zVg0d<8vR7P*2ArHCSY50GY)(5g3GwObH`pIH8}~-xe^utf-so6gU?r7UD9#SFNa|4 za5IJ62O;M<8zw=PZ`SRXV_$Qn9EEw^mUDby8zw-!z4$g3b<&39MED31Vcf8CRW+#; z5BnYw7lj2TLs6g&A2qAb-!DwHG5gU*yga@5-t!0z3X`MdF!4P0`?T0UZijyRR_il z6pxQY6XN2jUegaL@cW6hc^S6ml_*6WI)6gXaZ1bcXRomg>Of~s9;LuV3u(`hbJV3v zd&+9;+>zx`y%Z`>dP`lzgU4hWkwm4i3@*W36^?isO%V~{*ec?#;^mURKw)|l6H9T} zj+6)6=sL7)pwetJI$)~_mj4rQk{h-vI$;^Z26WnCyP1=t6WL+QP09y%3HW&5Sg6$- z*1#T|7@5gEJ=JSg1MrxdwC+M(d$gitY^Q71pb3^W6DU{F;#2^2#>L=+XW0iN0n4BI z8)xCEUi=Ndd;%x5l_lRCUMfpWf;Gh>pL{DSTQxd=-hnbZ%9fKP^7M5l2Rz-zvVQ)O z70KTp%d!ZuILR^rACaomN~y`eV19ZL_L5+w5ob|bj06eUE*FdCYefO%lhZc`RjXTG)z+!!Kx)^j zmgK@uHY!N13Jcr?dptuA8nkF)NX0^~PT2Z{_>c#v%h8qgZr_0|ob%}B!za|aOH&{o zNAZ>f%8}QHD%CEn;C1c&CADZ-iR>M$sbQ@;SniIe!eweu4)-iLu`fZfxgX^f| zqUA*7o+~G<-m-#TJ-bDXYSyAEm8;RopU*2Szra?rzMpoWal?jCF|@I7M~tK>*(#wz z67WdG*1J@CUfq6y?LcrzK zu1L^&n;6Gbqe)|`P%xK@b@k3l=^X9I3VP${43I*Im-%+uu8~UmBT_9M|7yWzF^)rWQ|A{lSV#Nk3 zU#%V$%$ZG;5$zFn35Bqu3%fi;qpj%4&FAEr!HL{lVH^c0wo2s5odc&uzNSPgNASdo z8n@PX7b#Vp#*7(=HYfTn+>y;~D5LlVF0mcz&|4Bc+`P%dGaseL_Fgy;x-BR~Mb6r4 z2*%hCPhPolQ|0odC?4Ci%GRh)E>76~iEOW5zNAR(1-gCdDlH0JOzXEEqE;O{;1sq9 zx^e9))otF`;5=aq9k$%c$Ijq&b}w)0(!CX+W0z?<2XfDzm8`IRrA*}-v}*AzIx;TGNuHL9_r{Mg9B>;c%S7$d^E|NwHL|QDtm_N{9BEfhJ9# zO1|FMeyBdKw^7@Rl|DkpB_t(SQBMCN)Uu`C!s(FSm9k`Wq1gLxC>DpIv<&#VP zf`utePHc_|!wItasoe*H8#YU3cA`vKon?X?Cu{Px? zQk z*WdxRF)mxYm=*>t#MZsiRH;&4z>cTbKAuuzuNkUe!=$M38f6|M8*7cnG|T)nr#c+Xy2y;r59y} z?6T&`245nXZelBN`O3A(CmT-jLx_u$qe&7e5&1;Fy?nD#mHJhbtlIbJOCL6`Mn9iA zfVN(Pys~CcWuo9eKxD)**jkGN+X70IRPn-lAP5TKCBarZpQy98)Llr6uywgg?HV|dIXhq^;PZ$Cs@JkM_->7Du-JAyYaH1-S?KdG zzM`-DcA&{4hfoRs!ZZYjj9~Lpsy$Gyt@t?#70T;hfWpI`(uS?O@u^9Fa!!n*^($7A ze-SU(1PMum@3{q2^`?6a^{6f>yiASyhfh*Q^jfufT%TRPL!QprD8E-uRn~<0_pC3 zJbX^*3~BfJbriHz&$`%aLlu>X_Al)WzGbG2nW%(x5z7d4$6s7sl+Fg|0<7DOTZ-o7yMWKiS#M&2CaK4 zt4>TvOu%vLMF}flDS0t~69~q8GK{h8rI0Y|UeRE-j?-$yB{26}T>D0kJ)UI_$nv|y`ecUvF(z&`)-+tdx(KG`mg#%23 zbRJT#aloZ*b#WyZI~Pi@ zwIhst6>_%d=ORFohYMg(guNSAK{l(ZG+WuvKnlgj1$1sLq`7?hKcL`&vB->KtOEu|4R$tERdKBh5E>d zbWosRZ;)@kM`@H2A*ux$lUIr7h>(066kGxGmBv{@ z3=IJSC6q2MGEK`|EY4r#R^)4gAvwUn&mW-&4N+_Z7(@h$&VK7M)^m2@biI2lI9Jav z4Qy8~s(1=M;rtCX$rco$TIcnRf?{gidci_h zr=?F6dwqex3DqjO&DP~o&(?sm$yrbN)^Fm|HGi44&`W{99=zb2Ul8*umqOoVAB0r$ z9pKJpYFKG&6sD78a5mS$4f%ofbK9Y_V_c0+SyfpRS)}I^sd%UeZ{&n3Wz7LO0~&!a zoNK2F0pg1xtny-AKZqI9yNJFgnT3i*HAoLV$UqB*opyvXCIQ)AFG; z3hrS<=#m9iyriBKXL!B9n#H+9uV+Hs%-nMm#0JWv!&z@<~AcB zo-UsptHf-y*@LInr zR?A$D!Hr7Q*0US6n0;TD6wQSmWXyMNd4l-`K>q_pl!6p{?b9g)Aop#c&E-!uJ4MZ&g&jYY#A7Uh%$%_2e}9sd)2E)EV*v zbE6ItRb$nVT#8eRhy_uq+3M`}kE>2n8h#^CRECxgTs6wQ8|3=pC(~j~WmKBtu=7v+ zhFVnaGYYZB73rUE1-CUy>g6`0+#Y1(RH^G^-D@uAm=&~H2*K+tmrfNFRo~|+LO4+x zM^H6!dI^&p=v95uJ@;}8eHvhTrJMoA7r`!|EV#zv_8y<+g2CJIjBvr@6__!D`EaQ! z_44USf&(FnS3&J*^?Lr~=&8I@+Wjucvo$CH{}jYhs7dH)GTCSG2ow+(geej{BgByJrc1h$-YYb3>)voVI#peNejPsZP^1)F2`;(Brk z8v|~3!!ER50PN$bh0`6n-dS6R3(%+ z`Y5Fdm!n(hii=xtT|l#NZ#_Rkou~BelO%o0UgSkEk;GGrAVN;I;GbApPTXVYsXOO@ zx!J|tXG1Qs>A)8a+B6FUH&ZO5MX;LAd2tQ}*#C7L_?%;2R9Ewo^>6<$1ps-K(2@A)H9(?sWU7P;2LTd2cN%2jQHif(obF1H#7dgn(;G$ao zU;d00v)-K9`LZqKSCb7|^LG)Oduq85PsX3sXMPsed{v*7B$`Z&VNsquVshR2I;_O~ zyqnS7-|7;9g-(%SSChOegeW)}&+efj&aTgdUh z5@=@3Zzm4LxZp4IYP2<>o+)pSX043I&kH~8<14Z1zzYfd3t+(&m!_xkuJcAuORK4QH(qVI3~9S-6tF_!wJ ze+^yHB>Z|fZ*YldrT3B8zbXt#iuK&=F}QN>dbogw)bm~{-Cj>lTCJRiR(h@+8%||L=N6)AO=d8v8o~Ot=y8*mCu#^=bBw5D@$Tbh<)lG%6+k z&mKg6WRs1$1d<$LJLEqYi`X!xAf2yzMb>UNF;!M;PalNmi0V zqAdkTLZOOd**H#j_bhubCnOvR283Q&x26VQ!*WHKn9sk0rQ{(^AVa*KSc3TwiVsij z?Ayt~(?gVq7Ery4PB3!T`BN-Z@MJ)xik+?oe4sTF&>EpwCB6=e+!epA(#@uW6i}nr56}Q924GWA*)1;r zQmwWq8>L8O2spAdT5UpNS#6b8X|pK-@DlMYMDWEcY-3O}byWc+vssZn=gYyIPq>1* zzHEtF44$Xws!Ja9HOoaAkCBWpG&H5n)E@0d7{QsA6B6 zE`+mwjLKDBNFIS$W?Z9ANnj`#oO+01$!)o@RwaaTFe-(X@;L>^mWIb=Y@h0Iy8r)nHj>PF=C24H>+_{~YT5Ip&93pDh09PUm$ zGlH;$4U51UKPW#`M8+Nydf2M9g zzL+Zd0|)l2J%R!m=HA#jeavWRrZENJ&R2J*fvGF`%!5E{+a67p>k z@u(n>8Fdv8_jhm_U3y4v+F=!{RX{jK{S#glJcLd4X=}od(G;P*5REtxHd}Vh!~1lF zdSuFPT4hVs~$-Wlv9!Xquwm`KFq)aGB zt+i1!eFUkwr#LK##$mvTB#V4Vgy_K3tf>dX%f}>*5JGXw49x)L<7=)^m|p*0YL@|d ze=Ia%_8D-)h4ce4X2mV?4}4cFclcwD#7p9OB94pL;J?#{G)oAOjDo@*BXqu3h>?vU z)?^3uM;V=K?wl?2XneOgAVE(A=eTt}F8YcvG==Lqh1YTVbL-MKVaEWS!5qPjcVNCb z^7Qx%zNJwn>%FgAdvW5C;c_nRj(FZA(flHX$L=&lG2DQoZPmXyIw$-OxC%aTL+b$F3v=c@LzEgv`2)HcpZYIz0&PqcoM3^2Piz| z8*nGYy?i$GF=~v@@Pqtmn*AdW+2q!iU6>-OLMyG>7f4|;d_K>Zo_YtWX6ob8{-Las z`71j#T)U*)%vEV|Wy^e9F@OR1cY^-f3K27uZ>!bi0LxOTwNN;+vx51;Y}f;FHKGm6 zvh93MiC1p`?@tv(v&~9c%5<9Knf;MZcZ5e?LaCYnbjWmerx}v5i%W==R+$s6YD7e& zB7B4%1xRRF8)RQ|J}}6K!b1naJpJsWG7^A~@$TSKC!psI`B4vj*PvzwhF1eCnb$me zNk}f;EeL}=asO1R*cbJB&}YD9R76zK!W%=Sl;uKfFuBL2Du)$>o9_rQu#$ZNU#`k9C_*z?zM#ZJk6x4VDZ)uvDE6wg^Ny) z>kHqXD2vM_5eV)r!K;%49fMwT8v3Y?e*a7(`mZXbREMCoSZobxKQIs{MjzQChtaI^JxibjjINWW?TWoa&fv(0V_ zqucRs)lkR`ZV&cmGnG(U1B1`TS<3fMJ{i(WPg))kkF`g&?@~{ zz%-xFOuNf`Dr1(QIsqAMWpWs^S^2-wEaf+4w<#b)pPFnpb2yzXR7NTYfKt%j*1wVvG5I5grOYvQwX} z`sf@zwzNPcYO+tgkNOB|-@`@N`rFYf`uGNCbHb}(eU4@gmeC?gbiz1N&yvTa&01V_ zF|j~f@1=+{OIJc9v3Wo;jcjxI}tkPGJ4+s)nuz&*0u@hfJ*o4|$0xVR!eF{07p< zy`+q4r^h1+(!IaanTnKsw|5h=p$G&c&%AsYEUw_|rGwC#;Ro_ZSx&E~=2`cyX*1be zVl)~}`PV$WWaLxKt7Yne_`6 z<#U1X*E^B_-Z%-#jVOPD6;~=lM*`Bft20{{qCFAJ_V)Cb>SbUcxzM{LOD0}5O=B39 zz=NS8!%8IoP$`8xKHKrvT|sRqp`AVdJym88usA$4Jlj7kbvl``rixk~z}?ZY1FeVq z-4E=x5|9vC7}`azRcn`_KYu_vK1hQiE7~Am++bV>jVi0dtD}qv!sbujxXY-q!lVux zn4H>RG9excPf)zoZeOujRgSqNrbs!Jp;o6&PVkzF6=%-RPdO2TQmfPD zCIKb|Ay0dY8m138v787_q)J5A0hDS)S+mQw;qtg}H-FWYCy4JOi=0l)@&QaADisRL z3J02kvdc7{EaFNTVW3ZzV5mU&esn!g@T*M~vhT7JVGtpMA_@J+Ncjz5CHmsx z00JaXuEkF(&DCXS8^x0vokh)U72Rdc%N>&alMCPb71i1+U=%upz(ImRpiyAx5fX$! zb|?-+}?sQC^&pQuOIXn+DTsL0Fcazx{f3T;lyi77v zhHGNMBGJe7Ey;=CfnrxR&z+m-GXp0X52ni<%Tz!D|t{jJ^z~9 z`}r_G`0MgwPTI6{q|o&WN#&6Zlk+(Tr_|t*y=&VKPD>rC?7vMy5X2z(KFS?@I_?mr zW9Lk!ynlQg&Wo)MYg}8WMylH&XP6yXuis_5`mv3(pF zp&mb~9*4gwYx3ro`i(yG0$-d&BqP?Q&xKii2}QoJTiZ&DgTK6x^Tgo$%{~2#0E@|V zCam*D2P>AZo$~wdV(zl=wBUI-2uD%!Tbnb4mT(v z07qn^zA-hcS~EBLDWUN?Ud&)W0IVUG!N+d1LIHsAQ=1zlRQe$&Pz+mLTo5Jk$iYjBgF1R8(=U$Y#Sl9`_;p4bcso$lv%R=;Gg!7th=F*(t{9V zwviE;*&&XTER4{zt3sAU+2-vBl?vHMTy_`J&yUuc*cb29`?P8SdTVqKA$Z;yX>&FF zZe8m5*v^-oj{~ytweDSi`{QNR;c;@jT7i78!4W(LvG0pN_oUtZ)RMwFrlk{^P}gkM zJGf$aj>=QHo)yGIm`6(r#yVbMSi!-$_JmD?7`G*Hd5{XS&r{-^$XZAgu zW&S5mnw;lmH=Q@UG_g6@kttmQyv{aeO#W@A%d-bBK0iVmPnX)~X*p;*Oa_%JG_yR8 zI=H9V)K7U`=8f+K?)IPcVk3C2)lr!?og6A=dG^l-jt8+%aeNw+I#uewlBhHmw;Kl2 zsU}C-F0TLFZQgVU2lnmWFO)8tPKUjj=2Q!OF=cFZ@?Q7;oOs-FlJ3;K!aC1*VAS-V zKWx-L!M;VzF9c>2An-YO;g*v2w{Cl6rgY;L_gv;40Kqk3^<{UTcDdcAS{fv~V00fX zOdkAx3%I*?UnI*d>dNPJ)U@)rUU##5!hTAxjYwZCF{@*v)^VHyrTOBdu6gT62%7EQ z$ahxBsxo#0y>G8K4%Q4+lFKYtzwkcX-2F89R0i3c(=KiS=f-Rt z<6z1VRm%A`y7=&j^9goy#PHd7m^D}--}vDN(k>w|>V5}+>u~!_^BMZ5#x^i}9;WDy zZoNGi*1tP??KbarlBv{sQq%SxB%b@yE(OgyW;m_kJsdYaBbjU@Np>>u+G-Yijyzjp*_Pz>FaXFhvr?Crk zg2%*AE|!k&Yp53{J2H6wwdywBJ_s!sj+~7cM-tAUNoCb6`VZp|58#HbFpvxrW zpp!H3yJm{UdSM#=O4O~@BnSOeB7ti z;9c@QAo!Mt4@KKojF@{J*r$xxzEo)ualMA`j`i5@r`vztjgIs{ToN!H;n?qKx7>HeJL8Gy?GC z{zbOhMLc$EvN;K&5PV*V?eD-Mv;OX6L$eKqN2?Z&k(o?o6f#^au?ura+`HL*gPn}@ z8$K&t%oQ2_w75A9zN`IHBVe$-w)(=f_xW2VgitAGyN-OZQoDRZYO|{5=+pD{Cvk)7 zD`#%a>?wnVLEUiwkQtpCj#jP$=a6hl%&xt zQS0ee?RG{Qb{QSc>NS&6nj1Z;ozu)9 zNglI^Y5aV)_!MFu`B}(2oW>=^s1NLW)l!{Itofu_%BODcs%5v|d1#pGEAC@ym{kL@ z`2Fpl)~;8hQg%gmu3hmF%^+i2Oa8CyN5w+A_i`ok;NJzT{Nv%_^?4s_0rk|D=5c@2 z_SI{%m}czcwLbwkR(dkIeKllm@u`08RX+@&$+!J#+T5oLnjBKgt0zpXC@n{DJF0ia z@3r=MZ)aEKx@i!`#xuvcSmi^;!S`hRJN`Usu{ucVf#|ts$`@TcnPS1RCSbq3{6pGj>05qdC1>Ji4JA7F=w+#=;~F!e-( z%Tx(20ejxcO!dDoXwS2B$7ZK?LJ?=%gw?i@Ipig_VHq_?-j2NU6twXQzjhdGOz1uab7Pb7L*m_Y zYcCC1&v(J=Jw{C&8Ml@^S_TwTVUFMhkr#ihPcwgms<2{s6qO?sa5g%9NX>wC z0&jXLWk_WgL!|Zo0974ymMU8JzU`F^8COpLwHBdghZC;)WZ(F!F0`GF3 zGhgh4Fi^28sCm*Z`(r)$eXLJd)>|q-m`v|8 zDYolb?uRn6f*tHso$qqDS*~WQX~F}vGB9ePA!9Hg=gv}{-*LtBxs|nXH|sd=LB*v7 z)31_GfhoApRt3V1%*~pj6pJd^D)2LM&@O9)uZTaVoK>x2gG>M&+AlB^W zGx0{J?Qq)$d_g2M;UA*Yn82ax(Hn&L@$cx?vu5A1mQ6sB9uoyqB~e4p83A%`zY7cp zYs56%@ej*XQjW&XU+fFU6Pk)%smsmH4G(LGopvu+j;Ph=5(s!X&eJiqUlSWCF6DAn zyF?E_w!P-PsXl2g3}v09Y|e!6p6ksOkyJ<1p4sdu`5+vSqRfq(;O(YoMSK)2A~*UJ zQ=W_S!IFjC;hqLE93f>8)fM}lrv)OR3a!566{RyjsrOD4p3tMD>>YI5gMYsgN%44C zWUE^Yxsx3!_%!N}E6!RdZsb}0&q+y6JlI|@Iq?f<@_t!LcI5tbdN|uoe)n;oXn0IR zwE%}XtM?%e?I5{F(Q+f&px(W?#|ppsCu|&;9n+go%uk$~>$V{kUVFVD&T%pFY19Me zyk>9S!>9;&y}jjyWK&P?xc z>u;u_tS}ToDZrsr=&@w2z^XJBose)135)8!rS_)yqb$Tq@L1F@IEZwTG^0al<2^yn z;vlMkQFo9hwHE+7&0+GQj2(_BSAJRqE-=Vu>+JKL>d@%O%r%;k??5I;C4EmC24Vbt zrwwBpfeAXW`(&ye7mIyR>Cvx$OpNrdrlfR22;Z#@@>ihaIE*Ner!2G*>1eRz$c%?> z(qrWGqaDhl`Qi^SZ7W4FMvOoAx(5Xvv_uUmcDTSr76LFLsS0j6`7X@Rxn8S{C*#h` z&dbm&fg5v)^x{pL*EzAgh7h*k#JESvgG8ojo9NjhWk$tqNqpO3NO-UVgEHYfDTcPL z^qj5aWi6J=ueMJ6a}Rzg5`h?hXk)p``C{u}&T);qan~jJdN>5XqE3P^f03@R#8WGd zgjpa>*6LA{P6pq?LZ{WZ4Fo(11E^{{Rcz!wf7?e)*u*_+fDvFThi*Rx>l)&fU$cQL6|Ri%L@^YydZ~-f^FR zwW_c89%#(m%FK!)1*BK!u>kGcmTq~qKRx8JP?D7@C&DVp=4$UFoVS#tPuEkm>81m+ zh*$Dw?Xhe6hMc_Xzz{({ApAZ-tN>Rxle?oRRfx<-9%&_%u~56>^8FUCLvb<*PP3o4 z{bYB?pM3)k+o`tREX`5cca%+OjhQzXnE5WQt>TY?TimXB1x&9#E5c`GS#j&d=(DCV zI!6fx(|l1%xq?{8Wt5edq#u!fR&#xuZCae%M#9 zFi#-8(u`J5Z4+%Meb@y!-&hHMBKaw3msI;lM#0g2I8wMo+RY(+YN*;{zACY3(51dC z9t}g7O8)PH+jJ=A3szC;chd1c4POnm$Mod;;-V&NNy012QdB|mOFr`ZB&nmJc5nN9 z{`NLdjer|8v3bQ1+Q+{&BvQEE;?EdOCsiMyzp&q}M#kHL@Sc*$G$ksfGlxqH%mXsa zZD?|qC>4jPodJc~>$IxeT8dl-oJQ_uCaIHT z>93Oy=LZFwx)&k124-{05m_e50wJ1=adw!Hq|M0Qjw;Lh+G$xlP;(x z-S`YJhAeV-P&X_#$jrLw1dR~WPBHEu-$sKtKf7;83ITpY<~aNV5k|^S{gM)g;0Q^O z=&Rlp3}lbEE!U=$14^Xz66C*sx8h@0929Oz!cqv}pQHnxig?3nF{Sv1 zVksth?W=s|VoD$%y(-5bHf{1${$-UtH-Slq?+Nr;1i^vxr*fN6cX`aQUhN5`U~C%N zE%sVhx;EkzugY+QCD1=QVlC$Mo3;_~*MVv=vMaI`&*yg{2%I6uJAXi#jw zMi$!bF>WeUL)i0U0^>Oq_{3R24WL5p32@s0a}mMI-U_wgRa~`APR)STuO*fv5n0SL zuusN_f#QLUWvUej_^1aY3V{Ph4%8^ClF4kIc#>5Fz@dr8+{+zD$Z|P_qubD`4flCH z$0bfh;+stn7PbJcxj@G!x%uluj>KM)pNhCiGwqpZCHG;=d!{$Km2gl`aXJ+W5$>-g zm#}?qT}pXh4Ij`DrmB9i#@NdH_1*849hc&;4w-y~QSp&@_nFZ! zAj^C0ZHZ|rP}M46x0G~3TRC?fWe=QFts?=Vmea}dRgpUqVeVGFg&UAuskvAvmlfgo-^ z+Q1(75fvBpAexWtU8mALHa>!;(@d^;_()&E^RVqBma+C-AjrI2;lxc^jH0w&5jbBT zW^B*5qv2cKmX|WLlK+FzLg7@XgC>AO+)7i)Nxf}qF20He0X7AlbBjrf|7xIGi~>{O zIpN>RxDY!>?pb#P#oDYss=%j?jL=B!#|~<;E&!gc7yk8F-aFOqXu-ZojFmUR&ufvy zhZipD9xS=v=^<1Fv(Rjgk}5n%4qE19FCl(CDKA<6GVVS%YK!87a*_9SF`oF?ON})5YanB$yc$C|QHzgX#5;_pJjdM}VQB5r7{;zaZOz zI(F+@*CTcRU-i=&bmK;N<3WUp9t^Q7 zSuvt|{*ujtv16x*7Zaal9dbBY`)RUBKk%G7hM!S9d zQ1bMd;QI*TN&`rszP~^_Z?KEOy^m7(ROvwD$0_RhPH9QgHzzLUbHpBvm&(HDqU#i~ z!0}y20G$F;O*g7x5R36=O{7I5VgYgh4bEZ385J-)?T9VOihB?Z7S*{YW1*EDmMkAG+`1jWeAPUdViqu2)V@qA{R<9PBZ?| z>6%?k8|5XL>oJPuCRD&)c5xw5k?{#3Xj2I6ZDLIl9bVEzp+B6I+#kpX<%XKYKZ{G; zN;b>cdB}~OB*#Lmkb$)B;;MQrTP?ELex1xp?sfj>B!q#+)_mhIDMN}bS-5Q_i8CfP z{t=a|kvxRxQFF`}Yv4t06X{J!(N4({)R=bjA$1tI%Puq*xM!>+zgG(*$ztqCEjw@! zTRalTCBdwOm=8=JkXqD-7ltC{d7Jm-I{9OOl&!K_hKj%Yg;JrlIsTro!{S4&H)I!FOIJ>0XIH36h5gHx~5=@|(4-@;&-%mVTY%XG(j@ zb-DK$l`tI}Jn-z=LSkBBWY)=1EAJR9+SZtztaWZCo_ZN?N~}LiG+P-EmqqHJf)J($ zjkS^*d?5>_kU_PBWBq!DiV4SYNi2t?VRq_S%Kctb9-2f?24R7ecV|C;Ux=*FCt&Nc3rn*9cvb*|1p_`fO@f+JMji z^le(hM~4B7$)SMIMx(l2*&J~cqn_4l*n2z`H35Zi?~vtsob&R@HNLIWg0|}i`|K1Q&Vzp84#rTzH!jhCljN#=l*&5JvL+$Q6NRV#kdn?L$>uaA zD>FC3QO2;H`JhmTd3Yb%{h5NVR?{PIp?2)g6O;Au8$!*U1Yvf)gwC~AcLgh zm2p%zj|QOI(i%UuhccG+kjx^c|1>Sfa9ZILRNTkuM+1zwG;ovE<)F1THNzX67X8o` z(wcn;Ke;C;W1o+BcNwGQjDaKG6_0&tftYSIi4l>3h##l&ERyF{vO+)CtUt-5JIk?- zu(fBp&ySPe3E&$-k|j0b3g5@JdfLg^Qd%WlLV<21PbBgpZZkhWf&w?u3%BATU{aY& zv{zay@g<4USSJ=&8u^(z4RV*)rN{5@qO{3`X&{k<#K}3Un zL)!SHtIC-Xql+Gb# zEvla~?{ibL^qZ7fl(pj#r$5a5z3Ms{dPW286%9=sCN}8_-V+uKokU=xb}ZcT0Y|UrHP-wDQWB${tPI6vkEMIXXU;)pM5+qGcG`gM zL6J}E%ff0&<2=Ebpjer|Vj(9yZKgt*j6E$`WlSHB5~+hMZ_a=kJ1nxq)KozQdoU|= z+y|yrRgYGME0yJ#kYQZ$=0(f)2PV;>OgP)}w?`QXxgnLcanfB20@`2FyMycXaoBH==mQL_R#qAc>KD55waoup9TM87zlI&M6EIKzYO0(6V2iG=@8{* zrv2CO-n92Q8PlxoCUT`}ZOqlDQAMGI&VRu*gY1F@eNB$* z5HsX%eNw&q3o{d2>wA#_AL)71MMKN|Q?RzMtDCd9#?s3Z;tXVg@n5+2NPo|z^6`!f z{Ta%y@AKW5fL#lDaJYwwkzhkUgk`j56=)IF|;cy|!Kuiu|Y42w$y)0ekq{|PCN z=oT4qPA=!ca)w*^YH4Dihuv$dsO|F0HoWrZ&awV3-|=v+P#4E^ibLeAQ<#k!e44)S4VWw|IMly7fWG(LQpg_$&+7!WpCQM);o2`tt>+n- zB<4McW`F&5*KS#w|DRv{ADa6n`T6M@CSJ5OLdO(^yCEV6V>7UCyf+Kj&9>UuZw4Bt zY)+SZ6IbC0OU;~8Mt-PFVB+IocZIpJ) zO*>`jjrRXyqze_a3Fd(I%KMZDQRYUhTHAworB&#~N={DNbB&Sx>i;^L{|oas0^}!x Zg70d*T!nYfzu$d -``` - -*以下所有命令的演示过程都是在wallet-cli这个软件中进行的。* - -### 2.2 匿名钱包和匿名地址 -匿名钱包和匿名地址是一对多的关系,即一个匿名钱包中可以创建多个匿名地址。另外,匿名地址可以从匿名钱包中被导出,并且匿名地址也可以被导入到匿名钱包中。下面介绍一些相关的命令。 -#### 2.2.1 创建匿名地址 -> 执行命令`GenerateShieldedAddress`创建匿名地址,如果此时本地还没有匿名钱包,这个命令会首先创建一个匿名钱包,然后再创建一个匿名地址。 - -```text -wallet> GenerateShieldedAddress -ShieldedAddress list: -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -``` - -至此成功创建了1个匿名地址: -`ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx` -**注意:`GenerateShieldedAddress n`命令也支持一次性创建n个匿名地址,如果不填写参数n则默认创建1个匿名地址,如一次性创建5个匿名地址的命令** -```test -GenerateShieldedAddress 5 -``` -此时,你可以尝试查看之前已经创建的匿名地址。 -#### 2.2.2 查看匿名地址 -> 执行命令`ListShieldedAddress`可以查看匿名钱包中已经创建的匿名地址。 -```test -wallet> ListShieldedAddress -ShieldedAddress : -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -``` - -如果你重新运行wallet-cli程序,可以通过下面命令登陆到已经创建成功的本地钱包。 -#### 2.2.3 登陆匿名钱包 -> 执行`LoadShieldedWallet`命令登陆本地的匿名钱包。 -```test -wallet> LoadShieldedWallet -Please input your password for shielded wallet. -password: -user defined config file doesn't exists, use default config file in jar -LoadShieldedWallet successful !!! -``` - -当然,你有时可能需要将本地的匿名地址同时备份到其他匿名钱包中,这可通过以下两个命令完成: -#### 2.2.4 导出匿名钱包地址 -> 在本地钱包执行命令`BackupShieldedWallet`将匿名钱包地址进行导出: - -```test -wallet> BackupShieldedWallet -Please input your password for shielded wallet. -password: -The 1th shielded address is ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea -d :8e52fc48c2a47509e7eb78 -BackupShieldedWallet successful !!! -``` -#### 2.2.5 导入匿名地址 -> 在其他匿名钱包中执行`ImportShieldedWallet`命令将该匿名地址进行导入: - -```test -wallet> ImportShieldedWallet -Please input your password for shielded wallet. -password: -Please input shielded wallet hex string. such as 'sk d',Max retry time:3 -00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea 8e52fc48c2a47509e7eb78 -Import shielded wallet hex string is : -sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea -d :8e52fc48c2a47509e7eb78 -Import new shielded wallet address is: ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx -ImportShieldedWallet successful !!! -``` - -**警告:导出匿名地址和导入匿名地址过程中的字符串`sk:00645e78310c0619a62defeb5be3d48ba183f66e249c63e2eed4164e072e87ea d :8e52fc48c2a47509e7eb78`是重要的秘密信息,请不要泄露给其他人。** - -如果你准备好了匿名钱包,就可以进行匿名转账了,当然在此之前,我们先通过普通钱包获取一些TRZ。你可以首先创建一个普通钱包,它包含了一个公开地址。我们使用已经注册好的一个普通钱包,它包含一个公开地址`TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq`,然后在[页面](http://nileex.io/join/getJoinPage)上请求获取一些TRZ用于测试。 -![](./../images/nile_shielded_usage7.png) - -*在完成这些准备工作后,下面介绍如何基于wallet-cli的匿名钱包地址进行匿名转账* - -### 2.3 匿名转账 - -#### 2.3.1 概述 -有三种模式的转账涉及到匿名地址,分别是: - -| 转出地址 | ---> | 转入地址 | -| ---------|---------|-------- | -| `公开地址` | ---> | `匿名地址` | -| `匿名地址` | ---> | `匿名地址` | -| `匿名地址` | ---> | `公开地址` | - -以上三种模式的匿名转账都可通过命令 —— `SendShieldedCoin`完成,无论是哪种模式的转账,**都要花费固定的手续费10TRZ**,这个数据会影响命令参数的设置,因此请务必谨记。另外一点,`SendShieldedCoin` 命令中TRZ数量单位都是`1,000,000`,即`1TRZ`在命令中的参数需要表示为`1000000`。 - -#### 2.3.2 SendShieldedCoin命令使用 -下面我们先看一下`SendShieldedCoin`命令的完整描述和相关参数的含义: -```test -SendShieldedCoin [publicFromAddress] fromAmount shieldedInputNum input1 publicToAddress toAmount shieldedOutputNum shieldedAddress1 amount1 memo1 ... -``` -`publicFromAddress` 转出的公开地址,公开地址转账给匿名地址时使用,不需要则设置为`null`,如果不设置这个参数,则会默认使用当前登陆的钱包地址。 -`fromAmount` 公开地址转出的金额,如果`publicFromAddress`设置为`null`,该变量必须设置为`0`。 -`shieldedInputNum` 转出匿名note的个数,可以设置成`0`或者`1`。 -`input1` 匿名note在本地的序号,个数跟`shieldedInputNum`保存一致,如果`shieldedInputNum`为`0`,则这些变量不需要设置。 -`publicToAddress` 转入公开地址,匿名地址转账给公开地址时使用。 -`toAmount` 转入到公开地址金额。 -`shieldedOutputNum` 转入匿名note的个数,可以设置成`0`、`1`或者`2`。 -`shieldedAddress1` 转入匿名地址。 -`amount1` 转入到匿名地址`shieldedAddress1`的金额。 -`memo1` note的备注(最多512个字节)可以在不需要时设置为`null`。 - -**注意:一个合法的`SendShieldedCoin`命令必须保证从转出地址转出的TRZ数量等于所有转入地址收到的TRZ数量与手续费之和。我们在下面的例子中也会提到这一点。** - -*下面是分别为这三种转账模式举的例子:* -##### 2.3.2.1 公开地址向匿名地址转账 -```test -SendShieldedCoin TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq 210000000 0 null 0 2 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 first ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 second -``` - -注意公开地址转出,需要额外进行签名的步骤,如果成功,可以看到下面的结果。 - -```test -wallet> SendShieldedCoin TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq 210000000 0 null 0 2 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 first ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 second - -... ... -Please confirm and input your permission id, if input y or Y means default 0, other non-numeric characters will cancell transaction. -y -Please choose your key for sign. -The 1th keystore file name is UTC--2019-12-31T17-01-51.940000000Z--TEKqBNRDPRW7MGS4SHvNwHAcgkhH6jzH87.json -The 2th keystore file name is UTC--2019-12-31T09-22-43.363000000Z--TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq.json - -Please choose between 1 and 2 -2 -Please input your password. -password: - -... ... - -txid is ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b -SendShieldedCoin successful !!! -``` - -*命令解读:* -从公开地址`TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq转出210TRZ`,其中向匿名地址`ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym`转出120TRZ,并附言`first`,向匿名地址 -`ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx`转出80TRZ,并附言`second`。 -手续费10TRZ。 -> *可以验证 210 TRZ = 120 TRZ + 80 TRZ + 10 TRZ* - -命令成功执行之后,本地钱包中会增加2个note,通过`listshieldednote 0`命令获取本地钱包中所有UnSpend状态的note,可以看到如下结果: -```test -wallet> listshieldednote 0 -Unspent note list like: -0 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 UnSpend first -1 ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 1 UnSpend second -``` - -**注意所有UnSpend状态的note都会有一个编号,即每条note开头的那个数字,这个会在我们下面介绍转出地址是匿名地址的时候很有用。** - -通过匿名地址向其他地址进行转账时,只有标记为UnSpend状态的note才能被匿名转出,由于1个匿名地址可以有多个note,所以需要填写note编号来指定要转出匿名地址中的note具体是哪一个。 - -##### 2.3.2.2 匿名地址向匿名地址转账 -为了更方便地说明,我们选择在本地钱包的两个匿名地址之间进行转账. - -```test -wallet> SendShieldedCoin null 0 1 1 null 0 1 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 third -``` - -*命令解读:* -匿名地址`ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx`的1号note转出的金额为80TRZ,转入到匿名地址`ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym`的金额为70TRZ。 -手续费是10TRZ。 -> *可以验证 80 TRZ = 70 TRZ + 10 TRZ* - -命令执行成功之后,再查看本地钱包所有的note,可以看出之前80TRZ的那个note已经变成Spent状态,多出来一个UnSpend状态的70TRZ的note。 -```test -wallet> listshieldednote 1 -All notes list like: -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 UnSpent first -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 f95185394430b49a865488a7ffc8c7e4306eecd6fcd94d9bdc3018a810879a49 0 UnSpent third -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 1 Spent second -``` - -##### 2.3.2.3 匿名地址向公开地址转账 -首先依然通过`listshieldednote 0`命令获取本地匿名地址对应的note, - -```test -wallet> listshieldednote 0 -Unspent note list like: -0 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 UnSpend first -2 ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 f95185394430b49a865488a7ffc8c7e4306eecd6fcd94d9bdc3018a810879a49 0 UnSpend third -``` - -然后执行下面的命令: -```test -sendshieldedcoin null 0 1 0 TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq 110000000 0 -``` - -*命令解读:* -匿名地址`ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym`的0号note转出120TRZ,公开地址 `TU23LEoPKbC5xKXTEJzLFp7R2ZEWbuKiXq`收到110TRZ。 -手续费10TRZ。 -> *可以验证 120 TRZ = 110 TRZ + 10 TRZ。* - -如果命令执行成功,再通过执行`listshieldednote 1`命令可以看到之前的UnSpend状态的120TRZ的note已经变为Spent状态了。 - -```test -wallet> listshieldednote 1 -All notes list like: -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 70000000 f95185394430b49a865488a7ffc8c7e4306eecd6fcd94d9bdc3018a810879a49 0 UnSpent third -ztron13ef0cjxz536snelt0rdnyqe80h2qq8j2zsh8kx7fqm4grh35rnnycx5rmewq6xwsn5elzfyshrx 80000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 1 Spent second -ztron16uz8hugh397ndwrxxxfr6kne2jc3zry4msdls4rw8d0m79v9w0tus9czwafys8qa9ynpkzlz4ym 120000000 ecd3149beb35b3dd817a1f26ebd5a471a3273429af643484f07bece892d4e45b 0 Spent first -``` - -## 支持 -本文尽可能向用户介绍wallet-cli软件的相关内容,并着重介绍通过wallet-cli进行匿名转账的一些基本命令,如果你有任何疑问或发现任何错误,欢迎加入我们的讨论 [Gitter](https://gitter.im/tronprotocol/wallet-cli) From efe3bce9b2389ab62ef785d1f2af8ae74716cc1a Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Sun, 5 Jul 2020 22:35:02 +0800 Subject: [PATCH 331/445] Update README.md (#361) update the readme style --- README.md | 111 +----------------------------------------------------- 1 file changed, 2 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index c46be0a65..ddc6f7b21 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # wallet-cli [![Build Status](https://travis-ci.org/tronprotocol/wallet-cli.svg?branch=master)](https://travis-ci.org/tronprotocol/wallet-cli) -Wallet CLI +Welcome to use the Wallet-CLI. -[Telegram](https://t.me/troncoredevscommunity) +If you need any help, please join the [Telegram](https://t.me/troncoredevscommunity) ## Get started @@ -2078,110 +2078,3 @@ d :da5fd7e087d48e3dcebff3 pkd:c7e719c5092f87f7c7bfe8bbb3b5b16d89f93003088c5c8b8cd53c11e3a99959 d :11db4baf6bd5d5afd3a8b1 ``` - -## Command List - -Following is a list of Tron Wallet-cli commands: -For more information on a specific command, just type the command on terminal when you start your Wallet. - - AddTransactionSign - ApproveProposal - AssetIssue - BackupShieldedTRC20Wallet - BackupWallet - BackupWallet2Base64 - BroadcastTransaction - ChangePassword - ClearContractABI - Create2 - CreateAccount - CreateProposal - CreateWitness - DeleteProposal - DeployContract - ExchangeCreate - ExchangeInject - ExchangeTransaction - ExchangeWithdraw - FreezeBalance - GenerateAddress - GenerateShieldedTRC20Address - GetAccount - GetAccountNet - GetAccountResource - GetAddress - GetAkFromAsk - GetAssetIssueByAccount - GetAssetIssueById - GetAssetIssueByName - GetAssetIssueListByName - GetBalance - GetBlock - GetBlockById - GetBlockByLatestNum - GetBlockByLimitNext - GetChainParameters - GetContract contractAddress - GetDelegatedResource - GetDelegatedResourceAccountIndex - GetDiversifier - GetExchange - GetExpandedSpendingKey - GetIncomingViewingKey - GetNextMaintenanceTime - GetNkFromNsk - GetProposal - GetShieldedPaymentAddress - GetSpendingKey - GetTotalTransaction - GetTransactionApprovedList - GetTransactionById - GetTransactionCountByBlockNum - GetTransactionInfoByBlockNum - GetTransactionInfoById - GetTransactionsFromThis - GetTransactionsToThis - GetTransactionSignWeight - ImportShieldedTRC20Wallet - ImportWallet - ImportWalletByBase64 - ListAssetIssue - ListAssetIssuePaginated - ListExchanges - ListExchangesPaginated - ListNodes - ListShieldedTRC20Address - ListShieldedTRC20Note - ListProposals - ListProposalsPaginated - ListWitnesses - LoadShieldedTRC20Wallet - Login - Logout - ParticipateAssetIssue - RegisterWallet - ResetShieldedTRC20Note - ScanShieldedTRC20NoteByIvk - ScanShieldedTRC20NoteByOvk - SendCoin - SendShieldedTRC20Coin - SendShieldedTRC20CoinWithoutAsk - SetAccountId - SetShieldedTRC20ContractAddress - ShowShieldedTRC20AddressInfo - TransferAsset - TriggerContract - TriggerConstantContract - UnfreezeAsset - UnfreezeBalance - UpdateAccount - UpdateAsset - UpdateEnergyLimit - UpdateSetting - UpdateWitness - UpdateAccountPermission - VoteWitness - WithdrawBalance - Exit or Quit - -Type any one of the listed commands, to display how-to tips. From 13a18e18d8fa7ced89bcb18930558b9269e430ea Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Sun, 5 Jul 2020 23:21:33 +0800 Subject: [PATCH 332/445] fix style of README --- README.md | 243 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 140 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index ddc6f7b21..d9334b4a6 100644 --- a/README.md +++ b/README.md @@ -1528,8 +1528,11 @@ Example: 0eb458b309fa544066c40d80ce30a8002756c37d2716315c59a98c893dbb5f6a ``` -### GetExpandedSpendingKey sk +### GetExpandedSpendingKey +```console +> GetExpandedSpendingKey sk +``` Generate ask, nsk, ovk from sk Example: @@ -1541,7 +1544,11 @@ nsk:5cd2bc8d9468dbad26ea37c5335a0cd25f110eaf533248c59a3310dcbc03e503 ovk:892a10c1d3e8ea22242849e13f177d69e1180d1d5bba118c586765241ba2d3d6 ``` -### GetAkFromAsk ask +### GetAkFromAsk + +```console +> GetAkFromAsk ask +``` Generate ak from ask Example: @@ -1551,8 +1558,11 @@ Example: ak:f1b843147150027daa5b522dd8d0757ec5c8c146defd8e01b62b34cf917299f1 ``` -### GetNkFromNsk nsk +### GetNkFromNsk +```console +> GetNkFromNsk nsk +``` Generate nk from nsk Example: @@ -1562,8 +1572,11 @@ Example: nk:ed3dc885049f0a716a4de8c08c6cabcad0da3c437202341aa3d9248d8eb2b74a ``` -### GetIncomingViewingKey ak[64] nk[64] +### GetIncomingViewingKey +```console +> GetIncomingViewingKey ak[64] nk[64] +``` Generate ivk from ak and nk Example: @@ -1585,8 +1598,11 @@ Example: 11db4baf6bd5d5afd3a8b5 ``` -### GetShieldedPaymentAddress ivk[64] d[22] +### GetShieldedPaymentAddress +```console +> GetShieldedPaymentAddress ivk[64] d[22] +``` Generate a shielded address from ivk and d Example: @@ -1598,18 +1614,23 @@ pkd:65c11642115d386ed716b9cc06a3498e86e303d7f20d0869c9de90e31322ac15 shieldedAddress:ztron1z8d5htmt6h26l5agk4juz9jzz9wnsmkhz6uucp4rfx8gdccr6leq6zrfe80fpccny2kp2cray8z ``` -### SetShieldedTRC20ContractAddress TRC20ContractAddress ShieldedContractAddress +### SetShieldedTRC20ContractAddress + +```console +> SetShieldedTRC20ContractAddress TRC20ContractAddress ShieldedContractAddress +``` +TRC20ContractAddress +> TRC20 contract address + +ShieldedContractAddress +> Shielded contract address Set TRC20 contract address and shielded contract address. Please execute this command before you perform all the following operations related to the shielded transaction of TRC20 token except `ScanShieldedTRC20NoteByIvk` and `ScanShieldedTRC20NoteByOvk`. When you execute this command, the `Scaling Factor` will be shown. The `Scaling Factor` is set in the shielded contract. -TRC20ContractAddress -> TRC20 contract address -ShieldedContractAddress -> Shielded contract address Example: @@ -1635,13 +1656,16 @@ Please input your password for shieldedTRC20 wallet. LoadShieldedTRC20Wallet successful !!! ``` -### GenerateShieldedTRC20Address number - -Generate TRC20 shielded addresses. +### GenerateShieldedTRC20Address +```console +> GenerateShieldedTRC20Address number +``` number > The number of TRC20 shielded addresses, the default is 1. +Generate TRC20 shielded addresses. + Example: ```console @@ -1814,13 +1838,16 @@ In this example, the scalingFactor is 1000. Usage and parameters are consistent with the command SendShieldedTRC20Coin, the only difference is that SendShieldedTRC20Coin uses ask for signature, but SendShieldedTRC20CoinWithoutAsk uses ak. -### ListShieldedTRC20Note type - -List the note scanned by the local cache address, and the `Scaling Factor`. +### ListShieldedTRC20Note +```console +> ListShieldedTRC20Note type +``` type > Shows the type of note. If the variable is omitted or set to 0, it shows all unspent notes; For other values, it shows all the notes, including spent notes and unspent notes. +List the note scanned by the local cache address, and the `Scaling Factor`. + Example: ```console @@ -1858,9 +1885,11 @@ No matter you MINT, TRANSFER or BURN, the value must be an integer multiple of 1 Clean all the notes scanned, and rescan all blocks. Generally used when there is a problem with the notes or when switching environments. -### ScanShieldedTRC20NoteByIvk shieldedTRC20ContractAddress ivk ak nk startNum endNum [event1] [event2] ... +### ScanShieldedTRC20NoteByIvk -Scan notes by ivk, ak and nk. +```console +> ScanShieldedTRC20NoteByIvk shieldedTRC20ContractAddress ivk ak nk startNum endNum [event1] [event2] ... +``` shieldedTRC20ContractAddress > The address of shielded contract @@ -1883,60 +1912,63 @@ endNum event1/event2 > The events you want to scan. These events must be compatible with standard events, that is, MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), TransferNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) and BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]). If you ignore this field, the command will scan the standard events. In most cases, you can ignore these parameters. +Scan notes by ivk, ak and nk. + Example: ```console > ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 5400 [ - { - note: { - value: 100000 - payment_address: ztron12dq4ktrydrxzxrsgpmusp4pe0xawqyz4qfxzsgjdauw99n4n3efnw4kmrptlw8jcrrydx5694mw - rcm: a45878a4e0d53f5cac79370fea1bf4aa82c67d3b2f647ac89c2b1e7061ea740a - memo: without ask 2v1 - } - position: 10 - is_spent: true - tx_id: 5891fd3a8e860b336b7f7d31f64ec52ec5dc76f81b9bb4e4d0fa8a5756a61dd6 - } + { + note: { + value: 100000 + payment_address: + ztron12dq4ktrydrxzxrsgpmusp4pe0xawqyz4qfxzsgjdauw99n4n3efnw4kmrptlw8jcrrydx5694mw + rcm: a45878a4e0d53f5cac79370fea1bf4aa82c67d3b2f647ac89c2b1e7061ea740a + memo: without ask 2v1 + } + position: 10 + is_spent: true + tx_id: 5891fd3a8e860b336b7f7d31f64ec52ec5dc76f81b9bb4e4d0fa8a5756a61dd6 + } ] > ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 6000 MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) - [ - { - note: { - value: 100000 - payment_address: ztron1z8d5htmt6h26l5agk4ywv86xv3shuv4gjc2rzufyz4s2g5x0035nwrcqmxj4a49n2dy5sq28s5p - rcm: 07604b4a8018d353c08f93044df0fc04ef988c2f65f9222eacc8d41f0e095404 - memo: mint - } - position: 16 - is_spent: false - tx_id: 38d759216f62503c2b8bf7fc9777e6e25f5f77ec22dd760cc03057c4704277a2 - } - ] +[ + { + note: { + value: 100000 + payment_address: ztron1z8d5htmt6h26l5agk4ywv86xv3shuv4gjc2rzufyz4s2g5x0035nwrcqmxj4a49n2dy5sq28s5p + rcm: 07604b4a8018d353c08f93044df0fc04ef988c2f65f9222eacc8d41f0e095404 + memo: mint + } + position: 16 + is_spent: false + tx_id: 38d759216f62503c2b8bf7fc9777e6e25f5f77ec22dd760cc03057c4704277a2 + } +] > ScanShieldedTRC20NoteByIvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu fed8fa4714e6a19511760f9b8ed33388f14c626adff26034f4a21557cb928f01 faf63a2d959df05d4441c0fd42262e0a53629c532e8d29501fe94f9d86c51313 66458c23d737a30146533374d7c5c78f3e05f8f158192e8855493cc55cf8953f 5000 5400 BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) [ - { - note: { - value: 100000 - payment_address: ztron12dq4ktrydrxzxrsgpmusp4pe0xawqyz4qfxzsgjdauw99n4n3efnw4kmrptlw8jcrrydx5694mw - rcm: a45878a4e0d53f5cac79370fea1bf4aa82c67d3b2f647ac89c2b1e7061ea740a - memo: without ask 2v1 - } - position: 10 - is_spent: true - tx_id: 5891fd3a8e860b336b7f7d31f64ec52ec5dc76f81b9bb4e4d0fa8a5756a61dd6 - } + { + note: { + value: 100000 + payment_address: ztron12dq4ktrydrxzxrsgpmusp4pe0xawqyz4qfxzsgjdauw99n4n3efnw4kmrptlw8jcrrydx5694mw + rcm: a45878a4e0d53f5cac79370fea1bf4aa82c67d3b2f647ac89c2b1e7061ea740a + memo: without ask 2v1 + } + position: 10 + is_spent: true + tx_id: 5891fd3a8e860b336b7f7d31f64ec52ec5dc76f81b9bb4e4d0fa8a5756a61dd6 + } ] - ``` -## ScanShieldedTRC20NoteByOvk shieldedTRC20ContractAddress ovk startNum endNum [event1] [event2] ... - -Scan notes by ovk +## ScanShieldedTRC20NoteByOvk +```console +> ScanShieldedTRC20NoteByOvk shieldedTRC20ContractAddress ovk startNum endNum [event1] [event2] ... +``` shieldedTRC20ContractAddress > The address of shielded contract @@ -1953,61 +1985,63 @@ event1/event2 > The event you want to scan. These events must be compatible with standard events, that is, MintNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), TransferNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]), BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) and TokenBurn(address,uint256,bytes32[3]). If you ignore this field, the command will scan the standard events. +Scan notes by ovk + Example: ```console > ScanShieldedTRC20NoteByOvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu 4b33fc947a53a5e2a1d1636b323f7f6cecff8c34c9fc511ccc7cfaf0dd6f4c03 5000 6000 [ - { - note: { - value: 60000 - payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 - rcm: 50698dc3c97fb4d2c818b62de2265a271eb9a58b5dd65074122ddf4d794c6b03 - memo: 1 - } - tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 - } - { - note: { - value: 40000 - payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 - rcm: 94afb02c6fd4b19ada89b6b85e2cc23f2fb76c5188ede646c5046b2539a3bf00 - memo: 2 - } - tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 - } - { - transparent_to_address: TV7ceN4tHDNPB47DMStcUFC3Y8QQ7KzN32 - transparent_amount: 130000 - tx_id: d45da3394be6c15220d31ac17c13e02130aab0c3edf97750620538f4efae366b - } + { + note: { + value: 60000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 50698dc3c97fb4d2c818b62de2265a271eb9a58b5dd65074122ddf4d794c6b03 + memo: 1 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + note: { + value: 40000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 94afb02c6fd4b19ada89b6b85e2cc23f2fb76c5188ede646c5046b2539a3bf00 + memo: 2 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + transparent_to_address: TV7ceN4tHDNPB47DMStcUFC3Y8QQ7KzN32 + transparent_amount: 130000 + tx_id: d45da3394be6c15220d31ac17c13e02130aab0c3edf97750620538f4efae366b + } ] > ScanShieldedTRC20NoteByOvk TVqa39sqP8ZJNTWjtKrDRifGdVmA4Ycsxu 4b33fc947a53a5e2a1d1636b323f7f6cecff8c34c9fc511ccc7cfaf0dd6f4c03 5000 6000 BurnNewLeaf(uint256,bytes32,bytes32,bytes32,bytes32[21]) TokenBurn(address,uint256,bytes32[3]) [ - { - note: { - value: 60000 - payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 - rcm: 50698dc3c97fb4d2c818b62de2265a271eb9a58b5dd65074122ddf4d794c6b03 - memo: 1 - } - tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 - } - { - note: { - value: 40000 - payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 - rcm: 94afb02c6fd4b19ada89b6b85e2cc23f2fb76c5188ede646c5046b2539a3bf00 - memo: 2 - } - tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 - } - { - transparent_to_address: TV7ceN4tHDNPB47DMStcUFC3Y8QQ7KzN32 - transparent_amount: 130000 - tx_id: d45da3394be6c15220d31ac17c13e02130aab0c3edf97750620538f4efae366b - } + { + note: { + value: 60000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 50698dc3c97fb4d2c818b62de2265a271eb9a58b5dd65074122ddf4d794c6b03 + memo: 1 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + note: { + value: 40000 + payment_address: ztron1z8d5htmt6h26l5agk5nlxdlz66fahhcp8vwhyydrwfdajc5yalftew5uhwn6wjz4pwrxu0msu34 + rcm: 94afb02c6fd4b19ada89b6b85e2cc23f2fb76c5188ede646c5046b2539a3bf00 + memo: 2 + } + tx_id: 19c8aaa244dbcdf30a4b2a02b9b17054dc5d8ebf41d1f82daea044e65dff29d5 + } + { + transparent_to_address: TV7ceN4tHDNPB47DMStcUFC3Y8QQ7KzN32 + transparent_amount: 130000 + tx_id: d45da3394be6c15220d31ac17c13e02130aab0c3edf97750620538f4efae366b + } ] ``` @@ -2055,6 +2089,9 @@ ImportShieldedTRC20Wallet successfully !!! ### ShowShieldedTRC20AddressInfo +```console +> ShowShieldedTRC20AddressInfo address +``` Display information about shielded addresses. If this address is not in the wallet, it will only display `d` and `pkd` Example: From 4455befc6e0f576428ed905c0b6d3ebeb3ce4ccf Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Mon, 6 Jul 2020 10:27:41 +0800 Subject: [PATCH 333/445] remove redundant config --- src/main/resources/config-back.conf | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/main/resources/config-back.conf diff --git a/src/main/resources/config-back.conf b/src/main/resources/config-back.conf deleted file mode 100644 index 171520768..000000000 --- a/src/main/resources/config-back.conf +++ /dev/null @@ -1,20 +0,0 @@ -net { - type = mainnet -} - -fullnode = { - ip.list = [ - "47.89.189.124:50055", - "47.89.178.193:50055" - ] -} - -#soliditynode = { -# ip.list = [ -# "127.0.0.1:50052" -# ] -#} -crypto { - engine=sm2 -} -RPC_version = 2 From 87557c0429874fa8e20e6c519272db417249bde5 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Mon, 6 Jul 2020 19:51:03 +0800 Subject: [PATCH 334/445] decrease fee_limit when triggering the shielded contract --- src/main/java/org/tron/walletcli/WalletApiWrapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index ffb97aa02..9188dbd92 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1768,7 +1768,7 @@ public boolean setAllowance(String contractAddress, String shieldedContractAddre byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, true)); byte[] ownerAddress = wallet.getAddress(); - return callContract(ownerAddress, contractAddressBytes, 0, inputData, 1000_000_000L, + return callContract(ownerAddress, contractAddressBytes, 0, inputData, 20_000_000L, 0, "", false); } @@ -1790,7 +1790,7 @@ public boolean triggerShieldedContract(String contractAddress, String data, byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, data, true)); byte[] ownerAddress = wallet.getAddress(); - return callContract(ownerAddress, contractAddressBytes, 0, inputData, 1000_000_000L, + return callContract(ownerAddress, contractAddressBytes, 0, inputData, 20_000_000L, 0, "", false); } From 18b27053721bd275d756fd3a33b8525278d7b682 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 21 Jul 2020 14:59:59 +0800 Subject: [PATCH 335/445] typo: update document --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35bb8bd8c..0badd6c35 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ For more information on a specific command, just type the command on terminal wh | [ListExchanges](#How-to-trade-on-the-exchange) | [ListExchangesPaginated](#How-to-trade-on-the-exchange) | [ListNodes](#Some-others) | | [ListShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token) | [ListShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | [ListProposals](#How-to-initiate-a-proposal) | | [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | [LoadShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | -| [MarketCancelOrder](#How-to-use-tron-dex-to-sell-asset) | [MarketSellAsset](#How-to-use-tron-dex-to-sell-asset) | [Login](#Command-line-operation-flow-example) | +| [Login](#Command-line-operation-flow-example) | [MarketCancelOrder](#How-to-use-tron-dex-to-sell-asset) | [MarketSellAsset](#How-to-use-tron-dex-to-sell-asset)| | [ParticipateAssetIssue](#How-to-issue-TRC10-tokens) | [RegisterWallet](#Wallet-related-commands) | [ResetShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | | [ScanShieldedTRC20NoteByIvk](#How-to-transfer-shielded-TRC20-token) | [ScanShieldedTRC20NoteByOvk](#How-to-transfer-shielded-TRC20-token) |[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) | | [SendShieldedTRC20Coin](#How-to-transfer-shielded-TRC20-token) | [SendShieldedTRC20CoinWithoutAsk](#How-to-transfer-shielded-TRC20-token) | [SetShieldedTRC20ContractAddress](#How-to-transfer-shielded-TRC20-token) | From f18dd3b98e225110f722331f8d847c02d4e4519d Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 21 Jul 2020 15:16:33 +0800 Subject: [PATCH 336/445] feat(market): add related proto of market --- api/api.proto | 38 +++++++++++++++ core/Tron.proto | 73 +++++++++++++++++++++++++++++ core/contract/market_contract.proto | 20 ++++++++ 3 files changed, 131 insertions(+) create mode 100644 core/contract/market_contract.proto diff --git a/api/api.proto b/api/api.proto index 05f50196a..c79a6f0ce 100644 --- a/api/api.proto +++ b/api/api.proto @@ -13,6 +13,7 @@ import "core/contract/storage_contract.proto"; import "core/contract/exchange_contract.proto"; import "core/contract/smart_contract.proto"; import "core/contract/shield_contract.proto"; +import "core/contract/market_contract.proto"; option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file @@ -731,6 +732,29 @@ service Wallet { rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { } + + // for market + rpc MarketSellAsset (MarketSellAssetContract) returns (TransactionExtention) { + } + + rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { + } + + rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { + } + + rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { + } + + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + } + + rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { + } + + rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { + } + // end for market }; service WalletSolidity { @@ -899,6 +923,20 @@ service WalletSolidity { rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { } + rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { + } + + rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { + } + + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + } + + rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { + } + + rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { + } }; service WalletExtension { diff --git a/core/Tron.proto b/core/Tron.proto index 797273ce6..182461ccc 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -236,6 +236,13 @@ message ResourceReceipt { Transaction.Result.contractResult result = 7; } +message MarketOrderDetail { + bytes makerOrderId = 1; + bytes takerOrderId = 2; + int64 fillSellQuantity = 3; + int64 fillBuyQuantity = 4; +} + message Transaction { message Contract { enum ContractType { @@ -272,6 +279,8 @@ message Transaction { ClearABIContract = 48; UpdateBrokerageContract = 49; ShieldedTransferContract = 51; + MarketSellAssetContract = 60; + MarketCancelOrderContract = 61; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -368,6 +377,9 @@ message TransactionInfo { int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; int64 shielded_transaction_fee = 22; + + bytes orderId = 25; + repeated MarketOrderDetail orderDetails = 26; } message TransactionRet { @@ -629,3 +641,64 @@ message NodeInfo { } } } + +// market +message MarketOrder { + bytes order_id = 1; + bytes owner_address = 2; + int64 create_time = 3; + bytes sell_token_id = 4; + int64 sell_token_quantity = 5; + bytes buy_token_id = 6; + int64 buy_token_quantity = 7; // min to receive + int64 sell_token_quantity_remain = 9; + // When state != ACTIVE and sell_token_quantity_return !=0, + //it means that some sell tokens are returned to the account due to insufficient remaining amount + int64 sell_token_quantity_return = 10; + + enum State { + ACTIVE = 0; + INACTIVE = 1; + CANCELED = 2; + } + State state = 11; + + bytes prev = 12; + bytes next = 13; +} + +message MarketOrderList { + repeated MarketOrder orders = 1; +} + +message MarketOrderPairList { + repeated MarketOrderPair orderPair = 1; +} + +message MarketOrderPair{ + bytes sell_token_id = 1; + bytes buy_token_id = 2; +} + +message MarketAccountOrder { + bytes owner_address = 1; + repeated bytes orders = 2; // order_id list + int64 count = 3; // active count + int64 total_count = 4; +} + +message MarketPrice { + int64 sell_token_quantity = 1; + int64 buy_token_quantity = 2; +} + +message MarketPriceList { + bytes sell_token_id = 1; + bytes buy_token_id = 2; + repeated MarketPrice prices = 3; +} + +message MarketOrderIdList { + bytes head = 1; + bytes tail = 2; +} diff --git a/core/contract/market_contract.proto b/core/contract/market_contract.proto new file mode 100644 index 000000000..0ad035e73 --- /dev/null +++ b/core/contract/market_contract.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package protocol; + +option java_package = "org.tron.protos.contract"; //Specify the name of the package that generated the Java file +option go_package = "github.com/tronprotocol/grpc-gateway/core"; + +message MarketSellAssetContract { + bytes owner_address = 1; + bytes sell_token_id = 2; + int64 sell_token_quantity = 3; + bytes buy_token_id = 4; + int64 buy_token_quantity = 5; // min to receive + bytes pre_price_key = 6; // order price position +} + +message MarketCancelOrderContract { + bytes owner_address = 1; + bytes order_id = 2; +} \ No newline at end of file From 82b0adfbc97f1c415db34460a393c7fd0bcac87d Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 23 Jul 2020 16:23:10 +0800 Subject: [PATCH 337/445] feat: update market contract enum value --- src/main/protos/core/Tron.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 182461ccc..cec919247 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -279,8 +279,8 @@ message Transaction { ClearABIContract = 48; UpdateBrokerageContract = 49; ShieldedTransferContract = 51; - MarketSellAssetContract = 60; - MarketCancelOrderContract = 61; + MarketSellAssetContract = 52; + MarketCancelOrderContract = 53; } ContractType type = 1; google.protobuf.Any parameter = 2; From 0c1f01c6e52aa5b7dab140def1837f9ec6cc1f7d Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 23 Jul 2020 16:24:45 +0800 Subject: [PATCH 338/445] feat(market): update market contract enum value --- core/Tron.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Tron.proto b/core/Tron.proto index 182461ccc..cec919247 100644 --- a/core/Tron.proto +++ b/core/Tron.proto @@ -279,8 +279,8 @@ message Transaction { ClearABIContract = 48; UpdateBrokerageContract = 49; ShieldedTransferContract = 51; - MarketSellAssetContract = 60; - MarketCancelOrderContract = 61; + MarketSellAssetContract = 52; + MarketCancelOrderContract = 53; } ContractType type = 1; google.protobuf.Any parameter = 2; From ec67cac420446bf145138b27b6e711967a01f884 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 27 Jul 2020 11:35:11 +0800 Subject: [PATCH 339/445] change http format for market api --- .../java/org/tron/common/utils/HttpSelfFormatFieldName.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index 61ae52870..e1f5f3463 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -221,6 +221,9 @@ public class HttpSelfFormatFieldName { NameFieldNameMap.put("protocol.MarketSellAssetContract.sell_token_id", 1); NameFieldNameMap.put("protocol.MarketSellAssetContract.buy_token_id", 1); + // MarketCancelOrderContract + AddressFieldNameMap.put("protocol.MarketCancelOrderContract.owner_address", 1); + // MarketOrder AddressFieldNameMap.put("protocol.MarketOrder.owner_address", 1); NameFieldNameMap.put("protocol.MarketOrder.sell_token_id", 1); From a6603dc9a4d9027884ee3c51b64c9869aede3e3e Mon Sep 17 00:00:00 2001 From: renchenchang Date: Wed, 12 Aug 2020 11:26:20 +0800 Subject: [PATCH 340/445] parse bigInteger --- src/main/java/org/tron/common/utils/AbiUtil.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tron/common/utils/AbiUtil.java b/src/main/java/org/tron/common/utils/AbiUtil.java index d9b64bc0c..1610bf254 100644 --- a/src/main/java/org/tron/common/utils/AbiUtil.java +++ b/src/main/java/org/tron/common/utils/AbiUtil.java @@ -1,6 +1,7 @@ package org.tron.common.utils; import com.fasterxml.jackson.databind.ObjectMapper; +import java.math.BigInteger; import org.apache.commons.lang3.StringUtils; import org.spongycastle.util.encoders.Hex; import org.tron.common.crypto.Hash; @@ -125,9 +126,9 @@ static class CoderNumber extends Coder { @Override byte[] encode(String value) { - long n = Long.valueOf(value); - DataWord word = new DataWord(Math.abs(n)); - if (n < 0) { + BigInteger bigInteger = new BigInteger(value); + DataWord word = new DataWord(bigInteger.abs().toByteArray()); + if (bigInteger.compareTo(new BigInteger("0")) == -1) { word.negate(); } return word.getData(); From 37c329e3106508bee51d75e42e6d47d9472da2cc Mon Sep 17 00:00:00 2001 From: taihaofu Date: Mon, 17 Aug 2020 15:19:47 +0800 Subject: [PATCH 341/445] add get contract info api --- src/main/java/org/tron/walletcli/Client.java | 29 +++++++++++++++++++ .../org/tron/walletserver/GrpcClient.java | 7 +++++ .../java/org/tron/walletserver/WalletApi.java | 6 ++++ src/main/protos/api/api.proto | 3 ++ .../protos/core/contract/smart_contract.proto | 5 ++++ 5 files changed, 50 insertions(+) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 4752e47c9..061c8a298 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -56,6 +56,7 @@ import org.tron.protos.contract.SmartContractOuterClass.SmartContract; import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.walletserver.WalletApi; @@ -108,6 +109,7 @@ public class Client { "GetBrokerage", "GetChainParameters", "GetContract contractAddress", + "GetContractInfo contractAddress", "GetDelegatedResource", "GetDelegatedResourceAccountIndex", "GetDiversifier", @@ -237,6 +239,7 @@ public class Client { "GetBrokerage", "GetChainParameters", "GetContract", + "GetContractInfo", "GetDelegatedResource", "GetDelegatedResourceAccountIndex", "GetDiversifier", @@ -2237,6 +2240,28 @@ private void getContract(String[] parameters) { } } + private void getContractInfo(String[] parameters) { + if (parameters == null || + parameters.length != 1) { + System.out.println("Using getContractInfo needs 1 parameter like: "); + System.out.println("GetContractInfo contractAddress"); + return; + } + + byte[] addressBytes = WalletApi.decodeFromBase58Check(parameters[0]); + if (addressBytes == null) { + System.out.println("GetContractInfo: invalid address !!!"); + return; + } + + SmartContractDataWrapper contractDeployContract = WalletApi.getContractInfo(addressBytes); + if (contractDeployContract != null) { + System.out.println(Utils.formatMessageString(contractDeployContract)); + } else { + System.out.println("Query contract failed !!!"); + } + } + private void generateAddress() { AddressPrKeyPairMessage result = walletApiWrapper.generateAddress(); if (null != result) { @@ -4078,6 +4103,10 @@ private void run() { getContract(parameters); break; } + case "getcontractinfo": { + getContractInfo(parameters); + break; + } case "generateaddress": { generateAddress(); break; diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 7b0931f75..19b461209 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -62,6 +62,7 @@ import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.TransactionInfo; import org.tron.protos.Protocol.TransactionSign; +import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; @@ -838,6 +839,12 @@ public SmartContract getContract(byte[] address) { return blockingStubFull.getContract(bytesMessage); } + public SmartContractDataWrapper getContractInfo(byte[] address) { + ByteString byteString = ByteString.copyFrom(address); + BytesMessage bytesMessage = BytesMessage.newBuilder().setValue(byteString).build(); + return blockingStubFull.getContractInfo(bytesMessage); + } + public TransactionExtention accountPermissionUpdate( AccountPermissionUpdateContract request) { return blockingStubFull.accountPermissionUpdate(request); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 2857197ce..262ba50dc 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -108,6 +108,7 @@ import org.tron.protos.contract.ShieldContract.OutputPointInfo; import org.tron.protos.contract.ShieldContract.ShieldedTransferContract; import org.tron.protos.contract.ShieldContract.SpendDescription; +import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; import org.tron.protos.contract.StorageContract.BuyStorageBytesContract; import org.tron.protos.contract.StorageContract.BuyStorageContract; @@ -139,6 +140,7 @@ import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; import org.tron.protos.contract.MarketContract.MarketCancelOrderContract; import org.tron.protos.contract.MarketContract.MarketSellAssetContract; +import org.tron.protos.contract.MarketContract.MarketSellAssetContract; import java.io.File; import java.io.IOException; @@ -2208,6 +2210,10 @@ public static SmartContract getContract(byte[] address) { return rpcCli.getContract(address); } + public static SmartContractDataWrapper getContractInfo(byte[] address) { + return rpcCli.getContractInfo(address); + } + public boolean accountPermissionUpdate(byte[] owner, String permissionJson) throws CipherException, IOException, CancelException { AccountPermissionUpdateContract contract = diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 9d4cc25ce..c77bcac36 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -411,6 +411,9 @@ service Wallet { rpc GetContract (BytesMessage) returns (SmartContract) { } + rpc GetContractInfo (BytesMessage) returns (SmartContractDataWrapper) { + } + rpc TriggerContract (TriggerSmartContract) returns (TransactionExtention) { } diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto index 739abdfba..4c9952156 100644 --- a/src/main/protos/core/contract/smart_contract.proto +++ b/src/main/protos/core/contract/smart_contract.proto @@ -87,3 +87,8 @@ message UpdateEnergyLimitContract { bytes contract_address = 2; int64 origin_energy_limit = 3; } + +message SmartContractDataWrapper { + SmartContract smart_contract = 1; + bytes runtimecode = 2; +} From 1cf5047eb951206a38fbbdc33d48ab366a8cd57c Mon Sep 17 00:00:00 2001 From: renchenchang Date: Mon, 24 Aug 2020 17:53:36 +0800 Subject: [PATCH 342/445] split cmd --- src/main/java/org/tron/walletcli/Client.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 4752e47c9..e69a769dc 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3679,7 +3679,7 @@ public static String[] getCmd(String cmdLine) { || cmdLine.toLowerCase().startsWith("triggercontract") || cmdLine.toLowerCase().startsWith("triggerconstantcontract") || cmdLine.toLowerCase().startsWith("updateaccountpermission")) { - return cmdLine.split("\\s+"); + return cmdLine.split(" ", -1); } String[] strArray = cmdLine.split("\""); int num = strArray.length; From acc1a349f8ed52d75b6d433050dd7558307fc8aa Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Thu, 27 Aug 2020 11:43:55 +0800 Subject: [PATCH 343/445] optimize the logic of scanning blocks, and add a configure for shielded transfer in config.conf --- README.md | 13 ++++++++- .../tron/core/zen/ShieldedTRC20Wrapper.java | 27 ++++++++++++++----- src/main/resources/config.conf | 6 +++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d9334b4a6..8412bcd6d 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ soliditynode = { "solidity ip : port" ] } // NOTE: solidity node is optional + +blockNumberStartToScan = 22690588 // NOTE: this field is optional ``` ### Run a web wallet @@ -1514,7 +1516,14 @@ d :2fd028965d3b455579ab28 ## How to transfer shielded TRC20 token -when you begin to transfer TRC20 token to shielded address, you must have a shielded address. The +If you want to try to transfer shielded TRC20 token, you'd better set the `blockNumberStartToScan` field in `config.conf` file. +This field is used to set the starting block that the wallet needs to scan. If you ignore this field, or set it to 0, +the notes you receive will probably take a long time to show up in the wallet. It is recommended that this field is +set to the block number in which the earliest relevant shielded contract was created. If the exact number is not known, +this field can be set as follows. If used in mainnet, please set 22690588. If used in Nile testnet, please set 6380000. +Otherwise, please set 0. + +When you begin to transfer TRC20 token to shielded address, you must have a shielded address. The following commands help to generate shielded account. ### GetSpendingKey @@ -1848,6 +1857,8 @@ type List the note scanned by the local cache address, and the `Scaling Factor`. +**NOTE** When you load shielded wallet, the wallet will scan blocks to find the notes others send to you in the backend. This will take a long time, so when you run `ListShieldedTRC20Note`, your notes will not be displayed immediately. + Example: ```console diff --git a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java index 2839ed2e5..93a949a5a 100644 --- a/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java +++ b/src/main/java/org/tron/core/zen/ShieldedTRC20Wrapper.java @@ -1,6 +1,7 @@ package org.tron.core.zen; import com.google.protobuf.ByteString; +import com.typesafe.config.Config; import io.netty.util.internal.StringUtil; import lombok.Getter; import lombok.Setter; @@ -11,6 +12,7 @@ import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Utils; +import org.tron.core.config.Configuration; import org.tron.core.exception.CipherException; import org.tron.core.exception.ZksnarkException; import org.tron.core.zen.address.KeyIo; @@ -62,9 +64,22 @@ public class ShieldedTRC20Wrapper { @Getter @Setter public List spendUtxoList = new ArrayList<>(); + @Getter + @Setter + public static long defaultBlockNumberToScan = 0; private boolean loadShieldedStatus = false; + static { + Config config = Configuration.getByPath("config.conf"); + if (config.hasPath("blockNumberStartToScan")) { + try { + defaultBlockNumberToScan = config.getLong("blockNumberStartToScan"); + } catch (Exception e) { + } + } + } + private ShieldedTRC20Wrapper() { thread = new Thread(new scanIvkRunable()); } @@ -206,7 +221,7 @@ private void resetShieldedTRC20Note() throws ZksnarkException { byte[] key = ByteUtil.merge(entry.getValue().getIvk(), entry.getValue().getFullViewingKey().getAk(), entry.getValue().getFullViewingKey().getNk()); - ivkMapScanBlockNum.put(ByteArray.toHexString(key), 0L); + ivkMapScanBlockNum.put(ByteArray.toHexString(key), defaultBlockNumberToScan); } utxoMapNote.clear(); @@ -234,8 +249,8 @@ private void scanBlockByIvk() throws CipherException { long start = entry.getValue(); long end = start; while (end < blockNum) { - if (blockNum - start > 1000) { - end = start + 1000; + if (blockNum - start > 200) { // scan 200 blocks at a time + end = start + 200; } else { end = blockNum; } @@ -293,10 +308,10 @@ private void scanBlockByIvk() throws CipherException { } } start = end; + ivkMapScanBlockNum.put(entry.getKey(), start); + updateIvkAndBlockNumFile(); } - ivkMapScanBlockNum.put(entry.getKey(), blockNum); } - updateIvkAndBlockNumFile(); } } @@ -369,7 +384,7 @@ public boolean addNewShieldedTRC20Address(final ShieldedAddressInfo addressInfo, boolean newAddress) throws CipherException, ZksnarkException { appendAddressInfoToFile(addressInfo); - long blockNum = 0; + long blockNum = defaultBlockNumberToScan; if (newAddress) { try { Block block = WalletApi.getBlock(-1); diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index fcf33ca1d..c04671554 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -15,3 +15,9 @@ fullnode = { #} RPC_version = 2 + +# This field used in shielded transaction. It is recommended that this field is set to the block +# number in which the earliest relevant shielded contract was created. If the exact number is not +# known, this field can be set as follows. If used in mainnet, please set 22690588. If used in Nile +# testnet, please set 6380000. Otherwise, please set 0. +blockNumberStartToScan = 22690588 \ No newline at end of file From 1cc9113287bc169a74c046ef7cd593f44d9218d5 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Wed, 16 Sep 2020 12:27:56 +0800 Subject: [PATCH 344/445] support big integer in constructor when deploying contract --- src/main/java/org/tron/common/utils/AbiUtil.java | 7 ++++--- src/main/java/org/tron/walletcli/Client.java | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/tron/common/utils/AbiUtil.java b/src/main/java/org/tron/common/utils/AbiUtil.java index d9b64bc0c..1610bf254 100644 --- a/src/main/java/org/tron/common/utils/AbiUtil.java +++ b/src/main/java/org/tron/common/utils/AbiUtil.java @@ -1,6 +1,7 @@ package org.tron.common.utils; import com.fasterxml.jackson.databind.ObjectMapper; +import java.math.BigInteger; import org.apache.commons.lang3.StringUtils; import org.spongycastle.util.encoders.Hex; import org.tron.common.crypto.Hash; @@ -125,9 +126,9 @@ static class CoderNumber extends Coder { @Override byte[] encode(String value) { - long n = Long.valueOf(value); - DataWord word = new DataWord(Math.abs(n)); - if (n < 0) { + BigInteger bigInteger = new BigInteger(value); + DataWord word = new DataWord(bigInteger.abs().toByteArray()); + if (bigInteger.compareTo(new BigInteger("0")) == -1) { word.negate(); } return word.getData(); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index a7141f7c5..c3f7d30eb 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2094,7 +2094,7 @@ private void deployContract(String[] parameter) if (isHex) { codeStr += argsStr; } else { - codeStr += Hex.toHexString(AbiUtil.encodeInput(constructorStr, argsStr)); + codeStr += AbiUtil.parseMethod(constructorStr, argsStr); } } long value = 0; @@ -3498,7 +3498,7 @@ public static String[] getCmd(String cmdLine) { || cmdLine.toLowerCase().startsWith("triggercontract") || cmdLine.toLowerCase().startsWith("triggerconstantcontract") || cmdLine.toLowerCase().startsWith("updateaccountpermission")) { - return cmdLine.split("\\s+"); + return cmdLine.split(" ", -1); } String[] strArray = cmdLine.split("\""); int num = strArray.length; From e695301affa5b495f58a9caf7bd377923f3b079f Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 24 Sep 2020 16:39:19 +0800 Subject: [PATCH 345/445] typo: remove unused param --- src/main/protos/core/contract/market_contract.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/protos/core/contract/market_contract.proto b/src/main/protos/core/contract/market_contract.proto index 0ad035e73..ae36fc62a 100644 --- a/src/main/protos/core/contract/market_contract.proto +++ b/src/main/protos/core/contract/market_contract.proto @@ -11,7 +11,6 @@ message MarketSellAssetContract { int64 sell_token_quantity = 3; bytes buy_token_id = 4; int64 buy_token_quantity = 5; // min to receive - bytes pre_price_key = 6; // order price position } message MarketCancelOrderContract { From fa8dec7f2037e82b6cfba151bb07d7c5c680b827 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 24 Sep 2020 17:49:30 +0800 Subject: [PATCH 346/445] typo: remove unused param for dex --- src/main/protos/core/contract/market_contract.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/protos/core/contract/market_contract.proto b/src/main/protos/core/contract/market_contract.proto index 0ad035e73..ae36fc62a 100644 --- a/src/main/protos/core/contract/market_contract.proto +++ b/src/main/protos/core/contract/market_contract.proto @@ -11,7 +11,6 @@ message MarketSellAssetContract { int64 sell_token_quantity = 3; bytes buy_token_id = 4; int64 buy_token_quantity = 5; // min to receive - bytes pre_price_key = 6; // order price position } message MarketCancelOrderContract { From dd5f6bf1f1cd9c3619fb07cd512d889b8e42aa14 Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Fri, 30 Oct 2020 18:42:22 +0800 Subject: [PATCH 347/445] add the shielded trc20 tx demo --- .../java/org/tron/demo/ShieldedTRC20Demo.java | 477 ++++++++++++++++++ src/main/resources/config.conf | 14 +- 2 files changed, 484 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/tron/demo/ShieldedTRC20Demo.java diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java new file mode 100644 index 000000000..1564760a7 --- /dev/null +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -0,0 +1,477 @@ +package org.tron.demo; + +import com.google.protobuf.ByteString; +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.spongycastle.util.encoders.Hex; +import org.tron.api.GrpcAPI; +import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.TransactionExtention; +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.Sha256Sm3Hash; +import org.tron.common.utils.AbiUtil; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.Hash; +import org.tron.common.utils.TransactionUtils; +import org.tron.core.config.Parameter.CommonConstant; +import org.tron.core.exception.ZksnarkException; +import org.tron.core.zen.address.DiversifierT; +import org.tron.core.zen.address.ExpandedSpendingKey; +import org.tron.core.zen.address.FullViewingKey; +import org.tron.core.zen.address.IncomingViewingKey; +import org.tron.core.zen.address.KeyIo; +import org.tron.core.zen.address.PaymentAddress; +import org.tron.core.zen.address.SpendingKey; +import org.tron.protos.Protocol; +import org.tron.protos.Protocol.Transaction; +import org.tron.protos.Protocol.Transaction.Result; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.contract.SmartContractOuterClass; +import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.walletserver.GrpcClient; +import org.tron.walletserver.WalletApi; + +@Slf4j +public class ShieldedTRC20Demo { + + private static String fullnode = "34.220.77.106:50051"; + private static String soliditynode = "52.15.93.92:50061"; + private static String trc20ContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; + private static String shieldedTRC20ContractAddress = "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"; + private static String privateKey = + "f4cc9a4bcd6a2167e734d0e3de349f93c3a0210fcf6c2390f0e20bc43832a13b"; + private static String pubAddress = "TCuT4T3xhrSuScmhbVuWf3ZAvRnMXvoqC6"; + private static String spendingKey = "004f74ce2bde08f0c936f2929b94cb2ca49111db95001576f99d04c3e671daf6"; + private static GrpcClient grpcClient = new GrpcClient(fullnode, soliditynode); + private static BigInteger scalingFactorBi = getScalingFactorBi(); + + public static void main(String[] args) throws ZksnarkException, InterruptedException { + + mintDemo(); + transferDemo(); + burnDemo(); + } + + private static String mintDemo() throws ZksnarkException { + + SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + + byte[] ovk = expsk.getOvk(); + long fromAmount = 10; + FullViewingKey fullViewingKey = sk.fullViewingKey(); + IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); + PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); + + //set approve + setAllowance(fromAmount); + //ReceiveNote + GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); + long revValue = fromAmount; + byte[] memo = new byte[512]; + byte[] rcm = grpcClient.getRcm().getValue().toByteArray(); + GrpcAPI.Note revNote = getNote(revValue, KeyIo.encodePaymentAddress(paymentAddress), rcm, memo); + revNoteBuilder.setNote(revNote); + + byte[] contractAddress = WalletApi.decodeFromBase58Check(shieldedTRC20ContractAddress); + GrpcAPI.PrivateShieldedTRC20Parameters.Builder paramBuilder = GrpcAPI + .PrivateShieldedTRC20Parameters.newBuilder(); + paramBuilder.setOvk(ByteString.copyFrom(ovk)); + paramBuilder.setFromAmount(getScaledPublicAmount(fromAmount)); + paramBuilder.addShieldedReceives(revNoteBuilder.build()); + paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + + GrpcAPI.ShieldedTRC20Parameters trc20MintParams = grpcClient + .createShieldedContractParameters(paramBuilder.build()); + byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); + return triggerMint(contractAddress, callerAddress, privateKey, + trc20MintParams.getTriggerContractInput()); + } + + public static void transferDemo() throws ZksnarkException, InterruptedException { + byte[] contractAddress = WalletApi + .decodeFromBase58Check(shieldedTRC20ContractAddress); + byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); + SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); + setAllowance(10); + GrpcAPI.PrivateShieldedTRC20Parameters mintPrivateParam1 = mintParams( + privateKey, 10, shieldedTRC20ContractAddress); + GrpcAPI.ShieldedTRC20Parameters mintParam1 = grpcClient.createShieldedContractParameters( + mintPrivateParam1); + String mintInput = mintParam1.getTriggerContractInput(); + String txid = triggerMint(contractAddress, callerAddress, privateKey, mintInput); + + // SpendNoteTRC20 1 + logger.info("mint txid: " + txid); + Optional infoById = grpcClient.getTransactionInfoById(txid); + while (infoById.get().getLogList().size() < 2) { + logger.info("Can not get transaction info, please wait...."); + Thread.sleep(5000); + infoById = grpcClient.getTransactionInfoById(txid); + } + byte[] txData = infoById.get().getLog(1).getData().toByteArray(); + long pos = bytes32Tolong(ByteArray.subArray(txData, 0, 32)); + byte[] contractResult = triggerGetPath(contractAddress, pos); + byte[] path = ByteArray.subArray(contractResult, 32, 1056); + byte[] root = ByteArray.subArray(contractResult, 0, 32); + GrpcAPI.SpendNoteTRC20.Builder note1Builder = GrpcAPI.SpendNoteTRC20.newBuilder(); + note1Builder.setAlpha(ByteString.copyFrom(grpcClient.getRcm().getValue().toByteArray())); + note1Builder.setPos(pos); + note1Builder.setPath(ByteString.copyFrom(path)); + note1Builder.setRoot(ByteString.copyFrom(root)); + note1Builder.setNote(mintPrivateParam1.getShieldedReceives(0).getNote()); + GrpcAPI.PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = GrpcAPI + .PrivateShieldedTRC20Parameters.newBuilder(); + privateTRC20Builder.addShieldedSpends(note1Builder.build()); + + //ReceiveNote 1 + GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); + FullViewingKey fullViewingKey = sk.fullViewingKey(); + IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); + PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); + long revValue = 10; + byte[] memo = new byte[512]; + byte[] rcm = grpcClient.getRcm().getValue().toByteArray(); + String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); + GrpcAPI.Note revNote = getNote(revValue, paymentAddressStr, rcm, memo); + revNoteBuilder.setNote(revNote); + privateTRC20Builder.addShieldedReceives(revNoteBuilder.build()); + + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); + privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); + privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); + privateTRC20Builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + GrpcAPI.ShieldedTRC20Parameters transferParam = grpcClient + .createShieldedContractParameters(privateTRC20Builder.build()); + triggerTransfer(contractAddress, callerAddress, privateKey, + transferParam.getTriggerContractInput()); + } + + public static void burnDemo() throws ZksnarkException, InterruptedException { + byte[] contractAddress = WalletApi + .decodeFromBase58Check(shieldedTRC20ContractAddress); + byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); + SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); + GrpcAPI.PrivateShieldedTRC20Parameters mintPrivateParam1 = mintParams( + privateKey, 10, shieldedTRC20ContractAddress); + setAllowance(10); + GrpcAPI.ShieldedTRC20Parameters mintParam1 = grpcClient.createShieldedContractParameters( + mintPrivateParam1); + long value = 10; + String mintInput1 = mintParam1.getTriggerContractInput(); + String txid = triggerMint(contractAddress, callerAddress, privateKey, mintInput1); + + // SpendNoteTRC20 1 + Optional infoById = grpcClient.getTransactionInfoById(txid); + while (infoById.get().getLogList().size() < 2) { + logger.info("Can not get transaction info, please wait...."); + Thread.sleep(5000); + infoById = grpcClient.getTransactionInfoById(txid); + } + byte[] tx1Data = infoById.get().getLog(1).getData().toByteArray(); + long pos = bytes32Tolong(ByteArray.subArray(tx1Data, 0, 32)); + byte[] contractResult = triggerGetPath(contractAddress, pos); + byte[] path = ByteArray.subArray(contractResult, 32, 1056); + byte[] root = ByteArray.subArray(contractResult, 0, 32); + GrpcAPI.SpendNoteTRC20.Builder note1Builder = GrpcAPI.SpendNoteTRC20.newBuilder(); + note1Builder.setAlpha(ByteString.copyFrom(grpcClient.getRcm().getValue().toByteArray())); + note1Builder.setPos(pos); + note1Builder.setPath(ByteString.copyFrom(path)); + note1Builder.setRoot(ByteString.copyFrom(root)); + note1Builder.setNote(mintPrivateParam1.getShieldedReceives(0).getNote()); + GrpcAPI.PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = GrpcAPI + .PrivateShieldedTRC20Parameters.newBuilder(); + privateTRC20Builder.addShieldedSpends(note1Builder.build()); + + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); + privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); + privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); + BigInteger toAmount = BigInteger.valueOf(10).multiply(scalingFactorBi); + privateTRC20Builder.setToAmount(toAmount.toString()); + privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(callerAddress)); + privateTRC20Builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + GrpcAPI.ShieldedTRC20Parameters burnParam = grpcClient.createShieldedContractParameters(privateTRC20Builder.build()); + + triggerBurn(contractAddress, callerAddress, privateKey, + burnParam.getTriggerContractInput()); + } + + private static GrpcAPI.Note getNote(long value, String paymentAddress, byte[] rcm, byte[] memo) { + GrpcAPI.Note.Builder noteBuilder = GrpcAPI.Note.newBuilder(); + noteBuilder.setValue(value); + noteBuilder.setPaymentAddress(paymentAddress); + noteBuilder.setRcm(ByteString.copyFrom(rcm)); + noteBuilder.setMemo(ByteString.copyFrom(memo)); + return noteBuilder.build(); + } + + private static String triggerMint(byte[] contractAddress, + byte[] callerAddress, String privateKey, String input) { + String methodSign = "mint(uint256,bytes32[9],bytes32[2],bytes32[21])"; + byte[] selector = new byte[4]; + System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); + return triggerContract(contractAddress, + "mint(uint256,bytes32[9],bytes32[2],bytes32[21])", + input, + true, + 0L, 10000000L, + "0", 0, + callerAddress, privateKey); + } + + private static String triggerTransfer( + byte[] contractAddress, + byte[] callerAddress, String privateKey, String input) { + String txid = triggerContract(contractAddress, + "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])", + input, + true, + 0L, 1000000000L, + "0", + 0, + callerAddress, privateKey); + Optional infoById = grpcClient.getTransactionInfoById(txid); + return txid; + } + + private static String triggerBurn(byte[] contractAddress, + byte[] callerAddress, String privateKey, String input) { + return triggerContract(contractAddress, + "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address,bytes32[3],bytes32[9][]," + + "bytes32[21][])", + input, + true, + 0L, 1000000000L, + "0", + 0, + callerAddress, privateKey); + } + + + private static String triggerContract(byte[] contractAddress, String method, String argsStr, + Boolean isHex, long callValue, long feeLimit, String tokenId, long tokenValue, + byte[] ownerAddress, + String priKey) { + WalletApi.setAddressPreFixByte(CommonConstant.ADD_PRE_FIX_BYTE_MAINNET); + ECKey temKey = null; + try { + BigInteger priK = new BigInteger(priKey, 16); + temKey = ECKey.fromPrivate(priK); + } catch (Exception ex) { + ex.printStackTrace(); + } + final ECKey ecKey = temKey; + if (argsStr.equalsIgnoreCase("#")) { + logger.info("argsstr is #"); + argsStr = ""; + } + + byte[] owner = ownerAddress; + byte[] input = Hex.decode(AbiUtil.parseMethod(method, argsStr, isHex)); + TriggerSmartContract.Builder builder = TriggerSmartContract.newBuilder(); + builder.setOwnerAddress(ByteString.copyFrom(owner)); + builder.setContractAddress(ByteString.copyFrom(contractAddress)); + builder.setData(ByteString.copyFrom(input)); + builder.setCallValue(callValue); + builder.setTokenId(Long.parseLong(tokenId)); + builder.setCallTokenValue(tokenValue); + TriggerSmartContract triggerContract = builder.build(); + + TransactionExtention transactionExtention = grpcClient.triggerContract(triggerContract); + if (transactionExtention == null || !transactionExtention.getResult().getResult()) { + System.out.println("RPC create call trx failed!"); + System.out.println("Code = " + transactionExtention.getResult().getCode()); + System.out + .println("Message = " + transactionExtention.getResult().getMessage().toStringUtf8()); + return null; + } + Transaction transaction = transactionExtention.getTransaction(); + if (transaction.getRetCount() != 0 + && transactionExtention.getConstantResult(0) != null + && transactionExtention.getResult() != null) { + byte[] result = transactionExtention.getConstantResult(0).toByteArray(); + System.out.println("message:" + transaction.getRet(0).getRet()); + System.out.println(":" + ByteArray + .toStr(transactionExtention.getResult().getMessage().toByteArray())); + System.out.println("Result:" + Hex.toHexString(result)); + return null; + } + + final TransactionExtention.Builder texBuilder = TransactionExtention.newBuilder(); + Transaction.Builder transBuilder = Transaction.newBuilder(); + Transaction.raw.Builder rawBuilder = transactionExtention.getTransaction().getRawData() + .toBuilder(); + rawBuilder.setFeeLimit(feeLimit); + + transBuilder.setRawData(rawBuilder); + for (int i = 0; i < transactionExtention.getTransaction().getSignatureCount(); i++) { + ByteString s = transactionExtention.getTransaction().getSignature(i); + transBuilder.setSignature(i, s); + } + for (int i = 0; i < transactionExtention.getTransaction().getRetCount(); i++) { + Result r = transactionExtention.getTransaction().getRet(i); + transBuilder.setRet(i, r); + } + texBuilder.setTransaction(transBuilder); + texBuilder.setResult(transactionExtention.getResult()); + texBuilder.setTxid(transactionExtention.getTxid()); + + transactionExtention = texBuilder.build(); + if (transactionExtention == null) { + return null; + } + Return ret = transactionExtention.getResult(); + if (!ret.getResult()) { + System.out.println("Code = " + ret.getCode()); + System.out.println("Message = " + ret.getMessage().toStringUtf8()); + return null; + } + transaction = transactionExtention.getTransaction(); + if (transaction == null || transaction.getRawData().getContractCount() == 0) { + System.out.println("Transaction is empty"); + return null; + } + transaction = signTransaction(ecKey, transaction); + String txid = ByteArray.toHexString(Sha256Sm3Hash.hash( + transaction.getRawData().toByteArray())); + System.out.println("trigger txid = " + txid); + grpcClient.broadcastTransaction(transaction); + return txid; + } + + public static Protocol.Transaction signTransaction(ECKey ecKey, + Protocol.Transaction transaction) { + WalletApi.setAddressPreFixByte(CommonConstant.ADD_PRE_FIX_BYTE_MAINNET); + if (ecKey == null || ecKey.getPrivKey() == null) { + //logger.warn("Warning: Can't sign,there is no private key !!"); + return null; + } + transaction = TransactionUtils.setTimestamp(transaction); + logger.info("Txid in sign is " + ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); + return TransactionUtils.sign(transaction, ecKey); + } + + private static BigInteger getScalingFactorBi() { + byte[] contractAddress = WalletApi + .decodeFromBase58Check(shieldedTRC20ContractAddress); + byte[] scalingFactorBytes = triggerGetScalingFactor(contractAddress); + return ByteUtil.bytesToBigInteger(scalingFactorBytes); + } + + private static byte[] triggerGetScalingFactor( + byte[] contractAddress) { + String methodSign = "scalingFactor()"; + byte[] selector = new byte[4]; + System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); + SmartContractOuterClass.TriggerSmartContract.Builder triggerBuilder = SmartContractOuterClass + .TriggerSmartContract.newBuilder(); + triggerBuilder.setContractAddress(ByteString.copyFrom(contractAddress)); + triggerBuilder.setData(ByteString.copyFrom(selector)); + GrpcAPI.TransactionExtention trxExt2 = grpcClient.triggerConstantContract( + triggerBuilder.build()); + List list = trxExt2.getConstantResultList(); + byte[] result = new byte[0]; + for (ByteString bs : list) { + result = ByteUtil.merge(result, bs.toByteArray()); + } + Assert.assertEquals(32, result.length); + System.out.println(ByteArray.toHexString(result)); + return result; + } + + private static String getScaledPublicAmount(long amount) { + BigInteger result = BigInteger.valueOf(amount).multiply(scalingFactorBi); + return result.toString(); + } + + + private static void setAllowance(long amount) { + byte[] contractAddress = WalletApi + .decodeFromBase58Check(trc20ContractAddress); + byte[] shieldedContractAddress = WalletApi + .decodeFromBase58Check(shieldedTRC20ContractAddress); + byte[] shieldedContractAddressPadding = new byte[32]; + System.arraycopy(shieldedContractAddress, 0, shieldedContractAddressPadding, 11, 21); + byte[] valueBytes = longTo32Bytes(amount); + String input = Hex.toHexString(ByteUtil.merge(shieldedContractAddressPadding, valueBytes)); + byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); + String txid = triggerContract(contractAddress, + "approve(address,uint256)", + input, + true, + 0L, + 10000000L, + "0", + 0, + callerAddress, + privateKey); + } + + private static GrpcAPI.PrivateShieldedTRC20Parameters mintParams(String privKey, + long value, String contractAddr) + throws ZksnarkException { + BigInteger fromAmount = BigInteger.valueOf(value).multiply(scalingFactorBi); + SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + byte[] ask = expsk.getAsk(); + byte[] nsk = expsk.getNsk(); + byte[] ovk = expsk.getOvk(); + + // ReceiveNote + GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); + // SpendingKey spendingKey = SpendingKey.random(); + FullViewingKey fullViewingKey = sk.fullViewingKey(); + IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); + PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); + byte[] memo = new byte[512]; + byte[] rcm = grpcClient.getRcm().getValue().toByteArray(); + String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); + GrpcAPI.Note revNote = getNote(value, paymentAddressStr, rcm, memo); + revNoteBuilder.setNote(revNote); + byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddr); + + GrpcAPI.PrivateShieldedTRC20Parameters.Builder paramBuilder = GrpcAPI + .PrivateShieldedTRC20Parameters.newBuilder(); + paramBuilder.setAsk(ByteString.copyFrom(ask)); + paramBuilder.setNsk(ByteString.copyFrom(nsk)); + paramBuilder.setOvk(ByteString.copyFrom(ovk)); + paramBuilder.setFromAmount(fromAmount.toString()); + paramBuilder.addShieldedReceives(revNoteBuilder.build()); + paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + return paramBuilder.build(); + } + + private static byte[] triggerGetPath(byte[] contractAddress, long pos) { + String methodSign = "getPath(uint256)"; + byte[] selector = new byte[4]; + System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); + SmartContractOuterClass.TriggerSmartContract.Builder triggerBuilder = SmartContractOuterClass + .TriggerSmartContract.newBuilder(); + triggerBuilder.setContractAddress(ByteString.copyFrom(contractAddress)); + byte[] input = ByteUtil.merge(selector, longTo32Bytes(pos)); + triggerBuilder.setData(ByteString.copyFrom(input)); + + GrpcAPI.TransactionExtention transactionExtention = grpcClient.triggerConstantContract( + triggerBuilder.build()); + Assert.assertEquals(0, transactionExtention.getResult().getCodeValue()); + byte[] result = transactionExtention.getConstantResult(0).toByteArray(); + Assert.assertEquals(1056, result.length); + return result; + } + + private static byte[] longTo32Bytes(long value) { + byte[] longBytes = ByteArray.fromLong(value); + byte[] zeroBytes = new byte[24]; + return ByteUtil.merge(zeroBytes, longBytes); + } + + private static long bytes32Tolong(byte[] value) { + return ByteArray.toLong(value); + } +} diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index c04671554..40adf6cd2 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,15 +4,15 @@ net { fullnode = { ip.list = [ - "127.0.0.1:50051" + "34.220.77.106:50051" ] } -#soliditynode = { -# ip.list = [ -# "127.0.0.1:50052" -# ] -#} +soliditynode = { + ip.list = [ + "52.15.93.92:50061" + ] +} RPC_version = 2 @@ -20,4 +20,4 @@ RPC_version = 2 # number in which the earliest relevant shielded contract was created. If the exact number is not # known, this field can be set as follows. If used in mainnet, please set 22690588. If used in Nile # testnet, please set 6380000. Otherwise, please set 0. -blockNumberStartToScan = 22690588 \ No newline at end of file +blockNumberStartToScan = 22690588 From 4d588a071e0bd304b205c5e81bd7d62a2ed6e102 Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Fri, 30 Oct 2020 18:53:14 +0800 Subject: [PATCH 348/445] modify the config file to default --- src/main/resources/config.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index 40adf6cd2..ac656b581 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -4,15 +4,15 @@ net { fullnode = { ip.list = [ - "34.220.77.106:50051" + "127.0.0.1:50051" ] } -soliditynode = { - ip.list = [ - "52.15.93.92:50061" - ] -} +#soliditynode = { +# ip.list = [ +# "127.0.0.1:50052" +# ] +#} RPC_version = 2 From 43c2618b756f97121b2b2b40d9d4ccf0c22ded1d Mon Sep 17 00:00:00 2001 From: running-tomato <31307926+guoquanwu@users.noreply.github.com> Date: Mon, 2 Nov 2020 09:22:43 +0800 Subject: [PATCH 349/445] Hotfix/update config (#385) * update config * update config * update to support pBFT --- README.md | 6 +++++- src/main/resources/config.conf | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 96c142ad9..6585b1368 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,13 @@ fullnode = { } soliditynode = { + // the IPs in this list can only be totally set to solidity or pBFT. ip.list = [ - "solidity ip : port" + "ip : solidity port" // default solidity ] +# ip.list = [ +# "ip : pBFT port" // or pBFT +# ] } // NOTE: solidity node is optional blockNumberStartToScan = 22690588 // NOTE: this field is optional diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index c04671554..89699034c 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -9,8 +9,12 @@ fullnode = { } #soliditynode = { +# // the IPs in this list can only be totally set to solidity or pBFT. # ip.list = [ -# "127.0.0.1:50052" +# "127.0.0.1:50052" // default solidity +# ] +# ip.list = [ +# "127.0.0.1:50071" // or pBFT # ] #} From dc701d95055dae5b048bdb8ff54c7320ca0fbb30 Mon Sep 17 00:00:00 2001 From: neo hong Date: Fri, 13 Nov 2020 17:58:00 +0800 Subject: [PATCH 350/445] add receive type --- src/main/java/org/tron/walletserver/WalletApi.java | 11 +++++++---- src/main/protos/core/contract/smart_contract.proto | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 2857197ce..6e7c147eb 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -1693,6 +1693,8 @@ public static SmartContract.ABI.Entry.EntryType getEntryType(String type) { return SmartContract.ABI.Entry.EntryType.Event; case "fallback": return SmartContract.ABI.Entry.EntryType.Fallback; + case "receive": + return SmartContract.ABI.Entry.EntryType.Receive; default: return SmartContract.ABI.Entry.EntryType.UNRECOGNIZED; } @@ -1767,11 +1769,12 @@ public static SmartContract.ABI jsonStr2ABI(String jsonStr) { System.out.println("No type!"); return null; } - if (!type.equalsIgnoreCase("fallback") && null == inputs) { - System.out.println("No inputs!"); - return null; + if(inputs == null) { + if(!(type.equalsIgnoreCase("fallback") || type.equalsIgnoreCase("receive"))) { + logger.error("No inputs!"); + return null; + } } - SmartContract.ABI.Entry.Builder entryBuilder = SmartContract.ABI.Entry.newBuilder(); entryBuilder.setAnonymous(anonymous); entryBuilder.setConstant(constant); diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto index 739abdfba..967c90046 100644 --- a/src/main/protos/core/contract/smart_contract.proto +++ b/src/main/protos/core/contract/smart_contract.proto @@ -17,6 +17,7 @@ message SmartContract { Function = 2; Event = 3; Fallback = 4; + Receive = 5; } message Param { bool indexed = 1; From 7cdac872816e424d38528bf7b7bfdf9c09d8aac2 Mon Sep 17 00:00:00 2001 From: neo hong Date: Mon, 16 Nov 2020 11:43:57 +0800 Subject: [PATCH 351/445] support call receive by input # --- src/main/java/org/tron/walletcli/Client.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index f6989a944..f04bf8e80 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2198,7 +2198,10 @@ private void triggerContract(String[] parameters, boolean isConstant) if (tokenId.equalsIgnoreCase("#")) { tokenId = ""; } - byte[] input = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, isHex)); + byte[] input = new byte[0]; + if (!methodStr.equalsIgnoreCase("#")) { + input = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, isHex)); + } byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddrStr); boolean result = walletApiWrapper From 5f67f5f9f55f06f71ea708dbf1080e7f8a9d8e3b Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Mon, 16 Nov 2020 16:44:34 +0800 Subject: [PATCH 352/445] modify the fullnode config for shielded trc20 demo --- .../java/org/tron/demo/ShieldedTRC20Demo.java | 134 ++++++++++-------- 1 file changed, 71 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index 1564760a7..da79b1212 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -8,7 +8,9 @@ import org.junit.Assert; import org.spongycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; +import org.tron.api.GrpcAPI.PrivateShieldedTRC20Parameters; import org.tron.api.GrpcAPI.Return; +import org.tron.api.GrpcAPI.SpendNoteTRC20; import org.tron.api.GrpcAPI.TransactionExtention; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Sha256Sm3Hash; @@ -38,15 +40,13 @@ @Slf4j public class ShieldedTRC20Demo { - private static String fullnode = "34.220.77.106:50051"; - private static String soliditynode = "52.15.93.92:50061"; private static String trc20ContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; private static String shieldedTRC20ContractAddress = "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"; private static String privateKey = - "f4cc9a4bcd6a2167e734d0e3de349f93c3a0210fcf6c2390f0e20bc43832a13b"; - private static String pubAddress = "TCuT4T3xhrSuScmhbVuWf3ZAvRnMXvoqC6"; + "2c8893287a87ac9f4b70af14fbae75e5c898e3b6645e5fed311f5fe60b2dff2f"; + private static String pubAddress = "TXmiKi5UZ6Pqe22aW5R8LEcNGGpgh2BNMH"; private static String spendingKey = "004f74ce2bde08f0c936f2929b94cb2ca49111db95001576f99d04c3e671daf6"; - private static GrpcClient grpcClient = new GrpcClient(fullnode, soliditynode); + private static GrpcClient grpcClient = WalletApi.init(); private static BigInteger scalingFactorBi = getScalingFactorBi(); public static void main(String[] args) throws ZksnarkException, InterruptedException { @@ -60,9 +60,8 @@ private static String mintDemo() throws ZksnarkException { SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); ExpandedSpendingKey expsk = sk.expandedSpendingKey(); - byte[] ovk = expsk.getOvk(); - long fromAmount = 10; + long fromAmount = 1; FullViewingKey fullViewingKey = sk.fullViewingKey(); IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); @@ -73,7 +72,7 @@ private static String mintDemo() throws ZksnarkException { GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); long revValue = fromAmount; byte[] memo = new byte[512]; - byte[] rcm = grpcClient.getRcm().getValue().toByteArray(); + byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); GrpcAPI.Note revNote = getNote(revValue, KeyIo.encodePaymentAddress(paymentAddress), rcm, memo); revNoteBuilder.setNote(revNote); @@ -85,7 +84,7 @@ private static String mintDemo() throws ZksnarkException { paramBuilder.addShieldedReceives(revNoteBuilder.build()); paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); - GrpcAPI.ShieldedTRC20Parameters trc20MintParams = grpcClient + GrpcAPI.ShieldedTRC20Parameters trc20MintParams = WalletApi .createShieldedContractParameters(paramBuilder.build()); byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); return triggerMint(contractAddress, callerAddress, privateKey, @@ -97,56 +96,52 @@ public static void transferDemo() throws ZksnarkException, InterruptedException .decodeFromBase58Check(shieldedTRC20ContractAddress); byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); - setAllowance(10); + setAllowance(2); GrpcAPI.PrivateShieldedTRC20Parameters mintPrivateParam1 = mintParams( - privateKey, 10, shieldedTRC20ContractAddress); - GrpcAPI.ShieldedTRC20Parameters mintParam1 = grpcClient.createShieldedContractParameters( + privateKey, 2, shieldedTRC20ContractAddress); + GrpcAPI.ShieldedTRC20Parameters mintParam1 = WalletApi.createShieldedContractParameters( mintPrivateParam1); String mintInput = mintParam1.getTriggerContractInput(); String txid = triggerMint(contractAddress, callerAddress, privateKey, mintInput); - // SpendNoteTRC20 1 - logger.info("mint txid: " + txid); - Optional infoById = grpcClient.getTransactionInfoById(txid); - while (infoById.get().getLogList().size() < 2) { - logger.info("Can not get transaction info, please wait...."); - Thread.sleep(5000); - infoById = grpcClient.getTransactionInfoById(txid); - } - byte[] txData = infoById.get().getLog(1).getData().toByteArray(); - long pos = bytes32Tolong(ByteArray.subArray(txData, 0, 32)); - byte[] contractResult = triggerGetPath(contractAddress, pos); - byte[] path = ByteArray.subArray(contractResult, 32, 1056); - byte[] root = ByteArray.subArray(contractResult, 0, 32); - GrpcAPI.SpendNoteTRC20.Builder note1Builder = GrpcAPI.SpendNoteTRC20.newBuilder(); - note1Builder.setAlpha(ByteString.copyFrom(grpcClient.getRcm().getValue().toByteArray())); - note1Builder.setPos(pos); - note1Builder.setPath(ByteString.copyFrom(path)); - note1Builder.setRoot(ByteString.copyFrom(root)); - note1Builder.setNote(mintPrivateParam1.getShieldedReceives(0).getNote()); + Optional infoById = waitToGetTransactionInfo(txid); GrpcAPI.PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = GrpcAPI .PrivateShieldedTRC20Parameters.newBuilder(); - privateTRC20Builder.addShieldedSpends(note1Builder.build()); + privateTRC20Builder + .addShieldedSpends(getSpendNote(infoById.get(), mintPrivateParam1, contractAddress)); //ReceiveNote 1 GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); FullViewingKey fullViewingKey = sk.fullViewingKey(); IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); - long revValue = 10; + long revValue = 1; byte[] memo = new byte[512]; - byte[] rcm = grpcClient.getRcm().getValue().toByteArray(); + byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); GrpcAPI.Note revNote = getNote(revValue, paymentAddressStr, rcm, memo); revNoteBuilder.setNote(revNote); privateTRC20Builder.addShieldedReceives(revNoteBuilder.build()); + //ReceiveNote 2 + GrpcAPI.ReceiveNote.Builder revNoteBuilder2 = GrpcAPI.ReceiveNote.newBuilder(); + PaymentAddress paymentAddress2 = incomingViewingKey.address(new DiversifierT().random()).get(); + String paymentAddressStr2 = KeyIo.encodePaymentAddress(paymentAddress2); + long revValue2 = 1; + byte[] memo2 = new byte[512]; + byte[] rcm2 = WalletApi.getRcm().get().getValue().toByteArray(); + + GrpcAPI.Note revNote2 = getNote(revValue2, paymentAddressStr2, rcm2, memo2); + revNoteBuilder2.setNote(revNote2); + privateTRC20Builder.addShieldedReceives(revNoteBuilder2.build()); + + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); privateTRC20Builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); - GrpcAPI.ShieldedTRC20Parameters transferParam = grpcClient + GrpcAPI.ShieldedTRC20Parameters transferParam = WalletApi .createShieldedContractParameters(privateTRC20Builder.build()); triggerTransfer(contractAddress, callerAddress, privateKey, transferParam.getTriggerContractInput()); @@ -158,45 +153,30 @@ public static void burnDemo() throws ZksnarkException, InterruptedException { byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); GrpcAPI.PrivateShieldedTRC20Parameters mintPrivateParam1 = mintParams( - privateKey, 10, shieldedTRC20ContractAddress); - setAllowance(10); - GrpcAPI.ShieldedTRC20Parameters mintParam1 = grpcClient.createShieldedContractParameters( + privateKey, 1, shieldedTRC20ContractAddress); + setAllowance(1); + GrpcAPI.ShieldedTRC20Parameters mintParam1 = WalletApi.createShieldedContractParameters( mintPrivateParam1); - long value = 10; String mintInput1 = mintParam1.getTriggerContractInput(); String txid = triggerMint(contractAddress, callerAddress, privateKey, mintInput1); // SpendNoteTRC20 1 - Optional infoById = grpcClient.getTransactionInfoById(txid); - while (infoById.get().getLogList().size() < 2) { - logger.info("Can not get transaction info, please wait...."); - Thread.sleep(5000); - infoById = grpcClient.getTransactionInfoById(txid); - } - byte[] tx1Data = infoById.get().getLog(1).getData().toByteArray(); - long pos = bytes32Tolong(ByteArray.subArray(tx1Data, 0, 32)); - byte[] contractResult = triggerGetPath(contractAddress, pos); - byte[] path = ByteArray.subArray(contractResult, 32, 1056); - byte[] root = ByteArray.subArray(contractResult, 0, 32); - GrpcAPI.SpendNoteTRC20.Builder note1Builder = GrpcAPI.SpendNoteTRC20.newBuilder(); - note1Builder.setAlpha(ByteString.copyFrom(grpcClient.getRcm().getValue().toByteArray())); - note1Builder.setPos(pos); - note1Builder.setPath(ByteString.copyFrom(path)); - note1Builder.setRoot(ByteString.copyFrom(root)); - note1Builder.setNote(mintPrivateParam1.getShieldedReceives(0).getNote()); + Optional infoById = waitToGetTransactionInfo(txid); GrpcAPI.PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = GrpcAPI .PrivateShieldedTRC20Parameters.newBuilder(); - privateTRC20Builder.addShieldedSpends(note1Builder.build()); + privateTRC20Builder + .addShieldedSpends(getSpendNote(infoById.get(), mintPrivateParam1, contractAddress)); ExpandedSpendingKey expsk = sk.expandedSpendingKey(); privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); - BigInteger toAmount = BigInteger.valueOf(10).multiply(scalingFactorBi); + BigInteger toAmount = BigInteger.valueOf(1).multiply(scalingFactorBi); privateTRC20Builder.setToAmount(toAmount.toString()); privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(callerAddress)); privateTRC20Builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); - GrpcAPI.ShieldedTRC20Parameters burnParam = grpcClient.createShieldedContractParameters(privateTRC20Builder.build()); + GrpcAPI.ShieldedTRC20Parameters burnParam = WalletApi + .createShieldedContractParameters(privateTRC20Builder.build()); triggerBurn(contractAddress, callerAddress, privateKey, burnParam.getTriggerContractInput()); @@ -236,7 +216,7 @@ private static String triggerTransfer( "0", 0, callerAddress, privateKey); - Optional infoById = grpcClient.getTransactionInfoById(txid); + Optional infoById = WalletApi.getTransactionInfoById(txid); return txid; } @@ -341,7 +321,7 @@ private static String triggerContract(byte[] contractAddress, String method, Str String txid = ByteArray.toHexString(Sha256Sm3Hash.hash( transaction.getRawData().toByteArray())); System.out.println("trigger txid = " + txid); - grpcClient.broadcastTransaction(transaction); + WalletApi.broadcastTransaction(transaction); return txid; } @@ -430,7 +410,7 @@ private static GrpcAPI.PrivateShieldedTRC20Parameters mintParams(String privKey, IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); byte[] memo = new byte[512]; - byte[] rcm = grpcClient.getRcm().getValue().toByteArray(); + byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); GrpcAPI.Note revNote = getNote(value, paymentAddressStr, rcm, memo); revNoteBuilder.setNote(revNote); @@ -465,13 +445,41 @@ private static byte[] triggerGetPath(byte[] contractAddress, long pos) { return result; } + private static Optional waitToGetTransactionInfo(String txid) + throws InterruptedException { + logger.info("mint txid: " + txid); + Optional infoById = WalletApi.getTransactionInfoById(txid); + while (infoById.get().getLogList().size() < 2) { + logger.info("Can not get transaction info, please wait...."); + Thread.sleep(5000); + return WalletApi.getTransactionInfoById(txid); + } + return null; + } + + private static SpendNoteTRC20 getSpendNote(TransactionInfo txInfo, + PrivateShieldedTRC20Parameters mintPrivateParam1, byte[] contractAddress) { + byte[] tx1Data = txInfo.getLog(1).getData().toByteArray(); + long pos = bytes32ToLong(ByteArray.subArray(tx1Data, 0, 32)); + byte[] contractResult = triggerGetPath(contractAddress, pos); + byte[] path = ByteArray.subArray(contractResult, 32, 1056); + byte[] root = ByteArray.subArray(contractResult, 0, 32); + GrpcAPI.SpendNoteTRC20.Builder noteBuilder = GrpcAPI.SpendNoteTRC20.newBuilder(); + noteBuilder.setAlpha(ByteString.copyFrom(WalletApi.getRcm().get().getValue().toByteArray())); + noteBuilder.setPos(pos); + noteBuilder.setPath(ByteString.copyFrom(path)); + noteBuilder.setRoot(ByteString.copyFrom(root)); + noteBuilder.setNote(mintPrivateParam1.getShieldedReceives(0).getNote()); + return noteBuilder.build(); + } + private static byte[] longTo32Bytes(long value) { byte[] longBytes = ByteArray.fromLong(value); byte[] zeroBytes = new byte[24]; return ByteUtil.merge(zeroBytes, longBytes); } - private static long bytes32Tolong(byte[] value) { + private static long bytes32ToLong(byte[] value) { return ByteArray.toLong(value); } } From 730bf6843e0c6398645575472c03ced13dbe66ce Mon Sep 17 00:00:00 2001 From: xq-lu <583591aa!!> Date: Mon, 7 Dec 2020 18:23:26 +0800 Subject: [PATCH 353/445] merge from java-tron --- src/main/protos/api/api.proto | 22 +++++++++ .../core/contract/balance_contract.proto | 47 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 9d4cc25ce..dd0b07cd2 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -41,6 +41,28 @@ service Wallet { }; }; + + rpc GetAccountBalance (AccountBalanceRequest) returns (AccountBalanceResponse) { + option (google.api.http) = { + post: "/wallet/getaccountbalance" + body: "*" + additional_bindings { + get: "/wallet/getaccountbalance" + } + }; + }; + + rpc GetBlockBalanceTrace (BlockBalanceTrace.BlockIdentifier) returns (BlockBalanceTrace) { + option (google.api.http) = { + post: "/wallet/getblockbalancetrace" + body: "*" + additional_bindings { + get: "/wallet/getblockbalancetrace" + } + }; + }; + + //Please use CreateTransaction2 instead of this function. rpc CreateTransaction (TransferContract) returns (Transaction) { option (google.api.http) = { diff --git a/src/main/protos/core/contract/balance_contract.proto b/src/main/protos/core/contract/balance_contract.proto index 07f7141f6..82a93d6a3 100644 --- a/src/main/protos/core/contract/balance_contract.proto +++ b/src/main/protos/core/contract/balance_contract.proto @@ -34,3 +34,50 @@ message TransferContract { bytes to_address = 2; int64 amount = 3; } + + +message TransactionBalanceTrace { + message Operation { + int64 operation_identifier = 1; + bytes address = 2; + int64 amount = 3; + } + + bytes transaction_identifier = 1; + repeated Operation operation = 2; + string type = 3; + string status = 4; +} + + + +message BlockBalanceTrace { + message BlockIdentifier { + bytes hash = 1; + int64 number = 2; + } + + BlockIdentifier block_identifier = 1; + int64 timestamp = 2; + repeated TransactionBalanceTrace transaction_balance_trace = 3; + // BlockIdentifier parent_block_identifier = 4; +} + +message AccountTrace { + int64 balance = 1; + int64 placeholder = 99; +} + +message AccountIdentifier { + bytes address = 1; +} + +message AccountBalanceRequest { + AccountIdentifier account_identifier = 1; + BlockBalanceTrace.BlockIdentifier block_identifier = 2; +} + +message AccountBalanceResponse { + int64 balance = 1; + BlockBalanceTrace.BlockIdentifier block_identifier = 2; +} From 8fd21f06460171257167f5611094a2f95371bfef Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Mon, 4 Jan 2021 12:42:14 +0800 Subject: [PATCH 354/445] fix the format of constructor and args of the deploycontract command --- src/main/java/org/tron/walletcli/Client.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 250c9bd95..e78157478 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2111,11 +2111,11 @@ private void deployContract(String[] parameter) System.out.println("origin_energy_limit must > 0"); return; } - if (!constructorStr.equals("#")) { + if (!(constructorStr.equals("#") || argsStr.equals("#"))) { if (isHex) { codeStr += argsStr; } else { - codeStr += AbiUtil.parseMethod(constructorStr, argsStr); + codeStr += Hex.toHexString(AbiUtil.encodeInput(constructorStr, argsStr)); } } long value = 0; @@ -3704,7 +3704,7 @@ public static String[] getCmd(String cmdLine) { || cmdLine.toLowerCase().startsWith("triggercontract") || cmdLine.toLowerCase().startsWith("triggerconstantcontract") || cmdLine.toLowerCase().startsWith("updateaccountpermission")) { - return cmdLine.split(" ", -1); + return cmdLine.split("\\s+", -1); } String[] strArray = cmdLine.split("\""); int num = strArray.length; From abcb15482e5d69e5de4aba01499f40458f82b7ff Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 8 Jan 2021 13:34:13 +0800 Subject: [PATCH 355/445] add punishment parameter into protocol --- src/main/protos/core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index cec919247..51bd43335 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -380,6 +380,7 @@ message TransactionInfo { bytes orderId = 25; repeated MarketOrderDetail orderDetails = 26; + int64 punishment = 27; } message TransactionRet { From 2d7ab1a6d12c2c6420696e1ee37796549c54bc10 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 12 Jan 2021 14:23:22 +0800 Subject: [PATCH 356/445] add packingFee parameter into protocol --- src/main/protos/core/Tron.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 51bd43335..ba26829f7 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -380,7 +380,7 @@ message TransactionInfo { bytes orderId = 25; repeated MarketOrderDetail orderDetails = 26; - int64 punishment = 27; + int64 packingFee = 27; } message TransactionRet { From 1507e210c4655af107ab9b2b86a58e5361277202 Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Sat, 23 Jan 2021 21:50:41 +0800 Subject: [PATCH 357/445] remove redundant mint params code --- src/main/java/org/tron/demo/ShieldedTRC20Demo.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index da79b1212..9f5a60677 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -200,7 +200,7 @@ private static String triggerMint(byte[] contractAddress, "mint(uint256,bytes32[9],bytes32[2],bytes32[21])", input, true, - 0L, 10000000L, + 0L, 50000000L, "0", 0, callerAddress, privateKey); } @@ -212,7 +212,7 @@ private static String triggerTransfer( "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])", input, true, - 0L, 1000000000L, + 0L, 50000000L, "0", 0, callerAddress, privateKey); @@ -227,7 +227,7 @@ private static String triggerBurn(byte[] contractAddress, + "bytes32[21][])", input, true, - 0L, 1000000000L, + 0L, 50000000L, "0", 0, callerAddress, privateKey); @@ -399,8 +399,6 @@ private static GrpcAPI.PrivateShieldedTRC20Parameters mintParams(String privKey, BigInteger fromAmount = BigInteger.valueOf(value).multiply(scalingFactorBi); SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); ExpandedSpendingKey expsk = sk.expandedSpendingKey(); - byte[] ask = expsk.getAsk(); - byte[] nsk = expsk.getNsk(); byte[] ovk = expsk.getOvk(); // ReceiveNote @@ -418,8 +416,6 @@ private static GrpcAPI.PrivateShieldedTRC20Parameters mintParams(String privKey, GrpcAPI.PrivateShieldedTRC20Parameters.Builder paramBuilder = GrpcAPI .PrivateShieldedTRC20Parameters.newBuilder(); - paramBuilder.setAsk(ByteString.copyFrom(ask)); - paramBuilder.setNsk(ByteString.copyFrom(nsk)); paramBuilder.setOvk(ByteString.copyFrom(ovk)); paramBuilder.setFromAmount(fromAmount.toString()); paramBuilder.addShieldedReceives(revNoteBuilder.build()); @@ -452,9 +448,9 @@ private static Optional waitToGetTransactionInfo(String txid) while (infoById.get().getLogList().size() < 2) { logger.info("Can not get transaction info, please wait...."); Thread.sleep(5000); - return WalletApi.getTransactionInfoById(txid); + infoById = WalletApi.getTransactionInfoById(txid); } - return null; + return infoById; } private static SpendNoteTRC20 getSpendNote(TransactionInfo txInfo, From 90bfb44f36f721d989679a135588afc2c995340e Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 29 Jan 2021 14:39:43 +0800 Subject: [PATCH 358/445] sign and generate keys natively --- build.gradle | 2 + .../tron/common/zksnark/JLibrustzcash.java | 199 ++++ .../org/tron/common/zksnark/JLibsodium.java | 62 ++ .../tron/common/zksnark/JLibsodiumParam.java | 287 ++++++ .../common/zksnark/LibrustzcashParam.java | 953 ++++++++++++++++++ .../tron/core/zen/ShieldedAddressInfo.java | 34 + .../tron/core/zen/address/DiversifierT.java | 19 +- .../core/zen/address/ExpandedSpendingKey.java | 23 +- .../tron/core/zen/address/FullViewingKey.java | 29 +- .../core/zen/address/IncomingViewingKey.java | 28 +- .../tron/core/zen/address/SpendingKey.java | 67 +- src/main/java/org/tron/walletcli/Client.java | 143 +-- .../org/tron/walletcli/WalletApiWrapper.java | 4 +- .../java/org/tron/walletserver/WalletApi.java | 16 +- 14 files changed, 1708 insertions(+), 158 deletions(-) create mode 100644 src/main/java/org/tron/common/zksnark/JLibrustzcash.java create mode 100644 src/main/java/org/tron/common/zksnark/JLibsodium.java create mode 100644 src/main/java/org/tron/common/zksnark/JLibsodiumParam.java create mode 100644 src/main/java/org/tron/common/zksnark/LibrustzcashParam.java diff --git a/build.gradle b/build.gradle index 0b7f9b13a..580dfa2ac 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,7 @@ compileJava.options*.compilerArgs = [ repositories { mavenLocal() mavenCentral() + maven { url 'https://jitpack.io' } } sourceSets { @@ -89,6 +90,7 @@ dependencies { compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' compile group: 'org.jline', name: 'jline', version: '3.15.0' + compile 'com.github.tronprotocol:zksnark-java-sdk:master-SNAPSHOT' } protobuf { diff --git a/src/main/java/org/tron/common/zksnark/JLibrustzcash.java b/src/main/java/org/tron/common/zksnark/JLibrustzcash.java new file mode 100644 index 000000000..9a5e0c44b --- /dev/null +++ b/src/main/java/org/tron/common/zksnark/JLibrustzcash.java @@ -0,0 +1,199 @@ +package org.tron.common.zksnark; + +import org.tron.common.zksnark.LibrustzcashParam.BindingSigParams; +import org.tron.common.zksnark.LibrustzcashParam.CheckOutputNewParams; +import org.tron.common.zksnark.LibrustzcashParam.CheckOutputParams; +import org.tron.common.zksnark.LibrustzcashParam.CheckSpendNewParams; +import org.tron.common.zksnark.LibrustzcashParam.CheckSpendParams; +import org.tron.common.zksnark.LibrustzcashParam.ComputeCmParams; +import org.tron.common.zksnark.LibrustzcashParam.ComputeNfParams; +import org.tron.common.zksnark.LibrustzcashParam.CrhIvkParams; +import org.tron.common.zksnark.LibrustzcashParam.FinalCheckNewParams; +import org.tron.common.zksnark.LibrustzcashParam.FinalCheckParams; +import org.tron.common.zksnark.LibrustzcashParam.InitZksnarkParams; +import org.tron.common.zksnark.LibrustzcashParam.IvkToPkdParams; +import org.tron.common.zksnark.LibrustzcashParam.KaAgreeParams; +import org.tron.common.zksnark.LibrustzcashParam.KaDerivepublicParams; +import org.tron.common.zksnark.LibrustzcashParam.MerkleHashParams; +import org.tron.common.zksnark.LibrustzcashParam.OutputProofParams; +import org.tron.common.zksnark.LibrustzcashParam.SpendProofParams; +import org.tron.common.zksnark.LibrustzcashParam.SpendSigParams; +import org.tron.core.exception.ZksnarkException; + +public class JLibrustzcash { + private static Librustzcash INSTANCE = LibrustzcashWrapper.getInstance(); + + public static void librustzcashInitZksnarkParams(InitZksnarkParams params) { + INSTANCE.librustzcashInitZksnarkParams(params.getSpend_path(), + params.getSpend_hash(), params.getOutput_path(), params.getOutput_hash()); + } + + public static void librustzcashCrhIvk(CrhIvkParams params) { + INSTANCE.librustzcashCrhIvk(params.getAk(), params.getNk(), params.getIvk()); + } + + public static boolean librustzcashKaAgree(KaAgreeParams params) { + return INSTANCE.librustzcashSaplingKaAgree(params.getP(), params.getSk(), params.getResult()); + } + + public static boolean librustzcashComputeCm(ComputeCmParams params) { + return INSTANCE.librustzcashSaplingComputeCm(params.getD(), params.getPkD(), + params.getValue(), params.getR(), params.getCm()); + } + + public static boolean librustzcashComputeNf(ComputeNfParams params) { + INSTANCE.librustzcashSaplingComputeNf(params.getD(), params.getPkD(), params.getValue(), + params.getR(), params.getAk(), params.getNk(), params.getPosition(), params.getResult()); + return true; + } + + /** + * @param ask the spend authorizing key,to generate ak, 32 bytes + * @return ak 32 bytes + */ + public static byte[] librustzcashAskToAk(byte[] ask) throws ZksnarkException { + LibrustzcashParam.valid32Params(ask); + byte[] ak = new byte[32]; + INSTANCE.librustzcashAskToAk(ask, ak); + return ak; + } + + /** + * @param nsk the proof authorizing key, to generate nk, 32 bytes + * @return 32 bytes + */ + public static byte[] librustzcashNskToNk(byte[] nsk) throws ZksnarkException { + LibrustzcashParam.valid32Params(nsk); + byte[] nk = new byte[32]; + INSTANCE.librustzcashNskToNk(nsk, nk); + return nk; + } + + // void librustzcash_nsk_to_nk(const unsigned char *nsk, unsigned char *result); + + /** + * @return r: random number, less than r_J, 32 bytes + */ + public static byte[] librustzcashSaplingGenerateR(byte[] r) throws ZksnarkException { + LibrustzcashParam.valid32Params(r); + INSTANCE.librustzcashSaplingGenerateR(r); + return r; + } + + public static boolean librustzcashSaplingKaDerivepublic(KaDerivepublicParams params) { + return INSTANCE.librustzcashSaplingKaDerivepublic(params.getDiversifier(), params.getEsk(), + params.getResult()); + } + + public static long librustzcashSaplingProvingCtxInit() { + return INSTANCE.librustzcashSaplingProvingCtxInit(); + } + + /** + * check validity of d + * + * @param d 11 bytes + */ + public static boolean librustzcashCheckDiversifier(byte[] d) throws ZksnarkException { + LibrustzcashParam.valid11Params(d); + return INSTANCE.librustzcashCheckDiversifier(d); + } + + public static boolean librustzcashSaplingSpendProof(SpendProofParams params) { + return INSTANCE.librustzcashSaplingSpendProof(params.getCtx(), params.getAk(), + params.getNsk(), params.getD(), params.getR(), params.getAlpha(), params.getValue(), + params.getAnchor(), params.getVoucherPath(), params.getCv(), params.getRk(), + params.getZkproof()); + } + + public static boolean librustzcashSaplingOutputProof(OutputProofParams params) { + return INSTANCE.librustzcashSaplingOutputProof(params.getCtx(), params.getEsk(), + params.getD(), params.getPkD(), params.getR(), params.getValue(), params.getCv(), + params.getZkproof()); + } + + public static boolean librustzcashSaplingSpendSig(SpendSigParams params) { + return INSTANCE.librustzcashSaplingSpendSig(params.getAsk(), params.getAlpha(), + params.getSigHash(), params.getResult()); + } + + public static boolean librustzcashSaplingBindingSig(BindingSigParams params) { + return INSTANCE.librustzcashSaplingBindingSig(params.getCtx(), + params.getValueBalance(), params.getSighash(), params.getResult()); + } + + /** + * convert value to 32-byte scalar + * + * @param value 64 bytes + * @param data 32 bytes + */ + public static void librustzcashToScalar(byte[] value, byte[] data) throws ZksnarkException { + LibrustzcashParam.validParamLength(value, 64); + LibrustzcashParam.valid32Params(data); + INSTANCE.librustzcashToScalar(value, data); + } + + public static void librustzcashSaplingProvingCtxFree(long ctx) { + INSTANCE.librustzcashSaplingProvingCtxFree(ctx); + } + + public static long librustzcashSaplingVerificationCtxInit() { + return INSTANCE.librustzcashSaplingVerificationCtxInit(); + } + + public static boolean librustzcashSaplingCheckSpend(CheckSpendParams params) { + return INSTANCE.librustzcashSaplingCheckSpend(params.getCtx(), params.getCv(), + params.getAnchor(), params.getNullifier(), params.getRk(), params.getZkproof(), + params.getSpendAuthSig(), params.getSighashValue()); + } + + public static boolean librustzcashSaplingCheckOutput(CheckOutputParams params) { + return INSTANCE.librustzcashSaplingCheckOutput(params.getCtx(), params.getCv(), + params.getCm(), params.getEphemeralKey(), params.getZkproof()); + } + + public static boolean librustzcashSaplingFinalCheck(FinalCheckParams params) { + return INSTANCE.librustzcashSaplingFinalCheck(params.getCtx(), + params.getValueBalance(), params.getBindingSig(), params.getSighashValue()); + } + + public static boolean librustzcashSaplingCheckSpendNew(CheckSpendNewParams params) { + return INSTANCE.librustzcashSaplingCheckSpendNew(params.getCv(), + params.getAnchor(), params.getNullifier(), params.getRk(), params.getZkproof(), + params.getSpendAuthSig(), params.getSighashValue()); + } + + public static boolean librustzcashSaplingCheckOutputNew(CheckOutputNewParams params) { + return INSTANCE.librustzcashSaplingCheckOutputNew(params.getCv(), params.getCm(), + params.getEphemeralKey(), params.getZkproof()); + } + + public static boolean librustzcashSaplingFinalCheckNew(FinalCheckNewParams params) { + return INSTANCE + .librustzcashSaplingFinalCheckNew(params.getValueBalance(), params.getBindingSig(), + params.getSighashValue(), params.getSpendCv(), params.getSpendCvLen(), + params.getOutputCv(), params.getOutputCvLen()); + } + + public static void librustzcashSaplingVerificationCtxFree(long ctx) { + INSTANCE.librustzcashSaplingVerificationCtxFree(ctx); + } + + public static boolean librustzcashIvkToPkd(IvkToPkdParams params) { + return INSTANCE.librustzcashIvkToPkd(params.getIvk(), params.getD(), params.getPkD()); + } + + public static void librustzcashMerkleHash(MerkleHashParams params) { + INSTANCE.librustzcashMerkleHash(params.getDepth(), params.getA(), params.getB(), + params.getResult()); + } + + /** + * @param result uncommitted value, 32 bytes + */ + public static void librustzcashTreeUncommitted(byte[] result) throws ZksnarkException { + LibrustzcashParam.valid32Params(result); + INSTANCE.librustzcashTreeUncommitted(result); + } +} diff --git a/src/main/java/org/tron/common/zksnark/JLibsodium.java b/src/main/java/org/tron/common/zksnark/JLibsodium.java new file mode 100644 index 000000000..de6b68a5e --- /dev/null +++ b/src/main/java/org/tron/common/zksnark/JLibsodium.java @@ -0,0 +1,62 @@ +package org.tron.common.zksnark; + +import org.tron.common.zksnark.JLibsodiumParam.Black2bSaltPersonalParams; +import org.tron.common.zksnark.JLibsodiumParam.Blake2bFinalParams; +import org.tron.common.zksnark.JLibsodiumParam.Blake2bInitSaltPersonalParams; +import org.tron.common.zksnark.JLibsodiumParam.Blake2bUpdateParams; +import org.tron.common.zksnark.JLibsodiumParam.Chacha20Poly1305IetfEncryptParams; +import org.tron.common.zksnark.JLibsodiumParam.Chacha20poly1305IetfDecryptParams; + +public class JLibsodium { + public static final int CRYPTO_GENERICHASH_BLAKE2B_PERSONALBYTES = 16; + public static final int CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12; + private static Libsodium INSTANCE = LibsodiumWrapper.getInstance(); + + public static int cryptoGenerichashBlake2bInitSaltPersonal(Blake2bInitSaltPersonalParams params) { + return INSTANCE + .cryptoGenerichashBlake2BInitSaltPersonal(params.getState(), params.getKey(), + params.getKeyLen(), params.getOutLen(), params.getSalt(), params.getPersonal()); + } + + public static int cryptoGenerichashBlake2bUpdate(Blake2bUpdateParams params) { + return INSTANCE + .cryptoGenerichashBlake2BUpdate(params.getState(), params.getIn(), params.getInLen()); + } + + public static int cryptoGenerichashBlake2bFinal(Blake2bFinalParams params) { + return INSTANCE.cryptoGenerichashBlake2BFinal(params.getState(), + params.getOut(), params.getOutLen()); + } + + public static int cryptoGenerichashBlack2bSaltPersonal(Black2bSaltPersonalParams params) { + return INSTANCE.cryptoGenerichashBlake2BSaltPersonal(params.getOut(), params.getOutLen(), + params.getIn(), params.getInLen(), params.getKey(), params.getKeyLen(), + params.getSalt(), + params.getPersonal()); + } + + public static int cryptoAeadChacha20poly1305IetfDecrypt( + Chacha20poly1305IetfDecryptParams params) { + return INSTANCE + .cryptoAeadChacha20Poly1305IetfDecrypt(params.getM(), params.getMLenP(), + params.getNSec(), + params.getC(), params.getCLen(), params.getAd(), + params.getAdLen(), params.getNPub(), params.getK()); + } + + public static int cryptoAeadChacha20Poly1305IetfEncrypt( + Chacha20Poly1305IetfEncryptParams params) { + return INSTANCE + .cryptoAeadChacha20Poly1305IetfEncrypt(params.getC(), params.getCLenP(), params.getM(), + params.getMLen(), params.getAd(), params.getAdLen(), + params.getNSec(), params.getNPub(), params.getK()); + } + + public static long initState() { + return INSTANCE.cryptoGenerichashBlake2BStateInit(); + } + + public static void freeState(long state) { + INSTANCE.cryptoGenerichashBlake2BStateFree(state); + } +} diff --git a/src/main/java/org/tron/common/zksnark/JLibsodiumParam.java b/src/main/java/org/tron/common/zksnark/JLibsodiumParam.java new file mode 100644 index 000000000..8b4a27259 --- /dev/null +++ b/src/main/java/org/tron/common/zksnark/JLibsodiumParam.java @@ -0,0 +1,287 @@ +package org.tron.common.zksnark; + +import lombok.Getter; +import lombok.Setter; +import org.tron.common.utils.ByteUtil; +import org.tron.core.exception.ZksnarkException; + +public class JLibsodiumParam { + interface ValidParam { + + void valid() throws ZksnarkException; + } + + public static void validNull(byte[] value) throws ZksnarkException { + if (ByteUtil.isNullOrZeroArray(value)) { + throw new ZksnarkException("param is null"); + } + } + + public static void validValueParams(long value) throws ZksnarkException { + if (value < 0) { + throw new ZksnarkException("Value should be non-negative."); + } + } + + public static void validParamLength(byte[] value, int length) throws ZksnarkException { + validNull(value); + if (value.length != length) { + throw new ZksnarkException("param length must be " + length); + } + } + + public static class Blake2bInitSaltPersonalParams implements ValidParam { + + @Setter + @Getter + private long state; + @Setter + @Getter + private byte[] key; + @Setter + @Getter + private int keyLen; + @Setter + @Getter + private int outLen; + @Setter + @Getter + private byte[] salt; + @Setter + @Getter + private byte[] personal; + + public Blake2bInitSaltPersonalParams(long state, byte[] key, int keyLen, int outLen, + byte[] salt, byte[] personal) throws ZksnarkException { + this.state = state; + this.key = key; + this.keyLen = keyLen; + this.outLen = outLen; + this.salt = salt; + this.personal = personal; + + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(state); + validParamLength(personal, 16); + } + } + + public static class Blake2bUpdateParams implements ValidParam { + + @Setter + @Getter + private long state; + @Setter + @Getter + private byte[] in; + @Setter + @Getter + private long inLen; + + public Blake2bUpdateParams(long state, byte[] in, long inLen) throws ZksnarkException { + this.state = state; + this.in = in; + this.inLen = inLen; + + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(state); + if (in.length != inLen || (in.length != 33 && in.length != 34)) { + throw new ZksnarkException("param length must be 33 or 34"); + } + } + } + + public static class Blake2bFinalParams implements ValidParam { + + @Setter + @Getter + private long state; + @Setter + @Getter + private byte[] out; + @Setter + @Getter + private int outLen; + + public Blake2bFinalParams(long state, byte[] out, int outLen) throws ZksnarkException { + this.state = state; + this.out = out; + this.outLen = outLen; + + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(state); + if (out.length != outLen || (out.length != 11 && out.length != 64)) { + throw new ZksnarkException("param length must be 11 or 64"); + } + } + } + + public static class Black2bSaltPersonalParams implements ValidParam { + + @Setter + @Getter + private byte[] out; + @Setter + @Getter + private int outLen; + @Setter + @Getter + private byte[] in; + @Setter + @Getter + private long inLen; + @Setter + @Getter + private byte[] key; + @Setter + @Getter + private int keyLen; + @Setter + @Getter + private byte[] salt; + @Setter + @Getter + private byte[] personal; + + + public Black2bSaltPersonalParams(byte[] out, int outLen, byte[] in, long inLen, byte[] key, + int keyLen, byte[] salt, byte[] personal) throws ZksnarkException { + this.out = out; + this.outLen = outLen; + this.in = in; + this.inLen = inLen; + this.key = key; + this.keyLen = keyLen; + this.salt = salt; + this.personal = personal; + + valid(); + } + + @Override + public void valid() throws ZksnarkException { + if (out.length != outLen || in.length != inLen) { + throw new ZksnarkException("out.length is not equal to outlen " + + "or in.length is not equal to inlen"); + } + validParamLength(out, 32); + validParamLength(personal, 16); + } + } + + public static class Chacha20poly1305IetfDecryptParams implements ValidParam { + + @Setter + @Getter + private byte[] m; + @Setter + @Getter + private long[] mLenP; + @Setter + @Getter + private byte[] nSec; + @Setter + @Getter + private byte[] c; + @Setter + @Getter + private long cLen; + @Setter + @Getter + private byte[] ad; + @Setter + @Getter + private long adLen; + @Setter + @Getter + private byte[] nPub; + @Setter + @Getter + private byte[] k; + + public Chacha20poly1305IetfDecryptParams(byte[] m, long[] mLenP, byte[] nSec, byte[] c, + long cLen, byte[] ad, long adLen, byte[] nPub, byte[] k) throws ZksnarkException { + this.m = m; + this.mLenP = mLenP; + this.nSec = nSec; + this.c = c; + this.cLen = cLen; + this.ad = ad; + this.adLen = adLen; + this.nPub = nPub; + this.k = k; + + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validParamLength(nPub, 12); + validParamLength(k, 32); + } + } + + public static class Chacha20Poly1305IetfEncryptParams implements ValidParam { + + @Setter + @Getter + private byte[] c; + @Setter + @Getter + private long[] cLenP; + @Setter + @Getter + private byte[] m; + @Setter + @Getter + private long mLen; + @Setter + @Getter + private byte[] ad; + @Setter + @Getter + private long adLen; + @Setter + @Getter + private byte[] nSec; + @Setter + @Getter + private byte[] nPub; + @Setter + @Getter + private byte[] k; + + public Chacha20Poly1305IetfEncryptParams(byte[] c, long[] cLenP, byte[] m, long mLen, + byte[] ad, long adLen, byte[] nSec, byte[] nPub, byte[] k) throws ZksnarkException { + this.c = c; + this.cLenP = cLenP; + this.m = m; + this.mLen = mLen; + this.ad = ad; + this.adLen = adLen; + this.nSec = nSec; + this.nPub = nPub; + this.k = k; + + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validParamLength(nPub, 12); + validParamLength(k, 32); + } + } +} diff --git a/src/main/java/org/tron/common/zksnark/LibrustzcashParam.java b/src/main/java/org/tron/common/zksnark/LibrustzcashParam.java new file mode 100644 index 000000000..78232ffee --- /dev/null +++ b/src/main/java/org/tron/common/zksnark/LibrustzcashParam.java @@ -0,0 +1,953 @@ +package org.tron.common.zksnark; + +import lombok.Getter; +import lombok.Setter; +import org.tron.common.utils.ByteArray; +import org.tron.common.utils.ByteUtil; +import org.tron.core.exception.ZksnarkException; + +public class LibrustzcashParam { + public static void validNull(byte[] value) throws ZksnarkException { + if (ByteUtil.isNullOrZeroArray(value)) { + throw new ZksnarkException("param is null"); + } + } + + public static void validObjectNull(Object object) throws ZksnarkException { + if (object == null) { + throw new ZksnarkException("param is null"); + } + } + + public static void validByteValue(byte src, byte desc) throws ZksnarkException { + if (src != desc) { + throw new ZksnarkException("param " + src + " not equals:" + desc); + } + } + + public static void validParamLength(byte[] value, int length) throws ZksnarkException { + validNull(value); + if (value.length != length) { + throw new ZksnarkException("param length must be " + length); + } + } + + public static void valid11Params(byte[] value) throws ZksnarkException { + validParamLength(value, 11); + } + + public static void valid32Params(byte[] value) throws ZksnarkException { + validParamLength(value, 32); + } + + public static void validVoucherPath(byte[] voucherPath) throws ZksnarkException { + validParamLength(voucherPath, 1 + 33 * 32 + 8); + validByteValue(voucherPath[0], (byte) 0x20); + for (int i = 0; i < 32; i++) { + validByteValue(voucherPath[1 + i * 33], (byte) 0x20); + } + } + + public static void validValueParams(long value) throws ZksnarkException { + if (value < 0) { + throw new ZksnarkException("Value should be non-negative."); + } + } + + public static void validPositionParams(long value) throws ZksnarkException { + if (value < 0) { + throw new ZksnarkException("Position should be non-negative."); + } + } + + interface ValidParam { + + void valid() throws ZksnarkException; + } + + public static class InitZksnarkParams implements ValidParam { + + @Setter + @Getter + private String spend_path; + @Setter + @Getter + private String spend_hash; + @Setter + @Getter + private String output_path; + @Setter + @Getter + private String output_hash; + + public InitZksnarkParams(String spend_path, String spend_hash, String output_path, + String output_hash) throws ZksnarkException { + this.spend_path = spend_path; + this.spend_hash = spend_hash; + this.output_path = output_path; + this.output_hash = output_hash; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + + } + } + + /** + * (ak,nk)--> ivk ak: spendAuthSig.publickey 32 bytes nk: 32 bytes ivk: incoming viewing key, 32 + * bytes + */ + public static class CrhIvkParams implements ValidParam { + + @Setter + @Getter + private byte[] ak; + @Setter + @Getter + private byte[] nk; + @Setter + @Getter + private byte[] ivk; + + public CrhIvkParams(byte[] ak, byte[] nk, byte[] ivk) throws ZksnarkException { + this.ak = ak; + this.nk = nk; + this.ivk = ivk; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(ak); + valid32Params(nk); + valid32Params(ivk); + } + } + + /** + * KaAgree(sk,p)=[h_J*sk]p p: point, 32 bytes sk: 32 bytes result: 32 bytes + */ + public static class KaAgreeParams implements ValidParam { + + @Setter + @Getter + private byte[] p; + @Setter + @Getter + private byte[] sk; + @Setter + @Getter + private byte[] result; + + public KaAgreeParams(byte[] p, byte[] sk, byte[] result) throws ZksnarkException { + this.p = p; + this.sk = sk; + this.result = result; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(p); + valid32Params(sk); + valid32Params(result); + } + } + + /** + * Compute note commitment d: diversifier, 11 bytes pkD: 32 bytes r: rcm, 32 bytes cm: note + * commitment, 32 bytes, value >= 0 + */ + public static class ComputeCmParams implements ValidParam { + + @Setter + @Getter + private byte[] d; + @Setter + @Getter + private byte[] pkD; + @Setter + @Getter + private long value; + @Setter + @Getter + private byte[] r; + @Setter + @Getter + private byte[] cm; + + public ComputeCmParams(byte[] d, byte[] pkD, long value, byte[] r, byte[] cm) + throws ZksnarkException { + this.d = d; + this.pkD = pkD; + this.value = value; + this.r = r; + this.cm = cm; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(value); + valid11Params(d); + valid32Params(pkD); + valid32Params(r); + valid32Params(cm); + } + } + + /** + * compute nullifier d: diversifier, 11 bytes pkD, 32 bytes r: rcm, 32 bytes ak: + * spendAuthSig.PulicKey, 32 bytes nk: to genarate nullifier, 32 bytes result: nullifier, 32 + * bytes, value >= 0, position >= 0 + */ + public static class ComputeNfParams implements ValidParam { + + @Setter + @Getter + private byte[] d; + @Setter + @Getter + private byte[] pkD; + @Setter + @Getter + private long value; + @Setter + @Getter + private byte[] r; + @Setter + @Getter + private byte[] ak; + @Setter + @Getter + private byte[] nk; + @Setter + @Getter + private long position; + @Setter + @Getter + private byte[] result; + + public ComputeNfParams(byte[] d, byte[] pkD, long value, byte[] r, byte[] ak, byte[] nk, + long position, byte[] result) throws ZksnarkException { + this.d = d; + this.pkD = pkD; + this.value = value; + this.r = r; + this.ak = ak; + this.nk = nk; + this.position = position; + this.result = result; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(value); + validPositionParams(position); + valid11Params(d); + valid32Params(pkD); + valid32Params(r); + valid32Params(ak); + valid32Params(nk); + valid32Params(result); + } + } + + /** + * diversifier: d, 11 bytes esk: 32 bytes result: return 32 bytes + */ + public static class KaDerivepublicParams implements ValidParam { + + @Setter + @Getter + private byte[] diversifier; + @Setter + @Getter + private byte[] esk; + @Setter + @Getter + private byte[] result; + + public KaDerivepublicParams(byte[] diversifier, byte[] esk, byte[] result) + throws ZksnarkException { + this.diversifier = diversifier; + this.esk = esk; + this.result = result; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid11Params(diversifier); + valid32Params(esk); + valid32Params(result); + } + } + + /** + * calculate spend proof ak: 32 bytes nsk: the proof authorizing key, 32 bytes d: 11 bytes r: rcm, + * 32 bytes alpha: random number, 32 bytes anchor: 32 bytes voucherPath: (1 + 33 * 32 + 8) bytes, + * voucherPath[0]=0x20, voucherPath[1+i*33]=0x20,i=0,1,...31. cv: value commitment, 32 bytes rk: + * spendAuthSig.randomizePublicKey 32 bytes zkproof: spend proof, 192 bytes value >= 0 + */ + public static class SpendProofParams implements ValidParam { + + @Setter + @Getter + private long ctx; + @Setter + @Getter + private byte[] ak; + @Setter + @Getter + private byte[] nsk; + @Setter + @Getter + private byte[] d; + @Setter + @Getter + private byte[] r; + @Setter + @Getter + private byte[] alpha; + @Setter + @Getter + private long value; + @Setter + @Getter + private byte[] anchor; + @Setter + @Getter + private byte[] voucherPath; + @Setter + @Getter + private byte[] cv; + @Setter + @Getter + private byte[] rk; + @Setter + @Getter + private byte[] zkproof; + + public SpendProofParams(long ctx, byte[] ak, byte[] nsk, byte[] d, byte[] r, + byte[] alpha, long value, byte[] anchor, byte[] voucherPath, byte[] cv, byte[] rk, + byte[] zkproof) throws ZksnarkException { + this.ctx = ctx; + this.ak = ak; + this.nsk = nsk; + this.d = d; + this.r = r; + this.alpha = alpha; + this.value = value; + this.anchor = anchor; + this.voucherPath = voucherPath; + this.cv = cv; + this.rk = rk; + this.zkproof = zkproof; + valid(); + } + + public static SpendProofParams decode(long ctx, byte[] data) + throws ZksnarkException { + byte[] ak = new byte[32]; + byte[] nsk = new byte[32]; + byte[] d = new byte[11]; + byte[] r = new byte[32]; + byte[] alpha = new byte[32]; + byte[] valueByte = new byte[8]; + byte[] anchor = new byte[32]; + byte[] voucherPath = new byte[1065]; + byte[] cv = new byte[32]; + byte[] rk = new byte[192]; + byte[] zkproof = new byte[64]; + + System.arraycopy(data, 0, ak, 0, 32); + System.arraycopy(data, 32, nsk, 0, 32); + System.arraycopy(data, 64, d, 0, 11); + System.arraycopy(data, 75, r, 0, 32); + System.arraycopy(data, 107, alpha, 0, 32); + System.arraycopy(data, 139, valueByte, 0, 8); + System.arraycopy(data, 147, anchor, 0, 32); + System.arraycopy(data, 179, voucherPath, 0, 1065); + System.arraycopy(data, 179 + 1065, cv, 0, 32); + System.arraycopy(data, 211 + 1065, rk, 0, 192); + System.arraycopy(data, 243 + 1065, zkproof, 0, 64); + + return new SpendProofParams(ctx, ak, nsk, d, r, alpha, ByteArray.toLong(valueByte), + anchor, voucherPath, cv, rk, zkproof); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(value); + valid32Params(ak); + valid32Params(nsk); + valid11Params(d); + valid32Params(r); + valid32Params(alpha); + valid32Params(anchor); + validVoucherPath(voucherPath); + valid32Params(cv); + valid32Params(rk); + validParamLength(zkproof, 192); + } + + public byte[] encode() { + byte[] data = new byte[32 + 32 + 11 + 32 + 32 + 8 + 32 + 1065 + 32 + 32 + 192]; + + System.arraycopy(ak, 0, data, 0, 32); + System.arraycopy(nsk, 0, data, 32, 32); + System.arraycopy(d, 0, data, 64, 11); + System.arraycopy(r, 0, data, 75, 32); + System.arraycopy(alpha, 0, data, 107, 32); + System.arraycopy(ByteArray.fromLong(value), 0, data, 139, 8); + System.arraycopy(anchor, 0, data, 147, 32); + System.arraycopy(voucherPath, 0, data, 179, 1065); + System.arraycopy(cv, 0, data, 179 + 1065, 32); + System.arraycopy(rk, 0, data, 211 + 1065, 32); + System.arraycopy(zkproof, 0, data, 243 + 1065, 192); + + return data; + } + } + + /** + * esk: 32 bytes d: 11 bytes pkD: 32 bytes r: rcm, 32 bytes cv: value commitment, 32 bytes + * zkproof: receive proof, 192 bytes, value >= 0 + */ + public static class OutputProofParams implements ValidParam { + + @Setter + @Getter + private long ctx; + @Setter + @Getter + private byte[] esk; + @Setter + @Getter + private byte[] d; + @Setter + @Getter + private byte[] pkD; + @Setter + @Getter + private byte[] r; + @Setter + @Getter + private long value; + @Setter + @Getter + private byte[] cv; + @Setter + @Getter + private byte[] zkproof; + + public OutputProofParams(long ctx, byte[] esk, byte[] d, byte[] pkD, byte[] r, + long value, byte[] cv, byte[] zkproof) throws ZksnarkException { + this.ctx = ctx; + this.esk = esk; + this.d = d; + this.pkD = pkD; + this.r = r; + this.value = value; + this.cv = cv; + this.zkproof = zkproof; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validValueParams(value); + valid32Params(esk); + valid11Params(d); + valid32Params(pkD); + valid32Params(r); + valid32Params(cv); + validParamLength(zkproof, 192); + } + } + + /** + * ask: the spend authorizing key, 32 bytes alpha: random number, 32 bytes sigHash: sha256 of + * transaction, 32 bytes result: spendAuthSig, 64 bytes + */ + public static class SpendSigParams implements ValidParam { + + @Setter + @Getter + private byte[] ask; + @Setter + @Getter + private byte[] alpha; + @Setter + @Getter + private byte[] sigHash; + @Setter + @Getter + private byte[] result; + + public SpendSigParams(byte[] ask, byte[] alpha, byte[] sigHash, byte[] result) + throws ZksnarkException { + this.ask = ask; + this.alpha = alpha; + this.sigHash = sigHash; + this.result = result; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(ask); + valid32Params(alpha); + valid32Params(sigHash); + validParamLength(result, 64); + } + } + + /** + * Generate binding signature sighash: sha256 of transaction,32 bytes result: binding signature, + * 64 bytes + */ + public static class BindingSigParams implements ValidParam { + + @Setter + @Getter + private long ctx; + @Setter + @Getter + private long valueBalance; + @Setter + @Getter + private byte[] sighash; + @Setter + @Getter + private byte[] result; + + public BindingSigParams(long ctx, long valueBalance, byte[] sighash, byte[] result) + throws ZksnarkException { + this.ctx = ctx; + this.valueBalance = valueBalance; + this.sighash = sighash; + this.result = result; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(sighash); + validParamLength(result, 64); + } + } + + /** + * cv: value commitments, 32 bytes anchor: 32 bytes nullifier: 32 bytes rk: + * spendAuthSig.randomizePublicKey, 32 bytes zkproof: spend proof, 192 bytes spendAuthSig: 64 + * bytes sighashValue: sha256 of transaction, 32 bytes + */ + public static class CheckSpendParams implements ValidParam { + + @Setter + @Getter + private long ctx; + @Setter + @Getter + private byte[] cv; + @Setter + @Getter + private byte[] anchor; + @Setter + @Getter + private byte[] nullifier; + @Setter + @Getter + private byte[] rk; + @Setter + @Getter + private byte[] zkproof; + @Setter + @Getter + private byte[] spendAuthSig; + @Setter + @Getter + private byte[] sighashValue; + + public CheckSpendParams(long ctx, byte[] cv, byte[] anchor, byte[] nullifier, + byte[] rk, byte[] zkproof, byte[] spendAuthSig, byte[] sighashValue) + throws ZksnarkException { + this.ctx = ctx; + this.cv = cv; + this.anchor = anchor; + this.nullifier = nullifier; + this.rk = rk; + this.zkproof = zkproof; + this.spendAuthSig = spendAuthSig; + this.sighashValue = sighashValue; + valid(); + } + + public static CheckSpendParams decode(long ctx, byte[] data, byte[] sigHashValue) + throws ZksnarkException { + byte[] cv = new byte[32]; + byte[] anchor = new byte[32]; + byte[] nullifier = new byte[32]; + byte[] rk = new byte[32]; + byte[] zkproof = new byte[192]; + byte[] spendAuthSig = new byte[64]; + + System.arraycopy(data, 0, cv, 0, 32); + System.arraycopy(data, 32, anchor, 0, 32); + System.arraycopy(data, 64, nullifier, 0, 32); + System.arraycopy(data, 96, rk, 0, 32); + System.arraycopy(data, 128, zkproof, 0, 192); + System.arraycopy(data, 320, spendAuthSig, 0, 64); + + return new CheckSpendParams(ctx, cv, anchor, nullifier, rk, zkproof, spendAuthSig, + sigHashValue); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(cv); + valid32Params(anchor); + valid32Params(nullifier); + valid32Params(rk); + validParamLength(zkproof, 192); + validParamLength(spendAuthSig, 64); + valid32Params(sighashValue); + } + } + + /** + * cv: value commitments, 32 bytes cm: note commitment, 32 bytes ephemeralKey: 32 bytes zkproof: + * 192 bytes + */ + public static class CheckOutputParams implements ValidParam { + + @Setter + @Getter + private long ctx; + @Setter + @Getter + private byte[] cv; + @Setter + @Getter + private byte[] cm; + @Setter + @Getter + private byte[] ephemeralKey; + @Setter + @Getter + private byte[] zkproof; + + public CheckOutputParams(long ctx, byte[] cv, byte[] cm, byte[] ephemeralKey, + byte[] zkproof) throws ZksnarkException { + this.ctx = ctx; + this.cv = cv; + this.cm = cm; + this.ephemeralKey = ephemeralKey; + this.zkproof = zkproof; + valid(); + } + + public static CheckOutputParams decode(long ctx, byte[] data) + throws ZksnarkException { + byte[] cv = new byte[32]; + byte[] cm = new byte[32]; + byte[] ephemeralKey = new byte[32]; + byte[] zkproof = new byte[192]; + + System.arraycopy(data, 0, cv, 0, 32); + System.arraycopy(data, 32, cm, 0, 32); + System.arraycopy(data, 64, ephemeralKey, 0, 32); + System.arraycopy(data, 96, zkproof, 0, 192); + + return new CheckOutputParams(ctx, cv, cm, ephemeralKey, zkproof); + } + + public static CheckOutputParams decodeZ(long ctx, byte[] data) + throws ZksnarkException { + byte[] cv = new byte[32]; + byte[] cm = new byte[32]; + byte[] ephemeralKey = new byte[32]; + byte[] zkproof = new byte[192]; + + System.arraycopy(data, 0, cv, 0, 32); + System.arraycopy(data, 32, cm, 0, 32); + System.arraycopy(data, 64, ephemeralKey, 0, 32); + System.arraycopy(data, 96 + 580 + 80, zkproof, 0, 192); + + return new CheckOutputParams(ctx, cv, cm, ephemeralKey, zkproof); + } + + public byte[] encode() { + byte[] data = new byte[32 + 32 + 32 + 192]; + System.arraycopy(cv, 0, data, 0, 32); + System.arraycopy(cm, 0, data, 32, 32); + System.arraycopy(ephemeralKey, 0, data, 64, 32); + System.arraycopy(zkproof, 0, data, 96, 192); + return data; + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(cv); + valid32Params(cm); + valid32Params(ephemeralKey); + validParamLength(zkproof, 192); + } + } + + /** + * bindingSig: 64 bytes sighashValue: sha256 of transaction,32 bytes + */ + public static class FinalCheckParams implements ValidParam { + + @Setter + @Getter + private long ctx; + @Setter + @Getter + private long valueBalance; + @Setter + @Getter + private byte[] bindingSig; + @Setter + @Getter + private byte[] sighashValue; + + public FinalCheckParams(long ctx, long valueBalance, byte[] bindingSig, + byte[] sighashValue) throws ZksnarkException { + this.ctx = ctx; + this.valueBalance = valueBalance; + this.bindingSig = bindingSig; + this.sighashValue = sighashValue; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validParamLength(bindingSig, 64); + valid32Params(sighashValue); + } + } + + /** + * cv: value commitments, 32 bytes anchor: 32 bytes nullifier: 32 bytes rk: + * spendAuthSig.randomizePublicKey, 32 bytes zkproof: spend proof, 192 bytes spendAuthSig: 64 + * bytes sighashValue: sha256 of transaction, 32 bytes + */ + public static class CheckSpendNewParams implements ValidParam { + + @Setter + @Getter + private byte[] cv; + @Setter + @Getter + private byte[] anchor; + @Setter + @Getter + private byte[] nullifier; + @Setter + @Getter + private byte[] rk; + @Setter + @Getter + private byte[] zkproof; + @Setter + @Getter + private byte[] spendAuthSig; + @Setter + @Getter + private byte[] sighashValue; + + public CheckSpendNewParams(byte[] cv, byte[] anchor, byte[] nullifier, + byte[] rk, byte[] zkproof, byte[] spendAuthSig, byte[] sighashValue) + throws ZksnarkException { + this.cv = cv; + this.anchor = anchor; + this.nullifier = nullifier; + this.rk = rk; + this.zkproof = zkproof; + this.spendAuthSig = spendAuthSig; + this.sighashValue = sighashValue; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(cv); + valid32Params(anchor); + valid32Params(nullifier); + valid32Params(rk); + validParamLength(zkproof, 192); + validParamLength(spendAuthSig, 64); + valid32Params(sighashValue); + } + } + + /** + * cv: value commitments, 32 bytes cm: note commitment, 32 bytes ephemeralKey: 32 bytes zkproof: + * 192 bytes + */ + public static class CheckOutputNewParams implements ValidParam { + + @Setter + @Getter + private byte[] cv; + @Setter + @Getter + private byte[] cm; + @Setter + @Getter + private byte[] ephemeralKey; + @Setter + @Getter + private byte[] zkproof; + + public CheckOutputNewParams(byte[] cv, byte[] cm, byte[] ephemeralKey, byte[] zkproof) + throws ZksnarkException { + this.cv = cv; + this.cm = cm; + this.ephemeralKey = ephemeralKey; + this.zkproof = zkproof; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(cv); + valid32Params(cm); + valid32Params(ephemeralKey); + validParamLength(zkproof, 192); + } + } + + /** + * bindingSig: 64 bytes sighashValue: sha256 of transaction,32 bytes, + */ + public static class FinalCheckNewParams implements ValidParam { + + @Setter + @Getter + private long valueBalance; + @Setter + @Getter + private byte[] bindingSig; + @Setter + @Getter + private byte[] sighashValue; + @Setter + @Getter + private byte[] spendCv; + @Setter + @Getter + private int spendCvLen; + @Setter + @Getter + private byte[] outputCv; + @Setter + @Getter + private int outputCvLen; + + public FinalCheckNewParams(long valueBalance, byte[] bindingSig, byte[] sighashValue, + byte[] spendCv, int spendCvLen, byte[] outputCv, int outputCvLen) throws ZksnarkException { + this.valueBalance = valueBalance; + this.bindingSig = bindingSig; + this.sighashValue = sighashValue; + this.spendCv = spendCv; + this.spendCvLen = spendCvLen; + this.outputCv = outputCv; + this.outputCvLen = outputCvLen; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + validParamLength(bindingSig, 64); + valid32Params(sighashValue); + if (spendCvLen <= 0 || outputCvLen <= 0) { + throw new ZksnarkException("spendCvLen and outputCvLen must be positive"); + } + if (spendCvLen % 32 != 0 || outputCvLen % 32 != 0) { + throw new ZksnarkException( + "spendCvLen and ouFinalCheckNewParamstputCvLen must be multiple of 32"); + } + validParamLength(spendCv, spendCvLen); + validParamLength(outputCv, outputCvLen); + } + } + + /** + * ivk: incoming viewing key, 32 bytes, should be 251bits , not checked; d: 11 bytes pkD: 32 + * bytes + */ + public static class IvkToPkdParams implements ValidParam { + + @Setter + @Getter + private byte[] ivk; + @Setter + @Getter + private byte[] d; + @Setter + @Getter + private byte[] pkD; + + public IvkToPkdParams(byte[] ivk, byte[] d, byte[] pkD) throws ZksnarkException { + this.ivk = ivk; + this.d = d; + this.pkD = pkD; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + valid32Params(ivk); + valid11Params(d); + valid32Params(pkD); + if ((ivk[31] >> 3) != 0) { + throw new ZksnarkException("Most significant five bits of ivk should be 0."); + } + } + } + + /** + * 0 <= depth < 63 a: 32 bytes b: 32 bytes result: 32 bytes + */ + public static class MerkleHashParams implements ValidParam { + + @Setter + @Getter + private int depth; + @Setter + @Getter + private byte[] a; + @Setter + @Getter + private byte[] b; + @Setter + @Getter + private byte[] result; + + public MerkleHashParams(int depth, byte[] a, byte[] b, byte[] result) throws ZksnarkException { + this.depth = depth; + this.a = a; + this.b = b; + this.result = result; + valid(); + } + + @Override + public void valid() throws ZksnarkException { + if (!((depth < 63) && (depth >= 0))) { + throw new ZksnarkException("Merkle tree depth must be smaller than 63"); + } + valid32Params(a); + valid32Params(b); + valid32Params(result); + } + } +} diff --git a/src/main/java/org/tron/core/zen/ShieldedAddressInfo.java b/src/main/java/org/tron/core/zen/ShieldedAddressInfo.java index 551ef9e20..5a6ef7be4 100644 --- a/src/main/java/org/tron/core/zen/ShieldedAddressInfo.java +++ b/src/main/java/org/tron/core/zen/ShieldedAddressInfo.java @@ -119,4 +119,38 @@ public boolean decode(final String data, byte[] encryptKey) throws CipherExcepti return false; } } + + public Optional getNewShieldedAddress() throws ZksnarkException{ + byte[] sk = org.tron.keystore.Wallet.generateRandomBytes(32); + byte[] d = new DiversifierT().random().getData(); + + return getNewShieldedAddressBySkAndD(sk, d); + } + + public Optional getNewShieldedAddressBySkAndD(byte[] skBytes, + byte[] dBytes) throws ZksnarkException { + if (!(skBytes.length == 32 && dBytes.length == 11)) { + return Optional.empty(); + } + SpendingKey sk = new SpendingKey(skBytes); + DiversifierT d = new DiversifierT(dBytes); + ExpandedSpendingKey esk = sk.expandedSpendingKey(); + FullViewingKey fvk = esk.fullViewingKey(); + IncomingViewingKey ivk = fvk.inViewingKey(); + Optional pa = ivk.address(d); + + ShieldedAddressInfo shieldedAddressInfo = new ShieldedAddressInfo( + skBytes, + ivk.value, + esk.getOvk(), + d, + pa.get().getPkD() + ); + + if (shieldedAddressInfo.validateCheck()) { + return Optional.of(shieldedAddressInfo); + } else { + return Optional.empty(); + } + } } diff --git a/src/main/java/org/tron/core/zen/address/DiversifierT.java b/src/main/java/org/tron/core/zen/address/DiversifierT.java index 14a3658d8..cfb27e2d1 100644 --- a/src/main/java/org/tron/core/zen/address/DiversifierT.java +++ b/src/main/java/org/tron/core/zen/address/DiversifierT.java @@ -3,10 +3,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.tron.api.GrpcAPI.DiversifierMessage; -import org.tron.walletserver.WalletApi; - -import java.util.Optional; +import org.tron.common.zksnark.JLibrustzcash; +import org.tron.core.exception.ZksnarkException; @AllArgsConstructor public class DiversifierT { @@ -19,15 +17,14 @@ public class DiversifierT { public DiversifierT() { } - public DiversifierT random() { - Optional diversifierMessage; - while ( true ) { - diversifierMessage = WalletApi.getDiversifier(); - if (diversifierMessage.isPresent()) { + public DiversifierT random() throws ZksnarkException { + byte[] d; + while (true) { + d = org.tron.keystore.Wallet.generateRandomBytes(ZC_DIVERSIFIER_SIZE); + if (JLibrustzcash.librustzcashCheckDiversifier(d)) { break; } } - this.data = diversifierMessage.get().getD().toByteArray(); - return this; + return new DiversifierT(d); } } diff --git a/src/main/java/org/tron/core/zen/address/ExpandedSpendingKey.java b/src/main/java/org/tron/core/zen/address/ExpandedSpendingKey.java index 5653b5f2e..0e9c8c076 100644 --- a/src/main/java/org/tron/core/zen/address/ExpandedSpendingKey.java +++ b/src/main/java/org/tron/core/zen/address/ExpandedSpendingKey.java @@ -1,16 +1,12 @@ package org.tron.core.zen.address; -import com.google.protobuf.ByteString; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.tron.api.GrpcAPI.BytesMessage; import org.tron.common.utils.ByteArray; +import org.tron.common.zksnark.JLibrustzcash; import org.tron.core.exception.ZksnarkException; -import org.tron.walletserver.WalletApi; - -import java.util.Optional; @Slf4j(topic = "shieldTransaction") @AllArgsConstructor @@ -31,24 +27,11 @@ public ExpandedSpendingKey() { } public static byte[] getAkFromAsk(byte[] ask) throws ZksnarkException { - BytesMessage ask1 = BytesMessage.newBuilder().setValue(ByteString.copyFrom(ask)).build(); - Optional ak = WalletApi.getAkFromAsk(ask1); - if (!ak.isPresent()) { - throw new ZksnarkException("getAkFromAsk failed !!!"); - } else { - return ak.get().getValue().toByteArray(); - } + return JLibrustzcash.librustzcashAskToAk(ask); } public static byte[] getNkFromNsk(byte[] nsk) throws ZksnarkException { - - BytesMessage nsk1 = BytesMessage.newBuilder().setValue(ByteString.copyFrom(nsk)).build(); - Optional nk = WalletApi.getNkFromNsk(nsk1); - if (!nk.isPresent()) { - throw new ZksnarkException("getNkFromNsk failed !!!"); - } else { - return nk.get().getValue().toByteArray(); - } + return JLibrustzcash.librustzcashNskToNk(nsk); } public static ExpandedSpendingKey decode(byte[] m_bytes) { diff --git a/src/main/java/org/tron/core/zen/address/FullViewingKey.java b/src/main/java/org/tron/core/zen/address/FullViewingKey.java index 0e58a8ddd..7ec8c737c 100644 --- a/src/main/java/org/tron/core/zen/address/FullViewingKey.java +++ b/src/main/java/org/tron/core/zen/address/FullViewingKey.java @@ -1,15 +1,12 @@ package org.tron.core.zen.address; -import com.google.protobuf.ByteString; +import java.util.Arrays; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.tron.api.GrpcAPI.IncomingViewingKeyMessage; -import org.tron.api.GrpcAPI.ViewingKeyMessage; +import org.tron.common.zksnark.JLibrustzcash; +import org.tron.common.zksnark.LibrustzcashParam; import org.tron.core.exception.ZksnarkException; -import org.tron.walletserver.WalletApi; - -import java.util.Optional; @AllArgsConstructor public class FullViewingKey { @@ -36,17 +33,15 @@ public static FullViewingKey decode(byte[] data) { } public IncomingViewingKey inViewingKey() throws ZksnarkException { - ViewingKeyMessage vk = ViewingKeyMessage.newBuilder() - .setAk(ByteString.copyFrom(ak)) - .setNk(ByteString.copyFrom(nk)) - .build(); - - Optional ivk = WalletApi.getIncomingViewingKey(vk); - if (!ivk.isPresent()) { - throw new ZksnarkException("getIncomingViewingKey failed !!!"); - } else { - return new IncomingViewingKey(ivk.get().getIvk().toByteArray()); - } + byte[] ivk = new byte[32]; // the incoming viewing keyåß + JLibrustzcash.librustzcashCrhIvk(new LibrustzcashParam.CrhIvkParams(ak, nk, ivk)); + return new IncomingViewingKey(ivk); + } + + public boolean isValid() throws ZksnarkException { + byte[] ivk = new byte[32]; + JLibrustzcash.librustzcashCrhIvk(new LibrustzcashParam.CrhIvkParams(ak, nk, ivk)); + return !Arrays.equals(ivk, new byte[32]); } public byte[] encode() { diff --git a/src/main/java/org/tron/core/zen/address/IncomingViewingKey.java b/src/main/java/org/tron/core/zen/address/IncomingViewingKey.java index 20434980e..f5995cee2 100644 --- a/src/main/java/org/tron/core/zen/address/IncomingViewingKey.java +++ b/src/main/java/org/tron/core/zen/address/IncomingViewingKey.java @@ -5,11 +5,9 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.tron.api.GrpcAPI.DiversifierMessage; -import org.tron.api.GrpcAPI.IncomingViewingKeyDiversifierMessage; -import org.tron.api.GrpcAPI.IncomingViewingKeyMessage; -import org.tron.api.GrpcAPI.PaymentAddressMessage; -import org.tron.walletserver.WalletApi; +import org.tron.common.zksnark.JLibrustzcash; +import org.tron.common.zksnark.LibrustzcashParam.IvkToPkdParams; +import org.tron.core.exception.ZksnarkException; import java.util.Optional; @@ -21,19 +19,13 @@ public class IncomingViewingKey { @Getter public byte[] value; // 256 - public Optional address(DiversifierT d) { - DiversifierMessage.Builder dBuilder = DiversifierMessage.newBuilder(); - dBuilder.setD(ByteString.copyFrom(d.getData())); - - IncomingViewingKeyMessage.Builder incomingBuilder = IncomingViewingKeyMessage.newBuilder(); - incomingBuilder.setIvk(ByteString.copyFrom(value)); - - IncomingViewingKeyDiversifierMessage.Builder builder = IncomingViewingKeyDiversifierMessage.newBuilder(); - builder.setD(dBuilder.build()); - builder.setIvk(incomingBuilder.build()); - Optional addressMessage = WalletApi.getZenPaymentAddress(builder.build()); - if (addressMessage.isPresent()) { - return Optional.of(new PaymentAddress(d, addressMessage.get().getPkD().toByteArray())); + public Optional address(DiversifierT d) throws ZksnarkException { + byte[] pkD = new byte[32]; // 32 + if (JLibrustzcash.librustzcashCheckDiversifier(d.getData())) { + if (!JLibrustzcash.librustzcashIvkToPkd(new IvkToPkdParams(value, d.getData(), pkD))) { + throw new ZksnarkException("librustzcashIvkToPkd error"); + } + return Optional.of(new PaymentAddress(d, pkD)); } else { return Optional.empty(); } diff --git a/src/main/java/org/tron/core/zen/address/SpendingKey.java b/src/main/java/org/tron/core/zen/address/SpendingKey.java index f105f33dc..41ec9229b 100644 --- a/src/main/java/org/tron/core/zen/address/SpendingKey.java +++ b/src/main/java/org/tron/core/zen/address/SpendingKey.java @@ -1,15 +1,12 @@ package org.tron.core.zen.address; -import com.google.protobuf.ByteString; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import org.tron.api.GrpcAPI.BytesMessage; -import org.tron.api.GrpcAPI.ExpandedSpendingKeyMessage; +import org.tron.common.zksnark.JLibrustzcash; +import org.tron.common.zksnark.JLibsodium; +import org.tron.common.zksnark.JLibsodiumParam; import org.tron.core.exception.ZksnarkException; -import org.tron.walletserver.WalletApi; - -import java.util.Optional; @AllArgsConstructor public class SpendingKey { @@ -19,20 +16,58 @@ public class SpendingKey { public byte[] value; public ExpandedSpendingKey expandedSpendingKey() throws ZksnarkException { - BytesMessage sk = BytesMessage.newBuilder().setValue(ByteString.copyFrom(value)).build(); - Optional esk = WalletApi.getExpandedSpendingKey(sk); - if (!esk.isPresent()) { - throw new ZksnarkException("getExpandedSpendingKey failed !!!"); - } else { - return new ExpandedSpendingKey( - esk.get().getAsk().toByteArray(), - esk.get().getNsk().toByteArray(), - esk.get().getOvk().toByteArray()); - } + return new ExpandedSpendingKey( + PRF.prfAsk(this.value), PRF.prfNsk(this.value), PRF.prfOvk(this.value)); } public FullViewingKey fullViewingKey() throws ZksnarkException { return expandedSpendingKey().fullViewingKey(); } + + private static class PRF { + public static final byte[] ZTRON_EXPANDSEED_PERSONALIZATION = {'Z', 't', 'r', 'o', 'n', '_', + 'E', 'x', 'p', 'a', 'n', 'd', 'S', 'e', 'e', 'd'}; + + public static byte[] prfAsk(byte[] sk) throws ZksnarkException { + byte[] ask = new byte[32]; + byte t = 0x00; + byte[] tmp = prfExpand(sk, t); + JLibrustzcash.librustzcashToScalar(tmp, ask); + return ask; + } + + public static byte[] prfNsk(byte[] sk) throws ZksnarkException { + byte[] nsk = new byte[32]; + byte t = 0x01; + byte[] tmp = prfExpand(sk, t); + JLibrustzcash.librustzcashToScalar(tmp, nsk); + return nsk; + } + + public static byte[] prfOvk(byte[] sk) throws ZksnarkException { + byte[] ovk = new byte[32]; + byte t = 0x02; + byte[] tmp = prfExpand(sk, t); + System.arraycopy(tmp, 0, ovk, 0, 32); + return ovk; + } + + private static byte[] prfExpand(byte[] sk, byte t) throws ZksnarkException { + byte[] res = new byte[64]; + byte[] blob = new byte[33]; + System.arraycopy(sk, 0, blob, 0, 32); + blob[32] = t; + long state = JLibsodium.initState(); + try { + JLibsodium.cryptoGenerichashBlake2bInitSaltPersonal(new JLibsodiumParam.Blake2bInitSaltPersonalParams( + state, null, 0, 64, null, ZTRON_EXPANDSEED_PERSONALIZATION)); + JLibsodium.cryptoGenerichashBlake2bUpdate(new JLibsodiumParam.Blake2bUpdateParams(state, blob, 33)); + JLibsodium.cryptoGenerichashBlake2bFinal(new JLibsodiumParam.Blake2bFinalParams(state, res, 64)); + } finally { + JLibsodium.freeState(state); + } + return res; + } + } } diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 0569a1fd2..1452e12d8 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.math.BigInteger; +import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.*; import java.util.Base64.Decoder; @@ -29,6 +30,8 @@ import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Utils; +import org.tron.common.zksnark.JLibrustzcash; +import org.tron.common.zksnark.LibrustzcashParam; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; import org.tron.core.exception.EncodingException; @@ -39,8 +42,12 @@ import org.tron.core.zen.ShieldedTRC20Wrapper; import org.tron.core.zen.ShieldedWrapper; import org.tron.core.zen.ZenUtils; +import org.tron.core.zen.address.DiversifierT; +import org.tron.core.zen.address.ExpandedSpendingKey; +import org.tron.core.zen.address.IncomingViewingKey; import org.tron.core.zen.address.KeyIo; import org.tron.core.zen.address.PaymentAddress; +import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; import org.tron.protos.Protocol.MarketOrder; import org.tron.protos.Protocol.MarketOrderList; @@ -2768,11 +2775,16 @@ private void getShieldedNullifier(String[] parameters) { } private void getSpendingKey() { - Optional sk = WalletApi.getSpendingKey(); - if (!sk.isPresent()) { - System.out.println("GetSpendingKey failed !!!"); - } else { - System.out.println(ByteArray.toHexString(sk.get().getValue().toByteArray())); + while (true) { + byte[] skBytes = org.tron.keystore.Wallet.generateRandomBytes(32); + SpendingKey sk = new SpendingKey(skBytes); + try { + if (sk.fullViewingKey().isValid()) { + System.out.println(ByteArray.toHexString(skBytes)); + break; + } + } catch (ZksnarkException e) { + } } } @@ -2782,17 +2794,18 @@ private void getExpandedSpendingKey(String[] parameters) { System.out.println("getExpandedSpendingKey sk "); return; } - String spendingKey = parameters[0]; - - BytesMessage sk = BytesMessage.newBuilder() - .setValue(ByteString.copyFrom(ByteArray.fromHexString(spendingKey))).build(); - Optional esk = WalletApi.getExpandedSpendingKey(sk); - if (!esk.isPresent()) { + byte[] spendingKey = ByteArray.fromHexString(parameters[0]); + if (spendingKey.length != 32) { + System.out.println("GetExpandedSpendingKey failed !!!"); + return; + } + try { + ExpandedSpendingKey esk = new SpendingKey(spendingKey).expandedSpendingKey(); + System.out.println("ask:" + ByteArray.toHexString(esk.getAsk())); + System.out.println("nsk:" + ByteArray.toHexString(esk.getNsk())); + System.out.println("ovk:" + ByteArray.toHexString(esk.getOvk())); + } catch (ZksnarkException e) { System.out.println("GetExpandedSpendingKey failed !!!"); - } else { - System.out.println("ask:" + ByteArray.toHexString(esk.get().getAsk().toByteArray())); - System.out.println("nsk:" + ByteArray.toHexString(esk.get().getNsk().toByteArray())); - System.out.println("ovk:" + ByteArray.toHexString(esk.get().getOvk().toByteArray())); } } @@ -2802,15 +2815,12 @@ private void getAkFromAsk(String[] parameters) { System.out.println("getAkFromAsk ask "); return; } - String ask = parameters[0]; - - BytesMessage ask1 = BytesMessage.newBuilder() - .setValue(ByteString.copyFrom(ByteArray.fromHexString(ask))).build(); - Optional ak = WalletApi.getAkFromAsk(ask1); - if (!ak.isPresent()) { + byte[] ask = ByteArray.fromHexString(parameters[0]); + try { + byte[] ak = ExpandedSpendingKey.getAkFromAsk(ask); + System.out.println("ak:" + ByteArray.toHexString(ak)); + } catch (ZksnarkException e) { System.out.println("GetAkFromAsk failed !!!"); - } else { - System.out.println("ak:" + ByteArray.toHexString(ak.get().getValue().toByteArray())); } } @@ -2820,15 +2830,12 @@ private void getNkFromNsk(String[] parameters) { System.out.println("getNkFromNsk nsk "); return; } - String nsk = parameters[0]; - - BytesMessage nsk1 = BytesMessage.newBuilder() - .setValue(ByteString.copyFrom(ByteArray.fromHexString(nsk))).build(); - Optional nk = WalletApi.getNkFromNsk(nsk1); - if (!nk.isPresent()) { + byte[] nsk = ByteArray.fromHexString(parameters[0]); + try { + byte[] nk = ExpandedSpendingKey.getNkFromNsk(nsk); + System.out.println("nk:" + ByteArray.toHexString(nk)); + } catch (ZksnarkException e) { System.out.println("GetNkFromNsk failed !!!"); - } else { - System.out.println("nk:" + ByteArray.toHexString(nk.get().getValue().toByteArray())); } } @@ -2839,27 +2846,24 @@ private void getIncomingViewingKey(String[] parameters) { System.out.println("getIncomingViewingKey ak[64] nk[64] "); return; } - String ak = parameters[0]; - String nk = parameters[1]; - ViewingKeyMessage vk = ViewingKeyMessage.newBuilder() - .setAk(ByteString.copyFrom(ByteArray.fromHexString(ak))) - .setNk(ByteString.copyFrom(ByteArray.fromHexString(nk))) - .build(); + byte[] ak = ByteArray.fromHexString(parameters[0]); + byte[] nk = ByteArray.fromHexString(parameters[1]); - Optional ivk = WalletApi.getIncomingViewingKey(vk); - if (!ivk.isPresent()) { + byte[] ivk = new byte[32]; // the incoming viewing key + try { + JLibrustzcash.librustzcashCrhIvk(new LibrustzcashParam.CrhIvkParams(ak, nk, ivk)); + System.out.println("ivk:" + ByteArray.toHexString(ivk)); + } catch (ZksnarkException e) { System.out.println("GetIncomingViewingKey failed !!!"); - } else { - System.out.println("ivk:" + ByteArray.toHexString(ivk.get().getIvk().toByteArray())); } } - private void getDiversifier(String[] parameters) { - Optional diversifierMessage = WalletApi.getDiversifier(); - if (!diversifierMessage.isPresent()) { + private void getDiversifier() { + try { + DiversifierT d = new DiversifierT().random(); + System.out.println(ByteArray.toHexString(d.getData())); + } catch (ZksnarkException e) { System.out.println("GetDiversifier failed !!!"); - } else { - System.out.println(ByteArray.toHexString(diversifierMessage.get().getD().toByteArray())); } } @@ -2869,26 +2873,22 @@ private void getShieldedPaymentAddress(String[] parameters) { System.out.println("getShieldedPaymentAddress ivk[64] d[22] "); return; } - String ivk = parameters[0]; - String d = parameters[1]; - IncomingViewingKeyMessage ivk1 = IncomingViewingKeyMessage.newBuilder() - .setIvk(ByteString.copyFrom(ByteArray.fromHexString(ivk))) - .build(); - DiversifierMessage d1 = DiversifierMessage.newBuilder() - .setD(ByteString.copyFrom(ByteArray.fromHexString(d))) - .build(); - IncomingViewingKeyDiversifierMessage ivk_d = IncomingViewingKeyDiversifierMessage.newBuilder() - .setIvk(ivk1) - .setD(d1) - .build(); + byte[] ivkBytes = ByteArray.fromHexString(parameters[0]); + byte[] d = ByteArray.fromHexString(parameters[1]); + IncomingViewingKey ivk = new IncomingViewingKey(ivkBytes); - Optional paymentAddress = WalletApi.getZenPaymentAddress(ivk_d); - if (!paymentAddress.isPresent()) { + try { + Optional paymentAddress = ivk.address(new DiversifierT(d)); + if (!paymentAddress.isPresent()) { + System.out.println("GetShieldedPaymentAddress failed !!!"); + } else { + PaymentAddress pa = paymentAddress.get(); + System.out.println("pkd:" + ByteArray.toHexString(pa.getPkD())); + System.out.println("shieldedAddress:" + KeyIo.encodePaymentAddress(pa)); + } + } catch (ZksnarkException e) { System.out.println("GetShieldedPaymentAddress failed !!!"); - } else { - System.out.println("pkd:" + ByteArray.toHexString(paymentAddress.get().getPkD().toByteArray())); - System.out.println("shieldedAddress:" + paymentAddress.get().getPaymentAddress()); } } @@ -3189,7 +3189,7 @@ private void generateShieldedTRC20Address(String[] parameters) throws IOExceptio System.out.println("ShieldedTRC20Address list:"); for (int i = 0; i < addressNum; ++i) { - Optional addressInfo = walletApiWrapper.getNewShieldedAddress(); + Optional addressInfo = new ShieldedAddressInfo().getNewShieldedAddress(); if (addressInfo.isPresent()) { if (ShieldedTRC20Wrapper.getInstance().addNewShieldedTRC20Address( addressInfo.get(), true)) { @@ -3197,7 +3197,6 @@ private void generateShieldedTRC20Address(String[] parameters) throws IOExceptio } } } - System.out.println("GenerateShieldedTRC20Address successful !!!"); } private void importShieldedTRC20Wallet() throws CipherException, IOException, ZksnarkException { @@ -3214,7 +3213,7 @@ private void importShieldedTRC20Wallet() throws CipherException, IOException, Zk System.arraycopy(priKey, 0, sk, 0, sk.length); System.arraycopy(priKey, sk.length, d, 0, d.length); Optional addressInfo = - walletApiWrapper.getNewShieldedAddressBySkAndD(sk, d); + new ShieldedAddressInfo().getNewShieldedAddressBySkAndD(sk, d); if (addressInfo.isPresent() && ShieldedTRC20Wrapper.getInstance().addNewShieldedTRC20Address( addressInfo.get(), false)) { System.out.println("Import new shieldedTRC20 wallet address is: " @@ -3354,6 +3353,12 @@ private void scanShieldedTRC20NoteByIvk(String[] parameters) { System.out.println("ScanShieldedTRC20NoteByIvk failed! Invalid shieldedTRC20ContractAddress"); return; } + String ak = parameters[2]; + String nk = parameters[3]; + if (ak.equals("null") || nk.equals("null")) { + ak = null; + nk = null; + } long startNum; long endNum; try { @@ -3370,10 +3375,10 @@ private void scanShieldedTRC20NoteByIvk(String[] parameters) { eventArray[i] = parameters[i + 6]; } walletApiWrapper.scanShieldedTRC20NoteByIvk(contractAddress, - parameters[1], parameters[2], parameters[3], startNum, endNum, eventArray); + parameters[1], ak, nk, startNum, endNum, eventArray); } else { walletApiWrapper.scanShieldedTRC20NoteByIvk(contractAddress, - parameters[1], parameters[2], parameters[3], startNum, endNum, null); + parameters[1], ak, nk, startNum, endNum, null); } } @@ -4075,7 +4080,7 @@ private void run() { break; } case "getdiversifier": { - getDiversifier(parameters); + getDiversifier(); break; } case "getshieldedpaymentaddress": { diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 976b1a6bd..80de58f2d 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1277,7 +1277,9 @@ public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, + ByteArray.toHexString(noteTx.getNote().getRcm().toByteArray())); System.out.println("\t\t\t memo: " + noteTx.getNote().getMemo().toStringUtf8()); System.out.println("\t\t }\n\t\t position: " + noteTx.getPosition()); - System.out.println("\t\t is_spent: " + noteTx.getIsSpent()); + if (!(ak == null || nk == null)) { + System.out.println("\t\t is_spent: " + noteTx.getIsSpent()); + } System.out.println("\t\t tx_id: " + ByteArray.toHexString(noteTx.getTxid().toByteArray())); System.out.println("\t}"); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 698072f70..4f5220b95 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -73,6 +73,8 @@ import org.tron.common.utils.ByteArray; import org.tron.common.utils.TransactionUtils; import org.tron.common.utils.Utils; +import org.tron.common.zksnark.JLibrustzcash; +import org.tron.common.zksnark.LibrustzcashParam.SpendSigParams; import org.tron.core.config.Configuration; import org.tron.core.config.Parameter.CommonConstant; import org.tron.core.exception.CancelException; @@ -2683,14 +2685,16 @@ public static ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk ShieldedTRC20Parameters.Builder newBuilder = ShieldedTRC20Parameters.newBuilder().mergeFrom(parameters); for (int i = 0; i < spendDescList.size(); i++) { - SpendAuthSigParameters.Builder builder = SpendAuthSigParameters.newBuilder(); - builder.setAsk(ByteString.copyFrom(ask)); - builder.setTxHash(messageHash); - builder.setAlpha(privateParameters.getShieldedSpends(i).getAlpha()); - BytesMessage authSig; try { - authSig = rpcCli.createSpendAuthSig(builder.build()); + byte[] sig = new byte[64]; + SpendSigParams spendSigParams = new SpendSigParams( + ask, + privateParameters.getShieldedSpends(i).getAlpha().toByteArray(), + messageHash.toByteArray(), + sig); + JLibrustzcash.librustzcashSaplingSpendSig(spendSigParams); + authSig = BytesMessage.newBuilder().setValue(ByteString.copyFrom(sig)).build(); } catch (Exception e) { Status status = Status.fromThrowable(e); System.out.println("createSpendAuthSig failed,error " From d7d89e54a88cd8c3f23e560543320052ef38ce70 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Fri, 29 Jan 2021 17:15:51 +0800 Subject: [PATCH 359/445] Supplementary description of the parameters of the command ScanShieldedTRC20NoteByIvk --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6585b1368..0dec55b07 100644 --- a/README.md +++ b/README.md @@ -1934,10 +1934,10 @@ ivk > The ivk of shielded address ak -> The ak of shielded address +> The ak of shielded address. Please set this field to null, if you don't care whether the notes are spent or not. nk -> The nk of shielded address +> The nk of shielded address. Please set this field to null, if you don't care whether the notes are spent or not. startNum > The starting block number of the scan From daea3550d7c0a1926ed0e77cad22e1b86540923a Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Tue, 2 Feb 2021 16:56:27 +0800 Subject: [PATCH 360/445] comment out useless command prompts --- src/main/java/org/tron/walletcli/Client.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 1452e12d8..073c6d3e6 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -159,10 +159,10 @@ public class Client { "ListShieldedTRC20Note", "ListProposals", "ListProposalsPaginated", - "ListShieldedAddress", - "ListShieldedNote", + // "ListShieldedAddress", + // "ListShieldedNote", "ListWitnesses", - "LoadShieldedWallet", + // "LoadShieldedWallet", "Login", "Logout", "LoadShieldedTRC20Wallet", @@ -290,8 +290,8 @@ public class Client { "ListShieldedTRC20Note", "ListProposals", "ListProposalsPaginated", - "ListShieldedAddress", - "ListShieldedNote", + // "ListShieldedAddress", + // "ListShieldedNote", "ListWitnesses", "Login", "Logout", From b7a8bd371734c527cf9af8574993b740c1bc9214 Mon Sep 17 00:00:00 2001 From: wenpinghou Date: Tue, 2 Feb 2021 18:01:58 +0800 Subject: [PATCH 361/445] increase the feeLimit of triggering shielded contract --- src/main/java/org/tron/walletcli/WalletApiWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 80de58f2d..d359a754d 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1796,7 +1796,7 @@ public boolean triggerShieldedContract(String contractAddress, String data, byte[] inputData = Hex.decode(AbiUtil.parseMethod(methodStr, data, true)); byte[] ownerAddress = wallet.getAddress(); - return callContract(ownerAddress, contractAddressBytes, 0, inputData, 20_000_000L, + return callContract(ownerAddress, contractAddressBytes, 0, inputData, 200_000_000L, 0, "", false); } From 481b728e1db58b6309d33b386646b33b46a11d17 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Fri, 5 Mar 2021 22:22:28 +0800 Subject: [PATCH 362/445] shielded transfer demo --- .../java/org/tron/demo/ShieldedTRC20Demo.java | 486 +++++++++++------- 1 file changed, 294 insertions(+), 192 deletions(-) diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index 9f5a60677..a42a6d132 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -2,13 +2,18 @@ import com.google.protobuf.ByteString; import java.math.BigInteger; +import java.security.SecureRandom; import java.util.List; import java.util.Optional; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.spongycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; +import org.tron.api.GrpcAPI.Note; import org.tron.api.GrpcAPI.PrivateShieldedTRC20Parameters; +import org.tron.api.GrpcAPI.ReceiveNote; import org.tron.api.GrpcAPI.Return; import org.tron.api.GrpcAPI.SpendNoteTRC20; import org.tron.api.GrpcAPI.TransactionExtention; @@ -19,6 +24,7 @@ import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Hash; import org.tron.common.utils.TransactionUtils; +import org.tron.common.zksnark.JLibrustzcash; import org.tron.core.config.Parameter.CommonConstant; import org.tron.core.exception.ZksnarkException; import org.tron.core.zen.address.DiversifierT; @@ -37,152 +43,201 @@ import org.tron.walletserver.GrpcClient; import org.tron.walletserver.WalletApi; + @Slf4j public class ShieldedTRC20Demo { - private static String trc20ContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; - private static String shieldedTRC20ContractAddress = "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"; - private static String privateKey = - "2c8893287a87ac9f4b70af14fbae75e5c898e3b6645e5fed311f5fe60b2dff2f"; - private static String pubAddress = "TXmiKi5UZ6Pqe22aW5R8LEcNGGpgh2BNMH"; - private static String spendingKey = "004f74ce2bde08f0c936f2929b94cb2ca49111db95001576f99d04c3e671daf6"; - private static GrpcClient grpcClient = WalletApi.init(); - private static BigInteger scalingFactorBi = getScalingFactorBi(); + private String trc20 = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; + private String shieldedTRC20 = "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"; + private byte[] deShieldedTRC20 = WalletApi.decodeFromBase58Check(shieldedTRC20); + + private String privateKey = "your private key of transparent address"; + + private String sk = "your sk of shielded address"; + private String rcm = "should generate new rcm when trigger contract"; + private ShieldedKey shieldedKey = generateShieldedKey(sk); + + private GrpcClient grpcClient = WalletApi.init(); + private BigInteger scalingFactorBi = getScalingFactorBi(); + + public ShieldedTRC20Demo() throws ZksnarkException { + } public static void main(String[] args) throws ZksnarkException, InterruptedException { + ShieldedTRC20Demo demo = new ShieldedTRC20Demo(); + demo.mintDemo(demo.privateKey, 1, demo.shieldedKey.getKioAddress()); + demo.transferDemo(demo.privateKey, 5, demo.shieldedKey.getKioAddress(), + 2, 3); + demo.burnDemo(demo.privateKey, 5, demo.shieldedKey.getKioAddress(), 3, + getAddressFromPK(demo.privateKey), 2); + } - mintDemo(); - transferDemo(); - burnDemo(); + public static byte[] getAddressFromPK(String pk) { + ECKey ecKey = ECKey.fromPrivate(ByteArray.fromHexString(pk)); + return ecKey.getAddress(); } - private static String mintDemo() throws ZksnarkException { + public static byte[] generateSk() { + byte[] bytes = new byte[32]; + new SecureRandom().nextBytes(bytes); + return bytes; + } - SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); - ExpandedSpendingKey expsk = sk.expandedSpendingKey(); - byte[] ovk = expsk.getOvk(); - long fromAmount = 1; - FullViewingKey fullViewingKey = sk.fullViewingKey(); - IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); - PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); + public static byte[] generateD() throws ZksnarkException { + byte[] bytes = new byte[11]; + while(true) { + new SecureRandom().nextBytes(bytes); + if (JLibrustzcash.librustzcashCheckDiversifier(bytes)) { + return bytes; + } + } + } - //set approve - setAllowance(fromAmount); - //ReceiveNote - GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); - long revValue = fromAmount; + public static String generateRcm() { + return ByteArray.toHexString(WalletApi.getRcm().get().getValue().toByteArray()); + } + + public void testGenerateKeys() throws ZksnarkException { + ShieldedKey shieldedKey = generateShieldedKey(sk); + System.out.println("ask=" + shieldedKey.getHexAsk()); + System.out.println("nsk=" + shieldedKey.getHexNsk()); + System.out.println("ovk=" + shieldedKey.getHexOvk()); + System.out.println("ak=" + shieldedKey.getHexAk()); + System.out.println("nk=" + shieldedKey.getHexNk()); + System.out.println("ivk=" + shieldedKey.getHexIvk()); + System.out.println("address=" + shieldedKey.getKioAddress()); + } + + public ShieldedKey generateShieldedKey(String sKey) throws ZksnarkException { + byte[] sk; + if (sKey.equalsIgnoreCase("")) { + sk = generateSk(); + } else { + sk = ByteArray.fromHexString(sKey); + } + byte[] d = generateD(); + ShieldedKey key = new ShieldedKey(); + SpendingKey spendingKey = new SpendingKey(sk); + ExpandedSpendingKey esk = spendingKey.expandedSpendingKey(); + key.setSk(sk); + key.setAsk(esk.getAsk()); + key.setNsk(esk.getNsk()); + key.setOvk(esk.getOvk()); + key.setAk(ExpandedSpendingKey.getAkFromAsk(esk.getAsk())); + key.setNk(ExpandedSpendingKey.getNkFromNsk(esk.getNsk())); + key.setIvk(spendingKey.fullViewingKey().inViewingKey().getValue()); + key.setD(d); + return key; + } + + public void addReceiveShieldedNote(PrivateShieldedTRC20Parameters.Builder paramBuilder, + String receiveShieldedAddress, long value) { byte[] memo = new byte[512]; - byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); - GrpcAPI.Note revNote = getNote(revValue, KeyIo.encodePaymentAddress(paymentAddress), rcm, memo); - revNoteBuilder.setNote(revNote); + Note note = buildNote(value, receiveShieldedAddress, ByteArray.fromHexString(rcm), memo); + ReceiveNote.Builder receiveNote = GrpcAPI.ReceiveNote.newBuilder(); + receiveNote.setNote(note); + paramBuilder.addShieldedReceives(receiveNote); + } - byte[] contractAddress = WalletApi.decodeFromBase58Check(shieldedTRC20ContractAddress); - GrpcAPI.PrivateShieldedTRC20Parameters.Builder paramBuilder = GrpcAPI - .PrivateShieldedTRC20Parameters.newBuilder(); - paramBuilder.setOvk(ByteString.copyFrom(ovk)); + public void setTransparent(PrivateShieldedTRC20Parameters.Builder paramBuilder, + long fromAmount, byte[] toTransparentAddress, long toTransparentAmount) { paramBuilder.setFromAmount(getScaledPublicAmount(fromAmount)); - paramBuilder.addShieldedReceives(revNoteBuilder.build()); - paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + paramBuilder.setToAmount(toTransparentAmount + ""); + if (toTransparentAddress != null) { + paramBuilder.setTransparentToAddress(ByteString.copyFrom(toTransparentAddress)); + } + } - GrpcAPI.ShieldedTRC20Parameters trc20MintParams = WalletApi - .createShieldedContractParameters(paramBuilder.build()); - byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); - return triggerMint(contractAddress, callerAddress, privateKey, - trc20MintParams.getTriggerContractInput()); - } - - public static void transferDemo() throws ZksnarkException, InterruptedException { - byte[] contractAddress = WalletApi - .decodeFromBase58Check(shieldedTRC20ContractAddress); - byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); - SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); - setAllowance(2); - GrpcAPI.PrivateShieldedTRC20Parameters mintPrivateParam1 = mintParams( - privateKey, 2, shieldedTRC20ContractAddress); - GrpcAPI.ShieldedTRC20Parameters mintParam1 = WalletApi.createShieldedContractParameters( - mintPrivateParam1); - String mintInput = mintParam1.getTriggerContractInput(); - String txid = triggerMint(contractAddress, callerAddress, privateKey, mintInput); - - Optional infoById = waitToGetTransactionInfo(txid); - GrpcAPI.PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = GrpcAPI - .PrivateShieldedTRC20Parameters.newBuilder(); - privateTRC20Builder - .addShieldedSpends(getSpendNote(infoById.get(), mintPrivateParam1, contractAddress)); + public void setContractAddress(PrivateShieldedTRC20Parameters.Builder paramBuilder) { + paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(deShieldedTRC20)); + } + + public void setKey(PrivateShieldedTRC20Parameters.Builder paramBuilder, byte[] ask, byte[] nsk, + byte[] ovk) { + if (ask != null) { + paramBuilder.setAsk(ByteString.copyFrom(ask)); + } + if (nsk != null) { + paramBuilder.setNsk(ByteString.copyFrom(nsk)); + } + if (ovk != null) { + paramBuilder.setOvk(ByteString.copyFrom(ovk)); + } + } + + public String mintDemo(String fromPrivate, long fromAmount, String toShieldedAddress) + throws InterruptedException { + setAllowance(fromPrivate, fromAmount); + Thread.sleep(2000); + PrivateShieldedTRC20Parameters.Builder paramBuilder = + GrpcAPI.PrivateShieldedTRC20Parameters.newBuilder(); + //set receive note + addReceiveShieldedNote(paramBuilder, toShieldedAddress, fromAmount); + //set transparent + setTransparent(paramBuilder, fromAmount, null, 0); + //set key + setKey(paramBuilder, null, null, shieldedKey.getOvk()); + //set contract address + setContractAddress(paramBuilder); + GrpcAPI.ShieldedTRC20Parameters trc20MintParams = + WalletApi.createShieldedContractParameters(paramBuilder.build()); + return triggerMint(fromPrivate, trc20MintParams.getTriggerContractInput()); + } + + public void transferDemo(String fromPrivate, long fromAmount, String toShieldedAddress, + long toAmount1, long toAmount2) throws InterruptedException { + String hash = mintDemo(fromPrivate, fromAmount, toShieldedAddress); + Optional infoById = waitToGetTransactionInfo(hash); + + PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = + PrivateShieldedTRC20Parameters.newBuilder(); + //set spend note + Note note = buildNote(5, toShieldedAddress, ByteArray.fromHexString(rcm), new byte[512]); + privateTRC20Builder.addShieldedSpends(getSpendNote(infoById.get(), note, deShieldedTRC20)); + //set receive note 1 + addReceiveShieldedNote(privateTRC20Builder, toShieldedAddress, toAmount1); + //set receive note 2 + addReceiveShieldedNote(privateTRC20Builder, toShieldedAddress, toAmount2); + //set contract address + setContractAddress(privateTRC20Builder); + //set key + setKey(privateTRC20Builder, shieldedKey.getAsk(), shieldedKey.getNsk(), shieldedKey.getOvk()); + //no need to set transparent - //ReceiveNote 1 - GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); - FullViewingKey fullViewingKey = sk.fullViewingKey(); - IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); - PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); - long revValue = 1; - byte[] memo = new byte[512]; - byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); - String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); - GrpcAPI.Note revNote = getNote(revValue, paymentAddressStr, rcm, memo); - revNoteBuilder.setNote(revNote); - privateTRC20Builder.addShieldedReceives(revNoteBuilder.build()); - - //ReceiveNote 2 - GrpcAPI.ReceiveNote.Builder revNoteBuilder2 = GrpcAPI.ReceiveNote.newBuilder(); - PaymentAddress paymentAddress2 = incomingViewingKey.address(new DiversifierT().random()).get(); - String paymentAddressStr2 = KeyIo.encodePaymentAddress(paymentAddress2); - long revValue2 = 1; - byte[] memo2 = new byte[512]; - byte[] rcm2 = WalletApi.getRcm().get().getValue().toByteArray(); - - GrpcAPI.Note revNote2 = getNote(revValue2, paymentAddressStr2, rcm2, memo2); - revNoteBuilder2.setNote(revNote2); - privateTRC20Builder.addShieldedReceives(revNoteBuilder2.build()); - - - ExpandedSpendingKey expsk = sk.expandedSpendingKey(); - privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); - privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); - privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); - privateTRC20Builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); GrpcAPI.ShieldedTRC20Parameters transferParam = WalletApi .createShieldedContractParameters(privateTRC20Builder.build()); - triggerTransfer(contractAddress, callerAddress, privateKey, - transferParam.getTriggerContractInput()); - } - - public static void burnDemo() throws ZksnarkException, InterruptedException { - byte[] contractAddress = WalletApi - .decodeFromBase58Check(shieldedTRC20ContractAddress); - byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); - SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); - GrpcAPI.PrivateShieldedTRC20Parameters mintPrivateParam1 = mintParams( - privateKey, 1, shieldedTRC20ContractAddress); - setAllowance(1); - GrpcAPI.ShieldedTRC20Parameters mintParam1 = WalletApi.createShieldedContractParameters( - mintPrivateParam1); - String mintInput1 = mintParam1.getTriggerContractInput(); - String txid = triggerMint(contractAddress, callerAddress, privateKey, mintInput1); - - // SpendNoteTRC20 1 - Optional infoById = waitToGetTransactionInfo(txid); - GrpcAPI.PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = GrpcAPI - .PrivateShieldedTRC20Parameters.newBuilder(); - privateTRC20Builder - .addShieldedSpends(getSpendNote(infoById.get(), mintPrivateParam1, contractAddress)); - - ExpandedSpendingKey expsk = sk.expandedSpendingKey(); - privateTRC20Builder.setAsk(ByteString.copyFrom(expsk.getAsk())); - privateTRC20Builder.setNsk(ByteString.copyFrom(expsk.getNsk())); - privateTRC20Builder.setOvk(ByteString.copyFrom(expsk.getOvk())); - BigInteger toAmount = BigInteger.valueOf(1).multiply(scalingFactorBi); - privateTRC20Builder.setToAmount(toAmount.toString()); - privateTRC20Builder.setTransparentToAddress(ByteString.copyFrom(callerAddress)); - privateTRC20Builder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); + triggerTransfer(deShieldedTRC20, privateKey, transferParam.getTriggerContractInput()); + } + + public void burnDemo(String fromPrivate, long fromAmount, String toShieldedAddress, + long toShieldedAmount, byte[] toTransparentAddress, long toTransparentAmount) + throws InterruptedException { + String hash = mintDemo(fromPrivate, fromAmount, toShieldedAddress); + Optional infoById = waitToGetTransactionInfo(hash); + Note note = buildNote(fromAmount, toShieldedAddress, + ByteArray.fromHexString(rcm), new byte[512]); + + PrivateShieldedTRC20Parameters.Builder privateTRC20Builder = + PrivateShieldedTRC20Parameters.newBuilder(); + //set key + setKey(privateTRC20Builder, shieldedKey.getAsk(), shieldedKey.getNsk(), shieldedKey.getOvk()); + //set transparent + setTransparent(privateTRC20Builder, 0, toTransparentAddress, toTransparentAmount); + //set spend note + privateTRC20Builder.addShieldedSpends(getSpendNote(infoById.get(), note, deShieldedTRC20)); + //set receive note + addReceiveShieldedNote(privateTRC20Builder, toShieldedAddress, toShieldedAmount); + //set contract address + setContractAddress(privateTRC20Builder); + GrpcAPI.ShieldedTRC20Parameters burnParam = WalletApi .createShieldedContractParameters(privateTRC20Builder.build()); - triggerBurn(contractAddress, callerAddress, privateKey, - burnParam.getTriggerContractInput()); + triggerBurn(deShieldedTRC20, privateKey, burnParam.getTriggerContractInput()); } - private static GrpcAPI.Note getNote(long value, String paymentAddress, byte[] rcm, byte[] memo) { + private static GrpcAPI.Note buildNote( + long value, String paymentAddress, byte[] rcm, byte[] memo) { GrpcAPI.Note.Builder noteBuilder = GrpcAPI.Note.newBuilder(); noteBuilder.setValue(value); noteBuilder.setPaymentAddress(paymentAddress); @@ -191,52 +246,63 @@ private static GrpcAPI.Note getNote(long value, String paymentAddress, byte[] rc return noteBuilder.build(); } - private static String triggerMint(byte[] contractAddress, - byte[] callerAddress, String privateKey, String input) { + private SpendNoteTRC20 getSpendNote(TransactionInfo txInfo, + Note note, byte[] contractAddress) { + byte[] txData = txInfo.getLog(1).getData().toByteArray(); + long pos = bytes32ToLong(ByteArray.subArray(txData, 0, 32)); + byte[] contractResult = triggerGetPath(contractAddress, pos); + byte[] path = ByteArray.subArray(contractResult, 32, 1056); + byte[] root = ByteArray.subArray(contractResult, 0, 32); + GrpcAPI.SpendNoteTRC20.Builder noteBuilder = GrpcAPI.SpendNoteTRC20.newBuilder(); + noteBuilder.setAlpha(ByteString.copyFrom(WalletApi.getRcm().get().getValue().toByteArray())); + noteBuilder.setPos(pos); + noteBuilder.setPath(ByteString.copyFrom(path)); + noteBuilder.setRoot(ByteString.copyFrom(root)); + noteBuilder.setNote(note); + return noteBuilder.build(); + } + + private String triggerMint(String privateKey, String input) { String methodSign = "mint(uint256,bytes32[9],bytes32[2],bytes32[21])"; byte[] selector = new byte[4]; System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); - return triggerContract(contractAddress, + return triggerContract(deShieldedTRC20, "mint(uint256,bytes32[9],bytes32[2],bytes32[21])", input, true, - 0L, 50000000L, + 0L, 90000000L, "0", 0, - callerAddress, privateKey); + privateKey); } - private static String triggerTransfer( - byte[] contractAddress, - byte[] callerAddress, String privateKey, String input) { + private String triggerTransfer(byte[] contractAddress, String privateKey, String input) { String txid = triggerContract(contractAddress, "transfer(bytes32[10][],bytes32[2][],bytes32[9][],bytes32[2],bytes32[21][])", input, true, - 0L, 50000000L, + 0L, 90000000L, "0", 0, - callerAddress, privateKey); + privateKey); Optional infoById = WalletApi.getTransactionInfoById(txid); return txid; } - private static String triggerBurn(byte[] contractAddress, - byte[] callerAddress, String privateKey, String input) { + private String triggerBurn(byte[] contractAddress, String privateKey, String input) { return triggerContract(contractAddress, "burn(bytes32[10],bytes32[2],uint256,bytes32[2],address,bytes32[3],bytes32[9][]," + "bytes32[21][])", input, true, - 0L, 50000000L, + 0L, 100000000L, "0", 0, - callerAddress, privateKey); + privateKey); } - private static String triggerContract(byte[] contractAddress, String method, String argsStr, + private String triggerContract(byte[] contractAddress, String method, String argsStr, Boolean isHex, long callValue, long feeLimit, String tokenId, long tokenValue, - byte[] ownerAddress, String priKey) { WalletApi.setAddressPreFixByte(CommonConstant.ADD_PRE_FIX_BYTE_MAINNET); ECKey temKey = null; @@ -252,7 +318,7 @@ private static String triggerContract(byte[] contractAddress, String method, Str argsStr = ""; } - byte[] owner = ownerAddress; + byte[] owner = getAddressFromPK(priKey); byte[] input = Hex.decode(AbiUtil.parseMethod(method, argsStr, isHex)); TriggerSmartContract.Builder builder = TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); @@ -333,18 +399,17 @@ public static Protocol.Transaction signTransaction(ECKey ecKey, return null; } transaction = TransactionUtils.setTimestamp(transaction); - logger.info("Txid in sign is " + ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); + logger.info("Txid in sign is " + ByteArray.toHexString( + Sha256Sm3Hash.hash(transaction.getRawData().toByteArray()))); return TransactionUtils.sign(transaction, ecKey); } - private static BigInteger getScalingFactorBi() { - byte[] contractAddress = WalletApi - .decodeFromBase58Check(shieldedTRC20ContractAddress); - byte[] scalingFactorBytes = triggerGetScalingFactor(contractAddress); + private BigInteger getScalingFactorBi() { + byte[] scalingFactorBytes = triggerGetScalingFactor(deShieldedTRC20); return ByteUtil.bytesToBigInteger(scalingFactorBytes); } - private static byte[] triggerGetScalingFactor( + private byte[] triggerGetScalingFactor( byte[] contractAddress) { String methodSign = "scalingFactor()"; byte[] selector = new byte[4]; @@ -361,56 +426,42 @@ private static byte[] triggerGetScalingFactor( result = ByteUtil.merge(result, bs.toByteArray()); } Assert.assertEquals(32, result.length); - System.out.println(ByteArray.toHexString(result)); return result; } - private static String getScaledPublicAmount(long amount) { + private String getScaledPublicAmount(long amount) { BigInteger result = BigInteger.valueOf(amount).multiply(scalingFactorBi); return result.toString(); } - - private static void setAllowance(long amount) { - byte[] contractAddress = WalletApi - .decodeFromBase58Check(trc20ContractAddress); - byte[] shieldedContractAddress = WalletApi - .decodeFromBase58Check(shieldedTRC20ContractAddress); + private void setAllowance(String privateKey, long amount) { + byte[] contractAddress = WalletApi.decodeFromBase58Check(trc20); byte[] shieldedContractAddressPadding = new byte[32]; - System.arraycopy(shieldedContractAddress, 0, shieldedContractAddressPadding, 11, 21); + System.arraycopy(deShieldedTRC20, 0, + shieldedContractAddressPadding, 11, 21); byte[] valueBytes = longTo32Bytes(amount); String input = Hex.toHexString(ByteUtil.merge(shieldedContractAddressPadding, valueBytes)); - byte[] callerAddress = WalletApi.decodeFromBase58Check(pubAddress); - String txid = triggerContract(contractAddress, - "approve(address,uint256)", - input, - true, - 0L, - 10000000L, - "0", - 0, - callerAddress, - privateKey); + triggerContract(contractAddress, "approve(address,uint256)", input, true, + 0L, 10000000L, "0", 0, privateKey); } - private static GrpcAPI.PrivateShieldedTRC20Parameters mintParams(String privKey, - long value, String contractAddr) + private PrivateShieldedTRC20Parameters mintParams(long value, String contractAddr) throws ZksnarkException { BigInteger fromAmount = BigInteger.valueOf(value).multiply(scalingFactorBi); - SpendingKey sk = new SpendingKey(ByteArray.fromHexString(spendingKey)); - ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + SpendingKey spendingKey = new SpendingKey(ByteArray.fromHexString(sk)); + ExpandedSpendingKey expsk = spendingKey.expandedSpendingKey(); byte[] ovk = expsk.getOvk(); // ReceiveNote GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); // SpendingKey spendingKey = SpendingKey.random(); - FullViewingKey fullViewingKey = sk.fullViewingKey(); + FullViewingKey fullViewingKey = spendingKey.fullViewingKey(); IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); byte[] memo = new byte[512]; - byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); +// byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); - GrpcAPI.Note revNote = getNote(value, paymentAddressStr, rcm, memo); + GrpcAPI.Note revNote = buildNote(value, paymentAddressStr, ByteArray.fromHexString(rcm), memo); revNoteBuilder.setNote(revNote); byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddr); @@ -423,7 +474,7 @@ private static GrpcAPI.PrivateShieldedTRC20Parameters mintParams(String privKey, return paramBuilder.build(); } - private static byte[] triggerGetPath(byte[] contractAddress, long pos) { + private byte[] triggerGetPath(byte[] contractAddress, long pos) { String methodSign = "getPath(uint256)"; byte[] selector = new byte[4]; System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); @@ -441,7 +492,7 @@ private static byte[] triggerGetPath(byte[] contractAddress, long pos) { return result; } - private static Optional waitToGetTransactionInfo(String txid) + private Optional waitToGetTransactionInfo(String txid) throws InterruptedException { logger.info("mint txid: " + txid); Optional infoById = WalletApi.getTransactionInfoById(txid); @@ -453,22 +504,6 @@ private static Optional waitToGetTransactionInfo(String txid) return infoById; } - private static SpendNoteTRC20 getSpendNote(TransactionInfo txInfo, - PrivateShieldedTRC20Parameters mintPrivateParam1, byte[] contractAddress) { - byte[] tx1Data = txInfo.getLog(1).getData().toByteArray(); - long pos = bytes32ToLong(ByteArray.subArray(tx1Data, 0, 32)); - byte[] contractResult = triggerGetPath(contractAddress, pos); - byte[] path = ByteArray.subArray(contractResult, 32, 1056); - byte[] root = ByteArray.subArray(contractResult, 0, 32); - GrpcAPI.SpendNoteTRC20.Builder noteBuilder = GrpcAPI.SpendNoteTRC20.newBuilder(); - noteBuilder.setAlpha(ByteString.copyFrom(WalletApi.getRcm().get().getValue().toByteArray())); - noteBuilder.setPos(pos); - noteBuilder.setPath(ByteString.copyFrom(path)); - noteBuilder.setRoot(ByteString.copyFrom(root)); - noteBuilder.setNote(mintPrivateParam1.getShieldedReceives(0).getNote()); - return noteBuilder.build(); - } - private static byte[] longTo32Bytes(long value) { byte[] longBytes = ByteArray.fromLong(value); byte[] zeroBytes = new byte[24]; @@ -478,4 +513,71 @@ private static byte[] longTo32Bytes(long value) { private static long bytes32ToLong(byte[] value) { return ByteArray.toLong(value); } + + class ShieldedKey { + @Getter + @Setter + byte[] sk; + @Getter + @Setter + byte[] ask; + @Getter + @Setter + byte[] nsk; + @Getter + @Setter + byte[] ovk; + @Getter + @Setter + byte[] ak; + @Getter + @Setter + byte[] nk; + @Getter + @Setter + byte[] ivk; + @Getter + @Setter + byte[] d; + @Getter + @Setter + String address; + + String getHexSk() { + return ByteArray.toHexString(sk); + } + + String getHexAsk() { + return ByteArray.toHexString(ask); + } + + String getHexNsk() { + return ByteArray.toHexString(nsk); + } + + String getHexOvk() { + return ByteArray.toHexString(ovk); + } + + String getHexAk() { + return ByteArray.toHexString(ak); + } + + String getHexNk() { + return ByteArray.toHexString(nk); + } + + String getHexIvk() { + return ByteArray.toHexString(ivk); + } + + String getHexD() { + return ByteArray.toHexString(d); + } + + String getKioAddress() throws ZksnarkException { + IncomingViewingKey incomingViewingKey = new IncomingViewingKey(ivk); + return KeyIo.encodePaymentAddress(incomingViewingKey.address(new DiversifierT(d)).get()); + } + } } From 1007a76b8bd831ee5094f5dbe25f97f7df424626 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Fri, 5 Mar 2021 23:39:14 +0800 Subject: [PATCH 363/445] refactor demo --- .../java/org/tron/demo/ShieldedTRC20Demo.java | 61 +++++-------------- 1 file changed, 14 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index a42a6d132..385e84a26 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -47,9 +47,10 @@ @Slf4j public class ShieldedTRC20Demo { - private String trc20 = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; - private String shieldedTRC20 = "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"; - private byte[] deShieldedTRC20 = WalletApi.decodeFromBase58Check(shieldedTRC20); + private byte[] trc20 = WalletApi.decodeFromBase58Check( + "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"); + private byte[] deShieldedTRC20 = WalletApi.decodeFromBase58Check( + "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"); private String privateKey = "your private key of transparent address"; @@ -236,8 +237,7 @@ public void burnDemo(String fromPrivate, long fromAmount, String toShieldedAddre triggerBurn(deShieldedTRC20, privateKey, burnParam.getTriggerContractInput()); } - private static GrpcAPI.Note buildNote( - long value, String paymentAddress, byte[] rcm, byte[] memo) { + public GrpcAPI.Note buildNote(long value, String paymentAddress, byte[] rcm, byte[] memo) { GrpcAPI.Note.Builder noteBuilder = GrpcAPI.Note.newBuilder(); noteBuilder.setValue(value); noteBuilder.setPaymentAddress(paymentAddress); @@ -246,8 +246,7 @@ private static GrpcAPI.Note buildNote( return noteBuilder.build(); } - private SpendNoteTRC20 getSpendNote(TransactionInfo txInfo, - Note note, byte[] contractAddress) { + public SpendNoteTRC20 getSpendNote(TransactionInfo txInfo, Note note, byte[] contractAddress) { byte[] txData = txInfo.getLog(1).getData().toByteArray(); long pos = bytes32ToLong(ByteArray.subArray(txData, 0, 32)); byte[] contractResult = triggerGetPath(contractAddress, pos); @@ -284,7 +283,7 @@ private String triggerTransfer(byte[] contractAddress, String privateKey, String "0", 0, privateKey); - Optional infoById = WalletApi.getTransactionInfoById(txid); + WalletApi.getTransactionInfoById(txid); return txid; } @@ -297,7 +296,7 @@ private String triggerBurn(byte[] contractAddress, String privateKey, String inp 0L, 100000000L, "0", 0, - privateKey); + privateKey); } @@ -391,8 +390,7 @@ private String triggerContract(byte[] contractAddress, String method, String arg return txid; } - public static Protocol.Transaction signTransaction(ECKey ecKey, - Protocol.Transaction transaction) { + public static Protocol.Transaction signTransaction(ECKey ecKey, Transaction transaction) { WalletApi.setAddressPreFixByte(CommonConstant.ADD_PRE_FIX_BYTE_MAINNET); if (ecKey == null || ecKey.getPrivKey() == null) { //logger.warn("Warning: Can't sign,there is no private key !!"); @@ -409,8 +407,7 @@ private BigInteger getScalingFactorBi() { return ByteUtil.bytesToBigInteger(scalingFactorBytes); } - private byte[] triggerGetScalingFactor( - byte[] contractAddress) { + private byte[] triggerGetScalingFactor(byte[] contractAddress) { String methodSign = "scalingFactor()"; byte[] selector = new byte[4]; System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); @@ -429,52 +426,22 @@ private byte[] triggerGetScalingFactor( return result; } - private String getScaledPublicAmount(long amount) { + public String getScaledPublicAmount(long amount) { BigInteger result = BigInteger.valueOf(amount).multiply(scalingFactorBi); return result.toString(); } - private void setAllowance(String privateKey, long amount) { - byte[] contractAddress = WalletApi.decodeFromBase58Check(trc20); + public void setAllowance(String privateKey, long amount) { byte[] shieldedContractAddressPadding = new byte[32]; System.arraycopy(deShieldedTRC20, 0, shieldedContractAddressPadding, 11, 21); byte[] valueBytes = longTo32Bytes(amount); String input = Hex.toHexString(ByteUtil.merge(shieldedContractAddressPadding, valueBytes)); - triggerContract(contractAddress, "approve(address,uint256)", input, true, + triggerContract(trc20, "approve(address,uint256)", input, true, 0L, 10000000L, "0", 0, privateKey); } - private PrivateShieldedTRC20Parameters mintParams(long value, String contractAddr) - throws ZksnarkException { - BigInteger fromAmount = BigInteger.valueOf(value).multiply(scalingFactorBi); - SpendingKey spendingKey = new SpendingKey(ByteArray.fromHexString(sk)); - ExpandedSpendingKey expsk = spendingKey.expandedSpendingKey(); - byte[] ovk = expsk.getOvk(); - - // ReceiveNote - GrpcAPI.ReceiveNote.Builder revNoteBuilder = GrpcAPI.ReceiveNote.newBuilder(); - // SpendingKey spendingKey = SpendingKey.random(); - FullViewingKey fullViewingKey = spendingKey.fullViewingKey(); - IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey(); - PaymentAddress paymentAddress = incomingViewingKey.address(new DiversifierT().random()).get(); - byte[] memo = new byte[512]; -// byte[] rcm = WalletApi.getRcm().get().getValue().toByteArray(); - String paymentAddressStr = KeyIo.encodePaymentAddress(paymentAddress); - GrpcAPI.Note revNote = buildNote(value, paymentAddressStr, ByteArray.fromHexString(rcm), memo); - revNoteBuilder.setNote(revNote); - byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddr); - - GrpcAPI.PrivateShieldedTRC20Parameters.Builder paramBuilder = GrpcAPI - .PrivateShieldedTRC20Parameters.newBuilder(); - paramBuilder.setOvk(ByteString.copyFrom(ovk)); - paramBuilder.setFromAmount(fromAmount.toString()); - paramBuilder.addShieldedReceives(revNoteBuilder.build()); - paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(contractAddress)); - return paramBuilder.build(); - } - - private byte[] triggerGetPath(byte[] contractAddress, long pos) { + public byte[] triggerGetPath(byte[] contractAddress, long pos) { String methodSign = "getPath(uint256)"; byte[] selector = new byte[4]; System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); From b8cb9628b68628000e0c566fc51a7d86c6733951 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Fri, 5 Mar 2021 23:43:03 +0800 Subject: [PATCH 364/445] refactor demo --- .../java/org/tron/demo/ShieldedTRC20Demo.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index 385e84a26..ff8d83f8c 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -29,10 +29,8 @@ import org.tron.core.exception.ZksnarkException; import org.tron.core.zen.address.DiversifierT; import org.tron.core.zen.address.ExpandedSpendingKey; -import org.tron.core.zen.address.FullViewingKey; import org.tron.core.zen.address.IncomingViewingKey; import org.tron.core.zen.address.KeyIo; -import org.tron.core.zen.address.PaymentAddress; import org.tron.core.zen.address.SpendingKey; import org.tron.protos.Protocol; import org.tron.protos.Protocol.Transaction; @@ -49,7 +47,7 @@ public class ShieldedTRC20Demo { private byte[] trc20 = WalletApi.decodeFromBase58Check( "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"); - private byte[] deShieldedTRC20 = WalletApi.decodeFromBase58Check( + private byte[] shieldedTRC20 = WalletApi.decodeFromBase58Check( "TQEuSEVRk1GtfExm5q9T8a1w84GvgQJ13V"); private String privateKey = "your private key of transparent address"; @@ -150,7 +148,7 @@ public void setTransparent(PrivateShieldedTRC20Parameters.Builder paramBuilder, } public void setContractAddress(PrivateShieldedTRC20Parameters.Builder paramBuilder) { - paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(deShieldedTRC20)); + paramBuilder.setShieldedTRC20ContractAddress(ByteString.copyFrom(shieldedTRC20)); } public void setKey(PrivateShieldedTRC20Parameters.Builder paramBuilder, byte[] ask, byte[] nsk, @@ -194,7 +192,7 @@ public void transferDemo(String fromPrivate, long fromAmount, String toShieldedA PrivateShieldedTRC20Parameters.newBuilder(); //set spend note Note note = buildNote(5, toShieldedAddress, ByteArray.fromHexString(rcm), new byte[512]); - privateTRC20Builder.addShieldedSpends(getSpendNote(infoById.get(), note, deShieldedTRC20)); + privateTRC20Builder.addShieldedSpends(getSpendNote(infoById.get(), note, shieldedTRC20)); //set receive note 1 addReceiveShieldedNote(privateTRC20Builder, toShieldedAddress, toAmount1); //set receive note 2 @@ -207,7 +205,7 @@ public void transferDemo(String fromPrivate, long fromAmount, String toShieldedA GrpcAPI.ShieldedTRC20Parameters transferParam = WalletApi .createShieldedContractParameters(privateTRC20Builder.build()); - triggerTransfer(deShieldedTRC20, privateKey, transferParam.getTriggerContractInput()); + triggerTransfer(shieldedTRC20, privateKey, transferParam.getTriggerContractInput()); } public void burnDemo(String fromPrivate, long fromAmount, String toShieldedAddress, @@ -225,7 +223,7 @@ public void burnDemo(String fromPrivate, long fromAmount, String toShieldedAddre //set transparent setTransparent(privateTRC20Builder, 0, toTransparentAddress, toTransparentAmount); //set spend note - privateTRC20Builder.addShieldedSpends(getSpendNote(infoById.get(), note, deShieldedTRC20)); + privateTRC20Builder.addShieldedSpends(getSpendNote(infoById.get(), note, shieldedTRC20)); //set receive note addReceiveShieldedNote(privateTRC20Builder, toShieldedAddress, toShieldedAmount); //set contract address @@ -234,7 +232,7 @@ public void burnDemo(String fromPrivate, long fromAmount, String toShieldedAddre GrpcAPI.ShieldedTRC20Parameters burnParam = WalletApi .createShieldedContractParameters(privateTRC20Builder.build()); - triggerBurn(deShieldedTRC20, privateKey, burnParam.getTriggerContractInput()); + triggerBurn(shieldedTRC20, privateKey, burnParam.getTriggerContractInput()); } public GrpcAPI.Note buildNote(long value, String paymentAddress, byte[] rcm, byte[] memo) { @@ -265,7 +263,7 @@ private String triggerMint(String privateKey, String input) { String methodSign = "mint(uint256,bytes32[9],bytes32[2],bytes32[21])"; byte[] selector = new byte[4]; System.arraycopy(Hash.sha3(methodSign.getBytes()), 0, selector, 0, 4); - return triggerContract(deShieldedTRC20, + return triggerContract(shieldedTRC20, "mint(uint256,bytes32[9],bytes32[2],bytes32[21])", input, true, @@ -403,7 +401,7 @@ public static Protocol.Transaction signTransaction(ECKey ecKey, Transaction tran } private BigInteger getScalingFactorBi() { - byte[] scalingFactorBytes = triggerGetScalingFactor(deShieldedTRC20); + byte[] scalingFactorBytes = triggerGetScalingFactor(shieldedTRC20); return ByteUtil.bytesToBigInteger(scalingFactorBytes); } @@ -433,7 +431,7 @@ public String getScaledPublicAmount(long amount) { public void setAllowance(String privateKey, long amount) { byte[] shieldedContractAddressPadding = new byte[32]; - System.arraycopy(deShieldedTRC20, 0, + System.arraycopy(shieldedTRC20, 0, shieldedContractAddressPadding, 11, 21); byte[] valueBytes = longTo32Bytes(amount); String input = Hex.toHexString(ByteUtil.merge(shieldedContractAddressPadding, valueBytes)); From e2e83d3a45a81e444b5aee804c5c33bad93df550 Mon Sep 17 00:00:00 2001 From: renchenchang Date: Fri, 5 Mar 2021 23:44:21 +0800 Subject: [PATCH 365/445] refactor demo --- src/main/java/org/tron/demo/ShieldedTRC20Demo.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index ff8d83f8c..fa0bbefb4 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -68,10 +68,10 @@ public static void main(String[] args) throws ZksnarkException, InterruptedExcep demo.transferDemo(demo.privateKey, 5, demo.shieldedKey.getKioAddress(), 2, 3); demo.burnDemo(demo.privateKey, 5, demo.shieldedKey.getKioAddress(), 3, - getAddressFromPK(demo.privateKey), 2); + getAddressFromPk(demo.privateKey), 2); } - public static byte[] getAddressFromPK(String pk) { + public static byte[] getAddressFromPk(String pk) { ECKey ecKey = ECKey.fromPrivate(ByteArray.fromHexString(pk)); return ecKey.getAddress(); } @@ -315,7 +315,7 @@ private String triggerContract(byte[] contractAddress, String method, String arg argsStr = ""; } - byte[] owner = getAddressFromPK(priKey); + byte[] owner = getAddressFromPk(priKey); byte[] input = Hex.decode(AbiUtil.parseMethod(method, argsStr, isHex)); TriggerSmartContract.Builder builder = TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(owner)); From 4fef7ed3d742b8c245c3d27ac6f2b63995d253a0 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 15 Mar 2021 11:57:50 +0800 Subject: [PATCH 366/445] add parameter into protocol --- src/main/protos/core/Tron.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index cec919247..5413c5623 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -148,6 +148,9 @@ message Account { Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; + + //vote power,include frozen bandwidth and frozen energy + int64 vote_power_413 = 60; } From 72f4ed17a5feeb14a051575411455b7a2193025b Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Mon, 15 Mar 2021 11:57:50 +0800 Subject: [PATCH 367/445] add parameter into protocol --- src/main/protos/core/Tron.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index cec919247..5413c5623 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -148,6 +148,9 @@ message Account { Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; + + //vote power,include frozen bandwidth and frozen energy + int64 vote_power_413 = 60; } From b4bccba7dc0a33a0ff207ddf68aa78112c3694a0 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 16 Mar 2021 12:44:00 +0800 Subject: [PATCH 368/445] add parameter into protocol --- src/main/protos/core/Tron.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index ba26829f7..d9a4150f3 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -148,6 +148,9 @@ message Account { Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; + + //vote power,include frozen bandwidth and frozen energy + int64 old_vote_power = 60; } From d0fcf0fa12f43a232c45427a8a631c94e0875a92 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 16 Mar 2021 17:52:14 +0800 Subject: [PATCH 369/445] add notice to readme for calling receive --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0dec55b07..e3dff8e05 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ AbbrName > The abbreviation of TRC10 token TotalSupply -> ​TotalSupply = Account Balance of Issuer + All Frozen Token Amount +> TotalSupply = Account Balance of Issuer + All Frozen Token Amount > TotalSupply: Total Issuing Amount > Account Balance Of Issuer: At the time of issuance > All Frozen Token Amount: Before asset transfer and the issuance @@ -959,7 +959,7 @@ method > The name of function and parameters, please refer to the example args -> Parameter value +> Parameter value, if you want to call `receive`, pass '#' instead isHex > The format of the parameters method and args, is hex string or not @@ -1783,7 +1783,7 @@ Example: In this example, the scalingFactor is 1000. 1. MINT - + **In this mode, some variables must be set as follows, shieldedInputNum = 0, publicToAddress = null, toAmount = 0.** ```console @@ -1791,7 +1791,7 @@ In this example, the scalingFactor is 1000. ``` 2. TRANSFER - + **In this mode, some variables must be set as follows, fromAmount = 0, publicToAddress = null,toAmount = 0.** Transfer from one shielded address to one shielded address. @@ -2160,7 +2160,7 @@ d :11db4baf6bd5d5afd3a8b1 Create an order to sell asset > MarketSellAsset owner_address sell_token_id sell_token_quantity buy_token_id buy_token_quantity - + ownerAddress > The address of the account that initiated the transaction @@ -2218,7 +2218,7 @@ GetMarketOrderByAccount TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW } ] } -``` +``` ### GetMarketOrderById @@ -2244,7 +2244,7 @@ GetMarketOrderById fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75 ### GetMarketPairList Get market pair list - + Example: ```console @@ -2324,7 +2324,7 @@ GetMarketPriceByPair _ 1000001 Cancel the order > MarketCancelOrder owner_address order_id - + owner_address > the account address who have created the order @@ -2336,7 +2336,7 @@ Example: ```console MarketCancelOrder TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW fc9c64dfd48ae58952e85f05ecb8ec87f55e19402493bb2df501ae9d2da75db0 ``` - + Get the result of the contract execution with the getTransactionInfoById command: ```console getTransactionInfoById b375787a098498623403c755b1399e82910385251b643811936d914c9f37bd27 From 364b1aefa4a63892b104c0b5fb0abf7e3bb08633 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 17 Mar 2021 11:58:57 +0800 Subject: [PATCH 370/445] rm parameter into protocol --- src/main/protos/core/Tron.proto | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index d9a4150f3..ba26829f7 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -148,9 +148,6 @@ message Account { Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; - - //vote power,include frozen bandwidth and frozen energy - int64 old_vote_power = 60; } From 309085e7d61a49fc33962aaa1e3fafb26aa998d0 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 17 Mar 2021 18:06:19 +0800 Subject: [PATCH 371/445] rm parameter into protocol --- src/main/protos/core/Tron.proto | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 5c8b5b321..ba26829f7 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -148,9 +148,6 @@ message Account { Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; - - //vote power,include frozen bandwidth and frozen energy - int64 vote_power_413 = 60; } From 9e9e4e468156582109f9590ac22611e0b10a99e9 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Thu, 18 Mar 2021 12:01:46 +0800 Subject: [PATCH 372/445] fix: fix gettransactioninfobyblocknum result format --- src/main/java/org/tron/common/utils/Utils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 99807ea2e..079f609d5 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -165,7 +165,7 @@ public static String printTransactionInfoList(TransactionInfoList transactionInf List infoList = transactionInfoList.getTransactionInfoList(); infoList.stream() .forEach( - transactionInfo -> jsonArray.add(formatMessageString(transactionInfo)) + transactionInfo -> jsonArray.add(printTransactionInfoToJSON(transactionInfo)) ); return JsonFormatUtil.formatJson(jsonArray.toJSONString()); } @@ -610,6 +610,10 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean return jsonTransaction; } + public static JSONObject printTransactionInfoToJSON(TransactionInfo transactioninfo) { + return JSONObject.parseObject(JsonFormat.printToString(transactioninfo, true)); + } + public static boolean confirmEncrption() { System.out.println( "Please confirm encryption module,if input y or Y means default Eckey, other means SM2."); From f11b36c04e5e78f1af66610dd4d088a662d4493e Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 18 Mar 2021 12:13:35 +0800 Subject: [PATCH 373/445] fix log error --- src/main/java/org/tron/walletcli/Client.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 073c6d3e6..83184f1b2 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -4251,6 +4251,7 @@ private void run() { } case "gettransactioninfobyblocknum": { getTransactionInfoByBlockNum(parameters); + break; } case "marketsellasset": { marketSellAsset(parameters); From 25efaf5554d96808a49bb36d6dfebefef2173151 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 18 Mar 2021 15:36:54 +0800 Subject: [PATCH 374/445] fix log error --- src/main/java/org/tron/walletcli/Client.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 83184f1b2..9ec0ccb4f 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2028,7 +2028,6 @@ private void getBrokerage(String[] parameters) { private void getTransactionInfoByBlockNum(String[] parameters) { if (parameters.length != 1) { - System.out.println("Too many parameters !!!"); System.out.println("You need input number with the following syntax:"); System.out.println("GetTransactionInfoByBlockNum number"); return; From e677e201114912af5b51b606bbe92857494a058c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 19 Mar 2021 14:42:29 +0800 Subject: [PATCH 375/445] change log --- src/main/java/org/tron/walletcli/Client.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 9ec0ccb4f..f9649b962 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -3343,7 +3343,7 @@ private void resetShieldedTRC20Note() { private void scanShieldedTRC20NoteByIvk(String[] parameters) { if (parameters == null || parameters.length < 6) { System.out.println("ScanShieldedTRC20NoteByIvk command needs at least 6 parameters like: "); - System.out.println("ScanShieldedTRC20NoteByIvk shieldedTRC20ContractAddress ivk ak nk " + + System.out.println("ScanShieldedTRC20NoteByIvk shieldedContractAddress ivk ak nk " + "startNum endNum [event1] [event2]"); return; } From d6d1ada3988c5c34714d8f1f0492335e89be393b Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Tue, 6 Apr 2021 15:03:22 +0800 Subject: [PATCH 376/445] change protocol for new resource model --- src/main/protos/api/api.proto | 3 +++ src/main/protos/core/Tron.proto | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 5631f3a70..7968dfeb4 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1135,6 +1135,9 @@ message AccountResourceMessage { map assetNetLimit = 6; int64 TotalNetLimit = 7; int64 TotalNetWeight = 8; + int64 TotalTronPowerWeight = 9; + int64 tronPowerUsed = 10; + int64 tronPowerLimit = 11; int64 EnergyUsed = 13; int64 EnergyLimit = 14; diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index ba26829f7..4e7adc79d 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -97,6 +97,9 @@ message Account { //Freeze and provide balances to other accounts int64 delegated_frozen_balance_for_bandwidth = 42; + int64 old_tron_power = 46; + Frozen tron_power = 47; + // this account create time int64 create_time = 0x09; // this last operation time, including transfer, voting and so on. //FIXME fix grammar From 62233bbab0811496a20aba007e70b6a978b15bc5 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Fri, 9 Apr 2021 14:08:50 +0800 Subject: [PATCH 377/445] add protocol for new resource model --- src/main/protos/core/contract/common.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/protos/core/contract/common.proto b/src/main/protos/core/contract/common.proto index 933c5f5d0..93f90b494 100644 --- a/src/main/protos/core/contract/common.proto +++ b/src/main/protos/core/contract/common.proto @@ -9,4 +9,5 @@ option go_package = "github.com/tronprotocol/grpc-gateway/core"; enum ResourceCode { BANDWIDTH = 0x00; ENERGY = 0x01; + TRON_POWER = 0x02; } From 603edd55405d2b8163b92fc59fef29636903f880 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 14 Apr 2021 13:27:04 +0800 Subject: [PATCH 378/445] change Prompt --- src/main/java/org/tron/walletcli/Client.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index f9649b962..043b2c4af 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1204,7 +1204,7 @@ private void freezeBalance(String[] parameters) || parameters.length == 4 || parameters.length == 5)) { System.out.println("Use freezeBalance command with below syntax: "); System.out.println("freezeBalance [OwnerAddress] frozen_balance frozen_duration " - + "[ResourceCode:0 BANDWIDTH,1 ENERGY] [receiverAddress]"); + + "[ResourceCode:0 BANDWIDTH,1 ENERGY,2 TRON_POWER] [receiverAddress]"); return; } @@ -1247,7 +1247,7 @@ private void unfreezeBalance(String[] parameters) if (parameters == null || parameters.length < 1 || parameters.length > 3) { System.out.println("Use unfreezeBalance command with below syntax: "); System.out.println( - "unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH,1 CPU) [receiverAddress]"); + "unfreezeBalance [OwnerAddress] ResourceCode(0 BANDWIDTH,1 ENERGY,2 TRON_POWER) [receiverAddress]"); return; } From 177b957899a47225a44f466ac2c8920295c11659 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 14 Apr 2021 13:37:33 +0800 Subject: [PATCH 379/445] empty for release --- src/main/java/org/tron/walletcli/Client.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 043b2c4af..0aa31c679 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1387,7 +1387,6 @@ private void deleteProposal(String[] parameters) } } - private void listProposals() { Optional result = walletApiWrapper.getProposalsList(); if (result.isPresent()) { From 2ca7904a406482d695b71b3ba454fb63e609a5cb Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 14 Apr 2021 17:20:23 +0800 Subject: [PATCH 380/445] fix unfreezeBalance error --- src/main/java/org/tron/walletcli/Client.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 0aa31c679..5a009046f 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1255,7 +1255,9 @@ private void unfreezeBalance(String[] parameters) byte[] ownerAddress = null; int resourceCode = 0; byte[] receiverAddress = null; - if (parameters.length == 2) { + if (parameters.length == 1) { + resourceCode = Integer.parseInt(parameters[index++]); + } else if (parameters.length == 2) { ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress != null) { index++; From a46569d823025fbb1f25cbef3413851dbc06af1c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 14 Apr 2021 17:56:30 +0800 Subject: [PATCH 381/445] add receiptsRoot into protocol --- src/main/protos/core/Tron.proto | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 4e7adc79d..bd0b8183a 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -413,6 +413,7 @@ message BlockHeader { bytes witness_address = 9; int32 version = 10; bytes accountStateRoot = 11; + bytes receiptsRoot = 12; } raw raw_data = 1; bytes witness_signature = 2; @@ -541,12 +542,6 @@ message InternalTransaction { bool rejected = 6; } -message DelegatedResourceAccountIndex { - bytes account = 1; - repeated bytes fromAccounts = 2; - repeated bytes toAccounts = 3; -} - message NodeInfo { int64 beginSyncNum = 1; string block = 2; From 6100399260967d7598ab9b28e432cc7b4ccb9cbd Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Wed, 14 Apr 2021 18:03:45 +0800 Subject: [PATCH 382/445] rm getdelegatedresourceaccountindex --- src/main/java/org/tron/walletcli/Client.java | 20 ------------------- .../org/tron/walletserver/GrpcClient.java | 15 -------------- .../java/org/tron/walletserver/WalletApi.java | 6 ------ src/main/protos/api/api.proto | 3 --- src/main/protos/core/Tron.proto | 6 ++++++ 5 files changed, 6 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 5a009046f..5a8c43029 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1434,22 +1434,6 @@ private void getDelegatedResource(String[] parameters) { } } - private void getDelegatedResourceAccountIndex(String[] parameters) { - if (parameters == null || parameters.length != 1) { - System.out.println("Using getDelegatedResourceAccountIndex command needs 1 parameter like: "); - System.out.println("getDelegatedResourceAccountIndex address"); - return; - } - String address = parameters[0]; - Optional result = WalletApi - .getDelegatedResourceAccountIndex(address); - if (result.isPresent()) { - DelegatedResourceAccountIndex delegatedResourceAccountIndex = result.get(); - System.out.println(Utils.formatMessageString(delegatedResourceAccountIndex)); - } else { - System.out.println("GetDelegatedResourceAccountIndex failed !!"); - } - } private void exchangeCreate(String[] parameters) @@ -3963,10 +3947,6 @@ private void run() { getDelegatedResource(parameters); break; } - case "getdelegatedresourceaccountindex": { - getDelegatedResourceAccountIndex(parameters); - break; - } case "exchangecreate": { exchangeCreate(parameters); break; diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 19b461209..361b94c82 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -360,21 +360,6 @@ public Optional getDelegatedResource(String fromAddress, return Optional.ofNullable(delegatedResource); } - public Optional getDelegatedResourceAccountIndex(String address) { - - ByteString addressBS = ByteString.copyFrom( - Objects.requireNonNull(WalletApi.decodeFromBase58Check(address))); - - BytesMessage bytesMessage = BytesMessage.newBuilder().setValue(addressBS).build(); - DelegatedResourceAccountIndex accountIndex; - if (blockingStubSolidity != null) { - accountIndex = blockingStubSolidity.getDelegatedResourceAccountIndex(bytesMessage); - } else { - accountIndex = blockingStubFull.getDelegatedResourceAccountIndex(bytesMessage); - } - return Optional.ofNullable(accountIndex); - } - public Optional listExchanges() { ExchangeList exchangeList; diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 4f5220b95..229d03b74 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -1504,12 +1504,6 @@ public static Optional getDelegatedResource( return rpcCli.getDelegatedResource(fromAddress, toAddress); } - public static Optional getDelegatedResourceAccountIndex - ( - String address) { - return rpcCli.getDelegatedResourceAccountIndex(address); - } - public static Optional listExchanges() { return rpcCli.listExchanges(); } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 7968dfeb4..d8d49bb0b 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -459,9 +459,6 @@ service Wallet { rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { }; - rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { - }; - rpc ListProposals (EmptyMessage) returns (ProposalList) { option (google.api.http) = { post: "/wallet/listproposals" diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index bd0b8183a..8d50ebbd0 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -542,6 +542,12 @@ message InternalTransaction { bool rejected = 6; } +message DelegatedResourceAccountIndex { + bytes account = 1; + repeated bytes fromAccounts = 2; + repeated bytes toAccounts = 3; +} + message NodeInfo { int64 beginSyncNum = 1; string block = 2; From 61a0839d7dfb58b55186ac795ef78fbe7d052e8c Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 15 Apr 2021 11:10:27 +0800 Subject: [PATCH 383/445] rm useless commandHelp --- src/main/java/org/tron/walletcli/Client.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 5a8c43029..a7c324d28 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -118,7 +118,6 @@ public class Client { "GetContract contractAddress", "GetContractInfo contractAddress", "GetDelegatedResource", - "GetDelegatedResourceAccountIndex", "GetDiversifier", "GetExchange", "GetExpandedSpendingKey", From 13944cc2ffa21bb04f4281e31ac633c9e3088b83 Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 20 May 2021 18:26:57 +0800 Subject: [PATCH 384/445] update json lib version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 580dfa2ac..6d2b9a88f 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { compile "org.apache.commons:commons-collections4:4.0" compile "org.apache.commons:commons-lang3:3.4" compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' - compile 'com.alibaba:fastjson:1.2.47' + compile 'com.alibaba:fastjson:1.2.76' compile group: 'commons-io', name: 'commons-io', version: '2.6' compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' From 14bb7d41fe13c28043aa9038def8f2cebcd4fa8d Mon Sep 17 00:00:00 2001 From: sean-liu55 Date: Thu, 20 May 2021 18:31:27 +0800 Subject: [PATCH 385/445] empty commit --- src/main/java/org/tron/walletcli/Client.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 043b2c4af..0aa31c679 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1387,7 +1387,6 @@ private void deleteProposal(String[] parameters) } } - private void listProposals() { Optional result = walletApiWrapper.getProposalsList(); if (result.isPresent()) { From 6e86007ee482b4d6ac661a5baf41c70fd0367372 Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Mon, 7 Jun 2021 16:00:40 +0800 Subject: [PATCH 386/445] update the bouncy castle module --- build.gradle | 5 +- .../java/org/tron/common/crypto/ECKey.java | 161 ++-- .../java/org/tron/common/crypto/SM3Hash.java | 9 +- .../org/tron/common/crypto/Sha256Sm3Hash.java | 3 +- .../common/crypto/jce/TronCastleProvider.java | 91 +- .../java/org/tron/common/crypto/sm2/SM2.java | 125 ++- .../org/tron/common/crypto/sm2/SM2Signer.java | 28 +- .../java/org/tron/common/utils/AbiUtil.java | 11 +- .../java/org/tron/common/utils/ByteArray.java | 173 ++-- .../tron/common/utils/ByteArrayWrapper.java | 3 +- .../java/org/tron/common/utils/ByteUtil.java | 836 +++++++++--------- .../java/org/tron/common/utils/DataWord.java | 5 +- src/main/java/org/tron/common/utils/Hash.java | 13 +- src/main/java/org/tron/demo/ECKeyDemo.java | 11 +- .../java/org/tron/demo/ShieldedTRC20Demo.java | 2 +- .../java/org/tron/walletserver/WalletApi.java | 84 +- 16 files changed, 790 insertions(+), 770 deletions(-) diff --git a/build.gradle b/build.gradle index 580dfa2ac..5f473816c 100644 --- a/build.gradle +++ b/build.gradle @@ -76,8 +76,9 @@ dependencies { compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile "com.madgag.spongycastle:core:1.58.0.0" - compile "com.madgag.spongycastle:prov:1.58.0.0" +// compile "com.madgag.spongycastle:core:1.58.0.0" +// compile "com.madgag.spongycastle:prov:1.58.0.0" + compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.68' compile group: 'com.typesafe', name: 'config', version: '1.3.2' compile "com.google.code.findbugs:jsr305:3.0.0" compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" diff --git a/src/main/java/org/tron/common/crypto/ECKey.java b/src/main/java/org/tron/common/crypto/ECKey.java index 44a34a67a..69407dc7c 100644 --- a/src/main/java/org/tron/common/crypto/ECKey.java +++ b/src/main/java/org/tron/common/crypto/ECKey.java @@ -17,48 +17,63 @@ * along with the ethereumJ library. If not, see . */ -import lombok.extern.slf4j.Slf4j; -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.asn1.sec.SECNamedCurves; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.agreement.ECDHBasicAgreement; -import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.crypto.engines.AESEngine; -import org.spongycastle.crypto.modes.SICBlockCipher; -import org.spongycastle.crypto.params.*; -import org.spongycastle.crypto.signers.ECDSASigner; -import org.spongycastle.crypto.signers.HMacDSAKCalculator; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.util.BigIntegers; -import org.spongycastle.util.encoders.Base64; -import org.spongycastle.util.encoders.Hex; -import org.tron.common.crypto.cryptohash.Keccak256; -import org.tron.common.crypto.jce.*; -import org.tron.common.utils.BIUtil; -import org.tron.common.utils.ByteUtil; -import org.tron.common.utils.Hash; - -import javax.annotation.Nullable; -import javax.crypto.KeyAgreement; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; import java.nio.charset.Charset; -import java.security.*; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import javax.annotation.Nullable; +import javax.crypto.KeyAgreement; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.agreement.ECDHBasicAgreement; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.SICBlockCipher; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Hex; +import org.tron.common.crypto.cryptohash.Keccak256; +import org.tron.common.crypto.jce.ECKeyAgreement; +import org.tron.common.crypto.jce.ECKeyFactory; +import org.tron.common.crypto.jce.ECKeyPairGenerator; +import org.tron.common.crypto.jce.ECSignatureFactory; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.BIUtil; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.Hash; @Slf4j(topic = "crypto") public class ECKey implements Serializable, SignInterface { @@ -680,30 +695,30 @@ private static void check(boolean test, String message) { } } - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public ECKey decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, decompressPoint(pub)); - } - } - - /** @deprecated per-point compression property will be removed in Bouncy Castle */ - public ECKey compress() { - if (pub.isCompressed()) { - return this; - } else { - return new ECKey(this.provider, this.privKey, compressPoint(pub)); - } - } +// /** +// * Returns a copy of this key, but with the public point represented in uncompressed form. +// * Normally you would never need this: it's for specialised scenarios or when backwards +// * compatibility in encoded form is necessary. +// * +// * @return - +// * @deprecated per-point compression property will be removed in Bouncy Castle +// */ +// public ECKey decompress() { +// if (!pub.isCompressed()) { +// return this; +// } else { +// return new ECKey(this.provider, this.privKey, decompressPoint(pub)); +// } +// } +// +// /** @deprecated per-point compression property will be removed in Bouncy Castle */ +// public ECKey compress() { +// if (pub.isCompressed()) { +// return this; +// } else { +// return new ECKey(this.provider, this.privKey, compressPoint(pub)); +// } +// } /** * Returns true if this key doesn't have access to private key bytes. This may be because it was @@ -809,21 +824,21 @@ public BigInteger getPrivKey() { } } - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } - - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - return b.toString(); - } +// /** +// * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 +// * bytes, not 64. +// * +// * @return - +// */ +// public boolean isCompressed() { +// return pub.isCompressed(); +// } +// +// public String toString() { +// StringBuilder b = new StringBuilder(); +// b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); +// return b.toString(); +// } /** * Produce a string rendering of the ECKey INCLUDING the private key. Unless you absolutely need diff --git a/src/main/java/org/tron/common/crypto/SM3Hash.java b/src/main/java/org/tron/common/crypto/SM3Hash.java index 3f11900dd..03acd486f 100644 --- a/src/main/java/org/tron/common/crypto/SM3Hash.java +++ b/src/main/java/org/tron/common/crypto/SM3Hash.java @@ -17,21 +17,20 @@ * limitations under the License. */ +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; -import org.spongycastle.crypto.digests.SM3Digest; -import org.tron.common.utils.ByteArray; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; import java.util.Arrays; - -import static com.google.common.base.Preconditions.checkArgument; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.tron.common.utils.ByteArray; /** * A SM3Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be used diff --git a/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java b/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java index 85bd46f86..77e4552df 100644 --- a/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java +++ b/src/main/java/org/tron/common/crypto/Sha256Sm3Hash.java @@ -24,8 +24,6 @@ import com.google.common.primitives.Longs; import com.google.protobuf.ByteString; import com.typesafe.config.Config; -import org.spongycastle.crypto.digests.SM3Digest; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -34,6 +32,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import org.bouncycastle.crypto.digests.SM3Digest; import org.tron.common.utils.ByteArray; import org.tron.core.config.Configuration; diff --git a/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java b/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java index 763942057..049d91006 100644 --- a/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java +++ b/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java @@ -1,46 +1,45 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.crypto.jce; - -import org.spongycastle.jce.provider.BouncyCastleProvider; - -import java.security.Provider; -import java.security.Security; - -public final class TronCastleProvider { - - public static Provider getInstance() { - return Holder.INSTANCE; - } - - private static class Holder { - private static final Provider INSTANCE; - - static { - Provider p = Security.getProvider("SC"); - - INSTANCE = (p != null) ? p : new BouncyCastleProvider(); - - INSTANCE.put("MessageDigest.TRON-KECCAK-256", "org.tron.common.crypto" + - ".cryptohash.Keccak256"); - INSTANCE.put("MessageDigest.TRON-KECCAK-512", "org.tron.common.crypto" + - ".cryptohash.Keccak512"); - } - } -} +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.crypto.jce; + +import java.security.Provider; +import java.security.Security; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +public final class TronCastleProvider { + + public static Provider getInstance() { + return Holder.INSTANCE; + } + + private static class Holder { + private static final Provider INSTANCE; + + static { + Provider p = Security.getProvider("BC"); + + INSTANCE = (p != null) ? p : new BouncyCastleProvider(); + + INSTANCE.put("MessageDigest.TRON-KECCAK-256", "org.tron.common.crypto" + + ".cryptohash.Keccak256"); + INSTANCE.put("MessageDigest.TRON-KECCAK-512", "org.tron.common.crypto" + + ".cryptohash.Keccak512"); + } + } +} diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2.java b/src/main/java/org/tron/common/crypto/sm2/SM2.java index 729d2fdf1..7504dceba 100644 --- a/src/main/java/org/tron/common/crypto/sm2/SM2.java +++ b/src/main/java/org/tron/common/crypto/sm2/SM2.java @@ -1,32 +1,9 @@ package org.tron.common.crypto.sm2; -import lombok.extern.slf4j.Slf4j; -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.AsymmetricCipherKeyPair; -import org.spongycastle.crypto.generators.ECKeyPairGenerator; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECKeyGenerationParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; -import org.spongycastle.util.encoders.Base64; -import org.spongycastle.util.encoders.Hex; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.SignInterface; -import org.tron.common.crypto.SignatureInterface; -import org.tron.common.crypto.jce.ECKeyFactory; -import org.tron.common.crypto.jce.TronCastleProvider; -import org.tron.common.utils.ByteUtil; +import static org.tron.common.utils.BIUtil.isLessThan; +import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; +import static org.tron.common.utils.Hash.computeAddress; -import javax.annotation.Nullable; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; @@ -38,10 +15,32 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; - -import static org.tron.common.utils.BIUtil.isLessThan; -import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; -import static org.tron.common.utils.Hash.computeAddress; +import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Hex; +import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignatureInterface; +import org.tron.common.crypto.jce.ECKeyFactory; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.ByteUtil; /** Implement Chinese Commercial Cryptographic Standard of SM2 */ @Slf4j(topic = "crypto") @@ -815,30 +814,30 @@ public static SM2 recoverFromSignature(int recId, SM2Signature sig, byte[] messa } } - /** - * Returns a copy of this key, but with the public point represented in uncompressed form. - * Normally you would never need this: it's for specialised scenarios or when backwards - * compatibility in encoded form is necessary. - * - * @return - - * @deprecated per-point compression property will be removed in Bouncy Castle - */ - public SM2 decompress() { - if (!pub.isCompressed()) { - return this; - } else { - return new SM2(this.privKey, decompressPoint(pub)); - } - } - - /** @deprecated per-point compression property will be removed in Bouncy Castle */ - public SM2 compress() { - if (pub.isCompressed()) { - return this; - } else { - return new SM2(this.privKey, compressPoint(pub)); - } - } +// /** +// * Returns a copy of this key, but with the public point represented in uncompressed form. +// * Normally you would never need this: it's for specialised scenarios or when backwards +// * compatibility in encoded form is necessary. +// * +// * @return - +// * @deprecated per-point compression property will be removed in Bouncy Castle +// */ +// public SM2 decompress() { +// if (!pub.isCompressed()) { +// return this; +// } else { +// return new SM2(this.privKey, decompressPoint(pub)); +// } +// } +// +// /** @deprecated per-point compression property will be removed in Bouncy Castle */ +// public SM2 compress() { +// if (pub.isCompressed()) { +// return this; +// } else { +// return new SM2(this.privKey, compressPoint(pub)); +// } +// } /** * Returns true if this key doesn't have access to private key bytes. This may be because it was @@ -894,15 +893,15 @@ public BigInteger getPrivKey() { } } - /** - * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 - * bytes, not 64. - * - * @return - - */ - public boolean isCompressed() { - return pub.isCompressed(); - } +// /** +// * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 33 +// * bytes, not 64. +// * +// * @return - +// */ +// public boolean isCompressed() { +// return pub.isCompressed(); +// } public String toString() { StringBuilder b = new StringBuilder(); diff --git a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java index d5cd75c04..5dac9bffb 100644 --- a/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java +++ b/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java @@ -1,17 +1,25 @@ package org.tron.common.crypto.sm2; -import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.Digest; -import org.spongycastle.crypto.digests.SM3Digest; -import org.spongycastle.crypto.params.*; -import org.spongycastle.crypto.signers.DSAKCalculator; -import org.spongycastle.crypto.signers.RandomDSAKCalculator; -import org.spongycastle.math.ec.*; -import org.spongycastle.util.BigIntegers; - -import javax.annotation.Nullable; import java.math.BigInteger; import java.security.SecureRandom; +import javax.annotation.Nullable; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithID; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.signers.DSAKCalculator; +import org.bouncycastle.crypto.signers.RandomDSAKCalculator; +import org.bouncycastle.math.ec.ECConstants; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECMultiplier; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointCombMultiplier; +import org.bouncycastle.util.BigIntegers; public class SM2Signer implements ECConstants { private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); diff --git a/src/main/java/org/tron/common/utils/AbiUtil.java b/src/main/java/org/tron/common/utils/AbiUtil.java index 1610bf254..e56aaa6d3 100644 --- a/src/main/java/org/tron/common/utils/AbiUtil.java +++ b/src/main/java/org/tron/common/utils/AbiUtil.java @@ -1,17 +1,16 @@ package org.tron.common.utils; import com.fasterxml.jackson.databind.ObjectMapper; -import java.math.BigInteger; -import org.apache.commons.lang3.StringUtils; -import org.spongycastle.util.encoders.Hex; -import org.tron.common.crypto.Hash; -import org.tron.walletserver.WalletApi; - import java.io.IOException; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.encoders.Hex; +import org.tron.common.crypto.Hash; +import org.tron.walletserver.WalletApi; public class AbiUtil { diff --git a/src/main/java/org/tron/common/utils/ByteArray.java b/src/main/java/org/tron/common/utils/ByteArray.java index 67503f956..3653b8893 100644 --- a/src/main/java/org/tron/common/utils/ByteArray.java +++ b/src/main/java/org/tron/common/utils/ByteArray.java @@ -1,87 +1,86 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.utils; - -import org.spongycastle.util.encoders.Hex; - -import java.math.BigInteger; -import java.nio.ByteBuffer; - -public class ByteArray { - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - public static String toHexString(byte[] data) { - return data == null ? "" : Hex.toHexString(data); - } - - public static byte[] fromHexString(String data) { - if (data == null) { - return EMPTY_BYTE_ARRAY; - } - if (data.startsWith("0x")) { - data = data.substring(2); - } - if (data.length() % 2 == 1) { - data = "0" + data; - } - return Hex.decode(data); - } - - public static long toLong(byte[] b) { - if (b == null || b.length == 0) { - return 0; - } - return new BigInteger(1, b).longValue(); - } - - public static byte[] fromString(String str) { - if (str == null) { - return null; - } - - return str.getBytes(); - } - - public static String toStr(byte[] byteArray) { - if (byteArray == null) { - return null; - } - - return new String(byteArray); - } - - public static byte[] fromLong(long val) { - return ByteBuffer.allocate(8).putLong(val).array(); - } - - /** - * Generate a subarray of a given byte array. - * - * @param input the input byte array - * @param start the start index - * @param end the end index - * @return a subarray of input, ranging from start (inclusively) to end - * (exclusively) - */ - public static byte[] subArray(byte[] input, int start, int end) { - byte[] result = new byte[end - start]; - System.arraycopy(input, start, result, 0, end - start); - return result; - } -} +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.utils; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import org.bouncycastle.util.encoders.Hex; + +public class ByteArray { + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + public static String toHexString(byte[] data) { + return data == null ? "" : Hex.toHexString(data); + } + + public static byte[] fromHexString(String data) { + if (data == null) { + return EMPTY_BYTE_ARRAY; + } + if (data.startsWith("0x")) { + data = data.substring(2); + } + if (data.length() % 2 == 1) { + data = "0" + data; + } + return Hex.decode(data); + } + + public static long toLong(byte[] b) { + if (b == null || b.length == 0) { + return 0; + } + return new BigInteger(1, b).longValue(); + } + + public static byte[] fromString(String str) { + if (str == null) { + return null; + } + + return str.getBytes(); + } + + public static String toStr(byte[] byteArray) { + if (byteArray == null) { + return null; + } + + return new String(byteArray); + } + + public static byte[] fromLong(long val) { + return ByteBuffer.allocate(8).putLong(val).array(); + } + + /** + * Generate a subarray of a given byte array. + * + * @param input the input byte array + * @param start the start index + * @param end the end index + * @return a subarray of input, ranging from start (inclusively) to end + * (exclusively) + */ + public static byte[] subArray(byte[] input, int start, int end) { + byte[] result = new byte[end - start]; + System.arraycopy(input, start, result, 0, end - start); + return result; + } +} diff --git a/src/main/java/org/tron/common/utils/ByteArrayWrapper.java b/src/main/java/org/tron/common/utils/ByteArrayWrapper.java index 78ccb7f11..6328f2cae 100644 --- a/src/main/java/org/tron/common/utils/ByteArrayWrapper.java +++ b/src/main/java/org/tron/common/utils/ByteArrayWrapper.java @@ -18,10 +18,9 @@ */ -import org.spongycastle.util.encoders.Hex; - import java.io.Serializable; import java.util.Arrays; +import org.bouncycastle.util.encoders.Hex; public class ByteArrayWrapper implements Comparable, Serializable { diff --git a/src/main/java/org/tron/common/utils/ByteUtil.java b/src/main/java/org/tron/common/utils/ByteUtil.java index 50ae507f7..b40788e97 100644 --- a/src/main/java/org/tron/common/utils/ByteUtil.java +++ b/src/main/java/org/tron/common/utils/ByteUtil.java @@ -1,418 +1,418 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.utils; - -import com.google.common.base.Preconditions; -import com.google.common.primitives.UnsignedBytes; -import org.spongycastle.util.encoders.Hex; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class ByteUtil { - - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - public static final byte[] ZERO_BYTE_ARRAY = new byte[]{0}; - - - /** - * return a cloned byte array. return null if parameter data is null - */ - public static byte[] cloneBytes(byte[] data) { - if (data == null) { - return null; - } - - int length = data.length; - byte[] rc = new byte[length]; - if (length > 0) { - System.arraycopy(data, 0, rc, 0, length); - } - return rc; - } - - /** - * The regular {@link BigInteger#toByteArray()} method isn't quite what we often need: - * it appends a leading zero to indicate that the number is positive and may need padding. - * - * @param b the integer to format into a byte array - * @param numBytes the desired size of the resulting byte array - * @return numBytes byte long array. - */ - public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) { - if (b == null) { - return null; - } - byte[] bytes = new byte[numBytes]; - byte[] biBytes = b.toByteArray(); - int start = (biBytes.length == numBytes + 1) ? 1 : 0; - int length = Math.min(biBytes.length, numBytes); - System.arraycopy(biBytes, start, bytes, numBytes - length, length); - return bytes; - } - - /** - * Omitting sign indication byte.

    Instead of {@link org.spongycastle.util.BigIntegers#asUnsignedByteArray(BigInteger)} - *
    we use this custom method to avoid an empty array in case of BigInteger.ZERO - * - * @param value - any big integer number. A null-value will return null - * @return A byte array without a leading zero byte if present in the signed encoding. - * BigInteger.ZERO will return an array with length 1 and byte-value 0. - */ - public static byte[] bigIntegerToBytes(BigInteger value) { - if (value == null) { - return null; - } - - byte[] data = value.toByteArray(); - - if (data.length != 1 && data[0] == 0) { - byte[] tmp = new byte[data.length - 1]; - System.arraycopy(data, 1, tmp, 0, tmp.length); - data = tmp; - } - return data; - } - - /** - * merge arrays. - * - * @param arrays - arrays to merge - * @return - merged array - */ - public static byte[] merge(byte[]... arrays) { - int count = 0; - for (byte[] array : arrays) { - count += array.length; - } - - // Create new array and copy all array contents - byte[] mergedArray = new byte[count]; - int start = 0; - for (byte[] array : arrays) { - System.arraycopy(array, 0, mergedArray, start, array.length); - start += array.length; - } - return mergedArray; - } - - /** - * Creates a copy of bytes and appends b to the end of it. - */ - public static byte[] appendByte(byte[] bytes, byte b) { - byte[] result = Arrays.copyOf(bytes, bytes.length + 1); - result[result.length - 1] = b; - return result; - } - - /** - * Turn nibbles to a pretty looking output string Example. [ 1, 2, 3, 4, 5 ] becomes - * '\x11\x23\x45' - * - * @param nibbles - getting byte of data [ 04 ] and turning it to a '\x04' representation - * @return pretty string of nibbles - */ - public static String nibblesToPrettyString(byte[] nibbles) { - StringBuilder builder = new StringBuilder(); - for (byte nibble : nibbles) { - final String nibbleString = oneByteToHexString(nibble); - builder.append("\\x").append(nibbleString); - } - return builder.toString(); - } - - /** - * get hex string data from byte data. - */ - public static String oneByteToHexString(byte value) { - String retVal = Integer.toString(value & 0xFF, 16); - if (retVal.length() == 1) { - retVal = "0" + retVal; - } - return retVal; - } - - /** - * Convert a byte-array into a hex String.
    Works similar to {@link Hex#toHexString} but allows - * for null - * - * @param data - byte-array to convert to a hex-string - * @return hex representation of the data.
    Returns an empty String if the input is - * null - * @see Hex#toHexString - */ - public static String toHexString(byte[] data) { - return data == null ? "" : Hex.toHexString(data); - } - - /** - * Cast hex encoded value from byte[] to int Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) - * - * @param b array contains the values - * @return unsigned positive int value. - */ - public static int byteArrayToInt(byte[] b) { - if (b == null || b.length == 0) { - return 0; - } - return new BigInteger(1, b).intValue(); - } - - public static boolean isSingleZero(byte[] array) { - return (array.length == 1 && array[0] == 0); - } - - /** - * Converts a int value into a byte array. - * - * @param val - int value to convert - * @return value with leading byte that are zeroes striped - */ - public static byte[] intToBytesNoLeadZeroes(int val) { - - if (val == 0) { - return EMPTY_BYTE_ARRAY; - } - - int lenght = 0; - - int tmpVal = val; - while (tmpVal != 0) { - tmpVal = tmpVal >>> 8; - ++lenght; - } - - byte[] result = new byte[lenght]; - - int index = result.length - 1; - while (val != 0) { - - result[index] = (byte) (val & 0xFF); - val = val >>> 8; - index -= 1; - } - - return result; - } - - /** - * Converts int value into a byte array. - * - * @param val - int value to convert - * @return byte[] of length 4, representing the int value - */ - public static byte[] intToBytes(int val) { - return ByteBuffer.allocate(4).putInt(val).array(); - } - - /** - * Cast hex encoded value from byte[] to BigInteger null is parsed like byte[0] - * - * @param bb byte array contains the values - * @return unsigned positive BigInteger value. - */ - public static BigInteger bytesToBigInteger(byte[] bb) { - return (bb == null || bb.length == 0) ? BigInteger.ZERO : new BigInteger(1, bb); - } - - /** - * Cast hex encoded value from byte[] to long null is parsed like byte[0] - * - * Limited to Long.MAX_VALUE: 263-1 (8 bytes) - * - * @param b array contains the values - * @return unsigned positive long value. - */ - public static long byteArrayToLong(byte[] b) { - if (b == null || b.length == 0) { - return 0; - } - return new BigInteger(1, b).longValueExact(); - } - - public static int firstNonZeroByte(byte[] data) { - for (int i = 0; i < data.length; ++i) { - if (data[i] != 0) { - return i; - } - } - return -1; - } - - public static byte[] stripLeadingZeroes(byte[] data) { - - if (data == null) { - return null; - } - - final int firstNonZero = firstNonZeroByte(data); - switch (firstNonZero) { - case -1: - return ZERO_BYTE_ARRAY; - - case 0: - return data; - - default: - byte[] result = new byte[data.length - firstNonZero]; - System.arraycopy(data, firstNonZero, result, 0, data.length - firstNonZero); - - return result; - } - } - - /** - * Utility function to copy a byte array into a new byte array with given size. If the src length - * is smaller than the given size, the result will be left-padded with zeros. - * - * @param value - a BigInteger with a maximum value of 2^256-1 - * @return Byte array of given size with a copy of the src - */ - public static byte[] copyToArray(BigInteger value) { - byte[] dest = ByteBuffer.allocate(32).array(); - byte[] src = ByteUtil.bigIntegerToBytes(value); - if (src != null) { - System.arraycopy(src, 0, dest, dest.length - src.length, src.length); - } - return dest; - } - - /** - * Returns a number of zero bits preceding the highest-order ("leftmost") one-bit interpreting - * input array as a big-endian integer value - */ - public static int numberOfLeadingZeros(byte[] bytes) { - - int i = firstNonZeroByte(bytes); - - if (i == -1) { - return bytes.length * 8; - } else { - int byteLeadingZeros = Integer.numberOfLeadingZeros((int) bytes[i] & 0xff) - 24; - return i * 8 + byteLeadingZeros; - } - } - - /** - * Parses fixed number of bytes starting from {@code offset} in {@code input} array. If {@code - * input} has not enough bytes return array will be right padded with zero bytes. I.e. if {@code - * offset} is higher than {@code input.length} then zero byte array of length {@code len} will be - * returned - */ - public static byte[] parseBytes(byte[] input, int offset, int len) { - - if (offset >= input.length || len == 0) { - return EMPTY_BYTE_ARRAY; - } - - byte[] bytes = new byte[len]; - System.arraycopy(input, offset, bytes, 0, Math.min(input.length - offset, len)); - return bytes; - } - - /** - * Parses 32-bytes word from given input. Uses {@link #parseBytes(byte[], int, int)} method, thus, - * result will be right-padded with zero bytes if there is not enough bytes in {@code input} - * - * @param idx an index of the word starting from {@code 0} - */ - public static byte[] parseWord(byte[] input, int idx) { - return parseBytes(input, 32 * idx, 32); - } - - /** - * Parses 32-bytes word from given input. Uses {@link #parseBytes(byte[], int, int)} method, thus, - * result will be right-padded with zero bytes if there is not enough bytes in {@code input} - * - * @param idx an index of the word starting from {@code 0} - * @param offset an offset in {@code input} array to start parsing from - */ - public static byte[] parseWord(byte[] input, int offset, int idx) { - return parseBytes(input, offset + 32 * idx, 32); - } - - public static boolean greater(byte[] bytes1, byte[] bytes2) { - return compare(bytes1, bytes2) > 0; - } - - public static boolean greaterOrEquals(byte[] bytes1, byte[] bytes2) { - return compare(bytes1, bytes2) >= 0; - } - - public static boolean less(byte[] bytes1, byte[] bytes2) { - return compare(bytes1, bytes2) < 0; - } - - public static boolean lessOrEquals(byte[] bytes1, byte[] bytes2) { - return compare(bytes1, bytes2) <= 0; - } - - public static boolean equals(byte[] bytes1, byte[] bytes2) { - return compare(bytes1, bytes2) == 0; - } - - public static boolean isNullOrZeroArray(byte[] array){ - return (array == null) || (array.length == 0); - } - - // lexicographical order - public static int compare(byte[] bytes1, byte[] bytes2) { - Preconditions.checkNotNull(bytes1); - Preconditions.checkNotNull(bytes2); - Preconditions.checkArgument(bytes1.length == bytes2.length); - int length = bytes1.length; - for (int i = 0; i < length; ++i) { - int ret = UnsignedBytes.compare(bytes1[i], bytes2[i]); - if (ret != 0) { - return ret; - } - } - - return 0; - } - - public static byte[] hexToBytes(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - return data; - } - - - public static List convertBytesVectorToVector(final byte[] bytes) { - List ret = new ArrayList<>(); - - byte c; - for (int i = 0; i < bytes.length; i++) { - c = bytes[i]; - for (int j = 0; j < 8; j++) { - ret.add(((c >> (7 - j)) & 1) == 1); - } - } - - return ret; - } - -} \ No newline at end of file +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.utils; + +import com.google.common.base.Preconditions; +import com.google.common.primitives.UnsignedBytes; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.bouncycastle.util.encoders.Hex; + +public class ByteUtil { + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static final byte[] ZERO_BYTE_ARRAY = new byte[]{0}; + + + /** + * return a cloned byte array. return null if parameter data is null + */ + public static byte[] cloneBytes(byte[] data) { + if (data == null) { + return null; + } + + int length = data.length; + byte[] rc = new byte[length]; + if (length > 0) { + System.arraycopy(data, 0, rc, 0, length); + } + return rc; + } + + /** + * The regular {@link BigInteger#toByteArray()} method isn't quite what we often need: + * it appends a leading zero to indicate that the number is positive and may need padding. + * + * @param b the integer to format into a byte array + * @param numBytes the desired size of the resulting byte array + * @return numBytes byte long array. + */ + public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) { + if (b == null) { + return null; + } + byte[] bytes = new byte[numBytes]; + byte[] biBytes = b.toByteArray(); + int start = (biBytes.length == numBytes + 1) ? 1 : 0; + int length = Math.min(biBytes.length, numBytes); + System.arraycopy(biBytes, start, bytes, numBytes - length, length); + return bytes; + } + + /** + * Omitting sign indication byte.

    Instead of + * {@link org.bouncycastle.util.BigIntegers#asUnsignedByteArray(BigInteger)} + *
    we use this custom method to avoid an empty array in case of BigInteger.ZERO + * + * @param value - any big integer number. A null-value will return null + * @return A byte array without a leading zero byte if present in the signed encoding. + * BigInteger.ZERO will return an array with length 1 and byte-value 0. + */ + public static byte[] bigIntegerToBytes(BigInteger value) { + if (value == null) { + return null; + } + + byte[] data = value.toByteArray(); + + if (data.length != 1 && data[0] == 0) { + byte[] tmp = new byte[data.length - 1]; + System.arraycopy(data, 1, tmp, 0, tmp.length); + data = tmp; + } + return data; + } + + /** + * merge arrays. + * + * @param arrays - arrays to merge + * @return - merged array + */ + public static byte[] merge(byte[]... arrays) { + int count = 0; + for (byte[] array : arrays) { + count += array.length; + } + + // Create new array and copy all array contents + byte[] mergedArray = new byte[count]; + int start = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, mergedArray, start, array.length); + start += array.length; + } + return mergedArray; + } + + /** + * Creates a copy of bytes and appends b to the end of it. + */ + public static byte[] appendByte(byte[] bytes, byte b) { + byte[] result = Arrays.copyOf(bytes, bytes.length + 1); + result[result.length - 1] = b; + return result; + } + + /** + * Turn nibbles to a pretty looking output string Example. [ 1, 2, 3, 4, 5 ] becomes + * '\x11\x23\x45' + * + * @param nibbles - getting byte of data [ 04 ] and turning it to a '\x04' representation + * @return pretty string of nibbles + */ + public static String nibblesToPrettyString(byte[] nibbles) { + StringBuilder builder = new StringBuilder(); + for (byte nibble : nibbles) { + final String nibbleString = oneByteToHexString(nibble); + builder.append("\\x").append(nibbleString); + } + return builder.toString(); + } + + /** + * get hex string data from byte data. + */ + public static String oneByteToHexString(byte value) { + String retVal = Integer.toString(value & 0xFF, 16); + if (retVal.length() == 1) { + retVal = "0" + retVal; + } + return retVal; + } + + /** + * Convert a byte-array into a hex String.
    Works similar to {@link Hex#toHexString} but allows + * for null + * + * @param data - byte-array to convert to a hex-string + * @return hex representation of the data.
    Returns an empty String if the input is + * null + * @see Hex#toHexString + */ + public static String toHexString(byte[] data) { + return data == null ? "" : Hex.toHexString(data); + } + + /** + * Cast hex encoded value from byte[] to int Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) + * + * @param b array contains the values + * @return unsigned positive int value. + */ + public static int byteArrayToInt(byte[] b) { + if (b == null || b.length == 0) { + return 0; + } + return new BigInteger(1, b).intValue(); + } + + public static boolean isSingleZero(byte[] array) { + return (array.length == 1 && array[0] == 0); + } + + /** + * Converts a int value into a byte array. + * + * @param val - int value to convert + * @return value with leading byte that are zeroes striped + */ + public static byte[] intToBytesNoLeadZeroes(int val) { + + if (val == 0) { + return EMPTY_BYTE_ARRAY; + } + + int lenght = 0; + + int tmpVal = val; + while (tmpVal != 0) { + tmpVal = tmpVal >>> 8; + ++lenght; + } + + byte[] result = new byte[lenght]; + + int index = result.length - 1; + while (val != 0) { + + result[index] = (byte) (val & 0xFF); + val = val >>> 8; + index -= 1; + } + + return result; + } + + /** + * Converts int value into a byte array. + * + * @param val - int value to convert + * @return byte[] of length 4, representing the int value + */ + public static byte[] intToBytes(int val) { + return ByteBuffer.allocate(4).putInt(val).array(); + } + + /** + * Cast hex encoded value from byte[] to BigInteger null is parsed like byte[0] + * + * @param bb byte array contains the values + * @return unsigned positive BigInteger value. + */ + public static BigInteger bytesToBigInteger(byte[] bb) { + return (bb == null || bb.length == 0) ? BigInteger.ZERO : new BigInteger(1, bb); + } + + /** + * Cast hex encoded value from byte[] to long null is parsed like byte[0] + * + * Limited to Long.MAX_VALUE: 263-1 (8 bytes) + * + * @param b array contains the values + * @return unsigned positive long value. + */ + public static long byteArrayToLong(byte[] b) { + if (b == null || b.length == 0) { + return 0; + } + return new BigInteger(1, b).longValueExact(); + } + + public static int firstNonZeroByte(byte[] data) { + for (int i = 0; i < data.length; ++i) { + if (data[i] != 0) { + return i; + } + } + return -1; + } + + public static byte[] stripLeadingZeroes(byte[] data) { + + if (data == null) { + return null; + } + + final int firstNonZero = firstNonZeroByte(data); + switch (firstNonZero) { + case -1: + return ZERO_BYTE_ARRAY; + + case 0: + return data; + + default: + byte[] result = new byte[data.length - firstNonZero]; + System.arraycopy(data, firstNonZero, result, 0, data.length - firstNonZero); + + return result; + } + } + + /** + * Utility function to copy a byte array into a new byte array with given size. If the src length + * is smaller than the given size, the result will be left-padded with zeros. + * + * @param value - a BigInteger with a maximum value of 2^256-1 + * @return Byte array of given size with a copy of the src + */ + public static byte[] copyToArray(BigInteger value) { + byte[] dest = ByteBuffer.allocate(32).array(); + byte[] src = ByteUtil.bigIntegerToBytes(value); + if (src != null) { + System.arraycopy(src, 0, dest, dest.length - src.length, src.length); + } + return dest; + } + + /** + * Returns a number of zero bits preceding the highest-order ("leftmost") one-bit interpreting + * input array as a big-endian integer value + */ + public static int numberOfLeadingZeros(byte[] bytes) { + + int i = firstNonZeroByte(bytes); + + if (i == -1) { + return bytes.length * 8; + } else { + int byteLeadingZeros = Integer.numberOfLeadingZeros((int) bytes[i] & 0xff) - 24; + return i * 8 + byteLeadingZeros; + } + } + + /** + * Parses fixed number of bytes starting from {@code offset} in {@code input} array. If {@code + * input} has not enough bytes return array will be right padded with zero bytes. I.e. if {@code + * offset} is higher than {@code input.length} then zero byte array of length {@code len} will be + * returned + */ + public static byte[] parseBytes(byte[] input, int offset, int len) { + + if (offset >= input.length || len == 0) { + return EMPTY_BYTE_ARRAY; + } + + byte[] bytes = new byte[len]; + System.arraycopy(input, offset, bytes, 0, Math.min(input.length - offset, len)); + return bytes; + } + + /** + * Parses 32-bytes word from given input. Uses {@link #parseBytes(byte[], int, int)} method, thus, + * result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + */ + public static byte[] parseWord(byte[] input, int idx) { + return parseBytes(input, 32 * idx, 32); + } + + /** + * Parses 32-bytes word from given input. Uses {@link #parseBytes(byte[], int, int)} method, thus, + * result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + * @param offset an offset in {@code input} array to start parsing from + */ + public static byte[] parseWord(byte[] input, int offset, int idx) { + return parseBytes(input, offset + 32 * idx, 32); + } + + public static boolean greater(byte[] bytes1, byte[] bytes2) { + return compare(bytes1, bytes2) > 0; + } + + public static boolean greaterOrEquals(byte[] bytes1, byte[] bytes2) { + return compare(bytes1, bytes2) >= 0; + } + + public static boolean less(byte[] bytes1, byte[] bytes2) { + return compare(bytes1, bytes2) < 0; + } + + public static boolean lessOrEquals(byte[] bytes1, byte[] bytes2) { + return compare(bytes1, bytes2) <= 0; + } + + public static boolean equals(byte[] bytes1, byte[] bytes2) { + return compare(bytes1, bytes2) == 0; + } + + public static boolean isNullOrZeroArray(byte[] array){ + return (array == null) || (array.length == 0); + } + + // lexicographical order + public static int compare(byte[] bytes1, byte[] bytes2) { + Preconditions.checkNotNull(bytes1); + Preconditions.checkNotNull(bytes2); + Preconditions.checkArgument(bytes1.length == bytes2.length); + int length = bytes1.length; + for (int i = 0; i < length; ++i) { + int ret = UnsignedBytes.compare(bytes1[i], bytes2[i]); + if (ret != 0) { + return ret; + } + } + + return 0; + } + + public static byte[] hexToBytes(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + + public static List convertBytesVectorToVector(final byte[] bytes) { + List ret = new ArrayList<>(); + + byte c; + for (int i = 0; i < bytes.length; i++) { + c = bytes[i]; + for (int j = 0; j < 8; j++) { + ret.add(((c >> (7 - j)) & 1) == 1); + } + } + + return ret; + } + +} diff --git a/src/main/java/org/tron/common/utils/DataWord.java b/src/main/java/org/tron/common/utils/DataWord.java index fc480c311..655975d7f 100644 --- a/src/main/java/org/tron/common/utils/DataWord.java +++ b/src/main/java/org/tron/common/utils/DataWord.java @@ -20,11 +20,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import org.spongycastle.util.Arrays; -import org.spongycastle.util.encoders.Hex; - import java.math.BigInteger; import java.nio.ByteBuffer; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; /** * DataWord is the 32-byte array representation of a 256-bit number diff --git a/src/main/java/org/tron/common/utils/Hash.java b/src/main/java/org/tron/common/utils/Hash.java index b82ed7237..b9f948f71 100644 --- a/src/main/java/org/tron/common/utils/Hash.java +++ b/src/main/java/org/tron/common/utils/Hash.java @@ -18,18 +18,19 @@ package org.tron.common.utils; -import lombok.extern.slf4j.Slf4j; -import org.spongycastle.math.ec.ECPoint; -import org.tron.common.crypto.jce.TronCastleProvider; +import static java.util.Arrays.copyOfRange; +import static org.tron.common.utils.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.tron.common.utils.ByteUtil.isNullOrZeroArray; +import static org.tron.common.utils.ByteUtil.isSingleZero; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.Security; import java.util.Arrays; - -import static java.util.Arrays.copyOfRange; -import static org.tron.common.utils.ByteUtil.*; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.math.ec.ECPoint; +import org.tron.common.crypto.jce.TronCastleProvider; @Slf4j(topic = "crypto") public class Hash { diff --git a/src/main/java/org/tron/demo/ECKeyDemo.java b/src/main/java/org/tron/demo/ECKeyDemo.java index d0a23ea10..3f3f4a707 100644 --- a/src/main/java/org/tron/demo/ECKeyDemo.java +++ b/src/main/java/org/tron/demo/ECKeyDemo.java @@ -1,6 +1,10 @@ package org.tron.demo; -import org.spongycastle.math.ec.ECPoint; +import static java.util.Arrays.copyOfRange; + +import java.math.BigInteger; +import java.util.Arrays; +import org.bouncycastle.math.ec.ECPoint; import org.springframework.util.StringUtils; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; @@ -11,11 +15,6 @@ import org.tron.core.exception.CipherException; import org.tron.walletserver.WalletApi; -import java.math.BigInteger; -import java.util.Arrays; - -import static java.util.Arrays.copyOfRange; - public class ECKeyDemo { private static byte[] private2PublicDemo(byte[] privateKey) { diff --git a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java index fa0bbefb4..bd45dc8ed 100644 --- a/src/main/java/org/tron/demo/ShieldedTRC20Demo.java +++ b/src/main/java/org/tron/demo/ShieldedTRC20Demo.java @@ -8,8 +8,8 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.util.encoders.Hex; import org.junit.Assert; -import org.spongycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.Note; import org.tron.api.GrpcAPI.PrivateShieldedTRC20Parameters; diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 229d03b74..1963daead 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -11,10 +11,23 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigObject; import io.grpc.Status; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import org.spongycastle.util.encoders.Hex; +import org.bouncycastle.util.encoders.Hex; import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.AccountNetMessage; import org.tron.api.GrpcAPI.AccountResourceMessage; @@ -37,12 +50,12 @@ import org.tron.api.GrpcAPI.IncomingViewingKeyMessage; import org.tron.api.GrpcAPI.IvkDecryptAndMarkParameters; import org.tron.api.GrpcAPI.IvkDecryptParameters; +import org.tron.api.GrpcAPI.IvkDecryptTRC20Parameters; import org.tron.api.GrpcAPI.NfParameters; import org.tron.api.GrpcAPI.NfTRC20Parameters; import org.tron.api.GrpcAPI.NodeList; import org.tron.api.GrpcAPI.NoteParameters; import org.tron.api.GrpcAPI.NullifierResult; -import org.tron.api.GrpcAPI.IvkDecryptTRC20Parameters; import org.tron.api.GrpcAPI.OvkDecryptParameters; import org.tron.api.GrpcAPI.OvkDecryptTRC20Parameters; import org.tron.api.GrpcAPI.PaymentAddressMessage; @@ -52,10 +65,10 @@ import org.tron.api.GrpcAPI.PrivateShieldedTRC20ParametersWithoutAsk; import org.tron.api.GrpcAPI.ProposalList; import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.SpendAuthSigParameters; -import org.tron.api.GrpcAPI.SpendResult; import org.tron.api.GrpcAPI.ShieldedTRC20Parameters; import org.tron.api.GrpcAPI.ShieldedTRC20TriggerContractParameters; +import org.tron.api.GrpcAPI.SpendAuthSigParameters; +import org.tron.api.GrpcAPI.SpendResult; import org.tron.api.GrpcAPI.TransactionApprovedList; import org.tron.api.GrpcAPI.TransactionExtention; import org.tron.api.GrpcAPI.TransactionInfoList; @@ -79,17 +92,28 @@ import org.tron.core.config.Parameter.CommonConstant; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; +import org.tron.keystore.CheckStrength; +import org.tron.keystore.Credentials; +import org.tron.keystore.Wallet; +import org.tron.keystore.WalletFile; +import org.tron.keystore.WalletUtils; +import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.ChainParameters; +import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Key; import org.tron.protos.Protocol.MarketOrder; import org.tron.protos.Protocol.MarketOrderList; import org.tron.protos.Protocol.MarketOrderPairList; import org.tron.protos.Protocol.MarketPriceList; +import org.tron.protos.Protocol.Permission; +import org.tron.protos.Protocol.Proposal; +import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.Protocol.Transaction.Result; -import org.tron.keystore.CheckStrength; -import org.tron.keystore.Credentials; -import org.tron.keystore.Wallet; -import org.tron.keystore.WalletFile; -import org.tron.keystore.WalletUtils; +import org.tron.protos.Protocol.TransactionInfo; +import org.tron.protos.Protocol.TransactionSign; +import org.tron.protos.Protocol.Witness; import org.tron.protos.contract.AccountContract.AccountCreateContract; import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; import org.tron.protos.contract.AccountContract.AccountUpdateContract; @@ -97,12 +121,18 @@ import org.tron.protos.contract.AssetIssueContractOuterClass.AssetIssueContract; import org.tron.protos.contract.AssetIssueContractOuterClass.ParticipateAssetIssueContract; import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; +import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.BalanceContract.TransferContract; +import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; +import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; import org.tron.protos.contract.ExchangeContract.ExchangeCreateContract; import org.tron.protos.contract.ExchangeContract.ExchangeInjectContract; import org.tron.protos.contract.ExchangeContract.ExchangeTransactionContract; import org.tron.protos.contract.ExchangeContract.ExchangeWithdrawContract; +import org.tron.protos.contract.MarketContract.MarketCancelOrderContract; +import org.tron.protos.contract.MarketContract.MarketSellAssetContract; import org.tron.protos.contract.ProposalContract.ProposalApproveContract; import org.tron.protos.contract.ProposalContract.ProposalCreateContract; import org.tron.protos.contract.ProposalContract.ProposalDeleteContract; @@ -110,46 +140,20 @@ import org.tron.protos.contract.ShieldContract.OutputPointInfo; import org.tron.protos.contract.ShieldContract.ShieldedTransferContract; import org.tron.protos.contract.ShieldContract.SpendDescription; +import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; +import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.SmartContract; import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; +import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; import org.tron.protos.contract.StorageContract.BuyStorageBytesContract; import org.tron.protos.contract.StorageContract.BuyStorageContract; -import org.tron.protos.contract.SmartContractOuterClass.ClearABIContract; -import org.tron.protos.contract.SmartContractOuterClass.CreateSmartContract; -import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.StorageContract.SellStorageContract; -import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; -import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; import org.tron.protos.contract.StorageContract.UpdateBrokerageContract; -import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; -import org.tron.protos.contract.SmartContractOuterClass.UpdateSettingContract; -import org.tron.protos.contract.BalanceContract.WithdrawBalanceContract; -import org.tron.protos.Protocol.Account; -import org.tron.protos.Protocol.Block; -import org.tron.protos.Protocol.ChainParameters; -import org.tron.protos.Protocol.DelegatedResourceAccountIndex; -import org.tron.protos.Protocol.Exchange; -import org.tron.protos.Protocol.Key; -import org.tron.protos.Protocol.Permission; -import org.tron.protos.Protocol.Proposal; -import org.tron.protos.contract.SmartContractOuterClass.SmartContract; -import org.tron.protos.Protocol.Transaction; -import org.tron.protos.Protocol.TransactionInfo; -import org.tron.protos.Protocol.TransactionSign; -import org.tron.protos.Protocol.Witness; import org.tron.protos.contract.WitnessContract.VoteWitnessContract; import org.tron.protos.contract.WitnessContract.WitnessCreateContract; import org.tron.protos.contract.WitnessContract.WitnessUpdateContract; -import org.tron.protos.contract.MarketContract.MarketCancelOrderContract; -import org.tron.protos.contract.MarketContract.MarketSellAssetContract; -import org.tron.protos.contract.MarketContract.MarketSellAssetContract; - -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; @Slf4j public class WalletApi { From 9fa5f2d0a45bcc38cb69afb34cfbc9949cdaee0f Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Mon, 7 Jun 2021 19:03:20 +0800 Subject: [PATCH 387/445] modify the BC provider --- build.gradle | 2 +- .../java/org/tron/common/crypto/Hash.java | 269 +++++++++--------- src/main/java/org/tron/common/utils/Hash.java | 2 +- 3 files changed, 136 insertions(+), 137 deletions(-) diff --git a/build.gradle b/build.gradle index 5f473816c..872846d64 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ task wrapper(type: Wrapper) { } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'junit', name: 'junit', version: '4.12' compile group: 'com.beust', name: 'jcommander', version: '1.72' //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' diff --git a/src/main/java/org/tron/common/crypto/Hash.java b/src/main/java/org/tron/common/crypto/Hash.java index 5131ddc6b..f679d0d52 100644 --- a/src/main/java/org/tron/common/crypto/Hash.java +++ b/src/main/java/org/tron/common/crypto/Hash.java @@ -1,135 +1,134 @@ -/* - * Copyright (c) [2016] [ ] - * This file is part of the ethereumJ library. - * - * The ethereumJ library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The ethereumJ library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with the ethereumJ library. If not, see . - */ - -package org.tron.common.crypto; - -import lombok.extern.slf4j.Slf4j; -import org.tron.common.crypto.jce.TronCastleProvider; -import org.tron.common.utils.ByteArray; -import org.tron.walletserver.WalletApi; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.Security; - -import static java.util.Arrays.copyOfRange; - -@Slf4j -public class Hash { - - private static final Provider CRYPTO_PROVIDER; - - private static final String HASH_256_ALGORITHM_NAME; - private static final String HASH_512_ALGORITHM_NAME; - - static { - Security.addProvider(TronCastleProvider.getInstance()); - CRYPTO_PROVIDER = Security.getProvider("SC"); - HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256"; - HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512"; - } - - public static byte[] sha3(byte[] input) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - System.out.println("Can't find such algorithm" + e); - throw new RuntimeException(e); - } - - } - - /** - * Keccak-256 hash function. - * - * @param hexInput hex encoded input data with optional 0x prefix - * @return hash value as hex encoded string - */ - public static String sha3(String hexInput) { - byte[] bytes = ByteArray.fromHexString(hexInput); - byte[] result = sha3(bytes); - return ByteArray.toHexString(result); - } - - - public static byte[] sha3(byte[] input1, byte[] input2) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input1, 0, input1.length); - digest.update(input2, 0, input2.length); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - System.out.println("Can't find such algorithm" + e); - throw new RuntimeException(e); - } - } - - /** - * hashing chunk of the data - * - * @param input - data for hash - * @param start - start of hashing chunk - * @param length - length of hashing chunk - * @return - keccak hash of the chunk - */ - public static byte[] sha3(byte[] input, int start, int length) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input, start, length); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - System.out.println("Can't find such algorithm" + e); - throw new RuntimeException(e); - } - } - - public static byte[] sha512(byte[] input) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance(HASH_512_ALGORITHM_NAME, - CRYPTO_PROVIDER); - digest.update(input); - return digest.digest(); - } catch (NoSuchAlgorithmException e) { - System.out.println("Can't find such algorithm" + e); - throw new RuntimeException(e); - } - } - - /** - * Calculates RIGTMOST160(SHA3(input)). This is used in address calculations. * - * - * @param input - data - * @return - add_pre_fix + 20 right bytes of the hash keccak of the data - */ - public static byte[] sha3omit12(byte[] input) { - byte[] hash = sha3(input); - byte[] address = copyOfRange(hash, 11, hash.length); - address[0] = WalletApi.getAddressPreFixByte(); - return address; - } -} +/* + * Copyright (c) [2016] [ ] + * This file is part of the ethereumJ library. + * + * The ethereumJ library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ethereumJ library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the ethereumJ library. If not, see . + */ + +package org.tron.common.crypto; + +import static java.util.Arrays.copyOfRange; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import lombok.extern.slf4j.Slf4j; +import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.ByteArray; +import org.tron.walletserver.WalletApi; + +@Slf4j +public class Hash { + + private static final Provider CRYPTO_PROVIDER; + + private static final String HASH_256_ALGORITHM_NAME; + private static final String HASH_512_ALGORITHM_NAME; + + static { + Security.addProvider(TronCastleProvider.getInstance()); + CRYPTO_PROVIDER = Security.getProvider("BC"); + HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256"; + HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512"; + } + + public static byte[] sha3(byte[] input) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + System.out.println("Can't find such algorithm" + e); + throw new RuntimeException(e); + } + + } + + /** + * Keccak-256 hash function. + * + * @param hexInput hex encoded input data with optional 0x prefix + * @return hash value as hex encoded string + */ + public static String sha3(String hexInput) { + byte[] bytes = ByteArray.fromHexString(hexInput); + byte[] result = sha3(bytes); + return ByteArray.toHexString(result); + } + + + public static byte[] sha3(byte[] input1, byte[] input2) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input1, 0, input1.length); + digest.update(input2, 0, input2.length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + System.out.println("Can't find such algorithm" + e); + throw new RuntimeException(e); + } + } + + /** + * hashing chunk of the data + * + * @param input - data for hash + * @param start - start of hashing chunk + * @param length - length of hashing chunk + * @return - keccak hash of the chunk + */ + public static byte[] sha3(byte[] input, int start, int length) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_256_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input, start, length); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + System.out.println("Can't find such algorithm" + e); + throw new RuntimeException(e); + } + } + + public static byte[] sha512(byte[] input) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_512_ALGORITHM_NAME, + CRYPTO_PROVIDER); + digest.update(input); + return digest.digest(); + } catch (NoSuchAlgorithmException e) { + System.out.println("Can't find such algorithm" + e); + throw new RuntimeException(e); + } + } + + /** + * Calculates RIGTMOST160(SHA3(input)). This is used in address calculations. * + * + * @param input - data + * @return - add_pre_fix + 20 right bytes of the hash keccak of the data + */ + public static byte[] sha3omit12(byte[] input) { + byte[] hash = sha3(input); + byte[] address = copyOfRange(hash, 11, hash.length); + address[0] = WalletApi.getAddressPreFixByte(); + return address; + } +} diff --git a/src/main/java/org/tron/common/utils/Hash.java b/src/main/java/org/tron/common/utils/Hash.java index b9f948f71..aa6336b1d 100644 --- a/src/main/java/org/tron/common/utils/Hash.java +++ b/src/main/java/org/tron/common/utils/Hash.java @@ -66,7 +66,7 @@ public class Hash { static { Security.addProvider(TronCastleProvider.getInstance()); - CRYPTO_PROVIDER = Security.getProvider("SC"); + CRYPTO_PROVIDER = Security.getProvider("BC"); HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256"; HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512"; EMPTY_TRIE_HASH = sha3(encodeElement(EMPTY_BYTE_ARRAY)); From 1886d0513c3333739f5c734025cffabcd723b390 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 30 Jun 2021 14:30:40 +0000 Subject: [PATCH 388/445] Refactor StringUtils.isContains --- .../java/org/tron/keystore/StringUtils.java | 20 +------------------ .../org/tron/keystore/StringUtilsTest.java | 5 +++++ 2 files changed, 6 insertions(+), 19 deletions(-) mode change 100644 => 100755 src/main/java/org/tron/keystore/StringUtils.java mode change 100644 => 100755 src/test/java/org/tron/keystore/StringUtilsTest.java diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java old mode 100644 new mode 100755 index d2224a508..a6676277b --- a/src/main/java/org/tron/keystore/StringUtils.java +++ b/src/main/java/org/tron/keystore/StringUtils.java @@ -57,25 +57,7 @@ public static boolean isContains(char[] a, char[] b) { if (ArrayUtils.isEmpty(a) || ArrayUtils.isEmpty(b)) { return false; } - - int alen = a.length; - int blen = b.length; - - for (int i = 0; i < alen; i++) { - if (alen - i < blen) { - return false; - } - int j; - for (j = 0; j < blen; j++) { - if (a[i + j] != b[j]) { - break; - } - } - if (j == blen) { - return true; - } - } - return false; + return new String(a).contains(new String(b)); } public static void clear(char[] a) { diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java old mode 100644 new mode 100755 index 194cc82db..6204fc308 --- a/src/test/java/org/tron/keystore/StringUtilsTest.java +++ b/src/test/java/org/tron/keystore/StringUtilsTest.java @@ -30,6 +30,11 @@ public void isContains() { char[] b = "ghijkl".toCharArray(); char[] c = "defghi".toCharArray(); char[] d = "abcdefghijkl".toCharArray(); + char[] empty = "".toCharArray(); + + Assert.assertFalse(StringUtils.isContains(empty, d)); + Assert.assertFalse(StringUtils.isContains(d, empty)); + Assert.assertTrue(StringUtils.isContains(d, d)); Assert.assertTrue(StringUtils.isContains(d, a)); Assert.assertTrue(StringUtils.isContains(d, b)); From 9606fe230ec7830b21b611e3361fadbe157d2824 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 1 Jul 2021 09:06:55 +0000 Subject: [PATCH 389/445] Use KMP --- src/main/java/org/tron/keystore/KMP.java | 52 +++++++++++++++++++ .../java/org/tron/keystore/StringUtils.java | 5 +- .../org/tron/keystore/StringUtilsTest.java | 6 ++- 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/tron/keystore/KMP.java diff --git a/src/main/java/org/tron/keystore/KMP.java b/src/main/java/org/tron/keystore/KMP.java new file mode 100644 index 000000000..363a5ef61 --- /dev/null +++ b/src/main/java/org/tron/keystore/KMP.java @@ -0,0 +1,52 @@ +package org.tron.keystore; + +public class KMP { + public static boolean search(char[] text, char[] pattern) { + int patternLength = pattern.length; + int textLength = text.length; + int[] lps = new int[patternLength]; + + prepareLPSArray(pattern, lps); + + int i = 0; + int j = 0; + while (i < textLength) { + if (text[i] == pattern[j]) { + i++; + j++; + } + + if (j == patternLength) { + return true; + } + + if (i < textLength && text[i] != pattern[j]) { + if (j != 0) { + j = lps[j - 1]; + } else { + i++; + } + } + } + return false; + } + + private static void prepareLPSArray(char[] pattern, int lps[]) { + int patternLength = pattern.length; + int len = 0, i = 1; + while (i < patternLength) { + if (pattern[i] == pattern[len]) { + len++; + lps[i] = len; + i++; + } else { + if (len == 0) { + lps[i] = len; + i++; + } else { + len = lps[len - 1]; + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java index a6676277b..1e447bba9 100755 --- a/src/main/java/org/tron/keystore/StringUtils.java +++ b/src/main/java/org/tron/keystore/StringUtils.java @@ -57,7 +57,10 @@ public static boolean isContains(char[] a, char[] b) { if (ArrayUtils.isEmpty(a) || ArrayUtils.isEmpty(b)) { return false; } - return new String(a).contains(new String(b)); + if (a.length < b.length) { + return false; + } + return KMP.search(a, b); } public static void clear(char[] a) { diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java index 6204fc308..ac0282a70 100755 --- a/src/test/java/org/tron/keystore/StringUtilsTest.java +++ b/src/test/java/org/tron/keystore/StringUtilsTest.java @@ -7,7 +7,6 @@ public class StringUtilsTest { - @Test public void isCharEqual() { char[] a = "aaaaaa".toCharArray(); @@ -32,6 +31,11 @@ public void isContains() { char[] d = "abcdefghijkl".toCharArray(); char[] empty = "".toCharArray(); + char[] longarr = "xxxxxxxx123xxxxxxxxx".toCharArray(); + char[] shortarr = "123".toCharArray(); + Assert.assertFalse(StringUtils.isContains(shortarr, longarr)); + Assert.assertTrue(StringUtils.isContains(longarr, shortarr)); + Assert.assertFalse(StringUtils.isContains(empty, d)); Assert.assertFalse(StringUtils.isContains(d, empty)); From f2186d41858ba4f1b2492b39649a80bed9269056 Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Tue, 6 Jul 2021 18:06:11 +0800 Subject: [PATCH 390/445] remove the spongycastle dependency --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 872846d64..a4ea5ecba 100644 --- a/build.gradle +++ b/build.gradle @@ -76,8 +76,6 @@ dependencies { compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' -// compile "com.madgag.spongycastle:core:1.58.0.0" -// compile "com.madgag.spongycastle:prov:1.58.0.0" compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.68' compile group: 'com.typesafe', name: 'config', version: '1.3.2' compile "com.google.code.findbugs:jsr305:3.0.0" From dcf17de69c994048409ea713c611b8fcec8a3be8 Mon Sep 17 00:00:00 2001 From: neo hong Date: Tue, 13 Jul 2021 16:55:48 +0800 Subject: [PATCH 391/445] add `error` type and fix bug which get unrecognized type --- src/main/java/org/tron/walletserver/WalletApi.java | 6 ++++-- src/main/protos/core/contract/smart_contract.proto | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 4f5220b95..3235723e7 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -1699,8 +1699,10 @@ public static SmartContract.ABI.Entry.EntryType getEntryType(String type) { return SmartContract.ABI.Entry.EntryType.Fallback; case "receive": return SmartContract.ABI.Entry.EntryType.Receive; + case "error": + return SmartContract.ABI.Entry.EntryType.Error; default: - return SmartContract.ABI.Entry.EntryType.UNRECOGNIZED; + return SmartContract.ABI.Entry.EntryType.UnknownEntryType; } } @@ -1716,7 +1718,7 @@ public static SmartContract.ABI.Entry.StateMutabilityType getStateMutability( case "payable": return SmartContract.ABI.Entry.StateMutabilityType.Payable; default: - return SmartContract.ABI.Entry.StateMutabilityType.UNRECOGNIZED; + return SmartContract.ABI.Entry.StateMutabilityType.UnknownMutabilityType; } } diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto index 74f17f5a7..3a21a6597 100644 --- a/src/main/protos/core/contract/smart_contract.proto +++ b/src/main/protos/core/contract/smart_contract.proto @@ -18,6 +18,7 @@ message SmartContract { Event = 3; Fallback = 4; Receive = 5; + Error = 6; } message Param { bool indexed = 1; From 5762dd968015fba060100c26f25a93119b376987 Mon Sep 17 00:00:00 2001 From: "federico.zhen" Date: Mon, 19 Jul 2021 12:08:49 +0800 Subject: [PATCH 392/445] update the bouncy castle version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a4ea5ecba..17e5dbb24 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.68' + compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.69' compile group: 'com.typesafe', name: 'config', version: '1.3.2' compile "com.google.code.findbugs:jsr305:3.0.0" compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" From 79fd8b9439a70e7e4be6172d812d7a41561be879 Mon Sep 17 00:00:00 2001 From: neo hong Date: Wed, 21 Jul 2021 15:51:12 +0800 Subject: [PATCH 393/445] add EnergyUsed field in TransactionExtention --- src/main/protos/api/api.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 7968dfeb4..d467142e1 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1195,6 +1195,7 @@ message TransactionExtention { bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; + int64 EnergyUsed = 5; } message BlockExtention { From f8b3472b088672b37a7c7cc8ce8077e092cd2f41 Mon Sep 17 00:00:00 2001 From: Asuka Date: Fri, 23 Jul 2021 11:11:29 +0800 Subject: [PATCH 394/445] feature: adapt to java-tron 4.3 - Add extra filed to internal transaction - Add energy used field to transaction extension --- src/main/java/org/tron/walletserver/WalletApi.java | 1 + src/main/protos/api/api.proto | 1 + src/main/protos/core/Tron.proto | 1 + 3 files changed, 3 insertions(+) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 4f5220b95..196838591 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2186,6 +2186,7 @@ public boolean triggerContract( System.out.println( ":" + ByteArray.toStr(transactionExtention.getResult().getMessage().toByteArray())); System.out.println("Result:" + Hex.toHexString(result)); + System.out.println("EnergyUsed:" + transactionExtention.getEnergyUsed()); return true; } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 7968dfeb4..d2b816d9b 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1195,6 +1195,7 @@ message TransactionExtention { bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; + uint64 EnergyUsed = 5; } message BlockExtention { diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 4e7adc79d..4ece6c6d9 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -539,6 +539,7 @@ message InternalTransaction { repeated CallValueInfo callValueInfo = 4; bytes note = 5; bool rejected = 6; + string extra = 7; } message DelegatedResourceAccountIndex { From faa86ab1214508b81c3383a659e34325d5be0bd5 Mon Sep 17 00:00:00 2001 From: neo hong Date: Mon, 26 Jul 2021 11:55:57 +0800 Subject: [PATCH 395/445] modify energy_used format --- src/main/protos/api/api.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index d467142e1..fe6ac237c 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1195,7 +1195,7 @@ message TransactionExtention { bytes txid = 2; //transaction id = sha256(transaction.raw_data) repeated bytes constant_result = 3; Return result = 4; - int64 EnergyUsed = 5; + int64 energy_used = 5; } message BlockExtention { From 286c254a55737f5017eb6716792024e613c20a5e Mon Sep 17 00:00:00 2001 From: Dr Lai Date: Wed, 28 Jul 2021 13:00:41 +0100 Subject: [PATCH 396/445] Add length check --- src/main/java/org/tron/keystore/KMP.java | 52 ------------------- .../java/org/tron/keystore/StringUtils.java | 20 ++++++- 2 files changed, 18 insertions(+), 54 deletions(-) delete mode 100644 src/main/java/org/tron/keystore/KMP.java diff --git a/src/main/java/org/tron/keystore/KMP.java b/src/main/java/org/tron/keystore/KMP.java deleted file mode 100644 index 363a5ef61..000000000 --- a/src/main/java/org/tron/keystore/KMP.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.tron.keystore; - -public class KMP { - public static boolean search(char[] text, char[] pattern) { - int patternLength = pattern.length; - int textLength = text.length; - int[] lps = new int[patternLength]; - - prepareLPSArray(pattern, lps); - - int i = 0; - int j = 0; - while (i < textLength) { - if (text[i] == pattern[j]) { - i++; - j++; - } - - if (j == patternLength) { - return true; - } - - if (i < textLength && text[i] != pattern[j]) { - if (j != 0) { - j = lps[j - 1]; - } else { - i++; - } - } - } - return false; - } - - private static void prepareLPSArray(char[] pattern, int lps[]) { - int patternLength = pattern.length; - int len = 0, i = 1; - while (i < patternLength) { - if (pattern[i] == pattern[len]) { - len++; - lps[i] = len; - i++; - } else { - if (len == 0) { - lps[i] = len; - i++; - } else { - len = lps[len - 1]; - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java index 1e447bba9..d722340db 100755 --- a/src/main/java/org/tron/keystore/StringUtils.java +++ b/src/main/java/org/tron/keystore/StringUtils.java @@ -57,10 +57,26 @@ public static boolean isContains(char[] a, char[] b) { if (ArrayUtils.isEmpty(a) || ArrayUtils.isEmpty(b)) { return false; } - if (a.length < b.length) { + int alen = a.length; + int blen = b.length; + if (alen < blen) { return false; } - return KMP.search(a, b); + for (int i = 0; i < alen; i++) { + if (alen - i < blen) { + return false; + } + int j; + for (j = 0; j < blen; j++) { + if (a[i + j] != b[j]) { + break; + } + } + if (j == blen) { + return true; + } + } + return false; } public static void clear(char[] a) { From f87ee8a5c079ecfc62ad48fb8ca59c8979745f9d Mon Sep 17 00:00:00 2001 From: Asuka Date: Mon, 30 Aug 2021 12:01:55 +0800 Subject: [PATCH 397/445] feature: modify proto buffer - Add version field to smart contract definition - Add log list and internal transaction list to transaction extension --- src/main/protos/api/api.proto | 2 ++ src/main/protos/core/contract/smart_contract.proto | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index ecfe7d626..f9dcc0f32 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1193,6 +1193,8 @@ message TransactionExtention { repeated bytes constant_result = 3; Return result = 4; int64 energy_used = 5; + repeated TransactionInfo.Log logs = 6; + repeated InternalTransaction internal_transactions = 7; } message BlockExtention { diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto index 3a21a6597..40fbb5b7d 100644 --- a/src/main/protos/core/contract/smart_contract.proto +++ b/src/main/protos/core/contract/smart_contract.proto @@ -55,6 +55,7 @@ message SmartContract { int64 origin_energy_limit = 8; bytes code_hash = 9; bytes trx_hash = 10; + int32 version = 11; } message CreateSmartContract { From 7e7d13d60fb3e35a608a016c66bf84e13500c625 Mon Sep 17 00:00:00 2001 From: Asuka Date: Mon, 30 Aug 2021 12:03:17 +0800 Subject: [PATCH 398/445] feature: adjust printed info for constant trigger --- .../java/org/tron/walletserver/WalletApi.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 8f5a05ccd..19ce7122a 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2178,15 +2178,13 @@ public boolean triggerContract( Transaction transaction = transactionExtention .getTransaction(); // for constant - if (transaction.getRetCount() != 0 - && transactionExtention.getConstantResult(0) != null - && transactionExtention.getResult() != null) { - byte[] result = transactionExtention.getConstantResult(0).toByteArray(); - System.out.println("message:" + transaction.getRet(0).getRet()); - System.out.println( - ":" + ByteArray.toStr(transactionExtention.getResult().getMessage().toByteArray())); - System.out.println("Result:" + Hex.toHexString(result)); - System.out.println("EnergyUsed:" + transactionExtention.getEnergyUsed()); + if (transaction.getRetCount() != 0) { + TransactionExtention.Builder builder = + transactionExtention.toBuilder().clearTransaction().clearTxid(); + if (transaction.getRet(0).getRet() == Result.code.FAILED) { + builder.setResult(builder.getResult().toBuilder().setResult(false)); + } + System.out.println("Execution result = " + Utils.formatMessageString(builder.build())); return true; } From 6f443486b605941d62416b972ef734ab5899bdcc Mon Sep 17 00:00:00 2001 From: Asuka Date: Wed, 1 Sep 2021 14:28:51 +0800 Subject: [PATCH 399/445] feature: add DeployConstantContract interface --- src/main/java/org/tron/walletcli/Client.java | 173 ++++++++++++++---- .../java/org/tron/walletserver/WalletApi.java | 4 +- 2 files changed, 138 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index a7c324d28..f9b6bb190 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2141,28 +2141,70 @@ private void deployContract(String[] parameter) } } - private void triggerContract(String[] parameters, boolean isConstant) - throws IOException, CipherException, CancelException, EncodingException { - String cmdMethodStr = isConstant ? "TriggerConstantContract" : "TriggerContract"; - - if (isConstant) { - if (parameters == null || (parameters.length != 4 && parameters.length != 5)) { - System.out.println(cmdMethodStr + " needs 4 or 5 parameters like: "); - System.out.println(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex"); + private void deployConstantContract(String[] parameters) + throws IOException, CipherException, CancelException { + + if (parameters == null || (parameters.length != 5 && parameters.length != 8)) { + System.out.println("DeployConstantContract needs at least 4 parameters like: "); + System.out.println("DeployConstantContract ownerAddress(use # if you own)" + + " byteCode constructor params isHex [value token_value token_id]"); + return; + } + + int idx = 0; + + String ownerAddressStr = parameters[idx++]; + byte[] ownerAddress = null; + if (!"#".equals(ownerAddressStr)) { + ownerAddress = WalletApi.decodeFromBase58Check(ownerAddressStr); + if (ownerAddress == null) { + System.out.println("Invalid Owner Address."); return; } - } else { - if (parameters == null || (parameters.length != 8 && parameters.length != 9)) { - System.out.println(cmdMethodStr + " needs 8 or 9 parameters like: "); - System.out.println(cmdMethodStr + " [OwnerAddress] contractAddress method args isHex" - + " fee_limit value token_value token_id(e.g: TRXTOKEN, use # if don't provided)"); - return; + } + + String codeStr = parameters[idx++]; + String constructorStr = parameters[idx++]; + String argsStr = parameters[idx++]; + boolean isHex = Boolean.parseBoolean(parameters[idx++]); + long callValue = 0; + long tokenValue = 0; + String tokenId = ""; + if (parameters.length == 8) { + callValue = Long.parseLong(parameters[idx++]); + tokenValue = Long.parseLong(parameters[idx++]); + tokenId = parameters[idx]; + } + + if (!(constructorStr.equals("#") || argsStr.equals("#"))) { + if (isHex) { + codeStr += argsStr; + } else { + codeStr += Hex.toHexString(AbiUtil.encodeInput(constructorStr, argsStr)); } } + if (tokenId.equalsIgnoreCase("#")) { + tokenId = ""; + } + + walletApiWrapper.callContract( + ownerAddress, null, callValue, Hex.decode(codeStr), 0, tokenValue, tokenId, true); + } + + private void triggerContract(String[] parameters) + throws IOException, CipherException, CancelException { + + if (parameters == null || (parameters.length != 8 && parameters.length != 9)) { + System.out.println("TriggerContract needs 8 or 9 parameters like: "); + System.out.println("TriggerContract [OwnerAddress] contractAddress method args isHex" + + " fee_limit value token_value token_id(e.g: TRXTOKEN, use # if don't provided)"); + return; + } + int index = 0; byte[] ownerAddress = null; - if (parameters.length == 5 || parameters.length == 9) { + if (parameters.length == 9) { ownerAddress = WalletApi.decodeFromBase58Check(parameters[index++]); if (ownerAddress == null) { System.out.println("Invalid OwnerAddress."); @@ -2173,42 +2215,92 @@ private void triggerContract(String[] parameters, boolean isConstant) String contractAddrStr = parameters[index++]; String methodStr = parameters[index++]; String argsStr = parameters[index++]; - boolean isHex = Boolean.valueOf(parameters[index++]); - long feeLimit = 0; - long callValue = 0; - long tokenCallValue = 0; - String tokenId = ""; + boolean isHex = Boolean.parseBoolean(parameters[index++]); + long feeLimit = Long.parseLong(parameters[index++]); + long callValue = Long.parseLong(parameters[index++]); + long tokenValue = Long.parseLong(parameters[index++]); + String tokenId = parameters[index]; - if (!isConstant) { - feeLimit = Long.valueOf(parameters[index++]); - callValue = Long.valueOf(parameters[index++]); - tokenCallValue = Long.valueOf(parameters[index++]); - tokenId = parameters[index++]; - } if (argsStr.equalsIgnoreCase("#")) { argsStr = ""; } + if (tokenId.equalsIgnoreCase("#")) { tokenId = ""; } + byte[] input = new byte[0]; if (!methodStr.equalsIgnoreCase("#")) { input = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, isHex)); } byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddrStr); - boolean result = walletApiWrapper - .callContract(ownerAddress, contractAddress, callValue, input, feeLimit, tokenCallValue, - tokenId, - isConstant); - if (!isConstant) { - if (result) { - System.out.println("Broadcast the " + cmdMethodStr + " successful.\n" - + "Please check the given transaction id to get the result on blockchain using getTransactionInfoById command"); - } else { - System.out.println("Broadcast the " + cmdMethodStr + " failed"); + boolean result = walletApiWrapper.callContract( + ownerAddress, contractAddress, callValue, input, feeLimit, tokenValue, tokenId, false); + if (result) { + System.out.println("Broadcast the TriggerContract successful.\n" + + "Please check the given transaction id to get the result on blockchain using getTransactionInfoById command"); + } else { + System.out.println("Broadcast the TriggerContract failed"); + } + } + + private void triggerConstantContract(String[] parameters) + throws IOException, CipherException, CancelException { + + if (parameters == null || (parameters.length != 5 && parameters.length != 8)) { + System.out.println("TriggerConstantContract needs 5 or 8 parameters like: "); + System.out.println("TriggerConstantContract ownerAddress(use # if you own)" + + " contractAddress method args isHex [value token_value token_id(e.g: TRXTOKEN, use # if don't provided)]"); + return; + } + + int idx = 0; + + String ownerAddressStr = parameters[idx++]; + byte[] ownerAddress = null; + if (!"#".equals(ownerAddressStr)) { + ownerAddress = WalletApi.decodeFromBase58Check(ownerAddressStr); + if (ownerAddress == null) { + System.out.println("Invalid Owner Address."); + return; } } + + String contractAddressStr = parameters[idx++]; + byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddressStr); + if (contractAddress == null) { + System.out.println("Invalid Contract Address."); + return; + } + + String methodStr = parameters[idx++]; + String argsStr = parameters[idx++]; + boolean isHex = Boolean.parseBoolean(parameters[idx++]); + long callValue = 0; + long tokenValue = 0; + String tokenId = ""; + if (parameters.length == 8) { + callValue = Long.parseLong(parameters[idx++]); + tokenValue = Long.parseLong(parameters[idx++]); + tokenId = parameters[idx]; + } + + if (argsStr.equalsIgnoreCase("#")) { + argsStr = ""; + } + + if (tokenId.equalsIgnoreCase("#")) { + tokenId = ""; + } + + byte[] input = new byte[0]; + if (!methodStr.equalsIgnoreCase("#")) { + input = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, isHex)); + } + + walletApiWrapper.callContract( + ownerAddress, contractAddress, callValue, input, 0, tokenValue, tokenId, true); } private void getContract(String[] parameters) { @@ -3692,6 +3784,7 @@ private void help() { public static String[] getCmd(String cmdLine) { if (cmdLine.indexOf("\"") < 0 || cmdLine.toLowerCase().startsWith("deploycontract") + || cmdLine.toLowerCase().startsWith("deployconstantcontract") || cmdLine.toLowerCase().startsWith("triggercontract") || cmdLine.toLowerCase().startsWith("triggerconstantcontract") || cmdLine.toLowerCase().startsWith("updateaccountpermission")) { @@ -4078,12 +4171,16 @@ private void run() { deployContract(parameters); break; } + case "deployconstantcontract": { + deployConstantContract(parameters); + break; + } case "triggercontract": { - triggerContract(parameters, false); + triggerContract(parameters); break; } case "triggerconstantcontract": { - triggerContract(parameters, true); + triggerConstantContract(parameters); break; } case "getcontract": { diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 19ce7122a..201d6c090 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -1985,7 +1985,9 @@ public static TriggerSmartContract triggerCallContract( String tokenId) { TriggerSmartContract.Builder builder = TriggerSmartContract.newBuilder(); builder.setOwnerAddress(ByteString.copyFrom(address)); - builder.setContractAddress(ByteString.copyFrom(contractAddress)); + if (contractAddress != null) { + builder.setContractAddress(ByteString.copyFrom(contractAddress)); + } builder.setData(ByteString.copyFrom(data)); builder.setCallValue(callValue); if (tokenId != null && tokenId != "") { From 5f64660396db41c93d052d3dc3f86e8891f6efb6 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 26 Apr 2022 18:02:24 +0800 Subject: [PATCH 400/445] fix: do not print unknown fields --- src/main/java/org/tron/common/utils/JsonFormat.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/tron/common/utils/JsonFormat.java b/src/main/java/org/tron/common/utils/JsonFormat.java index d79bf70ee..049c5e7cb 100644 --- a/src/main/java/org/tron/common/utils/JsonFormat.java +++ b/src/main/java/org/tron/common/utils/JsonFormat.java @@ -103,10 +103,12 @@ protected static void print(Message message, JsonGenerator generator, boolean se generator.print(","); } } - if (message.getUnknownFields().asMap().size() > 0) { - generator.print(", "); - } - printUnknownFields(message.getUnknownFields(), generator, selfType); + + // do not print UnknownFields + // if (message.getUnknownFields().asMap().size() > 0) { + // generator.print(", "); + // } + // printUnknownFields(message.getUnknownFields(), generator, selfType); } /** From 2014cd8122051abf3fe5338126ee46b62181d240 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Tue, 26 Apr 2022 18:39:55 +0800 Subject: [PATCH 401/445] fix: add double quotation marks to make it a valid json --- src/main/java/org/tron/common/utils/JsonFormat.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/tron/common/utils/JsonFormat.java b/src/main/java/org/tron/common/utils/JsonFormat.java index 049c5e7cb..af1cb7fdc 100644 --- a/src/main/java/org/tron/common/utils/JsonFormat.java +++ b/src/main/java/org/tron/common/utils/JsonFormat.java @@ -322,7 +322,9 @@ protected static void printUnknownFields(UnknownFieldSet unknownFields, JsonGene } else { generator.print(", "); } + generator.print("\""); generator.print(String.format((Locale) null, "0x%08x", value)); + generator.print("\""); } for (long value : field.getFixed64List()) { if (firstValue) { @@ -330,7 +332,9 @@ protected static void printUnknownFields(UnknownFieldSet unknownFields, JsonGene } else { generator.print(", "); } + generator.print("\""); generator.print(String.format((Locale) null, "0x%016x", value)); + generator.print("\""); } for (ByteString value : field.getLengthDelimitedList()) { if (firstValue) { From 5fc12375993872ba095e780ad4760717c56a20df Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Wed, 27 Jul 2022 11:07:14 +0800 Subject: [PATCH 402/445] feature: add getheader method. --- src/main/java/org/tron/walletcli/Client.java | 47 +++++++++++++++++++ .../org/tron/walletcli/WalletApiWrapper.java | 4 ++ .../org/tron/walletserver/GrpcClient.java | 19 ++++++++ .../java/org/tron/walletserver/WalletApi.java | 4 ++ src/main/protos/api/api.proto | 8 ++++ 5 files changed, 82 insertions(+) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index f9b6bb190..f84d5c731 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -111,6 +111,7 @@ public class Client { "GetBalance", "GetBlock", "GetBlockById", + "GetBlockByIdOrNum", "GetBlockByLatestNum", "GetBlockByLimitNext", "GetBrokerage", @@ -240,6 +241,7 @@ public class Client { "GetBalance", "GetBlock", "GetBlockById", + "GetBlockByIdOrNum", "GetBlockByLatestNum", "GetBlockByLimitNext", "GetBrokerage", @@ -4357,6 +4359,10 @@ private void run() { getMarketOrderById(parameters); break; } + case "getblockbyidornum": { + getBlockByIdOrNum(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); @@ -4403,6 +4409,47 @@ private void getChainParameters() { } } + private void getBlockByIdOrNum(String[] parameters) { + String idOrNum = null; + boolean detail = false; + if (parameters == null || parameters.length == 0) { + // query current header + System.out.println("Get current header !!!"); + } else { + if (parameters.length == 1) { + String param = parameters[0]; + if ("help".equalsIgnoreCase(param)) { + // print help + System.out.println("1.get current header using the following command:"); + System.out.println("getBlockByIdOrNum"); + System.out.println("2. get current block command:"); + System.out.println("getBlockByIdOrNum true"); + System.out.println("3. get header by id or number with the following syntax:"); + System.out.println("getBlockByIdOrNum idOrNum"); + System.out.println("4. get block by id or number with the following syntax:"); + System.out.println("getBlockByIdOrNum idOrNum true"); + return; + } + if ("true".equalsIgnoreCase(param)) { + // query current block + detail = true; + } else { + // query header by id or num + idOrNum = parameters[0]; + } + } else { + idOrNum = parameters[0]; + detail = Boolean.parseBoolean(parameters[1]); + } + } + BlockExtention blockExtention = walletApiWrapper.getBlock(idOrNum, detail); + if (blockExtention == null) { + System.out.println("No header for idOrNum : " + idOrNum); + return; + } + System.out.println(Utils.printBlockExtention(blockExtention)); + } + public static void main(String[] args) { Client cli = new Client(); JCommander.newBuilder() diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index d359a754d..5778eceaa 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1852,4 +1852,8 @@ public Optional getMarketOrderById(byte[] order) { return WalletApi.getMarketOrderById(order); } + public BlockExtention getBlock(String idOrNum, boolean detail) { + return WalletApi.getBlock(idOrNum, detail); + } + } diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 361b94c82..2ea3ea9c0 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -1083,4 +1083,23 @@ public Optional getMarketOrderById(byte[] order) { return Optional.ofNullable(orderPair); } + public BlockExtention getBlock(String idOrNum, boolean detail) { + BlockExtention block; + + BlockReq.Builder builder = BlockReq.newBuilder(); + if (idOrNum != null && !idOrNum.isEmpty()) { + builder.setIdOrNum(idOrNum); + } + builder.setDetail(detail); + if (blockingStubSolidity != null) { + block = blockingStubSolidity.getBlock(builder.build()); + } else { + block = blockingStubFull.getBlock(builder.build()); + } + if (block == BlockExtention.getDefaultInstance()) { + block = null; // set to null + } + return block; + } + } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 201d6c090..a000cace5 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -2806,4 +2806,8 @@ public static Optional getMarketOrderById(byte[] order) { return rpcCli.getMarketOrderById(order); } + public static BlockExtention getBlock(String idOrNum, boolean detail) { + return rpcCli.getBlock(idOrNum, detail); + } + } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index f9dcc0f32..6cfa57986 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -778,6 +778,8 @@ service Wallet { rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { } // end for market + rpc GetBlock (BlockReq) returns (BlockExtention) { + } }; service WalletSolidity { @@ -960,6 +962,8 @@ service WalletSolidity { rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { } + rpc GetBlock (BlockReq) returns (BlockExtention) { + } }; service WalletExtension { @@ -1094,6 +1098,10 @@ message TimeMessage { int64 beginInMilliseconds = 1; int64 endInMilliseconds = 2; } +message BlockReq { + string id_or_num = 1; + bool detail = 2; +} message BlockLimit { int64 startNum = 1; int64 endNum = 2; From d488c9680fb998a609f13f9d696f1113fdb04503 Mon Sep 17 00:00:00 2001 From: Wenhua Zhang Date: Mon, 1 Aug 2022 15:35:47 +0800 Subject: [PATCH 403/445] feat: update install-googleapis.sh to make it work --- src/main/protos/install-googleapis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/protos/install-googleapis.sh b/src/main/protos/install-googleapis.sh index 0d44f6108..e1c1df801 100755 --- a/src/main/protos/install-googleapis.sh +++ b/src/main/protos/install-googleapis.sh @@ -5,7 +5,7 @@ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger go get -u github.com/golang/protobuf/protoc-gen-go -wget http://central.maven.org/maven2/com/google/api/grpc/googleapis-common-protos/0.0.3/googleapis-common-protos-0.0.3.jar +wget https://repo1.maven.org/maven2/com/google/api/grpc/googleapis-common-protos/0.0.3/googleapis-common-protos-0.0.3.jar jar xvf googleapis-common-protos-0.0.3.jar cp -r google/ $HOME/protobuf/include/ ls -l From 71fa3ae767ce460edd75968ebbdd27116e09abf7 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Fri, 9 Dec 2022 15:48:00 +0800 Subject: [PATCH 404/445] =?UTF-8?q?feat(freezeV2):=20merge=20feature=20?= =?UTF-8?q?=E2=80=98stake=202.0=E2=80=99=20into=20release=20v4.7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/tron/common/utils/Utils.java | 38 ++ src/main/java/org/tron/walletcli/Client.java | 391 ++++++++++++++++++ .../org/tron/walletcli/WalletApiWrapper.java | 85 ++++ .../org/tron/walletserver/GrpcClient.java | 119 ++++++ .../java/org/tron/walletserver/WalletApi.java | 173 ++++++++ src/main/protos/api/api.proto | 78 ++++ src/main/protos/core/Tron.proto | 32 ++ .../core/contract/balance_contract.proto | 31 ++ 8 files changed, 947 insertions(+) diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index 079f609d5..caa517565 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -41,6 +41,7 @@ import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.keystore.StringUtils; +import org.tron.protos.contract.BalanceContract; import org.tron.walletserver.WalletApi; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.Transaction; @@ -581,6 +582,43 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean contractJson = JSONObject .parseObject(JsonFormat.printToString(marketCancelOrderContract, selfType)); break; + // new freeze begin + case FreezeBalanceV2Contract: + BalanceContract.FreezeBalanceV2Contract freezeBalanceV2Contract = + contractParameter.unpack(BalanceContract.FreezeBalanceV2Contract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(freezeBalanceV2Contract, selfType)); + break; + case UnfreezeBalanceV2Contract: + BalanceContract.UnfreezeBalanceV2Contract unfreezeBalanceV2Contract = + contractParameter.unpack(BalanceContract.UnfreezeBalanceV2Contract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(unfreezeBalanceV2Contract, selfType)); + break; + case WithdrawExpireUnfreezeContract: + BalanceContract.WithdrawExpireUnfreezeContract withdrawExpireUnfreezeContract = + contractParameter.unpack(BalanceContract.WithdrawExpireUnfreezeContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(withdrawExpireUnfreezeContract, selfType)); + break; + case DelegateResourceContract: + BalanceContract.DelegateResourceContract delegateResourceContract = + contractParameter.unpack(BalanceContract.DelegateResourceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(delegateResourceContract, selfType)); + break; + case UnDelegateResourceContract: + BalanceContract.UnDelegateResourceContract unDelegateResourceContract = + contractParameter.unpack(BalanceContract.UnDelegateResourceContract.class); + contractJson = + JSONObject.parseObject( + JsonFormat.printToString(unDelegateResourceContract, selfType)); + break; + // new freeze end // todo add other contract default: } diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index f84d5c731..5b2b3a3e3 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -65,6 +65,7 @@ import org.tron.protos.Protocol.TransactionInfo; import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.walletserver.WalletApi; +import org.tron.protos.contract.Common.ResourceCode; @@ -96,6 +97,7 @@ public class Client { "ExchangeTransaction", "ExchangeWithdraw", "FreezeBalance", + "FreezeBalanceV2", "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", @@ -119,6 +121,12 @@ public class Client { "GetContract contractAddress", "GetContractInfo contractAddress", "GetDelegatedResource", + "GetDelegatedResourceV2", + "GetDelegatedResourceAccountIndex", + "GetDelegatedResourceAccountIndexV2", + "GetCanDelegatedMaxSize", + "GetAvailableUnfreezeCount", + "GetCanWithdrawUnfreezeAmount", "GetDiversifier", "GetExchange", "GetExpandedSpendingKey", @@ -192,6 +200,7 @@ public class Client { "TriggerContract contractAddress method args isHex fee_limit value", "UnfreezeAsset", "UnfreezeBalance", + "UnfreezeBalanceV2", "UpdateAccount", "UpdateAccountPermission", "UpdateAsset", @@ -201,6 +210,9 @@ public class Client { "UpdateWitness", "VoteWitness", "WithdrawBalance", + "WithdrawExpireUnfreeze", + "DelegateResource", + "UnDelegateResource", }; // note: this is sorted by alpha @@ -249,7 +261,12 @@ public class Client { "GetContract", "GetContractInfo", "GetDelegatedResource", + "GetDelegatedResourceV2", "GetDelegatedResourceAccountIndex", + "GetDelegatedResourceAccountIndexV2", + "GetCanDelegatedMaxSize", + "GetAvailableUnfreezeCount", + "GetCanWithdrawUnfreezeAmount", "GetDiversifier", "GetExchange", "GetExpandedSpendingKey", @@ -1243,6 +1260,45 @@ private void freezeBalance(String[] parameters) } } + private void freezeBalanceV2(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || !(parameters.length == 2 || parameters.length == 3)) { + System.out.println("Use freezeBalanceV2 command with below syntax: "); + System.out.println("freezeBalanceV2 [OwnerAddress] frozen_balance " + + "[ResourceCode:0 BANDWIDTH,1 ENERGY,2 TRON_POWER]"); + return; + } + + int index = 0; + boolean hasOwnerAddressPara = false; + byte[] ownerAddress = getAddressBytes(parameters[index]); + if (ownerAddress != null) { + index++; + hasOwnerAddressPara = true; + } + + long frozen_balance = Long.parseLong(parameters[index++]); + int resourceCode = 0; + + if ((!hasOwnerAddressPara && (parameters.length == 2)) || + (hasOwnerAddressPara && (parameters.length == 3))) { + try { + resourceCode = Integer.parseInt(parameters[index]); + } catch (NumberFormatException e) { + System.out.println("freezeBalanceV2 [ResourceCode:0 BANDWIDTH,1 ENERGY,2 TRON_POWER]"); + return; + } + } + + boolean result = walletApiWrapper.freezeBalanceV2(ownerAddress, frozen_balance + , resourceCode); + if (result) { + System.out.println("freezeBalanceV2 successful !!!"); + } else { + System.out.println("freezeBalanceV2 failed !!!"); + } + } + private void unfreezeBalance(String[] parameters) throws IOException, CipherException, CancelException { if (parameters == null || parameters.length < 1 || parameters.length > 3) { @@ -1281,6 +1337,174 @@ private void unfreezeBalance(String[] parameters) } } + private void unfreezeBalanceV2(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || !(parameters.length == 2 || parameters.length == 3)) { + System.out.println("Use unfreezeBalanceV2 command with below syntax: "); + System.out.println( + "unfreezeBalanceV2 [OwnerAddress] unfreezeBalance ResourceCode(0 BANDWIDTH,1 ENERGY,2 TRON_POWER)"); + return; + } + + int index = 0; + byte[] ownerAddress = null; + long unfreezeBalance = 0; + int resourceCode = 0; + if (parameters.length == 2) { + unfreezeBalance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + } else if (parameters.length == 3) { + ownerAddress = getAddressBytes(parameters[index]); + if (ownerAddress != null) { + index++; + unfreezeBalance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + } else { + System.out.println( + "unfreezeBalanceV2 OwnerAddress is invalid"); + return; + } + } + + boolean result = walletApiWrapper.unfreezeBalanceV2(ownerAddress, unfreezeBalance, resourceCode); + if (result) { + System.out.println("unfreezeBalanceV2 successful !!!"); + } else { + System.out.println("unfreezeBalanceV2 failed !!!"); + } + } + + private void withdrawExpireUnfreeze(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || !(parameters.length == 0 || parameters.length == 1)) { + System.out.println("Use withdrawExpireUnfreeze command with below syntax: "); + System.out.println( + "withdrawExpireUnfreeze OwnerAddress"); + return; + } + + byte[] ownerAddress = null; + if (parameters.length == 1) { + ownerAddress = getAddressBytes(parameters[0]); + if (ownerAddress == null) { + System.out.println( + "withdrawExpireUnfreeze OwnerAddress is invalid"); + return; + } + } + + boolean result = walletApiWrapper.withdrawExpireUnfreeze(ownerAddress); + if (result) { + System.out.println("withdrawExpireUnfreeze successful !!!"); + } else { + System.out.println("withdrawExpireUnfreeze failed !!!"); + } + } + + private void delegateResource(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || !(parameters.length == 3 || parameters.length == 4 || parameters.length == 5)) { + System.out.println("Use delegateResource command with below syntax: "); + System.out.println( + "delegateResource [OwnerAddress] balance ResourceCode(0 BANDWIDTH,1 ENERGY), ReceiverAddress [lock]"); + return; + } + + int index = 0; + byte[] ownerAddress = null; + long balance = 0; + int resourceCode = 0; + byte[] receiverAddress = null; + boolean lock = false; + + if (parameters.length == 3) { + balance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + receiverAddress = getAddressBytes(parameters[index++]); + if (receiverAddress == null) { + System.out.println( + "delegateResource receiverAddress is invalid"); + return; + } + } else if (parameters.length == 4 || parameters.length == 5) { + ownerAddress = getAddressBytes(parameters[index++]); + if (ownerAddress != null) { + balance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + receiverAddress = getAddressBytes(parameters[index++]); + if (receiverAddress == null) { + System.out.println( + "delegateResource receiverAddress is invalid"); + return; + } + if (parameters.length == 5) { + lock = Boolean.parseBoolean(parameters[index++]); + } + } if (ownerAddress == null) { + System.out.println( + "delegateResource ownerAddress is invalid"); + return; + } + } + + + boolean result = walletApiWrapper.delegateresource( + ownerAddress, balance, resourceCode, receiverAddress, lock); + if (result) { + System.out.println("delegateResource successful !!!"); + } else { + System.out.println("delegateResource failed !!!"); + } + } + + private void unDelegateResource(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters == null || !(parameters.length == 3 || parameters.length == 4)) { + System.out.println("Use unDelegateResource command with below syntax: "); + System.out.println( + "unDelegateResource [OwnerAddress] balance ResourceCode(0 BANDWIDTH,1 ENERGY), ReceiverAddress"); + return; + } + + int index = 0; + byte[] ownerAddress = null; + long balance = 0; + int resourceCode = 0; + byte[] receiverAddress = null; + if (parameters.length == 3) { + balance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + receiverAddress = getAddressBytes(parameters[index++]); + if (receiverAddress == null) { + System.out.println( + "unDelegateResource receiverAddress is invalid"); + return; + } + } else if (parameters.length == 4) { + ownerAddress = getAddressBytes(parameters[index++]); + if (ownerAddress != null) { + balance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + receiverAddress = getAddressBytes(parameters[index++]); + if (receiverAddress == null) { + System.out.println( + "unDelegateResource receiverAddress is invalid"); + return; + } + } if (ownerAddress == null) { + System.out.println( + "unDelegateResource ownerAddress is invalid"); + return; + } + } + boolean result = walletApiWrapper.undelegateresource(ownerAddress, balance, resourceCode, receiverAddress); + if (result) { + System.out.println("unDelegateResource successful !!!"); + } else { + System.out.println("unDelegateResource failed !!!"); + } + } + private void unfreezeAsset(String[] parameters) throws IOException, CipherException, CancelException { System.out.println("Use UnfreezeAsset command like: "); @@ -1435,7 +1659,130 @@ private void getDelegatedResource(String[] parameters) { } } + private void getDelegatedResourceAccountIndex(String[] parameters) { + if (parameters == null || parameters.length != 1) { + System.out.println("Using getDelegatedResourceAccountIndex command needs 1 parameters like: "); + System.out.println("getDelegatedResourceAccountIndex ownerAddress"); + return; + } + String ownerAddress = parameters[0]; + Optional result = WalletApi.getDelegatedResourceAccountIndex(ownerAddress); + if (result.isPresent()) { + DelegatedResourceAccountIndex delegatedResourceAccountIndex = result.get(); + System.out.println(Utils.formatMessageString(delegatedResourceAccountIndex)); + } else { + System.out.println("GetDelegatedResourceAccountIndex failed !!!"); + } + } + + private void getDelegatedResourceV2(String[] parameters) { + if (parameters == null || parameters.length != 2) { + System.out.println("Using getdelegatedresourcev2 command needs 2 parameters like: "); + System.out.println("getdelegatedresourcev2 fromAddress toAddress"); + return; + } + String fromAddress = parameters[0]; + String toAddress = parameters[1]; + Optional result = WalletApi.getDelegatedResourceV2(fromAddress, toAddress); + if (result.isPresent()) { + DelegatedResourceList delegatedResourceList = result.get(); + System.out.println(Utils.formatMessageString(delegatedResourceList)); + } else { + System.out.println("GetDelegatedResourceV2 failed !!!"); + } + } + + private void getDelegatedResourceAccountIndexV2(String[] parameters) { + if (parameters == null || parameters.length != 1) { + System.out.println("Using getDelegatedResourceAccountIndexV2 command needs 1 parameters like: "); + System.out.println("getdelegatedresourceaccountindexv2 ownerAddress"); + return; + } + String ownerAddress = parameters[0]; + Optional result = WalletApi.getDelegatedResourceAccountIndexV2(ownerAddress); + if (result.isPresent()) { + DelegatedResourceAccountIndex delegatedResourceAccountIndex = result.get(); + System.out.println(Utils.formatMessageString(delegatedResourceAccountIndex)); + } else { + System.out.println("GetDelegatedResourceAccountIndexV2 failed !!!"); + } + } + + private void getCanWithdrawUnfreezeAmount(String[] parameters) throws CipherException, IOException, CancelException { + if (parameters == null || !(parameters.length == 1 || parameters.length == 2)) { + System.out.println("Using getCanWithdrawUnfreezeAmount command needs 2 parameters like: "); + System.out.println("getcanwithdrawunfreezeamount [ownerAddress] timestamp"); + return; + } + int index = 0; + long timestamp = 0; + byte[] ownerAddress = getAddressBytes(parameters[index]); + if (ownerAddress != null) { + index++; + } + timestamp = Long.parseLong(parameters[index++]); + if (timestamp < 0) { + System.out.println("Invalid param, timestamp >= 0"); + return; + } + + Optional result = walletApiWrapper. + getCanWithdrawUnfreezeAmount(ownerAddress, timestamp); + if (result.isPresent()) { + CanWithdrawUnfreezeAmountResponseMessage canWithdrawUnfreezeAmountResponseMessage = result.get(); + System.out.println(Utils.formatMessageString(canWithdrawUnfreezeAmountResponseMessage)); + } else { + System.out.println("GetCanWithdrawUnfreezeAmount failed !!!"); + } + } + + private void getCanDelegatedMaxSize(String[] parameters) throws CipherException, IOException, CancelException { + if (parameters == null || !(parameters.length == 1 || parameters.length == 2)) { + System.out.println("Using getcandelegatedmaxsize command needs 2 parameters like: "); + System.out.println("getcandelegatedmaxsize [ownerAddress] type"); + return; + } + int index = 0; + int type = 0; + byte[] ownerAddress = getAddressBytes(parameters[index]); + if (ownerAddress != null) { + index++; + } + type = Integer.parseInt(parameters[index++]); + if (ResourceCode.BANDWIDTH.ordinal() != type && ResourceCode.ENERGY.ordinal() != type) { + System.out.println("getcandelegatedmaxsize param type must be: 0 or 1"); + return; + } + + Optional result = walletApiWrapper.getCanDelegatedMaxSize(ownerAddress, type); + if (result.isPresent()) { + CanDelegatedMaxSizeResponseMessage canDelegatedMaxSizeResponseMessage = result.get(); + System.out.println(Utils.formatMessageString(canDelegatedMaxSizeResponseMessage)); + } else { + System.out.println("GetCanDelegatedMaxSize failed !!!"); + } + } + + private void getAvailableUnfreezeCount(String[] parameters) throws CipherException, IOException, CancelException { + if (parameters == null || !(parameters.length == 0 || parameters.length == 1)) { + System.out.println("Using getavailableunfreezecount command needs 1 parameters like: "); + System.out.println("getavailableunfreezecount [owner_address] "); + return; + } + int index = 0; + byte[] ownerAddress = null; + if (parameters.length == 1) { + ownerAddress = getAddressBytes(parameters[index]); + } + Optional result = walletApiWrapper.getAvailableUnfreezeCount(ownerAddress); + if (result.isPresent()) { + GetAvailableUnfreezeCountResponseMessage getAvailableUnfreezeCountResponseMessage = result.get(); + System.out.println(Utils.formatMessageString(getAvailableUnfreezeCountResponseMessage)); + } else { + System.out.println("GetAvailableUnfreezeCount failed !!!"); + } + } private void exchangeCreate(String[] parameters) throws IOException, CipherException, CancelException { @@ -4001,10 +4348,30 @@ private void run() { freezeBalance(parameters); break; } + case "freezebalancev2": { + freezeBalanceV2(parameters); + break; + } case "unfreezebalance": { unfreezeBalance(parameters); break; } + case "unfreezebalancev2": { + unfreezeBalanceV2(parameters); + break; + } + case "withdrawexpireunfreeze": { + withdrawExpireUnfreeze(parameters); + break; + } + case "delegateresource": { + delegateResource(parameters); + break; + } + case "undelegateresource": { + unDelegateResource(parameters); + break; + } case "withdrawbalance": { withdrawBalance(parameters); break; @@ -4041,6 +4408,30 @@ private void run() { getDelegatedResource(parameters); break; } + case "getdelegatedresourceaccountindex": { + getDelegatedResourceAccountIndex(parameters); + break; + } + case "getdelegatedresourcev2": { + getDelegatedResourceV2(parameters); + break; + } + case "getdelegatedresourceaccountindexv2": { + getDelegatedResourceAccountIndexV2(parameters); + break; + } + case "getcandelegatedmaxsize": { + getCanDelegatedMaxSize(parameters); + break; + } + case "getavailableunfreezecount": { + getAvailableUnfreezeCount(parameters); + break; + } + case "getcanwithdrawunfreezeamount": { + getCanWithdrawUnfreezeAmount(parameters); + break; + } case "exchangecreate": { exchangeCreate(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 5778eceaa..d68f666d6 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -482,6 +482,17 @@ public boolean freezeBalance(byte[] ownerAddress, long frozen_balance, long froz receiverAddress); } + public boolean freezeBalanceV2(byte[] ownerAddress, long frozen_balance, + int resourceCode) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: freezeBalanceV2 failed, Please login first !!"); + return false; + } + + return wallet.freezeBalanceV2(ownerAddress, frozen_balance, resourceCode); + } + public boolean buyStorage(byte[] ownerAddress, long quantity) throws CipherException, IOException, CancelException { if (wallet == null || !wallet.isLoginState()) { @@ -523,6 +534,80 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec return wallet.unfreezeBalance(ownerAddress, resourceCode, receiverAddress); } + public boolean unfreezeBalanceV2(byte[] ownerAddress, long unfreezeBalance + , int resourceCode) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: unfreezeBalanceV2 failed, Please login first !!"); + return false; + } + + return wallet.unfreezeBalanceV2(ownerAddress, unfreezeBalance, resourceCode); + } + + public boolean withdrawExpireUnfreeze(byte[] ownerAddress) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: withdrawExpireUnfreeze failed, Please login first !!"); + return false; + } + + return wallet.withdrawExpireUnfreeze(ownerAddress); + } + + public boolean delegateresource(byte[] ownerAddress, long balance + , int resourceCode, byte[] receiverAddress, boolean lock) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: delegateresource failed, Please login first !!"); + return false; + } + + return wallet.delegateResource(ownerAddress, balance + , resourceCode, receiverAddress, lock); + } + + public boolean undelegateresource(byte[] ownerAddress, long balance + , int resourceCode, byte[] receiverAddress) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: undelegateresource failed, Please login first !!"); + return false; + } + + return wallet.unDelegateResource(ownerAddress, balance, resourceCode, receiverAddress); + } + + public Optional getCanWithdrawUnfreezeAmount( + byte[] ownerAddress, long timestamp) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: getCanWithdrawUnfreezeAmount failed, Please login first !!"); + return Optional.empty(); + } + + return wallet.getCanWithdrawUnfreezeAmount(ownerAddress, timestamp); + } + + public Optional getCanDelegatedMaxSize(byte[] ownerAddress, int type) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: getCanDelegatedMaxSize failed, Please login first !!"); + return Optional.empty(); + } + + return wallet.getCanDelegatedMaxSize(ownerAddress, type); + } + + public Optional getAvailableUnfreezeCount(byte[] ownerAddress) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: getAvailableUnfreezeCount failed, Please login first !!"); + return Optional.empty(); + } + + return wallet.getAvailableUnfreezeCount(ownerAddress); + } public boolean unfreezeAsset(byte[] ownerAddress) throws CipherException, IOException, CancelException { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 2ea3ea9c0..a83a9a7d0 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -43,6 +43,7 @@ import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract; import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; @@ -263,6 +264,10 @@ public TransactionExtention createTransaction2(FreezeBalanceContract contract) { return blockingStubFull.freezeBalance2(contract); } + public TransactionExtention createTransaction2(BalanceContract.FreezeBalanceV2Contract contract) { + return blockingStubFull.freezeBalanceV2(contract); + } + public Transaction createTransaction(WithdrawBalanceContract contract) { return blockingStubFull.withdrawBalance(contract); } @@ -279,6 +284,22 @@ public TransactionExtention createTransaction2(UnfreezeBalanceContract contract) return blockingStubFull.unfreezeBalance2(contract); } + public TransactionExtention createTransactionV2(BalanceContract.UnfreezeBalanceV2Contract contract) { + return blockingStubFull.unfreezeBalanceV2(contract); + } + + public TransactionExtention createTransactionV2(BalanceContract.WithdrawExpireUnfreezeContract contract) { + return blockingStubFull.withdrawExpireUnfreeze(contract); + } + + public TransactionExtention createTransactionV2(BalanceContract.DelegateResourceContract contract) { + return blockingStubFull.delegateResource(contract); + } + + public TransactionExtention createTransactionV2(BalanceContract.UnDelegateResourceContract contract) { + return blockingStubFull.unDelegateResource(contract); + } + public Transaction createTransaction(UnfreezeAssetContract contract) { return blockingStubFull.unfreezeAsset(contract); } @@ -360,6 +381,104 @@ public Optional getDelegatedResource(String fromAddress, return Optional.ofNullable(delegatedResource); } + public Optional getDelegatedResourceAccountIndex(String ownerAddress) { + ByteString ownerAddressBS = ByteString.copyFrom( + Objects.requireNonNull(WalletApi.decodeFromBase58Check(ownerAddress))); + + BytesMessage request = BytesMessage.newBuilder() + .setValue(ownerAddressBS) + .build(); + DelegatedResourceAccountIndex delegatedResourceAccountIndex; + if (blockingStubSolidity != null) { + delegatedResourceAccountIndex = blockingStubSolidity.getDelegatedResourceAccountIndex(request); + } else { + delegatedResourceAccountIndex = blockingStubFull.getDelegatedResourceAccountIndex(request); + } + return Optional.ofNullable(delegatedResourceAccountIndex); + } + + public Optional getDelegatedResourceV2(String fromAddress, + String toAddress) { + ByteString fromAddressBS = ByteString.copyFrom( + Objects.requireNonNull(WalletApi.decodeFromBase58Check(fromAddress))); + ByteString toAddressBS = ByteString.copyFrom( + Objects.requireNonNull(WalletApi.decodeFromBase58Check(toAddress))); + + DelegatedResourceMessage request = DelegatedResourceMessage.newBuilder() + .setFromAddress(fromAddressBS) + .setToAddress(toAddressBS) + .build(); + DelegatedResourceList delegatedResource; + if (blockingStubSolidity != null) { + delegatedResource = blockingStubSolidity.getDelegatedResourceV2(request); + } else { + delegatedResource = blockingStubFull.getDelegatedResourceV2(request); + } + return Optional.ofNullable(delegatedResource); + } + + public Optional getDelegatedResourceAccountIndexV2(String ownerAddress) { + ByteString ownerAddressBS = ByteString.copyFrom( + Objects.requireNonNull(WalletApi.decodeFromBase58Check(ownerAddress))); + + BytesMessage request = BytesMessage.newBuilder() + .setValue(ownerAddressBS) + .build(); + DelegatedResourceAccountIndex delegatedResourceAccountIndex; + if (blockingStubSolidity != null) { + delegatedResourceAccountIndex = blockingStubSolidity.getDelegatedResourceAccountIndexV2(request); + } else { + delegatedResourceAccountIndex = blockingStubFull.getDelegatedResourceAccountIndexV2(request); + } + return Optional.ofNullable(delegatedResourceAccountIndex); + } + + public Optional getCanDelegatedMaxSize( + byte[] ownerAddress, int type) { + ByteString ownerAddressBS = ByteString.copyFrom(ownerAddress); + CanDelegatedMaxSizeRequestMessage request = CanDelegatedMaxSizeRequestMessage.newBuilder() + .setOwnerAddress(ownerAddressBS) + .setType(type) + .build(); + CanDelegatedMaxSizeResponseMessage canDelegatedMaxSizeResponseMessage; + if (blockingStubSolidity != null) { + canDelegatedMaxSizeResponseMessage = blockingStubSolidity.getCanDelegatedMaxSize(request); + } else { + canDelegatedMaxSizeResponseMessage = blockingStubFull.getCanDelegatedMaxSize(request); + } + return Optional.ofNullable(canDelegatedMaxSizeResponseMessage); + } + + public Optional getCanWithdrawUnfreezeAmount( + byte[] ownerAddress, long timestamp) { + ByteString ownerAddressBS = ByteString.copyFrom(ownerAddress); + CanWithdrawUnfreezeAmountRequestMessage request = CanWithdrawUnfreezeAmountRequestMessage.newBuilder() + .setOwnerAddress(ownerAddressBS) + .setTimestamp(timestamp) + .build(); + CanWithdrawUnfreezeAmountResponseMessage canDelegatedMaxSizeResponseMessage; + if (blockingStubSolidity != null) { + canDelegatedMaxSizeResponseMessage = blockingStubSolidity.getCanWithdrawUnfreezeAmount(request); + } else { + canDelegatedMaxSizeResponseMessage = blockingStubFull.getCanWithdrawUnfreezeAmount(request); + } + return Optional.ofNullable(canDelegatedMaxSizeResponseMessage); + } + + public Optional getAvailableUnfreezeCount( + byte[] ownerAddress) { + ByteString ownerAddressBS = ByteString.copyFrom(ownerAddress); + GetAvailableUnfreezeCountRequestMessage request = GetAvailableUnfreezeCountRequestMessage.newBuilder() + .setOwnerAddress(ownerAddressBS) + .build(); + GetAvailableUnfreezeCountResponseMessage getAvailableUnfreezeCountResponseMessage; + if (blockingStubSolidity != null) { + getAvailableUnfreezeCountResponseMessage = blockingStubSolidity.getAvailableUnfreezeCount(request); + } else { + getAvailableUnfreezeCountResponseMessage = blockingStubFull.getAvailableUnfreezeCount(request); + } + return Optional.ofNullable(getAvailableUnfreezeCountResponseMessage); + } public Optional listExchanges() { ExchangeList exchangeList; diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index a000cace5..a11bdcab5 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -97,6 +97,7 @@ import org.tron.keystore.Wallet; import org.tron.keystore.WalletFile; import org.tron.keystore.WalletUtils; +import org.tron.protos.Protocol; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.ChainParameters; @@ -123,6 +124,7 @@ import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract; import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; @@ -1295,6 +1297,20 @@ public boolean freezeBalance( } } + public boolean freezeBalanceV2( + byte[] ownerAddress, + long frozen_balance, + int resourceCode) + throws CipherException, IOException, CancelException { + BalanceContract.FreezeBalanceV2Contract contract = + createFreezeBalanceContractV2( + ownerAddress, frozen_balance, resourceCode); + + TransactionExtention transactionExtention = rpcCli.createTransaction2(contract); + return processTransactionExtention(transactionExtention); + } + + public boolean buyStorage(byte[] ownerAddress, long quantity) throws CipherException, IOException, CancelException { BuyStorageContract contract = createBuyStorageContract(ownerAddress, quantity); @@ -1342,6 +1358,23 @@ private FreezeBalanceContract createFreezeBalanceContract( return builder.build(); } + private BalanceContract.FreezeBalanceV2Contract createFreezeBalanceContractV2( + byte[] address, + long frozen_balance, + int resourceCode) { + if (address == null) { + address = getAddress(); + } + + BalanceContract.FreezeBalanceV2Contract.Builder builder = BalanceContract.FreezeBalanceV2Contract.newBuilder(); + ByteString byteAddress = ByteString.copyFrom(address); + builder.setOwnerAddress(byteAddress) + .setFrozenBalance(frozen_balance) + .setResourceValue(resourceCode); + + return builder.build(); + } + private BuyStorageContract createBuyStorageContract(byte[] address, long quantity) { if (address == null) { address = getAddress(); @@ -1391,6 +1424,41 @@ public boolean unfreezeBalance(byte[] ownerAddress, int resourceCode, byte[] rec } } + public boolean unfreezeBalanceV2(byte[] ownerAddress, long unfreezeBalance + , int resourceCode) + throws CipherException, IOException, CancelException { + BalanceContract.UnfreezeBalanceV2Contract contract = + createUnfreezeBalanceContractV2(ownerAddress, unfreezeBalance, resourceCode); + TransactionExtention transactionExtention = rpcCli.createTransactionV2(contract); + return processTransactionExtention(transactionExtention); + } + + public boolean withdrawExpireUnfreeze(byte[] ownerAddress) + throws CipherException, IOException, CancelException { + BalanceContract.WithdrawExpireUnfreezeContract contract = + createWithdrawExpireUnfreezeContract(ownerAddress); + TransactionExtention transactionExtention = rpcCli.createTransactionV2(contract); + return processTransactionExtention(transactionExtention); + } + + public boolean delegateResource(byte[] ownerAddress, long balance + ,int resourceCode, byte[] receiverAddress, boolean lock) + throws CipherException, IOException, CancelException { + BalanceContract.DelegateResourceContract contract = createDelegateResourceContract( + ownerAddress, balance, resourceCode, receiverAddress, lock); + TransactionExtention transactionExtention = rpcCli.createTransactionV2(contract); + return processTransactionExtention(transactionExtention); + } + + public boolean unDelegateResource(byte[] ownerAddress, long balance + ,int resourceCode, byte[] receiverAddress) + throws CipherException, IOException, CancelException { + BalanceContract.UnDelegateResourceContract contract = + createUnDelegateResourceContract(ownerAddress, balance, resourceCode, receiverAddress); + TransactionExtention transactionExtention = rpcCli.createTransactionV2(contract); + return processTransactionExtention(transactionExtention); + } + private UnfreezeBalanceContract createUnfreezeBalanceContract( byte[] address, int resourceCode, byte[] receiverAddress) { if (address == null) { @@ -1411,6 +1479,72 @@ private UnfreezeBalanceContract createUnfreezeBalanceContract( return builder.build(); } + private BalanceContract.UnfreezeBalanceV2Contract createUnfreezeBalanceContractV2( + byte[] address, long unfreezeBalance, int resourceCode) { + if (address == null) { + address = getAddress(); + } + + BalanceContract.UnfreezeBalanceV2Contract.Builder builder = + BalanceContract.UnfreezeBalanceV2Contract.newBuilder(); + ByteString byteAddreess = ByteString.copyFrom(address); + builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode).setUnfreezeBalance(unfreezeBalance); + + return builder.build(); + } + + private BalanceContract.WithdrawExpireUnfreezeContract createWithdrawExpireUnfreezeContract(byte[] address) { + if (address == null) { + address = getAddress(); + } + + BalanceContract.WithdrawExpireUnfreezeContract.Builder builder = + BalanceContract.WithdrawExpireUnfreezeContract.newBuilder(); + ByteString byteAddreess = ByteString.copyFrom(address); + builder.setOwnerAddress(byteAddreess); + + return builder.build(); + } + + private BalanceContract.DelegateResourceContract createDelegateResourceContract( + byte[] address, long balance + ,int resourceCode, byte[] receiver, boolean lock) { + if (address == null) { + address = getAddress(); + } + + BalanceContract.DelegateResourceContract.Builder builder = + BalanceContract.DelegateResourceContract.newBuilder(); + ByteString byteAddreess = ByteString.copyFrom(address); + ByteString byteReceiverAddreess = ByteString.copyFrom(receiver); + builder.setOwnerAddress(byteAddreess) + .setResourceValue(resourceCode) + .setBalance(balance) + .setReceiverAddress(byteReceiverAddreess) + .setLock(lock); + + return builder.build(); + } + + private BalanceContract.UnDelegateResourceContract createUnDelegateResourceContract( + byte[] address, long balance + ,int resourceCode, byte[] receiver) { + if (address == null) { + address = getAddress(); + } + + BalanceContract.UnDelegateResourceContract.Builder builder = + BalanceContract.UnDelegateResourceContract.newBuilder(); + ByteString byteAddreess = ByteString.copyFrom(address); + ByteString byteReceiverAddreess = ByteString.copyFrom(receiver); + builder.setOwnerAddress(byteAddreess) + .setResourceValue(resourceCode) + .setBalance(balance) + .setReceiverAddress(byteReceiverAddreess); + + return builder.build(); + } + public boolean unfreezeAsset(byte[] ownerAddress) throws CipherException, IOException, CancelException { UnfreezeAssetContract contract = createUnfreezeAssetContract(ownerAddress); @@ -1508,6 +1642,45 @@ public static Optional getDelegatedResource( return rpcCli.getDelegatedResource(fromAddress, toAddress); } + public static Optional getDelegatedResourceAccountIndex( + String ownerAddress) { + return rpcCli.getDelegatedResourceAccountIndex(ownerAddress); + } + + public static Optional getDelegatedResourceV2( + String fromAddress, String toAddress) { + return rpcCli.getDelegatedResourceV2(fromAddress, toAddress); + } + + public static Optional getDelegatedResourceAccountIndexV2( + String ownerAddress) { + return rpcCli.getDelegatedResourceAccountIndexV2(ownerAddress); + } + + public Optional getCanWithdrawUnfreezeAmount( + byte[] ownerAddress, long timestamp) { + if (ownerAddress == null) { + ownerAddress = this.getAddress(); + } + return rpcCli.getCanWithdrawUnfreezeAmount(ownerAddress, timestamp); + } + + public Optional getCanDelegatedMaxSize( + byte[] ownerAddress, int type) { + if (ownerAddress == null) { + ownerAddress = this.getAddress(); + } + return rpcCli.getCanDelegatedMaxSize(ownerAddress, type); + } + + public Optional getAvailableUnfreezeCount( + byte[] ownerAddress) { + if (ownerAddress == null) { + ownerAddress = this.getAddress(); + } + return rpcCli.getAvailableUnfreezeCount(ownerAddress); + } + public static Optional listExchanges() { return rpcCli.listExchanges(); } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6cfa57986..56c714c8d 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -226,6 +226,10 @@ service Wallet { //Use this function instead of FreezeBalance. rpc FreezeBalance2 (FreezeBalanceContract) returns (TransactionExtention) { } + //Use this function when FreezeBalanceV2. + rpc FreezeBalanceV2 (FreezeBalanceV2Contract) returns (TransactionExtention) { + } + //Please use UnfreezeBalance2 instead of this function. rpc UnfreezeBalance (UnfreezeBalanceContract) returns (Transaction) { option (google.api.http) = { @@ -239,6 +243,20 @@ service Wallet { //Use this function instead of UnfreezeBalance. rpc UnfreezeBalance2 (UnfreezeBalanceContract) returns (TransactionExtention) { } + //Use this function when UnfreezeBalanceV2. + rpc UnfreezeBalanceV2 (UnfreezeBalanceV2Contract) returns (TransactionExtention) { + } + + + rpc WithdrawExpireUnfreeze (WithdrawExpireUnfreezeContract) returns (TransactionExtention) { + } + + rpc DelegateResource (DelegateResourceContract) returns (TransactionExtention) { + } + + rpc UnDelegateResource (UnDelegateResourceContract) returns (TransactionExtention) { + } + //Please use UnfreezeAsset2 instead of this function. rpc UnfreezeAsset (UnfreezeAssetContract) returns (Transaction) { option (google.api.http) = { @@ -459,6 +477,26 @@ service Wallet { rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { }; + rpc GetDelegatedResourceV2 (DelegatedResourceMessage) returns (DelegatedResourceList) { + }; + + rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + + rpc GetDelegatedResourceAccountIndexV2 (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + + rpc GetCanDelegatedMaxSize (CanDelegatedMaxSizeRequestMessage) returns (CanDelegatedMaxSizeResponseMessage) { + }; + + rpc GetAvailableUnfreezeCount (GetAvailableUnfreezeCountRequestMessage) + returns (GetAvailableUnfreezeCountResponseMessage) { + }; + + rpc GetCanWithdrawUnfreezeAmount (CanWithdrawUnfreezeAmountRequestMessage) + returns (CanWithdrawUnfreezeAmountResponseMessage) { + } + rpc ListProposals (EmptyMessage) returns (ProposalList) { option (google.api.http) = { post: "/wallet/listproposals" @@ -871,9 +909,26 @@ service WalletSolidity { rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { }; + rpc GetDelegatedResourceV2 (DelegatedResourceMessage) returns (DelegatedResourceList) { + }; + rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { }; + rpc GetDelegatedResourceAccountIndexV2 (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + + rpc GetCanDelegatedMaxSize (CanDelegatedMaxSizeRequestMessage) returns (CanDelegatedMaxSizeResponseMessage) { + }; + + rpc GetAvailableUnfreezeCount (GetAvailableUnfreezeCountRequestMessage) + returns (GetAvailableUnfreezeCountResponseMessage) { + }; + + rpc GetCanWithdrawUnfreezeAmount (CanWithdrawUnfreezeAmountRequestMessage) + returns (CanWithdrawUnfreezeAmountResponseMessage) { + } + rpc GetExchangeById (BytesMessage) returns (Exchange) { }; @@ -1070,6 +1125,29 @@ message DelegatedResourceList { repeated DelegatedResource delegatedResource = 1; } +message GetAvailableUnfreezeCountRequestMessage { + bytes owner_address = 1; +} +message GetAvailableUnfreezeCountResponseMessage { + int64 count = 1; +} + +message CanDelegatedMaxSizeRequestMessage { + int32 type = 1; + bytes owner_address = 2; +} +message CanDelegatedMaxSizeResponseMessage { + int64 max_size = 1; +} + +message CanWithdrawUnfreezeAmountRequestMessage { + bytes owner_address = 1; + int64 timestamp = 2; +} +message CanWithdrawUnfreezeAmountResponseMessage { + int64 amount = 1; +} + // Gossip node list message NodeList { repeated Node nodes = 1; diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index fcf0e8758..c9c230864 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -2,6 +2,7 @@ syntax = "proto3"; import "google/protobuf/any.proto"; import "core/Discover.proto"; +import "core/contract/common.proto"; package protocol; @@ -128,6 +129,8 @@ message Account { // the identity of this account, case insensitive bytes account_id = 23; + int64 net_window_size = 24; + message AccountResource { // energy resource, get from frozen int64 energy_usage = 1; @@ -145,12 +148,31 @@ message Account { int64 storage_usage = 7; int64 latest_exchange_storage_time = 8; + int64 energy_window_size = 9; + + int64 delegated_frozenV2_balance_for_energy = 10; + int64 acquired_delegated_frozenV2_balance_for_energy = 11; } AccountResource account_resource = 26; bytes codeHash = 30; Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; + + message FreezeV2 { + ResourceCode type = 1; + int64 amount = 2; + } + message UnFreezeV2 { + ResourceCode type = 1; + int64 unfreeze_amount = 3; + int64 unfreeze_expire_time = 4; + } + repeated FreezeV2 frozenV2 = 34; + repeated UnFreezeV2 unfrozenV2 = 35; + + int64 delegated_frozenV2_balance_for_bandwidth = 36; + int64 acquired_delegated_frozenV2_balance_for_bandwidth = 37; } @@ -284,6 +306,11 @@ message Transaction { ShieldedTransferContract = 51; MarketSellAssetContract = 52; MarketCancelOrderContract = 53; + FreezeBalanceV2Contract = 54; + UnfreezeBalanceV2Contract = 55; + WithdrawExpireUnfreezeContract = 56; + DelegateResourceContract = 57; + UnDelegateResourceContract = 58; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -326,6 +353,8 @@ message Transaction { int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; int64 shielded_transaction_fee = 22; + + int64 withdraw_expire_amount = 27; } message raw { @@ -384,6 +413,8 @@ message TransactionInfo { bytes orderId = 25; repeated MarketOrderDetail orderDetails = 26; int64 packingFee = 27; + + int64 withdraw_expire_amount = 28; } message TransactionRet { @@ -547,6 +578,7 @@ message DelegatedResourceAccountIndex { bytes account = 1; repeated bytes fromAccounts = 2; repeated bytes toAccounts = 3; + int64 timestamp = 4; } message NodeInfo { diff --git a/src/main/protos/core/contract/balance_contract.proto b/src/main/protos/core/contract/balance_contract.proto index 82a93d6a3..54dcc4c40 100644 --- a/src/main/protos/core/contract/balance_contract.proto +++ b/src/main/protos/core/contract/balance_contract.proto @@ -81,3 +81,34 @@ message AccountBalanceResponse { int64 balance = 1; BlockBalanceTrace.BlockIdentifier block_identifier = 2; } + +message FreezeBalanceV2Contract { + bytes owner_address = 1; + int64 frozen_balance = 2; + ResourceCode resource = 3; +} + +message UnfreezeBalanceV2Contract { + bytes owner_address = 1; + int64 unfreeze_balance = 2; + ResourceCode resource = 3; +} + +message WithdrawExpireUnfreezeContract { + bytes owner_address = 1; +} + +message DelegateResourceContract { + bytes owner_address = 1; + ResourceCode resource = 2; + int64 balance = 3; + bytes receiver_address = 4; + bool lock = 5; +} + +message UnDelegateResourceContract { + bytes owner_address = 1; + ResourceCode resource = 2; + int64 balance = 3; + bytes receiver_address = 4; +} \ No newline at end of file From 214e5d478bfe6db7f3d2c476eff36d4d4a78c549 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Thu, 15 Dec 2022 14:01:54 +0800 Subject: [PATCH 405/445] update README.md file --- README.md | 350 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 349 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e3dff8e05..c93b8a081 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,10 @@ For more information on a specific command, just type the command on terminal wh | [ShowShieldedTRC20AddressInfo](#How-to-transfer-shielded-TRC20-token) | [TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | | [UnfreezeAsset](#How-to-issue-TRC10-tokens) | [UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#How-to-issue-TRC10-tokens) | | [UpdateBrokerage](#Brokerage) | [UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | -| [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | +| [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | +| [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | +| [WithdrawExpireUnfreeze](#How-to-freezev2) | [GetDelegatedResourceV2](#How-to-freezev2) | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | +| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) | Type any one of the listed commands, to display how-to tips. @@ -1087,6 +1090,351 @@ getDelegatedResource fromAddress toAddress getDelegatedResourceAccountIndex address > get the information that address is delegated to other account resources + +## How to freezev2 + +### freezev2/unfreezev2 resource + + > freezeBalanceV2 [OwnerAddress] frozen_balance [ResourceCode:0 BANDWIDTH,1 ENERGY,2 TRON_POWER] + +OwnerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +frozen_balance +> The amount of frozen, the unit is the smallest unit (Sun), the minimum is 1000000sun. + +ResourceCode +> 0 BANDWIDTH;1 ENERGY + +Example: +```console +wallet> FreezeBalanceV2 TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 1000000000000000 0 +txid is 82244829971b4235d98a9f09ba67ddb09690ac2f879ad93e09ba3ec1ab29177d +wallet> GetTransactionById 82244829971b4235d98a9f09ba67ddb09690ac2f879ad93e09ba3ec1ab29177d +{ + "ret":[ + { + "contractRet":"SUCCESS" + } + ], + "signature":[ + "4faa3772fa3d3e4792e8126cafed2dc2c5c069cd09c29532f0119bc982bf356004772e16fad86e401f5818c35b96d214d693efab06997ca2f07044d4494f12fd01" + ], + "txID":"82244829971b4235d98a9f09ba67ddb09690ac2f879ad93e09ba3ec1ab29177d", + "raw_data":{ + "contract":[ + { + "parameter":{ + "value":{ + "frozen_balance":1000000000000000, + "owner_address":"4159e3741a68ec3e1ebba80ad809d5ccd31674236e" + }, + "type_url":"type.googleapis.com/protocol.FreezeBalanceV2Contract" + }, + "type":"FreezeBalanceV2Contract" + } + ], + "ref_block_bytes":"0000", + "ref_block_hash":"19b59068c6058ff4", + "expiration":1671109891800, + "timestamp":1671088291796 + }, + "raw_data_hex":"0a020000220819b59068c6058ff440d8ada5afd1305a5c083612580a34747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e467265657a6542616c616e63655632436f6e747261637412200a154159e3741a68ec3e1ebba80ad809d5ccd31674236e1080809aa6eaafe30170d4fffea4d130" +} +``` + + > unfreezeBalanceV2 [OwnerAddress] unfreezeBalance ResourceCode(0 BANDWIDTH,1 ENERGY,2 TRON_POWER) + +OwnerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +unfreezeBalance +> The amount of unfreeze, the unit is the smallest unit (Sun) + +ResourceCode +> 0 BANDWIDTH;1 ENERGY + +Example: +```console +wallet> UnFreezeBalanceV2 TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 9000000 0 +txid is dcfea1d92fc928d24c88f7f71a03ae8105d0b5b112d6d48be93d3b9c73bea634 +wallet> GetTransactionById dcfea1d92fc928d24c88f7f71a03ae8105d0b5b112d6d48be93d3b9c73bea634 +{ + "ret":[ + { + "contractRet":"SUCCESS" + } + ], + "signature":[ + "f73a278f742c11e8e5ede693ca09b0447a804fcb28ea2bfdfd8545bb05da7be44bd08cfaa92bd4d159178f763fcf753f28d5296bd0c3d4557532cce3b256b9da00" + ], + "txID":"dcfea1d92fc928d24c88f7f71a03ae8105d0b5b112d6d48be93d3b9c73bea634", + "raw_data":{ + "contract":[ + { + "parameter":{ + "value":{ + "owner_address":"4159e3741a68ec3e1ebba80ad809d5ccd31674236e", + "unfreeze_balance":9000000 + }, + "type_url":"type.googleapis.com/protocol.UnfreezeBalanceV2Contract" + }, + "type":"UnfreezeBalanceV2Contract" + } + ], + "ref_block_bytes":"0000", + "ref_block_hash":"19b59068c6058ff4", + "expiration":1671119916913, + "timestamp":1671098316907 + }, + "raw_data_hex":"0a020000220819b59068c6058ff440f19e89b4d1305a5a083712560a36747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e556e667265657a6542616c616e63655632436f6e7472616374121c0a154159e3741a68ec3e1ebba80ad809d5ccd31674236e10c0a8a50470ebf0e2a9d130" +} +``` + +### delegate/undelegate resource + + > delegateResource [OwnerAddress] balance ResourceCode(0 BANDWIDTH,1 ENERGY), ReceiverAddress [lock] + +OwnerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +balance +> The amount of delegate, the unit is the smallest unit (Sun), the minimum is 1000000sun. + +ResourceCode +> 0 BANDWIDTH;1 ENERGY + +ReceiverAddress +> The address of the account + +lock +> default is false, set true if need lock delegate for 3 days + +Example: +```console +wallet> DelegateResource TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 10000000 0 TQ4gjjpAjLNnE67UFbmK5wVt5fzLfyEVs3 true +txid is 363ac0b82b6ad3e0d3cad90f7d72b3eceafe36585432a3e013389db36152b6ed +wallet> GetTransactionById 363ac0b82b6ad3e0d3cad90f7d72b3eceafe36585432a3e013389db36152b6ed +{ + "ret":[ + { + "contractRet":"SUCCESS" + } + ], + "signature":[ + "1f57fd78456136faadc5091b47f5fd27a8e1181621e49129df6a4062499429fb48ee72e5f9a9ff5bfb7f2575f01f4076f7d4b89ca382d36af46a6fa4bc749f4301" + ], + "txID":"363ac0b82b6ad3e0d3cad90f7d72b3eceafe36585432a3e013389db36152b6ed", + "raw_data":{ + "contract":[ + { + "parameter":{ + "value":{ + "balance":10000000, + "receiver_address":"419a9afe56e155ef0ff3f680d00ecf19deff60bdca", + "lock":true, + "owner_address":"4159e3741a68ec3e1ebba80ad809d5ccd31674236e" + }, + "type_url":"type.googleapis.com/protocol.DelegateResourceContract" + }, + "type":"DelegateResourceContract" + } + ], + "ref_block_bytes":"0000", + "ref_block_hash":"19b59068c6058ff4", + "expiration":1671120059226, + "timestamp":1671098459216 + }, + "raw_data_hex":"0a020000220819b59068c6058ff440daf691b4d1305a720839126e0a35747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e44656c65676174655265736f75726365436f6e747261637412350a154159e3741a68ec3e1ebba80ad809d5ccd31674236e1880ade2042215419a9afe56e155ef0ff3f680d00ecf19deff60bdca280170d0c8eba9d130" +} + +``` + + > unDelegateResource [OwnerAddress] balance ResourceCode(0 BANDWIDTH,1 ENERGY), ReceiverAddress + +OwnerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +balance +> The amount of unDelegate, the unit is the smallest unit (Sun) + +ResourceCode +> 0 BANDWIDTH;1 ENERGY + +ReceiverAddress +> The address of the account + +Example: +```console +wallet> UnDelegateResource TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 1000000 0 TQ4gjjpAjLNnE67UFbmK5wVt5fzLfyEVs3 +txid is feb334794cf361fd351728026ccf7319e6ae90eba622b9eb53c626cdcae4965c +wallet> GetTransactionById feb334794cf361fd351728026ccf7319e6ae90eba622b9eb53c626cdcae4965c +{ + "ret":[ + { + "contractRet":"SUCCESS" + } + ], + "signature":[ + "85a41a4e44780ffbe0841a44fd71cf621f129d98e84984cfca68e03364f781aa7f9d44177af0b40d82da052feec9f47a399ed6e51be66c5db07cb13477dcde8c01" + ], + "txID":"feb334794cf361fd351728026ccf7319e6ae90eba622b9eb53c626cdcae4965c", + "raw_data":{ + "contract":[ + { + "parameter":{ + "value":{ + "balance":1000000, + "receiver_address":"419a9afe56e155ef0ff3f680d00ecf19deff60bdca", + "owner_address":"4159e3741a68ec3e1ebba80ad809d5ccd31674236e" + }, + "type_url":"type.googleapis.com/protocol.UnDelegateResourceContract" + }, + "type":"UnDelegateResourceContract" + } + ], + "ref_block_bytes":"0000", + "ref_block_hash":"19b59068c6058ff4", + "expiration":1671120342283, + "timestamp":1671098742280 + }, + "raw_data_hex":"0a020000220819b59068c6058ff4408b9aa3b4d1305a71083a126d0a37747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e556e44656c65676174655265736f75726365436f6e747261637412320a154159e3741a68ec3e1ebba80ad809d5ccd31674236e18c0843d2215419a9afe56e155ef0ff3f680d00ecf19deff60bdca7088ecfca9d130" +} +``` + + > withdrawExpireUnfreeze [OwnerAddress] + +OwnerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +Example: +```console +wallet> withdrawexpireunfreeze TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh +txid is e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df7e98f54a147 +wallet> GetTransactionById e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df7e98f54a147 +{ + "ret":[ + { + "contractRet":"SUCCESS" + } + ], + "signature":[ + "f8f02b5aa634b8666862a6d2ed68fcfd90afc616d14062952b0b09f0404d9bca6c4d3dc6dab082784950ff1ded235a07dab0d738c8a202be9451d5ca92b8eece01" + ], + "txID":"e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df7e98f54a147", + "raw_data":{ + "contract":[ + { + "parameter":{ + "value":{ + "owner_address":"4159e3741a68ec3e1ebba80ad809d5ccd31674236e" + }, + "type_url":"type.googleapis.com/protocol.WithdrawExpireUnfreezeContract" + }, + "type":"WithdrawExpireUnfreezeContract" + } + ], + "ref_block_bytes":"0000", + "ref_block_hash":"19b59068c6058ff4", + "expiration":1671122055318, + "timestamp":1671100455315 + }, + "raw_data_hex":"0a020000220819b59068c6058ff44096e18bb5d1305a5a083812560a3b747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5769746864726177457870697265556e667265657a65436f6e747261637412170a154159e3741a68ec3e1ebba80ad809d5ccd31674236e7093b3e5aad130" +} +``` + +### get resource delegation information use v2 API + + > getDelegatedResourceV2 fromAddress toAddress +> get the information from the fromAddress to the toAddress resource delegate use v2 API + +fromAddress +> The address of the account that start the delegate + +toAddress +> The address of the account that receive the delegate + +Example: +```console +wallet> getDelegatedResourceV2 TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh TQ4gjjpAjLNnE67UFbmK5wVt5fzLfyEVs3 +{ + "delegatedResource": [ + { + "from": "TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh", + "to": "TQ4gjjpAjLNnE67UFbmK5wVt5fzLfyEVs3", + "frozen_balance_for_bandwidth": 10000000 + } + ] +} +``` + + > getDelegatedResourceAccountIndexV2 address +> get the information that address is delegated to other account resources use v2 API + +address +> The address of the account that start the delegate or receive the delegate + +Example: +```console +wallet> getDelegatedResourceAccountIndexV2 TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh +{ + "account": "TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh", + "toAccounts": [ + "TQ4gjjpAjLNnE67UFbmK5wVt5fzLfyEVs3" + ] +} +``` + + > getcandelegatedmaxsize [ownerAddress] type +> get the max size that the ownerAddress can delegate use delegateResource + +ownerAddress +> The address of the account that start the delegate, optional, default is the address of the login account. + +type +> 0 bandwidth, 1 energy + +Example: +```console +wallet> getCanDelegatedMaxSize 0 +{ + "max_size": 999999978708334 +} +``` + + > getavailableunfreezecount [ownerAddress] +> get the available unfreeze count that the ownerAddress can call unfreezeBalanceV2 + +ownerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +Example: +```console +wallet> getAvailableUnfreezeCount +{ + "count": 31 +} +``` + + > getcanwithdrawunfreezeamount [ownerAddress] timestamp +> get the withdraw unfreeze amount that the ownerAddress can get by withdrawexpireunfreeze + +ownerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +timestamp +> get can withdraw unfreeze amount until timestamp, + + +Example: +```console +wallet> getCanWithdrawUnfreezeAmount 1671100335000 +{ + "amount": 9000000 +} +``` + + ## Wallet related commands **RegisterWallet** From 31bed1fd010195e715786a1c555f710b72aa1fa9 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Mon, 26 Dec 2022 14:24:49 +0800 Subject: [PATCH 406/445] add some param check --- .../common/utils/HttpSelfFormatFieldName.java | 16 ++++++++ src/main/java/org/tron/walletcli/Client.java | 39 ++++++++++++------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index e1f5f3463..83ca4f30a 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -115,6 +115,22 @@ public class HttpSelfFormatFieldName { AddressFieldNameMap.put("protocol.ShieldedTransferContract.transparent_from_address", 1); AddressFieldNameMap.put("protocol.ShieldedTransferContract.transparent_to_address", 1); + //FreezeBalanceV2Contract + AddressFieldNameMap.put("protocol.FreezeBalanceV2Contract.owner_address", 1); + //UnfreezeBalanceV2Contract + AddressFieldNameMap.put("protocol.UnfreezeBalanceV2Contract.owner_address", 1); + //WithdrawExpireUnfreezeContract + AddressFieldNameMap.put("protocol.WithdrawExpireUnfreezeContract.owner_address", 1); + //DelegateResourceContract + AddressFieldNameMap.put("protocol.DelegateResourceContract.owner_address", 1); + AddressFieldNameMap.put("protocol.DelegateResourceContract.receiver_address", 1); + //UnDelegateResourceContract + AddressFieldNameMap.put("protocol.UnDelegateResourceContract.owner_address", 1); + AddressFieldNameMap.put("protocol.UnDelegateResourceContract.receiver_address", 1); + AddressFieldNameMap.put("protocol.CanDelegatedMaxSizeRequestMessage.owner_address", 1); + AddressFieldNameMap.put("protocol.GetAvailableUnfreezeCountRequestMessage.owner_address", 1); + AddressFieldNameMap.put("protocol.CanWithdrawUnfreezeAmountRequestMessage.owner_address", 1); + //***** Tron.proto ***** //AccountId AddressFieldNameMap.put("protocol.AccountId.address", 1); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 5b2b3a3e3..a7971838c 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1427,24 +1427,19 @@ private void delegateResource(String[] parameters) return; } } else if (parameters.length == 4 || parameters.length == 5) { - ownerAddress = getAddressBytes(parameters[index++]); + ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress != null) { - balance = Long.parseLong(parameters[index++]); - resourceCode = Integer.parseInt(parameters[index++]); - receiverAddress = getAddressBytes(parameters[index++]); - if (receiverAddress == null) { - System.out.println( - "delegateResource receiverAddress is invalid"); - return; - } - if (parameters.length == 5) { - lock = Boolean.parseBoolean(parameters[index++]); - } - } if (ownerAddress == null) { + index ++; + } + balance = Long.parseLong(parameters[index++]); + resourceCode = Integer.parseInt(parameters[index++]); + receiverAddress = getAddressBytes(parameters[index++]); + if (receiverAddress == null) { System.out.println( - "delegateResource ownerAddress is invalid"); + "delegateResource receiverAddress is invalid"); return; } + lock = Boolean.parseBoolean(parameters[index++]); } @@ -1719,7 +1714,13 @@ private void getCanWithdrawUnfreezeAmount(String[] parameters) throws CipherExce byte[] ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress != null) { index++; + if (parameters.length != 2) { + System.out.println("Using getCanWithdrawUnfreezeAmount command needs 2 parameters like: "); + System.out.println("getcanwithdrawunfreezeamount [ownerAddress] timestamp"); + return; + } } + timestamp = Long.parseLong(parameters[index++]); if (timestamp < 0) { System.out.println("Invalid param, timestamp >= 0"); @@ -1747,8 +1748,15 @@ private void getCanDelegatedMaxSize(String[] parameters) throws CipherException, byte[] ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress != null) { index++; + if (parameters.length < 2) { + System.out.println("Using getcandelegatedmaxsize command needs 2 parameters like: "); + System.out.println("getcandelegatedmaxsize [ownerAddress] type"); + return ; + } } + type = Integer.parseInt(parameters[index++]); + if (ResourceCode.BANDWIDTH.ordinal() != type && ResourceCode.ENERGY.ordinal() != type) { System.out.println("getcandelegatedmaxsize param type must be: 0 or 1"); return; @@ -1773,6 +1781,9 @@ private void getAvailableUnfreezeCount(String[] parameters) throws CipherExcepti byte[] ownerAddress = null; if (parameters.length == 1) { ownerAddress = getAddressBytes(parameters[index]); + if (ownerAddress == null) { + return ; + } } Optional result = walletApiWrapper.getAvailableUnfreezeCount(ownerAddress); From 5b9eaf86a6b837557686b9b0eabd4e9af8a0ec90 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Mon, 26 Dec 2022 17:43:27 +0800 Subject: [PATCH 407/445] query api not need check login --- src/main/java/org/tron/walletcli/Client.java | 19 ++++++++---- .../org/tron/walletcli/WalletApiWrapper.java | 31 ------------------- .../java/org/tron/walletserver/WalletApi.java | 15 ++------- 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index a7971838c..3109173b3 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -90,6 +90,7 @@ public class Client { "CreateAccount", "CreateProposal", "CreateWitness", + "DelegateResource", "DeleteProposal", "DeployContract contractName ABI byteCode constructor params isHex fee_limit consume_user_resource_percent origin_energy_limit value token_value token_id ", "ExchangeCreate", @@ -198,6 +199,7 @@ public class Client { "TransferAsset", "TriggerConstantContract contractAddress method args isHex", "TriggerContract contractAddress method args isHex fee_limit value", + "UnDelegateResource", "UnfreezeAsset", "UnfreezeBalance", "UnfreezeBalanceV2", @@ -211,8 +213,6 @@ public class Client { "VoteWitness", "WithdrawBalance", "WithdrawExpireUnfreeze", - "DelegateResource", - "UnDelegateResource", }; // note: this is sorted by alpha @@ -231,6 +231,7 @@ public class Client { "CreateAccount", "CreateProposal", "CreateWitness", + "DelegateResource", "DeleteProposal", "DeployContract", "ExchangeCreate", @@ -238,6 +239,7 @@ public class Client { "ExchangeTransaction", "ExchangeWithdraw", "FreezeBalance", + "FreezeBalanceV2", "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", @@ -338,8 +340,10 @@ public class Client { "TransferAsset", "TriggerConstantContract", "TriggerContract", + "UnDelegateResource", "UnfreezeAsset", "UnfreezeBalance", + "UnfreezeBalanceV2", "UpdateAccount", "UpdateAccountPermission", "UpdateAsset", @@ -349,6 +353,7 @@ public class Client { "UpdateWitness", "VoteWitness", "WithdrawBalance", + "WithdrawExpireUnfreeze", }; private byte[] inputPrivateKey() throws IOException { @@ -1727,8 +1732,8 @@ private void getCanWithdrawUnfreezeAmount(String[] parameters) throws CipherExce return; } - Optional result = walletApiWrapper. - getCanWithdrawUnfreezeAmount(ownerAddress, timestamp); + Optional result = WalletApi.getCanWithdrawUnfreezeAmount( + ownerAddress, timestamp); if (result.isPresent()) { CanWithdrawUnfreezeAmountResponseMessage canWithdrawUnfreezeAmountResponseMessage = result.get(); System.out.println(Utils.formatMessageString(canWithdrawUnfreezeAmountResponseMessage)); @@ -1762,7 +1767,7 @@ private void getCanDelegatedMaxSize(String[] parameters) throws CipherException, return; } - Optional result = walletApiWrapper.getCanDelegatedMaxSize(ownerAddress, type); + Optional result = WalletApi.getCanDelegatedMaxSize(ownerAddress, type); if (result.isPresent()) { CanDelegatedMaxSizeResponseMessage canDelegatedMaxSizeResponseMessage = result.get(); System.out.println(Utils.formatMessageString(canDelegatedMaxSizeResponseMessage)); @@ -1782,11 +1787,13 @@ private void getAvailableUnfreezeCount(String[] parameters) throws CipherExcepti if (parameters.length == 1) { ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress == null) { + System.out.println("Using getavailableunfreezecount command needs 1 parameters like: "); + System.out.println("getavailableunfreezecount [owner_address] "); return ; } } - Optional result = walletApiWrapper.getAvailableUnfreezeCount(ownerAddress); + Optional result = WalletApi.getAvailableUnfreezeCount(ownerAddress); if (result.isPresent()) { GetAvailableUnfreezeCountResponseMessage getAvailableUnfreezeCountResponseMessage = result.get(); System.out.println(Utils.formatMessageString(getAvailableUnfreezeCountResponseMessage)); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index d68f666d6..68cd2dd89 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -577,37 +577,6 @@ public boolean undelegateresource(byte[] ownerAddress, long balance return wallet.unDelegateResource(ownerAddress, balance, resourceCode, receiverAddress); } - - public Optional getCanWithdrawUnfreezeAmount( - byte[] ownerAddress, long timestamp) - throws CipherException, IOException, CancelException { - if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: getCanWithdrawUnfreezeAmount failed, Please login first !!"); - return Optional.empty(); - } - - return wallet.getCanWithdrawUnfreezeAmount(ownerAddress, timestamp); - } - - public Optional getCanDelegatedMaxSize(byte[] ownerAddress, int type) - throws CipherException, IOException, CancelException { - if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: getCanDelegatedMaxSize failed, Please login first !!"); - return Optional.empty(); - } - - return wallet.getCanDelegatedMaxSize(ownerAddress, type); - } - - public Optional getAvailableUnfreezeCount(byte[] ownerAddress) - throws CipherException, IOException, CancelException { - if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: getAvailableUnfreezeCount failed, Please login first !!"); - return Optional.empty(); - } - - return wallet.getAvailableUnfreezeCount(ownerAddress); - } public boolean unfreezeAsset(byte[] ownerAddress) throws CipherException, IOException, CancelException { diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index a11bdcab5..344c92533 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -1657,27 +1657,18 @@ public static Optional getDelegatedResou return rpcCli.getDelegatedResourceAccountIndexV2(ownerAddress); } - public Optional getCanWithdrawUnfreezeAmount( + public static Optional getCanWithdrawUnfreezeAmount( byte[] ownerAddress, long timestamp) { - if (ownerAddress == null) { - ownerAddress = this.getAddress(); - } return rpcCli.getCanWithdrawUnfreezeAmount(ownerAddress, timestamp); } - public Optional getCanDelegatedMaxSize( + public static Optional getCanDelegatedMaxSize( byte[] ownerAddress, int type) { - if (ownerAddress == null) { - ownerAddress = this.getAddress(); - } return rpcCli.getCanDelegatedMaxSize(ownerAddress, type); } - public Optional getAvailableUnfreezeCount( + public static Optional getAvailableUnfreezeCount( byte[] ownerAddress) { - if (ownerAddress == null) { - ownerAddress = this.getAddress(); - } return rpcCli.getAvailableUnfreezeCount(ownerAddress); } From 8dd116f92fd222f1694d744cbca67e68d974c7be Mon Sep 17 00:00:00 2001 From: chaozhu Date: Wed, 28 Dec 2022 16:15:42 +0800 Subject: [PATCH 408/445] fix some parameter verification problems --- README.md | 12 +- src/main/java/org/tron/walletcli/Client.java | 140 ++++++++++++++----- 2 files changed, 112 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index c93b8a081..cbbfdebaf 100644 --- a/README.md +++ b/README.md @@ -1385,7 +1385,7 @@ wallet> getDelegatedResourceAccountIndexV2 TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh } ``` - > getcandelegatedmaxsize [ownerAddress] type + > getcandelegatedmaxsize ownerAddress type > get the max size that the ownerAddress can delegate use delegateResource ownerAddress @@ -1396,13 +1396,13 @@ type Example: ```console -wallet> getCanDelegatedMaxSize 0 +wallet> getCanDelegatedMaxSize TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 0 { "max_size": 999999978708334 } ``` - > getavailableunfreezecount [ownerAddress] + > getavailableunfreezecount ownerAddress > get the available unfreeze count that the ownerAddress can call unfreezeBalanceV2 ownerAddress @@ -1410,13 +1410,13 @@ ownerAddress Example: ```console -wallet> getAvailableUnfreezeCount +wallet> getAvailableUnfreezeCount TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh { "count": 31 } ``` - > getcanwithdrawunfreezeamount [ownerAddress] timestamp + > getcanwithdrawunfreezeamount ownerAddress timestamp > get the withdraw unfreeze amount that the ownerAddress can get by withdrawexpireunfreeze ownerAddress @@ -1428,7 +1428,7 @@ timestamp Example: ```console -wallet> getCanWithdrawUnfreezeAmount 1671100335000 +wallet> getCanWithdrawUnfreezeAmount TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 1671100335000 { "amount": 9000000 } diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 3109173b3..c13b18341 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1444,9 +1444,12 @@ private void delegateResource(String[] parameters) "delegateResource receiverAddress is invalid"); return; } - lock = Boolean.parseBoolean(parameters[index++]); - } + if (parameters.length == 5 || + (ownerAddress == null && parameters.length == 4)) { + lock = Boolean.parseBoolean(parameters[index++]); + } + } boolean result = walletApiWrapper.delegateresource( ownerAddress, balance, resourceCode, receiverAddress, lock); @@ -1708,28 +1711,54 @@ private void getDelegatedResourceAccountIndexV2(String[] parameters) { } } + private void outputGetCanWithdrawUnfreezeAmountTip() { + System.out.println("Using getCanWithdrawUnfreezeAmount command needs 2 parameters like: "); + System.out.println("getcanwithdrawunfreezeamount ownerAddress timestamp"); + } + private void getCanWithdrawUnfreezeAmount(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || !(parameters.length == 1 || parameters.length == 2)) { - System.out.println("Using getCanWithdrawUnfreezeAmount command needs 2 parameters like: "); - System.out.println("getcanwithdrawunfreezeamount [ownerAddress] timestamp"); + this.outputGetCanWithdrawUnfreezeAmountTip(); return; } int index = 0; long timestamp = 0; - byte[] ownerAddress = getAddressBytes(parameters[index]); - if (ownerAddress != null) { - index++; - if (parameters.length != 2) { - System.out.println("Using getCanWithdrawUnfreezeAmount command needs 2 parameters like: "); - System.out.println("getcanwithdrawunfreezeamount [ownerAddress] timestamp"); + byte[] ownerAddress = null; + + if (parameters.length == 1) { + try { + timestamp = Long.parseLong(parameters[index]); + if (timestamp < 0) { + System.out.println("Invalid param, timestamp >= 0"); + return; + } + } catch (NumberFormatException nfe) { + this.outputGetCanWithdrawUnfreezeAmountTip(); return; } - } - timestamp = Long.parseLong(parameters[index++]); - if (timestamp < 0) { - System.out.println("Invalid param, timestamp >= 0"); - return; + ownerAddress = this.getLoginAddreess(); + if (ownerAddress == null) { + System.out.println("getcanwithdrawunfreezeamount ownerAddress is invalid"); + return ; + } + } else if (parameters.length == 2) { + ownerAddress = getAddressBytes(parameters[index++]); + if (ownerAddress == null) { + this.outputGetCanWithdrawUnfreezeAmountTip(); + return; + } + + try { + timestamp = Long.parseLong(parameters[index]); + if (timestamp < 0) { + System.out.println("Invalid param, timestamp >= 0"); + return; + } + } catch (NumberFormatException nfe) { + this.outputGetCanWithdrawUnfreezeAmountTip(); + return; + } } Optional result = WalletApi.getCanWithdrawUnfreezeAmount( @@ -1742,29 +1771,55 @@ private void getCanWithdrawUnfreezeAmount(String[] parameters) throws CipherExce } } + + private void outputGetCanDelegatedMaxSizeTip() { + System.out.println("Using getcandelegatedmaxsize command needs 2 parameters like: "); + System.out.println("getcandelegatedmaxsize ownerAddress type"); + } + private void getCanDelegatedMaxSize(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || !(parameters.length == 1 || parameters.length == 2)) { - System.out.println("Using getcandelegatedmaxsize command needs 2 parameters like: "); - System.out.println("getcandelegatedmaxsize [ownerAddress] type"); + this.outputGetCanDelegatedMaxSizeTip(); return; } int index = 0; int type = 0; - byte[] ownerAddress = getAddressBytes(parameters[index]); - if (ownerAddress != null) { - index++; - if (parameters.length < 2) { - System.out.println("Using getcandelegatedmaxsize command needs 2 parameters like: "); - System.out.println("getcandelegatedmaxsize [ownerAddress] type"); - return ; + byte[] ownerAddress = null; + + if (parameters.length == 1) { + try { + type = Integer.parseInt(parameters[index]); + if (ResourceCode.BANDWIDTH.ordinal() != type && ResourceCode.ENERGY.ordinal() != type) { + System.out.println("getcandelegatedmaxsize type must be: 0 or 1"); + return; + } + } catch (NumberFormatException nfe) { + this.outputGetCanDelegatedMaxSizeTip(); + return; } - } - type = Integer.parseInt(parameters[index++]); + ownerAddress = this.getLoginAddreess(); + if (ownerAddress == null) { + System.out.println("getcandelegatedmaxsize ownerAddress is invalid"); + return ; + } + } else if (parameters.length == 2) { + ownerAddress = getAddressBytes(parameters[index++]); + if (ownerAddress == null) { + this.outputGetCanDelegatedMaxSizeTip(); + return ; + } - if (ResourceCode.BANDWIDTH.ordinal() != type && ResourceCode.ENERGY.ordinal() != type) { - System.out.println("getcandelegatedmaxsize param type must be: 0 or 1"); - return; + try { + type = Integer.parseInt(parameters[index]); + if (ResourceCode.BANDWIDTH.ordinal() != type && ResourceCode.ENERGY.ordinal() != type) { + System.out.println("getcandelegatedmaxsize type must be: 0 or 1"); + return; + } + } catch (NumberFormatException nfe) { + this.outputGetCanDelegatedMaxSizeTip(); + return; + } } Optional result = WalletApi.getCanDelegatedMaxSize(ownerAddress, type); @@ -1776,10 +1831,14 @@ private void getCanDelegatedMaxSize(String[] parameters) throws CipherException, } } + private void outputGetAvailableUnfreezeCountTip() { + System.out.println("Using getavailableunfreezecount command needs 1 parameters like: "); + System.out.println("getavailableunfreezecount owner_address "); + } + private void getAvailableUnfreezeCount(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || !(parameters.length == 0 || parameters.length == 1)) { - System.out.println("Using getavailableunfreezecount command needs 1 parameters like: "); - System.out.println("getavailableunfreezecount [owner_address] "); + this.outputGetAvailableUnfreezeCountTip(); return; } int index = 0; @@ -1787,10 +1846,15 @@ private void getAvailableUnfreezeCount(String[] parameters) throws CipherExcepti if (parameters.length == 1) { ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress == null) { - System.out.println("Using getavailableunfreezecount command needs 1 parameters like: "); - System.out.println("getavailableunfreezecount [owner_address] "); - return ; + this.outputGetAvailableUnfreezeCountTip(); + return; } + } else { + ownerAddress = this.getLoginAddreess(); + if (ownerAddress == null) { + this.outputGetAvailableUnfreezeCountTip(); + return; + } } Optional result = WalletApi.getAvailableUnfreezeCount(ownerAddress); @@ -4818,6 +4882,14 @@ private void getChainParameters() { } } + private byte[] getLoginAddreess() { + if (walletApiWrapper.isLoginState()) { + String ownerAddressStr = walletApiWrapper.getAddress(); + return WalletApi.decodeFromBase58Check(ownerAddressStr); + } + return null; + } + private void getBlockByIdOrNum(String[] parameters) { String idOrNum = null; boolean detail = false; From 593c475d16eec10570dda0591995dae15ffa208c Mon Sep 17 00:00:00 2001 From: "daniel.cao" Date: Thu, 29 Dec 2022 12:36:36 +0800 Subject: [PATCH 409/445] feat(DynamicEnergy): add dynamic energy apis for wallet --- src/main/java/org/tron/walletcli/Client.java | 65 +++++++++++++++++++ .../org/tron/walletcli/WalletApiWrapper.java | 12 ++++ .../org/tron/walletserver/GrpcClient.java | 4 ++ .../java/org/tron/walletserver/WalletApi.java | 34 ++++++++++ src/main/protos/api/api.proto | 9 +++ src/main/protos/core/Tron.proto | 1 + .../protos/core/contract/smart_contract.proto | 7 ++ 7 files changed, 132 insertions(+) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index c13b18341..27eae01b0 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -93,6 +93,7 @@ public class Client { "DelegateResource", "DeleteProposal", "DeployContract contractName ABI byteCode constructor params isHex fee_limit consume_user_resource_percent origin_energy_limit value token_value token_id ", + "EstimateEnergy", "ExchangeCreate", "ExchangeInject", "ExchangeTransaction", @@ -234,6 +235,7 @@ public class Client { "DelegateResource", "DeleteProposal", "DeployContract", + "EstimateEnergy", "ExchangeCreate", "ExchangeInject", "ExchangeTransaction", @@ -2734,6 +2736,65 @@ private void triggerConstantContract(String[] parameters) ownerAddress, contractAddress, callValue, input, 0, tokenValue, tokenId, true); } + private void estimateEnergy(String[] parameters) + throws IOException, CipherException, CancelException { + + if (parameters == null || (parameters.length != 5 && parameters.length != 8)) { + System.out.println("EstimateEnergy needs 5 or 8 parameters like: "); + System.out.println("EstimateEnergy ownerAddress(use # if you own)" + + " contractAddress method args isHex " + + "[value token_value token_id(e.g: TRXTOKEN, use # if don't provided)]"); + return; + } + + int idx = 0; + + String ownerAddressStr = parameters[idx++]; + byte[] ownerAddress = null; + if (!"#".equals(ownerAddressStr)) { + ownerAddress = WalletApi.decodeFromBase58Check(ownerAddressStr); + if (ownerAddress == null) { + System.out.println("Invalid Owner Address."); + return; + } + } + + String contractAddressStr = parameters[idx++]; + byte[] contractAddress = WalletApi.decodeFromBase58Check(contractAddressStr); + if (contractAddress == null) { + System.out.println("Invalid Contract Address."); + return; + } + + String methodStr = parameters[idx++]; + String argsStr = parameters[idx++]; + boolean isHex = Boolean.parseBoolean(parameters[idx++]); + long callValue = 0; + long tokenValue = 0; + String tokenId = ""; + if (parameters.length == 8) { + callValue = Long.parseLong(parameters[idx++]); + tokenValue = Long.parseLong(parameters[idx++]); + tokenId = parameters[idx]; + } + + if (argsStr.equalsIgnoreCase("#")) { + argsStr = ""; + } + + if (tokenId.equalsIgnoreCase("#")) { + tokenId = ""; + } + + byte[] input = new byte[0]; + if (!methodStr.equalsIgnoreCase("#")) { + input = Hex.decode(AbiUtil.parseMethod(methodStr, argsStr, isHex)); + } + + walletApiWrapper.estimateEnergy( + ownerAddress, contractAddress, callValue, input, tokenValue, tokenId); + } + private void getContract(String[] parameters) { if (parameters == null || parameters.length != 1) { @@ -4658,6 +4719,10 @@ private void run() { triggerConstantContract(parameters); break; } + case "estimateenergy": { + estimateEnergy(parameters); + break; + } case "getcontract": { getContract(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 68cd2dd89..eb587d64b 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -779,6 +779,18 @@ public boolean callContract(byte[] ownerAddress, byte[] contractAddress, long ca isConstant); } + public boolean estimateEnergy(byte[] ownerAddress, byte[] contractAddress, long callValue, + byte[] data, long tokenValue, String tokenId) + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: estimateEnergy failed, Please login first !!"); + return false; + } + + return wallet + .estimateEnergy(ownerAddress, contractAddress, callValue, data, tokenValue, tokenId); + } + public boolean accountPermissionUpdate(byte[] ownerAddress, String permission) throws IOException, CipherException, CancelException { if (wallet == null || !wallet.isLoginState()) { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index a83a9a7d0..eac321095 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -937,6 +937,10 @@ public TransactionExtention triggerConstantContract(TriggerSmartContract request return blockingStubFull.triggerConstantContract(request); } + public EstimateEnergyMessage estimateEnergy(TriggerSmartContract request) { + return blockingStubFull.estimateEnergy(request); + } + public SmartContract getContract(byte[] address) { ByteString byteString = ByteString.copyFrom(address); BytesMessage bytesMessage = BytesMessage.newBuilder().setValue(byteString).build(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 344c92533..a0a57e349 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -44,6 +44,7 @@ import org.tron.api.GrpcAPI.DiversifierMessage; import org.tron.api.GrpcAPI.EasyTransferResponse; import org.tron.api.GrpcAPI.EmptyMessage; +import org.tron.api.GrpcAPI.EstimateEnergyMessage; import org.tron.api.GrpcAPI.ExchangeList; import org.tron.api.GrpcAPI.ExpandedSpendingKeyMessage; import org.tron.api.GrpcAPI.IncomingViewingKeyDiversifierMessage; @@ -2376,6 +2377,39 @@ public boolean triggerContract( return processTransactionExtention(transactionExtention); } + public boolean estimateEnergy( + byte[] owner, + byte[] contractAddress, + long callValue, + byte[] data, + long tokenValue, + String tokenId) + throws IOException, CipherException, CancelException { + if (owner == null) { + owner = getAddress(); + } + + TriggerSmartContract triggerContract = triggerCallContract(owner, contractAddress, callValue, + data, tokenValue, tokenId); + + EstimateEnergyMessage estimateEnergyMessage = rpcCli.estimateEnergy(triggerContract); + + if (estimateEnergyMessage == null) { + System.out.println("RPC create call trx failed!"); + return false; + } + + if (!estimateEnergyMessage.getResult().getResult()) { + System.out.println("RPC estimate energy failed!"); + System.out.println("Code = " + estimateEnergyMessage.getResult().getCode()); + System.out + .println("Message = " + estimateEnergyMessage.getResult().getMessage().toStringUtf8()); + return false; + } + System.out.println("Estimate energy result = " + Utils.formatMessageString(estimateEnergyMessage)); + return true; + } + public static SmartContract getContract(byte[] address) { return rpcCli.getContract(address); } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 56c714c8d..5a40c75ab 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -460,6 +460,9 @@ service Wallet { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } + rpc EstimateEnergy (TriggerSmartContract) returns (EstimateEnergyMessage) { + } + rpc ClearContractABI (ClearABIContract) returns (TransactionExtention) { } @@ -1281,6 +1284,12 @@ message TransactionExtention { int64 energy_used = 5; repeated TransactionInfo.Log logs = 6; repeated InternalTransaction internal_transactions = 7; + int64 energy_penalty = 8; +} + +message EstimateEnergyMessage { + Return result = 1; + int64 energy_required = 2; } message BlockExtention { diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index c9c230864..e2a402c4d 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -259,6 +259,7 @@ message ResourceReceipt { int64 net_usage = 5; int64 net_fee = 6; Transaction.Result.contractResult result = 7; + int64 energy_penalty_total = 8; } message MarketOrderDetail { diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto index 40fbb5b7d..ede03e88f 100644 --- a/src/main/protos/core/contract/smart_contract.proto +++ b/src/main/protos/core/contract/smart_contract.proto @@ -58,6 +58,12 @@ message SmartContract { int32 version = 11; } +message ContractState { + int64 energy_usage = 1; + int64 energy_factor = 2; + int64 update_cycle = 3; +} + message CreateSmartContract { bytes owner_address = 1; SmartContract new_contract = 2; @@ -94,4 +100,5 @@ message UpdateEnergyLimitContract { message SmartContractDataWrapper { SmartContract smart_contract = 1; bytes runtimecode = 2; + ContractState contract_state = 3; } From 2fbc9f972301a66867a38bcb1bdf3f50fd0a191d Mon Sep 17 00:00:00 2001 From: "daniel.cao" Date: Tue, 3 Jan 2023 14:37:59 +0800 Subject: [PATCH 410/445] feat(DynamicEnergy): optimize estimate energy cmd parameters parse --- src/main/java/org/tron/walletcli/Client.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 27eae01b0..c0ca0ce10 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -4279,7 +4279,8 @@ public static String[] getCmd(String cmdLine) { || cmdLine.toLowerCase().startsWith("deployconstantcontract") || cmdLine.toLowerCase().startsWith("triggercontract") || cmdLine.toLowerCase().startsWith("triggerconstantcontract") - || cmdLine.toLowerCase().startsWith("updateaccountpermission")) { + || cmdLine.toLowerCase().startsWith("updateaccountpermission") + || cmdLine.toLowerCase().startsWith("estimateenergy")) { return cmdLine.split("\\s+", -1); } String[] strArray = cmdLine.split("\""); From 0c3abf929a149d306e24aa778e3367bb2df44375 Mon Sep 17 00:00:00 2001 From: liukai Date: Wed, 11 Jan 2023 11:37:48 +0800 Subject: [PATCH 411/445] feat(zksnark): update zksnark 1. update zksnark dependency --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9a5221a42..54016c42b 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ dependencies { compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' compile group: 'org.jline', name: 'jline', version: '3.15.0' - compile 'com.github.tronprotocol:zksnark-java-sdk:master-SNAPSHOT' + compile 'io.github.tronprotocol:zksnark-java-sdk:1.0.0' } protobuf { From 4ce756d59cf877db92d1e790acc7a68b267c9444 Mon Sep 17 00:00:00 2001 From: forfreeday Date: Wed, 11 Jan 2023 12:27:40 +0800 Subject: [PATCH 412/445] feat(stake): add new protobuf 1. modify api.proto 2. modify smart_contract.proto 3. modify Tron.proto --- src/main/protos/api/api.proto | 177 +++++++++-- src/main/protos/core/Tron.proto | 284 ++++++++++++++---- .../protos/core/contract/smart_contract.proto | 139 +++++---- 3 files changed, 452 insertions(+), 148 deletions(-) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 6cfa57986..95871f5aa 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -11,9 +11,9 @@ import "core/contract/balance_contract.proto"; import "core/contract/proposal_contract.proto"; import "core/contract/storage_contract.proto"; import "core/contract/exchange_contract.proto"; +import "core/contract/market_contract.proto"; import "core/contract/smart_contract.proto"; import "core/contract/shield_contract.proto"; -import "core/contract/market_contract.proto"; option java_package = "org.tron.api"; //Specify the name of the package that generated the Java file option java_outer_classname = "GrpcAPI"; //Specify the class name of the generated Java file @@ -41,7 +41,6 @@ service Wallet { }; }; - rpc GetAccountBalance (AccountBalanceRequest) returns (AccountBalanceResponse) { option (google.api.http) = { post: "/wallet/getaccountbalance" @@ -62,7 +61,6 @@ service Wallet { }; }; - //Please use CreateTransaction2 instead of this function. rpc CreateTransaction (TransferContract) returns (Transaction) { option (google.api.http) = { @@ -226,6 +224,10 @@ service Wallet { //Use this function instead of FreezeBalance. rpc FreezeBalance2 (FreezeBalanceContract) returns (TransactionExtention) { } + //Use this function when FreezeBalanceV2. + rpc FreezeBalanceV2 (FreezeBalanceV2Contract) returns (TransactionExtention) { + } + //Please use UnfreezeBalance2 instead of this function. rpc UnfreezeBalance (UnfreezeBalanceContract) returns (Transaction) { option (google.api.http) = { @@ -239,6 +241,10 @@ service Wallet { //Use this function instead of UnfreezeBalance. rpc UnfreezeBalance2 (UnfreezeBalanceContract) returns (TransactionExtention) { } + //Use this function when UnfreezeBalanceV2. + rpc UnfreezeBalanceV2 (UnfreezeBalanceV2Contract) returns (TransactionExtention) { + } + //Please use UnfreezeAsset2 instead of this function. rpc UnfreezeAsset (UnfreezeAssetContract) returns (Transaction) { option (google.api.http) = { @@ -265,6 +271,16 @@ service Wallet { //Use this function instead of WithdrawBalance. rpc WithdrawBalance2 (WithdrawBalanceContract) returns (TransactionExtention) { } + + rpc WithdrawExpireUnfreeze (WithdrawExpireUnfreezeContract) returns (TransactionExtention) { + } + + rpc DelegateResource (DelegateResourceContract) returns (TransactionExtention) { + } + + rpc UnDelegateResource (UnDelegateResourceContract) returns (TransactionExtention) { + } + //Please use UpdateAsset2 instead of this function. rpc UpdateAsset (UpdateAssetContract) returns (Transaction) { option (google.api.http) = { @@ -309,6 +325,28 @@ service Wallet { rpc ExchangeTransaction (ExchangeTransactionContract) returns (TransactionExtention) { } + rpc MarketSellAsset (MarketSellAssetContract) returns (TransactionExtention) { + } + + rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { + } + + rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { + } + + rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { + } + + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { + } + + rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { + } + + rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { + } + + rpc ListNodes (EmptyMessage) returns (NodeList) { option (google.api.http) = { post: "/wallet/listnodes" @@ -442,6 +480,9 @@ service Wallet { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } + rpc EstimateEnergy (TriggerSmartContract) returns (EstimateEnergyMessage) { + } + rpc ClearContractABI (ClearABIContract) returns (TransactionExtention) { } @@ -459,6 +500,26 @@ service Wallet { rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { }; + rpc GetDelegatedResourceV2 (DelegatedResourceMessage) returns (DelegatedResourceList) { + }; + + rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + + rpc GetDelegatedResourceAccountIndexV2 (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + + rpc GetCanDelegatedMaxSize (CanDelegatedMaxSizeRequestMessage) returns (CanDelegatedMaxSizeResponseMessage) { + }; + + rpc GetAvailableUnfreezeCount (GetAvailableUnfreezeCountRequestMessage) + returns (GetAvailableUnfreezeCountResponseMessage) { + }; + + rpc GetCanWithdrawUnfreezeAmount (CanWithdrawUnfreezeAmountRequestMessage) + returns (CanWithdrawUnfreezeAmountResponseMessage) { + } + rpc ListProposals (EmptyMessage) returns (ProposalList) { option (google.api.http) = { post: "/wallet/listproposals" @@ -728,7 +789,6 @@ service Wallet { rpc CreateShieldNullifier (NfParameters) returns (BytesMessage) { }; - // end for shiededTransaction //for shielded contract rpc CreateShieldedContractParameters (PrivateShieldedTRC20Parameters) returns (ShieldedTRC20Parameters) { @@ -748,7 +808,7 @@ service Wallet { rpc GetTriggerInputForShieldedTRC20Contract (ShieldedTRC20TriggerContractParameters) returns (BytesMessage) { }; - // end for shieldedTransaction + // end for shiededTransaction rpc CreateCommonTransaction (Transaction) returns (TransactionExtention) { }; @@ -756,28 +816,18 @@ service Wallet { rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { } - // for market - rpc MarketSellAsset (MarketSellAssetContract) returns (TransactionExtention) { + rpc GetBurnTrx (EmptyMessage) returns (NumberMessage) { } - rpc MarketCancelOrder (MarketCancelOrderContract) returns (TransactionExtention) { + rpc GetTransactionFromPending (BytesMessage) returns (Transaction) { } - rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { + rpc GetTransactionListFromPending (EmptyMessage) returns (TransactionIdList) { } - rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { + rpc GetPendingSize (EmptyMessage) returns (NumberMessage) { } - rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { - } - - rpc GetMarketOrderListByPair (MarketOrderPair) returns (MarketOrderList) { - } - - rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { - } - // end for market rpc GetBlock (BlockReq) returns (BlockExtention) { } }; @@ -871,9 +921,26 @@ service WalletSolidity { rpc GetDelegatedResource (DelegatedResourceMessage) returns (DelegatedResourceList) { }; + rpc GetDelegatedResourceV2 (DelegatedResourceMessage) returns (DelegatedResourceList) { + }; + rpc GetDelegatedResourceAccountIndex (BytesMessage) returns (DelegatedResourceAccountIndex) { }; + rpc GetDelegatedResourceAccountIndexV2 (BytesMessage) returns (DelegatedResourceAccountIndex) { + }; + + rpc GetCanDelegatedMaxSize (CanDelegatedMaxSizeRequestMessage) returns (CanDelegatedMaxSizeResponseMessage) { + }; + + rpc GetAvailableUnfreezeCount (GetAvailableUnfreezeCountRequestMessage) + returns (GetAvailableUnfreezeCountResponseMessage) { + }; + + rpc GetCanWithdrawUnfreezeAmount (CanWithdrawUnfreezeAmountRequestMessage) + returns (CanWithdrawUnfreezeAmountResponseMessage) { + } + rpc GetExchangeById (BytesMessage) returns (Exchange) { }; @@ -945,15 +1012,18 @@ service WalletSolidity { rpc TriggerConstantContract (TriggerSmartContract) returns (TransactionExtention) { } - rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { + rpc EstimateEnergy (TriggerSmartContract) returns (EstimateEnergyMessage) { } - rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { + rpc GetTransactionInfoByBlockNum (NumberMessage) returns (TransactionInfoList) { } rpc GetMarketOrderById (BytesMessage) returns (MarketOrder) { } + rpc GetMarketOrderByAccount (BytesMessage) returns (MarketOrderList) { + } + rpc GetMarketPriceByPair (MarketOrderPair) returns (MarketPriceList) { } @@ -962,6 +1032,9 @@ service WalletSolidity { rpc GetMarketPairList (EmptyMessage) returns (MarketOrderPairList) { } + + rpc GetBurnTrx (EmptyMessage) returns (NumberMessage) { + } rpc GetBlock (BlockReq) returns (BlockExtention) { } }; @@ -1012,6 +1085,18 @@ service Database { } }; +service Monitor { + rpc GetStatsInfo (EmptyMessage) returns (MetricsInfo) { + option (google.api.http) = { + post: "/monitor/getstatsinfo" + body: "*" + additional_bindings { + get: "/monitor/getstatsinfo" + } + }; + } +} + message Return { enum response_code { SUCCESS = 0; @@ -1062,6 +1147,9 @@ message BlockList { message TransactionList { repeated Transaction transaction = 1; } +message TransactionIdList { + repeated string txId = 1; +} message DelegatedResourceMessage { bytes fromAddress = 1; bytes toAddress = 2; @@ -1070,6 +1158,31 @@ message DelegatedResourceList { repeated DelegatedResource delegatedResource = 1; } +message GetAvailableUnfreezeCountRequestMessage { + bytes owner_address = 1; +} +message GetAvailableUnfreezeCountResponseMessage { + int64 count = 1; +} + +//GetCanDelegatedMaxSize +message CanDelegatedMaxSizeRequestMessage { + int32 type = 1; + bytes owner_address = 2; +} +message CanDelegatedMaxSizeResponseMessage { + int64 max_size = 1; +} + +//GetCanWithdrawUnfreezeAmount +message CanWithdrawUnfreezeAmountRequestMessage { + bytes owner_address = 1; + int64 timestamp = 2; +} +message CanWithdrawUnfreezeAmountResponseMessage { + int64 amount = 1; +} + // Gossip node list message NodeList { repeated Node nodes = 1; @@ -1187,7 +1300,7 @@ message EasyTransferAssetByPrivateMessage { message EasyTransferResponse { Transaction transaction = 1; Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.raw_data) + bytes txid = 3; //transaction id = sha256(transaction.rowdata) } message AddressPrKeyPairMessage { @@ -1197,12 +1310,18 @@ message AddressPrKeyPairMessage { message TransactionExtention { Transaction transaction = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) repeated bytes constant_result = 3; Return result = 4; int64 energy_used = 5; repeated TransactionInfo.Log logs = 6; repeated InternalTransaction internal_transactions = 7; + int64 energy_penalty = 8; +} + +message EstimateEnergyMessage { + Return result = 1; + int64 energy_required = 2; } message BlockExtention { @@ -1285,7 +1404,7 @@ message OvkDecryptParameters { message DecryptNotes { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) int32 index = 3; //the index of note in receive } repeated NoteTx noteTxs = 1; @@ -1294,7 +1413,7 @@ message DecryptNotes { message DecryptNotesMarked { message NoteTx { Note note = 1; - bytes txid = 2; //transaction id = sha256(transaction.raw_data) + bytes txid = 2; //transaction id = sha256(transaction.rowdata) int32 index = 3; //the index of note in receive bool is_spend = 4; } @@ -1329,7 +1448,7 @@ message PrivateParameters { repeated ReceiveNote shielded_receives = 7; bytes transparent_to_address = 8; int64 to_amount = 9; - int64 timeout = 10; // timeout in seconds, it works only when it bigger than 0 + int64 timeout = 10; // timeout in seconds, it works only when it bigger than 0 } message PrivateParametersWithoutAsk { @@ -1342,7 +1461,7 @@ message PrivateParametersWithoutAsk { repeated ReceiveNote shielded_receives = 7; bytes transparent_to_address = 8; int64 to_amount = 9; - int64 timeout = 10; // timeout in seconds, it works only when it bigger than 0 + int64 timeout = 10; // timeout in seconds, it works only when it bigger than 0 } message SpendAuthSigParameters { @@ -1388,7 +1507,7 @@ message PaymentAddressMessage { string payment_address = 3; } -message ShieldedAddressInfo{ +message ShieldedAddressInfo { bytes sk = 1; bytes ask = 2; bytes nsk = 3; @@ -1507,4 +1626,4 @@ message ShieldedTRC20TriggerContractParameters { repeated BytesMessage spend_authority_signature = 2; string amount = 3; bytes transparent_to_address = 4; -} \ No newline at end of file +} diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index fcf0e8758..e6aac408f 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -2,6 +2,7 @@ syntax = "proto3"; import "google/protobuf/any.proto"; import "core/Discover.proto"; +import "core/contract/common.proto"; package protocol; @@ -58,6 +59,67 @@ message Exchange { int64 second_token_balance = 9; } +// market +message MarketOrder { + bytes order_id = 1; + bytes owner_address = 2; + int64 create_time = 3; + bytes sell_token_id = 4; + int64 sell_token_quantity = 5; + bytes buy_token_id = 6; + int64 buy_token_quantity = 7; // min to receive + int64 sell_token_quantity_remain = 9; + // When state != ACTIVE and sell_token_quantity_return !=0, + //it means that some sell tokens are returned to the account due to insufficient remaining amount + int64 sell_token_quantity_return = 10; + + enum State { + ACTIVE = 0; + INACTIVE = 1; + CANCELED = 2; + } + State state = 11; + + bytes prev = 12; + bytes next = 13; +} + +message MarketOrderList { + repeated MarketOrder orders = 1; +} + +message MarketOrderPairList { + repeated MarketOrderPair orderPair = 1; +} + +message MarketOrderPair{ + bytes sell_token_id = 1; + bytes buy_token_id = 2; +} + +message MarketAccountOrder { + bytes owner_address = 1; + repeated bytes orders = 2; // order_id list + int64 count = 3; // active count + int64 total_count = 4; +} + +message MarketPrice { + int64 sell_token_quantity = 1; + int64 buy_token_quantity = 2; +} + +message MarketPriceList { + bytes sell_token_id = 1; + bytes buy_token_id = 2; + repeated MarketPrice prices = 3; +} + +message MarketOrderIdList { + bytes head = 1; + bytes tail = 2; +} + message ChainParameters { repeated ChainParameter chainParameter = 1; message ChainParameter { @@ -100,6 +162,8 @@ message Account { int64 old_tron_power = 46; Frozen tron_power = 47; + bool asset_optimized = 60; + // this account create time int64 create_time = 0x09; // this last operation time, including transfer, voting and so on. //FIXME fix grammar @@ -128,6 +192,8 @@ message Account { // the identity of this account, case insensitive bytes account_id = 23; + int64 net_window_size = 24; + message AccountResource { // energy resource, get from frozen int64 energy_usage = 1; @@ -145,14 +211,32 @@ message Account { int64 storage_usage = 7; int64 latest_exchange_storage_time = 8; + int64 energy_window_size = 9; + + int64 delegated_frozenV2_balance_for_energy = 10; + int64 acquired_delegated_frozenV2_balance_for_energy = 11; } AccountResource account_resource = 26; bytes codeHash = 30; Permission owner_permission = 31; Permission witness_permission = 32; repeated Permission active_permission = 33; -} + message FreezeV2 { + ResourceCode type = 1; + int64 amount = 2; + } + message UnFreezeV2 { + ResourceCode type = 1; + int64 unfreeze_amount = 3; + int64 unfreeze_expire_time = 4; + } + repeated FreezeV2 frozenV2 = 34; + repeated UnFreezeV2 unfrozenV2 = 35; + + int64 delegated_frozenV2_balance_for_bandwidth = 36; + int64 acquired_delegated_frozenV2_balance_for_bandwidth = 37; +} message Key { bytes address = 1; @@ -237,6 +321,7 @@ message ResourceReceipt { int64 net_usage = 5; int64 net_fee = 6; Transaction.Result.contractResult result = 7; + int64 energy_penalty_total = 8; } message MarketOrderDetail { @@ -284,6 +369,11 @@ message Transaction { ShieldedTransferContract = 51; MarketSellAssetContract = 52; MarketCancelOrderContract = 53; + FreezeBalanceV2Contract = 54; + UnfreezeBalanceV2Contract = 55; + WithdrawExpireUnfreezeContract = 56; + DelegateResourceContract = 57; + UnDelegateResourceContract = 58; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -313,6 +403,7 @@ message Transaction { JVM_STACK_OVER_FLOW = 12; UNKNOWN = 13; TRANSFER_FAILED = 14; + INVALID_CODE = 15; } int64 fee = 1; code ret = 2; @@ -326,6 +417,11 @@ message Transaction { int64 exchange_withdraw_another_amount = 20; int64 exchange_id = 21; int64 shielded_transaction_fee = 22; + + + bytes orderId = 25; + repeated MarketOrderDetail orderDetails = 26; + int64 withdraw_expire_amount = 27; } message raw { @@ -334,9 +430,9 @@ message Transaction { bytes ref_block_hash = 4; int64 expiration = 8; repeated authority auths = 9; - // transaction note + // data not used bytes data = 10; - //only support size = 1, repeated list here for extension + //only support size = 1, repeated list here for extension repeated Contract contract = 11; // scripts not used bytes scripts = 12; @@ -345,7 +441,7 @@ message Transaction { } raw raw_data = 1; - // only support size = 1, repeated list here for muti-sig extension + // only support size = 1, repeated list here for muti-sig extension repeated bytes signature = 2; repeated Result ret = 5; } @@ -384,6 +480,8 @@ message TransactionInfo { bytes orderId = 25; repeated MarketOrderDetail orderDetails = 26; int64 packingFee = 27; + + int64 withdraw_expire_amount = 28; } message TransactionRet { @@ -413,7 +511,6 @@ message BlockHeader { bytes witness_address = 9; int32 version = 10; bytes accountStateRoot = 11; - bytes receiptsRoot = 12; } raw raw_data = 1; bytes witness_signature = 2; @@ -484,7 +581,7 @@ enum ReasonCode { TOO_MANY_PEERS = 0x04; DUPLICATE_PEER = 0x05; INCOMPATIBLE_PROTOCOL = 0x06; - NULL_IDENTITY = 0x07; + RANDOM_ELIMINATION = 0x07; PEER_QUITING = 0x08; UNEXPECTED_IDENTITY = 0x09; LOCAL_IDENTITY = 0x0A; @@ -502,6 +599,7 @@ enum ReasonCode { TIME_OUT = 0x20; CONNECT_FAIL = 0x21; TOO_MANY_PEERS_WITH_SAME_IP = 0x22; + LIGHT_NODE_SYNC_FAIL = 0x23; UNKNOWN = 0xFF; } @@ -521,6 +619,10 @@ message HelloMessage { BlockId genesisBlockId = 4; BlockId solidBlockId = 5; BlockId headBlockId = 6; + bytes address = 7; + bytes signature = 8; + int32 nodeType = 9; + int64 lowestBlockNum = 10; } message InternalTransaction { @@ -547,6 +649,7 @@ message DelegatedResourceAccountIndex { bytes account = 1; repeated bytes fromAccounts = 2; repeated bytes toAccounts = 3; + int64 timestamp = 4; } message NodeInfo { @@ -622,7 +725,7 @@ message NodeInfo { double cpuRate = 6; string javaVersion = 7; string osName = 8; - int64 jvmTotalMemoery = 9; + int64 jvmTotalMemory = 9; int64 jvmFreeMemory = 10; double processCpuRate = 11; repeated MemoryDescInfo memoryDescInfoList = 12; @@ -648,63 +751,138 @@ message NodeInfo { } } -// market -message MarketOrder { - bytes order_id = 1; - bytes owner_address = 2; - int64 create_time = 3; - bytes sell_token_id = 4; - int64 sell_token_quantity = 5; - bytes buy_token_id = 6; - int64 buy_token_quantity = 7; // min to receive - int64 sell_token_quantity_remain = 9; - // When state != ACTIVE and sell_token_quantity_return !=0, - //it means that some sell tokens are returned to the account due to insufficient remaining amount - int64 sell_token_quantity_return = 10; +message MetricsInfo { + int64 interval = 1; + NodeInfo node = 2; + BlockChainInfo blockchain = 3; + NetInfo net = 4; - enum State { - ACTIVE = 0; - INACTIVE = 1; - CANCELED = 2; + message NodeInfo { + string ip = 1; + int32 nodeType = 2; + string version = 3; + int32 backupStatus = 4; } - State state = 11; - bytes prev = 12; - bytes next = 13; -} + message BlockChainInfo { + int64 headBlockNum = 1; + int64 headBlockTimestamp = 2; + string headBlockHash = 3; + int32 forkCount = 4; + int32 failForkCount = 5; + RateInfo blockProcessTime = 6; + RateInfo tps = 7; + int32 transactionCacheSize = 8; + RateInfo missedTransaction = 9; + repeated Witness witnesses = 10; + int64 failProcessBlockNum = 11; + string failProcessBlockReason = 12; + repeated DupWitness dupWitness = 13; + + message Witness { + string address = 1; + int32 version = 2; + } -message MarketOrderList { - repeated MarketOrder orders = 1; -} + message DupWitness { + string address = 1; + int64 blockNum = 2; + int32 count = 3; + } + } -message MarketOrderPairList { - repeated MarketOrderPair orderPair = 1; -} + message RateInfo { + int64 count = 1; + double meanRate = 2; + double oneMinuteRate = 3; + double fiveMinuteRate = 4; + double fifteenMinuteRate = 5; + } -message MarketOrderPair{ - bytes sell_token_id = 1; - bytes buy_token_id = 2; -} + message NetInfo { + int32 errorProtoCount = 1; + ApiInfo api = 2; + int32 connectionCount = 3; + int32 validConnectionCount = 4; + RateInfo tcpInTraffic = 5; + RateInfo tcpOutTraffic = 6; + int32 disconnectionCount = 7; + repeated DisconnectionDetailInfo disconnectionDetail = 8; + RateInfo udpInTraffic = 9; + RateInfo udpOutTraffic = 10; + LatencyInfo latency = 11; + + message ApiInfo { + RateInfo qps = 1; + RateInfo failQps = 2; + RateInfo outTraffic = 3; + repeated ApiDetailInfo detail = 4; + + message ApiDetailInfo { + string name = 1; + RateInfo qps = 2; + RateInfo failQps = 3; + RateInfo outTraffic = 4; + } + } -message MarketAccountOrder { - bytes owner_address = 1; - repeated bytes orders = 2; // order_id list - int64 count = 3; // active count - int64 total_count = 4; + message DisconnectionDetailInfo { + string reason = 1; + int32 count = 2; + } + + message LatencyInfo { + int32 top99 = 1; + int32 top95 = 2; + int32 top75 = 3; + int32 totalCount = 4; + int32 delay1S = 5; + int32 delay2S = 6; + int32 delay3S = 7; + repeated LatencyDetailInfo detail = 8; + + message LatencyDetailInfo { + string witness = 1; + int32 top99 = 2; + int32 top95 = 3; + int32 top75 = 4; + int32 count = 5; + int32 delay1S = 6; + int32 delay2S = 7; + int32 delay3S = 8; + } + } + } } -message MarketPrice { - int64 sell_token_quantity = 1; - int64 buy_token_quantity = 2; +message PBFTMessage { + enum MsgType { + VIEW_CHANGE = 0; + REQUEST = 1; + PREPREPARE = 2; + PREPARE = 3; + COMMIT = 4; + } + enum DataType { + BLOCK = 0; + SRL = 1; + } + message Raw { + MsgType msg_type = 1; + DataType data_type = 2; + int64 view_n = 3; + int64 epoch = 4; + bytes data = 5; + } + Raw raw_data = 1; + bytes signature = 2; } -message MarketPriceList { - bytes sell_token_id = 1; - bytes buy_token_id = 2; - repeated MarketPrice prices = 3; +message PBFTCommitResult { + bytes data = 1; + repeated bytes signature = 2; } -message MarketOrderIdList { - bytes head = 1; - bytes tail = 2; +message SRL { + repeated bytes srAddress = 1; } diff --git a/src/main/protos/core/contract/smart_contract.proto b/src/main/protos/core/contract/smart_contract.proto index 40fbb5b7d..c913f7f75 100644 --- a/src/main/protos/core/contract/smart_contract.proto +++ b/src/main/protos/core/contract/smart_contract.proto @@ -9,89 +9,96 @@ option go_package = "github.com/tronprotocol/grpc-gateway/core"; import "core/Tron.proto"; message SmartContract { - message ABI { - message Entry { - enum EntryType { - UnknownEntryType = 0; - Constructor = 1; - Function = 2; - Event = 3; - Fallback = 4; - Receive = 5; - Error = 6; - } - message Param { - bool indexed = 1; - string name = 2; - string type = 3; - // SolidityType type = 3; - } - enum StateMutabilityType { - UnknownMutabilityType = 0; - Pure = 1; - View = 2; - Nonpayable = 3; - Payable = 4; - } + message ABI { + message Entry { + enum EntryType { + UnknownEntryType = 0; + Constructor = 1; + Function = 2; + Event = 3; + Fallback = 4; + Receive = 5; + Error = 6; + } + message Param { + bool indexed = 1; + string name = 2; + string type = 3; + // SolidityType type = 3; + } + enum StateMutabilityType { + UnknownMutabilityType = 0; + Pure = 1; + View = 2; + Nonpayable = 3; + Payable = 4; + } - bool anonymous = 1; - bool constant = 2; - string name = 3; - repeated Param inputs = 4; - repeated Param outputs = 5; - EntryType type = 6; - bool payable = 7; - StateMutabilityType stateMutability = 8; - } - repeated Entry entrys = 1; + bool anonymous = 1; + bool constant = 2; + string name = 3; + repeated Param inputs = 4; + repeated Param outputs = 5; + EntryType type = 6; + bool payable = 7; + StateMutabilityType stateMutability = 8; } - bytes origin_address = 1; - bytes contract_address = 2; - ABI abi = 3; - bytes bytecode = 4; - int64 call_value = 5; - int64 consume_user_resource_percent = 6; - string name = 7; - int64 origin_energy_limit = 8; - bytes code_hash = 9; - bytes trx_hash = 10; - int32 version = 11; + repeated Entry entrys = 1; + } + bytes origin_address = 1; + bytes contract_address = 2; + ABI abi = 3; + bytes bytecode = 4; + int64 call_value = 5; + int64 consume_user_resource_percent = 6; + string name = 7; + int64 origin_energy_limit = 8; + bytes code_hash = 9; + bytes trx_hash = 10; + int32 version = 11; +} + +message ContractState { + int64 energy_usage = 1; + int64 energy_factor = 2; + int64 update_cycle = 3; } message CreateSmartContract { - bytes owner_address = 1; - SmartContract new_contract = 2; - int64 call_token_value = 3; - int64 token_id = 4; + bytes owner_address = 1; + SmartContract new_contract = 2; + int64 call_token_value = 3; + int64 token_id = 4; } message TriggerSmartContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 call_value = 3; - bytes data = 4; - int64 call_token_value = 5; - int64 token_id = 6; + bytes owner_address = 1; + bytes contract_address = 2; + int64 call_value = 3; + bytes data = 4; + int64 call_token_value = 5; + int64 token_id = 6; } message ClearABIContract { - bytes owner_address = 1; - bytes contract_address = 2; + bytes owner_address = 1; + bytes contract_address = 2; } message UpdateSettingContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 consume_user_resource_percent = 3; + bytes owner_address = 1; + bytes contract_address = 2; + int64 consume_user_resource_percent = 3; } message UpdateEnergyLimitContract { - bytes owner_address = 1; - bytes contract_address = 2; - int64 origin_energy_limit = 3; + bytes owner_address = 1; + bytes contract_address = 2; + int64 origin_energy_limit = 3; } message SmartContractDataWrapper { - SmartContract smart_contract = 1; - bytes runtimecode = 2; -} + SmartContract smart_contract = 1; + bytes runtimecode = 2; + ContractState contract_state = 3; +} \ No newline at end of file From 963d9389e2a2de1a9a67754ac9e4ed36827abc81 Mon Sep 17 00:00:00 2001 From: Asuka Date: Fri, 24 Feb 2023 11:55:56 +0800 Subject: [PATCH 413/445] fix(abi): do not always set dynamic to true for array type --- src/main/java/org/tron/common/utils/AbiUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/tron/common/utils/AbiUtil.java b/src/main/java/org/tron/common/utils/AbiUtil.java index e56aaa6d3..dd310fe46 100644 --- a/src/main/java/org/tron/common/utils/AbiUtil.java +++ b/src/main/java/org/tron/common/utils/AbiUtil.java @@ -79,7 +79,6 @@ static class CoderArray extends Coder { if (length == -1) { this.dynamic = true; } - this.dynamic = true; } @Override From 91d49948345c333cdeccc6b5783d28e24518a0ea Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Fri, 31 Mar 2023 17:04:20 +0800 Subject: [PATCH 414/445] feat(net): add BELOW_THAN_ME for ReasonCode. --- src/main/protos/core/Tron.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index e6aac408f..437f5bf85 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -600,6 +600,7 @@ enum ReasonCode { CONNECT_FAIL = 0x21; TOO_MANY_PEERS_WITH_SAME_IP = 0x22; LIGHT_NODE_SYNC_FAIL = 0x23; + BELOW_THAN_ME = 0X24; UNKNOWN = 0xFF; } From def16e3651351b5c736838f107de7c601794222b Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Thu, 30 Mar 2023 17:03:19 +0800 Subject: [PATCH 415/445] feat(api): remove insecure APIs --- README.md | 11 +- .../common/utils/HttpSelfFormatFieldName.java | 43 +++-- .../demo/EasyTransferAssetByPrivateDemo.java | 31 ---- .../org/tron/demo/EasyTransferAssetDemo.java | 47 ------ .../tron/demo/EasyTransferByPrivateDemo.java | 30 ---- .../java/org/tron/demo/EasyTransferDemo.java | 44 ----- .../java/org/tron/demo/MultiSignDemo.java | 42 ----- .../org/tron/demo/TransactionSignDemo.java | 157 ------------------ src/main/java/org/tron/walletcli/Client.java | 15 -- .../org/tron/walletcli/WalletApiWrapper.java | 8 - .../org/tron/walletserver/GrpcClient.java | 79 --------- .../java/org/tron/walletserver/WalletApi.java | 75 --------- src/main/protos/api/api.proto | 116 ------------- src/main/protos/core/Tron.proto | 5 - 14 files changed, 34 insertions(+), 669 deletions(-) delete mode 100644 src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java delete mode 100644 src/main/java/org/tron/demo/EasyTransferAssetDemo.java delete mode 100644 src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java delete mode 100644 src/main/java/org/tron/demo/EasyTransferDemo.java delete mode 100644 src/main/java/org/tron/demo/MultiSignDemo.java delete mode 100644 src/main/java/org/tron/demo/TransactionSignDemo.java diff --git a/README.md b/README.md index cbbfdebaf..75aee3dcb 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,15 @@ For more information on a specific command, just type the command on terminal wh | [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) | [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | | [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | -| [FreezeBalance](#How-to-delegate-resourcee) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [FreezeBalance](#How-to-delegate-resourcee) |[GetCanWithdrawUnfreezeAmount](#How-to-freezev2)| [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | | [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | | [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | | [GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | | [GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) | [GetBrokerage](#Brokerage) | | [GetContract](#How-to-use-smart-contracts) | [GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | -| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | -| [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | +| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | +| [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | | [GetMarketPairList](#How-to-use-tron-dex-to-sell-asset)| [GetMarketPriceByPair](#How-to-use-tron-dex-to-sell-asset)| [GetNextMaintenanceTime](#Some-others) | | [GetNkFromNsk](#How-to-transfer-shielded-TRC20-token) | [GetProposal](#Get-proposal-information) | [GetShieldedPaymentAddress](#How-to-transfer-shielded-TRC20-token)| | [GetSpendingKey](#How-to-transfer-shielded-TRC20-token) | [GetReward](#Brokerage) | [GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) | @@ -98,7 +98,7 @@ For more information on a specific command, just type the command on terminal wh | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | | [WithdrawExpireUnfreeze](#How-to-freezev2) | [GetDelegatedResourceV2](#How-to-freezev2) | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | -| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) | +| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | Type any one of the listed commands, to display how-to tips. @@ -1458,9 +1458,6 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 ## Account related commands -**GenerateAddress** -> Generate an address and print out the public and private keys - **GetAccount** > Get account information based on address diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index 83ca4f30a..bfff7ca22 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -13,14 +13,6 @@ public class HttpSelfFormatFieldName { //DelegatedResourceMessage AddressFieldNameMap.put("protocol.DelegatedResourceMessage.fromAddress", 1); AddressFieldNameMap.put("protocol.DelegatedResourceMessage.toAddress", 1); - //EasyTransferMessage - AddressFieldNameMap.put("protocol.EasyTransferMessage.toAddress", 1); - //EasyTransferAssetMessage - AddressFieldNameMap.put("protocol.EasyTransferAssetMessage.toAddress", 1); - //EasyTransferByPrivateMessage - AddressFieldNameMap.put("protocol.EasyTransferByPrivateMessage.toAddress", 1); - //EasyTransferAssetByPrivateMessage - AddressFieldNameMap.put("protocol.EasyTransferAssetByPrivateMessage.toAddress", 1); //TransactionSignWeight AddressFieldNameMap.put("protocol.TransactionSignWeight.approved_list", 1); //TransactionApprovedList @@ -31,6 +23,30 @@ public class HttpSelfFormatFieldName { //PrivateParametersWithoutAsk AddressFieldNameMap.put("protocol.PrivateParametersWithoutAsk.transparent_from_address", 1); AddressFieldNameMap.put("protocol.PrivateParametersWithoutAsk.transparent_to_address", 1); + //PrivateShieldedTRC20Parameters + AddressFieldNameMap.put( + "protocol.PrivateShieldedTRC20Parameters.transparent_to_address", 1); + AddressFieldNameMap.put( + "protocol.PrivateShieldedTRC20Parameters.shielded_TRC20_contract_address", 1); + //PrivateShieldedTRC20ParametersWithoutAsk + AddressFieldNameMap.put( + "protocol.PrivateShieldedTRC20ParametersWithoutAsk.transparent_to_address", 1); + AddressFieldNameMap.put( + "protocol.PrivateShieldedTRC20ParametersWithoutAsk.shielded_TRC20_contract_address", 1); + //IvkDecryptTRC20Parameters + AddressFieldNameMap.put( + "protocol.IvkDecryptTRC20Parameters.shielded_TRC20_contract_address", 1); + //OvkDecryptTRC20Parameters + AddressFieldNameMap.put( + "protocol.OvkDecryptTRC20Parameters.shielded_TRC20_contract_address", 1); + //NfTRC20Parameters + AddressFieldNameMap.put( + "protocol.NfTRC20Parameters.shielded_TRC20_contract_address", 1); + //ShieldedTRC20TriggerContractParameters + AddressFieldNameMap.put( + "protocol.ShieldedTRC20TriggerContractParameters.transparent_to_address", 1); + AddressFieldNameMap.put( + "protocol.DecryptNotesTRC20.NoteTx.transparent_to_address", 1); //***** Contract.proto ***** //AccountCreateContract @@ -111,10 +127,11 @@ public class HttpSelfFormatFieldName { AddressFieldNameMap.put("protocol.ExchangeTransactionContract.owner_address", 1); //AccountPermissionUpdateContract AddressFieldNameMap.put("protocol.AccountPermissionUpdateContract.owner_address", 1); + //UpdateBrokerageContract + AddressFieldNameMap.put("protocol.UpdateBrokerageContract.owner_address", 1); //ShieldedTransferContract AddressFieldNameMap.put("protocol.ShieldedTransferContract.transparent_from_address", 1); AddressFieldNameMap.put("protocol.ShieldedTransferContract.transparent_to_address", 1); - //FreezeBalanceV2Contract AddressFieldNameMap.put("protocol.FreezeBalanceV2Contract.owner_address", 1); //UnfreezeBalanceV2Contract @@ -171,15 +188,14 @@ public class HttpSelfFormatFieldName { AddressFieldNameMap.put("protocol.DelegatedResourceAccountIndex.fromAccounts", 1); AddressFieldNameMap.put("protocol.DelegatedResourceAccountIndex.toAccounts", 1); + AddressFieldNameMap.put("protocol.AccountIdentifier.address", 1); + AddressFieldNameMap.put("protocol.TransactionBalanceTrace.Operation.address", 1); + //***** api.proto ***** //Return NameFieldNameMap.put("protocol.Return.message", 1); //Address NameFieldNameMap.put("protocol.Address.host", 1); - //EasyTransferMessage - NameFieldNameMap.put("protocol.EasyTransferMessage.passPhrase", 1); - //EasyTransferAssetMessage - NameFieldNameMap.put("protocol.EasyTransferAssetMessage.passPhrase", 1); //Note NameFieldNameMap.put("protocol.Note.memo", 1); @@ -232,6 +248,7 @@ public class HttpSelfFormatFieldName { //TransactionInfo NameFieldNameMap.put("protocol.TransactionInfo.resMessage", 1); + //***** market.proto ***** // MarketSellAssetContract AddressFieldNameMap.put("protocol.MarketSellAssetContract.owner_address", 1); NameFieldNameMap.put("protocol.MarketSellAssetContract.sell_token_id", 1); diff --git a/src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java b/src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java deleted file mode 100644 index f9ea72923..000000000 --- a/src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -public class EasyTransferAssetByPrivateDemo { - - public static void main(String[] args) { - String privateKey = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - String toAddress = "TKMZBoWbXbYedcBnQugYT7DaFnSgi9qg78"; - String tokenId = "1000001"; - EasyTransferResponse response = WalletApi - .easyTransferAssetByPrivate(ByteArray.fromHexString(privateKey), - WalletApi.decodeFromBase58Check(toAddress), tokenId, 1000000L); - - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java b/src/main/java/org/tron/demo/EasyTransferAssetDemo.java deleted file mode 100644 index b64faf72f..000000000 --- a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class EasyTransferAssetDemo { - - private static byte[] getAddressByPassphrase(String passPhrase) { - byte[] privateKey = Sha256Sm3Hash.hash(passPhrase.getBytes()); - ECKey ecKey = ECKey.fromPrivate(privateKey); - byte[] address = ecKey.getAddress(); - return address; - } - - public static void main(String[] args) { - String passPhrase = "test pass phrase"; - byte[] address = WalletApi.createAdresss(passPhrase.getBytes()); - String tokenId = "1000001"; - if (!Arrays.equals(address, getAddressByPassphrase(passPhrase))) { - System.out.println("The address is diffrent !!"); - } - System.out.println("address === " + WalletApi.encode58Check(address)); - - EasyTransferResponse response = WalletApi - .easyTransferAsset( - passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), - tokenId, 10000L); - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java b/src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java deleted file mode 100644 index 66c056742..000000000 --- a/src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -public class EasyTransferByPrivateDemo { - - public static void main(String[] args) { - String privateKey = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - String toAddress = "TKMZBoWbXbYedcBnQugYT7DaFnSgi9qg78"; - EasyTransferResponse response = WalletApi - .easyTransferByPrivate(ByteArray.fromHexString(privateKey), - WalletApi.decodeFromBase58Check(toAddress), 1000000L); - - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/EasyTransferDemo.java b/src/main/java/org/tron/demo/EasyTransferDemo.java deleted file mode 100644 index 1a57ff642..000000000 --- a/src/main/java/org/tron/demo/EasyTransferDemo.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class EasyTransferDemo { - - private static byte[] getAddressByPassphrase(String passPhrase) { - byte[] privateKey = Sha256Sm3Hash.hash(passPhrase.getBytes()); - ECKey ecKey = ECKey.fromPrivate(privateKey); - byte[] address = ecKey.getAddress(); - return address; - } - - public static void main(String[] args) { - String passPhrase = "test pass phrase"; - byte[] address = WalletApi.createAdresss(passPhrase.getBytes()); - if (!Arrays.equals(address, getAddressByPassphrase(passPhrase))) { - System.out.println("The address is diffrent !!"); - } - System.out.println("address === " + WalletApi.encode58Check(address)); - - EasyTransferResponse response = WalletApi - .easyTransfer(passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), 10000L); - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/MultiSignDemo.java b/src/main/java/org/tron/demo/MultiSignDemo.java deleted file mode 100644 index 95b5aa0d5..000000000 --- a/src/main/java/org/tron/demo/MultiSignDemo.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.TransactionExtention; -import org.tron.api.GrpcAPI.TransactionSignWeight; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.core.exception.CancelException; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -public class MultiSignDemo { - - public static void main(String[] args) throws CancelException { - String to = "TL5mpGbtr5L2Gi7CtotBQzjN8pK7SmbyFz"; - String owner = "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW"; - String private0 = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - String private1 = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; - String private2 = "8E812436A0E3323166E1F0E8BA79E19E217B2C4A53C970D4CCA0CFB1078979DF"; - long amount = 10_000_000_000L; - Transaction transaction = TransactionSignDemo - .createTransaction(WalletApi.decodeFromBase58Check(owner), - WalletApi.decodeFromBase58Check(to), amount); - TransactionExtention transactionExtention = WalletApi - .addSignByApi(transaction, ByteArray.fromHexString(private0)); - // System.out.println(Utils.printTransaction(transactionExtention)); - TransactionSignWeight transactionSignWeight = WalletApi - .getTransactionSignWeight(transactionExtention.getTransaction()); - // System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); - - transactionExtention = WalletApi - .addSignByApi(transactionExtention.getTransaction(), ByteArray.fromHexString(private1)); - System.out.println(Utils.printTransaction(transactionExtention)); - transactionSignWeight = WalletApi - .getTransactionSignWeight(transactionExtention.getTransaction()); -// System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); - - transactionExtention = WalletApi - .addSignByApi(transactionExtention.getTransaction(), ByteArray.fromHexString(private2)); -// System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); - // System.out.println(Utils.printTransaction(transactionExtention)); - } -} diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java deleted file mode 100644 index b787981c7..000000000 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.tron.demo; - -import com.google.protobuf.Any; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.TransactionExtention; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.common.utils.ByteArray; -import org.tron.core.exception.CancelException; -import org.tron.protos.Protocol.Block; -import org.tron.protos.Protocol.Transaction; -import org.tron.protos.contract.BalanceContract.TransferContract; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class TransactionSignDemo { - - public static Transaction setReference(Transaction transaction, Block newestBlock) { - long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); - byte[] blockHash = getBlockHash(newestBlock).getBytes(); - byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); - return transaction.toBuilder().setRawData(rawData).build(); - } - - public static Sha256Sm3Hash getBlockHash(Block block) { - return Sha256Sm3Hash.of(block.getBlockHeader().getRawData().toByteArray()); - } - - public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray())); - return txid; - } - - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { - Transaction.Builder transactionBuilder = Transaction.newBuilder(); - Block newestBlock = WalletApi.getBlock(-1); - - Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - TransferContract.Builder transferContractBuilder = TransferContract.newBuilder(); - transferContractBuilder.setAmount(amount); - ByteString bsTo = ByteString.copyFrom(to); - ByteString bsOwner = ByteString.copyFrom(from); - transferContractBuilder.setToAddress(bsTo); - transferContractBuilder.setOwnerAddress(bsOwner); - try { - Any any = Any.pack(transferContractBuilder.build()); - contractBuilder.setParameter(any); - } catch (Exception e) { - return null; - } - contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) - .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); - Transaction transaction = transactionBuilder.build(); - Transaction refTransaction = setReference(transaction, newestBlock); - return refTransaction; - } - - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) - throws InvalidProtocolBufferException { - ECKey ecKey = ECKey.fromPrivate(privateKey); - Transaction transaction1 = Transaction.parseFrom(transaction); - byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = Sha256Sm3Hash.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); - return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); - } - - private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) - throws InvalidProtocolBufferException { - ECKey ecKey = ECKey.fromPrivate(privateKey); - Transaction transaction1 = Transaction.parseFrom(transaction); - byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = Sha256Sm3Hash.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); - return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); - } - - private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException { - return WalletApi.broadcastTransaction(transactionBytes); - } - - private static void base58checkToHexString() { - String base58check = "TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"; - String hexString = ByteArray.toHexString(WalletApi.decodeFromBase58Check(base58check)); - System.out.println(hexString); - } - - private static void hexStringTobase58check() { - String hexString = "414948c2e8a756d9437037dcd8c7e0c73d560ca38d"; - String base58check = WalletApi.encode58Check(ByteArray.fromHexString(hexString)); - System.out.println(base58check); - } - - public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - byte[] privateBytes = ByteArray.fromHexString(privateStr); - ECKey ecKey = ECKey.fromPrivate(privateBytes); - byte[] from = ecKey.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in Sun, and 1 trx = 1000000 Sun - Transaction transaction = createTransaction(from, to, amount); - byte[] transactionBytes = transaction.toByteArray(); - - /* - //sign a transaction - Transaction transaction1 = TransactionUtils.sign(transaction, ecKey); - //get byte transaction - byte[] transaction2 = transaction1.toByteArray(); - System.out.println("transaction2 ::::: " + ByteArray.toHexString(transaction2)); - - //sign a transaction in byte format and return a Transaction object - Transaction transaction3 = signTransaction2Object(transactionBytes, privateBytes); - System.out.println("transaction3 ::::: " + ByteArray.toHexString(transaction3.toByteArray())); - */ - - // sign a transaction in byte format and return a Transaction in byte format - byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); - System.out.println("transaction4 ::::: " + ByteArray.toHexString(transaction4)); - Transaction transactionSigned; - if (WalletApi.getRpcVersion() == 2) { - TransactionExtention transactionExtention = - WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); - if (transactionExtention == null) { - System.out.println("transactionExtention is null"); - return; - } - Return ret = transactionExtention.getResult(); - if (!ret.getResult()) { - System.out.println("Code = " + ret.getCode()); - System.out.println("Message = " + ret.getMessage().toStringUtf8()); - return; - } - System.out.println( - "Receive txid = " + ByteArray.toHexString(transactionExtention.getTxid().toByteArray())); - transactionSigned = transactionExtention.getTransaction(); - } else { - transactionSigned = WalletApi.signTransactionByApi(transaction, ecKey.getPrivKeyBytes()); - } - byte[] transaction5 = transactionSigned.toByteArray(); - System.out.println("transaction5 ::::: " + ByteArray.toHexString(transaction5)); - if (!Arrays.equals(transaction4, transaction5)) { - System.out.println("transaction4 is not equals to transaction5 !!!!!"); - } - boolean result = broadcast(transaction4); - - System.out.println(result); - } -} diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index c0ca0ce10..8f3f27c73 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -100,7 +100,6 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "FreezeBalanceV2", - "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", @@ -242,7 +241,6 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "FreezeBalanceV2", - "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", @@ -2839,15 +2837,6 @@ private void getContractInfo(String[] parameters) { } } - private void generateAddress() { - AddressPrKeyPairMessage result = walletApiWrapper.generateAddress(); - if (null != result) { - System.out.println(Utils.formatMessageString(result)); - } else { - System.out.println("GenerateAddress failed !!!"); - } - } - private void updateAccountPermission(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 2) { @@ -4732,10 +4721,6 @@ private void run() { getContractInfo(parameters); break; } - case "generateaddress": { - generateAddress(); - break; - } case "updateaccountpermission": { updateAccountPermission(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index eb587d64b..dec4b03b1 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -309,14 +309,6 @@ public boolean createAccount(byte[] ownerAddress, byte[] address) return wallet.createAccount(ownerAddress, address); } - public AddressPrKeyPairMessage generateAddress() { - if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: createAccount failed, Please login first !!"); - return null; - } - return WalletApi.generateAddress(); - } - public boolean createWitness(byte[] ownerAddress, String url) throws CipherException, IOException, CancelException { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index eac321095..ca44b39a9 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -62,7 +62,6 @@ import org.tron.protos.contract.SmartContractOuterClass.SmartContract; import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.TransactionInfo; -import org.tron.protos.Protocol.TransactionSign; import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; @@ -138,21 +137,6 @@ public Account queryAccountById(String accountId) { } } - //Warning: do not invoke this interface provided by others. - public Transaction signTransaction(TransactionSign transactionSign) { - return blockingStubFull.getTransactionSign(transactionSign); - } - - //Warning: do not invoke this interface provided by others. - public TransactionExtention signTransaction2(TransactionSign transactionSign) { - return blockingStubFull.getTransactionSign2(transactionSign); - } - - //Warning: do not invoke this interface provided by others. - public TransactionExtention addSign(TransactionSign transactionSign) { - return blockingStubFull.addSign(transactionSign); - } - public TransactionSignWeight getTransactionSignWeight(Transaction transaction) { return blockingStubFull.getTransactionSignWeight(transaction); } @@ -161,61 +145,6 @@ public TransactionApprovedList getTransactionApprovedList(Transaction transactio return blockingStubFull.getTransactionApprovedList(transaction); } - //Warning: do not invoke this interface provided by others. - public byte[] createAdresss(byte[] passPhrase) { - BytesMessage.Builder builder = BytesMessage.newBuilder(); - builder.setValue(ByteString.copyFrom(passPhrase)); - - BytesMessage result = blockingStubFull.createAddress(builder.build()); - return result.getValue().toByteArray(); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, long amount) { - EasyTransferMessage.Builder builder = EasyTransferMessage.newBuilder(); - builder.setPassPhrase(ByteString.copyFrom(passPhrase)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAmount(amount); - - return blockingStubFull.easyTransfer(builder.build()); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { - EasyTransferByPrivateMessage.Builder builder = EasyTransferByPrivateMessage.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAmount(amount); - - return blockingStubFull.easyTransferByPrivate(builder.build()); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { - EasyTransferAssetMessage.Builder builder = EasyTransferAssetMessage.newBuilder(); - builder.setPassPhrase(ByteString.copyFrom(passPhrase)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAssetId(assetId); - builder.setAmount(amount); - - return blockingStubFull.easyTransferAsset(builder.build()); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, byte[] toAddress, - String assetId, long amount) { - EasyTransferAssetByPrivateMessage.Builder builder = EasyTransferAssetByPrivateMessage - .newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAssetId(assetId); - builder.setAmount(amount); - - return blockingStubFull.easyTransferAssetByPrivate(builder.build()); - } - public Transaction createTransaction(AccountUpdateContract contract) { return blockingStubFull.updateAccount(contract); } @@ -544,14 +473,6 @@ public TransactionExtention createAccount2(AccountCreateContract contract) { return blockingStubFull.createAccount2(contract); } - public AddressPrKeyPairMessage generateAddress(EmptyMessage emptyMessage) { - if (blockingStubSolidity != null) { - return blockingStubSolidity.generateAddress(emptyMessage); - } else { - return blockingStubFull.generateAddress(emptyMessage); - } - } - public Transaction createWitness(WitnessCreateContract contract) { return blockingStubFull.createWitness(contract); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index a0a57e349..ce0d535c6 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -31,7 +31,6 @@ import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.AccountNetMessage; import org.tron.api.GrpcAPI.AccountResourceMessage; -import org.tron.api.GrpcAPI.AddressPrKeyPairMessage; import org.tron.api.GrpcAPI.AssetIssueList; import org.tron.api.GrpcAPI.BlockExtention; import org.tron.api.GrpcAPI.BlockList; @@ -42,8 +41,6 @@ import org.tron.api.GrpcAPI.DecryptNotesTRC20; import org.tron.api.GrpcAPI.DelegatedResourceList; import org.tron.api.GrpcAPI.DiversifierMessage; -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.api.GrpcAPI.EmptyMessage; import org.tron.api.GrpcAPI.EstimateEnergyMessage; import org.tron.api.GrpcAPI.ExchangeList; import org.tron.api.GrpcAPI.ExpandedSpendingKeyMessage; @@ -114,7 +111,6 @@ import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.Protocol.Transaction.Result; import org.tron.protos.Protocol.TransactionInfo; -import org.tron.protos.Protocol.TransactionSign; import org.tron.protos.Protocol.Witness; import org.tron.protos.contract.AccountContract.AccountCreateContract; import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; @@ -647,42 +643,6 @@ private boolean processTransaction(Transaction transaction) return rpcCli.broadcastTransaction(transaction); } - // Warning: do not invoke this interface provided by others. - public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) - throws CancelException { - transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please input permission id."; - transaction = TransactionUtils.setPermissionId(transaction, tipsString); - TransactionSign.Builder builder = TransactionSign.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setTransaction(transaction); - return rpcCli.signTransaction(builder.build()); - } - - // Warning: do not invoke this interface provided by others. - public static TransactionExtention signTransactionByApi2( - Transaction transaction, byte[] privateKey) throws CancelException { - transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please input permission id."; - transaction = TransactionUtils.setPermissionId(transaction, tipsString); - TransactionSign.Builder builder = TransactionSign.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setTransaction(transaction); - return rpcCli.signTransaction2(builder.build()); - } - - // Warning: do not invoke this interface provided by others. - public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) - throws CancelException { - transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please input permission id."; - transaction = TransactionUtils.setPermissionId(transaction, tipsString); - TransactionSign.Builder builder = TransactionSign.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setTransaction(transaction); - return rpcCli.addSign(builder.build()); - } - public static TransactionSignWeight getTransactionSignWeight(Transaction transaction) { return rpcCli.getTransactionSignWeight(transaction); } @@ -691,35 +651,6 @@ public static TransactionApprovedList getTransactionApprovedList(Transaction tra return rpcCli.getTransactionApprovedList(transaction); } - // Warning: do not invoke this interface provided by others. - public static byte[] createAdresss(byte[] passPhrase) { - return rpcCli.createAdresss(passPhrase); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransfer( - byte[] passPhrase, byte[] toAddress, long amount) { - return rpcCli.easyTransfer(passPhrase, toAddress, amount); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferByPrivate( - byte[] privateKey, byte[] toAddress, long amount) { - return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAsset( - byte[] passPhrase, byte[] toAddress, String assetId, long amount) { - return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAssetByPrivate( - byte[] privateKey, byte[] toAddress, String assetId, long amount) { - return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); - } - public boolean sendCoin(byte[] owner, byte[] to, long amount) throws CipherException, IOException, CancelException { if (owner == null) { @@ -856,12 +787,6 @@ public boolean createAccount(byte[] owner, byte[] address) } } - // Warning: do not invoke this interface provided by others. - public static AddressPrKeyPairMessage generateAddress() { - EmptyMessage.Builder builder = EmptyMessage.newBuilder(); - return rpcCli.generateAddress(builder.build()); - } - public boolean createWitness(byte[] owner, byte[] url) throws CipherException, IOException, CancelException { if (owner == null) { diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 95871f5aa..70297d095 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -624,68 +624,6 @@ service Wallet { } }; } - //Warning: do not invoke this interface provided by others. - //Please use GetTransactionSign2 instead of this function. - rpc GetTransactionSign (TransactionSign) returns (Transaction) { - option (google.api.http) = { - post: "/wallet/gettransactionsign" - body: "*" - additional_bindings { - get: "/wallet/gettransactionsign" - } - }; - }; - //Warning: do not invoke this interface provided by others. - //Use this function instead of GetTransactionSign. - rpc GetTransactionSign2 (TransactionSign) returns (TransactionExtention) { - }; - //Warning: do not invoke this interface provided by others. - rpc CreateAddress (BytesMessage) returns (BytesMessage) { - option (google.api.http) = { - post: "/wallet/createaddress" - body: "*" - additional_bindings { - get: "/wallet/createaddress" - } - }; - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransferAsset (EasyTransferAssetMessage) returns (EasyTransferResponse) { - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransferAssetByPrivate (EasyTransferAssetByPrivateMessage) returns (EasyTransferResponse) { - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { - option (google.api.http) = { - post: "/wallet/easytransfer" - body: "*" - additional_bindings { - get: "/wallet/easytransfer" - } - }; - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransferByPrivate (EasyTransferByPrivateMessage) returns (EasyTransferResponse) { - option (google.api.http) = { - post: "/wallet/easytransferbyprivate" - body: "*" - additional_bindings { - get: "/wallet/easytransferbyprivate" - } - }; - }; - //Warning: do not invoke this interface provided by others. - rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { - - option (google.api.http) = { - post: "/wallet/generateaddress" - body: "*" - additional_bindings { - get: "/wallet/generateaddress" - } - }; - } rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { option (google.api.http) = { @@ -707,10 +645,6 @@ service Wallet { }; } - rpc AddSign (TransactionSign) returns (TransactionExtention) { - - } - rpc GetTransactionSignWeight (Transaction) returns (TransactionSignWeight) { } @@ -968,17 +902,6 @@ service WalletSolidity { }; } - //Warning: do not invoke this interface provided by others. - rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { - option (google.api.http) = { - post: "/walletsolidity/generateaddress" - body: "*" - additional_bindings { - get: "/walletsolidity/generateaddress" - } - }; - } - rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { } @@ -1165,7 +1088,6 @@ message GetAvailableUnfreezeCountResponseMessage { int64 count = 1; } -//GetCanDelegatedMaxSize message CanDelegatedMaxSizeRequestMessage { int32 type = 1; bytes owner_address = 2; @@ -1174,7 +1096,6 @@ message CanDelegatedMaxSizeResponseMessage { int64 max_size = 1; } -//GetCanWithdrawUnfreezeAmount message CanWithdrawUnfreezeAmountRequestMessage { bytes owner_address = 1; int64 timestamp = 2; @@ -1271,43 +1192,6 @@ message PaginatedMessage { int64 limit = 2; } -message EasyTransferMessage { - bytes passPhrase = 1; - bytes toAddress = 2; - int64 amount = 3; -} - -message EasyTransferAssetMessage { - bytes passPhrase = 1; - bytes toAddress = 2; - string assetId = 3; - int64 amount = 4; -} - -message EasyTransferByPrivateMessage { - bytes privateKey = 1; - bytes toAddress = 2; - int64 amount = 3; -} - -message EasyTransferAssetByPrivateMessage { - bytes privateKey = 1; - bytes toAddress = 2; - string assetId = 3; - int64 amount = 4; -} - -message EasyTransferResponse { - Transaction transaction = 1; - Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) -} - -message AddressPrKeyPairMessage { - string address = 1; - string privateKey = 2; -} - message TransactionExtention { Transaction transaction = 1; bytes txid = 2; //transaction id = sha256(transaction.rowdata) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 437f5bf85..e5145484f 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -494,11 +494,6 @@ message Transactions { repeated Transaction transactions = 1; } -message TransactionSign { - Transaction transaction = 1; - bytes privateKey = 2; -} - message BlockHeader { message raw { int64 timestamp = 1; From 8a2eeebe1fa7cfe5ccc71c2f12370f6378f41022 Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Thu, 30 Mar 2023 17:03:19 +0800 Subject: [PATCH 416/445] feat(api): remove insecure APIs --- README.md | 11 +- .../common/utils/HttpSelfFormatFieldName.java | 12 -- .../demo/EasyTransferAssetByPrivateDemo.java | 31 ---- .../org/tron/demo/EasyTransferAssetDemo.java | 47 ------ .../tron/demo/EasyTransferByPrivateDemo.java | 30 ---- .../java/org/tron/demo/EasyTransferDemo.java | 44 ----- .../java/org/tron/demo/MultiSignDemo.java | 42 ----- .../org/tron/demo/TransactionSignDemo.java | 157 ------------------ src/main/java/org/tron/walletcli/Client.java | 15 -- .../org/tron/walletcli/WalletApiWrapper.java | 8 - .../org/tron/walletserver/GrpcClient.java | 79 --------- .../java/org/tron/walletserver/WalletApi.java | 75 --------- src/main/protos/api/api.proto | 116 ------------- src/main/protos/core/Tron.proto | 5 - 14 files changed, 4 insertions(+), 668 deletions(-) delete mode 100644 src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java delete mode 100644 src/main/java/org/tron/demo/EasyTransferAssetDemo.java delete mode 100644 src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java delete mode 100644 src/main/java/org/tron/demo/EasyTransferDemo.java delete mode 100644 src/main/java/org/tron/demo/MultiSignDemo.java delete mode 100644 src/main/java/org/tron/demo/TransactionSignDemo.java diff --git a/README.md b/README.md index cbbfdebaf..75aee3dcb 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,15 @@ For more information on a specific command, just type the command on terminal wh | [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) | [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | | [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | -| [FreezeBalance](#How-to-delegate-resourcee) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [FreezeBalance](#How-to-delegate-resourcee) |[GetCanWithdrawUnfreezeAmount](#How-to-freezev2)| [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | | [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | | [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | | [GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | | [GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) | [GetBrokerage](#Brokerage) | | [GetContract](#How-to-use-smart-contracts) | [GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | -| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | -| [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | +| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | +| [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | | [GetMarketPairList](#How-to-use-tron-dex-to-sell-asset)| [GetMarketPriceByPair](#How-to-use-tron-dex-to-sell-asset)| [GetNextMaintenanceTime](#Some-others) | | [GetNkFromNsk](#How-to-transfer-shielded-TRC20-token) | [GetProposal](#Get-proposal-information) | [GetShieldedPaymentAddress](#How-to-transfer-shielded-TRC20-token)| | [GetSpendingKey](#How-to-transfer-shielded-TRC20-token) | [GetReward](#Brokerage) | [GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) | @@ -98,7 +98,7 @@ For more information on a specific command, just type the command on terminal wh | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | | [WithdrawExpireUnfreeze](#How-to-freezev2) | [GetDelegatedResourceV2](#How-to-freezev2) | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | -| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) | +| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | Type any one of the listed commands, to display how-to tips. @@ -1458,9 +1458,6 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 ## Account related commands -**GenerateAddress** -> Generate an address and print out the public and private keys - **GetAccount** > Get account information based on address diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index 83ca4f30a..1d0ae5acb 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -13,14 +13,6 @@ public class HttpSelfFormatFieldName { //DelegatedResourceMessage AddressFieldNameMap.put("protocol.DelegatedResourceMessage.fromAddress", 1); AddressFieldNameMap.put("protocol.DelegatedResourceMessage.toAddress", 1); - //EasyTransferMessage - AddressFieldNameMap.put("protocol.EasyTransferMessage.toAddress", 1); - //EasyTransferAssetMessage - AddressFieldNameMap.put("protocol.EasyTransferAssetMessage.toAddress", 1); - //EasyTransferByPrivateMessage - AddressFieldNameMap.put("protocol.EasyTransferByPrivateMessage.toAddress", 1); - //EasyTransferAssetByPrivateMessage - AddressFieldNameMap.put("protocol.EasyTransferAssetByPrivateMessage.toAddress", 1); //TransactionSignWeight AddressFieldNameMap.put("protocol.TransactionSignWeight.approved_list", 1); //TransactionApprovedList @@ -176,10 +168,6 @@ public class HttpSelfFormatFieldName { NameFieldNameMap.put("protocol.Return.message", 1); //Address NameFieldNameMap.put("protocol.Address.host", 1); - //EasyTransferMessage - NameFieldNameMap.put("protocol.EasyTransferMessage.passPhrase", 1); - //EasyTransferAssetMessage - NameFieldNameMap.put("protocol.EasyTransferAssetMessage.passPhrase", 1); //Note NameFieldNameMap.put("protocol.Note.memo", 1); diff --git a/src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java b/src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java deleted file mode 100644 index f9ea72923..000000000 --- a/src/main/java/org/tron/demo/EasyTransferAssetByPrivateDemo.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -public class EasyTransferAssetByPrivateDemo { - - public static void main(String[] args) { - String privateKey = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - String toAddress = "TKMZBoWbXbYedcBnQugYT7DaFnSgi9qg78"; - String tokenId = "1000001"; - EasyTransferResponse response = WalletApi - .easyTransferAssetByPrivate(ByteArray.fromHexString(privateKey), - WalletApi.decodeFromBase58Check(toAddress), tokenId, 1000000L); - - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java b/src/main/java/org/tron/demo/EasyTransferAssetDemo.java deleted file mode 100644 index b64faf72f..000000000 --- a/src/main/java/org/tron/demo/EasyTransferAssetDemo.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class EasyTransferAssetDemo { - - private static byte[] getAddressByPassphrase(String passPhrase) { - byte[] privateKey = Sha256Sm3Hash.hash(passPhrase.getBytes()); - ECKey ecKey = ECKey.fromPrivate(privateKey); - byte[] address = ecKey.getAddress(); - return address; - } - - public static void main(String[] args) { - String passPhrase = "test pass phrase"; - byte[] address = WalletApi.createAdresss(passPhrase.getBytes()); - String tokenId = "1000001"; - if (!Arrays.equals(address, getAddressByPassphrase(passPhrase))) { - System.out.println("The address is diffrent !!"); - } - System.out.println("address === " + WalletApi.encode58Check(address)); - - EasyTransferResponse response = WalletApi - .easyTransferAsset( - passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), - tokenId, 10000L); - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java b/src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java deleted file mode 100644 index 66c056742..000000000 --- a/src/main/java/org/tron/demo/EasyTransferByPrivateDemo.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -public class EasyTransferByPrivateDemo { - - public static void main(String[] args) { - String privateKey = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - String toAddress = "TKMZBoWbXbYedcBnQugYT7DaFnSgi9qg78"; - EasyTransferResponse response = WalletApi - .easyTransferByPrivate(ByteArray.fromHexString(privateKey), - WalletApi.decodeFromBase58Check(toAddress), 1000000L); - - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/EasyTransferDemo.java b/src/main/java/org/tron/demo/EasyTransferDemo.java deleted file mode 100644 index 1a57ff642..000000000 --- a/src/main/java/org/tron/demo/EasyTransferDemo.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class EasyTransferDemo { - - private static byte[] getAddressByPassphrase(String passPhrase) { - byte[] privateKey = Sha256Sm3Hash.hash(passPhrase.getBytes()); - ECKey ecKey = ECKey.fromPrivate(privateKey); - byte[] address = ecKey.getAddress(); - return address; - } - - public static void main(String[] args) { - String passPhrase = "test pass phrase"; - byte[] address = WalletApi.createAdresss(passPhrase.getBytes()); - if (!Arrays.equals(address, getAddressByPassphrase(passPhrase))) { - System.out.println("The address is diffrent !!"); - } - System.out.println("address === " + WalletApi.encode58Check(address)); - - EasyTransferResponse response = WalletApi - .easyTransfer(passPhrase.getBytes(), getAddressByPassphrase("test pass phrase 2"), 10000L); - if (response.getResult().getResult() == true) { - Transaction transaction = response.getTransaction(); - System.out.println("Easy transfer successful!!!"); - System.out.println( - "Receive txid = " + ByteArray.toHexString(response.getTxid().toByteArray())); - System.out.println(Utils.printTransaction(transaction)); - } else { - System.out.println("Easy transfer failed!!!"); - System.out.println("Code = " + response.getResult().getCode()); - System.out.println("Message = " + response.getResult().getMessage().toStringUtf8()); - } - } -} diff --git a/src/main/java/org/tron/demo/MultiSignDemo.java b/src/main/java/org/tron/demo/MultiSignDemo.java deleted file mode 100644 index 95b5aa0d5..000000000 --- a/src/main/java/org/tron/demo/MultiSignDemo.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.tron.demo; - -import org.tron.api.GrpcAPI.TransactionExtention; -import org.tron.api.GrpcAPI.TransactionSignWeight; -import org.tron.common.utils.ByteArray; -import org.tron.common.utils.Utils; -import org.tron.core.exception.CancelException; -import org.tron.protos.Protocol.Transaction; -import org.tron.walletserver.WalletApi; - -public class MultiSignDemo { - - public static void main(String[] args) throws CancelException { - String to = "TL5mpGbtr5L2Gi7CtotBQzjN8pK7SmbyFz"; - String owner = "TJCnKsPa7y5okkXvQAidZBzqx3QyQ6sxMW"; - String private0 = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - String private1 = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; - String private2 = "8E812436A0E3323166E1F0E8BA79E19E217B2C4A53C970D4CCA0CFB1078979DF"; - long amount = 10_000_000_000L; - Transaction transaction = TransactionSignDemo - .createTransaction(WalletApi.decodeFromBase58Check(owner), - WalletApi.decodeFromBase58Check(to), amount); - TransactionExtention transactionExtention = WalletApi - .addSignByApi(transaction, ByteArray.fromHexString(private0)); - // System.out.println(Utils.printTransaction(transactionExtention)); - TransactionSignWeight transactionSignWeight = WalletApi - .getTransactionSignWeight(transactionExtention.getTransaction()); - // System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); - - transactionExtention = WalletApi - .addSignByApi(transactionExtention.getTransaction(), ByteArray.fromHexString(private1)); - System.out.println(Utils.printTransaction(transactionExtention)); - transactionSignWeight = WalletApi - .getTransactionSignWeight(transactionExtention.getTransaction()); -// System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); - - transactionExtention = WalletApi - .addSignByApi(transactionExtention.getTransaction(), ByteArray.fromHexString(private2)); -// System.out.println(Utils.printTransactionSignWeight(transactionSignWeight)); - // System.out.println(Utils.printTransaction(transactionExtention)); - } -} diff --git a/src/main/java/org/tron/demo/TransactionSignDemo.java b/src/main/java/org/tron/demo/TransactionSignDemo.java deleted file mode 100644 index b787981c7..000000000 --- a/src/main/java/org/tron/demo/TransactionSignDemo.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.tron.demo; - -import com.google.protobuf.Any; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import org.tron.api.GrpcAPI.Return; -import org.tron.api.GrpcAPI.TransactionExtention; -import org.tron.common.crypto.ECKey; -import org.tron.common.crypto.Sha256Sm3Hash; -import org.tron.common.utils.ByteArray; -import org.tron.core.exception.CancelException; -import org.tron.protos.Protocol.Block; -import org.tron.protos.Protocol.Transaction; -import org.tron.protos.contract.BalanceContract.TransferContract; -import org.tron.walletserver.WalletApi; - -import java.util.Arrays; - -public class TransactionSignDemo { - - public static Transaction setReference(Transaction transaction, Block newestBlock) { - long blockHeight = newestBlock.getBlockHeader().getRawData().getNumber(); - byte[] blockHash = getBlockHash(newestBlock).getBytes(); - byte[] refBlockNum = ByteArray.fromLong(blockHeight); - Transaction.raw rawData = transaction.getRawData().toBuilder() - .setRefBlockHash(ByteString.copyFrom(ByteArray.subArray(blockHash, 8, 16))) - .setRefBlockBytes(ByteString.copyFrom(ByteArray.subArray(refBlockNum, 6, 8))) - .build(); - return transaction.toBuilder().setRawData(rawData).build(); - } - - public static Sha256Sm3Hash getBlockHash(Block block) { - return Sha256Sm3Hash.of(block.getBlockHeader().getRawData().toByteArray()); - } - - public static String getTransactionHash(Transaction transaction) { - String txid = ByteArray.toHexString(Sha256Sm3Hash.hash(transaction.getRawData().toByteArray())); - return txid; - } - - public static Transaction createTransaction(byte[] from, byte[] to, long amount) { - Transaction.Builder transactionBuilder = Transaction.newBuilder(); - Block newestBlock = WalletApi.getBlock(-1); - - Transaction.Contract.Builder contractBuilder = Transaction.Contract.newBuilder(); - TransferContract.Builder transferContractBuilder = TransferContract.newBuilder(); - transferContractBuilder.setAmount(amount); - ByteString bsTo = ByteString.copyFrom(to); - ByteString bsOwner = ByteString.copyFrom(from); - transferContractBuilder.setToAddress(bsTo); - transferContractBuilder.setOwnerAddress(bsOwner); - try { - Any any = Any.pack(transferContractBuilder.build()); - contractBuilder.setParameter(any); - } catch (Exception e) { - return null; - } - contractBuilder.setType(Transaction.Contract.ContractType.TransferContract); - transactionBuilder.getRawDataBuilder().addContract(contractBuilder) - .setTimestamp(System.currentTimeMillis()) - .setExpiration(newestBlock.getBlockHeader().getRawData().getTimestamp() + 10 * 60 * 60 * 1000); - Transaction transaction = transactionBuilder.build(); - Transaction refTransaction = setReference(transaction, newestBlock); - return refTransaction; - } - - private static byte[] signTransaction2Byte(byte[] transaction, byte[] privateKey) - throws InvalidProtocolBufferException { - ECKey ecKey = ECKey.fromPrivate(privateKey); - Transaction transaction1 = Transaction.parseFrom(transaction); - byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = Sha256Sm3Hash.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); - return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray(); - } - - private static Transaction signTransaction2Object(byte[] transaction, byte[] privateKey) - throws InvalidProtocolBufferException { - ECKey ecKey = ECKey.fromPrivate(privateKey); - Transaction transaction1 = Transaction.parseFrom(transaction); - byte[] rawdata = transaction1.getRawData().toByteArray(); - byte[] hash = Sha256Sm3Hash.hash(rawdata); - byte[] sign = ecKey.sign(hash).toByteArray(); - return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build(); - } - - private static boolean broadcast(byte[] transactionBytes) throws InvalidProtocolBufferException { - return WalletApi.broadcastTransaction(transactionBytes); - } - - private static void base58checkToHexString() { - String base58check = "TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"; - String hexString = ByteArray.toHexString(WalletApi.decodeFromBase58Check(base58check)); - System.out.println(hexString); - } - - private static void hexStringTobase58check() { - String hexString = "414948c2e8a756d9437037dcd8c7e0c73d560ca38d"; - String base58check = WalletApi.encode58Check(ByteArray.fromHexString(hexString)); - System.out.println(base58check); - } - - public static void main(String[] args) throws InvalidProtocolBufferException, CancelException { - String privateStr = "D95611A9AF2A2A45359106222ED1AFED48853D9A44DEFF8DC7913F5CBA727366"; - byte[] privateBytes = ByteArray.fromHexString(privateStr); - ECKey ecKey = ECKey.fromPrivate(privateBytes); - byte[] from = ecKey.getAddress(); - byte[] to = WalletApi.decodeFromBase58Check("TGehVcNhud84JDCGrNHKVz9jEAVKUpbuiv"); - long amount = 100_000_000L; // 100 TRX, api only receive trx in Sun, and 1 trx = 1000000 Sun - Transaction transaction = createTransaction(from, to, amount); - byte[] transactionBytes = transaction.toByteArray(); - - /* - //sign a transaction - Transaction transaction1 = TransactionUtils.sign(transaction, ecKey); - //get byte transaction - byte[] transaction2 = transaction1.toByteArray(); - System.out.println("transaction2 ::::: " + ByteArray.toHexString(transaction2)); - - //sign a transaction in byte format and return a Transaction object - Transaction transaction3 = signTransaction2Object(transactionBytes, privateBytes); - System.out.println("transaction3 ::::: " + ByteArray.toHexString(transaction3.toByteArray())); - */ - - // sign a transaction in byte format and return a Transaction in byte format - byte[] transaction4 = signTransaction2Byte(transactionBytes, privateBytes); - System.out.println("transaction4 ::::: " + ByteArray.toHexString(transaction4)); - Transaction transactionSigned; - if (WalletApi.getRpcVersion() == 2) { - TransactionExtention transactionExtention = - WalletApi.signTransactionByApi2(transaction, ecKey.getPrivKeyBytes()); - if (transactionExtention == null) { - System.out.println("transactionExtention is null"); - return; - } - Return ret = transactionExtention.getResult(); - if (!ret.getResult()) { - System.out.println("Code = " + ret.getCode()); - System.out.println("Message = " + ret.getMessage().toStringUtf8()); - return; - } - System.out.println( - "Receive txid = " + ByteArray.toHexString(transactionExtention.getTxid().toByteArray())); - transactionSigned = transactionExtention.getTransaction(); - } else { - transactionSigned = WalletApi.signTransactionByApi(transaction, ecKey.getPrivKeyBytes()); - } - byte[] transaction5 = transactionSigned.toByteArray(); - System.out.println("transaction5 ::::: " + ByteArray.toHexString(transaction5)); - if (!Arrays.equals(transaction4, transaction5)) { - System.out.println("transaction4 is not equals to transaction5 !!!!!"); - } - boolean result = broadcast(transaction4); - - System.out.println(result); - } -} diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index c0ca0ce10..8f3f27c73 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -100,7 +100,6 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "FreezeBalanceV2", - "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", @@ -242,7 +241,6 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "FreezeBalanceV2", - "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", @@ -2839,15 +2837,6 @@ private void getContractInfo(String[] parameters) { } } - private void generateAddress() { - AddressPrKeyPairMessage result = walletApiWrapper.generateAddress(); - if (null != result) { - System.out.println(Utils.formatMessageString(result)); - } else { - System.out.println("GenerateAddress failed !!!"); - } - } - private void updateAccountPermission(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 2) { @@ -4732,10 +4721,6 @@ private void run() { getContractInfo(parameters); break; } - case "generateaddress": { - generateAddress(); - break; - } case "updateaccountpermission": { updateAccountPermission(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index eb587d64b..dec4b03b1 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -309,14 +309,6 @@ public boolean createAccount(byte[] ownerAddress, byte[] address) return wallet.createAccount(ownerAddress, address); } - public AddressPrKeyPairMessage generateAddress() { - if (wallet == null || !wallet.isLoginState()) { - System.out.println("Warning: createAccount failed, Please login first !!"); - return null; - } - return WalletApi.generateAddress(); - } - public boolean createWitness(byte[] ownerAddress, String url) throws CipherException, IOException, CancelException { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index eac321095..ca44b39a9 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -62,7 +62,6 @@ import org.tron.protos.contract.SmartContractOuterClass.SmartContract; import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.TransactionInfo; -import org.tron.protos.Protocol.TransactionSign; import org.tron.protos.contract.SmartContractOuterClass.SmartContractDataWrapper; import org.tron.protos.contract.SmartContractOuterClass.TriggerSmartContract; import org.tron.protos.contract.SmartContractOuterClass.UpdateEnergyLimitContract; @@ -138,21 +137,6 @@ public Account queryAccountById(String accountId) { } } - //Warning: do not invoke this interface provided by others. - public Transaction signTransaction(TransactionSign transactionSign) { - return blockingStubFull.getTransactionSign(transactionSign); - } - - //Warning: do not invoke this interface provided by others. - public TransactionExtention signTransaction2(TransactionSign transactionSign) { - return blockingStubFull.getTransactionSign2(transactionSign); - } - - //Warning: do not invoke this interface provided by others. - public TransactionExtention addSign(TransactionSign transactionSign) { - return blockingStubFull.addSign(transactionSign); - } - public TransactionSignWeight getTransactionSignWeight(Transaction transaction) { return blockingStubFull.getTransactionSignWeight(transaction); } @@ -161,61 +145,6 @@ public TransactionApprovedList getTransactionApprovedList(Transaction transactio return blockingStubFull.getTransactionApprovedList(transaction); } - //Warning: do not invoke this interface provided by others. - public byte[] createAdresss(byte[] passPhrase) { - BytesMessage.Builder builder = BytesMessage.newBuilder(); - builder.setValue(ByteString.copyFrom(passPhrase)); - - BytesMessage result = blockingStubFull.createAddress(builder.build()); - return result.getValue().toByteArray(); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransfer(byte[] passPhrase, byte[] toAddress, long amount) { - EasyTransferMessage.Builder builder = EasyTransferMessage.newBuilder(); - builder.setPassPhrase(ByteString.copyFrom(passPhrase)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAmount(amount); - - return blockingStubFull.easyTransfer(builder.build()); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransferByPrivate(byte[] privateKey, byte[] toAddress, - long amount) { - EasyTransferByPrivateMessage.Builder builder = EasyTransferByPrivateMessage.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAmount(amount); - - return blockingStubFull.easyTransferByPrivate(builder.build()); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransferAsset(byte[] passPhrase, byte[] toAddress, - String assetId, long amount) { - EasyTransferAssetMessage.Builder builder = EasyTransferAssetMessage.newBuilder(); - builder.setPassPhrase(ByteString.copyFrom(passPhrase)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAssetId(assetId); - builder.setAmount(amount); - - return blockingStubFull.easyTransferAsset(builder.build()); - } - - //Warning: do not invoke this interface provided by others. - public EasyTransferResponse easyTransferAssetByPrivate(byte[] privateKey, byte[] toAddress, - String assetId, long amount) { - EasyTransferAssetByPrivateMessage.Builder builder = EasyTransferAssetByPrivateMessage - .newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setToAddress(ByteString.copyFrom(toAddress)); - builder.setAssetId(assetId); - builder.setAmount(amount); - - return blockingStubFull.easyTransferAssetByPrivate(builder.build()); - } - public Transaction createTransaction(AccountUpdateContract contract) { return blockingStubFull.updateAccount(contract); } @@ -544,14 +473,6 @@ public TransactionExtention createAccount2(AccountCreateContract contract) { return blockingStubFull.createAccount2(contract); } - public AddressPrKeyPairMessage generateAddress(EmptyMessage emptyMessage) { - if (blockingStubSolidity != null) { - return blockingStubSolidity.generateAddress(emptyMessage); - } else { - return blockingStubFull.generateAddress(emptyMessage); - } - } - public Transaction createWitness(WitnessCreateContract contract) { return blockingStubFull.createWitness(contract); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index a0a57e349..ce0d535c6 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -31,7 +31,6 @@ import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.AccountNetMessage; import org.tron.api.GrpcAPI.AccountResourceMessage; -import org.tron.api.GrpcAPI.AddressPrKeyPairMessage; import org.tron.api.GrpcAPI.AssetIssueList; import org.tron.api.GrpcAPI.BlockExtention; import org.tron.api.GrpcAPI.BlockList; @@ -42,8 +41,6 @@ import org.tron.api.GrpcAPI.DecryptNotesTRC20; import org.tron.api.GrpcAPI.DelegatedResourceList; import org.tron.api.GrpcAPI.DiversifierMessage; -import org.tron.api.GrpcAPI.EasyTransferResponse; -import org.tron.api.GrpcAPI.EmptyMessage; import org.tron.api.GrpcAPI.EstimateEnergyMessage; import org.tron.api.GrpcAPI.ExchangeList; import org.tron.api.GrpcAPI.ExpandedSpendingKeyMessage; @@ -114,7 +111,6 @@ import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.Protocol.Transaction.Result; import org.tron.protos.Protocol.TransactionInfo; -import org.tron.protos.Protocol.TransactionSign; import org.tron.protos.Protocol.Witness; import org.tron.protos.contract.AccountContract.AccountCreateContract; import org.tron.protos.contract.AccountContract.AccountPermissionUpdateContract; @@ -647,42 +643,6 @@ private boolean processTransaction(Transaction transaction) return rpcCli.broadcastTransaction(transaction); } - // Warning: do not invoke this interface provided by others. - public static Transaction signTransactionByApi(Transaction transaction, byte[] privateKey) - throws CancelException { - transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please input permission id."; - transaction = TransactionUtils.setPermissionId(transaction, tipsString); - TransactionSign.Builder builder = TransactionSign.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setTransaction(transaction); - return rpcCli.signTransaction(builder.build()); - } - - // Warning: do not invoke this interface provided by others. - public static TransactionExtention signTransactionByApi2( - Transaction transaction, byte[] privateKey) throws CancelException { - transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please input permission id."; - transaction = TransactionUtils.setPermissionId(transaction, tipsString); - TransactionSign.Builder builder = TransactionSign.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setTransaction(transaction); - return rpcCli.signTransaction2(builder.build()); - } - - // Warning: do not invoke this interface provided by others. - public static TransactionExtention addSignByApi(Transaction transaction, byte[] privateKey) - throws CancelException { - transaction = TransactionUtils.setExpirationTime(transaction); - String tipsString = "Please input permission id."; - transaction = TransactionUtils.setPermissionId(transaction, tipsString); - TransactionSign.Builder builder = TransactionSign.newBuilder(); - builder.setPrivateKey(ByteString.copyFrom(privateKey)); - builder.setTransaction(transaction); - return rpcCli.addSign(builder.build()); - } - public static TransactionSignWeight getTransactionSignWeight(Transaction transaction) { return rpcCli.getTransactionSignWeight(transaction); } @@ -691,35 +651,6 @@ public static TransactionApprovedList getTransactionApprovedList(Transaction tra return rpcCli.getTransactionApprovedList(transaction); } - // Warning: do not invoke this interface provided by others. - public static byte[] createAdresss(byte[] passPhrase) { - return rpcCli.createAdresss(passPhrase); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransfer( - byte[] passPhrase, byte[] toAddress, long amount) { - return rpcCli.easyTransfer(passPhrase, toAddress, amount); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferByPrivate( - byte[] privateKey, byte[] toAddress, long amount) { - return rpcCli.easyTransferByPrivate(privateKey, toAddress, amount); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAsset( - byte[] passPhrase, byte[] toAddress, String assetId, long amount) { - return rpcCli.easyTransferAsset(passPhrase, toAddress, assetId, amount); - } - - // Warning: do not invoke this interface provided by others. - public static EasyTransferResponse easyTransferAssetByPrivate( - byte[] privateKey, byte[] toAddress, String assetId, long amount) { - return rpcCli.easyTransferAssetByPrivate(privateKey, toAddress, assetId, amount); - } - public boolean sendCoin(byte[] owner, byte[] to, long amount) throws CipherException, IOException, CancelException { if (owner == null) { @@ -856,12 +787,6 @@ public boolean createAccount(byte[] owner, byte[] address) } } - // Warning: do not invoke this interface provided by others. - public static AddressPrKeyPairMessage generateAddress() { - EmptyMessage.Builder builder = EmptyMessage.newBuilder(); - return rpcCli.generateAddress(builder.build()); - } - public boolean createWitness(byte[] owner, byte[] url) throws CipherException, IOException, CancelException { if (owner == null) { diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 95871f5aa..70297d095 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -624,68 +624,6 @@ service Wallet { } }; } - //Warning: do not invoke this interface provided by others. - //Please use GetTransactionSign2 instead of this function. - rpc GetTransactionSign (TransactionSign) returns (Transaction) { - option (google.api.http) = { - post: "/wallet/gettransactionsign" - body: "*" - additional_bindings { - get: "/wallet/gettransactionsign" - } - }; - }; - //Warning: do not invoke this interface provided by others. - //Use this function instead of GetTransactionSign. - rpc GetTransactionSign2 (TransactionSign) returns (TransactionExtention) { - }; - //Warning: do not invoke this interface provided by others. - rpc CreateAddress (BytesMessage) returns (BytesMessage) { - option (google.api.http) = { - post: "/wallet/createaddress" - body: "*" - additional_bindings { - get: "/wallet/createaddress" - } - }; - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransferAsset (EasyTransferAssetMessage) returns (EasyTransferResponse) { - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransferAssetByPrivate (EasyTransferAssetByPrivateMessage) returns (EasyTransferResponse) { - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransfer (EasyTransferMessage) returns (EasyTransferResponse) { - option (google.api.http) = { - post: "/wallet/easytransfer" - body: "*" - additional_bindings { - get: "/wallet/easytransfer" - } - }; - }; - //Warning: do not invoke this interface provided by others. - rpc EasyTransferByPrivate (EasyTransferByPrivateMessage) returns (EasyTransferResponse) { - option (google.api.http) = { - post: "/wallet/easytransferbyprivate" - body: "*" - additional_bindings { - get: "/wallet/easytransferbyprivate" - } - }; - }; - //Warning: do not invoke this interface provided by others. - rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { - - option (google.api.http) = { - post: "/wallet/generateaddress" - body: "*" - additional_bindings { - get: "/wallet/generateaddress" - } - }; - } rpc GetTransactionInfoById (BytesMessage) returns (TransactionInfo) { option (google.api.http) = { @@ -707,10 +645,6 @@ service Wallet { }; } - rpc AddSign (TransactionSign) returns (TransactionExtention) { - - } - rpc GetTransactionSignWeight (Transaction) returns (TransactionSignWeight) { } @@ -968,17 +902,6 @@ service WalletSolidity { }; } - //Warning: do not invoke this interface provided by others. - rpc GenerateAddress (EmptyMessage) returns (AddressPrKeyPairMessage) { - option (google.api.http) = { - post: "/walletsolidity/generateaddress" - body: "*" - additional_bindings { - get: "/walletsolidity/generateaddress" - } - }; - } - rpc GetMerkleTreeVoucherInfo (OutputPointInfo) returns (IncrementalMerkleVoucherInfo) { } @@ -1165,7 +1088,6 @@ message GetAvailableUnfreezeCountResponseMessage { int64 count = 1; } -//GetCanDelegatedMaxSize message CanDelegatedMaxSizeRequestMessage { int32 type = 1; bytes owner_address = 2; @@ -1174,7 +1096,6 @@ message CanDelegatedMaxSizeResponseMessage { int64 max_size = 1; } -//GetCanWithdrawUnfreezeAmount message CanWithdrawUnfreezeAmountRequestMessage { bytes owner_address = 1; int64 timestamp = 2; @@ -1271,43 +1192,6 @@ message PaginatedMessage { int64 limit = 2; } -message EasyTransferMessage { - bytes passPhrase = 1; - bytes toAddress = 2; - int64 amount = 3; -} - -message EasyTransferAssetMessage { - bytes passPhrase = 1; - bytes toAddress = 2; - string assetId = 3; - int64 amount = 4; -} - -message EasyTransferByPrivateMessage { - bytes privateKey = 1; - bytes toAddress = 2; - int64 amount = 3; -} - -message EasyTransferAssetByPrivateMessage { - bytes privateKey = 1; - bytes toAddress = 2; - string assetId = 3; - int64 amount = 4; -} - -message EasyTransferResponse { - Transaction transaction = 1; - Return result = 2; - bytes txid = 3; //transaction id = sha256(transaction.rowdata) -} - -message AddressPrKeyPairMessage { - string address = 1; - string privateKey = 2; -} - message TransactionExtention { Transaction transaction = 1; bytes txid = 2; //transaction id = sha256(transaction.rowdata) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index e6aac408f..dafee3a6a 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -494,11 +494,6 @@ message Transactions { repeated Transaction transactions = 1; } -message TransactionSign { - Transaction transaction = 1; - bytes privateKey = 2; -} - message BlockHeader { message raw { int64 timestamp = 1; From bd7c85648b7a723d077263dfa5d434c8ff91e823 Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Fri, 14 Apr 2023 14:42:18 +0800 Subject: [PATCH 417/445] feat(api): generateAddress use local method --- README.md | 11 +++++--- src/main/java/org/tron/walletcli/Client.java | 27 ++++++++++++++++++++ src/main/protos/api/api.proto | 5 ++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 75aee3dcb..cbbfdebaf 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,15 @@ For more information on a specific command, just type the command on terminal wh | [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) | [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | | [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | -| [FreezeBalance](#How-to-delegate-resourcee) |[GetCanWithdrawUnfreezeAmount](#How-to-freezev2)| [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [FreezeBalance](#How-to-delegate-resourcee) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | | [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | | [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | | [GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | | [GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) | [GetBrokerage](#Brokerage) | | [GetContract](#How-to-use-smart-contracts) | [GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | -| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | -| [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | +| [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | +| [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | | [GetMarketPairList](#How-to-use-tron-dex-to-sell-asset)| [GetMarketPriceByPair](#How-to-use-tron-dex-to-sell-asset)| [GetNextMaintenanceTime](#Some-others) | | [GetNkFromNsk](#How-to-transfer-shielded-TRC20-token) | [GetProposal](#Get-proposal-information) | [GetShieldedPaymentAddress](#How-to-transfer-shielded-TRC20-token)| | [GetSpendingKey](#How-to-transfer-shielded-TRC20-token) | [GetReward](#Brokerage) | [GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) | @@ -98,7 +98,7 @@ For more information on a specific command, just type the command on terminal wh | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | | [WithdrawExpireUnfreeze](#How-to-freezev2) | [GetDelegatedResourceV2](#How-to-freezev2) | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | -| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | +| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) | Type any one of the listed commands, to display how-to tips. @@ -1458,6 +1458,9 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 ## Account related commands +**GenerateAddress** +> Generate an address and print out the public and private keys + **GetAccount** > Get account information based on address diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 8f3f27c73..7f061c35b 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -26,7 +26,10 @@ import org.jline.terminal.TerminalBuilder; import org.tron.api.GrpcAPI.*; import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignUtils; import org.tron.common.utils.AbiUtil; +import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Utils; @@ -100,6 +103,7 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "FreezeBalanceV2", + "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", @@ -241,6 +245,7 @@ public class Client { "ExchangeWithdraw", "FreezeBalance", "FreezeBalanceV2", + "GenerateAddress", // "GenerateShieldedAddress", "GenerateShieldedTRC20Address", "GetAccount", @@ -2837,6 +2842,24 @@ private void getContractInfo(String[] parameters) { } } + private void generateAddress(String[] parameters) { + try { + boolean isECKey = parameters == null || parameters.length == 0 + || Boolean.parseBoolean(parameters[0]); + SignInterface cryptoEngine = SignUtils.getGeneratedRandomSign(Utils.getRandom(), isECKey); + byte[] priKey = cryptoEngine.getPrivateKey(); + byte[] address = cryptoEngine.getAddress(); + String addressStr = Base58.encode(address); + String priKeyStr = ByteArray.toHexString(priKey); + AddressPrKeyPairMessage.Builder builder = AddressPrKeyPairMessage.newBuilder(); + builder.setAddress(addressStr); + builder.setPrivateKey(priKeyStr); + System.out.println(Utils.formatMessageString(builder.build())); + } catch (Exception e) { + System.out.println("GenerateAddress failed !!!"); + } + } + private void updateAccountPermission(String[] parameters) throws CipherException, IOException, CancelException { if (parameters == null || parameters.length != 2) { @@ -4721,6 +4744,10 @@ private void run() { getContractInfo(parameters); break; } + case "generateaddress": { + generateAddress(parameters); + break; + } case "updateaccountpermission": { updateAccountPermission(parameters); break; diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 70297d095..61df80491 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1191,6 +1191,11 @@ message PaginatedMessage { int64 offset = 1; int64 limit = 2; } +// for only wallet-cli, do not delete +message AddressPrKeyPairMessage { + string address = 1; + string privateKey = 2; +} message TransactionExtention { Transaction transaction = 1; From b27c9b34768b320fd7d7d22d4ec27519e29ebb18 Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Fri, 14 Apr 2023 18:41:08 +0800 Subject: [PATCH 418/445] fix(api): fix encode58Check --- src/main/java/org/tron/walletcli/Client.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 7f061c35b..5ec3ff48c 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2849,7 +2849,7 @@ private void generateAddress(String[] parameters) { SignInterface cryptoEngine = SignUtils.getGeneratedRandomSign(Utils.getRandom(), isECKey); byte[] priKey = cryptoEngine.getPrivateKey(); byte[] address = cryptoEngine.getAddress(); - String addressStr = Base58.encode(address); + String addressStr = WalletApi.encode58Check(address); String priKeyStr = ByteArray.toHexString(priKey); AddressPrKeyPairMessage.Builder builder = AddressPrKeyPairMessage.newBuilder(); builder.setAddress(addressStr); From dd5c16be18ec8a0d66d1f4fed4bad9b28281779a Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Mon, 22 May 2023 16:57:04 +0800 Subject: [PATCH 419/445] feat(freezeV2): upgrade stake2.0 APIs --- README.md | 44 ++++++++++++++++- .../common/utils/HttpSelfFormatFieldName.java | 3 ++ .../java/org/tron/common/utils/Utils.java | 8 ++++ src/main/java/org/tron/walletcli/Client.java | 48 ++++++++++++++----- .../org/tron/walletcli/WalletApiWrapper.java | 14 ++++-- .../org/tron/walletserver/GrpcClient.java | 5 ++ .../java/org/tron/walletserver/WalletApi.java | 48 ++++++++++++------- src/main/protos/api/api.proto | 3 ++ src/main/protos/core/Tron.proto | 3 ++ .../core/contract/balance_contract.proto | 5 ++ 10 files changed, 148 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 75aee3dcb..303495aac 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,8 @@ For more information on a specific command, just type the command on terminal wh | [UpdateBrokerage](#Brokerage) | [UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | -| [WithdrawExpireUnfreeze](#How-to-freezev2) | [GetDelegatedResourceV2](#How-to-freezev2) | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | -| [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | +| [WithdrawExpireUnfreeze](#How-to-freezev2) | [CancelAllUnfreezeV2](#How-to-freezev2) |[GetDelegatedResourceV2](#How-to-freezev2) | +| [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | Type any one of the listed commands, to display how-to tips. @@ -1342,6 +1342,46 @@ wallet> GetTransactionById e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df "raw_data_hex":"0a020000220819b59068c6058ff44096e18bb5d1305a5a083812560a3b747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5769746864726177457870697265556e667265657a65436f6e747261637412170a154159e3741a68ec3e1ebba80ad809d5ccd31674236e7093b3e5aad130" } ``` +> cancelAllUnfreezeV2 [OwnerAddress] + +OwnerAddress +> The address of the account that initiated the transaction, optional, default is the address of the login account. + +Example: +```console +wallet> cancelAllUnfreezeV2 TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh +txid is e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df7e98f54a147 +wallet> GetTransactionById e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df7e98f54a147 +{ + "ret":[ + { + "contractRet":"SUCCESS" + } + ], + "signature":[ + "f8f02b5aa634b8666862a6d2ed68fcfd90afc616d14062952b0b09f0404d9bca6c4d3dc6dab082784950ff1ded235a07dab0d738c8a202be9451d5ca92b8eece01" + ], + "txID":"e5763ab8dfb1e7ed076770d55cf3c1ddaf36d75e23ec8330f99df7e98f54a147", + "raw_data":{ + "contract":[ + { + "parameter":{ + "value":{ + "owner_address":"4159e3741a68ec3e1ebba80ad809d5ccd31674236e" + }, + "type_url":"type.googleapis.com/protocol.CancelAllUnfreezeV2" + }, + "type":"CancelAllUnfreezeV2Contract" + } + ], + "ref_block_bytes":"0000", + "ref_block_hash":"19b59068c6058ff4", + "expiration":1671122055318, + "timestamp":1671100455315 + }, + "raw_data_hex":"0a020000220819b59068c6058ff44096e18bb5d1305a5a083812560a3b747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5769746864726177457870697265556e667265657a65436f6e747261637412170a154159e3741a68ec3e1ebba80ad809d5ccd31674236e7093b3e5aad130" +} +``` ### get resource delegation information use v2 API diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index bfff7ca22..4d33868f4 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -144,6 +144,9 @@ public class HttpSelfFormatFieldName { //UnDelegateResourceContract AddressFieldNameMap.put("protocol.UnDelegateResourceContract.owner_address", 1); AddressFieldNameMap.put("protocol.UnDelegateResourceContract.receiver_address", 1); + //CancelAllUnfreezeV2Contract + AddressFieldNameMap.put("protocol.CancelAllUnfreezeV2Contract.owner_address", 1); + AddressFieldNameMap.put("protocol.CanDelegatedMaxSizeRequestMessage.owner_address", 1); AddressFieldNameMap.put("protocol.GetAvailableUnfreezeCountRequestMessage.owner_address", 1); AddressFieldNameMap.put("protocol.CanWithdrawUnfreezeAmountRequestMessage.owner_address", 1); diff --git a/src/main/java/org/tron/common/utils/Utils.java b/src/main/java/org/tron/common/utils/Utils.java index caa517565..b70124bf4 100644 --- a/src/main/java/org/tron/common/utils/Utils.java +++ b/src/main/java/org/tron/common/utils/Utils.java @@ -18,6 +18,7 @@ package org.tron.common.utils; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.protobuf.Any; @@ -55,6 +56,7 @@ import org.tron.protos.contract.AssetIssueContractOuterClass.TransferAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; +import org.tron.protos.contract.BalanceContract.CancelAllUnfreezeV2Contract; import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; @@ -618,6 +620,12 @@ public static JSONObject printTransactionToJSON(Transaction transaction, boolean JSONObject.parseObject( JsonFormat.printToString(unDelegateResourceContract, selfType)); break; + case CancelAllUnfreezeV2Contract: + CancelAllUnfreezeV2Contract cancelAllUnfreezeV2Contract = + contractParameter.unpack(CancelAllUnfreezeV2Contract.class); + contractJson = JSON.parseObject( + JsonFormat.printToString(cancelAllUnfreezeV2Contract, selfType)); + break; // new freeze end // todo add other contract default: diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 8f3f27c73..fddba3cd0 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.math.BigInteger; -import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.*; import java.util.Base64.Decoder; @@ -34,7 +33,6 @@ import org.tron.common.zksnark.LibrustzcashParam; import org.tron.core.exception.CancelException; import org.tron.core.exception.CipherException; -import org.tron.core.exception.EncodingException; import org.tron.core.exception.ZksnarkException; import org.tron.core.zen.ShieldedAddressInfo; import org.tron.core.zen.ShieldedNoteInfo; @@ -84,6 +82,7 @@ public class Client { "BackupWallet", "BackupWallet2Base64", "BroadcastTransaction", + "CancelAllUnfreezeV2", "ChangePassword", "ClearContractABI", "Create2", @@ -225,6 +224,7 @@ public class Client { "BackupWallet", "BackupWallet2Base64", "BroadcastTransaction", + "CancelAllUnfreezeV2", "ChangePassword", "ClearContractABI", "Create2", @@ -1408,30 +1408,32 @@ private void withdrawExpireUnfreeze(String[] parameters) private void delegateResource(String[] parameters) throws IOException, CipherException, CancelException { - if (parameters == null || !(parameters.length == 3 || parameters.length == 4 || parameters.length == 5)) { + if (parameters == null || !(parameters.length == 3 || parameters.length == 4 || parameters.length == 5 || parameters.length == 6)) { System.out.println("Use delegateResource command with below syntax: "); System.out.println( - "delegateResource [OwnerAddress] balance ResourceCode(0 BANDWIDTH,1 ENERGY), ReceiverAddress [lock]"); + "delegateResource [OwnerAddress] balance ResourceCode(0 BANDWIDTH,1 ENERGY), " + + "ReceiverAddress [lock] [lockPeriod]"); return; } int index = 0; byte[] ownerAddress = null; - long balance = 0; - int resourceCode = 0; - byte[] receiverAddress = null; + long balance; + int resourceCode; + byte[] receiverAddress; boolean lock = false; + long lockPeriod = 0; if (parameters.length == 3) { balance = Long.parseLong(parameters[index++]); resourceCode = Integer.parseInt(parameters[index++]); - receiverAddress = getAddressBytes(parameters[index++]); + receiverAddress = getAddressBytes(parameters[index]); if (receiverAddress == null) { System.out.println( "delegateResource receiverAddress is invalid"); return; } - } else if (parameters.length == 4 || parameters.length == 5) { + } else { ownerAddress = getAddressBytes(parameters[index]); if (ownerAddress != null) { index ++; @@ -1445,14 +1447,17 @@ private void delegateResource(String[] parameters) return; } - if (parameters.length == 5 || - (ownerAddress == null && parameters.length == 4)) { + if (parameters.length == 5 || (ownerAddress == null && parameters.length == 4)) { lock = Boolean.parseBoolean(parameters[index++]); } + if (parameters.length == 6 || (ownerAddress == null && parameters.length == 5)) { + lock = Boolean.parseBoolean(parameters[index++]); + lockPeriod = Long.parseLong(parameters[index]); + } } boolean result = walletApiWrapper.delegateresource( - ownerAddress, balance, resourceCode, receiverAddress, lock); + ownerAddress, balance, resourceCode, receiverAddress, lock, lockPeriod); if (result) { System.out.println("delegateResource successful !!!"); } else { @@ -1508,6 +1513,21 @@ private void unDelegateResource(String[] parameters) } } + private void cancelAllUnfreezeV2(String[] parameters) + throws IOException, CipherException, CancelException { + if (parameters.length > 0) { + System.out.println("Use CancelAllUnfreezeV2 command with below syntax: "); + System.out.println("CancelAllUnfreezeV2"); + return; + } + boolean result = walletApiWrapper.cancelAllUnfreezeV2(); + if (result) { + System.out.println("cancelAllUnfreezeV2 successful !!!"); + } else { + System.out.println("cancelAllUnfreezeV2 failed !!!"); + } + } + private void unfreezeAsset(String[] parameters) throws IOException, CipherException, CancelException { System.out.println("Use UnfreezeAsset command like: "); @@ -4505,6 +4525,10 @@ private void run() { unDelegateResource(parameters); break; } + case "cancelallunfreezev2": { + cancelAllUnfreezeV2(parameters); + break; + } case "withdrawbalance": { withdrawBalance(parameters); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index dec4b03b1..03c857b7d 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -28,7 +28,6 @@ import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; import org.tron.keystore.WalletFile; -import org.tron.keystore.WalletUtils; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.ChainParameters; @@ -548,7 +547,7 @@ public boolean withdrawExpireUnfreeze(byte[] ownerAddress) } public boolean delegateresource(byte[] ownerAddress, long balance - , int resourceCode, byte[] receiverAddress, boolean lock) + , int resourceCode, byte[] receiverAddress, boolean lock, long lockPeriod) throws CipherException, IOException, CancelException { if (wallet == null || !wallet.isLoginState()) { System.out.println("Warning: delegateresource failed, Please login first !!"); @@ -556,7 +555,7 @@ public boolean delegateresource(byte[] ownerAddress, long balance } return wallet.delegateResource(ownerAddress, balance - , resourceCode, receiverAddress, lock); + , resourceCode, receiverAddress, lock, lockPeriod); } public boolean undelegateresource(byte[] ownerAddress, long balance @@ -570,6 +569,15 @@ public boolean undelegateresource(byte[] ownerAddress, long balance return wallet.unDelegateResource(ownerAddress, balance, resourceCode, receiverAddress); } + public boolean cancelAllUnfreezeV2() + throws CipherException, IOException, CancelException { + if (wallet == null || !wallet.isLoginState()) { + System.out.println("Warning: cancelAllUnfreezeV2 failed, Please login first !!"); + return false; + } + return wallet.cancelAllUnfreezeV2(); + } + public boolean unfreezeAsset(byte[] ownerAddress) throws CipherException, IOException, CancelException { if (wallet == null || !wallet.isLoginState()) { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index ca44b39a9..0380e02a5 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -44,6 +44,7 @@ import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; import org.tron.protos.contract.BalanceContract; +import org.tron.protos.contract.BalanceContract.CancelAllUnfreezeV2Contract; import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; @@ -229,6 +230,10 @@ public TransactionExtention createTransactionV2(BalanceContract.UnDelegateResour return blockingStubFull.unDelegateResource(contract); } + public TransactionExtention createTransactionV2(CancelAllUnfreezeV2Contract contract) { + return blockingStubFull.cancelAllUnfreezeV2(contract); + } + public Transaction createTransaction(UnfreezeAssetContract contract) { return blockingStubFull.unfreezeAsset(contract); } diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index ce0d535c6..a1db8ab64 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -122,6 +122,7 @@ import org.tron.protos.contract.AssetIssueContractOuterClass.UnfreezeAssetContract; import org.tron.protos.contract.AssetIssueContractOuterClass.UpdateAssetContract; import org.tron.protos.contract.BalanceContract; +import org.tron.protos.contract.BalanceContract.CancelAllUnfreezeV2Contract; import org.tron.protos.contract.BalanceContract.FreezeBalanceContract; import org.tron.protos.contract.BalanceContract.TransferContract; import org.tron.protos.contract.BalanceContract.UnfreezeBalanceContract; @@ -1368,10 +1369,10 @@ public boolean withdrawExpireUnfreeze(byte[] ownerAddress) } public boolean delegateResource(byte[] ownerAddress, long balance - ,int resourceCode, byte[] receiverAddress, boolean lock) + ,int resourceCode, byte[] receiverAddress, boolean lock, long lockPeriod) throws CipherException, IOException, CancelException { BalanceContract.DelegateResourceContract contract = createDelegateResourceContract( - ownerAddress, balance, resourceCode, receiverAddress, lock); + ownerAddress, balance, resourceCode, receiverAddress, lock, lockPeriod); TransactionExtention transactionExtention = rpcCli.createTransactionV2(contract); return processTransactionExtention(transactionExtention); } @@ -1385,6 +1386,13 @@ public boolean unDelegateResource(byte[] ownerAddress, long balance return processTransactionExtention(transactionExtention); } + public boolean cancelAllUnfreezeV2() + throws CipherException, IOException, CancelException { + CancelAllUnfreezeV2Contract contract = createCancelAllUnfreezeV2Contract(); + TransactionExtention transactionExtention = rpcCli.createTransactionV2(contract); + return processTransactionExtention(transactionExtention); + } + private UnfreezeBalanceContract createUnfreezeBalanceContract( byte[] address, int resourceCode, byte[] receiverAddress) { if (address == null) { @@ -1393,8 +1401,8 @@ private UnfreezeBalanceContract createUnfreezeBalanceContract( UnfreezeBalanceContract.Builder builder = UnfreezeBalanceContract.newBuilder(); - ByteString byteAddreess = ByteString.copyFrom(address); - builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode); + ByteString byteAddress = ByteString.copyFrom(address); + builder.setOwnerAddress(byteAddress).setResourceValue(resourceCode); if (receiverAddress != null) { ByteString receiverAddressBytes = @@ -1413,8 +1421,8 @@ private BalanceContract.UnfreezeBalanceV2Contract createUnfreezeBalanceContractV BalanceContract.UnfreezeBalanceV2Contract.Builder builder = BalanceContract.UnfreezeBalanceV2Contract.newBuilder(); - ByteString byteAddreess = ByteString.copyFrom(address); - builder.setOwnerAddress(byteAddreess).setResourceValue(resourceCode).setUnfreezeBalance(unfreezeBalance); + ByteString byteAddress = ByteString.copyFrom(address); + builder.setOwnerAddress(byteAddress).setResourceValue(resourceCode).setUnfreezeBalance(unfreezeBalance); return builder.build(); } @@ -1434,20 +1442,21 @@ private BalanceContract.WithdrawExpireUnfreezeContract createWithdrawExpireUnfre private BalanceContract.DelegateResourceContract createDelegateResourceContract( byte[] address, long balance - ,int resourceCode, byte[] receiver, boolean lock) { + ,int resourceCode, byte[] receiver, boolean lock, long lockPeriod) { if (address == null) { address = getAddress(); } BalanceContract.DelegateResourceContract.Builder builder = BalanceContract.DelegateResourceContract.newBuilder(); - ByteString byteAddreess = ByteString.copyFrom(address); - ByteString byteReceiverAddreess = ByteString.copyFrom(receiver); - builder.setOwnerAddress(byteAddreess) + ByteString byteAddress = ByteString.copyFrom(address); + ByteString byteReceiverAddress = ByteString.copyFrom(receiver); + builder.setOwnerAddress(byteAddress) .setResourceValue(resourceCode) .setBalance(balance) - .setReceiverAddress(byteReceiverAddreess) - .setLock(lock); + .setReceiverAddress(byteReceiverAddress) + .setLock(lock) + .setLockPeriod(lockPeriod); return builder.build(); } @@ -1461,13 +1470,20 @@ private BalanceContract.UnDelegateResourceContract createUnDelegateResourceContr BalanceContract.UnDelegateResourceContract.Builder builder = BalanceContract.UnDelegateResourceContract.newBuilder(); - ByteString byteAddreess = ByteString.copyFrom(address); - ByteString byteReceiverAddreess = ByteString.copyFrom(receiver); - builder.setOwnerAddress(byteAddreess) + ByteString byteAddress = ByteString.copyFrom(address); + ByteString byteReceiverAddress = ByteString.copyFrom(receiver); + builder.setOwnerAddress(byteAddress) .setResourceValue(resourceCode) .setBalance(balance) - .setReceiverAddress(byteReceiverAddreess); + .setReceiverAddress(byteReceiverAddress); + + return builder.build(); + } + private CancelAllUnfreezeV2Contract createCancelAllUnfreezeV2Contract() { + CancelAllUnfreezeV2Contract.Builder builder = CancelAllUnfreezeV2Contract.newBuilder(); + ByteString byteAddress = ByteString.copyFrom(getAddress()); + builder.setOwnerAddress(byteAddress); return builder.build(); } diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 70297d095..d23d6e017 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -281,6 +281,9 @@ service Wallet { rpc UnDelegateResource (UnDelegateResourceContract) returns (TransactionExtention) { } + rpc CancelAllUnfreezeV2 (CancelAllUnfreezeV2Contract) returns (TransactionExtention) { + } + //Please use UpdateAsset2 instead of this function. rpc UpdateAsset (UpdateAssetContract) returns (Transaction) { option (google.api.http) = { diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index e5145484f..f52c21162 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -374,6 +374,7 @@ message Transaction { WithdrawExpireUnfreezeContract = 56; DelegateResourceContract = 57; UnDelegateResourceContract = 58; + CancelAllUnfreezeV2Contract = 59; } ContractType type = 1; google.protobuf.Any parameter = 2; @@ -422,6 +423,7 @@ message Transaction { bytes orderId = 25; repeated MarketOrderDetail orderDetails = 26; int64 withdraw_expire_amount = 27; + int64 cancel_all_unfreezeV2_amount = 28; } message raw { @@ -482,6 +484,7 @@ message TransactionInfo { int64 packingFee = 27; int64 withdraw_expire_amount = 28; + int64 cancel_all_unfreezeV2_amount = 29; } message TransactionRet { diff --git a/src/main/protos/core/contract/balance_contract.proto b/src/main/protos/core/contract/balance_contract.proto index 54dcc4c40..4da0b778d 100644 --- a/src/main/protos/core/contract/balance_contract.proto +++ b/src/main/protos/core/contract/balance_contract.proto @@ -104,6 +104,7 @@ message DelegateResourceContract { int64 balance = 3; bytes receiver_address = 4; bool lock = 5; + int64 lock_period = 6; } message UnDelegateResourceContract { @@ -111,4 +112,8 @@ message UnDelegateResourceContract { ResourceCode resource = 2; int64 balance = 3; bytes receiver_address = 4; +} + +message CancelAllUnfreezeV2Contract { + bytes owner_address = 1; } \ No newline at end of file From 6fd62f52fc9bc83be9fb4dd55b19f2a7ea50e9f5 Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Fri, 9 Jun 2023 09:38:33 +0800 Subject: [PATCH 420/445] feat(freezeV2): optimize validation --- src/main/java/org/tron/walletcli/Client.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index fddba3cd0..581b72a99 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -1447,7 +1447,7 @@ private void delegateResource(String[] parameters) return; } - if (parameters.length == 5 || (ownerAddress == null && parameters.length == 4)) { + if ((ownerAddress != null && parameters.length == 5) || (ownerAddress == null && parameters.length == 4)) { lock = Boolean.parseBoolean(parameters[index++]); } if (parameters.length == 6 || (ownerAddress == null && parameters.length == 5)) { From 4a5b888beacff79b7f043e249c825ef7ecd3e566 Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Thu, 15 Jun 2023 18:48:09 +0800 Subject: [PATCH 421/445] feat(freezeV2): modify CancelAllUnfreezeV2 return info --- src/main/protos/core/Tron.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index f52c21162..05f8b990c 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -423,7 +423,7 @@ message Transaction { bytes orderId = 25; repeated MarketOrderDetail orderDetails = 26; int64 withdraw_expire_amount = 27; - int64 cancel_all_unfreezeV2_amount = 28; + map cancel_unfreezeV2_amount = 28; } message raw { @@ -484,7 +484,7 @@ message TransactionInfo { int64 packingFee = 27; int64 withdraw_expire_amount = 28; - int64 cancel_all_unfreezeV2_amount = 29; + map cancel_unfreezeV2_amount = 29; } message TransactionRet { From 92b9721061510e6134086994133540e43406fe41 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Sun, 25 Jun 2023 15:47:20 +0800 Subject: [PATCH 422/445] feature(mechanism): optmize resource recovery --- src/main/protos/core/Tron.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 05f8b990c..aa6a96ba1 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -193,6 +193,7 @@ message Account { bytes account_id = 23; int64 net_window_size = 24; + bool net_window_optimized = 25; message AccountResource { // energy resource, get from frozen @@ -215,6 +216,7 @@ message Account { int64 delegated_frozenV2_balance_for_energy = 10; int64 acquired_delegated_frozenV2_balance_for_energy = 11; + bool energy_window_optimized = 12; } AccountResource account_resource = 26; bytes codeHash = 30; From 1901d7dcdf418be2e889633a6beb51c08ba86798 Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Sat, 1 Jul 2023 19:27:00 +0800 Subject: [PATCH 423/445] feat(readme): add GenerateAddress api --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 303495aac..94f8e0ada 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ For more information on a specific command, just type the command on terminal wh | [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) | [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | | [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | -| [FreezeBalance](#How-to-delegate-resourcee) |[GetCanWithdrawUnfreezeAmount](#How-to-freezev2)| [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [FreezeBalance](#How-to-delegate-resourcee) |[GenerateAddress](#Account-related-commands) |[GetCanWithdrawUnfreezeAmount](#How-to-freezev2)| [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | | [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | | [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | From 6c4fff6c4bc0e1d01d6ddece769a02319df499d1 Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Mon, 22 May 2023 16:57:04 +0800 Subject: [PATCH 424/445] feat(readme): update GenerateAddress api --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94f8e0ada..9b44172a8 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ For more information on a specific command, just type the command on terminal wh | [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) | [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | | [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | -| [FreezeBalance](#How-to-delegate-resourcee) |[GenerateAddress](#Account-related-commands) |[GetCanWithdrawUnfreezeAmount](#How-to-freezev2)| [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [FreezeBalance](#How-to-delegate-resourcee) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | | [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | | [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | @@ -98,7 +98,8 @@ For more information on a specific command, just type the command on terminal wh | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | | [WithdrawExpireUnfreeze](#How-to-freezev2) | [CancelAllUnfreezeV2](#How-to-freezev2) |[GetDelegatedResourceV2](#How-to-freezev2) | -| [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | +| [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | +| [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) | | | Type any one of the listed commands, to display how-to tips. @@ -1498,6 +1499,9 @@ as: 721d63b074f18d41c147e04c952ec93467777a30b6f16745bc47a8eae5076545 ## Account related commands +**GenerateAddress** +> Generate an address and print out the public and private keys + **GetAccount** > Get account information based on address From bf33f90a25fefdf4d5d9fbbb4341a8279083b305 Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Sat, 1 Jul 2023 21:47:38 +0800 Subject: [PATCH 425/445] fix(wallet-cli): revert address encoding mapping --- .../common/utils/HttpSelfFormatFieldName.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java index 4d33868f4..aeb17decb 100644 --- a/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java +++ b/src/main/java/org/tron/common/utils/HttpSelfFormatFieldName.java @@ -23,30 +23,6 @@ public class HttpSelfFormatFieldName { //PrivateParametersWithoutAsk AddressFieldNameMap.put("protocol.PrivateParametersWithoutAsk.transparent_from_address", 1); AddressFieldNameMap.put("protocol.PrivateParametersWithoutAsk.transparent_to_address", 1); - //PrivateShieldedTRC20Parameters - AddressFieldNameMap.put( - "protocol.PrivateShieldedTRC20Parameters.transparent_to_address", 1); - AddressFieldNameMap.put( - "protocol.PrivateShieldedTRC20Parameters.shielded_TRC20_contract_address", 1); - //PrivateShieldedTRC20ParametersWithoutAsk - AddressFieldNameMap.put( - "protocol.PrivateShieldedTRC20ParametersWithoutAsk.transparent_to_address", 1); - AddressFieldNameMap.put( - "protocol.PrivateShieldedTRC20ParametersWithoutAsk.shielded_TRC20_contract_address", 1); - //IvkDecryptTRC20Parameters - AddressFieldNameMap.put( - "protocol.IvkDecryptTRC20Parameters.shielded_TRC20_contract_address", 1); - //OvkDecryptTRC20Parameters - AddressFieldNameMap.put( - "protocol.OvkDecryptTRC20Parameters.shielded_TRC20_contract_address", 1); - //NfTRC20Parameters - AddressFieldNameMap.put( - "protocol.NfTRC20Parameters.shielded_TRC20_contract_address", 1); - //ShieldedTRC20TriggerContractParameters - AddressFieldNameMap.put( - "protocol.ShieldedTRC20TriggerContractParameters.transparent_to_address", 1); - AddressFieldNameMap.put( - "protocol.DecryptNotesTRC20.NoteTx.transparent_to_address", 1); //***** Contract.proto ***** //AccountCreateContract @@ -127,8 +103,6 @@ public class HttpSelfFormatFieldName { AddressFieldNameMap.put("protocol.ExchangeTransactionContract.owner_address", 1); //AccountPermissionUpdateContract AddressFieldNameMap.put("protocol.AccountPermissionUpdateContract.owner_address", 1); - //UpdateBrokerageContract - AddressFieldNameMap.put("protocol.UpdateBrokerageContract.owner_address", 1); //ShieldedTransferContract AddressFieldNameMap.put("protocol.ShieldedTransferContract.transparent_from_address", 1); AddressFieldNameMap.put("protocol.ShieldedTransferContract.transparent_to_address", 1); @@ -191,9 +165,6 @@ public class HttpSelfFormatFieldName { AddressFieldNameMap.put("protocol.DelegatedResourceAccountIndex.fromAccounts", 1); AddressFieldNameMap.put("protocol.DelegatedResourceAccountIndex.toAccounts", 1); - AddressFieldNameMap.put("protocol.AccountIdentifier.address", 1); - AddressFieldNameMap.put("protocol.TransactionBalanceTrace.Operation.address", 1); - //***** api.proto ***** //Return NameFieldNameMap.put("protocol.Return.message", 1); @@ -251,7 +222,6 @@ public class HttpSelfFormatFieldName { //TransactionInfo NameFieldNameMap.put("protocol.TransactionInfo.resMessage", 1); - //***** market.proto ***** // MarketSellAssetContract AddressFieldNameMap.put("protocol.MarketSellAssetContract.owner_address", 1); NameFieldNameMap.put("protocol.MarketSellAssetContract.sell_token_id", 1); From a1f69f25115ded458ff0aa75dbae4a992206372f Mon Sep 17 00:00:00 2001 From: liuxincheng Date: Fri, 15 Sep 2023 11:15:28 +0800 Subject: [PATCH 426/445] feat(protocol): upgrade protocol --- src/main/protos/api/api.proto | 18 ++++++++++++++++++ src/main/protos/core/Tron.proto | 2 ++ 2 files changed, 20 insertions(+) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 428a2edcc..26c1429b5 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -767,6 +767,15 @@ service Wallet { rpc GetBlock (BlockReq) returns (BlockExtention) { } + + rpc GetBandwidthPrices (EmptyMessage) returns (PricesResponseMessage) { + } + + rpc GetEnergyPrices (EmptyMessage) returns (PricesResponseMessage) { + } + + rpc GetMemoFee (EmptyMessage) returns (PricesResponseMessage) { + } }; service WalletSolidity { @@ -963,6 +972,11 @@ service WalletSolidity { } rpc GetBlock (BlockReq) returns (BlockExtention) { } + rpc GetBandwidthPrices (EmptyMessage) returns (PricesResponseMessage) { + } + + rpc GetEnergyPrices (EmptyMessage) returns (PricesResponseMessage) { + } }; service WalletExtension { @@ -1109,6 +1123,10 @@ message CanWithdrawUnfreezeAmountResponseMessage { int64 amount = 1; } +message PricesResponseMessage { + string prices = 1; +} + // Gossip node list message NodeList { repeated Node nodes = 1; diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index aa6a96ba1..2fc08901e 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -601,6 +601,8 @@ enum ReasonCode { TOO_MANY_PEERS_WITH_SAME_IP = 0x22; LIGHT_NODE_SYNC_FAIL = 0x23; BELOW_THAN_ME = 0X24; + NOT_WITNESS = 0x25; + NO_SUCH_MESSAGE = 0x26; UNKNOWN = 0xFF; } From cf2f831699226f34ef032cf4e07829ed81927336 Mon Sep 17 00:00:00 2001 From: lxcmyf Date: Mon, 25 Sep 2023 16:36:39 +0800 Subject: [PATCH 427/445] feat(api): add resource price query API (#667) --- README.md | 33 ++++++++++++- src/main/java/org/tron/walletcli/Client.java | 48 +++++++++++++++++++ .../org/tron/walletcli/WalletApiWrapper.java | 12 +++++ .../org/tron/walletserver/GrpcClient.java | 23 +++++++++ .../java/org/tron/walletserver/WalletApi.java | 13 +++++ 5 files changed, 128 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b44172a8..3d05fb675 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,8 @@ For more information on a specific command, just type the command on terminal wh | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | | [WithdrawExpireUnfreeze](#How-to-freezev2) | [CancelAllUnfreezeV2](#How-to-freezev2) |[GetDelegatedResourceV2](#How-to-freezev2) | | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | -| [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) | | | +| [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) |[GetBandwidthPrices](#Prices) | [GetEnergyPrices](#Prices)| +| [GetMemoFee](#Prices) ||| Type any one of the listed commands, to display how-to tips. @@ -1473,6 +1474,36 @@ wallet> getCanWithdrawUnfreezeAmount TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 16711003 { "amount": 9000000 } +``` + > getbandwidthprices +> get historical unit price of bandwidth + +Example: +```console +wallet> getBandwidthPrices +{ + "prices": "0:10,1606537680000:40,1614238080000:140,1626581880000:1000,1626925680000:140,1627731480000:1000" +} +``` + > getenergyprices +> get historical unit price of energy + +Example: +```console +wallet> getEnergyPrices +{ + "prices": "0:100,1575871200000:10,1606537680000:40,1614238080000:140,1635739080000:280,1681895880000:420" +} +``` + > getmemofee +> get memo fee + +Example: +```console +wallet> getMemoFee +{ + "prices": "0:0,1675492680000:1000000" +} ``` diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index 0df2e528a..d068c2ada 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -115,6 +115,7 @@ public class Client { "GetAssetIssueByName", "GetAssetIssueListByName", "GetBalance", + "GetBandwidthPrices", "GetBlock", "GetBlockById", "GetBlockByIdOrNum", @@ -132,6 +133,7 @@ public class Client { "GetAvailableUnfreezeCount", "GetCanWithdrawUnfreezeAmount", "GetDiversifier", + "GetEnergyPrices", "GetExchange", "GetExpandedSpendingKey", "GetIncomingViewingKey", @@ -140,6 +142,7 @@ public class Client { "GetMarketOrderListByPair", "GetMarketPairList", "GetMarketPriceByPair", + "GetMemoFee", "GetNextMaintenanceTime", "GetNkFromNsk", "GetProposal", @@ -258,6 +261,7 @@ public class Client { "GetAssetIssueByName", "GetAssetIssueListByName", "GetBalance", + "GetBandwidthPrices", "GetBlock", "GetBlockById", "GetBlockByIdOrNum", @@ -275,6 +279,7 @@ public class Client { "GetAvailableUnfreezeCount", "GetCanWithdrawUnfreezeAmount", "GetDiversifier", + "GetEnergyPrices", "GetExchange", "GetExpandedSpendingKey", "GetIncomingViewingKey", @@ -283,6 +288,7 @@ public class Client { "GetMarketOrderListByPair", "GetMarketPairList", "GetMarketPriceByPair", + "GetMemoFee", "GetNextMaintenanceTime", "GetNkFromNsk", "GetProposal", @@ -1533,6 +1539,36 @@ private void cancelAllUnfreezeV2(String[] parameters) } } + private void getBandwidthPrices(String[] parameters) { + if (parameters.length > 0) { + System.out.println("Use GetBandwidthPrices command with below syntax: "); + System.out.println("GetBandwidthPrices"); + return; + } + PricesResponseMessage result = walletApiWrapper.getBandwidthPrices(); + System.out.println("The BandwidthPrices is " + result.getPrices()); + } + + private void getEnergyPrices(String[] parameters) { + if (parameters.length > 0) { + System.out.println("Use GetEnergyPrices command with below syntax: "); + System.out.println("GetEnergyPrices"); + return; + } + PricesResponseMessage result = walletApiWrapper.getEnergyPrices(); + System.out.println("The EnergyPrices is "+ result.getPrices()); + } + + private void getMemoFee(String[] parameters) { + if (parameters.length > 0) { + System.out.println("Use GetMemoFee command with below syntax: "); + System.out.println("GetMemoFee"); + return; + } + PricesResponseMessage result = walletApiWrapper.getMemoFee(); + System.out.println("The MemoFee is " + result.getPrices()); + } + private void unfreezeAsset(String[] parameters) throws IOException, CipherException, CancelException { System.out.println("Use UnfreezeAsset command like: "); @@ -4938,6 +4974,18 @@ private void run() { getBlockByIdOrNum(parameters); break; } + case "getbandwidthprices": { + getBandwidthPrices(parameters); + break; + } + case "getenergyprices": { + getEnergyPrices(parameters); + break; + } + case "getmemofee": { + getMemoFee(parameters); + break; + } case "exit": case "quit": { System.out.println("Exit !!!"); diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index 03c857b7d..fde565fb2 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -1293,6 +1293,18 @@ public GrpcAPI.NumberMessage getBrokerage(byte[] ownerAddress) { return WalletApi.getBrokerage(ownerAddress); } + public PricesResponseMessage getBandwidthPrices() { + return WalletApi.getBandwidthPrices(); + } + + public PricesResponseMessage getEnergyPrices() { + return WalletApi.getEnergyPrices(); + } + + public PricesResponseMessage getMemoFee() { + return WalletApi.getMemoFee(); + } + public boolean scanShieldedTRC20NoteByIvk(byte[] address, final String ivk, final String ak, final String nk, long start, long end, String[] events) { diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 0380e02a5..00d23983f 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -1001,6 +1001,29 @@ public NumberMessage getBrokerage(byte[] address) { } } + public PricesResponseMessage getBandwidthPrices() { + EmptyMessage message = EmptyMessage.newBuilder().build(); + if (blockingStubSolidity != null) { + return blockingStubSolidity.getBandwidthPrices(message); + } else { + return blockingStubFull.getBandwidthPrices(message); + } + } + + public PricesResponseMessage getEnergyPrices() { + EmptyMessage message = EmptyMessage.newBuilder().build(); + if (blockingStubSolidity != null) { + return blockingStubSolidity.getEnergyPrices(message); + } else { + return blockingStubFull.getEnergyPrices(message); + } + } + + public PricesResponseMessage getMemoFee() { + EmptyMessage message = EmptyMessage.newBuilder().build(); + return blockingStubFull.getMemoFee(message); + } + public Optional getTransactionInfoByBlockNum(long blockNum) { TransactionInfoList transactionInfoList; NumberMessage.Builder builder = NumberMessage.newBuilder(); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index a1db8ab64..68c10e45d 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -57,6 +57,7 @@ import org.tron.api.GrpcAPI.OvkDecryptParameters; import org.tron.api.GrpcAPI.OvkDecryptTRC20Parameters; import org.tron.api.GrpcAPI.PaymentAddressMessage; +import org.tron.api.GrpcAPI.PricesResponseMessage; import org.tron.api.GrpcAPI.PrivateParameters; import org.tron.api.GrpcAPI.PrivateParametersWithoutAsk; import org.tron.api.GrpcAPI.PrivateShieldedTRC20Parameters; @@ -2730,6 +2731,18 @@ public static GrpcAPI.NumberMessage getBrokerage(byte[] owner) { return rpcCli.getBrokerage(owner); } + public static PricesResponseMessage getBandwidthPrices() { + return rpcCli.getBandwidthPrices(); + } + + public static PricesResponseMessage getEnergyPrices() { + return rpcCli.getEnergyPrices(); + } + + public static PricesResponseMessage getMemoFee() { + return rpcCli.getMemoFee(); + } + public static Optional scanShieldedTRC20NoteByIvk( IvkDecryptTRC20Parameters parameters, boolean showErrorMsg) { try { From 230a078fe6c52b0bfd93ef9414fd72da495947dd Mon Sep 17 00:00:00 2001 From: lxcmyf Date: Sun, 8 Oct 2023 12:36:57 +0800 Subject: [PATCH 428/445] fix(api): fix API doc link jump (#670) --- README.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3d05fb675..f9f60ef72 100644 --- a/README.md +++ b/README.md @@ -64,43 +64,43 @@ We can configure java-tron node IP and port in ``src/main/resources/config.conf` Following is a list of Tron Wallet-cli commands: For more information on a specific command, just type the command on terminal when you start your Wallet. -| [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [ApproveProposal](#Approvecancel-the-proposal) | [AssetIssue](#How-to-issue-TRC10-tokens) | +| [AddTransactionSign](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [ApproveProposal](#Approve--disapprove-a-proposal) | [AssetIssue](#Issue-trc10-tokens) | | :---------:|:---------:|:--------: | | [BackupShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | [BackupWallet](#Wallet-related-commands)| [BackupWallet2Base64](#Wallet-related-commands) | -| [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#How-to-initiate-a-proposal) -| [DeleteProposal](#Cancel-the-created-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | +| [BroadcastTransaction](#Some-others) | [ChangePassword](#Wallet-related-commands)| [CreateProposal](#Initiate-a-proposal) +| [DeleteProposal](#Delete-an-existed-proposal) | [DeployContract](#How-to-use-smart-contract) | [ExchangeCreate](#How-to-trade-on-the-exchange) | | [ExchangeInject](#How-to-trade-on-the-exchange) | [ExchangeTransaction](#How-to-trade-on-the-exchange) | [ExchangeWithdraw](#How-to-trade-on-the-exchange) | -| [FreezeBalance](#How-to-delegate-resourcee) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| +| [FreezeBalance](#Delegate-resource) | [GenerateAddress](#Account-related-commands) | [GenerateShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token)| | [GetAccount](#Account-related-commands) |[GetAccountNet](#Account-related-commands) | [GetAccountResource](#Account-related-commands) | -| [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-issue-TRC10-tokens) | -| [GetAssetIssueById](#How-to-issue-TRC10-tokens) | [GetAssetIssueByName](#How-to-issue-TRC10-tokens) |[GetAssetIssueListByName](#How-to-issue-TRC10-tokens) | +| [GetAddress](#Account-related-commands) | [GetAkFromAsk](#How-to-transfer-shielded-TRC20-token) |[GetAssetIssueByAccount](#How-to-obtain-trc10-token-information) | +| [GetAssetIssueById](#How-to-obtain-trc10-token-information) | [GetAssetIssueByName](#How-to-obtain-trc10-token-information) |[GetAssetIssueListByName](#How-to-obtain-trc10-token-information) | | [GetBalance](#Account-related-commands) | [GetBlock](#How-to-get-block-information) |[GetBlockById](#How-to-get-block-information) | | [GetBlockByLatestNum](#How-to-get-block-information) | [GetBlockByLimitNext](#How-to-get-block-information) | [GetBrokerage](#Brokerage) | -| [GetContract](#How-to-use-smart-contracts) | [GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | +| [GetContract](#Get-details-of-a-smart-contract) | [GetDelegatedResource](#How-to-delegate-resource) |[GetDelegatedResourceAccountIndex](#How-to-delegate-resource) | | [GetDiversifier](#How-to-transfer-shielded-TRC20-token)| [GetExpandedSpendingKey](#How-to-transfer-shielded-TRC20-token)| [GetIncomingViewingKey](#How-to-transfer-shielded-TRC20-token) | | [GetMarketOrderByAccount](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderById](#How-to-use-tron-dex-to-sell-asset)| [GetMarketOrderListByPair](#How-to-use-tron-dex-to-sell-asset) | | [GetMarketPairList](#How-to-use-tron-dex-to-sell-asset)| [GetMarketPriceByPair](#How-to-use-tron-dex-to-sell-asset)| [GetNextMaintenanceTime](#Some-others) | -| [GetNkFromNsk](#How-to-transfer-shielded-TRC20-token) | [GetProposal](#Get-proposal-information) | [GetShieldedPaymentAddress](#How-to-transfer-shielded-TRC20-token)| +| [GetNkFromNsk](#How-to-transfer-shielded-TRC20-token) | [GetProposal](#Obtain-proposal-information) | [GetShieldedPaymentAddress](#How-to-transfer-shielded-TRC20-token)| | [GetSpendingKey](#How-to-transfer-shielded-TRC20-token) | [GetReward](#Brokerage) | [GetTransactionApprovedList](#How-to-use-the-multi-signature-feature-of-wallet-cli) | | [GetTransactionById](#How-to-get-transaction-information) | [GetTransactionCountByBlockNum](#How-to-get-transaction-information) | [GetTransactionInfoByBlockNum](#How-to-get-transaction-information) | | [GetTransactionInfoById](#How-to-get-transaction-information) | [GetTransactionSignWeight](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [ImportShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | -| [ImportWallet](#Wallet-related-commands) | [ImportWalletByBase64](#Wallet-related-commands) | [ListAssetIssue](#Get-Token10) | +| [ImportWallet](#Wallet-related-commands) | [ImportWalletByBase64](#Wallet-related-commands) | [ListAssetIssue](#How-to-obtain-trc10-token-information) | | [ListExchanges](#How-to-trade-on-the-exchange) | [ListExchangesPaginated](#How-to-trade-on-the-exchange) | [ListNodes](#Some-others) | -| [ListShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token) | [ListShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | [ListProposals](#How-to-initiate-a-proposal) | -| [ListProposalsPaginated](#How-to-initiate-a-proposal) | [ListWitnesses](#Some-others) | [LoadShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | +| [ListShieldedTRC20Address](#How-to-transfer-shielded-TRC20-token) | [ListShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | [ListProposals](#Obtain-proposal-information) | +| [ListProposalsPaginated](#Obtain-proposal-information) | [ListWitnesses](#Some-others) | [LoadShieldedTRC20Wallet](#How-to-transfer-shielded-TRC20-token) | | [Login](#Command-line-operation-flow-example) | [MarketCancelOrder](#How-to-use-tron-dex-to-sell-asset) | [MarketSellAsset](#How-to-use-tron-dex-to-sell-asset)| -| [ParticipateAssetIssue](#How-to-issue-TRC10-tokens) | [RegisterWallet](#Wallet-related-commands) | [ResetShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | +| [ParticipateAssetIssue](#Participating-in-the-issue-of-trc10-token) | [RegisterWallet](#Wallet-related-commands) | [ResetShieldedTRC20Note](#How-to-transfer-shielded-TRC20-token) | | [ScanShieldedTRC20NoteByIvk](#How-to-transfer-shielded-TRC20-token) | [ScanShieldedTRC20NoteByOvk](#How-to-transfer-shielded-TRC20-token) |[SendCoin](#How-to-use-the-multi-signature-feature-of-wallet-cli) | | [SendShieldedTRC20Coin](#How-to-transfer-shielded-TRC20-token) | [SendShieldedTRC20CoinWithoutAsk](#How-to-transfer-shielded-TRC20-token) | [SetShieldedTRC20ContractAddress](#How-to-transfer-shielded-TRC20-token) | -| [ShowShieldedTRC20AddressInfo](#How-to-transfer-shielded-TRC20-token) | [TransferAsset](#How-to-issue-TRC10-tokens) | [TriggerContract](#How-to-use-smart-contracts) | -| [UnfreezeAsset](#How-to-issue-TRC10-tokens) | [UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#How-to-issue-TRC10-tokens) | -| [UpdateBrokerage](#Brokerage) | [UpdateEnergyLimit](#How-to-use-smart-contracts) |[UpdateSetting](#How-to-use-smart-contracts) | +| [ShowShieldedTRC20AddressInfo](#How-to-transfer-shielded-TRC20-token) | [TransferAsset](#Trc10-token-transfer) | [TriggerContract](#Trigger-smart-contarct) | +| [UnfreezeAsset](#Unfreeze-trc10-token) | [UnfreezeBalance](#How-to-delegate-resource) |[UpdateAsset](#Update-parameters-of-trc10-token) | +| [UpdateBrokerage](#Brokerage) | [UpdateEnergyLimit](#Update-smart-contract-parameters) |[UpdateSetting](#Update-smart-contract-parameters) | | [UpdateAccountPermission](#How-to-use-the-multi-signature-feature-of-wallet-cli) | [VoteWitness](#How-to-vote) | [FreezeBalanceV2](#How-to-freezev2) | | [UnfreezeBalanceV2](#How-to-freezev2) | [DelegateResource](#How-to-freezev2) | [UnDelegateResource](#How-to-freezev2) | | [WithdrawExpireUnfreeze](#How-to-freezev2) | [CancelAllUnfreezeV2](#How-to-freezev2) |[GetDelegatedResourceV2](#How-to-freezev2) | | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | -| [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) |[GetBandwidthPrices](#Prices) | [GetEnergyPrices](#Prices)| -| [GetMemoFee](#Prices) ||| +| [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) |[GetBandwidthPrices](#Get-resource-prices-and-memo-fee) | [GetEnergyPrices](#Get-resource-prices-and-memo-fee)| +| [GetMemoFee](#Get-resource-prices-and-memo-fee) ||| Type any one of the listed commands, to display how-to tips. @@ -1475,6 +1475,7 @@ wallet> getCanWithdrawUnfreezeAmount TJAVcszse667FmSNCwU2fm6DmfM5D4AyDh 16711003 "amount": 9000000 } ``` +## Get resource prices and memo fee > getbandwidthprices > get historical unit price of bandwidth From e568763557b8820a6b3755092101c071d86d84fd Mon Sep 17 00:00:00 2001 From: liukai Date: Fri, 15 Mar 2024 18:28:24 +0800 Subject: [PATCH 429/445] feat(protobuf): add fields --- src/main/protos/api/api.proto | 2 ++ src/main/protos/core/Tron.proto | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/protos/api/api.proto b/src/main/protos/api/api.proto index 26c1429b5..1b479a8a5 100644 --- a/src/main/protos/api/api.proto +++ b/src/main/protos/api/api.proto @@ -1051,6 +1051,8 @@ message Return { SERVER_BUSY = 9; NO_CONNECTION = 10; NOT_ENOUGH_EFFECTIVE_CONNECTION = 11; + BLOCK_UNSOLIDIFIED = 12; + OTHER_ERROR = 20; } diff --git a/src/main/protos/core/Tron.proto b/src/main/protos/core/Tron.proto index 2fc08901e..41ef968d9 100644 --- a/src/main/protos/core/Tron.proto +++ b/src/main/protos/core/Tron.proto @@ -626,6 +626,7 @@ message HelloMessage { bytes signature = 8; int32 nodeType = 9; int64 lowestBlockNum = 10; + bytes codeVersion = 11; } message InternalTransaction { From 5072b67978df8d087ca7cbefa65761ea7a527706 Mon Sep 17 00:00:00 2001 From: halibobo1205 Date: Tue, 23 Apr 2024 17:37:33 +0800 Subject: [PATCH 430/445] feat(dependency): update dependencies --- build.gradle | 36 +++++++++---------- src/main/java/org/tron/demo/ECKeyDemo.java | 3 +- .../java/org/tron/demo/SelectFileDemo.java | 2 +- src/main/java/org/tron/walletcli/Client.java | 1 + .../org/tron/walletserver/GrpcClient.java | 6 ++-- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index 54016c42b..166a51994 100644 --- a/build.gradle +++ b/build.gradle @@ -45,10 +45,9 @@ buildscript { } ext { projectVersion = '1.3.0-RELEASE' - grpcVersion = '1.6.1' - protobufVersion = '3.3.0' + grpcVersion = '1.60.0' + protobufVersion = '3.21.12' protobufGradlePluginVersion = '0.8.0' - springCloudConsulVersion = '1.2.1.RELEASE' } dependencies { @@ -63,44 +62,40 @@ task wrapper(type: Wrapper) { } dependencies { - compile group: 'junit', name: 'junit', version: '4.12' - compile group: 'com.beust', name: 'jcommander', version: '1.72' + compile group: 'junit', name: 'junit', version: '4.13.2' + compile group: 'com.beust', name: 'jcommander', version: '1.82' //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' - compile 'com.maxmind.geoip2:geoip2:2.10.0' + compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.13' // google grpc - compile group: 'io.grpc', name: 'grpc-netty', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-protobuf', version: '1.9.0' - compile group: 'io.grpc', name: 'grpc-stub', version: '1.9.0' + compile group: 'io.grpc', name: 'grpc-netty', version: "${grpcVersion}" + compile group: 'io.grpc', name: 'grpc-protobuf', version: "${grpcVersion}" + compile group: 'io.grpc', name: 'grpc-stub', version: "${grpcVersion}" compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.69' + compile group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: '1.78.1' compile group: 'com.typesafe', name: 'config', version: '1.3.2' - compile "com.google.code.findbugs:jsr305:3.0.0" - compile "org.springframework.cloud:spring-cloud-starter-consul-discovery:${springCloudConsulVersion}" - compile "org.apache.commons:commons-collections4:4.0" - compile "org.apache.commons:commons-lang3:3.4" + compile "org.apache.commons:commons-lang3:3.14.0" compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' - compile 'com.alibaba:fastjson:1.2.76' + compile 'com.alibaba:fastjson:1.2.83' - compile group: 'commons-io', name: 'commons-io', version: '2.6' + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1' compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - compile group: 'org.jline', name: 'jline', version: '3.15.0' + compile group: 'org.jline', name: 'jline', version: '3.25.0' compile 'io.github.tronprotocol:zksnark-java-sdk:1.0.0' } protobuf { generatedFilesBaseDir = "$projectDir/src/" protoc { - artifact = "com.google.protobuf:protoc:3.5.1-1" + artifact = "com.google.protobuf:protoc:${protobufVersion}" } plugins { grpc { - artifact = 'io.grpc:protoc-gen-grpc-java:1.9.0' + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } } generateProtoTasks { @@ -129,4 +124,5 @@ shadowJar { baseName = 'wallet-cli' classifier = null version = null + mergeServiceFiles() // https://github.com/grpc/grpc-java/issues/10853 } diff --git a/src/main/java/org/tron/demo/ECKeyDemo.java b/src/main/java/org/tron/demo/ECKeyDemo.java index 3f3f4a707..d0a13292a 100644 --- a/src/main/java/org/tron/demo/ECKeyDemo.java +++ b/src/main/java/org/tron/demo/ECKeyDemo.java @@ -5,7 +5,6 @@ import java.math.BigInteger; import java.util.Arrays; import org.bouncycastle.math.ec.ECPoint; -import org.springframework.util.StringUtils; import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; @@ -50,7 +49,7 @@ public static String address2Encode58CheckDemo(byte[] input) { private static String private2Address(byte[] privateKey) throws CipherException { ECKey eCkey; - if (StringUtils.isEmpty(privateKey)) { + if (privateKey == null ) { eCkey = new ECKey(Utils.getRandom()); // Gen new Keypair } else { eCkey = ECKey.fromPrivate(privateKey); diff --git a/src/main/java/org/tron/demo/SelectFileDemo.java b/src/main/java/org/tron/demo/SelectFileDemo.java index 764d496b8..a0050e339 100644 --- a/src/main/java/org/tron/demo/SelectFileDemo.java +++ b/src/main/java/org/tron/demo/SelectFileDemo.java @@ -1,6 +1,6 @@ package org.tron.demo; -import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.ArrayUtils; import java.io.File; import java.io.IOException; diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index d068c2ada..c1c20e361 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -4399,6 +4399,7 @@ private void run() { LineReader lineReader = LineReaderBuilder.builder() .terminal(terminal) .completer(commandCompleter) + .option(LineReader.Option.CASE_INSENSITIVE, true) .build(); String prompt = "wallet> "; diff --git a/src/main/java/org/tron/walletserver/GrpcClient.java b/src/main/java/org/tron/walletserver/GrpcClient.java index 00d23983f..6e6146614 100644 --- a/src/main/java/org/tron/walletserver/GrpcClient.java +++ b/src/main/java/org/tron/walletserver/GrpcClient.java @@ -8,7 +8,7 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.*; import org.tron.api.GrpcAPI.Return.response_code; @@ -96,13 +96,13 @@ public class GrpcClient { public GrpcClient(String fullnode, String soliditynode) { if (!StringUtils.isEmpty(fullnode)) { channelFull = ManagedChannelBuilder.forTarget(fullnode) - .usePlaintext(true) + .usePlaintext() .build(); blockingStubFull = WalletGrpc.newBlockingStub(channelFull); } if (!StringUtils.isEmpty(soliditynode)) { channelSolidity = ManagedChannelBuilder.forTarget(soliditynode) - .usePlaintext(true) + .usePlaintext() .build(); blockingStubSolidity = WalletSolidityGrpc.newBlockingStub(channelSolidity); blockingStubExtension = WalletExtensionGrpc.newBlockingStub(channelSolidity); From e6c54bf250f478d25c3d78563b267c2cfa383768 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 24 Sep 2024 15:02:11 +0800 Subject: [PATCH 431/445] update gradle to 6.4 and protobufVersion,grpcVersion --- build.gradle | 92 +++++++++++++----------- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/build.gradle b/build.gradle index 166a51994..54cada71b 100644 --- a/build.gradle +++ b/build.gradle @@ -10,12 +10,12 @@ sourceCompatibility = 1.8 targetCompatibility = JavaVersion.VERSION_1_8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' -compileJava.options*.compilerArgs = [ - "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", - "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", - "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", - "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" -] +//compileJava.options*.compilerArgs = [ +// "-Xlint:serial", "-Xlint:varargs", "-Xlint:classfile", "-Xlint:dep-ann", +// "-Xlint:divzero", "-Xlint:empty", "-Xlint:finally", "-Xlint:overrides", +// "-Xlint:path", "-Xlint:static", "-Xlint:try", "-Xlint:fallthrough", +// "-Xlint:deprecation", "-Xlint:unchecked", "-Xlint:-options" +//] repositories { mavenLocal() @@ -23,6 +23,9 @@ repositories { maven { url 'https://jitpack.io' } } +def protobufVersion = "3.25.5" +def grpcVersion = "1.60.0" + sourceSets { main { proto { @@ -33,8 +36,8 @@ sourceSets { srcDir 'src/main/java' } } - } + buildscript { repositories { mavenLocal() @@ -43,59 +46,58 @@ buildscript { } mavenCentral() } - ext { - projectVersion = '1.3.0-RELEASE' - grpcVersion = '1.60.0' - protobufVersion = '3.21.12' - protobufGradlePluginVersion = '0.8.0' - } dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2' - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' - classpath "gradle.plugin.com.liferay:gradle-plugins-node:4.3.0" + classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' + classpath "gradle.plugin.com.liferay:gradle-plugins-node:7.0.2" } } -task wrapper(type: Wrapper) { - gradleVersion = '3.3' -} dependencies { - compile group: 'junit', name: 'junit', version: '4.13.2' - compile group: 'com.beust', name: 'jcommander', version: '1.82' + implementation group: 'junit', name: 'junit', version: '4.13.2' + implementation group: 'com.beust', name: 'jcommander', version: '1.82' //compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.13' + implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' + implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.13' + + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' // google grpc - compile group: 'io.grpc', name: 'grpc-netty', version: "${grpcVersion}" - compile group: 'io.grpc', name: 'grpc-protobuf', version: "${grpcVersion}" - compile group: 'io.grpc', name: 'grpc-stub', version: "${grpcVersion}" - - compile group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' - compile group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: '1.78.1' - compile group: 'com.typesafe', name: 'config', version: '1.3.2' - compile "org.apache.commons:commons-lang3:3.14.0" - compile group: 'com.google.api.grpc', name: 'googleapis-common-protos', version: '0.0.3' - compile 'com.alibaba:fastjson:1.2.83' - - compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1' - compile group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - - compile group: 'org.jline', name: 'jline', version: '3.25.0' - compile 'io.github.tronprotocol:zksnark-java-sdk:1.0.0' + implementation group: 'io.grpc', name: 'grpc-netty', version: grpcVersion + implementation group: 'io.grpc', name: 'grpc-protobuf', version: grpcVersion + implementation group: 'io.grpc', name: 'grpc-stub', version: grpcVersion + + implementation group: 'com.google.protobuf', name: 'protobuf-java', version: protobufVersion + implementation group: 'com.google.protobuf', name: 'protobuf-java-util', version: protobufVersion + + implementation group: 'com.google.code.gson', name: 'gson', version: '2.11.0' + + implementation group: 'com.googlecode.protobuf-java-format', name: 'protobuf-java-format', version: '1.4' + implementation group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: '1.78.1' + implementation group: 'com.typesafe', name: 'config', version: '1.3.2' + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0' + implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.83' + + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1' + implementation group: 'org.projectlombok', name: 'lombok', version: '1.18.12' + + implementation group: 'org.jline', name: 'jline', version: '3.25.0' + implementation 'io.github.tronprotocol:zksnark-java-sdk:1.0.0' } protobuf { - generatedFilesBaseDir = "$projectDir/src/" + generatedFilesBaseDir = "$projectDir/src" protoc { - artifact = "com.google.protobuf:protoc:${protobufVersion}" - + artifact = "com.google.protobuf:protoc:$protobufVersion" } plugins { grpc { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" } } generateProtoTasks { @@ -114,6 +116,10 @@ protobuf { } } +clean.doFirst { + delete "src/main/gen" +} + run { standardInput = System.in mainClassName = 'org.tron.walletcli.Client' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0ac4c699..a7b108d65 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip From 1503b4040939dff9b67d848f67a6f12a5841146c Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 24 Sep 2024 15:14:54 +0800 Subject: [PATCH 432/445] reorg gradle --- build.gradle | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 54cada71b..b954b21cf 100644 --- a/build.gradle +++ b/build.gradle @@ -62,16 +62,12 @@ dependencies { implementation group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.13' - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' - testCompileOnly 'org.projectlombok:lombok:1.18.24' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' - - // google grpc + // grpc implementation group: 'io.grpc', name: 'grpc-netty', version: grpcVersion implementation group: 'io.grpc', name: 'grpc-protobuf', version: grpcVersion implementation group: 'io.grpc', name: 'grpc-stub', version: grpcVersion + // google protobuf implementation group: 'com.google.protobuf', name: 'protobuf-java', version: protobufVersion implementation group: 'com.google.protobuf', name: 'protobuf-java-util', version: protobufVersion @@ -82,12 +78,15 @@ dependencies { implementation group: 'com.typesafe', name: 'config', version: '1.3.2' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0' implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.83' - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1' - implementation group: 'org.projectlombok', name: 'lombok', version: '1.18.12' + + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' implementation group: 'org.jline', name: 'jline', version: '3.25.0' - implementation 'io.github.tronprotocol:zksnark-java-sdk:1.0.0' + implementation group: 'io.github.tronprotocol', name: 'zksnark-java-sdk', version: '1.0.0' } protobuf { From 0ab3e47a707bca157c4f9f92280b0007d8bf0194 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 11:30:45 +0800 Subject: [PATCH 433/445] update gradle config --- gradle/wrapper/gradle-wrapper.jar | Bin 54706 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 51 ++++++++++++++--------- gradlew.bat | 21 +++++++++- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5df2223776002d810d5773dd0f9b597fda4e54ad..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch literal 58694 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 literal 54706 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a7b108d65..a4b442974 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Mar 06 21:13:26 CST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip diff --git a/gradlew b/gradlew index cccdd3d51..2fe81a7d9 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -138,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a..9109989e3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome From cd4b5edc70c0fe18aedaad049a303af2705c3711 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 14:51:03 +0800 Subject: [PATCH 434/445] revert some changes --- build.gradle | 2 +- src/main/java/org/tron/keystore/StringUtils.java | 3 --- src/test/java/org/tron/keystore/StringUtilsTest.java | 10 ---------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index b954b21cf..b5fbcae26 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { maven { url 'https://jitpack.io' } } -def protobufVersion = "3.25.5" +def protobufVersion = "3.21.12" def grpcVersion = "1.60.0" sourceSets { diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java index d722340db..66a9f5cde 100755 --- a/src/main/java/org/tron/keystore/StringUtils.java +++ b/src/main/java/org/tron/keystore/StringUtils.java @@ -59,9 +59,6 @@ public static boolean isContains(char[] a, char[] b) { } int alen = a.length; int blen = b.length; - if (alen < blen) { - return false; - } for (int i = 0; i < alen; i++) { if (alen - i < blen) { return false; diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java index ac0282a70..f6ff38edc 100755 --- a/src/test/java/org/tron/keystore/StringUtilsTest.java +++ b/src/test/java/org/tron/keystore/StringUtilsTest.java @@ -29,16 +29,6 @@ public void isContains() { char[] b = "ghijkl".toCharArray(); char[] c = "defghi".toCharArray(); char[] d = "abcdefghijkl".toCharArray(); - char[] empty = "".toCharArray(); - - char[] longarr = "xxxxxxxx123xxxxxxxxx".toCharArray(); - char[] shortarr = "123".toCharArray(); - Assert.assertFalse(StringUtils.isContains(shortarr, longarr)); - Assert.assertTrue(StringUtils.isContains(longarr, shortarr)); - - Assert.assertFalse(StringUtils.isContains(empty, d)); - Assert.assertFalse(StringUtils.isContains(d, empty)); - Assert.assertTrue(StringUtils.isContains(d, d)); Assert.assertTrue(StringUtils.isContains(d, a)); Assert.assertTrue(StringUtils.isContains(d, b)); From cb3dd8448a6250220ed1d43c8625d78a6b667d05 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 14:53:17 +0800 Subject: [PATCH 435/445] revert some changes --- src/main/java/org/tron/keystore/StringUtils.java | 2 ++ src/test/java/org/tron/keystore/StringUtilsTest.java | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java index 66a9f5cde..d2224a508 100755 --- a/src/main/java/org/tron/keystore/StringUtils.java +++ b/src/main/java/org/tron/keystore/StringUtils.java @@ -57,8 +57,10 @@ public static boolean isContains(char[] a, char[] b) { if (ArrayUtils.isEmpty(a) || ArrayUtils.isEmpty(b)) { return false; } + int alen = a.length; int blen = b.length; + for (int i = 0; i < alen; i++) { if (alen - i < blen) { return false; diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java index f6ff38edc..cf9d91b45 100755 --- a/src/test/java/org/tron/keystore/StringUtilsTest.java +++ b/src/test/java/org/tron/keystore/StringUtilsTest.java @@ -7,6 +7,7 @@ public class StringUtilsTest { + @Test public void isCharEqual() { char[] a = "aaaaaa".toCharArray(); From 2771dd9bbcf1faa80e8b23b56760500c85918bfa Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 14:54:50 +0800 Subject: [PATCH 436/445] revert some changes --- src/test/java/org/tron/keystore/StringUtilsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java index cf9d91b45..194cc82db 100755 --- a/src/test/java/org/tron/keystore/StringUtilsTest.java +++ b/src/test/java/org/tron/keystore/StringUtilsTest.java @@ -7,7 +7,7 @@ public class StringUtilsTest { - + @Test public void isCharEqual() { char[] a = "aaaaaa".toCharArray(); From 1e3286d5dce56c7e82761e6f07d547b2c6441274 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 15:16:22 +0800 Subject: [PATCH 437/445] revert some changes --- src/main/java/org/tron/keystore/StringUtils.java | 0 src/test/java/org/tron/keystore/StringUtilsTest.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/main/java/org/tron/keystore/StringUtils.java mode change 100755 => 100644 src/test/java/org/tron/keystore/StringUtilsTest.java diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java old mode 100755 new mode 100644 diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java old mode 100755 new mode 100644 From cc1d4c8088048560b6e46808769a55378db82b67 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 16:01:16 +0800 Subject: [PATCH 438/445] update protobufVersion from 3.21.12 to 3.25.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b5fbcae26..b954b21cf 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { maven { url 'https://jitpack.io' } } -def protobufVersion = "3.21.12" +def protobufVersion = "3.25.5" def grpcVersion = "1.60.0" sourceSets { From 707624379a646114ef0437ea5a8f82ed9940d25b Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Tue, 8 Oct 2024 16:43:41 +0800 Subject: [PATCH 439/445] merge pr 436 into 4.7.6 --- src/main/java/org/tron/keystore/StringUtils.java | 5 +++-- src/test/java/org/tron/keystore/StringUtilsTest.java | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/keystore/StringUtils.java b/src/main/java/org/tron/keystore/StringUtils.java index d2224a508..d722340db 100644 --- a/src/main/java/org/tron/keystore/StringUtils.java +++ b/src/main/java/org/tron/keystore/StringUtils.java @@ -57,10 +57,11 @@ public static boolean isContains(char[] a, char[] b) { if (ArrayUtils.isEmpty(a) || ArrayUtils.isEmpty(b)) { return false; } - int alen = a.length; int blen = b.length; - + if (alen < blen) { + return false; + } for (int i = 0; i < alen; i++) { if (alen - i < blen) { return false; diff --git a/src/test/java/org/tron/keystore/StringUtilsTest.java b/src/test/java/org/tron/keystore/StringUtilsTest.java index 194cc82db..5046e73d3 100644 --- a/src/test/java/org/tron/keystore/StringUtilsTest.java +++ b/src/test/java/org/tron/keystore/StringUtilsTest.java @@ -30,6 +30,16 @@ public void isContains() { char[] b = "ghijkl".toCharArray(); char[] c = "defghi".toCharArray(); char[] d = "abcdefghijkl".toCharArray(); + char[] empty = "".toCharArray(); + + char[] longarr = "xxxxxxxx123xxxxxxxxx".toCharArray(); + char[] shortarr = "123".toCharArray(); + Assert.assertFalse(StringUtils.isContains(shortarr, longarr)); + Assert.assertTrue(StringUtils.isContains(longarr, shortarr)); + + Assert.assertFalse(StringUtils.isContains(empty, d)); + Assert.assertFalse(StringUtils.isContains(d, empty)); + Assert.assertTrue(StringUtils.isContains(d, d)); Assert.assertTrue(StringUtils.isContains(d, a)); Assert.assertTrue(StringUtils.isContains(d, b)); From a7aba0653de1c7f1f6c6fc4ae8fca5b7b62622a1 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Wed, 6 Nov 2024 17:15:16 +0800 Subject: [PATCH 440/445] update dex instruction url to replace invalid url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9f60ef72..be4ba8681 100644 --- a/README.md +++ b/README.md @@ -584,8 +584,8 @@ GetProposal ## How to trade on the exchange -The trading and price fluctuations of trading pairs are in accordance with the [Bancor Agreement](https://storage.googleapis.com/website-bancor/2018/04/01ba8253-bancor_protocol_whitepaper_en.pdf), -which can be found in TRON's [related documents](https://developers.tron.network/docs/tronscan-dex). +The trading and price fluctuations of trading pairs are in accordance with the [Bancor Agreement](https://cryptopapers.info/assets/pdf/bancor.pdf), +which can be found in TRON's [related documents](https://github.com/tronprotocol/documentation-en/blob/master/docs/mechanism-algorithm/dex.md). ### Create a trading pair From 61e5632f46ba91e1e4d701333658b0c243c5e636 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Thu, 7 Nov 2024 11:40:13 +0800 Subject: [PATCH 441/445] upload bancor.pdf --- README.md | 2 +- docs/bancor.pdf | Bin 0 -> 213151 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/bancor.pdf diff --git a/README.md b/README.md index be4ba8681..935bbfe8c 100644 --- a/README.md +++ b/README.md @@ -584,7 +584,7 @@ GetProposal ## How to trade on the exchange -The trading and price fluctuations of trading pairs are in accordance with the [Bancor Agreement](https://cryptopapers.info/assets/pdf/bancor.pdf), +The trading and price fluctuations of trading pairs are in accordance with the [Bancor Agreement](https://github.com/tronprotocol/wallet-cli/blob/develop/docs/bancor.pdf), which can be found in TRON's [related documents](https://github.com/tronprotocol/documentation-en/blob/master/docs/mechanism-algorithm/dex.md). ### Create a trading pair diff --git a/docs/bancor.pdf b/docs/bancor.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b9bf0e7166659d5cb99d0848bfcdd0c0846e7777 GIT binary patch literal 213151 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
      K&`Z{+7oV={DQ-J+7P|_Q1bDqj_s%w8l|xe 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

      u>6;{&3(z7~HZxIF1tfT+%nS_5t1frt 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 z®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 ABLDyZ literal 0 HcmV?d00001 From 334da91fa37f7ee6fa1e274d46c236f2f11f31a1 Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Thu, 7 Nov 2024 17:20:34 +0800 Subject: [PATCH 442/445] update Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 935bbfe8c..8008661d6 100644 --- a/README.md +++ b/README.md @@ -584,8 +584,8 @@ GetProposal ## How to trade on the exchange -The trading and price fluctuations of trading pairs are in accordance with the [Bancor Agreement](https://github.com/tronprotocol/wallet-cli/blob/develop/docs/bancor.pdf), -which can be found in TRON's [related documents](https://github.com/tronprotocol/documentation-en/blob/master/docs/mechanism-algorithm/dex.md). +The trading and price fluctuations of trading pairs are in accordance with the Bancor Agreement, +which can be found in TRON's [related documents](https://tronprotocol.github.io/documentation-en/clients/wallet-cli-command/#dex). ### Create a trading pair From 796d738c78aacf0269828b1df832bfaa7d57ac8a Mon Sep 17 00:00:00 2001 From: jiangyuanshu <317787106@qq.com> Date: Thu, 7 Nov 2024 17:22:48 +0800 Subject: [PATCH 443/445] remove pdf --- docs/bancor.pdf | Bin 213151 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/bancor.pdf diff --git a/docs/bancor.pdf b/docs/bancor.pdf deleted file mode 100644 index b9bf0e7166659d5cb99d0848bfcdd0c0846e7777..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213151 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

        K&`Z{+7oV={DQ-J+7P|_Q1bDqj_s%w8l|xe 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

        u>6;{&3(z7~HZxIF1tfT+%nS_5t1frt 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 z®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 ABLDyZ From e327d7444fb73ebe48260fedfb04940309afa8df Mon Sep 17 00:00:00 2001 From: Marek Lazar Date: Fri, 31 Jan 2025 06:22:13 +0100 Subject: [PATCH 444/445] Create jekyll-gh-pages.yml --- .github/workflows/jekyll-gh-pages.yml | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/jekyll-gh-pages.yml diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml new file mode 100644 index 000000000..f8822cea6 --- /dev/null +++ b/.github/workflows/jekyll-gh-pages.yml @@ -0,0 +1,51 @@ +# Sample workflow for building and deploying a Jekyll site to GitHub Pages +name: Deploy Jekyll with GitHub Pages dependencies preinstalled + +on: + # Runs on pushes targeting the default branch + push: + branches: ["develop"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./ + destination: ./_site + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 785f7a502dbac682860a6fd89c299084608204aa Mon Sep 17 00:00:00 2001 From: Marek Lazar Date: Fri, 31 Jan 2025 06:46:00 +0000 Subject: [PATCH 445/445] Mareklazar:develop --- .vscode/extensions.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..d1987c7ba --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file