@proofmeid/commons-js-web
TypeScript icon, indicating that this package has built-in type declarations

0.0.2 • Public • Published

ProofmeId Commons JS for Web

The ProofmeId Commons JS library contains several building blocks to make it easier for developers to integrate with the Didux Blockchain in a Javascript environment.

Installation

You can install this package with NPM:

npm install @proofmeid/commons-js-web

Next make sure the file 'proofmeid-web.js' is loaded in your web page.

A note about UglifyJS

This library does not play nicely with the typeofs compress option of UglifyJS.

When this compression option is enabled an error will be thrown when generating a Merkle Tree.

If you are using UglifyJS either do not uglify/compress this library or disable this option.

A minimal working configuration file for UglifyJS could look like this:

{
    "compress": {
        "typeofs": false
    }
}

Examples

Below you will find several examples on how to use the most common functionality of the library. As part of this library we also provide a Typescript definitions file ('proofmeid-*.d.ts') with a more detailed description of each part of this library.

Generating

To generate a new Merkle Tree you must use the MerkleTreeBuilder class.

var builder = new ProofmeId.MerkleTreeBuilder();

var privateKey = "PRIVATE_KEY";
var layerCount = 14;

builder.generate(
    privateKey, 
    layerCount, 
    function(progress) {
        var progressPercentage = Math.round(progress * 100);

        console.log(`Progress = ${ progressPercentage }%`);
    }
).then(
    function(merkleTree) {
        // Merkle Tree generated!
    },
    function(error) {
        // Something went wrong...
        console.error(error);
    }
);

For web environments Web Workers will be used to efficiently generate the layers of the Merkle Tree. Node environments will use child processes to achieve the same effect.

Write to storage

The MerkleTreeBuilder is only responsible for creating a Merkle Tree. It is not responsible for storing a Merkle Tree.

To serialize and deserialize a Merkle Tree you can use the MerkleTreeSerializer class.

This class requires a storage manager responsible for the actual writing and reading from whatever storage device you require.

A storage manager is defined, in Typescript, as shown below:

interface IStorageManager {
    /**
     * Reads the content of a file as text.
     */
    read(path: string): Promise<string>;
    /**
     * Writes the given text data to the given path.
     */
    write(path: string, data: string): Promise<void>;

    /**
     * Reads the content of a file and parses it as JSON.
     * A Javascript object will be returned.
     */
    readJSON<T>(path: string): Promise<T>;
    /**
     * Writes the given Javascript object as JSON to the given path.
     */
    writeJSON(path: string, data: any): Promise<void>;

    /**
     * Removes the data at the given path from storage.
     */
    remove(path: string): Promise<void>;
}

A simple storage manager writing to local storage could then look like this:

function LocalStorageManager() {
    this.read = function(path) {
        var data = localStorage.getItem(path);

        return Promise.resolve(data);
    }    
    this.write = function(path, data) {
        localStorage.setItem(path, data);

        return Promise.resolve();
    }

    this.readJSON = function(path) {
        return this.read(path).then(
            function(data){ 
                return JSON.parse(data);
            }
        );
    }
    this.writeJSON = function(path, data) {
        return this.write(path, JSON.stringify(data));
    }

    this.remove = function(path) {
        localStorage.removeItem(path);

        return Promise.resolve();
    }
}

The storage manager will be passed a fully encrypted Merkle Tree.

To serialize a Merkle Tree you could do this:

// We use the LocalStorageManager described above.
var storageManager = new LocalStorageManager();
var serializer = new ProofmeId.MerkleTreeSerializer(storageManager);
var merkleTree = ...;

serializer.serialize(merkleTree).then(
    function() {
        // Merkle Tree was written to storage.
    },
    function(error) {
        // Failed to write Merkle Tree to storage.
    }
);

To deserialize a Merkle Tree you could do this:

// We use the LocalStorageManager described above.
var storageManager = new LocalStorageManager();
var serializer = new ProofmeId.MerkleTreeSerializer(storageManager);

serializer.serialize("path/to/merkle/tree").then(
    function(merkleTree) {
        // Merkle tree was read from storage.
    },
    function(error) {
        // Something went wrong reading the Merkle Tree.
    }
);

To clean a Merkle Tree from storage you could do this:

// We use the LocalStorageManager described above.
var storageManager = new LocalStorageManager();
var serializer = new ProofmeId.MerkleTreeSerializer(storageManager);

serializer.clean("path/to/merkle/tree").then(
    function() {
        // Merkle tree was cleaned from storage
    },
    function(error) {
        // Something went wrong cleaning the Merkle Tree from storage
    }
);

Signatures

Every transaction on the Didux.io Blockchain has to be cryptographically signed with a valid Lamport signature. Because a Lamport private key, used to create a Lamport signature, should only ever be used once we use a Merkle Tree to easily provide many private keys derived from a single root key.

To sign a message you therefore need a Merkle Tree. You also need to provide a signature index which specifies which private key in the Merkle Tree should be used. The ProofmeId Commons JS library does not track which index has been used and/or is available.

Sign a message

To sign a message use the MerkleLamportSigner class.

var signer = new ProofmeId.MerkleLamportSigner();

var merkleTree = ...;           // Your Merkle Tree
var data = "Hello World";       // Data you want to sign
var privateKey = "PRIVATE_KEY"; // Private key used to generate the Merkle Tree
var signatureIndex = 0;         // Merkle Tree leaf index

var signature = signer.getSignature(merkleTree, data, privateKey, signatureIndex);

The signature is a string combining the signature and the authentication path. The authentication path is used when other people want to verify the signature.

Verify a signature

To verify a message signature use the MerkleLamportVerifier class.

var verifier = new ProofmeId.MerkleLamportVerifier();

var data = "Hello World";       // The message
var signature = ...;            // The signature part of the message
var signatureIndex = 0;         // The Merkle tree leaf index used to sign this message
var layerCount = 14;            // The amount of layers of the Merkle Tree
var expectedRootAddress = ...;  // The expected root address e.g. public key

if(verifier.verifyMerkleSignature(data, signature, signatureIndex, layerCount, expectedRootAddress)) {
    // Valid signature
}
else {
    // Invalid signature
}

Transactions

The below example demonstrates how to create and sign a transaction. Next you could send this transaction to the blockchain for processing.

Note how we use the TransactionHelper class. This class contains several methods which help when creating a transaction.

// Create the base of the transaction
var transaction = {
    timestamp: Date().now(),
    inputAddress: "FROM_ADDRESS",                               // The address we are sending from.
    fee: new ProofmeId.FixedBigNumber(0, 0),                       // The fee for the miners.
    assetId: "000x0123",                                        // The asset we are sending.
    inputAmount: new ProofmeId.FixedBigNumber(100, 0),             // The amount of the asset we want to send.
    transactionOutputs: [
        {
            outputAddress: "TO_ADDRESS",                        // The address we are sending to.
            outputAmount: new ProofmeId.FixedBigNumber(100, 0)     // The amount of we are sending to this address.
        }
    ]
};

// Compute data hash for transaction
var transactionHelper = new ProofmeId.TransactionHelper();
transaction.dataHash = transactionHelper.getDataHash(transaction);

// Sign transaction
var signer = new ProofmeId.MerkleLamportSigner();

var merkleTree = ...;           // Your Merkle Tree
var privateKey = "PRIVATE_KEY"; // Private key used to generate the Merkle Tree
var signatureIndex = 0;         // Merkle Tree leaf index

transaction.signatureData = signer.getSignature(merkleTree, transactionHelper.transactionToString(transaction), privateKey, signatureIndex);
transaction.signatureIndex = signatureIndex;

You can also add input data to a transaction. This data will be used by smart contracts. This input data must be formatted correctly as shown in the example below.

var transaction = {...};
var transactionHelper = new ProofmeId.TransactionHelper();

transaction.inputData = transactionHelper.formatInputData("Your input data goes here");

Big Number

Because Javascript numbers are not precise enough to accurately describe all transaction amounts on the Didux blockchain we use big numbers instead.

These numbers are defined with two parameters. The initial value and the amount of decimals.

We have defined the FixedBigNumber class to easily deal with big numbers.

Defining big numbers

To define a FixedBigNumber you need to pass an initial value and the amount of decimals.

100 Didux

Didux does not support fractional numbers. Therefore we set the amount of decimals to 0. Values like 0.1 can therefore never be defined.

var amount = new ProofmeId.FixedBigNumber(100, 0);

100 DiduxPay

DiduxPay does support fractional numbers up to 18 decimals.

var amount = new ProofmeId.FixedBigNumber(100, 18);

Comparing big numbers

Big numbers can be compared against each other:

var bn1 = new ProofmeId.FixedBigNumber(100, 18);
var bn2 = new ProofmeId.FixedBigNumber(200, 18);

// ==
bn1.eq(bn2);    // false

// >
bn1.gt(bn2);    // false

// >=
bn1.gte(bn2);   // false

// <
bn1.lt(bn2);    // true

// <=
bn1.lte(bn2);   // true

You can also mix FixedBigNumbers with Javascript numbers or strings:

var bn1 = new ProofmeId.FixedBigNumber(100, 18);

bn1.eq(100);    // true

bn1.lte("90");  // false

Doing math on big numbers

Basic mathematical operations can be performed on big numbers. The decimal count of the big number you call these methods on are preserved. For example if you multiply a FixedBigNumber with 10 decimals with another FixedBigNumber with 20 decimals the resulting FixedBigNumber will have 10 decimals.

var bn1 = new ProofmeId.FixedBigNumber(100, 18);
var bn2 = new ProofmeId.FixedBigNumber(200, 18);

// Multiply
bn1.mul(bn2);

// Divide
bn1.div(bn2);

// Add
bn1.add(bn2);

// Subtract
bn1.sub(bn2);

These operations return a new FixedBigNumber so the original operand remain unaltered. This also allows for chaining function calls:

var bn1 = new ProofmeId.FixedBigNumber(100, 18);
var bn2 = new ProofmeId.FixedBigNumber(200, 18);
var bn3 = new ProofmeId.FixedBigNumber(300, 18);

// (bn1 + bn2) * bn3;
bn1.add(bn2).mul(bn3);

You can also mix FixedBigNumbers with Javascript numbers or strings:

var bn1 = new ProofmeId.FixedBigNumber(100, 18);

bn1.add(10);

bn1.mul("100");

BIP39

The BIP39 standard can be used to generate mnemonic phrases which serve as a base for a private key seed.

The ProofmeId Commons JS has integrated support for BIP39.

The example below shows how to generate a mnemonic phrase.

var bip39 = new ProofmeId.BIP39();

var mnemonicPhrase = bip39.generate(256);

To generate a phrase a strength in bits must be given. This number will determine how many words are generated. You cannot however just enter any number.

For reference the following parameters generate X amount of words:

  • 128 bits = 12 words
  • 160 bits = 15 words
  • 192 bits = 18 words
  • 224 bits = 21 words
  • 256 bits = 24 words

Given a mnemonic passphrase you can also validate its correctness:

var bip39 = new ProofmeId.BIP39();

var phrase = ...;

var result = bip39.check(phrase);
if(result.isValid) {
    // Phrase is valid
}
else if(!result.isBlocking) {
    // Phrase might not be valid
    // This happens when the checksum is invalid
    // At the very least we can say the phrase was not 
    // generated by this library. It can still be used
    // to generate a seed though.
}
else {
    // Phrase is certainly not valid
    // This can mean:
    // - The phrase has an invalid size
    // - The phrase contains an unrecognized word
    // Check result.errorMessage for more info
}

To convert the phrase to a seed ready for a random number generator do:

var bip39 = new ProofmeId.BIP39();

var phrase = ...;

// Seed without passphrase
var seed = bip39.toSeed(phrase);

// Seed with extra passphrase
var seedWithPassphrase = bip39.toSeed(phrase, "PASSPHRASE");

The generated seed can then be used to generate a private key (see chapter BIP32).

BIP32

The BIP32 standard is used to generate a private key from a seed. The ProofmeId Commons JS library combines BIP32 with BIP44.

To generate a private key do:

var bip32 = new ProofmeId.BIP32();

var privateKey = bip32.getPrivateKey("SOME_RANDOM_SEED");

This will generate a private key using the BIP32 path m/44'/0x1991'/0'/0/0.

You can change the coin type and index:

var bip32 = new ProofmeId.BIP32();

var coinType = ...;
var index = ...;

var privateKey = bip32.getPrivateKey("SOME_RANDOM_SEED", coinType, index);

Package Sidebar

Install

npm i @proofmeid/commons-js-web

Weekly Downloads

5

Version

0.0.2

License

Apache-2.0

Unpacked Size

5.03 MB

Total Files

11

Last publish

Collaborators

  • elkan
  • danielsmilo