UNPKG

@chainsafe/libp2p-noise

Version:
220 lines (186 loc) 6.59 kB
import crypto from 'node:crypto' import { newInstance, ChaCha20Poly1305 } from '@chainsafe/as-chacha20poly1305' import { digest } from '@chainsafe/as-sha256' import { Uint8ArrayList } from 'uint8arraylist' import { isElectronMain } from 'wherearewe' import { pureJsCrypto } from './js.js' import type { ICryptoInterface } from '../crypto.js' import type { KeyPair } from '../types.js' const ctx = newInstance() const asImpl = new ChaCha20Poly1305(ctx) const CHACHA_POLY1305 = 'chacha20-poly1305' const PKCS8_PREFIX = Buffer.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20]) const X25519_PREFIX = Buffer.from([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00]) const nodeCrypto: Pick<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt' | 'chaCha20Poly1305Decrypt'> = { hashSHA256 (data) { const hash = crypto.createHash('sha256') if (data instanceof Uint8Array) { return hash.update(data).digest() } for (const buf of data) { hash.update(buf) } return hash.digest() }, chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) { const cipher = crypto.createCipheriv(CHACHA_POLY1305, k, nonce, { authTagLength: 16 }) cipher.setAAD(ad, { plaintextLength: plaintext.byteLength }) if (plaintext instanceof Uint8Array) { const updated = cipher.update(plaintext) const final = cipher.final() const tag = cipher.getAuthTag() return Buffer.concat([updated, final, tag], updated.byteLength + final.byteLength + tag.byteLength) } const output = new Uint8ArrayList() for (const buf of plaintext) { output.append(cipher.update(buf)) } const final = cipher.final() if (final.byteLength > 0) { output.append(final) } output.append(cipher.getAuthTag()) return output }, chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, _dst) { const authTag = ciphertext.subarray(ciphertext.length - 16) const decipher = crypto.createDecipheriv(CHACHA_POLY1305, k, nonce, { authTagLength: 16 }) let text: Uint8Array | Uint8ArrayList if (ciphertext instanceof Uint8Array) { text = ciphertext.subarray(0, ciphertext.length - 16) } else { text = ciphertext.sublist(0, ciphertext.length - 16) } decipher.setAAD(ad, { plaintextLength: text.byteLength }) decipher.setAuthTag(authTag) if (text instanceof Uint8Array) { const output = decipher.update(text) const final = decipher.final() if (final.byteLength > 0) { return Buffer.concat([output, final], output.byteLength + final.byteLength) } return output } const output = new Uint8ArrayList() for (const buf of text) { output.append(decipher.update(buf)) } const final = decipher.final() if (final.byteLength > 0) { output.append(final) } return output } } const asCrypto: Pick<ICryptoInterface, 'hashSHA256' | 'chaCha20Poly1305Encrypt' | 'chaCha20Poly1305Decrypt'> = { hashSHA256 (data) { return digest(data.subarray()) }, chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) { return asImpl.seal(k, nonce, plaintext.subarray(), ad) }, chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) { const plaintext = asImpl.open(k, nonce, ciphertext.subarray(), ad, dst) if (!plaintext) { throw new Error('Invalid chacha20poly1305 decryption') } return plaintext } } // benchmarks show that for chacha20poly1305 // the as implementation is faster for smaller payloads(<1200) // and the node implementation is faster for larger payloads export const defaultCrypto: ICryptoInterface = { ...pureJsCrypto, hashSHA256 (data) { return nodeCrypto.hashSHA256(data) }, chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) { if (plaintext.byteLength < 1200) { return asCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) } return nodeCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) }, chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) { if (ciphertext.byteLength < 1200) { return asCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst) } return nodeCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst) }, generateX25519KeyPair (): KeyPair { const { publicKey, privateKey } = crypto.generateKeyPairSync('x25519', { publicKeyEncoding: { type: 'spki', format: 'der' }, privateKeyEncoding: { type: 'pkcs8', format: 'der' } }) return { publicKey: publicKey.subarray(X25519_PREFIX.length), privateKey: privateKey.subarray(PKCS8_PREFIX.length) } }, generateX25519KeyPairFromSeed (seed: Uint8Array): KeyPair { const privateKey = crypto.createPrivateKey({ key: Buffer.concat([ PKCS8_PREFIX, seed ], PKCS8_PREFIX.byteLength + seed.byteLength), type: 'pkcs8', format: 'der' }) const publicKey = crypto.createPublicKey(privateKey) .export({ type: 'spki', format: 'der' }).subarray(X25519_PREFIX.length) return { publicKey, privateKey: seed } }, generateX25519SharedKey (privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array { if (publicKey instanceof Uint8Array) { publicKey = Buffer.concat([ X25519_PREFIX, publicKey ], X25519_PREFIX.byteLength + publicKey.byteLength) } else { publicKey = new Uint8ArrayList(X25519_PREFIX, publicKey).subarray() } if (privateKey instanceof Uint8Array) { privateKey = Buffer.concat([ PKCS8_PREFIX, privateKey ], PKCS8_PREFIX.byteLength + privateKey.byteLength) } else { privateKey = new Uint8ArrayList(PKCS8_PREFIX, privateKey).subarray() } return crypto.diffieHellman({ publicKey: crypto.createPublicKey({ key: Buffer.from(publicKey.buffer, publicKey.byteOffset, publicKey.byteLength), type: 'spki', format: 'der' }), privateKey: crypto.createPrivateKey({ key: Buffer.from(privateKey.buffer, privateKey.byteOffset, privateKey.byteLength), type: 'pkcs8', format: 'der' }) }) } } // no chacha20-poly1305 in electron https://github.com/electron/electron/issues/24024 if (isElectronMain) { defaultCrypto.chaCha20Poly1305Encrypt = asCrypto.chaCha20Poly1305Encrypt defaultCrypto.chaCha20Poly1305Decrypt = asCrypto.chaCha20Poly1305Decrypt }