cosmjs-util/packages/crypto/src/libsodium.ts
2020-07-28 13:16:08 +02:00

146 lines
4.7 KiB
TypeScript

// Keep all classes requiring libsodium-js in one file as having multiple
// requiring of the libsodium-wrappers module currently crashes browsers
//
// libsodium.js API: https://gist.github.com/webmaster128/b2dbe6d54d36dd168c9fabf441b9b09c
import { isNonNullObject } from "@cosmjs/utils";
import sodium from "libsodium-wrappers";
export interface Argon2idOptions {
/** Output length in bytes */
readonly outputLength: number;
/**
* An integer between 1 and 4294967295 representing the computational difficulty.
*
* @see https://libsodium.gitbook.io/doc/password_hashing/default_phf#key-derivation
*/
readonly opsLimit: number;
/**
* Memory limit measured in KiB (like argon2 command line tool)
*
* Note: only approximately 16 MiB of memory are available using the non-sumo version of libsodium.js
*
* @see https://libsodium.gitbook.io/doc/password_hashing/default_phf#key-derivation
*/
readonly memLimitKib: number;
}
export function isArgon2idOptions(thing: unknown): thing is Argon2idOptions {
if (!isNonNullObject(thing)) return false;
if (typeof (thing as Argon2idOptions).outputLength !== "number") return false;
if (typeof (thing as Argon2idOptions).opsLimit !== "number") return false;
if (typeof (thing as Argon2idOptions).memLimitKib !== "number") return false;
return true;
}
export class Argon2id {
public static async execute(
password: string,
salt: Uint8Array,
options: Argon2idOptions,
): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_pwhash(
options.outputLength,
password,
salt, // libsodium only supports 16 byte salts and will throw when you don't respect that
options.opsLimit,
options.memLimitKib * 1024,
sodium.crypto_pwhash_ALG_ARGON2ID13,
);
}
}
export class Ed25519Keypair {
// a libsodium privkey has the format `<ed25519 privkey> + <ed25519 pubkey>`
public static fromLibsodiumPrivkey(libsodiumPrivkey: Uint8Array): Ed25519Keypair {
if (libsodiumPrivkey.length !== 64) {
throw new Error(`Unexpected key length ${libsodiumPrivkey.length}. Must be 64.`);
}
return new Ed25519Keypair(libsodiumPrivkey.slice(0, 32), libsodiumPrivkey.slice(32, 64));
}
public readonly privkey: Uint8Array;
public readonly pubkey: Uint8Array;
public constructor(privkey: Uint8Array, pubkey: Uint8Array) {
this.privkey = privkey;
this.pubkey = pubkey;
}
public toLibsodiumPrivkey(): Uint8Array {
return new Uint8Array([...this.privkey, ...this.pubkey]);
}
}
export class Ed25519 {
/**
* Generates a keypair deterministically from a given 32 bytes seed.
*
* This seed equals the Ed25519 private key.
* For implementation details see crypto_sign_seed_keypair in
* https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures.html
* and diagram on https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
public static async makeKeypair(seed: Uint8Array): Promise<Ed25519Keypair> {
await sodium.ready;
const keypair = sodium.crypto_sign_seed_keypair(seed);
return Ed25519Keypair.fromLibsodiumPrivkey(keypair.privateKey);
}
public static async createSignature(message: Uint8Array, keyPair: Ed25519Keypair): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_sign_detached(message, keyPair.toLibsodiumPrivkey());
}
public static async verifySignature(
signature: Uint8Array,
message: Uint8Array,
pubkey: Uint8Array,
): Promise<boolean> {
await sodium.ready;
return sodium.crypto_sign_verify_detached(signature, message, pubkey);
}
}
/**
* Nonce length in bytes for all flavours of XChaCha20.
*
* @see https://libsodium.gitbook.io/doc/advanced/stream_ciphers/xchacha20#notes
*/
export const xchacha20NonceLength = 24;
export class Xchacha20poly1305Ietf {
public static async encrypt(message: Uint8Array, key: Uint8Array, nonce: Uint8Array): Promise<Uint8Array> {
await sodium.ready;
const additionalData = null;
return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
message,
additionalData,
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
nonce,
key,
);
}
public static async decrypt(
ciphertext: Uint8Array,
key: Uint8Array,
nonce: Uint8Array,
): Promise<Uint8Array> {
await sodium.ready;
const additionalData = null;
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
ciphertext,
additionalData,
nonce,
key,
);
}
}