replace Uint8Array with b4a

This commit is contained in:
Andre Staltz 2023-06-15 13:53:47 +03:00
parent e9de23376e
commit 6757cab543
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
7 changed files with 58 additions and 62 deletions

View File

@ -1,17 +1,13 @@
// @ts-ignore // @ts-ignore
const sodium = require('sodium-universal') const sodium = require('sodium-universal')
const b4a = require('b4a')
const base58 = require('bs58') const base58 = require('bs58')
const _TextEncoder =
typeof window !== 'undefined'
? window.TextEncoder
: require('node:util').TextEncoder
/** /**
* @typedef {import('.').Keypair} Keypair * @typedef {import('.').Keypair} Keypair
*
* @typedef {import('.').KeypairPublicSlice} KeypairPublicSlice * @typedef {import('.').KeypairPublicSlice} KeypairPublicSlice
*
* @typedef {import('.').KeypairPrivateSlice} KeypairPrivateSlice * @typedef {import('.').KeypairPrivateSlice} KeypairPrivateSlice
* @typedef {Buffer | Uint8Array} B4A
*/ */
const SEEDBYTES = sodium.crypto_sign_SEEDBYTES const SEEDBYTES = sodium.crypto_sign_SEEDBYTES
@ -20,24 +16,24 @@ const SECRETKEYBYTES = sodium.crypto_sign_SECRETKEYBYTES
const ed25519 = { const ed25519 = {
/** /**
* @param {(string | Uint8Array)=} seed * @param {(string | B4A)=} seed
* @returns {Keypair} * @returns {Keypair}
*/ */
generate(seed) { generate(seed) {
let seedBytes let seedBytes
if (seed) { if (seed) {
if (seed instanceof Uint8Array) { if (b4a.isBuffer(seed)) {
// prettier-ignore // prettier-ignore
if (seed.length !== SEEDBYTES) throw new Error(`seed must be ${SEEDBYTES} bytes`) if (seed.length !== SEEDBYTES) throw new Error(`seed must be ${SEEDBYTES} bytes`)
seedBytes = seed seedBytes = seed
} else if (typeof seed === 'string') { } else if (typeof seed === 'string') {
seedBytes = new Uint8Array(SEEDBYTES) seedBytes = b4a.alloc(SEEDBYTES)
new _TextEncoder().encodeInto(seed, seedBytes) b4a.copy(b4a.from(seed.substring(0, 32), 'utf-8'), seedBytes)
} }
} }
const publicKeyBytes = new Uint8Array(PUBLICKEYBYTES) const publicKeyBytes = b4a.alloc(PUBLICKEYBYTES)
const secretKeyBytes = new Uint8Array(SECRETKEYBYTES) const secretKeyBytes = b4a.alloc(SECRETKEYBYTES)
if (seedBytes) { if (seedBytes) {
sodium.crypto_sign_seed_keypair(publicKeyBytes, secretKeyBytes, seedBytes) sodium.crypto_sign_seed_keypair(publicKeyBytes, secretKeyBytes, seedBytes)
} else { } else {
@ -72,15 +68,15 @@ const ed25519 = {
/** /**
* @param {KeypairPrivateSlice} keypair * @param {KeypairPrivateSlice} keypair
* @param {Uint8Array} message * @param {B4A} message
* @returns {string} * @returns {string}
*/ */
sign(keypair, message) { sign(keypair, message) {
if (!keypair._private && !keypair.private) { if (!keypair._private && !keypair.private) {
throw new Error(`invalid ed25519 keypair with missing private key`) throw new Error(`invalid ed25519 keypair with missing private key`)
} }
keypair._private ??= base58.decode(keypair.private) keypair._private ??= b4a.from(base58.decode(keypair.private))
const sig = new Uint8Array(sodium.crypto_sign_BYTES) const sig = b4a.alloc(sodium.crypto_sign_BYTES)
sodium.crypto_sign_detached(sig, message, keypair._private) sodium.crypto_sign_detached(sig, message, keypair._private)
return base58.encode(sig) return base58.encode(sig)
}, },
@ -88,15 +84,15 @@ const ed25519 = {
/** /**
* @param {KeypairPublicSlice} keypair * @param {KeypairPublicSlice} keypair
* @param {string} sig * @param {string} sig
* @param {Uint8Array} message * @param {B4A} message
* @returns {boolean} * @returns {boolean}
*/ */
verify(keypair, sig, message) { verify(keypair, sig, message) {
if (!keypair._public && !keypair.public) { if (!keypair._public && !keypair.public) {
throw new Error(`invalid ed25519 keypair with missing public key`) throw new Error(`invalid ed25519 keypair with missing public key`)
} }
keypair._public ??= base58.decode(keypair.public) keypair._public ??= b4a.from(base58.decode(keypair.public))
const sigBytes = new Uint8Array(base58.decode(sig)) const sigBytes = b4a.from(base58.decode(sig))
return sodium.crypto_sign_verify_detached( return sodium.crypto_sign_verify_detached(
sigBytes, sigBytes,
message, message,

View File

@ -7,12 +7,14 @@ const curves = {
/** /**
* @typedef {keyof typeof curves} CurveName * @typedef {keyof typeof curves} CurveName
* *
* @typedef {Buffer | Uint8Array} B4A
*
* @typedef {{ * @typedef {{
* curve: CurveName, * curve: CurveName,
* public: string, * public: string,
* private: string, * private: string,
* _public?: Uint8Array, * _public?: B4A,
* _private?: Uint8Array, * _private?: B4A,
* }} Keypair * }} Keypair
* *
* @typedef {(Pick<Keypair, 'curve' | 'public'> & {_public: never}) | * @typedef {(Pick<Keypair, 'curve' | 'public'> & {_public: never}) |
@ -24,10 +26,10 @@ const curves = {
* } KeypairPrivateSlice * } KeypairPrivateSlice
* *
* @typedef {{ * @typedef {{
* generate: (seed?: Uint8Array | string) => Keypair, * generate: (seed?: B4A | string) => Keypair,
* toJSON: (keypair: Keypair, opts?: {indented?: boolean}) => string, * toJSON: (keypair: Keypair, opts?: {indented?: boolean}) => string,
* sign: (keypair: KeypairPrivateSlice, message: Uint8Array) => Uint8Array, * sign: (keypair: KeypairPrivateSlice, message: B4A) => B4A,
* verify: (keypair: KeypairPublicSlice, message: Uint8Array, sig: Uint8Array) => boolean, * verify: (keypair: KeypairPublicSlice, message: B4A, sig: B4A) => boolean,
* }} Curve * }} Curve
*/ */
@ -46,7 +48,7 @@ function getCurve(curveName) {
* This function generates a keypair for the given curve. The seed is optional. * This function generates a keypair for the given curve. The seed is optional.
* *
* @param {CurveName=} curveName * @param {CurveName=} curveName
* @param {(Uint8Array | string)=} seed * @param {(B4A | string)=} seed
* @returns {Keypair} * @returns {Keypair}
*/ */
function generate(curveName, seed) { function generate(curveName, seed) {

View File

@ -1,5 +1,6 @@
// @ts-ignore // @ts-ignore
const sodium = require('sodium-universal') const sodium = require('sodium-universal')
const b4a = require('b4a')
const base58 = require('bs58') const base58 = require('bs58')
const { getCurve, generate } = require('./curves') const { getCurve, generate } = require('./curves')
const StorageClass = const StorageClass =
@ -12,6 +13,8 @@ const StorageClass =
* @typedef {import('./curves').KeypairPublicSlice} KeypairPublicSlice * @typedef {import('./curves').KeypairPublicSlice} KeypairPublicSlice
* @typedef {import('./curves').KeypairPrivateSlice} KeypairPrivateSlice * @typedef {import('./curves').KeypairPrivateSlice} KeypairPrivateSlice
* @typedef {import('./curves').CurveName} CurveName * @typedef {import('./curves').CurveName} CurveName
*
* @typedef {Buffer | Uint8Array} B4A
*/ */
/** /**
@ -23,37 +26,29 @@ function isString(x) {
} }
/** /**
* @param {any} x * @param {B4A} input
* @returns {x is Uint8Array} * @param {string | B4A} key
*/ * @returns {B4A}
function isUint8(x) {
return x instanceof Uint8Array
}
/**
* @param {Uint8Array} input
* @param {string | Uint8Array} key
* @returns {Uint8Array}
*/ */
function hmac(input, key) { function hmac(input, key) {
if (isString(key)) key = base58.decode(key) if (isString(key)) key = b4a.from(base58.decode(key))
const output = new Uint8Array(sodium.crypto_auth_BYTES) const output = b4a.alloc(sodium.crypto_auth_BYTES)
sodium.crypto_auth(output, input, key) sodium.crypto_auth(output, input, key)
return output return output
} }
/** /**
* Takes a keypair object (where `.public` is allowed to be undefined), a * 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. * the given message. The signature is string encoded in base58.
* *
* @param {KeypairPrivateSlice} keypair * @param {KeypairPrivateSlice} keypair
* @param {Uint8Array} msg * @param {B4A} msg
* @param {Uint8Array | string | undefined} hmacKey * @param {B4A | string | undefined} hmacKey
* @returns {string} * @returns {string}
*/ */
function sign(keypair, msg, hmacKey) { 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) const curve = getCurve(keypair.curve)
if (hmacKey) msg = hmac(msg, hmacKey) 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 * 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. * returns true if the signature is valid for the message, false otherwise.
* *
* @param {KeypairPublicSlice} keypair * @param {KeypairPublicSlice} keypair
* @param {Uint8Array} msg * @param {B4A} msg
* @param {string} sig * @param {string} sig
* @param {Uint8Array | string | undefined} hmacKey * @param {B4A | string | undefined} hmacKey
* @returns {boolean} * @returns {boolean}
*/ */
function verify(keypair, msg, sig, hmacKey) { function verify(keypair, msg, sig, hmacKey) {
if (!isString(sig)) throw new Error('sig should be string') 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) const curve = getCurve(keypair.curve)
if (hmacKey) msg = hmac(msg, hmacKey) if (hmacKey) msg = hmac(msg, hmacKey)

View File

@ -23,11 +23,13 @@
} }
}, },
"dependencies": { "dependencies": {
"b4a": "^1.6.4",
"bs58": "^5.0.0", "bs58": "^5.0.0",
"sodium-universal": "^4.0.0", "sodium-universal": "^4.0.0",
"mkdirp": "~3.0.1" "mkdirp": "~3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/b4a": "^1.6.0",
"@types/node": "^20.2.5", "@types/node": "^20.2.5",
"c8": "^7.11.0", "c8": "^7.11.0",
"husky": "^4.3.0", "husky": "^4.3.0",

View File

@ -1,8 +1,8 @@
const test = require('node:test') const test = require('node:test')
const assert = require('node:assert') const assert = require('node:assert')
const fs = require('fs') const fs = require('node:fs')
const os = require('os') const os = require('node:os')
const path = require('path') const path = require('node:path')
const Keypair = require('../lib/index') const Keypair = require('../lib/index')
const keyPath = path.join(os.tmpdir(), `ppppp-keypair-${Date.now()}`) const keyPath = path.join(os.tmpdir(), `ppppp-keypair-${Date.now()}`)

View File

@ -1,5 +1,6 @@
const test = require('node:test') const test = require('node:test')
const assert = require('node:assert') const assert = require('node:assert')
const b4a = require('b4a')
const Keypair = require('../lib/index') const Keypair = require('../lib/index')
test('generate() default', (t) => { test('generate() default', (t) => {
@ -7,8 +8,8 @@ test('generate() default', (t) => {
assert.equal(keypair.curve, 'ed25519') assert.equal(keypair.curve, 'ed25519')
assert.equal(typeof keypair.public, 'string') assert.equal(typeof keypair.public, 'string')
assert.equal(typeof keypair.private, 'string') assert.equal(typeof keypair.private, 'string')
assert.equal(keypair._public instanceof Uint8Array, true) assert.ok(b4a.isBuffer(keypair._public), true)
assert.equal(keypair._private instanceof Uint8Array, true) assert.ok(b4a.isBuffer(keypair._private), true)
assert.deepEqual(Object.keys(keypair), [ assert.deepEqual(Object.keys(keypair), [
'curve', 'curve',
'public', 'public',
@ -34,4 +35,4 @@ test('generate() with unknown curve', (t) => {
test('generate() with seed', (t) => { test('generate() with seed', (t) => {
const keypair = Keypair.generate('ed25519', 'alice') const keypair = Keypair.generate('ed25519', 'alice')
assert.equal(keypair.public, '4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW') assert.equal(keypair.public, '4mjQ5aJu378cEu6TksRG3uXAiKFiwGjYQtWAjfVjDAJW')
}) })

View File

@ -1,8 +1,8 @@
const test = require('node:test') const test = require('node:test')
const assert = require('node:assert') 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 Keypair = require('../lib/index')
const crypto = require('crypto')
test('sign()/verify() does not work on strings', (t) => { test('sign()/verify() does not work on strings', (t) => {
const str = 'ppppp' 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) => { test('sign()/verify() a buffer without hmac key', (t) => {
const bytes = (new TextEncoder()).encode('ppppp') const buf = b4a.from('ppppp')
const keypair = Keypair.generate() const keypair = Keypair.generate()
const sig = Keypair.sign(keypair, bytes) const sig = Keypair.sign(keypair, buf)
assert.ok(sig) assert.ok(sig)
const { public, curve } = keypair 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) => { test('sign()/verify a buffer with hmac key', (t) => {
const bytes = (new TextEncoder()).encode('ppppp') const buf = b4a.from('ppppp')
const keypair = Keypair.generate() const keypair = Keypair.generate()
const hmac_key = crypto.randomBytes(32) const hmac_key = crypto.randomBytes(32)
const hmac_key2 = 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.ok(sig)
assert.equal(Keypair.verify(keypair, bytes, sig, hmac_key), true) assert.equal(Keypair.verify(keypair, buf, sig, hmac_key), true)
assert.equal(Keypair.verify(keypair, bytes, sig, hmac_key2), false) assert.equal(Keypair.verify(keypair, buf, sig, hmac_key2), false)
}) })