]> git.codecow.com Git - nano25519.git/commitdiff
Require output buffer arg in order to receive output in bytes, else return string...
authorChris Duncan <chris@zoso.dev>
Sun, 6 Sep 2026 07:31:48 +0000 (00:31 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 6 Sep 2026 07:31:48 +0000 (00:31 -0700)
src/assembly/crypto_verify.ts
src/assembly/index.ts
src/index.ts
src/lib/derive.ts
src/lib/sign.ts
src/lib/verify.ts
test/node.mjs

index 9c98c97b3663ae212e541e4533bb12d2bbd1c554..ccd1965064d81c424a29d0584025cc7f7eab1bc5 100644 (file)
@@ -40,12 +40,12 @@ export function crypto_verify_decodepubkey (pub: StaticArray<u8>): ge_p3 | null
  *
  * Call `crypto_verify_decodepubkey` first in order to get decode the public key
  * to `A` for the scalar multiplication step before checking `sB = R + hA`.
- * @returns -1 if signature fails to verify, else return 0 if signature is good
+ * @returns 0 if signature fails to verify, else return 1 if signature is good
  */
 export function crypto_verify_donna (sig: StaticArray<u8>, M: StaticArray<u8>, A: ge_p3, pub: StaticArray<u8>): i32 {
 
        // fail if private scalar `S` is out of range (`2²⁵³ ≤ S`)
-       if ((sig[63] & 224) != 0) return -1
+       if ((sig[63] & 224) != 0) return 0
 
        memory.copy(changetype<usize>(S), changetype<usize>(sig) + 32, 32)
 
@@ -59,7 +59,7 @@ export function crypto_verify_donna (sig: StaticArray<u8>, M: StaticArray<u8>, A
        ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S)
        ge_p3_tobytes(check_r, sb_ah)
 
-       return equalbytes(sig, check_r, KEY_BYTELENGTH) - 1
+       return equalbytes(sig, check_r, KEY_BYTELENGTH)
 }
 
 /**
@@ -68,25 +68,25 @@ export function crypto_verify_donna (sig: StaticArray<u8>, M: StaticArray<u8>, A
  *
  * Unlike `crypto_verify_donna`, this function is intended for single-use
  * verifications and thus can compute and set `A` itself.
- * @returns -1 if signature fails to verify, else return 0 if signature is good
+ * @returns 0 if signature fails to verify, else return 1 if signature is good
  */
 export function crypto_verify_sodium (sig: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, pub: StaticArray<u8>): i32 {
 
        // fail if public key `k` is non-canonical (`p = 2²⁵⁵-19 ≤ k`)
-       if (!ge_is_canonical(pub)) return -1
+       if (!ge_is_canonical(pub)) return 0
 
        // fail if public key cannot be decoded
-       if (ge_frombytes_negate_vartime(A, pub) != 0) return -1
+       if (ge_frombytes_negate_vartime(A, pub) != 0) return 0
 
        // fail if public key `k` is small order
-       if (ge_has_small_order(A) != 0) return -1
+       if (ge_has_small_order(A) != 0) return 0
 
        // fail if private scalar `S` is non-canonical (`L ≤ S`)
        memory.copy(changetype<usize>(S), changetype<usize>(sig) + 32, 32)
-       if (!sc_is_canonical(S)) return -1
+       if (!sc_is_canonical(S)) return 0
 
-       if (ge_frombytes(expected_r, sig) != 0) return -1
-       if (ge_has_small_order(expected_r) != 0) return -1
+       if (ge_frombytes(expected_r, sig) != 0) return 0
+       if (ge_has_small_order(expected_r) != 0) return 0
 
        // signature is nonce point R and scalar S (R || S)
        // data to hash is nonce point R, public key A, and message M
@@ -98,7 +98,7 @@ export function crypto_verify_sodium (sig: StaticArray<u8>, M: StaticArray<u8>,
        ge_double_scalarmult_vartime_to_p3(sb_ah, h, A, S)
        ge_sub_p3(check, expected_r, sb_ah)
 
-       return ge_has_small_order(check) - 1
+       return ge_has_small_order(check)
 
        // ge_p3_tobytes(check_r, sb_ah)
        // return equalbytes(s, check_r, 32) - 1
index 16d1feaac0a31bbfe36bb175dcd3ff1337607afa..dfc27d14f2395e0da3b4391260c16fc3bc8e0757 100644 (file)
@@ -18,7 +18,7 @@ const INPUT_SIG = new StaticArray<u8>(SIGNATURE_BYTELENGTH)
 
 const OUTPUT_DERIVE = new StaticArray<u8>(KEY_BYTELENGTH)
 const OUTPUT_SIGN = new StaticArray<u8>(SIGNATURE_BYTELENGTH)
-const OUTPUT_VERIFY = new StaticArray<u8>(MAX_VERIFY_BLOCKS).fill(255)
+const OUTPUT_VERIFY = new StaticArray<u8>(MAX_VERIFY_BLOCKS)
 
 /** Returns a pointer to the static message input buffer. */
 export function ptrInputMsg (): usize {
@@ -145,8 +145,8 @@ export function sign (mlen: i32): void {
  * @returns {boolean} True if message was signed by public key's private key
  */
 export function verify (mlen: i32): void {
-       // Set all output to false so errors fail closed
-       OUTPUT_VERIFY.fill(255)
+       // Clear output buffer so errors do not retain stale prior data
+       OUTPUT_VERIFY.fill(0)
 
        // Clear local buffers
        pub.fill(0)
@@ -192,8 +192,8 @@ export function verify (mlen: i32): void {
  * @param {i32} count Number of signatures to verify, up to 64
  */
 export function verify_blocks (count: i32): void {
-       // Set all output to false so errors fail closed
-       OUTPUT_VERIFY.fill(255)
+       // Clear output buffer so errors do not retain stale prior data
+       OUTPUT_VERIFY.fill(0)
 
        // Clear local buffers
        h.fill(0)
index 8d00b9e64e8602ab0328b6bff767a93d3cdff29c..38cb81cf3f278ea0863fd72fd3638a1a0f0c50dc 100644 (file)
@@ -6,103 +6,69 @@ import * as nano25519 from './lib'
 export { constants } from './lib'
 
 /**
- * Nano public key derivation using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
+ * Nano public key derivation using WebAssembly. If byte output is required,
+ * pass a target buffer as a second argument.
+ * @param {(Uint8Array<ArrayBuffer> | string)} prv - 32-byte private key
  * @returns 32-byte public key
  */
-export function derive (prv: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
-/**
- * Nano public key derivation using WebAssembly.
- * @param {string} prv - 64-character hexadecimal private key
- * @returns 64-character hexadecimal public key
- */
-export function derive (prv: string): string
+export function derive (prv: Uint8Array<ArrayBuffer> | string): string
 /**
  * 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<ArrayBuffer>} prv - 32-byte private key
+ * @param {(Uint8Array<ArrayBuffer> | string)} prv - 32-byte private key
  * @param {Uint8Array<ArrayBuffer>} out - buffer to receive 32-byte public key
  */
-export function derive (prv: Uint8Array<ArrayBuffer>, out: Uint8Array<ArrayBuffer>): void
-export function derive (prv: string | Uint8Array<ArrayBuffer>, out?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> | void {
+export function derive (prv: Uint8Array<ArrayBuffer> | string, out: Uint8Array<ArrayBuffer>): void
+export function derive (prv: Uint8Array<ArrayBuffer> | string, out?: Uint8Array<ArrayBuffer>): string | void {
        return nano25519.derive(prv, out)
 }
 
 /**
  * Signing using WebAssembly. To sign Nano blocks, the message should be a
  * 32-byte block hash.
- * @param {Uint8Array<ArrayBuffer>} msg - Variable-byte-length message up to 64 KiB
- * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
- * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
+ * @param {(Uint8Array<ArrayBuffer> | string)} msg - Variable-byte-length message up to 64 KiB
+ * @param {(Uint8Array<ArrayBuffer> | string)} prv - 32-byte private key
+ * @param {(Uint8Array<ArrayBuffer> | string)} pub - 32-byte public key
  * @returns 64-byte detached signature
  */
-export function sign (msg: Uint8Array<ArrayBuffer>, prv: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
-/**
- * Signing using WebAssembly. To sign Nano blocks, the message should be a
- * 64-character hexadecimal block hash.
- * @param {string} msg - Variable-length hexadecimal message up to 64 KiB
- * @param {string} prv - 64-character hexadecimal private key
- * @param {string} pub - 64-character hexadecimal public key
- * @returns 128-character hexadecimal detached signature
- */
-export function sign (msg: string, prv: string, pub: string): string
+export function sign (msg: Uint8Array<ArrayBuffer> | string, prv: Uint8Array<ArrayBuffer> | string, pub: Uint8Array<ArrayBuffer> | string): string
 /**
  * Signing using WebAssembly. To sign Nano blocks, the message should be a
  * 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<ArrayBuffer>} msg - Variable-byte-length message up to 64 KiB
- * @param {Uint8Array<ArrayBuffer>} prv - 32-byte private key
- * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
- * @param {Uint8Array<ArrayBuffer>} out - buffer to receive 64-byte detached signature
+ * @param {(Uint8Array<ArrayBuffer> | string)} msg - Variable-byte-length message up to 64 KiB
+ * @param {(Uint8Array<ArrayBuffer> | string)} prv - 32-byte private key
+ * @param {(Uint8Array<ArrayBuffer> | string)} pub - 32-byte public key
+ * @param {(Uint8Array<ArrayBuffer> | string)} out - buffer to receive 64-byte detached signature
  */
-export function sign (msg: Uint8Array<ArrayBuffer>, prv: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>, out: Uint8Array<ArrayBuffer>): void
-export function sign (msg: string | Uint8Array<ArrayBuffer>, prv: string | Uint8Array<ArrayBuffer>, pub: string | Uint8Array<ArrayBuffer>, out?: Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> | void {
+export function sign (msg: Uint8Array<ArrayBuffer> | string, prv: Uint8Array<ArrayBuffer> | string, pub: Uint8Array<ArrayBuffer> | string, out: Uint8Array<ArrayBuffer>): void
+export function sign (msg: Uint8Array<ArrayBuffer> | string, prv: Uint8Array<ArrayBuffer> | string, pub: Uint8Array<ArrayBuffer> | string, out?: Uint8Array<ArrayBuffer>): string | void {
        return nano25519.sign(msg, prv, pub, out)
 }
 
 /**
  * Signature verification using WebAssembly. To verify Nano block signatures,
  * use `verify_blocks()` instead.
- * @param {Uint8Array<ArrayBuffer>} sig - 64-byte detached signature
- * @param {Uint8Array<ArrayBuffer>} msg - Variable-byte-length message up to 64 KiB
- * @param {Uint8Array<ArrayBuffer>} pub - 32-byte public key
- * @returns true if signature matches block hash and public key, else false
- */
-export function verify (sig: Uint8Array<ArrayBuffer>, msg: Uint8Array<ArrayBuffer>, pub: Uint8Array<ArrayBuffer>): boolean
-/**
- * Signature verification using WebAssembly. To verify Nano block signatures,
- * the message should be a 64-character block hash.
- * @param {string} sig - 128-character hexadecimal detached signature
- * @param {string} msg - Variable-length message up to 64 KiB
- * @param {string} pub - 64-character hexadecimal public key
+ * @param {(Uint8Array<ArrayBuffer> | string)} sig - 64-byte detached signature
+ * @param {(Uint8Array<ArrayBuffer> | string)} msg - Variable-byte-length message up to 64 KiB
+ * @param {(Uint8Array<ArrayBuffer> | string)} pub - 32-byte public key
  * @returns true if signature matches block hash and public key, else false
  */
-export function verify (sig: string, msg: string, pub: string): boolean
-export function verify (sig: string | Uint8Array<ArrayBuffer>, msg: string | Uint8Array<ArrayBuffer>, pub: string | Uint8Array<ArrayBuffer>): boolean {
+export function verify (sig: Uint8Array<ArrayBuffer> | string, msg: Uint8Array<ArrayBuffer> | string, pub: Uint8Array<ArrayBuffer> | string): boolean {
        return nano25519.verify(sig, msg, pub)
 }
 
 /**
  * Nano block signature bulk verification using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} publicKey - 32-byte public key
- * @param {object[]} blocks - Array of up to 64 block signature/hash pairs
- * @param {Uint8Array<ArrayBuffer>} blocks.signature - 64-byte block signature
- * @param {Uint8Array<ArrayBuffer>} blocks.hash - 32-byte block hash
- * @returns Array of results with, for each case, true if signature matches block hash and public key, else false
- */
-export function verify_blocks (publicKey: Uint8Array<ArrayBuffer>, blocks: { hash: Uint8Array<ArrayBuffer>, signature: Uint8Array<ArrayBuffer> }[]): boolean[]
-/**
- * Nano block signature bulk verification using WebAssembly.
- * @param {string} publicKey - 64-character hexadecimal public key
+ * @param {(Uint8Array<ArrayBuffer> | string)} publicKey - 32-byte public key
  * @param {object[]} blocks - Array of up to 64 block signature/hash pairs
- * @param {string} blocks.signature - 128-character hexadecimal block signature
- * @param {string} blocks.hash - 64-character hexadecimal block hash
+ * @param {(Uint8Array<ArrayBuffer> | string)} blocks.signature - 64-byte block signature
+ * @param {(Uint8Array<ArrayBuffer> | string)} blocks.hash - 32-byte block hash
  * @returns Array of results with, for each case, true if signature matches block hash and public key, else false
  */
-export function verify_blocks (publicKey: string, blocks: { signature: string, hash: string }[]): boolean[]
-export function verify_blocks (publicKey: string | Uint8Array<ArrayBuffer>, blocks: { signature: string | Uint8Array<ArrayBuffer>, hash: string | Uint8Array<ArrayBuffer> }[]): boolean[] {
+export function verify_blocks (publicKey: Uint8Array<ArrayBuffer> | string, blocks: { signature: Uint8Array<ArrayBuffer> | string, hash: Uint8Array<ArrayBuffer> | string }[]): boolean[] {
        return nano25519.verify_blocks(publicKey, blocks)
 }
 
index 1a1288ffaa8eb8ab4dd91cf29abdfcb2583aebaa..115a0af7496d53af4e79a5a2a2d5129bc28be21b 100644 (file)
@@ -5,7 +5,7 @@ import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, i
 
 const { KEY_BYTELENGTH, } = constants
 
-export function derive (prv: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
+export function derive (prv: unknown, out?: unknown): string | void {
        if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === KEY_BYTELENGTH)) {
                throw new TypeError('Derive output buffer must be 32-byte Uint8Array<ArrayBuffer>')
        }
@@ -24,21 +24,19 @@ export function derive (prv: unknown, out?: unknown): string | Uint8Array<ArrayB
                for (let i = 0; i < KEY_BYTELENGTH; i++) {
                        publicKey[i] = buffer[Pointers.OUTPUT_DERIVE + i]
                }
-               if (typeof prv === 'string') {
+               if (out != null) {
+                       out.set(publicKey)
+                       return void 0
+               } else {
                        let hex = ''
                        for (const byte of publicKey) {
                                hex += byte.toString(16).padStart(2, '0')
                        }
                        return hex
-               } else if (isBytes(prv) && out != null) {
-                       out.set(publicKey)
-                       publicKey.fill(0)
-                       return
-               } else {
-                       return publicKey
                }
        } finally {
                privateKey.fill(0)
+               publicKey.fill(0)
                clearMemory()
        }
 }
index 8bbefcd1ae3550d0be1ba0dc9b0eeebb0aecd064..b6936b981cf62d30dab0fe9fafcb7adc0cf3e77e 100644 (file)
@@ -5,7 +5,7 @@ import { Mutex, Nano25519TypeError, Pointers, clearMemory, constants, exports, i
 
 const { KEY_BYTELENGTH, MAX_MESSAGE_BYTELENGTH, SIGNATURE_BYTELENGTH, } = constants
 
-export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): string | Uint8Array<ArrayBuffer> | void {
+export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown): string | void {
        if (typeof out !== 'undefined' && !(isBytes(out) && out.byteLength === SIGNATURE_BYTELENGTH)) {
                throw new TypeError(`Sign output buffer must be ${SIGNATURE_BYTELENGTH}-byte Uint8Array<ArrayBuffer>`)
        }
@@ -32,22 +32,20 @@ export function sign (msg: unknown, prv: unknown, pub: unknown, out?: unknown):
                for (let i = 0; i < SIGNATURE_BYTELENGTH; i++) {
                        signature[i] = buffer[Pointers.OUTPUT_SIGN + i]
                }
-               if (typeof prv === 'string') {
+               if (out != null) {
+                       out.set(signature)
+                       return void 0
+               } else {
                        let hex = ''
                        for (const byte of signature) {
                                hex += byte.toString(16).padStart(2, '0')
                        }
                        return hex
-               } else if (isBytes(prv) && out != null) {
-                       out.set(signature)
-                       signature.fill(0)
-                       return
-               } else {
-                       return signature
                }
        } finally {
                privateKey.fill(0)
                publicKey.fill(0)
+               signature.fill(0)
                clearMemory()
        }
 }
index 34dfd0308056673fc45aa747393ab471cdf2c237..9d4c088f2b43fcc39d6c23065137df239b0aa4be 100644 (file)
@@ -26,7 +26,7 @@ export function verify (sig: unknown, msg: unknown, pub: unknown): boolean {
                        throw new Nano25519TypeError('WASM memory buffer detached')
                }
                const verified = buffer[Pointers.OUTPUT_VERIFY]
-               return verified === 0
+               return verified === 1
        } finally {
                clearMemory()
        }
@@ -78,7 +78,7 @@ export function verify_blocks (pub: unknown, data: unknown): boolean[] {
                for (let i = 0; i < count; i++) {
                        verified[i] = buffer[Pointers.OUTPUT_VERIFY + i]
                }
-               return [...verified].map(v => v === 0)
+               return [...verified].map(v => v === 1)
        } finally {
                clearMemory()
        }
index dfa215c898ef4320e2e9ff0d1b5a6755755cf5ec..5cf43de1e08e886ad78f5bdb824a7bba81e47439 100644 (file)
@@ -268,13 +268,20 @@ failures += +!test
 
 // Check byte inputs
 result = derive(NANO_ORG_VECTOR.privateKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
+test = result.toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
 check(`derive from private key bytes ${NANO_ORG_VECTOR.privateKey}`, test)
 passes += +test
 failures += +!test
 
+outbuf = new Uint8Array(32)
+derive(NANO_ORG_VECTOR.privateKeyBytes, outbuf)
+test = [...outbuf].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
+check(`derive from private key bytes to output buffer`, test)
+passes += +test
+failures += +!test
+
 result = sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.privateKeyBytes, NANO_ORG_VECTOR.publicKeyBytes)
-test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
+test = result.toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
 check(`sign message bytes ${NANO_ORG_VECTOR.blockHash}`, test)
 passes += +test
 failures += +!test
@@ -371,13 +378,13 @@ for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE
        const signatureBytes = new Uint8Array(signature.match(/.{2}/g)?.slice(0, 64).map(b => parseInt(b, 16)) ?? [])
 
        result = derive(privateKeyBytes)
-       test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === publicKey
+       test = result.toLowerCase() === publicKey
        check(`derive from private key bytes ${privateKey}`, test)
        passes += +test
        failures += +!test
 
        result = sign(messageBytes, privateKeyBytes, publicKeyBytes)
-       test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === signature.slice(0, 128)
+       test = result.toLowerCase() === signature.slice(0, 128)
        check(`sign message bytes length ${messageBytes.byteLength}`, test)
        passes += +test
        failures += +!test