@smilo-platform/smilo-commons-js-web
v0.1.13
Published
Common set of Javascript functionality for the Smilo Platform.
Downloads
15
Readme
Smilo Commons JS for Web
The Smilo Commons JS library contains several building blocks to make it easier for developers to integrate with the Smilo Blockchain in a Javascript environment.
Installation
You can install this package with NPM:
npm install @smilo-platform/smilo-commons-js-web
Next make sure the file 'smilo-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 ('smilo-*.d.ts') with a more detailed description of each part of this library.
Merkle Trees
The Smilo blockchain uses a Merkle Tree to sign transactions.
Generating
To generate a new Merkle Tree you must use the MerkleTreeBuilder
class.
var builder = new Smilo.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 Smilo.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 Smilo.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 Smilo.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 Smilo 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 Smilo 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 Smilo.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 Smilo.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 Smilo.FixedBigNumber(0, 0), // The fee for the miners.
assetId: "000x0123", // The asset we are sending.
inputAmount: new Smilo.FixedBigNumber(100, 0), // The amount of the asset we want to send.
transactionOutputs: [
{
outputAddress: "TO_ADDRESS", // The address we are sending to.
outputAmount: new Smilo.FixedBigNumber(100, 0) // The amount of we are sending to this address.
}
]
};
// Compute data hash for transaction
var transactionHelper = new Smilo.TransactionHelper();
transaction.dataHash = transactionHelper.getDataHash(transaction);
// Sign transaction
var signer = new Smilo.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 Smilo.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 Smilo 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 Smilo
Smilo 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 Smilo.FixedBigNumber(100, 0);
100 SmiloPay
SmiloPay does support fractional numbers up to 18 decimals.
var amount = new Smilo.FixedBigNumber(100, 18);
Comparing big numbers
Big numbers can be compared against each other:
var bn1 = new Smilo.FixedBigNumber(100, 18);
var bn2 = new Smilo.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 Smilo.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 Smilo.FixedBigNumber(100, 18);
var bn2 = new Smilo.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 Smilo.FixedBigNumber(100, 18);
var bn2 = new Smilo.FixedBigNumber(200, 18);
var bn3 = new Smilo.FixedBigNumber(300, 18);
// (bn1 + bn2) * bn3;
bn1.add(bn2).mul(bn3);
You can also mix FixedBigNumbers
with Javascript numbers or strings:
var bn1 = new Smilo.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 Smilo Commons JS has integrated support for BIP39.
The example below shows how to generate a mnemonic phrase.
var bip39 = new Smilo.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 Smilo.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 Smilo.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 Smilo Commons JS library combines BIP32 with BIP44.
To generate a private key do:
var bip32 = new Smilo.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 Smilo.BIP32();
var coinType = ...;
var index = ...;
var privateKey = bip32.getPrivateKey("SOME_RANDOM_SEED", coinType, index);