From 7c2f712c089d4fd56008426353b3bdb70c36bcda Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sun, 4 Oct 2026 11:17:23 -0700 Subject: [PATCH] Stage draft canonicity check test. --- src/assembly/tests.ts | 59 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/assembly/tests.ts b/src/assembly/tests.ts index ffa1250..865677e 100644 --- a/src/assembly/tests.ts +++ b/src/assembly/tests.ts @@ -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(KEY_BYTELENGTH) + // Vector index is the error detail: 0 zeros, 1 ones, 2 spread const expecteds = [zeros, ones, spread,] -const actual = new StaticArray(KEY_BYTELENGTH) - for (let v = 0; v < expecteds.length; v++) { const expected = expecteds[v] memory.copy(changetype(actual), changetype(expected), KEY_BYTELENGTH) @@ -76,3 +77,57 @@ for (let v = 0; v < expecteds.length; v++) { } } } + +function xpass_ge_is_canonical (vector: i32, data: StaticArray): 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): 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): 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(actual), changetype(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] + } + } +} -- 2.52.0