]> git.codecow.com Git - nano25519.git/commitdiff
Move curve arithmetic into subdirectory.
authorChris Duncan <chris@zoso.dev>
Thu, 24 Sep 2026 06:50:51 +0000 (23:50 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 24 Sep 2026 06:50:51 +0000 (23:50 -0700)
15 files changed:
src/assembly/constants.ts
src/assembly/crypto_derive.ts
src/assembly/crypto_sign.ts
src/assembly/crypto_verify.ts
src/assembly/ed25519/base.ts [moved from src/assembly/base.ts with 100% similarity]
src/assembly/ed25519/base2.ts [moved from src/assembly/base2.ts with 100% similarity]
src/assembly/ed25519/blake2b.ts [moved from src/assembly/blake2b.ts with 100% similarity]
src/assembly/ed25519/crypto_sign.ts [new file with mode: 0644]
src/assembly/ed25519/fe.ts [moved from src/assembly/fe.ts with 100% similarity]
src/assembly/ed25519/ge.ts [moved from src/assembly/ge.ts with 95% similarity]
src/assembly/ed25519/p.ts [moved from src/assembly/p.ts with 94% similarity]
src/assembly/ed25519/sc.ts [moved from src/assembly/sc.ts with 100% similarity]
src/assembly/ed25519/utils.ts [moved from src/assembly/utils.ts with 100% similarity]
src/assembly/tests.ts
test/node.mjs

index 41a9b55cb8bfa71514280b6ebf145d3a50b19936..6dd90a76290ff9ba4f889346b3e9868a9caaaf53 100644 (file)
@@ -1,31 +1,6 @@
 //! SPDX-FileCopyrightText: 2013-2026 Frank Denis <j@pureftpd.org>
 //! SPDX-License-Identifier: ISC
 
-import { FieldElement } from './fe'
-
-/**
- * √-1
- */
-export const fe_sqrtm1: FieldElement = StaticArray.fromArray<i32>([
-       -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, 0, 0
-])
-
-/**
- * d = −121665 / 121666
- *   = 37095705934669439343138083508754565189542113879843219016388785533085940283555
- */
-export const ed25519_d: FieldElement = StaticArray.fromArray<i32>([
-       -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116, 0, 0
-])
-
-/**
- * 2d = −121665 / 60833
- *    = 16295367250680780974490674513165176452449235426866156013048779062215315747161
- */
-export const ed25519_d2: FieldElement = StaticArray.fromArray<i32>([
-       -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
index 64cda1ac0dc3351e7aebaea38b43767af58ebd1c..2fa58aa586e0636b9ed081c9a41b171c76eb011b 100644 (file)
@@ -1,9 +1,9 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! 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
index d0b3be5df5bca973534e9b4e6c1073fc94cf19e3..064e493e5b7a50c813b003c79638959aecd8a5b1 100644 (file)
@@ -1,12 +1,12 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! 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()
index 0909f77551d11a2dca147f6af26fecbfc4fe0113..6540c5c106156d69e9924ab73208338266a586b7 100644 (file)
@@ -1,12 +1,12 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! 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/ed25519/crypto_sign.ts b/src/assembly/ed25519/crypto_sign.ts
new file mode 100644 (file)
index 0000000..7e0eb09
--- /dev/null
@@ -0,0 +1,92 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! 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<u8>(32)
+const h = new StaticArray<u8>(64)
+const s = new StaticArray<u8>(32)
+const prefix = new StaticArray<u8>(32)
+const r = new StaticArray<u8>(64)
+const R = new StaticArray<u8>(64)
+const k = new StaticArray<u8>(64)
+const S = new StaticArray<u8>(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<u8>} RS 64-byte output buffer for detached signature
+ * @param {StaticArray<u8>} M variable-length message to be signed
+ * @param {i32} mlen bytelength of `m`
+ * @param {StaticArray<u8>} prv 32-byte private key from input buffer
+ * @param {StaticArray<u8>} pub 32-byte public key from input buffer
+ */
+export function crypto_sign (RS: StaticArray<u8>, M: StaticArray<u8>, mlen: i32, prv: StaticArray<u8>, pub: StaticArray<u8>): 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<usize>(s), changetype<usize>(h), 32)
+       clamp(s)
+       memory.copy(changetype<usize>(prefix), changetype<usize>(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<usize>(RS), changetype<usize>(R), 32)
+       R.fill(0)
+       memory.copy(changetype<usize>(RS) + 32, changetype<usize>(S), 32)
+       S.fill(0)
+
+       return 0
+}
similarity index 95%
rename from src/assembly/ge.ts
rename to src/assembly/ed25519/ge.ts
index db4ed8bcbc466bba860ffd65bdddf85eeea9c6cb..7b9882332496e0b4cfee8206c36a5e5b7256536b 100644 (file)
 */
 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<i32>([
+       -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482, 0, 0
+])
+
+/**
+ * d = −121665 / 121666
+ *   = 37095705934669439343138083508754565189542113879843219016388785533085940283555
+ */
+const ed25519_d: FieldElement = StaticArray.fromArray<i32>([
+       -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 {
similarity index 94%
rename from src/assembly/p.ts
rename to src/assembly/ed25519/p.ts
index 7440943d770c7a6cc14f9f5db79eea774afab3ba..8d98e90588e9c1b9e0bf6ec50a3bd753e62d7fcd 100644 (file)
@@ -1,9 +1,16 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! 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<i32>([
+       -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199, 0, 0
+])
+
 export class ge_p2 {
        X: FieldElement = fe()
        Y: FieldElement = fe()
index bf3c13af418598139ed4e610ad450668ac6f44a5..ffa1250862804f6b9d9de4f7ff434ef7c0ff2469 100644 (file)
@@ -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.
index aaf4385ab28061a1c6b91e5a4e70585bd8d5aa27..cc82c925bc7789ea58a7f820da94ef75be66e96e 100644 (file)
@@ -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 {