Light Client for Exonum Blockchain
A JavaScript library to work with Exonum blockchain from browser and Node.js. Used to sign transactions before sending to blockchain and verify blockchain responses using cryptographic proofs. Contains numerous helper functions. Find out more information about the architecture and tasks of light clients in Exonum.
If you are using Exonum in your project and want to be listed on our website & GitHub list — write us a line to exonum@bitfury.com.
Library compatibility with Exonum core:
JavaScript light client | Exonum core |
---|---|
0.17.1 | 0.12.* |
0.16.9 | 0.11.* |
0.16.9 | 0.10.* |
0.13.0 | 0.9.* |
0.10.2 | 0.8.* |
0.9.0 | 0.7.* |
0.6.1 | 0.6.* |
0.6.1 | 0.5.* |
0.3.0 | 0.4.0 |
0.3.0 | 0.3.0 |
0.2.0 | 0.2.0 |
0.1.1 | 0.1.* |
- Getting started
- Data types
- Hash
- Signature
- Transactions
- Cryptographic proofs
- Integrity checks
- Helpers
- Contributing
- Changelog
- License
Getting started
First of all, you should have installed these tools and libraries:
- C/C++ compiler
- libtool
- automake
- autoconf
On MacOS:
- Install XCode
brew install libtool automake autoconf
On Ubuntu:
sudo apt install gcc libtool automake autoconf
There are several options to include light client library in the application:
The most preferred way is to install Exonum Client as a package from npm registry:
npm install exonum-client
Otherwise you can download the source code from GitHub and compile it before use in browser.
Include in browser:
Usage in Node.js:
let Exonum =
Data types
Exonum use protobufjs library to serialize structured data into protobuf format.
Each transaction is signed before sending into blockchain. Before the transaction is signed it is converted into byte array under the hood.
The data received from the blockchain should be converted into byte array under the hood before it will be possible to verify proof of its existence using cryptographic algorithm.
Developer can both define data stuctures on the fly or use precompiled stubs with data structures.
To define protobuf structures use protobufjs library.
Example:
let Message = 'CustomMessage'MessageMessage let type = Exonum
Exonum.newType function requires a single argument of protobuf.Type
type with next structure.
Hash
Exonum uses cryptographic hashes of certain data for transactions and proofs.
Different signatures of the hash
function are possible:
Exonumhashdata type
typehashdata
Argument | Description | Type |
---|---|---|
data | Data to be processed using a hash function. | Object |
type | Definition of the data type. | Custom data type or transaction. |
An example of hash calculation:
// Define a data structurelet Message = 'CustomMessage'MessageMessage // Define a data typeconst User = Exonum // Data that has been hashedconst data = balance: 100 name: 'John Doe' // Get a hashlet hash = Userhashdata // 9786347be1ab7e8f3d68a49ef8a995a4decb31103c53565a108170dec4c1c2fa
It is also possible to get a hash from byte array:
Exonumhashbuffer
Argument | Description | Type |
---|---|---|
buffer | Byte array. | Array or Uint8Array . |
An example of byte array hash calculation:
const arr = 8 100 18 8 74 111 104 110 32 68 111 101 let hash = Exonumhasharr // 9786347be1ab7e8f3d68a49ef8a995a4decb31103c53565a108170dec4c1c2fa
Signature
The procedure for signing data using signing key pair and verifying of obtained signature is commonly used in the process of data exchange between the client and the service.
Built-in Exonum.keyPair helper function can be used to generate a new random signing key pair.
Sign data
The signature can be obtained using the secret key of the signing pair.
There are three possible signatures of the sign
function:
Exonum
type
Exonum
Argument | Description | Type |
---|---|---|
secretKey | Secret key as hexadecimal string. | String |
data | Data to be signed. | Object |
type | Definition of the data type. | Custom data type or transaction. |
buffer | Byte array. | Array or Uint8Array . |
The sign
function returns value as hexadecimal String
.
An example of data signing:
// Define a data structurelet Message = 'CustomMessage'MessageMessage // Define a data typeconst User = Exonum // Data that has been hashedconst data = balance: 100 name: 'John Doe' // Define a signing key pairconst keyPair = publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a' secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' + 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a' // Sign the datalet signature = Exonum
Verify signature
The signature can be verified using the author's public key.
There are two possible signatures of the verifySignature
function:
Exonum
type
Argument | Description | Type |
---|---|---|
signature | Signature as hexadecimal string. | String |
publicKey | Author's public key as hexadecimal string. | String |
data | Data that has been signed. | Object |
type | Definition of the data type. | Custom data type or transaction. |
The verifySignature
function returns value of Boolean
type.
An example of signature verification:
// Define a data structurelet Message = 'CustomMessage'MessageMessage // Define a data typeconst User = Exonum // Data that has been hashedconst data = balance: 100 name: 'John Doe' // Define a signing key pairconst keyPair = publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a' secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' + 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a' // Signature obtained upon signing using secret keyconst signature = 'a4cf7c457e3f4d54ef0c87900e7c860d2faa17a8dccbaafa573a3a960cda3f66' + '27911088138526d9d7e46feba471e6bc7b93262349a5ed18262cbc39c8a47b04' // Verify the signaturelet result = Exonum // true
Transactions
Transaction in Exonum is a operation to change the data stored in blockchain. Transaction processing rules is a part of business logic implemented on service side.
Sending data to the blockchain from a light client consist of 3 steps:
- Describe the fields of transaction using custom data types;
- Sign data of transaction using signing key pair;
- Send transaction to the blockchain.
Read more about transactions in Exonum.
Define transaction
An example of a transaction definition:
let Transaction = 'CustomMessage'TransactionTransactionTransaction let sendFunds = Exonum
Exonum.newTransaction function requires a single argument of Object
type with next structure:
Property | Description | Type |
---|---|---|
author | Author's public key as hexadecimal string. | String |
instance_id | Instance ID. | Number |
method_id | Method ID. | Number |
schema | Protobuf data structure. | Array |
signature | Signature as hexadecimal string. Optional. | String |
Field structure is identical to field structure of custom data type.
Sign transaction
An example of a transaction signing:
// Signing key pairconst keyPair = publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a' secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' + 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a' // Transaction data to be signedconst data = from: 'John' to: 'Adam' amount: 50 // Sign the datalet signature = sendFunds
Send transaction
To submit transaction to the blockchain send
function can be used.
There are two possible signatures of the send
function:
Exonum sendFunds
Property | Description | Type |
---|---|---|
explorerBasePath | API address of transaction explorer on a blockchain node. | String |
type | Definition of the transaction. | Transaction. |
data | Data that has been signed. | Object |
secretKey | Secret key as hexadecimal string. | String |
attempts | Number of attempts to check transaction status. Pass 0 in case you do not need to verify if the transaction is accepted to the block. Optional. Default value is 10 . |
Number |
timeout | Timeout between attempts to check transaction status. Optional. Default value is 500 . |
Number |
The send
function returns value of Promise
type with transaction hash as a fulfilled value.
Fulfilled state means that transaction is accepted to the block.
Fulfilled value contained transaction with its proof.
An example of a transaction sending:
// Define transaction explorer addressconst explorerBasePath = 'http://127.0.0.1:8200/api/explorer/v1/transactions' sendFunds
Send multiple transactions
To submit multiple transactions to the blockchain sendQueue
function can be used.
Transactions will be stored in the appropriate order.
Each transaction from the queue will be sent to the blockchain only after the previous transaction is accepted to the block.
Exonum
Property | Description | Type |
---|---|---|
explorerBasePath | API address of transaction explorer on a blockchain node. | String |
transactions | List of transactions. | Array |
secretKey | Secret key as hexadecimal string. | String |
attempts | Number of attempts to check each transaction status. Pass 0 in case you do not need to verify if the transactions are accepted to the block. Optional. Default value is 10 . |
Number |
timeout | Timeout between attempts to check each transaction status. Optional. Default value is 500 . |
Number |
Transaction structure:
Field | Description | Type |
---|---|---|
type | Definition of the transaction. | Transaction. |
data | Transaction data that has been signed. | Object |
The sendQueue
function returns value of Promise
type with an array of transaction hashes as a fulfilled value.
Fulfilled state means that all transactions are accepted to the block.
Fulfilled value contained an array of transactions with its proofs.
Find more examples of operations on transactions:
- Define transaction
- Serialize transaction
- Sign transaction
- Verify signed transaction
- Get a transaction hash
- Send transaction
- Send multiple transactions
Cryptographic proofs
A cryptographic proof is a format in which a Exonum node can provide sensitive data from a blockchain. These proofs are based on Merkle trees and their variants.
Light client library validates the cryptographic proof and can prove the integrity and reliability of the received data.
Read more about design of cryptographic proofs in Exonum.
Merkle tree proof
let elements = Exonum
The merkleProof
method is used to validate the Merkle tree and extract a list of data elements.
Argument | Description | Type |
---|---|---|
rootHash | The root hash of the Merkle tree as hexadecimal string. | String |
count | The total number of elements in the Merkle tree. | Number |
proofNode | The Merkle tree. | Object |
range | An array of two elements of Number type. Represents list of obtained elements: [startIndex; endIndex) . |
Array |
type | Definition of the elements type. Optional. The merkleProof method expects to find byte arrays or hashes as values in the tree if type is not passed. |
Custom data type |
An example of verifying a Merkle tree.
Map proof
let proof = json KeyType ValueTypeconsole
The MapProof
class is used to validate proofs for Merkelized maps.
Argument | Description | Type |
---|---|---|
json | The JSON presentation of the proof obtained from a full node. | Object |
KeyType | Data type for keys in the Merkelized map. | Custom or built-in data type |
ValueType | Data type for values in the Merkelized map. | Custom data type |
The returned object has the following fields:
Field | Description | Type |
---|---|---|
merkleRoot | Hexadecimal hash of the root of the underlying Merkelized map | String |
missingKeys | Set of keys which the proof asserts as missing from the map | Set<KeyType> |
entries | Map of key-value pairs that the are proved to exist in the map | Map<KeyType, ValueType> |
An example of using a MapProof
.
Integrity checks
Verify block
Exonum
Each new block in Exonum blockchain is signed by validators. To prove the integrity and reliability of the block, it is necessary to verify their signatures. The signature of each validator are stored in the precommits.
The merkleProof
method is used to validate block and its precommits.
The verifyBlock
function returns value of Promise
type.
Fulfilled state means that block is valid.
Argument | Description | Type |
---|---|---|
data | Structure with block and precommits. | Object |
validators | An array of validators public keys as a hexadecimal strings. | Array |
An example of block verification.
Verify table
Exonum
Verify table existence in the root tree.
Returns root hash for the table as hexadecimal String
.
Argument | Description | Type |
---|---|---|
proof | The JSON presentation of the proof obtained from a full node. | Object |
stateHash | Hash of current blockchain state stored in each block. | String |
instanceId | Instance ID. | Number |
tableIndex | Table index. | Number |
Built-in structures
List of built-in Exonum blockchain structures:
Structure | Use as |
---|---|
AdditionalHeaders | Exonum.protocol.exonum.AdditionalHeaders |
Block | Exonum.protocol.exonum.Block |
TxLocation | Exonum.protocol.exonum.TxLocation |
CallInBlock | Exonum.protocol.exonum.CallInBlock |
ValidatorKeys | Exonum.protocol.exonum.ValidatorKeys |
List of built-in Exonum consensus structures:
Structure | Use as |
---|---|
SignedMessage | Exonum.protocol.exonum.consensus.SignedMessage |
Verified | Exonum.protocol.exonum.consensus.Verified |
Connect | Exonum.protocol.exonum.consensus.Connect |
Status | Exonum.protocol.exonum.consensus.Status |
Propose | Exonum.protocol.exonum.consensus.Propose |
Prevote | Exonum.protocol.exonum.consensus.Prevote |
Precommit | Exonum.protocol.exonum.consensus.Precommit |
BlockResponse | Exonum.protocol.exonum.consensus.BlockResponse |
TransactionsResponse | Exonum.protocol.exonum.consensus.TransactionsResponse |
ProposeRequest | Exonum.protocol.exonum.consensus.ProposeRequest |
TransactionsRequest | Exonum.protocol.exonum.consensus.TransactionsRequest |
PrevotesRequest | Exonum.protocol.exonum.consensus.PrevotesRequest |
PeersRequest | Exonum.protocol.exonum.consensus.PeersRequest |
BlockRequest | Exonum.protocol.exonum.consensus.BlockRequest |
PoolTransactionsRequest | Exonum.protocol.exonum.consensus.PoolTransactionsRequest |
ExonumMessage | Exonum.protocol.exonum.consensus.ExonumMessage |
List of built-in Exonum key-value sequence structures:
Structure | Use as |
---|---|
KeyValue | Exonum.protocol.exonum.KeyValue |
KeyValueSequence | Exonum.protocol.exonum.KeyValueSequence |
List of built-in Exonum proofs structures:
Structure | Use as |
---|---|
BlockProof | Exonum.protocol.exonum.BlockProof |
IndexProof | Exonum.protocol.exonum.IndexProof |
List of built-in Exonum runtime structures:
Structure | Use as |
---|---|
CallInfo | Exonum.protocol.exonum.runtime.CallInfo |
AnyTx | Exonum.protocol.exonum.runtime.AnyTx |
ArtifactId | Exonum.protocol.exonum.runtime.ArtifactId |
ArtifactSpec | Exonum.protocol.exonum.runtime.ArtifactSpec |
InstanceSpec | Exonum.protocol.exonum.runtime.InstanceSpec |
InstanceInitParams | Exonum.protocol.exonum.runtime.InstanceInitParams |
GenesisConfig | Exonum.protocol.exonum.runtime.GenesisConfig |
ExecutionError | Exonum.protocol.exonum.runtime.ExecutionError |
CallSite | Exonum.protocol.exonum.runtime.CallSite |
ExecutionStatus | Exonum.protocol.exonum.runtime.ExecutionStatus |
ArtifactState | Exonum.protocol.exonum.runtime.ArtifactState |
InstanceStatus | Exonum.protocol.exonum.runtime.InstanceStatus |
InstanceMigration | Exonum.protocol.exonum.runtime.InstanceMigration |
InstanceState | Exonum.protocol.exonum.runtime.InstanceState |
MigrationStatus | Exonum.protocol.exonum.runtime.MigrationStatus |
ModifiedInstanceInfo | Exonum.protocol.exonum.runtime.ModifiedInstanceInfo |
List of built-in Exonum crypto structures:
Structure | Use as |
---|---|
Hash | Exonum.protocol.exonum.Hash |
PublicKey | Exonum.protocol.exonum.PublicKey |
Signature | Exonum.protocol.exonum.Signature |
List of built-in Exonum common structures:
Structure | Use as |
---|---|
BitVec | Exonum.protocol.exonum.BitVec |
List of built-in Exonum list proof structures:
Structure | Use as |
---|---|
ListProof | Exonum.protocol.exonum.proof.ListProof |
HashedEntry | Exonum.protocol.exonum.proof.HashedEntry |
ListProofEntry | Exonum.protocol.exonum.proof.ListProofEntry |
ProofListKey | Exonum.protocol.exonum.proof.ProofListKey |
List of built-in Exonum map proof structures:
Structure | Use as |
---|---|
MapProof | Exonum.protocol.exonum.proof.MapProof |
OptionalEntry | Exonum.protocol.exonum.proof.OptionalEntry |
MapProofEntry | Exonum.protocol.exonum.proof.MapProofEntry |
Helpers
Generate key pair
const pair = Exonum
publicKey: "..." // 32-byte public key secretKey: "..." // 64-byte secret key
Exonum.keyPair function generates a new random Ed25519 signing key pair using the TweetNaCl cryptographic library.
Get random number
const rand = Exonum
Exonum.randomUint64 function generates a new random Uint64
number of cryptographic quality using the
TweetNaCl cryptographic library.
Converters
Hexadecimal to Uint8Array
const hex = '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b' Exonum // [103, 71, 24, 23, 139, 217, 125, 58, 197, 149, 61, 13, 142, 86, 73, 234, 55, 60, 77, 152, 179, 182, 27, 239, 213, 105, 152, 0, 234, 168, 81, 59]
Hexadecimal to String
const hex = '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b' Exonum // '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'
Uint8Array to Hexadecimal
const arr = 103 71 24 23 139 217 125 58 197 149 61 13 142 86 73 234 55 60 77 152 179 182 27 239 213 105 152 0 234 168 81 59 Exonum // '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'
Uint8Array to BinaryString
const arr = 103 71 24 23 139 217 125 58 197 149 61 13 142 86 73 234 55 60 77 152 179 182 27 239 213 105 152 0 234 168 81 59 Exonum // '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'
Binary String to Uint8Array
const str = '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011' Exonum // [103, 71, 24, 23, 139, 217, 125, 58, 197, 149, 61, 13, 142, 86, 73, 234, 55, 60, 77, 152, 179, 182, 27, 239, 213, 105, 152, 0, 234, 168, 81, 59]
Binary String to Hexadecimal
const str = '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011' Exonum // '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'
String to Uint8Array
const str = 'Hello world' Exonum // [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
Contributing
The contributing to the Exonum Client is based on the same principles and rules as the contributing to exonum-core.
Coding standards
The coding standards are described in the .eslintrc
file.
To help developers define and maintain consistent coding styles between different editors and IDEs
we used .editorconfig
configuration file.
Test coverage
All functions must include relevant unit tests. This applies to both of adding new features and fixing existed bugs.
Changelog
Detailed changes for each release are documented in the CHANGELOG file.
License
Exonum Client is licensed under the Apache License (Version 2.0). See LICENSE for details.