curvy_core/lib.rs
1#![doc = include_str!("../README.md")]
2//!
3//! ## Implementation notes
4//!
5//! Every function here is pinned to a reference implementation by **golden
6//! vectors** (see "Verification" below), so behaviour must stay byte-for-byte
7//! identical - even where that means code that looks unusual.
8//!
9//! ## New here? Read this first
10//!
11//! The crate is split into two cryptographic *domains*. They are very different;
12//! treat them separately.
13//!
14//! - **Domain B - the circuit/commitment layer.** BabyJubjub + Poseidon over the
15//! BN254 scalar field, a note cipher, note commitments, and the Merkle trees and
16//! witness builders the zk-circuits consume. Start here - it is self-contained
17//! and where most code lives.
18//! - **Domain A - the stealth addressing core** ([`stealth`]). The hard part:
19//! *dual-curve* and *pairing-based* (secp256k1 spend keys + BN254 viewing keys),
20//! ported from the Go `curvy-core`.
21//!
22//! ## Module map
23//!
24//! | Module | What it is | Mirrors |
25//! |---|---|---|
26//! | [`field`] | BN254 scalar field `Fr` + decimal⇄`Fr` helpers (the boundary) | - |
27//! | [`encoding`] | hex / little-endian / big-endian byte helpers | - |
28//! | [`poseidon`](mod@poseidon) | Poseidon hash over `Fr` | `poseidon-lite` |
29//! | [`babyjubjub`] | BabyJubjub curve (point add + scalar mul) | `@zk-kit/baby-jubjub` |
30//! | [`blake512`] | original BLAKE-512 (not BLAKE2) | `@zk-kit/eddsa-poseidon` |
31//! | [`eddsa`] | EdDSA-Poseidon signing + key derivation | `@zk-kit/eddsa-poseidon` |
32//! | [`cipher`] | note-data AES-256-CTR additive field-OTP | `balanceCipher.ts` |
33//! | [`note`] | note `id` / `ownerHash` / `nullifier` commitments | `note.ts` |
34//! | [`hash_utils`] | `sha256BigInt` | `proving/utils.ts` |
35//! | [`imt`] | indexed IMT + stateful bounded sharded tree | `@zk-kit/imt` / `shardedNotesTree.ts` |
36//! | [`witness`] | aggregation / withdrawal / pending-commit witness builders | `witnessFromNotes.ts` |
37//! | [`stealth`] | **Domain A** stealth addressing (pairing) | Go `curvy-core` |
38//!
39//! ## The boundary: how values cross in and out
40//!
41//! Scalar crypto boundaries speak **decimal strings** (and `"X.Y"` for points),
42//! matching the existing TypeScript/Go wire shapes. Bulk tree boundaries use
43//! canonical packed 32-byte field elements. Two conversions matter and are easy
44//! to get wrong, so they live in exactly one place each:
45//!
46//! - **Trusted/internal field elements** → [`field::fr_from_dec`] /
47//! [`field::fr_to_dec`], which reduce modulo the field. Use them for amounts,
48//! hashes, and commitments -
49//! anything that *is* a field element.
50//! - **Untrusted canonical field elements** → [`field::Bn254Fr`], which rejects
51//! values outside the field instead of reducing them.
52//! - **Scalar-native BabyJubJub keys** → [`babyjubjub::BabyJubSecretScalar`] and
53//! [`eddsa::ScalarSigningKey`], which derive `A = scalar·Base8` directly without
54//! the seed-backed profile's hash/prune step.
55//! - **Raw 256-bit integers** (the cipher key material, [`hash_utils::sha256_bigint`]
56//! inputs, the EdDSA message) → `num_bigint::BigUint`, packed **without** field
57//! reduction. See [`encoding`].
58//! - **Endianness:** big-endian for the cipher / `sha256BigInt`; little-endian for
59//! EdDSA. They are named explicitly in [`encoding`] so the two never get mixed up.
60//!
61//! ## Error convention
62//!
63//! Internal boundary parsers **panic** on malformed input (e.g. a non-numeric
64//! "decimal"), because callers pass already-validated values and a panic surfaces a
65//! programming error loudly. Untrusted input is validated at the wasm boundary
66//! before reaching here.
67//!
68//! ## Signing profiles
69//!
70//! Seed-backed keys and direct-scalar keys are co-equal supported profiles. Use
71//! [`witness::SeedNoteSigner`] for established seed-derived accounts and
72//! [`eddsa::ScalarSigningKey`] when the account stores a canonical BabyJubjub
73//! subgroup scalar. Both implement [`witness::NoteSigner`] and produce the same
74//! Curvy circuit-input shapes; neither profile is deprecated.
75//!
76//! ## Verification
77//!
78//! Committed compatibility vectors from the production TypeScript and Go
79//! implementations are asserted in the crate's test suite. Primitive behavior
80//! must remain byte-for-byte compatible with those vectors.
81//!
82//! ## Example
83//!
84//! ```
85//! use curvy_core::field::{fr_from_dec, fr_to_dec};
86//! use curvy_core::poseidon::poseidon;
87//!
88//! // The canonical circomlib test vector: Poseidon([1, 2]).
89//! let h = poseidon(&[fr_from_dec("1"), fr_from_dec("2")]);
90//! assert_eq!(
91//! fr_to_dec(&h),
92//! "7853200120776062878684798364095072458815029376092732009249414926327459813530",
93//! );
94//! ```
95
96// ── Shared: the boundary (field arithmetic + byte encodings) ────────────────────
97pub mod encoding;
98pub mod field;
99
100// ── Domain B: circuit/commitment layer (BabyJubjub + Poseidon over BN254 Fr) ────
101pub mod babyjubjub;
102pub mod blake512;
103pub mod cipher;
104pub mod eddsa;
105pub mod hash_utils;
106pub mod note;
107pub mod poseidon;
108
109// ── Trees & witness builders (consumed by the v2 zk-circuits) ───────────────────
110pub mod imt;
111pub mod witness;
112
113// ── Domain A: stealth addressing core (secp256k1 + BN254 pairing) ───────────────
114pub mod stealth;
115
116// Convenience re-exports for the two most-used items.
117pub use field::Fr;
118pub use imt::{NOTES_SHARD_HEIGHT, NOTES_SHARD_SIZE, NOTES_TREE_DEPTH, NOTES_TREE_VERSION};
119pub use poseidon::poseidon;