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
const sodium = require('sodium-universal')
const base58 = require('bs58')
const _TextEncoder =
typeof window !== 'undefined'
? window.TextEncoder
: require('node:util').TextEncoder
/**
* @typedef {import('.').Keypair} Keypair
@ -16,37 +20,36 @@ const SECRETKEYBYTES = sodium.crypto_sign_SECRETKEYBYTES
const ed25519 = {
/**
* @param {(string | Buffer)=} seed
* @param {(string | Uint8Array)=} seed
* @returns {Keypair}
*/
generate(seed) {
let seedBuf
let seedBytes
if (seed) {
if (Buffer.isBuffer(seed)) {
if (seed instanceof Uint8Array) {
// prettier-ignore
if (seed.length !== SEEDBYTES) throw new Error(`seed must be ${SEEDBYTES} bytes`)
seedBuf = seed
seedBytes = seed
} else if (typeof seed === 'string') {
seedBuf = Buffer.alloc(SEEDBYTES)
const slice = seed.substring(0, SEEDBYTES)
Buffer.from(slice, 'utf-8').copy(seedBuf)
seedBytes = new Uint8Array(SEEDBYTES)
new _TextEncoder().encodeInto(seed, seedBytes)
}
}
const publicKeyBuf = Buffer.alloc(PUBLICKEYBYTES)
const secretKeyBuf = Buffer.alloc(SECRETKEYBYTES)
if (seedBuf) {
sodium.crypto_sign_seed_keypair(publicKeyBuf, secretKeyBuf, seedBuf)
const publicKeyBytes = new Uint8Array(PUBLICKEYBYTES)
const secretKeyBytes = new Uint8Array(SECRETKEYBYTES)
if (seedBytes) {
sodium.crypto_sign_seed_keypair(publicKeyBytes, secretKeyBytes, seedBytes)
} else {
sodium.crypto_sign_keypair(publicKeyBuf, secretKeyBuf)
sodium.crypto_sign_keypair(publicKeyBytes, secretKeyBytes)
}
return {
curve: 'ed25519',
public: base58.encode(publicKeyBuf),
private: base58.encode(secretKeyBuf),
_public: publicKeyBuf,
_private: secretKeyBuf,
public: base58.encode(publicKeyBytes),
private: base58.encode(secretKeyBytes),
_public: publicKeyBytes,
_private: secretKeyBytes,
}
},
@ -69,15 +72,15 @@ const ed25519 = {
/**
* @param {KeypairPrivateSlice} keypair
* @param {Buffer} message
* @param {Uint8Array} message
* @returns {string}
*/
sign(keypair, message) {
if (!keypair._private && !keypair.private) {
throw new Error(`invalid ed25519 keypair with missing private key`)
}
keypair._private ??= Buffer.from(base58.decode(keypair.private))
const sig = Buffer.alloc(sodium.crypto_sign_BYTES)
keypair._private ??= base58.decode(keypair.private)
const sig = new Uint8Array(sodium.crypto_sign_BYTES)
sodium.crypto_sign_detached(sig, message, keypair._private)
return base58.encode(sig)
},
@ -85,16 +88,20 @@ const ed25519 = {
/**
* @param {KeypairPublicSlice} keypair
* @param {string} sig
* @param {Buffer} message
* @param {Uint8Array} message
* @returns {boolean}
*/
verify(keypair, sig, message) {
if (!keypair._public && !keypair.public) {
throw new Error(`invalid ed25519 keypair with missing public key`)
}
keypair._public ??= Buffer.from(base58.decode(keypair.public))
const sigBuf = Buffer.from(base58.decode(sig))
return sodium.crypto_sign_verify_detached(sigBuf, message, keypair._public)
keypair._public ??= base58.decode(keypair.public)
const sigBytes = new Uint8Array(base58.decode(sig))
return sodium.crypto_sign_verify_detached(
sigBytes,
message,
keypair._public
)
},
}

View File

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

View File

@ -24,36 +24,36 @@ function isString(x) {
/**
* @param {any} x
* @returns {x is Buffer}
* @returns {x is Uint8Array}
*/
function isBuffer(x) {
return Buffer.isBuffer(x)
function isUint8(x) {
return x instanceof Uint8Array
}
/**
* @param {Buffer} input
* @param {string | Buffer} key
* @returns {Buffer}
* @param {Uint8Array} input
* @param {string | Uint8Array} key
* @returns {Uint8Array}
*/
function hmac(input, key) {
if (isString(key)) key = Buffer.from(base58.decode(key))
const output = Buffer.alloc(sodium.crypto_auth_BYTES)
if (isString(key)) key = base58.decode(key)
const output = new Uint8Array(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 buffer (and an optional hmacKey) and returns a signature of the
* given message. The signature is string encoded in base58.
* message as a Uint8Array (and an optional hmacKey) and returns a signature of
* the given message. The signature is string encoded in base58.
*
* @param {KeypairPrivateSlice} keypair
* @param {Buffer} msg
* @param {Buffer | string | undefined} hmacKey
* @param {Uint8Array} msg
* @param {Uint8Array | string | undefined} hmacKey
* @returns {string}
*/
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)
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
* 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.
*
* @param {KeypairPublicSlice} keypair
* @param {Buffer} msg
* @param {Uint8Array} msg
* @param {string} sig
* @param {Buffer | string | undefined} hmacKey
* @param {Uint8Array | string | undefined} hmacKey
* @returns {boolean}
*/
function verify(keypair, msg, sig, hmacKey) {
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)
if (hmacKey) msg = hmac(msg, hmacKey)

View File

@ -7,8 +7,8 @@ test('generate() default', (t) => {
assert.equal(keypair.curve, 'ed25519')
assert.equal(typeof keypair.public, 'string')
assert.equal(typeof keypair.private, 'string')
assert.equal(Buffer.isBuffer(keypair._public), true)
assert.equal(Buffer.isBuffer(keypair._private), true)
assert.equal(keypair._public instanceof Uint8Array, true)
assert.equal(keypair._private instanceof Uint8Array, true)
assert.deepEqual(Object.keys(keypair), [
'curve',
'public',

View File

@ -1,5 +1,6 @@
const test = require('node:test')
const assert = require('node:assert')
const TextEncoder = require('node:util').TextEncoder
const Keypair = require('../lib/index')
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) => {
const buf = Buffer.from('ppppp')
test('sign()/verify() a uint8arr without hmac key', (t) => {
const bytes = (new TextEncoder()).encode('ppppp')
const keypair = Keypair.generate()
const sig = Keypair.sign(keypair, buf)
const sig = Keypair.sign(keypair, bytes)
assert.ok(sig)
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) => {
const str = Buffer.from('ppppp')
test('sign()/verify a uint8arr with hmac key', (t) => {
const bytes = (new TextEncoder()).encode('ppppp')
const keypair = Keypair.generate()
const hmac_key = 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.equal(Keypair.verify(keypair, str, sig, hmac_key), true)
assert.equal(Keypair.verify(keypair, str, sig, hmac_key2), false)
assert.equal(Keypair.verify(keypair, bytes, sig, hmac_key), true)
assert.equal(Keypair.verify(keypair, bytes, sig, hmac_key2), false)
})