From 1e3b83ab3dd7a7baf5feacbf579c636d68483b9d Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Wed, 12 Aug 2026 01:42:58 -0700 Subject: [PATCH] Add support for preallocated output buffer on sync derive calls. --- src/lib/nano25519.ts | 28 ++++++++++++++++++++-------- src/sync.ts | 15 +++++++++++++-- test.mjs | 20 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/lib/nano25519.ts b/src/lib/nano25519.ts index 3c99075..33c80a7 100644 --- a/src/lib/nano25519.ts +++ b/src/lib/nano25519.ts @@ -67,24 +67,36 @@ const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: typeof } }) as Exports - function derive (k: unknown): string | Uint8Array { - const privateKey = normalize('private key', 32, 32, k) - let buffer: DataView | undefined = new DataView(exports.memory.buffer) + function derive (prv: unknown, pub?: unknown): string | Uint8Array { + let privateKey = new Uint8Array(32) + let buffer = new DataView(exports.memory.buffer) + pub ??= new Uint8Array(32) + if (!(pub instanceof Uint8Array && pub.buffer instanceof ArrayBuffer && pub.byteLength === 32)) { + throw new TypeError('Output buffer must be 32-byte Uint8Array') + } + privateKey.set(normalize('private key', 32, 32, prv)) let inPtr = exports.getInputPointer() for (let i = 0; i < 32; i++) { buffer.setUint8(inPtr + i, privateKey[i]) } + privateKey.fill(0) exports.derive() const outPtr = exports.getOutputPointer() - const sk = new Uint8Array(32) + const publicKey = new Uint8Array(pub.buffer) buffer = new DataView(exports.memory.buffer) for (let i = 0; i < 32; i++) { - sk[i] = buffer.getUint8(outPtr + i + 32) + publicKey[i] = buffer.getUint8(outPtr + i + 32) } clear(buffer) - return typeof k === 'string' - ? [...sk].map(b => b.toString(16).padStart(2, '0')).join('') - : sk + if (typeof prv === 'string') { + let hex = '' + for (const byte of publicKey) { + hex += byte.toString(16).padStart(2, '0') + } + return hex + } else { + return publicKey + } } function sign (m: unknown, k: unknown): string | Uint8Array { diff --git a/src/sync.ts b/src/sync.ts index 0e2b3b6..6839b82 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -15,8 +15,19 @@ export function derive (k: string): string * @returns 32-byte public key */ export function derive (k: Uint8Array): Uint8Array -export function derive (k: string | Uint8Array): string | Uint8Array { - return nano25519.derive(k) +/** + * Nano public key derivation using WebAssembly. Public key bytes are written to + * the user-supplied output buffer instead of being allocated internally. + * Technically, the buffer is also returned for API consistency, but the + * location in memory is the same as the output buffer, so the return value can + * be ignored. + * @param {Uint8Array} input - 32-byte private key + * @param {Uint8Array} output - buffer to receive 32-byte public key + * @returns the same buffer allocated in memory to `output` + */ +export function derive (input: Uint8Array, output: Uint8Array): Uint8Array +export function derive (prv: string | Uint8Array, pub?: Uint8Array): string | Uint8Array { + return nano25519.derive(prv, pub) } /** diff --git a/test.mjs b/test.mjs index cb5bb24..3d8760c 100644 --- a/test.mjs +++ b/test.mjs @@ -17,6 +17,18 @@ function check (name, test) { console.log(`${test ? '\x1b[32mPASS' : '\x1b[31mFAIL'}\x1b[0m: ${name}`) } +/** + * + * @param {Uint8Array} a + * @param {Uint8Array} b + */ +function overlaps (a, b) { + return a.buffer === b.buffer + && a.byteOffset === b.byteOffset + && a.byteLength === b.byteLength + && a.byteLength !== 0 +} + let result, test, passes = 0, failures = 0 // Check string input validation @@ -292,6 +304,14 @@ check(`verify signature string ${NANO_ORG_VECTOR.signature}`, test) passes += +test failures += +!test +// Check sync imports with byte inputs and preallocated output buffer +const output = new Uint8Array(32) +result = derive(NANO_ORG_VECTOR.privateKeyBytes, output) +test = overlaps(result, output) && [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase() +check(`derive from private key string ${NANO_ORG_VECTOR.privateKey}`, test) +passes += +test +failures += +!test + // test both string inputs and byte inputs for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE2B_VECTORS) { result = derive(privateKey) -- 2.52.0