From b17a6a26a49e77bb4298f87c655bb772e86d7347 Mon Sep 17 00:00:00 2001 From: liqiang Date: Thu, 25 Oct 2018 11:51:44 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E9=80=BB=E8=BE=91;=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bytom-sdk/README.md | 160 +++ bytom-sdk/doc/index.md | 849 ++++++++++++ bytom-sdk/doc/transactions.md | 245 ++++ bytom-sdk/pom.xml | 180 +++ .../main/java/io/bytom/api/AccessToken.java | 164 +++ .../src/main/java/io/bytom/api/Account.java | 336 +++++ .../src/main/java/io/bytom/api/Asset.java | 369 +++++ .../src/main/java/io/bytom/api/Balance.java | 130 ++ .../src/main/java/io/bytom/api/Block.java | 324 +++++ .../main/java/io/bytom/api/CoreConfig.java | 96 ++ bytom-sdk/src/main/java/io/bytom/api/Key.java | 148 +++ .../src/main/java/io/bytom/api/Message.java | 122 ++ .../java/io/bytom/api/RawTransaction.java | 154 +++ .../src/main/java/io/bytom/api/Receiver.java | 49 + .../main/java/io/bytom/api/Transaction.java | 1182 +++++++++++++++++ .../io/bytom/api/UnconfirmedTransaction.java | 197 +++ .../main/java/io/bytom/api/UnspentOutput.java | 132 ++ .../src/main/java/io/bytom/api/Wallet.java | 153 +++ .../java/io/bytom/common/Configuration.java | 29 + .../bytom/common/ParameterizedTypeImpl.java | 30 + .../src/main/java/io/bytom/common/Utils.java | 9 + .../java/io/bytom/exception/APIException.java | 169 +++ .../io/bytom/exception/BadURLException.java | 14 + .../io/bytom/exception/BuildException.java | 49 + .../io/bytom/exception/BytomException.java | 32 + .../exception/ConfigurationException.java | 23 + .../exception/ConnectivityException.java | 36 + .../io/bytom/exception/HTTPException.java | 23 + .../io/bytom/exception/JSONException.java | 39 + .../java/io/bytom/http/BatchResponse.java | 140 ++ .../src/main/java/io/bytom/http/Client.java | 815 ++++++++++++ .../java/io/bytom/http/SuccessMessage.java | 8 + .../src/main/resources/config.properties | 8 + bytom-sdk/src/main/resources/log4j.properties | 20 + bytom-sdk/src/test/java/io/bytom/AppTest.java | 109 ++ .../src/test/java/io/bytom/TestUtils.java | 31 + .../io/bytom/integration/AccessTokenTest.java | 39 + .../io/bytom/integration/AccountTest.java | 97 ++ .../java/io/bytom/integration/AssetTest.java | 71 + .../io/bytom/integration/BalanceTest.java | 46 + .../java/io/bytom/integration/BlockTest.java | 85 ++ .../io/bytom/integration/CoreConfigTest.java | 40 + .../java/io/bytom/integration/KeyTest.java | 59 + .../io/bytom/integration/MessageTest.java | 50 + .../bytom/integration/RawTransactionTest.java | 34 + .../io/bytom/integration/TransactionTest.java | 334 +++++ .../java/io/bytom/integration/UTXOTest.java | 31 + .../UnconfirmedTransactionTest.java | 39 + .../java/io/bytom/integration/WalletTest.java | 43 + pom.xml | 123 +- tx-signer | 1 + 51 files changed, 7554 insertions(+), 112 deletions(-) create mode 100644 bytom-sdk/README.md create mode 100644 bytom-sdk/doc/index.md create mode 100644 bytom-sdk/doc/transactions.md create mode 100644 bytom-sdk/pom.xml create mode 100644 bytom-sdk/src/main/java/io/bytom/api/AccessToken.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Account.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Asset.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Balance.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Block.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/CoreConfig.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Key.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Message.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/RawTransaction.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Receiver.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Transaction.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/UnconfirmedTransaction.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/UnspentOutput.java create mode 100644 bytom-sdk/src/main/java/io/bytom/api/Wallet.java create mode 100644 bytom-sdk/src/main/java/io/bytom/common/Configuration.java create mode 100644 bytom-sdk/src/main/java/io/bytom/common/ParameterizedTypeImpl.java create mode 100644 bytom-sdk/src/main/java/io/bytom/common/Utils.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/APIException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/BadURLException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/BuildException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/BytomException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/ConfigurationException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/ConnectivityException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/HTTPException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/exception/JSONException.java create mode 100644 bytom-sdk/src/main/java/io/bytom/http/BatchResponse.java create mode 100644 bytom-sdk/src/main/java/io/bytom/http/Client.java create mode 100644 bytom-sdk/src/main/java/io/bytom/http/SuccessMessage.java create mode 100644 bytom-sdk/src/main/resources/config.properties create mode 100644 bytom-sdk/src/main/resources/log4j.properties create mode 100644 bytom-sdk/src/test/java/io/bytom/AppTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/TestUtils.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/AccessTokenTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/AccountTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/AssetTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/BalanceTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/BlockTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/CoreConfigTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/KeyTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/MessageTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/RawTransactionTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/TransactionTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/UTXOTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java create mode 100644 bytom-sdk/src/test/java/io/bytom/integration/WalletTest.java create mode 160000 tx-signer diff --git a/bytom-sdk/README.md b/bytom-sdk/README.md new file mode 100644 index 0000000..903792c --- /dev/null +++ b/bytom-sdk/README.md @@ -0,0 +1,160 @@ +# Bytom java-sdk + +This page will document the API classes and ways to properly use the API. +Subsequent new releases also maintain backward compatibility with this class +approach. For more information, please see Bytom API reference documentation +at [Bytom wiki](https://github.com/Bytom/bytom/wiki/API-Reference) + +## Installation + +There are various ways to install and use this sdk. We'll provide three ways to get it. Note that the bytom-sdk requires JAVA 7 or newer. + +### Apache Maven + +```xml + + io.bytom + bytom-sdk + 1.0.0 + +``` + +### Gradle/Grails +```xml +compile 'io.bytom:bytom-sdk:1.0.0' +``` + +### Building from source code + +To clone, compile, and install in your local maven repository (or copy the artifacts from the target/ directory to wherever you need them): + +```shell +git clone https://github.com/Bytom/bytom-java-sdk.git +cd java-sdk +mvn package -Dmaven.test.skip=true +mvn install +``` + +## Basic Usage + +```java +public static Client generateClient() throws BytomException { + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888/"; + } + return new Client(coreURL, accessToken); +} + +Client client = Client.generateClient(); +``` +> Note: you can touch a file named ```config.properties``` in resources folder to config ```bytom.api.url``` and ```client.access.token``` by custom. + +## Usage + +* [`Step 1: Create a key`](#create-a-key) +* [`Step 2: Create an account`](#create-an-account) +* [`Step 3: Create an receiver`](#create-an-receiver) +* [`Step 4: Create an asset`](#create-an-asset) +* [`Step 5: Issue asset`](#issue-asset) + * [`Firstly build the transaction`](#firstly-build-the-transaction) + * [`Secondly sign the transaction`](#secondly-sign-the-transaction) + * [`Finally submit the transaction`](#finally-submit-the-transaction) + +> For more details, see [API methods](https://github.com/Bytom/java-sdk/blob/master/doc/index.md#api-methods) + +### Create a key + +```java +String alias = "test"; +String password = "123456"; + +Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); +Key key = Key.create(client, builder); +``` + +### Create an account + +```java +String alias = "sender-account"; +Integer quorum = 1; +List root_xpubs = new ArrayList(); +root_xpubs.add(senderKey.xpub); + +Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + +Account account = Account.create(client, builder); +``` + +### Create an receiver + +```java +String alias = receiverAccount.alias; +String id = receiverAccount.id; + +Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); +Receiver receiver = receiverBuilder.create(client); +``` + +### Create an asset + +```java + String alias = "receiver-asset"; + +List xpubs = receiverAccount.xpubs; + +Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); +receiverAsset = builder.create(client); +``` + +### Issue asset + +For more transaction details, see [transactions](https://github.com/Bytom/java-sdk/blob/master/doc/transactions.md) + +#### Firstly build the transaction + +```java +Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); +``` + +#### Secondly sign the transaction + +```java +Transaction.Template singer = new Transaction.SignerBuilder().sign(client, + controlAddress, "123456"); +``` + +#### Finally submit the transaction + +```java +Transaction.SubmitResponse txs = Transaction.submit(client, singer); +``` + + +### All usage examples + +For more details you can see [doc](https://github.com/Bytom/bytom-java-sdk/blob/master/doc/index.md#api-methods). And you can find Test Cases at [Junit Test Cases](https://github.com/Bytom/bytom-java-sdk/tree/master/src/test/java/io/bytom/integration) + +## Support and Feedback + +If you find a bug, please submit the issue in Github directly by using [Issues](https://github.com/Bytom/bytom-java-sdk/issues) + +## License + +Bytom JAVA SDK is based on the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) protocol. diff --git a/bytom-sdk/doc/index.md b/bytom-sdk/doc/index.md new file mode 100644 index 0000000..1839c8f --- /dev/null +++ b/bytom-sdk/doc/index.md @@ -0,0 +1,849 @@ +# Java SDK documentation + +This page will document the API classes and ways to properly use the API. +Subsequent new releases also maintain backward compatibility with this class approach. + +## Basic Usage + +``` +public static Client generateClient() throws BytomException { + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888/"; + } + return new Client(coreURL, accessToken); +} + +Client client = TestUtils.generateClient(); +``` + +## Usage + +* [`Step 1: Create a key`](#create-a-key) +* [`Step 2: Create an account`](#create-an-account) +* [`Step 3: Create an receiver`](#create-an-receiver) +* [`Step 4: Create an asset`](#create-an-asset) +* [`Step 5: Issue asset`](#issue-asset) + * [`Firstly build the transaction`](#firstly-build-the-transaction) + * [`Secondly sign the transaction`](#secondly-sign-the-transaction) + * [`Finally submit the transaction`](#finally-submit-the-transaction) + +> For more details, see [`API methods`](#api-methods) + +## Create a key + +```java +String alias = "test"; +String password = "123456"; + +Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); +Key key = Key.create(client, builder); +``` + +## Create an account + +```java +String alias = "sender-account"; +Integer quorum = 1; +List root_xpubs = new ArrayList(); +root_xpubs.add(senderKey.xpub); + +Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + +Account account = Account.create(client, builder); +``` + +## Create an receiver + +```java +String alias = receiverAccount.alias; +String id = receiverAccount.id; + +Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); +Receiver receiver = receiverBuilder.create(client); +``` + +## Create an asset + +```java + String alias = "receiver-asset"; + +List xpubs = receiverAccount.xpubs; + +Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); +receiverAsset = builder.create(client); +``` + +## Issue asset + +### Firstly build the transaction + +```java +Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); +``` + +### Secondly sign the transaction + +```java +Transaction.Template singer = new Transaction.SignerBuilder().sign(client, + controlAddress, "123456"); +``` + +### Finally submit the transaction + +```java +Transaction.SubmitResponse txs = Transaction.submit(client, singer); +``` + +---- + +## API methods + +* [`Key API`](#key-api) +* [`Account API`](#account-api) +* [`Asset API`](#asset-api) +* [`Transaction API`](#transaction-api) +* [`Wallet API`](#wallet-api) +* [`Access Token API`](#access-token-api) +* [`Block API`](#block-api) +* [`Other API`](#other-api) + +## Key API + + +#### 1.createKey + +```java +Key create(Client client, Builder builder); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `Key.Builder` - *builder*, Builder object that builds request parameters. + +##### Returns + +- `Key` - *key*, Key object. + +---- + +#### 2.listKeys + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `List of Key`, *List*, an ArrayList object contains Key objects. + +---- + +#### 3.deleteKey + +```java +void delete(Client client, String xpub, String password); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `String` - *xpub*, pubkey of the key. +- `String` - *password*, password of the key. + +##### Returns + +none if the key is deleted successfully. + +---- + +#### 4.resetKeyPassword + +```java +void resetPwd(Client client, String xpub, String oldPwd, String newPwd); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `String` - *xpub*, pubkey of the key. +- `String` - *oldPwd*, old password of the key. +- `String` - *newPwd*, new password of the key. + +##### Returns + +none if the key password is reset successfully. + + +## Account API + + +#### 1.createAccount + +```java +Account create(Client client, Builder builder); +``` +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `Account.Builder` - *builder*, Builder object that builds request parameters. + +##### Returns + +- `Account` - *account*, Account object. + +---- + +#### 2.listAccounts + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `List of Account`, *List*, an ArrayList object contains Account objects. + +---- + +#### 3.deleteAccount + +```java +void delete(Client client, String account_info); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `String` - *account_info*, alias or ID of account. + +##### Returns + +none if the account is deleted successfully. + +---- + +#### 4.createAccountReceiver + +```java +Receiver create(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `Receiver` - *receiver*, Receiver object. + + +---- + +#### 5.listAddresses + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `List of Address`, *List*, an ArrayList object contains Account.Address objects. + +---- + +#### 6.validateAddress + +```java +Address validate(Client client, String address); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *address*, address of account. + +##### Returns + +- `Address` - *address*, an Address object. + + +## Asset API + + +#### 1.createAsset + +```java +Asset create(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Asset` - *asset*, an Asset object. + +---- + +#### 2.getAsset + +```java +Asset get(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Asset` - *asset*, an Asset object. + +---- + +#### 3.listAssets + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Asset`, *List*, an ArrayList object contains Asset objects. + +---- + +#### 4.updateAssetAlias + +```java +void update(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +none if the asset alias is updated success. + +---- + +#### 5.listBalances + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Balance`, an ArrayList object contains Balance objects. + +---- + +#### 6.listBalancesByAssetAlias + +```java +Balance listByAssetAlias(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Balance`, a Balance objects. + +---- + +#### 7.listBalancesByAccountAlias + +```java +List listByAccountAlias(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Balance`, an ArrayList object contains Balance objects. + +---- + +#### 8.listUnspentOutPuts + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of UnspentOutput`, an ArrayList object contains UnspentOutput objects. + + +## Transaction API + + +#### 1.buildTransaction + +```java +Template build(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Template` - *template*, a template object. + +---- + +#### 2.signTransaction + +```java +Template sign(Client client, Template template, String password); +``` + +##### Parameters + +`Object`: + +- `Client` - *Client*, Client object that makes requests to the core. +- `Template` - *template*, a template object. +- `String` - *password*, signature of the password. + +##### Returns + +- `Template` - *template*, a template object. + +---- + +#### 3.submitTransaction + +```java +SubmitResponse submit(Client client, Template template); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `Template` - *template*, a template object. + +##### Returns + +- `SubmitResponse` - *submitResponse*, a SubmitResponse object + +---- + +#### 4.estimateTransactionGas + +```java +TransactionGas estimateGas(Client client, Template template); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `Template` - *template*, a template object. + +##### Returns + +- `TransactionGas` - *transactionGas*, a TransactionGas object + +---- + +#### 5.getTransaction + +```java +Transaction get(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Transaction` - *transaction*, a Transaction object + +---- + +#### 6.listTransactions + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. + +##### Example + +```java +//list all transactions +List transactionList = new Transaction.QueryBuilder().list(client); +``` + +---- + +#### 7.listTransactionsById + +```java +List listById(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. + +##### Example + +```java +String tx_id = "f04d4d9b2580ff6496f9f08d903de5a2365975fb8d65b66ca4259f152c5dd134"; +//list all transactions by tx_id +List transactionList = new Transaction.QueryBuilder().setTxId(tx_id).list(client); +``` + +---- + +#### 8.listTransactionsByAccountId + +```java +List listByAccountId(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. + +##### Example + +```java +String account_id = "0E6KP8C100A02"; +//list all transactions by account_id +List transactionList = new Transaction.QueryBuilder().setAccountId(account_id).list(client); +``` + +## Wallet API + + +#### 1.backupWallet + +```java +Wallet backupWallet(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Wallet` - *wallet*, a Wallet object + +---- + +#### 2.restoreWallet + +```java +void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages); +``` + +##### Parameters + +`Object`: +- `Client` - *Client*, Client object that makes requests to the core. +- `Object` - *account_image*, account image. +- `Object` - *asset_image*, asset image. +- `Object` - *key_images*, key image. + +##### Returns + +none if restore wallet success. + + +## Access Token API + +```java +//example +AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client); + +List tokenList = AccessToken.list(client); + +String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799"; +AccessToken.check(client, "sheng", secret); +``` + +#### 1.createAccessToken + +```java +AccessToken create(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `AccessToken` - *accessToken*, an AccessToken object. + +---- + +#### 2.listAccessTokens + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Array of Object`, access token array. + +---- + +#### 3.deleteAccessToken + +```java +void delete(Client client, String id); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *id*, token ID. + +##### Returns + +none if the access token is deleted successfully. + +---- + +#### 4.checkAccessToken + +```java +void check(Client client, String id, String secret); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *id*, token ID. +- `String` - *secret*, secret of token, the second part of the colon division for token. + +##### Returns + +none if the access token is checked valid. + + +## Block API + + +#### 1.getBlockCount + +```java +Integer getBlockCount(Client client); +``` + +##### Parameters + +none + +##### Returns + +- `Integer` - *block_count*, recent block height of the blockchain. + +---- + +#### 2.getBlockHash + +```java +String getBlockHash(Client client); +``` + +##### Parameters + +none + +##### Returns + +- `String` - *block_hash*, recent block hash of the blockchain. + +---- + +#### 3.getBlock +```java +Block getBlock(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Block` - *block*, a Block object. + +---- + +#### 4.getBlockHeader + +```java +BlockHeader getBlockHeader(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `BlockHeader` - *blockHeader*, header of block. + +---- + +#### 5.getDifficulty + +```java +BlockDifficulty getBlockDifficulty(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `BlockDifficulty` - *blockDifficulty*, a BlockDifficulty object + +---- + +#### 6.getHashRate + +```java +BlockHashRate getHashRate(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `BlockHashRate` - *blockHashRate*, a BlockHashRate object + + +## Other API + + +#### 1.coreConfig + +```java +NetInfo getNetInfo(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `NetInfo` - *coreConfig*, a NetInfo object. + +---- + +#### 2.gasRate + +```java +Gas gasRate(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Gas` - *gas*, a Gas object. + +---- + +#### 3.verifyMessage + +```java +Boolean verifyMessage(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Boolean` - *result*, verify result. + diff --git a/bytom-sdk/doc/transactions.md b/bytom-sdk/doc/transactions.md new file mode 100644 index 0000000..6e095f3 --- /dev/null +++ b/bytom-sdk/doc/transactions.md @@ -0,0 +1,245 @@ +## Creating transactions + +Creating a transaction consists of three steps: + +1. **Build transaction**: Define what the transaction is supposed to do: issue new units of an asset, spend assets held in an account, control assets with an account, etc. +2. **Sign transaction**: Authorize the spending of assets or the issuance of new asset units using private keys. +3. **Submit transaction**: Submit a complete, signed transaction to the blockchain, and propagate it to other cores on the network. + +### Build transaction + +Rather than forcing you to manipulate inputs, outputs and change directly, the Bytom Core API allows you to build transactions using a list of high-level **actions**. + +There are five types of actions: + +| ACTION | DESCRIPTION | +| --------------------------------------- | ------------------------------------------------------------ | +| Issue | Issues new units of a specified asset. | +| Spend from account | Spends units of a specified asset from a specified account. Automatically handles locating outputs with enough units, and the creation of change outputs. | +| Spend an unspent output from an account | Spends an entire, specific unspent output in an account. Change must be handled manually, using other actions. | +| Control with receiver | Receives units of an asset into a receiver, which contains a control program and supplementary payment information, such as an expiration date. Used when making a payment to an external party/account in another Bytom Core. | +| Retire | Retires units of a specified asset. | + +### Sign transaction + +In order for a transaction to be accepted into the blockchain, its inputs must contain valid signatures. For issuance inputs, the signature must correspond to public keys named in the issuance program. For spending inputs, the signature must correspond to the public keys named in the control programs of the outputs being spent. + +Transaction signing provides the blockchain with its security. Strong cryptography prevents everyone–even the operators of the blockchain network–from producing valid transaction signatures without the relevant private keys. + +### Submit transaction + +Once a transaction is balanced and all inputs are signed, it is considered valid and can be submitted to the blockchain. The local core will forward the transaction to the generator, which adds it to the blockchain and propagates it to other cores on the network. + +The Chain Core API does not return a response until either the transaction has been added to the blockchain and indexed by the local core, or there was an error. This allows you to write your applications in a linear fashion. In general, if a submission responds with success, the rest of your application may proceed with the guarantee that the transaction has been committed to the blockchain. + +## Examples + +### Asset issuance + +Issue 300000000 units of gold to Alice's address. + +#### Within a Chain Core + +```java +Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); + +Transaction.Template signedIssuance = new Transaction.SignerBuilder().sign(client, + issuance, "123456"); + +Transaction.submit(client, signedIssuance); +``` + +#### Between two Chain Cores + +First, Bob creates a receiver in his account, which he can serialize and send to the issuer of gold. + +```java +Receiver bobIssuanceReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); +String bobIssuanceReceiverSerialized = bobIssuanceReceiver.toJson(); +``` + +The issuer then builds, signs, and submits a transaction, sending gold to Bob’s receiver. + +```java +Transaction.Template issuanceToReceiver = new Transaction.Builder() + .addAction(new Transaction.Action.Issue() + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobIssuanceReceiverSerialized)) + .setAssetAlias("gold") + .setAmount(10) + ).build(client); + +Transaction.Template signedIssuanceToReceiver = new Transaction.SignerBuilder().sign(client, + issuanceToReceiver, "123456"); + +Transaction.submit(client, signedIssuanceToReceiver); +``` + +### Simple payment + +Alice pays 10 units of gold to Bob. + +#### Within a Chain Core + +```java +Transaction.Template payment = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetAlias("gold") + .setAmount(10) + ).build(client); + +Transaction.Template signedPayment = new Transaction.SignerBuilder().sign(client, + payment, "123456"); + +Transaction.submit(client, signedPayment); +``` + +#### Between two Chain Cores + +First, Bob creates a receiver in his account, which he can serialize and send to Alice. + +```java +Receiver bobPaymentReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); +String bobPaymentReceiverSerialized = bobPaymentReceiver.toJson(); +``` + +Alice then builds, signs, and submits a transaction, sending gold to Bob’s receiver. + +```java +Transaction.Template paymentToReceiver = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobPaymentReceiverSerialized)) + .setAssetAlias("gold") + .setAmount(10) + ).build(client); + +Transaction.Template signedPaymentToReceiver = new Transaction.SignerBuilder().sign(client, + paymentToReceiver, "123456"); + +Transaction.submit(client, signedPaymentToReceiver); +``` + +### Multi-asset payment + +Alice pays 10 units of gold and 20 units of silver to Bob. + +#### Within a Chain Core + +```java +Transaction.Template multiAssetPayment = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("silver") + .setAmount(20) + ).addAction(new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetAlias("silver") + .setAmount(20) + ).build(client); + +Transaction.Template signedMultiAssetPayment = new Transaction.SignerBuilder().sign(client, + multiAssetPayment, "123456"); + +Transaction.submit(client, signedMultiAssetPayment); +``` + +#### Between two Chain Cores + +Currently, the transaction builder API assigns each receiver to its own output, which means that a single receiver can only be used to receive a single asset type. It’s important for Bob not to re-use receivers, so he creates one for each asset payment he will receive. He serializes both and sends them to Alice. + +```java +Receiver bobGoldReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); +String bobGoldReceiverSerialized = bobGoldReceiver.toJson(); + +Receiver bobSilverReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); +String bobSilverReceiverSerialized = bobSilverReceiver.toJson(); +``` + +Alice then builds, signs, and submits a transaction, sending gold and silver to Bob’s receivers. + +```java +Transaction.Template multiAssetToReceiver = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("silver") + .setAmount(20) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobGoldReceiverSerialized)) + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobSilverReceiverSerialized)) + .setAssetAlias("silver") + .setAmount(20) + ).build(client); + +Transaction.Template signedMultiAssetToReceiver = new Transaction.SignerBuilder().sign(client, + multiAssetToReceiver, "123456"); + +Transaction.submit(client, signedMultiAssetToReceiver); +``` + +### Asset retirement + +Alice retires 50 units of gold from her account. + +```java +Transaction.Template retirement = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(50) + ).addAction(new Transaction.Action.Retire() + .setAssetAlias("gold") + .setAmount(50) + ).build(client); + +Transaction.Template signedRetirement = new Transaction.SignerBuilder().sign(client, + retirement, "123456"); + +Transaction.submit(client, signedRetirement); +``` + diff --git a/bytom-sdk/pom.xml b/bytom-sdk/pom.xml new file mode 100644 index 0000000..ad365fc --- /dev/null +++ b/bytom-sdk/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + + io.bytom + bytom-sdk-all + 1.0.0 + + + io.bytom + bytom-sdk + 1.0.0 + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 7 + 7 + + + + + jar + + bytom-sdk-java + + http://www.example.com + SDK for Bytom project in github. + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + https://github.com/Bytom/bytom-java-sdk/issues + GitHub Issues + + + + + successli + successli@outlook.com + https://github.com/successli + + + + + scm:git:git@github.com:Bytom/bytom-java-sdk.git + scm:git:git@github.com:Bytom/bytom-java-sdk.git + git@github.com:Bytom/bytom-java-sdk.git + + + + UTF-8 + + + + + junit + junit + 4.12 + test + + + log4j + log4j + 1.2.17 + + + com.squareup.okhttp + okhttp + 2.5.0 + + + com.squareup.okhttp + mockwebserver + 2.5.0 + test + + + com.google.code.gson + gson + 2.8.0 + + + org.bouncycastle + bcpkix-jdk15on + 1.56 + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + maven-assembly-plugin + 2.3 + + + jar-with-dependencies + + + + + package + + single + + + + + + + + + + + + oss + https://oss.sonatype.org/content/repositories/snapshots + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + diff --git a/bytom-sdk/src/main/java/io/bytom/api/AccessToken.java b/bytom-sdk/src/main/java/io/bytom/api/AccessToken.java new file mode 100644 index 0000000..4541834 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/AccessToken.java @@ -0,0 +1,164 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

AccessToken Class

+ */ +public class AccessToken { + /** + * Token id + */ + public String id; + /** + * Token token + */ + public String token; + /** + * Token type + */ + public String type; + /** + * create time of token + */ + @SerializedName(value = "created_at", alternate = {"create"}) + public String createTime; + + private static Logger logger = Logger.getLogger(AccessToken.class); + + /** + * Serializes the AccessToken into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the AccessToken object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + /** + * id of Token + */ + public String id; + /** + * type of Token + */ + public String type; + + public Builder() { + } + + /** + * @param id the id to set + * @return Builder + */ + public Builder setId(String id) { + this.id = id; + return this; + } + + /** + * @param type the type to set, possibly null + * @return Builder + */ + public Builder setType(String type) { + this.type = type; + return this; + } + + /** + * Call create-access-token api + * + * @param client client object that makes requests to the core + * @return AccessToken object + * @throws BytomException + */ + public AccessToken create(Client client) throws BytomException { + AccessToken accessToken = client.request("create-access-token", this, AccessToken.class); + + logger.info("create-access-token:"); + logger.info(accessToken.toJson()); + + return accessToken; + } + } + + /** + * Call check-access-token api + * + * @param client client object that makes requests to the core + * @param id id + * @param secret secret + * @throws BytomException + */ + public static void check(Client client, String id, String secret) throws BytomException { + Map req = new HashMap(); + req.put("id", id); + req.put("secret", secret); + // add a native control + if (client.getUrl().equals("http://127.0.0.1:9888") || + client.getUrl().equals("http://127.0.0.1:9888/")) { + client.request("check-access-token", req); + logger.info("check-access-token successfully."); + } else { + logger.info("this is a native method."); + } + } + + /** + * Call delete-access-token api + * native method, can't rpc + * + * @param client client object that makes requests to the core + * @param id id + * @throws BytomException + */ + public static void delete(Client client, String id) throws BytomException { + Map req = new HashMap(); + req.put("id", id); + // add a native control + if (client.getUrl().equals("http://127.0.0.1:9888") || + client.getUrl().equals("http://127.0.0.1:9888/")) { + client.request("delete-access-token", req); + logger.info("delete-access-token."); + } else { + logger.info("this is a native method."); + } + } + + /** + * Call list-access-tokens api.
+ * native method, can't rpc + * + * @param client client object that makes requests to the core + * @return list of AccessToken objects + * @throws BytomException + */ + public static List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{AccessToken.class}); + List accessTokenList = null; + if (client.getUrl().equals("http://127.0.0.1:9888") || + client.getUrl().equals("http://127.0.0.1:9888/")) { + accessTokenList = client.request("list-access-tokens", null, listType); + + logger.info("list-access-tokens:"); + logger.info("size of accessTokenList:" + accessTokenList.size()); + logger.info(accessTokenList.get(0).toJson()); + } else { + logger.info("this is a native method."); + } + + return accessTokenList; + } + +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Account.java b/bytom-sdk/src/main/java/io/bytom/api/Account.java new file mode 100644 index 0000000..c84e526 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Account.java @@ -0,0 +1,336 @@ +package io.bytom.api; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.*; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Account Class

+ */ +public class Account { + + @SerializedName("id") + public String id; + + @SerializedName("alias") + public String alias; + + @SerializedName("key_index") + public Integer key_index; + + @SerializedName("quorum") + public Integer quorum; + + @SerializedName("xpubs") + public List xpubs; + + private static Logger logger = Logger.getLogger(Account.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * create-account + * + * @param client client object that makes requests to the core + * @param builder Account.Builder to make parameters + * @return Account return a account object + * @throws BytomException BytomException + */ + public static Account create(Client client, Builder builder) throws BytomException { + Account account = client.request("create-account", builder, Account.class); + logger.info("create-account"); + logger.info(account.toString()); + return account; + } + + /** + * list-accounts + * + * @param client client object that makes requests to the core + * @return return a list of account object + * @throws BytomException BytomException + */ + public static List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Account.class}); + List accountList = client.request("list-accounts", null, listType); + logger.info("list-accounts:"); + logger.info("size of accountList:"+accountList.size()); + logger.info(accountList); + return accountList; + } + + /** + * delete-account + * @param client client object that makes requests to the core + * @param account_info account_info + * @throws BytomException BytomException + */ + public static void delete(Client client, String account_info) throws BytomException { + Map req = new HashMap<>(); + req.put("account_info", account_info); + client.request("delete-account", req); + } + + public static class Builder { + + public List root_xpubs; + + public String alias; + + public Integer quorum; + + /** + * add a xpub to root_xpubs + * + * @param xpub xpub + * @return this Builder object + */ + public Builder addRootXpub(String xpub) { + this.root_xpubs.add(xpub); + return this; + } + + /** + * set xpubs to root_xpubs + * + * @param xpubs xpubs + * @return this Builder object + */ + public Builder setRootXpub(List xpubs) { + this.root_xpubs = new ArrayList<>(xpubs); + return this; + } + + /** + * set alias to alias + * @param alias alias + * @return this Builder object + */ + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * set quorum to quorum + * + * @param quorum quorum + * @return this Builder object + */ + public Builder setQuorum(Integer quorum) { + this.quorum = quorum; + return this; + } + + } + + /** + * Use this class to create a {@link Receiver} under an account. + */ + public static class ReceiverBuilder { + + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("account_id") + public String accountId; + + /** + * Specifies the account under which the receiver is created. You must use + * this method or @{link ReceiverBuilder#setAccountId}, but not both. + * + * @param alias the unique alias of the account + * @return this ReceiverBuilder object + */ + public ReceiverBuilder setAccountAlias(String alias) { + this.accountAlias = alias; + return this; + } + + /** + * Specifies the account under which the receiver is created. You must use + * this method or @{link ReceiverBuilder#setAccountAlias}, but not both. + * + * @param id the unique ID of the account + * @return this ReceiverBuilder object + */ + public ReceiverBuilder setAccountId(String id) { + this.accountId = id; + return this; + } + + /** + * Creates a single Receiver object under an account. + * + * @param client the client object providing access to an instance of Chain Core + * @return a new Receiver object + * @throws APIException This exception is raised if the api returns errors while creating the control programs. + * @throws BadURLException This exception wraps java.net.MalformedURLException. + * @throws ConnectivityException This exception is raised if there are connectivity issues with the server. + * @throws HTTPException This exception is raised when errors occur making http requests. + * @throws JSONException This exception is raised due to malformed json requests or responses. + */ + public Receiver create(Client client) throws BytomException { + Gson gson = new Gson(); + Receiver receiver = client.request( + "create-account-receiver", this, Receiver.class); + logger.info("create-account-receiver:"); + logger.info(receiver.toJson()); + return receiver; + } + + + @Override + public String toString() { + return "ReceiverBuilder{" + + "accountAlias='" + accountAlias + '\'' + + ", accountId='" + accountId + '\'' + + '}'; + } + } + + /** + * Address Class + */ + public static class Address { + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("account_id") + public String accountId; + + @SerializedName("address") + public String address; + + @SerializedName("change") + public Boolean change; + + @SerializedName("vaild") + public Boolean vaild; + + @SerializedName("is_local") + public Boolean is_local; + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Deserializes a Address from JSON. + * + * @param json a JSON-serialized Receiver object + * @return the deserialized Receiver object + * @throws JSONException Raised if the provided string is not valid JSON. + */ + public static Address fromJson(String json) throws JSONException { + try { + return Utils.serializer.fromJson(json, Address.class); + } catch (IllegalStateException e) { + throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); + } + } + + } + + /** + * Use this class to create a {@link Address} under an account. + */ + public static class AddressBuilder { + + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("account_id") + public String accountId; + + /** + * Specifies the account under which the address is created. You must use + * this method or @{link AddressBuilder#setAccountId}, but not both. + * + * @param alias the unique alias of the account + * @return this AddressBuilder object + */ + public AddressBuilder setAccountAlias(String alias) { + this.accountAlias = alias; + return this; + } + + /** + * Specifies the account under which the address is created. You must use + * this method or @{link AddressBuilder#setAccountAlias}, but not both. + * + * @param id the unique ID of the account + * @return this AddressBuilder object + */ + public AddressBuilder setAccountId(String id) { + this.accountId = id; + return this; + } + + /** + * list-addresses + * @param client client object that makes requests to the core + * @return list of address object + * @throws BytomException BytomException + */ + public List
list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Address.class}); + List
addressList = client.request("list-addresses", this, listType); + logger.info("list-addresses:"); + logger.info("size of addressList:" + addressList.size()); + logger.info(addressList.get(0).toJson()); + + return addressList; + } + + /** + * validate-address + * @param client client object that makes requests to the core + * @param address an address string + * @return an address object + * @throws BytomException BytomException + */ + public Address validate(Client client, String address) throws BytomException { + Map req = new HashMap<>(); + req.put("address", address); + Address addressResult = client.request("validate-address", req, Address.class); + logger.info("validate-address:"); + logger.info(addressResult.toJson()); + + return addressResult; + } + + @Override + public String toString() { + return "AddressBuilder{" + + "accountAlias='" + accountAlias + '\'' + + ", accountId='" + accountId + '\'' + + '}'; + } + } + + @Override + public String toString() { + return "Account{" + + "id='" + id + '\'' + + ", alias='" + alias + '\'' + + ", key_index=" + key_index + + ", quorum=" + quorum + + ", xpubs=" + xpubs + + '}'; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Asset.java b/bytom-sdk/src/main/java/io/bytom/api/Asset.java new file mode 100644 index 0000000..9fdb375 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Asset.java @@ -0,0 +1,369 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Asset Class

+ *
+ * String - id, asset id.
+ * String - alias, name of the asset.
+ * String - issuance_program, control program of the issuance of asset.
+ * Array of Object - keys, information of asset pubkey.
+ * String - definition, definition of asset.
+ * Integer - quorum, threshold of keys that must sign a transaction to spend asset units controlled by the account.
+ */ +public class Asset { + + /** + * Globally unique identifier of the asset.
+ * Asset version 1 specifies the asset id as the hash of:
+ * - the asset version
+ * - the asset's issuance program
+ * - the core's VM version
+ * - the hash of the network's initial block + */ + public String id; + + /** + * User specified, unique identifier. + */ + public String alias; + + /** + * A program specifying a predicate to be satisfied when issuing the asset. + */ + @SerializedName(value = "issuance_program", alternate = {"issue_program"}) + public String issuanceProgram; + + /** + * The list of keys used to create the issuance program for the asset.
+ * Signatures from these keys are required for issuing units of the asset. + */ + public Key[] keys; + + @SerializedName("key_index") + public Integer keyIndex; + + @SerializedName("xpubs") + public List xpubs; + + /** + * The number of keys required to sign an issuance of the asset. + */ + @SerializedName("quorum") + public int quorum; + + /** + * User-specified, arbitrary/unstructured data visible across blockchain networks.
+ * Version 1 assets specify the definition in their issuance programs, rendering the + * definition immutable. + */ + @SerializedName("definition") + public Map definition; + + /** + * version of VM. + */ + @SerializedName("vm_version") + public int vmVersion; + + /** + * type of asset. + */ + @SerializedName("type") + public String type; + + /** + * byte of asset definition. + */ + @SerializedName("raw_definition_byte") + public String rawDefinitionByte; + + public static Logger logger = Logger.getLogger(Asset.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Key { + /** + * Hex-encoded representation of the root extended public key + */ + @SerializedName("root_xpub") + public String rootXpub; + + /** + * The derived public key, used in the asset's issuance program. + */ + @SerializedName("asset_pubkey") + public String assetPubkey; + + /** + * The derivation path of the derived key. + */ + @SerializedName("asset_derivation_path") + public String[] assetDerivationPath; + + @Override + public String toString() { + return "Key{" + + "rootXpub='" + rootXpub + '\'' + + ", assetPubkey='" + assetPubkey + '\'' + + ", assetDerivationPath=" + Arrays.toString(assetDerivationPath) + + '}'; + } + } + + /** + *

Builder Class

+ */ + public static class Builder { + /** + * User specified, unique identifier. + */ + public String alias; + + /** + * User-specified, arbitrary/unstructured data visible across blockchain networks.
+ * Version 1 assets specify the definition in their issuance programs, rendering + * the definition immutable. + */ + public Map definition; + + /** + * The list of keys used to create the issuance program for the asset.
+ * Signatures from these keys are required for issuing units of the asset.
+ * Must set with {@link #addRootXpub(String)} or + * {@link #setRootXpubs(List)} before calling {@link #create(Client)}. + */ + @SerializedName("root_xpubs") + public List rootXpubs; + + /** + * The number of keys required to sign an issuance of the asset.
+ * Must set with {@link #setQuorum(int)} before calling + * {@link #create(Client)}. + */ + public int quorum; + + /** + * Unique identifier used for request idempotence. + */ + @SerializedName("access_token") + private String access_token; + + /** + * Default constructor initializes the list of keys. + */ + public Builder() { + this.rootXpubs = new ArrayList<>(); + } + + /** + * Creates an asset object. + * + * @param client client object that makes request to the core + * @return an asset object + * @throws BytomException BytomException + */ + public Asset create(Client client) throws BytomException { + Asset asset = client.request("create-asset", this, Asset.class); + logger.info("create-asset:"); + logger.info(asset.toString()); + return asset; + } + + /** + * Sets the alias on the builder object. + * @param alias alias + * @return updated builder object + */ + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * Adds a field to the existing definition object (initializing the object if it + * doesn't exist). + * @param key key of the definition field + * @param value value of the definition field + * @return updated builder object + */ + public Builder addDefinitionField(String key, Object value) { + if (this.definition == null) { + this.definition = new HashMap<>(); + } + this.definition.put(key, value); + return this; + } + + /** + * Sets the asset definition object.
+ * Note: any existing asset definition fields will be replaced. + * @param definition asset definition object + * @return updated builder object + */ + public Builder setDefinition(Map definition) { + this.definition = definition; + return this; + } + + /** + * Sets the quorum of the issuance program. Must be called before + * {@link #create(Client)}. + * @param quorum proposed quorum + * @return updated builder object + */ + public Builder setQuorum(int quorum) { + this.quorum = quorum; + return this; + } + + /** + * Adds a key to the builder's list.
+ * Either this or {@link #setRootXpubs(List)} must be called before + * {@link #create(Client)}. + * @param xpub key + * @return updated builder object. + */ + public Builder addRootXpub(String xpub) { + this.rootXpubs.add(xpub); + return this; + } + + /** + * Sets the builder's list of keys.
+ * Note: any existing keys will be replaced.
+ * Either this or {@link #addRootXpub(String)} must be called before + * {@link #create(Client)}. + * @param xpubs list of xpubs + * @return updated builder object + */ + public Builder setRootXpubs(List xpubs) { + this.rootXpubs = new ArrayList<>(xpubs); + return this; + } + + @Override + public String toString() { + return "Builder{" + + "alias='" + alias + '\'' + + ", definition=" + definition + + ", rootXpubs=" + rootXpubs + + ", quorum=" + quorum + + ", access_token='" + access_token + '\'' + + '}'; + } + } + + /** + *

QueryBuilder Class

+ */ + public static class QueryBuilder { + + @SerializedName("id") + public String id; + + public QueryBuilder setId(String assetId) { + this.id = assetId; + return this; + } + + /** + * get-asset from bytomd + * + * @param client client object that makes requests to the core + * @return The Asset Object + * @throws BytomException BytomException + */ + public Asset get(Client client) throws BytomException { + Asset asset = client.request("get-asset", this, Asset.class); + logger.info("get-asset:"); + logger.info(asset.toJson()); + return asset; + } + + /** + * get all assets from bytomd + * + * @param client client object that makes requests to the core + * @return return list of asset object + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Asset.class}); + List assetList = client.request("list-assets", null, listType); + logger.info("list-assets:"); + logger.info("size of assetList:"+assetList.size()); + logger.info(assetList); + return assetList; + } + + } + + /** + *

AliasUpdateBuilder Class

+ */ + public static class AliasUpdateBuilder { + /** + * id of asset. + */ + @SerializedName("id") + public String id; + /** + * new alias of asset + */ + @SerializedName("alias") + public String alias; + + public AliasUpdateBuilder setAssetId(String assetId) { + this.id = assetId; + return this; + } + + public AliasUpdateBuilder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * update-asset-alias + * + * @param client client object that makes requests to the core + * @throws BytomException BytomException + */ + public void update(Client client) throws BytomException { + client.request("update-asset-alias", this); + logger.info("update-asset-alias:"); + logger.info("id:"+id); + logger.info("alias:"+alias); + } + + } + + @Override + public String toString() { + return "Asset{" + + "id='" + id + '\'' + + ", alias='" + alias + '\'' + + ", issuanceProgram='" + issuanceProgram + '\'' + + ", keys=" + Arrays.toString(keys) + + ", keyIndex=" + keyIndex + + ", xpubs=" + xpubs + + ", quorum=" + quorum + + ", definition=" + definition + + ", vmVersion=" + vmVersion + + ", type='" + type + '\'' + + ", rawDefinitionByte='" + rawDefinitionByte + '\'' + + '}'; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Balance.java b/bytom-sdk/src/main/java/io/bytom/api/Balance.java new file mode 100644 index 0000000..9ad8bc9 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Balance.java @@ -0,0 +1,130 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Balance { + + /** + * account id + */ + @SerializedName("account_id") + public String accountId; + + /** + * name of account + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * sum of the unspent outputs. + * specified asset balance of account. + */ + public long amount; + + /** + * asset id + */ + @SerializedName("asset_id") + public String assetId; + + /** + * name of asset + */ + @SerializedName("asset_alias") + public String assetAlias; + + @SerializedName("asset_definition") + public Map definition; + + private static Logger logger = Logger.getLogger(Balance.class); + + /** + * Serializes the Balance into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + + public static class QueryBuilder { + + /** + * Call list-Balances api + * + * @param client + * @return + * @throws BytomException + */ + public List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Balance.class}); + List balanceList = client.request("list-balances", null, listType); + logger.info("list-balances:"); + logger.info("size of :" + balanceList.size()); + for (Balance result : balanceList) { + logger.info(result.toJson()); + } + + return balanceList; + } + + /** + * sum of all Asset alias amount + * + * @param client + * @param assetAlias + * @return + * @throws BytomException + */ + public Balance listByAssetAlias(Client client, String assetAlias) throws BytomException { + List balanceList = list(client); + Balance assetBalance = new Balance(); + assetBalance.assetAlias = assetAlias; + long amount = 0; + for (Balance result : balanceList) { + if (result.assetAlias.equals(assetAlias)) { + amount += result.amount; + assetBalance.assetId = result.assetId; + } + } + assetBalance.amount = amount; + + logger.info(assetBalance.toJson()); + + return assetBalance; + } + + /** + * sum of all Account alias amount + * + * @param client + * @param accountAlias + * @return + * @throws BytomException + */ + public List listByAccountAlias(Client client, String accountAlias) throws BytomException { + List balanceList = list(client); + List accountBalance = new ArrayList<>(); + for (Balance result : balanceList) { + if (result.accountAlias.equals(accountAlias)) { + accountBalance.add(result); + logger.info(result.toJson()); + } + } + return accountBalance; + } + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Block.java b/bytom-sdk/src/main/java/io/bytom/api/Block.java new file mode 100644 index 0000000..ecda5b1 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Block.java @@ -0,0 +1,324 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.List; +import java.util.Map; + +public class Block { + + public String hash; + + public Integer size; + + public Integer version; + + public Integer height; + + @SerializedName("previous_block_hash") + public String previousBlockHash; + + public Integer timestamp; + + public Integer nonce; + + public long bits; + + public String difficulty; + + @SerializedName("transaction_merkle_root") + public String transactionsMerkleRoot; + + @SerializedName("transaction_status_hash") + public String transactionStatusHash; + + public List transactions; + + + private static Logger logger = Logger.getLogger(Block.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call get-block-count api + * + * @param client + * @return + * @throws BytomException + */ + public static Integer getBlockCount(Client client) throws BytomException { + Integer blockCount = + client.requestGet("get-block-count", null, "block_count", Integer.class); + + logger.info("get-block-count:"+blockCount); + return blockCount; + } + + /** + * Call get-block-hash api + * + * @param client + * @return + * @throws BytomException + */ + public static String getBlockHash(Client client) throws BytomException { + String blockHash = + client.requestGet("get-block-hash", null, "block_hash", String.class); + + logger.info("get-block-hash:"+blockHash); + + return blockHash; + } + + public static class QueryBuilder { + + /** + * block_height, height of block. + */ + @SerializedName("block_height") + public int blockHeight; + /** + * block_hash, hash of block. + */ + @SerializedName("block_hash") + public String blockHash; + + public QueryBuilder setBlockHeight(int blockHeight) { + this.blockHeight = blockHeight; + return this; + } + + public QueryBuilder setBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + /** + * Call get-block api + * + * @param client + * @return + * @throws BytomException + */ + public Block getBlock(Client client) throws BytomException { + + Block block = client.request("get-block", this, Block.class); + + logger.info("get-block:"); + logger.info(block.toJson()); + + return block; + } + + /** + * Call get-block-header api + * + * @param client + * @return + * @throws BytomException + */ + public BlockHeader getBlockHeader(Client client) throws BytomException { + BlockHeader blockHeader = + client.request("get-block-header", this, BlockHeader.class); + + logger.info("get-block-header:"); + logger.info(blockHeader.toJson()); + + return blockHeader; + } + + /** + * Call get-difficulty api + * + * @param client + * @return + * @throws BytomException + */ + public BlockDifficulty getBlockDifficulty(Client client) throws BytomException { + BlockDifficulty blockDifficulty = + client.request("get-difficulty", this, BlockDifficulty.class); + + logger.info("get-difficulty:"); + logger.info(blockDifficulty.toJson()); + + return blockDifficulty; + } + + /** + * Call get-hash-rate api + * + * @param client + * @return + * @throws BytomException + */ + public BlockHashRate getHashRate(Client client) throws BytomException { + BlockHashRate blockHashRate = + client.request("get-hash-rate", this, BlockHashRate.class); + + logger.info("get-hash-rate:"); + logger.info(blockHashRate.toJson()); + + return blockHashRate; + } + + } + + public static class BlockTx { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + private String id; + + /** + * version + */ + private Integer version; + + /** + * size + */ + private Integer size; + /** + * time_range + */ + @SerializedName("time_range") + private Integer timeRange; + + /** + * status + */ + @SerializedName("status_fail") + private boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + @SerializedName("inputs") + private List inputs; + + /** + * List of specified outputs for a transaction. + */ + @SerializedName("outputs") + private List outputs; + } + + public static class AnnotatedInput { + + /** + * The number of units of the asset being issued or spent. + */ + private Integer amount; + + /** + * inputs param + */ + private String arbitrary; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + } + + public static class AnnotatedOutput { + + /** + * The number of units of the asset being controlled. + */ + private long amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } + + public static class BlockHeader { + + @SerializedName("block_header") + public String blockHeader; + + @SerializedName("reward") + public Integer reward; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + public static class BlockDifficulty { + public String hash; + public Integer height; + public Integer bits; + public String difficulty; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + + } + + public static class BlockHashRate { + public String hash; + public Integer height; + public Integer hash_rate; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/CoreConfig.java b/bytom-sdk/src/main/java/io/bytom/api/CoreConfig.java new file mode 100644 index 0000000..3334773 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/CoreConfig.java @@ -0,0 +1,96 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +public class CoreConfig { + + + private static Logger logger = Logger.getLogger(CoreConfig.class); + + /** + * Call net-info api + * + * @param client + * @return + * @throws BytomException + */ + public static NetInfo getNetInfo(Client client) throws BytomException { + NetInfo netInfo = client.request("net-info", null, NetInfo.class); + + logger.info("net-info:"); + logger.info(netInfo.toJson()); + + return netInfo; + } + + /** + * Call gas-rate api + * + * @param client + * @return + * @throws BytomException + */ + public static Integer getGasRate(Client client) throws BytomException { + Integer gas = client.requestGet("gas-rate", null, "gas_rate", Integer.class); + + logger.info("gas-rate:"); + logger.info(gas); + + return gas; + } + + public static class NetInfo { + /** + * listening, whether the node is listening. + */ + public boolean listening; + + /** + * syncing, whether the node is syncing. + */ + public boolean syncing; + + /** + * mining, whether the node is mining. + */ + public boolean mining; + + /** + * peer_count, current count of connected peers. + */ + @SerializedName("peer_count") + public int peerCount; + + /** + * current_block, current block height in the node's blockchain. + */ + @SerializedName("current_block") + public long currentBlock; + + /** + * highest_block, current highest block of the connected peers. + */ + @SerializedName("highest_block") + public long highestBlock; + + /** + * network_id, network id. + */ + @SerializedName("network_id") + public String networkID; + + /** + * version, bytom version. + */ + @SerializedName("version") + public String version; + + public String toJson() { + return Utils.serializer.toJson(this); + } + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Key.java b/bytom-sdk/src/main/java/io/bytom/api/Key.java new file mode 100644 index 0000000..558b6a7 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Key.java @@ -0,0 +1,148 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.*; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Key Class

+ * + * @version 1.0 + * @since 2018-05-18 + */ +public class Key { + + @SerializedName("alias") + public String alias; + + @SerializedName("xpub") + public String xpub; + + @SerializedName("file") + public String file; + + private static Logger logger = Logger.getLogger(Key.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Create a key object + * + * @param client client object that makes requests to the core + * @param builder Key.Builder object that make parameters + * @return Key a key object + * @throws BytomException BytomException + */ + public static Key create(Client client, Builder builder) throws BytomException { + Key key = client.request("create-key", builder, Key.class); + return key; + } + + /** + * List all key objects + * + * @param client client object that makes requests to the core + * @return a list of key object + * @throws BytomException BytomException + */ + public static List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Key.class}); + List keyList = client.request("list-keys", null, listType); + + logger.info("list-key:"); + logger.info("size of key:"+keyList.size()); + logger.info(keyList); + + return keyList; + } + + /** + * delete a key + * + * @param client client object that makes requests to the core + * @param xpub the xpub is given when creates key + * @param password the password is given when creates key + * @throws BytomException BytomException + */ + public static void delete(Client client, String xpub, String password) throws BytomException { + Map req = new HashMap(); + req.put("xpub", xpub); + req.put("password", password); + client.request("delete-key", req); + logger.info("delete-key successfully."); + } + + /** + * reset password + * + * @param client client object that makes requests to the core + * @param xpub the xpub is given when creates key + * @param oldPwd the old password is given when creates key + * @param newPwd new password used to set + * @throws BytomException BytomException + */ + public static void resetPassword(Client client, String xpub, String oldPwd, String newPwd) throws BytomException { + Map req = new HashMap<>(); + req.put("xpub", xpub); + req.put("old_password", oldPwd); + req.put("new_password", newPwd); + client.request("reset-key-password", req); + } + + /** + *

Key.Builder Class

+ */ + public static class Builder { + /** + * User specified, unique identifier. + */ + public String alias; + + /** + * User specified. + */ + public String password; + + /** + * Sets the alias on the builder object. + * + * @param alias alias + * @return updated builder object + */ + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * Sets the alias on the builder object. + * + * @param password password + * @return updated builder object + */ + public Builder setPassword(String password) { + this.password = password; + return this; + } + + } + + @Override + public String toString() { + return "Key{" + + "alias='" + alias + '\'' + + ", xpub='" + xpub + '\'' + + ", file='" + file + '\'' + + '}'; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Message.java b/bytom-sdk/src/main/java/io/bytom/api/Message.java new file mode 100644 index 0000000..4ce1e70 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Message.java @@ -0,0 +1,122 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +public class Message { + + @SerializedName("derived_xpub") + public String derivedXpub; + @SerializedName("signature") + public String signature; + + private static Logger logger = Logger.getLogger(Message.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class SignBuilder { + + public String address; + public String message; + public String password; + + public SignBuilder setAddress(String address) { + this.address = address; + return this; + } + + public SignBuilder setMessage(String message) { + this.message = message; + return this; + } + + public SignBuilder setPassword(String password) { + this.password = password; + return this; + } + + /** + * Call sign-message api + * + * @param client + * @return + * @throws BytomException + */ + public Message sign(Client client) throws BytomException { + Message message = client.request("sign-message", this, Message.class); + + logger.info("sign-message:"); + logger.info(message.toJson()); + + return message; + } + + } + + public static class VerifyBuilder { + + /** + * address, address for account. + */ + public String address; + + /** + * derived_xpub, derived xpub. + */ + @SerializedName("derived_xpub") + public String derivedXpub; + + /** + * message, message for signature by derived_xpub. + */ + public String message; + + /** + * signature, signature for message. + */ + public String signature; + + + public VerifyBuilder setAddress(String address) { + this.address = address; + return this; + } + + public VerifyBuilder setDerivedXpub(String derivedXpub) { + this.derivedXpub = derivedXpub; + return this; + } + + public VerifyBuilder setMessage(String message) { + this.message = message; + return this; + } + + public VerifyBuilder setSignature(String signature) { + this.signature = signature; + return this; + } + + /** + * Call verify-message api + * @param client + * @return + * @throws BytomException + */ + public Boolean verifyMessage(Client client) throws BytomException { + Boolean result = client.requestGet("verify-message", this, "result", Boolean.class); + + logger.info("verify-message:"+result); + + return result; + } + + } + + +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/RawTransaction.java b/bytom-sdk/src/main/java/io/bytom/api/RawTransaction.java new file mode 100644 index 0000000..519cf27 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/RawTransaction.java @@ -0,0 +1,154 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RawTransaction { + /** + * version + */ + public Integer version; + /** + * size + */ + public Integer size; + /** + * time_range + */ + @SerializedName("time_range") + public Integer timeRange; + + /** + * status + */ + public Integer fee; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(RawTransaction.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + + public static RawTransaction decode(Client client, String txId) throws BytomException { + Map req = new HashMap(); + req.put("raw_transaction", txId); + RawTransaction rawTransaction = + client.request("decode-raw-transaction", req, RawTransaction.class); + + logger.info("decode-raw-transaction:"); + logger.info(rawTransaction.toJson()); + + return rawTransaction; + } + + public static class AnnotatedInput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being issued or spent. + */ + private long amount; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output consumed by this input. Null if the input is an + * issuance. + */ + @SerializedName("spent_output_id") + private String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + } + + public static class AnnotatedOutput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being controlled. + */ + private long amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Receiver.java b/bytom-sdk/src/main/java/io/bytom/api/Receiver.java new file mode 100644 index 0000000..add4a6a --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Receiver.java @@ -0,0 +1,49 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.JSONException; + +/** + * Receivers are used to facilitate payments between accounts on different + * cores. They contain a control program and an expiration date. In the future, + * more payment-related metadata may be placed here. + *

+ * Receivers are typically created under accounts via the + * {@link Account.ReceiverBuilder} class. + */ +public class Receiver { + + @SerializedName("address") + public String address; + /** + * Hex-encoded string representation of the control program. + */ + @SerializedName("control_program") + public String controlProgram; + + + /** + * Serializes the receiver into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Deserializes a Receiver from JSON. + * + * @param json a JSON-serialized Receiver object + * @return the deserialized Receiver object + * @throws JSONException Raised if the provided string is not valid JSON. + */ + public static Receiver fromJson(String json) throws JSONException { + try { + return Utils.serializer.fromJson(json, Receiver.class); + } catch (IllegalStateException e) { + throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); + } + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Transaction.java b/bytom-sdk/src/main/java/io/bytom/api/Transaction.java new file mode 100644 index 0000000..d848986 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Transaction.java @@ -0,0 +1,1182 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Transaction Class

+ */ +public class Transaction { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + @SerializedName("tx_id") + public String txId; + + /** + * Time of transaction. + */ + @SerializedName("block_time") + public String blockTime; + + /** + * Unique identifier, or block hash, of the block containing a transaction. + */ + @SerializedName("block_hash") + public String blockHash; + + /** + * Index of a transaction within the block. + */ + @SerializedName("block_index") + public String blockIndex; + + @SerializedName("block_transactions_count") + public String blockTransactionsCount; + + /** + * Height of the block containing a transaction. + */ + @SerializedName("block_height") + public int blockHeight; + + /** + * whether the state of the request has failed. + */ + @SerializedName("status_fail") + public boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(Transaction.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + /** + * Hex-encoded serialization of a transaction to add to the current template. + */ + @SerializedName("base_transaction") + protected String baseTransaction; + + /** + * List of actions in a transaction. + */ + protected List actions; + + /** + * A time duration in milliseconds. If the transaction is not fully signed and + * submitted within this time, it will be rejected by the blockchain. + * Additionally, any outputs reserved when building this transaction will remain + * reserved for this duration. + */ + protected long ttl; + + /** + * Call build-transaction api.
+ * + * Builds a single transaction template. + * + * @param client client object which makes requests to the server + * @return a transaction template + */ + public Template build(Client client) throws BytomException { + return client.request("build-transaction", this, Template.class); + } + + /** + * Default constructor initializes actions list. + */ + public Builder() { + this.actions = new ArrayList<>(); + } + + /** + * Sets the baseTransaction field and initializes the actions lists.
+ * This constructor can be used when executing an atomic swap and the counter + * party has sent an initialized tx template. + */ + public Builder(String baseTransaction) { + this.setBaseTransaction(baseTransaction); + this.actions = new ArrayList<>(); + } + + /** + * Sets the base transaction that will be added to the current template. + */ + public Builder setBaseTransaction(String baseTransaction) { + this.baseTransaction = baseTransaction; + return this; + } + + /** + * Adds an action to a transaction builder. + * @param action action to add + * @return updated builder object + */ + public Builder addAction(Action action) { + this.actions.add(action); + return this; + } + + /** + * Sets a transaction's time-to-live, which indicates how long outputs will be + * reserved for, and how long the transaction will remain valid. Passing zero will + * use the default TTL, which is 300000ms (5 minutes). + * @param ms the duration of the TTL, in milliseconds. + * @return updated builder object + */ + public Builder setTtl(long ms) { + this.ttl = ms; + return this; + } + } + + public static class QueryBuilder { + + public String txId; + + public String accountId; + + public QueryBuilder setTxId(String txId) { + this.txId = txId; + return this; + } + + public QueryBuilder setAccountId(String accountId) { + this.accountId = accountId; + return this; + } + + + /** + * call list-transactions api + * + * @param client client object that makes requests to the core + * @return Transaction Info + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", null, listType); + + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info("all transactions:"); + for (int i = 0; i < transactionList.size(); i++) { + logger.info(transactionList.get(i).toJson()); + } + + return transactionList; + } + + public List listById(Client client) throws BytomException { + Map req = new HashMap(); + req.put("tx_id", this.txId); + req.put("detail", true); + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", req, listType); + + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info("all transactions:"); + for (int i = 0; i < transactionList.size(); i++) { + logger.info(transactionList.get(i).toJson()); + } + + return transactionList; + } + + public List listByAccountId(Client client) throws BytomException { + Map req = new HashMap(); + req.put("account_id", this.accountId); + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", req, listType); + + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info("all transactions:"); + for (int i = 0; i < transactionList.size(); i++) { + logger.info(transactionList.get(i).toJson()); + } + + return transactionList; + } + + /** + * call get-transaction api + * + * @param client + * @return + * @throws BytomException + */ + public Transaction get(Client client) throws BytomException { + Map req = new HashMap(); + req.put("tx_id", this.txId); + + Transaction transaction = client.request("get-transaction", req, Transaction.class); + + logger.info("get-transaction:"); + logger.info(transaction.toJson()); + + return transaction; + } + + } + + public static class SignerBuilder { + /** + * call sign-transaction api + * + * Sends a transaction template to a remote password for signing. + * + * @param client + * @param template a signed transaction template + * @param password + * @return + * @throws BytomException + */ + public Template sign(Client client, Template template, + String password) throws BytomException { + HashMap req = new HashMap(); + req.put("transaction", template); + req.put("password", password); + + Template templateResult = client.requestGet("sign-transaction", req, "transaction", + Transaction.Template.class); + + logger.info("sign-transaction:"); + logger.info(templateResult.toJson()); + + return templateResult; + } + + } + + /** + * A single input included in a transaction. + */ + public static class Input { + /** + * The alias of the account transferring the asset (possibly null if the input is + * an issuance or an unspent output is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * The id of the account transferring the asset (possibly null if the input is an + * issuance or an unspent output is specified). + */ + @SerializedName("account_id") + public String accountId; + + @SerializedName("address") + public String address; + + /** + * The number of units of the asset being issued or spent. + */ + public long amount; + + /** + * The alias of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The id of the output consumed by this input. Null if the input is an issuance. + */ + @SerializedName("spent_output_id") + public String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + public String type; + + public String arbitrary; + + @SerializedName("control_program") + public String controlProgram; + + } + + /** + * A single output included in a transaction. + */ + public static class Output { + /** + * The id of the output. + */ + @SerializedName("id") + public String id; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + public String type; + + /** + * The output's position in a transaction's list of outputs. + */ + public int position; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + public String controlProgram; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The alias of the asset being controlled. + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The number of units of the asset being controlled. + */ + public long amount; + + /** + * The id of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_id") + public String accountId; + + /** + * The alias of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("address") + public String address; + + } + + /** + * Base class representing actions that can be taken within a transaction. + */ + public static class Action extends HashMap { + /** + * + */ + private static final long serialVersionUID = 7948250382060074590L; + + /** + * Default constructor initializes list and sets the client token. + */ + public Action() { + // Several action types require client_token as an idempotency key. + // It's safest to include a default value for this param. + this.put("client_token", UUID.randomUUID().toString()); + } + + /** + * Represents an issuance action. + */ + public static class Issue extends Action { + /** + * + */ + private static final long serialVersionUID = -6296543909434749786L; + + /** + * Default constructor defines the action type as "issue" + */ + public Issue() { + this.put("type", "issue"); + } + + /** + * Specifies the asset to be issued using its alias.
+ * Either this or {@link Issue#setAssetId(String)} must be + * called. + * @param alias alias of the asset to be issued + * @return updated action object + */ + public Issue setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be issued using its id.
+ * Either this or {@link Issue#setAssetAlias(String)} must be + * called. + * @param id id of the asset to be issued + * @return updated action object + */ + public Issue setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be issued.
+ * Must be called. + * @param amount number of units of the asset to be issued + * @return updated action object + */ + public Issue setAmount(long amount) { + this.put("amount", amount); + return this; + } + } + + /** + * Represents a spend action taken on a particular account. + */ + public static class SpendFromAccount extends Action { + /** + * + */ + private static final long serialVersionUID = 6444162327409625893L; + + /** + * Default constructor defines the action type as "spend_account" + */ + public SpendFromAccount() { + this.put("type", "spend_account"); + } + + /** + * Specifies the spending account using its alias.
+ * Either this or {@link SpendFromAccount#setAccountId(String)} must + * be called.
+ * Must be used with {@link SpendFromAccount#setAssetAlias(String)} + * . + * @param alias alias of the spending account + * @return updated action object + */ + public SpendFromAccount setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + /** + * Specifies the spending account using its id.
+ * Either this or {@link SpendFromAccount#setAccountAlias(String)} + * must be called.
+ * Must be used with {@link SpendFromAccount#setAssetId(String)} + * . + * @param id id of the spending account + * @return updated action object + */ + public SpendFromAccount setAccountId(String id) { + this.put("account_id", id); + return this; + } + + /** + * Specifies the asset to be spent using its alias.
+ * Either this or {@link SpendFromAccount#setAssetId(String)} must be + * called.
+ * Must be used with {@link SpendFromAccount#setAccountAlias(String)} + * . + * @param alias alias of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be spent using its id.
+ * Either this or {@link SpendFromAccount#setAssetAlias(String)} must + * be called.
+ * Must be used with {@link SpendFromAccount#setAccountId(String)} + * .
+ * @param id id of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of asset to be spent.
+ * Must be called. + * @param amount number of units of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAmount(long amount) { + this.put("amount", amount); + return this; + } + } + + /** + * Represents a control action taken on a particular account. + */ + public static class ControlWithAccount extends Action { + /** + * + */ + private static final long serialVersionUID = -1067464339402520620L; + + /** + * Default constructor defines the action type as "control_account" + */ + public ControlWithAccount() { + this.put("type", "control_account"); + } + + /** + * Specifies the controlling account using its alias.
+ * Either this or {@link ControlWithAccount#setAccountId(String)} must + * be called.
+ * Must be used with {@link ControlWithAccount#setAssetAlias(String)} + * . + * @param alias alias of the controlling account + * @return updated action object + */ + public ControlWithAccount setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + /** + * Specifies the controlling account using its id.
+ * Either this or {@link ControlWithAccount#setAccountAlias(String)} + * must be called.
+ * Must be used with {@link ControlWithAccount#setAssetId(String)} + * . + * @param id id of the controlling account + * @return updated action object + */ + public ControlWithAccount setAccountId(String id) { + this.put("account_id", id); + return this; + } + + /** + * Specifies the asset to be controlled using its alias.
+ * Either this or {@link ControlWithAccount#setAssetId(String)} must + * be called.
+ * Must be used with + * {@link ControlWithAccount#setAccountAlias(String)}. + * @param alias alias of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id.
+ * Either this or {@link ControlWithAccount#setAssetAlias(String)} + * must be called.
+ * Must be used with {@link ControlWithAccount#setAccountId(String)} + * . + * @param id id of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled.
+ * Must be called. + * @param amount number of units of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Represents a control action taken on a particular address. + */ + public static class ControlWithAddress extends Action { + /** + * + */ + private static final long serialVersionUID = 1292007349260961499L; + + /** + * Default constructor defines the action type as "control_address" + */ + public ControlWithAddress() { + this.put("type", "control_address"); + } + + public ControlWithAddress setAddress(String address) { + this.put("address", address); + return this; + } + + /** + * Specifies the asset to be controlled using its alias.
+ * Either this or {@link ControlWithAddress#setAssetId(String)} must + * be called.
+ * @param alias alias of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id.
+ * Either this or {@link ControlWithAccount#setAssetAlias(String)} + * must be called.
+ * @param id id of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled.
+ * Must be called. + * @param amount number of units of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Use this action to pay assets into a {@link Receiver}. + */ + public static class ControlWithReceiver extends Action { + /** + * + */ + private static final long serialVersionUID = 7280759134960453401L; + + /** + * Default constructor. + */ + public ControlWithReceiver() { + this.put("type", "control_receiver"); + } + + /** + * Specifies the receiver that is being paid to. + * + * @param receiver the receiver being paid to + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setReceiver(Receiver receiver) { + this.put("receiver", receiver); + return this; + } + + /** + * Specifies the asset to be controlled using its alias. + *

+ * Either this or {@link ControlWithReceiver#setAssetId(String)} must + * be called. + * @param alias unique alias of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id. + *

+ * Either this or {@link ControlWithReceiver#setAssetAlias(String)} + * must be called. + * @param id unique ID of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled. + *

+ * Must be called. + * @param amount the number of units of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Represents a retire action. + */ + public static class Retire extends Action { + /** + * + */ + private static final long serialVersionUID = -8434272436211832706L; + + /** + * Default constructor defines the action type as "control_program" + */ + public Retire() { + this.put("type", "retire"); + } + + /** + * Specifies the amount of the asset to be retired.
+ * Must be called. + * @param amount number of units of the asset to be retired + * @return updated action object + */ + public Retire setAmount(long amount) { + this.put("amount", amount); + return this; + } + + /** + * Specifies the asset to be retired using its alias.
+ * Either this or {@link Retire#setAssetId(String)} must be + * called. + * @param alias alias of the asset to be retired + * @return updated action object + */ + public Retire setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be retired using its id.
+ * Either this or {@link Retire#setAssetAlias(String)} must be + * called. + * @param id id of the asset to be retired + * @return updated action object + */ + public Retire setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + public Retire setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + public Retire setAccountId(String id) { + this.put("account_id", id); + return this; + } + + } + + /** + * Sets a k,v parameter pair. + * @param key the key on the parameter object + * @param value the corresponding value + * @return updated action object + */ + public Action setParameter(String key, Object value) { + this.put(key, value); + return this; + } + } + + public static class Template { + /** + * A hex-encoded representation of a transaction template. + */ + @SerializedName("raw_transaction") + public String rawTransaction; + + /** + * The list of signing instructions for inputs in the transaction. + */ + @SerializedName("signing_instructions") + public List signingInstructions; + + /** + * For core use only. + */ + @SerializedName("local") + private boolean local; + + /** + * False (the default) makes the transaction "final" when signing, preventing + * further changes - the signature program commits to the transaction's signature + * hash. True makes the transaction extensible, committing only to the elements in + * the transaction so far, permitting the addition of new elements. + */ + @SerializedName("allow_additional_actions") + private boolean allowAdditionalActions; + + /** + * allowAdditionalActions causes the transaction to be signed so that it can be + * used as a base transaction in a multiparty trade flow. To enable this setting, + * call this method after building the transaction, but before sending it to the + * signer. + * + * All participants in a multiparty trade flow should call this method except for + * the last signer. Do not call this option if the transaction is complete, i.e. + * if it will not be used as a base transaction. + * @return updated transaction template + */ + public Template allowAdditionalActions() { + this.allowAdditionalActions = true; + return this; + } + + /** + * A single signing instruction included in a transaction template. + */ + public static class SigningInstruction { + /** + * The input's position in a transaction's list of inputs. + */ + public int position; + + /** + * A list of components used to coordinate the signing of an input. + */ + @SerializedName("witness_components") + public WitnessComponent[] witnessComponents; + } + + /** + * A single witness component, holding information that will become the input + * witness. + */ + public static class WitnessComponent { + /** + * The type of witness component.
+ * Possible types are "data" and "raw_tx_signature". + */ + public String type; + + /** + * Data to be included in the input witness (null unless type is "data"). + */ + public String value; + + /** + * The number of signatures required for an input (null unless type is + * "signature"). + */ + public int quorum; + + /** + * The list of keys to sign with (null unless type is "signature"). + */ + public KeyID[] keys; + + /** + * The list of signatures made with the specified keys (null unless type is + * "signature"). + */ + public String[] signatures; + } + + /** + * A class representing a derived signing key. + */ + public static class KeyID { + /** + * The extended public key associated with the private key used to sign. + */ + public String xpub; + + /** + * The derivation path of the extended public key. + */ + @SerializedName("derivation_path") + public String[] derivationPath; + } + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + public static class SubmitResponse { + /** + * The transaction id. + */ + public String tx_id; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + /** + * Call submit-transaction api + * + * @param client + * @param template + * @return + * @throws BytomException + */ + public static SubmitResponse submit(Client client, Template template) + throws BytomException { + HashMap body = new HashMap<>(); + body.put("raw_transaction", template.rawTransaction); + return client.request("submit-transaction", body, SubmitResponse.class); + } + + public static class TransactionGas { + /** + * total consumed neu(1BTM = 10^8NEU) for execute transaction. + */ + @SerializedName("total_neu") + public int totalNeu; + + /** + * consumed neu for storage transaction . + */ + @SerializedName("storage_neu") + public int storageNeu; + /** + * consumed neu for execute VM. + */ + @SerializedName("vm_neu") + public int vmNeu; + } + + /** + * call estimate-transaction-gas api + * + * @param client + * @param template + * @return + * @throws BytomException + */ + public static TransactionGas estimateGas(Client client, Template template) + throws BytomException { + HashMap body = new HashMap<>(); + body.put("transaction_template", template); + return client.request("estimate-transaction-gas", body, TransactionGas.class); + } + + public static class Feed { + /** + * name of the transaction feed. + */ + public String alias; + /** + * filter, filter of the transaction feed. + */ + public String filter; + + /** + * param, param of the transaction feed. + */ + public TransactionFeedParam param; + + private static Logger logger = Logger.getLogger(Transaction.class); + + /** + * Serializes the TransactionFeed into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the TransactionFeed object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + + public String alias; + + public String filter; + + public Builder() { + } + + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + public Builder setFilter(String filter) { + this.filter = filter; + return this; + } + + /** + * Call create-transaction-feed api + * + * @param client + * @throws BytomException + */ + public void create(Client client) throws BytomException { + client.request("create-transaction-feed", this); + logger.info("create-transaction-feed"); + } + } + + /** + * Call get-transaction-feed api + * + * @param client + * @param alias + * @return + * @throws BytomException + */ + public static Feed getByAlias(Client client, String alias) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + + // the return param contains txfeed. + Feed transactionFeed = client.requestGet("get-transaction-feed", + req, "txfeed", Feed.class); + logger.info("get-transaction-feed."); + logger.info(transactionFeed.toJson()); + + return transactionFeed; + } + + /** + * Call update-transaction-feed api + * + * @param client + * @param alias + * @param filter + * @throws BytomException + */ + public static void update(Client client, String alias, String filter) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + req.put("filter", filter); + client.request("update-transaction-feed", req); + logger.info("update-transaction-feed successfully"); + } + + /** + * Call list-transaction-feeds api + * @param client + * @return + * @throws BytomException + */ + public static List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Feed.class}); + List transactionFeedList = client.request("list-transaction-feeds", null, listType); + + logger.info("list-transaction-feeds:"); + logger.info("size of transactionList:" + transactionFeedList.size()); + for (int i =0 ;i < transactionFeedList.size();i++) { + logger.info(transactionFeedList.get(i).toJson()); + } + return transactionFeedList; + } + + /** + * Call delete-transaction-feed api + * + * @param client + * @param alias + * @throws BytomException + */ + public static void deleteByAlias(Client client, String alias) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + client.request("delete-transaction-feed", req); + logger.info("delete-transaction-feed successfully"); + } + + + + public class TransactionFeedParam { + + /** + * assetid + */ + public String assetid; + + /** + * lowerlimit + */ + public long lowerlimit; + + /** + * upperlimit + */ + public long upperlimit; + + } + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/UnconfirmedTransaction.java b/bytom-sdk/src/main/java/io/bytom/api/UnconfirmedTransaction.java new file mode 100644 index 0000000..67690a4 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/UnconfirmedTransaction.java @@ -0,0 +1,197 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class UnconfirmedTransaction { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + private String id; + + /** + * version + */ + private Integer version; + + /** + * size + */ + private Integer size; + /** + * time_range + */ + @SerializedName("time_range") + private Integer timeRange; + + /** + * status + */ + @SerializedName("status_fail") + private boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + private List inputs; + + /** + * List of specified outputs for a transaction. + */ + private List outputs; + + private static Logger logger = Logger.getLogger(UnconfirmedTransaction.class); + + /** + * Serializes the UnconfirmedTransaction into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the UnconfirmedTransaction object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call get-unconfirmed-transaction api + * + * @param client + * @param txId + * @return + * @throws BytomException + */ + public static UnconfirmedTransaction get(Client client, String txId) throws BytomException { + Map req = new HashMap(); + req.put("tx_id", txId); + UnconfirmedTransaction UCTX = client.request("get-unconfirmed-transaction", + req, UnconfirmedTransaction.class); + + logger.info("get-unconfirmed-transaction:"); + logger.info(UCTX.toJson()); + + return UCTX; + } + + public static UTXResponse list(Client client) throws BytomException { + UTXResponse utxResponse = + client.request("list-unconfirmed-transactions", null, UTXResponse.class); + + logger.info("list-unconfirmed-transactions:"); + logger.info(utxResponse.toJson()); + + return utxResponse; + } + + public static class UTXResponse { + + @SerializedName("total") + public Integer total; + + @SerializedName("tx_ids") + public List txIds; + + public String toJson() { + return Utils.serializer.toJson(this); + } + } + + public static class AnnotatedInput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being issued or spent. + */ + private long amount; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output consumed by this input. Null if the input is an + * issuance. + */ + @SerializedName("spent_output_id") + private String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + } + + public static class AnnotatedOutput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being controlled. + */ + private long amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/UnspentOutput.java b/bytom-sdk/src/main/java/io/bytom/api/UnspentOutput.java new file mode 100644 index 0000000..1447093 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/UnspentOutput.java @@ -0,0 +1,132 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.List; + +public class UnspentOutput { + /** + * The id of the account controlling this output (possibly null if a control program + * is specified). + */ + @SerializedName("account_id") + public String accountId; + + /** + * The alias of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The alias of the asset being controlled. + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The number of units of the asset being controlled. + */ + public long amount; + + /** + * address of account + */ + public String address; + + /** + * whether the account address is change + */ + public boolean change; + + /** + * The ID of the output. + */ + @SerializedName("id") + public String id; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("program") + public String program; + + @SerializedName("control_program_index") + public String controlProgramIndex; + + /** + * source unspent output id + */ + @SerializedName("source_id") + public String sourceId; + + /** + * position of source unspent output id in block + */ + @SerializedName("source_pos") + public int sourcePos; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("valid_height") + public int validHeight; + + private static Logger logger = Logger.getLogger(UnspentOutput.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class QueryBuilder { + + /** + * id of unspent output. + */ + public String id; + + public QueryBuilder setId(String id) { + this.id = id; + return this; + } + + /** + * call list-unspent-outputs api + * + * @param client client object that makes requests to the core + * @return + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{UnspentOutput.class}); + List unspentOutputList = client.request("list-unspent-outputs", this, listType); + logger.info("list-unspent-outputs:"); + logger.info("size of unspentOutputList:" + unspentOutputList.size()); + for (UnspentOutput UTXO : unspentOutputList) { + logger.info(UTXO.toJson()); + } + + return unspentOutputList; + } + + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/api/Wallet.java b/bytom-sdk/src/main/java/io/bytom/api/Wallet.java new file mode 100644 index 0000000..5145757 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/api/Wallet.java @@ -0,0 +1,153 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Wallet Class

+ */ +public class Wallet { + + @SerializedName("account_image") + public AccountImage accountImage; + + @SerializedName("asset_image") + public AssetImage assetImage; + + @SerializedName("key_images") + public KeyImages keyImages; + + private static Logger logger = Logger.getLogger(Wallet.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call backup-wallet api + * + * @param client + * @return + * @throws BytomException + */ + public static Wallet backupWallet(Client client) throws BytomException { + Wallet wallet = client.request("backup-wallet", null, Wallet.class); + + logger.info("backup-wallet:"); + logger.info(wallet.toJson()); + + return wallet; + } + + /** + * Call restore-wallet api + * + * @param client + * @param accountImage + * @param assetImage + * @param keyImages + * @throws BytomException + */ + public static void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages) throws BytomException{ + Map body = new HashMap(); + body.put("account_image", accountImage); + body.put("asset_image", assetImage); + body.put("key_images", keyImages); + + logger.info("restore-wallet:"); + logger.info(body.toString()); + + client.request("restore-wallet", body); + } + + public static class AccountImage { + + public Slices[] slices; + + public static class Slices { + + @SerializedName("contract_index") + public int contractIndex; + + public Account account; + + public static class Account { + + public String type; + + public List xpubs; + + public int quorum; + + @SerializedName("key_index") + public int keyIndex; + + public String id; + + public String alias; + + } + + } + } + + public static class AssetImage { + + public Assets[] assets; + + public static class Assets { + public String type; + public List xpubs; + public int quorum; + public String id; + public String alias; + public Map definition; + @SerializedName("key_index") + public int keyIndex; + @SerializedName("vm_version") + public int vmVersion; + @SerializedName("asset_image") + public String issueProgram; + @SerializedName("raw_definition_byte") + public String rawDefinitionByte; + } + } + + public static class KeyImages { + + public Xkeys[] xkeys; + + public static class Xkeys { + + public Crypto crypto; + public String id; + public String type; + public int version; + public String alias; + public String xpub; + + public static class Crypto { + public String cipher; + public String ciphertext; + public Map cipherparams; + public String kdf; + public Map kdfparams; + public String mac; + } + + } + } + +} diff --git a/bytom-sdk/src/main/java/io/bytom/common/Configuration.java b/bytom-sdk/src/main/java/io/bytom/common/Configuration.java new file mode 100644 index 0000000..21dd949 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/common/Configuration.java @@ -0,0 +1,29 @@ +package io.bytom.common; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +public class Configuration { + + private static Properties props = new Properties(); + static { + try { + props.load(Thread.currentThread().getContextClassLoader() + .getResourceAsStream("config.properties")); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getValue(String key) { + return props.getProperty(key); + } + + public static void updateProperties(String key, String value) { + props.setProperty(key, value); + } + +} diff --git a/bytom-sdk/src/main/java/io/bytom/common/ParameterizedTypeImpl.java b/bytom-sdk/src/main/java/io/bytom/common/ParameterizedTypeImpl.java new file mode 100644 index 0000000..d66be8c --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/common/ParameterizedTypeImpl.java @@ -0,0 +1,30 @@ +package io.bytom.common; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class ParameterizedTypeImpl implements ParameterizedType { + + private final Class raw; + private final Type[] args; + + public ParameterizedTypeImpl(Class raw, Type[] args) { + this.raw = raw; + this.args = args != null ? args : new Type[0]; + } + + @Override + public Type[] getActualTypeArguments() { + return args; + } + + @Override + public Type getRawType() { + return raw; + } + + @Override + public Type getOwnerType() { + return null; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/common/Utils.java b/bytom-sdk/src/main/java/io/bytom/common/Utils.java new file mode 100644 index 0000000..7a7f69e --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/common/Utils.java @@ -0,0 +1,9 @@ +package io.bytom.common; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class Utils { + public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create(); +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/APIException.java b/bytom-sdk/src/main/java/io/bytom/exception/APIException.java new file mode 100644 index 0000000..52ff64d --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/APIException.java @@ -0,0 +1,169 @@ +package io.bytom.exception; + +import com.google.gson.annotations.SerializedName; + +/** + * APIException wraps errors returned by the API. + * Each error contains a brief description in addition to a unique error code.
+ * The error code can be used by Chain Support to diagnose the exact cause of the error. + * The mapping of error codes to messages is as follows:

+ * + *

General errors

+ * CH001 - Request timed out + * CH002 - Not found + * CH003 - Invalid request body + * CH004 - Invalid request header + * CH006 - Not found + * + *

Account/Asset errors

+ * CH200 - Quorum must be greater than 1 and less than or equal to the length of xpubs + * CH201 - Invalid xpub format + * CH202 - At least one xpub is required + * CH203 - Retrieved type does not match expected type + * + *

Access token errors

+ * CH300 - Malformed or empty access token id + * CH301 - Access tokens must be type client or network + * CH302 - Access token id is already in use + * CH310 - The access token used to authenticate this request cannot be deleted + * + *

Query errors

+ * CH600 - Malformed pagination parameter `after` + * CH601 - Incorrect number of parameters to filter + * CH602 - Malformed query filter + * + *

Transaction errors

+ * CH700 - Reference data does not match previous transaction's reference data
+ * CH701 - Invalid action type
+ * CH702 - Invalid alias on action
+ * CH730 - Missing raw transaction
+ * CH731 - Too many signing instructions in template for transaction
+ * CH732 - Invalid transaction input index
+ * CH733 - Invalid signature script component
+ * CH734 - Missing signature in template
+ * CH735 - Transaction rejected
+ * CH760 - Insufficient funds for tx
+ * CH761 - Some outputs are reserved; try again
+ */ +public class APIException extends BytomException { + /** + * Unique identifier for the error. + */ + public String code; + + /** + * Message describing the general nature of the error. + */ + @SerializedName("message") + public String chainMessage; + + /** + * Additional information about the error (possibly null). + */ + public String detail; + + /** + * Specifies whether the error is temporary, or a change to the request is necessary. + */ + public boolean temporary; + + /** + * Unique identifier of the request to the server. + */ + public String requestId; + + /** + * HTTP status code returned by the server. + */ + public int statusCode; + + /** + * Initializes exception with its message and requestId attributes. + * @param message error message + * @param requestId unique identifier of the request + */ + public APIException(String message, String requestId) { + super(message); + this.requestId = requestId; + } + + /** + * Intitializes exception with its code, message, detail & temporary field set. + * @param code error code + * @param message error message + * @param detail additional error information + * @param temporary unique identifier of the request + */ + public APIException(String code, String message, String detail, boolean temporary) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.temporary = temporary; + } + + /** + * Initializes exception with its code, message, detail & requestId attributes. + * @param code error code + * @param message error message + * @param detail additional error information + * @param requestId unique identifier of the request + */ + public APIException(String code, String message, String detail, String requestId) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.requestId = requestId; + } + + /** + * Initializes exception with all of its attributes. + * @param code error code + * @param message error message + * @param detail additional error information + * @param temporary specifies if the error is temporary + * @param requestId unique identifier of the request + * @param statusCode HTTP status code + */ + public APIException( + String code, + String message, + String detail, + boolean temporary, + String requestId, + int statusCode) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.temporary = temporary; + this.requestId = requestId; + this.statusCode = statusCode; + } + + /** + * Constructs a detailed message of the error. + * @return detailed error message + */ + @Override + public String getMessage() { + String s = ""; + + if (this.code != null && this.code.length() > 0) { + s += "Code: " + this.code + " "; + } + + s += "Message: " + this.chainMessage; + + if (this.detail != null && this.detail.length() > 0) { + s += " Detail: " + this.detail; + } + + if (this.requestId != null) { + s += " Request-ID: " + this.requestId; + } + + return s; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/BadURLException.java b/bytom-sdk/src/main/java/io/bytom/exception/BadURLException.java new file mode 100644 index 0000000..dd16377 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/BadURLException.java @@ -0,0 +1,14 @@ +package io.bytom.exception; + +/** + * BadURLException wraps errors due to malformed URLs. + */ +public class BadURLException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public BadURLException(String message) { + super(message); + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/BuildException.java b/bytom-sdk/src/main/java/io/bytom/exception/BuildException.java new file mode 100644 index 0000000..19a2f89 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/BuildException.java @@ -0,0 +1,49 @@ +package io.bytom.exception; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * BuildException wraps errors returned by the build-transaction endpoint. + */ +public class BuildException extends APIException { + + public BuildException(String message, String requestId) { + super(message, requestId); + } + + public static class ActionError extends APIException { + + public static class Data { + /** + * The index of the action that caused this error. + */ + @SerializedName("index") + public Integer index; + } + + public ActionError(String message, String requestId) { + super(message, requestId); + } + + /** + * Additional data pertaining to the error. + */ + public Data data; + } + + public static class Data { + /** + * A list of errors resulting from building actions. + */ + @SerializedName("actions") + public List actionErrors; + } + + /** + * Extra data associated with this error, if any. + */ + @SerializedName("data") + public Data data; +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/BytomException.java b/bytom-sdk/src/main/java/io/bytom/exception/BytomException.java new file mode 100644 index 0000000..4043065 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/BytomException.java @@ -0,0 +1,32 @@ +package io.bytom.exception; + +/** + * Base exception class for the Chain Core SDK. + */ +public class BytomException extends Exception { + /** + * Default constructor. + */ + public BytomException() { + super(); + } + + /** + * Initializes exception with its message attribute. + * + * @param message error message + */ + public BytomException(String message) { + super(message); + } + + /** + * Initializes a new exception while storing the original cause. + * + * @param message the error message + * @param cause the cause of the exception + */ + public BytomException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/ConfigurationException.java b/bytom-sdk/src/main/java/io/bytom/exception/ConfigurationException.java new file mode 100644 index 0000000..695e873 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/ConfigurationException.java @@ -0,0 +1,23 @@ +package io.bytom.exception; + +/** + * ConfigurationException wraps errors during client configuration. + */ +public class ConfigurationException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public ConfigurationException(String message) { + super(message); + } + + /** + * Initializes new exception while storing original cause. + * @param message the error message + * @param cause the original cause + */ + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/ConnectivityException.java b/bytom-sdk/src/main/java/io/bytom/exception/ConnectivityException.java new file mode 100644 index 0000000..4f50197 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/ConnectivityException.java @@ -0,0 +1,36 @@ +package io.bytom.exception; + +import com.squareup.okhttp.Response; + +import java.io.IOException; + +/** + * ConnectivityException wraps errors due to connectivity issues with the server. + */ +public class ConnectivityException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param resp the server response used to create error message + */ + public ConnectivityException(Response resp) { + super(formatMessage(resp)); + } + + /** + * Parses the the server response into a detailed error message. + * @param resp the server response + * @return detailed error message + */ + private static String formatMessage(Response resp) { + String s = + "Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings."; + // TODO(kr): include client-generated reqid here once we have that. + String body; + try { + body = resp.body().string(); + } catch (IOException ex) { + body = "[unable to read response body: " + ex.toString() + "]"; + } + return String.format("%s status=%d body=%s", s, resp.code(), body); + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/HTTPException.java b/bytom-sdk/src/main/java/io/bytom/exception/HTTPException.java new file mode 100644 index 0000000..04646bd --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/HTTPException.java @@ -0,0 +1,23 @@ +package io.bytom.exception; + +/** + * HTTPException wraps generic HTTP errors. + */ +public class HTTPException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public HTTPException(String message) { + super(message); + } + + /** + * Initializes new exception while storing original cause. + * @param message the error message + * @param cause the original cause + */ + public HTTPException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/exception/JSONException.java b/bytom-sdk/src/main/java/io/bytom/exception/JSONException.java new file mode 100644 index 0000000..a81cef6 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/exception/JSONException.java @@ -0,0 +1,39 @@ +package io.bytom.exception; + +/** + * JSONException wraps errors due to marshaling/unmarshaling json payloads. + */ +public class JSONException extends BytomException { + + /** + * Unique indentifier of the request to the server. + */ + public String requestId; + + /** + * Default constructor. + */ + public JSONException(String message) { + super(message); + } + + /** + * Initializes exception with its message and requestId attributes. + * Use this constructor in context of an API call. + * + * @param message error message + * @param requestId unique identifier of the request + */ + public JSONException(String message, String requestId) { + super(message); + this.requestId = requestId; + } + + public String getMessage() { + String message = "Message: " + super.getMessage(); + if (requestId != null) { + message += " Request-ID: " + requestId; + } + return message; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/http/BatchResponse.java b/bytom-sdk/src/main/java/io/bytom/http/BatchResponse.java new file mode 100644 index 0000000..da29b75 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/http/BatchResponse.java @@ -0,0 +1,140 @@ +package io.bytom.http; + +import io.bytom.exception.*; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.*; + +/** + * BatchResponse provides a convenient interface for handling the results of + * batched API calls. The response contains one success or error per outgoing + * request item in the batch. Errors are always of type APIExcpetion. + */ +public class BatchResponse { + private Response response; + private Map successesByIndex = new LinkedHashMap<>(); + private Map errorsByIndex = new LinkedHashMap<>(); + + /** + * This constructor is used when deserializing a response from an API call. + */ + public BatchResponse(Response response, Gson serializer, Type tClass, Type eClass) + throws BytomException, IOException { + this.response = response; + + try { + JsonArray root = new JsonParser().parse(response.body().charStream()).getAsJsonArray(); + for (int i = 0; i < root.size(); i++) { + JsonElement elem = root.get(i); + + // Test for interleaved errors + APIException err = serializer.fromJson(elem, eClass); + if (err.code != null) { + errorsByIndex.put(i, err); + continue; + } + + successesByIndex.put(i, (T) serializer.fromJson(elem, tClass)); + } + } catch (IllegalStateException e) { + throw new JSONException( + "Unable to read body: " + e.getMessage(), response.headers().get("Chain-Request-ID")); + } + } + + /** + * This constructor is used for synthetically generating a batch response + * object from a map of successes and a map of errors. It ensures that + * the successes and errors are stored in an order-preserving fashion. + */ + public BatchResponse(Map successes, Map errors) { + List successIndexes = new ArrayList<>(); + Iterator successIter = successes.keySet().iterator(); + while (successIter.hasNext()) successIndexes.add(successIter.next()); + Collections.sort(successIndexes); + for (int i : successIndexes) successesByIndex.put(i, successes.get(i)); + + List errorIndexes = new ArrayList<>(); + Iterator errorIter = errors.keySet().iterator(); + while (errorIter.hasNext()) errorIndexes.add(errorIter.next()); + Collections.sort(errorIndexes); + for (int i : errorIndexes) errorsByIndex.put(i, errors.get(i)); + } + + /** + * Returns the internal response object. + */ + public Response response() { + return response; + } + + /** + * Returns the total number of response objects. This should equal the number + * of request objects in the batch. + */ + public int size() { + return successesByIndex.size() + errorsByIndex.size(); + } + + /** + * Returns whether the request object at the given index produced a success. + * @param index the index of the request object + */ + public boolean isSuccess(int index) { + return successesByIndex.containsKey(index); + } + + /** + * Returns whether the request object at the given index produced an error. + * @param index the index of the request object + */ + public boolean isError(int index) { + return errorsByIndex.containsKey(index); + } + + /** + * Returns a list of successful response objects in the batch. The order of + * the list corresponds to the order of the request objects that produced the + * successes. + */ + public List successes() { + List res = new ArrayList<>(); + res.addAll(successesByIndex.values()); + return res; + } + + /** + * Returns a list of error objects in the batch. The order of the list + * corresponds to the order of the request objects that produced the + * errors. + */ + public List errors() { + List res = new ArrayList<>(); + res.addAll(errorsByIndex.values()); + return res; + } + + /** + * Returns a map of success responses, keyed by the index of the request + * object that produced the success. The set of this map's keys is mutually + * exclusive of the keys returned by errorsByIndex. + */ + public Map successesByIndex() { + return successesByIndex; + } + + /** + * Returns a map of error responses, keyed by the index of the request + * object that produced the error. The set of this map's keys is mutually + * exclusive of the keys returned by successByIndex. + */ + public Map errorsByIndex() { + return errorsByIndex; + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/http/Client.java b/bytom-sdk/src/main/java/io/bytom/http/Client.java new file mode 100644 index 0000000..958f9f6 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/http/Client.java @@ -0,0 +1,815 @@ +package io.bytom.http; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import io.bytom.common.*; +import io.bytom.exception.*; +import com.google.gson.Gson; +import com.squareup.okhttp.*; +import org.apache.log4j.Logger; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; + +import javax.net.ssl.*; +import java.io.*; +import java.lang.reflect.Type; +import java.net.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The Client object contains all information necessary to + * perform an HTTP request against a remote API. Typically, + * an application will have a client that makes requests to + * a Chain Core, and a separate Client that makes requests + * to an HSM server. + */ +public class Client { + private String url; + private String accessToken; + private OkHttpClient httpClient; + + // Used to create empty, in-memory key stores. + private static final char[] DEFAULT_KEYSTORE_PASSWORD = "123456".toCharArray(); + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static String version = "dev"; // updated in the static initializer + + public static Logger logger = Logger.getLogger(Client.class); + + private static class BuildProperties { + public String version; + } + + static { + InputStream in = Client.class.getClassLoader().getResourceAsStream("properties.json"); + if (in != null) { + InputStreamReader inr = new InputStreamReader(in); + version = Utils.serializer.fromJson(inr, BuildProperties.class).version; + } + } + + public static Client generateClient() throws BytomException { + + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888"; + } + + if (coreURL.endsWith("/")) { + //split the last char "/" + coreURL = coreURL.substring(0, coreURL.length()-1); + logger.info("check the coreURL is right."); + } + + return new Client(coreURL, accessToken); + } + + public Client(Builder builder) throws ConfigurationException { + if (builder.url.endsWith("/")) { + //split the last char "/" + builder.url = builder.url.substring(0, builder.url.length()-1); + } + this.url = builder.url; + this.accessToken = builder.accessToken; + this.httpClient = buildHttpClient(builder); + } + + /** + * Create a new http Client object using the default development host URL. + */ + public Client() throws BytomException { + this(new Builder()); + } + + /** + * Create a new http Client object + * + * @param url the URL of the Chain Core or HSM + */ + public Client(String url) throws BytomException { + this(new Builder().setUrl(url)); + } + + /** + * Create a new http Client object + * + * @param url the URL of the Chain Core or HSM + * @param accessToken a Client API access token + */ + public Client(String url, String accessToken) throws BytomException { + this(new Builder().setUrl(url).setAccessToken(accessToken)); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public T request(String action, Object body, final Type tClass) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } else if (data != null) { + return deserializer.fromJson(data, tClass); + } else { + return deserializer.fromJson(response.body().charStream(), tClass); + } + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action, + * ignoring the body of the response. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @throws BytomException + */ + public void request(String action, Object body) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public Object create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } + return null; + } + }; + post(action, body, rc); + } + + /** + * return the value of named as key from json + * + * @param action + * @param body + * @param key + * @param tClass + * @param + * @return + * @throws BytomException + */ + public T requestGet(String action, Object body, final String key, final Type tClass) + throws BytomException { + ResponseCreator rc = new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, + BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + + if (status != null && status.toString().contains("fail")) + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + else if (data != null) + return deserializer.fromJson(data.getAsJsonObject().get(key), tClass); + else + return deserializer.fromJson(response.body().charStream(), tClass); + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * Use this method if you want batch semantics, i.e., the endpoint response + * is an array of valid objects interleaved with arrays, once corresponding to + * each input object. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @param eClass Type of error object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public BatchResponse batchRequest( + String action, Object body, final Type tClass, final Type eClass) throws BytomException { + ResponseCreator> rc = + new ResponseCreator>() { + public BatchResponse create(Response response, Gson deserializer) + throws BytomException, IOException { + return new BatchResponse(response, deserializer, tClass, eClass); + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * Use this method if you want single-item semantics (creating single assets, + * building single transactions) but the API endpoint is implemented as a + * batch call. + *

+ * Because request bodies for batch calls do not share a consistent format, + * this method does not perform any automatic arrayification of outgoing + * parameters. Remember to arrayify your request objects where appropriate. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public T singletonBatchRequest( + String action, Object body, final Type tClass, final Type eClass) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public T create(Response response, Gson deserializer) throws BytomException, IOException { + BatchResponse batch = new BatchResponse<>(response, deserializer, tClass, eClass); + + List errors = batch.errors(); + if (errors.size() == 1) { + // This throw must occur within this lambda in order for APIClient's + // retry logic to take effect. + throw errors.get(0); + } + + List successes = batch.successes(); + if (successes.size() == 1) { + return successes.get(0); + } + + // We should never get here, unless there is a bug in either the SDK or + // API code, causing a non-singleton response. + /* + throw new BytomException( + "Invalid singleton response, request ID " + + batch.response().headers().get("Bytom-Request-ID")); + */ + throw new BytomException("Invalid singleton response."); + } + }; + return post(action, body, rc); + } + + /** + * Returns true if a client access token stored in the client. + * + * @return a boolean + */ + public boolean hasAccessToken() { + return this.accessToken != null && !this.accessToken.isEmpty(); + } + + /** + * Returns the client access token (possibly null). + * + * @return the client access token + */ + public String accessToken() { + return accessToken; + } + + public String getUrl() { + return url; + } + + /** + * Pins a public key to the HTTP client. + * + * @param provider certificate provider + * @param subjPubKeyInfoHash public key hash + */ + public void pinCertificate(String provider, String subjPubKeyInfoHash) { + CertificatePinner cp = + new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); + this.httpClient.setCertificatePinner(cp); + } + + /** + * Sets the default connect timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setConnectTimeout(long timeout, TimeUnit unit) { + this.httpClient.setConnectTimeout(timeout, unit); + } + + /** + * Sets the default read timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setReadTimeout(long timeout, TimeUnit unit) { + this.httpClient.setReadTimeout(timeout, unit); + } + + /** + * Sets the default write timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setWriteTimeout(long timeout, TimeUnit unit) { + this.httpClient.setWriteTimeout(timeout, unit); + } + + /** + * Sets the proxy information for the HTTP client. + * + * @param proxy proxy object + */ + public void setProxy(Proxy proxy) { + this.httpClient.setProxy(proxy); + } + + /** + * Defines an interface for deserializing HTTP responses into objects. + * + * @param the type of object to return + */ + public interface ResponseCreator { + /** + * Deserializes an HTTP response into a Java object of type T. + * + * @param response HTTP response object + * @param deserializer json deserializer + * @return an object of type T + * @throws BytomException + * @throws IOException + */ + T create(Response response, Gson deserializer) throws BytomException, IOException; + } + + /** + * Builds and executes an HTTP Post request. + * + * @param path the path to the endpoint + * @param body the request body + * @param respCreator object specifying the response structure + * @return a response deserialized into type T + * @throws BytomException + */ + private T post(String path, Object body, ResponseCreator respCreator) + throws BytomException { + + RequestBody requestBody = RequestBody.create(this.JSON, Utils.serializer.toJson(body)); + Request req; + + BytomException exception = null; + URL endpointURL = null; + + try { + endpointURL = new URL(url + "/" + path); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + Request.Builder builder = + new Request.Builder() + .header("User-Agent", "bytom-sdk-java/" + version) + .url(endpointURL) + .method("POST", requestBody); + if (hasAccessToken()) { + builder = builder.header("Authorization", buildCredentials()); + } + req = builder.build(); + + Response resp = null; + + T object = null; + + try { + resp = this.checkError(this.httpClient.newCall(req).execute()); + object = respCreator.create(resp, Utils.serializer); + } catch (IOException e) { + e.printStackTrace(); + } + + return object; + } + + private OkHttpClient buildHttpClient(Builder builder) throws ConfigurationException { + OkHttpClient httpClient = builder.baseHttpClient.clone(); + + try { + if (builder.trustManagers != null) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(builder.keyManagers, builder.trustManagers, null); + httpClient.setSslSocketFactory(sslContext.getSocketFactory()); + } + } catch (GeneralSecurityException ex) { + throw new ConfigurationException("Unable to configure TLS", ex); + } + if (builder.readTimeoutUnit != null) { + httpClient.setReadTimeout(builder.readTimeout, builder.readTimeoutUnit); + } + if (builder.writeTimeoutUnit != null) { + httpClient.setWriteTimeout(builder.writeTimeout, builder.writeTimeoutUnit); + } + if (builder.connectTimeoutUnit != null) { + httpClient.setConnectTimeout(builder.connectTimeout, builder.connectTimeoutUnit); + } + if (builder.pool != null) { + httpClient.setConnectionPool(builder.pool); + } + if (builder.proxy != null) { + httpClient.setProxy(builder.proxy); + } + if (builder.cp != null) { + httpClient.setCertificatePinner(builder.cp); + } + + return httpClient; + } + + private static final Random randomGenerator = new Random(); + private static final int MAX_RETRIES = 10; + private static final int RETRY_BASE_DELAY_MILLIS = 40; + + // the max amount of time cored leader election could take + private static final int RETRY_MAX_DELAY_MILLIS = 15000; + + private static int retryDelayMillis(int retryAttempt) { + // Calculate the max delay as base * 2 ^ (retryAttempt - 1). + int max = RETRY_BASE_DELAY_MILLIS * (1 << (retryAttempt - 1)); + max = Math.min(max, RETRY_MAX_DELAY_MILLIS); + + // To incorporate jitter, use a pseudo random delay between [max/2, max] millis. + return randomGenerator.nextInt(max / 2) + max / 2 + 1; + } + + private static final int[] RETRIABLE_STATUS_CODES = { + 408, // Request Timeout + 429, // Too Many Requests + 500, // Internal Server Error + 502, // Bad Gateway + 503, // Service Unavailable + 504, // Gateway Timeout + 509, // Bandwidth Limit Exceeded + }; + + private static boolean isRetriableStatusCode(int statusCode) { + for (int i = 0; i < RETRIABLE_STATUS_CODES.length; i++) { + if (RETRIABLE_STATUS_CODES[i] == statusCode) { + return true; + } + } + return false; + } + + private Response checkError(Response response) throws BytomException { + /* + String rid = response.headers().get("Bytom-Request-ID"); + if (rid == null || rid.length() == 0) { + // Header field Bytom-Request-ID is set by the backend + // API server. If this field is set, then we can expect + // the body to be well-formed JSON. If it's not set, + // then we are probably talking to a gateway or proxy. + throw new ConnectivityException(response); + } */ + + if ((response.code() / 100) != 2) { + try { + APIException err = + Utils.serializer.fromJson(response.body().charStream(), APIException.class); + if (err.code != null) { + //err.requestId = rid; + err.statusCode = response.code(); + throw err; + } + } catch (IOException ex) { + //throw new JSONException("Unable to read body. " + ex.getMessage(), rid); + throw new JSONException("Unable to read body. "); + } + } + return response; + } + + private String buildCredentials() { + String user = ""; + String pass = ""; + if (hasAccessToken()) { + String[] parts = accessToken.split(":"); + if (parts.length >= 1) { + user = parts[0]; + } + if (parts.length >= 2) { + pass = parts[1]; + } + } + return Credentials.basic(user, pass); + } + + /** + * Overrides {@link Object#hashCode()} + * + * @return the hash code + */ + @Override + public int hashCode() { + int code = this.url.hashCode(); + if (this.hasAccessToken()) { + code = code * 31 + this.accessToken.hashCode(); + } + return code; + } + + /** + * Overrides {@link Object#equals(Object)} + * + * @param o the object to compare + * @return a boolean specifying equality + */ + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (!(o instanceof Client)) return false; + + Client other = (Client) o; + if (!this.url.equalsIgnoreCase(other.url)) { + return false; + } + return Objects.equals(this.accessToken, other.accessToken); + } + + /** + * A builder class for creating client objects + */ + public static class Builder { + + private String url; + + private OkHttpClient baseHttpClient; + private String accessToken; + private CertificatePinner cp; + private KeyManager[] keyManagers; + private TrustManager[] trustManagers; + private long connectTimeout; + private TimeUnit connectTimeoutUnit; + private long readTimeout; + private TimeUnit readTimeoutUnit; + private long writeTimeout; + private TimeUnit writeTimeoutUnit; + private Proxy proxy; + private ConnectionPool pool; + + public Builder() { + this.baseHttpClient = new OkHttpClient(); + this.baseHttpClient.setFollowRedirects(false); + this.setDefaults(); + } + + public Builder(Client client) { + this.baseHttpClient = client.httpClient.clone(); + this.url = client.url; + this.accessToken = client.accessToken; + } + + private void setDefaults() { + this.setReadTimeout(30, TimeUnit.SECONDS); + this.setWriteTimeout(30, TimeUnit.SECONDS); + this.setConnectTimeout(30, TimeUnit.SECONDS); + this.setConnectionPool(50, 2, TimeUnit.MINUTES); + } + + public Builder setUrl(String url) { + this.url = url; + return this; + } + + /** + * Sets the access token for the client + * + * @param accessToken The access token for the Chain Core or HSM + */ + public Builder setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + /** + * Sets the client's certificate and key for TLS client authentication. + * PEM-encoded, RSA private keys adhering to PKCS#1 or PKCS#8 are supported. + * + * @param certStream input stream of PEM-encoded X.509 certificate + * @param keyStream input stream of PEM-encoded private key + */ + public Builder setX509KeyPair(InputStream certStream, InputStream keyStream) + throws ConfigurationException { + try (PEMParser parser = new PEMParser(new InputStreamReader(keyStream))) { + // Extract certs from PEM-encoded input. + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream); + + // Parse the private key from PEM-encoded input. + Object obj = parser.readObject(); + PrivateKeyInfo info; + if (obj instanceof PEMKeyPair) { + // PKCS#1 Private Key found. + PEMKeyPair kp = (PEMKeyPair) obj; + info = kp.getPrivateKeyInfo(); + } else if (obj instanceof PrivateKeyInfo) { + // PKCS#8 Private Key found. + info = (PrivateKeyInfo) obj; + } else { + throw new ConfigurationException("Unsupported private key provided."); + } + + // Create a new key store and input the pair. + KeySpec spec = new PKCS8EncodedKeySpec(info.getEncoded()); + KeyFactory kf = KeyFactory.getInstance("RSA"); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); + keyStore.setCertificateEntry("cert", certificate); + keyStore.setKeyEntry( + "key", + kf.generatePrivate(spec), + DEFAULT_KEYSTORE_PASSWORD, + new X509Certificate[]{certificate}); + + // Use key store to build a key manager. + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); + + this.keyManagers = keyManagerFactory.getKeyManagers(); + return this; + } catch (GeneralSecurityException | IOException ex) { + throw new ConfigurationException("Unable to store X.509 cert/key pair", ex); + } + } + + /** + * Sets the client's certificate and key for TLS client authentication. + * + * @param certPath file path to PEM-encoded X.509 certificate + * @param keyPath file path to PEM-encoded private key + */ + public Builder setX509KeyPair(String certPath, String keyPath) throws ConfigurationException { + try (InputStream certStream = + new ByteArrayInputStream(Files.readAllBytes(Paths.get(certPath))); + InputStream keyStream = + new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyPath)))) { + return setX509KeyPair(certStream, keyStream); + } catch (IOException ex) { + throw new ConfigurationException("Unable to store X509 cert/key pair", ex); + } + } + + /** + * Trusts the given CA certs, and no others. Use this if you are running + * your own CA, or are using a self-signed server certificate. + * + * @param is input stream of the certificates to trust, in PEM format. + */ + public Builder setTrustedCerts(InputStream is) throws ConfigurationException { + try { + // Extract certs from PEM-encoded input. + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Collection certificates = + certificateFactory.generateCertificates(is); + if (certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + + // Create a new key store and input the cert. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = Integer.toString(index++); + keyStore.setCertificateEntry(certificateAlias, certificate); + } + + // Use key store to build an X509 trust manager. + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException( + "Unexpected default trust managers:" + Arrays.toString(trustManagers)); + } + + this.trustManagers = trustManagers; + return this; + } catch (GeneralSecurityException | IOException ex) { + throw new ConfigurationException("Unable to configure trusted CA certs", ex); + } + } + + /** + * Trusts the given CA certs, and no others. Use this if you are running + * your own CA, or are using a self-signed server certificate. + * + * @param path The path of a file containing certificates to trust, in PEM format. + */ + public Builder setTrustedCerts(String path) throws ConfigurationException { + try (InputStream is = new FileInputStream(path)) { + return setTrustedCerts(is); + } catch (IOException ex) { + throw new ConfigurationException("Unable to configure trusted CA certs", ex); + } + } + + /** + * Sets the certificate pinner for the client + * + * @param provider certificate provider + * @param subjPubKeyInfoHash public key hash + */ + public Builder pinCertificate(String provider, String subjPubKeyInfoHash) { + this.cp = new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); + return this; + } + + /** + * Sets the connect timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setConnectTimeout(long timeout, TimeUnit unit) { + this.connectTimeout = timeout; + this.connectTimeoutUnit = unit; + return this; + } + + /** + * Sets the read timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setReadTimeout(long timeout, TimeUnit unit) { + this.readTimeout = timeout; + this.readTimeoutUnit = unit; + return this; + } + + /** + * Sets the write timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setWriteTimeout(long timeout, TimeUnit unit) { + this.writeTimeout = timeout; + this.writeTimeoutUnit = unit; + return this; + } + + /** + * Sets the proxy for the client + * + * @param proxy + */ + public Builder setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + /** + * Sets the connection pool for the client + * + * @param maxIdle the maximum number of idle http connections in the pool + * @param timeout the number of time units until an idle http connection in the pool is closed + * @param unit the unit of time + */ + public Builder setConnectionPool(int maxIdle, long timeout, TimeUnit unit) { + this.pool = new ConnectionPool(maxIdle, unit.toMillis(timeout)); + return this; + } + + /** + * Builds a client with all of the provided parameters. + */ + public Client build() throws ConfigurationException { + return new Client(this); + } + } +} diff --git a/bytom-sdk/src/main/java/io/bytom/http/SuccessMessage.java b/bytom-sdk/src/main/java/io/bytom/http/SuccessMessage.java new file mode 100644 index 0000000..c069a82 --- /dev/null +++ b/bytom-sdk/src/main/java/io/bytom/http/SuccessMessage.java @@ -0,0 +1,8 @@ +package io.bytom.http; + +/** + * This class represents RPC success responses whose content is not meaningful. + */ +public class SuccessMessage { + public String message; +} diff --git a/bytom-sdk/src/main/resources/config.properties b/bytom-sdk/src/main/resources/config.properties new file mode 100644 index 0000000..e9252be --- /dev/null +++ b/bytom-sdk/src/main/resources/config.properties @@ -0,0 +1,8 @@ +bytom.api.url= +client.access.token= + +# bytom.api.url=http://10.100.7.47:9888/ +# client.access.token=wt:3d17dbb953cedd53353bf3f342bb2929e9505105ffeb21670e6bd00abeef3772 + +#bytom.api.url=http://127.0.0.1:9888/ +#client.access.token=sheng:49d1623f5991c62a5094e761477ddd2838dceb49c22fbf84b492a54f1df88123 diff --git a/bytom-sdk/src/main/resources/log4j.properties b/bytom-sdk/src/main/resources/log4j.properties new file mode 100644 index 0000000..b64ec56 --- /dev/null +++ b/bytom-sdk/src/main/resources/log4j.properties @@ -0,0 +1,20 @@ + log4j.rootLogger=debug, stdout, R + log4j.appender.stdout=org.apache.log4j.ConsoleAppender + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + + log4j.logger.org.apache.commons.httpclient=info + log4j.logger.httpclient.wire.content=info + log4j.logger.httpclient.wire.header=info + + # Pattern to output the caller's file name and line number. + log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n + + log4j.appender.R=org.apache.log4j.RollingFileAppender + log4j.appender.R.File=bytom.log + log4j.appender.R.MaxFileSize= 1000KB + + # Keep one backup file + log4j.appender.R.MaxBackupIndex=1 + + log4j.appender.R.layout=org.apache.log4j.PatternLayout + log4j.appender.R.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n \ No newline at end of file diff --git a/bytom-sdk/src/test/java/io/bytom/AppTest.java b/bytom-sdk/src/test/java/io/bytom/AppTest.java new file mode 100644 index 0000000..d25fadf --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/AppTest.java @@ -0,0 +1,109 @@ +package io.bytom; + +import com.google.gson.Gson; +import com.squareup.okhttp.*; +import io.bytom.api.Block; +import org.junit.Test; + +import java.io.IOException; + +/** + * Unit test for simple App. + */ +public class AppTest { + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient client = new OkHttpClient(); + + + @Test + public void testListAccounts() throws IOException { + + String postBody = "{}"; + + Request request = new Request.Builder() + .url("http://127.0.0.1:9888/list-accounts") + .post(RequestBody.create(JSON, postBody)) + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } + + + @Test + public void testCreateAccount() throws IOException { + + String postBody = "{\n" + + " \"root_xpubs\": [\n" + + " \"012454d25928d52d42e3ee1f2bebe0916974d958f9ec08c9a028043ffe3dd95630c1b788c947b8c07ede2a4b5e3e3bbe0e305bab4526a7bc67b21e1d051e74ef\"\n" + + " ], \n" + + " \"quorum\": 1, \n" + + " \"alias\": \"sheng\"\n" + + "}"; + + Request request = new Request.Builder() + .url("http://127.0.0.1:9888/create-account") + .post(RequestBody.create(JSON, postBody)) + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } + + @Test + public void testBlockGet() throws Exception { + String postBody = "{\n" + + " \"hash\": \"37eabf4d321f43b930f52b3af2e53b2ad2dbb234949e778213a4a54e4be04ea8\",\n" + + " \"size\": 388,\n" + + " \"version\": 1,\n" + + " \"height\": 158,\n" + + " \"previous_block_hash\": \"2cfa73d6ed14a7fcb3a8ccc2d4c45b0fd4c7754ec6fafaa0964045c23e2131cd\",\n" + + " \"timestamp\": 1527241075,\n" + + " \"nonce\": 0,\n" + + " \"bits\": 2305843009214532812,\n" + + " \"difficulty\": \"5789598940468732338727519302629705571043137272394850465663635070376277442560\",\n" + + " \"transaction_merkle_root\": \"62755c4770374e696c4fecd94e022ccf6f2adc00f409ac694a5cb92fe02353eb\",\n" + + " \"transaction_status_hash\": \"c9c377e5192668bc0a367e4a4764f11e7c725ecced1d7b6a492974fab1b6d5bc\",\n" + + " \"transactions\": [\n" + + " {\n" + + " \"id\": \"5c38b8107d53cbfebd19adbd11f2839d914099a74e491f1cf75f8b18320c84e2\",\n" + + " \"version\": 1,\n" + + " \"size\": 77,\n" + + " \"time_range\": 0,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"type\": \"coinbase\",\n" + + " \"asset_id\": \"0000000000000000000000000000000000000000000000000000000000000000\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 0,\n" + + " \"arbitrary\": \"c29e\"\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"9acda4f2bd83c7114a8837b596860df773a7e61b1d6b0a774addda1473361ad4\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 41250000000,\n" + + " \"control_program\": \"001412a47b53b3bbdaf9f3510e1d8c19d69f81ca9142\",\n" + + " \"address\": \"sm1qz2j8k5anh0d0nu63pcwccxwkn7qu4y2zjwaj5h\"\n" + + " }\n" + + " ],\n" + + " \"status_fail\": false\n" + + " }\n" + + " ]\n" + + " }"; + Gson gson = new Gson(); + Block block = gson.fromJson(postBody, Block.class); + } +} + diff --git a/bytom-sdk/src/test/java/io/bytom/TestUtils.java b/bytom-sdk/src/test/java/io/bytom/TestUtils.java new file mode 100644 index 0000000..7d40cfc --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/TestUtils.java @@ -0,0 +1,31 @@ +package io.bytom; + +import io.bytom.common.Configuration; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +/** + * TestUtils provides a simplified api for testing. + */ +public class TestUtils { + + public static Logger logger = Logger.getLogger(TestUtils.class); + + public static Client generateClient() throws BytomException { + + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888"; + } + + if (coreURL.endsWith("/")) { + //split the last char "/" + coreURL = coreURL.substring(0, coreURL.length()-1); + } + + return new Client(coreURL, accessToken); + } +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/AccessTokenTest.java b/bytom-sdk/src/test/java/io/bytom/integration/AccessTokenTest.java new file mode 100644 index 0000000..49930b9 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/AccessTokenTest.java @@ -0,0 +1,39 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.AccessToken; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.List; + +public class AccessTokenTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + @Test + public void testTokenCreate() throws Exception { + AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client); + } + + + @Test + public void testTokenList() throws Exception { + List tokenList = AccessToken.list(client); + } + + @Test + public void testTokenCheck() throws Exception { + String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799"; + AccessToken.check(client, "sheng", secret); + } +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/AccountTest.java b/bytom-sdk/src/test/java/io/bytom/integration/AccountTest.java new file mode 100644 index 0000000..d8314f2 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/AccountTest.java @@ -0,0 +1,97 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Account; +import io.bytom.api.Receiver; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AccountTest { + + static Client client; + static Account account; + + @Test + public void testAccountCreate() throws Exception { + client = TestUtils.generateClient(); + + String alias = "AccountTest.testAccountCreate.002"; + Integer quorum = 1; + List root_xpubs = new ArrayList<>(); + root_xpubs.add("c4b25825e92cd8623de4fd6a35952ad0efb2ed215fdb1b40754f0ed12eff7827d147d1e8b003601ba2f78a4a84dcc77e93ed282633f2679048c5d5ac5ea10cb5"); + + +// Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); +// key = Key.create(client, builder); + + Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + account = Account.create(client, builder); + + assertNotNull(account.id); + assertEquals(alias.toLowerCase(), account.alias); + } + + @Test + public void testAccountList() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + } + + @Test + public void testAccountDelete() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + //delete the last Account Object + Account.delete(client, alias); + } + + @Test + public void testReceiverCreate() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + String id = accountList.get(accountList.size()-1).id; + + Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); + Receiver receiver = receiverBuilder.create(client); + + assertNotNull(receiver.address); + assertNotNull(receiver.controlProgram); + } + + @Test + public void testAddressList() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + String id = accountList.get(accountList.size()-1).id; + + Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias); + List addressList = addressBuilder.list(client); + + assertNotNull(addressList); + } + + @Test + public void testAddressValidate() throws Exception { + client = TestUtils.generateClient(); + + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + String id = accountList.get(accountList.size()-1).id; + + Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias); + List addressList = addressBuilder.list(client); + + Account.Address address = addressBuilder.validate(client, addressList.get(0).address); + assertEquals(true, address.is_local); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/AssetTest.java b/bytom-sdk/src/test/java/io/bytom/integration/AssetTest.java new file mode 100644 index 0000000..2971275 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/AssetTest.java @@ -0,0 +1,71 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Account; +import io.bytom.api.Asset; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AssetTest { + + static Client client; + static Account account; + static Asset asset; + + @Test + public void testAssetCreate() throws Exception { + client = TestUtils.generateClient(); + + List accountList = Account.list(client); + String alias = "GOLD"; + + List xpubs = accountList.get(0).xpubs; + + Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); + asset = builder.create(client); + assertNotNull(asset); + } + + @Test + public void testAssetGet() throws Exception { + client = TestUtils.generateClient(); + Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); + String id = queryBuilder.list(client).get(1).id; + queryBuilder.setId(id); + Asset asset = queryBuilder.get(client); + } + + @Test + public void testAssetList() throws Exception { + client = TestUtils.generateClient(); + Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); + List assetList = queryBuilder.list(client); + assertEquals(2, assetList.size()); + } + + @Test + public void testUpdateAssetAlias() throws Exception { + client = TestUtils.generateClient(); + + Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); + String id = queryBuilder.list(client).get(1).id; + + String alias = "HELLOWORLD"; + + + Asset.AliasUpdateBuilder aliasUpdateBuilder = + new Asset.AliasUpdateBuilder() + .setAlias(alias) + .setAssetId(id); + aliasUpdateBuilder.update(client); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/BalanceTest.java b/bytom-sdk/src/test/java/io/bytom/integration/BalanceTest.java new file mode 100644 index 0000000..8015f4c --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/BalanceTest.java @@ -0,0 +1,46 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Balance; +import io.bytom.api.Wallet; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class BalanceTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static Balance balance; + + @Test + public void testBalanceList() throws Exception { + List balanceList = new Balance.QueryBuilder().list(client); + Assert.assertNotNull(balanceList); + } + + @Test + public void testBalanceByAssetAlias() throws Exception { + Balance balance = new Balance.QueryBuilder().listByAssetAlias(client, "BTM"); + Assert.assertNotNull(balance); + } + + @Test + public void testBalanceByAccountAlias() throws Exception { + List balanceList = new Balance.QueryBuilder().listByAccountAlias(client, "test"); + Assert.assertNotNull(balanceList); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/BlockTest.java b/bytom-sdk/src/test/java/io/bytom/integration/BlockTest.java new file mode 100644 index 0000000..71b8e3f --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/BlockTest.java @@ -0,0 +1,85 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Asset; +import io.bytom.api.Block; +import io.bytom.api.Wallet; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Assert; +import org.junit.Test; + +public class BlockTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static Block block; + static Block.BlockHeader blockHeader; + static Block.BlockDifficulty blockDifficulty; + static Block.BlockHashRate blockHashRate; + + @Test + public void testBlockCountGet() throws Exception { + int count = Block.getBlockCount(client); + Assert.assertEquals(158, count); + } + + @Test + public void testBlockHashGet() throws Exception { + String blockHash = Block.getBlockHash(client); + Assert.assertNotNull(blockHash); + } + + @Test + public void testBlockGet() throws Exception { + int height = Block.getBlockCount(client); + String blockHash = Block.getBlockHash(client); + + block = new Block.QueryBuilder() + .setBlockHeight(height) + .setBlockHash(blockHash) + .getBlock(client); + } + + @Test + public void testBlockHeader() throws Exception { + int height = Block.getBlockCount(client); + String blockHash = Block.getBlockHash(client); + + blockHeader = new Block.QueryBuilder() + .setBlockHeight(height) + .setBlockHash(blockHash) + .getBlockHeader(client); + } + + @Test + public void testBlockDifficulty() throws Exception { + int height = Block.getBlockCount(client); + String blockHash = Block.getBlockHash(client); + + blockDifficulty = new Block.QueryBuilder() + .setBlockHeight(height) + .setBlockHash(blockHash) + .getBlockDifficulty(client); + } + + @Test + public void testBlockHashRate() throws Exception { + int height = Block.getBlockCount(client); + String blockHash = Block.getBlockHash(client); + + blockHashRate = new Block.QueryBuilder() + .setBlockHeight(height) + .setBlockHash(blockHash) + .getHashRate(client); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/CoreConfigTest.java b/bytom-sdk/src/test/java/io/bytom/integration/CoreConfigTest.java new file mode 100644 index 0000000..d656da8 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/CoreConfigTest.java @@ -0,0 +1,40 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.CoreConfig; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Assert; +import org.junit.Test; + +public class CoreConfigTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static CoreConfig.NetInfo netInfo; + + static Integer gasRate; + + @Test + public void testNetInfo() throws Exception { + netInfo = CoreConfig.getNetInfo(client); + Assert.assertNotNull(netInfo); + } + + @Test + public void testGasRate() throws Exception { + gasRate = CoreConfig.getGasRate(client); + Assert.assertNotNull(gasRate); + } + + + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/KeyTest.java b/bytom-sdk/src/test/java/io/bytom/integration/KeyTest.java new file mode 100644 index 0000000..ec68a92 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/KeyTest.java @@ -0,0 +1,59 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Key; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +public class KeyTest { + + static Client client; + static Key key; + + @Test + public void testClientKeyCreate() throws Exception { + client = TestUtils.generateClient(); + + String alias = "KeyTest.testKeyCreate.successli004"; + String password = "123456"; + + Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); + key = Key.create(client, builder); + + assertNotNull(key.xpub); + assertEquals(alias.toLowerCase(), key.alias); + } + + @Test + public void testClientKeyList() throws Exception { + //client = TestUtils.generateClient(); + client = new Client("http://127.0.0.1:9888/"); + List keyList = Key.list(client); + } + + @Test + public void testClientKeyDelete() throws Exception { + client = TestUtils.generateClient(); + List keyList = Key.list(client); + String xpub = keyList.get(keyList.size()-1).xpub; + //delete the last Key Object + Key.delete(client, xpub, "123456"); + } + + @Test + public void testClientKeyResetPassword() throws BytomException { + client = TestUtils.generateClient(); + List keyList = Key.list(client); + String xpub = keyList.get(keyList.size()-1).xpub; + Key.resetPassword(client, xpub, "123456", "123456789"); + Key.delete(client, xpub, "123456789"); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/MessageTest.java b/bytom-sdk/src/test/java/io/bytom/integration/MessageTest.java new file mode 100644 index 0000000..bd7fc1f --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/MessageTest.java @@ -0,0 +1,50 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Message; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Assert; +import org.junit.Test; + +public class MessageTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static Message message; + + @Test + public void testMessageSign() throws Exception { + + message = new Message.SignBuilder() + .setAddress("sm1qz2j8k5anh0d0nu63pcwccxwkn7qu4y2zjwaj5h") + .setMessage("test") + .setPassword("123456") + .sign(client); + + Assert.assertNotNull(message); + } + + @Test + public void testMessageVerify() throws Exception { + String derived_xpub = "6e1efce70e2b29efa348aec7c148edc2beb72edc0d4422a03cfb0f40e6e4cfc6e6050b5863bbe84c44131280dff68614e0308a4d081e8b677d0f7f09fb3390c4"; + String signature = "0d840d5b6a6df028013260e94e871c1443686c446a65db4ee93005c5395c3607feb0ac5bf583a3139c8a3d0afe757448ff49fa17ffd2377831ce5f925c846b0b"; + + Boolean verify = new Message.VerifyBuilder() + .setDerivedXpub(derived_xpub) + .setSignature(signature) + .setAddress("sm1qz2j8k5anh0d0nu63pcwccxwkn7qu4y2zjwaj5h") + .setMessage("test") + .verifyMessage(client); + } + + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/RawTransactionTest.java b/bytom-sdk/src/test/java/io/bytom/integration/RawTransactionTest.java new file mode 100644 index 0000000..a4b3960 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/RawTransactionTest.java @@ -0,0 +1,34 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.RawTransaction; +import io.bytom.api.Wallet; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Assert; +import org.junit.Test; + +public class RawTransactionTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static RawTransaction rawTransaction; + + + @Test + public void testRawTxDecode() throws Exception { + String rawTxId = "070100010161015f30e052cd50e385951936c08fb5642bd12b727da958960249ddad8c9a77e5371fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d59901000116001412a47b53b3bbdaf9f3510e1d8c19d69f81ca91426302405a8b5adebd2ab63ba1ac55a7bbadc1fe246806c37732df0442b827fa4e06058137711d9d012becdc9de507a8ad0de0dd50780c0503c0dcff2dc03d7592e31a08206e1efce70e2b29efa348aec7c148edc2beb72edc0d4422a03cfb0f40e6e4cfc602013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d3bdc6980101160014a9c0ea4abe4d09546197bac3c86f4dd39fde1afb00013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8084af5f011600144cc6edb1f4077c740e0201bb3688e6efba4a098c00"; + rawTransaction = RawTransaction.decode(client, rawTxId); + Assert.assertNotNull(rawTransaction); + } + + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/TransactionTest.java b/bytom-sdk/src/test/java/io/bytom/integration/TransactionTest.java new file mode 100644 index 0000000..180b12d --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/TransactionTest.java @@ -0,0 +1,334 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.*; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TransactionTest { + + static Client client; + static Client otherCoreClient; + + static Key senderKey; + static Key receiverKey; + static Account senderAccount; + static Account receiverAccount; + static Account.Address receiverAddress; + static Asset senderAsset; + static Asset receiverAsset; + + static Transaction.Feed transactionFeed; + + static { + try { + client = TestUtils.generateClient(); + otherCoreClient = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + + private static Logger logger = Logger.getLogger(TransactionTest.class); + + + @Test + public void testGetAll() throws Exception { + + senderKey = Key.list(client).get(0); + receiverKey = Key.list(client).get(1); + logger.info("senderKey:" + senderKey.toJson()); + logger.info("receiverKey:" + receiverKey.toJson()); + + senderAccount = Account.list(client).get(0); + receiverAccount = Account.list(client).get(1); + logger.info("senderAccount:" + senderAccount.toJson()); + logger.info("receiverAccount:" + receiverAccount.toJson()); + + receiverAddress = new Account.AddressBuilder() + .setAccountAlias(receiverAccount.alias) + .setAccountId(receiverAccount.id) + .list(client).get(0); + logger.info("receiver-address:" + receiverAddress.toJson()); + + senderAsset = new Asset.QueryBuilder().list(client).get(0); + receiverAsset = new Asset.QueryBuilder().list(client).get(1); + logger.info("senderAsset:" + senderAsset.toJson()); + logger.info("receiverAsset:" + receiverAsset.toJson()); + } + + @Test + public void testTransactionAll() throws Exception { + testGetAll(); + + logger.info("before transaction:"); + + List balanceList = new Balance.QueryBuilder().list(client); + + logger.info("transaction:"); + + Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); + + Transaction.Template singer = new Transaction.SignerBuilder().sign(client, + controlAddress, "123456"); + + logger.info("rawTransaction:" + singer.rawTransaction); + + logger.info("singer:" + singer.toJson()); + + Transaction.SubmitResponse txs = Transaction.submit(client, singer); + + logger.info("txs:" + txs.toJson()); + + logger.info("after transaction."); + + balanceList = new Balance.QueryBuilder().list(client); + + } + + //Asset issuance + //Issue 1000 units of gold to Alice. + @Test + public void testAssetIssue() throws BytomException { + Transaction.Template issuance = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); + + Transaction.Template signedIssuance = new Transaction.SignerBuilder().sign(client, + issuance, "123456"); + + Transaction.submit(client, signedIssuance); + } + + //Between two Chain Cores + @Test + public void testAssetIssueBetween() throws BytomException { + Receiver bobIssuanceReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); + String bobIssuanceReceiverSerialized = bobIssuanceReceiver.toJson(); + + + Transaction.Template issuanceToReceiver = new Transaction.Builder() + .addAction(new Transaction.Action.Issue() + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobIssuanceReceiverSerialized)) + .setAssetAlias("gold") + .setAmount(10) + ).build(client); + + Transaction.Template signedIssuanceToReceiver = new Transaction.SignerBuilder().sign(client, + issuanceToReceiver, "123456"); + + Transaction.submit(client, signedIssuanceToReceiver); + } + + //Simple payment + //Alice pays 10 units of gold to Bob. + @Test + public void testSimplePayment() throws BytomException { + Transaction.Template payment = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetAlias("gold") + .setAmount(10) + ).build(client); + + Transaction.Template signedPayment = new Transaction.SignerBuilder().sign(client, + payment, "123456"); + + Transaction.submit(client, signedPayment); + } + + //Between two Chain Cores + @Test + public void testSimplePaymentBetween() throws BytomException { + Receiver bobPaymentReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); + String bobPaymentReceiverSerialized = bobPaymentReceiver.toJson(); + + + Transaction.Template paymentToReceiver = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobPaymentReceiverSerialized)) + .setAssetAlias("gold") + .setAmount(10) + ).build(client); + + Transaction.Template signedPaymentToReceiver = new Transaction.SignerBuilder().sign(client, + paymentToReceiver, "123456"); + + Transaction.submit(client, signedPaymentToReceiver); + } + + //Multi-asset payment + //Alice pays 10 units of gold and 20 units of silver to Bob. + @Test + public void testMultiAssetPayment() throws BytomException { + Transaction.Template multiAssetPayment = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("silver") + .setAmount(20) + ).addAction(new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetAlias("silver") + .setAmount(20) + ).build(client); + + Transaction.Template signedMultiAssetPayment = new Transaction.SignerBuilder().sign(client, + multiAssetPayment, "123456"); + + Transaction.submit(client, signedMultiAssetPayment); + } + + //Between two Chain Cores + @Test + public void testMultiAssetPaymentBetween() throws BytomException { + Receiver bobGoldReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); + String bobGoldReceiverSerialized = bobGoldReceiver.toJson(); + + Receiver bobSilverReceiver = new Account.ReceiverBuilder() + .setAccountAlias("bob") + .create(otherCoreClient); + String bobSilverReceiverSerialized = bobSilverReceiver.toJson(); + + + Transaction.Template multiAssetToReceiver = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("silver") + .setAmount(20) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobGoldReceiverSerialized)) + .setAssetAlias("gold") + .setAmount(10) + ).addAction(new Transaction.Action.ControlWithReceiver() + .setReceiver(Receiver.fromJson(bobSilverReceiverSerialized)) + .setAssetAlias("silver") + .setAmount(20) + ).build(client); + + Transaction.Template signedMultiAssetToReceiver = new Transaction.SignerBuilder().sign(client, + multiAssetToReceiver, "123456");; + + Transaction.submit(client, signedMultiAssetToReceiver); + } + + //Asset retirement + //Alice retires 50 units of gold from her account. + @Test + public void testRetirement() throws BytomException { + Transaction.Template retirement = new Transaction.Builder() + .addAction(new Transaction.Action.SpendFromAccount() + .setAccountAlias("alice") + .setAssetAlias("gold") + .setAmount(50) + ).addAction(new Transaction.Action.Retire() + .setAssetAlias("gold") + .setAmount(50) + ).build(client); + + Transaction.Template signedRetirement = new Transaction.SignerBuilder().sign(client, + retirement, "123456"); + + Transaction.submit(client, signedRetirement); + } + + //TransactionFeed + @Test + public void testTXFeedCreate() throws Exception { + String filter = "asset_id='57fab05b689a2b8b6738cffb5cf6cffcd0bf6156a04b7d9ba0173e384fe38c8c' AND amount_lower_limit = 50 AND amount_upper_limit = 100"; + String alias = "test1"; + new Transaction.Feed.Builder() + .setAlias(alias) + .setFilter(filter) + .create(client); + } + + @Test + public void testTXFeedGet() throws Exception { + String alias = "test2"; + transactionFeed = Transaction.Feed.getByAlias(client, alias); + + Assert.assertNotNull(transactionFeed); + } + + @Test + public void testTXFeedUpdate() throws Exception { + String filter = "asset_id='57fab05b689a2b8b6738cffb5cf6cffcd0bf6156a04b7d9ba0173e384fe38c8c' AND amount_lower_limit = 50 AND amount_upper_limit = 100"; + String alias = "test2"; + + Transaction.Feed.update(client, alias, filter); + } + + @Test + public void testTXFeedList() throws Exception { + List txFeedList = Transaction.Feed.list(client); + Assert.assertNotNull(txFeedList); + } + + @Test + public void testTXFeedDelete() throws Exception { + String alias = "test2"; + Transaction.Feed.deleteByAlias(client, alias); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/UTXOTest.java b/bytom-sdk/src/test/java/io/bytom/integration/UTXOTest.java new file mode 100644 index 0000000..aca21dc --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/UTXOTest.java @@ -0,0 +1,31 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.UnspentOutput; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class UTXOTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + + @Test + public void testBalanceList() throws Exception { + List UTXOList = new UnspentOutput.QueryBuilder().list(client); + Assert.assertNotNull(UTXOList); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java b/bytom-sdk/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java new file mode 100644 index 0000000..f63ae7f --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java @@ -0,0 +1,39 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.UnconfirmedTransaction; +import io.bytom.api.Wallet; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Assert; +import org.junit.Test; + +public class UnconfirmedTransactionTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static UnconfirmedTransaction unconfirmedTransaction; + static UnconfirmedTransaction.UTXResponse utxResponse; + + @Test + public void testUCTXList() throws Exception { + utxResponse = UnconfirmedTransaction.list(client); + Assert.assertNotNull(utxResponse); + } + + @Test + public void testUCTXGet() throws Exception { + utxResponse = UnconfirmedTransaction.list(client); + unconfirmedTransaction = UnconfirmedTransaction.get(client, utxResponse.txIds.get(0)); + Assert.assertNotNull(unconfirmedTransaction); + } + +} diff --git a/bytom-sdk/src/test/java/io/bytom/integration/WalletTest.java b/bytom-sdk/src/test/java/io/bytom/integration/WalletTest.java new file mode 100644 index 0000000..194fbb6 --- /dev/null +++ b/bytom-sdk/src/test/java/io/bytom/integration/WalletTest.java @@ -0,0 +1,43 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Key; +import io.bytom.api.Wallet; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class WalletTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + static Wallet wallet; + + @Test + public void testWalletBackUp() throws Exception { + + wallet = Wallet.backupWallet(client); + } + + @Test + public void testWallerRestore() throws Exception { + // get wallet object + wallet = Wallet.backupWallet(client); + //restore + Wallet.restoreWallet(client, wallet.accountImage, wallet.assetImage, wallet.keyImages); + } + + + +} diff --git a/pom.xml b/pom.xml index 7425c0c..26fa96a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,19 @@ - - 4.0.0 io.bytom - bytom-sdk + bytom-sdk-all 1.0.0 + pom + + + bytom-sdk + tx-signer + + @@ -19,12 +26,6 @@ - jar - - bytom-sdk-java - - http://www.example.com - SDK for Bytom project in github. @@ -58,108 +59,6 @@ UTF-8 - - - junit - junit - 4.12 - test - - - log4j - log4j - 1.2.17 - - - com.squareup.okhttp - okhttp - 2.5.0 - - - com.squareup.okhttp - mockwebserver - 2.5.0 - test - - - com.google.code.gson - gson - 2.8.0 - - - org.bouncycastle - bcpkix-jdk15on - 1.56 - - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - maven-assembly-plugin - 2.3 - - - jar-with-dependencies - - - - - package - - single - - - - - - - - - oss @@ -170,4 +69,4 @@ https://oss.sonatype.org/service/local/staging/deploy/maven2/ - + \ No newline at end of file diff --git a/tx-signer b/tx-signer new file mode 160000 index 0000000..7d47124 --- /dev/null +++ b/tx-signer @@ -0,0 +1 @@ +Subproject commit 7d47124fe9633b8ea37f494e6ec731ac1423378d From 358ea819d976f7a8f3012b11588a608dac6c6284 Mon Sep 17 00:00:00 2001 From: liqiang Date: Thu, 25 Oct 2018 16:36:02 +0800 Subject: [PATCH 02/13] delete old files --- README.md | 160 --- doc/index.md | 849 ------------ doc/transactions.md | 245 ---- src/main/java/io/bytom/api/AccessToken.java | 164 --- src/main/java/io/bytom/api/Account.java | 336 ----- src/main/java/io/bytom/api/Asset.java | 369 ----- src/main/java/io/bytom/api/Balance.java | 130 -- src/main/java/io/bytom/api/Block.java | 324 ----- src/main/java/io/bytom/api/CoreConfig.java | 96 -- src/main/java/io/bytom/api/Key.java | 148 --- src/main/java/io/bytom/api/Message.java | 122 -- .../java/io/bytom/api/RawTransaction.java | 154 --- src/main/java/io/bytom/api/Receiver.java | 49 - src/main/java/io/bytom/api/Transaction.java | 1182 ----------------- .../io/bytom/api/UnconfirmedTransaction.java | 197 --- src/main/java/io/bytom/api/UnspentOutput.java | 132 -- src/main/java/io/bytom/api/Wallet.java | 153 --- .../java/io/bytom/common/Configuration.java | 29 - .../bytom/common/ParameterizedTypeImpl.java | 30 - src/main/java/io/bytom/common/Utils.java | 9 - .../java/io/bytom/exception/APIException.java | 169 --- .../io/bytom/exception/BadURLException.java | 14 - .../io/bytom/exception/BuildException.java | 49 - .../io/bytom/exception/BytomException.java | 32 - .../exception/ConfigurationException.java | 23 - .../exception/ConnectivityException.java | 36 - .../io/bytom/exception/HTTPException.java | 23 - .../io/bytom/exception/JSONException.java | 39 - .../java/io/bytom/http/BatchResponse.java | 140 -- src/main/java/io/bytom/http/Client.java | 815 ------------ .../java/io/bytom/http/SuccessMessage.java | 8 - src/main/resources/config.properties | 8 - src/main/resources/log4j.properties | 20 - src/test/java/io/bytom/AppTest.java | 109 -- src/test/java/io/bytom/TestUtils.java | 31 - .../io/bytom/integration/AccessTokenTest.java | 39 - .../io/bytom/integration/AccountTest.java | 97 -- .../java/io/bytom/integration/AssetTest.java | 71 - .../io/bytom/integration/BalanceTest.java | 46 - .../java/io/bytom/integration/BlockTest.java | 85 -- .../io/bytom/integration/CoreConfigTest.java | 40 - .../java/io/bytom/integration/KeyTest.java | 59 - .../io/bytom/integration/MessageTest.java | 50 - .../bytom/integration/RawTransactionTest.java | 34 - .../io/bytom/integration/TransactionTest.java | 334 ----- .../java/io/bytom/integration/UTXOTest.java | 31 - .../UnconfirmedTransactionTest.java | 39 - .../java/io/bytom/integration/WalletTest.java | 43 - 48 files changed, 7362 deletions(-) delete mode 100644 README.md delete mode 100644 doc/index.md delete mode 100644 doc/transactions.md delete mode 100644 src/main/java/io/bytom/api/AccessToken.java delete mode 100644 src/main/java/io/bytom/api/Account.java delete mode 100644 src/main/java/io/bytom/api/Asset.java delete mode 100644 src/main/java/io/bytom/api/Balance.java delete mode 100644 src/main/java/io/bytom/api/Block.java delete mode 100644 src/main/java/io/bytom/api/CoreConfig.java delete mode 100644 src/main/java/io/bytom/api/Key.java delete mode 100644 src/main/java/io/bytom/api/Message.java delete mode 100644 src/main/java/io/bytom/api/RawTransaction.java delete mode 100644 src/main/java/io/bytom/api/Receiver.java delete mode 100644 src/main/java/io/bytom/api/Transaction.java delete mode 100644 src/main/java/io/bytom/api/UnconfirmedTransaction.java delete mode 100644 src/main/java/io/bytom/api/UnspentOutput.java delete mode 100644 src/main/java/io/bytom/api/Wallet.java delete mode 100644 src/main/java/io/bytom/common/Configuration.java delete mode 100644 src/main/java/io/bytom/common/ParameterizedTypeImpl.java delete mode 100644 src/main/java/io/bytom/common/Utils.java delete mode 100644 src/main/java/io/bytom/exception/APIException.java delete mode 100644 src/main/java/io/bytom/exception/BadURLException.java delete mode 100644 src/main/java/io/bytom/exception/BuildException.java delete mode 100644 src/main/java/io/bytom/exception/BytomException.java delete mode 100644 src/main/java/io/bytom/exception/ConfigurationException.java delete mode 100644 src/main/java/io/bytom/exception/ConnectivityException.java delete mode 100644 src/main/java/io/bytom/exception/HTTPException.java delete mode 100644 src/main/java/io/bytom/exception/JSONException.java delete mode 100644 src/main/java/io/bytom/http/BatchResponse.java delete mode 100644 src/main/java/io/bytom/http/Client.java delete mode 100644 src/main/java/io/bytom/http/SuccessMessage.java delete mode 100644 src/main/resources/config.properties delete mode 100644 src/main/resources/log4j.properties delete mode 100644 src/test/java/io/bytom/AppTest.java delete mode 100644 src/test/java/io/bytom/TestUtils.java delete mode 100644 src/test/java/io/bytom/integration/AccessTokenTest.java delete mode 100644 src/test/java/io/bytom/integration/AccountTest.java delete mode 100644 src/test/java/io/bytom/integration/AssetTest.java delete mode 100644 src/test/java/io/bytom/integration/BalanceTest.java delete mode 100644 src/test/java/io/bytom/integration/BlockTest.java delete mode 100644 src/test/java/io/bytom/integration/CoreConfigTest.java delete mode 100644 src/test/java/io/bytom/integration/KeyTest.java delete mode 100644 src/test/java/io/bytom/integration/MessageTest.java delete mode 100644 src/test/java/io/bytom/integration/RawTransactionTest.java delete mode 100644 src/test/java/io/bytom/integration/TransactionTest.java delete mode 100644 src/test/java/io/bytom/integration/UTXOTest.java delete mode 100644 src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java delete mode 100644 src/test/java/io/bytom/integration/WalletTest.java diff --git a/README.md b/README.md deleted file mode 100644 index 903792c..0000000 --- a/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# Bytom java-sdk - -This page will document the API classes and ways to properly use the API. -Subsequent new releases also maintain backward compatibility with this class -approach. For more information, please see Bytom API reference documentation -at [Bytom wiki](https://github.com/Bytom/bytom/wiki/API-Reference) - -## Installation - -There are various ways to install and use this sdk. We'll provide three ways to get it. Note that the bytom-sdk requires JAVA 7 or newer. - -### Apache Maven - -```xml - - io.bytom - bytom-sdk - 1.0.0 - -``` - -### Gradle/Grails -```xml -compile 'io.bytom:bytom-sdk:1.0.0' -``` - -### Building from source code - -To clone, compile, and install in your local maven repository (or copy the artifacts from the target/ directory to wherever you need them): - -```shell -git clone https://github.com/Bytom/bytom-java-sdk.git -cd java-sdk -mvn package -Dmaven.test.skip=true -mvn install -``` - -## Basic Usage - -```java -public static Client generateClient() throws BytomException { - String coreURL = Configuration.getValue("bytom.api.url"); - String accessToken = Configuration.getValue("client.access.token"); - if (coreURL == null || coreURL.isEmpty()) { - coreURL = "http://127.0.0.1:9888/"; - } - return new Client(coreURL, accessToken); -} - -Client client = Client.generateClient(); -``` -> Note: you can touch a file named ```config.properties``` in resources folder to config ```bytom.api.url``` and ```client.access.token``` by custom. - -## Usage - -* [`Step 1: Create a key`](#create-a-key) -* [`Step 2: Create an account`](#create-an-account) -* [`Step 3: Create an receiver`](#create-an-receiver) -* [`Step 4: Create an asset`](#create-an-asset) -* [`Step 5: Issue asset`](#issue-asset) - * [`Firstly build the transaction`](#firstly-build-the-transaction) - * [`Secondly sign the transaction`](#secondly-sign-the-transaction) - * [`Finally submit the transaction`](#finally-submit-the-transaction) - -> For more details, see [API methods](https://github.com/Bytom/java-sdk/blob/master/doc/index.md#api-methods) - -### Create a key - -```java -String alias = "test"; -String password = "123456"; - -Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); -Key key = Key.create(client, builder); -``` - -### Create an account - -```java -String alias = "sender-account"; -Integer quorum = 1; -List root_xpubs = new ArrayList(); -root_xpubs.add(senderKey.xpub); - -Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); - -Account account = Account.create(client, builder); -``` - -### Create an receiver - -```java -String alias = receiverAccount.alias; -String id = receiverAccount.id; - -Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); -Receiver receiver = receiverBuilder.create(client); -``` - -### Create an asset - -```java - String alias = "receiver-asset"; - -List xpubs = receiverAccount.xpubs; - -Asset.Builder builder = new Asset.Builder() - .setAlias(alias) - .setQuorum(1) - .setRootXpubs(xpubs); -receiverAsset = builder.create(client); -``` - -### Issue asset - -For more transaction details, see [transactions](https://github.com/Bytom/java-sdk/blob/master/doc/transactions.md) - -#### Firstly build the transaction - -```java -Transaction.Template controlAddress = new Transaction.Builder() - .addAction( - new Transaction.Action.SpendFromAccount() - .setAccountId(senderAccount.id) - .setAssetId(senderAsset.id) - .setAmount(300000000) - ) - .addAction( - new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetId(senderAsset.id) - .setAmount(200000000) - ).build(client); -``` - -#### Secondly sign the transaction - -```java -Transaction.Template singer = new Transaction.SignerBuilder().sign(client, - controlAddress, "123456"); -``` - -#### Finally submit the transaction - -```java -Transaction.SubmitResponse txs = Transaction.submit(client, singer); -``` - - -### All usage examples - -For more details you can see [doc](https://github.com/Bytom/bytom-java-sdk/blob/master/doc/index.md#api-methods). And you can find Test Cases at [Junit Test Cases](https://github.com/Bytom/bytom-java-sdk/tree/master/src/test/java/io/bytom/integration) - -## Support and Feedback - -If you find a bug, please submit the issue in Github directly by using [Issues](https://github.com/Bytom/bytom-java-sdk/issues) - -## License - -Bytom JAVA SDK is based on the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) protocol. diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index 1839c8f..0000000 --- a/doc/index.md +++ /dev/null @@ -1,849 +0,0 @@ -# Java SDK documentation - -This page will document the API classes and ways to properly use the API. -Subsequent new releases also maintain backward compatibility with this class approach. - -## Basic Usage - -``` -public static Client generateClient() throws BytomException { - String coreURL = Configuration.getValue("bytom.api.url"); - String accessToken = Configuration.getValue("client.access.token"); - if (coreURL == null || coreURL.isEmpty()) { - coreURL = "http://127.0.0.1:9888/"; - } - return new Client(coreURL, accessToken); -} - -Client client = TestUtils.generateClient(); -``` - -## Usage - -* [`Step 1: Create a key`](#create-a-key) -* [`Step 2: Create an account`](#create-an-account) -* [`Step 3: Create an receiver`](#create-an-receiver) -* [`Step 4: Create an asset`](#create-an-asset) -* [`Step 5: Issue asset`](#issue-asset) - * [`Firstly build the transaction`](#firstly-build-the-transaction) - * [`Secondly sign the transaction`](#secondly-sign-the-transaction) - * [`Finally submit the transaction`](#finally-submit-the-transaction) - -> For more details, see [`API methods`](#api-methods) - -## Create a key - -```java -String alias = "test"; -String password = "123456"; - -Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); -Key key = Key.create(client, builder); -``` - -## Create an account - -```java -String alias = "sender-account"; -Integer quorum = 1; -List root_xpubs = new ArrayList(); -root_xpubs.add(senderKey.xpub); - -Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); - -Account account = Account.create(client, builder); -``` - -## Create an receiver - -```java -String alias = receiverAccount.alias; -String id = receiverAccount.id; - -Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); -Receiver receiver = receiverBuilder.create(client); -``` - -## Create an asset - -```java - String alias = "receiver-asset"; - -List xpubs = receiverAccount.xpubs; - -Asset.Builder builder = new Asset.Builder() - .setAlias(alias) - .setQuorum(1) - .setRootXpubs(xpubs); -receiverAsset = builder.create(client); -``` - -## Issue asset - -### Firstly build the transaction - -```java -Transaction.Template controlAddress = new Transaction.Builder() - .addAction( - new Transaction.Action.SpendFromAccount() - .setAccountId(senderAccount.id) - .setAssetId(senderAsset.id) - .setAmount(300000000) - ) - .addAction( - new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetId(senderAsset.id) - .setAmount(200000000) - ).build(client); -``` - -### Secondly sign the transaction - -```java -Transaction.Template singer = new Transaction.SignerBuilder().sign(client, - controlAddress, "123456"); -``` - -### Finally submit the transaction - -```java -Transaction.SubmitResponse txs = Transaction.submit(client, singer); -``` - ----- - -## API methods - -* [`Key API`](#key-api) -* [`Account API`](#account-api) -* [`Asset API`](#asset-api) -* [`Transaction API`](#transaction-api) -* [`Wallet API`](#wallet-api) -* [`Access Token API`](#access-token-api) -* [`Block API`](#block-api) -* [`Other API`](#other-api) - -## Key API - - -#### 1.createKey - -```java -Key create(Client client, Builder builder); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. -- `Key.Builder` - *builder*, Builder object that builds request parameters. - -##### Returns - -- `Key` - *key*, Key object. - ----- - -#### 2.listKeys - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. - -##### Returns - -- `List of Key`, *List*, an ArrayList object contains Key objects. - ----- - -#### 3.deleteKey - -```java -void delete(Client client, String xpub, String password); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. -- `String` - *xpub*, pubkey of the key. -- `String` - *password*, password of the key. - -##### Returns - -none if the key is deleted successfully. - ----- - -#### 4.resetKeyPassword - -```java -void resetPwd(Client client, String xpub, String oldPwd, String newPwd); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. -- `String` - *xpub*, pubkey of the key. -- `String` - *oldPwd*, old password of the key. -- `String` - *newPwd*, new password of the key. - -##### Returns - -none if the key password is reset successfully. - - -## Account API - - -#### 1.createAccount - -```java -Account create(Client client, Builder builder); -``` -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. -- `Account.Builder` - *builder*, Builder object that builds request parameters. - -##### Returns - -- `Account` - *account*, Account object. - ----- - -#### 2.listAccounts - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. - -##### Returns - -- `List of Account`, *List*, an ArrayList object contains Account objects. - ----- - -#### 3.deleteAccount - -```java -void delete(Client client, String account_info); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. -- `String` - *account_info*, alias or ID of account. - -##### Returns - -none if the account is deleted successfully. - ----- - -#### 4.createAccountReceiver - -```java -Receiver create(Client client); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. - -##### Returns - -- `Receiver` - *receiver*, Receiver object. - - ----- - -#### 5.listAddresses - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *client*, Client object that makes requests to the core. - -##### Returns - -- `List of Address`, *List*, an ArrayList object contains Account.Address objects. - ----- - -#### 6.validateAddress - -```java -Address validate(Client client, String address); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. -- `String` - *address*, address of account. - -##### Returns - -- `Address` - *address*, an Address object. - - -## Asset API - - -#### 1.createAsset - -```java -Asset create(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Asset` - *asset*, an Asset object. - ----- - -#### 2.getAsset - -```java -Asset get(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Asset` - *asset*, an Asset object. - ----- - -#### 3.listAssets - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of Asset`, *List*, an ArrayList object contains Asset objects. - ----- - -#### 4.updateAssetAlias - -```java -void update(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -none if the asset alias is updated success. - ----- - -#### 5.listBalances - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of Balance`, an ArrayList object contains Balance objects. - ----- - -#### 6.listBalancesByAssetAlias - -```java -Balance listByAssetAlias(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Balance`, a Balance objects. - ----- - -#### 7.listBalancesByAccountAlias - -```java -List listByAccountAlias(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of Balance`, an ArrayList object contains Balance objects. - ----- - -#### 8.listUnspentOutPuts - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of UnspentOutput`, an ArrayList object contains UnspentOutput objects. - - -## Transaction API - - -#### 1.buildTransaction - -```java -Template build(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Template` - *template*, a template object. - ----- - -#### 2.signTransaction - -```java -Template sign(Client client, Template template, String password); -``` - -##### Parameters - -`Object`: - -- `Client` - *Client*, Client object that makes requests to the core. -- `Template` - *template*, a template object. -- `String` - *password*, signature of the password. - -##### Returns - -- `Template` - *template*, a template object. - ----- - -#### 3.submitTransaction - -```java -SubmitResponse submit(Client client, Template template); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. -- `Template` - *template*, a template object. - -##### Returns - -- `SubmitResponse` - *submitResponse*, a SubmitResponse object - ----- - -#### 4.estimateTransactionGas - -```java -TransactionGas estimateGas(Client client, Template template); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. -- `Template` - *template*, a template object. - -##### Returns - -- `TransactionGas` - *transactionGas*, a TransactionGas object - ----- - -#### 5.getTransaction - -```java -Transaction get(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Transaction` - *transaction*, a Transaction object - ----- - -#### 6.listTransactions - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. - -##### Example - -```java -//list all transactions -List transactionList = new Transaction.QueryBuilder().list(client); -``` - ----- - -#### 7.listTransactionsById - -```java -List listById(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. - -##### Example - -```java -String tx_id = "f04d4d9b2580ff6496f9f08d903de5a2365975fb8d65b66ca4259f152c5dd134"; -//list all transactions by tx_id -List transactionList = new Transaction.QueryBuilder().setTxId(tx_id).list(client); -``` - ----- - -#### 8.listTransactionsByAccountId - -```java -List listByAccountId(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. - -##### Example - -```java -String account_id = "0E6KP8C100A02"; -//list all transactions by account_id -List transactionList = new Transaction.QueryBuilder().setAccountId(account_id).list(client); -``` - -## Wallet API - - -#### 1.backupWallet - -```java -Wallet backupWallet(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Wallet` - *wallet*, a Wallet object - ----- - -#### 2.restoreWallet - -```java -void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages); -``` - -##### Parameters - -`Object`: -- `Client` - *Client*, Client object that makes requests to the core. -- `Object` - *account_image*, account image. -- `Object` - *asset_image*, asset image. -- `Object` - *key_images*, key image. - -##### Returns - -none if restore wallet success. - - -## Access Token API - -```java -//example -AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client); - -List tokenList = AccessToken.list(client); - -String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799"; -AccessToken.check(client, "sheng", secret); -``` - -#### 1.createAccessToken - -```java -AccessToken create(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `AccessToken` - *accessToken*, an AccessToken object. - ----- - -#### 2.listAccessTokens - -```java -List list(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Array of Object`, access token array. - ----- - -#### 3.deleteAccessToken - -```java -void delete(Client client, String id); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. -- `String` - *id*, token ID. - -##### Returns - -none if the access token is deleted successfully. - ----- - -#### 4.checkAccessToken - -```java -void check(Client client, String id, String secret); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. -- `String` - *id*, token ID. -- `String` - *secret*, secret of token, the second part of the colon division for token. - -##### Returns - -none if the access token is checked valid. - - -## Block API - - -#### 1.getBlockCount - -```java -Integer getBlockCount(Client client); -``` - -##### Parameters - -none - -##### Returns - -- `Integer` - *block_count*, recent block height of the blockchain. - ----- - -#### 2.getBlockHash - -```java -String getBlockHash(Client client); -``` - -##### Parameters - -none - -##### Returns - -- `String` - *block_hash*, recent block hash of the blockchain. - ----- - -#### 3.getBlock -```java -Block getBlock(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Block` - *block*, a Block object. - ----- - -#### 4.getBlockHeader - -```java -BlockHeader getBlockHeader(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `BlockHeader` - *blockHeader*, header of block. - ----- - -#### 5.getDifficulty - -```java -BlockDifficulty getBlockDifficulty(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `BlockDifficulty` - *blockDifficulty*, a BlockDifficulty object - ----- - -#### 6.getHashRate - -```java -BlockHashRate getHashRate(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `BlockHashRate` - *blockHashRate*, a BlockHashRate object - - -## Other API - - -#### 1.coreConfig - -```java -NetInfo getNetInfo(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `NetInfo` - *coreConfig*, a NetInfo object. - ----- - -#### 2.gasRate - -```java -Gas gasRate(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Gas` - *gas*, a Gas object. - ----- - -#### 3.verifyMessage - -```java -Boolean verifyMessage(Client client); -``` - -##### Parameters - -- `Client` - *Client*, Client object that makes requests to the core. - -##### Returns - -- `Boolean` - *result*, verify result. - diff --git a/doc/transactions.md b/doc/transactions.md deleted file mode 100644 index 6e095f3..0000000 --- a/doc/transactions.md +++ /dev/null @@ -1,245 +0,0 @@ -## Creating transactions - -Creating a transaction consists of three steps: - -1. **Build transaction**: Define what the transaction is supposed to do: issue new units of an asset, spend assets held in an account, control assets with an account, etc. -2. **Sign transaction**: Authorize the spending of assets or the issuance of new asset units using private keys. -3. **Submit transaction**: Submit a complete, signed transaction to the blockchain, and propagate it to other cores on the network. - -### Build transaction - -Rather than forcing you to manipulate inputs, outputs and change directly, the Bytom Core API allows you to build transactions using a list of high-level **actions**. - -There are five types of actions: - -| ACTION | DESCRIPTION | -| --------------------------------------- | ------------------------------------------------------------ | -| Issue | Issues new units of a specified asset. | -| Spend from account | Spends units of a specified asset from a specified account. Automatically handles locating outputs with enough units, and the creation of change outputs. | -| Spend an unspent output from an account | Spends an entire, specific unspent output in an account. Change must be handled manually, using other actions. | -| Control with receiver | Receives units of an asset into a receiver, which contains a control program and supplementary payment information, such as an expiration date. Used when making a payment to an external party/account in another Bytom Core. | -| Retire | Retires units of a specified asset. | - -### Sign transaction - -In order for a transaction to be accepted into the blockchain, its inputs must contain valid signatures. For issuance inputs, the signature must correspond to public keys named in the issuance program. For spending inputs, the signature must correspond to the public keys named in the control programs of the outputs being spent. - -Transaction signing provides the blockchain with its security. Strong cryptography prevents everyone–even the operators of the blockchain network–from producing valid transaction signatures without the relevant private keys. - -### Submit transaction - -Once a transaction is balanced and all inputs are signed, it is considered valid and can be submitted to the blockchain. The local core will forward the transaction to the generator, which adds it to the blockchain and propagates it to other cores on the network. - -The Chain Core API does not return a response until either the transaction has been added to the blockchain and indexed by the local core, or there was an error. This allows you to write your applications in a linear fashion. In general, if a submission responds with success, the rest of your application may proceed with the guarantee that the transaction has been committed to the blockchain. - -## Examples - -### Asset issuance - -Issue 300000000 units of gold to Alice's address. - -#### Within a Chain Core - -```java -Transaction.Template controlAddress = new Transaction.Builder() - .addAction( - new Transaction.Action.SpendFromAccount() - .setAccountId(senderAccount.id) - .setAssetId(senderAsset.id) - .setAmount(300000000) - ) - .addAction( - new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetId(senderAsset.id) - .setAmount(200000000) - ).build(client); - -Transaction.Template signedIssuance = new Transaction.SignerBuilder().sign(client, - issuance, "123456"); - -Transaction.submit(client, signedIssuance); -``` - -#### Between two Chain Cores - -First, Bob creates a receiver in his account, which he can serialize and send to the issuer of gold. - -```java -Receiver bobIssuanceReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); -String bobIssuanceReceiverSerialized = bobIssuanceReceiver.toJson(); -``` - -The issuer then builds, signs, and submits a transaction, sending gold to Bob’s receiver. - -```java -Transaction.Template issuanceToReceiver = new Transaction.Builder() - .addAction(new Transaction.Action.Issue() - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobIssuanceReceiverSerialized)) - .setAssetAlias("gold") - .setAmount(10) - ).build(client); - -Transaction.Template signedIssuanceToReceiver = new Transaction.SignerBuilder().sign(client, - issuanceToReceiver, "123456"); - -Transaction.submit(client, signedIssuanceToReceiver); -``` - -### Simple payment - -Alice pays 10 units of gold to Bob. - -#### Within a Chain Core - -```java -Transaction.Template payment = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetAlias("gold") - .setAmount(10) - ).build(client); - -Transaction.Template signedPayment = new Transaction.SignerBuilder().sign(client, - payment, "123456"); - -Transaction.submit(client, signedPayment); -``` - -#### Between two Chain Cores - -First, Bob creates a receiver in his account, which he can serialize and send to Alice. - -```java -Receiver bobPaymentReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); -String bobPaymentReceiverSerialized = bobPaymentReceiver.toJson(); -``` - -Alice then builds, signs, and submits a transaction, sending gold to Bob’s receiver. - -```java -Transaction.Template paymentToReceiver = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobPaymentReceiverSerialized)) - .setAssetAlias("gold") - .setAmount(10) - ).build(client); - -Transaction.Template signedPaymentToReceiver = new Transaction.SignerBuilder().sign(client, - paymentToReceiver, "123456"); - -Transaction.submit(client, signedPaymentToReceiver); -``` - -### Multi-asset payment - -Alice pays 10 units of gold and 20 units of silver to Bob. - -#### Within a Chain Core - -```java -Transaction.Template multiAssetPayment = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("silver") - .setAmount(20) - ).addAction(new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetAlias("silver") - .setAmount(20) - ).build(client); - -Transaction.Template signedMultiAssetPayment = new Transaction.SignerBuilder().sign(client, - multiAssetPayment, "123456"); - -Transaction.submit(client, signedMultiAssetPayment); -``` - -#### Between two Chain Cores - -Currently, the transaction builder API assigns each receiver to its own output, which means that a single receiver can only be used to receive a single asset type. It’s important for Bob not to re-use receivers, so he creates one for each asset payment he will receive. He serializes both and sends them to Alice. - -```java -Receiver bobGoldReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); -String bobGoldReceiverSerialized = bobGoldReceiver.toJson(); - -Receiver bobSilverReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); -String bobSilverReceiverSerialized = bobSilverReceiver.toJson(); -``` - -Alice then builds, signs, and submits a transaction, sending gold and silver to Bob’s receivers. - -```java -Transaction.Template multiAssetToReceiver = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("silver") - .setAmount(20) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobGoldReceiverSerialized)) - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobSilverReceiverSerialized)) - .setAssetAlias("silver") - .setAmount(20) - ).build(client); - -Transaction.Template signedMultiAssetToReceiver = new Transaction.SignerBuilder().sign(client, - multiAssetToReceiver, "123456"); - -Transaction.submit(client, signedMultiAssetToReceiver); -``` - -### Asset retirement - -Alice retires 50 units of gold from her account. - -```java -Transaction.Template retirement = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(50) - ).addAction(new Transaction.Action.Retire() - .setAssetAlias("gold") - .setAmount(50) - ).build(client); - -Transaction.Template signedRetirement = new Transaction.SignerBuilder().sign(client, - retirement, "123456"); - -Transaction.submit(client, signedRetirement); -``` - diff --git a/src/main/java/io/bytom/api/AccessToken.java b/src/main/java/io/bytom/api/AccessToken.java deleted file mode 100644 index 4541834..0000000 --- a/src/main/java/io/bytom/api/AccessToken.java +++ /dev/null @@ -1,164 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - *

AccessToken Class

- */ -public class AccessToken { - /** - * Token id - */ - public String id; - /** - * Token token - */ - public String token; - /** - * Token type - */ - public String type; - /** - * create time of token - */ - @SerializedName(value = "created_at", alternate = {"create"}) - public String createTime; - - private static Logger logger = Logger.getLogger(AccessToken.class); - - /** - * Serializes the AccessToken into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the AccessToken object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - public static class Builder { - /** - * id of Token - */ - public String id; - /** - * type of Token - */ - public String type; - - public Builder() { - } - - /** - * @param id the id to set - * @return Builder - */ - public Builder setId(String id) { - this.id = id; - return this; - } - - /** - * @param type the type to set, possibly null - * @return Builder - */ - public Builder setType(String type) { - this.type = type; - return this; - } - - /** - * Call create-access-token api - * - * @param client client object that makes requests to the core - * @return AccessToken object - * @throws BytomException - */ - public AccessToken create(Client client) throws BytomException { - AccessToken accessToken = client.request("create-access-token", this, AccessToken.class); - - logger.info("create-access-token:"); - logger.info(accessToken.toJson()); - - return accessToken; - } - } - - /** - * Call check-access-token api - * - * @param client client object that makes requests to the core - * @param id id - * @param secret secret - * @throws BytomException - */ - public static void check(Client client, String id, String secret) throws BytomException { - Map req = new HashMap(); - req.put("id", id); - req.put("secret", secret); - // add a native control - if (client.getUrl().equals("http://127.0.0.1:9888") || - client.getUrl().equals("http://127.0.0.1:9888/")) { - client.request("check-access-token", req); - logger.info("check-access-token successfully."); - } else { - logger.info("this is a native method."); - } - } - - /** - * Call delete-access-token api - * native method, can't rpc - * - * @param client client object that makes requests to the core - * @param id id - * @throws BytomException - */ - public static void delete(Client client, String id) throws BytomException { - Map req = new HashMap(); - req.put("id", id); - // add a native control - if (client.getUrl().equals("http://127.0.0.1:9888") || - client.getUrl().equals("http://127.0.0.1:9888/")) { - client.request("delete-access-token", req); - logger.info("delete-access-token."); - } else { - logger.info("this is a native method."); - } - } - - /** - * Call list-access-tokens api.
- * native method, can't rpc - * - * @param client client object that makes requests to the core - * @return list of AccessToken objects - * @throws BytomException - */ - public static List list(Client client) throws BytomException { - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{AccessToken.class}); - List accessTokenList = null; - if (client.getUrl().equals("http://127.0.0.1:9888") || - client.getUrl().equals("http://127.0.0.1:9888/")) { - accessTokenList = client.request("list-access-tokens", null, listType); - - logger.info("list-access-tokens:"); - logger.info("size of accessTokenList:" + accessTokenList.size()); - logger.info(accessTokenList.get(0).toJson()); - } else { - logger.info("this is a native method."); - } - - return accessTokenList; - } - -} diff --git a/src/main/java/io/bytom/api/Account.java b/src/main/java/io/bytom/api/Account.java deleted file mode 100644 index c84e526..0000000 --- a/src/main/java/io/bytom/api/Account.java +++ /dev/null @@ -1,336 +0,0 @@ -package io.bytom.api; - -import com.google.gson.Gson; -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.*; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.*; - -/** - *

Account Class

- */ -public class Account { - - @SerializedName("id") - public String id; - - @SerializedName("alias") - public String alias; - - @SerializedName("key_index") - public Integer key_index; - - @SerializedName("quorum") - public Integer quorum; - - @SerializedName("xpubs") - public List xpubs; - - private static Logger logger = Logger.getLogger(Account.class); - - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * create-account - * - * @param client client object that makes requests to the core - * @param builder Account.Builder to make parameters - * @return Account return a account object - * @throws BytomException BytomException - */ - public static Account create(Client client, Builder builder) throws BytomException { - Account account = client.request("create-account", builder, Account.class); - logger.info("create-account"); - logger.info(account.toString()); - return account; - } - - /** - * list-accounts - * - * @param client client object that makes requests to the core - * @return return a list of account object - * @throws BytomException BytomException - */ - public static List list(Client client) throws BytomException { - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Account.class}); - List accountList = client.request("list-accounts", null, listType); - logger.info("list-accounts:"); - logger.info("size of accountList:"+accountList.size()); - logger.info(accountList); - return accountList; - } - - /** - * delete-account - * @param client client object that makes requests to the core - * @param account_info account_info - * @throws BytomException BytomException - */ - public static void delete(Client client, String account_info) throws BytomException { - Map req = new HashMap<>(); - req.put("account_info", account_info); - client.request("delete-account", req); - } - - public static class Builder { - - public List root_xpubs; - - public String alias; - - public Integer quorum; - - /** - * add a xpub to root_xpubs - * - * @param xpub xpub - * @return this Builder object - */ - public Builder addRootXpub(String xpub) { - this.root_xpubs.add(xpub); - return this; - } - - /** - * set xpubs to root_xpubs - * - * @param xpubs xpubs - * @return this Builder object - */ - public Builder setRootXpub(List xpubs) { - this.root_xpubs = new ArrayList<>(xpubs); - return this; - } - - /** - * set alias to alias - * @param alias alias - * @return this Builder object - */ - public Builder setAlias(String alias) { - this.alias = alias; - return this; - } - - /** - * set quorum to quorum - * - * @param quorum quorum - * @return this Builder object - */ - public Builder setQuorum(Integer quorum) { - this.quorum = quorum; - return this; - } - - } - - /** - * Use this class to create a {@link Receiver} under an account. - */ - public static class ReceiverBuilder { - - @SerializedName("account_alias") - public String accountAlias; - - @SerializedName("account_id") - public String accountId; - - /** - * Specifies the account under which the receiver is created. You must use - * this method or @{link ReceiverBuilder#setAccountId}, but not both. - * - * @param alias the unique alias of the account - * @return this ReceiverBuilder object - */ - public ReceiverBuilder setAccountAlias(String alias) { - this.accountAlias = alias; - return this; - } - - /** - * Specifies the account under which the receiver is created. You must use - * this method or @{link ReceiverBuilder#setAccountAlias}, but not both. - * - * @param id the unique ID of the account - * @return this ReceiverBuilder object - */ - public ReceiverBuilder setAccountId(String id) { - this.accountId = id; - return this; - } - - /** - * Creates a single Receiver object under an account. - * - * @param client the client object providing access to an instance of Chain Core - * @return a new Receiver object - * @throws APIException This exception is raised if the api returns errors while creating the control programs. - * @throws BadURLException This exception wraps java.net.MalformedURLException. - * @throws ConnectivityException This exception is raised if there are connectivity issues with the server. - * @throws HTTPException This exception is raised when errors occur making http requests. - * @throws JSONException This exception is raised due to malformed json requests or responses. - */ - public Receiver create(Client client) throws BytomException { - Gson gson = new Gson(); - Receiver receiver = client.request( - "create-account-receiver", this, Receiver.class); - logger.info("create-account-receiver:"); - logger.info(receiver.toJson()); - return receiver; - } - - - @Override - public String toString() { - return "ReceiverBuilder{" + - "accountAlias='" + accountAlias + '\'' + - ", accountId='" + accountId + '\'' + - '}'; - } - } - - /** - * Address Class - */ - public static class Address { - @SerializedName("account_alias") - public String accountAlias; - - @SerializedName("account_id") - public String accountId; - - @SerializedName("address") - public String address; - - @SerializedName("change") - public Boolean change; - - @SerializedName("vaild") - public Boolean vaild; - - @SerializedName("is_local") - public Boolean is_local; - - /** - * Serializes the Address into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * Deserializes a Address from JSON. - * - * @param json a JSON-serialized Receiver object - * @return the deserialized Receiver object - * @throws JSONException Raised if the provided string is not valid JSON. - */ - public static Address fromJson(String json) throws JSONException { - try { - return Utils.serializer.fromJson(json, Address.class); - } catch (IllegalStateException e) { - throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); - } - } - - } - - /** - * Use this class to create a {@link Address} under an account. - */ - public static class AddressBuilder { - - @SerializedName("account_alias") - public String accountAlias; - - @SerializedName("account_id") - public String accountId; - - /** - * Specifies the account under which the address is created. You must use - * this method or @{link AddressBuilder#setAccountId}, but not both. - * - * @param alias the unique alias of the account - * @return this AddressBuilder object - */ - public AddressBuilder setAccountAlias(String alias) { - this.accountAlias = alias; - return this; - } - - /** - * Specifies the account under which the address is created. You must use - * this method or @{link AddressBuilder#setAccountAlias}, but not both. - * - * @param id the unique ID of the account - * @return this AddressBuilder object - */ - public AddressBuilder setAccountId(String id) { - this.accountId = id; - return this; - } - - /** - * list-addresses - * @param client client object that makes requests to the core - * @return list of address object - * @throws BytomException BytomException - */ - public List
list(Client client) throws BytomException { - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Address.class}); - List
addressList = client.request("list-addresses", this, listType); - logger.info("list-addresses:"); - logger.info("size of addressList:" + addressList.size()); - logger.info(addressList.get(0).toJson()); - - return addressList; - } - - /** - * validate-address - * @param client client object that makes requests to the core - * @param address an address string - * @return an address object - * @throws BytomException BytomException - */ - public Address validate(Client client, String address) throws BytomException { - Map req = new HashMap<>(); - req.put("address", address); - Address addressResult = client.request("validate-address", req, Address.class); - logger.info("validate-address:"); - logger.info(addressResult.toJson()); - - return addressResult; - } - - @Override - public String toString() { - return "AddressBuilder{" + - "accountAlias='" + accountAlias + '\'' + - ", accountId='" + accountId + '\'' + - '}'; - } - } - - @Override - public String toString() { - return "Account{" + - "id='" + id + '\'' + - ", alias='" + alias + '\'' + - ", key_index=" + key_index + - ", quorum=" + quorum + - ", xpubs=" + xpubs + - '}'; - } -} diff --git a/src/main/java/io/bytom/api/Asset.java b/src/main/java/io/bytom/api/Asset.java deleted file mode 100644 index 9fdb375..0000000 --- a/src/main/java/io/bytom/api/Asset.java +++ /dev/null @@ -1,369 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.*; - -/** - *

Asset Class

- *
- * String - id, asset id.
- * String - alias, name of the asset.
- * String - issuance_program, control program of the issuance of asset.
- * Array of Object - keys, information of asset pubkey.
- * String - definition, definition of asset.
- * Integer - quorum, threshold of keys that must sign a transaction to spend asset units controlled by the account.
- */ -public class Asset { - - /** - * Globally unique identifier of the asset.
- * Asset version 1 specifies the asset id as the hash of:
- * - the asset version
- * - the asset's issuance program
- * - the core's VM version
- * - the hash of the network's initial block - */ - public String id; - - /** - * User specified, unique identifier. - */ - public String alias; - - /** - * A program specifying a predicate to be satisfied when issuing the asset. - */ - @SerializedName(value = "issuance_program", alternate = {"issue_program"}) - public String issuanceProgram; - - /** - * The list of keys used to create the issuance program for the asset.
- * Signatures from these keys are required for issuing units of the asset. - */ - public Key[] keys; - - @SerializedName("key_index") - public Integer keyIndex; - - @SerializedName("xpubs") - public List xpubs; - - /** - * The number of keys required to sign an issuance of the asset. - */ - @SerializedName("quorum") - public int quorum; - - /** - * User-specified, arbitrary/unstructured data visible across blockchain networks.
- * Version 1 assets specify the definition in their issuance programs, rendering the - * definition immutable. - */ - @SerializedName("definition") - public Map definition; - - /** - * version of VM. - */ - @SerializedName("vm_version") - public int vmVersion; - - /** - * type of asset. - */ - @SerializedName("type") - public String type; - - /** - * byte of asset definition. - */ - @SerializedName("raw_definition_byte") - public String rawDefinitionByte; - - public static Logger logger = Logger.getLogger(Asset.class); - - public String toJson() { - return Utils.serializer.toJson(this); - } - - public static class Key { - /** - * Hex-encoded representation of the root extended public key - */ - @SerializedName("root_xpub") - public String rootXpub; - - /** - * The derived public key, used in the asset's issuance program. - */ - @SerializedName("asset_pubkey") - public String assetPubkey; - - /** - * The derivation path of the derived key. - */ - @SerializedName("asset_derivation_path") - public String[] assetDerivationPath; - - @Override - public String toString() { - return "Key{" + - "rootXpub='" + rootXpub + '\'' + - ", assetPubkey='" + assetPubkey + '\'' + - ", assetDerivationPath=" + Arrays.toString(assetDerivationPath) + - '}'; - } - } - - /** - *

Builder Class

- */ - public static class Builder { - /** - * User specified, unique identifier. - */ - public String alias; - - /** - * User-specified, arbitrary/unstructured data visible across blockchain networks.
- * Version 1 assets specify the definition in their issuance programs, rendering - * the definition immutable. - */ - public Map definition; - - /** - * The list of keys used to create the issuance program for the asset.
- * Signatures from these keys are required for issuing units of the asset.
- * Must set with {@link #addRootXpub(String)} or - * {@link #setRootXpubs(List)} before calling {@link #create(Client)}. - */ - @SerializedName("root_xpubs") - public List rootXpubs; - - /** - * The number of keys required to sign an issuance of the asset.
- * Must set with {@link #setQuorum(int)} before calling - * {@link #create(Client)}. - */ - public int quorum; - - /** - * Unique identifier used for request idempotence. - */ - @SerializedName("access_token") - private String access_token; - - /** - * Default constructor initializes the list of keys. - */ - public Builder() { - this.rootXpubs = new ArrayList<>(); - } - - /** - * Creates an asset object. - * - * @param client client object that makes request to the core - * @return an asset object - * @throws BytomException BytomException - */ - public Asset create(Client client) throws BytomException { - Asset asset = client.request("create-asset", this, Asset.class); - logger.info("create-asset:"); - logger.info(asset.toString()); - return asset; - } - - /** - * Sets the alias on the builder object. - * @param alias alias - * @return updated builder object - */ - public Builder setAlias(String alias) { - this.alias = alias; - return this; - } - - /** - * Adds a field to the existing definition object (initializing the object if it - * doesn't exist). - * @param key key of the definition field - * @param value value of the definition field - * @return updated builder object - */ - public Builder addDefinitionField(String key, Object value) { - if (this.definition == null) { - this.definition = new HashMap<>(); - } - this.definition.put(key, value); - return this; - } - - /** - * Sets the asset definition object.
- * Note: any existing asset definition fields will be replaced. - * @param definition asset definition object - * @return updated builder object - */ - public Builder setDefinition(Map definition) { - this.definition = definition; - return this; - } - - /** - * Sets the quorum of the issuance program. Must be called before - * {@link #create(Client)}. - * @param quorum proposed quorum - * @return updated builder object - */ - public Builder setQuorum(int quorum) { - this.quorum = quorum; - return this; - } - - /** - * Adds a key to the builder's list.
- * Either this or {@link #setRootXpubs(List)} must be called before - * {@link #create(Client)}. - * @param xpub key - * @return updated builder object. - */ - public Builder addRootXpub(String xpub) { - this.rootXpubs.add(xpub); - return this; - } - - /** - * Sets the builder's list of keys.
- * Note: any existing keys will be replaced.
- * Either this or {@link #addRootXpub(String)} must be called before - * {@link #create(Client)}. - * @param xpubs list of xpubs - * @return updated builder object - */ - public Builder setRootXpubs(List xpubs) { - this.rootXpubs = new ArrayList<>(xpubs); - return this; - } - - @Override - public String toString() { - return "Builder{" + - "alias='" + alias + '\'' + - ", definition=" + definition + - ", rootXpubs=" + rootXpubs + - ", quorum=" + quorum + - ", access_token='" + access_token + '\'' + - '}'; - } - } - - /** - *

QueryBuilder Class

- */ - public static class QueryBuilder { - - @SerializedName("id") - public String id; - - public QueryBuilder setId(String assetId) { - this.id = assetId; - return this; - } - - /** - * get-asset from bytomd - * - * @param client client object that makes requests to the core - * @return The Asset Object - * @throws BytomException BytomException - */ - public Asset get(Client client) throws BytomException { - Asset asset = client.request("get-asset", this, Asset.class); - logger.info("get-asset:"); - logger.info(asset.toJson()); - return asset; - } - - /** - * get all assets from bytomd - * - * @param client client object that makes requests to the core - * @return return list of asset object - * @throws BytomException BytomException - */ - public List list(Client client) throws BytomException { - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Asset.class}); - List assetList = client.request("list-assets", null, listType); - logger.info("list-assets:"); - logger.info("size of assetList:"+assetList.size()); - logger.info(assetList); - return assetList; - } - - } - - /** - *

AliasUpdateBuilder Class

- */ - public static class AliasUpdateBuilder { - /** - * id of asset. - */ - @SerializedName("id") - public String id; - /** - * new alias of asset - */ - @SerializedName("alias") - public String alias; - - public AliasUpdateBuilder setAssetId(String assetId) { - this.id = assetId; - return this; - } - - public AliasUpdateBuilder setAlias(String alias) { - this.alias = alias; - return this; - } - - /** - * update-asset-alias - * - * @param client client object that makes requests to the core - * @throws BytomException BytomException - */ - public void update(Client client) throws BytomException { - client.request("update-asset-alias", this); - logger.info("update-asset-alias:"); - logger.info("id:"+id); - logger.info("alias:"+alias); - } - - } - - @Override - public String toString() { - return "Asset{" + - "id='" + id + '\'' + - ", alias='" + alias + '\'' + - ", issuanceProgram='" + issuanceProgram + '\'' + - ", keys=" + Arrays.toString(keys) + - ", keyIndex=" + keyIndex + - ", xpubs=" + xpubs + - ", quorum=" + quorum + - ", definition=" + definition + - ", vmVersion=" + vmVersion + - ", type='" + type + '\'' + - ", rawDefinitionByte='" + rawDefinitionByte + '\'' + - '}'; - } -} diff --git a/src/main/java/io/bytom/api/Balance.java b/src/main/java/io/bytom/api/Balance.java deleted file mode 100644 index 9ad8bc9..0000000 --- a/src/main/java/io/bytom/api/Balance.java +++ /dev/null @@ -1,130 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class Balance { - - /** - * account id - */ - @SerializedName("account_id") - public String accountId; - - /** - * name of account - */ - @SerializedName("account_alias") - public String accountAlias; - - /** - * sum of the unspent outputs. - * specified asset balance of account. - */ - public long amount; - - /** - * asset id - */ - @SerializedName("asset_id") - public String assetId; - - /** - * name of asset - */ - @SerializedName("asset_alias") - public String assetAlias; - - @SerializedName("asset_definition") - public Map definition; - - private static Logger logger = Logger.getLogger(Balance.class); - - /** - * Serializes the Balance into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - - public static class QueryBuilder { - - /** - * Call list-Balances api - * - * @param client - * @return - * @throws BytomException - */ - public List list(Client client) throws BytomException { - - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Balance.class}); - List balanceList = client.request("list-balances", null, listType); - logger.info("list-balances:"); - logger.info("size of :" + balanceList.size()); - for (Balance result : balanceList) { - logger.info(result.toJson()); - } - - return balanceList; - } - - /** - * sum of all Asset alias amount - * - * @param client - * @param assetAlias - * @return - * @throws BytomException - */ - public Balance listByAssetAlias(Client client, String assetAlias) throws BytomException { - List balanceList = list(client); - Balance assetBalance = new Balance(); - assetBalance.assetAlias = assetAlias; - long amount = 0; - for (Balance result : balanceList) { - if (result.assetAlias.equals(assetAlias)) { - amount += result.amount; - assetBalance.assetId = result.assetId; - } - } - assetBalance.amount = amount; - - logger.info(assetBalance.toJson()); - - return assetBalance; - } - - /** - * sum of all Account alias amount - * - * @param client - * @param accountAlias - * @return - * @throws BytomException - */ - public List listByAccountAlias(Client client, String accountAlias) throws BytomException { - List balanceList = list(client); - List accountBalance = new ArrayList<>(); - for (Balance result : balanceList) { - if (result.accountAlias.equals(accountAlias)) { - accountBalance.add(result); - logger.info(result.toJson()); - } - } - return accountBalance; - } - } -} diff --git a/src/main/java/io/bytom/api/Block.java b/src/main/java/io/bytom/api/Block.java deleted file mode 100644 index ecda5b1..0000000 --- a/src/main/java/io/bytom/api/Block.java +++ /dev/null @@ -1,324 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.util.List; -import java.util.Map; - -public class Block { - - public String hash; - - public Integer size; - - public Integer version; - - public Integer height; - - @SerializedName("previous_block_hash") - public String previousBlockHash; - - public Integer timestamp; - - public Integer nonce; - - public long bits; - - public String difficulty; - - @SerializedName("transaction_merkle_root") - public String transactionsMerkleRoot; - - @SerializedName("transaction_status_hash") - public String transactionStatusHash; - - public List transactions; - - - private static Logger logger = Logger.getLogger(Block.class); - - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * Call get-block-count api - * - * @param client - * @return - * @throws BytomException - */ - public static Integer getBlockCount(Client client) throws BytomException { - Integer blockCount = - client.requestGet("get-block-count", null, "block_count", Integer.class); - - logger.info("get-block-count:"+blockCount); - return blockCount; - } - - /** - * Call get-block-hash api - * - * @param client - * @return - * @throws BytomException - */ - public static String getBlockHash(Client client) throws BytomException { - String blockHash = - client.requestGet("get-block-hash", null, "block_hash", String.class); - - logger.info("get-block-hash:"+blockHash); - - return blockHash; - } - - public static class QueryBuilder { - - /** - * block_height, height of block. - */ - @SerializedName("block_height") - public int blockHeight; - /** - * block_hash, hash of block. - */ - @SerializedName("block_hash") - public String blockHash; - - public QueryBuilder setBlockHeight(int blockHeight) { - this.blockHeight = blockHeight; - return this; - } - - public QueryBuilder setBlockHash(String blockHash) { - this.blockHash = blockHash; - return this; - } - - /** - * Call get-block api - * - * @param client - * @return - * @throws BytomException - */ - public Block getBlock(Client client) throws BytomException { - - Block block = client.request("get-block", this, Block.class); - - logger.info("get-block:"); - logger.info(block.toJson()); - - return block; - } - - /** - * Call get-block-header api - * - * @param client - * @return - * @throws BytomException - */ - public BlockHeader getBlockHeader(Client client) throws BytomException { - BlockHeader blockHeader = - client.request("get-block-header", this, BlockHeader.class); - - logger.info("get-block-header:"); - logger.info(blockHeader.toJson()); - - return blockHeader; - } - - /** - * Call get-difficulty api - * - * @param client - * @return - * @throws BytomException - */ - public BlockDifficulty getBlockDifficulty(Client client) throws BytomException { - BlockDifficulty blockDifficulty = - client.request("get-difficulty", this, BlockDifficulty.class); - - logger.info("get-difficulty:"); - logger.info(blockDifficulty.toJson()); - - return blockDifficulty; - } - - /** - * Call get-hash-rate api - * - * @param client - * @return - * @throws BytomException - */ - public BlockHashRate getHashRate(Client client) throws BytomException { - BlockHashRate blockHashRate = - client.request("get-hash-rate", this, BlockHashRate.class); - - logger.info("get-hash-rate:"); - logger.info(blockHashRate.toJson()); - - return blockHashRate; - } - - } - - public static class BlockTx { - /** - * Unique identifier, or transaction hash, of a transaction. - */ - private String id; - - /** - * version - */ - private Integer version; - - /** - * size - */ - private Integer size; - /** - * time_range - */ - @SerializedName("time_range") - private Integer timeRange; - - /** - * status - */ - @SerializedName("status_fail") - private boolean statusFail; - - /** - * List of specified inputs for a transaction. - */ - @SerializedName("inputs") - private List inputs; - - /** - * List of specified outputs for a transaction. - */ - @SerializedName("outputs") - private List outputs; - } - - public static class AnnotatedInput { - - /** - * The number of units of the asset being issued or spent. - */ - private Integer amount; - - /** - * inputs param - */ - private String arbitrary; - - /** - * The definition of the asset being issued or spent (possibly null). - */ - @SerializedName("asset_definition") - private Map assetDefinition; - - /** - * The id of the asset being issued or spent. - */ - @SerializedName("asset_id") - private String assetId; - - /** - * The type of the input.
- * Possible values are "issue" and "spend". - */ - private String type; - } - - public static class AnnotatedOutput { - - /** - * The number of units of the asset being controlled. - */ - private long amount; - - /** - * The definition of the asset being controlled (possibly null). - */ - @SerializedName("asset_definition") - private Map assetDefinition; - - /** - * The id of the asset being controlled. - */ - @SerializedName("asset_id") - public String assetId; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("control_program") - private String controlProgram; - - /** - * The id of the output. - */ - @SerializedName("id") - private String id; - - /** - * The output's position in a transaction's list of outputs. - */ - private Integer position; - - /** - * The type the output.
- * Possible values are "control" and "retire". - */ - private String type; - - } - - public static class BlockHeader { - - @SerializedName("block_header") - public String blockHeader; - - @SerializedName("reward") - public Integer reward; - - public String toJson() { - return Utils.serializer.toJson(this); - } - - } - - public static class BlockDifficulty { - public String hash; - public Integer height; - public Integer bits; - public String difficulty; - - public String toJson() { - return Utils.serializer.toJson(this); - } - - - } - - public static class BlockHashRate { - public String hash; - public Integer height; - public Integer hash_rate; - - public String toJson() { - return Utils.serializer.toJson(this); - } - - } -} diff --git a/src/main/java/io/bytom/api/CoreConfig.java b/src/main/java/io/bytom/api/CoreConfig.java deleted file mode 100644 index 3334773..0000000 --- a/src/main/java/io/bytom/api/CoreConfig.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -public class CoreConfig { - - - private static Logger logger = Logger.getLogger(CoreConfig.class); - - /** - * Call net-info api - * - * @param client - * @return - * @throws BytomException - */ - public static NetInfo getNetInfo(Client client) throws BytomException { - NetInfo netInfo = client.request("net-info", null, NetInfo.class); - - logger.info("net-info:"); - logger.info(netInfo.toJson()); - - return netInfo; - } - - /** - * Call gas-rate api - * - * @param client - * @return - * @throws BytomException - */ - public static Integer getGasRate(Client client) throws BytomException { - Integer gas = client.requestGet("gas-rate", null, "gas_rate", Integer.class); - - logger.info("gas-rate:"); - logger.info(gas); - - return gas; - } - - public static class NetInfo { - /** - * listening, whether the node is listening. - */ - public boolean listening; - - /** - * syncing, whether the node is syncing. - */ - public boolean syncing; - - /** - * mining, whether the node is mining. - */ - public boolean mining; - - /** - * peer_count, current count of connected peers. - */ - @SerializedName("peer_count") - public int peerCount; - - /** - * current_block, current block height in the node's blockchain. - */ - @SerializedName("current_block") - public long currentBlock; - - /** - * highest_block, current highest block of the connected peers. - */ - @SerializedName("highest_block") - public long highestBlock; - - /** - * network_id, network id. - */ - @SerializedName("network_id") - public String networkID; - - /** - * version, bytom version. - */ - @SerializedName("version") - public String version; - - public String toJson() { - return Utils.serializer.toJson(this); - } - } -} diff --git a/src/main/java/io/bytom/api/Key.java b/src/main/java/io/bytom/api/Key.java deleted file mode 100644 index 558b6a7..0000000 --- a/src/main/java/io/bytom/api/Key.java +++ /dev/null @@ -1,148 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.*; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - *

Key Class

- * - * @version 1.0 - * @since 2018-05-18 - */ -public class Key { - - @SerializedName("alias") - public String alias; - - @SerializedName("xpub") - public String xpub; - - @SerializedName("file") - public String file; - - private static Logger logger = Logger.getLogger(Key.class); - - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * Create a key object - * - * @param client client object that makes requests to the core - * @param builder Key.Builder object that make parameters - * @return Key a key object - * @throws BytomException BytomException - */ - public static Key create(Client client, Builder builder) throws BytomException { - Key key = client.request("create-key", builder, Key.class); - return key; - } - - /** - * List all key objects - * - * @param client client object that makes requests to the core - * @return a list of key object - * @throws BytomException BytomException - */ - public static List list(Client client) throws BytomException { - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Key.class}); - List keyList = client.request("list-keys", null, listType); - - logger.info("list-key:"); - logger.info("size of key:"+keyList.size()); - logger.info(keyList); - - return keyList; - } - - /** - * delete a key - * - * @param client client object that makes requests to the core - * @param xpub the xpub is given when creates key - * @param password the password is given when creates key - * @throws BytomException BytomException - */ - public static void delete(Client client, String xpub, String password) throws BytomException { - Map req = new HashMap(); - req.put("xpub", xpub); - req.put("password", password); - client.request("delete-key", req); - logger.info("delete-key successfully."); - } - - /** - * reset password - * - * @param client client object that makes requests to the core - * @param xpub the xpub is given when creates key - * @param oldPwd the old password is given when creates key - * @param newPwd new password used to set - * @throws BytomException BytomException - */ - public static void resetPassword(Client client, String xpub, String oldPwd, String newPwd) throws BytomException { - Map req = new HashMap<>(); - req.put("xpub", xpub); - req.put("old_password", oldPwd); - req.put("new_password", newPwd); - client.request("reset-key-password", req); - } - - /** - *

Key.Builder Class

- */ - public static class Builder { - /** - * User specified, unique identifier. - */ - public String alias; - - /** - * User specified. - */ - public String password; - - /** - * Sets the alias on the builder object. - * - * @param alias alias - * @return updated builder object - */ - public Builder setAlias(String alias) { - this.alias = alias; - return this; - } - - /** - * Sets the alias on the builder object. - * - * @param password password - * @return updated builder object - */ - public Builder setPassword(String password) { - this.password = password; - return this; - } - - } - - @Override - public String toString() { - return "Key{" + - "alias='" + alias + '\'' + - ", xpub='" + xpub + '\'' + - ", file='" + file + '\'' + - '}'; - } -} diff --git a/src/main/java/io/bytom/api/Message.java b/src/main/java/io/bytom/api/Message.java deleted file mode 100644 index 4ce1e70..0000000 --- a/src/main/java/io/bytom/api/Message.java +++ /dev/null @@ -1,122 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -public class Message { - - @SerializedName("derived_xpub") - public String derivedXpub; - @SerializedName("signature") - public String signature; - - private static Logger logger = Logger.getLogger(Message.class); - - public String toJson() { - return Utils.serializer.toJson(this); - } - - public static class SignBuilder { - - public String address; - public String message; - public String password; - - public SignBuilder setAddress(String address) { - this.address = address; - return this; - } - - public SignBuilder setMessage(String message) { - this.message = message; - return this; - } - - public SignBuilder setPassword(String password) { - this.password = password; - return this; - } - - /** - * Call sign-message api - * - * @param client - * @return - * @throws BytomException - */ - public Message sign(Client client) throws BytomException { - Message message = client.request("sign-message", this, Message.class); - - logger.info("sign-message:"); - logger.info(message.toJson()); - - return message; - } - - } - - public static class VerifyBuilder { - - /** - * address, address for account. - */ - public String address; - - /** - * derived_xpub, derived xpub. - */ - @SerializedName("derived_xpub") - public String derivedXpub; - - /** - * message, message for signature by derived_xpub. - */ - public String message; - - /** - * signature, signature for message. - */ - public String signature; - - - public VerifyBuilder setAddress(String address) { - this.address = address; - return this; - } - - public VerifyBuilder setDerivedXpub(String derivedXpub) { - this.derivedXpub = derivedXpub; - return this; - } - - public VerifyBuilder setMessage(String message) { - this.message = message; - return this; - } - - public VerifyBuilder setSignature(String signature) { - this.signature = signature; - return this; - } - - /** - * Call verify-message api - * @param client - * @return - * @throws BytomException - */ - public Boolean verifyMessage(Client client) throws BytomException { - Boolean result = client.requestGet("verify-message", this, "result", Boolean.class); - - logger.info("verify-message:"+result); - - return result; - } - - } - - -} diff --git a/src/main/java/io/bytom/api/RawTransaction.java b/src/main/java/io/bytom/api/RawTransaction.java deleted file mode 100644 index 519cf27..0000000 --- a/src/main/java/io/bytom/api/RawTransaction.java +++ /dev/null @@ -1,154 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class RawTransaction { - /** - * version - */ - public Integer version; - /** - * size - */ - public Integer size; - /** - * time_range - */ - @SerializedName("time_range") - public Integer timeRange; - - /** - * status - */ - public Integer fee; - - /** - * List of specified inputs for a transaction. - */ - public List inputs; - - /** - * List of specified outputs for a transaction. - */ - public List outputs; - - private static Logger logger = Logger.getLogger(RawTransaction.class); - - public String toJson() { - return Utils.serializer.toJson(this); - } - - - public static RawTransaction decode(Client client, String txId) throws BytomException { - Map req = new HashMap(); - req.put("raw_transaction", txId); - RawTransaction rawTransaction = - client.request("decode-raw-transaction", req, RawTransaction.class); - - logger.info("decode-raw-transaction:"); - logger.info(rawTransaction.toJson()); - - return rawTransaction; - } - - public static class AnnotatedInput { - - /** - * address - */ - private String address; - - /** - * The number of units of the asset being issued or spent. - */ - private long amount; - - /** - * The definition of the asset being issued or spent (possibly null). - */ - @SerializedName("asset_definition") - private Map assetDefinition; - - /** - * The id of the asset being issued or spent. - */ - @SerializedName("asset_id") - private String assetId; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("control_program") - private String controlProgram; - - /** - * The id of the output consumed by this input. Null if the input is an - * issuance. - */ - @SerializedName("spent_output_id") - private String spentOutputId; - - /** - * The type of the input.
- * Possible values are "issue" and "spend". - */ - private String type; - } - - public static class AnnotatedOutput { - - /** - * address - */ - private String address; - - /** - * The number of units of the asset being controlled. - */ - private long amount; - - /** - * The definition of the asset being controlled (possibly null). - */ - @SerializedName("asset_definition") - private Map assetDefinition; - - /** - * The id of the asset being controlled. - */ - @SerializedName("asset_id") - public String assetId; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("control_program") - private String controlProgram; - - /** - * The id of the output. - */ - @SerializedName("id") - private String id; - - /** - * The output's position in a transaction's list of outputs. - */ - private Integer position; - - /** - * The type the output.
- * Possible values are "control" and "retire". - */ - private String type; - - } -} diff --git a/src/main/java/io/bytom/api/Receiver.java b/src/main/java/io/bytom/api/Receiver.java deleted file mode 100644 index add4a6a..0000000 --- a/src/main/java/io/bytom/api/Receiver.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.JSONException; - -/** - * Receivers are used to facilitate payments between accounts on different - * cores. They contain a control program and an expiration date. In the future, - * more payment-related metadata may be placed here. - *

- * Receivers are typically created under accounts via the - * {@link Account.ReceiverBuilder} class. - */ -public class Receiver { - - @SerializedName("address") - public String address; - /** - * Hex-encoded string representation of the control program. - */ - @SerializedName("control_program") - public String controlProgram; - - - /** - * Serializes the receiver into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * Deserializes a Receiver from JSON. - * - * @param json a JSON-serialized Receiver object - * @return the deserialized Receiver object - * @throws JSONException Raised if the provided string is not valid JSON. - */ - public static Receiver fromJson(String json) throws JSONException { - try { - return Utils.serializer.fromJson(json, Receiver.class); - } catch (IllegalStateException e) { - throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); - } - } -} diff --git a/src/main/java/io/bytom/api/Transaction.java b/src/main/java/io/bytom/api/Transaction.java deleted file mode 100644 index d848986..0000000 --- a/src/main/java/io/bytom/api/Transaction.java +++ /dev/null @@ -1,1182 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.*; - -/** - *

Transaction Class

- */ -public class Transaction { - /** - * Unique identifier, or transaction hash, of a transaction. - */ - @SerializedName("tx_id") - public String txId; - - /** - * Time of transaction. - */ - @SerializedName("block_time") - public String blockTime; - - /** - * Unique identifier, or block hash, of the block containing a transaction. - */ - @SerializedName("block_hash") - public String blockHash; - - /** - * Index of a transaction within the block. - */ - @SerializedName("block_index") - public String blockIndex; - - @SerializedName("block_transactions_count") - public String blockTransactionsCount; - - /** - * Height of the block containing a transaction. - */ - @SerializedName("block_height") - public int blockHeight; - - /** - * whether the state of the request has failed. - */ - @SerializedName("status_fail") - public boolean statusFail; - - /** - * List of specified inputs for a transaction. - */ - public List inputs; - - /** - * List of specified outputs for a transaction. - */ - public List outputs; - - private static Logger logger = Logger.getLogger(Transaction.class); - - /** - * Serializes the Address into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - public static class Builder { - /** - * Hex-encoded serialization of a transaction to add to the current template. - */ - @SerializedName("base_transaction") - protected String baseTransaction; - - /** - * List of actions in a transaction. - */ - protected List actions; - - /** - * A time duration in milliseconds. If the transaction is not fully signed and - * submitted within this time, it will be rejected by the blockchain. - * Additionally, any outputs reserved when building this transaction will remain - * reserved for this duration. - */ - protected long ttl; - - /** - * Call build-transaction api.
- * - * Builds a single transaction template. - * - * @param client client object which makes requests to the server - * @return a transaction template - */ - public Template build(Client client) throws BytomException { - return client.request("build-transaction", this, Template.class); - } - - /** - * Default constructor initializes actions list. - */ - public Builder() { - this.actions = new ArrayList<>(); - } - - /** - * Sets the baseTransaction field and initializes the actions lists.
- * This constructor can be used when executing an atomic swap and the counter - * party has sent an initialized tx template. - */ - public Builder(String baseTransaction) { - this.setBaseTransaction(baseTransaction); - this.actions = new ArrayList<>(); - } - - /** - * Sets the base transaction that will be added to the current template. - */ - public Builder setBaseTransaction(String baseTransaction) { - this.baseTransaction = baseTransaction; - return this; - } - - /** - * Adds an action to a transaction builder. - * @param action action to add - * @return updated builder object - */ - public Builder addAction(Action action) { - this.actions.add(action); - return this; - } - - /** - * Sets a transaction's time-to-live, which indicates how long outputs will be - * reserved for, and how long the transaction will remain valid. Passing zero will - * use the default TTL, which is 300000ms (5 minutes). - * @param ms the duration of the TTL, in milliseconds. - * @return updated builder object - */ - public Builder setTtl(long ms) { - this.ttl = ms; - return this; - } - } - - public static class QueryBuilder { - - public String txId; - - public String accountId; - - public QueryBuilder setTxId(String txId) { - this.txId = txId; - return this; - } - - public QueryBuilder setAccountId(String accountId) { - this.accountId = accountId; - return this; - } - - - /** - * call list-transactions api - * - * @param client client object that makes requests to the core - * @return Transaction Info - * @throws BytomException BytomException - */ - public List list(Client client) throws BytomException { - - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); - List transactionList = client.request("list-transactions", null, listType); - - logger.info("list-transactions:"); - logger.info("size of transactionList:" + transactionList.size()); - logger.info("all transactions:"); - for (int i = 0; i < transactionList.size(); i++) { - logger.info(transactionList.get(i).toJson()); - } - - return transactionList; - } - - public List listById(Client client) throws BytomException { - Map req = new HashMap(); - req.put("tx_id", this.txId); - req.put("detail", true); - - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); - List transactionList = client.request("list-transactions", req, listType); - - logger.info("list-transactions:"); - logger.info("size of transactionList:" + transactionList.size()); - logger.info("all transactions:"); - for (int i = 0; i < transactionList.size(); i++) { - logger.info(transactionList.get(i).toJson()); - } - - return transactionList; - } - - public List listByAccountId(Client client) throws BytomException { - Map req = new HashMap(); - req.put("account_id", this.accountId); - - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); - List transactionList = client.request("list-transactions", req, listType); - - logger.info("list-transactions:"); - logger.info("size of transactionList:" + transactionList.size()); - logger.info("all transactions:"); - for (int i = 0; i < transactionList.size(); i++) { - logger.info(transactionList.get(i).toJson()); - } - - return transactionList; - } - - /** - * call get-transaction api - * - * @param client - * @return - * @throws BytomException - */ - public Transaction get(Client client) throws BytomException { - Map req = new HashMap(); - req.put("tx_id", this.txId); - - Transaction transaction = client.request("get-transaction", req, Transaction.class); - - logger.info("get-transaction:"); - logger.info(transaction.toJson()); - - return transaction; - } - - } - - public static class SignerBuilder { - /** - * call sign-transaction api - * - * Sends a transaction template to a remote password for signing. - * - * @param client - * @param template a signed transaction template - * @param password - * @return - * @throws BytomException - */ - public Template sign(Client client, Template template, - String password) throws BytomException { - HashMap req = new HashMap(); - req.put("transaction", template); - req.put("password", password); - - Template templateResult = client.requestGet("sign-transaction", req, "transaction", - Transaction.Template.class); - - logger.info("sign-transaction:"); - logger.info(templateResult.toJson()); - - return templateResult; - } - - } - - /** - * A single input included in a transaction. - */ - public static class Input { - /** - * The alias of the account transferring the asset (possibly null if the input is - * an issuance or an unspent output is specified). - */ - @SerializedName("account_alias") - public String accountAlias; - - /** - * The id of the account transferring the asset (possibly null if the input is an - * issuance or an unspent output is specified). - */ - @SerializedName("account_id") - public String accountId; - - @SerializedName("address") - public String address; - - /** - * The number of units of the asset being issued or spent. - */ - public long amount; - - /** - * The alias of the asset being issued or spent (possibly null). - */ - @SerializedName("asset_alias") - public String assetAlias; - - /** - * The definition of the asset being issued or spent (possibly null). - */ - @SerializedName("asset_definition") - public Map assetDefinition; - - /** - * The id of the asset being issued or spent. - */ - @SerializedName("asset_id") - public String assetId; - - /** - * The id of the output consumed by this input. Null if the input is an issuance. - */ - @SerializedName("spent_output_id") - public String spentOutputId; - - /** - * The type of the input.
- * Possible values are "issue" and "spend". - */ - public String type; - - public String arbitrary; - - @SerializedName("control_program") - public String controlProgram; - - } - - /** - * A single output included in a transaction. - */ - public static class Output { - /** - * The id of the output. - */ - @SerializedName("id") - public String id; - - /** - * The type the output.
- * Possible values are "control" and "retire". - */ - public String type; - - /** - * The output's position in a transaction's list of outputs. - */ - public int position; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("control_program") - public String controlProgram; - - /** - * The id of the asset being controlled. - */ - @SerializedName("asset_id") - public String assetId; - - /** - * The alias of the asset being controlled. - */ - @SerializedName("asset_alias") - public String assetAlias; - - /** - * The definition of the asset being controlled (possibly null). - */ - @SerializedName("asset_definition") - public Map assetDefinition; - - /** - * The number of units of the asset being controlled. - */ - public long amount; - - /** - * The id of the account controlling this output (possibly null if a control - * program is specified). - */ - @SerializedName("account_id") - public String accountId; - - /** - * The alias of the account controlling this output (possibly null if a control - * program is specified). - */ - @SerializedName("account_alias") - public String accountAlias; - - @SerializedName("address") - public String address; - - } - - /** - * Base class representing actions that can be taken within a transaction. - */ - public static class Action extends HashMap { - /** - * - */ - private static final long serialVersionUID = 7948250382060074590L; - - /** - * Default constructor initializes list and sets the client token. - */ - public Action() { - // Several action types require client_token as an idempotency key. - // It's safest to include a default value for this param. - this.put("client_token", UUID.randomUUID().toString()); - } - - /** - * Represents an issuance action. - */ - public static class Issue extends Action { - /** - * - */ - private static final long serialVersionUID = -6296543909434749786L; - - /** - * Default constructor defines the action type as "issue" - */ - public Issue() { - this.put("type", "issue"); - } - - /** - * Specifies the asset to be issued using its alias.
- * Either this or {@link Issue#setAssetId(String)} must be - * called. - * @param alias alias of the asset to be issued - * @return updated action object - */ - public Issue setAssetAlias(String alias) { - this.put("asset_alias", alias); - return this; - } - - /** - * Specifies the asset to be issued using its id.
- * Either this or {@link Issue#setAssetAlias(String)} must be - * called. - * @param id id of the asset to be issued - * @return updated action object - */ - public Issue setAssetId(String id) { - this.put("asset_id", id); - return this; - } - - /** - * Specifies the amount of the asset to be issued.
- * Must be called. - * @param amount number of units of the asset to be issued - * @return updated action object - */ - public Issue setAmount(long amount) { - this.put("amount", amount); - return this; - } - } - - /** - * Represents a spend action taken on a particular account. - */ - public static class SpendFromAccount extends Action { - /** - * - */ - private static final long serialVersionUID = 6444162327409625893L; - - /** - * Default constructor defines the action type as "spend_account" - */ - public SpendFromAccount() { - this.put("type", "spend_account"); - } - - /** - * Specifies the spending account using its alias.
- * Either this or {@link SpendFromAccount#setAccountId(String)} must - * be called.
- * Must be used with {@link SpendFromAccount#setAssetAlias(String)} - * . - * @param alias alias of the spending account - * @return updated action object - */ - public SpendFromAccount setAccountAlias(String alias) { - this.put("account_alias", alias); - return this; - } - - /** - * Specifies the spending account using its id.
- * Either this or {@link SpendFromAccount#setAccountAlias(String)} - * must be called.
- * Must be used with {@link SpendFromAccount#setAssetId(String)} - * . - * @param id id of the spending account - * @return updated action object - */ - public SpendFromAccount setAccountId(String id) { - this.put("account_id", id); - return this; - } - - /** - * Specifies the asset to be spent using its alias.
- * Either this or {@link SpendFromAccount#setAssetId(String)} must be - * called.
- * Must be used with {@link SpendFromAccount#setAccountAlias(String)} - * . - * @param alias alias of the asset to be spent - * @return updated action object - */ - public SpendFromAccount setAssetAlias(String alias) { - this.put("asset_alias", alias); - return this; - } - - /** - * Specifies the asset to be spent using its id.
- * Either this or {@link SpendFromAccount#setAssetAlias(String)} must - * be called.
- * Must be used with {@link SpendFromAccount#setAccountId(String)} - * .
- * @param id id of the asset to be spent - * @return updated action object - */ - public SpendFromAccount setAssetId(String id) { - this.put("asset_id", id); - return this; - } - - /** - * Specifies the amount of asset to be spent.
- * Must be called. - * @param amount number of units of the asset to be spent - * @return updated action object - */ - public SpendFromAccount setAmount(long amount) { - this.put("amount", amount); - return this; - } - } - - /** - * Represents a control action taken on a particular account. - */ - public static class ControlWithAccount extends Action { - /** - * - */ - private static final long serialVersionUID = -1067464339402520620L; - - /** - * Default constructor defines the action type as "control_account" - */ - public ControlWithAccount() { - this.put("type", "control_account"); - } - - /** - * Specifies the controlling account using its alias.
- * Either this or {@link ControlWithAccount#setAccountId(String)} must - * be called.
- * Must be used with {@link ControlWithAccount#setAssetAlias(String)} - * . - * @param alias alias of the controlling account - * @return updated action object - */ - public ControlWithAccount setAccountAlias(String alias) { - this.put("account_alias", alias); - return this; - } - - /** - * Specifies the controlling account using its id.
- * Either this or {@link ControlWithAccount#setAccountAlias(String)} - * must be called.
- * Must be used with {@link ControlWithAccount#setAssetId(String)} - * . - * @param id id of the controlling account - * @return updated action object - */ - public ControlWithAccount setAccountId(String id) { - this.put("account_id", id); - return this; - } - - /** - * Specifies the asset to be controlled using its alias.
- * Either this or {@link ControlWithAccount#setAssetId(String)} must - * be called.
- * Must be used with - * {@link ControlWithAccount#setAccountAlias(String)}. - * @param alias alias of the asset to be controlled - * @return updated action object - */ - public ControlWithAccount setAssetAlias(String alias) { - this.put("asset_alias", alias); - return this; - } - - /** - * Specifies the asset to be controlled using its id.
- * Either this or {@link ControlWithAccount#setAssetAlias(String)} - * must be called.
- * Must be used with {@link ControlWithAccount#setAccountId(String)} - * . - * @param id id of the asset to be controlled - * @return updated action object - */ - public ControlWithAccount setAssetId(String id) { - this.put("asset_id", id); - return this; - } - - /** - * Specifies the amount of the asset to be controlled.
- * Must be called. - * @param amount number of units of the asset to be controlled - * @return updated action object - */ - public ControlWithAccount setAmount(long amount) { - this.put("amount", amount); - return this; - } - - } - - /** - * Represents a control action taken on a particular address. - */ - public static class ControlWithAddress extends Action { - /** - * - */ - private static final long serialVersionUID = 1292007349260961499L; - - /** - * Default constructor defines the action type as "control_address" - */ - public ControlWithAddress() { - this.put("type", "control_address"); - } - - public ControlWithAddress setAddress(String address) { - this.put("address", address); - return this; - } - - /** - * Specifies the asset to be controlled using its alias.
- * Either this or {@link ControlWithAddress#setAssetId(String)} must - * be called.
- * @param alias alias of the asset to be controlled - * @return updated action object - */ - public ControlWithAddress setAssetAlias(String alias) { - this.put("asset_alias", alias); - return this; - } - - /** - * Specifies the asset to be controlled using its id.
- * Either this or {@link ControlWithAccount#setAssetAlias(String)} - * must be called.
- * @param id id of the asset to be controlled - * @return updated action object - */ - public ControlWithAddress setAssetId(String id) { - this.put("asset_id", id); - return this; - } - - /** - * Specifies the amount of the asset to be controlled.
- * Must be called. - * @param amount number of units of the asset to be controlled - * @return updated action object - */ - public ControlWithAddress setAmount(long amount) { - this.put("amount", amount); - return this; - } - - } - - /** - * Use this action to pay assets into a {@link Receiver}. - */ - public static class ControlWithReceiver extends Action { - /** - * - */ - private static final long serialVersionUID = 7280759134960453401L; - - /** - * Default constructor. - */ - public ControlWithReceiver() { - this.put("type", "control_receiver"); - } - - /** - * Specifies the receiver that is being paid to. - * - * @param receiver the receiver being paid to - * @return this ControlWithReceiver object - */ - public ControlWithReceiver setReceiver(Receiver receiver) { - this.put("receiver", receiver); - return this; - } - - /** - * Specifies the asset to be controlled using its alias. - *

- * Either this or {@link ControlWithReceiver#setAssetId(String)} must - * be called. - * @param alias unique alias of the asset to be controlled - * @return this ControlWithReceiver object - */ - public ControlWithReceiver setAssetAlias(String alias) { - this.put("asset_alias", alias); - return this; - } - - /** - * Specifies the asset to be controlled using its id. - *

- * Either this or {@link ControlWithReceiver#setAssetAlias(String)} - * must be called. - * @param id unique ID of the asset to be controlled - * @return this ControlWithReceiver object - */ - public ControlWithReceiver setAssetId(String id) { - this.put("asset_id", id); - return this; - } - - /** - * Specifies the amount of the asset to be controlled. - *

- * Must be called. - * @param amount the number of units of the asset to be controlled - * @return this ControlWithReceiver object - */ - public ControlWithReceiver setAmount(long amount) { - this.put("amount", amount); - return this; - } - - } - - /** - * Represents a retire action. - */ - public static class Retire extends Action { - /** - * - */ - private static final long serialVersionUID = -8434272436211832706L; - - /** - * Default constructor defines the action type as "control_program" - */ - public Retire() { - this.put("type", "retire"); - } - - /** - * Specifies the amount of the asset to be retired.
- * Must be called. - * @param amount number of units of the asset to be retired - * @return updated action object - */ - public Retire setAmount(long amount) { - this.put("amount", amount); - return this; - } - - /** - * Specifies the asset to be retired using its alias.
- * Either this or {@link Retire#setAssetId(String)} must be - * called. - * @param alias alias of the asset to be retired - * @return updated action object - */ - public Retire setAssetAlias(String alias) { - this.put("asset_alias", alias); - return this; - } - - /** - * Specifies the asset to be retired using its id.
- * Either this or {@link Retire#setAssetAlias(String)} must be - * called. - * @param id id of the asset to be retired - * @return updated action object - */ - public Retire setAssetId(String id) { - this.put("asset_id", id); - return this; - } - - public Retire setAccountAlias(String alias) { - this.put("account_alias", alias); - return this; - } - - public Retire setAccountId(String id) { - this.put("account_id", id); - return this; - } - - } - - /** - * Sets a k,v parameter pair. - * @param key the key on the parameter object - * @param value the corresponding value - * @return updated action object - */ - public Action setParameter(String key, Object value) { - this.put(key, value); - return this; - } - } - - public static class Template { - /** - * A hex-encoded representation of a transaction template. - */ - @SerializedName("raw_transaction") - public String rawTransaction; - - /** - * The list of signing instructions for inputs in the transaction. - */ - @SerializedName("signing_instructions") - public List signingInstructions; - - /** - * For core use only. - */ - @SerializedName("local") - private boolean local; - - /** - * False (the default) makes the transaction "final" when signing, preventing - * further changes - the signature program commits to the transaction's signature - * hash. True makes the transaction extensible, committing only to the elements in - * the transaction so far, permitting the addition of new elements. - */ - @SerializedName("allow_additional_actions") - private boolean allowAdditionalActions; - - /** - * allowAdditionalActions causes the transaction to be signed so that it can be - * used as a base transaction in a multiparty trade flow. To enable this setting, - * call this method after building the transaction, but before sending it to the - * signer. - * - * All participants in a multiparty trade flow should call this method except for - * the last signer. Do not call this option if the transaction is complete, i.e. - * if it will not be used as a base transaction. - * @return updated transaction template - */ - public Template allowAdditionalActions() { - this.allowAdditionalActions = true; - return this; - } - - /** - * A single signing instruction included in a transaction template. - */ - public static class SigningInstruction { - /** - * The input's position in a transaction's list of inputs. - */ - public int position; - - /** - * A list of components used to coordinate the signing of an input. - */ - @SerializedName("witness_components") - public WitnessComponent[] witnessComponents; - } - - /** - * A single witness component, holding information that will become the input - * witness. - */ - public static class WitnessComponent { - /** - * The type of witness component.
- * Possible types are "data" and "raw_tx_signature". - */ - public String type; - - /** - * Data to be included in the input witness (null unless type is "data"). - */ - public String value; - - /** - * The number of signatures required for an input (null unless type is - * "signature"). - */ - public int quorum; - - /** - * The list of keys to sign with (null unless type is "signature"). - */ - public KeyID[] keys; - - /** - * The list of signatures made with the specified keys (null unless type is - * "signature"). - */ - public String[] signatures; - } - - /** - * A class representing a derived signing key. - */ - public static class KeyID { - /** - * The extended public key associated with the private key used to sign. - */ - public String xpub; - - /** - * The derivation path of the extended public key. - */ - @SerializedName("derivation_path") - public String[] derivationPath; - } - - /** - * Serializes the Address into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - } - - public static class SubmitResponse { - /** - * The transaction id. - */ - public String tx_id; - - public String toJson() { - return Utils.serializer.toJson(this); - } - - } - - /** - * Call submit-transaction api - * - * @param client - * @param template - * @return - * @throws BytomException - */ - public static SubmitResponse submit(Client client, Template template) - throws BytomException { - HashMap body = new HashMap<>(); - body.put("raw_transaction", template.rawTransaction); - return client.request("submit-transaction", body, SubmitResponse.class); - } - - public static class TransactionGas { - /** - * total consumed neu(1BTM = 10^8NEU) for execute transaction. - */ - @SerializedName("total_neu") - public int totalNeu; - - /** - * consumed neu for storage transaction . - */ - @SerializedName("storage_neu") - public int storageNeu; - /** - * consumed neu for execute VM. - */ - @SerializedName("vm_neu") - public int vmNeu; - } - - /** - * call estimate-transaction-gas api - * - * @param client - * @param template - * @return - * @throws BytomException - */ - public static TransactionGas estimateGas(Client client, Template template) - throws BytomException { - HashMap body = new HashMap<>(); - body.put("transaction_template", template); - return client.request("estimate-transaction-gas", body, TransactionGas.class); - } - - public static class Feed { - /** - * name of the transaction feed. - */ - public String alias; - /** - * filter, filter of the transaction feed. - */ - public String filter; - - /** - * param, param of the transaction feed. - */ - public TransactionFeedParam param; - - private static Logger logger = Logger.getLogger(Transaction.class); - - /** - * Serializes the TransactionFeed into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the TransactionFeed object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - public static class Builder { - - public String alias; - - public String filter; - - public Builder() { - } - - public Builder setAlias(String alias) { - this.alias = alias; - return this; - } - - public Builder setFilter(String filter) { - this.filter = filter; - return this; - } - - /** - * Call create-transaction-feed api - * - * @param client - * @throws BytomException - */ - public void create(Client client) throws BytomException { - client.request("create-transaction-feed", this); - logger.info("create-transaction-feed"); - } - } - - /** - * Call get-transaction-feed api - * - * @param client - * @param alias - * @return - * @throws BytomException - */ - public static Feed getByAlias(Client client, String alias) throws BytomException { - Map req = new HashMap(); - req.put("alias", alias); - - // the return param contains txfeed. - Feed transactionFeed = client.requestGet("get-transaction-feed", - req, "txfeed", Feed.class); - logger.info("get-transaction-feed."); - logger.info(transactionFeed.toJson()); - - return transactionFeed; - } - - /** - * Call update-transaction-feed api - * - * @param client - * @param alias - * @param filter - * @throws BytomException - */ - public static void update(Client client, String alias, String filter) throws BytomException { - Map req = new HashMap(); - req.put("alias", alias); - req.put("filter", filter); - client.request("update-transaction-feed", req); - logger.info("update-transaction-feed successfully"); - } - - /** - * Call list-transaction-feeds api - * @param client - * @return - * @throws BytomException - */ - public static List list(Client client) throws BytomException { - - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Feed.class}); - List transactionFeedList = client.request("list-transaction-feeds", null, listType); - - logger.info("list-transaction-feeds:"); - logger.info("size of transactionList:" + transactionFeedList.size()); - for (int i =0 ;i < transactionFeedList.size();i++) { - logger.info(transactionFeedList.get(i).toJson()); - } - return transactionFeedList; - } - - /** - * Call delete-transaction-feed api - * - * @param client - * @param alias - * @throws BytomException - */ - public static void deleteByAlias(Client client, String alias) throws BytomException { - Map req = new HashMap(); - req.put("alias", alias); - client.request("delete-transaction-feed", req); - logger.info("delete-transaction-feed successfully"); - } - - - - public class TransactionFeedParam { - - /** - * assetid - */ - public String assetid; - - /** - * lowerlimit - */ - public long lowerlimit; - - /** - * upperlimit - */ - public long upperlimit; - - } - } -} diff --git a/src/main/java/io/bytom/api/UnconfirmedTransaction.java b/src/main/java/io/bytom/api/UnconfirmedTransaction.java deleted file mode 100644 index 67690a4..0000000 --- a/src/main/java/io/bytom/api/UnconfirmedTransaction.java +++ /dev/null @@ -1,197 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class UnconfirmedTransaction { - /** - * Unique identifier, or transaction hash, of a transaction. - */ - private String id; - - /** - * version - */ - private Integer version; - - /** - * size - */ - private Integer size; - /** - * time_range - */ - @SerializedName("time_range") - private Integer timeRange; - - /** - * status - */ - @SerializedName("status_fail") - private boolean statusFail; - - /** - * List of specified inputs for a transaction. - */ - private List inputs; - - /** - * List of specified outputs for a transaction. - */ - private List outputs; - - private static Logger logger = Logger.getLogger(UnconfirmedTransaction.class); - - /** - * Serializes the UnconfirmedTransaction into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the UnconfirmedTransaction object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * Call get-unconfirmed-transaction api - * - * @param client - * @param txId - * @return - * @throws BytomException - */ - public static UnconfirmedTransaction get(Client client, String txId) throws BytomException { - Map req = new HashMap(); - req.put("tx_id", txId); - UnconfirmedTransaction UCTX = client.request("get-unconfirmed-transaction", - req, UnconfirmedTransaction.class); - - logger.info("get-unconfirmed-transaction:"); - logger.info(UCTX.toJson()); - - return UCTX; - } - - public static UTXResponse list(Client client) throws BytomException { - UTXResponse utxResponse = - client.request("list-unconfirmed-transactions", null, UTXResponse.class); - - logger.info("list-unconfirmed-transactions:"); - logger.info(utxResponse.toJson()); - - return utxResponse; - } - - public static class UTXResponse { - - @SerializedName("total") - public Integer total; - - @SerializedName("tx_ids") - public List txIds; - - public String toJson() { - return Utils.serializer.toJson(this); - } - } - - public static class AnnotatedInput { - - /** - * address - */ - private String address; - - /** - * The number of units of the asset being issued or spent. - */ - private long amount; - - /** - * The definition of the asset being issued or spent (possibly null). - */ - @SerializedName("asset_definition") - private Map assetDefinition; - - /** - * The id of the asset being issued or spent. - */ - @SerializedName("asset_id") - private String assetId; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("control_program") - private String controlProgram; - - /** - * The id of the output consumed by this input. Null if the input is an - * issuance. - */ - @SerializedName("spent_output_id") - private String spentOutputId; - - /** - * The type of the input.
- * Possible values are "issue" and "spend". - */ - private String type; - } - - public static class AnnotatedOutput { - - /** - * address - */ - private String address; - - /** - * The number of units of the asset being controlled. - */ - private long amount; - - /** - * The definition of the asset being controlled (possibly null). - */ - @SerializedName("asset_definition") - private Map assetDefinition; - - /** - * The id of the asset being controlled. - */ - @SerializedName("asset_id") - public String assetId; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("control_program") - private String controlProgram; - - /** - * The id of the output. - */ - @SerializedName("id") - private String id; - - /** - * The output's position in a transaction's list of outputs. - */ - private Integer position; - - /** - * The type the output.
- * Possible values are "control" and "retire". - */ - private String type; - - } -} diff --git a/src/main/java/io/bytom/api/UnspentOutput.java b/src/main/java/io/bytom/api/UnspentOutput.java deleted file mode 100644 index 1447093..0000000 --- a/src/main/java/io/bytom/api/UnspentOutput.java +++ /dev/null @@ -1,132 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.ParameterizedTypeImpl; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.lang.reflect.Type; -import java.util.List; - -public class UnspentOutput { - /** - * The id of the account controlling this output (possibly null if a control program - * is specified). - */ - @SerializedName("account_id") - public String accountId; - - /** - * The alias of the account controlling this output (possibly null if a control - * program is specified). - */ - @SerializedName("account_alias") - public String accountAlias; - - /** - * The id of the asset being controlled. - */ - @SerializedName("asset_id") - public String assetId; - - /** - * The alias of the asset being controlled. - */ - @SerializedName("asset_alias") - public String assetAlias; - - /** - * The number of units of the asset being controlled. - */ - public long amount; - - /** - * address of account - */ - public String address; - - /** - * whether the account address is change - */ - public boolean change; - - /** - * The ID of the output. - */ - @SerializedName("id") - public String id; - - /** - * The control program which must be satisfied to transfer this output. - */ - @SerializedName("program") - public String program; - - @SerializedName("control_program_index") - public String controlProgramIndex; - - /** - * source unspent output id - */ - @SerializedName("source_id") - public String sourceId; - - /** - * position of source unspent output id in block - */ - @SerializedName("source_pos") - public int sourcePos; - - /** - * The definition of the asset being controlled (possibly null). - */ - @SerializedName("valid_height") - public int validHeight; - - private static Logger logger = Logger.getLogger(UnspentOutput.class); - - /** - * Serializes the Address into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - public static class QueryBuilder { - - /** - * id of unspent output. - */ - public String id; - - public QueryBuilder setId(String id) { - this.id = id; - return this; - } - - /** - * call list-unspent-outputs api - * - * @param client client object that makes requests to the core - * @return - * @throws BytomException BytomException - */ - public List list(Client client) throws BytomException { - - Type listType = new ParameterizedTypeImpl(List.class, new Class[]{UnspentOutput.class}); - List unspentOutputList = client.request("list-unspent-outputs", this, listType); - logger.info("list-unspent-outputs:"); - logger.info("size of unspentOutputList:" + unspentOutputList.size()); - for (UnspentOutput UTXO : unspentOutputList) { - logger.info(UTXO.toJson()); - } - - return unspentOutputList; - } - - } -} diff --git a/src/main/java/io/bytom/api/Wallet.java b/src/main/java/io/bytom/api/Wallet.java deleted file mode 100644 index 5145757..0000000 --- a/src/main/java/io/bytom/api/Wallet.java +++ /dev/null @@ -1,153 +0,0 @@ -package io.bytom.api; - -import com.google.gson.annotations.SerializedName; -import io.bytom.common.Utils; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - *

Wallet Class

- */ -public class Wallet { - - @SerializedName("account_image") - public AccountImage accountImage; - - @SerializedName("asset_image") - public AssetImage assetImage; - - @SerializedName("key_images") - public KeyImages keyImages; - - private static Logger logger = Logger.getLogger(Wallet.class); - - /** - * Serializes the Address into a form that is safe to transfer over the wire. - * - * @return the JSON-serialized representation of the Receiver object - */ - public String toJson() { - return Utils.serializer.toJson(this); - } - - /** - * Call backup-wallet api - * - * @param client - * @return - * @throws BytomException - */ - public static Wallet backupWallet(Client client) throws BytomException { - Wallet wallet = client.request("backup-wallet", null, Wallet.class); - - logger.info("backup-wallet:"); - logger.info(wallet.toJson()); - - return wallet; - } - - /** - * Call restore-wallet api - * - * @param client - * @param accountImage - * @param assetImage - * @param keyImages - * @throws BytomException - */ - public static void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages) throws BytomException{ - Map body = new HashMap(); - body.put("account_image", accountImage); - body.put("asset_image", assetImage); - body.put("key_images", keyImages); - - logger.info("restore-wallet:"); - logger.info(body.toString()); - - client.request("restore-wallet", body); - } - - public static class AccountImage { - - public Slices[] slices; - - public static class Slices { - - @SerializedName("contract_index") - public int contractIndex; - - public Account account; - - public static class Account { - - public String type; - - public List xpubs; - - public int quorum; - - @SerializedName("key_index") - public int keyIndex; - - public String id; - - public String alias; - - } - - } - } - - public static class AssetImage { - - public Assets[] assets; - - public static class Assets { - public String type; - public List xpubs; - public int quorum; - public String id; - public String alias; - public Map definition; - @SerializedName("key_index") - public int keyIndex; - @SerializedName("vm_version") - public int vmVersion; - @SerializedName("asset_image") - public String issueProgram; - @SerializedName("raw_definition_byte") - public String rawDefinitionByte; - } - } - - public static class KeyImages { - - public Xkeys[] xkeys; - - public static class Xkeys { - - public Crypto crypto; - public String id; - public String type; - public int version; - public String alias; - public String xpub; - - public static class Crypto { - public String cipher; - public String ciphertext; - public Map cipherparams; - public String kdf; - public Map kdfparams; - public String mac; - } - - } - } - -} diff --git a/src/main/java/io/bytom/common/Configuration.java b/src/main/java/io/bytom/common/Configuration.java deleted file mode 100644 index 21dd949..0000000 --- a/src/main/java/io/bytom/common/Configuration.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.bytom.common; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Properties; - -public class Configuration { - - private static Properties props = new Properties(); - static { - try { - props.load(Thread.currentThread().getContextClassLoader() - .getResourceAsStream("config.properties")); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static String getValue(String key) { - return props.getProperty(key); - } - - public static void updateProperties(String key, String value) { - props.setProperty(key, value); - } - -} diff --git a/src/main/java/io/bytom/common/ParameterizedTypeImpl.java b/src/main/java/io/bytom/common/ParameterizedTypeImpl.java deleted file mode 100644 index d66be8c..0000000 --- a/src/main/java/io/bytom/common/ParameterizedTypeImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.bytom.common; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; - -public class ParameterizedTypeImpl implements ParameterizedType { - - private final Class raw; - private final Type[] args; - - public ParameterizedTypeImpl(Class raw, Type[] args) { - this.raw = raw; - this.args = args != null ? args : new Type[0]; - } - - @Override - public Type[] getActualTypeArguments() { - return args; - } - - @Override - public Type getRawType() { - return raw; - } - - @Override - public Type getOwnerType() { - return null; - } -} diff --git a/src/main/java/io/bytom/common/Utils.java b/src/main/java/io/bytom/common/Utils.java deleted file mode 100644 index 7a7f69e..0000000 --- a/src/main/java/io/bytom/common/Utils.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.bytom.common; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -public class Utils { - public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; - public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create(); -} diff --git a/src/main/java/io/bytom/exception/APIException.java b/src/main/java/io/bytom/exception/APIException.java deleted file mode 100644 index 52ff64d..0000000 --- a/src/main/java/io/bytom/exception/APIException.java +++ /dev/null @@ -1,169 +0,0 @@ -package io.bytom.exception; - -import com.google.gson.annotations.SerializedName; - -/** - * APIException wraps errors returned by the API. - * Each error contains a brief description in addition to a unique error code.
- * The error code can be used by Chain Support to diagnose the exact cause of the error. - * The mapping of error codes to messages is as follows:

- * - *

General errors

- * CH001 - Request timed out - * CH002 - Not found - * CH003 - Invalid request body - * CH004 - Invalid request header - * CH006 - Not found - * - *

Account/Asset errors

- * CH200 - Quorum must be greater than 1 and less than or equal to the length of xpubs - * CH201 - Invalid xpub format - * CH202 - At least one xpub is required - * CH203 - Retrieved type does not match expected type - * - *

Access token errors

- * CH300 - Malformed or empty access token id - * CH301 - Access tokens must be type client or network - * CH302 - Access token id is already in use - * CH310 - The access token used to authenticate this request cannot be deleted - * - *

Query errors

- * CH600 - Malformed pagination parameter `after` - * CH601 - Incorrect number of parameters to filter - * CH602 - Malformed query filter - * - *

Transaction errors

- * CH700 - Reference data does not match previous transaction's reference data
- * CH701 - Invalid action type
- * CH702 - Invalid alias on action
- * CH730 - Missing raw transaction
- * CH731 - Too many signing instructions in template for transaction
- * CH732 - Invalid transaction input index
- * CH733 - Invalid signature script component
- * CH734 - Missing signature in template
- * CH735 - Transaction rejected
- * CH760 - Insufficient funds for tx
- * CH761 - Some outputs are reserved; try again
- */ -public class APIException extends BytomException { - /** - * Unique identifier for the error. - */ - public String code; - - /** - * Message describing the general nature of the error. - */ - @SerializedName("message") - public String chainMessage; - - /** - * Additional information about the error (possibly null). - */ - public String detail; - - /** - * Specifies whether the error is temporary, or a change to the request is necessary. - */ - public boolean temporary; - - /** - * Unique identifier of the request to the server. - */ - public String requestId; - - /** - * HTTP status code returned by the server. - */ - public int statusCode; - - /** - * Initializes exception with its message and requestId attributes. - * @param message error message - * @param requestId unique identifier of the request - */ - public APIException(String message, String requestId) { - super(message); - this.requestId = requestId; - } - - /** - * Intitializes exception with its code, message, detail & temporary field set. - * @param code error code - * @param message error message - * @param detail additional error information - * @param temporary unique identifier of the request - */ - public APIException(String code, String message, String detail, boolean temporary) { - super(message); - this.chainMessage = message; - this.code = code; - this.detail = detail; - this.temporary = temporary; - } - - /** - * Initializes exception with its code, message, detail & requestId attributes. - * @param code error code - * @param message error message - * @param detail additional error information - * @param requestId unique identifier of the request - */ - public APIException(String code, String message, String detail, String requestId) { - super(message); - this.chainMessage = message; - this.code = code; - this.detail = detail; - this.requestId = requestId; - } - - /** - * Initializes exception with all of its attributes. - * @param code error code - * @param message error message - * @param detail additional error information - * @param temporary specifies if the error is temporary - * @param requestId unique identifier of the request - * @param statusCode HTTP status code - */ - public APIException( - String code, - String message, - String detail, - boolean temporary, - String requestId, - int statusCode) { - super(message); - this.chainMessage = message; - this.code = code; - this.detail = detail; - this.temporary = temporary; - this.requestId = requestId; - this.statusCode = statusCode; - } - - /** - * Constructs a detailed message of the error. - * @return detailed error message - */ - @Override - public String getMessage() { - String s = ""; - - if (this.code != null && this.code.length() > 0) { - s += "Code: " + this.code + " "; - } - - s += "Message: " + this.chainMessage; - - if (this.detail != null && this.detail.length() > 0) { - s += " Detail: " + this.detail; - } - - if (this.requestId != null) { - s += " Request-ID: " + this.requestId; - } - - return s; - } -} diff --git a/src/main/java/io/bytom/exception/BadURLException.java b/src/main/java/io/bytom/exception/BadURLException.java deleted file mode 100644 index dd16377..0000000 --- a/src/main/java/io/bytom/exception/BadURLException.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.bytom.exception; - -/** - * BadURLException wraps errors due to malformed URLs. - */ -public class BadURLException extends BytomException { - /** - * Initializes exception with its message attribute. - * @param message error message - */ - public BadURLException(String message) { - super(message); - } -} diff --git a/src/main/java/io/bytom/exception/BuildException.java b/src/main/java/io/bytom/exception/BuildException.java deleted file mode 100644 index 19a2f89..0000000 --- a/src/main/java/io/bytom/exception/BuildException.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.bytom.exception; - -import com.google.gson.annotations.SerializedName; - -import java.util.List; - -/** - * BuildException wraps errors returned by the build-transaction endpoint. - */ -public class BuildException extends APIException { - - public BuildException(String message, String requestId) { - super(message, requestId); - } - - public static class ActionError extends APIException { - - public static class Data { - /** - * The index of the action that caused this error. - */ - @SerializedName("index") - public Integer index; - } - - public ActionError(String message, String requestId) { - super(message, requestId); - } - - /** - * Additional data pertaining to the error. - */ - public Data data; - } - - public static class Data { - /** - * A list of errors resulting from building actions. - */ - @SerializedName("actions") - public List actionErrors; - } - - /** - * Extra data associated with this error, if any. - */ - @SerializedName("data") - public Data data; -} diff --git a/src/main/java/io/bytom/exception/BytomException.java b/src/main/java/io/bytom/exception/BytomException.java deleted file mode 100644 index 4043065..0000000 --- a/src/main/java/io/bytom/exception/BytomException.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.bytom.exception; - -/** - * Base exception class for the Chain Core SDK. - */ -public class BytomException extends Exception { - /** - * Default constructor. - */ - public BytomException() { - super(); - } - - /** - * Initializes exception with its message attribute. - * - * @param message error message - */ - public BytomException(String message) { - super(message); - } - - /** - * Initializes a new exception while storing the original cause. - * - * @param message the error message - * @param cause the cause of the exception - */ - public BytomException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/bytom/exception/ConfigurationException.java b/src/main/java/io/bytom/exception/ConfigurationException.java deleted file mode 100644 index 695e873..0000000 --- a/src/main/java/io/bytom/exception/ConfigurationException.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.bytom.exception; - -/** - * ConfigurationException wraps errors during client configuration. - */ -public class ConfigurationException extends BytomException { - /** - * Initializes exception with its message attribute. - * @param message error message - */ - public ConfigurationException(String message) { - super(message); - } - - /** - * Initializes new exception while storing original cause. - * @param message the error message - * @param cause the original cause - */ - public ConfigurationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/bytom/exception/ConnectivityException.java b/src/main/java/io/bytom/exception/ConnectivityException.java deleted file mode 100644 index 4f50197..0000000 --- a/src/main/java/io/bytom/exception/ConnectivityException.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.bytom.exception; - -import com.squareup.okhttp.Response; - -import java.io.IOException; - -/** - * ConnectivityException wraps errors due to connectivity issues with the server. - */ -public class ConnectivityException extends BytomException { - /** - * Initializes exception with its message attribute. - * @param resp the server response used to create error message - */ - public ConnectivityException(Response resp) { - super(formatMessage(resp)); - } - - /** - * Parses the the server response into a detailed error message. - * @param resp the server response - * @return detailed error message - */ - private static String formatMessage(Response resp) { - String s = - "Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings."; - // TODO(kr): include client-generated reqid here once we have that. - String body; - try { - body = resp.body().string(); - } catch (IOException ex) { - body = "[unable to read response body: " + ex.toString() + "]"; - } - return String.format("%s status=%d body=%s", s, resp.code(), body); - } -} diff --git a/src/main/java/io/bytom/exception/HTTPException.java b/src/main/java/io/bytom/exception/HTTPException.java deleted file mode 100644 index 04646bd..0000000 --- a/src/main/java/io/bytom/exception/HTTPException.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.bytom.exception; - -/** - * HTTPException wraps generic HTTP errors. - */ -public class HTTPException extends BytomException { - /** - * Initializes exception with its message attribute. - * @param message error message - */ - public HTTPException(String message) { - super(message); - } - - /** - * Initializes new exception while storing original cause. - * @param message the error message - * @param cause the original cause - */ - public HTTPException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/bytom/exception/JSONException.java b/src/main/java/io/bytom/exception/JSONException.java deleted file mode 100644 index a81cef6..0000000 --- a/src/main/java/io/bytom/exception/JSONException.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.bytom.exception; - -/** - * JSONException wraps errors due to marshaling/unmarshaling json payloads. - */ -public class JSONException extends BytomException { - - /** - * Unique indentifier of the request to the server. - */ - public String requestId; - - /** - * Default constructor. - */ - public JSONException(String message) { - super(message); - } - - /** - * Initializes exception with its message and requestId attributes. - * Use this constructor in context of an API call. - * - * @param message error message - * @param requestId unique identifier of the request - */ - public JSONException(String message, String requestId) { - super(message); - this.requestId = requestId; - } - - public String getMessage() { - String message = "Message: " + super.getMessage(); - if (requestId != null) { - message += " Request-ID: " + requestId; - } - return message; - } -} diff --git a/src/main/java/io/bytom/http/BatchResponse.java b/src/main/java/io/bytom/http/BatchResponse.java deleted file mode 100644 index da29b75..0000000 --- a/src/main/java/io/bytom/http/BatchResponse.java +++ /dev/null @@ -1,140 +0,0 @@ -package io.bytom.http; - -import io.bytom.exception.*; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.squareup.okhttp.Response; - -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.*; - -/** - * BatchResponse provides a convenient interface for handling the results of - * batched API calls. The response contains one success or error per outgoing - * request item in the batch. Errors are always of type APIExcpetion. - */ -public class BatchResponse { - private Response response; - private Map successesByIndex = new LinkedHashMap<>(); - private Map errorsByIndex = new LinkedHashMap<>(); - - /** - * This constructor is used when deserializing a response from an API call. - */ - public BatchResponse(Response response, Gson serializer, Type tClass, Type eClass) - throws BytomException, IOException { - this.response = response; - - try { - JsonArray root = new JsonParser().parse(response.body().charStream()).getAsJsonArray(); - for (int i = 0; i < root.size(); i++) { - JsonElement elem = root.get(i); - - // Test for interleaved errors - APIException err = serializer.fromJson(elem, eClass); - if (err.code != null) { - errorsByIndex.put(i, err); - continue; - } - - successesByIndex.put(i, (T) serializer.fromJson(elem, tClass)); - } - } catch (IllegalStateException e) { - throw new JSONException( - "Unable to read body: " + e.getMessage(), response.headers().get("Chain-Request-ID")); - } - } - - /** - * This constructor is used for synthetically generating a batch response - * object from a map of successes and a map of errors. It ensures that - * the successes and errors are stored in an order-preserving fashion. - */ - public BatchResponse(Map successes, Map errors) { - List successIndexes = new ArrayList<>(); - Iterator successIter = successes.keySet().iterator(); - while (successIter.hasNext()) successIndexes.add(successIter.next()); - Collections.sort(successIndexes); - for (int i : successIndexes) successesByIndex.put(i, successes.get(i)); - - List errorIndexes = new ArrayList<>(); - Iterator errorIter = errors.keySet().iterator(); - while (errorIter.hasNext()) errorIndexes.add(errorIter.next()); - Collections.sort(errorIndexes); - for (int i : errorIndexes) errorsByIndex.put(i, errors.get(i)); - } - - /** - * Returns the internal response object. - */ - public Response response() { - return response; - } - - /** - * Returns the total number of response objects. This should equal the number - * of request objects in the batch. - */ - public int size() { - return successesByIndex.size() + errorsByIndex.size(); - } - - /** - * Returns whether the request object at the given index produced a success. - * @param index the index of the request object - */ - public boolean isSuccess(int index) { - return successesByIndex.containsKey(index); - } - - /** - * Returns whether the request object at the given index produced an error. - * @param index the index of the request object - */ - public boolean isError(int index) { - return errorsByIndex.containsKey(index); - } - - /** - * Returns a list of successful response objects in the batch. The order of - * the list corresponds to the order of the request objects that produced the - * successes. - */ - public List successes() { - List res = new ArrayList<>(); - res.addAll(successesByIndex.values()); - return res; - } - - /** - * Returns a list of error objects in the batch. The order of the list - * corresponds to the order of the request objects that produced the - * errors. - */ - public List errors() { - List res = new ArrayList<>(); - res.addAll(errorsByIndex.values()); - return res; - } - - /** - * Returns a map of success responses, keyed by the index of the request - * object that produced the success. The set of this map's keys is mutually - * exclusive of the keys returned by errorsByIndex. - */ - public Map successesByIndex() { - return successesByIndex; - } - - /** - * Returns a map of error responses, keyed by the index of the request - * object that produced the error. The set of this map's keys is mutually - * exclusive of the keys returned by successByIndex. - */ - public Map errorsByIndex() { - return errorsByIndex; - } -} diff --git a/src/main/java/io/bytom/http/Client.java b/src/main/java/io/bytom/http/Client.java deleted file mode 100644 index 958f9f6..0000000 --- a/src/main/java/io/bytom/http/Client.java +++ /dev/null @@ -1,815 +0,0 @@ -package io.bytom.http; - -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import io.bytom.common.*; -import io.bytom.exception.*; -import com.google.gson.Gson; -import com.squareup.okhttp.*; -import org.apache.log4j.Logger; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; - -import javax.net.ssl.*; -import java.io.*; -import java.lang.reflect.Type; -import java.net.*; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.spec.KeySpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * The Client object contains all information necessary to - * perform an HTTP request against a remote API. Typically, - * an application will have a client that makes requests to - * a Chain Core, and a separate Client that makes requests - * to an HSM server. - */ -public class Client { - private String url; - private String accessToken; - private OkHttpClient httpClient; - - // Used to create empty, in-memory key stores. - private static final char[] DEFAULT_KEYSTORE_PASSWORD = "123456".toCharArray(); - private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static String version = "dev"; // updated in the static initializer - - public static Logger logger = Logger.getLogger(Client.class); - - private static class BuildProperties { - public String version; - } - - static { - InputStream in = Client.class.getClassLoader().getResourceAsStream("properties.json"); - if (in != null) { - InputStreamReader inr = new InputStreamReader(in); - version = Utils.serializer.fromJson(inr, BuildProperties.class).version; - } - } - - public static Client generateClient() throws BytomException { - - String coreURL = Configuration.getValue("bytom.api.url"); - String accessToken = Configuration.getValue("client.access.token"); - - if (coreURL == null || coreURL.isEmpty()) { - coreURL = "http://127.0.0.1:9888"; - } - - if (coreURL.endsWith("/")) { - //split the last char "/" - coreURL = coreURL.substring(0, coreURL.length()-1); - logger.info("check the coreURL is right."); - } - - return new Client(coreURL, accessToken); - } - - public Client(Builder builder) throws ConfigurationException { - if (builder.url.endsWith("/")) { - //split the last char "/" - builder.url = builder.url.substring(0, builder.url.length()-1); - } - this.url = builder.url; - this.accessToken = builder.accessToken; - this.httpClient = buildHttpClient(builder); - } - - /** - * Create a new http Client object using the default development host URL. - */ - public Client() throws BytomException { - this(new Builder()); - } - - /** - * Create a new http Client object - * - * @param url the URL of the Chain Core or HSM - */ - public Client(String url) throws BytomException { - this(new Builder().setUrl(url)); - } - - /** - * Create a new http Client object - * - * @param url the URL of the Chain Core or HSM - * @param accessToken a Client API access token - */ - public Client(String url, String accessToken) throws BytomException { - this(new Builder().setUrl(url).setAccessToken(accessToken)); - } - - /** - * Perform a single HTTP POST request against the API for a specific action. - * - * @param action The requested API action - * @param body Body payload sent to the API as JSON - * @param tClass Type of object to be deserialized from the response JSON - * @return the result of the post request - * @throws BytomException - */ - public T request(String action, Object body, final Type tClass) throws BytomException { - ResponseCreator rc = - new ResponseCreator() { - public T create(Response response, Gson deserializer) throws IOException, BytomException { - JsonElement root = new JsonParser().parse(response.body().charStream()); - JsonElement status = root.getAsJsonObject().get("status"); - JsonElement data = root.getAsJsonObject().get("data"); - if (status != null && status.toString().contains("fail")) { - throw new BytomException(root.getAsJsonObject().get("msg").toString()); - } else if (data != null) { - return deserializer.fromJson(data, tClass); - } else { - return deserializer.fromJson(response.body().charStream(), tClass); - } - } - }; - return post(action, body, rc); - } - - /** - * Perform a single HTTP POST request against the API for a specific action, - * ignoring the body of the response. - * - * @param action The requested API action - * @param body Body payload sent to the API as JSON - * @throws BytomException - */ - public void request(String action, Object body) throws BytomException { - ResponseCreator rc = - new ResponseCreator() { - public Object create(Response response, Gson deserializer) throws IOException, BytomException { - JsonElement root = new JsonParser().parse(response.body().charStream()); - JsonElement status = root.getAsJsonObject().get("status"); - JsonElement data = root.getAsJsonObject().get("data"); - if (status != null && status.toString().contains("fail")) { - throw new BytomException(root.getAsJsonObject().get("msg").toString()); - } - return null; - } - }; - post(action, body, rc); - } - - /** - * return the value of named as key from json - * - * @param action - * @param body - * @param key - * @param tClass - * @param - * @return - * @throws BytomException - */ - public T requestGet(String action, Object body, final String key, final Type tClass) - throws BytomException { - ResponseCreator rc = new ResponseCreator() { - public T create(Response response, Gson deserializer) throws IOException, - BytomException { - JsonElement root = new JsonParser().parse(response.body().charStream()); - JsonElement status = root.getAsJsonObject().get("status"); - JsonElement data = root.getAsJsonObject().get("data"); - - if (status != null && status.toString().contains("fail")) - throw new BytomException(root.getAsJsonObject().get("msg").toString()); - else if (data != null) - return deserializer.fromJson(data.getAsJsonObject().get(key), tClass); - else - return deserializer.fromJson(response.body().charStream(), tClass); - } - }; - return post(action, body, rc); - } - - /** - * Perform a single HTTP POST request against the API for a specific action. - * Use this method if you want batch semantics, i.e., the endpoint response - * is an array of valid objects interleaved with arrays, once corresponding to - * each input object. - * - * @param action The requested API action - * @param body Body payload sent to the API as JSON - * @param tClass Type of object to be deserialized from the response JSON - * @param eClass Type of error object to be deserialized from the response JSON - * @return the result of the post request - * @throws BytomException - */ - public BatchResponse batchRequest( - String action, Object body, final Type tClass, final Type eClass) throws BytomException { - ResponseCreator> rc = - new ResponseCreator>() { - public BatchResponse create(Response response, Gson deserializer) - throws BytomException, IOException { - return new BatchResponse(response, deserializer, tClass, eClass); - } - }; - return post(action, body, rc); - } - - /** - * Perform a single HTTP POST request against the API for a specific action. - * Use this method if you want single-item semantics (creating single assets, - * building single transactions) but the API endpoint is implemented as a - * batch call. - *

- * Because request bodies for batch calls do not share a consistent format, - * this method does not perform any automatic arrayification of outgoing - * parameters. Remember to arrayify your request objects where appropriate. - * - * @param action The requested API action - * @param body Body payload sent to the API as JSON - * @param tClass Type of object to be deserialized from the response JSON - * @return the result of the post request - * @throws BytomException - */ - public T singletonBatchRequest( - String action, Object body, final Type tClass, final Type eClass) throws BytomException { - ResponseCreator rc = - new ResponseCreator() { - public T create(Response response, Gson deserializer) throws BytomException, IOException { - BatchResponse batch = new BatchResponse<>(response, deserializer, tClass, eClass); - - List errors = batch.errors(); - if (errors.size() == 1) { - // This throw must occur within this lambda in order for APIClient's - // retry logic to take effect. - throw errors.get(0); - } - - List successes = batch.successes(); - if (successes.size() == 1) { - return successes.get(0); - } - - // We should never get here, unless there is a bug in either the SDK or - // API code, causing a non-singleton response. - /* - throw new BytomException( - "Invalid singleton response, request ID " - + batch.response().headers().get("Bytom-Request-ID")); - */ - throw new BytomException("Invalid singleton response."); - } - }; - return post(action, body, rc); - } - - /** - * Returns true if a client access token stored in the client. - * - * @return a boolean - */ - public boolean hasAccessToken() { - return this.accessToken != null && !this.accessToken.isEmpty(); - } - - /** - * Returns the client access token (possibly null). - * - * @return the client access token - */ - public String accessToken() { - return accessToken; - } - - public String getUrl() { - return url; - } - - /** - * Pins a public key to the HTTP client. - * - * @param provider certificate provider - * @param subjPubKeyInfoHash public key hash - */ - public void pinCertificate(String provider, String subjPubKeyInfoHash) { - CertificatePinner cp = - new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); - this.httpClient.setCertificatePinner(cp); - } - - /** - * Sets the default connect timeout for new connections. A value of 0 means no timeout. - * - * @param timeout the number of time units for the default timeout - * @param unit the unit of time - */ - public void setConnectTimeout(long timeout, TimeUnit unit) { - this.httpClient.setConnectTimeout(timeout, unit); - } - - /** - * Sets the default read timeout for new connections. A value of 0 means no timeout. - * - * @param timeout the number of time units for the default timeout - * @param unit the unit of time - */ - public void setReadTimeout(long timeout, TimeUnit unit) { - this.httpClient.setReadTimeout(timeout, unit); - } - - /** - * Sets the default write timeout for new connections. A value of 0 means no timeout. - * - * @param timeout the number of time units for the default timeout - * @param unit the unit of time - */ - public void setWriteTimeout(long timeout, TimeUnit unit) { - this.httpClient.setWriteTimeout(timeout, unit); - } - - /** - * Sets the proxy information for the HTTP client. - * - * @param proxy proxy object - */ - public void setProxy(Proxy proxy) { - this.httpClient.setProxy(proxy); - } - - /** - * Defines an interface for deserializing HTTP responses into objects. - * - * @param the type of object to return - */ - public interface ResponseCreator { - /** - * Deserializes an HTTP response into a Java object of type T. - * - * @param response HTTP response object - * @param deserializer json deserializer - * @return an object of type T - * @throws BytomException - * @throws IOException - */ - T create(Response response, Gson deserializer) throws BytomException, IOException; - } - - /** - * Builds and executes an HTTP Post request. - * - * @param path the path to the endpoint - * @param body the request body - * @param respCreator object specifying the response structure - * @return a response deserialized into type T - * @throws BytomException - */ - private T post(String path, Object body, ResponseCreator respCreator) - throws BytomException { - - RequestBody requestBody = RequestBody.create(this.JSON, Utils.serializer.toJson(body)); - Request req; - - BytomException exception = null; - URL endpointURL = null; - - try { - endpointURL = new URL(url + "/" + path); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - Request.Builder builder = - new Request.Builder() - .header("User-Agent", "bytom-sdk-java/" + version) - .url(endpointURL) - .method("POST", requestBody); - if (hasAccessToken()) { - builder = builder.header("Authorization", buildCredentials()); - } - req = builder.build(); - - Response resp = null; - - T object = null; - - try { - resp = this.checkError(this.httpClient.newCall(req).execute()); - object = respCreator.create(resp, Utils.serializer); - } catch (IOException e) { - e.printStackTrace(); - } - - return object; - } - - private OkHttpClient buildHttpClient(Builder builder) throws ConfigurationException { - OkHttpClient httpClient = builder.baseHttpClient.clone(); - - try { - if (builder.trustManagers != null) { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(builder.keyManagers, builder.trustManagers, null); - httpClient.setSslSocketFactory(sslContext.getSocketFactory()); - } - } catch (GeneralSecurityException ex) { - throw new ConfigurationException("Unable to configure TLS", ex); - } - if (builder.readTimeoutUnit != null) { - httpClient.setReadTimeout(builder.readTimeout, builder.readTimeoutUnit); - } - if (builder.writeTimeoutUnit != null) { - httpClient.setWriteTimeout(builder.writeTimeout, builder.writeTimeoutUnit); - } - if (builder.connectTimeoutUnit != null) { - httpClient.setConnectTimeout(builder.connectTimeout, builder.connectTimeoutUnit); - } - if (builder.pool != null) { - httpClient.setConnectionPool(builder.pool); - } - if (builder.proxy != null) { - httpClient.setProxy(builder.proxy); - } - if (builder.cp != null) { - httpClient.setCertificatePinner(builder.cp); - } - - return httpClient; - } - - private static final Random randomGenerator = new Random(); - private static final int MAX_RETRIES = 10; - private static final int RETRY_BASE_DELAY_MILLIS = 40; - - // the max amount of time cored leader election could take - private static final int RETRY_MAX_DELAY_MILLIS = 15000; - - private static int retryDelayMillis(int retryAttempt) { - // Calculate the max delay as base * 2 ^ (retryAttempt - 1). - int max = RETRY_BASE_DELAY_MILLIS * (1 << (retryAttempt - 1)); - max = Math.min(max, RETRY_MAX_DELAY_MILLIS); - - // To incorporate jitter, use a pseudo random delay between [max/2, max] millis. - return randomGenerator.nextInt(max / 2) + max / 2 + 1; - } - - private static final int[] RETRIABLE_STATUS_CODES = { - 408, // Request Timeout - 429, // Too Many Requests - 500, // Internal Server Error - 502, // Bad Gateway - 503, // Service Unavailable - 504, // Gateway Timeout - 509, // Bandwidth Limit Exceeded - }; - - private static boolean isRetriableStatusCode(int statusCode) { - for (int i = 0; i < RETRIABLE_STATUS_CODES.length; i++) { - if (RETRIABLE_STATUS_CODES[i] == statusCode) { - return true; - } - } - return false; - } - - private Response checkError(Response response) throws BytomException { - /* - String rid = response.headers().get("Bytom-Request-ID"); - if (rid == null || rid.length() == 0) { - // Header field Bytom-Request-ID is set by the backend - // API server. If this field is set, then we can expect - // the body to be well-formed JSON. If it's not set, - // then we are probably talking to a gateway or proxy. - throw new ConnectivityException(response); - } */ - - if ((response.code() / 100) != 2) { - try { - APIException err = - Utils.serializer.fromJson(response.body().charStream(), APIException.class); - if (err.code != null) { - //err.requestId = rid; - err.statusCode = response.code(); - throw err; - } - } catch (IOException ex) { - //throw new JSONException("Unable to read body. " + ex.getMessage(), rid); - throw new JSONException("Unable to read body. "); - } - } - return response; - } - - private String buildCredentials() { - String user = ""; - String pass = ""; - if (hasAccessToken()) { - String[] parts = accessToken.split(":"); - if (parts.length >= 1) { - user = parts[0]; - } - if (parts.length >= 2) { - pass = parts[1]; - } - } - return Credentials.basic(user, pass); - } - - /** - * Overrides {@link Object#hashCode()} - * - * @return the hash code - */ - @Override - public int hashCode() { - int code = this.url.hashCode(); - if (this.hasAccessToken()) { - code = code * 31 + this.accessToken.hashCode(); - } - return code; - } - - /** - * Overrides {@link Object#equals(Object)} - * - * @param o the object to compare - * @return a boolean specifying equality - */ - @Override - public boolean equals(Object o) { - if (o == null) return false; - if (!(o instanceof Client)) return false; - - Client other = (Client) o; - if (!this.url.equalsIgnoreCase(other.url)) { - return false; - } - return Objects.equals(this.accessToken, other.accessToken); - } - - /** - * A builder class for creating client objects - */ - public static class Builder { - - private String url; - - private OkHttpClient baseHttpClient; - private String accessToken; - private CertificatePinner cp; - private KeyManager[] keyManagers; - private TrustManager[] trustManagers; - private long connectTimeout; - private TimeUnit connectTimeoutUnit; - private long readTimeout; - private TimeUnit readTimeoutUnit; - private long writeTimeout; - private TimeUnit writeTimeoutUnit; - private Proxy proxy; - private ConnectionPool pool; - - public Builder() { - this.baseHttpClient = new OkHttpClient(); - this.baseHttpClient.setFollowRedirects(false); - this.setDefaults(); - } - - public Builder(Client client) { - this.baseHttpClient = client.httpClient.clone(); - this.url = client.url; - this.accessToken = client.accessToken; - } - - private void setDefaults() { - this.setReadTimeout(30, TimeUnit.SECONDS); - this.setWriteTimeout(30, TimeUnit.SECONDS); - this.setConnectTimeout(30, TimeUnit.SECONDS); - this.setConnectionPool(50, 2, TimeUnit.MINUTES); - } - - public Builder setUrl(String url) { - this.url = url; - return this; - } - - /** - * Sets the access token for the client - * - * @param accessToken The access token for the Chain Core or HSM - */ - public Builder setAccessToken(String accessToken) { - this.accessToken = accessToken; - return this; - } - - /** - * Sets the client's certificate and key for TLS client authentication. - * PEM-encoded, RSA private keys adhering to PKCS#1 or PKCS#8 are supported. - * - * @param certStream input stream of PEM-encoded X.509 certificate - * @param keyStream input stream of PEM-encoded private key - */ - public Builder setX509KeyPair(InputStream certStream, InputStream keyStream) - throws ConfigurationException { - try (PEMParser parser = new PEMParser(new InputStreamReader(keyStream))) { - // Extract certs from PEM-encoded input. - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream); - - // Parse the private key from PEM-encoded input. - Object obj = parser.readObject(); - PrivateKeyInfo info; - if (obj instanceof PEMKeyPair) { - // PKCS#1 Private Key found. - PEMKeyPair kp = (PEMKeyPair) obj; - info = kp.getPrivateKeyInfo(); - } else if (obj instanceof PrivateKeyInfo) { - // PKCS#8 Private Key found. - info = (PrivateKeyInfo) obj; - } else { - throw new ConfigurationException("Unsupported private key provided."); - } - - // Create a new key store and input the pair. - KeySpec spec = new PKCS8EncodedKeySpec(info.getEncoded()); - KeyFactory kf = KeyFactory.getInstance("RSA"); - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); - keyStore.setCertificateEntry("cert", certificate); - keyStore.setKeyEntry( - "key", - kf.generatePrivate(spec), - DEFAULT_KEYSTORE_PASSWORD, - new X509Certificate[]{certificate}); - - // Use key store to build a key manager. - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); - - this.keyManagers = keyManagerFactory.getKeyManagers(); - return this; - } catch (GeneralSecurityException | IOException ex) { - throw new ConfigurationException("Unable to store X.509 cert/key pair", ex); - } - } - - /** - * Sets the client's certificate and key for TLS client authentication. - * - * @param certPath file path to PEM-encoded X.509 certificate - * @param keyPath file path to PEM-encoded private key - */ - public Builder setX509KeyPair(String certPath, String keyPath) throws ConfigurationException { - try (InputStream certStream = - new ByteArrayInputStream(Files.readAllBytes(Paths.get(certPath))); - InputStream keyStream = - new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyPath)))) { - return setX509KeyPair(certStream, keyStream); - } catch (IOException ex) { - throw new ConfigurationException("Unable to store X509 cert/key pair", ex); - } - } - - /** - * Trusts the given CA certs, and no others. Use this if you are running - * your own CA, or are using a self-signed server certificate. - * - * @param is input stream of the certificates to trust, in PEM format. - */ - public Builder setTrustedCerts(InputStream is) throws ConfigurationException { - try { - // Extract certs from PEM-encoded input. - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - Collection certificates = - certificateFactory.generateCertificates(is); - if (certificates.isEmpty()) { - throw new IllegalArgumentException("expected non-empty set of trusted certificates"); - } - - // Create a new key store and input the cert. - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); - int index = 0; - for (Certificate certificate : certificates) { - String certificateAlias = Integer.toString(index++); - keyStore.setCertificateEntry(certificateAlias, certificate); - } - - // Use key store to build an X509 trust manager. - KeyManagerFactory keyManagerFactory = - KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keyStore); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new IllegalStateException( - "Unexpected default trust managers:" + Arrays.toString(trustManagers)); - } - - this.trustManagers = trustManagers; - return this; - } catch (GeneralSecurityException | IOException ex) { - throw new ConfigurationException("Unable to configure trusted CA certs", ex); - } - } - - /** - * Trusts the given CA certs, and no others. Use this if you are running - * your own CA, or are using a self-signed server certificate. - * - * @param path The path of a file containing certificates to trust, in PEM format. - */ - public Builder setTrustedCerts(String path) throws ConfigurationException { - try (InputStream is = new FileInputStream(path)) { - return setTrustedCerts(is); - } catch (IOException ex) { - throw new ConfigurationException("Unable to configure trusted CA certs", ex); - } - } - - /** - * Sets the certificate pinner for the client - * - * @param provider certificate provider - * @param subjPubKeyInfoHash public key hash - */ - public Builder pinCertificate(String provider, String subjPubKeyInfoHash) { - this.cp = new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); - return this; - } - - /** - * Sets the connect timeout for the client - * - * @param timeout the number of time units for the default timeout - * @param unit the unit of time - */ - public Builder setConnectTimeout(long timeout, TimeUnit unit) { - this.connectTimeout = timeout; - this.connectTimeoutUnit = unit; - return this; - } - - /** - * Sets the read timeout for the client - * - * @param timeout the number of time units for the default timeout - * @param unit the unit of time - */ - public Builder setReadTimeout(long timeout, TimeUnit unit) { - this.readTimeout = timeout; - this.readTimeoutUnit = unit; - return this; - } - - /** - * Sets the write timeout for the client - * - * @param timeout the number of time units for the default timeout - * @param unit the unit of time - */ - public Builder setWriteTimeout(long timeout, TimeUnit unit) { - this.writeTimeout = timeout; - this.writeTimeoutUnit = unit; - return this; - } - - /** - * Sets the proxy for the client - * - * @param proxy - */ - public Builder setProxy(Proxy proxy) { - this.proxy = proxy; - return this; - } - - /** - * Sets the connection pool for the client - * - * @param maxIdle the maximum number of idle http connections in the pool - * @param timeout the number of time units until an idle http connection in the pool is closed - * @param unit the unit of time - */ - public Builder setConnectionPool(int maxIdle, long timeout, TimeUnit unit) { - this.pool = new ConnectionPool(maxIdle, unit.toMillis(timeout)); - return this; - } - - /** - * Builds a client with all of the provided parameters. - */ - public Client build() throws ConfigurationException { - return new Client(this); - } - } -} diff --git a/src/main/java/io/bytom/http/SuccessMessage.java b/src/main/java/io/bytom/http/SuccessMessage.java deleted file mode 100644 index c069a82..0000000 --- a/src/main/java/io/bytom/http/SuccessMessage.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.bytom.http; - -/** - * This class represents RPC success responses whose content is not meaningful. - */ -public class SuccessMessage { - public String message; -} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties deleted file mode 100644 index e9252be..0000000 --- a/src/main/resources/config.properties +++ /dev/null @@ -1,8 +0,0 @@ -bytom.api.url= -client.access.token= - -# bytom.api.url=http://10.100.7.47:9888/ -# client.access.token=wt:3d17dbb953cedd53353bf3f342bb2929e9505105ffeb21670e6bd00abeef3772 - -#bytom.api.url=http://127.0.0.1:9888/ -#client.access.token=sheng:49d1623f5991c62a5094e761477ddd2838dceb49c22fbf84b492a54f1df88123 diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties deleted file mode 100644 index b64ec56..0000000 --- a/src/main/resources/log4j.properties +++ /dev/null @@ -1,20 +0,0 @@ - log4j.rootLogger=debug, stdout, R - log4j.appender.stdout=org.apache.log4j.ConsoleAppender - log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - - log4j.logger.org.apache.commons.httpclient=info - log4j.logger.httpclient.wire.content=info - log4j.logger.httpclient.wire.header=info - - # Pattern to output the caller's file name and line number. - log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n - - log4j.appender.R=org.apache.log4j.RollingFileAppender - log4j.appender.R.File=bytom.log - log4j.appender.R.MaxFileSize= 1000KB - - # Keep one backup file - log4j.appender.R.MaxBackupIndex=1 - - log4j.appender.R.layout=org.apache.log4j.PatternLayout - log4j.appender.R.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n \ No newline at end of file diff --git a/src/test/java/io/bytom/AppTest.java b/src/test/java/io/bytom/AppTest.java deleted file mode 100644 index d25fadf..0000000 --- a/src/test/java/io/bytom/AppTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package io.bytom; - -import com.google.gson.Gson; -import com.squareup.okhttp.*; -import io.bytom.api.Block; -import org.junit.Test; - -import java.io.IOException; - -/** - * Unit test for simple App. - */ -public class AppTest { - private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - - private final OkHttpClient client = new OkHttpClient(); - - - @Test - public void testListAccounts() throws IOException { - - String postBody = "{}"; - - Request request = new Request.Builder() - .url("http://127.0.0.1:9888/list-accounts") - .post(RequestBody.create(JSON, postBody)) - .build(); - - Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - System.out.println(response.body().string()); - } - - - @Test - public void testCreateAccount() throws IOException { - - String postBody = "{\n" + - " \"root_xpubs\": [\n" + - " \"012454d25928d52d42e3ee1f2bebe0916974d958f9ec08c9a028043ffe3dd95630c1b788c947b8c07ede2a4b5e3e3bbe0e305bab4526a7bc67b21e1d051e74ef\"\n" + - " ], \n" + - " \"quorum\": 1, \n" + - " \"alias\": \"sheng\"\n" + - "}"; - - Request request = new Request.Builder() - .url("http://127.0.0.1:9888/create-account") - .post(RequestBody.create(JSON, postBody)) - .build(); - - Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - System.out.println(response.body().string()); - } - - @Test - public void testBlockGet() throws Exception { - String postBody = "{\n" + - " \"hash\": \"37eabf4d321f43b930f52b3af2e53b2ad2dbb234949e778213a4a54e4be04ea8\",\n" + - " \"size\": 388,\n" + - " \"version\": 1,\n" + - " \"height\": 158,\n" + - " \"previous_block_hash\": \"2cfa73d6ed14a7fcb3a8ccc2d4c45b0fd4c7754ec6fafaa0964045c23e2131cd\",\n" + - " \"timestamp\": 1527241075,\n" + - " \"nonce\": 0,\n" + - " \"bits\": 2305843009214532812,\n" + - " \"difficulty\": \"5789598940468732338727519302629705571043137272394850465663635070376277442560\",\n" + - " \"transaction_merkle_root\": \"62755c4770374e696c4fecd94e022ccf6f2adc00f409ac694a5cb92fe02353eb\",\n" + - " \"transaction_status_hash\": \"c9c377e5192668bc0a367e4a4764f11e7c725ecced1d7b6a492974fab1b6d5bc\",\n" + - " \"transactions\": [\n" + - " {\n" + - " \"id\": \"5c38b8107d53cbfebd19adbd11f2839d914099a74e491f1cf75f8b18320c84e2\",\n" + - " \"version\": 1,\n" + - " \"size\": 77,\n" + - " \"time_range\": 0,\n" + - " \"inputs\": [\n" + - " {\n" + - " \"type\": \"coinbase\",\n" + - " \"asset_id\": \"0000000000000000000000000000000000000000000000000000000000000000\",\n" + - " \"asset_definition\": {},\n" + - " \"amount\": 0,\n" + - " \"arbitrary\": \"c29e\"\n" + - " }\n" + - " ],\n" + - " \"outputs\": [\n" + - " {\n" + - " \"type\": \"control\",\n" + - " \"id\": \"9acda4f2bd83c7114a8837b596860df773a7e61b1d6b0a774addda1473361ad4\",\n" + - " \"position\": 0,\n" + - " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + - " \"asset_definition\": {},\n" + - " \"amount\": 41250000000,\n" + - " \"control_program\": \"001412a47b53b3bbdaf9f3510e1d8c19d69f81ca9142\",\n" + - " \"address\": \"sm1qz2j8k5anh0d0nu63pcwccxwkn7qu4y2zjwaj5h\"\n" + - " }\n" + - " ],\n" + - " \"status_fail\": false\n" + - " }\n" + - " ]\n" + - " }"; - Gson gson = new Gson(); - Block block = gson.fromJson(postBody, Block.class); - } -} - diff --git a/src/test/java/io/bytom/TestUtils.java b/src/test/java/io/bytom/TestUtils.java deleted file mode 100644 index 7d40cfc..0000000 --- a/src/test/java/io/bytom/TestUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.bytom; - -import io.bytom.common.Configuration; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; - -/** - * TestUtils provides a simplified api for testing. - */ -public class TestUtils { - - public static Logger logger = Logger.getLogger(TestUtils.class); - - public static Client generateClient() throws BytomException { - - String coreURL = Configuration.getValue("bytom.api.url"); - String accessToken = Configuration.getValue("client.access.token"); - - if (coreURL == null || coreURL.isEmpty()) { - coreURL = "http://127.0.0.1:9888"; - } - - if (coreURL.endsWith("/")) { - //split the last char "/" - coreURL = coreURL.substring(0, coreURL.length()-1); - } - - return new Client(coreURL, accessToken); - } -} diff --git a/src/test/java/io/bytom/integration/AccessTokenTest.java b/src/test/java/io/bytom/integration/AccessTokenTest.java deleted file mode 100644 index 49930b9..0000000 --- a/src/test/java/io/bytom/integration/AccessTokenTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.AccessToken; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Test; - -import java.util.List; - -public class AccessTokenTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - @Test - public void testTokenCreate() throws Exception { - AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client); - } - - - @Test - public void testTokenList() throws Exception { - List tokenList = AccessToken.list(client); - } - - @Test - public void testTokenCheck() throws Exception { - String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799"; - AccessToken.check(client, "sheng", secret); - } -} diff --git a/src/test/java/io/bytom/integration/AccountTest.java b/src/test/java/io/bytom/integration/AccountTest.java deleted file mode 100644 index d8314f2..0000000 --- a/src/test/java/io/bytom/integration/AccountTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Account; -import io.bytom.api.Receiver; -import io.bytom.http.Client; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class AccountTest { - - static Client client; - static Account account; - - @Test - public void testAccountCreate() throws Exception { - client = TestUtils.generateClient(); - - String alias = "AccountTest.testAccountCreate.002"; - Integer quorum = 1; - List root_xpubs = new ArrayList<>(); - root_xpubs.add("c4b25825e92cd8623de4fd6a35952ad0efb2ed215fdb1b40754f0ed12eff7827d147d1e8b003601ba2f78a4a84dcc77e93ed282633f2679048c5d5ac5ea10cb5"); - - -// Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); -// key = Key.create(client, builder); - - Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); - account = Account.create(client, builder); - - assertNotNull(account.id); - assertEquals(alias.toLowerCase(), account.alias); - } - - @Test - public void testAccountList() throws Exception { - client = TestUtils.generateClient(); - List accountList = Account.list(client); - } - - @Test - public void testAccountDelete() throws Exception { - client = TestUtils.generateClient(); - List accountList = Account.list(client); - String alias = accountList.get(accountList.size()-1).alias; - //delete the last Account Object - Account.delete(client, alias); - } - - @Test - public void testReceiverCreate() throws Exception { - client = TestUtils.generateClient(); - List accountList = Account.list(client); - String alias = accountList.get(accountList.size()-1).alias; - String id = accountList.get(accountList.size()-1).id; - - Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); - Receiver receiver = receiverBuilder.create(client); - - assertNotNull(receiver.address); - assertNotNull(receiver.controlProgram); - } - - @Test - public void testAddressList() throws Exception { - client = TestUtils.generateClient(); - List accountList = Account.list(client); - String alias = accountList.get(accountList.size()-1).alias; - String id = accountList.get(accountList.size()-1).id; - - Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias); - List addressList = addressBuilder.list(client); - - assertNotNull(addressList); - } - - @Test - public void testAddressValidate() throws Exception { - client = TestUtils.generateClient(); - - List accountList = Account.list(client); - String alias = accountList.get(accountList.size()-1).alias; - String id = accountList.get(accountList.size()-1).id; - - Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias); - List addressList = addressBuilder.list(client); - - Account.Address address = addressBuilder.validate(client, addressList.get(0).address); - assertEquals(true, address.is_local); - } - -} diff --git a/src/test/java/io/bytom/integration/AssetTest.java b/src/test/java/io/bytom/integration/AssetTest.java deleted file mode 100644 index 2971275..0000000 --- a/src/test/java/io/bytom/integration/AssetTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Account; -import io.bytom.api.Asset; -import io.bytom.http.Client; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class AssetTest { - - static Client client; - static Account account; - static Asset asset; - - @Test - public void testAssetCreate() throws Exception { - client = TestUtils.generateClient(); - - List accountList = Account.list(client); - String alias = "GOLD"; - - List xpubs = accountList.get(0).xpubs; - - Asset.Builder builder = new Asset.Builder() - .setAlias(alias) - .setQuorum(1) - .setRootXpubs(xpubs); - asset = builder.create(client); - assertNotNull(asset); - } - - @Test - public void testAssetGet() throws Exception { - client = TestUtils.generateClient(); - Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); - String id = queryBuilder.list(client).get(1).id; - queryBuilder.setId(id); - Asset asset = queryBuilder.get(client); - } - - @Test - public void testAssetList() throws Exception { - client = TestUtils.generateClient(); - Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); - List assetList = queryBuilder.list(client); - assertEquals(2, assetList.size()); - } - - @Test - public void testUpdateAssetAlias() throws Exception { - client = TestUtils.generateClient(); - - Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); - String id = queryBuilder.list(client).get(1).id; - - String alias = "HELLOWORLD"; - - - Asset.AliasUpdateBuilder aliasUpdateBuilder = - new Asset.AliasUpdateBuilder() - .setAlias(alias) - .setAssetId(id); - aliasUpdateBuilder.update(client); - } - -} diff --git a/src/test/java/io/bytom/integration/BalanceTest.java b/src/test/java/io/bytom/integration/BalanceTest.java deleted file mode 100644 index 8015f4c..0000000 --- a/src/test/java/io/bytom/integration/BalanceTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Balance; -import io.bytom.api.Wallet; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.Test; - -import java.util.List; - -public class BalanceTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static Balance balance; - - @Test - public void testBalanceList() throws Exception { - List balanceList = new Balance.QueryBuilder().list(client); - Assert.assertNotNull(balanceList); - } - - @Test - public void testBalanceByAssetAlias() throws Exception { - Balance balance = new Balance.QueryBuilder().listByAssetAlias(client, "BTM"); - Assert.assertNotNull(balance); - } - - @Test - public void testBalanceByAccountAlias() throws Exception { - List balanceList = new Balance.QueryBuilder().listByAccountAlias(client, "test"); - Assert.assertNotNull(balanceList); - } - -} diff --git a/src/test/java/io/bytom/integration/BlockTest.java b/src/test/java/io/bytom/integration/BlockTest.java deleted file mode 100644 index 71b8e3f..0000000 --- a/src/test/java/io/bytom/integration/BlockTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Asset; -import io.bytom.api.Block; -import io.bytom.api.Wallet; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Assert; -import org.junit.Test; - -public class BlockTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static Block block; - static Block.BlockHeader blockHeader; - static Block.BlockDifficulty blockDifficulty; - static Block.BlockHashRate blockHashRate; - - @Test - public void testBlockCountGet() throws Exception { - int count = Block.getBlockCount(client); - Assert.assertEquals(158, count); - } - - @Test - public void testBlockHashGet() throws Exception { - String blockHash = Block.getBlockHash(client); - Assert.assertNotNull(blockHash); - } - - @Test - public void testBlockGet() throws Exception { - int height = Block.getBlockCount(client); - String blockHash = Block.getBlockHash(client); - - block = new Block.QueryBuilder() - .setBlockHeight(height) - .setBlockHash(blockHash) - .getBlock(client); - } - - @Test - public void testBlockHeader() throws Exception { - int height = Block.getBlockCount(client); - String blockHash = Block.getBlockHash(client); - - blockHeader = new Block.QueryBuilder() - .setBlockHeight(height) - .setBlockHash(blockHash) - .getBlockHeader(client); - } - - @Test - public void testBlockDifficulty() throws Exception { - int height = Block.getBlockCount(client); - String blockHash = Block.getBlockHash(client); - - blockDifficulty = new Block.QueryBuilder() - .setBlockHeight(height) - .setBlockHash(blockHash) - .getBlockDifficulty(client); - } - - @Test - public void testBlockHashRate() throws Exception { - int height = Block.getBlockCount(client); - String blockHash = Block.getBlockHash(client); - - blockHashRate = new Block.QueryBuilder() - .setBlockHeight(height) - .setBlockHash(blockHash) - .getHashRate(client); - } - -} diff --git a/src/test/java/io/bytom/integration/CoreConfigTest.java b/src/test/java/io/bytom/integration/CoreConfigTest.java deleted file mode 100644 index d656da8..0000000 --- a/src/test/java/io/bytom/integration/CoreConfigTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.CoreConfig; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Assert; -import org.junit.Test; - -public class CoreConfigTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static CoreConfig.NetInfo netInfo; - - static Integer gasRate; - - @Test - public void testNetInfo() throws Exception { - netInfo = CoreConfig.getNetInfo(client); - Assert.assertNotNull(netInfo); - } - - @Test - public void testGasRate() throws Exception { - gasRate = CoreConfig.getGasRate(client); - Assert.assertNotNull(gasRate); - } - - - -} diff --git a/src/test/java/io/bytom/integration/KeyTest.java b/src/test/java/io/bytom/integration/KeyTest.java deleted file mode 100644 index ec68a92..0000000 --- a/src/test/java/io/bytom/integration/KeyTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Key; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; - -public class KeyTest { - - static Client client; - static Key key; - - @Test - public void testClientKeyCreate() throws Exception { - client = TestUtils.generateClient(); - - String alias = "KeyTest.testKeyCreate.successli004"; - String password = "123456"; - - Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); - key = Key.create(client, builder); - - assertNotNull(key.xpub); - assertEquals(alias.toLowerCase(), key.alias); - } - - @Test - public void testClientKeyList() throws Exception { - //client = TestUtils.generateClient(); - client = new Client("http://127.0.0.1:9888/"); - List keyList = Key.list(client); - } - - @Test - public void testClientKeyDelete() throws Exception { - client = TestUtils.generateClient(); - List keyList = Key.list(client); - String xpub = keyList.get(keyList.size()-1).xpub; - //delete the last Key Object - Key.delete(client, xpub, "123456"); - } - - @Test - public void testClientKeyResetPassword() throws BytomException { - client = TestUtils.generateClient(); - List keyList = Key.list(client); - String xpub = keyList.get(keyList.size()-1).xpub; - Key.resetPassword(client, xpub, "123456", "123456789"); - Key.delete(client, xpub, "123456789"); - } - -} diff --git a/src/test/java/io/bytom/integration/MessageTest.java b/src/test/java/io/bytom/integration/MessageTest.java deleted file mode 100644 index bd7fc1f..0000000 --- a/src/test/java/io/bytom/integration/MessageTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Message; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Assert; -import org.junit.Test; - -public class MessageTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static Message message; - - @Test - public void testMessageSign() throws Exception { - - message = new Message.SignBuilder() - .setAddress("sm1qz2j8k5anh0d0nu63pcwccxwkn7qu4y2zjwaj5h") - .setMessage("test") - .setPassword("123456") - .sign(client); - - Assert.assertNotNull(message); - } - - @Test - public void testMessageVerify() throws Exception { - String derived_xpub = "6e1efce70e2b29efa348aec7c148edc2beb72edc0d4422a03cfb0f40e6e4cfc6e6050b5863bbe84c44131280dff68614e0308a4d081e8b677d0f7f09fb3390c4"; - String signature = "0d840d5b6a6df028013260e94e871c1443686c446a65db4ee93005c5395c3607feb0ac5bf583a3139c8a3d0afe757448ff49fa17ffd2377831ce5f925c846b0b"; - - Boolean verify = new Message.VerifyBuilder() - .setDerivedXpub(derived_xpub) - .setSignature(signature) - .setAddress("sm1qz2j8k5anh0d0nu63pcwccxwkn7qu4y2zjwaj5h") - .setMessage("test") - .verifyMessage(client); - } - - -} diff --git a/src/test/java/io/bytom/integration/RawTransactionTest.java b/src/test/java/io/bytom/integration/RawTransactionTest.java deleted file mode 100644 index a4b3960..0000000 --- a/src/test/java/io/bytom/integration/RawTransactionTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.RawTransaction; -import io.bytom.api.Wallet; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Assert; -import org.junit.Test; - -public class RawTransactionTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static RawTransaction rawTransaction; - - - @Test - public void testRawTxDecode() throws Exception { - String rawTxId = "070100010161015f30e052cd50e385951936c08fb5642bd12b727da958960249ddad8c9a77e5371fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d59901000116001412a47b53b3bbdaf9f3510e1d8c19d69f81ca91426302405a8b5adebd2ab63ba1ac55a7bbadc1fe246806c37732df0442b827fa4e06058137711d9d012becdc9de507a8ad0de0dd50780c0503c0dcff2dc03d7592e31a08206e1efce70e2b29efa348aec7c148edc2beb72edc0d4422a03cfb0f40e6e4cfc602013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d3bdc6980101160014a9c0ea4abe4d09546197bac3c86f4dd39fde1afb00013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8084af5f011600144cc6edb1f4077c740e0201bb3688e6efba4a098c00"; - rawTransaction = RawTransaction.decode(client, rawTxId); - Assert.assertNotNull(rawTransaction); - } - - -} diff --git a/src/test/java/io/bytom/integration/TransactionTest.java b/src/test/java/io/bytom/integration/TransactionTest.java deleted file mode 100644 index 180b12d..0000000 --- a/src/test/java/io/bytom/integration/TransactionTest.java +++ /dev/null @@ -1,334 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.*; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.apache.log4j.Logger; -import org.junit.Assert; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class TransactionTest { - - static Client client; - static Client otherCoreClient; - - static Key senderKey; - static Key receiverKey; - static Account senderAccount; - static Account receiverAccount; - static Account.Address receiverAddress; - static Asset senderAsset; - static Asset receiverAsset; - - static Transaction.Feed transactionFeed; - - static { - try { - client = TestUtils.generateClient(); - otherCoreClient = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - - private static Logger logger = Logger.getLogger(TransactionTest.class); - - - @Test - public void testGetAll() throws Exception { - - senderKey = Key.list(client).get(0); - receiverKey = Key.list(client).get(1); - logger.info("senderKey:" + senderKey.toJson()); - logger.info("receiverKey:" + receiverKey.toJson()); - - senderAccount = Account.list(client).get(0); - receiverAccount = Account.list(client).get(1); - logger.info("senderAccount:" + senderAccount.toJson()); - logger.info("receiverAccount:" + receiverAccount.toJson()); - - receiverAddress = new Account.AddressBuilder() - .setAccountAlias(receiverAccount.alias) - .setAccountId(receiverAccount.id) - .list(client).get(0); - logger.info("receiver-address:" + receiverAddress.toJson()); - - senderAsset = new Asset.QueryBuilder().list(client).get(0); - receiverAsset = new Asset.QueryBuilder().list(client).get(1); - logger.info("senderAsset:" + senderAsset.toJson()); - logger.info("receiverAsset:" + receiverAsset.toJson()); - } - - @Test - public void testTransactionAll() throws Exception { - testGetAll(); - - logger.info("before transaction:"); - - List balanceList = new Balance.QueryBuilder().list(client); - - logger.info("transaction:"); - - Transaction.Template controlAddress = new Transaction.Builder() - .addAction( - new Transaction.Action.SpendFromAccount() - .setAccountId(senderAccount.id) - .setAssetId(senderAsset.id) - .setAmount(300000000) - ) - .addAction( - new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetId(senderAsset.id) - .setAmount(200000000) - ).build(client); - - Transaction.Template singer = new Transaction.SignerBuilder().sign(client, - controlAddress, "123456"); - - logger.info("rawTransaction:" + singer.rawTransaction); - - logger.info("singer:" + singer.toJson()); - - Transaction.SubmitResponse txs = Transaction.submit(client, singer); - - logger.info("txs:" + txs.toJson()); - - logger.info("after transaction."); - - balanceList = new Balance.QueryBuilder().list(client); - - } - - //Asset issuance - //Issue 1000 units of gold to Alice. - @Test - public void testAssetIssue() throws BytomException { - Transaction.Template issuance = new Transaction.Builder() - .addAction( - new Transaction.Action.SpendFromAccount() - .setAccountId(senderAccount.id) - .setAssetId(senderAsset.id) - .setAmount(300000000) - ) - .addAction( - new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetId(senderAsset.id) - .setAmount(200000000) - ).build(client); - - Transaction.Template signedIssuance = new Transaction.SignerBuilder().sign(client, - issuance, "123456"); - - Transaction.submit(client, signedIssuance); - } - - //Between two Chain Cores - @Test - public void testAssetIssueBetween() throws BytomException { - Receiver bobIssuanceReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); - String bobIssuanceReceiverSerialized = bobIssuanceReceiver.toJson(); - - - Transaction.Template issuanceToReceiver = new Transaction.Builder() - .addAction(new Transaction.Action.Issue() - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobIssuanceReceiverSerialized)) - .setAssetAlias("gold") - .setAmount(10) - ).build(client); - - Transaction.Template signedIssuanceToReceiver = new Transaction.SignerBuilder().sign(client, - issuanceToReceiver, "123456"); - - Transaction.submit(client, signedIssuanceToReceiver); - } - - //Simple payment - //Alice pays 10 units of gold to Bob. - @Test - public void testSimplePayment() throws BytomException { - Transaction.Template payment = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetAlias("gold") - .setAmount(10) - ).build(client); - - Transaction.Template signedPayment = new Transaction.SignerBuilder().sign(client, - payment, "123456"); - - Transaction.submit(client, signedPayment); - } - - //Between two Chain Cores - @Test - public void testSimplePaymentBetween() throws BytomException { - Receiver bobPaymentReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); - String bobPaymentReceiverSerialized = bobPaymentReceiver.toJson(); - - - Transaction.Template paymentToReceiver = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobPaymentReceiverSerialized)) - .setAssetAlias("gold") - .setAmount(10) - ).build(client); - - Transaction.Template signedPaymentToReceiver = new Transaction.SignerBuilder().sign(client, - paymentToReceiver, "123456"); - - Transaction.submit(client, signedPaymentToReceiver); - } - - //Multi-asset payment - //Alice pays 10 units of gold and 20 units of silver to Bob. - @Test - public void testMultiAssetPayment() throws BytomException { - Transaction.Template multiAssetPayment = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("silver") - .setAmount(20) - ).addAction(new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithAddress() - .setAddress(receiverAddress.address) - .setAssetAlias("silver") - .setAmount(20) - ).build(client); - - Transaction.Template signedMultiAssetPayment = new Transaction.SignerBuilder().sign(client, - multiAssetPayment, "123456"); - - Transaction.submit(client, signedMultiAssetPayment); - } - - //Between two Chain Cores - @Test - public void testMultiAssetPaymentBetween() throws BytomException { - Receiver bobGoldReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); - String bobGoldReceiverSerialized = bobGoldReceiver.toJson(); - - Receiver bobSilverReceiver = new Account.ReceiverBuilder() - .setAccountAlias("bob") - .create(otherCoreClient); - String bobSilverReceiverSerialized = bobSilverReceiver.toJson(); - - - Transaction.Template multiAssetToReceiver = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("silver") - .setAmount(20) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobGoldReceiverSerialized)) - .setAssetAlias("gold") - .setAmount(10) - ).addAction(new Transaction.Action.ControlWithReceiver() - .setReceiver(Receiver.fromJson(bobSilverReceiverSerialized)) - .setAssetAlias("silver") - .setAmount(20) - ).build(client); - - Transaction.Template signedMultiAssetToReceiver = new Transaction.SignerBuilder().sign(client, - multiAssetToReceiver, "123456");; - - Transaction.submit(client, signedMultiAssetToReceiver); - } - - //Asset retirement - //Alice retires 50 units of gold from her account. - @Test - public void testRetirement() throws BytomException { - Transaction.Template retirement = new Transaction.Builder() - .addAction(new Transaction.Action.SpendFromAccount() - .setAccountAlias("alice") - .setAssetAlias("gold") - .setAmount(50) - ).addAction(new Transaction.Action.Retire() - .setAssetAlias("gold") - .setAmount(50) - ).build(client); - - Transaction.Template signedRetirement = new Transaction.SignerBuilder().sign(client, - retirement, "123456"); - - Transaction.submit(client, signedRetirement); - } - - //TransactionFeed - @Test - public void testTXFeedCreate() throws Exception { - String filter = "asset_id='57fab05b689a2b8b6738cffb5cf6cffcd0bf6156a04b7d9ba0173e384fe38c8c' AND amount_lower_limit = 50 AND amount_upper_limit = 100"; - String alias = "test1"; - new Transaction.Feed.Builder() - .setAlias(alias) - .setFilter(filter) - .create(client); - } - - @Test - public void testTXFeedGet() throws Exception { - String alias = "test2"; - transactionFeed = Transaction.Feed.getByAlias(client, alias); - - Assert.assertNotNull(transactionFeed); - } - - @Test - public void testTXFeedUpdate() throws Exception { - String filter = "asset_id='57fab05b689a2b8b6738cffb5cf6cffcd0bf6156a04b7d9ba0173e384fe38c8c' AND amount_lower_limit = 50 AND amount_upper_limit = 100"; - String alias = "test2"; - - Transaction.Feed.update(client, alias, filter); - } - - @Test - public void testTXFeedList() throws Exception { - List txFeedList = Transaction.Feed.list(client); - Assert.assertNotNull(txFeedList); - } - - @Test - public void testTXFeedDelete() throws Exception { - String alias = "test2"; - Transaction.Feed.deleteByAlias(client, alias); - } - -} diff --git a/src/test/java/io/bytom/integration/UTXOTest.java b/src/test/java/io/bytom/integration/UTXOTest.java deleted file mode 100644 index aca21dc..0000000 --- a/src/test/java/io/bytom/integration/UTXOTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.UnspentOutput; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Assert; -import org.junit.Test; - -import java.util.List; - -public class UTXOTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - - @Test - public void testBalanceList() throws Exception { - List UTXOList = new UnspentOutput.QueryBuilder().list(client); - Assert.assertNotNull(UTXOList); - } - -} diff --git a/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java b/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java deleted file mode 100644 index f63ae7f..0000000 --- a/src/test/java/io/bytom/integration/UnconfirmedTransactionTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.UnconfirmedTransaction; -import io.bytom.api.Wallet; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Assert; -import org.junit.Test; - -public class UnconfirmedTransactionTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static UnconfirmedTransaction unconfirmedTransaction; - static UnconfirmedTransaction.UTXResponse utxResponse; - - @Test - public void testUCTXList() throws Exception { - utxResponse = UnconfirmedTransaction.list(client); - Assert.assertNotNull(utxResponse); - } - - @Test - public void testUCTXGet() throws Exception { - utxResponse = UnconfirmedTransaction.list(client); - unconfirmedTransaction = UnconfirmedTransaction.get(client, utxResponse.txIds.get(0)); - Assert.assertNotNull(unconfirmedTransaction); - } - -} diff --git a/src/test/java/io/bytom/integration/WalletTest.java b/src/test/java/io/bytom/integration/WalletTest.java deleted file mode 100644 index 194fbb6..0000000 --- a/src/test/java/io/bytom/integration/WalletTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.bytom.integration; - -import io.bytom.TestUtils; -import io.bytom.api.Key; -import io.bytom.api.Wallet; -import io.bytom.exception.BytomException; -import io.bytom.http.Client; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class WalletTest { - - static Client client; - - static { - try { - client = TestUtils.generateClient(); - } catch (BytomException e) { - e.printStackTrace(); - } - } - - static Wallet wallet; - - @Test - public void testWalletBackUp() throws Exception { - - wallet = Wallet.backupWallet(client); - } - - @Test - public void testWallerRestore() throws Exception { - // get wallet object - wallet = Wallet.backupWallet(client); - //restore - Wallet.restoreWallet(client, wallet.accountImage, wallet.assetImage, wallet.keyImages); - } - - - -} From 42939aee65b7351c422ca3565f4b0776ee820bee Mon Sep 17 00:00:00 2001 From: liqiang Date: Thu, 25 Oct 2018 16:55:09 +0800 Subject: [PATCH 03/13] delete tx-signer --- tx-signer | 1 - 1 file changed, 1 deletion(-) delete mode 160000 tx-signer diff --git a/tx-signer b/tx-signer deleted file mode 160000 index 7d47124..0000000 --- a/tx-signer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7d47124fe9633b8ea37f494e6ec731ac1423378d From 737801388e627a708b107da5cb6d1f7062d0d597 Mon Sep 17 00:00:00 2001 From: liqiang Date: Thu, 25 Oct 2018 17:01:42 +0800 Subject: [PATCH 04/13] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tx-signer/README.md | 254 +++ tx-signer/pom.xml | 124 ++ .../com/google/crypto/tink/subtle/Bytes.java | 182 ++ .../google/crypto/tink/subtle/Curve25519.java | 429 +++++ .../google/crypto/tink/subtle/Ed25519.java | 1612 +++++++++++++++++ .../crypto/tink/subtle/Ed25519Constants.java | 130 ++ .../google/crypto/tink/subtle/Field25519.java | 597 ++++++ .../src/main/java/io/bytom/api/Keys.java | 44 + .../java/io/bytom/api/RawTransaction.java | 193 ++ .../src/main/java/io/bytom/api/Receiver.java | 48 + .../java/io/bytom/api/SignTransaction.java | 205 +++ .../io/bytom/api/SignTransactionImpl.java | 108 ++ .../main/java/io/bytom/api/Signatures.java | 14 + .../java/io/bytom/api/SignaturesImpl.java | 81 + .../src/main/java/io/bytom/api/Signer.java | 70 + .../src/main/java/io/bytom/api/Template.java | 132 ++ .../main/java/io/bytom/api/Transaction.java | 1061 +++++++++++ .../java/io/bytom/common/Configuration.java | 29 + .../main/java/io/bytom/common/Constants.java | 13 + .../main/java/io/bytom/common/DeriveXpub.java | 24 + .../io/bytom/common/ExpandedPrivateKey.java | 33 + .../main/java/io/bytom/common/FindDst.java | 26 + .../io/bytom/common/NonHardenedChild.java | 85 + .../bytom/common/ParameterizedTypeImpl.java | 30 + .../java/io/bytom/common/SuccessRespon.java | 9 + .../src/main/java/io/bytom/common/Utils.java | 9 + .../java/io/bytom/exception/APIException.java | 178 ++ .../io/bytom/exception/BadURLException.java | 14 + .../io/bytom/exception/BuildException.java | 49 + .../io/bytom/exception/BytomException.java | 32 + .../exception/ConfigurationException.java | 23 + .../exception/ConnectivityException.java | 36 + .../io/bytom/exception/HTTPException.java | 23 + .../io/bytom/exception/JSONException.java | 39 + .../java/io/bytom/http/BatchResponse.java | 114 ++ .../src/main/java/io/bytom/http/Client.java | 726 ++++++++ .../src/main/resources/config.properties | 8 + tx-signer/src/main/resources/log4j.properties | 20 + tx-signer/src/test/java/io/bytom/.DS_Store | Bin 0 -> 6148 bytes .../java/io/bytom/api/RawTransactionTest.java | 122 ++ .../io/bytom/api/SignTransactionTest.java | 227 +++ .../java/io/bytom/api/SignaturesImplTest.java | 213 +++ .../test/java/io/bytom/api/SignerTest.java | 24 + .../test/java/io/bytom/api/TemplateTest.java | 43 + .../java/io/bytom/common/DeriveXpubTest.java | 17 + .../bytom/common/ExpandedPrivateKeyTest.java | 21 + .../io/bytom/common/NonHardenedChildTest.java | 43 + 47 files changed, 7514 insertions(+) create mode 100644 tx-signer/README.md create mode 100644 tx-signer/pom.xml create mode 100644 tx-signer/src/main/java/com/google/crypto/tink/subtle/Bytes.java create mode 100644 tx-signer/src/main/java/com/google/crypto/tink/subtle/Curve25519.java create mode 100644 tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519.java create mode 100644 tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519Constants.java create mode 100644 tx-signer/src/main/java/com/google/crypto/tink/subtle/Field25519.java create mode 100644 tx-signer/src/main/java/io/bytom/api/Keys.java create mode 100644 tx-signer/src/main/java/io/bytom/api/RawTransaction.java create mode 100644 tx-signer/src/main/java/io/bytom/api/Receiver.java create mode 100644 tx-signer/src/main/java/io/bytom/api/SignTransaction.java create mode 100644 tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java create mode 100644 tx-signer/src/main/java/io/bytom/api/Signatures.java create mode 100644 tx-signer/src/main/java/io/bytom/api/SignaturesImpl.java create mode 100644 tx-signer/src/main/java/io/bytom/api/Signer.java create mode 100644 tx-signer/src/main/java/io/bytom/api/Template.java create mode 100644 tx-signer/src/main/java/io/bytom/api/Transaction.java create mode 100644 tx-signer/src/main/java/io/bytom/common/Configuration.java create mode 100644 tx-signer/src/main/java/io/bytom/common/Constants.java create mode 100644 tx-signer/src/main/java/io/bytom/common/DeriveXpub.java create mode 100644 tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java create mode 100644 tx-signer/src/main/java/io/bytom/common/FindDst.java create mode 100644 tx-signer/src/main/java/io/bytom/common/NonHardenedChild.java create mode 100644 tx-signer/src/main/java/io/bytom/common/ParameterizedTypeImpl.java create mode 100644 tx-signer/src/main/java/io/bytom/common/SuccessRespon.java create mode 100644 tx-signer/src/main/java/io/bytom/common/Utils.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/APIException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/BadURLException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/BuildException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/BytomException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/ConfigurationException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/ConnectivityException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/HTTPException.java create mode 100644 tx-signer/src/main/java/io/bytom/exception/JSONException.java create mode 100644 tx-signer/src/main/java/io/bytom/http/BatchResponse.java create mode 100644 tx-signer/src/main/java/io/bytom/http/Client.java create mode 100644 tx-signer/src/main/resources/config.properties create mode 100644 tx-signer/src/main/resources/log4j.properties create mode 100644 tx-signer/src/test/java/io/bytom/.DS_Store create mode 100644 tx-signer/src/test/java/io/bytom/api/RawTransactionTest.java create mode 100644 tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java create mode 100644 tx-signer/src/test/java/io/bytom/api/SignaturesImplTest.java create mode 100644 tx-signer/src/test/java/io/bytom/api/SignerTest.java create mode 100644 tx-signer/src/test/java/io/bytom/api/TemplateTest.java create mode 100644 tx-signer/src/test/java/io/bytom/common/DeriveXpubTest.java create mode 100644 tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java create mode 100644 tx-signer/src/test/java/io/bytom/common/NonHardenedChildTest.java diff --git a/tx-signer/README.md b/tx-signer/README.md new file mode 100644 index 0000000..d1ebb51 --- /dev/null +++ b/tx-signer/README.md @@ -0,0 +1,254 @@ +# tx_signer + +Java implementation of signing transaction offline to bytomd. + +## Pre + +#### Get the source code + +``` +$ git clone https://github.com/Bytom/bytom.git $GOPATH/src/github.com/bytom +``` + +#### git checkout + +``` +$ git checkout dev +``` + +**Why need dev branch? Because you could call decode transaction api from dev branch and obtain tx_id and some inputs ids.** + +#### Build + +``` +$ cd $GOPATH/src/github.com/bytom +$ make bytomd # build bytomd +$ make bytomcli # build bytomcli +``` + +When successfully building the project, the `bytom` and `bytomcli` binary should be present in `cmd/bytomd` and `cmd/bytomcli` directory, respectively. + +#### Initialize + +First of all, initialize the node: + +``` +$ cd ./cmd/bytomd +$ ./bytomd init --chain_id solonet +``` + +#### launch + +``` +$ ./bytomd node --mining +``` + +## Usage + +#### Build jar + +1. first get source code + + ``` + git clone https://github.com/successli/tx_signer.git + ``` + +2. get jar package + + ``` + $ mvn assembly:assembly -Dmaven.test.skip=true + ``` + + You can get a jar with dependencies, and you can use it in your project. + +#### Test cases + +Need 3 Parameters: + +- Private Keys Array +- Template Object + - After call build transaction api return a Template json object. [build transaction api](https://github.com/Bytom/bytom/wiki/API-Reference#build-transaction) + - use bytom java sdk return a Template object. +- Raw Transaction + - call decode raw-transaction api from dev branch. [decode raw-transaction api](https://github.com/Bytom/bytom/wiki/API-Reference#decode-raw-transaction) + +Call method: + +```java +// return a Template object signed offline basically. +Template result = signatures.generateSignatures(privates, template, rawTransaction); +// use result's raw_transaction call sign transaction api to build another data but not need password or private key. +``` + +Single-key Example: + +```java +@Test +// 使用 SDK 来构造 Template 对象参数, 单签 +public void testSignSingleKey() throws BytomException { + Client client = Client.generateClient(); + + String asset_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String address = "sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em"; + // build transaction obtain a Template object + Template template = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G0NLBNU00A02") + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G0NLBNU00A02") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(address) + .setAssetId(asset_id) + .setAmount(30000000) + ).build(client); + logger.info("template: " + template.toJson()); + // use Template object's raw_transaction id to decode raw_transaction obtain a RawTransaction object + RawTransaction decodedTx = RawTransaction.decode(client, template.rawTransaction); + logger.info("decodeTx: " + decodedTx.toJson()); + // need a private key array + String[] privateKeys = new String[]{"10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"}; + logger.info("private key:" + privateKeys[0]); + // call offline sign method to obtain a basic offline signed template + Signatures signatures = new SignaturesImpl(); + Template basicSigned = signatures.generateSignatures(privateKeys, template, decodedTx); + logger.info("basic signed raw: " + basicSigned.toJson()); + // call sign transaction api to calculate whole raw_transaction id + // sign password is None or another random String + Template result = new Transaction.SignerBuilder().sign(client, + basicSigned, ""); + logger.info("result raw_transaction: " + result.toJson()); + // success to submit transaction +} +``` + +Multi-keys Example: + +> Need an account has two or more keys. + +```java +@Test +// 使用 SDK 来构造 Template 对象参数, 多签 +public void testSignMultiKeys() throws BytomException { + Client client = Client.generateClient(); + + String asset_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String address = "sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em"; + // build transaction obtain a Template object + // account 0G1RPP6OG0A06 has two keys + Template template = new Transaction.Builder() + .setTtl(10) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(address) + .setAssetId(asset_id) + .setAmount(30000000) + ).build(client); + logger.info("template: " + template.toJson()); + // use Template object's raw_transaction id to decode raw_transaction obtain a RawTransaction object + RawTransaction decodedTx = RawTransaction.decode(client, template.rawTransaction); + logger.info("decodeTx: " + decodedTx.toJson()); + // need a private key array + String[] privateKeys = new String[]{"08bdbd6c22856c5747c930f64d0e5d58ded17c4473910c6c0c3f94e485833a436247976253c8e29e961041ad8dfad9309744255364323163837cbef2483b4f67", + "40c821f736f60805ad59b1fea158762fa6355e258601dfb49dda6f672092ae5adf072d5cab2ceaaa0d68dd3fe7fa04869d95afed8c20069f446a338576901e1b"}; + logger.info("private key 1:" + privateKeys[0]); + logger.info("private key 2:" + privateKeys[1]); + // call offline sign method to obtain a basic offline signed template + Signatures signatures = new SignaturesImpl(); + Template basicSigned = signatures.generateSignatures(privateKeys, template, decodedTx); + logger.info("basic signed raw: " + basicSigned.toJson()); + // call sign transaction api to calculate whole raw_transaction id + // sign password is None or another random String + Template result = new Transaction.SignerBuilder().sign(client, + basicSigned, ""); + logger.info("result raw_transaction: " + result.toJson()); + // success to submit transaction +} +``` +Multi-keys and Multi-inputs Example: + +```java +@Test +// 使用 SDK 来构造 Template 对象参数, 多签, 多输入 +public void testSignMultiKeysMultiInputs() throws BytomException { + Client client = Client.generateClient(); + + String asset_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String address = "sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em"; + // build transaction obtain a Template object + Template template = new Transaction.Builder() + .setTtl(10) + // 1 input + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") // Multi-keys account + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(300000000) + ) // 2 input + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1Q6V1P00A02") // Multi-keys account + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1Q6V1P00A02") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(address) + .setAssetId(asset_id) + .setAmount(60000000) + ).build(client); + logger.info("template: " + template.toJson()); + // use Template object's raw_transaction id to decode raw_transaction obtain a RawTransaction object + RawTransaction decodedTx = RawTransaction.decode(client, template.rawTransaction); + logger.info("decodeTx: " + decodedTx.toJson()); + // need a private key array + String[] privateKeys = new String[]{"08bdbd6c22856c5747c930f64d0e5d58ded17c4473910c6c0c3f94e485833a436247976253c8e29e961041ad8dfad9309744255364323163837cbef2483b4f67", + "40c821f736f60805ad59b1fea158762fa6355e258601dfb49dda6f672092ae5adf072d5cab2ceaaa0d68dd3fe7fa04869d95afed8c20069f446a338576901e1b", + "08bdbd6c22856c5747c930f64d0e5d58ded17c4473910c6c0c3f94e485833a436247976253c8e29e961041ad8dfad9309744255364323163837cbef2483b4f67"}; + logger.info("private key 1:" + privateKeys[0]); + logger.info("private key 2:" + privateKeys[1]); + // call offline sign method to obtain a basic offline signed template + Signatures signatures = new SignaturesImpl(); + Template basicSigned = signatures.generateSignatures(privateKeys, template, decodedTx); + logger.info("basic signed raw: " + basicSigned.toJson()); + // call sign transaction api to calculate whole raw_transaction id + // sign password is None or another random String + Template result = new Transaction.SignerBuilder().sign(client, + basicSigned, ""); + logger.info("result raw_transaction: " + result.toJson()); + // success to submit transaction +} +``` + diff --git a/tx-signer/pom.xml b/tx-signer/pom.xml new file mode 100644 index 0000000..a4312a6 --- /dev/null +++ b/tx-signer/pom.xml @@ -0,0 +1,124 @@ + + + + 4.0.0 + + + io.bytom + bytom-sdk-all + 1.0.0 + + + io.bytom + tx-signer + 1.0-SNAPSHOT + + tx-signer + + http://www.example.com + + + UTF-8 + 1.7 + 1.7 + + + + + junit + junit + 4.12 + test + + + + com.google.crypto.tink + tink + 1.1.1 + + + + org.bouncycastle + bcprov-ext-jdk15on + 1.55 + + + + + net.i2p.crypto + eddsa + 0.3.0 + + + log4j + log4j + 1.2.17 + + + com.squareup.okhttp + okhttp + 2.5.0 + + + com.squareup.okhttp + mockwebserver + 2.5.0 + test + + + com.google.code.gson + gson + 2.8.0 + + + org.bouncycastle + bcprov-jdk15on + 1.59 + + + + + + + + maven-clean-plugin + 3.0.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.7.0 + + + maven-surefire-plugin + 2.20.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + maven-assembly-plugin + + + jar-with-dependencies + + + + + + + diff --git a/tx-signer/src/main/java/com/google/crypto/tink/subtle/Bytes.java b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Bytes.java new file mode 100644 index 0000000..af98103 --- /dev/null +++ b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Bytes.java @@ -0,0 +1,182 @@ +// Copyright 2017 Google Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.subtle; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.util.Arrays; + +/** + * Helper methods that deal with byte arrays. + * + * @since 1.0.0 + */ +public final class Bytes { + /** + * Best effort fix-timing array comparison. + * + * @return true if two arrays are equal. + */ + public static final boolean equal(final byte[] x, final byte[] y) { + if (x == null || y == null) { + return false; + } + if (x.length != y.length) { + return false; + } + int res = 0; + for (int i = 0; i < x.length; i++) { + res |= x[i] ^ y[i]; + } + return res == 0; + } + + /** + * Returns the concatenation of the input arrays in a single array. For example, {@code concat(new + * byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}. + * + * @return a single array containing all the values from the source arrays, in order + */ + public static byte[] concat(byte[]... chunks) throws GeneralSecurityException { + int length = 0; + for (byte[] chunk : chunks) { + if (length > Integer.MAX_VALUE - chunk.length) { + throw new GeneralSecurityException("exceeded size limit"); + } + length += chunk.length; + } + byte[] res = new byte[length]; + int pos = 0; + for (byte[] chunk : chunks) { + System.arraycopy(chunk, 0, res, pos, chunk.length); + pos += chunk.length; + } + return res; + } + + /** + * Computes the xor of two byte arrays, specifying offsets and the length to xor. + * + * @return a new byte[] of length len. + */ + public static final byte[] xor( + final byte[] x, int offsetX, final byte[] y, int offsetY, int len) { + if (len < 0 || x.length - len < offsetX || y.length - len < offsetY) { + throw new IllegalArgumentException( + "That combination of buffers, offsets and length to xor result in out-of-bond accesses."); + } + byte[] res = new byte[len]; + for (int i = 0; i < len; i++) { + res[i] = (byte) (x[i + offsetX] ^ y[i + offsetY]); + } + return res; + } + + /** + * Computes the xor of two byte buffers, specifying the length to xor, and + * stores the result to {@code output}. + * + * @return a new byte[] of length len. + */ + public static final void xor(ByteBuffer output, ByteBuffer x, ByteBuffer y, int len) { + if (len < 0 || x.remaining() < len || y.remaining() < len || output.remaining() < len) { + throw new IllegalArgumentException( + "That combination of buffers, offsets and length to xor result in out-of-bond accesses."); + } + for (int i = 0; i < len; i++) { + output.put((byte) (x.get() ^ y.get())); + } + } + + /** + * Computes the xor of two byte arrays of equal size. + * + * @return a new byte[] of length x.length. + */ + public static final byte[] xor(final byte[] x, final byte[] y) { + if (x.length != y.length) { + throw new IllegalArgumentException("The lengths of x and y should match."); + } + return xor(x, 0, y, 0, x.length); + } + + /** + * xors b to the end of a. + * + * @return a new byte[] of length x.length. + */ + public static final byte[] xorEnd(final byte[] a, final byte[] b) { + if (a.length < b.length) { + throw new IllegalArgumentException("xorEnd requires a.length >= b.length"); + } + int paddingLength = a.length - b.length; + byte[] res = Arrays.copyOf(a, a.length); + for (int i = 0; i < b.length; i++) { + res[paddingLength + i] ^= b[i]; + } + return res; + } + + // TODO(thaidn): add checks for boundary conditions/overflows. + /** + * Transforms a passed value to a LSB first byte array with the size of the specified capacity + * + * @param capacity size of the resulting byte array + * @param value that should be represented as a byte array + */ + public static byte[] intToByteArray(int capacity, int value) { + final byte[] result = new byte[capacity]; + for (int i = 0; i < capacity; i++) { + result[i] = (byte) ((value >> (8 * i)) & 0xFF); + } + return result; + } + + /** + * Transforms a passed LSB first byte array to an int + * + * @param bytes that should be transformed to a byte array + */ + public static int byteArrayToInt(byte[] bytes) { + return byteArrayToInt(bytes, bytes.length); + } + + /** + * Transforms a passed LSB first byte array to an int + * + * @param bytes that should be transformed to a byte array + * @param length amount of the passed {@code bytes} that should be transformed + */ + public static int byteArrayToInt(byte[] bytes, int length) { + return byteArrayToInt(bytes, 0, length); + } + + /** + * Transforms a passed LSB first byte array to an int + * + * @param bytes that should be transformed to a byte array + * @param offset start index to start the transformation + * @param length amount of the passed {@code bytes} that should be transformed + */ + public static int byteArrayToInt(byte[] bytes, int offset, int length) { + int value = 0; + for (int i = 0; i < length; i++) { + value += (bytes[i + offset] & 0xFF) << (i * 8); + } + return value; + } +} diff --git a/tx-signer/src/main/java/com/google/crypto/tink/subtle/Curve25519.java b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Curve25519.java new file mode 100644 index 0000000..7e22ef4 --- /dev/null +++ b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Curve25519.java @@ -0,0 +1,429 @@ +// Copyright 2017 Google Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.subtle; + +import com.google.crypto.tink.annotations.Alpha; +import java.security.InvalidKeyException; +import java.util.Arrays; + +/** + * This class implements point arithmetic on the elliptic curve Curve25519. + * + *

This class only implements point arithmetic, if you want to use the ECDH Curve25519 function, + * please checkout {@link X25519}. + * + *

This implementation is based on curve255-donna C + * implementation. + */ +@Alpha +final class Curve25519 { + // https://cr.yp.to/ecdh.html#validate doesn't recommend validating peer's public key. However, + // validating public key doesn't harm security and in certain cases, prevents unwanted edge + // cases. + // As we clear the most significant bit of peer's public key, we don't have to include public keys + // that are larger than 2^255. + static final byte[][] BANNED_PUBLIC_KEYS = + new byte[][] { + // 0 + new byte[] { + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }, + // 1 + new byte[] { + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }, + // 325606250916557431795983626356110631294008115727848805560023387167927233504 + new byte[] { + (byte) 0xe0, (byte) 0xeb, (byte) 0x7a, (byte) 0x7c, + (byte) 0x3b, (byte) 0x41, (byte) 0xb8, (byte) 0xae, + (byte) 0x16, (byte) 0x56, (byte) 0xe3, (byte) 0xfa, + (byte) 0xf1, (byte) 0x9f, (byte) 0xc4, (byte) 0x6a, + (byte) 0xda, (byte) 0x09, (byte) 0x8d, (byte) 0xeb, + (byte) 0x9c, (byte) 0x32, (byte) 0xb1, (byte) 0xfd, + (byte) 0x86, (byte) 0x62, (byte) 0x05, (byte) 0x16, + (byte) 0x5f, (byte) 0x49, (byte) 0xb8, (byte) 0x00, + }, + // 39382357235489614581723060781553021112529911719440698176882885853963445705823 + new byte[] { + (byte) 0x5f, (byte) 0x9c, (byte) 0x95, (byte) 0xbc, + (byte) 0xa3, (byte) 0x50, (byte) 0x8c, (byte) 0x24, + (byte) 0xb1, (byte) 0xd0, (byte) 0xb1, (byte) 0x55, + (byte) 0x9c, (byte) 0x83, (byte) 0xef, (byte) 0x5b, + (byte) 0x04, (byte) 0x44, (byte) 0x5c, (byte) 0xc4, + (byte) 0x58, (byte) 0x1c, (byte) 0x8e, (byte) 0x86, + (byte) 0xd8, (byte) 0x22, (byte) 0x4e, (byte) 0xdd, + (byte) 0xd0, (byte) 0x9f, (byte) 0x11, (byte) 0x57 + }, + // 2^255 - 19 - 1 + new byte[] { + (byte) 0xec, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f, + }, + // 2^255 - 19 + new byte[] { + (byte) 0xed, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f + }, + // 2^255 - 19 + 1 + new byte[] { + (byte) 0xee, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f + } + }; + /** + * Computes Montgomery's double-and-add formulas. + * + *

On entry and exit, the absolute value of the limbs of all inputs and outputs are < 2^26. + * + * @param x2 x projective coordinate of output 2Q, long form + * @param z2 z projective coordinate of output 2Q, long form + * @param x3 x projective coordinate of output Q + Q', long form + * @param z3 z projective coordinate of output Q + Q', long form + * @param x x projective coordinate of input Q, short form, destroyed + * @param z z projective coordinate of input Q, short form, destroyed + * @param xprime x projective coordinate of input Q', short form, destroyed + * @param zprime z projective coordinate of input Q', short form, destroyed + * @param qmqp input Q - Q', short form, preserved + */ + private static void monty( + long[] x2, + long[] z2, + long[] x3, + long[] z3, + long[] x, + long[] z, + long[] xprime, + long[] zprime, + long[] qmqp) { + long[] origx = Arrays.copyOf(x, Field25519.LIMB_CNT); + long[] zzz = new long[19]; + long[] xx = new long[19]; + long[] zz = new long[19]; + long[] xxprime = new long[19]; + long[] zzprime = new long[19]; + long[] zzzprime = new long[19]; + long[] xxxprime = new long[19]; + + Field25519.sum(x, z); + // |x[i]| < 2^27 + Field25519.sub(z, origx); // does x - z + // |z[i]| < 2^27 + + long[] origxprime = Arrays.copyOf(xprime, Field25519.LIMB_CNT); + Field25519.sum(xprime, zprime); + // |xprime[i]| < 2^27 + Field25519.sub(zprime, origxprime); + // |zprime[i]| < 2^27 + Field25519.product(xxprime, xprime, z); + // |xxprime[i]| < 14*2^54: the largest product of two limbs will be < 2^(27+27) and {@ref + // Field25519#product} adds together, at most, 14 of those products. (Approximating that to + // 2^58 doesn't work out.) + Field25519.product(zzprime, x, zprime); + // |zzprime[i]| < 14*2^54 + Field25519.reduceSizeByModularReduction(xxprime); + Field25519.reduceCoefficients(xxprime); + // |xxprime[i]| < 2^26 + Field25519.reduceSizeByModularReduction(zzprime); + Field25519.reduceCoefficients(zzprime); + // |zzprime[i]| < 2^26 + System.arraycopy(xxprime, 0, origxprime, 0, Field25519.LIMB_CNT); + Field25519.sum(xxprime, zzprime); + // |xxprime[i]| < 2^27 + Field25519.sub(zzprime, origxprime); + // |zzprime[i]| < 2^27 + Field25519.square(xxxprime, xxprime); + // |xxxprime[i]| < 2^26 + Field25519.square(zzzprime, zzprime); + // |zzzprime[i]| < 2^26 + Field25519.product(zzprime, zzzprime, qmqp); + // |zzprime[i]| < 14*2^52 + Field25519.reduceSizeByModularReduction(zzprime); + Field25519.reduceCoefficients(zzprime); + // |zzprime[i]| < 2^26 + System.arraycopy(xxxprime, 0, x3, 0, Field25519.LIMB_CNT); + System.arraycopy(zzprime, 0, z3, 0, Field25519.LIMB_CNT); + + Field25519.square(xx, x); + // |xx[i]| < 2^26 + Field25519.square(zz, z); + // |zz[i]| < 2^26 + Field25519.product(x2, xx, zz); + // |x2[i]| < 14*2^52 + Field25519.reduceSizeByModularReduction(x2); + Field25519.reduceCoefficients(x2); + // |x2[i]| < 2^26 + Field25519.sub(zz, xx); // does zz = xx - zz + // |zz[i]| < 2^27 + Arrays.fill(zzz, Field25519.LIMB_CNT, zzz.length - 1, 0); + Field25519.scalarProduct(zzz, zz, 121665); + // |zzz[i]| < 2^(27+17) + // No need to call reduceSizeByModularReduction here: scalarProduct doesn't increase the degree + // of its input. + Field25519.reduceCoefficients(zzz); + // |zzz[i]| < 2^26 + Field25519.sum(zzz, xx); + // |zzz[i]| < 2^27 + Field25519.product(z2, zz, zzz); + // |z2[i]| < 14*2^(26+27) + Field25519.reduceSizeByModularReduction(z2); + Field25519.reduceCoefficients(z2); + // |z2|i| < 2^26 + } + + /** + * Conditionally swap two reduced-form limb arrays if {@code iswap} is 1, but leave them unchanged + * if {@code iswap} is 0. Runs in data-invariant time to avoid side-channel attacks. + * + *

NOTE that this function requires that {@code iswap} be 1 or 0; other values give wrong + * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the + * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must + * have magnitude less than Integer.MAX_VALUE. + */ + static void swapConditional(long[] a, long[] b, int iswap) { + int swap = -iswap; + for (int i = 0; i < Field25519.LIMB_CNT; i++) { + int x = swap & (((int) a[i]) ^ ((int) b[i])); + a[i] = ((int) a[i]) ^ x; + b[i] = ((int) b[i]) ^ x; + } + } + + /** + * Conditionally copies a reduced-form limb arrays {@code b} into {@code a} if {@code icopy} is 1, + * but leave {@code a} unchanged if 'iswap' is 0. Runs in data-invariant time to avoid + * side-channel attacks. + * + *

NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong + * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the + * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must + * have magnitude less than Integer.MAX_VALUE. + */ + static void copyConditional(long[] a, long[] b, int icopy) { + int copy = -icopy; + for (int i = 0; i < Field25519.LIMB_CNT; i++) { + int x = copy & (((int) a[i]) ^ ((int) b[i])); + a[i] = ((int) a[i]) ^ x; + } + } + + /** + * Calculates nQ where Q is the x-coordinate of a point on the curve. + * + * @param resultx the x projective coordinate of the resulting curve point (short form). + * @param n a little endian, 32-byte number. + * @param qBytes a little endian, 32-byte number representing the public point' x coordinate. + * @throws InvalidKeyException iff the public key is in the banned list or its length is not + * 32-byte. + * @throws IllegalStateException iff there is arithmetic error. + */ + static void curveMult(long[] resultx, byte[] n, byte[] qBytes) throws InvalidKeyException { + validatePubKeyAndClearMsb(qBytes); + + long[] q = Field25519.expand(qBytes); + long[] nqpqx = new long[19]; + long[] nqpqz = new long[19]; + nqpqz[0] = 1; + long[] nqx = new long[19]; + nqx[0] = 1; + long[] nqz = new long[19]; + long[] nqpqx2 = new long[19]; + long[] nqpqz2 = new long[19]; + nqpqz2[0] = 1; + long[] nqx2 = new long[19]; + long[] nqz2 = new long[19]; + nqz2[0] = 1; + long[] t = new long[19]; + + System.arraycopy(q, 0, nqpqx, 0, Field25519.LIMB_CNT); + + for (int i = 0; i < Field25519.FIELD_LEN; i++) { + int b = n[Field25519.FIELD_LEN - i - 1] & 0xff; + for (int j = 0; j < 8; j++) { + int bit = (b >> (7 - j)) & 1; + + swapConditional(nqx, nqpqx, bit); + swapConditional(nqz, nqpqz, bit); + monty(nqx2, nqz2, nqpqx2, nqpqz2, nqx, nqz, nqpqx, nqpqz, q); + swapConditional(nqx2, nqpqx2, bit); + swapConditional(nqz2, nqpqz2, bit); + + t = nqx; + nqx = nqx2; + nqx2 = t; + t = nqz; + nqz = nqz2; + nqz2 = t; + t = nqpqx; + nqpqx = nqpqx2; + nqpqx2 = t; + t = nqpqz; + nqpqz = nqpqz2; + nqpqz2 = t; + } + } + + // Computes nqx/nqz. + long[] zmone = new long[Field25519.LIMB_CNT]; + Field25519.inverse(zmone, nqz); + Field25519.mult(resultx, nqx, zmone); + + // Nowadays it should be standard to protect public key crypto against flaws. I.e. if there is a + // computation error through a faulty CPU or if the implementation contains a bug, then if + // possible this should be detected at run time. + // + // The situation is a bit more tricky for X25519 where for example the implementation + // proposed in https://tools.ietf.org/html/rfc7748 only uses the x-coordinate. However, a + // verification is still possible, but depends on the actual computation. + // + // Tink's Java implementation is equivalent to RFC7748. We will use the loop invariant in the + // Montgomery ladder to detect fault computation. In particular, we use the following invariant: + // q, resultx, nqpqx/nqpqx are x coordinates of 3 collinear points q, n*q, (n + 1)*q. + if (!isCollinear(q, resultx, nqpqx, nqpqz)) { + throw new IllegalStateException( + "Arithmetic error in curve multiplication with the public key: " + + Hex.encode(qBytes)); + } + } + + /** + * Validates public key and clear its most significant bit. + * + * @throws InvalidKeyException iff the {@code pubKey} is in the banned list or its length is not + * 32-byte. + */ + private static void validatePubKeyAndClearMsb(byte[] pubKey) throws InvalidKeyException { + if (pubKey.length != 32) { + throw new InvalidKeyException("Public key length is not 32-byte"); + } + // Clears the most significant bit as in the method decodeUCoordinate() of RFC7748. + pubKey[31] &= (byte) 0x7f; + + for (int i = 0; i < BANNED_PUBLIC_KEYS.length; i++) { + if (Bytes.equal(BANNED_PUBLIC_KEYS[i], pubKey)) { + throw new InvalidKeyException("Banned public key: " + Hex.encode(BANNED_PUBLIC_KEYS[i])); + } + } + } + + /** + * Checks whether there are three collinear points with x coordinate x1, x2, x3/z3. + * + * @return true if three collinear points with x coordianate x1, x2, x3/z3 are collinear. + */ + private static boolean isCollinear(long[] x1, long[] x2, long[] x3, long[] z3) { + // If x1, x2, x3 (in this method x3 is represented as x3/z3) are the x-coordinates of three + // collinear points on a curve, then they satisfy the equation + // y^2 = x^3 + ax^2 + x + // They also satisfy the equation + // 0 = (x - x1)(x - x2)(x - x3) + // = x^3 + Ax^2 + Bx + C + // where + // A = - x1 - x2 - x3 + // B = x1*x2 + x2*x3 + x3*x1 + // C = - x1*x2*x3 + // Hence, the three points also satisfy + // y^2 = (a - A)x^2 + (1 - B)x - C + // This is a quadratic curve. Three distinct collinear points can only be on a quadratic + // curve if the quadratic curve has a line as component. And if a quadratic curve has a line + // as component then its discriminant is 0. + // Therefore, discriminant((a - A)x^2 + (1-B)x - C) = 0. + // In particular: + // a = 486662 + // lhs = 4 * ((x1 + x2 + a) * z3 + x3) * (x1 * x2 * x3) + // rhs = ((x1 * x2 - 1) * z3 + x3 * (x1 + x2))**2 + // assert (lhs - rhs) == 0 + // + // There are 2 cases that we haven't discussed: + // + // * If x1 and x2 are both points with y-coordinate 0 then the argument doesn't hold. + // However, our ECDH computation doesn't allow points of low order (see {@code + // validatePublicKey}). Therefore, this edge case never happen. + // + // * x1, x2 or x3/y3 may be points on the twist. If so, they satisfy the equation + // 2y^2 = x^3 + ax^2 + x + // Hence, the three points also satisfy + // 2y^2 = (a - A)x^2 + (1 - B)x - C + // Thus, this collinear check should work for this case too. + long[] x1multx2 = new long[Field25519.LIMB_CNT]; + long[] x1addx2 = new long[Field25519.LIMB_CNT]; + long[] lhs = new long[Field25519.LIMB_CNT + 1]; + long[] t = new long[Field25519.LIMB_CNT + 1]; + long[] t2 = new long[Field25519.LIMB_CNT + 1]; + Field25519.mult(x1multx2, x1, x2); + Field25519.sum(x1addx2, x1, x2); + long[] a = new long[Field25519.LIMB_CNT]; + a[0] = 486662; + // t = x1 + x2 + a + Field25519.sum(t, x1addx2, a); + // t = (x1 + x2 + a) * z3 + Field25519.mult(t, t, z3); + // t = (x1 + x2 + a) * z3 + x3 + Field25519.sum(t, x3); + // t = ((x1 + x2 + a) * z3 + x3) * x1 * x2 + Field25519.mult(t, t, x1multx2); + // t = ((x1 + x2 + a) * z3 + x3) * (x1 * x2 * x3) + Field25519.mult(t, t, x3); + Field25519.scalarProduct(lhs, t, 4); + Field25519.reduceCoefficients(lhs); + + // t = x1 * x2 * z3 + Field25519.mult(t, x1multx2, z3); + // t = x1 * x2 * z3 - z3 + Field25519.sub(t, t, z3); + // t2 = (x1 + x2) * x3 + Field25519.mult(t2, x1addx2, x3); + // t = x1 * x2 * z3 - z3 + (x1 + x2) * x3 + Field25519.sum(t, t, t2); + // t = (x1 * x2 * z3 - z3 + (x1 + x2) * x3)^2 + Field25519.square(t, t); + return Bytes.equal(Field25519.contract(lhs), Field25519.contract(t)); + } +} diff --git a/tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519.java b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519.java new file mode 100644 index 0000000..b864ea8 --- /dev/null +++ b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519.java @@ -0,0 +1,1612 @@ +// Copyright 2017 Google Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.subtle; + +import static com.google.crypto.tink.subtle.Ed25519Constants.B2; +import static com.google.crypto.tink.subtle.Ed25519Constants.B_TABLE; +import static com.google.crypto.tink.subtle.Ed25519Constants.D; +import static com.google.crypto.tink.subtle.Ed25519Constants.D2; +import static com.google.crypto.tink.subtle.Ed25519Constants.SQRTM1; +import static com.google.crypto.tink.subtle.Field25519.FIELD_LEN; +import static com.google.crypto.tink.subtle.Field25519.LIMB_CNT; + +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * This implementation is based on the ed25519/ref10 implementation in NaCl. + * + *

It implements this twisted Edwards curve: + * + *

+ * -x^2 + y^2 = 1 + (-121665 / 121666 mod 2^255-19)*x^2*y^2
+ * 
+ * + * @see Bernstein D.J., Birkner P., Joye M., Lange + * T., Peters C. (2008) Twisted Edwards Curves + * @see Hisil H., Wong K.KH., Carter G., Dawson E. + * (2008) Twisted Edwards Curves Revisited + */ +public final class Ed25519 { + + public static final int SECRET_KEY_LEN = FIELD_LEN; + public static final int PUBLIC_KEY_LEN = FIELD_LEN; + public static final int SIGNATURE_LEN = FIELD_LEN * 2; + + // (x = 0, y = 1) point + private static final CachedXYT CACHED_NEUTRAL = new CachedXYT( + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + private static final PartialXYZT NEUTRAL = new PartialXYZT( + new XYZ(new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}), + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + /** + * Projective point representation (X:Y:Z) satisfying x = X/Z, y = Y/Z + * + * Note that this is referred as ge_p2 in ref10 impl. + * Also note that x = X, y = Y and z = Z below following Java coding style. + * + * See + * Koyama K., Tsuruoka Y. (1993) Speeding up Elliptic Cryptosystems by Using a Signed Binary + * Window Method. + * + * https://hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + */ + private static class XYZ { + + final long[] x; + final long[] y; + final long[] z; + + XYZ() { + this(new long[LIMB_CNT], new long[LIMB_CNT], new long[LIMB_CNT]); + } + + XYZ(long[] x, long[] y, long[] z) { + this.x = x; + this.y = y; + this.z = z; + } + + XYZ(XYZ xyz) { + x = Arrays.copyOf(xyz.x, LIMB_CNT); + y = Arrays.copyOf(xyz.y, LIMB_CNT); + z = Arrays.copyOf(xyz.z, LIMB_CNT); + } + + XYZ(PartialXYZT partialXYZT) { + this(); + fromPartialXYZT(this, partialXYZT); + } + + /** + * ge_p1p1_to_p2.c + */ + static XYZ fromPartialXYZT(XYZ out, PartialXYZT in) { + Field25519.mult(out.x, in.xyz.x, in.t); + Field25519.mult(out.y, in.xyz.y, in.xyz.z); + Field25519.mult(out.z, in.xyz.z, in.t); + return out; + } + + /** + * Encodes this point to bytes. + */ + byte[] toBytes() { + long[] recip = new long[LIMB_CNT]; + long[] x = new long[LIMB_CNT]; + long[] y = new long[LIMB_CNT]; + Field25519.inverse(recip, z); + Field25519.mult(x, this.x, recip); + Field25519.mult(y, this.y, recip); + byte[] s = Field25519.contract(y); + s[31] = (byte) (s[31] ^ (getLsb(x) << 7)); + return s; + } + + /** Checks that the point is on curve */ + boolean isOnCurve() { + long[] x2 = new long[LIMB_CNT]; + Field25519.square(x2, x); + long[] y2 = new long[LIMB_CNT]; + Field25519.square(y2, y); + long[] z2 = new long[LIMB_CNT]; + Field25519.square(z2, z); + long[] z4 = new long[LIMB_CNT]; + Field25519.square(z4, z2); + long[] lhs = new long[LIMB_CNT]; + // lhs = y^2 - x^2 + Field25519.sub(lhs, y2, x2); + // lhs = z^2 * (y2 - x2) + Field25519.mult(lhs, lhs, z2); + long[] rhs = new long[LIMB_CNT]; + // rhs = x^2 * y^2 + Field25519.mult(rhs, x2, y2); + // rhs = D * x^2 * y^2 + Field25519.mult(rhs, rhs, D); + // rhs = z^4 + D * x^2 * y^2 + Field25519.sum(rhs, z4); + // z^2 (y^2 - x^2) == z^4 + D * x^2 * y^2 + return Bytes.equal(Field25519.contract(lhs), Field25519.contract(rhs)); + } + } + + /** + * Represents extended projective point representation (X:Y:Z:T) satisfying x = X/Z, y = Y/Z, + * XY = ZT + * + * Note that this is referred as ge_p3 in ref10 impl. + * Also note that t = T below following Java coding style. + * + * See + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + * + * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html + */ + private static class XYZT { + + final XYZ xyz; + final long[] t; + + XYZT() { + this(new XYZ(), new long[LIMB_CNT]); + } + + XYZT(XYZ xyz, long[] t) { + this.xyz = xyz; + this.t = t; + } + + XYZT(PartialXYZT partialXYZT) { + this(); + fromPartialXYZT(this, partialXYZT); + } + + /** + * ge_p1p1_to_p2.c + */ + private static XYZT fromPartialXYZT(XYZT out, PartialXYZT in) { + Field25519.mult(out.xyz.x, in.xyz.x, in.t); + Field25519.mult(out.xyz.y, in.xyz.y, in.xyz.z); + Field25519.mult(out.xyz.z, in.xyz.z, in.t); + Field25519.mult(out.t, in.xyz.x, in.xyz.y); + return out; + } + + /** + * Decodes {@code s} into an extented projective point. + * See Section 5.1.3 Decoding in https://tools.ietf.org/html/rfc8032#section-5.1.3 + */ + private static XYZT fromBytesNegateVarTime(byte[] s) throws GeneralSecurityException { + long[] x = new long[LIMB_CNT]; + long[] y = Field25519.expand(s); + long[] z = new long[LIMB_CNT]; z[0] = 1; + long[] t = new long[LIMB_CNT]; + long[] u = new long[LIMB_CNT]; + long[] v = new long[LIMB_CNT]; + long[] vxx = new long[LIMB_CNT]; + long[] check = new long[LIMB_CNT]; + Field25519.square(u, y); + Field25519.mult(v, u, D); + Field25519.sub(u, u, z); // u = y^2 - 1 + Field25519.sum(v, v, z); // v = dy^2 + 1 + + long[] v3 = new long[LIMB_CNT]; + Field25519.square(v3, v); + Field25519.mult(v3, v3, v); // v3 = v^3 + Field25519.square(x, v3); + Field25519.mult(x, x, v); + Field25519.mult(x, x, u); // x = uv^7 + + pow2252m3(x, x); // x = (uv^7)^((q-5)/8) + Field25519.mult(x, x, v3); + Field25519.mult(x, x, u); // x = uv^3(uv^7)^((q-5)/8) + + Field25519.square(vxx, x); + Field25519.mult(vxx, vxx, v); + Field25519.sub(check, vxx, u); // vx^2-u + if (isNonZeroVarTime(check)) { + Field25519.sum(check, vxx, u); // vx^2+u + if (isNonZeroVarTime(check)) { + throw new GeneralSecurityException("Cannot convert given bytes to extended projective " + + "coordinates. No square root exists for modulo 2^255-19"); + } + Field25519.mult(x, x, SQRTM1); + } + + if (!isNonZeroVarTime(x) && (s[31] & 0xff) >> 7 != 0) { + throw new GeneralSecurityException("Cannot convert given bytes to extended projective " + + "coordinates. Computed x is zero and encoded x's least significant bit is not zero"); + } + if (getLsb(x) == ((s[31] & 0xff) >> 7)) { + neg(x, x); + } + + Field25519.mult(t, x, y); + return new XYZT(new XYZ(x, y, z), t); + } + } + + /** + * Partial projective point representation ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + * + * Note that this is referred as complete form in the original ref10 impl (ge_p1p1). + * Also note that t = T below following Java coding style. + * + * Although this has the same types as XYZT, it is redefined to have its own type so that it is + * readable and 1:1 corresponds to ref10 impl. + * + * Can be converted to XYZT as follows: + * X1 = X * T = x * Z * T = x * Z1 + * Y1 = Y * Z = y * T * Z = y * Z1 + * Z1 = Z * T = Z * T + * T1 = X * Y = x * Z * y * T = x * y * Z1 = X1Y1 / Z1 + */ + private static class PartialXYZT { + + final XYZ xyz; + final long[] t; + + PartialXYZT() { + this(new XYZ(), new long[LIMB_CNT]); + } + + PartialXYZT(XYZ xyz, long[] t) { + this.xyz = xyz; + this.t = t; + } + + PartialXYZT(PartialXYZT other) { + xyz = new XYZ(other.xyz); + t = Arrays.copyOf(other.t, LIMB_CNT); + } + } + + /** + * Corresponds to the caching mentioned in the last paragraph of Section 3.1 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + * with Z = 1. + */ + static class CachedXYT { + + final long[] yPlusX; + final long[] yMinusX; + final long[] t2d; + + CachedXYT() { + this(new long[LIMB_CNT], new long[LIMB_CNT], new long[LIMB_CNT]); + } + + /** + * Creates a cached XYZT with Z = 1 + * + * @param yPlusX y + x + * @param yMinusX y - x + * @param t2d 2d * xy + */ + CachedXYT(long[] yPlusX, long[] yMinusX, long[] t2d) { + this.yPlusX = yPlusX; + this.yMinusX = yMinusX; + this.t2d = t2d; + } + + CachedXYT(CachedXYT other) { + yPlusX = Arrays.copyOf(other.yPlusX, LIMB_CNT); + yMinusX = Arrays.copyOf(other.yMinusX, LIMB_CNT); + t2d = Arrays.copyOf(other.t2d, LIMB_CNT); + } + + // z is one implicitly, so this just copies {@code in} to {@code output}. + void multByZ(long[] output, long[] in) { + System.arraycopy(in, 0, output, 0, LIMB_CNT); + } + + /** + * If icopy is 1, copies {@code other} into this point. Time invariant wrt to icopy value. + */ + void copyConditional(CachedXYT other, int icopy) { + Curve25519.copyConditional(yPlusX, other.yPlusX, icopy); + Curve25519.copyConditional(yMinusX, other.yMinusX, icopy); + Curve25519.copyConditional(t2d, other.t2d, icopy); + } + } + + private static class CachedXYZT extends CachedXYT { + + private final long[] z; + + CachedXYZT() { + this(new long[LIMB_CNT], new long[LIMB_CNT], new long[LIMB_CNT], new long[LIMB_CNT]); + } + + /** + * ge_p3_to_cached.c + */ + CachedXYZT(XYZT xyzt) { + this(); + Field25519.sum(yPlusX, xyzt.xyz.y, xyzt.xyz.x); + Field25519.sub(yMinusX, xyzt.xyz.y, xyzt.xyz.x); + System.arraycopy(xyzt.xyz.z, 0, z, 0, LIMB_CNT); + Field25519.mult(t2d, xyzt.t, D2); + } + + /** + * Creates a cached XYZT + * + * @param yPlusX Y + X + * @param yMinusX Y - X + * @param z Z + * @param t2d 2d * (XY/Z) + */ + CachedXYZT(long[] yPlusX, long[] yMinusX, long[] z, long[] t2d) { + super(yPlusX, yMinusX, t2d); + this.z = z; + } + + @Override + public void multByZ(long[] output, long[] in) { + Field25519.mult(output, in, z); + } + } + + /** + * Addition defined in Section 3.1 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + * + * Please note that this is a partial of the operation listed there leaving out the final + * conversion from PartialXYZT to XYZT. + * + * @param extended extended projective point input + * @param cached cached projective point input + */ + private static void add(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) { + long[] t = new long[LIMB_CNT]; + + // Y1 + X1 + Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x); + + // Y1 - X1 + Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x); + + // A = (Y1 - X1) * (Y2 - X2) + Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yMinusX); + + // B = (Y1 + X1) * (Y2 + X2) + Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yPlusX); + + // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper) + Field25519.mult(partialXYZT.t, extended.t, cached.t2d); + + // Z1 * Z2 + cached.multByZ(partialXYZT.xyz.x, extended.xyz.z); + + // D = 2 * Z1 * Z2 + Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x); + + // X3 = B - A + Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Y3 = B + A + Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Z3 = D + C + Field25519.sum(partialXYZT.xyz.z, t, partialXYZT.t); + + // T3 = D - C + Field25519.sub(partialXYZT.t, t, partialXYZT.t); + } + + /** + * Based on the addition defined in Section 3.1 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + * + * Please note that this is a partial of the operation listed there leaving out the final + * conversion from PartialXYZT to XYZT. + * + * @param extended extended projective point input + * @param cached cached projective point input + */ + private static void sub(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) { + long[] t = new long[LIMB_CNT]; + + // Y1 + X1 + Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x); + + // Y1 - X1 + Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x); + + // A = (Y1 - X1) * (Y2 + X2) + Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yPlusX); + + // B = (Y1 + X1) * (Y2 - X2) + Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yMinusX); + + // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper) + Field25519.mult(partialXYZT.t, extended.t, cached.t2d); + + // Z1 * Z2 + cached.multByZ(partialXYZT.xyz.x, extended.xyz.z); + + // D = 2 * Z1 * Z2 + Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x); + + // X3 = B - A + Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Y3 = B + A + Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Z3 = D - C + Field25519.sub(partialXYZT.xyz.z, t, partialXYZT.t); + + // T3 = D + C + Field25519.sum(partialXYZT.t, t, partialXYZT.t); + } + + /** + * Doubles {@code p} and puts the result into this PartialXYZT. + * + * This is based on the addition defined in formula 7 in Section 3.3 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + * + * Please note that this is a partial of the operation listed there leaving out the final + * conversion from PartialXYZT to XYZT and also this fixes a typo in calculation of Y3 and T3 in + * the paper, H should be replaced with A+B. + */ + private static void doubleXYZ(PartialXYZT partialXYZT, XYZ p) { + long[] t0 = new long[LIMB_CNT]; + + // XX = X1^2 + Field25519.square(partialXYZT.xyz.x, p.x); + + // YY = Y1^2 + Field25519.square(partialXYZT.xyz.z, p.y); + + // B' = Z1^2 + Field25519.square(partialXYZT.t, p.z); + + // B = 2 * B' + Field25519.sum(partialXYZT.t, partialXYZT.t, partialXYZT.t); + + // A = X1 + Y1 + Field25519.sum(partialXYZT.xyz.y, p.x, p.y); + + // AA = A^2 + Field25519.square(t0, partialXYZT.xyz.y); + + // Y3 = YY + XX + Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.x); + + // Z3 = YY - XX + Field25519.sub(partialXYZT.xyz.z, partialXYZT.xyz.z, partialXYZT.xyz.x); + + // X3 = AA - Y3 + Field25519.sub(partialXYZT.xyz.x, t0, partialXYZT.xyz.y); + + // T3 = B - Z3 + Field25519.sub(partialXYZT.t, partialXYZT.t, partialXYZT.xyz.z); + } + + /** + * Doubles {@code p} and puts the result into this PartialXYZT. + */ + private static void doubleXYZT(PartialXYZT partialXYZT, XYZT p) { + doubleXYZ(partialXYZT, p.xyz); + } + + /** + * Compares two byte values in constant time. + * + * Please note that this doesn't reuse {@link Curve25519#eq} method since the below inputs are + * byte values. + */ + private static int eq(int a, int b) { + int r = ~(a ^ b) & 0xff; + r &= r << 4; + r &= r << 2; + r &= r << 1; + return (r >> 7) & 1; + } + + /** + * This is a constant time operation where point b*B*256^pos is stored in {@code t}. + * When b is 0, t remains the same (i.e., neutral point). + * + * Although B_TABLE[32][8] (B_TABLE[i][j] = (j+1)*B*256^i) has j values in [0, 7], the select + * method negates the corresponding point if b is negative (which is straight forward in elliptic + * curves by just negating y coordinate). Therefore we can get multiples of B with the half of + * memory requirements. + * + * @param t neutral element (i.e., point 0), also serves as output. + * @param pos in B[pos][j] = (j+1)*B*256^pos + * @param b value in [-8, 8] range. + */ + private static void select(CachedXYT t, int pos, byte b) { + int bnegative = (b & 0xff) >> 7; + int babs = b - (((-bnegative) & b) << 1); + + t.copyConditional(B_TABLE[pos][0], eq(babs, 1)); + t.copyConditional(B_TABLE[pos][1], eq(babs, 2)); + t.copyConditional(B_TABLE[pos][2], eq(babs, 3)); + t.copyConditional(B_TABLE[pos][3], eq(babs, 4)); + t.copyConditional(B_TABLE[pos][4], eq(babs, 5)); + t.copyConditional(B_TABLE[pos][5], eq(babs, 6)); + t.copyConditional(B_TABLE[pos][6], eq(babs, 7)); + t.copyConditional(B_TABLE[pos][7], eq(babs, 8)); + + long[] yPlusX = Arrays.copyOf(t.yMinusX, LIMB_CNT); + long[] yMinusX = Arrays.copyOf(t.yPlusX, LIMB_CNT); + long[] t2d = Arrays.copyOf(t.t2d, LIMB_CNT); + neg(t2d, t2d); + CachedXYT minust = new CachedXYT(yPlusX, yMinusX, t2d); + t.copyConditional(minust, bnegative); + } + + /** + * Computes {@code a}*B + * where a = a[0]+256*a[1]+...+256^31 a[31] and + * B is the Ed25519 base point (x,4/5) with x positive. + * + * Preconditions: + * a[31] <= 127 + * @throws IllegalStateException iff there is arithmetic error. + */ + @SuppressWarnings("NarrowingCompoundAssignment") + private static XYZ scalarMultWithBase(byte[] a) { + byte[] e = new byte[2 * FIELD_LEN]; + for (int i = 0; i < FIELD_LEN; i++) { + e[2 * i + 0] = (byte) (((a[i] & 0xff) >> 0) & 0xf); + e[2 * i + 1] = (byte) (((a[i] & 0xff) >> 4) & 0xf); + } + // each e[i] is between 0 and 15 + // e[63] is between 0 and 7 + + // Rewrite e in a way that each e[i] is in [-8, 8]. + // This can be done since a[63] is in [0, 7], the carry-over onto the most significant byte + // a[63] can be at most 1. + int carry = 0; + for (int i = 0; i < e.length - 1; i++) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + e[e.length - 1] += carry; + + PartialXYZT ret = new PartialXYZT(NEUTRAL); + XYZT xyzt = new XYZT(); + // Although B_TABLE's i can be at most 31 (stores only 32 4bit multiples of B) and we have 64 + // 4bit values in e array, the below for loop adds cached values by iterating e by two in odd + // indices. After the result, we can double the result point 4 times to shift the multiplication + // scalar by 4 bits. + for (int i = 1; i < e.length; i += 2) { + CachedXYT t = new CachedXYT(CACHED_NEUTRAL); + select(t, i / 2, e[i]); + add(ret, XYZT.fromPartialXYZT(xyzt, ret), t); + } + + // Doubles the result 4 times to shift the multiplication scalar 4 bits to get the actual result + // for the odd indices in e. + XYZ xyz = new XYZ(); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + + // Add multiples of B for even indices of e. + for (int i = 0; i < e.length; i += 2) { + CachedXYT t = new CachedXYT(CACHED_NEUTRAL); + select(t, i / 2, e[i]); + add(ret, XYZT.fromPartialXYZT(xyzt, ret), t); + } + + // This check is to protect against flaws, i.e. if there is a computation error through a + // faulty CPU or if the implementation contains a bug. + XYZ result = new XYZ(ret); + if (!result.isOnCurve()) { + throw new IllegalStateException("arithmetic error in scalar multiplication"); + } + return result; + } + + /** + * Computes {@code a}*B + * where a = a[0]+256*a[1]+...+256^31 a[31] and + * B is the Ed25519 base point (x,4/5) with x positive. + * + * Preconditions: + * a[31] <= 127 + */ + public static byte[] scalarMultWithBaseToBytes(byte[] a) { + return scalarMultWithBase(a).toBytes(); + } + + @SuppressWarnings("NarrowingCompoundAssignment") + private static byte[] slide(byte[] a) { + byte[] r = new byte[256]; + // Writes each bit in a[0..31] into r[0..255]: + // a = a[0]+256*a[1]+...+256^31*a[31] is equal to + // r = r[0]+2*r[1]+...+2^255*r[255] + for (int i = 0; i < 256; i++) { + r[i] = (byte) (1 & ((a[i >> 3] & 0xff) >> (i & 7))); + } + + // Transforms r[i] as odd values in [-15, 15] + for (int i = 0; i < 256; i++) { + if (r[i] != 0) { + for (int b = 1; b <= 6 && i + b < 256; b++) { + if (r[i + b] != 0) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + for (int k = i + b; k < 256; k++) { + if (r[k] == 0) { + r[k] = 1; + break; + } + r[k] = 0; + } + } else { + break; + } + } + } + } + } + return r; + } + + /** + * Computes {@code a}*{@code pointA}+{@code b}*B + * where a = a[0]+256*a[1]+...+256^31*a[31]. + * and b = b[0]+256*b[1]+...+256^31*b[31]. + * B is the Ed25519 base point (x,4/5) with x positive. + * + * Note that execution time varies based on the input since this will only be used in verification + * of signatures. + */ + private static XYZ doubleScalarMultVarTime(byte[] a, XYZT pointA, byte[] b) { + // pointA, 3*pointA, 5*pointA, 7*pointA, 9*pointA, 11*pointA, 13*pointA, 15*pointA + CachedXYZT[] pointAArray = new CachedXYZT[8]; + pointAArray[0] = new CachedXYZT(pointA); + PartialXYZT t = new PartialXYZT(); + doubleXYZT(t, pointA); + XYZT doubleA = new XYZT(t); + for (int i = 1; i < pointAArray.length; i++) { + add(t, doubleA, pointAArray[i - 1]); + pointAArray[i] = new CachedXYZT(new XYZT(t)); + } + + byte[] aSlide = slide(a); + byte[] bSlide = slide(b); + t = new PartialXYZT(NEUTRAL); + XYZT u = new XYZT(); + int i = 255; + for (; i >= 0; i--) { + if (aSlide[i] != 0 || bSlide[i] != 0) { + break; + } + } + for (; i >= 0; i--) { + doubleXYZ(t, new XYZ(t)); + if (aSlide[i] > 0) { + add(t, XYZT.fromPartialXYZT(u, t), pointAArray[aSlide[i] / 2]); + } else if (aSlide[i] < 0) { + sub(t, XYZT.fromPartialXYZT(u, t), pointAArray[-aSlide[i] / 2]); + } + if (bSlide[i] > 0) { + add(t, XYZT.fromPartialXYZT(u, t), B2[bSlide[i] / 2]); + } else if (bSlide[i] < 0) { + sub(t, XYZT.fromPartialXYZT(u, t), B2[-bSlide[i] / 2]); + } + } + + return new XYZ(t); + } + + /** + * Returns true if {@code in} is nonzero. + * + * Note that execution time might depend on the input {@code in}. + */ + private static boolean isNonZeroVarTime(long[] in) { + long[] inCopy = new long[in.length + 1]; + System.arraycopy(in, 0, inCopy, 0, in.length); + Field25519.reduceCoefficients(inCopy); + byte[] bytes = Field25519.contract(inCopy); + for (byte b : bytes) { + if (b != 0) { + return true; + } + } + return false; + } + + /** + * Returns the least significant bit of {@code in}. + */ + private static int getLsb(long[] in) { + return Field25519.contract(in)[0] & 1; + } + + /** + * Negates all values in {@code in} and store it in {@code out}. + */ + private static void neg(long[] out, long[] in) { + for (int i = 0; i < in.length; i++) { + out[i] = -in[i]; + } + } + + /** + * Computes {@code in}^(2^252-3) mod 2^255-19 and puts the result in {@code out}. + */ + private static void pow2252m3(long[] out, long[] in) { + long[] t0 = new long[LIMB_CNT]; + long[] t1 = new long[LIMB_CNT]; + long[] t2 = new long[LIMB_CNT]; + + // z2 = z1^2^1 + Field25519.square(t0, in); + + // z8 = z2^2^2 + Field25519.square(t1, t0); + for (int i = 1; i < 2; i++) { + Field25519.square(t1, t1); + } + + // z9 = z1*z8 + Field25519.mult(t1, in, t1); + + // z11 = z2*z9 + Field25519.mult(t0, t0, t1); + + // z22 = z11^2^1 + Field25519.square(t0, t0); + + // z_5_0 = z9*z22 + Field25519.mult(t0, t1, t0); + + // z_10_5 = z_5_0^2^5 + Field25519.square(t1, t0); + for (int i = 1; i < 5; i++) { + Field25519.square(t1, t1); + } + + // z_10_0 = z_10_5*z_5_0 + Field25519.mult(t0, t1, t0); + + // z_20_10 = z_10_0^2^10 + Field25519.square(t1, t0); + for (int i = 1; i < 10; i++) { + Field25519.square(t1, t1); + } + + // z_20_0 = z_20_10*z_10_0 + Field25519.mult(t1, t1, t0); + + // z_40_20 = z_20_0^2^20 + Field25519.square(t2, t1); + for (int i = 1; i < 20; i++) { + Field25519.square(t2, t2); + } + + // z_40_0 = z_40_20*z_20_0 + Field25519.mult(t1, t2, t1); + + // z_50_10 = z_40_0^2^10 + Field25519.square(t1, t1); + for (int i = 1; i < 10; i++) { + Field25519.square(t1, t1); + } + + // z_50_0 = z_50_10*z_10_0 + Field25519.mult(t0, t1, t0); + + // z_100_50 = z_50_0^2^50 + Field25519.square(t1, t0); + for (int i = 1; i < 50; i++) { + Field25519.square(t1, t1); + } + + // z_100_0 = z_100_50*z_50_0 + Field25519.mult(t1, t1, t0); + + // z_200_100 = z_100_0^2^100 + Field25519.square(t2, t1); + for (int i = 1; i < 100; i++) { + Field25519.square(t2, t2); + } + + // z_200_0 = z_200_100*z_100_0 + Field25519.mult(t1, t2, t1); + + // z_250_50 = z_200_0^2^50 + Field25519.square(t1, t1); + for (int i = 1; i < 50; i++) { + Field25519.square(t1, t1); + } + + // z_250_0 = z_250_50*z_50_0 + Field25519.mult(t0, t1, t0); + + // z_252_2 = z_250_0^2^2 + Field25519.square(t0, t0); + for (int i = 1; i < 2; i++) { + Field25519.square(t0, t0); + } + + // z_252_3 = z_252_2*z1 + Field25519.mult(out, t0, in); + } + + /** + * Returns 3 bytes of {@code in} starting from {@code idx} in Little-Endian format. + */ + private static long load3(byte[] in, int idx) { + long result; + result = (long) in[idx] & 0xff; + result |= (long) (in[idx + 1] & 0xff) << 8; + result |= (long) (in[idx + 2] & 0xff) << 16; + return result; + } + + /** + * Returns 4 bytes of {@code in} starting from {@code idx} in Little-Endian format. + */ + private static long load4(byte[] in, int idx) { + long result = load3(in, idx); + result |= (long) (in[idx + 3] & 0xff) << 24; + return result; + } + + /** + * Input: + * s[0]+256*s[1]+...+256^63*s[63] = s + * + * Output: + * s[0]+256*s[1]+...+256^31*s[31] = s mod l + * where l = 2^252 + 27742317777372353535851937790883648493. + * Overwrites s in place. + */ + public static void reduce(byte[] s) { + // Observation: + // 2^252 mod l is equivalent to -27742317777372353535851937790883648493 mod l + // Let m = -27742317777372353535851937790883648493 + // Thus a*2^252+b mod l is equivalent to a*m+b mod l + // + // First s is divided into chunks of 21 bits as follows: + // s0+2^21*s1+2^42*s3+...+2^462*s23 = s[0]+256*s[1]+...+256^63*s[63] + long s0 = 2097151 & load3(s, 0); + long s1 = 2097151 & (load4(s, 2) >> 5); + long s2 = 2097151 & (load3(s, 5) >> 2); + long s3 = 2097151 & (load4(s, 7) >> 7); + long s4 = 2097151 & (load4(s, 10) >> 4); + long s5 = 2097151 & (load3(s, 13) >> 1); + long s6 = 2097151 & (load4(s, 15) >> 6); + long s7 = 2097151 & (load3(s, 18) >> 3); + long s8 = 2097151 & load3(s, 21); + long s9 = 2097151 & (load4(s, 23) >> 5); + long s10 = 2097151 & (load3(s, 26) >> 2); + long s11 = 2097151 & (load4(s, 28) >> 7); + long s12 = 2097151 & (load4(s, 31) >> 4); + long s13 = 2097151 & (load3(s, 34) >> 1); + long s14 = 2097151 & (load4(s, 36) >> 6); + long s15 = 2097151 & (load3(s, 39) >> 3); + long s16 = 2097151 & load3(s, 42); + long s17 = 2097151 & (load4(s, 44) >> 5); + long s18 = 2097151 & (load3(s, 47) >> 2); + long s19 = 2097151 & (load4(s, 49) >> 7); + long s20 = 2097151 & (load4(s, 52) >> 4); + long s21 = 2097151 & (load3(s, 55) >> 1); + long s22 = 2097151 & (load4(s, 57) >> 6); + long s23 = (load4(s, 60) >> 3); + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + + // s23*2^462 = s23*2^210*2^252 is equivalent to s23*2^210*m in mod l + // As m is a 125 bit number, the result needs to scattered to 6 limbs (125/21 ceil is 6) + // starting from s11 (s11*2^210) + // m = [666643, 470296, 654183, -997805, 136657, -683901] in 21-bit limbs + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + // s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + // s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + // s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + // s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + // s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + // s18 = 0; + + // Reduce the bit length of limbs from s6 to s15 to 21-bits. + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + // Resume reduction where we left off. + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + // s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + // s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + // s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + // s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + // s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + // Reduce the range of limbs from s0 to s11 to 21-bits. + carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + // Carry chain reduction to propagate excess bits from s0 to s5 to the most significant limbs. + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + // Do one last reduction as s12 might be 1. + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + // s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + // Serialize the result into the s. + s[0] = (byte) s0; + s[1] = (byte) (s0 >> 8); + s[2] = (byte) ((s0 >> 16) | (s1 << 5)); + s[3] = (byte) (s1 >> 3); + s[4] = (byte) (s1 >> 11); + s[5] = (byte) ((s1 >> 19) | (s2 << 2)); + s[6] = (byte) (s2 >> 6); + s[7] = (byte) ((s2 >> 14) | (s3 << 7)); + s[8] = (byte) (s3 >> 1); + s[9] = (byte) (s3 >> 9); + s[10] = (byte) ((s3 >> 17) | (s4 << 4)); + s[11] = (byte) (s4 >> 4); + s[12] = (byte) (s4 >> 12); + s[13] = (byte) ((s4 >> 20) | (s5 << 1)); + s[14] = (byte) (s5 >> 7); + s[15] = (byte) ((s5 >> 15) | (s6 << 6)); + s[16] = (byte) (s6 >> 2); + s[17] = (byte) (s6 >> 10); + s[18] = (byte) ((s6 >> 18) | (s7 << 3)); + s[19] = (byte) (s7 >> 5); + s[20] = (byte) (s7 >> 13); + s[21] = (byte) s8; + s[22] = (byte) (s8 >> 8); + s[23] = (byte) ((s8 >> 16) | (s9 << 5)); + s[24] = (byte) (s9 >> 3); + s[25] = (byte) (s9 >> 11); + s[26] = (byte) ((s9 >> 19) | (s10 << 2)); + s[27] = (byte) (s10 >> 6); + s[28] = (byte) ((s10 >> 14) | (s11 << 7)); + s[29] = (byte) (s11 >> 1); + s[30] = (byte) (s11 >> 9); + s[31] = (byte) (s11 >> 17); + } + + /** + * Input: + * a[0]+256*a[1]+...+256^31*a[31] = a + * b[0]+256*b[1]+...+256^31*b[31] = b + * c[0]+256*c[1]+...+256^31*c[31] = c + * + * Output: + * s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + * where l = 2^252 + 27742317777372353535851937790883648493. + */ + public static void mulAdd(byte[] s, byte[] a, byte[] b, byte[] c) { + // This is very similar to Ed25519.reduce, the difference in here is that it computes ab+c + // See Ed25519.reduce for related comments. + long a0 = 2097151 & load3(a, 0); + long a1 = 2097151 & (load4(a, 2) >> 5); + long a2 = 2097151 & (load3(a, 5) >> 2); + long a3 = 2097151 & (load4(a, 7) >> 7); + long a4 = 2097151 & (load4(a, 10) >> 4); + long a5 = 2097151 & (load3(a, 13) >> 1); + long a6 = 2097151 & (load4(a, 15) >> 6); + long a7 = 2097151 & (load3(a, 18) >> 3); + long a8 = 2097151 & load3(a, 21); + long a9 = 2097151 & (load4(a, 23) >> 5); + long a10 = 2097151 & (load3(a, 26) >> 2); + long a11 = (load4(a, 28) >> 7); + long b0 = 2097151 & load3(b, 0); + long b1 = 2097151 & (load4(b, 2) >> 5); + long b2 = 2097151 & (load3(b, 5) >> 2); + long b3 = 2097151 & (load4(b, 7) >> 7); + long b4 = 2097151 & (load4(b, 10) >> 4); + long b5 = 2097151 & (load3(b, 13) >> 1); + long b6 = 2097151 & (load4(b, 15) >> 6); + long b7 = 2097151 & (load3(b, 18) >> 3); + long b8 = 2097151 & load3(b, 21); + long b9 = 2097151 & (load4(b, 23) >> 5); + long b10 = 2097151 & (load3(b, 26) >> 2); + long b11 = (load4(b, 28) >> 7); + long c0 = 2097151 & load3(c, 0); + long c1 = 2097151 & (load4(c, 2) >> 5); + long c2 = 2097151 & (load3(c, 5) >> 2); + long c3 = 2097151 & (load4(c, 7) >> 7); + long c4 = 2097151 & (load4(c, 10) >> 4); + long c5 = 2097151 & (load3(c, 13) >> 1); + long c6 = 2097151 & (load4(c, 15) >> 6); + long c7 = 2097151 & (load3(c, 18) >> 3); + long c8 = 2097151 & load3(c, 21); + long c9 = 2097151 & (load4(c, 23) >> 5); + long c10 = 2097151 & (load3(c, 26) >> 2); + long c11 = (load4(c, 28) >> 7); + long s0; + long s1; + long s2; + long s3; + long s4; + long s5; + long s6; + long s7; + long s8; + long s9; + long s10; + long s11; + long s12; + long s13; + long s14; + long s15; + long s16; + long s17; + long s18; + long s19; + long s20; + long s21; + long s22; + long s23; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + long carry17; + long carry18; + long carry19; + long carry20; + long carry21; + long carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + // s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + // s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + // s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + // s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + // s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + // s18 = 0; + + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + // s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + // s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + // s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + // s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + // s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + // s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = (byte) s0; + s[1] = (byte) (s0 >> 8); + s[2] = (byte) ((s0 >> 16) | (s1 << 5)); + s[3] = (byte) (s1 >> 3); + s[4] = (byte) (s1 >> 11); + s[5] = (byte) ((s1 >> 19) | (s2 << 2)); + s[6] = (byte) (s2 >> 6); + s[7] = (byte) ((s2 >> 14) | (s3 << 7)); + s[8] = (byte) (s3 >> 1); + s[9] = (byte) (s3 >> 9); + s[10] = (byte) ((s3 >> 17) | (s4 << 4)); + s[11] = (byte) (s4 >> 4); + s[12] = (byte) (s4 >> 12); + s[13] = (byte) ((s4 >> 20) | (s5 << 1)); + s[14] = (byte) (s5 >> 7); + s[15] = (byte) ((s5 >> 15) | (s6 << 6)); + s[16] = (byte) (s6 >> 2); + s[17] = (byte) (s6 >> 10); + s[18] = (byte) ((s6 >> 18) | (s7 << 3)); + s[19] = (byte) (s7 >> 5); + s[20] = (byte) (s7 >> 13); + s[21] = (byte) s8; + s[22] = (byte) (s8 >> 8); + s[23] = (byte) ((s8 >> 16) | (s9 << 5)); + s[24] = (byte) (s9 >> 3); + s[25] = (byte) (s9 >> 11); + s[26] = (byte) ((s9 >> 19) | (s10 << 2)); + s[27] = (byte) (s10 >> 6); + s[28] = (byte) ((s10 >> 14) | (s11 << 7)); + s[29] = (byte) (s11 >> 1); + s[30] = (byte) (s11 >> 9); + s[31] = (byte) (s11 >> 17); + } + + static byte[] getHashedScalar(final byte[] privateKey) + throws GeneralSecurityException { + MessageDigest digest = EngineFactory.MESSAGE_DIGEST.getInstance("SHA-512"); + digest.update(privateKey, 0, FIELD_LEN); + byte[] h = digest.digest(); + // https://tools.ietf.org/html/rfc8032#section-5.1.2. + // Clear the lowest three bits of the first octet. + h[0] = (byte) (h[0] & 248); + // Clear the highest bit of the last octet. + h[31] = (byte) (h[31] & 127); + // Set the second highest bit if the last octet. + h[31] = (byte) (h[31] | 64); + return h; + } + + /** + * Returns the EdDSA signature for the {@code message} based on the {@code hashedPrivateKey}. + * + * @param message to sign + * @param hashedPrivateKey {@link Ed25519#getHashedScalar(byte[])} of the private key + * @return signature for the {@code message}. + * @throws GeneralSecurityException if there is no SHA-512 algorithm defined in + * {@link EngineFactory}.MESSAGE_DIGEST. + */ + public static byte[] sign(final byte[] message, final byte[] publicKey, final byte[] hashedPrivateKey) + throws GeneralSecurityException { + // Copying the message to make it thread-safe. Otherwise, if the caller modifies the message + // between the first and the second hash then it might leak the private key. + byte[] messageCopy = Arrays.copyOfRange(message, 0, message.length); + MessageDigest digest = EngineFactory.MESSAGE_DIGEST.getInstance("SHA-512"); + digest.update(hashedPrivateKey, FIELD_LEN, FIELD_LEN); + digest.update(messageCopy); + byte[] r = digest.digest(); + reduce(r); + + byte[] rB = Arrays.copyOfRange(scalarMultWithBase(r).toBytes(), 0, FIELD_LEN); + digest.reset(); + digest.update(rB); + digest.update(publicKey); + digest.update(messageCopy); + byte[] hram = digest.digest(); + reduce(hram); + byte[] s = new byte[FIELD_LEN]; + mulAdd(s, hram, hashedPrivateKey, r); + return Bytes.concat(rB, s); + } + + + // The order of the generator as unsigned bytes in little endian order. + // (2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed, cf. RFC 7748) + static final byte[] GROUP_ORDER = new byte[] { + (byte) 0xed, (byte) 0xd3, (byte) 0xf5, (byte) 0x5c, + (byte) 0x1a, (byte) 0x63, (byte) 0x12, (byte) 0x58, + (byte) 0xd6, (byte) 0x9c, (byte) 0xf7, (byte) 0xa2, + (byte) 0xde, (byte) 0xf9, (byte) 0xde, (byte) 0x14, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10}; + + // Checks whether s represents an integer smaller than the order of the group. + // This is needed to ensure that EdDSA signatures are non-malleable, as failing to check + // the range of S allows to modify signatures (cf. RFC 8032, Section 5.2.7 and Section 8.4.) + // @param s an integer in little-endian order. + private static boolean isSmallerThanGroupOrder(byte[] s) { + for (int j = FIELD_LEN - 1; j >= 0; j--) { + // compare unsigned bytes + int a = s[j] & 0xff; + int b = GROUP_ORDER[j] & 0xff; + if (a != b) { + return a < b; + } + } + return false; + } + + /** + * Returns true if the EdDSA {@code signature} with {@code message}, can be verified with + * {@code publicKey}. + * + * @throws GeneralSecurityException if there is no SHA-512 algorithm defined in + * {@link EngineFactory}.MESSAGE_DIGEST. + */ + static boolean verify(final byte[] message, final byte[] signature, + final byte[] publicKey) throws GeneralSecurityException { + if (signature.length != SIGNATURE_LEN) { + return false; + } + byte[] s = Arrays.copyOfRange(signature, FIELD_LEN, SIGNATURE_LEN); + if (!isSmallerThanGroupOrder(s)) { + return false; + } + MessageDigest digest = EngineFactory.MESSAGE_DIGEST.getInstance("SHA-512"); + digest.update(signature, 0, FIELD_LEN); + digest.update(publicKey); + digest.update(message); + byte[] h = digest.digest(); + reduce(h); + + XYZT negPublicKey = XYZT.fromBytesNegateVarTime(publicKey); + XYZ xyz = doubleScalarMultVarTime(h, negPublicKey, s); + byte[] expectedR = xyz.toBytes(); + for (int i = 0; i < FIELD_LEN; i++) { + if (expectedR[i] != signature[i]) { + return false; + } + } + return true; + } +} diff --git a/tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519Constants.java b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519Constants.java new file mode 100644 index 0000000..a8f6e40 --- /dev/null +++ b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Ed25519Constants.java @@ -0,0 +1,130 @@ +// Copyright 2017 Google Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.subtle; + +import java.math.BigInteger; + +/** Constants used in {@link Ed25519}. */ +final class Ed25519Constants { + + // d = -121665 / 121666 mod 2^255-19 + static final long[] D; + // 2d + static final long[] D2; + // 2^((p-1)/4) mod p where p = 2^255-19 + static final long[] SQRTM1; + + /** + * Base point for the Edwards twisted curve = (x, 4/5) and its exponentiations. B_TABLE[i][j] = + * (j+1)*256^i*B for i in [0, 32) and j in [0, 8). Base point B = B_TABLE[0][0] + * + */ + static final Ed25519.CachedXYT[][] B_TABLE; + static final Ed25519.CachedXYT[] B2; + + private static final BigInteger P_BI = + BigInteger.valueOf(2).pow(255).subtract(BigInteger.valueOf(19)); + private static final BigInteger D_BI = + BigInteger.valueOf(-121665).multiply(BigInteger.valueOf(121666).modInverse(P_BI)).mod(P_BI); + private static final BigInteger D2_BI = BigInteger.valueOf(2).multiply(D_BI).mod(P_BI); + private static final BigInteger SQRTM1_BI = + BigInteger.valueOf(2).modPow(P_BI.subtract(BigInteger.ONE).divide(BigInteger.valueOf(4)), P_BI); + + private static class Point { + private BigInteger x; + private BigInteger y; + } + + private static BigInteger recoverX(BigInteger y) { + // x^2 = (y^2 - 1) / (d * y^2 + 1) mod 2^255-19 + BigInteger xx = + y.pow(2) + .subtract(BigInteger.ONE) + .multiply(D_BI.multiply(y.pow(2)).add(BigInteger.ONE).modInverse(P_BI)); + BigInteger x = xx.modPow(P_BI.add(BigInteger.valueOf(3)).divide(BigInteger.valueOf(8)), P_BI); + if (!x.pow(2).subtract(xx).mod(P_BI).equals(BigInteger.ZERO)) { + x = x.multiply(SQRTM1_BI).mod(P_BI); + } + if (x.testBit(0)) { + x = P_BI.subtract(x); + } + return x; + } + + private static Point edwards(Point a, Point b) { + Point o = new Point(); + BigInteger xxyy = D_BI.multiply(a.x.multiply(b.x).multiply(a.y).multiply(b.y)).mod(P_BI); + o.x = + (a.x.multiply(b.y).add(b.x.multiply(a.y))) + .multiply(BigInteger.ONE.add(xxyy).modInverse(P_BI)) + .mod(P_BI); + o.y = + (a.y.multiply(b.y).add(a.x.multiply(b.x))) + .multiply(BigInteger.ONE.subtract(xxyy).modInverse(P_BI)) + .mod(P_BI); + return o; + } + + private static byte[] toLittleEndian(BigInteger n) { + byte[] b = new byte[32]; + byte[] nBytes = n.toByteArray(); + System.arraycopy(nBytes, 0, b, 32 - nBytes.length, nBytes.length); + for (int i = 0; i < b.length / 2; i++) { + byte t = b[i]; + b[i] = b[b.length - i - 1]; + b[b.length - i - 1] = t; + } + return b; + } + + private static Ed25519.CachedXYT getCachedXYT(Point p) { + return new Ed25519.CachedXYT( + Field25519.expand(toLittleEndian(p.y.add(p.x).mod(P_BI))), + Field25519.expand(toLittleEndian(p.y.subtract(p.x).mod(P_BI))), + Field25519.expand(toLittleEndian(D2_BI.multiply(p.x).multiply(p.y).mod(P_BI)))); + } + + static { + Point b = new Point(); + b.y = BigInteger.valueOf(4).multiply(BigInteger.valueOf(5).modInverse(P_BI)).mod(P_BI); + b.x = recoverX(b.y); + + D = Field25519.expand(toLittleEndian(D_BI)); + D2 = Field25519.expand(toLittleEndian(D2_BI)); + SQRTM1 = Field25519.expand(toLittleEndian(SQRTM1_BI)); + + Point bi = b; + B_TABLE = new Ed25519.CachedXYT[32][8]; + for (int i = 0; i < 32; i++) { + Point bij = bi; + for (int j = 0; j < 8; j++) { + B_TABLE[i][j] = getCachedXYT(bij); + bij = edwards(bij, bi); + } + for (int j = 0; j < 8; j++) { + bi = edwards(bi, bi); + } + } + bi = b; + Point b2 = edwards(b, b); + B2 = new Ed25519.CachedXYT[8]; + for (int i = 0; i < 8; i++) { + B2[i] = getCachedXYT(bi); + bi = edwards(bi, b2); + } + } +} diff --git a/tx-signer/src/main/java/com/google/crypto/tink/subtle/Field25519.java b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Field25519.java new file mode 100644 index 0000000..1795008 --- /dev/null +++ b/tx-signer/src/main/java/com/google/crypto/tink/subtle/Field25519.java @@ -0,0 +1,597 @@ +// Copyright 2017 Google Inc. +// +// 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. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.subtle; + +import com.google.crypto.tink.annotations.Alpha; +import java.util.Arrays; + +/** + * Defines field 25519 function based on curve25519-donna C + * implementation (mostly identical). + * + *

Field elements are written as an array of signed, 64-bit limbs (an array of longs), least + * significant first. The value of the field element is: + * + *

+ * x[0] + 2^26·x[1] + 2^51·x[2] + 2^77·x[3] + 2^102·x[4] + 2^128·x[5] + 2^153·x[6] + 2^179·x[7] +
+ * 2^204·x[8] + 2^230·x[9],
+ * 
+ * + *

i.e. the limbs are 26, 25, 26, 25, ... bits wide. + */ +@Alpha +final class Field25519 { + /** + * During Field25519 computation, the mixed radix representation may be in different forms: + *

    + *
  • Reduced-size form: the array has size at most 10. + *
  • Non-reduced-size form: the array is not reduced modulo 2^255 - 19 and has size at most + * 19. + *
+ * + * TODO(quannguyen): + *
    + *
  • Clarify ill-defined terminologies. + *
  • The reduction procedure is different from DJB's paper + * (http://cr.yp.to/ecdh/curve25519-20060209.pdf). The coefficients after reducing degree and + * reducing coefficients aren't guaranteed to be in range {-2^25, ..., 2^25}. We should check to + * see what's going on. + *
  • Consider using method mult() everywhere and making product() private. + *
+ */ + + static final int FIELD_LEN = 32; + static final int LIMB_CNT = 10; + private static final long TWO_TO_25 = 1 << 25; + private static final long TWO_TO_26 = TWO_TO_25 << 1; + + private static final int[] EXPAND_START = {0, 3, 6, 9, 12, 16, 19, 22, 25, 28}; + private static final int[] EXPAND_SHIFT = {0, 2, 3, 5, 6, 0, 1, 3, 4, 6}; + private static final int[] MASK = {0x3ffffff, 0x1ffffff}; + private static final int[] SHIFT = {26, 25}; + + /** + * Sums two numbers: output = in1 + in2 + * + * On entry: in1, in2 are in reduced-size form. + */ + static void sum(long[] output, long[] in1, long[] in2) { + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = in1[i] + in2[i]; + } + } + + /** + * Sums two numbers: output += in + * + * On entry: in is in reduced-size form. + */ + static void sum(long[] output, long[] in) { + sum(output, output, in); + } + + /** + * Find the difference of two numbers: output = in1 - in2 + * (note the order of the arguments!). + * + * On entry: in1, in2 are in reduced-size form. + */ + static void sub(long[] output, long[] in1, long[] in2) { + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = in1[i] - in2[i]; + } + } + + /** + * Find the difference of two numbers: output = in - output + * (note the order of the arguments!). + * + * On entry: in, output are in reduced-size form. + */ + static void sub(long[] output, long[] in) { + sub(output, in, output); + } + + /** + * Multiply a number by a scalar: output = in * scalar + */ + static void scalarProduct(long[] output, long[] in, long scalar) { + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = in[i] * scalar; + } + } + + /** + * Multiply two numbers: out = in2 * in + * + * output must be distinct to both inputs. The inputs are reduced coefficient form, + * the output is not. + * + * out[x] <= 14 * the largest product of the input limbs. + */ + static void product(long[] out, long[] in2, long[] in) { + out[0] = in2[0] * in[0]; + out[1] = in2[0] * in[1] + + in2[1] * in[0]; + out[2] = 2 * in2[1] * in[1] + + in2[0] * in[2] + + in2[2] * in[0]; + out[3] = in2[1] * in[2] + + in2[2] * in[1] + + in2[0] * in[3] + + in2[3] * in[0]; + out[4] = in2[2] * in[2] + + 2 * (in2[1] * in[3] + in2[3] * in[1]) + + in2[0] * in[4] + + in2[4] * in[0]; + out[5] = in2[2] * in[3] + + in2[3] * in[2] + + in2[1] * in[4] + + in2[4] * in[1] + + in2[0] * in[5] + + in2[5] * in[0]; + out[6] = 2 * (in2[3] * in[3] + in2[1] * in[5] + in2[5] * in[1]) + + in2[2] * in[4] + + in2[4] * in[2] + + in2[0] * in[6] + + in2[6] * in[0]; + out[7] = in2[3] * in[4] + + in2[4] * in[3] + + in2[2] * in[5] + + in2[5] * in[2] + + in2[1] * in[6] + + in2[6] * in[1] + + in2[0] * in[7] + + in2[7] * in[0]; + out[8] = in2[4] * in[4] + + 2 * (in2[3] * in[5] + in2[5] * in[3] + in2[1] * in[7] + in2[7] * in[1]) + + in2[2] * in[6] + + in2[6] * in[2] + + in2[0] * in[8] + + in2[8] * in[0]; + out[9] = in2[4] * in[5] + + in2[5] * in[4] + + in2[3] * in[6] + + in2[6] * in[3] + + in2[2] * in[7] + + in2[7] * in[2] + + in2[1] * in[8] + + in2[8] * in[1] + + in2[0] * in[9] + + in2[9] * in[0]; + out[10] = + 2 * (in2[5] * in[5] + in2[3] * in[7] + in2[7] * in[3] + in2[1] * in[9] + in2[9] * in[1]) + + in2[4] * in[6] + + in2[6] * in[4] + + in2[2] * in[8] + + in2[8] * in[2]; + out[11] = in2[5] * in[6] + + in2[6] * in[5] + + in2[4] * in[7] + + in2[7] * in[4] + + in2[3] * in[8] + + in2[8] * in[3] + + in2[2] * in[9] + + in2[9] * in[2]; + out[12] = in2[6] * in[6] + + 2 * (in2[5] * in[7] + in2[7] * in[5] + in2[3] * in[9] + in2[9] * in[3]) + + in2[4] * in[8] + + in2[8] * in[4]; + out[13] = in2[6] * in[7] + + in2[7] * in[6] + + in2[5] * in[8] + + in2[8] * in[5] + + in2[4] * in[9] + + in2[9] * in[4]; + out[14] = 2 * (in2[7] * in[7] + in2[5] * in[9] + in2[9] * in[5]) + + in2[6] * in[8] + + in2[8] * in[6]; + out[15] = in2[7] * in[8] + + in2[8] * in[7] + + in2[6] * in[9] + + in2[9] * in[6]; + out[16] = in2[8] * in[8] + + 2 * (in2[7] * in[9] + in2[9] * in[7]); + out[17] = in2[8] * in[9] + + in2[9] * in[8]; + out[18] = 2 * in2[9] * in[9]; + } + + /** + * Reduce a long form to a reduced-size form by taking the input mod 2^255 - 19. + * + * On entry: |output[i]| < 14*2^54 + * On exit: |output[0..8]| < 280*2^54 + */ + static void reduceSizeByModularReduction(long[] output) { + // The coefficients x[10], x[11],..., x[18] are eliminated by reduction modulo 2^255 - 19. + // For example, the coefficient x[18] is multiplied by 19 and added to the coefficient x[8]. + // + // Each of these shifts and adds ends up multiplying the value by 19. + // + // For output[0..8], the absolute entry value is < 14*2^54 and we add, at most, 19*14*2^54 thus, + // on exit, |output[0..8]| < 280*2^54. + output[8] += output[18] << 4; + output[8] += output[18] << 1; + output[8] += output[18]; + output[7] += output[17] << 4; + output[7] += output[17] << 1; + output[7] += output[17]; + output[6] += output[16] << 4; + output[6] += output[16] << 1; + output[6] += output[16]; + output[5] += output[15] << 4; + output[5] += output[15] << 1; + output[5] += output[15]; + output[4] += output[14] << 4; + output[4] += output[14] << 1; + output[4] += output[14]; + output[3] += output[13] << 4; + output[3] += output[13] << 1; + output[3] += output[13]; + output[2] += output[12] << 4; + output[2] += output[12] << 1; + output[2] += output[12]; + output[1] += output[11] << 4; + output[1] += output[11] << 1; + output[1] += output[11]; + output[0] += output[10] << 4; + output[0] += output[10] << 1; + output[0] += output[10]; + } + + /** + * Reduce all coefficients of the short form input so that |x| < 2^26. + * + * On entry: |output[i]| < 280*2^54 + */ + static void reduceCoefficients(long[] output) { + output[10] = 0; + + for (int i = 0; i < LIMB_CNT; i += 2) { + long over = output[i] / TWO_TO_26; + // The entry condition (that |output[i]| < 280*2^54) means that over is, at most, 280*2^28 in + // the first iteration of this loop. This is added to the next limb and we can approximate the + // resulting bound of that limb by 281*2^54. + output[i] -= over << 26; + output[i + 1] += over; + + // For the first iteration, |output[i+1]| < 281*2^54, thus |over| < 281*2^29. When this is + // added to the next limb, the resulting bound can be approximated as 281*2^54. + // + // For subsequent iterations of the loop, 281*2^54 remains a conservative bound and no + // overflow occurs. + over = output[i + 1] / TWO_TO_25; + output[i + 1] -= over << 25; + output[i + 2] += over; + } + // Now |output[10]| < 281*2^29 and all other coefficients are reduced. + output[0] += output[10] << 4; + output[0] += output[10] << 1; + output[0] += output[10]; + + output[10] = 0; + // Now output[1..9] are reduced, and |output[0]| < 2^26 + 19*281*2^29 so |over| will be no more + // than 2^16. + long over = output[0] / TWO_TO_26; + output[0] -= over << 26; + output[1] += over; + // Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 2^16 < 2^26. The bound on + // |output[1]| is sufficient to meet our needs. + } + + /** + * A helpful wrapper around {@ref Field25519#product}: output = in * in2. + * + * On entry: |in[i]| < 2^27 and |in2[i]| < 2^27. + * + * The output is reduced degree (indeed, one need only provide storage for 10 limbs) and + * |output[i]| < 2^26. + */ + static void mult(long[] output, long[] in, long[] in2) { + long[] t = new long[19]; + product(t, in, in2); + // |t[i]| < 14*2^54 + reduceSizeByModularReduction(t); + reduceCoefficients(t); + // |t[i]| < 2^26 + System.arraycopy(t, 0, output, 0, LIMB_CNT); + } + + /** + * Square a number: out = in**2 + * + * output must be distinct from the input. The inputs are reduced coefficient form, the output is + * not. + * + * out[x] <= 14 * the largest product of the input limbs. + */ + private static void squareInner(long[] out, long[] in) { + out[0] = in[0] * in[0]; + out[1] = 2 * in[0] * in[1]; + out[2] = 2 * (in[1] * in[1] + in[0] * in[2]); + out[3] = 2 * (in[1] * in[2] + in[0] * in[3]); + out[4] = in[2] * in[2] + + 4 * in[1] * in[3] + + 2 * in[0] * in[4]; + out[5] = 2 * (in[2] * in[3] + in[1] * in[4] + in[0] * in[5]); + out[6] = 2 * (in[3] * in[3] + in[2] * in[4] + in[0] * in[6] + 2 * in[1] * in[5]); + out[7] = 2 * (in[3] * in[4] + in[2] * in[5] + in[1] * in[6] + in[0] * in[7]); + out[8] = in[4] * in[4] + + 2 * (in[2] * in[6] + in[0] * in[8] + 2 * (in[1] * in[7] + in[3] * in[5])); + out[9] = 2 * (in[4] * in[5] + in[3] * in[6] + in[2] * in[7] + in[1] * in[8] + in[0] * in[9]); + out[10] = 2 * (in[5] * in[5] + + in[4] * in[6] + + in[2] * in[8] + + 2 * (in[3] * in[7] + in[1] * in[9])); + out[11] = 2 * (in[5] * in[6] + in[4] * in[7] + in[3] * in[8] + in[2] * in[9]); + out[12] = in[6] * in[6] + + 2 * (in[4] * in[8] + 2 * (in[5] * in[7] + in[3] * in[9])); + out[13] = 2 * (in[6] * in[7] + in[5] * in[8] + in[4] * in[9]); + out[14] = 2 * (in[7] * in[7] + in[6] * in[8] + 2 * in[5] * in[9]); + out[15] = 2 * (in[7] * in[8] + in[6] * in[9]); + out[16] = in[8] * in[8] + 4 * in[7] * in[9]; + out[17] = 2 * in[8] * in[9]; + out[18] = 2 * in[9] * in[9]; + } + + /** + * Returns in^2. + * + * On entry: The |in| argument is in reduced coefficients form and |in[i]| < 2^27. + * + * On exit: The |output| argument is in reduced coefficients form (indeed, one need only provide + * storage for 10 limbs) and |out[i]| < 2^26. + */ + static void square(long[] output, long[] in) { + long[] t = new long[19]; + squareInner(t, in); + // |t[i]| < 14*2^54 because the largest product of two limbs will be < 2^(27+27) and SquareInner + // adds together, at most, 14 of those products. + reduceSizeByModularReduction(t); + reduceCoefficients(t); + // |t[i]| < 2^26 + System.arraycopy(t, 0, output, 0, LIMB_CNT); + } + + /** + * Takes a little-endian, 32-byte number and expands it into mixed radix form. + */ + static long[] expand(byte[] input) { + long[] output = new long[LIMB_CNT]; + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = ((((long) (input[EXPAND_START[i]] & 0xff)) + | ((long) (input[EXPAND_START[i] + 1] & 0xff)) << 8 + | ((long) (input[EXPAND_START[i] + 2] & 0xff)) << 16 + | ((long) (input[EXPAND_START[i] + 3] & 0xff)) << 24) >> EXPAND_SHIFT[i]) & MASK[i & 1]; + } + return output; + } + + /** + * Takes a fully reduced mixed radix form number and contract it into a little-endian, 32-byte + * array. + * + * On entry: |input_limbs[i]| < 2^26 + */ + @SuppressWarnings("NarrowingCompoundAssignment") + static byte[] contract(long[] inputLimbs) { + long[] input = Arrays.copyOf(inputLimbs, LIMB_CNT); + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 9; i++) { + // This calculation is a time-invariant way to make input[i] non-negative by borrowing + // from the next-larger limb. + int carry = -(int) ((input[i] & (input[i] >> 31)) >> SHIFT[i & 1]); + input[i] = input[i] + (carry << SHIFT[i & 1]); + input[i + 1] -= carry; + } + + // There's no greater limb for input[9] to borrow from, but we can multiply by 19 and borrow + // from input[0], which is valid mod 2^255-19. + { + int carry = -(int) ((input[9] & (input[9] >> 31)) >> 25); + input[9] += (carry << 25); + input[0] -= (carry * 19); + } + + // After the first iteration, input[1..9] are non-negative and fit within 25 or 26 bits, + // depending on position. However, input[0] may be negative. + } + + // The first borrow-propagation pass above ended with every limb except (possibly) input[0] + // non-negative. + // + // If input[0] was negative after the first pass, then it was because of a carry from input[9]. + // On entry, input[9] < 2^26 so the carry was, at most, one, since (2**26-1) >> 25 = 1. Thus + // input[0] >= -19. + // + // In the second pass, each limb is decreased by at most one. Thus the second borrow-propagation + // pass could only have wrapped around to decrease input[0] again if the first pass left + // input[0] negative *and* input[1] through input[9] were all zero. In that case, input[1] is + // now 2^25 - 1, and this last borrow-propagation step will leave input[1] non-negative. + { + int carry = -(int) ((input[0] & (input[0] >> 31)) >> 26); + input[0] += (carry << 26); + input[1] -= carry; + } + + // All input[i] are now non-negative. However, there might be values between 2^25 and 2^26 in a + // limb which is, nominally, 25 bits wide. + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 9; i++) { + int carry = (int) (input[i] >> SHIFT[i & 1]); + input[i] &= MASK[i & 1]; + input[i + 1] += carry; + } + } + + { + int carry = (int) (input[9] >> 25); + input[9] &= 0x1ffffff; + input[0] += 19 * carry; + } + + // If the first carry-chain pass, just above, ended up with a carry from input[9], and that + // caused input[0] to be out-of-bounds, then input[0] was < 2^26 + 2*19, because the carry was, + // at most, two. + // + // If the second pass carried from input[9] again then input[0] is < 2*19 and the input[9] -> + // input[0] carry didn't push input[0] out of bounds. + + // It still remains the case that input might be between 2^255-19 and 2^255. In this case, + // input[1..9] must take their maximum value and input[0] must be >= (2^255-19) & 0x3ffffff, + // which is 0x3ffffed. + int mask = gte((int) input[0], 0x3ffffed); + for (int i = 1; i < LIMB_CNT; i++) { + mask &= eq((int) input[i], MASK[i & 1]); + } + + // mask is either 0xffffffff (if input >= 2^255-19) and zero otherwise. Thus this conditionally + // subtracts 2^255-19. + input[0] -= mask & 0x3ffffed; + input[1] -= mask & 0x1ffffff; + for (int i = 2; i < LIMB_CNT; i += 2) { + input[i] -= mask & 0x3ffffff; + input[i + 1] -= mask & 0x1ffffff; + } + + for (int i = 0; i < LIMB_CNT; i++) { + input[i] <<= EXPAND_SHIFT[i]; + } + byte[] output = new byte[FIELD_LEN]; + for (int i = 0; i < LIMB_CNT; i++) { + output[EXPAND_START[i]] |= input[i] & 0xff; + output[EXPAND_START[i] + 1] |= (input[i] >> 8) & 0xff; + output[EXPAND_START[i] + 2] |= (input[i] >> 16) & 0xff; + output[EXPAND_START[i] + 3] |= (input[i] >> 24) & 0xff; + } + return output; + } + + /** + * Computes inverse of z = z(2^255 - 21) + * + * Shamelessly copied from agl's code which was shamelessly copied from djb's code. Only the + * comment format and the variable namings are different from those. + */ + static void inverse(long[] out, long[] z) { + long[] z2 = new long[Field25519.LIMB_CNT]; + long[] z9 = new long[Field25519.LIMB_CNT]; + long[] z11 = new long[Field25519.LIMB_CNT]; + long[] z2To5Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To10Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To20Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To50Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To100Minus1 = new long[Field25519.LIMB_CNT]; + long[] t0 = new long[Field25519.LIMB_CNT]; + long[] t1 = new long[Field25519.LIMB_CNT]; + + square(z2, z); // 2 + square(t1, z2); // 4 + square(t0, t1); // 8 + mult(z9, t0, z); // 9 + mult(z11, z9, z2); // 11 + square(t0, z11); // 22 + mult(z2To5Minus1, t0, z9); // 2^5 - 2^0 = 31 + + square(t0, z2To5Minus1); // 2^6 - 2^1 + square(t1, t0); // 2^7 - 2^2 + square(t0, t1); // 2^8 - 2^3 + square(t1, t0); // 2^9 - 2^4 + square(t0, t1); // 2^10 - 2^5 + mult(z2To10Minus1, t0, z2To5Minus1); // 2^10 - 2^0 + + square(t0, z2To10Minus1); // 2^11 - 2^1 + square(t1, t0); // 2^12 - 2^2 + for (int i = 2; i < 10; i += 2) { // 2^20 - 2^10 + square(t0, t1); + square(t1, t0); + } + mult(z2To20Minus1, t1, z2To10Minus1); // 2^20 - 2^0 + + square(t0, z2To20Minus1); // 2^21 - 2^1 + square(t1, t0); // 2^22 - 2^2 + for (int i = 2; i < 20; i += 2) { // 2^40 - 2^20 + square(t0, t1); + square(t1, t0); + } + mult(t0, t1, z2To20Minus1); // 2^40 - 2^0 + + square(t1, t0); // 2^41 - 2^1 + square(t0, t1); // 2^42 - 2^2 + for (int i = 2; i < 10; i += 2) { // 2^50 - 2^10 + square(t1, t0); + square(t0, t1); + } + mult(z2To50Minus1, t0, z2To10Minus1); // 2^50 - 2^0 + + square(t0, z2To50Minus1); // 2^51 - 2^1 + square(t1, t0); // 2^52 - 2^2 + for (int i = 2; i < 50; i += 2) { // 2^100 - 2^50 + square(t0, t1); + square(t1, t0); + } + mult(z2To100Minus1, t1, z2To50Minus1); // 2^100 - 2^0 + + square(t1, z2To100Minus1); // 2^101 - 2^1 + square(t0, t1); // 2^102 - 2^2 + for (int i = 2; i < 100; i += 2) { // 2^200 - 2^100 + square(t1, t0); + square(t0, t1); + } + mult(t1, t0, z2To100Minus1); // 2^200 - 2^0 + + square(t0, t1); // 2^201 - 2^1 + square(t1, t0); // 2^202 - 2^2 + for (int i = 2; i < 50; i += 2) { // 2^250 - 2^50 + square(t0, t1); + square(t1, t0); + } + mult(t0, t1, z2To50Minus1); // 2^250 - 2^0 + + square(t1, t0); // 2^251 - 2^1 + square(t0, t1); // 2^252 - 2^2 + square(t1, t0); // 2^253 - 2^3 + square(t0, t1); // 2^254 - 2^4 + square(t1, t0); // 2^255 - 2^5 + mult(out, t1, z11); // 2^255 - 21 + } + + + /** + * Returns 0xffffffff iff a == b and zero otherwise. + */ + private static int eq(int a, int b) { + a = ~(a ^ b); + a &= a << 16; + a &= a << 8; + a &= a << 4; + a &= a << 2; + a &= a << 1; + return a >> 31; + } + + /** + * returns 0xffffffff if a >= b and zero otherwise, where a and b are both non-negative. + */ + private static int gte(int a, int b) { + a -= b; + // a >= 0 iff a >= b. + return ~(a >> 31); + } +} diff --git a/tx-signer/src/main/java/io/bytom/api/Keys.java b/tx-signer/src/main/java/io/bytom/api/Keys.java new file mode 100644 index 0000000..3a60880 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/Keys.java @@ -0,0 +1,44 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; + +public class Keys { + + @SerializedName("alias") + public String alias; + + @SerializedName("xpub") + public String xpub; + + @SerializedName("file") + public String file; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getXpub() { + return xpub; + } + + public void setXpub(String xpub) { + this.xpub = xpub; + } + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } +} diff --git a/tx-signer/src/main/java/io/bytom/api/RawTransaction.java b/tx-signer/src/main/java/io/bytom/api/RawTransaction.java new file mode 100644 index 0000000..0e36826 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/RawTransaction.java @@ -0,0 +1,193 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.SuccessRespon; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; +import org.bouncycastle.jcajce.provider.digest.SHA3; + +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RawTransaction { + + @SerializedName("tx_id") + public String txID; + /** + * version + */ + public Integer version; + /** + * size + */ + public Integer size; + /** + * time_range + */ + @SerializedName("time_range") + public Integer timeRange; + + /** + * status + */ + public Integer fee; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(RawTransaction.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static RawTransaction fromJson(String json) { + return Utils.serializer.fromJson(json, RawTransaction.class); + } + + public static RawTransaction fromSuccessRespon(String json) { + Type responType = new ParameterizedTypeImpl(SuccessRespon.class, new Class[]{RawTransaction.class}); + SuccessRespon result = Utils.serializer.fromJson(json, responType); + return result.dataObject; + } + + public static RawTransaction decode(Client client, String txId) throws BytomException { + Map req = new HashMap(); + req.put("raw_transaction", txId); + RawTransaction rawTransaction = + client.request("decode-raw-transaction", req, RawTransaction.class); + + logger.info("decode-raw-transaction:"); + logger.info(rawTransaction.toJson()); + + return rawTransaction; + } + + public static class AnnotatedInput { + + @SerializedName("input_id") + public String inputID; + /** + * address + */ + private String address; + + /** + * The number of units of the asset being issued or spent. + */ + private long amount; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output consumed by this input. Null if the input is an + * issuance. + */ + @SerializedName("spent_output_id") + private String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + + @Override + public String toString() { + return Utils.serializer.toJson(this); + } + + } + + public static class AnnotatedOutput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being controlled. + */ + private long amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } + + public byte[] hashFn(byte[] hashedInputHex, byte[] txID) { + + SHA3.Digest256 digest256 = new SHA3.Digest256(); + + // data = hashedInputHex + txID + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(hashedInputHex, 0, hashedInputHex.length); + out.write(txID, 0, txID.length); + byte[] data = out.toByteArray(); + + return digest256.digest(data); + } + +} diff --git a/tx-signer/src/main/java/io/bytom/api/Receiver.java b/tx-signer/src/main/java/io/bytom/api/Receiver.java new file mode 100644 index 0000000..070542c --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/Receiver.java @@ -0,0 +1,48 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.JSONException; + +/** + * Receivers are used to facilitate payments between accounts on different + * cores. They contain a control program and an expiration date. In the future, + * more payment-related metadata may be placed here. + *

+ * Receivers are typically created under accounts via the + */ +public class Receiver { + + @SerializedName("address") + public String address; + /** + * Hex-encoded string representation of the control program. + */ + @SerializedName("control_program") + public String controlProgram; + + + /** + * Serializes the receiver into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Deserializes a Receiver from JSON. + * + * @param json a JSON-serialized Receiver object + * @return the deserialized Receiver object + * @throws JSONException Raised if the provided string is not valid JSON. + */ + public static Receiver fromJson(String json) throws JSONException { + try { + return Utils.serializer.fromJson(json, Receiver.class); + } catch (IllegalStateException e) { + throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); + } + } +} diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java new file mode 100644 index 0000000..8e36a61 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java @@ -0,0 +1,205 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ExpandedPrivateKey; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.SuccessRespon; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Created by liqiang on 2018/10/24. + */ +public class SignTransaction { + + @SerializedName("tx_id") + public String txID; + /** + * version + */ + public Integer version; + /** + * size + */ + public Integer size; + /** + * time_range + */ + @SerializedName("time_range") + public Integer timeRange; + + /** + * status + */ + public Integer fee; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(SignTransaction.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static SignTransaction fromJson(String json) { + return Utils.serializer.fromJson(json, SignTransaction.class); + } + + public static SignTransaction fromSuccessRespon(String json) { + Type responType = new ParameterizedTypeImpl(SuccessRespon.class, new Class[]{SignTransaction.class}); + SuccessRespon result = Utils.serializer.fromJson(json, responType); + return result.dataObject; + } + + public static SignTransaction decode(Client client, String txId) throws BytomException { + Map req = new HashMap(); + req.put("raw_transaction", txId); + SignTransaction SignTransaction = + client.request("decode-raw-transaction", req, SignTransaction.class); + + logger.info("decode-raw-transaction:"); + logger.info(SignTransaction.toJson()); + + return SignTransaction; + } + + public class AnnotatedInput { + + @SerializedName("input_id") + public String inputID; + /** + * address + */ + public String address; + + /** + * The number of units of the asset being issued or spent. + */ + public long amount; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + public String controlProgram; + + /** + * The id of the output consumed by this input. Null if the input is an + * issuance. + */ + @SerializedName("spent_output_id") + public String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + + public String sourceId; + + public long sourcePosition; + + @SerializedName("witness_component") + public InputWitnessComponent witnessComponent; + + @Override + public String toString() { + return Utils.serializer.toJson(this); + } + + } + + public class AnnotatedOutput { + + /** + * address + */ + public String address; + + /** + * The number of units of the asset being controlled. + */ + public long amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + public String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + public String id; + + /** + * The output's position in a transaction's list of outputs. + */ + public Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + public String type; + + } + + /** + * A single witness component, holding information that will become the input + * witness. + */ + public static class InputWitnessComponent { + + /** + * The list of signatures made with the specified keys (null unless type is + * "signature"). + */ + public String[] signatures; + } +} diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java new file mode 100644 index 0000000..0cac43d --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -0,0 +1,108 @@ +package io.bytom.api; + +import io.bytom.common.ExpandedPrivateKey; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; + +import org.bouncycastle.util.encoders.Hex; + +/** + * Created by liqiang on 2018/10/24. + */ +public class SignTransactionImpl { + + public byte[] hashFn(byte[] hashedInputHex, byte[] txID) { + + SHA3.Digest256 digest256 = new SHA3.Digest256(); + + // data = hashedInputHex + txID + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(hashedInputHex, 0, hashedInputHex.length); + out.write(txID, 0, txID.length); + byte[] data = out.toByteArray(); + + return digest256.digest(data); + } + + public SignTransaction buildData(SignTransaction signTransaction) { + //build program by address + + //build sourceId(muxId), inputId, txId + + return signTransaction; + } + + public SignTransaction generateSignatures(SignTransaction signTransaction, BigInteger[] keys) { + SignTransaction.AnnotatedInput input = signTransaction.inputs.get(0); + input.witnessComponent.signatures = new String[keys.length]; + for (int i=0; i= 0x80) { + buf[i] = (byte)(x | 0x80); + x >>= 7; + i++; + } + buf[i] = (byte)x; + return i + 1; + } + + private byte[] BigIntegerToBytes(BigInteger value) { + if(value == null) { + return null; + } else { + 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; + } + } +} diff --git a/tx-signer/src/main/java/io/bytom/api/Signatures.java b/tx-signer/src/main/java/io/bytom/api/Signatures.java new file mode 100644 index 0000000..0ef4749 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/Signatures.java @@ -0,0 +1,14 @@ +package io.bytom.api; + +public interface Signatures { + + /** + * return signed transaction + * + * @param privateKeys + * @param template + * @param decodedTx + * @return + */ + public Template generateSignatures(String[] privateKeys, Template template, RawTransaction decodedTx); +} diff --git a/tx-signer/src/main/java/io/bytom/api/SignaturesImpl.java b/tx-signer/src/main/java/io/bytom/api/SignaturesImpl.java new file mode 100644 index 0000000..57844a7 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/SignaturesImpl.java @@ -0,0 +1,81 @@ +package io.bytom.api; + +import io.bytom.common.DeriveXpub; +import io.bytom.common.ExpandedPrivateKey; +import io.bytom.common.FindDst; +import io.bytom.common.NonHardenedChild; +import org.apache.log4j.Logger; +import org.bouncycastle.util.encoders.Hex; + +public class SignaturesImpl implements Signatures { + + private static Logger logger = Logger.getLogger(SignaturesImpl.class); + + @Override + public Template generateSignatures(String[] privateKeys, Template template, RawTransaction decodedTx) { + Template result = template; + for (int i = 0; i < template.signingInstructions.size(); i++) { + Template.SigningInstruction sigIns = template.signingInstructions.get(i); + for (Template.WitnessComponent wc : sigIns.witnessComponents) { + + // Have two cases + switch (wc.type) { + case "raw_tx_signature": + logger.info("=====raw_tx_signature"); + logger.info("keys.length: "+wc.keys.length); + if (wc.signatures==null || wc.signatures.length < wc.keys.length) { + wc.signatures = new String[wc.keys.length]; + } + // 一个input对应一个Template.WitnessComponent + String input = decodedTx.inputs.get(sigIns.position).inputID; + String tx_id = decodedTx.txID; + byte[] message = decodedTx.hashFn(Hex.decode(input), Hex.decode(tx_id)); + for (int j = 0; j < wc.keys.length; j++) { + if (wc.signatures[j] == null || wc.signatures[j].isEmpty()) { + + byte[] sig = new byte[64]; + try { + String publicKey = wc.keys[j].xpub; + // 多签情况下,找到xpub对应的private key的下标 dst + int dst = FindDst.find(privateKeys, publicKey); + //一级私钥 + byte[] privateKey = Hex.decode(privateKeys[dst]); + // 一级私钥推出二级私钥 + String[] hpaths = wc.keys[j].derivationPath; + byte[] childXprv = NonHardenedChild.child(privateKey, hpaths); + // 一级私钥推出公钥 + byte[] xpub = DeriveXpub.deriveXpub(privateKey); + // 二级私钥得到扩展私钥 + byte[] expandedPrv = ExpandedPrivateKey.ExpandedPrivateKey(childXprv); + logger.info("privateKey: "+Hex.toHexString(privateKey)); + logger.info("childXpriv: "+Hex.toHexString(childXprv)); + logger.info("xpub: "+Hex.toHexString(xpub)); + logger.info("message: "+Hex.toHexString(message)); +// sig = com.google.crypto.tink.subtle.Ed25519.sign(message, xpub, expandedPrv); + sig = Signer.Ed25519InnerSign(expandedPrv, message); + logger.info("sig google: "+Hex.toHexString(sig)); + + } catch (Exception e) { + e.printStackTrace(); + } + logger.info("sig:"+Hex.toHexString(sig)); + wc.signatures[j] = Hex.toHexString(sig); + result.signingInstructions.get(i).witnessComponents[j].signatures = wc.signatures; + } + + } + break; + case "": + + break; + default: + + } + } + } + return result; + } + + + +} diff --git a/tx-signer/src/main/java/io/bytom/api/Signer.java b/tx-signer/src/main/java/io/bytom/api/Signer.java new file mode 100644 index 0000000..736d416 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/Signer.java @@ -0,0 +1,70 @@ +package io.bytom.api; + +import io.bytom.common.DeriveXpub; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.util.Arrays; + +public class Signer { + + public static byte[] Ed25519InnerSign(byte[] privateKey, byte[] message) + throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + byte[] digestData = new byte[32 + message.length]; + int digestDataIndex = 0; + for (int i = 32; i < 64; i++) { + digestData[digestDataIndex] = privateKey[i]; + digestDataIndex++; + } + for (int i = 0; i < message.length; i++) { + digestData[digestDataIndex] = message[i]; + digestDataIndex++; + } + md.update(digestData); + byte[] messageDigest = md.digest(); + + com.google.crypto.tink.subtle.Ed25519.reduce(messageDigest); + byte[] messageDigestReduced = Arrays.copyOfRange(messageDigest, 0, 32); + byte[] encodedR = com.google.crypto.tink.subtle.Ed25519.scalarMultWithBaseToBytes(messageDigestReduced); + byte[] publicKey = DeriveXpub.deriveXpub(privateKey); + + byte[] hramDigestData = new byte[32 + encodedR.length + message.length]; + int hramDigestIndex = 0; + for (int i = 0; i < encodedR.length; i++) { + hramDigestData[hramDigestIndex] = encodedR[i]; + hramDigestIndex++; + } + for (int i = 0; i < 32; i++) { + hramDigestData[hramDigestIndex] = publicKey[i]; + hramDigestIndex++; + } + for (int i = 0; i < message.length; i++) { + hramDigestData[hramDigestIndex] = message[i]; + hramDigestIndex++; + } + md.reset(); + md.update(hramDigestData); + byte[] hramDigest = md.digest(); + com.google.crypto.tink.subtle.Ed25519.reduce(hramDigest); + byte[] hramDigestReduced = Arrays.copyOfRange(hramDigest, 0, 32); + + byte[] sk = Arrays.copyOfRange(privateKey, 0, 32); + byte[] s = new byte[32]; + com.google.crypto.tink.subtle.Ed25519.mulAdd(s, hramDigestReduced, sk, messageDigestReduced); + + byte[] signature = new byte[64]; + for (int i = 0; i < encodedR.length; i++) { + signature[i] = encodedR[i]; + } + int signatureIndex = 32; + for (int i = 0; i < s.length; i++) { + signature[signatureIndex] = s[i]; + signatureIndex++; + } + return signature; + } + +} diff --git a/tx-signer/src/main/java/io/bytom/api/Template.java b/tx-signer/src/main/java/io/bytom/api/Template.java new file mode 100644 index 0000000..5b8cb88 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/Template.java @@ -0,0 +1,132 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; + +import java.util.List; + +public class Template { + /** + * A hex-encoded representation of a transaction template. + */ + @SerializedName("raw_transaction") + public String rawTransaction; + + /** + * The list of signing instructions for inputs in the transaction. + */ + @SerializedName("signing_instructions") + public List signingInstructions; + + /** + * For core use only. + */ + @SerializedName("local") + private boolean local; + + /** + * False (the default) makes the transaction "final" when signing, preventing + * further changes - the signature program commits to the transaction's signature + * hash. True makes the transaction extensible, committing only to the elements in + * the transaction so far, permitting the addition of new elements. + */ + @SerializedName("allow_additional_actions") + private boolean allowAdditionalActions; + + /** + * allowAdditionalActions causes the transaction to be signed so that it can be + * used as a base transaction in a multiparty trade flow. To enable this setting, + * call this method after building the transaction, but before sending it to the + * signer. + *

+ * All participants in a multiparty trade flow should call this method except for + * the last signer. Do not call this option if the transaction is complete, i.e. + * if it will not be used as a base transaction. + * + * @return updated transaction template + */ + public Template allowAdditionalActions() { + this.allowAdditionalActions = true; + return this; + } + + /** + * A single signing instruction included in a transaction template. + */ + public static class SigningInstruction { + /** + * The input's position in a transaction's list of inputs. + */ + public int position; + + /** + * A list of components used to coordinate the signing of an input. + */ + @SerializedName("witness_components") + public WitnessComponent[] witnessComponents; + } + + /** + * A single witness component, holding information that will become the input + * witness. + */ + public static class WitnessComponent { + /** + * The type of witness component.
+ * Possible types are "data" and "raw_tx_signature". + */ + public String type; + + /** + * Data to be included in the input witness (null unless type is "data"). + */ + public String value; + + /** + * The number of signatures required for an input (null unless type is + * "signature"). + */ + public int quorum; + + /** + * The list of keys to sign with (null unless type is "signature"). + */ + public KeyID[] keys; + + /** + * The list of signatures made with the specified keys (null unless type is + * "signature"). + */ + public String[] signatures; + } + + /** + * A class representing a derived signing key. + */ + public static class KeyID { + /** + * The extended public key associated with the private key used to sign. + */ + public String xpub; + + /** + * The derivation path of the extended public key. + */ + @SerializedName("derivation_path") + public String[] derivationPath; + } + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static Template fromJson(String json) { + return Utils.serializer.fromJson(json, Template.class); + } + +} diff --git a/tx-signer/src/main/java/io/bytom/api/Transaction.java b/tx-signer/src/main/java/io/bytom/api/Transaction.java new file mode 100644 index 0000000..2f11bf2 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/api/Transaction.java @@ -0,0 +1,1061 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.ParameterizedTypeImpl; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Transaction Class

+ */ +public class Transaction { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + @SerializedName("tx_id") + public String txId; + + /** + * Time of transaction. + */ + @SerializedName("block_time") + public String blockTime; + + /** + * Unique identifier, or block hash, of the block containing a transaction. + */ + @SerializedName("block_hash") + public String blockHash; + + /** + * Index of a transaction within the block. + */ + @SerializedName("block_index") + public String blockIndex; + + @SerializedName("block_transactions_count") + public String blockTransactionsCount; + + /** + * Height of the block containing a transaction. + */ + @SerializedName("block_height") + public int blockHeight; + + /** + * whether the state of the request has failed. + */ + @SerializedName("status_fail") + public boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(Transaction.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + /** + * Hex-encoded serialization of a transaction to add to the current template. + */ + @SerializedName("base_transaction") + protected String baseTransaction; + + /** + * List of actions in a transaction. + */ + protected List actions; + + /** + * A time duration in milliseconds. If the transaction is not fully signed and + * submitted within this time, it will be rejected by the blockchain. + * Additionally, any outputs reserved when building this transaction will remain + * reserved for this duration. + */ + protected long ttl; + + /** + * Call build-transaction api.
+ * + * Builds a single transaction template. + * + * @param client client object which makes requests to the server + * @return a transaction template + */ + public Template build(Client client) throws BytomException { + return client.request("build-transaction", this, Template.class); + } + + /** + * Default constructor initializes actions list. + */ + public Builder() { + this.actions = new ArrayList<>(); + } + + /** + * Sets the baseTransaction field and initializes the actions lists.
+ * This constructor can be used when executing an atomic swap and the counter + * party has sent an initialized tx template. + */ + public Builder(String baseTransaction) { + this.setBaseTransaction(baseTransaction); + this.actions = new ArrayList<>(); + } + + /** + * Sets the base transaction that will be added to the current template. + */ + public Builder setBaseTransaction(String baseTransaction) { + this.baseTransaction = baseTransaction; + return this; + } + + /** + * Adds an action to a transaction builder. + * @param action action to add + * @return updated builder object + */ + public Builder addAction(Action action) { + this.actions.add(action); + return this; + } + + /** + * Sets a transaction's time-to-live, which indicates how long outputs will be + * reserved for, and how long the transaction will remain valid. Passing zero will + * use the default TTL, which is 300000ms (5 minutes). + * @param ms the duration of the TTL, in milliseconds. + * @return updated builder object + */ + public Builder setTtl(long ms) { + this.ttl = ms; + return this; + } + } + + public static class QueryBuilder { + + public String txId; + + public String accountId; + + public QueryBuilder setTxId(String txId) { + this.txId = txId; + return this; + } + + public QueryBuilder setAccountId(String accountId) { + this.accountId = accountId; + return this; + } + + + /** + * call list-transactions api + * + * @param client client object that makes requests to the core + * @return Transaction Info + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", null, listType); + + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info("all transactions:"); + for (int i = 0; i < transactionList.size(); i++) { + logger.info(transactionList.get(i).toJson()); + } + + return transactionList; + } + + public List listById(Client client) throws BytomException { + Map req = new HashMap(); + req.put("tx_id", this.txId); + req.put("detail", true); + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", req, listType); + + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info("all transactions:"); + for (int i = 0; i < transactionList.size(); i++) { + logger.info(transactionList.get(i).toJson()); + } + + return transactionList; + } + + public List listByAccountId(Client client) throws BytomException { + Map req = new HashMap(); + req.put("account_id", this.accountId); + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", req, listType); + + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info("all transactions:"); + for (int i = 0; i < transactionList.size(); i++) { + logger.info(transactionList.get(i).toJson()); + } + + return transactionList; + } + + /** + * call get-transaction api + * + * @param client + * @return + * @throws BytomException + */ + public Transaction get(Client client) throws BytomException { + Map req = new HashMap(); + req.put("tx_id", this.txId); + + Transaction transaction = client.request("get-transaction", req, Transaction.class); + + logger.info("get-transaction:"); + logger.info(transaction.toJson()); + + return transaction; + } + + } + + public static class SignerBuilder { + /** + * call sign-transaction api + * + * Sends a transaction template to a remote password for signing. + * + * @param client + * @param template a signed transaction template + * @param password + * @return + * @throws BytomException + */ + public Template sign(Client client, Template template, + String password) throws BytomException { + HashMap req = new HashMap(); + req.put("transaction", template); + req.put("password", password); + + Template templateResult = client.requestGet("sign-transaction", req, "transaction", + Template.class); + + logger.info("sign-transaction:"); + logger.info(templateResult.toJson()); + + return templateResult; + } + + } + + /** + * A single input included in a transaction. + */ + public static class Input { + /** + * The alias of the account transferring the asset (possibly null if the input is + * an issuance or an unspent output is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * The id of the account transferring the asset (possibly null if the input is an + * issuance or an unspent output is specified). + */ + @SerializedName("account_id") + public String accountId; + + @SerializedName("address") + public String address; + + /** + * The number of units of the asset being issued or spent. + */ + public long amount; + + /** + * The alias of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The id of the output consumed by this input. Null if the input is an issuance. + */ + @SerializedName("spent_output_id") + public String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + public String type; + + public String arbitrary; + + @SerializedName("control_program") + public String controlProgram; + + } + + /** + * A single output included in a transaction. + */ + public static class Output { + /** + * The id of the output. + */ + @SerializedName("id") + public String id; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + public String type; + + /** + * The output's position in a transaction's list of outputs. + */ + public int position; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + public String controlProgram; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The alias of the asset being controlled. + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The number of units of the asset being controlled. + */ + public long amount; + + /** + * The id of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_id") + public String accountId; + + /** + * The alias of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("address") + public String address; + + } + + /** + * Base class representing actions that can be taken within a transaction. + */ + public static class Action extends HashMap { + /** + * + */ + private static final long serialVersionUID = 7948250382060074590L; + + /** + * Default constructor initializes list and sets the client token. + */ + public Action() { + // Several action types require client_token as an idempotency key. + // It's safest to include a default value for this param. + this.put("client_token", UUID.randomUUID().toString()); + } + + /** + * Represents an issuance action. + */ + public static class Issue extends Action { + /** + * + */ + private static final long serialVersionUID = -6296543909434749786L; + + /** + * Default constructor defines the action type as "issue" + */ + public Issue() { + this.put("type", "issue"); + } + + /** + * Specifies the asset to be issued using its alias.
+ * Either this or {@link Issue#setAssetId(String)} must be + * called. + * @param alias alias of the asset to be issued + * @return updated action object + */ + public Issue setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be issued using its id.
+ * Either this or {@link Issue#setAssetAlias(String)} must be + * called. + * @param id id of the asset to be issued + * @return updated action object + */ + public Issue setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be issued.
+ * Must be called. + * @param amount number of units of the asset to be issued + * @return updated action object + */ + public Issue setAmount(long amount) { + this.put("amount", amount); + return this; + } + } + + /** + * Represents a spend action taken on a particular account. + */ + public static class SpendFromAccount extends Action { + /** + * + */ + private static final long serialVersionUID = 6444162327409625893L; + + /** + * Default constructor defines the action type as "spend_account" + */ + public SpendFromAccount() { + this.put("type", "spend_account"); + } + + /** + * Specifies the spending account using its alias.
+ * Either this or {@link SpendFromAccount#setAccountId(String)} must + * be called.
+ * Must be used with {@link SpendFromAccount#setAssetAlias(String)} + * . + * @param alias alias of the spending account + * @return updated action object + */ + public SpendFromAccount setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + /** + * Specifies the spending account using its id.
+ * Either this or {@link SpendFromAccount#setAccountAlias(String)} + * must be called.
+ * Must be used with {@link SpendFromAccount#setAssetId(String)} + * . + * @param id id of the spending account + * @return updated action object + */ + public SpendFromAccount setAccountId(String id) { + this.put("account_id", id); + return this; + } + + /** + * Specifies the asset to be spent using its alias.
+ * Either this or {@link SpendFromAccount#setAssetId(String)} must be + * called.
+ * Must be used with {@link SpendFromAccount#setAccountAlias(String)} + * . + * @param alias alias of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be spent using its id.
+ * Either this or {@link SpendFromAccount#setAssetAlias(String)} must + * be called.
+ * Must be used with {@link SpendFromAccount#setAccountId(String)} + * .
+ * @param id id of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of asset to be spent.
+ * Must be called. + * @param amount number of units of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAmount(long amount) { + this.put("amount", amount); + return this; + } + } + + /** + * Represents a control action taken on a particular account. + */ + public static class ControlWithAccount extends Action { + /** + * + */ + private static final long serialVersionUID = -1067464339402520620L; + + /** + * Default constructor defines the action type as "control_account" + */ + public ControlWithAccount() { + this.put("type", "control_account"); + } + + /** + * Specifies the controlling account using its alias.
+ * Either this or {@link ControlWithAccount#setAccountId(String)} must + * be called.
+ * Must be used with {@link ControlWithAccount#setAssetAlias(String)} + * . + * @param alias alias of the controlling account + * @return updated action object + */ + public ControlWithAccount setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + /** + * Specifies the controlling account using its id.
+ * Either this or {@link ControlWithAccount#setAccountAlias(String)} + * must be called.
+ * Must be used with {@link ControlWithAccount#setAssetId(String)} + * . + * @param id id of the controlling account + * @return updated action object + */ + public ControlWithAccount setAccountId(String id) { + this.put("account_id", id); + return this; + } + + /** + * Specifies the asset to be controlled using its alias.
+ * Either this or {@link ControlWithAccount#setAssetId(String)} must + * be called.
+ * Must be used with + * {@link ControlWithAccount#setAccountAlias(String)}. + * @param alias alias of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id.
+ * Either this or {@link ControlWithAccount#setAssetAlias(String)} + * must be called.
+ * Must be used with {@link ControlWithAccount#setAccountId(String)} + * . + * @param id id of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled.
+ * Must be called. + * @param amount number of units of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Represents a control action taken on a particular address. + */ + public static class ControlWithAddress extends Action { + /** + * + */ + private static final long serialVersionUID = 1292007349260961499L; + + /** + * Default constructor defines the action type as "control_address" + */ + public ControlWithAddress() { + this.put("type", "control_address"); + } + + public ControlWithAddress setAddress(String address) { + this.put("address", address); + return this; + } + + /** + * Specifies the asset to be controlled using its alias.
+ * Either this or {@link ControlWithAddress#setAssetId(String)} must + * be called.
+ * @param alias alias of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id.
+ * Either this or {@link ControlWithAccount#setAssetAlias(String)} + * must be called.
+ * @param id id of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled.
+ * Must be called. + * @param amount number of units of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Use this action to pay assets into a {@link Receiver}. + */ + public static class ControlWithReceiver extends Action { + /** + * + */ + private static final long serialVersionUID = 7280759134960453401L; + + /** + * Default constructor. + */ + public ControlWithReceiver() { + this.put("type", "control_receiver"); + } + + /** + * Specifies the receiver that is being paid to. + * + * @param receiver the receiver being paid to + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setReceiver(Receiver receiver) { + this.put("receiver", receiver); + return this; + } + + /** + * Specifies the asset to be controlled using its alias. + *

+ * Either this or {@link ControlWithReceiver#setAssetId(String)} must + * be called. + * @param alias unique alias of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id. + *

+ * Either this or {@link ControlWithReceiver#setAssetAlias(String)} + * must be called. + * @param id unique ID of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled. + *

+ * Must be called. + * @param amount the number of units of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Represents a retire action. + */ + public static class Retire extends Action { + /** + * + */ + private static final long serialVersionUID = -8434272436211832706L; + + /** + * Default constructor defines the action type as "control_program" + */ + public Retire() { + this.put("type", "retire"); + } + + /** + * Specifies the amount of the asset to be retired.
+ * Must be called. + * @param amount number of units of the asset to be retired + * @return updated action object + */ + public Retire setAmount(long amount) { + this.put("amount", amount); + return this; + } + + /** + * Specifies the asset to be retired using its alias.
+ * Either this or {@link Retire#setAssetId(String)} must be + * called. + * @param alias alias of the asset to be retired + * @return updated action object + */ + public Retire setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be retired using its id.
+ * Either this or {@link Retire#setAssetAlias(String)} must be + * called. + * @param id id of the asset to be retired + * @return updated action object + */ + public Retire setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + public Retire setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + public Retire setAccountId(String id) { + this.put("account_id", id); + return this; + } + + } + + /** + * Sets a k,v parameter pair. + * @param key the key on the parameter object + * @param value the corresponding value + * @return updated action object + */ + public Action setParameter(String key, Object value) { + this.put(key, value); + return this; + } + } + + public static class SubmitResponse { + /** + * The transaction id. + */ + public String tx_id; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + /** + * Call submit-transaction api + * + * @param client + * @param template + * @return + * @throws BytomException + */ + public static SubmitResponse submit(Client client, Template template) + throws BytomException { + HashMap body = new HashMap<>(); + body.put("raw_transaction", template.rawTransaction); + return client.request("submit-transaction", body, SubmitResponse.class); + } + + public static class TransactionGas { + /** + * total consumed neu(1BTM = 10^8NEU) for execute transaction. + */ + @SerializedName("total_neu") + public int totalNeu; + + /** + * consumed neu for storage transaction . + */ + @SerializedName("storage_neu") + public int storageNeu; + /** + * consumed neu for execute VM. + */ + @SerializedName("vm_neu") + public int vmNeu; + } + + /** + * call estimate-transaction-gas api + * + * @param client + * @param template + * @return + * @throws BytomException + */ + public static TransactionGas estimateGas(Client client, Template template) + throws BytomException { + HashMap body = new HashMap<>(); + body.put("transaction_template", template); + return client.request("estimate-transaction-gas", body, TransactionGas.class); + } + + public static class Feed { + /** + * name of the transaction feed. + */ + public String alias; + /** + * filter, filter of the transaction feed. + */ + public String filter; + + /** + * param, param of the transaction feed. + */ + public TransactionFeedParam param; + + private static Logger logger = Logger.getLogger(Transaction.class); + + /** + * Serializes the TransactionFeed into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the TransactionFeed object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + + public String alias; + + public String filter; + + public Builder() { + } + + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + public Builder setFilter(String filter) { + this.filter = filter; + return this; + } + + /** + * Call create-transaction-feed api + * + * @param client + * @throws BytomException + */ + public void create(Client client) throws BytomException { + client.request("create-transaction-feed", this); + logger.info("create-transaction-feed"); + } + } + + /** + * Call get-transaction-feed api + * + * @param client + * @param alias + * @return + * @throws BytomException + */ + public static Feed getByAlias(Client client, String alias) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + + // the return param contains txfeed. + Feed transactionFeed = client.requestGet("get-transaction-feed", + req, "txfeed", Feed.class); + logger.info("get-transaction-feed."); + logger.info(transactionFeed.toJson()); + + return transactionFeed; + } + + /** + * Call update-transaction-feed api + * + * @param client + * @param alias + * @param filter + * @throws BytomException + */ + public static void update(Client client, String alias, String filter) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + req.put("filter", filter); + client.request("update-transaction-feed", req); + logger.info("update-transaction-feed successfully"); + } + + /** + * Call list-transaction-feeds api + * @param client + * @return + * @throws BytomException + */ + public static List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Feed.class}); + List transactionFeedList = client.request("list-transaction-feeds", null, listType); + + logger.info("list-transaction-feeds:"); + logger.info("size of transactionList:" + transactionFeedList.size()); + for (int i =0 ;i < transactionFeedList.size();i++) { + logger.info(transactionFeedList.get(i).toJson()); + } + return transactionFeedList; + } + + /** + * Call delete-transaction-feed api + * + * @param client + * @param alias + * @throws BytomException + */ + public static void deleteByAlias(Client client, String alias) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + client.request("delete-transaction-feed", req); + logger.info("delete-transaction-feed successfully"); + } + + + + public class TransactionFeedParam { + + /** + * assetid + */ + public String assetid; + + /** + * lowerlimit + */ + public long lowerlimit; + + /** + * upperlimit + */ + public long upperlimit; + + } + } +} diff --git a/tx-signer/src/main/java/io/bytom/common/Configuration.java b/tx-signer/src/main/java/io/bytom/common/Configuration.java new file mode 100644 index 0000000..21dd949 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/Configuration.java @@ -0,0 +1,29 @@ +package io.bytom.common; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +public class Configuration { + + private static Properties props = new Properties(); + static { + try { + props.load(Thread.currentThread().getContextClassLoader() + .getResourceAsStream("config.properties")); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getValue(String key) { + return props.getProperty(key); + } + + public static void updateProperties(String key, String value) { + props.setProperty(key, value); + } + +} diff --git a/tx-signer/src/main/java/io/bytom/common/Constants.java b/tx-signer/src/main/java/io/bytom/common/Constants.java new file mode 100644 index 0000000..98df866 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/Constants.java @@ -0,0 +1,13 @@ +package io.bytom.common; + +/** + * Created by liqiang on 2018/10/17. + */ +public class Constants { + + public static int INPUT_TYPE_ISSUANCE = 0; + + public static int INPUT_TYPE_SPEND = 1; + + public static int INPUT_TYPE_COINBASE = 2; +} diff --git a/tx-signer/src/main/java/io/bytom/common/DeriveXpub.java b/tx-signer/src/main/java/io/bytom/common/DeriveXpub.java new file mode 100644 index 0000000..92cae49 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/DeriveXpub.java @@ -0,0 +1,24 @@ +package io.bytom.common; + +import com.google.crypto.tink.subtle.Ed25519; + +public class DeriveXpub { + public static byte[] deriveXpub(byte[] xprv) { + byte[] xpub = new byte[xprv.length]; + byte[] scalar = new byte[xprv.length / 2]; +// for (int i = 0; i < xprv.length / 2; i++) { +// scalar[i] = xprv[i]; +// } + System.arraycopy(xprv, 0, scalar, 0, xprv.length / 2); + byte[] buf = Ed25519.scalarMultWithBaseToBytes(scalar); +// for (int i = 0; i < buf.length; i++) { +// xpub[i] = buf[i]; +// } + System.arraycopy(buf, 0, xpub, 0, buf.length); +// for (int i = xprv.length / 2; i < xprv.length; i++) { +// xpub[i] = xprv[i]; +// } + System.arraycopy(xprv, xprv.length / 2, xpub, xprv.length / 2, xprv.length / 2); + return xpub; + } +} diff --git a/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java b/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java new file mode 100644 index 0000000..9842e10 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java @@ -0,0 +1,33 @@ +package io.bytom.common; + +import org.bouncycastle.util.encoders.Hex; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public class ExpandedPrivateKey { + private static byte[] HMacSha512(byte[] data, byte[] key) + throws SignatureException, NoSuchAlgorithmException, InvalidKeyException + { + SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA512"); + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(signingKey); + return mac.doFinal(data); + } + + public static byte[] ExpandedPrivateKey(byte[] data) + throws SignatureException, NoSuchAlgorithmException, InvalidKeyException + { + // "457870616e64" is "Expand" hex. + byte[] res = HMacSha512(data, Hex.decode("457870616e64")); + for (int i = 0; i <= 31; i++) { + res[i] = data[i]; + } + return res; + } +} + + diff --git a/tx-signer/src/main/java/io/bytom/common/FindDst.java b/tx-signer/src/main/java/io/bytom/common/FindDst.java new file mode 100644 index 0000000..1a34844 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/FindDst.java @@ -0,0 +1,26 @@ +package io.bytom.common; + +import org.apache.log4j.Logger; +import org.bouncycastle.util.encoders.Hex; + +public class FindDst { + + public static Logger logger = Logger.getLogger(FindDst.class); + + public static int find(String[] privateKeys, String xpub) throws Exception { + // 多签情况下,找到xpub对应的private key的下标 dst + int dst = -1; + for (int k = 0; k < privateKeys.length; k++) { + byte[] tempXpub = DeriveXpub.deriveXpub(Hex.decode(privateKeys[k])); + if (xpub.equals(Hex.toHexString(tempXpub))) { + dst = k; + logger.info("private[dst]: "+privateKeys[dst]); + break; + } + } + if (dst == -1) { + throw new Exception("Not a proper private key to sign transaction."); + } + return dst; + } +} diff --git a/tx-signer/src/main/java/io/bytom/common/NonHardenedChild.java b/tx-signer/src/main/java/io/bytom/common/NonHardenedChild.java new file mode 100644 index 0000000..912d7c3 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/NonHardenedChild.java @@ -0,0 +1,85 @@ +package io.bytom.common; + +import org.bouncycastle.util.encoders.Hex; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public class NonHardenedChild { + + private static byte[] HMacSha512(byte[] data, byte[] key) + throws NoSuchAlgorithmException, InvalidKeyException { + SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA512"); + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(signingKey); + return mac.doFinal(data); + } + + public static byte[] NHchild(byte[] path, byte[] xprv, byte[] xpub) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + // begin build data + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write('N'); + out.write(xpub, 0, xpub.length / 2); + out.write(path, 0, path.length); + byte[] data = out.toByteArray(); + // end build data + + // begin build key + byte[] key = new byte[xpub.length / 2]; + System.arraycopy(xpub, xpub.length / 2, key, 0, xpub.length / 2); + // end build key + + // doFinal() + byte[] res = HMacSha512(data, key); + + //begin operate res[:32] + byte[] f = new byte[res.length / 2]; + System.arraycopy(res, 0, f, 0, res.length / 2); + f = pruneIntermediateScalar(f); + System.arraycopy(f, 0, res, 0, res.length / 2); + //end operate res[:32] + + //begin operate res[:32] again + int carry = 0; + int sum = 0; + for (int i = 0; i < 32; i++) { + int xprvInt = xprv[i] & 0xFF; + int resInt = res[i] & 0xFF; + sum = xprvInt + resInt + carry; + res[i] = (byte) sum; + carry = sum >> 8; + } + if ((sum >> 8) != 0) { + System.err.println("sum does not fit in 256-bit int"); + } + //end operate res[:32] again + return res; + } + + private static byte[] pruneIntermediateScalar(byte[] f) { + f[0] &= 248; // clear bottom 3 bits + f[29] &= 1; // clear 7 high bits + f[30] = 0; // clear 8 bits + f[31] = 0; // clear 8 bits + return f; + } + + public static byte[] child(byte[] xprv, String[] hpaths) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + byte[][] paths = new byte[][]{ + Hex.decode(hpaths[0]), + Hex.decode(hpaths[1]) + }; + byte[] res = xprv; + for (int i = 0; i < hpaths.length; i++) { + byte[] xpub = DeriveXpub.deriveXpub(res); + res = NonHardenedChild.NHchild(paths[i], res, xpub); + } + return res; + } +} + + diff --git a/tx-signer/src/main/java/io/bytom/common/ParameterizedTypeImpl.java b/tx-signer/src/main/java/io/bytom/common/ParameterizedTypeImpl.java new file mode 100644 index 0000000..d66be8c --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/ParameterizedTypeImpl.java @@ -0,0 +1,30 @@ +package io.bytom.common; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class ParameterizedTypeImpl implements ParameterizedType { + + private final Class raw; + private final Type[] args; + + public ParameterizedTypeImpl(Class raw, Type[] args) { + this.raw = raw; + this.args = args != null ? args : new Type[0]; + } + + @Override + public Type[] getActualTypeArguments() { + return args; + } + + @Override + public Type getRawType() { + return raw; + } + + @Override + public Type getOwnerType() { + return null; + } +} diff --git a/tx-signer/src/main/java/io/bytom/common/SuccessRespon.java b/tx-signer/src/main/java/io/bytom/common/SuccessRespon.java new file mode 100644 index 0000000..d7813f9 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/SuccessRespon.java @@ -0,0 +1,9 @@ +package io.bytom.common; + +import com.google.gson.annotations.SerializedName; + +public class SuccessRespon { + public String status; + @SerializedName("data") + public T dataObject; +} diff --git a/tx-signer/src/main/java/io/bytom/common/Utils.java b/tx-signer/src/main/java/io/bytom/common/Utils.java new file mode 100644 index 0000000..7a7f69e --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/common/Utils.java @@ -0,0 +1,9 @@ +package io.bytom.common; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class Utils { + public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create(); +} diff --git a/tx-signer/src/main/java/io/bytom/exception/APIException.java b/tx-signer/src/main/java/io/bytom/exception/APIException.java new file mode 100644 index 0000000..1746409 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/APIException.java @@ -0,0 +1,178 @@ +package io.bytom.exception; + +import com.google.gson.annotations.SerializedName; + +/** + * APIException wraps errors returned by the API. + * Each error contains a brief description in addition to a unique error code.
+ * The error code can be used by Chain Support to diagnose the exact cause of the error. + * The mapping of error codes to messages is as follows:

+ * + *

General errors

+ * CH001 - Request timed out + * CH002 - Not found + * CH003 - Invalid request body + * CH004 - Invalid request header + * CH006 - Not found + * + *

Account/Asset errors

+ * CH200 - Quorum must be greater than 1 and less than or equal to the length of xpubs + * CH201 - Invalid xpub format + * CH202 - At least one xpub is required + * CH203 - Retrieved type does not match expected type + * + *

Access token errors

+ * CH300 - Malformed or empty access token id + * CH301 - Access tokens must be type client or network + * CH302 - Access token id is already in use + * CH310 - The access token used to authenticate this request cannot be deleted + * + *

Query errors

+ * CH600 - Malformed pagination parameter `after` + * CH601 - Incorrect number of parameters to filter + * CH602 - Malformed query filter + * + *

Transaction errors

+ * CH700 - Reference data does not match previous transaction's reference data
+ * CH701 - Invalid action type
+ * CH702 - Invalid alias on action
+ * CH730 - Missing raw transaction
+ * CH731 - Too many signing instructions in template for transaction
+ * CH732 - Invalid transaction input index
+ * CH733 - Invalid signature script component
+ * CH734 - Missing signature in template
+ * CH735 - Transaction rejected
+ * CH760 - Insufficient funds for tx
+ * CH761 - Some outputs are reserved; try again
+ */ +public class APIException extends BytomException { + /** + * Unique identifier for the error. + */ + public String code; + + /** + * Message describing the general nature of the error. + */ + @SerializedName("message") + public String chainMessage; + + /** + * Additional information about the error (possibly null). + */ + public String detail; + + /** + * Specifies whether the error is temporary, or a change to the request is necessary. + */ + public boolean temporary; + + /** + * Unique identifier of the request to the server. + */ + public String requestId; + + /** + * HTTP status code returned by the server. + */ + public int statusCode; + + public APIException(String message) { + super(message); + } + + /** + * Initializes exception with its message and requestId attributes. + * + * @param message error message + * @param requestId unique identifier of the request + */ + public APIException(String message, String requestId) { + super(message); + this.requestId = requestId; + } + + /** + * Intitializes exception with its code, message, detail & temporary field set. + * + * @param code error code + * @param message error message + * @param detail additional error information + * @param temporary unique identifier of the request + */ + public APIException(String code, String message, String detail, boolean temporary) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.temporary = temporary; + } + + /** + * Initializes exception with its code, message, detail & requestId attributes. + * + * @param code error code + * @param message error message + * @param detail additional error information + * @param requestId unique identifier of the request + */ + public APIException(String code, String message, String detail, String requestId) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.requestId = requestId; + } + + /** + * Initializes exception with all of its attributes. + * + * @param code error code + * @param message error message + * @param detail additional error information + * @param temporary specifies if the error is temporary + * @param requestId unique identifier of the request + * @param statusCode HTTP status code + */ + public APIException( + String code, + String message, + String detail, + boolean temporary, + String requestId, + int statusCode) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.temporary = temporary; + this.requestId = requestId; + this.statusCode = statusCode; + } + + /** + * Constructs a detailed message of the error. + * + * @return detailed error message + */ + @Override + public String getMessage() { + String s = ""; + + if (this.code != null && this.code.length() > 0) { + s += "Code: " + this.code + " "; + } + + s += "Message: " + this.chainMessage; + + if (this.detail != null && this.detail.length() > 0) { + s += " Detail: " + this.detail; + } + + if (this.requestId != null) { + s += " Request-ID: " + this.requestId; + } + + return s; + } +} diff --git a/tx-signer/src/main/java/io/bytom/exception/BadURLException.java b/tx-signer/src/main/java/io/bytom/exception/BadURLException.java new file mode 100644 index 0000000..dd16377 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/BadURLException.java @@ -0,0 +1,14 @@ +package io.bytom.exception; + +/** + * BadURLException wraps errors due to malformed URLs. + */ +public class BadURLException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public BadURLException(String message) { + super(message); + } +} diff --git a/tx-signer/src/main/java/io/bytom/exception/BuildException.java b/tx-signer/src/main/java/io/bytom/exception/BuildException.java new file mode 100644 index 0000000..19a2f89 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/BuildException.java @@ -0,0 +1,49 @@ +package io.bytom.exception; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * BuildException wraps errors returned by the build-transaction endpoint. + */ +public class BuildException extends APIException { + + public BuildException(String message, String requestId) { + super(message, requestId); + } + + public static class ActionError extends APIException { + + public static class Data { + /** + * The index of the action that caused this error. + */ + @SerializedName("index") + public Integer index; + } + + public ActionError(String message, String requestId) { + super(message, requestId); + } + + /** + * Additional data pertaining to the error. + */ + public Data data; + } + + public static class Data { + /** + * A list of errors resulting from building actions. + */ + @SerializedName("actions") + public List actionErrors; + } + + /** + * Extra data associated with this error, if any. + */ + @SerializedName("data") + public Data data; +} diff --git a/tx-signer/src/main/java/io/bytom/exception/BytomException.java b/tx-signer/src/main/java/io/bytom/exception/BytomException.java new file mode 100644 index 0000000..4043065 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/BytomException.java @@ -0,0 +1,32 @@ +package io.bytom.exception; + +/** + * Base exception class for the Chain Core SDK. + */ +public class BytomException extends Exception { + /** + * Default constructor. + */ + public BytomException() { + super(); + } + + /** + * Initializes exception with its message attribute. + * + * @param message error message + */ + public BytomException(String message) { + super(message); + } + + /** + * Initializes a new exception while storing the original cause. + * + * @param message the error message + * @param cause the cause of the exception + */ + public BytomException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/tx-signer/src/main/java/io/bytom/exception/ConfigurationException.java b/tx-signer/src/main/java/io/bytom/exception/ConfigurationException.java new file mode 100644 index 0000000..695e873 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/ConfigurationException.java @@ -0,0 +1,23 @@ +package io.bytom.exception; + +/** + * ConfigurationException wraps errors during client configuration. + */ +public class ConfigurationException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public ConfigurationException(String message) { + super(message); + } + + /** + * Initializes new exception while storing original cause. + * @param message the error message + * @param cause the original cause + */ + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/tx-signer/src/main/java/io/bytom/exception/ConnectivityException.java b/tx-signer/src/main/java/io/bytom/exception/ConnectivityException.java new file mode 100644 index 0000000..4f50197 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/ConnectivityException.java @@ -0,0 +1,36 @@ +package io.bytom.exception; + +import com.squareup.okhttp.Response; + +import java.io.IOException; + +/** + * ConnectivityException wraps errors due to connectivity issues with the server. + */ +public class ConnectivityException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param resp the server response used to create error message + */ + public ConnectivityException(Response resp) { + super(formatMessage(resp)); + } + + /** + * Parses the the server response into a detailed error message. + * @param resp the server response + * @return detailed error message + */ + private static String formatMessage(Response resp) { + String s = + "Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings."; + // TODO(kr): include client-generated reqid here once we have that. + String body; + try { + body = resp.body().string(); + } catch (IOException ex) { + body = "[unable to read response body: " + ex.toString() + "]"; + } + return String.format("%s status=%d body=%s", s, resp.code(), body); + } +} diff --git a/tx-signer/src/main/java/io/bytom/exception/HTTPException.java b/tx-signer/src/main/java/io/bytom/exception/HTTPException.java new file mode 100644 index 0000000..04646bd --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/HTTPException.java @@ -0,0 +1,23 @@ +package io.bytom.exception; + +/** + * HTTPException wraps generic HTTP errors. + */ +public class HTTPException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public HTTPException(String message) { + super(message); + } + + /** + * Initializes new exception while storing original cause. + * @param message the error message + * @param cause the original cause + */ + public HTTPException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/tx-signer/src/main/java/io/bytom/exception/JSONException.java b/tx-signer/src/main/java/io/bytom/exception/JSONException.java new file mode 100644 index 0000000..a81cef6 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/exception/JSONException.java @@ -0,0 +1,39 @@ +package io.bytom.exception; + +/** + * JSONException wraps errors due to marshaling/unmarshaling json payloads. + */ +public class JSONException extends BytomException { + + /** + * Unique indentifier of the request to the server. + */ + public String requestId; + + /** + * Default constructor. + */ + public JSONException(String message) { + super(message); + } + + /** + * Initializes exception with its message and requestId attributes. + * Use this constructor in context of an API call. + * + * @param message error message + * @param requestId unique identifier of the request + */ + public JSONException(String message, String requestId) { + super(message); + this.requestId = requestId; + } + + public String getMessage() { + String message = "Message: " + super.getMessage(); + if (requestId != null) { + message += " Request-ID: " + requestId; + } + return message; + } +} diff --git a/tx-signer/src/main/java/io/bytom/http/BatchResponse.java b/tx-signer/src/main/java/io/bytom/http/BatchResponse.java new file mode 100644 index 0000000..035d900 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/http/BatchResponse.java @@ -0,0 +1,114 @@ +package io.bytom.http; + +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; + +import java.util.*; + +/** + * BatchResponse provides a convenient interface for handling the results of + * batched API calls. The response contains one success or error per outgoing + * request item in the batch. Errors are always of type BytomException. + */ +public class BatchResponse { + private Map successesByIndex = new LinkedHashMap<>(); + private Map errorsByIndex = new LinkedHashMap<>(); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * This constructor is used for synthetically generating a batch response + * object from a map of successes and a map of errors. It ensures that + * the successes and errors are stored in an order-preserving fashion. + */ + public BatchResponse(Map successes, Map errors) { + List successIndexes = new ArrayList<>(); + Iterator successIter = successes.keySet().iterator(); + while (successIter.hasNext()) { + successIndexes.add(successIter.next()); + } + Collections.sort(successIndexes); + + for (int i : successIndexes) { + successesByIndex.put(i, successes.get(i)); + } + + List errorIndexes = new ArrayList<>(); + Iterator errorIter = errors.keySet().iterator(); + while (errorIter.hasNext()) { + errorIndexes.add(errorIter.next()); + } + Collections.sort(errorIndexes); + for (int i : errorIndexes) { + errorsByIndex.put(i, errors.get(i)); + } + } + + /** + * Returns the total number of response objects. This should equal the number + * of request objects in the batch. + */ + public int size() { + return successesByIndex.size() + errorsByIndex.size(); + } + + /** + * Returns whether the request object at the given index produced a success. + * + * @param index the index of the request object + */ + public boolean isSuccess(int index) { + return successesByIndex.containsKey(index); + } + + /** + * Returns whether the request object at the given index produced an error. + * + * @param index the index of the request object + */ + public boolean isError(int index) { + return errorsByIndex.containsKey(index); + } + + /** + * Returns a list of successful response objects in the batch. The order of + * the list corresponds to the order of the request objects that produced the + * successes. + */ + public List successes() { + List res = new ArrayList<>(); + res.addAll(successesByIndex.values()); + return res; + } + + /** + * Returns a list of error objects in the batch. The order of the list + * corresponds to the order of the request objects that produced the + * errors. + */ + public List errors() { + List res = new ArrayList<>(); + res.addAll(errorsByIndex.values()); + return res; + } + + /** + * Returns a map of success responses, keyed by the index of the request + * object that produced the success. The set of this map's keys is mutually + * exclusive of the keys returned by errorsByIndex. + */ + public Map successesByIndex() { + return successesByIndex; + } + + /** + * Returns a map of error responses, keyed by the index of the request + * object that produced the error. The set of this map's keys is mutually + * exclusive of the keys returned by successByIndex. + */ + public Map errorsByIndex() { + return errorsByIndex; + } +} diff --git a/tx-signer/src/main/java/io/bytom/http/Client.java b/tx-signer/src/main/java/io/bytom/http/Client.java new file mode 100644 index 0000000..f872090 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/http/Client.java @@ -0,0 +1,726 @@ +package io.bytom.http; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.squareup.okhttp.*; +import io.bytom.common.Configuration; +import io.bytom.common.Utils; +import io.bytom.exception.APIException; +import io.bytom.exception.BytomException; +import io.bytom.exception.ConfigurationException; +import io.bytom.exception.JSONException; +import org.apache.log4j.Logger; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; + +import javax.net.ssl.*; +import java.io.*; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * The Client object contains all information necessary to + * perform an HTTP request against a remote API. Typically, + * an application will have a client that makes requests to + * a Chain Core, and a separate Client that makes requests + * to an HSM server. + */ +public class Client { + private String url; + private String accessToken; + private OkHttpClient httpClient; + + // Used to create empty, in-memory key stores. + private static final char[] DEFAULT_KEYSTORE_PASSWORD = "123456".toCharArray(); + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static String version = "dev"; // updated in the static initializer + + public static Logger logger = Logger.getLogger(Client.class); + + private static class BuildProperties { + public String version; + } + + static { + InputStream in = Client.class.getClassLoader().getResourceAsStream("properties.json"); + if (in != null) { + InputStreamReader inr = new InputStreamReader(in); + version = Utils.serializer.fromJson(inr, BuildProperties.class).version; + } + } + + public static Client generateClient() throws BytomException { + + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888"; + } + + if (coreURL.endsWith("/")) { + //split the last char "/" + coreURL = coreURL.substring(0, coreURL.length()-1); + logger.info("check the coreURL is right."); + } + + return new Client(coreURL, accessToken); + } + + public Client(Builder builder) throws ConfigurationException { + if (builder.url.endsWith("/")) { + //split the last char "/" + builder.url = builder.url.substring(0, builder.url.length()-1); + } + this.url = builder.url; + this.accessToken = builder.accessToken; + this.httpClient = buildHttpClient(builder); + } + + /** + * Create a new http Client object using the default development host URL. + */ + public Client() throws BytomException { + this(new Builder()); + } + + /** + * Create a new http Client object + * + * @param url the URL of the Chain Core or HSM + */ + public Client(String url) throws BytomException { + this(new Builder().setUrl(url)); + } + + /** + * Create a new http Client object + * + * @param url the URL of the Chain Core or HSM + * @param accessToken a Client API access token + */ + public Client(String url, String accessToken) throws BytomException { + this(new Builder().setUrl(url).setAccessToken(accessToken)); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public T request(String action, Object body, final Type tClass) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } else if (data != null) { + return deserializer.fromJson(data, tClass); + } else { + return deserializer.fromJson(response.body().charStream(), tClass); + } + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action, + * ignoring the body of the response. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @throws BytomException + */ + public void request(String action, Object body) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public Object create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } + return null; + } + }; + post(action, body, rc); + } + + /** + * return the value of named as key from json + * + * @param action + * @param body + * @param key + * @param tClass + * @param + * @return + * @throws BytomException + */ + public T requestGet(String action, Object body, final String key, final Type tClass) + throws BytomException { + ResponseCreator rc = new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, + BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + + if (status != null && status.toString().contains("fail")) + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + else if (data != null) + return deserializer.fromJson(data.getAsJsonObject().get(key), tClass); + else + return deserializer.fromJson(response.body().charStream(), tClass); + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * Use this method if you want batch semantics, i.e., the endpoint response + * is an array of valid objects interleaved with arrays, once corresponding to + * each input object. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + /* + public T requestBatch(String action, Object body, final Type tClass) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } else if (data != null) { + return deserializer.fromJson(data, tClass); + } else { + return deserializer.fromJson(response.body().charStream(), tClass); + } + } + }; + //把object转换为json对象数组(有点难) + + //根据数组的大小循环调用post()方法 + + //重写create()接口方法,对成功和失败做不同的处理 + + //调用BatchResponse(Map successes, Map errors) + //构造方法,最后返回BatchResponse实例对象 + + return post(action, body, rc); + } + */ + + + /** + * Returns true if a client access token stored in the client. + * + * @return a boolean + */ + public boolean hasAccessToken() { + return this.accessToken != null && !this.accessToken.isEmpty(); + } + + /** + * Returns the client access token (possibly null). + * + * @return the client access token + */ + public String accessToken() { + return accessToken; + } + + public String getUrl() { + return url; + } + + /** + * Pins a public key to the HTTP client. + * + * @param provider certificate provider + * @param subjPubKeyInfoHash public key hash + */ + public void pinCertificate(String provider, String subjPubKeyInfoHash) { + CertificatePinner cp = + new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); + this.httpClient.setCertificatePinner(cp); + } + + /** + * Sets the default connect timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setConnectTimeout(long timeout, TimeUnit unit) { + this.httpClient.setConnectTimeout(timeout, unit); + } + + /** + * Sets the default read timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setReadTimeout(long timeout, TimeUnit unit) { + this.httpClient.setReadTimeout(timeout, unit); + } + + /** + * Sets the default write timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setWriteTimeout(long timeout, TimeUnit unit) { + this.httpClient.setWriteTimeout(timeout, unit); + } + + /** + * Sets the proxy information for the HTTP client. + * + * @param proxy proxy object + */ + public void setProxy(Proxy proxy) { + this.httpClient.setProxy(proxy); + } + + /** + * Defines an interface for deserializing HTTP responses into objects. + * + * @param the type of object to return + */ + public interface ResponseCreator { + /** + * Deserializes an HTTP response into a Java object of type T. + * + * @param response HTTP response object + * @param deserializer json deserializer + * @return an object of type T + * @throws BytomException + * @throws IOException + */ + T create(Response response, Gson deserializer) throws BytomException, IOException; + } + + /** + * Builds and executes an HTTP Post request. + * + * @param path the path to the endpoint + * @param body the request body + * @param respCreator object specifying the response structure + * @return a response deserialized into type T + * @throws BytomException + */ + private T post(String path, Object body, ResponseCreator respCreator) + throws BytomException { + + RequestBody requestBody = RequestBody.create(this.JSON, Utils.serializer.toJson(body)); + Request req; + + BytomException exception = null; + URL endpointURL = null; + + try { + endpointURL = new URL(url + "/" + path); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + Request.Builder builder = + new Request.Builder() + .header("User-Agent", "bytom-sdk-java/" + version) + .url(endpointURL) + .method("POST", requestBody); + if (hasAccessToken()) { + builder = builder.header("Authorization", buildCredentials()); + } + req = builder.build(); + + Response resp = null; + + T object = null; + + try { + resp = this.checkError(this.httpClient.newCall(req).execute()); + object = respCreator.create(resp, Utils.serializer); + } catch (IOException e) { + e.printStackTrace(); + } + + return object; + } + + private OkHttpClient buildHttpClient(Builder builder) throws ConfigurationException { + OkHttpClient httpClient = builder.baseHttpClient.clone(); + + try { + if (builder.trustManagers != null) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(builder.keyManagers, builder.trustManagers, null); + httpClient.setSslSocketFactory(sslContext.getSocketFactory()); + } + } catch (GeneralSecurityException ex) { + throw new ConfigurationException("Unable to configure TLS", ex); + } + if (builder.readTimeoutUnit != null) { + httpClient.setReadTimeout(builder.readTimeout, builder.readTimeoutUnit); + } + if (builder.writeTimeoutUnit != null) { + httpClient.setWriteTimeout(builder.writeTimeout, builder.writeTimeoutUnit); + } + if (builder.connectTimeoutUnit != null) { + httpClient.setConnectTimeout(builder.connectTimeout, builder.connectTimeoutUnit); + } + if (builder.pool != null) { + httpClient.setConnectionPool(builder.pool); + } + if (builder.proxy != null) { + httpClient.setProxy(builder.proxy); + } + if (builder.cp != null) { + httpClient.setCertificatePinner(builder.cp); + } + + return httpClient; + } + + private static final Random randomGenerator = new Random(); + private static final int MAX_RETRIES = 10; + private static final int RETRY_BASE_DELAY_MILLIS = 40; + + // the max amount of time cored leader election could take + private static final int RETRY_MAX_DELAY_MILLIS = 15000; + + private static int retryDelayMillis(int retryAttempt) { + // Calculate the max delay as base * 2 ^ (retryAttempt - 1). + int max = RETRY_BASE_DELAY_MILLIS * (1 << (retryAttempt - 1)); + max = Math.min(max, RETRY_MAX_DELAY_MILLIS); + + // To incorporate jitter, use a pseudo random delay between [max/2, max] millis. + return randomGenerator.nextInt(max / 2) + max / 2 + 1; + } + + private static final int[] RETRIABLE_STATUS_CODES = { + 408, // Request Timeout + 429, // Too Many Requests + 500, // Internal Server Error + 502, // Bad Gateway + 503, // Service Unavailable + 504, // Gateway Timeout + 509, // Bandwidth Limit Exceeded + }; + + private static boolean isRetriableStatusCode(int statusCode) { + for (int i = 0; i < RETRIABLE_STATUS_CODES.length; i++) { + if (RETRIABLE_STATUS_CODES[i] == statusCode) { + return true; + } + } + return false; + } + + private Response checkError(Response response) throws BytomException { + /* + String rid = response.headers().get("Bytom-Request-ID"); + if (rid == null || rid.length() == 0) { + // Header field Bytom-Request-ID is set by the backend + // API server. If this field is set, then we can expect + // the body to be well-formed JSON. If it's not set, + // then we are probably talking to a gateway or proxy. + throw new ConnectivityException(response); + } */ + + if ((response.code() / 100) != 2) { + try { + APIException err = + Utils.serializer.fromJson(response.body().charStream(), APIException.class); + if (err.code != null) { + //err.requestId = rid; + err.statusCode = response.code(); + throw err; + } + } catch (IOException ex) { + //throw new JSONException("Unable to read body. " + ex.getMessage(), rid); + throw new JSONException("Unable to read body. "); + } + } + return response; + } + + private String buildCredentials() { + String user = ""; + String pass = ""; + if (hasAccessToken()) { + String[] parts = accessToken.split(":"); + if (parts.length >= 1) { + user = parts[0]; + } + if (parts.length >= 2) { + pass = parts[1]; + } + } + return Credentials.basic(user, pass); + } + + /** + * Overrides {@link Object#hashCode()} + * + * @return the hash code + */ + @Override + public int hashCode() { + int code = this.url.hashCode(); + if (this.hasAccessToken()) { + code = code * 31 + this.accessToken.hashCode(); + } + return code; + } + + /** + * Overrides {@link Object#equals(Object)} + * + * @param o the object to compare + * @return a boolean specifying equality + */ + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (!(o instanceof Client)) return false; + + Client other = (Client) o; + if (!this.url.equalsIgnoreCase(other.url)) { + return false; + } + return Objects.equals(this.accessToken, other.accessToken); + } + + /** + * A builder class for creating client objects + */ + public static class Builder { + + private String url; + + private OkHttpClient baseHttpClient; + private String accessToken; + private CertificatePinner cp; + private KeyManager[] keyManagers; + private TrustManager[] trustManagers; + private long connectTimeout; + private TimeUnit connectTimeoutUnit; + private long readTimeout; + private TimeUnit readTimeoutUnit; + private long writeTimeout; + private TimeUnit writeTimeoutUnit; + private Proxy proxy; + private ConnectionPool pool; + + public Builder() { + this.baseHttpClient = new OkHttpClient(); + this.baseHttpClient.setFollowRedirects(false); + this.setDefaults(); + } + + public Builder(Client client) { + this.baseHttpClient = client.httpClient.clone(); + this.url = client.url; + this.accessToken = client.accessToken; + } + + private void setDefaults() { + this.setReadTimeout(30, TimeUnit.SECONDS); + this.setWriteTimeout(30, TimeUnit.SECONDS); + this.setConnectTimeout(30, TimeUnit.SECONDS); + this.setConnectionPool(50, 2, TimeUnit.MINUTES); + } + + public Builder setUrl(String url) { + this.url = url; + return this; + } + + /** + * Sets the access token for the client + * + * @param accessToken The access token for the Chain Core or HSM + */ + public Builder setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + + + + + /** + * Trusts the given CA certs, and no others. Use this if you are running + * your own CA, or are using a self-signed server certificate. + * + * @param is input stream of the certificates to trust, in PEM format. + */ + public Builder setTrustedCerts(InputStream is) throws ConfigurationException { + try { + // Extract certs from PEM-encoded input. + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Collection certificates = + certificateFactory.generateCertificates(is); + if (certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + + // Create a new key store and input the cert. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = Integer.toString(index++); + keyStore.setCertificateEntry(certificateAlias, certificate); + } + + // Use key store to build an X509 trust manager. + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException( + "Unexpected default trust managers:" + Arrays.toString(trustManagers)); + } + + this.trustManagers = trustManagers; + return this; + } catch (GeneralSecurityException | IOException ex) { + throw new ConfigurationException("Unable to configure trusted CA certs", ex); + } + } + + /** + * Trusts the given CA certs, and no others. Use this if you are running + * your own CA, or are using a self-signed server certificate. + * + * @param path The path of a file containing certificates to trust, in PEM format. + */ + public Builder setTrustedCerts(String path) throws ConfigurationException { + try (InputStream is = new FileInputStream(path)) { + return setTrustedCerts(is); + } catch (IOException ex) { + throw new ConfigurationException("Unable to configure trusted CA certs", ex); + } + } + + /** + * Sets the certificate pinner for the client + * + * @param provider certificate provider + * @param subjPubKeyInfoHash public key hash + */ + public Builder pinCertificate(String provider, String subjPubKeyInfoHash) { + this.cp = new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); + return this; + } + + /** + * Sets the connect timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setConnectTimeout(long timeout, TimeUnit unit) { + this.connectTimeout = timeout; + this.connectTimeoutUnit = unit; + return this; + } + + /** + * Sets the read timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setReadTimeout(long timeout, TimeUnit unit) { + this.readTimeout = timeout; + this.readTimeoutUnit = unit; + return this; + } + + /** + * Sets the write timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setWriteTimeout(long timeout, TimeUnit unit) { + this.writeTimeout = timeout; + this.writeTimeoutUnit = unit; + return this; + } + + /** + * Sets the proxy for the client + * + * @param proxy + */ + public Builder setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + /** + * Sets the connection pool for the client + * + * @param maxIdle the maximum number of idle http connections in the pool + * @param timeout the number of time units until an idle http connection in the pool is closed + * @param unit the unit of time + */ + public Builder setConnectionPool(int maxIdle, long timeout, TimeUnit unit) { + this.pool = new ConnectionPool(maxIdle, unit.toMillis(timeout)); + return this; + } + + /** + * Builds a client with all of the provided parameters. + */ + public Client build() throws ConfigurationException { + return new Client(this); + } + } +} diff --git a/tx-signer/src/main/resources/config.properties b/tx-signer/src/main/resources/config.properties new file mode 100644 index 0000000..e9252be --- /dev/null +++ b/tx-signer/src/main/resources/config.properties @@ -0,0 +1,8 @@ +bytom.api.url= +client.access.token= + +# bytom.api.url=http://10.100.7.47:9888/ +# client.access.token=wt:3d17dbb953cedd53353bf3f342bb2929e9505105ffeb21670e6bd00abeef3772 + +#bytom.api.url=http://127.0.0.1:9888/ +#client.access.token=sheng:49d1623f5991c62a5094e761477ddd2838dceb49c22fbf84b492a54f1df88123 diff --git a/tx-signer/src/main/resources/log4j.properties b/tx-signer/src/main/resources/log4j.properties new file mode 100644 index 0000000..b64ec56 --- /dev/null +++ b/tx-signer/src/main/resources/log4j.properties @@ -0,0 +1,20 @@ + log4j.rootLogger=debug, stdout, R + log4j.appender.stdout=org.apache.log4j.ConsoleAppender + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + + log4j.logger.org.apache.commons.httpclient=info + log4j.logger.httpclient.wire.content=info + log4j.logger.httpclient.wire.header=info + + # Pattern to output the caller's file name and line number. + log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n + + log4j.appender.R=org.apache.log4j.RollingFileAppender + log4j.appender.R.File=bytom.log + log4j.appender.R.MaxFileSize= 1000KB + + # Keep one backup file + log4j.appender.R.MaxBackupIndex=1 + + log4j.appender.R.layout=org.apache.log4j.PatternLayout + log4j.appender.R.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/.DS_Store b/tx-signer/src/test/java/io/bytom/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..941feefd24bf5da2c753d1819aa501371792b4ad GIT binary patch literal 6148 zcmeH~O=`nH427SXECStlndNMHfZkvT$q90S1_~h%(o$&GbM!v_Y~prZOnCz7jWiav zzlFyFV7t%PXJ7)bp}XSA!_18N3Mahd@^$>UUEgkJR=f&4M9hqp3A6p$mWY4|h=2%) zfCwyzK%U~*JTB;&^e7@A0?Q!a--kwb?WH3%J{=4(0#Mg1hjAUV1hsjA+Dk_&D>SR= z!K&3_4DoujQ(IlvOGj$9!)o}jy0iHdL$hp$H6}FcAqpZO0y6@u%qKtp5A@&W|5=Mt z5fFiYM!?ql<9^4Nsv{d9s=gj{YFy56_Y=UxkK!#ojQhk literal 0 HcmV?d00001 diff --git a/tx-signer/src/test/java/io/bytom/api/RawTransactionTest.java b/tx-signer/src/test/java/io/bytom/api/RawTransactionTest.java new file mode 100644 index 0000000..e78f99e --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/api/RawTransactionTest.java @@ -0,0 +1,122 @@ +package io.bytom.api; + +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +public class RawTransactionTest { + + @Test + public void testHashFn() throws BytomException { + String raw_tx = "070100010161015fc8215913a270d3d953ef431626b19a89adf38e2486bb235da732f0afed515299ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8099c4d59901000116001456ac170c7965eeac1cc34928c9f464e3f88c17d8630240b1e99a3590d7db80126b273088937a87ba1e8d2f91021a2fd2c36579f7713926e8c7b46c047a43933b008ff16ecc2eb8ee888b4ca1fe3fdf082824e0b3899b02202fb851c6ed665fcd9ebc259da1461a1e284ac3b27f5e86c84164aa518648222602013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80bbd0ec980101160014c3d320e1dc4fe787e9f13c1464e3ea5aae96a58f00013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8084af5f01160014bb93cdb4eca74b068321eeb84ac5d33686281b6500"; + String tx_id = "4c97d7412b04d49acc33762fc748cd0780d8b44086c229c1a6d0f2adfaaac2db"; + String input_id = "9963265eb601df48501cc240e1480780e9ed6e0c8f18fd7dd57954068c5dfd02"; + Client client = Client.generateClient(); + RawTransaction decodedTx = RawTransaction.decode(client, raw_tx); + + byte[] signedHash = decodedTx.hashFn(Hex.decode(input_id), Hex.decode(tx_id)); + System.out.println("signedHash: "+Hex.toHexString(signedHash)); + // expect: 8d2bb534c819464472a94b41cea788e97a2c9dae09a6cb3b7024a44ce5a27835 + // 8d2bb534c819464472a94b41cea788e97a2c9dae09a6cb3b7024a44ce5a27835 + } + + @Test + public void testFromJson() { +// String raw_tx = "0701dfd5c8d505010161015f0434bc790dbb3746c88fd301b9839a0f7c990bb8bdc96881d17bc2fb47525ad8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d0dbc3f4020101160014f54622eeb837e39d359f7530b6fbbd7256c9e73d010002013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100"; + String rawTxJson = "{\n" + + " \"tx_id\": \"88367f8df0e3bbb0027b1133b3de36ab779e26af00fc256bde7228c9727d20ef\",\n" + + " \"version\": 1,\n" + + " \"size\": 236,\n" + + " \"time_range\": 1521625823,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"type\": \"spend\",\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 100000000000,\n" + + " \"control_program\": \"0014f54622eeb837e39d359f7530b6fbbd7256c9e73d\",\n" + + " \"address\": \"sm1q74rz9m4cxl3e6dvlw5ctd7aawftvneeaqsuq3v\",\n" + + " \"spent_output_id\": \"34d739d5020d7e92477222b652e8fbe08467f5eb03700ce2ef57752930b05ff1\",\n" + + " \"input_id\": \"4ae0a25ea92e8c2749099576a234e7dfacb643597545873549c5000ba83fdd9a\"\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"8a511581e2fb6986abc3be3bbd842434f642db7c56a1fc5c4c7adf93c750e9a4\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 99959999500,\n" + + " \"control_program\": \"00144453a011caf735428d0291d82b186e976e286fc1\",\n" + + " \"address\": \"sm1qg3f6qyw27u659rgzj8vzkxrwjahzsm7pyjen5j\"\n" + + " },\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"03b6ac529c2d1c7d422a7c063d74893e8ca2003b2b3368c27d0ede2d2f6ea3ba\",\n" + + " \"position\": 1,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 500,\n" + + " \"control_program\": \"0014613908c28df499e3aa04e033100efaa24ca8fd01\",\n" + + " \"address\": \"sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em\"\n" + + " }\n" + + " ],\n" + + " \"fee\": 40000000\n" + + " }"; + RawTransaction decodedTx = RawTransaction.fromJson(rawTxJson); + System.out.println(decodedTx.toJson()); + } + + @Test + public void testFromSuccessRespon() { + String successRespon = "{\n" + + " \"status\": \"success\",\n" + + " \"data\": {\n" + + " \"tx_id\": \"88367f8df0e3bbb0027b1133b3de36ab779e26af00fc256bde7228c9727d20ef\",\n" + + " \"version\": 1,\n" + + " \"size\": 236,\n" + + " \"time_range\": 1521625823,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"type\": \"spend\",\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 100000000000,\n" + + " \"control_program\": \"0014f54622eeb837e39d359f7530b6fbbd7256c9e73d\",\n" + + " \"address\": \"sm1q74rz9m4cxl3e6dvlw5ctd7aawftvneeaqsuq3v\",\n" + + " \"spent_output_id\": \"34d739d5020d7e92477222b652e8fbe08467f5eb03700ce2ef57752930b05ff1\",\n" + + " \"input_id\": \"4ae0a25ea92e8c2749099576a234e7dfacb643597545873549c5000ba83fdd9a\"\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"8a511581e2fb6986abc3be3bbd842434f642db7c56a1fc5c4c7adf93c750e9a4\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 99959999500,\n" + + " \"control_program\": \"00144453a011caf735428d0291d82b186e976e286fc1\",\n" + + " \"address\": \"sm1qg3f6qyw27u659rgzj8vzkxrwjahzsm7pyjen5j\"\n" + + " },\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"03b6ac529c2d1c7d422a7c063d74893e8ca2003b2b3368c27d0ede2d2f6ea3ba\",\n" + + " \"position\": 1,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 500,\n" + + " \"control_program\": \"0014613908c28df499e3aa04e033100efaa24ca8fd01\",\n" + + " \"address\": \"sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em\"\n" + + " }\n" + + " ],\n" + + " \"fee\": 40000000\n" + + " }\n" + + "}"; + RawTransaction decodeTx = RawTransaction.fromSuccessRespon(successRespon); + System.out.println(decodeTx.toJson()); + } + +} \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java new file mode 100644 index 0000000..04f65bd --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java @@ -0,0 +1,227 @@ +package io.bytom.api; + +import io.bytom.common.Constants; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; + +import org.bouncycastle.util.encoders.Hex; +/** + * Created by liqiang on 2018/10/24. + */ +public class SignTransactionTest { + + //以下为测试用的区块上的交易utxo,即output中第二个输出 + //新交易接收地址为bm1qdpc5sejdkm22uv23jwd8pg6lyqz2trz4trgxh0,需要找零 + /*{ + "id": "3b36453f7dc03b13523d6431afd7e544f60339daed52ba8fca7ebf88cd5e5939", + "version": 1, + "size": 330, + "time_range": 0, + "inputs": [ + { + "type": "spend", + "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "asset_definition": {}, + "amount": 482000000, + "control_program": "00148da6ccbc216f9019cf80d23fd2083c80e29fcba2", + "address": "bm1q3knve0ppd7gpnnuq6glayzpusr3fljazzcq0eh", + "spent_output_id": "d11967ce15741217c650bc0b9dd7a390aaedd8ea5c645266920a7d19d8be681a", + "input_id": "caae7c37f6cecce6854e6488cc389379e312acd2f7495337633501fc7f72b5f3" + } + ], + "outputs": [ + { + "type": "control", + "id": "3110bc8e7d713c17fb3dc3c9deadbfc419a25c25252c8e613d1fa54cc4d05dbd", + "position": 0, + "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "asset_definition": {}, + "amount": 281500000, + "control_program": "00145d6ba5bf0cfdb2487abd594429cd04c2ba566f9f", + "address": "bm1qt446t0cvlkeys74at9zznngyc2a9vmulcr2xy6" + }, + { + "type": "control", + "id": "db5afebb5b33aec2c46fcebb20b98fffa8c065a101f4c1789fe5491b34dc1b8f", + "position": 1, + "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "asset_definition": {}, + "amount": 200000000, + "control_program": "00140d074bc86bd388a45f1c8911a41b8f0705d9058b", + "address": "bm1qp5r5hjrt6wy2ghcu3yg6gxu0quzajpvtsm2gnc" + } + ], + "status_fail": false, + "mux_id": "0e97230a7347967764fd77c8cfa96b38ec6ff08465300a01900c645dfb694f24" + }*/ + + @Test + public void testSerializeRawTransaction() { + //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 + String txJson = "{\n" + + " \"tx_id\": \"0434bc790dbb3746c88fd301b9839a0f7c990bb8bdc96881d17bc2fb47525ad8\",\n" + + " \"version\": 1,\n" + + " \"size\": 236,\n" + + " \"time_range\": 1521625823,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 200000000,\n" + + " \"sourceId\": \"0e97230a7347967764fd77c8cfa96b38ec6ff08465300a01900c645dfb694f24\",\n" + + " \"sourcePosition\": 1,\n" + + " \"control_program\": \"00140d074bc86bd388a45f1c8911a41b8f0705d9058b\",\n" + + " \"address\": \"bm1qp5r5hjrt6wy2ghcu3yg6gxu0quzajpvtsm2gnc\",\n" + + " \"spent_output_id\": \"\",\n" + + " \"input_id\": \"3b36453f7dc03b13523d6431afd7e544f60339daed52ba8fca7ebf88cd5e5939\",\n" + + " \"witness_component\": \n" + + " {\n" + + " \"signatures\": []\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"id\": \"8a511581e2fb6986abc3be3bbd842434f642db7c56a1fc5c4c7adf93c750e9a4\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 10000000,\n" + + " \"control_program\": \"0014687148664db6d4ae3151939a70a35f2004a58c55\",\n" + + " \"address\": \"bm1qdpc5sejdkm22uv23jwd8pg6lyqz2trz4trgxh0\"\n" + + " },\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"03b6ac529c2d1c7d422a7c063d74893e8ca2003b2b3368c27d0ede2d2f6ea3ba\",\n" + + " \"position\": 1,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 185000000,\n" + + " \"control_program\": \"00140d074bc86bd388a45f1c8911a41b8f0705d9058b\",\n" + + " \"address\": \"bm1qp5r5hjrt6wy2ghcu3yg6gxu0quzajpvtsm2gnc\"\n" + + " }\n" + + " ]\n" + + " }"; + //0701dfd5c8d50501015f015d0e97230a7347967764fd77c8cfa96b38ec6ff08465300a01900c645dfb694f24ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8084af5f01011600140d074bc86bd388a45f1c8911a41b8f0705d9058b8301024038c7df4301e070dc888b49309fa9b20266cde899069b099fe0bb562b93518e7876ea0f2676e488dd0a4946802e0ad8447521771d7163449ed222048a1443e801406488f52223be8da187f13348dadff6e829efb807540007eeaa51feefd6cc8be238eec83aff903cfd9de7a712debb5a81c96d2cb8937a4c0384c12dbd17b6e20802013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80ade20401160014687148664db6d4ae3151939a70a35f2004a58c5500013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0c09b58011600140d074bc86bd388a45f1c8911a41b8f0705d9058b00 + + //String result = "07 01 dfd5c8d505 01 01 5f 01 5d 0e97230a7347967764fd77c8cfa96b38ec6ff08465300a01900c645dfb694f24 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 8084af5f 01 01 16 00140d074bc86bd388a45f1c8911a41b8f0705d9058b + // serflag version timeTrange input.len assertVerison len type len sourceID assetId amount sourcePos vmVersion spendLen + + // 8301 02 40 38c7df4301e070dc888b49309fa9b20266cde899069b099fe0bb562b93518e7876ea0f2676e488dd0a4946802e0ad8447521771d7163449ed222048a1443e801 40 6488f52223be8da187f13348dadff6e829efb807540007eeaa51feefd6cc8be238eec83aff903cfd9de7a712debb5a81c96d2cb8937a4c0384c12dbd17b6e208 + // witnessLen argulen argu1Len argu1data argu2Len argu2Data + + // 02 01 3c ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 80ade20401160014687148664db6d4ae3151939a70a35f2004a58c5500 01 3c ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff c0c09b58011600140d074bc86bd388a45f1c8911a41b8f0705d9058b00 + // outputlen version serialen assetid + + SignTransaction tx = SignTransaction.fromJson(txJson); + SignTransactionImpl signImpl = new SignTransactionImpl(); + + //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 + signImpl.buildData(tx); + + //签名得到signatures + BigInteger[] keys = new BigInteger[2]; + BigInteger key1 = new BigInteger("58627734430160897710546100937464200251109455274527146106473181212575120553961");//子私钥 + BigInteger key2 = new BigInteger("40205913350867552217212167676397244457827512592372060624640880098442502612286");//子私钥 + keys[0] = key1; + keys[1] = key2; + signImpl.generateSignatures(tx,keys); + + //开始序列化 + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + stream.write(7); + // version + if (null != tx.version) + signImpl.writeVarint(tx.version, stream); + if (null != tx.timeRange) + signImpl.writeVarint(tx.timeRange, stream); + //inputs + if (null != tx.inputs && tx.inputs.size() > 0) { + signImpl.writeVarint(tx.inputs.size(), stream); + for (SignTransaction.AnnotatedInput input:tx.inputs) { + //assertVersion + signImpl.writeVarint(tx.version, stream); //AssetVersion是否默认为1 + + //inputCommitment + ByteArrayOutputStream inputCommitStream = new ByteArrayOutputStream(); + //spend type flag + signImpl.writeVarint(Constants.INPUT_TYPE_SPEND, inputCommitStream); + //spendCommitment + ByteArrayOutputStream spendCommitSteam = new ByteArrayOutputStream(); + spendCommitSteam.write(Hex.decode(input.sourceId)); //计算muxID + spendCommitSteam.write(Hex.decode(input.assetId)); + signImpl.writeVarint(input.amount, spendCommitSteam); + //sourcePosition + signImpl.writeVarint(input.sourcePosition, spendCommitSteam); //db中获取position + //vmVersion + signImpl.writeVarint(1, spendCommitSteam); //db中获取vm_version + //controlProgram + signImpl.writeVarStr(Hex.decode(input.controlProgram), spendCommitSteam); + + byte[] dataSpendCommit = spendCommitSteam.toByteArray(); + signImpl.writeVarint(dataSpendCommit.length, inputCommitStream); + inputCommitStream.write(dataSpendCommit); + byte[] dataInputCommit = inputCommitStream.toByteArray(); + //inputCommit的length + signImpl.writeVarint(dataInputCommit.length, stream); + stream.write(dataInputCommit); + + + //inputWitness + ByteArrayOutputStream witnessStream = new ByteArrayOutputStream(); + //arguments + int lenSigs = input.witnessComponent.signatures.length; + //arguments的length: 应是qorum和sigs + signImpl.writeVarint(lenSigs, witnessStream); + for (int i =0; i 0) { + signImpl.writeVarint(tx.outputs.size(), stream); + for (SignTransaction.AnnotatedOutput output:tx.outputs) { + //assertVersion + signImpl.writeVarint(tx.version, stream); //AssetVersion是否默认为1 + //outputCommit + ByteArrayOutputStream outputCommitSteam = new ByteArrayOutputStream(); + //assetId + outputCommitSteam.write(Hex.decode(output.assetId)); + //amount + signImpl.writeVarint(output.amount, outputCommitSteam); + //vmVersion + signImpl.writeVarint(1, outputCommitSteam); //db中获取vm_version + //controlProgram + signImpl.writeVarStr(Hex.decode(output.controlProgram), outputCommitSteam); + + byte[] dataOutputCommit = outputCommitSteam.toByteArray(); + //outputCommit的length + signImpl.writeVarint(dataOutputCommit.length, stream); + stream.write(dataOutputCommit); + + //outputWitness + signImpl.writeVarint(0, stream); + } + } + //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 + //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 + byte[] data = stream.toByteArray(); + System.out.println(Hex.toHexString(data)); + + System.out.println("testSerializeSignTransaction success."); + } catch (IOException e) { + System.out.println("testSerializeSignTransaction failed."); + } + } +} diff --git a/tx-signer/src/test/java/io/bytom/api/SignaturesImplTest.java b/tx-signer/src/test/java/io/bytom/api/SignaturesImplTest.java new file mode 100644 index 0000000..c9ffd53 --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/api/SignaturesImplTest.java @@ -0,0 +1,213 @@ +package io.bytom.api; + +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; +import org.junit.Test; + +public class SignaturesImplTest { + + public static Logger logger = Logger.getLogger(SignaturesImplTest.class); + + @Test + // 使用build transaction 得到的 template json 来构造 Template 对象参数 + public void testSignUseTemplateJson() throws BytomException { + String[] privates = new String[1]; + privates[0] = "10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"; + + String raw_tx = "0701dfd5c8d505010161015f0434bc790dbb3746c88fd301b9839a0f7c990bb8bdc96881d17bc2fb47525ad8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d0dbc3f4020101160014f54622eeb837e39d359f7530b6fbbd7256c9e73d010002013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100"; + Client client = Client.generateClient(); + RawTransaction rawTransaction = RawTransaction.decode(client, raw_tx); + + String json = "{\n" + + " \"raw_transaction\": \"0701dfd5c8d505010161015f0434bc790dbb3746c88fd301b9839a0f7c990bb8bdc96881d17bc2fb47525ad8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d0dbc3f4020101160014f54622eeb837e39d359f7530b6fbbd7256c9e73d010002013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100\",\n" + + " \"signing_instructions\": [\n" + + " {\n" + + " \"position\": 0,\n" + + " \"witness_components\": [\n" + + " {\n" + + " \"type\": \"raw_tx_signature\",\n" + + " \"quorum\": 1,\n" + + " \"keys\": [\n" + + " {\n" + + " \"xpub\": \"d9c7b41f030a398dada343096040c675be48278046623849977cb0fd01d395a51c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b\",\n" + + " \"derivation_path\": [\n" + + " \"010400000000000000\",\n" + + " \"0100000000000000\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"signatures\": null\n" + + " },\n" + + " {\n" + + " \"type\": \"data\",\n" + + " \"value\": \"5024b9d7cdfe9b3ece98bc06111e06dd79d425411614bfbb473d07ca44795612\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"allow_additional_actions\": false\n" + + " }"; + Template template = Template.fromJson(json); + Signatures signatures = new SignaturesImpl(); + Template result = signatures.generateSignatures(privates, template, rawTransaction); + System.out.println(result.toJson()); + // use result's raw_transaction call sign transaction api to build another data but not need password or private key. + } + + @Test + // 使用 SDK 来构造 Template 对象参数, 单签 + public void testSignSingleKey() throws BytomException { + Client client = Client.generateClient(); + + String asset_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String address = "sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em"; + // build transaction obtain a Template object + Template template = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G0NLBNU00A02") + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G0NLBNU00A02") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(address) + .setAssetId(asset_id) + .setAmount(30000000) + ).build(client); + logger.info("template: " + template.toJson()); + // use Template object's raw_transaction id to decode raw_transaction obtain a RawTransaction object + RawTransaction decodedTx = RawTransaction.decode(client, template.rawTransaction); + logger.info("decodeTx: " + decodedTx.toJson()); + // need a private key array + String[] privateKeys = new String[]{"10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"}; + logger.info("private key:" + privateKeys[0]); + // call offline sign method to obtain a basic offline signed template + Signatures signatures = new SignaturesImpl(); + Template basicSigned = signatures.generateSignatures(privateKeys, template, decodedTx); + logger.info("basic signed raw: " + basicSigned.toJson()); + // call sign transaction api to calculate whole raw_transaction id + // sign password is None or another random String + Template result = new Transaction.SignerBuilder().sign(client, + basicSigned, ""); + logger.info("result raw_transaction: " + result.toJson()); + // success to submit transaction + } + + @Test + // 使用 SDK 来构造 Template 对象参数, 多签 + public void testSignMultiKeys() throws BytomException { + Client client = Client.generateClient(); + + String asset_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String address = "sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em"; + // build transaction obtain a Template object + Template template = new Transaction.Builder() + .setTtl(10) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(address) + .setAssetId(asset_id) + .setAmount(30000000) + ).build(client); + logger.info("template: " + template.toJson()); + // use Template object's raw_transaction id to decode raw_transaction obtain a RawTransaction object + RawTransaction decodedTx = RawTransaction.decode(client, template.rawTransaction); + logger.info("decodeTx: " + decodedTx.toJson()); + // need a private key array + String[] privateKeys = new String[]{"08bdbd6c22856c5747c930f64d0e5d58ded17c4473910c6c0c3f94e485833a436247976253c8e29e961041ad8dfad9309744255364323163837cbef2483b4f67", + "40c821f736f60805ad59b1fea158762fa6355e258601dfb49dda6f672092ae5adf072d5cab2ceaaa0d68dd3fe7fa04869d95afed8c20069f446a338576901e1b"}; + logger.info("private key 1:" + privateKeys[0]); + logger.info("private key 2:" + privateKeys[1]); + // call offline sign method to obtain a basic offline signed template + Signatures signatures = new SignaturesImpl(); + Template basicSigned = signatures.generateSignatures(privateKeys, template, decodedTx); + logger.info("basic signed raw: " + basicSigned.toJson()); + // call sign transaction api to calculate whole raw_transaction id + // sign password is None or another random String + Template result = new Transaction.SignerBuilder().sign(client, + basicSigned, ""); + logger.info("result raw_transaction: " + result.toJson()); + // success to submit transaction + } + @Test + // 使用 SDK 来构造 Template 对象参数, 多签, 多输入 + public void testSignMultiKeysMultiInputs() throws BytomException { + Client client = Client.generateClient(); + + String asset_id = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + String address = "sm1qvyus3s5d7jv782syuqe3qrh65fx23lgpzf33em"; + // build transaction obtain a Template object + Template template = new Transaction.Builder() + .setTtl(10) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1RPP6OG0A06") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1Q6V1P00A02") + .setAssetId(asset_id) + .setAmount(40000000) + ) + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId("0G1Q6V1P00A02") + .setAssetId(asset_id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(address) + .setAssetId(asset_id) + .setAmount(60000000) + ).build(client); + logger.info("template: " + template.toJson()); + // use Template object's raw_transaction id to decode raw_transaction obtain a RawTransaction object + RawTransaction decodedTx = RawTransaction.decode(client, template.rawTransaction); + logger.info("decodeTx: " + decodedTx.toJson()); + // need a private key array + String[] privateKeys = new String[]{"08bdbd6c22856c5747c930f64d0e5d58ded17c4473910c6c0c3f94e485833a436247976253c8e29e961041ad8dfad9309744255364323163837cbef2483b4f67", + "40c821f736f60805ad59b1fea158762fa6355e258601dfb49dda6f672092ae5adf072d5cab2ceaaa0d68dd3fe7fa04869d95afed8c20069f446a338576901e1b", + "08bdbd6c22856c5747c930f64d0e5d58ded17c4473910c6c0c3f94e485833a436247976253c8e29e961041ad8dfad9309744255364323163837cbef2483b4f67"}; + logger.info("private key 1:" + privateKeys[0]); + logger.info("private key 2:" + privateKeys[1]); + // call offline sign method to obtain a basic offline signed template + Signatures signatures = new SignaturesImpl(); + Template basicSigned = signatures.generateSignatures(privateKeys, template, decodedTx); + logger.info("basic signed raw: " + basicSigned.toJson()); + // call sign transaction api to calculate whole raw_transaction id + // sign password is None or another random String + Template result = new Transaction.SignerBuilder().sign(client, + basicSigned, ""); + logger.info("result raw_transaction: " + result.toJson()); + // success to submit transaction + } +} \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/api/SignerTest.java b/tx-signer/src/test/java/io/bytom/api/SignerTest.java new file mode 100644 index 0000000..190967c --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/api/SignerTest.java @@ -0,0 +1,24 @@ +package io.bytom.api; + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public class SignerTest { + + @Test + public void testEd25519InnerSign() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + String rootXprv = "10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"; + String childXprv = "e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954"; + String expandedXprv = "e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3"; + String hashedMessage = "02eda3cd8d1b0efaf7382af6ea53a429ed3ed6042998d2b4a382575248ebc922"; + byte[] sig = Signer.Ed25519InnerSign(Hex.decode(expandedXprv), Hex.decode(hashedMessage)); + System.out.println("sig:" + Hex.toHexString(sig)); + //expected: 38b11090e8dd5372018acc24ea4db2c3d82cf01ed5c69a0fae95bff2379c1630f8c8f96937b22685142b4181e6ef5072e7945c101eb81814a20d90cb1d1f0c08 + // 38b11090e8dd5372018acc24ea4db2c3d82cf01ed5c69a0fae95bff2379c1630f8c8f96937b22685142b4181e6ef5072e7945c101eb81814a20d90cb1d1f0c08 + } + +} \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/api/TemplateTest.java b/tx-signer/src/test/java/io/bytom/api/TemplateTest.java new file mode 100644 index 0000000..6e333cb --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/api/TemplateTest.java @@ -0,0 +1,43 @@ +package io.bytom.api; + +import org.junit.Test; + + +public class TemplateTest { + + @Test + public void testFromJson() { + String json = "{\n" + + " \"raw_transaction\": \"0701dfd5c8d505010161015f0434bc790dbb3746c88fd301b9839a0f7c990bb8bdc96881d17bc2fb47525ad8ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80d0dbc3f4020101160014f54622eeb837e39d359f7530b6fbbd7256c9e73d010002013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100\",\n" + + " \"signing_instructions\": [\n" + + " {\n" + + " \"position\": 0,\n" + + " \"witness_components\": [\n" + + " {\n" + + " \"type\": \"raw_tx_signature\",\n" + + " \"quorum\": 1,\n" + + " \"keys\": [\n" + + " {\n" + + " \"xpub\": \"d9c7b41f030a398dada343096040c675be48278046623849977cb0fd01d395a51c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b\",\n" + + " \"derivation_path\": [\n" + + " \"010400000000000000\",\n" + + " \"0100000000000000\"\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"signatures\": null\n" + + " },\n" + + " {\n" + + " \"type\": \"data\",\n" + + " \"value\": \"5024b9d7cdfe9b3ece98bc06111e06dd79d425411614bfbb473d07ca44795612\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"allow_additional_actions\": false\n" + + " }"; + Template template = Template.fromJson(json); + System.out.println(template.toJson()); + } + +} \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/common/DeriveXpubTest.java b/tx-signer/src/test/java/io/bytom/common/DeriveXpubTest.java new file mode 100644 index 0000000..95a5d5f --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/common/DeriveXpubTest.java @@ -0,0 +1,17 @@ +package io.bytom.common; + + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +public class DeriveXpubTest { + + @Test + public void testDeriveXpub() { + String hxprv = "10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"; + byte[] xpub = DeriveXpub.deriveXpub(Hex.decode(hxprv)); + System.out.println("hxpub: "+Hex.toHexString(xpub)); + //expected: d9c7b41f030a398dada343096040c675be48278046623849977cb0fd01d395a51c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b + // d9c7b41f030a398dada343096040c675be48278046623849977cb0fd01d395a51c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b + } +} \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java b/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java new file mode 100644 index 0000000..16f88da --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java @@ -0,0 +1,21 @@ +package io.bytom.common; + + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +public class ExpandedPrivateKeyTest { + + @Test + public void testExpandedKey() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + String childXprv = "e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954"; + byte[] z = ExpandedPrivateKey.ExpandedPrivateKey(Hex.decode(childXprv)); + System.out.println(Hex.toHexString(z)); + //expect: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3 + // e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3 + } +} \ No newline at end of file diff --git a/tx-signer/src/test/java/io/bytom/common/NonHardenedChildTest.java b/tx-signer/src/test/java/io/bytom/common/NonHardenedChildTest.java new file mode 100644 index 0000000..fe5dbc5 --- /dev/null +++ b/tx-signer/src/test/java/io/bytom/common/NonHardenedChildTest.java @@ -0,0 +1,43 @@ +package io.bytom.common; + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Test; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + + +public class NonHardenedChildTest { + + @Test + public void testNHChild() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + String hxprv = "10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"; + byte[] xprv = Hex.decode(hxprv); + //expected: d9c7b41f030a398dada343096040c675be48278046623849977cb0fd01d395a51c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b + String[] hpaths = {"010400000000000000", "0100000000000000"}; + byte[][] paths = new byte[][]{ + Hex.decode(hpaths[0]), + Hex.decode(hpaths[1]) + }; + byte[] res = xprv; + for (int i = 0; i < hpaths.length; i++) { + byte[] xpub = DeriveXpub.deriveXpub(res); +// System.out.println("xpub: "+Hex.toHexString(xpub)); + res = NonHardenedChild.NHchild(paths[i], res, xpub); + } + System.out.println("res: "+Hex.toHexString(res)); + //expected: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954 + // e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954 + } + + @Test + public void testChild() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + String hxprv = "10fdbc41a4d3b8e5a0f50dd3905c1660e7476d4db3dbd9454fa4347500a633531c487e8174ffc0cfa76c3be6833111a9b8cd94446e37a76ee18bb21a7d6ea66b"; + String[] hpaths = {"010400000000000000", "0100000000000000"}; + byte[] childXprv = NonHardenedChild.child(Hex.decode(hxprv), hpaths); + System.out.println("childXprv: "+Hex.toHexString(childXprv)); + //expected: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954 + // e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a6335398a3720b3f96077fa187fdde48fe7dc293984b196f5e292ef8ed78fdbd8ed954 + } +} \ No newline at end of file From bca093b20c50f156a8ace44017a796939ff0aa28 Mon Sep 17 00:00:00 2001 From: liqiang Date: Thu, 25 Oct 2018 17:09:00 +0800 Subject: [PATCH 05/13] fix pom.xml --- bytom-sdk/pom.xml | 2 +- pom.xml | 2 +- tx-signer/pom.xml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bytom-sdk/pom.xml b/bytom-sdk/pom.xml index ad365fc..72536c5 100644 --- a/bytom-sdk/pom.xml +++ b/bytom-sdk/pom.xml @@ -6,7 +6,7 @@ io.bytom - bytom-sdk-all + bytom-java-sdk 1.0.0 diff --git a/pom.xml b/pom.xml index 26fa96a..3586f95 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.bytom - bytom-sdk-all + bytom-java-sdk 1.0.0 pom diff --git a/tx-signer/pom.xml b/tx-signer/pom.xml index a4312a6..0413a5d 100644 --- a/tx-signer/pom.xml +++ b/tx-signer/pom.xml @@ -6,7 +6,7 @@ io.bytom - bytom-sdk-all + bytom-java-sdk 1.0.0 @@ -121,4 +121,5 @@ + jar From 052d656c0c1863f8f556521f986241c13cf5193f Mon Sep 17 00:00:00 2001 From: liqiang Date: Mon, 29 Oct 2018 16:31:46 +0800 Subject: [PATCH 06/13] =?UTF-8?q?=E5=B0=81=E8=A3=85=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tx-signer/.DS_Store | Bin 0 -> 6148 bytes .../io/bytom/api/SignTransactionImpl.java | 107 ++++++++++++++++++ .../io/bytom/api/SignTransactionTest.java | 99 +--------------- 3 files changed, 109 insertions(+), 97 deletions(-) create mode 100644 tx-signer/.DS_Store diff --git a/tx-signer/.DS_Store b/tx-signer/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5172429f264de2441865cb4700216d4256da9242 GIT binary patch literal 6148 zcmeH~J!%6%427R!7lt%jx}3%b$PET#pTHLgIFQEJ;E>dF^gR7ES*H$5cmnB-G%I%Z zD|S`@Z2$T80!#olbXV*=%*>dt@PRwdU#I)^a=X5>;#J@&VrHyNnC;iLL0pQvfVyTmjO&;ssLc!1UOG})p;=82 zR;?Ceh}WZ?+UmMqI#RP8R>OzYoz15hnq@nzF`-!xQ4j$Um=RcIKKc27r2jVm&svm< zfC&6E0=7P!4tu^-ovjbA=k?dB`g+i*aXG_}p8zI)6mRKa+;6_1_R^8c3Qa!(fk8n8 H{*=HsM+*^= literal 0 HcmV?d00001 diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java index 0cac43d..5869934 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -1,5 +1,6 @@ package io.bytom.api; +import io.bytom.common.Constants; import io.bytom.common.ExpandedPrivateKey; import org.bouncycastle.jcajce.provider.digest.SHA3; import org.bouncycastle.util.encoders.Hex; @@ -16,6 +17,112 @@ */ public class SignTransactionImpl { + public String signTransaction(SignTransaction tx, BigInteger[] keys) { + + String txSign = null; + //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 + buildData(tx); + + //签名得到signatures + generateSignatures(tx,keys); + + //开始序列化 + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + stream.write(7); + // version + if (null != tx.version) + writeVarint(tx.version, stream); + if (null != tx.timeRange) + writeVarint(tx.timeRange, stream); + //inputs + if (null != tx.inputs && tx.inputs.size() > 0) { + writeVarint(tx.inputs.size(), stream); + for (SignTransaction.AnnotatedInput input:tx.inputs) { + //assertVersion + writeVarint(tx.version, stream); //AssetVersion是否默认为1 + + //inputCommitment + ByteArrayOutputStream inputCommitStream = new ByteArrayOutputStream(); + //spend type flag + writeVarint(Constants.INPUT_TYPE_SPEND, inputCommitStream); + //spendCommitment + ByteArrayOutputStream spendCommitSteam = new ByteArrayOutputStream(); + spendCommitSteam.write(Hex.decode(input.sourceId)); //计算muxID + spendCommitSteam.write(Hex.decode(input.assetId)); + writeVarint(input.amount, spendCommitSteam); + //sourcePosition + writeVarint(input.sourcePosition, spendCommitSteam); //db中获取position + //vmVersion + writeVarint(1, spendCommitSteam); //db中获取vm_version + //controlProgram + writeVarStr(Hex.decode(input.controlProgram), spendCommitSteam); + + byte[] dataSpendCommit = spendCommitSteam.toByteArray(); + writeVarint(dataSpendCommit.length, inputCommitStream); + inputCommitStream.write(dataSpendCommit); + byte[] dataInputCommit = inputCommitStream.toByteArray(); + //inputCommit的length + writeVarint(dataInputCommit.length, stream); + stream.write(dataInputCommit); + + + //inputWitness + ByteArrayOutputStream witnessStream = new ByteArrayOutputStream(); + //arguments + int lenSigs = input.witnessComponent.signatures.length; + //arguments的length: 应是qorum和sigs + writeVarint(lenSigs, witnessStream); + for (int i =0; i 0) { + writeVarint(tx.outputs.size(), stream); + for (SignTransaction.AnnotatedOutput output:tx.outputs) { + //assertVersion + writeVarint(tx.version, stream); //AssetVersion是否默认为1 + //outputCommit + ByteArrayOutputStream outputCommitSteam = new ByteArrayOutputStream(); + //assetId + outputCommitSteam.write(Hex.decode(output.assetId)); + //amount + writeVarint(output.amount, outputCommitSteam); + //vmVersion + writeVarint(1, outputCommitSteam); //db中获取vm_version + //controlProgram + writeVarStr(Hex.decode(output.controlProgram), outputCommitSteam); + + byte[] dataOutputCommit = outputCommitSteam.toByteArray(); + //outputCommit的length + writeVarint(dataOutputCommit.length, stream); + stream.write(dataOutputCommit); + + //outputWitness + writeVarint(0, stream); + } + } + //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 + //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 + byte[] data = stream.toByteArray(); + txSign = Hex.toHexString(data); + System.out.println(txSign); + + System.out.println("sign Transaction success."); + } catch (IOException e) { + System.out.println("sign Transaction failed."); + } + return txSign; + } + public byte[] hashFn(byte[] hashedInputHex, byte[] txID) { SHA3.Digest256 digest256 = new SHA3.Digest256(); diff --git a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java index 04f65bd..a424886 100644 --- a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java +++ b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java @@ -119,109 +119,14 @@ public void testSerializeRawTransaction() { SignTransaction tx = SignTransaction.fromJson(txJson); SignTransactionImpl signImpl = new SignTransactionImpl(); - //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 - signImpl.buildData(tx); - - //签名得到signatures BigInteger[] keys = new BigInteger[2]; BigInteger key1 = new BigInteger("58627734430160897710546100937464200251109455274527146106473181212575120553961");//子私钥 BigInteger key2 = new BigInteger("40205913350867552217212167676397244457827512592372060624640880098442502612286");//子私钥 keys[0] = key1; keys[1] = key2; - signImpl.generateSignatures(tx,keys); - - //开始序列化 - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - stream.write(7); - // version - if (null != tx.version) - signImpl.writeVarint(tx.version, stream); - if (null != tx.timeRange) - signImpl.writeVarint(tx.timeRange, stream); - //inputs - if (null != tx.inputs && tx.inputs.size() > 0) { - signImpl.writeVarint(tx.inputs.size(), stream); - for (SignTransaction.AnnotatedInput input:tx.inputs) { - //assertVersion - signImpl.writeVarint(tx.version, stream); //AssetVersion是否默认为1 - - //inputCommitment - ByteArrayOutputStream inputCommitStream = new ByteArrayOutputStream(); - //spend type flag - signImpl.writeVarint(Constants.INPUT_TYPE_SPEND, inputCommitStream); - //spendCommitment - ByteArrayOutputStream spendCommitSteam = new ByteArrayOutputStream(); - spendCommitSteam.write(Hex.decode(input.sourceId)); //计算muxID - spendCommitSteam.write(Hex.decode(input.assetId)); - signImpl.writeVarint(input.amount, spendCommitSteam); - //sourcePosition - signImpl.writeVarint(input.sourcePosition, spendCommitSteam); //db中获取position - //vmVersion - signImpl.writeVarint(1, spendCommitSteam); //db中获取vm_version - //controlProgram - signImpl.writeVarStr(Hex.decode(input.controlProgram), spendCommitSteam); - - byte[] dataSpendCommit = spendCommitSteam.toByteArray(); - signImpl.writeVarint(dataSpendCommit.length, inputCommitStream); - inputCommitStream.write(dataSpendCommit); - byte[] dataInputCommit = inputCommitStream.toByteArray(); - //inputCommit的length - signImpl.writeVarint(dataInputCommit.length, stream); - stream.write(dataInputCommit); + String txSign = signImpl.signTransaction(tx, keys); - //inputWitness - ByteArrayOutputStream witnessStream = new ByteArrayOutputStream(); - //arguments - int lenSigs = input.witnessComponent.signatures.length; - //arguments的length: 应是qorum和sigs - signImpl.writeVarint(lenSigs, witnessStream); - for (int i =0; i 0) { - signImpl.writeVarint(tx.outputs.size(), stream); - for (SignTransaction.AnnotatedOutput output:tx.outputs) { - //assertVersion - signImpl.writeVarint(tx.version, stream); //AssetVersion是否默认为1 - //outputCommit - ByteArrayOutputStream outputCommitSteam = new ByteArrayOutputStream(); - //assetId - outputCommitSteam.write(Hex.decode(output.assetId)); - //amount - signImpl.writeVarint(output.amount, outputCommitSteam); - //vmVersion - signImpl.writeVarint(1, outputCommitSteam); //db中获取vm_version - //controlProgram - signImpl.writeVarStr(Hex.decode(output.controlProgram), outputCommitSteam); - - byte[] dataOutputCommit = outputCommitSteam.toByteArray(); - //outputCommit的length - signImpl.writeVarint(dataOutputCommit.length, stream); - stream.write(dataOutputCommit); - - //outputWitness - signImpl.writeVarint(0, stream); - } - } - //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 - //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 - byte[] data = stream.toByteArray(); - System.out.println(Hex.toHexString(data)); - - System.out.println("testSerializeSignTransaction success."); - } catch (IOException e) { - System.out.println("testSerializeSignTransaction failed."); - } + System.out.print(txSign); } } From 3646f251daca2e41b9b6a05250d1de17bff9304b Mon Sep 17 00:00:00 2001 From: shenao78 Date: Mon, 5 Nov 2018 11:12:28 +0800 Subject: [PATCH 07/13] add trandaction bc layer --- tx-signer/pom.xml | 12 ++ .../io/bytom/api/SignTransactionImpl.java | 76 ++++++++++- .../main/java/io/bytom/types/AssetAmount.java | 15 +++ .../src/main/java/io/bytom/types/AssetID.java | 42 ++++++ .../src/main/java/io/bytom/types/Entry.java | 76 +++++++++++ .../src/main/java/io/bytom/types/Hash.java | 42 ++++++ .../src/main/java/io/bytom/types/Mux.java | 33 +++++ .../src/main/java/io/bytom/types/Output.java | 35 +++++ .../src/main/java/io/bytom/types/Program.java | 16 +++ .../src/main/java/io/bytom/types/Spend.java | 33 +++++ .../main/java/io/bytom/types/TxHeader.java | 35 +++++ .../java/io/bytom/types/ValueDestination.java | 18 +++ .../main/java/io/bytom/types/ValueSource.java | 20 +++ .../main/java/io/bytom/util/OutputUtil.java | 47 +++++++ .../io/bytom/api/SignTransactionTest.java | 127 +++++++++++++++++- 15 files changed, 616 insertions(+), 11 deletions(-) create mode 100644 tx-signer/src/main/java/io/bytom/types/AssetAmount.java create mode 100644 tx-signer/src/main/java/io/bytom/types/AssetID.java create mode 100644 tx-signer/src/main/java/io/bytom/types/Entry.java create mode 100644 tx-signer/src/main/java/io/bytom/types/Hash.java create mode 100644 tx-signer/src/main/java/io/bytom/types/Mux.java create mode 100644 tx-signer/src/main/java/io/bytom/types/Output.java create mode 100644 tx-signer/src/main/java/io/bytom/types/Program.java create mode 100644 tx-signer/src/main/java/io/bytom/types/Spend.java create mode 100644 tx-signer/src/main/java/io/bytom/types/TxHeader.java create mode 100644 tx-signer/src/main/java/io/bytom/types/ValueDestination.java create mode 100644 tx-signer/src/main/java/io/bytom/types/ValueSource.java create mode 100644 tx-signer/src/main/java/io/bytom/util/OutputUtil.java diff --git a/tx-signer/pom.xml b/tx-signer/pom.xml index 0413a5d..578ee10 100644 --- a/tx-signer/pom.xml +++ b/tx-signer/pom.xml @@ -36,6 +36,18 @@ com.google.crypto.tink tink 1.1.1 + + + com.google.protobuf + protobuf-java + + + + + + com.google.protobuf + protobuf-java + 3.6.1 diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java index 5869934..87ebb6d 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -2,15 +2,15 @@ import io.bytom.common.Constants; import io.bytom.common.ExpandedPrivateKey; +import io.bytom.types.*; import org.bouncycastle.jcajce.provider.digest.SHA3; import org.bouncycastle.util.encoders.Hex; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; -import java.util.Arrays; +import java.util.*; -import org.bouncycastle.util.encoders.Hex; /** * Created by liqiang on 2018/10/24. @@ -21,7 +21,7 @@ public String signTransaction(SignTransaction tx, BigInteger[] keys) { String txSign = null; //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 - buildData(tx); + mapTransaction(tx); //签名得到signatures generateSignatures(tx,keys); @@ -136,14 +136,76 @@ public byte[] hashFn(byte[] hashedInputHex, byte[] txID) { return digest256.digest(data); } - public SignTransaction buildData(SignTransaction signTransaction) { - //build program by address + public void mapTransaction(SignTransaction signTransaction) { + Map entryMap = new HashMap<>(); + ValueSource[] muxSources = new ValueSource[signTransaction.inputs.size()]; + List spends = new ArrayList<>(); - //build sourceId(muxId), inputId, txId + try { + for (int i = 0; i < signTransaction.inputs.size(); i++) { + SignTransaction.AnnotatedInput input = signTransaction.inputs.get(i); + Program proc = new Program(1, Hex.decode(input.controlProgram)); - return signTransaction; + AssetID assetID = new AssetID(input.assetId); + AssetAmount assetAmount = new AssetAmount(assetID, input.amount); + + Hash sourceID = new Hash(input.sourceId); + ValueSource src = new ValueSource(sourceID, assetAmount, input.sourcePosition); + Output prevout = new Output(src, proc, 0); + Hash prevoutID = addEntry(entryMap, prevout); + + input.spentOutputId = prevoutID.toString(); + + Spend spend = new Spend(prevoutID, i); + Hash spendID = addEntry(entryMap, spend); + + input.inputID = spendID.toString(); + + muxSources[i] = new ValueSource(spendID, assetAmount, 0); + spends.add(spend); + } + + Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51})); + Hash muxID = addEntry(entryMap, mux); + + for (Spend spend : spends) { + Output spendOutput = (Output) entryMap.get(spend.spentOutputID); + spend.setDestination(muxID, spendOutput.source.value, spend.ordinal); + } + + List resultIDList = new ArrayList<>(); + for (int i = 0; i < signTransaction.outputs.size(); i++) { + SignTransaction.AnnotatedOutput output = signTransaction.outputs.get(i); + + AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount); + ValueSource src = new ValueSource(muxID, amount, i); + Program prog = new Program(1, Hex.decode(output.controlProgram)); + Output oup = new Output(src, prog, i); + + Hash resultID = addEntry(entryMap, oup); + resultIDList.add(resultID); + + output.id = resultID.toString(); + + ValueDestination destination = new ValueDestination(resultID, src.value, 0); + mux.witnessDestinations.add(destination); + } + + TxHeader txHeader = new TxHeader(signTransaction.version, signTransaction.size, signTransaction.timeRange, resultIDList.toArray(new Hash[]{})); + Hash txID = addEntry(entryMap, txHeader); + signTransaction.txID = txID.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } } + private Hash addEntry(Map entryMap, Entry entry) { + Hash id = entry.entryID(); + entryMap.put(id, entry); + return id; + } + + public SignTransaction generateSignatures(SignTransaction signTransaction, BigInteger[] keys) { SignTransaction.AnnotatedInput input = signTransaction.inputs.get(0); input.witnessComponent.signatures = new String[keys.length]; diff --git a/tx-signer/src/main/java/io/bytom/types/AssetAmount.java b/tx-signer/src/main/java/io/bytom/types/AssetAmount.java new file mode 100644 index 0000000..04e79a8 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/AssetAmount.java @@ -0,0 +1,15 @@ +package io.bytom.types; + +public class AssetAmount { + + public AssetID assetID; + + public long amount; + + public AssetAmount() {} + + public AssetAmount(AssetID assetID, long amount) { + this.assetID = assetID; + this.amount = amount; + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/AssetID.java b/tx-signer/src/main/java/io/bytom/types/AssetID.java new file mode 100644 index 0000000..c5d7fb7 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/AssetID.java @@ -0,0 +1,42 @@ +package io.bytom.types; + +import org.bouncycastle.util.encoders.Hex; + +import java.util.Objects; + +public class AssetID { + + private String hexValue; + + public AssetID() {} + + public AssetID(String hexValue) { + this.hexValue = hexValue; + } + + public AssetID(byte[] byteArray) { + this.hexValue = Hex.toHexString(byteArray); + } + + public byte[] toByteArray() { + return Hex.decode(this.hexValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AssetID assetID = (AssetID) o; + return Objects.equals(hexValue, assetID.hexValue); + } + + @Override + public int hashCode() { + return Objects.hash(hexValue); + } + + @Override + public String toString() { + return this.hexValue; + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/Entry.java b/tx-signer/src/main/java/io/bytom/types/Entry.java new file mode 100644 index 0000000..6efb901 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/Entry.java @@ -0,0 +1,76 @@ +package io.bytom.types; + +import io.bytom.util.OutputUtil; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +public abstract class Entry { + + public abstract String typ(); + + public abstract void writeForHash(ByteArrayOutputStream out); + + public void mustWriteForHash(ByteArrayOutputStream out, Object data) { + try { + if (data == null) { + return; + } + if (data instanceof Byte) { + OutputUtil.writeByte(out, (byte) data); + } else if (data instanceof Long) { + OutputUtil.writeLong(out, (long) data); + } else if (data instanceof byte[]) { + OutputUtil.writeVarstr(out, (byte[]) data); + } else if (data instanceof byte[][]) { + OutputUtil.writeVarstrList(out, (byte[][]) data); + } else if (data instanceof String) { + OutputUtil.writeVarstr(out, ((String) data).getBytes()); + } else if (data instanceof Hash) { + out.write(((Hash) data).toByteArray()); + } else if (data instanceof AssetID) { + out.write(((AssetID) data).toByteArray()); + } else if (data.getClass().isArray()) { + Object[] array = (Object[]) data; + OutputUtil.writeVarint(out, array.length); + for (Object obj : array) { + mustWriteForHash(out, obj); + } + } else { + Class cls = data.getClass(); + Field[] fields = cls.getFields(); + for (Field field : fields) { + mustWriteForHash(out, field.get(data)); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Hash entryID() { + SHA3.Digest256 digest256 = new SHA3.Digest256(); + ByteArrayOutputStream hasher = new ByteArrayOutputStream(); + ByteArrayOutputStream bh = new ByteArrayOutputStream(); + try { + hasher.write("entryid:".getBytes()); + hasher.write(this.typ().getBytes()); + hasher.write(":".getBytes()); + + this.writeForHash(bh); + hasher.write(digest256.digest(bh.toByteArray())); + + return new Hash(digest256.digest(hasher.toByteArray())); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + bh.close(); + hasher.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/Hash.java b/tx-signer/src/main/java/io/bytom/types/Hash.java new file mode 100644 index 0000000..11b3b3e --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/Hash.java @@ -0,0 +1,42 @@ +package io.bytom.types; + +import org.bouncycastle.util.encoders.Hex; + +import java.util.Objects; + +public class Hash { + + private String hexValue; + + public Hash() {} + + public Hash(String hexValue) { + this.hexValue = hexValue; + } + + public Hash(byte[] byteArray) { + this.hexValue = Hex.toHexString(byteArray); + } + + public byte[] toByteArray() { + return Hex.decode(this.hexValue); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Hash hash = (Hash) o; + return Objects.equals(hexValue, hash.hexValue); + } + + @Override + public int hashCode() { + return Objects.hash(hexValue); + } + + @Override + public String toString() { + return this.hexValue; + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/Mux.java b/tx-signer/src/main/java/io/bytom/types/Mux.java new file mode 100644 index 0000000..760da33 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/Mux.java @@ -0,0 +1,33 @@ +package io.bytom.types; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +public class Mux extends Entry { + + public ValueSource[] sources; + + public Program program; + + public List witnessDestinations = new ArrayList<>(); + + public Mux() {} + + public Mux(ValueSource[] sources, Program program) { + this(); + this.sources = sources; + this.program = program; + } + + @Override + public String typ() { + return "mux1"; + } + + @Override + public void writeForHash(ByteArrayOutputStream out) { + mustWriteForHash(out, this.sources); + mustWriteForHash(out, this.program); + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/Output.java b/tx-signer/src/main/java/io/bytom/types/Output.java new file mode 100644 index 0000000..b789614 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/Output.java @@ -0,0 +1,35 @@ +package io.bytom.types; + +import java.io.ByteArrayOutputStream; + +public class Output extends Entry { + + public ValueSource source; + + public Program controlProgram; + + public Integer ordinal; + + public Output() { + this.source = new ValueSource(); + this.controlProgram = new Program(); + } + + + public Output(ValueSource source, Program controlProgram, Integer ordinal) { + this.source = source; + this.controlProgram = controlProgram; + this.ordinal = ordinal; + } + + @Override + public String typ() { + return "output1"; + } + + @Override + public void writeForHash(ByteArrayOutputStream out) { + mustWriteForHash(out, this.source); + mustWriteForHash(out, this.controlProgram); + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/Program.java b/tx-signer/src/main/java/io/bytom/types/Program.java new file mode 100644 index 0000000..11e5d58 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/Program.java @@ -0,0 +1,16 @@ +package io.bytom.types; + + +public class Program { + + public long vmVersion; + + public byte[] code; + + public Program() {} + + public Program(long vmVersion, byte[] code) { + this.vmVersion = vmVersion; + this.code = code; + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/Spend.java b/tx-signer/src/main/java/io/bytom/types/Spend.java new file mode 100644 index 0000000..87c5928 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/Spend.java @@ -0,0 +1,33 @@ +package io.bytom.types; + +import java.io.ByteArrayOutputStream; + +public class Spend extends Entry { + + public Hash spentOutputID; + + public int ordinal; + + public ValueDestination witnessDestination; + + public byte[][] witnessArguments; + + public Spend(Hash spentOutputID, int ordinal) { + this.spentOutputID = spentOutputID; + this.ordinal = ordinal; + } + + public void setDestination(Hash id, AssetAmount val, long pos) { + this.witnessDestination = new ValueDestination(id, val, pos); + } + + @Override + public String typ() { + return "spend1"; + } + + @Override + public void writeForHash(ByteArrayOutputStream out) { + mustWriteForHash(out, this.spentOutputID); + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/TxHeader.java b/tx-signer/src/main/java/io/bytom/types/TxHeader.java new file mode 100644 index 0000000..8d902a8 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/TxHeader.java @@ -0,0 +1,35 @@ +package io.bytom.types; + +import java.io.ByteArrayOutputStream; + +public class TxHeader extends Entry { + + public long version; + + public long serializedSize; + + public long timeRange; + + public Hash[] resultIDs; + + public TxHeader() {} + + public TxHeader(long version, long serializedSize, long timeRange, Hash[] resultIDs) { + this.version = version; + this.serializedSize = serializedSize; + this.timeRange = timeRange; + this.resultIDs = resultIDs; + } + + @Override + public String typ() { + return "txheader"; + } + + @Override + public void writeForHash(ByteArrayOutputStream out) { + mustWriteForHash(out, this.version); + mustWriteForHash(out, this.timeRange); + mustWriteForHash(out, this.resultIDs); + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/ValueDestination.java b/tx-signer/src/main/java/io/bytom/types/ValueDestination.java new file mode 100644 index 0000000..9981afc --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/ValueDestination.java @@ -0,0 +1,18 @@ +package io.bytom.types; + +public class ValueDestination { + + public Hash ref; + + public AssetAmount value; + + public long position; + + public ValueDestination() {} + + public ValueDestination(Hash ref, AssetAmount value, long position) { + this.ref = ref; + this.value = value; + this.position = position; + } +} diff --git a/tx-signer/src/main/java/io/bytom/types/ValueSource.java b/tx-signer/src/main/java/io/bytom/types/ValueSource.java new file mode 100644 index 0000000..00899fb --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/ValueSource.java @@ -0,0 +1,20 @@ +package io.bytom.types; + + +public class ValueSource { + + public Hash ref; + + public AssetAmount value; + + public long position; + + public ValueSource() {} + + public ValueSource(Hash ref, AssetAmount value, long position) { + this.ref = ref; + this.value = value; + this.position = position; + } + +} diff --git a/tx-signer/src/main/java/io/bytom/util/OutputUtil.java b/tx-signer/src/main/java/io/bytom/util/OutputUtil.java new file mode 100644 index 0000000..8c6b000 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/util/OutputUtil.java @@ -0,0 +1,47 @@ +package io.bytom.util; + +import java.io.IOException; +import java.io.OutputStream; + +public final class OutputUtil { + + public static void writeByte(OutputStream out, byte data) throws IOException { + out.write(data); + } + + public static void writeLong(OutputStream out, long data) throws IOException { + for (int i = 0; i < 8; i++) { + out.write((byte) data); + data >>= 8; + } + } + + public static void writeVarint(OutputStream out, long data) throws IOException { + byte[] buf = new byte[9]; + int n = putUvarint(buf, data); + out.write(buf, 0, n); + } + + public static void writeVarstr(OutputStream out, byte[] str) throws IOException { + writeVarint(out, str.length); + out.write(str); + } + + public static void writeVarstrList(OutputStream out, byte[][] l) throws IOException { + writeVarint(out, l.length); + for (byte[] str : l) { + writeVarstr(out, str); + } + } + + private static int putUvarint(byte[] buf, long x) { + int i = 0; + while (x >= 0x80) { + buf[i] = (byte)(x | 0x80); + x >>= 7; + i++; + } + buf[i] = (byte)x; + return i + 1; + } +} diff --git a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java index a424886..d224fc2 100644 --- a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java +++ b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java @@ -1,12 +1,9 @@ package io.bytom.api; -import io.bytom.common.Constants; +import io.bytom.types.*; import org.junit.Test; - import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.math.BigInteger; - import org.bouncycastle.util.encoders.Hex; /** * Created by liqiang on 2018/10/24. @@ -129,4 +126,126 @@ public void testSerializeRawTransaction() { System.out.print(txSign); } + + @Test + public void testMustWriteForHash() throws Exception { + Entry entry = new Entry() { + @Override + public String typ() { + return null; + } + + @Override + public void writeForHash(ByteArrayOutputStream out) { + + } + }; + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + entry.mustWriteForHash(out, (byte) 2); + assert Hex.toHexString(out.toByteArray()).equals("02"); + + out.reset(); + entry.mustWriteForHash(out, 1L); + assert Hex.toHexString(out.toByteArray()).equals("0100000000000000"); + + out.reset(); + entry.mustWriteForHash(out, 0x3456584738473837L); + assert Hex.toHexString(out.toByteArray()).equals("3738473847585634"); + + out.reset(); + entry.mustWriteForHash(out, new byte[]{0x12, 0x34, (byte) 0x85}); + assert Hex.toHexString(out.toByteArray()).equals("03123485"); + + out.reset(); + entry.mustWriteForHash(out, new byte[][]{{0x12, 0x34, (byte) 0x85}, {(byte) 0x86, 0x17, 0x40}}); + assert Hex.toHexString(out.toByteArray()).equals("020312348503861740"); + + out.reset(); + entry.mustWriteForHash(out, "hello, 世界"); + assert Hex.toHexString(out.toByteArray()).equals("0d68656c6c6f2c20e4b896e7958c"); + + out.reset(); + entry.mustWriteForHash(out, new String[]{"hi", "你好", "hello"}); + assert Hex.toHexString(out.toByteArray()).equals("0302686906e4bda0e5a5bd0568656c6c6f"); + + out.reset(); + String hash = "d8ab56a5c9296f591db071a8b63f395e1485b12d4b105b49fee287c03888331f"; + entry.mustWriteForHash(out, new Hash(hash)); + assert Hex.toHexString(out.toByteArray()).equals(hash); + + out.reset(); + ValueSource valueSource = new ValueSource(new Hash(hash), null, 1); + Program program = new Program(1, new byte[]{1}); + Mux mux = new Mux(); + mux.sources = new ValueSource[]{valueSource}; + mux.program = program; + entry.mustWriteForHash(out, mux); + assert Hex.toHexString(out.toByteArray()).equals("01d8ab56a5c9296f591db071a8b63f395e1485b12d4b105b49fee287c03888331f010000000000000001000000000000000101"); + } + + @Test + public void testEntryID() throws Exception { + String hash = "d8ab56a5c9296f591db071a8b63f395e1485b12d4b105b49fee287c03888331f"; + ValueSource valueSource = new ValueSource(new Hash(hash), null, 1); + Program program = new Program(1, new byte[]{1}); + Mux mux = new Mux(); + mux.sources = new ValueSource[]{valueSource}; + mux.program = program; + String entryID = mux.entryID().toString(); + assert entryID.equals("ebd967df33a3373ab85521fba24c22bf993c73f46fa96254b0c86646093184e9"); + } + + @Test + public void testMapTransaction() throws Exception { + String txJson = "{\n" + + " \"tx_id\": \"\",\n" + + " \"version\": 1,\n" + + " \"size\": 236,\n" + + " \"time_range\": 1521625823,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 200000000,\n" + + " \"sourceId\": \"0e97230a7347967764fd77c8cfa96b38ec6ff08465300a01900c645dfb694f24\",\n" + + " \"sourcePosition\": 1,\n" + + " \"control_program\": \"00140d074bc86bd388a45f1c8911a41b8f0705d9058b\",\n" + + " \"address\": \"bm1qp5r5hjrt6wy2ghcu3yg6gxu0quzajpvtsm2gnc\",\n" + + " \"spent_output_id\": \"\",\n" + + " \"input_id\": \"\",\n" + + " \"witness_component\": \n" + + " {\n" + + " \"signatures\": []\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"id\": \"\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 10000000,\n" + + " \"control_program\": \"0014687148664db6d4ae3151939a70a35f2004a58c55\",\n" + + " \"address\": \"bm1qdpc5sejdkm22uv23jwd8pg6lyqz2trz4trgxh0\"\n" + + " },\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"\",\n" + + " \"position\": 1,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 185000000,\n" + + " \"control_program\": \"00140d074bc86bd388a45f1c8911a41b8f0705d9058b\",\n" + + " \"address\": \"bm1qp5r5hjrt6wy2ghcu3yg6gxu0quzajpvtsm2gnc\"\n" + + " }\n" + + " ]\n" + + " }"; + + SignTransaction transaction = SignTransaction.fromJson(txJson); + SignTransactionImpl signTransactionImpl = new SignTransactionImpl(); + signTransactionImpl.mapTransaction(transaction); + assert transaction.txID.equals("4cb066e3d10e72b9c79b39109b69f142fc120e54f4a5ef3e5195a443b497f9d5"); + } } From 91834a86b41f6a0a85602a5251cf695aafabe3b4 Mon Sep 17 00:00:00 2001 From: liqiang Date: Mon, 5 Nov 2018 11:38:14 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/bytom/api/SignTransaction.java | 2 ++ .../io/bytom/api/SignTransactionImpl.java | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java index 8e36a61..9e5002b 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java @@ -131,6 +131,8 @@ public class AnnotatedInput { public long sourcePosition; + public String chainPath; + @SerializedName("witness_component") public InputWitnessComponent witnessComponent; diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java index 5869934..7b1f140 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -166,6 +166,29 @@ public SignTransaction generateSignatures(SignTransaction signTransaction, BigIn return signTransaction; } + //分多次签名 + public boolean generateSignature(SignTransaction signTransaction, BigInteger key, int index) { + boolean result = false; + SignTransaction.AnnotatedInput input = signTransaction.inputs.get(index); + if (null != input) { + try { + byte[] message = hashFn(Hex.decode(input.inputID), Hex.decode(signTransaction.txID)); + byte[] expandedPrv = BigIntegerToBytes(key); + byte[] priKey = ExpandedPrivateKey.ExpandedPrivateKey(expandedPrv); + byte[] sig = Signer.Ed25519InnerSign(priKey, message); + input.witnessComponent.signatures[index] = Hex.toHexString(sig); + System.out.println("sig: " + Hex.toHexString(sig)); + } catch (Exception e) { + e.printStackTrace(); + result = false; + } + } else { + System.out.println("generate signatures failed."); + result = false; + } + return result; + } + public int writeVarint(long value, ByteArrayOutputStream stream) throws IOException { byte[] varint = new byte[9]; int n = putUvarint(varint, value); From 1a8840a558107a2365e0a5b36fce2fb788bd3367 Mon Sep 17 00:00:00 2001 From: liqiang Date: Wed, 7 Nov 2018 20:16:48 +0800 Subject: [PATCH 09/13] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/bytom/api/SignTransaction.java | 2 + .../io/bytom/api/SignTransactionImpl.java | 81 +++++++++++++++- .../src/main/java/io/bytom/http/Client.java | 2 +- .../io/bytom/api/SignTransactionTest.java | 96 ++++++++++++++++++- .../bytom/common/ExpandedPrivateKeyTest.java | 18 ++++ 5 files changed, 193 insertions(+), 6 deletions(-) diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java index 9e5002b..030cbf0 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java @@ -203,5 +203,7 @@ public static class InputWitnessComponent { * "signature"). */ public String[] signatures; + + public String pubKey; } } diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java index 2a02f0a..7a59b7c 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -1,5 +1,6 @@ package io.bytom.api; +import com.google.common.base.Preconditions; import io.bytom.common.Constants; import io.bytom.common.ExpandedPrivateKey; import io.bytom.types.*; @@ -8,6 +9,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.math.BigInteger; import java.util.*; @@ -17,14 +19,20 @@ */ public class SignTransactionImpl { - public String signTransaction(SignTransaction tx, BigInteger[] keys) { + public boolean signTransaction(SignTransaction tx, String priKey) { - String txSign = null; //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 mapTransaction(tx); //签名得到signatures - generateSignatures(tx,keys); + generateSignature(tx,priKey); + + return true; + } + + public String serializeTransaction(SignTransaction tx) { + + String txSign = null; //开始序列化 ByteArrayOutputStream stream = new ByteArrayOutputStream(); @@ -66,6 +74,7 @@ public String signTransaction(SignTransaction tx, BigInteger[] keys) { writeVarint(dataInputCommit.length, stream); stream.write(dataInputCommit); + System.out.println("serialize1: " + Hex.toHexString(stream.toByteArray())); //inputWitness ByteArrayOutputStream witnessStream = new ByteArrayOutputStream(); @@ -75,12 +84,20 @@ public String signTransaction(SignTransaction tx, BigInteger[] keys) { writeVarint(lenSigs, witnessStream); for (int i =0; i entryMap, Entry entry) { public SignTransaction generateSignatures(SignTransaction signTransaction, BigInteger[] keys) { + + String[] keyArray = {"819e1f2b7e01ce55594fa1ef9f676517099e5a8b1b4b1628abc34b74e0e8f7e9","58e3bd92f34faac04bc0b00190841180f76ded55105056ad24b36566d4be253e"}; SignTransaction.AnnotatedInput input = signTransaction.inputs.get(0); input.witnessComponent.signatures = new String[keys.length]; for (int i=0; i= 0, "b must be positive or zero"); + Preconditions.checkArgument(numBytes > 0, "numBytes must be positive"); + byte[] src = b.toByteArray(); + byte[] dest = new byte[numBytes]; + boolean isFirstByteOnlyForSign = src[0] == 0; + int length = isFirstByteOnlyForSign ? src.length - 1 : src.length; + Preconditions.checkArgument(length <= numBytes, "The given number does not fit in " + numBytes); + int srcPos = isFirstByteOnlyForSign ? 1 : 0; + int destPos = numBytes - length; + System.arraycopy(src, srcPos, dest, destPos, length); + return dest; + } + + public static byte[] pruneIntermediateScalar(byte[] f) { + f[0] &= 248; + f[31] &= 31; // clear top 3 bits + f[31] |= 64; // set second highest bit + return f; + } } diff --git a/tx-signer/src/main/java/io/bytom/http/Client.java b/tx-signer/src/main/java/io/bytom/http/Client.java index f872090..c416659 100644 --- a/tx-signer/src/main/java/io/bytom/http/Client.java +++ b/tx-signer/src/main/java/io/bytom/http/Client.java @@ -72,7 +72,7 @@ public static Client generateClient() throws BytomException { String accessToken = Configuration.getValue("client.access.token"); if (coreURL == null || coreURL.isEmpty()) { - coreURL = "http://127.0.0.1:9888"; + coreURL = "http://47.91.254.104:8888"; } if (coreURL.endsWith("/")) { diff --git a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java index d224fc2..fc287d2 100644 --- a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java +++ b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java @@ -113,6 +113,96 @@ public void testSerializeRawTransaction() { // 02 01 3c ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 80ade20401160014687148664db6d4ae3151939a70a35f2004a58c5500 01 3c ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff c0c09b58011600140d074bc86bd388a45f1c8911a41b8f0705d9058b00 // outputlen version serialen assetid + txJson = "{\n" + + " \"tx_id\": \"6c846ae763c64b69040f9bad0402e1a34f5a2065f67622bc9d849a1547af0092\",\n" + + " \"version\": 1,\n" + + " \"size\": 236,\n" + + " \"time_range\": 1521625823,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 200000000,\n" + + " \"sourceId\": \"45729e2c2211128049f44d35d2ea782ef0a1cdc363bd0ca37e4028db82504499\",\n" + + " \"sourcePosition\": 1,\n" + + " \"control_program\": \"0014e18654db91f9d0f4e4a86271b189dc79b0be6adf\",\n" + + " \"address\": \"bm1quxr9fku3l8g0fe9gvfcmrzwu0xctu6klf0xf7j\",\n" + + " \"spent_output_id\": \"\",\n" + + " \"input_id\": \"3b36453f7dc03b13523d6431afd7e544f60339daed52ba8fca7ebf88cd5e5939\",\n" + + " \"witness_component\": \n" + + " {\n" + + " \"signatures\": []\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"id\": \"8a511581e2fb6986abc3be3bbd842434f642db7c56a1fc5c4c7adf93c750e9a4\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 10000000,\n" + + " \"control_program\": \"0014adf7d56795abb622edaece42d5ea193463ddf06a\",\n" + + " \"address\": \"bm1q4hma2eu44wmz9mdweepdt6sex33amur25rwjre\"\n" + + " },\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"03b6ac529c2d1c7d422a7c063d74893e8ca2003b2b3368c27d0ede2d2f6ea3ba\",\n" + + " \"position\": 1,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 185000000,\n" + + " \"control_program\": \"0014e18654db91f9d0f4e4a86271b189dc79b0be6adf\",\n" + + " \"address\": \"bm1quxr9fku3l8g0fe9gvfcmrzwu0xctu6klf0xf7j\"\n" + + " }\n" + + " ]\n" + + " }"; + + String rawJson = "{\n" + + " \"tx_id\": \"6c846ae763c64b69040f9bad0402e1a34f5a2065f67622bc9d849a1547af0092\",\n" + + " \"version\": 1,\n" + + " \"size\": 236,\n" + + " \"time_range\": 0,\n" + + " \"inputs\": [\n" + + " {\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 100000000,\n" + + " \"sourceId\": \"4971509ca7729013fcd699a79bf2a823050ed34690d820872aa43d38954d706e\",\n" + + " \"sourcePosition\": 1,\n" + + " \"control_program\": \"0014405f72b55a5a1d9fc2ede25a7e568a8ca91fc9c0\",\n" + + " \"address\": \"bm1qgp0h9d26tgwelshdufd8u4523j53ljwqxkazhc\",\n" + + " \"spent_output_id\": \"\",\n" + + " \"input_id\": \"3b36453f7dc03b13523d6431afd7e544f60339daed52ba8fca7ebf88cd5e5939\",\n" + + " \"witness_component\": \n" + + " {\n" + + " \"signatures\": []\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"outputs\": [\n" + + " {\n" + + " \"id\": \"8a511581e2fb6986abc3be3bbd842434f642db7c56a1fc5c4c7adf93c750e9a4\",\n" + + " \"position\": 0,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 2000000,\n" + + " \"control_program\": \"001412d988a2086c084eeac343277709ff8d24eb9f98\",\n" + + " \"address\": \"bm1qztvc3gsgdsyya6krgvnhwz0l35jwh8ucqe3uzw\"\n" + + " },\n" + + " {\n" + + " \"type\": \"control\",\n" + + " \"id\": \"03b6ac529c2d1c7d422a7c063d74893e8ca2003b2b3368c27d0ede2d2f6ea3ba\",\n" + + " \"position\": 1,\n" + + " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + + " \"asset_definition\": {},\n" + + " \"amount\": 93000000,\n" + + " \"control_program\": \"0014405f72b55a5a1d9fc2ede25a7e568a8ca91fc9c0\",\n" + + " \"address\": \"bm1qgp0h9d26tgwelshdufd8u4523j53ljwqxkazhc\"\n" + + " }\n" + + " ]\n" + + " }"; + SignTransaction tx = SignTransaction.fromJson(txJson); SignTransactionImpl signImpl = new SignTransactionImpl(); @@ -121,9 +211,13 @@ public void testSerializeRawTransaction() { BigInteger key2 = new BigInteger("40205913350867552217212167676397244457827512592372060624640880098442502612286");//子私钥 keys[0] = key1; keys[1] = key2; + String priKey = "e089bf65a759318960225c7b03954a606c39260f4773213b044e9e2df52cb556f75e6e31880a3418d06b6c578415a3b57c0d925ca2151f5313e5afc88c324a35"; + + signImpl.signTransaction(tx, priKey); - String txSign = signImpl.signTransaction(tx, keys); + String txSign = signImpl.serializeTransaction(tx); + //0701dfd5c8d50501015f015d45729e2c2211128049f44d35d2ea782ef0a1cdc363bd0ca37e4028db82504499ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8084af5f0101160014e18654db91f9d0f4e4a86271b189dc79b0be6adf6302407ca47a522ae0035a3c4aaa7406c1fbecbf4fc6c66cdcffe6dddcaf309f4b2a68f1e90e78dbf88b6c9af67f451a43df338f9073741bfc75cf674573453d528c03205b9adf91a5d20f4d45cabe72f21077335152b3f1acf6f7ffee9e1ed975550ed702013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80ade20401160014adf7d56795abb622edaece42d5ea193463ddf06a00013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0c09b5801160014e18654db91f9d0f4e4a86271b189dc79b0be6adf00 System.out.print(txSign); } diff --git a/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java b/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java index 16f88da..0397918 100644 --- a/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java +++ b/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java @@ -1,6 +1,7 @@ package io.bytom.common; +import io.bytom.api.SignTransactionImpl; import org.bouncycastle.util.encoders.Hex; import org.junit.Test; @@ -18,4 +19,21 @@ public void testExpandedKey() throws NoSuchAlgorithmException, SignatureExceptio //expect: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3 // e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3 } + + @Test + public void testExpandedPriKey() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + String childXprv = "5c0fd0d454c4e06fa9083a14d786d714e74088359f917777d6e0847ddddec01a"; + childXprv = "0466f40dab3655412dcbdc71ae98a93db2fc4c32e6f54b900ce6e1ec0b63b39e"; + String key = "Root"; + byte[] hashPriKey = ExpandedPrivateKey.HMacSha512(Hex.decode(childXprv), key.getBytes()); + //begin operate res[:32] + byte[] f = new byte[hashPriKey.length / 2]; + System.arraycopy(hashPriKey, 0, f, 0, hashPriKey.length / 2); + f = SignTransactionImpl.pruneIntermediateScalar(f); + System.arraycopy(f, 0, hashPriKey, 0, hashPriKey.length / 2); + //end operate res[:32] + byte[] hashPubKey = DeriveXpub.deriveXpub(hashPriKey); + System.out.println(Hex.toHexString(hashPriKey)); + System.out.println(Hex.toHexString(hashPubKey)); + } } \ No newline at end of file From be32054cb352e5b140cdd03cb8a1f292184634b8 Mon Sep 17 00:00:00 2001 From: liqiang Date: Fri, 9 Nov 2018 10:22:05 +0800 Subject: [PATCH 10/13] fix bug --- .../java/io/bytom/api/SignTransaction.java | 2 - .../io/bytom/api/SignTransactionImpl.java | 341 ++++++------------ .../io/bytom/common/ExpandedPrivateKey.java | 2 +- .../src/main/java/io/bytom/common/Utils.java | 86 ++++- .../java/io/bytom/types/ExpandedKeys.java | 24 ++ .../io/bytom/api/SignTransactionTest.java | 62 +--- .../bytom/common/ExpandedPrivateKeyTest.java | 17 - 7 files changed, 225 insertions(+), 309 deletions(-) create mode 100644 tx-signer/src/main/java/io/bytom/types/ExpandedKeys.java diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java index 030cbf0..9e5002b 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransaction.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransaction.java @@ -203,7 +203,5 @@ public static class InputWitnessComponent { * "signature"). */ public String[] signatures; - - public String pubKey; } } diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java index 7a59b7c..007fe05 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -2,7 +2,9 @@ import com.google.common.base.Preconditions; import io.bytom.common.Constants; +import io.bytom.common.DeriveXpub; import io.bytom.common.ExpandedPrivateKey; +import io.bytom.common.Utils; import io.bytom.types.*; import org.bouncycastle.jcajce.provider.digest.SHA3; import org.bouncycastle.util.encoders.Hex; @@ -19,127 +21,187 @@ */ public class SignTransactionImpl { - public boolean signTransaction(SignTransaction tx, String priKey) { - + public void mapTransaction(SignTransaction signTransaction) { //组装计算program、inputID、sourceID(muxID)、txID, json数据中这些字段的值为测试值,需重新计算 - mapTransaction(tx); + Map entryMap = new HashMap<>(); + ValueSource[] muxSources = new ValueSource[signTransaction.inputs.size()]; + List spends = new ArrayList<>(); + + try { + for (int i = 0; i < signTransaction.inputs.size(); i++) { + SignTransaction.AnnotatedInput input = signTransaction.inputs.get(i); + Program proc = new Program(1, Hex.decode(input.controlProgram)); + + AssetID assetID = new AssetID(input.assetId); + AssetAmount assetAmount = new AssetAmount(assetID, input.amount); + + Hash sourceID = new Hash(input.sourceId); + ValueSource src = new ValueSource(sourceID, assetAmount, input.sourcePosition); + Output prevout = new Output(src, proc, 0); + Hash prevoutID = addEntry(entryMap, prevout); + + input.spentOutputId = prevoutID.toString(); + + Spend spend = new Spend(prevoutID, i); + Hash spendID = addEntry(entryMap, spend); + + input.inputID = spendID.toString(); + + muxSources[i] = new ValueSource(spendID, assetAmount, 0); + spends.add(spend); + } + + Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51})); + Hash muxID = addEntry(entryMap, mux); + + for (Spend spend : spends) { + Output spendOutput = (Output) entryMap.get(spend.spentOutputID); + spend.setDestination(muxID, spendOutput.source.value, spend.ordinal); + } + + List resultIDList = new ArrayList<>(); + for (int i = 0; i < signTransaction.outputs.size(); i++) { + SignTransaction.AnnotatedOutput output = signTransaction.outputs.get(i); + + AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount); + ValueSource src = new ValueSource(muxID, amount, i); + Program prog = new Program(1, Hex.decode(output.controlProgram)); + Output oup = new Output(src, prog, i); - //签名得到signatures - generateSignature(tx,priKey); + Hash resultID = addEntry(entryMap, oup); + resultIDList.add(resultID); - return true; + output.id = resultID.toString(); + + ValueDestination destination = new ValueDestination(resultID, src.value, 0); + mux.witnessDestinations.add(destination); + } + + TxHeader txHeader = new TxHeader(signTransaction.version, signTransaction.size, signTransaction.timeRange, resultIDList.toArray(new Hash[]{})); + Hash txID = addEntry(entryMap, txHeader); + signTransaction.txID = txID.toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } } public String serializeTransaction(SignTransaction tx) { String txSign = null; - //开始序列化 ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { stream.write(7); // version if (null != tx.version) - writeVarint(tx.version, stream); + Utils.writeVarint(tx.version, stream); if (null != tx.timeRange) - writeVarint(tx.timeRange, stream); + Utils.writeVarint(tx.timeRange, stream); //inputs if (null != tx.inputs && tx.inputs.size() > 0) { - writeVarint(tx.inputs.size(), stream); + Utils.writeVarint(tx.inputs.size(), stream); for (SignTransaction.AnnotatedInput input:tx.inputs) { //assertVersion - writeVarint(tx.version, stream); //AssetVersion是否默认为1 + Utils.writeVarint(tx.version, stream); //AssetVersion是否默认为1 //inputCommitment ByteArrayOutputStream inputCommitStream = new ByteArrayOutputStream(); //spend type flag - writeVarint(Constants.INPUT_TYPE_SPEND, inputCommitStream); + Utils.writeVarint(Constants.INPUT_TYPE_SPEND, inputCommitStream); //spendCommitment ByteArrayOutputStream spendCommitSteam = new ByteArrayOutputStream(); spendCommitSteam.write(Hex.decode(input.sourceId)); //计算muxID spendCommitSteam.write(Hex.decode(input.assetId)); - writeVarint(input.amount, spendCommitSteam); + Utils.writeVarint(input.amount, spendCommitSteam); //sourcePosition - writeVarint(input.sourcePosition, spendCommitSteam); //db中获取position + Utils.writeVarint(input.sourcePosition, spendCommitSteam); //db中获取position //vmVersion - writeVarint(1, spendCommitSteam); //db中获取vm_version + Utils.writeVarint(1, spendCommitSteam); //db中获取vm_version //controlProgram - writeVarStr(Hex.decode(input.controlProgram), spendCommitSteam); + Utils.writeVarStr(Hex.decode(input.controlProgram), spendCommitSteam); byte[] dataSpendCommit = spendCommitSteam.toByteArray(); - writeVarint(dataSpendCommit.length, inputCommitStream); + Utils.writeVarint(dataSpendCommit.length, inputCommitStream); inputCommitStream.write(dataSpendCommit); byte[] dataInputCommit = inputCommitStream.toByteArray(); //inputCommit的length - writeVarint(dataInputCommit.length, stream); + Utils.writeVarint(dataInputCommit.length, stream); stream.write(dataInputCommit); - System.out.println("serialize1: " + Hex.toHexString(stream.toByteArray())); - //inputWitness ByteArrayOutputStream witnessStream = new ByteArrayOutputStream(); //arguments int lenSigs = input.witnessComponent.signatures.length; - //arguments的length: 应是qorum和sigs - writeVarint(lenSigs, witnessStream); + //arguments的length + Utils.writeVarint(lenSigs, witnessStream); for (int i =0; i 0) { - writeVarint(tx.outputs.size(), stream); + Utils.writeVarint(tx.outputs.size(), stream); for (SignTransaction.AnnotatedOutput output:tx.outputs) { //assertVersion - writeVarint(tx.version, stream); //AssetVersion是否默认为1 + Utils.writeVarint(tx.version, stream); //AssetVersion是否默认为1 //outputCommit ByteArrayOutputStream outputCommitSteam = new ByteArrayOutputStream(); //assetId outputCommitSteam.write(Hex.decode(output.assetId)); //amount - writeVarint(output.amount, outputCommitSteam); + Utils.writeVarint(output.amount, outputCommitSteam); //vmVersion - writeVarint(1, outputCommitSteam); //db中获取vm_version + Utils.writeVarint(1, outputCommitSteam); //db中获取vm_version //controlProgram - writeVarStr(Hex.decode(output.controlProgram), outputCommitSteam); + Utils.writeVarStr(Hex.decode(output.controlProgram), outputCommitSteam); byte[] dataOutputCommit = outputCommitSteam.toByteArray(); //outputCommit的length - writeVarint(dataOutputCommit.length, stream); + Utils.writeVarint(dataOutputCommit.length, stream); stream.write(dataOutputCommit); //outputWitness - writeVarint(0, stream); + Utils.writeVarint(0, stream); } } - //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 - //02013effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8c98d2b0f402011600144453a011caf735428d0291d82b186e976e286fc100013afffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40301160014613908c28df499e3aa04e033100efaa24ca8fd0100 + byte[] data = stream.toByteArray(); txSign = Hex.toHexString(data); - System.out.println(txSign); - System.out.println("sign Transaction success."); } catch (IOException e) { - System.out.println("sign Transaction failed."); + throw new RuntimeException(e); } return txSign; } + //签名组装witness + public SignTransaction buildWitness(SignTransaction signTransaction, int index, String priKey, String pubKey) { + + SignTransaction.AnnotatedInput input = signTransaction.inputs.get(index); + input.witnessComponent.signatures = new String[2]; + if (null != input) { + try { + byte[] message = hashFn(Hex.decode(input.inputID), Hex.decode(signTransaction.txID)); + byte[] key = Hex.decode(priKey); + byte[] sig = Signer.Ed25519InnerSign(key, message); + input.witnessComponent.signatures[0] = Hex.toHexString(sig); + input.witnessComponent.signatures[1] = pubKey; + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + System.out.println("build witness failed."); + } + return signTransaction; + } + public byte[] hashFn(byte[] hashedInputHex, byte[] txID) { SHA3.Digest256 digest256 = new SHA3.Digest256(); @@ -153,69 +215,6 @@ public byte[] hashFn(byte[] hashedInputHex, byte[] txID) { return digest256.digest(data); } - public void mapTransaction(SignTransaction signTransaction) { - Map entryMap = new HashMap<>(); - ValueSource[] muxSources = new ValueSource[signTransaction.inputs.size()]; - List spends = new ArrayList<>(); - - try { - for (int i = 0; i < signTransaction.inputs.size(); i++) { - SignTransaction.AnnotatedInput input = signTransaction.inputs.get(i); - Program proc = new Program(1, Hex.decode(input.controlProgram)); - - AssetID assetID = new AssetID(input.assetId); - AssetAmount assetAmount = new AssetAmount(assetID, input.amount); - - Hash sourceID = new Hash(input.sourceId); - ValueSource src = new ValueSource(sourceID, assetAmount, input.sourcePosition); - Output prevout = new Output(src, proc, 0); - Hash prevoutID = addEntry(entryMap, prevout); - - input.spentOutputId = prevoutID.toString(); - - Spend spend = new Spend(prevoutID, i); - Hash spendID = addEntry(entryMap, spend); - - input.inputID = spendID.toString(); - - muxSources[i] = new ValueSource(spendID, assetAmount, 0); - spends.add(spend); - } - - Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51})); - Hash muxID = addEntry(entryMap, mux); - - for (Spend spend : spends) { - Output spendOutput = (Output) entryMap.get(spend.spentOutputID); - spend.setDestination(muxID, spendOutput.source.value, spend.ordinal); - } - - List resultIDList = new ArrayList<>(); - for (int i = 0; i < signTransaction.outputs.size(); i++) { - SignTransaction.AnnotatedOutput output = signTransaction.outputs.get(i); - - AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount); - ValueSource src = new ValueSource(muxID, amount, i); - Program prog = new Program(1, Hex.decode(output.controlProgram)); - Output oup = new Output(src, prog, i); - - Hash resultID = addEntry(entryMap, oup); - resultIDList.add(resultID); - - output.id = resultID.toString(); - - ValueDestination destination = new ValueDestination(resultID, src.value, 0); - mux.witnessDestinations.add(destination); - } - - TxHeader txHeader = new TxHeader(signTransaction.version, signTransaction.size, signTransaction.timeRange, resultIDList.toArray(new Hash[]{})); - Hash txID = addEntry(entryMap, txHeader); - signTransaction.txID = txID.toString(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - private Hash addEntry(Map entryMap, Entry entry) { Hash id = entry.entryID(); entryMap.put(id, entry); @@ -224,15 +223,13 @@ private Hash addEntry(Map entryMap, Entry entry) { public SignTransaction generateSignatures(SignTransaction signTransaction, BigInteger[] keys) { - - String[] keyArray = {"819e1f2b7e01ce55594fa1ef9f676517099e5a8b1b4b1628abc34b74e0e8f7e9","58e3bd92f34faac04bc0b00190841180f76ded55105056ad24b36566d4be253e"}; SignTransaction.AnnotatedInput input = signTransaction.inputs.get(0); input.witnessComponent.signatures = new String[keys.length]; for (int i=0; i= 0x80) { - buf[i] = (byte)(x | 0x80); - x >>= 7; - i++; - } - buf[i] = (byte)x; - return i + 1; - } - - private byte[] BigIntegerToBytes(BigInteger value) { - if(value == null) { - return null; - } else { - 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; - } - } - - public static byte[] bigIntegerToBytes(BigInteger b, int numBytes) { - Preconditions.checkArgument(b.signum() >= 0, "b must be positive or zero"); - Preconditions.checkArgument(numBytes > 0, "numBytes must be positive"); - byte[] src = b.toByteArray(); - byte[] dest = new byte[numBytes]; - boolean isFirstByteOnlyForSign = src[0] == 0; - int length = isFirstByteOnlyForSign ? src.length - 1 : src.length; - Preconditions.checkArgument(length <= numBytes, "The given number does not fit in " + numBytes); - int srcPos = isFirstByteOnlyForSign ? 1 : 0; - int destPos = numBytes - length; - System.arraycopy(src, srcPos, dest, destPos, length); - return dest; - } - - public static byte[] pruneIntermediateScalar(byte[] f) { - f[0] &= 248; - f[31] &= 31; // clear top 3 bits - f[31] |= 64; // set second highest bit - return f; - } } diff --git a/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java b/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java index 9842e10..e7a418a 100644 --- a/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java +++ b/tx-signer/src/main/java/io/bytom/common/ExpandedPrivateKey.java @@ -9,7 +9,7 @@ import java.security.SignatureException; public class ExpandedPrivateKey { - private static byte[] HMacSha512(byte[] data, byte[] key) + public static byte[] HMacSha512(byte[] data, byte[] key) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA512"); diff --git a/tx-signer/src/main/java/io/bytom/common/Utils.java b/tx-signer/src/main/java/io/bytom/common/Utils.java index 7a7f69e..7ea543d 100644 --- a/tx-signer/src/main/java/io/bytom/common/Utils.java +++ b/tx-signer/src/main/java/io/bytom/common/Utils.java @@ -2,8 +2,90 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import io.bytom.api.SignTransactionImpl; +import io.bytom.types.ExpandedKeys; +import org.bouncycastle.util.encoders.Hex; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.util.Arrays; public class Utils { - public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; - public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create(); + + public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + + public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create(); + + public static int writeVarint(long value, ByteArrayOutputStream stream) throws IOException { + byte[] varint = new byte[9]; + int n = putUvarint(varint, value); + byte[] varintTime = Arrays.copyOf(varint, n); + stream.write(varintTime); + return n; + } + + public static int writeVarStr(byte[] buf, ByteArrayOutputStream stream) throws IOException { + int n = writeVarint(buf.length, stream); + stream.write(buf); + return n + (buf.length); + } + + public static int getLengthVarInt(long x) { + byte[] varint = new byte[9]; + int n = putUvarint(varint, x); + byte[] varintTime = Arrays.copyOf(varint, n); + return varintTime.length; + } + + private static int putUvarint(byte[] buf, long x) { + int i = 0; + while (x >= 0x80) { + buf[i] = (byte) (x | 0x80); + x >>= 7; + i++; + } + buf[i] = (byte) x; + return i + 1; + } + + public static byte[] BigIntegerToBytes(BigInteger value) { + if (value == null) { + return null; + } else { + 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; + } + } + + public static byte[] pruneIntermediateScalar(byte[] f) { + f[0] &= 248; + f[31] &= 31; // clear top 3 bits + f[31] |= 64; // set second highest bit + return f; + } + + public static ExpandedKeys expandedPriKey(String priKey, String key) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + byte[] hashPriKey = ExpandedPrivateKey.HMacSha512(Hex.decode(priKey), key.getBytes()); + //begin operate res[:32] + byte[] f = new byte[hashPriKey.length / 2]; + System.arraycopy(hashPriKey, 0, f, 0, hashPriKey.length / 2); + f = pruneIntermediateScalar(f); + System.arraycopy(f, 0, hashPriKey, 0, hashPriKey.length / 2); + //end operate res[:32] + byte[] hashPubKey = DeriveXpub.deriveXpub(hashPriKey); + ExpandedKeys expandedKeys = new ExpandedKeys(); + expandedKeys.setPriKey(Hex.toHexString(hashPriKey)); + expandedKeys.setPubKey(Hex.toHexString(hashPubKey)); + return expandedKeys; + } } diff --git a/tx-signer/src/main/java/io/bytom/types/ExpandedKeys.java b/tx-signer/src/main/java/io/bytom/types/ExpandedKeys.java new file mode 100644 index 0000000..1a61ef4 --- /dev/null +++ b/tx-signer/src/main/java/io/bytom/types/ExpandedKeys.java @@ -0,0 +1,24 @@ +package io.bytom.types; + +public class ExpandedKeys { + + private String expandedPriKey; + + private String expandedPubKey; + + public void setPriKey(String priKey) { + this.expandedPriKey = priKey; + } + + public String getPriKey() { + return this.expandedPriKey; + } + + public void setPubKey(String pubKey) { + this.expandedPubKey = pubKey; + } + + public String getPubKey() { + return this.expandedPubKey; + } +} diff --git a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java index fc287d2..f91201d 100644 --- a/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java +++ b/tx-signer/src/test/java/io/bytom/api/SignTransactionTest.java @@ -158,66 +158,22 @@ public void testSerializeRawTransaction() { " ]\n" + " }"; - String rawJson = "{\n" + - " \"tx_id\": \"6c846ae763c64b69040f9bad0402e1a34f5a2065f67622bc9d849a1547af0092\",\n" + - " \"version\": 1,\n" + - " \"size\": 236,\n" + - " \"time_range\": 0,\n" + - " \"inputs\": [\n" + - " {\n" + - " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + - " \"asset_definition\": {},\n" + - " \"amount\": 100000000,\n" + - " \"sourceId\": \"4971509ca7729013fcd699a79bf2a823050ed34690d820872aa43d38954d706e\",\n" + - " \"sourcePosition\": 1,\n" + - " \"control_program\": \"0014405f72b55a5a1d9fc2ede25a7e568a8ca91fc9c0\",\n" + - " \"address\": \"bm1qgp0h9d26tgwelshdufd8u4523j53ljwqxkazhc\",\n" + - " \"spent_output_id\": \"\",\n" + - " \"input_id\": \"3b36453f7dc03b13523d6431afd7e544f60339daed52ba8fca7ebf88cd5e5939\",\n" + - " \"witness_component\": \n" + - " {\n" + - " \"signatures\": []\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"outputs\": [\n" + - " {\n" + - " \"id\": \"8a511581e2fb6986abc3be3bbd842434f642db7c56a1fc5c4c7adf93c750e9a4\",\n" + - " \"position\": 0,\n" + - " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + - " \"asset_definition\": {},\n" + - " \"amount\": 2000000,\n" + - " \"control_program\": \"001412d988a2086c084eeac343277709ff8d24eb9f98\",\n" + - " \"address\": \"bm1qztvc3gsgdsyya6krgvnhwz0l35jwh8ucqe3uzw\"\n" + - " },\n" + - " {\n" + - " \"type\": \"control\",\n" + - " \"id\": \"03b6ac529c2d1c7d422a7c063d74893e8ca2003b2b3368c27d0ede2d2f6ea3ba\",\n" + - " \"position\": 1,\n" + - " \"asset_id\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n" + - " \"asset_definition\": {},\n" + - " \"amount\": 93000000,\n" + - " \"control_program\": \"0014405f72b55a5a1d9fc2ede25a7e568a8ca91fc9c0\",\n" + - " \"address\": \"bm1qgp0h9d26tgwelshdufd8u4523j53ljwqxkazhc\"\n" + - " }\n" + - " ]\n" + - " }"; - SignTransaction tx = SignTransaction.fromJson(txJson); SignTransactionImpl signImpl = new SignTransactionImpl(); - BigInteger[] keys = new BigInteger[2]; - BigInteger key1 = new BigInteger("58627734430160897710546100937464200251109455274527146106473181212575120553961");//子私钥 - BigInteger key2 = new BigInteger("40205913350867552217212167676397244457827512592372060624640880098442502612286");//子私钥 - keys[0] = key1; - keys[1] = key2; + //组装交易计算inputId,txID + signImpl.mapTransaction(tx); + //签名组装交易 String priKey = "e089bf65a759318960225c7b03954a606c39260f4773213b044e9e2df52cb556f75e6e31880a3418d06b6c578415a3b57c0d925ca2151f5313e5afc88c324a35"; - - signImpl.signTransaction(tx, priKey); - + String pubKey = "5b9adf91a5d20f4d45cabe72f21077335152b3f1acf6f7ffee9e1ed975550ed7f75e6e31880a3418d06b6c578415a3b57c0d925ca2151f5313e5afc88c324a35"; + String key = pubKey.substring(0,64); + int index = 0; + signImpl.buildWitness(tx, index, priKey, key); + //序列化 String txSign = signImpl.serializeTransaction(tx); //0701dfd5c8d50501015f015d45729e2c2211128049f44d35d2ea782ef0a1cdc363bd0ca37e4028db82504499ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8084af5f0101160014e18654db91f9d0f4e4a86271b189dc79b0be6adf6302407ca47a522ae0035a3c4aaa7406c1fbecbf4fc6c66cdcffe6dddcaf309f4b2a68f1e90e78dbf88b6c9af67f451a43df338f9073741bfc75cf674573453d528c03205b9adf91a5d20f4d45cabe72f21077335152b3f1acf6f7ffee9e1ed975550ed702013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80ade20401160014adf7d56795abb622edaece42d5ea193463ddf06a00013cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0c09b5801160014e18654db91f9d0f4e4a86271b189dc79b0be6adf00 + System.out.print(txSign); } diff --git a/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java b/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java index 0397918..ceb3e76 100644 --- a/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java +++ b/tx-signer/src/test/java/io/bytom/common/ExpandedPrivateKeyTest.java @@ -19,21 +19,4 @@ public void testExpandedKey() throws NoSuchAlgorithmException, SignatureExceptio //expect: e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3 // e8c0965af60563c4cabcf2e947b1cd955c4f501eb946ffc8c3447e5ec8a633535b899d45316cd83e027913d3ff3dc52f6a951a686fd2b750099e1f7c70cb98c3 } - - @Test - public void testExpandedPriKey() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { - String childXprv = "5c0fd0d454c4e06fa9083a14d786d714e74088359f917777d6e0847ddddec01a"; - childXprv = "0466f40dab3655412dcbdc71ae98a93db2fc4c32e6f54b900ce6e1ec0b63b39e"; - String key = "Root"; - byte[] hashPriKey = ExpandedPrivateKey.HMacSha512(Hex.decode(childXprv), key.getBytes()); - //begin operate res[:32] - byte[] f = new byte[hashPriKey.length / 2]; - System.arraycopy(hashPriKey, 0, f, 0, hashPriKey.length / 2); - f = SignTransactionImpl.pruneIntermediateScalar(f); - System.arraycopy(f, 0, hashPriKey, 0, hashPriKey.length / 2); - //end operate res[:32] - byte[] hashPubKey = DeriveXpub.deriveXpub(hashPriKey); - System.out.println(Hex.toHexString(hashPriKey)); - System.out.println(Hex.toHexString(hashPubKey)); - } } \ No newline at end of file From b5aad2e1839dc609460a8a0ed7bd174a4b8f3bbd Mon Sep 17 00:00:00 2001 From: liqiang Date: Mon, 12 Nov 2018 10:55:47 +0800 Subject: [PATCH 11/13] modify pom xml --- tx-signer/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tx-signer/pom.xml b/tx-signer/pom.xml index 578ee10..b9284bb 100644 --- a/tx-signer/pom.xml +++ b/tx-signer/pom.xml @@ -12,7 +12,7 @@ io.bytom tx-signer - 1.0-SNAPSHOT + 1.0.0-SNAPSHOT tx-signer From e96c384171c258f54f2e6832e5ce8a3c00d43d91 Mon Sep 17 00:00:00 2001 From: liqiang Date: Wed, 21 Nov 2018 15:17:27 +0800 Subject: [PATCH 12/13] fix some bug --- tx-signer/.DS_Store | Bin 6148 -> 6148 bytes .../io/bytom/api/SignTransactionImpl.java | 33 +++++++++++------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/tx-signer/.DS_Store b/tx-signer/.DS_Store index 5172429f264de2441865cb4700216d4256da9242..bcb830beb4e78ebeacb0eee2644ed0b435bc373b 100644 GIT binary patch delta 369 zcmZoMXfc=|#>B)qu~3YagMop8V`8BQClinX0?Zo&b~7?EPt=jDXJaT~NMtBtNM}f8 zC`l?WE=bDBPXa3Wl2nkBSzKaZaD$PFnT3^&or9B;n}?H!pC>jrBfmVjB(bEl*eS6n z8pI1oEXhbpEQ%M93C>JO1+v03Q%W*IQuADMQp-~F{EK`NOHv_9L-O-;fC|9cLK2he z!!lFL;{`;V^Ycm)GxJi5z=lH%i3!ilOUW;H$}i1JDF$nRn$E$Y>=5q&@(2Sb2WPy1 zcy+b8v5tbFnR%^_LbZjZg^q%$kx6YWCx@iEaZp@VW8bXVbCxb!zTyyA4+A43gk}(A zP=vCe)L|fv5+Y^68zT<0Og0c<+04zs%>fM1jfLNtC-aLavVsCrVX~o!^yUDOEzAJl CC}K(g delta 79 zcmZoMXfc=|#>AjHu~3+iaWW5+BsT*C69WV=ZVcGXxH*A&Im^a|P{z&d9Q+(Wm74>Z bzcWwf7g6M71S(4aDV=P>qr5prWCb$-fkhA? diff --git a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java index 007fe05..57b9278 100644 --- a/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java +++ b/tx-signer/src/main/java/io/bytom/api/SignTransactionImpl.java @@ -129,19 +129,21 @@ public String serializeTransaction(SignTransaction tx) { stream.write(dataInputCommit); //inputWitness - ByteArrayOutputStream witnessStream = new ByteArrayOutputStream(); - //arguments - int lenSigs = input.witnessComponent.signatures.length; - //arguments的length - Utils.writeVarint(lenSigs, witnessStream); - for (int i =0; i Date: Tue, 11 Dec 2018 11:12:22 +0800 Subject: [PATCH 13/13] add modify --- tx-signer/.DS_Store | Bin 6148 -> 6148 bytes tx-signer/src/.DS_Store | Bin 0 -> 6148 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tx-signer/src/.DS_Store diff --git a/tx-signer/.DS_Store b/tx-signer/.DS_Store index bcb830beb4e78ebeacb0eee2644ed0b435bc373b..8478ae5660ce4b990338a12dea145e3c8b4b13cf 100644 GIT binary patch delta 317 zcmZoMXfc=|#>B!ku~2NHo}wr>0|Nsi1A_pAVQ_MOZUKMI&gdq`Ve>y`dLkW^iQrkhY81iuYu*##@%6R}?|7J(#dyJdeIruq%fddSH@640= SMI1SRVZ#Jcx;a8*4Kn~H5ksE< delta 77 zcmZoMXfc=|#>B)qu~2NHo+2kR0|Nsi1A_p=W*xT8jEu~iOIXYpH*2xoW!%{Cf@w25 e2R{c;@n%7e@640=MI1SR%0cQ`Hb;o8VFmz~=@5zl diff --git a/tx-signer/src/.DS_Store b/tx-signer/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..170e46742fc20bd9d20e3e77f6353ae030b52daa GIT binary patch literal 6148 zcmeHKu}%Xq41K1DREeb{V?Kc{NNiDcd_gxRP!9nT*Q>;J&mZtB{018vpTdeAo9ZIM ziV(7;_$786Cr`TM41iYqZV4;^%()4YbSfg|s_V!mi<4xH$GhcvGxWR4B7fB+dvDO= z1`pU4`=33(PJR8*w%vZ*0Pi0c=lM8?xPIt<^zH5^oE*PMV1p62c*0oZ!`s!=SIuW} z)qLhF^3hq|s4Hi{8E^)i0cT*s0KeHH^Akna&VV!E3>+Ac`yp@>jD|@uUmdJ+3BcH} zIScotmXMfW7!8vmdss+A88wuw#2^i4d$4)YFez#{$chiL%wNR|2L2fX)U@k%#fP%D_1p2}tqt6+xrxZUBpnv+7e4_! ikbC4bJJlbg&-0>TQj}H1p2HdaM<5g8${F|t20j64sX!|L literal 0 HcmV?d00001