npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

o2-client

v1.0.0

Published

Light Client for O2 Blockchain

Downloads

16

Readme

Light Client for O2 Blockchain

Build status npm version Coverage Status js-standard-style

A JavaScript library to work with O2 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 O2.

If you are using O2 in your project and want to be listed on our website & GitHub list — write us a line to [email protected].

Library compatibility with O2 core:

| JavaScript light client | O2 core | |---|---| | 0.15.0 | unreleased | | 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

There are several options to include light client library in the application:

The most preferred way is to install O2 Client as a package from npm registry:

npm install o2-client

Otherwise you can download the source code from GitHub and compile it before use in browser.

Include in browser:

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

Usage in Node.js:

let O2 = require('o2-client')

Data types

The definition of data structures is the main part of each application based on O2 blockchain.

On the one hand, each transaction must be signed before sending into blockchain. Before the transaction is signed it is converted into byte array under the hood.

On the other hand, 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.

Converting data into a byte array is called serialization. To get the same serialization result on the client and on the service side, there must be a strict serialization rules. This rules are formed by the data structure definition.

Define data type

let type = O2.newType({
  fields: [
    { name: 'balance', type: O2.Uint32 },
    { name: 'name', type: O2.String }
  ]
})

O2.newType function requires a single argument of Object type with next structure:

| Property | Description | Type | |---|---|---| | fields | List of fields. | Array |

Field structure:

| Field | Description | Type | |---|---|---| | name | Field name. | String | | type | Definition of the field type. | Built-in type, array or custom data type defined by the developer. |

Built-in types

There are several primitive types are built it into the library. These types must be used when constructing custom data types.

| Name | Description | Type | |---|---|---| | Int8 | Number in a range from -128 to 127. | Number | | Int16 | Number in a range from -32768 to 32767. | Number | | Int32 | Number in a range from -2147483648 to 2147483647. | Number | | Int64 | Number in a range from -9223372036854775808 to 9223372036854775807. | Number or String* | | Uint8 | Number in a range from 0 to 255. | Number | | Uint16 | Number in a range from 0 to 65535. | Number | | Uint32 | Number in a range from 0 to 4294967295. | Number | | Uint64 | Number in a range from 0 to 18446744073709551615. | Number or String* | | Float32 | Floating point number in a range from -3.40282347e+38f32 to 3.40282347e+38f32. | Number or String* | | Float64 | Floating point number in a range from -1.7976931348623157e+308f64 to 1.7976931348623157e+308f64. | Number or String* | | Decimal | Decimal fixed point 79228162514264337593543950336 * 10^-28 to 79228162514264337593543950336. | String* | | String | A string of variable length consisting of UTF-8 characters. | String | | Hash | Hexadecimal string. | String | | PublicKey | Hexadecimal string. | String | | Digest | Hexadecimal string. | String | | Uuid | Hexadecimal string. | String | | Bool | Value of boolean type. | Boolean |

*JavaScript limits minimum and maximum integer number. Minimum safe integer in JavaScript is -(2^53-1) which is equal to -9007199254740991. Maximum safe integer in JavaScript is 2^53-1 which is equal to 9007199254740991. For unsafe numbers out of the safe range use String only. To determine either number is safe use built-in JavaScript function Number.isSafeInteger().

Nested data types

Custom data type defined by the developer can be a field of other custom data type.

An example of a nested type:

// Define a nested data type
let date = O2.newType({
  fields: [
    { name: 'day', type: O2.Uint8 },
    { name: 'month', type: O2.Uint8 },
    { name: 'year', type: O2.Uint16 }
  ]
})

// Define a data type
let payment = O2.newType({
  fields: [
    { name: 'date', type: date },
    { name: 'amount', type: O2.Uint64 }
  ]
})

There is no limitation on the depth of nested data types.

Arrays

The array in the light client library corresponds to the vector structure in the Rust language.

O2.newArray function requires a single argument of Object type with next structure:

| Property | Description | Type | |---|---|---| | type | Definition of the field type. | Built-in type, array or custom data type defined by the developer. |

An example of an array type field:

// Define an array
let year = O2.newArray({
  type: O2.Uint16
})

// Define a data type
let type = O2.newType({
  fields: [
    { name: 'years', type: year }
  ]
})

An example of an array nested in an array:

// Define an array
let distance = O2.newArray({
  type: O2.Uint32
})

// Define an array with child elements of an array type
let distances = O2.newArray({
  type: distance
})

// Define a data type
let type = O2.newType({
  fields: [
    { name: 'measurements', type: distances }
  ]
})

Serialization

Each serializable data type has its (de)serialization rules, which govern how the instances of this type are (de)serialized from/to a binary buffer. Check serialization guide for details.

Signature of serialize function:

type.serialize(data)

| Argument | Description | Type | |---|---|---| | data | Data to serialize. | Object | | type | Definition of the field type. | Custom data type or transaction. |

An example of serialization into a byte array:

// Define a data type
let user = O2.newType({
  fields: [
    { name: 'firstName', type: O2.String },
    { name: 'lastName', type: O2.String },
    { name: 'age', type: O2.Uint8 },
    { name: 'balance', type: O2.Uint32 }
  ]
})

// Data to be serialized
const data = {
  firstName: 'John',
  lastName: 'Doe',
  age: 28,
  balance: 2500
}


// Serialize
let buffer = user.serialize(data) // [21, 0, 0, 0, 4, 0, 0, 0, 25, 0, 0, 0, 3, 0, 0, 0, 28, 196, 9, 0, 0, 74, 111, 104, 110, 68, 111, 101]

The value of the buffer array:

Serialization example

Hash

O2 uses cryptographic hashes of certain data for transactions and proofs.

Different signatures of the hash function are possible:

O2.hash(data, type)
type.hash(data)

| 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 type
let user = O2.newType({
  fields: [
    { name: 'firstName', type: O2.String },
    { name: 'lastName', type: O2.String },
    { name: 'age', type: O2.Uint8 },
    { name: 'balance', type: O2.Uint32 }
  ]
})

// Data that has been hashed
const data = {
  firstName: 'John',
  lastName: 'Doe',
  age: 28,
  balance: 2500
}

// Get a hash
let hash = user.hash(data) // 1e53d91704b4b6adcbea13d2f57f41cfbdee8f47225e99bb1ff25d85474185af

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

O2.hash(buffer)

| Argument | Description | Type | |---|---|---| | buffer | Byte array. | Array or Uint8Array. |

An example of byte array hash calculation:

const arr = [132, 0, 0, 5, 89, 64, 0, 7]

let hash = O2.hash(arr) // 9518aeb60d386ae4b4ecc64e1a464affc052e4c3950c58e32478c0caa9e414db

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 O2.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:

O2.sign(secretKey, data, type)
type.sign(secretKey, data)
O2.sign(secretKey, buffer)

| 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 type
let user = O2.newType({
  fields: [
    { name: 'firstName', type: O2.String },
    { name: 'lastName', type: O2.String },
    { name: 'age', type: O2.Uint8 },
    { name: 'balance', type: O2.Uint32 }
  ]
})

// Data to be signed
const data = {
  firstName: 'John',
  lastName: 'Doe',
  age: 28,
  balance: 2500
}

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

// Sign the data
let signature = O2.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:

O2.verifySignature(signature, publicKey, data, type)
type.verifySignature(signature, publicKey, data)

| 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 type
let user = O2.newType({
  fields: [
    { name: 'firstName', type: O2.String },
    { name: 'lastName', type: O2.String },
    { name: 'age', type: O2.Uint8 },
    { name: 'balance', type: O2.Uint32 }
  ]
})

// Data that has been signed
const data = {
  firstName: 'John',
  lastName: 'Doe',
  age: 28,
  balance: 2500
}

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

// Signature obtained upon signing using secret key
const signature = '41884c5270631510357bb37e6bcbc8da61603b4bdb05a2c70fc11d6624792e07' +
 'c99321f8cffac02bbf028398a4118801a2cf1750f5de84cc654f7bf0df71ec00'

// Verify the signature
let result = O2.verifySignature(signature, publicKey, data, user) // true

Transactions

Transaction in O2 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 O2.

Define transaction

An example of a transaction definition:

let sendFunds = O2.newTransaction({
  author: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  service_id: 130,
  message_id: 0,
  fields: [
    { name: 'from', type: O2.Hash },
    { name: 'to', type: O2.Hash },
    { name: 'amount', type: O2.Uint64 }
  ]
})

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

| Property | Description | Type | |---|---|---| | author | Author's public key as hexadecimal string. | String | | service_id | Service ID. | Number | | message_id | Message ID. | Number | | fields | List of fields. | 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 pair
const keyPair = {
  publicKey: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  secretKey: '978e3321bd6331d56e5f4c2bdb95bf471e95a77a6839e68d4241e7b0932ebe2b' +
  'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a'
}

// Transaction data to be signed
const data = {
  from: 'fa7f9ee43aff70c879f80fa7fd15955c18b98c72310b09e7818310325050cf7a',
  to: 'f7ea8fd02cb41cc2cd45fd5adc89ca1bf605b2e31f796a3417ddbcd4a3634647',
  amount: 1000
}

// 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:

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

sendFunds.send(explorerBasePath, data, secretKey, attempts, timeout)

| 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 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.

O2.sendQueue(explorerBasePath, transactions, secretKey, attempts, timeout)

| 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:

Cryptographic proofs

A cryptographic proof is a format in which a O2 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 O2.

Merkle tree proof

let elements = O2.merkleProof(rootHash, count, tree, range, type)

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 = new O2.MapProof(json, KeyType, ValueType)
console.log(proof.entries)

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

O2.verifyBlock(data, validators)

Each new block in O2 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.

Returns true if verification is succeeded or false if it is failed.

| 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

O2.verifyTable(proof, stateHash, serviceId, tableIndex)

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 | | serviceId | Service ID. | Number | | tableIndex | Table index. | Number |

Helpers

Generate key pair

const pair = O2.keyPair()
{
  publicKey: "...", // 32-byte public key
  secretKey: "..." // 64-byte secret key
}

O2.keyPair function generates a new random Ed25519 signing key pair using the TweetNaCl cryptographic library.

Get random number

const rand = O2.randomUint64()

O2.randomUint64 function generates a new random Uint64 number of cryptographic quality using the TweetNaCl cryptographic library.

Converters

Hexadecimal to Uint8Array

const hex = '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'

O2.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'

O2.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])

O2.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])

O2.uint8ArrayToBinaryString(arr) // '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'

Binary String to Uint8Array

const str = '0110011101000111000110000001011110001011110110010111110100111010110001011001010100111101000011011000111001010110010010011110101000110111001111000100110110011000101100111011011000011011111011111101010101101001100110000000000011101010101010000101000100111011'

O2.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'

O2.binaryStringToHexadecimal(str) // '674718178bd97d3ac5953d0d8e5649ea373c4d98b3b61befd5699800eaa8513b'

String to Uint8Array

const str = 'Hello world'

O2.stringToUint8Array(str) // [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]

Contributing

The contributing to the O2 Client is based on the same principles and rules as the contributing to o2-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

O2 Client is licensed under the Apache License (Version 2.0). See LICENSE for details.