replace Buffer with Uint8Array

This commit is contained in:
Andre Staltz 2023-06-12 14:44:25 +03:00
parent 73972ce951
commit 9cc0cedead
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
5 changed files with 65 additions and 57 deletions

View File

@ -1,6 +1,10 @@
// @ts-ignore // @ts-ignore
const sodium = require('sodium-universal') const sodium = require('sodium-universal')
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
@ -16,37 +20,36 @@ const SECRETKEYBYTES = sodium.crypto_sign_SECRETKEYBYTES
const ed25519 = { const ed25519 = {
/** /**
* @param {(string | Buffer)=} seed * @param {(string | Uint8Array)=} seed
* @returns {Keypair} * @returns {Keypair}
*/ */
generate(seed) { generate(seed) {
let seedBuf let seedBytes
if (seed) { if (seed) {
if (Buffer.isBuffer(seed)) { if (seed instanceof Uint8Array) {
// 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`)
seedBuf = seed seedBytes = seed
} else if (typeof seed === 'string') { } else if (typeof seed === 'string') {
seedBuf = Buffer.alloc(SEEDBYTES) seedBytes = new Uint8Array(SEEDBYTES)
const slice = seed.substring(0, SEEDBYTES) new _TextEncoder().encodeInto(seed, seedBytes)
Buffer.from(slice, 'utf-8').copy(seedBuf)
} }
} }
const publicKeyBuf = Buffer.alloc(PUBLICKEYBYTES) const publicKeyBytes = new Uint8Array(PUBLICKEYBYTES)
const secretKeyBuf = Buffer.alloc(SECRETKEYBYTES) const secretKeyBytes = new Uint8Array(SECRETKEYBYTES)
if (seedBuf) { if (seedBytes) {
sodium.crypto_sign_seed_keypair(publicKeyBuf, secretKeyBuf, seedBuf) sodium.crypto_sign_seed_keypair(publicKeyBytes, secretKeyBytes, seedBytes)
} else { } else {
sodium.crypto_sign_keypair(publicKeyBuf, secretKeyBuf) sodium.crypto_sign_keypair(publicKeyBytes, secretKeyBytes)
} }
return { return {
curve: 'ed25519', curve: 'ed25519',
public: base58.encode(publicKeyBuf), public: base58.encode(publicKeyBytes),
private: base58.encode(secretKeyBuf), private: base58.encode(secretKeyBytes),
_public: publicKeyBuf, _public: publicKeyBytes,
_private: secretKeyBuf, _private: secretKeyBytes,
} }
}, },
@ -69,15 +72,15 @@ const ed25519 = {
/** /**
* @param {KeypairPrivateSlice} keypair * @param {KeypairPrivateSlice} keypair
* @param {Buffer} message * @param {Uint8Array} 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 ??= Buffer.from(base58.decode(keypair.private)) keypair._private ??= base58.decode(keypair.private)
const sig = Buffer.alloc(sodium.crypto_sign_BYTES) const sig = new Uint8Array(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)
}, },
@ -85,16 +88,20 @@ const ed25519 = {
/** /**
* @param {KeypairPublicSlice} keypair * @param {KeypairPublicSlice} keypair
* @param {string} sig * @param {string} sig
* @param {Buffer} message * @param {Uint8Array} 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 ??= Buffer.from(base58.decode(keypair.public)) keypair._public ??= base58.decode(keypair.public)
const sigBuf = Buffer.from(base58.decode(sig)) const sigBytes = new Uint8Array(base58.decode(sig))
return sodium.crypto_sign_verify_detached(sigBuf, message, keypair._public) return sodium.crypto_sign_verify_detached(
sigBytes,
message,
keypair._public
)
}, },
} }

View File

@ -11,8 +11,8 @@ const curves = {
* curve: CurveName, * curve: CurveName,
* public: string, * public: string,
* private: string, * private: string,
* _public?: Buffer, * _public?: Uint8Array,
* _private?: Buffer, * _private?: Uint8Array,
* }} Keypair * }} Keypair
* *
* @typedef {(Pick<Keypair, 'curve' | 'public'> & {_public: never}) | * @typedef {(Pick<Keypair, 'curve' | 'public'> & {_public: never}) |
@ -24,10 +24,10 @@ const curves = {
* } KeypairPrivateSlice * } KeypairPrivateSlice
* *
* @typedef {{ * @typedef {{
* generate: (seed?: Buffer | string) => Keypair, * generate: (seed?: Uint8Array | string) => Keypair,
* toJSON: (keypair: Keypair, opts?: {indented?: boolean}) => string, * toJSON: (keypair: Keypair, opts?: {indented?: boolean}) => string,
* sign: (keypair: KeypairPrivateSlice, message: Buffer) => Buffer, * sign: (keypair: KeypairPrivateSlice, message: Uint8Array) => Uint8Array,
* verify: (keypair: KeypairPublicSlice, message: Buffer, sig: Buffer) => boolean, * verify: (keypair: KeypairPublicSlice, message: Uint8Array, sig: Uint8Array) => boolean,
* }} Curve * }} Curve
*/ */
@ -46,7 +46,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 {(Buffer | string)=} seed * @param {(Uint8Array | string)=} seed
* @returns {Keypair} * @returns {Keypair}
*/ */
function generate(curveName, seed) { function generate(curveName, seed) {

View File

@ -24,36 +24,36 @@ function isString(x) {
/** /**
* @param {any} x * @param {any} x
* @returns {x is Buffer} * @returns {x is Uint8Array}
*/ */
function isBuffer(x) { function isUint8(x) {
return Buffer.isBuffer(x) return x instanceof Uint8Array
} }
/** /**
* @param {Buffer} input * @param {Uint8Array} input
* @param {string | Buffer} key * @param {string | Uint8Array} key
* @returns {Buffer} * @returns {Uint8Array}
*/ */
function hmac(input, key) { function hmac(input, key) {
if (isString(key)) key = Buffer.from(base58.decode(key)) if (isString(key)) key = base58.decode(key)
const output = Buffer.alloc(sodium.crypto_auth_BYTES) const output = new Uint8Array(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 buffer (and an optional hmacKey) and returns a signature of the * message as a Uint8Array (and an optional hmacKey) and returns a signature of
* 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 {Buffer} msg * @param {Uint8Array} msg
* @param {Buffer | string | undefined} hmacKey * @param {Uint8Array | string | undefined} hmacKey
* @returns {string} * @returns {string}
*/ */
function sign(keypair, msg, hmacKey) { function sign(keypair, msg, hmacKey) {
if (!isBuffer(msg)) throw new Error('Signable message should be buffer') if (!isUint8(msg)) throw new Error('Signable message should be Uint8Array')
const curve = getCurve(keypair.curve) const curve = getCurve(keypair.curve)
if (hmacKey) msg = hmac(msg, hmacKey) if (hmacKey) msg = hmac(msg, hmacKey)
@ -63,18 +63,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 buffer and its signature string (and an optional hmacKey), and * message Uint8Array 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 {Buffer} msg * @param {Uint8Array} msg
* @param {string} sig * @param {string} sig
* @param {Buffer | string | undefined} hmacKey * @param {Uint8Array | 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 (!isBuffer(msg)) throw new Error('Signed message should be buffer') if (!isUint8(msg)) throw new Error('Signed message should be Uint8Array')
const curve = getCurve(keypair.curve) const curve = getCurve(keypair.curve)
if (hmacKey) msg = hmac(msg, hmacKey) if (hmacKey) msg = hmac(msg, hmacKey)

View File

@ -7,8 +7,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(Buffer.isBuffer(keypair._public), true) assert.equal(keypair._public instanceof Uint8Array, true)
assert.equal(Buffer.isBuffer(keypair._private), true) assert.equal(keypair._private instanceof Uint8Array, true)
assert.deepEqual(Object.keys(keypair), [ assert.deepEqual(Object.keys(keypair), [
'curve', 'curve',
'public', 'public',

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