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
|
// @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
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
34
lib/index.js
34
lib/index.js
|
@ -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)
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue