]> git.codecow.com Git - nano25519.git/commitdiff
Add check for canonical scalar S from signature.
authorChris Duncan <chris@codecow.com>
Wed, 25 Feb 2026 23:16:19 +0000 (15:16 -0800)
committerChris Duncan <chris@codecow.com>
Wed, 25 Feb 2026 23:16:19 +0000 (15:16 -0800)
assembly/nano-nacl.ts
index.html

index 5bcc896f78604fc85176de02b7d7dcf291a832fd..1119380b8b7db8cbeb6d07b8335418609906a14d 100644 (file)
@@ -806,6 +806,20 @@ function unpackneg (r: Array<StaticArray<i32>>, p: StaticArray<u8>): i8 {
        return 0\r
 }\r
 \r
+// Validate signature scalar S is canonical (S < L)\r
+function canonical (S: StaticArray<u8>): boolean {\r
+       // If S >= 2^253 then S >= L for sure.\r
+       if ((S[32] & 0xE0) != 0) return false\r
+\r
+       // Check S-L for underflow (c=1) which means S < L\r
+       let c = 0\r
+       for (let i = 0; i < 32; i++) {\r
+               const diff = S[i] - L[i] - c\r
+               c = (diff >> 31) & 1\r
+       }\r
+       return c == 1\r
+}\r
+\r
 const blake2b = new Blake2b()\r
 function crypto_hash (o: StaticArray<u8>, i: StaticArray<u8>): void {\r
        blake2b.init().update(i).digest(o)\r
@@ -917,9 +931,7 @@ function crypto_verify (h: StaticArray<u8>, s: StaticArray<u8>, k: StaticArray<u
        q[3].fill(0)\r
 \r
        // fail\r
-       if (unpackneg(q, k)) {\r
-               return false\r
-       }\r
+       if (unpackneg(q, k)) return false\r
 \r
        // signature is nonce point R and scalar S (R || S)\r
        // data to hash is nonce point R, public key A, and message M (R || A || M)\r
@@ -930,6 +942,12 @@ function crypto_verify (h: StaticArray<u8>, s: StaticArray<u8>, k: StaticArray<u
                sm[i + 32] = k[i]\r
                sm[i + 64] = h[i]\r
        }\r
+\r
+       // fail as non-canonical\r
+       const c = canonical(S)\r
+       trace(`c: ${c}`)\r
+       if (!canonical(S)) return false\r
+\r
        crypto_hash(d, sm)\r
        reduce(d)\r
        scalarmult(p, q, d)\r
index 06670ff4587d0bb6403dd51242eb2732b7278002..874de6e4227020edbfdfe6de42c937eff6675f5e 100644 (file)
@@ -178,7 +178,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
                                        blockHash: 'BB569136FA05F8CBF65CEF2EDE368475B289C4477342976556BA4C0DDF216E45',
                                        privateKey: '781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3',
                                        publicKey: '3068BB1CA04525BB0E416C485FE6A67FD52540227D267CC8B6E8DA958A7FA039',
-                                       signature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900'
+                                       signature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804228008F98DDF4A15FD35705102785C50EF76732C3A74B0FEC5B0DD67B574A5900',
+                                       badSignature: '74BCC59DBA39A1E34A5F75F96D6DE9154E3477AAD7DE30EA563DFCFE501A804215d484f5f757b4b7a9f4fcb2057fa423f76732c3a74b0fec5b0dd67b574a5910'
                                }
                                const zeroes = '0000000000000000000000000000000000000000000000000000000000000000'
                                const expect = []
@@ -204,6 +205,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                                expect.push(result)
 
                                // XFAIL
+                               result = await NanoNaCl.verify(TEST_VECTORS.blockHash, TEST_VECTORS.badSignature, TEST_VECTORS.publicKey)
+                               console.log(result)
+                               result = result === false
+                               console.log(`verify() output for non-canonical signature is ${result === true ? 'correct' : 'incorrect'}`)
+                               expect.push(result)
+
                                result = await NanoNaCl.verify(zeroes, TEST_VECTORS.signature, TEST_VECTORS.publicKey)
                                console.log(result)
                                result = result === false