From 1f7f197e61b4f4272d4ac17620fb540021091ff6 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sat, 15 Aug 2026 00:06:26 -0700 Subject: [PATCH] Update overloads and README examples with output buffer functionality. --- README.md | 80 ++++++++++++++++++++++++++++++------------- src/assembly/index.ts | 2 +- src/lib/nano25519.ts | 10 +++--- src/sync.ts | 27 ++++++--------- 4 files changed, 75 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index d3bb4be..4c6baac 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,34 @@ If a non-blocking solution is required, asynchronous implementations are provided which use Web Workers in the browser or Worker threads in NodeJS. Inputs can be passed as Uint8Array byte arrays (preferable) or strings. The -output will return the same type as the input. +output will return the same type as the input. A preallocated output buffer can +be passed as an additional optional argument to the sync versions of `derive()` +and `sign()` into which the result bytes will be written. ### Derive ```javascript -// `prv` is a 32-byte Uint8Array private key -const prv = new Uint8Array(32); -const pub = nano25519.derive(prvBytes); +// `prv` is a 32-byte private key +const prv = new Uint8Array(usersPrivateKeyBuffer); + // `pub` is a 32-byte Uint8Array public key for a Nano account +const pub = nano25519.derive(prv); +// clean up +prv.fill(0); +``` + +### Derive into preallocated buffer + +```javascript +// `prv` is a 32-byte private key +const prv = new Uint8Array(usersPrivateKeyBuffer); +// `pub` is an empty 32-byte Uint8Array +const pub = new Uint8Array(32); + +// return discarded, 32-byte public key is written to `pub` +nano25519.derive(prv, pub); +// clean up +prv.fill(0); ``` ### Derive (async) @@ -79,24 +98,39 @@ const pub = nano25519.derive(prvBytes); ```javascript // `prv` is a 64-character hex string private key const prv = "0000000000000000000000000000000000000000000000000000000000000000"; -const pub = await nano25519.deriveAsync(prv); + // `pub` is a 64-character hex string public key for a Nano account +const pub = await nano25519.deriveAsync(prv); ``` ### Sign ```javascript -// `msg` is a 32-byte Uint8Array hash of a valid Nano transaction block -const msg = new Uint8Array(32); -// `prv` is a 32-byte Uint8Array private key -const prv = new Uint8Array(32); -// `pub` is a 32-byte Uint8Array public key derived from `prv` -const pub = nano25519.derive(prv); -// `sk` is a 64-byte Uint8Array secret key joining private and public keys -const sk = new Uint8Array([...prv, ...pub]); +// `msg` is a 32-byte hash of a valid Nano transaction block +const msg = new Uint8Array(blockHashBuffer); +// `sk` is a 64-byte secret key joining private and public keys +const sk = new Uint8Array(joinPrivateKeyPublicKeyBuffer); -const sig = nano25519.sign(msg, sk); // `sig` is a 64-byte Uint8Array signature for the block hash +const sig = nano25519.sign(msg, sk); +// clean up +sk.fill(0); +``` + +### Sign into preallocated buffer + +```javascript +// `msg` is a 32-byte hash of a valid Nano transaction block +const msg = new Uint8Array(blockHashBuffer); +// `sk` is a 64-byte secret key joining private and public keys +const sk = new Uint8Array(joinPrivateKeyPublicKeyBuffer); +// `sig` is an empty 64-byte Uint8Array +const sig = new Uint8Array(64); + +// return discarded, 64-byte signature is written to `sig` +nano25519.sign(msg, sk); +// clean up +sk.fill(0); ``` ### Sign (async) @@ -111,22 +145,22 @@ const pub = await nano25519.deriveAsync(prv); // `sk` is a 128-char hex string secret key joining private and public keys const sk = prv + pub; -const sig = await nano25519.signAsync(msg, sk); // `sig` is a 128-char hex string signature for the block hash +const sig = await nano25519.signAsync(msg, sk); ``` ### Verify ```javascript -// `sig` is a 64-byte Uint8Array signature -const sig = new Uint8Array(64); -// `msg` is a 32-byte Uint8Array hash of a valid Nano transaction block -const msg = new Uint8Array(32); -// `pub` is a 32-byte Uint8Array public key for a Nano account -const pub = new Uint8Array(32); +// `sig` is a 64-byte signature +const sig = new Uint8Array(signatureBuffer); +// `msg` is a 32-byte hash of a valid Nano transaction block +const msg = new Uint8Array(blockHashBuffer); +// `pub` is a 32-byte public key for a Nano account +const pub = new Uint8Array(usersPublicKeyBuffer); -const v = nano25519.verify(sig, msg, pub); // `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `msg`, else 'false' +const v = nano25519.verify(sig, msg, pub); ``` ### Verify (async) @@ -140,8 +174,8 @@ const msg = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // `pub` is a 64-char hex string public key for a Nano account const pub = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"; -const v = await nano25519.verifyAsync(sig, msg, pub); // `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `msg`, else 'false' +const v = await nano25519.verifyAsync(sig, msg, pub); ``` ## Notes diff --git a/src/assembly/index.ts b/src/assembly/index.ts index da1d0ee..549d116 100644 --- a/src/assembly/index.ts +++ b/src/assembly/index.ts @@ -62,7 +62,7 @@ function crypto_sign (s: StaticArray, m: usize, mlen: i32, sk: StaticArray(s), 32).update(changetype(sk) + 32, 32).update(m, mlen).digest(hram) sc_reduce(hram) diff --git a/src/lib/nano25519.ts b/src/lib/nano25519.ts index ca2fe32..288bd41 100644 --- a/src/lib/nano25519.ts +++ b/src/lib/nano25519.ts @@ -67,15 +67,15 @@ export const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: } }) as Exports - function derive (k: unknown, out?: unknown): string | Uint8Array { - out ??= new Uint8Array(32) + function derive (prv: unknown, pub?: unknown): string | Uint8Array | void { + const out = pub ?? new Uint8Array(32) if (!(isBytes(out) && out.byteLength === 32)) { throw new TypeError('Derive output buffer must be 32-byte Uint8Array') } let privateKey = new Uint8Array(32) let buffer = new DataView(exports.memory.buffer) try { - privateKey.set(normalize('private key', 32, 32, k)) + 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]) @@ -88,7 +88,9 @@ export const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: out[i] = buffer.getUint8(outPtr + i) } clear(buffer) - if (typeof k === 'string') { + if (typeof pub === 'undefined') { + return + } else if (typeof prv === 'string') { let hex = '' for (const byte of out) { hex += byte.toString(16).padStart(2, '0') diff --git a/src/sync.ts b/src/sync.ts index 59e192e..b857bda 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -16,17 +16,14 @@ export function derive (k: string): string */ export function derive (k: Uint8Array): Uint8Array /** - * 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. + * Nano public key derivation using WebAssembly. Instead of allocating an output + * buffer internally for the return value, public key bytes are written to the + * the user-supplied output buffer. * @param {Uint8Array} prv - 32-byte private key * @param {Uint8Array} pub - buffer to receive 32-byte public key - * @returns the same buffer allocated in memory to `pub` */ -export function derive (k: Uint8Array, out: Uint8Array): Uint8Array -export function derive (k: string | Uint8Array, out?: Uint8Array): string | Uint8Array { +export function derive (k: Uint8Array, out: Uint8Array): void +export function derive (k: string | Uint8Array, out?: Uint8Array): string | Uint8Array | void { return nano25519.derive(k, out) } @@ -48,17 +45,15 @@ export function sign (m: string, k: string): string export function sign (m: Uint8Array, k: Uint8Array): Uint8Array /** * Signing using WebAssembly. To sign Nano blocks, the message should be a - * 32-byte block hash. Signature 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. + * 32-byte block hash. Instead of allocating an output buffer internally for the + * return value, signature bytes are written to the the user-supplied output + * buffer. * @param {Uint8Array} m - Variable-byte-length message up to 32 KiB * @param {Uint8Array} k - 64-byte secret key (prv + pub) * @param {Uint8Array} out - buffer to receive 64-byte detached signature - * @returns the same buffer allocated in memory to `s` */ -export function sign (m: Uint8Array, k: Uint8Array, out: Uint8Array): Uint8Array -export function sign (m: string | Uint8Array, k: string | Uint8Array, out?: Uint8Array): string | Uint8Array { +export function sign (m: Uint8Array, k: Uint8Array, out: Uint8Array): void +export function sign (m: string | Uint8Array, k: string | Uint8Array, out?: Uint8Array): string | Uint8Array | void { return nano25519.sign(m, k, out) } @@ -73,7 +68,7 @@ export function sign (m: string | Uint8Array, k: string | Uint8Arra export function verify (s: string, m: string, k: string): boolean /** * Signature verification using WebAssembly. To verify Nano block signatures, - * the message should be a 64-character block hash. + * the message should be a 32-byte block hash. * @param {Uint8Array} s - 64-byte detached signature * @param {Uint8Array} m - Variable-byte-length message up to 32 KiB * @param {Uint8Array} k - 32-byte public key -- 2.52.0