This library makes a tested implementation of Ring Signatures readily available and easy to use.
Ring signatures can be used to disconnect a signer from a message while still validating that the message came from a valid signer.
The WASM is bundled as a base64 buffer inline with the JS. This increases the bundle size and causes a large slowdown on initial loading, but is very convenient for use. It is recommended to load the WASM directly (this is currently not supported by this lib out of the box).
Compilation of the Monero Crypto library to WebAssembly, with an easy to use Javascript interface. Tested through the JS interface against Monero test cases.
npm install ring-crypto
const crypto = require("ring-crypto").Crypto;
import { Crypto } from "ring-crypto";
All samples to follow are in Typescript
Hash.data(buf: Buffer) : Promise<{
hash: String
}>
Perform the Cryptonight Hash Function on the input Buffer. Returns Hash object, with member hash of type String.
const msg = "Hash me!";
const hash = (await Crypto.Hash.data(Buffer.from(msg))).hash;
Secret Box is used for synchronous, secret key encryption. This can be used to encrypt data locally to be stored locally or remote safely.
SecretBox.key() : Promise<{
sb_secret: Uint8Array
}>
Generates a key suitable for use in secret box encryption. Returns SecretBoxSecret object, with member sb_secret of type Uint8Array.
SecretBox.box(msg: Buffer, nonce: Buffer, secretKey: SecretBoxSecret) : Promise<Uint8Array>
Encrypts message using specified secret key and random nonce. Returns Uint8Array of encrypted data.
SecretBox.unbox(encryptedData: Buffer, nonce: Buffer, secretKey: SecretBoxSecret) : Promise<Uint8Array>
Decrypts message using specified secret key and random nonce. Returns Uint8Array of unencrypted data.
const secretKey = await Crypto.SecretBox.key();
const msg = Buffer.from("box this message up");
const nonce = Crypto.Random.bytes(Crypto.SecretBox.constants.NONCE_LENGTH);
const box = await Crypto.SecretBox.box(msg, nonce, secretKey);
const unbox = await Crypto.SecretBox.open(box, nonce, secretKey);
const ourMsg = Buffer.from(unbox).toString("utf8");
This can be used to setup secure comms between two parties.
Box.keyPair() : Promise<{
b_public_key: {
b_public_data: Uint8Array
},
b_secret_key: {
b_secret_data: Uint8Array
}
}>
Generates a keyPair suitable for use in shared secret box encryption. Returns BoxKeyPair object.
Box.sharedKey(remotePub: BoxPublicKey, localSec: BoxSecretKey) : Promise<{
b_shared_secret: Uint8Array
}>
Derives a shared secret key using a box public key and a box secret key. Returns a BoxSharedSecret object with member b_shared_secret of type Uint8Array.
Box.box(msg: Buffer, nonce: Buffer, sharedSecretKey: BoxSharedSecret) : Promise<Uint8Array>
Encrypts message using specified shared secret key and random nonce. Returns Uint8Array of encrypted data.
Box.unbox(encryptedData: Buffer, nonce: Buffer, sharedSecretKey: BoxSharedSecret) : Promise<Uint8Array>
Decrypts message using specified shared secret key and random nonce. Returns Uint8Array of unencrypted data.
const keyPairA = await Crypto.Box.keyPair();
const keyPairB = await Crypto.Box.keyPair();
const sharedKeyA = await Crypto.Box.sharedKey(keyPairB.b_public_key, keyPairA.b_secret_key);
const sharedKeyB = await Crypto.Box.sharedKey(keyPairA.b_public_key, keyPairB.b_secret_key);
const msg = Buffer.from("box this message up");
const nonce = Crypto.Random.bytes(Crypto.Box.constants.NONCE_LENGTH);
const box = await Crypto.Box.box(msg, nonce, sharedKeyA);
const unbox = await Crypto.Box.open(box, nonce, sharedKeyB);
Signing use Elliptic Curve Cryptography via curve Ed25519
Sign.keyPair() : Promise<{
s_public_key: {
s_public_data: Uint8Array
},
s_secret_key: {
s_secret_data: Uint8Array
}
}>
Generates a keyPair suitable for use in signing and signature validation. Returns SignKeyPair object.
Sign.sign(msg: Uint8Array, keyPair: SignKeyPair) : Promise<{
s_sig: Uint8Array
}>
Signs a message buffer. Returns a Signature object with member s_sig of type Uint8Array.
Sign.verify(msg: Uint8Array, pub: SignPublicKey, sig: Signature) : Promise<boolean>
Verifies message using specified public key and signature. Returns true if valid, false if invalid.
const keyPair = await Crypto.Sign.keyPair();
const msg = Buffer.from("sign me up!");
const sign = await Crypto.Sign.sign(msg, keyPair);
const valid = await Crypto.Sign.verify(msg, keyPair.s_public_key, sign);
Ring Signatures for plausible deniability in message origination via ECC curve Ed25519
Ring.sign(msg: Uint8Array, secretKeyPair: SignKeyPair, ring: Array<SignPublicKey>) : Promise<{
r_signature: Uint8Array,
r_key_image: {
r_key_image: Uint8Array
}
}>
Generates a Ring Signature against a message buffer. Returns a RingSignature object.
The KeyImage object may be used to link two signatures together.
Ring.verify(msg: Uint8Array, ring: Array<SignPublicKey>, ringSig: RingSignature) : Promise<boolean>
Verifies message using specified ring and ring signature. Returns true if valid, false if invalid.
const ringSize = 20;
const ring = [];
for (let i = 0; i < ringSize - 1; i++) {
const keyPair = await Crypto.Sign.keyPair();
ring.push(keyPair.s_public_key);
}
const secretKeyPair = await Crypto.Sign.keyPair();
ring.push(secretKeyPair.s_public_key);
const msg = Buffer.from("ring sign me!");
const ringSig = await Crypto.Ring.sign(msg, secretKeyPair, ring);
const valid = await Crypto.Ring.verify(msg, ring, ringSig);
Contributions from anyone are welcome. If you see any bugs or can provide enhancements, please open an issue.
The primary language used is Typescript, with an ES5 target of NodeJS and Browsers. After the transpilation step, the output is ran through Babel for flexible compatibility targets. The crypto library is in C/C++ from the Monero project, with a WASM target. The WASM is injected into bundled builds and is called via Typescript bindings.
This project relies on EMSCRIPTEN to function. Follow the install instructions and install to a folder two levels above this folder.
Development has only been tested on Linux, but should be doable on any host OS with a few modifications. The expectation for wasm builds is that the emscripten SDK is located two levels above this folder. This can be modified by editing the build/wasm-build.ts "emsdk" variable.
Testing is done using mocha and are located in the test
sub-directory. The tests run against the various levels of the JS interface.
test/tests.txt
contain a large suite of test cases used in the Monero project. These tests are loaded in test/crypto-wasm.ts
and are all run.
npm run test
MIT Copyright © 2022 Survirtual