0.17.4 • Published 4 years ago

exonum-client-cis v0.17.4

Weekly downloads
1
License
Apache-2.0
Repository
-
Last release
4 years ago

Light Client for Exonum Blockchain

Build status npm version Coverage Status js-standard-style

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 clientExonum core
0.17.10.12.*
0.16.90.11.*
0.16.90.10.*
0.13.00.9.*
0.10.20.8.*
0.9.00.7.*
0.6.10.6.*
0.6.10.5.*
0.3.00.4.0
0.3.00.3.0
0.2.00.2.0
0.1.10.1.*

Getting started

First of all, you should have installed these tools and libraries:

  • C/C++ compiler
  • libtool
  • automake
  • autoconf

On MacOS: 1. Install XCode 1. 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:

<script src="node_modules/exonum-client/dist/exonum-client.min.js"></script>

Usage in Node.js:

let Exonum = require('exonum-client')

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 = new Type('CustomMessage')
Message.add(new Field('balance', 1, 'uint32'))
Message.add(new Field('name', 2, 'string'))

let type = Exonum.newType(Message)

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:

Exonum.hash(data, type)
type.hash(data)
ArgumentDescriptionType
dataData to be processed using a hash function.Object
typeDefinition of the data type.Custom data type or transaction.

An example of hash calculation:

// Define a data structure
let Message = new Type('CustomMessage')
Message.add(new Field('balance', 1, 'uint32'))
Message.add(new Field('name', 2, 'string'))

// Define a data type
const User = Exonum.newType(Message)

// Data that has been hashed
const data = {
  balance: 100,
  name: 'John Doe'
}

// Get a hash
let hash = User.hash(data) // 9786347be1ab7e8f3d68a49ef8a995a4decb31103c53565a108170dec4c1c2fa

It is also possible to get a hash from byte array:

Exonum.hash(buffer)
ArgumentDescriptionType
bufferByte 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 = Exonum.hash(arr) // 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.sign(secretKey, data, type)
type.sign(secretKey, data)
Exonum.sign(secretKey, buffer)
ArgumentDescriptionType
secretKeySecret key as hexadecimal string.String
dataData to be signed.Object
typeDefinition of the data type.Custom data type or transaction.
bufferByte array.Array or Uint8Array.

The sign function returns value as hexadecimal String.

An example of data signing:

// Define a data structure
let Message = new Type('CustomMessage')
Message.add(new Field('balance', 1, 'uint32'))
Message.add(new Field('name', 2, 'string'))

// Define a data type
const User = Exonum.newType(Message)

// Data that has been hashed
const data = {
  balance: 100,
  name: 'John Doe'
}

// Define a signing key pair
const keyPair = {
  publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' +
  'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a'
}

// Sign the data
let signature = Exonum.sign(keyPair.secretKey, data, User)

Verify signature

The signature can be verified using the author's public key.

There are two possible signatures of the verifySignature function:

Exonum.verifySignature(signature, publicKey, data, type)
type.verifySignature(signature, publicKey, data)
ArgumentDescriptionType
signatureSignature as hexadecimal string.String
publicKeyAuthor's public key as hexadecimal string.String
dataData that has been signed.Object
typeDefinition 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 structure
let Message = new Type('CustomMessage')
Message.add(new Field('balance', 1, 'uint32'))
Message.add(new Field('name', 2, 'string'))

// Define a data type
const User = Exonum.newType(Message)

// Data that has been hashed
const data = {
  balance: 100,
  name: 'John Doe'
}

// Define a signing key pair
const keyPair = {
  publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' +
  'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a'
}

// Signature obtained upon signing using secret key
const signature = 'a4cf7c457e3f4d54ef0c87900e7c860d2faa17a8dccbaafa573a3a960cda3f66' +
 '27911088138526d9d7e46feba471e6bc7b93262349a5ed18262cbc39c8a47b04'

// Verify the signature
let result = Exonum.verifySignature(signature, keyPair.publicKey, data, User) // 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:

1) Describe the fields of transaction using custom data types; 2) Sign data of transaction using signing key pair; 3) Send transaction to the blockchain.

Read more about transactions in Exonum.

Define transaction

An example of a transaction definition:

let Transaction = new Type('CustomMessage')
Transaction.add(new Field('from', 1, 'string'))
Transaction.add(new Field('to', 2, 'string'))
Transaction.add(new Field('amount', 3, 'uint32'))

let sendFunds = Exonum.newTransaction({
  author: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  chain_id: 0,
  instance_id: 130,
  method_id: 0,
  schema: Transaction
})

Exonum.newTransaction function requires a single argument of Object type with next structure:

PropertyDescriptionType
authorAuthor's public key as hexadecimal string.String
instance_idInstance ID.Number
method_idMethod ID.Number
schemaProtobuf data structure.Array
signatureSignature 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 pair
const keyPair = {
  publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' +
  'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a'
}

// Transaction data to be signed
const data = {
  from: 'John',
  to: 'Adam',
  amount: 50
}

// Sign the data
let signature = sendFunds.sign(keyPair.secretKey, data)

Send transaction

To submit transaction to the blockchain send function can be used.

There are two possible signatures of the send function:

Exonum.send(explorerBasePath, type, data, secretKey, attempts, timeout)

sendFunds.send(explorerBasePath, data, secretKey, attempts, timeout)
PropertyDescriptionType
explorerBasePathAPI address of transaction explorer on a blockchain node.String
typeDefinition of the transaction.Transaction.
dataData that has been signed.Object
secretKeySecret key as hexadecimal string.String
attemptsNumber 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
timeoutTimeout 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 address
const explorerBasePath = 'http://127.0.0.1:8200/api/explorer/v1/transactions'

sendFunds.send(explorerBasePath, data, keyPair.secretKey).then(txHash => {})

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.sendQueue(explorerBasePath, transactions, secretKey, attempts, timeout)
PropertyDescriptionType
explorerBasePathAPI address of transaction explorer on a blockchain node.String
transactionsList of transactions.Array
secretKeySecret key as hexadecimal string.String
attemptsNumber 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
timeoutTimeout between attempts to check each transaction status. Optional. Default value is 500.Number

Transaction structure:

FieldDescriptionType
typeDefinition of the transaction.Transaction.
dataTransaction 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:

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.merkleProof(rootHash, count, tree, range, type)

The merkleProof method is used to validate the Merkle tree and extract a list of data elements.

ArgumentDescriptionType
rootHashThe root hash of the Merkle tree as hexadecimal string.String
countThe total number of elements in the Merkle tree.Number
proofNodeThe Merkle tree.Object
rangeAn array of two elements of Number type. Represents list of obtained elements: [startIndex; endIndex).Array
typeDefinition 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 = new Exonum.MapProof(json, KeyType, ValueType)
console.log(proof.entries)

The MapProof class is used to validate proofs for Merkelized maps.

ArgumentDescriptionType
jsonThe JSON presentation of the proof obtained from a full node.Object
KeyTypeData type for keys in the Merkelized map.Custom or built-in data type
ValueTypeData type for values in the Merkelized map.Custom data type

The returned object has the following fields:

FieldDescriptionType
merkleRootHexadecimal hash of the root of the underlying Merkelized mapString
missingKeysSet of keys which the proof asserts as missing from the mapSet<KeyType>
entriesMap of key-value pairs that the are proved to exist in the mapMap<KeyType, ValueType>

An example of using a MapProof.

Integrity checks

Verify block

Exonum.verifyBlock(data, validators)

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.

ArgumentDescriptionType
dataStructure with block and precommits.Object
validatorsAn array of validators public keys as a hexadecimal strings.Array

An example of block verification.

Verify table

Exonum.verifyTable(proof, stateHash, instanceId, tableIndex)

Verify table existence in the root tree.

Returns root hash for the table as hexadecimal String.

ArgumentDescriptionType
proofThe JSON presentation of the proof obtained from a full node.Object
stateHashHash of current blockchain state stored in each block.String
instanceIdInstance ID.Number
tableIndexTable index.Number

Built-in structures

List of built-in Exonum blockchain structures:

StructureUse as
AdditionalHeadersExonum.protocol.exonum.AdditionalHeaders
BlockExonum.protocol.exonum.Block
TxLocationExonum.protocol.exonum.TxLocation
CallInBlockExonum.protocol.exonum.CallInBlock
ValidatorKeysExonum.protocol.exonum.ValidatorKeys

List of built-in Exonum consensus structures:

StructureUse as
SignedMessageExonum.protocol.exonum.consensus.SignedMessage
VerifiedExonum.protocol.exonum.consensus.Verified
ConnectExonum.protocol.exonum.consensus.Connect
StatusExonum.protocol.exonum.consensus.Status
ProposeExonum.protocol.exonum.consensus.Propose
PrevoteExonum.protocol.exonum.consensus.Prevote
PrecommitExonum.protocol.exonum.consensus.Precommit
BlockResponseExonum.protocol.exonum.consensus.BlockResponse
TransactionsResponseExonum.protocol.exonum.consensus.TransactionsResponse
ProposeRequestExonum.protocol.exonum.consensus.ProposeRequest
TransactionsRequestExonum.protocol.exonum.consensus.TransactionsRequest
PrevotesRequestExonum.protocol.exonum.consensus.PrevotesRequest
PeersRequestExonum.protocol.exonum.consensus.PeersRequest
BlockRequestExonum.protocol.exonum.consensus.BlockRequest
PoolTransactionsRequestExonum.protocol.exonum.consensus.PoolTransactionsRequest
ExonumMessageExonum.protocol.exonum.consensus.ExonumMessage

List of built-in Exonum key-value sequence structures:

StructureUse as
KeyValueExonum.protocol.exonum.KeyValue
KeyValueSequenceExonum.protocol.exonum.KeyValueSequence

List of built-in Exonum proofs structures:

StructureUse as
BlockProofExonum.protocol.exonum.BlockProof
IndexProofExonum.protocol.exonum.IndexProof

List of built-in Exonum runtime structures:

StructureUse as
CallInfoExonum.protocol.exonum.runtime.CallInfo
AnyTxExonum.protocol.exonum.runtime.AnyTx
ArtifactIdExonum.protocol.exonum.runtime.ArtifactId
ArtifactSpecExonum.protocol.exonum.runtime.ArtifactSpec
InstanceSpecExonum.protocol.exonum.runtime.InstanceSpec
InstanceInitParamsExonum.protocol.exonum.runtime.InstanceInitParams
GenesisConfigExonum.protocol.exonum.runtime.GenesisConfig
ExecutionErrorExonum.protocol.exonum.runtime.ExecutionError
CallSiteExonum.protocol.exonum.runtime.CallSite
ExecutionStatusExonum.protocol.exonum.runtime.ExecutionStatus
ArtifactStateExonum.protocol.exonum.runtime.ArtifactState
InstanceStatusExonum.protocol.exonum.runtime.InstanceStatus
InstanceMigrationExonum.protocol.exonum.runtime.InstanceMigration
InstanceStateExonum.protocol.exonum.runtime.InstanceState
MigrationStatusExonum.protocol.exonum.runtime.MigrationStatus
ModifiedInstanceInfoExonum.protocol.exonum.runtime.ModifiedInstanceInfo

List of built-in Exonum crypto structures:

StructureUse as
HashExonum.protocol.exonum.Hash
PublicKeyExonum.protocol.exonum.PublicKey
SignatureExonum.protocol.exonum.Signature

List of built-in Exonum common structures:

StructureUse as
BitVecExonum.protocol.exonum.BitVec

List of built-in Exonum list proof structures:

StructureUse as
ListProofExonum.protocol.exonum.proof.ListProof
HashedEntryExonum.protocol.exonum.proof.HashedEntry
ListProofEntryExonum.protocol.exonum.proof.ListProofEntry
ProofListKeyExonum.protocol.exonum.proof.ProofListKey

List of built-in Exonum map proof structures:

StructureUse as
MapProofExonum.protocol.exonum.proof.MapProof
OptionalEntryExonum.protocol.exonum.proof.OptionalEntry
MapProofEntryExonum.protocol.exonum.proof.MapProofEntry

Helpers

Generate key pair

const pair = Exonum.keyPair()
{
  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.randomUint64()

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.hexadecimalToUint8Array(hex) // [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.hexadecimalToBinaryString(hex) // '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'

Uint8Array to Hexadecimal

const arr = new Uint8Array([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.uint8ArrayToHexadecimal(arr) // '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'

Uint8Array to BinaryString

const arr = new Uint8Array([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.uint8ArrayToBinaryString(arr) // '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'

Binary String to Uint8Array

const str = '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'

Exonum.binaryStringToUint8Array(str) // [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.binaryStringToHexadecimal(str) // '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'

String to Uint8Array

const str = 'Hello world'

Exonum.stringToUint8Array(str) // [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.