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
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,

View File

@ -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<Keypair, 'curve' | 'public'> & {_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) {

View File

@ -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)

View File

@ -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",

View File

@ -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()}`)

View File

@ -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')
})
})

View File

@ -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)
})