mirror of https://codeberg.org/pzp/pzp-keypair.git
replace Uint8Array with b4a
This commit is contained in:
parent
e9de23376e
commit
6757cab543
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
37
lib/index.js
37
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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()}`)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue