]> git.codecow.com Git - nano25519.git/commitdiff
Stage draft canonicity check test. next/rc
authorChris Duncan <chris@codecow.com>
Sun, 4 Oct 2026 18:17:23 +0000 (11:17 -0700)
committerChris Duncan <chris@codecow.com>
Sun, 4 Oct 2026 18:17:23 +0000 (11:17 -0700)
src/assembly/tests.ts

index ffa1250862804f6b9d9de4f7ff434ef7c0ff2469..865677e26f3c784fb1bf1ca37cee172d4e9caad0 100644 (file)
@@ -2,6 +2,7 @@
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
 import { KEY_BYTELENGTH } from './constants'
+import { ge_is_canonical } from './ed25519/ge'
 import { equalbytes } from './ed25519/utils'
 import { raise, } from './env'
 import { ERROR_SELFTEST_XFAIL_ONE, ERROR_SELFTEST_XFAIL_PAIR, ERROR_SELFTEST_XPASS, } from './errors'
@@ -46,11 +47,11 @@ for (let i = 0; i < KEY_BYTELENGTH; i++) {
        spread[i] = u8(i * 37 + 11)
 }
 
+const actual = new StaticArray<u8>(KEY_BYTELENGTH)
+
 // Vector index is the error detail: 0 zeros, 1 ones, 2 spread
 const expecteds = [zeros, ones, spread,]
 
-const actual = new StaticArray<u8>(KEY_BYTELENGTH)
-
 for (let v = 0; v < expecteds.length; v++) {
        const expected = expecteds[v]
        memory.copy(changetype<usize>(actual), changetype<usize>(expected), KEY_BYTELENGTH)
@@ -76,3 +77,57 @@ for (let v = 0; v < expecteds.length; v++) {
                }
        }
 }
+
+function xpass_ge_is_canonical (vector: i32, data: StaticArray<u8>): void {
+       if (!ge_is_canonical(data)) {
+               raise(ERROR_SELFTEST_XPASS, vector, 0)
+       }
+}
+
+function xfail_ge_is_canonical_one (vector: i32, position: i32, mask: i32, data: StaticArray<u8>): void {
+       if (ge_is_canonical(data)) {
+               raise(ERROR_SELFTEST_XFAIL_ONE, vector, (position << 8) | mask)
+       }
+}
+
+function xfail_ge_is_canonical_pair (vector: i32, i: i32, j: i32, data: StaticArray<u8>): void {
+       if (ge_is_canonical(data)) {
+               raise(ERROR_SELFTEST_XFAIL_PAIR, vector, (i << 8) | j)
+       }
+}
+
+/**
+ * ge_is_canonical must report "non-canonical" for every difference regardless of shape
+ * - `zeros`: lower boundary with every bit cleared
+ * - `ones`: upper boundary with every bit set
+ * - `spread`: all bytes distinct from each other and setting each bit to 0 or 1
+ */
+
+// Vector index is the error detail: 0 zeros, 1 ones, 2 spread
+const expected_ge_is_canonical = [zeros, ones, spread,]
+
+for (let v = 0; v < expected_ge_is_canonical.length; v++) {
+       const expected = expected_ge_is_canonical[v]
+       memory.copy(changetype<usize>(actual), changetype<usize>(expected), KEY_BYTELENGTH)
+       xpass_ge_is_canonical(v, actual)
+
+       for (let i = 0; i < KEY_BYTELENGTH; i++) {
+
+               // verify any individual bit difference fails
+               for (let j = 1; j < 256; j++) {
+                       actual[i] ^= u8(j)
+                       xfail_ge_is_canonical_one(v, i, j, actual)
+                       actual[i] = expected[i]
+               }
+
+               // verify |= was not replaced by ^= or += due to a typo
+               // invisible to single byte checks, caught only by comparing byte pairs
+               for (let j = i + 1; j < KEY_BYTELENGTH; j++) {
+                       actual[i] ^= 128
+                       actual[j] ^= 128
+                       xfail_ge_is_canonical_pair(v, i, j, actual)
+                       actual[i] = expected[i]
+                       actual[j] = expected[j]
+               }
+       }
+}