]> git.codecow.com Git - nano25519.git/commitdiff
Implement mutex pattern to prevent async wrappers or similar from breaking shared...
authorChris Duncan <chris@zoso.dev>
Sat, 29 Aug 2026 08:46:09 +0000 (01:46 -0700)
committerChris Duncan <chris@zoso.dev>
Sat, 29 Aug 2026 08:46:09 +0000 (01:46 -0700)
src/lib/derive.ts
src/lib/sign.ts
src/lib/verify.ts
src/lib/wasm.ts

index 5a831f885d7fdc087c5e52ef06e5b01aca175fb6..30f01ad2fe605951325cdace7f58730ae7554f81 100644 (file)
@@ -1,10 +1,9 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { clear, constants, exports, isBytes, normalize, pointers, } from './wasm'
+import { Mutex, Pointers, clear, constants, exports, isBytes, normalize } from './wasm'
 
 const { KEY_BYTELENGTH, } = constants
-const { INPUT_PRV, OUTPUT_DERIVE, } = pointers
 
 export function derive (prv: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
        if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === KEY_BYTELENGTH)) {
@@ -14,13 +13,14 @@ export function derive (prv: unknown, out?: unknown): string | Uint8Array<ArrayB
        const publicKey = new Uint8Array(KEY_BYTELENGTH)
        let buffer = new Uint8Array(exports.memory.buffer)
        try {
+               Mutex.lock()
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
-                       buffer[INPUT_PRV + i] = privateKey[i]
+                       buffer[Pointers.INPUT_PRV + i] = privateKey[i]
                }
                exports.derive()
                buffer = new Uint8Array(exports.memory.buffer)
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
-                       publicKey[i] = buffer[OUTPUT_DERIVE + i]
+                       publicKey[i] = buffer[Pointers.OUTPUT_DERIVE + i]
                }
                if (typeof prv === 'string') {
                        let hex = ''
@@ -36,7 +36,7 @@ export function derive (prv: unknown, out?: unknown): string | Uint8Array<ArrayB
                        return publicKey
                }
        } finally {
-               clear(buffer)
                privateKey.fill(0)
+               clear(buffer)
        }
 }
index 832e5a51701475bd6d8713718f88bcf42bd4a8db..fe505707b80e3938a328ab3e05a81f56236bc1c4 100644 (file)
@@ -1,10 +1,9 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { clear, constants, exports, isBytes, normalize, pointers, } from './wasm'
+import { Mutex, Pointers, clear, constants, exports, isBytes, normalize } from './wasm'
 
 const { KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, SIGNATURE_BYTELENGTH, } = constants
-const { INPUT_MSG, INPUT_PRV, INPUT_PUB, OUTPUT_SIGN, } = pointers
 
 export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
        if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === SIGNATURE_BYTELENGTH)) {
@@ -16,19 +15,20 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown):
        const signature = new Uint8Array(SIGNATURE_BYTELENGTH)
        let buffer = new Uint8Array(exports.memory.buffer)
        try {
+               Mutex.lock()
                for (let i = 0; i < message.byteLength; i++) {
-                       buffer[INPUT_MSG + i] = message[i]
+                       buffer[Pointers.INPUT_MSG + i] = message[i]
                }
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
-                       buffer[INPUT_PRV + i] = privateKey[i]
+                       buffer[Pointers.INPUT_PRV + i] = privateKey[i]
                }
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
-                       buffer[INPUT_PUB + i] = publicKey[i]
+                       buffer[Pointers.INPUT_PUB + i] = publicKey[i]
                }
                exports.sign(message.byteLength)
                buffer = new Uint8Array(exports.memory.buffer)
                for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) {
-                       signature[i] = buffer[OUTPUT_SIGN + i]
+                       signature[i] = buffer[Pointers.OUTPUT_SIGN + i]
                }
                if (typeof prv === 'string') {
                        let hex = ''
@@ -44,8 +44,8 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown):
                        return signature
                }
        } finally {
-               clear(buffer)
                privateKey.fill(0)
                publicKey.fill(0)
+               clear(buffer)
        }
 }
index b622c5f968d5e386af04218ef284ff233233924d..2b2ae1ef085ab6c500df8ce180c54682af1a763d 100644 (file)
@@ -1,29 +1,29 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { clear, constants, exports, normalize, pointers, } from './wasm'
+import { Mutex, Pointers, clear, constants, exports, normalize, } from './wasm'
 
 const { BLOCKHASH_BYTELENGTH, KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, MAX_VERIFY_BLOCKS, SIGNATURE_BYTELENGTH, } = constants
-const { INPUT_MSG, INPUT_PUB, INPUT_SIG, OUTPUT_VERIFY, } = pointers
 
 export function verify (sig: unknown, msg: unknown, pub: unknown): boolean {
        let buffer = new Uint8Array(exports.memory.buffer)
        try {
+               Mutex.lock()
                const message = normalize('message', 0, MAX_MESSAGE_BYTELENGTH, msg)
                const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub)
                const signature = normalize('signature', SIGNATURE_BYTELENGTH, SIGNATURE_BYTELENGTH, sig)
                for (let i = 0; i < message.byteLength; i++) {
-                       buffer[INPUT_MSG + i] = message[i]
+                       buffer[Pointers.INPUT_MSG + i] = message[i]
                }
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
-                       buffer[INPUT_PUB + i] = publicKey[i]
+                       buffer[Pointers.INPUT_PUB + i] = publicKey[i]
                }
                for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) {
-                       buffer[INPUT_SIG + i] = signature[i]
+                       buffer[Pointers.INPUT_SIG + i] = signature[i]
                }
                exports.verify(message.byteLength)
                buffer = new Uint8Array(exports.memory.buffer)
-               const verified = buffer[OUTPUT_VERIFY]
+               const verified = buffer[Pointers.OUTPUT_VERIFY]
                return verified === 0
        } finally {
                clear(buffer)
@@ -42,11 +42,12 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] {
        const verified = new Uint8Array(count)
        let buffer = new Uint8Array(exports.memory.buffer)
        try {
+               Mutex.lock()
                const publicKey = normalize('public key', KEY_BYTELENGTH, KEY_BYTELENGTH, pub)
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
-                       buffer[INPUT_PUB + i] = publicKey[i]
+                       buffer[Pointers.INPUT_PUB + i] = publicKey[i]
                }
-               for (let b = 0, p = INPUT_MSG; b < count; b++) {
+               for (let b = 0, p = Pointers.INPUT_MSG; b < count; b++) {
                        const block = blocks[b]
                        if (block == null || typeof block !== 'object') {
                                throw new TypeError('Invalid block', { cause: block })
@@ -71,7 +72,7 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] {
                exports.verify_blocks(count)
                buffer = new Uint8Array(exports.memory.buffer)
                for (let i = 0; i < count; i++) {
-                       verified[i] = buffer[OUTPUT_VERIFY + i]
+                       verified[i] = buffer[Pointers.OUTPUT_VERIFY + i]
                }
                return [...verified].map(v => v === 0)
        } finally {
index 37bdbb438057814623862c668db77b41277c2440..664070027b68ac1a1f359f600fbffa8f43e59206 100644 (file)
@@ -27,6 +27,16 @@ type Exports = {
        }
 }
 
+export class Nano25519TypeError extends TypeError { }
+
+let locked = false
+export const Mutex = {
+       lock (): void {
+               if (locked) throw new Nano25519TypeError('Failed to acquire mutex')
+               locked = true
+       }
+}
+
 const wasm: Uint8Array<ArrayBuffer> = Uint8Array.from(nano25519_wasm)
 const module = new WebAssembly.Module(wasm)
 
@@ -79,14 +89,18 @@ const OUTPUT_DERIVE = exports.ptrOutputDerive()
 const OUTPUT_SIGN = exports.ptrOutputSign()
 const OUTPUT_VERIFY = exports.ptrOutputVerify()
 
-export const pointers = {
-       INPUT_MSG,
-       INPUT_PRV,
-       INPUT_PUB,
-       INPUT_SIG,
-       OUTPUT_DERIVE,
-       OUTPUT_SIGN,
-       OUTPUT_VERIFY,
+export class Pointers {
+       static #get (ptr: number): number {
+               if (locked) return ptr
+               throw new Nano25519TypeError('Mutex not acquired')
+       }
+       static get INPUT_MSG (): number { return this.#get(INPUT_MSG) }
+       static get INPUT_PRV (): number { return this.#get(INPUT_PRV) }
+       static get INPUT_PUB (): number { return this.#get(INPUT_PUB) }
+       static get INPUT_SIG (): number { return this.#get(INPUT_SIG) }
+       static get OUTPUT_DERIVE (): number { return this.#get(OUTPUT_DERIVE) }
+       static get OUTPUT_SIGN (): number { return this.#get(OUTPUT_SIGN) }
+       static get OUTPUT_VERIFY (): number { return this.#get(OUTPUT_VERIFY) }
 }
 
 export function clear (memory: Uint8Array): void {
@@ -97,6 +111,7 @@ export function clear (memory: Uint8Array): void {
        memory.fill(0, OUTPUT_DERIVE, OUTPUT_DERIVE + KEY_BYTELENGTH)
        memory.fill(0, OUTPUT_SIGN, OUTPUT_SIGN + SIGNATURE_BYTELENGTH)
        memory.fill(255, OUTPUT_VERIFY, OUTPUT_VERIFY + MAX_VERIFY_BLOCKS)
+       locked = false
 }
 
 export function isBytes (a: unknown): a is Uint8Array<ArrayBuffer> {