Skip to content
This repository has been archived by the owner on May 10, 2020. It is now read-only.
/ exonum-types Public archive

Exonum type system for JavaScript

License

Notifications You must be signed in to change notification settings

slowli/exonum-types

Repository files navigation

Exonum Serialization for JavaScript

Build status Code coverage Code style

This is the alternative/experimental JavaScript implementation of the type system for the Exonum framework. It provides a library of standard types and the ability to define new types (including generic types).

Each Exonum type can be serialized to and deserialized from JSON, and also serialized into the binary format (but not deserialized from it for the moment). There are also special types, such as ListView<E> and MapView<K, V>, which implement views of Merkelized lists and maps, respectively.

WORK IN PROGRESS. PLEASE DO NOT USE IN PRODUCTION. See exonum-client for the current stable implementation.

Example

import * as exonum from 'exonum-types'

First, define some new types. This one defines a structure with 2 fields, both of which are 32-bit unsigned integers.

const Point = exonum.resolve({
  struct: [
    { name: 'x', type: 'Uint32' },
    { name: 'y', type: 'Uint32' }
  ]
})

Now, it's possible to create type instances:

const a = new Point({ x: 3, y: 4 })
const b = Point.from([10, 20])
const c = a.set('x', 9)

All instances of Exonum types are immutable and persistent.

It is possible to define types with a complex inner structure:

const WeightedPoints = exonum.resolve({
  array: {
    struct: [
      { name: 'point', type: Point },
      { name: 'weight', type: 'Uint64' }
    ]
  }
})

const wPoints = WeightedPoints.from([
  { point: [1, 2], weight: 555 },
  { point: [7, 8], weight: 1000000000 },
])

console.log(wPoints.get(0).x)

Usually, the created datatypes are used in context of cryptographic operations. For example, it is possible to calculate the hash of an Exonum-typed data with crypto.hash() and sign it using crypto.sign():

const { hash, sign, randomKey } = exonum.crypto

console.log(hash(wPoints)) // outputs a SHA-256 hash of the binary serialization

// Ed25519 private key
const secretKey = randomKey()
// 64-byte Ed25519 signature over `wPoints`
const signature = sign(wPoints, secretKey)
// Check that the signature is verifiable
console.log(verify(wPoints, signature, secretKey.pub()))

Another use case is verifying proofs sent to an Exonum lightweight client from the full node. This library provides listView and mapView factories to do that.

Usage

All Exonum types have a common basic interface, which could be expressed in the Flow notation as

interface ExonumType {
  static typeLength(): number | void;
  static from(json: any): ExonumType;
  constructor(json: any);
  byteLength(): number;
  serialize(buffer?: Uint8Array): Uint8Array;
  toJSON(): any;
}

Here, from() and constructor are used to create type instances from JSON; toJSON() performs the opposite transformation; and the remaining methods are used for binary serialization.

Primitive Types

Integers

Similar to Rust, there are eight built-in primitive types: Uint8, Int8, Uint16, Int16, Uint32, Int32, Uint64, Int64. More types can be created with uinteger and integer factories.

Binary serialization: little-endian.
JSON: number or string iff the underlying number exceeds the bound on safe JS integers.

Str

Str represents a string.

Binary serialization: UTF-8.
JSON: string.

Bool

Bool is a boolean type.

Binary serialization: single byte; 0 for false, 1 for true.
JSON: boolean.

Factories

Factories fulfill the same role as generic types in other languages. For example, uinteger(6) creates a new unsigned integer type occupying 6 bytes. In the type specification, factories can be specified in the form { uinteger: 6 }.

  • fixedBuffer
  • integer, uinteger
  • array
  • struct
  • union
  • option
  • message

Type Resolver

Use resolve() to create new types using existing types from the standard library, like in the example above. For more complex operations, you may use resolver; it allows to parse sequential type definitions (including recursive and cross-recursive) fully specified in JSON.

spec.json

[
  {
    "name": "Point",
    "type": {
      "struct": [
        { "name": "x", "type": "Uint32" },
        { "name": "y", "type": "Uint32" }
      ]
    }
  },
  {
    "name": "List",
    "option": {
      "struct": [
        { "name": "head", "type": "Point" },
        { "name": "tail", "type": "List" }
      ]
    }
  }
]

spec.js

import defs from './spec.json'

const myResolver = exonum.resolver.addTypes(defs)
// `myResolver` now knows about `Point` and `List` types:
const List = myResolver.resolve('List')
const lst = List.from([[1, 2], [{ x: 3, y: 4 }, null]])

Just like Exonum types and instances, resolvers are immutable and persistent. This means new types do not influence the standard resolver; calling resolve('List') will throw an exception.

License

Available under the Apache-2.0 license.