Skip to content

deliberative/crypto

Repository files navigation

@deliberative/crypto

codecov Known Vulnerabilities
NPM Version NPM License code-style-prettier
NPM Downloads

This repository is part of the reference implementation of the Deliberative Ledger Protocol, the infrastructure for futuristic democracies.

It does not have any native dependencies and can be used in both Nodejs and the browser.

The API is not completely stable and the code has not undergone external security audits. Use at your own risk.

Introduction

This library relies heavily on libsodium for cryptographic operations, which is a battle-tested project, compiled to WebAssembly for speed. In comparison to tweetnacl this library is much faster. You can see some Benchmarks below.

We have also introduced Shamir secret sharing functionality, where a user can split and restore a secret through the Shamir threshold sharing scheme. To our knowledge, there is not any well-tested open-source implementation of it in WebAssembly and we use it heavily on the Deliberative Ledger protocol.

The library also has mnemonic generation, validation and Ed25519 key pair from mnemonic functionality that was inspired by bip39 but instead of Blake2b we use Argon2 and instead of SHA256 we use SHA512, both of which can be found in libsodium.

Finally, you can calculate Merkle roots, proofs and validate proofs from trees of arbitrary types, as long as you provide a serializer.

Files

The libsodium directory contains a fork of libsodium whose only differences with the master branch of libsodium are name changes to the implementation structs.

The asymmetric directory contains asymmetric key cryptography functions. The encryption/decryption schema is AEAD with forward secrecy, meaning that a throwaway x25519 keypair is generated inside WebAssembly to make the key exchange with the x25519 equivalent of the client's Ed25519 public key.

The symmetric directory contains AEAD encryption/decryption with a symmetric key.

The mnemonic directory contains all the relevant to mnemonic generation functions.

The hash directory contains a sha512 hashing function and an Argon2 with optional salt hashing function.

The merkle directory contains a Merkle root getter function, a Merkle proof artifacts getter, a root from proof getter and a proof verification function.

The shamir directory contains a WASM implementation of a cryptographic technique called Shamir's secret sharing, which allows one to split a secret into random shares that can only recreate it if a threshold of them is combined. Under the hood it uses the libsodium randombytes js method to generate random coefficients for the polynomial.

The utils directory contains helper methods such as cryptographic random slicing of arrays etc.

Getting Started

To get started you have to install the package with

npm install @deliberative/crypto

You can include as ES module

import dcrypto from "@deliberative/crypto";

as CommonJS module

const dcrypto = require("@deliberative/crypto");

or as UMD in the browser with

<script src="https://cdn.jsdelivr.net/npm/@deliberative/crypto@latest/lib/index.min.js"></script>

Examples

You can visit the examples folder, where you will find examples in CommonJS, ES module and html in the browser. For thorough tests of every exposed function you can look the tests folder.

For Curve25519 public key cryptography we have the following methods

import dcrypto from "@deliberative/crypto";

// Words from dictionary create random seed for Ed25519 private key.
// Default entropy is 128bits, which results in 12 words.
const mnemonic = await dcrypto.generateMnemonic();
console.log(`Mnemonic with 128 bits of entropy => 12 words: ${mnemonic}`);
// Max entropy is 256bit, where generateMnemonic(256) results in 24 words.

// Keypair is an object representing an Ed25519 keypair with { publicKey: Uint8Array(32), secretKey: Uint8Array(64) }
const keypair = await dcrypto.keyPairFromMnemonic(mnemonic);
console.log(
  `Keypair from mnemonic: {\n\
  secretKey: ${Buffer.from(keypair.secretKey).toString("hex")}\n\
  publicKey: ${Buffer.from(keypair.publicKey).toString("hex")}\n}\
`,
);

// Generates a Uint8Array(128) full of random bytes
const message = await dcrypto.randomBytes(128);

// EdDSA
const signature = await dcrypto.sign(message, keypair.secretKey);

const verify = await dcrypto.verify(message, signature, keypair.publicKey);
console.log(verify); // true

const hash = await dcrypto.sha512(message);

const keypair2 = await dcrypto.keyPair();

// Forward secrecy box.
// Encryptor generates a random keypair. The public key is contained in the
// "encrypted" box and the secret key is used for the key exchange with
// "keypair2.publicKey" and then it is removed from memory.
const encrypted = await dcrypto.encryptForwardSecrecy(
  message,
  keypair2.publicKey,
  hash,
);

const decrypted = await dcrypto.decryptForwardSecrecy(
  encrypted,
  keypair2.secretKey,
  hash,
);

// To test equality for two Uint8Arrays in js you need to check if each of their elements are equal
// The === operator does not work
for (let i = 0; i < message.length; i++) {
  if (message[i] !== decrypted[i]) console.error("Arrays unequal");
}

const symmetricKey = await dcrypto.randomBytes(
  dcrypto.interfaces.crypto_kx_SESSIONKEYBYTES,
);
const encrypted1 = await dcrypto.encrypt(message, symmetricKey, hash);
const decrypted1 = await dcrypto.decrypt(encrypted1, key, hash);

For Shamir secret sharing you can test the following

import dcrypto from "@deliberative/crypto";

const keypair = await dcrypto.keyPair();

// 100 splitted shares, you need 60 to recreate keypair.secretKey
// Note that you can have max 255 shares and threshold <= shares
const shares = await dcrypto.splitSecret(keypair.secretKey, 100, 60);

// Should be equal to keypair.secretKey
const sk1 = await dcrypto.restoreSecret(shares);

console.log("sk1 and kaypair.secretKey are equal");

// Remove 40 shares to see if it will still work
const lessShares = shares.slice(0, shares.length - 40);

// Should be equal to sk1 and keypair.secretKey
const sk2 = await dcrypto.restoreSecret(lessShares);

console.log("sk2 and kaypair.secretKey are equal");

const evenLessShares = lessShares.slice(0, lessShares.length - 1);

// Should not be equal to sk1 and sk2.
const sk3 = await dcrypto.restoreSecret(evenLessShares);

console.log("sk3 and kaypair.secretKey are NOT equal");

In order to find the Merkle root, proof and to verify the proof you can do the following:

import dcrypto from "@deliberative/crypto";

const randomArrays: Uint8Array[] = [];
for (let i = 0; i < 50; i++) {
  randomArrays.push(await dcrypto.randomBytes(32));
}

// dcrypto.constants.crypto_hash_sha512_BYTES
// Function also accepts any type of data but it then requires a serializer function.
const randomArraysMerkleRoot = await dcrypto.getMerkleRoot(randomArrays);

// Multiple of dcrypto.constants.crypto_hash_sha512_BYTES
const randomArrayMerkleProof = await dcrypto.getMerkleProof(
  randomArrays,
  randomArrays[43],
);

const elementHash = await dcrypto.sha512(randomArrays[43]);

const verify = await dcrypto.verifyMerkleProof(
  elementHash,
  randomArraysMerkleRoot,
  randomArrayMerkleProof,
);

console.log(verify); // should be true

For more examples you can see the tests directory.

Development

If you want to bundle the library yourselves, you need to have Emscripten installed on your machine in order to compile the C code into WebAssembly. We have the -s SINGLE_FILE=1 option for the emcc compiler, which converts the wasm file to a base64 string that will be compiled by the glue js code into a WebAssembly module. This was done for the purpose of interoperability and modularity.

Clone the repo, download the libsodium submodule and install packages:

git clone https://github.com/deliberative/crypto.git
git submodule init
git submodule update
npm i

Once you have all the dependencies installed, you can run

npm run build

and Rollup will generate the UMD, ESM and CJS bundles.

For development compilation you can run

npm run build:debug

and everything will work in debug mode.

Benchmarks

> node benchmarks/hash/index.js
# sha512 native crypto 10000 times
ok ~33 ms

# sha512 @deliberative/crypto 10000 times
ok ~37 ms

# sha512 tweetnacl 10000 times
ok ~84 ms

> node benchmarks/symmetric/index.js
# X25519 e2e encrypt/decrypt native crypto 10000 times
ok ~83 ms

# X25519 @deliberative/crypto 10000 times
ok ~5.73 s

# X25519 tweetnacl 10000 times
ok ~12 s

> node benchmarks/asymmetric/index.js
# Ed25519 sign/verify native crypto 10000 times
ok ~1.34 s

# Ed25519 @deliberative/crypto 10000 times
ok ~1.91 s

# Ed25519 tweetnacl 10000 times
ok ~1.65 min

Releases

Releases are available on Github and npmjs.com

License

The source code is licensed under the terms of the Apache License version 2.0 (see LICENSE).

Copyright

Copyright (C) 2022-2023 Deliberative Technologies P.C.