const Backend = require('./backend'); const CryptographyKey = require('./cryptography-key'); const Ed25519SecretKey = require('./keytypes/ed25519sk'); const Ed25519PublicKey = require('./keytypes/ed25519pk'); const LibsodiumWrappersBackend = require('./backend/libsodium-wrappers'); const SodiumError = require('./sodium-error'); const SodiumNativeBackend = require('./backend/sodiumnative'); const X25519PublicKey = require('./keytypes/x25519pk'); const X25519SecretKey = require('./keytypes/x25519sk'); const Util = require('./util'); /* istanbul ignore if */ if (typeof (Buffer) === 'undefined') { let Buffer = require('buffer/').Buffer; } class SodiumPlus { constructor(backend) { /* istanbul ignore if */ if (!(backend instanceof Backend)) { throw new TypeError('Backend object must implement the backend function'); } this.backend = backend; Util.populateConstants(this); } /** * Returns the name of the current active backend. * This method is NOT async. * * @return {string} */ getBackendName() { return this.backend.backendName; } /** * Is this powered by sodium-native? * This method is NOT async. * * @return {boolean} */ isSodiumNative() { return (this.backend instanceof SodiumNativeBackend); } /** * Is this powered by libsodium-wrappers? * This method is NOT async. * * @return {boolean} */ isLibsodiumWrappers() { return (this.backend instanceof LibsodiumWrappersBackend); } /** * Automatically select a backend. * * @return {Promise} */ static async auto() { let backend; try { backend = await SodiumNativeBackend.init(); } catch (e) { backend = await LibsodiumWrappersBackend.init(); } /* istanbul ignore if */ if (!backend) { backend = await LibsodiumWrappersBackend.init(); } Util.populateConstants(backend); return new SodiumPlus(backend); } /** * If our backend isn't defined, it will trigger an autoload. * * Mostly used internally. `await SodiumPlus.auto()` provides the same * exact guarantee as this method. * * @return {Promise} */ async ensureLoaded() { /* istanbul ignore if */ if (typeof (this.backend) === 'undefined') { try { await SodiumPlus.auto(); } catch (e) { this.backend = await LibsodiumWrappersBackend.init(); } } } /** * Decrypt a message (and optional associated data) with XChaCha20-Poly1305 * * @param {String|Buffer} ciphertext * @param {String|Buffer} nonce * @param {CryptographyKey} key * @param {String|Buffer} assocData * @return {Promise} * @throws {SodiumError} */ async crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, nonce, key, assocData = '') { await this.ensureLoaded(); if (nonce.length !== 24) { throw new SodiumError('Argument 2 must be 24 bytes'); } if (!(key instanceof CryptographyKey)) { throw new TypeError('Argument 3 must be an instance of CryptographyKey'); } return await this.backend.crypto_aead_xchacha20poly1305_ietf_decrypt( await Util.toBuffer(ciphertext), assocData.length > 0 ? await Util.toBuffer(assocData) : null, await Util.toBuffer(nonce), key ); } /** * Encrypt a message (and optional associated data) with XChaCha20-Poly1305. * * Throws a SodiumError if an invalid ciphertext/AAD is provided for this * nonce and key. * * @param {String|Buffer} plaintext * @param {String|Buffer} nonce * @param {CryptographyKey} key * @param {String|Buffer} assocData * @return {Promise} * @throws {SodiumError} */ async crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, nonce, key, assocData = '') { await this.ensureLoaded(); if (nonce.length !== 24) { throw new SodiumError('Argument 2 must be 24 bytes'); } if (!(key instanceof CryptographyKey)) { throw new TypeError('Argument 3 must be an instance of CryptographyKey'); } return await this.backend.crypto_aead_xchacha20poly1305_ietf_encrypt( await Util.toBuffer(plaintext), assocData.length > 0 ? await Util.toBuffer(assocData) : null, await Util.toBuffer(nonce), key ); } /** * Generate an XChaCha20-Poly1305 key. * * @return {Promise} */ async crypto_aead_xchacha20poly1305_ietf_keygen() { return new CryptographyKey(await this.backend.randombytes_buf(32)); } /** * Get an authenticator for a message for a given key. * * Algorithm: HMAC-SHA512 truncated to 32 bytes. * * @param {string|Buffer} message * @param {CryptographyKey} key * @return {Promise} */ async crypto_auth(message, key) { await this.ensureLoaded(); if (!(key instanceof CryptographyKey)) { throw new TypeError('Argument 2 must be an instance of CryptographyKey'); } await this.ensureLoaded(); return await this.backend.crypto_auth( await Util.toBuffer(message), key ); } /** * @return {Promise} */ async crypto_auth_keygen() { return new CryptographyKey(await this.backend.randombytes_buf(32)); } /** * Verify an authenticator for a message for a given key. * * @param {string|Buffer} message * @param {CryptographyKey} key * @param {Buffer} mac * @return {Promise} */ async crypto_auth_verify(message, key, mac) { await this.ensureLoaded(); if (!(key instanceof CryptographyKey)) { throw new TypeError('Argument 2 must be an instance of CryptographyKey'); } await this.ensureLoaded(); return await this.backend.crypto_auth_verify( await Util.toBuffer(mac), await Util.toBuffer(message), key ); } /** * Public-key authenticated encryption. * * @param {string|Buffer} plaintext * @param {Buffer} nonce * @param {X25519SecretKey} myPrivateKey * @param {X25519PublicKey} theirPublicKey * @return {Promise} */ async crypto_box(plaintext, nonce, myPrivateKey, theirPublicKey) { await this.ensureLoaded(); if (!(myPrivateKey instanceof X25519SecretKey)) { throw new TypeError('Argument 3 must be an instance of X25519SecretKey'); } if (!(theirPublicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 4 must be an instance of X25519PublicKey'); } nonce = await Util.toBuffer(nonce); if (nonce.length !== 24) { throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); } return this.backend.crypto_box( await Util.toBuffer(plaintext), await Util.toBuffer(nonce), myPrivateKey, theirPublicKey ); } /** * Public-key authenticated decryption. * * @param {Buffer} ciphertext * @param {Buffer} nonce * @param {X25519SecretKey} myPrivateKey * @param {X25519PublicKey} theirPublicKey * @return {Promise} */ async crypto_box_open(ciphertext, nonce, myPrivateKey, theirPublicKey) { await this.ensureLoaded(); if (!(myPrivateKey instanceof X25519SecretKey)) { throw new TypeError('Argument 3 must be an instance of X25519SecretKey'); } if (!(theirPublicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 4 must be an instance of X25519PublicKey'); } ciphertext = await Util.toBuffer(ciphertext); if (ciphertext.length < 16) { throw new SodiumError('Ciphertext must be a buffer of at least 16 bytes'); } nonce = await Util.toBuffer(nonce); if (nonce.length !== 24) { throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); } return this.backend.crypto_box_open( ciphertext, nonce, myPrivateKey, theirPublicKey ); } /** * @return {Promise} */ async crypto_box_keypair() { await this.ensureLoaded(); return this.backend.crypto_box_keypair(); } /** * Combine two X25519 keys (secret, public) into a keypair object. * * @param {X25519SecretKey} sKey * @param {X25519PublicKey} pKey * @return {Promise} */ async crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey) { await this.ensureLoaded(); if (!(sKey instanceof X25519SecretKey)) { throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); } if (!(pKey instanceof X25519PublicKey)) { throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); } return await this.backend.crypto_box_keypair_from_secretkey_and_publickey(sKey, pKey); } /** * Extract the secret key from an X25519 keypair object. * * @param {CryptographyKey} keypair * @return {Promise} */ async crypto_box_secretkey(keypair) { if (keypair.getLength()!== 64) { throw new SodiumError('Keypair must be 64 bytes'); } return new X25519SecretKey( Buffer.from(keypair.getBuffer().slice(0, 32)) ); } /** * Extract the public key from an X25519 keypair object. * * @param {CryptographyKey} keypair * @return {Promise} */ async crypto_box_publickey(keypair) { if (keypair.getLength() !== 64) { throw new SodiumError('Keypair must be 64 bytes'); } return new X25519PublicKey( Buffer.from(keypair.getBuffer().slice(32, 64)) ); } /** * Derive the public key from a given X25519 secret key. * * @param {X25519SecretKey} secretKey * @return {Promise} */ async crypto_box_publickey_from_secretkey(secretKey) { await this.ensureLoaded(); if (!(secretKey instanceof X25519SecretKey)) { throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); } return new X25519PublicKey( await this.backend.crypto_scalarmult_base(secretKey) ); } /** * Anonymous public-key encryption. (Message integrity is still assured.) * * @param {string|Buffer} plaintext * @param {X25519PublicKey} publicKey * @return {Promise} */ async crypto_box_seal(plaintext, publicKey) { await this.ensureLoaded(); if (!(publicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); } return await this.backend.crypto_box_seal(plaintext, publicKey); } /** * Anonymous public-key decryption. (Message integrity is still assured.) * * @param {Buffer} ciphertext * @param {X25519PublicKey} publicKey * @param {X25519SecretKey} secretKey * @return {Promise} */ async crypto_box_seal_open(ciphertext, publicKey, secretKey) { await this.ensureLoaded(); if (!(publicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); } if (!(secretKey instanceof X25519SecretKey)) { throw new TypeError('Argument 3 must be an instance of X25519SecretKey'); } return await this.backend.crypto_box_seal_open( await Util.toBuffer(ciphertext), publicKey, secretKey ); } /** * Generic-purpose cryptographic hash. * * @param {string|Buffer} message * @param {CryptographyKey|null} key * @param {number} outputLength * @return {Promise} */ async crypto_generichash(message, key = null, outputLength = 32) { await this.ensureLoaded(); return await this.backend.crypto_generichash(message, key, outputLength); } /** * Initialize a BLAKE2 hash context for stream hashing. * * @param {CryptographyKey|null} key * @param {number} outputLength * @return {Promise} */ async crypto_generichash_init(key = null, outputLength = 32) { await this.ensureLoaded(); return await this.backend.crypto_generichash_init(key, outputLength); } /** * Update the BLAKE2 hash state with a block of data. * * @param {*} state * @param {string|Buffer} message * @return {Promise<*>} */ async crypto_generichash_update(state, message) { await this.ensureLoaded(); return await this.backend.crypto_generichash_update(state, message); } /** * Obtain the final BLAKE2 hash output. * * @param {*} state * @param {number} outputLength * @return {Promise} */ async crypto_generichash_final(state, outputLength = 32) { await this.ensureLoaded(); return await this.backend.crypto_generichash_final(state, outputLength); } /** * Generate a 256-bit random key for BLAKE2. * * @return {Promise} */ async crypto_generichash_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(this.CRYPTO_GENERICHASH_KEYBYTES) ); } /** * Derive a subkey from a master key. * * @param {number} length * @param {number} subKeyId * @param {string|Buffer} context * @param {CryptographyKey} key * @return {Promise} */ async crypto_kdf_derive_from_key(length, subKeyId, context, key) { await this.ensureLoaded(); if (length < 1) { throw new SodiumError('Length must be a positive integer.'); } if (subKeyId < 0) { throw new SodiumError('Key ID must be an unsigned integer'); } return await this.backend.crypto_kdf_derive_from_key( length, subKeyId, context, key ); } /** * Generate a 256-bit random key for our KDF. * * @return {Promise} */ async crypto_kdf_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(this.CRYPTO_KDF_KEYBYTES) ); } /** * This is functionally identical to crypto_box_keypair(). * * @return {Promise} */ async crypto_kx_keypair() { return this.crypto_box_keypair(); } /** * Generate an X25519 keypair from a seed. * * @param {string|Buffer} seed * @return {Promise} */ async crypto_kx_seed_keypair(seed) { await this.ensureLoaded(); const sk = await this.backend.crypto_generichash(seed, null, this.CRYPTO_KX_SECRETKEYBYTES); const pk = await this.backend.crypto_scalarmult_base(new CryptographyKey(sk)); return new CryptographyKey(Buffer.concat([sk, pk])); } /** * Perform a key exchange from the client's perspective. * * Returns an array of two CryptographyKey objects. * * The first is meant for data sent from the server to the client (incoming decryption). * The second is meant for data sent from the client to the server (outgoing encryption). * * @param {X25519PublicKey} clientPublicKey * @param {X25519SecretKey} clientSecretKey * @param {X25519PublicKey} serverPublicKey * @return {Promise} */ async crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey) { await this.ensureLoaded(); if (!(clientPublicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 1 must be an instance of X25519PublicKey'); } if (!(clientSecretKey instanceof X25519SecretKey)) { throw new TypeError('Argument 2 must be an instance of X25519SecretKey'); } if (!(serverPublicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 3 must be an instance of X25519PublicKey'); } return this.backend.crypto_kx_client_session_keys(clientPublicKey, clientSecretKey, serverPublicKey); } /** * Perform a key exchange from the server's perspective. * * Returns an array of two CryptographyKey objects. * * The first is meant for data sent from the client to the server (incoming decryption). * The second is meant for data sent from the server to the client (outgoing encryption). * * @param {X25519PublicKey} serverPublicKey * @param {X25519SecretKey} serverSecretKey * @param {X25519PublicKey} clientPublicKey * @return {Promise} */ async crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey) { await this.ensureLoaded(); if (!(serverPublicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 1 must be an instance of X25519PublicKey'); } if (!(serverSecretKey instanceof X25519SecretKey)) { throw new TypeError('Argument 2 must be an instance of X25519SecretKey'); } if (!(clientPublicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 3 must be an instance of X25519PublicKey'); } return this.backend.crypto_kx_server_session_keys(serverPublicKey, serverSecretKey, clientPublicKey); } /** * @param {string|Buffer} message * @param {CryptographyKey} key * @return {Promise} */ async crypto_onetimeauth(message, key) { if (!(key instanceof CryptographyKey)) { throw new TypeError('Argument 2 must be an instance of CryptographyKey'); } return await this.backend.crypto_onetimeauth(await Util.toBuffer(message), key); } /** * @param {string|Buffer} message * @param {CryptographyKey} key * @param {Buffer} tag * @return {Promise} */ async crypto_onetimeauth_verify(message, key, tag) { if (!(key instanceof CryptographyKey)) { throw new TypeError('Argument 2 must be an instance of CryptographyKey'); } return await this.backend.crypto_onetimeauth_verify( await Util.toBuffer(message), key, await Util.toBuffer(tag) ); } /** * @return {Promise} */ async crypto_onetimeauth_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(32) ); } /** * Derive a cryptography key from a password and salt. * * @param {number} length * @param {string|Buffer} password * @param {Buffer} salt * @param {number} opslimit * @param {number} memlimit * @param {number|null} algorithm * @return {Promise} */ async crypto_pwhash(length, password, salt, opslimit, memlimit, algorithm = null) { await this.ensureLoaded(); /* istanbul ignore if */ if (!algorithm) { algorithm = this.CRYPTO_PWHASH_ALG_DEFAULT; } return new CryptographyKey( await this.backend.crypto_pwhash( length, await Util.toBuffer(password), await Util.toBuffer(salt), opslimit, memlimit, algorithm ) ); } /** * Get a password hash (in a safe-for-storage format) * * @param {string|Buffer} password * @param {number} opslimit * @param {number} memlimit * @return {Promise} */ async crypto_pwhash_str(password, opslimit, memlimit) { await this.ensureLoaded(); return await this.backend.crypto_pwhash_str(password, opslimit, memlimit); } /** * Verify a password against a known password hash * * @param {string|Buffer} password * @param {string|Buffer} hash * @return {Promise} */ async crypto_pwhash_str_verify(password, hash) { await this.ensureLoaded(); return await this.backend.crypto_pwhash_str_verify(password, hash); } /** * Does this password need to be rehashed? * * @param {string|Buffer} hash * @param {number} opslimit * @param {number} memlimit * @return {Promise} */ async crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit) { await this.ensureLoaded(); return await this.backend.crypto_pwhash_str_needs_rehash(hash, opslimit, memlimit); } /** * Elliptic Curve Diffie-Hellman key exchange * * @param {X25519SecretKey} secretKey * @param {X25519PublicKey} publicKey * @return {Promise} */ async crypto_scalarmult(secretKey, publicKey) { await this.ensureLoaded(); if (!(secretKey instanceof X25519SecretKey)) { throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); } if (!(publicKey instanceof X25519PublicKey)) { throw new TypeError('Argument 2 must be an instance of X25519PublicKey'); } return await this.backend.crypto_scalarmult(secretKey, publicKey); } /** * Generate an X25519PublicKey from an X25519SecretKey * * @param {X25519SecretKey} secretKey * @return {Promise} */ async crypto_scalarmult_base(secretKey) { await this.ensureLoaded(); if (!(secretKey instanceof X25519SecretKey)) { throw new TypeError('Argument 1 must be an instance of X25519SecretKey'); } return new X25519PublicKey( await this.backend.crypto_scalarmult_base(secretKey) ); } /** * Shared-key authenticated encryption * * @param {string|Buffer} plaintext * @param {Buffer} nonce * @param {CryptographyKey} key * @return {Promise} */ async crypto_secretbox(plaintext, nonce, key) { await this.ensureLoaded(); if (key.isEd25519Key() || key.isX25519Key()) { throw new TypeError('Argument 3 must not be an asymmetric key'); } nonce = await Util.toBuffer(nonce); if (nonce.length !== 24) { throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); } return await this.backend.crypto_secretbox( plaintext, nonce, key ); } /** * Shared-key authenticated decryption * * @param {Buffer} ciphertext * @param {Buffer} nonce * @param {CryptographyKey} key * @return {Promise} */ async crypto_secretbox_open(ciphertext, nonce, key) { await this.ensureLoaded(); if (key.isEd25519Key() || key.isX25519Key()) { throw new TypeError('Argument 3 must not be an asymmetric key'); } ciphertext = await Util.toBuffer(ciphertext); if (ciphertext.length < 16) { throw new SodiumError('Ciphertext must be a buffer of at least 16 bytes'); } nonce = await Util.toBuffer(nonce); if (nonce.length !== 24) { throw new SodiumError('Nonce must be a buffer of exactly 24 bytes'); } return await this.backend.crypto_secretbox_open( ciphertext, nonce, key ); } /** * Generate a key for shared-key authenticated encryption. * * @return {Promise} */ async crypto_secretbox_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(this.CRYPTO_SECRETBOX_KEYBYTES) ); } /** * Internalize the internal state and a random header for stream encryption. * * @param {CryptographyKey} key * @return {Promise} */ async crypto_secretstream_xchacha20poly1305_init_push(key) { await this.ensureLoaded(); if (!(key instanceof CryptographyKey)) { throw new TypeError('Key must be an instance of CryptographyKey'); } if (key.getLength() !== 32) { throw new SodiumError('crypto_secretstream keys must be 32 bytes long'); } const [state, header] = await this.backend.crypto_secretstream_xchacha20poly1305_init_push(key); return Object.freeze({ header: header, push: this.crypto_secretstream_xchacha20poly1305_push.bind(this, state), rekey: this.crypto_secretstream_xchacha20poly1305_rekey.bind(this, state) }); } /** * Initialize the internal state for stream decryption. * * @param {Buffer} header * @param {CryptographyKey} key * @return {Promise<*>} */ async crypto_secretstream_xchacha20poly1305_init_pull(key, header) { await this.ensureLoaded(); header = await Util.toBuffer(header); if (header.length !== 24) { throw new SodiumError('crypto_secretstream headers must be 24 bytes long'); } if (!(key instanceof CryptographyKey)) { throw new TypeError('Key must be an instance of CryptographyKey'); } if (key.getLength() !== 32) { throw new SodiumError('crypto_secretstream keys must be 32 bytes long'); } const state = await this.backend.crypto_secretstream_xchacha20poly1305_init_pull(header, key); return Object.freeze({ pull: this.crypto_secretstream_xchacha20poly1305_pull.bind(this, state) }); } /** * Stream encryption. * * @param {*} state * @param {string|Buffer} message * @param {string|Buffer} ad * @param {number} tag * @return {Promise} */ async crypto_secretstream_xchacha20poly1305_push(state, message, ad = '', tag = 0) { await this.ensureLoaded(); return this.backend.crypto_secretstream_xchacha20poly1305_push(state, message, ad, tag); } /** * Stream decryption. * * @param {*} state * @param {Buffer} ciphertext * @param {string|Buffer} ad * @param {number} tag * @return {Promise} */ async crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad = '', tag = 0) { await this.ensureLoaded(); return this.backend.crypto_secretstream_xchacha20poly1305_pull(state, ciphertext, ad, tag); } /** * Deterministic rekeying. * * @param {*} state * @return {Promise} */ async crypto_secretstream_xchacha20poly1305_rekey(state) { await this.ensureLoaded(); await this.backend.crypto_secretstream_xchacha20poly1305_rekey(state); } /** * Generate a key for shared-key authenticated encryption. * * @return {Promise} */ async crypto_secretstream_xchacha20poly1305_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(this.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES) ); } /** * Calculate a fast hash for short inputs. * * Algorithm: SipHash-2-4 * * @param {string|Buffer} message * @param {CryptographyKey} key * @return {Promise} */ async crypto_shorthash(message, key) { await this.ensureLoaded(); return await this.backend.crypto_shorthash(await Util.toBuffer(message), key); } /** * @return {Promise} */ async crypto_shorthash_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(this.CRYPTO_SHORTHASH_KEYBYTES) ); } /** * Returns a signed message. * * @param {string|Buffer} message, * @param {Ed25519SecretKey} secretKey * @return {Promise} */ async crypto_sign(message, secretKey) { await this.ensureLoaded(); if (!(secretKey instanceof Ed25519SecretKey)) { throw new TypeError('Argument 2 must be an instance of Ed25519SecretKey'); } return this.backend.crypto_sign(message, secretKey); } /** * Given a signed message, verify the Ed25519 signature. If it matches, return the * bare message (no signature). * * @param {string|Buffer} message, * @param {Ed25519PublicKey} publicKey * @return {Promise} */ async crypto_sign_open(message, publicKey) { await this.ensureLoaded(); if (!(publicKey instanceof Ed25519PublicKey)) { throw new TypeError('Argument 2 must be an instance of Ed25519PublicKey'); } return this.backend.crypto_sign_open(message, publicKey); } /** * Returns the Ed25519 signature of the message, for the given secret key. * * @param {string|Buffer} message, * @param {Ed25519SecretKey} secretKey * @return {Promise} */ async crypto_sign_detached(message, secretKey) { await this.ensureLoaded(); if (!(secretKey instanceof Ed25519SecretKey)) { throw new TypeError('Argument 2 must be an instance of Ed25519SecretKey'); } return this.backend.crypto_sign_detached(message, secretKey); } /** * Returns true if the Ed25519 signature is valid for a given message and public key. * * @param {string|Buffer} message, * @param {Ed25519PublicKey} publicKey * @param {Buffer} signature * @return {Promise} */ async crypto_sign_verify_detached(message, publicKey, signature) { await this.ensureLoaded(); if (!(publicKey instanceof Ed25519PublicKey)) { throw new TypeError('Argument 2 must be an instance of Ed25519PublicKey'); } return this.backend.crypto_sign_verify_detached(message, publicKey, signature); } /** * Extract the secret key from an Ed25519 keypair object. * * @param {CryptographyKey} keypair * @return {Promise} */ async crypto_sign_secretkey(keypair) { if (keypair.getLength() !== 96) { throw new SodiumError('Keypair must be 96 bytes'); } return new Ed25519SecretKey( await Util.toBuffer( keypair.getBuffer().slice(0, 64) ) ); } /** * Extract the public key from an Ed25519 keypair object. * * @param {CryptographyKey} keypair * @return {Promise} */ async crypto_sign_publickey(keypair) { if (keypair.getLength() !== 96) { throw new SodiumError('Keypair must be 96 bytes'); } return new Ed25519PublicKey( keypair.getBuffer().slice(64, 96) ); } /** * Generate an Ed25519 keypair object. * * @return {Promise} */ async crypto_sign_keypair() { await this.ensureLoaded(); return this.backend.crypto_sign_keypair(); } /** * Generate an Ed25519 keypair object from a seed. * * @param {Buffer} seed * @return {Promise} */ async crypto_sign_seed_keypair(seed) { await this.ensureLoaded(); if (seed instanceof CryptographyKey) { seed = seed.getBuffer(); } seed = await Util.toBuffer(seed); if (seed.length !== 32) { throw new SodiumError(`Seed must be 32 bytes long; got ${seed.length}`); } return this.backend.crypto_sign_seed_keypair(seed); } /** * Obtain a birationally equivalent X25519 secret key, * given an Ed25519 secret key. * * @param {Ed25519SecretKey} sk * @return {Promise} */ async crypto_sign_ed25519_sk_to_curve25519(sk) { await this.ensureLoaded(); return new X25519SecretKey( await this.backend.crypto_sign_ed25519_sk_to_curve25519(sk) ); } /** * Obtain a birationally equivalent X25519 public key, * given an Ed25519 public key. * * @param {Ed25519PublicKey} pk * @return {Promise} */ async crypto_sign_ed25519_pk_to_curve25519(pk) { await this.ensureLoaded(); return new X25519PublicKey( await this.backend.crypto_sign_ed25519_pk_to_curve25519(pk) ); } /** * Generate an arbitrary number of pseudorandom bytes from a given * nonce and key. * * @param {number} length * @param {Buffer} nonce * @param {CryptographyKey} key * @return {Promise} */ async crypto_stream(length, nonce, key) { await this.ensureLoaded(); return this.backend.crypto_stream(length, nonce, key); } /** * Encrypts a string (without authentication). * * @param {string|Buffer} plaintext * @param {Buffer} nonce * @param {CryptographyKey} key * @return {Promise} */ async crypto_stream_xor(plaintext, nonce, key) { await this.ensureLoaded(); return this.backend.crypto_stream_xor(plaintext, nonce, key); } /** * Generate a key for stream ciphers. * * @return {Promise} */ async crypto_stream_keygen() { return new CryptographyKey( await this.backend.randombytes_buf(this.CRYPTO_STREAM_KEYBYTES) ); } /** * Returns a buffer filled with random bytes. * * @param {number} num * @return {Promise} */ async randombytes_buf(num) { await this.ensureLoaded(); return await this.backend.randombytes_buf(num); } /** * Generate an integer between 0 and upperBound (non-inclusive). * * For example, randombytes_uniform(10) returns an integer between 0 and 9. * * @param {number} upperBound * @return {Promise} */ async randombytes_uniform(upperBound) { await this.ensureLoaded(); return this.backend.randombytes_uniform(upperBound); } /** * Add two buffers (little-endian). Returns the value. * * @param {Buffer} val * @param {Buffer} addv * @return {Promise} */ async sodium_add(val, addv) { await this.ensureLoaded(); return await this.backend.sodium_add( await Util.toBuffer(val), await Util.toBuffer(addv) ); } /** * Convert to hex. * * @param {Buffer} decoded * @return {Promise} */ async sodium_bin2hex(decoded) { await this.ensureLoaded(); return this.backend.sodium_bin2hex(decoded); } /** * Compare two buffers in constant time. * * Returns -1 if b1 is less than b2. * Returns 1 if b1 is greater than b2. * Returns 0 if b1 is equal to b2. * * @param {Buffer} b1 * @param {Buffer} b2 * @return {Promise} */ async sodium_compare(b1, b2) { await this.ensureLoaded(); return this.backend.sodium_compare(b1, b2); } /** * Convert to hex. * * @param {Buffer|string} encoded * @return {Promise} */ async sodium_hex2bin(encoded) { await this.ensureLoaded(); return this.backend.sodium_hex2bin(encoded); } /** * Increment a buffer (little endian). Overwrites the buffer in-place. * * @param {Buffer} buf * @return {Promise} */ async sodium_increment(buf) { await this.ensureLoaded(); return this.backend.sodium_increment(buf); } /** * Returns true if the buffer is zero. * * @param {Buffer} buf * @param {number} len * @return {Promise} */ async sodium_is_zero(buf, len) { await this.ensureLoaded(); return this.backend.sodium_is_zero(buf, len); } /** * Timing-safe buffer comparison. * * @param {Buffer} b1 * @param {Buffer} b2 * @return {Promise} */ async sodium_memcmp(b1, b2) { await this.ensureLoaded(); return this.backend.sodium_memcmp(b1, b2); } /** * Zero out a buffer. Overwrites the buffer in-place. * * @param {Buffer} buf * @return {Promise} */ async sodium_memzero(buf) { await this.ensureLoaded(); await this.backend.sodium_memzero(buf); } /** * Pad a string. * * @param {string|Buffer} buf * @param {number} blockSize * @return {Promise} */ async sodium_pad(buf, blockSize) { await this.ensureLoaded(); return this.backend.sodium_pad(buf, blockSize); } /** * Unpad a string. * * @param {string|Buffer} buf * @param {number} blockSize * @return {Promise} */ async sodium_unpad(buf, blockSize) { await this.ensureLoaded(); return this.backend.sodium_unpad(buf, blockSize); } } module.exports = SodiumPlus;