]> git.codecow.com Git - nano25519.git/commitdiff
Add support for preallocated output buffer on sync derive calls.
authorChris Duncan <chris@zoso.dev>
Wed, 12 Aug 2026 08:42:58 +0000 (01:42 -0700)
committerChris Duncan <chris@zoso.dev>
Wed, 12 Aug 2026 08:42:58 +0000 (01:42 -0700)
src/lib/nano25519.ts
src/sync.ts
test.mjs

index 3c9907509ce8185ed7a0d0f38a513be20fc4a8b1..33c80a7e4a43db0ddb9b7f251955ddbed65d830e 100644 (file)
@@ -67,24 +67,36 @@ const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: typeof
                }
        }) as Exports
 
-       function derive (k: unknown): string | Uint8Array<ArrayBuffer> {
-               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<ArrayBuffer> {
+               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<ArrayBuffer>')
+               }
+               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<ArrayBuffer> {
index 0e2b3b655222371fd89ff83d0cfdca5f8610588a..6839b82b21b297711854c119c84d7d55a4f13332 100644 (file)
@@ -15,8 +15,19 @@ export function derive (k: string): string
  * @returns 32-byte public key
  */
 export function derive (k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
-export function derive (k: string | Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> {
-       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<ArrayBuffer>} input - 32-byte private key
+ * @param {Uint8Array<ArrayBuffer>} output - buffer to receive 32-byte public key
+ * @returns the same buffer allocated in memory to `output`
+ */
+export function derive (input: Uint8Array<ArrayBuffer>, output: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
+export function derive (prv: string | Uint8Array<ArrayBuffer>, pub?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> {
+       return nano25519.derive(prv, pub)
 }
 
 /**
index cb5bb24d37eb52f8948f550320088ad771d8fc20..3d8760c7a46e7aecbedcd24c324e568e42684b27 100644 (file)
--- 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<ArrayBuffer>} a
+ * @param {Uint8Array<ArrayBuffer>} 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)