mirror of https://codeberg.org/pzp/pzp-keypair.git
replace Buffer with Uint8Array
This commit is contained in:
parent
73972ce951
commit
9cc0cedead
|
@ -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
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
34
lib/index.js
34
lib/index.js
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue