From: Chris Duncan Date: Thu, 24 Sep 2026 06:50:51 +0000 (-0700) Subject: Move curve arithmetic into subdirectory. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=33c0a7f15f4f281b0be604d3915908652304c593;p=nano25519.git Move curve arithmetic into subdirectory. --- diff --git a/src/assembly/constants.ts b/src/assembly/constants.ts index 41a9b55..6dd90a7 100644 --- a/src/assembly/constants.ts +++ b/src/assembly/constants.ts @@ -1,31 +1,6 @@ //! SPDX-FileCopyrightText: 2013-2026 Frank Denis //! SPDX-License-Identifier: ISC -import { FieldElement } from './fe' - -/** - * √-1 - */ -export const fe_sqrtm1: FieldElement = StaticArray.fromArray([ - -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, 0, 0 -]) - -/** - * d = −121665 / 121666 - * = 37095705934669439343138083508754565189542113879843219016388785533085940283555 - */ -export const ed25519_d: FieldElement = StaticArray.fromArray([ - -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116, 0, 0 -]) - -/** - * 2d = −121665 / 60833 - * = 16295367250680780974490674513165176452449235426866156013048779062215315747161 - */ -export const ed25519_d2: FieldElement = StaticArray.fromArray([ - -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199, 0, 0 -]) - const PAGE_BYTELENGTH: i32 = 1 << 16 export const BLOCKHASH_BYTELENGTH: i32 = 32 export const KEY_BYTELENGTH: i32 = 32 diff --git a/src/assembly/crypto_derive.ts b/src/assembly/crypto_derive.ts index 64cda1a..2fa58aa 100644 --- a/src/assembly/crypto_derive.ts +++ b/src/assembly/crypto_derive.ts @@ -1,9 +1,9 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Blake2b } from './blake2b' -import { ge_scalarmult_base_tobytes } from './ge' -import { clamp } from './utils' +import { Blake2b } from './ed25519/blake2b' +import { ge_scalarmult_base_tobytes } from './ed25519/ge' +import { clamp } from './ed25519/utils' const PRIVATEKEY_BYTES: i32 = 32 const PUBLICKEY_BYTES: i32 = 32 diff --git a/src/assembly/crypto_sign.ts b/src/assembly/crypto_sign.ts index d0b3be5..064e493 100644 --- a/src/assembly/crypto_sign.ts +++ b/src/assembly/crypto_sign.ts @@ -1,12 +1,12 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Blake2b } from './blake2b' import { KEY_BYTELENGTH } from './constants' import { crypto_derive } from './crypto_derive' -import { ge_scalarmult_base_tobytes } from './ge' -import { sc_muladd, sc_reduce } from './sc' -import { clamp } from './utils' +import { Blake2b } from './ed25519/blake2b' +import { ge_scalarmult_base_tobytes } from './ed25519/ge' +import { sc_muladd, sc_reduce } from './ed25519/sc' +import { clamp } from './ed25519/utils' // crypto_hash function const blake2b = new Blake2b() diff --git a/src/assembly/crypto_verify.ts b/src/assembly/crypto_verify.ts index 0909f77..6540c5c 100644 --- a/src/assembly/crypto_verify.ts +++ b/src/assembly/crypto_verify.ts @@ -1,12 +1,12 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Blake2b } from './blake2b' import { BLOCKHASH_BYTELENGTH, KEY_BYTELENGTH } from './constants' -import { ge_double_scalarmult_vartime_to_p3, ge_frombytes, ge_frombytes_negate_vartime, ge_has_small_order, ge_is_canonical } from './ge' -import { ge_p3, ge_p3_tobytes, ge_sub_p3 } from './p' -import { sc_is_canonical, sc_reduce } from './sc' -import { equalbytes } from './utils' +import { Blake2b } from './ed25519/blake2b' +import { ge_double_scalarmult_vartime_to_p3, ge_frombytes, ge_frombytes_negate_vartime, ge_has_small_order, ge_is_canonical } from './ed25519/ge' +import { ge_p3, ge_p3_tobytes, ge_sub_p3 } from './ed25519/p' +import { sc_is_canonical, sc_reduce } from './ed25519/sc' +import { equalbytes } from './ed25519/utils' // crypto_hash function const blake2b = new Blake2b() diff --git a/src/assembly/base.ts b/src/assembly/ed25519/base.ts similarity index 100% rename from src/assembly/base.ts rename to src/assembly/ed25519/base.ts diff --git a/src/assembly/base2.ts b/src/assembly/ed25519/base2.ts similarity index 100% rename from src/assembly/base2.ts rename to src/assembly/ed25519/base2.ts diff --git a/src/assembly/blake2b.ts b/src/assembly/ed25519/blake2b.ts similarity index 100% rename from src/assembly/blake2b.ts rename to src/assembly/ed25519/blake2b.ts diff --git a/src/assembly/ed25519/crypto_sign.ts b/src/assembly/ed25519/crypto_sign.ts new file mode 100644 index 0000000..7e0eb09 --- /dev/null +++ b/src/assembly/ed25519/crypto_sign.ts @@ -0,0 +1,92 @@ +//! SPDX-FileCopyrightText: 2026 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +import { KEY_BYTELENGTH } from '../constants' +import { crypto_derive } from '../crypto_derive' +import { Blake2b } from './blake2b' +import { ge_scalarmult_base_tobytes } from './ge' +import { sc_muladd, sc_reduce } from './sc' +import { clamp } from './utils' + +// crypto_hash function +const blake2b = new Blake2b() + +// algorithm variables +const A = new StaticArray(32) +const h = new StaticArray(64) +const s = new StaticArray(32) +const prefix = new StaticArray(32) +const r = new StaticArray(64) +const R = new StaticArray(64) +const k = new StaticArray(64) +const S = new StaticArray(64) +/** + * Sign a message with a private key. The Nano specification uses BLAKE2b as the + * hash function instead of SHA-512 specified by RFC 8032. + * + * In this implementation, both the private key and the public key are required. + * This ensures the user has a full correct keypair for data integrity. It also + * offers a small performance improvement in the case of an invalid public key + * by returning early and avoiding expensive point multiplication; RFC 8032 + * indicates the public key should be recomputed from the private key when + * signing anyway, so there is no penalty for checking key validity first. + * + * https://www.rfc-editor.org/info/rfc8032/#section-5.1.6 + * + * @param {StaticArray} RS 64-byte output buffer for detached signature + * @param {StaticArray} M variable-length message to be signed + * @param {i32} mlen bytelength of `m` + * @param {StaticArray} prv 32-byte private key from input buffer + * @param {StaticArray} pub 32-byte public key from input buffer + */ +export function crypto_sign (RS: StaticArray, M: StaticArray, mlen: i32, prv: StaticArray, pub: StaticArray): i32 { + // Derive `A` from private key + crypto_derive(A, prv) + let c = 0 + for (let i = 0; i < KEY_BYTELENGTH; i++) { + c |= A[i] ^ pub[i] + } + // Return early if `A` does not match public key + if (c != 0) return -1 + + // Hash private key to `h` + blake2b.init().update(prv, KEY_BYTELENGTH).digest(h) + prv.fill(0) + + // Split `h` into clamped secret scalar `s` and nonce prefix + memory.copy(changetype(s), changetype(h), 32) + clamp(s) + memory.copy(changetype(prefix), changetype(h) + 32, 32) + h.fill(0) + + // Hash prefix and message `M` to nonce `r` + blake2b.init().update(prefix, 32).update(M, mlen).digest(r) + prefix.fill(0) + + // Reduce `r` modulo `L`, the group order of the base point `B` + sc_reduce(r) + + // Perform fixed-base scalar multiplication `[r]B`, output to point `R` + ge_scalarmult_base_tobytes(R, r) + + // Compute challenge hash `blake2b(R || A || M)`, output to `k` + blake2b.init().update(R, 32).update(A, 32).update(M, mlen).digest(k) + A.fill(0) + + // Reduce `k` modulo `L` for efficiency + sc_reduce(k) + + // Compute `S = (k * s + r) mod L` + sc_muladd(S, k, s, r) + k.fill(0) + s.fill(0) + r.fill(0) + + // Construct final signature `(R || S)` + memory.copy(changetype(RS), changetype(R), 32) + R.fill(0) + memory.copy(changetype(RS) + 32, changetype(S), 32) + S.fill(0) + + return 0 +} diff --git a/src/assembly/fe.ts b/src/assembly/ed25519/fe.ts similarity index 100% rename from src/assembly/fe.ts rename to src/assembly/ed25519/fe.ts diff --git a/src/assembly/ge.ts b/src/assembly/ed25519/ge.ts similarity index 95% rename from src/assembly/ge.ts rename to src/assembly/ed25519/ge.ts index db4ed8b..7b98823 100644 --- a/src/assembly/ge.ts +++ b/src/assembly/ed25519/ge.ts @@ -18,11 +18,25 @@ */ import { base } from './base' import { base2 } from './base2' -import { ed25519_d, fe_sqrtm1 } from './constants' import { FieldElement, fe, fe_1, fe_add, fe_cmov, fe_frombytes, fe_isnegative, fe_iszero, fe_memcmov, fe_memcopy, fe_memneg, fe_mul, fe_neg, fe_pow22523, fe_sq, fe_sub } from './fe' import { ge_add_cached, ge_add_precomp, ge_cached, ge_p1p1, ge_p1p1_0, ge_p1p1_to_p2, ge_p1p1_to_p3, ge_p2, ge_p2_0, ge_p2_dbl, ge_p2_to_p3, ge_p3, ge_p3_0, ge_p3_dbl, ge_p3_to_cached, ge_p3_tobytes, ge_precomp, ge_precomp_0, ge_sub_cached, ge_sub_precomp } from './p' import { equal, negative } from './utils' +/** + * √-1 + */ +const fe_sqrtm1: FieldElement = StaticArray.fromArray([ + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, 0, 0 +]) + +/** + * d = −121665 / 121666 + * = 37095705934669439343138083508754565189542113879843219016388785533085940283555 + */ +const ed25519_d: FieldElement = StaticArray.fromArray([ + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116, 0, 0 +]) + //@ts-expect-error @inline function ge_cmov (t: ge_precomp, u: usize, b: u8): void { diff --git a/src/assembly/p.ts b/src/assembly/ed25519/p.ts similarity index 94% rename from src/assembly/p.ts rename to src/assembly/ed25519/p.ts index 7440943..8d98e90 100644 --- a/src/assembly/p.ts +++ b/src/assembly/ed25519/p.ts @@ -1,9 +1,16 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { ed25519_d2 } from './constants' import { FieldElement, fe, fe_0, fe_1, fe_add, fe_copy, fe_dbl, fe_invert, fe_isnegative, fe_mul, fe_neg, fe_sq, fe_sq2, fe_sq_vec, fe_sub, fe_tobytes } from './fe' +/** + * 2d = −121665 / 60833 + * = 16295367250680780974490674513165176452449235426866156013048779062215315747161 + */ +const ed25519_d2: FieldElement = StaticArray.fromArray([ + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199, 0, 0 +]) + export class ge_p2 { X: FieldElement = fe() Y: FieldElement = fe() diff --git a/src/assembly/sc.ts b/src/assembly/ed25519/sc.ts similarity index 100% rename from src/assembly/sc.ts rename to src/assembly/ed25519/sc.ts diff --git a/src/assembly/utils.ts b/src/assembly/ed25519/utils.ts similarity index 100% rename from src/assembly/utils.ts rename to src/assembly/ed25519/utils.ts diff --git a/src/assembly/tests.ts b/src/assembly/tests.ts index bf3c13a..ffa1250 100644 --- a/src/assembly/tests.ts +++ b/src/assembly/tests.ts @@ -2,9 +2,9 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { KEY_BYTELENGTH } from './constants' +import { equalbytes } from './ed25519/utils' import { raise, } from './env' import { ERROR_SELFTEST_XFAIL_ONE, ERROR_SELFTEST_XFAIL_PAIR, ERROR_SELFTEST_XPASS, } from './errors' -import { equalbytes } from './utils' /** * Tests run on initialization to ensure build is correct. diff --git a/test/node.mjs b/test/node.mjs index aaf4385..cc82c92 100644 --- a/test/node.mjs +++ b/test/node.mjs @@ -161,10 +161,8 @@ failures += +!test try { result = sign(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.privateKey, NANO_ORG_VECTOR.publicKey.replace('3', '1')) - console.log(result) result = false } catch (err) { - console.log(err) if (err != null && typeof err === 'object' && 'message' in err && typeof err.message === 'string') { result = err.constructor.name === 'Nano25519WasmError' && err.message === 'Invalid public key' } else {