From 6757cab543fa1b25e27cfb3b81be0537da2699da Mon Sep 17 00:00:00 2001 From: Andre Staltz Date: Thu, 15 Jun 2023 13:53:47 +0300 Subject: [PATCH] replace Uint8Array with b4a --- lib/curves/ed25519.js | 32 ++++++++++++++------------------ lib/curves/index.js | 14 ++++++++------ lib/index.js | 37 ++++++++++++++++--------------------- package.json | 2 ++ test/fs.test.js | 6 +++--- test/generate.test.js | 7 ++++--- test/signing.test.js | 22 +++++++++++----------- 7 files changed, 58 insertions(+), 62 deletions(-) diff --git a/lib/curves/ed25519.js b/lib/curves/ed25519.js index eb61683..4a482e2 100644 --- a/lib/curves/ed25519.js +++ b/lib/curves/ed25519.js @@ -1,17 +1,13 @@ // @ts-ignore const sodium = require('sodium-universal') +const b4a = require('b4a') const base58 = require('bs58') -const _TextEncoder = - typeof window !== 'undefined' - ? window.TextEncoder - : require('node:util').TextEncoder /** * @typedef {import('.').Keypair} Keypair - * * @typedef {import('.').KeypairPublicSlice} KeypairPublicSlice - * * @typedef {import('.').KeypairPrivateSlice} KeypairPrivateSlice + * @typedef {Buffer | Uint8Array} B4A */ const SEEDBYTES = sodium.crypto_sign_SEEDBYTES @@ -20,24 +16,24 @@ const SECRETKEYBYTES = sodium.crypto_sign_SECRETKEYBYTES const ed25519 = { /** - * @param {(string | Uint8Array)=} seed + * @param {(string | B4A)=} seed * @returns {Keypair} */ generate(seed) { let seedBytes if (seed) { - if (seed instanceof Uint8Array) { + if (b4a.isBuffer(seed)) { // prettier-ignore if (seed.length !== SEEDBYTES) throw new Error(`seed must be ${SEEDBYTES} bytes`) seedBytes = seed } else if (typeof seed === 'string') { - seedBytes = new Uint8Array(SEEDBYTES) - new _TextEncoder().encodeInto(seed, seedBytes) + seedBytes = b4a.alloc(SEEDBYTES) + b4a.copy(b4a.from(seed.substring(0, 32), 'utf-8'), seedBytes) } } - const publicKeyBytes = new Uint8Array(PUBLICKEYBYTES) - const secretKeyBytes = new Uint8Array(SECRETKEYBYTES) + const publicKeyBytes = b4a.alloc(PUBLICKEYBYTES) + const secretKeyBytes = b4a.alloc(SECRETKEYBYTES) if (seedBytes) { sodium.crypto_sign_seed_keypair(publicKeyBytes, secretKeyBytes, seedBytes) } else { @@ -72,15 +68,15 @@ const ed25519 = { /** * @param {KeypairPrivateSlice} keypair - * @param {Uint8Array} message + * @param {B4A} message * @returns {string} */ sign(keypair, message) { if (!keypair._private && !keypair.private) { throw new Error(`invalid ed25519 keypair with missing private key`) } - keypair._private ??= base58.decode(keypair.private) - const sig = new Uint8Array(sodium.crypto_sign_BYTES) + keypair._private ??= b4a.from(base58.decode(keypair.private)) + const sig = b4a.alloc(sodium.crypto_sign_BYTES) sodium.crypto_sign_detached(sig, message, keypair._private) return base58.encode(sig) }, @@ -88,15 +84,15 @@ const ed25519 = { /** * @param {KeypairPublicSlice} keypair * @param {string} sig - * @param {Uint8Array} message + * @param {B4A} message * @returns {boolean} */ verify(keypair, sig, message) { if (!keypair._public && !keypair.public) { throw new Error(`invalid ed25519 keypair with missing public key`) } - keypair._public ??= base58.decode(keypair.public) - const sigBytes = new Uint8Array(base58.decode(sig)) + keypair._public ??= b4a.from(base58.decode(keypair.public)) + const sigBytes = b4a.from(base58.decode(sig)) return sodium.crypto_sign_verify_detached( sigBytes, message, diff --git a/lib/curves/index.js b/lib/curves/index.js index 3d91c18..c572ff3 100644 --- a/lib/curves/index.js +++ b/lib/curves/index.js @@ -7,12 +7,14 @@ const curves = { /** * @typedef {keyof typeof curves} CurveName * + * @typedef {Buffer | Uint8Array} B4A + * * @typedef {{ * curve: CurveName, * public: string, * private: string, - * _public?: Uint8Array, - * _private?: Uint8Array, + * _public?: B4A, + * _private?: B4A, * }} Keypair * * @typedef {(Pick & {_public: never}) | @@ -24,10 +26,10 @@ const curves = { * } KeypairPrivateSlice * * @typedef {{ - * generate: (seed?: Uint8Array | string) => Keypair, + * generate: (seed?: B4A | string) => Keypair, * toJSON: (keypair: Keypair, opts?: {indented?: boolean}) => string, - * sign: (keypair: KeypairPrivateSlice, message: Uint8Array) => Uint8Array, - * verify: (keypair: KeypairPublicSlice, message: Uint8Array, sig: Uint8Array) => boolean, + * sign: (keypair: KeypairPrivateSlice, message: B4A) => B4A, + * verify: (keypair: KeypairPublicSlice, message: B4A, sig: B4A) => boolean, * }} Curve */ @@ -46,7 +48,7 @@ function getCurve(curveName) { * This function generates a keypair for the given curve. The seed is optional. * * @param {CurveName=} curveName - * @param {(Uint8Array | string)=} seed + * @param {(B4A | string)=} seed * @returns {Keypair} */ function generate(curveName, seed) { diff --git a/lib/index.js b/lib/index.js index 274c56f..54afc88 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,6 @@ // @ts-ignore const sodium = require('sodium-universal') +const b4a = require('b4a') const base58 = require('bs58') const { getCurve, generate } = require('./curves') const StorageClass = @@ -12,6 +13,8 @@ const StorageClass = * @typedef {import('./curves').KeypairPublicSlice} KeypairPublicSlice * @typedef {import('./curves').KeypairPrivateSlice} KeypairPrivateSlice * @typedef {import('./curves').CurveName} CurveName + * + * @typedef {Buffer | Uint8Array} B4A */ /** @@ -23,37 +26,29 @@ function isString(x) { } /** - * @param {any} x - * @returns {x is Uint8Array} - */ -function isUint8(x) { - return x instanceof Uint8Array -} - -/** - * @param {Uint8Array} input - * @param {string | Uint8Array} key - * @returns {Uint8Array} + * @param {B4A} input + * @param {string | B4A} key + * @returns {B4A} */ function hmac(input, key) { - if (isString(key)) key = base58.decode(key) - const output = new Uint8Array(sodium.crypto_auth_BYTES) + if (isString(key)) key = b4a.from(base58.decode(key)) + const output = b4a.alloc(sodium.crypto_auth_BYTES) sodium.crypto_auth(output, input, key) return output } /** * Takes a keypair object (where `.public` is allowed to be undefined), a - * message as a Uint8Array (and an optional hmacKey) and returns a signature of + * message as a Buffer (and an optional hmacKey) and returns a signature of * the given message. The signature is string encoded in base58. * * @param {KeypairPrivateSlice} keypair - * @param {Uint8Array} msg - * @param {Uint8Array | string | undefined} hmacKey + * @param {B4A} msg + * @param {B4A | string | undefined} hmacKey * @returns {string} */ function sign(keypair, msg, hmacKey) { - if (!isUint8(msg)) throw new Error('Signable message should be Uint8Array') + if (!b4a.isBuffer(msg)) throw new Error('Signable message should be Buffer') const curve = getCurve(keypair.curve) if (hmacKey) msg = hmac(msg, hmacKey) @@ -63,18 +58,18 @@ function sign(keypair, msg, hmacKey) { /** * Takes a keypair object (where `private` is allowed to be undefined), a - * message Uint8Array and its signature string (and an optional hmacKey), and + * message Buffer and its signature string (and an optional hmacKey), and * returns true if the signature is valid for the message, false otherwise. * * @param {KeypairPublicSlice} keypair - * @param {Uint8Array} msg + * @param {B4A} msg * @param {string} sig - * @param {Uint8Array | string | undefined} hmacKey + * @param {B4A | string | undefined} hmacKey * @returns {boolean} */ function verify(keypair, msg, sig, hmacKey) { if (!isString(sig)) throw new Error('sig should be string') - if (!isUint8(msg)) throw new Error('Signed message should be Uint8Array') + if (!b4a.isBuffer(msg)) throw new Error('Signed message should be Buffer') const curve = getCurve(keypair.curve) if (hmacKey) msg = hmac(msg, hmacKey) diff --git a/package.json b/package.json index db96ac4..58c2f5e 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,13 @@ } }, "dependencies": { + "b4a": "^1.6.4", "bs58": "^5.0.0", "sodium-universal": "^4.0.0", "mkdirp": "~3.0.1" }, "devDependencies": { + "@types/b4a": "^1.6.0", "@types/node": "^20.2.5", "c8": "^7.11.0", "husky": "^4.3.0", diff --git a/test/fs.test.js b/test/fs.test.js index 44f9da4..1b286b8 100644 --- a/test/fs.test.js +++ b/test/fs.test.js @@ -1,8 +1,8 @@ const test = require('node:test') const assert = require('node:assert') -const fs = require('fs') -const os = require('os') -const path = require('path') +const fs = require('node:fs') +const os = require('node:os') +const path = require('node:path') const Keypair = require('../lib/index') const keyPath = path.join(os.tmpdir(), `ppppp-keypair-${Date.now()}`) diff --git a/test/generate.test.js b/test/generate.test.js index 6a9453d..e35ae6b 100644 --- a/test/generate.test.js +++ b/test/generate.test.js @@ -1,5 +1,6 @@ const test = require('node:test') const assert = require('node:assert') +const b4a = require('b4a') const Keypair = require('../lib/index') test('generate() default', (t) => { @@ -7,8 +8,8 @@ test('generate() default', (t) => { assert.equal(keypair.curve, 'ed25519') assert.equal(typeof keypair.public, 'string') assert.equal(typeof keypair.private, 'string') - assert.equal(keypair._public instanceof Uint8Array, true) - assert.equal(keypair._private instanceof Uint8Array, true) + assert.ok(b4a.isBuffer(keypair._public), true) + assert.ok(b4a.isBuffer(keypair._private), true) assert.deepEqual(Object.keys(keypair), [ 'curve', 'public', @@ -34,4 +35,4 @@ test('generate() with unknown curve', (t) => { test('generate() with seed', (t) => { const keypair = Keypair.generate('ed25519', 'alice') assert.equal(keypair.public, '4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW') -}) \ No newline at end of file +}) diff --git a/test/signing.test.js b/test/signing.test.js index 8b8a3da..19857e4 100644 --- a/test/signing.test.js +++ b/test/signing.test.js @@ -1,8 +1,8 @@ const test = require('node:test') const assert = require('node:assert') -const TextEncoder = require('node:util').TextEncoder +const crypto = require('node:crypto') +const b4a = require('b4a') const Keypair = require('../lib/index') -const crypto = require('crypto') test('sign()/verify() does not work on strings', (t) => { const str = 'ppppp' @@ -12,23 +12,23 @@ test('sign()/verify() does not work on strings', (t) => { }) }) -test('sign()/verify() a uint8arr without hmac key', (t) => { - const bytes = (new TextEncoder()).encode('ppppp') +test('sign()/verify() a buffer without hmac key', (t) => { + const buf = b4a.from('ppppp') const keypair = Keypair.generate() - const sig = Keypair.sign(keypair, bytes) + const sig = Keypair.sign(keypair, buf) assert.ok(sig) const { public, curve } = keypair - assert.ok(Keypair.verify({ public, curve }, bytes, sig)) + assert.ok(Keypair.verify({ public, curve }, buf, sig)) }) -test('sign()/verify a uint8arr with hmac key', (t) => { - const bytes = (new TextEncoder()).encode('ppppp') +test('sign()/verify a buffer with hmac key', (t) => { + const buf = b4a.from('ppppp') const keypair = Keypair.generate() const hmac_key = crypto.randomBytes(32) const hmac_key2 = crypto.randomBytes(32) - const sig = Keypair.sign(keypair, bytes, hmac_key) + const sig = Keypair.sign(keypair, buf, hmac_key) assert.ok(sig) - assert.equal(Keypair.verify(keypair, bytes, sig, hmac_key), true) - assert.equal(Keypair.verify(keypair, bytes, sig, hmac_key2), false) + assert.equal(Keypair.verify(keypair, buf, sig, hmac_key), true) + assert.equal(Keypair.verify(keypair, buf, sig, hmac_key2), false) })