diff --git a/.gitignore b/.gitignore index 532d021..5fe490f 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ Thumbs.db ### Development planning (not part of the source) ### plan.md +/.playwright-mcp/ diff --git a/docs/adr/0014-w3c-verifiable-credential-circuit-support.md b/docs/adr/0014-w3c-verifiable-credential-circuit-support.md new file mode 100644 index 0000000..3083f4c --- /dev/null +++ b/docs/adr/0014-w3c-verifiable-credential-circuit-support.md @@ -0,0 +1,172 @@ +# ADR-0014: W3C Verifiable Credential Support in Circuit Lib + +## Status +Proposed + +## Date +2026-04-02 + +## Context + +The identity-kyc demo (`zeroj-usecases/identity-kyc`) demonstrates privacy-preserving KYC on Cardano — users prove eligibility (age, country) without revealing personal data. However, it uses **Poseidon-signed credentials** (a shared-secret scheme), which has two limitations: + +1. **No standard interoperability** — Poseidon-signed credentials are ZeroJ-specific. They can't be issued or verified by W3C VC tooling, DID wallets, or Atala PRISM. +2. **Weak security model** — the issuer and holder share the same secret (`credentialSecret`). The issuer can impersonate the holder, and the secret must be transmitted securely. + +To support W3C Verifiable Credentials and DID-based identity, the ZK circuit library needs three new capabilities that enable **asymmetric credential signatures** verifiable inside a ZK proof. + +### Current state of zeroj-circuit-lib + +| Primitive | Status | Notes | +|-----------|--------|-------| +| Poseidon hash | Available | Used for credential hashing today | +| Merkle proof verification | Available | Used for country whitelist | +| Range comparisons | Available | Used for age >= minAge | +| **BabyJubJub curve** | **Removed** | Was in circuit-lib, removed during security audit (missing subgroup checks) | +| **EdDSA signature verification** | **Removed** | Depended on BabyJubJub, removed with it (signature malleability issues) | +| **BBS+ signature verification** | **Not implemented** | Requires pairing math inside circuit | + +The BabyJubJub and EdDSA implementations were removed after a security audit identified: +- BabyJubJub: missing cofactor-clearing / subgroup checks — points from the larger curve group could bypass verification +- EdDSA: signature malleability — multiple valid signatures for the same message (S + L is also valid where L is the subgroup order) +- Both: zero test coverage for edge cases + +### Why these matter for Cardano + +Cardano's identity ecosystem is moving toward DID and W3C VCs: +- **Atala PRISM** uses W3C Verifiable Credentials with DID:prism identifiers +- **CIP-0030 / CIP-0045** wallet standards support credential presentation +- **EU eIDAS 2.0** mandates interoperable digital identity — W3C VC is the baseline format + +Without in-circuit signature verification, ZeroJ can only prove statements about credentials signed with symmetric (Poseidon) schemes, limiting adoption to single-issuer, closed systems. + +## Decision + +### Add three capabilities to zeroj-circuit-lib, in priority order: + +### 1. BabyJubJub curve (re-add with hardening) + +Re-implement the BabyJubJub twisted Edwards curve with proper security: + +```java +public class SignalBabyJubJub { + // Point addition on BabyJubJub (twisted Edwards: ax^2 + y^2 = 1 + dx^2y^2) + // a = 168700, d = 168696 (matching circomlib) + static Signal[] add(SignalBuilder c, Signal[] p1, Signal[] p2); + + // Scalar multiplication via double-and-add (constant-time ladder) + static Signal[] scalarMul(SignalBuilder c, Signal scalar, Signal[] basePoint, int nBits); + + // Subgroup check: verify point is in the prime-order subgroup + // Multiplies by cofactor (8) and checks result is not identity + static void assertInSubgroup(SignalBuilder c, Signal[] point); +} +``` + +**Security fixes:** +- Cofactor clearing: `assertInSubgroup()` ensures points are in the prime-order subgroup (order `l`), not the larger curve group (order `8l`) +- All public inputs that are curve points must pass subgroup check before use in arithmetic +- Test vectors from circomlib/iden3 for edge cases (identity, generator, cofactor multiples) + +**Constraints:** ~500 per scalar multiplication (256 doublings + ~128 additions for 256-bit scalar) + +### 2. EdDSA signature verification in-circuit + +Verify EdDSA (EdDSA-BabyJubJub, compatible with circomlib/iden3) signatures inside a ZK proof: + +```java +public class SignalEdDSA { + /** + * Verify an EdDSA signature (R, S) on message M under public key A. + * + * Checks: [S]B == R + [H(R, A, M)]A + * where B is the generator and H is Poseidon (matching circomlib). + */ + static Signal verify(SignalBuilder c, + Signal[] pubKeyA, // [Ax, Ay] + Signal[] sigR, // [Rx, Ry] + Signal sigS, // scalar S + Signal message); // message hash +} +``` + +**Security fixes:** +- Strict S range check: `S < l` (subgroup order) — prevents signature malleability where `S + l` is also valid +- R point subgroup check via `assertInSubgroup()` +- Public key subgroup check via `assertInSubgroup()` +- Use Poseidon for H(R, A, M) — matching circomlib's `EdDSAPoseidonVerifier` + +**Constraints:** ~3,000 (2 scalar multiplications + 1 point addition + Poseidon hash + range check) + +### 3. BBS+ selective disclosure (future) + +BBS+ signatures allow a holder to selectively disclose individual claims from a credential without revealing others. This is the gold standard for W3C VC privacy. + +```java +public class SignalBBS { + /** + * Verify a BBS+ signature on a set of messages, with selective disclosure. + * + * Only disclosed messages are public inputs. Hidden messages are private. + * The verifier learns: "a trusted issuer signed these claims, and the + * disclosed claims have these values" — without learning the hidden claims. + */ + static Signal verify(SignalBuilder c, + Signal[] disclosedMessages, // public + Signal[] hiddenMessages, // private + Signal[] signature, // private + Signal[] issuerPublicKey); // public +} +``` + +**Complexity:** Requires BLS12-381 pairing operations inside the circuit (~10,000+ constraints). This is the most complex primitive and should be implemented after EdDSA is stable. + +**Note:** BBS+ is still evolving as a standard (IETF draft). Implementation should track the latest spec. + +## Consequences + +### Positive + +1. **W3C VC compatibility** — credentials issued by any W3C VC compliant issuer (Atala PRISM, Spruce, etc.) can be verified inside ZeroJ ZK proofs +2. **Multi-issuer ecosystems** — asymmetric signatures mean the issuer can't impersonate the holder +3. **Atala PRISM integration** — enables ZK proofs over PRISM-issued credentials on Cardano +4. **EU eIDAS readiness** — Cardano DApps can accept European Digital Identity Wallet credentials +5. **Selective disclosure (BBS+)** — holders reveal only the claims needed, not the entire credential +6. **Ecosystem alignment** — matches what other ZK projects (circomlib, Polygon ID, Iden3) provide + +### Negative + +1. **Circuit size increase** — EdDSA verification adds ~3,000 constraints vs ~660 for Poseidon-signed, increasing proof generation time by ~3x +2. **BabyJubJub is not Cardano-native** — Cardano's Plutus V3 has BLS12-381 builtins, not BabyJubJub. EdDSA signature verification happens inside the ZK circuit (off-chain), not via on-chain builtins +3. **Implementation complexity** — elliptic curve arithmetic in R1CS is error-prone; incomplete addition formulas, special-case handling for identity/doubling +4. **BBS+ is speculative** — the IETF standard is not finalized; implementation may need to change + +## Risks + +| Risk | Severity | Mitigation | +|------|----------|------------| +| BabyJubJub subgroup check bypass | **Critical** | Mandatory cofactor clearing; test with all 8 coset representatives; differential testing against circomlib | +| EdDSA signature malleability | **Critical** | Strict S < l range check (bit decomposition); test with S, S+l, S+2l; compare against circomlib EdDSAPoseidonVerifier | +| Incomplete addition formula edge cases | High | Use complete twisted Edwards addition formula (no exceptions for doubling/identity); exhaustive test with identity, generator, -generator, cofactor points | +| BBS+ spec changes | Medium | Implement behind feature flag; track IETF draft; plan for breaking changes | +| Performance regression for simple use cases | Low | Poseidon-signed credentials remain available as a lightweight option; circuit choice is per-application | + +## Implementation Order + +1. **Phase 1: BabyJubJub** — re-add with subgroup checks, 100% test coverage, differential testing against circomlib +2. **Phase 2: EdDSA** — signature verification circuit, malleability fix, test against circomlib EdDSAPoseidonVerifier +3. **Phase 3: Identity KYC demo upgrade** — switch from Poseidon-signed to EdDSA-signed credentials +4. **Phase 4: BBS+** — selective disclosure (when IETF spec stabilizes) + +## References + +- [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) +- [W3C Decentralized Identifiers (DIDs)](https://www.w3.org/TR/did-core/) +- [BabyJubJub — iden3 specification](https://iden3-docs.readthedocs.io/en/latest/iden3_repos/research/publications/zkproof-standards-workshop-2/baby-jubjub/baby-jubjub.html) +- [circomlib EdDSAPoseidonVerifier](https://github.com/iden3/circomlib/blob/master/circuits/eddsa.circom) +- [BBS+ Signatures — IETF Draft](https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-07.html) +- [Atala PRISM — Cardano Identity](https://atalaprism.io/) +- [EU eIDAS 2.0 — European Digital Identity](https://digital-strategy.ec.europa.eu/en/policies/eidas-regulation) +- ZeroJ security audit findings (BabyJubJub/EdDSA removal rationale) +- `zeroj-usecases/identity-kyc` — current Poseidon-signed credential demo +- `docs/usecases/identity-and-credentials.md` — full design doc with 3 credential approaches diff --git a/docs/adr/0015-bls12-381-poseidon-constants.md b/docs/adr/0015-bls12-381-poseidon-constants.md new file mode 100644 index 0000000..6bb7dfa --- /dev/null +++ b/docs/adr/0015-bls12-381-poseidon-constants.md @@ -0,0 +1,194 @@ +# ADR-0015: Standards-Compatible Poseidon Constants for BLS12-381 + +## Status +Accepted + +## Date +2026-04-17 + +## Context + +ZeroJ's circuit library ships a Poseidon hash gadget (`zeroj-circuit-lib/Poseidon.java`) that is field-agnostic at the gadget level — it uses `CircuitAPI.add` / `CircuitAPI.mul`, which reduce modulo whatever prime the `FieldConfig` provides. The host field is selected at `CircuitBuilder.compileR1CS(CurveId)` time and currently supports `BN254`, `BLS12_381`, and `PALLAS`. + +The **round constants** (`PoseidonConstants.C`) and **MDS matrix** (`PoseidonConstants.M`), however, are hardcoded from **iden3 / circomlibjs**, which were generated for the **BN254 scalar field**. When a circuit is compiled with `FieldConfig.BLS12_381`, those same constants are used — they happen to be valid BLS12-381 field elements (every BN254 constant is numerically less than the BN254 prime, which is less than the BLS12-381 prime), so arithmetic works and the off-circuit Java hash (`zeroj-usecases/*/PoseidonCompute.java`) matches the in-circuit gadget. + +This works end-to-end for ZeroJ-internal flows — `proof-of-reserves`, `digital-product-passport`, and `identity-kyc` all rely on it today. But the resulting hash function is **non-standard**: + +- Constants are not derived from the BLS12-381 scalar field parameters. +- Output values differ from every published BLS12-381 Poseidon reference. +- No external library can independently compute the same hash. + +### Interoperability problem + +The Jubjub ecosystem (Jubjub is the BLS12-381 sibling of BabyJubJub — a twisted Edwards curve embedded in the BLS12-381 scalar field) standardizes on **proper, field-derived BLS12-381 Poseidon**. Published references include: + +| Reference | Use | +|-----------|-----| +| Arkworks `ark-crypto-primitives::sponge::poseidon` | Widely used Rust ZK stack; reference for many circuits | +| zkcrypto / neptune (Filecoin, Lurk) | Merkle-tree-optimized Poseidon | +| Dusk Network `poseidon252` | Poseidon-based BLS12-381 stack | + +All of these use round constants generated by the **Grain LFSR procedure** specified in the Poseidon paper (Grassi et al., §5.1), which deterministically derives `(C, M)` from `(prime p, t, α, RF, RP)`. Given the same parameters, every correct implementation produces identical constants — so constants *can* be ported by extraction, or regenerated from the spec. + +Without standards-compatible Poseidon, ZeroJ cannot: + +1. Interoperate with external Jubjub circuit libraries (e.g., in-circuit EdDSA-over-Jubjub, Pedersen-over-Jubjub gadgets ported from arkworks / circom-jubjub / zcash). +2. Accept Poseidon-based commitments produced by external tooling (e.g., off-chain indexers, wallet SDKs, third-party identity issuers). +3. Publish Poseidon hashes that downstream consumers can independently re-verify. + +### Why this ADR now + +ADR-0014 (W3C Verifiable Credentials) and the upcoming Jubjub-in-circuit work (needed for standards-compatible in-circuit EdDSA, Pedersen commitments, and Merkle trees over Jubjub) both assume a BLS12-381-native Poseidon. Fixing constants is a **prerequisite** — the Jubjub gadgets hash into the BLS12-381 scalar field, and those hashes must be reproducible by external verifiers. + +### Current state + +| Component | Field | Constants source | Standards-compatible? | +|-----------|-------|------------------|----------------------| +| `PoseidonConstants.java` | BN254-derived | iden3/circomlibjs | Yes (for BN254 / circom) | +| `Poseidon.hash()` gadget | Whatever `FieldConfig` selects | Reads `PoseidonConstants` unconditionally | No, if field ≠ BN254 | +| `PoseidonCompute.java` (usecases) | BLS12-381 | Reflectively loads `PoseidonConstants` | No | + +## Decision + +### 1. Introduce a parameterized Poseidon configuration + +Replace the singleton `PoseidonConstants` with a `PoseidonParams` value type keyed on `(field, t, α, RF, RP)`: + +```java +public record PoseidonParams( + FieldConfig field, + int t, // state width (e.g., 3 for 2-to-1 hash) + int alpha, // S-box exponent (5) + int rf, // full rounds (8) + int rp, // partial rounds (depends on t and field) + BigInteger[] c, // round constants, length = (rf + rp) * t + BigInteger[] m // MDS matrix, length = t * t +) {} +``` + +Provide named presets: + +- `PoseidonParams.BN254_T3` — existing circomlib constants (unchanged, preserves BN254 / circom interop). +- `PoseidonParams.BLS12_381_T3` — **new**, standards-compatible (paper-spec, `α=5, RF=8, RP=57`). + +`BLS12_381_T5` and other widths (`t=5` for 4-ary Merkle, `t=9` for batch hashing) are **deferred to ADR-0016 (Jubjub-in-circuit)**, where they are a concrete requirement. The generator built here makes those additions a one-line preset extension when needed. + +### 2. Generate BLS12-381 constants via Grain LFSR (not hand-ported) + +Implement `PoseidonGrainLFSR` — a Java port of the Grain LFSR procedure from the Poseidon paper (§5.1, also `poseidon_hash/generate_parameters_grain.sage` from the original repo). Inputs: `(field_bytes, prime, t, α, RF, RP)`. Outputs: round constants and MDS matrix. + +Benefits over hand-porting from a single reference: + +- Any future `(p, t, α, ...)` combination is free — e.g., `t=5` for 4-ary Merkle, `t=9` for batch hashing. +- Reproducibility is self-evident; no "why these numbers" opacity. +- Cross-verification is straightforward: run generator, compare output byte-for-byte with arkworks' and zkcrypto's published constants. + +The generator runs **at build time** (Gradle task) and caches constants as Java source (same pattern as current `PoseidonConstants.java`) — no runtime generation, no reflection, GraalVM-native-image friendly. + +### 3. Anchor correctness on the Poseidon paper spec, not a specific implementation + +The canonical reference is the **original Poseidon paper (Grassi et al., 2021)** and its published Sage generator (`generate_parameters_grain.sage` from the IAIK `hadeshash` repository). Arkworks, zkcrypto, and Dusk are all *implementations* of this spec; conforming implementations produce identical `(C, M)` for the same `(p, t, α, RF, RP)`. + +Target parameters for v1: +- Field: **BLS12-381 scalar** +- `t = 3` (state width — two-to-one hash) +- `α = 5` (S-box exponent) +- `RF = 8` full rounds +- `RP = 57` partial rounds + +Implementation approach: +- Pin the **`hadeshash` commit** of `generate_parameters_grain.sage` as the authoritative generator. This is the source of truth. +- Port the LFSR logic to Java (see §2 above). +- Use **arkworks `ark-crypto-primitives`** and **zkcrypto reference** as cross-check oracles only. ZeroJ's Java-generated constants must match both byte-for-byte before merge. +- If any implementation disagrees with the paper's Sage script, the script wins — we file bugs upstream rather than match a divergent impl. + +This framing is resistant to "arkworks changed their default" and grounds correctness in the paper, not a moving downstream target. + +### 4. Delete the BN254-over-BLS hybrid; no legacy preset + +The current behavior — where BN254-derived `PoseidonConstants` are used unchanged over the BLS12-381 scalar field — has **no external conformance** and is **not yet released**. It is deleted outright. No `BLS12_381_T3_LEGACY` preset is introduced. + +Rationale: +- ZeroJ has not shipped a stable release; no downstream consumer depends on the hybrid's outputs. +- None of the usecases persist Poseidon hashes across runs — they recompute state each execution. Verified at ADR authoring: `proof-of-reserves`, `digital-product-passport`, and `identity-kyc` demos rebuild trees / commitments fresh. +- Keeping a legacy preset only invites accidental selection and multiplies the test surface. + +### 5. Migrate circuit-lib gadget and usecases + +- `Poseidon.hash(api, a, b)` → new overload `Poseidon.hash(api, params, a, b)`. +- **Legacy no-params overload delegates to `PoseidonParamsBN254T3.INSTANCE`** (not the compile-curve-derived preset as originally proposed). Reason: the gadget's `define` callback runs before `CircuitBuilder.compileR1CS(CurveId)` is called, so the curve is not available at gadget-define time. Auto-selection by `FieldConfig` would require plumbing the expected field through `CircuitBuilder` so the gadget can read it during `define` — a larger change deferred to a follow-up. BN254 is the safest back-compat default (circom-compatible, matches the pre-parameterization behavior exactly). +- **Footgun acknowledged**: a caller can pass `PoseidonParamsBLS12_381T3.INSTANCE` and then compile with `CurveId.BN254` (or vice versa). The result is a syntactically valid witness with a non-canonical hash. No runtime error is raised. Mitigation today is Javadoc-level; proper fix is the CircuitBuilder-field-threading follow-up above. +- `PoseidonN` likewise parameterized. +- `PoseidonCompute.java` in each usecase moves out of reflection into direct `PoseidonParams` usage. +- `PoseidonConstants` retained as a thin facade that exposes `BN254_T3` constants, marked `@Deprecated` with pointer to `PoseidonParams.BN254_T3`. + +### 6. Cross-verification test suite + +New module `zeroj-circuit-lib/src/test/.../PoseidonCrossVerificationTest.java`: + +- **Grain LFSR regeneration**: regenerate constants at test time from the pinned Sage-script logic, compare to committed constants byte-for-byte. +- **Paper-spec cross-check** ✓ executed: `zeroj-circuit-lib/src/test/resources/poseidon-sage/` contains an independent SageMath reference (Grain LFSR + Poseidon permutation per the paper spec) plus a pinned golden output file. Running it against `sagemath/sagemath:latest` in Docker produced `Poseidon_BLS12_381(0,0)`, `(1,2)`, `(123,456)` that byte-match ZeroJ's Java output for all three fixtures. `PoseidonCrossVerificationTest.java_matches_sageReferenceOutput` asserts this at every build. +- **Implementation cross-check**: hardcode known `Poseidon(a, b) = h` triples from **arkworks** and **zkcrypto** published test vectors; verify both the off-circuit Java and the in-circuit witness produce `h`. Both implementations must agree; if they diverge, fall back to the paper spec as arbiter. +- **Circomlibjs vectors**: retain existing BN254 test vectors for `BN254_T3` preset (no change). +- **Self-consistency**: for each preset, assert in-circuit gadget output matches off-circuit `PoseidonCompute` output across 100 random inputs. + +## Consequences + +### Easier +- Jubjub-in-circuit work (upcoming) can assume standards-compatible Poseidon — no custom fork needed. +- External tools (indexers, wallets, other circuits) can independently compute and verify ZeroJ-produced Poseidon hashes. +- W3C VC / BBS+ adjacent work (ADR-0014) gets a spec-aligned primitive. +- Adding new Poseidon widths (t=5, t=9) becomes a one-line preset addition. +- BN254 / circom interop path remains intact — explicit and named. + +### Harder / more work +- One-time migration: the three usecases (`proof-of-reserves`, `digital-product-passport`, `identity-kyc`) recompute any persisted Poseidon commitments. Current demos compute fresh each run — no stored state needs migration — but this must be verified before the switch. +- Test vectors in existing tests that assert specific hash outputs will change for BLS12-381 circuits; need regeneration from new constants. +- Build system gains a constant-generation step (kept deterministic and cached, but adds a dependency). + +### Neutral +- Performance unchanged — same round count, same S-box, same MDS dimensions. +- Code size slightly larger (two constant sets vs. one). + +### Migration note for existing installs +Any pre-ADR-0015 demo install that has **persisted hash-dependent state** must wipe and rebuild it on upgrade. Specifically: + +- `zeroj-usecases/digital-product-passport/data/dpp-trie*` (RocksDB MPF with Poseidon leaves) +- `zeroj-usecases/digital-product-passport/data/dpp-db*` (H2 DB with MPF root columns) +- `zeroj-usecases/digital-product-passport/data/setup-*.bin` and `srs.bin` (R1CS + SRS caches — circuit constants differ post-migration) +- Equivalent `./data/*` caches in other usecases + +There is no automatic migration path; pre-ADR hashes are the BN254-over-BLS hybrid and are not recoverable from the new standards-compatible Poseidon. Demos recompute state from seed data on startup, so wiping is safe. Any production system would need a one-time rehash pass; none exists today. + +## Risks + +1. **Grain LFSR porting error** — subtle endianness / bit-ordering mistakes in the LFSR can produce *almost* correct constants that drift from the spec. Mitigation: byte-level equality against output of the pinned `hadeshash` Sage script is the primary merge gate; arkworks + zkcrypto act as secondary oracles. If we can't reproduce the spec output exactly, we don't ship. + +2. **Parameter divergence between implementations** — arkworks, zkcrypto, and Dusk may choose different `(RF, RP)` or security-margin heuristics for the same `t`. Decision: follow the **Poseidon v1 paper's recommended parameters** for our target security level (128-bit), not any one downstream impl. If a user needs a specific library's variant (e.g., Dusk's `poseidon252`), add a clearly named preset (`BLS12_381_T3_DUSK`) rather than bending defaults. + +3. **Paper parameter interpretation** — the paper offers parameter *tables*; the reference Sage generator makes specific choices (e.g., exact RP for a given security level). Decision: follow the Sage generator's choices, not our own reading of the table. This removes ambiguity. + +4. **GraalVM native-image compatibility** — must keep constants as hardcoded `BigInteger` arrays in generated Java source, not loaded from resource files or computed at class init. Current approach already complies; the Gradle code-gen task must preserve this. + +5. **Accidental reflection reintroduction** — `PoseidonCompute.java` in usecases currently uses reflection to peek at `PoseidonConstants`. Migration must remove reflection entirely; otherwise we've just renamed the problem. + +6. **On-chain input compatibility** — Poseidon outputs appear as **public inputs** to Cardano-verified Groth16 proofs. Any party independently reconstructing a public input (e.g., recomputing a Merkle root from chain data) must reproduce the exact hash. Standards-compatible Poseidon is therefore not merely an interop nicety but a **correctness requirement for third-party verifiability** of ZeroJ proofs on Cardano. + +## Resolved questions + +All prior open questions resolved: + +- **Reference anchor**: pin the Poseidon v1 paper + `hadeshash` Sage script as the spec. Arkworks / zkcrypto are cross-check oracles, not the source of truth. +- **Widths in v1**: ship `t=3` only. Additional widths (`t=5`, others) deferred to ADR-0016 (Jubjub-in-circuit), where they are a concrete requirement driven by 4-ary Merkle and similar structures. +- **Legacy BN254-over-BLS variant**: delete outright. ZeroJ is pre-release, no persisted hashes depend on the hybrid, no legacy preset is retained. + +## Scope (implementation plan) + +1. **Grain LFSR generator** (`zeroj-circuit-lib`, ~200 LoC + tests) — 3 days. +2. **Gradle code-gen task** — generate `PoseidonParams.BLS12_381_T3` Java source at build time; commit the generated file for IDE ergonomics. 1 day. +3. **Parameterize `Poseidon`, `PoseidonN` gadgets** — 1 day. +4. **Cross-verification tests** (paper-spec Sage output + arkworks + zkcrypto vectors + self-consistency) — 2 days. +5. **Delete BN254-over-BLS hybrid path; migrate three usecases off reflection onto `PoseidonParams`** — 1 day. +6. **Documentation**: update `zeroj-circuit-lib/README.md`, add entry in `docs/circuit-primitives.md` — 0.5 day. + +**Total: ~8 working days**, single contributor. Sequenced before Jubjub-in-circuit work (ADR-0016), which depends on `PoseidonParams.BLS12_381_T3` and adds further widths (`t=5`, etc.) driven by concrete Jubjub Merkle / commitment requirements. \ No newline at end of file diff --git a/docs/adr/0016-jubjub-in-circuit.md b/docs/adr/0016-jubjub-in-circuit.md new file mode 100644 index 0000000..7082a6a --- /dev/null +++ b/docs/adr/0016-jubjub-in-circuit.md @@ -0,0 +1,291 @@ +# ADR-0016: Jubjub-in-Circuit for BLS12-381 Cardano Proofs + +## Status +Accepted + +## Date +2026-04-18 + +## Context + +ADR-0015 shipped standards-compatible Poseidon over the BLS12-381 scalar +field. That unlocks hash-based primitives (Merkle trees, nullifiers, +commitments from `Poseidon(secret, value)`) but leaves a gap: **no +elliptic-curve operations inside the circuit.** + +ZeroJ's current circuit library offers `api.add`, `api.mul`, `api.toBinary`, +etc. — field-level arithmetic. The `Poseidon`/`PoseidonN` gadgets layer on +top of those. What's missing for the Cardano ZK roadmap: + +- **Asymmetric signatures in-circuit.** Today `identity-kyc` uses + `Poseidon(issuerSecret, claims)` — a shared-secret scheme where issuer + and holder both know the secret. ADR-0014 flagged this as inadequate for + W3C VC / DID / Atala PRISM interop. The fix is in-circuit EdDSA. EdDSA + over any useful curve inside a BLS12-381 SNARK is prohibitively expensive + unless the curve's base field *is* BLS12-381's scalar field. + +- **Pedersen commitments.** Hiding + binding + homomorphic — the workhorse + of confidential amounts, private voting tallies, and range proofs. Built + from two EC scalar-mults plus one addition per commitment. + +- **Alternative Merkle layouts.** Jubjub-Pedersen Merkle trees, hybrid + Jubjub/Poseidon Merkle, etc. + +### Why Jubjub, not BabyJubJub + +BabyJubJub is the twisted-Edwards curve embedded in **BN254's** scalar +field — the Ethereum / circom-ecosystem choice. Using BabyJubJub inside a +Cardano-verifiable SNARK means either (a) proving over BN254 (no Plutus +builtin; unverifiable on Cardano) or (b) emulating BabyJubJub inside a +BLS12-381 SNARK (tens of thousands of constraints per scalar-mul). + +**Jubjub** is the Zcash/zkcrypto curve whose base field *is* the BLS12-381 +scalar field. A Jubjub scalar-mul inside a BLS12-381 SNARK costs roughly +one constraint per bit of the scalar — a few hundred constraints total. +That's the whole point of an "embedded curve". + +The upshot: **Jubjub is Cardano-native**, BabyJubJub is not. Existing +Plutus V3 Groth16 verifiers (`zeroj-onchain-julc/Groth16BLS12381Verifier`, +`PlonkBLS12381FullVerifier`) accept Jubjub proofs without modification — +all the complexity lives inside the SNARK. + +### Terminology note + +Early zeroj code and ADR-0014 occasionally used "BabyJubJub" loosely. For +this ADR and all subsequent work, "Jubjub" means the Zcash/zkcrypto curve +with the parameters pinned below. BabyJubJub (BN254 sibling) is not +implemented by zeroj. + +### Security history + +ADR-0014 notes that an earlier BabyJubJub implementation was removed from +`zeroj-circuit-lib` during a security audit. The issues found were generic +to twisted-Edwards in-circuit work and apply equally to Jubjub: + +- Missing cofactor-clearing / subgroup checks → malicious points from the + full curve group bypass verification. +- EdDSA signature malleability → multiple valid `S` exist for the same + message (`S` and `S + l` both verify). +- Zero test coverage for edge cases. + +This ADR explicitly handles all three. + +## Decision + +### 1. Curve parameters — pin the Zcash/zkcrypto Jubjub + +Authoritative reference: . + +| Parameter | Value | +|---|---| +| Host field | BLS12-381 scalar field (r = 0x73eda753…00000001) | +| Curve form | Twisted Edwards: `-u² + v² = 1 + d·u²·v²` | +| a (Edwards param) | -1 | +| d | `0x2a9318e74bfa2b48 f5fd9207e6bd7fd4 292d7f6d37579d26 01065fd6d6343eb1` (little-endian limbs) | +| Base point u | `0x62edcbb8bf3787c8 8b0f03ddd60a8187 caf55d1b29bf81af e4b3d35df1a7adfe` | +| Base point v | `0x000000000000000b` (= 11) | +| Subgroup order `l` | 0x0e7db4ea6533afa906673b0101343b00 (Jubjub scalar field prime) | +| Cofactor | 8 | + +Values extracted verbatim from `zkcrypto/jubjub:src/lib.rs` at HEAD. The +zeroj implementation will pin a specific commit and regenerate if upstream +releases a parameter update (none expected). + +### 2. Coordinate system — extended twisted Edwards + +In-circuit representation uses **affine (u, v)** coordinates (cheap for a +one-shot use but costly for multi-step chains because of the inverse in +the addition formula). + +Off-circuit and in-gadget-internal representation uses **extended Edwards +coordinates (U, V, Z, T)** per Hisil–Wong–Carter–Dawson 2008: + +- Point = `(U, V, Z, T)` with affine `(u, v) = (U/Z, V/Z)` and `T = U·V/Z`. +- Unified addition formula (no branching; complete for `a = -1` and + non-square `d`, both satisfied by Jubjub). +- Doubling formula specialized for 2P. + +This is the same choice zkcrypto and ark-ed-on-bls12-381 make. It +minimizes both the off-circuit CPU cost and the in-circuit constraint +count for scalar-mul. + +### 3. Scalar multiplication — fixed-base via windowed table, variable-base via double-and-add + +- **Fixed-base scalar-mul** (for the generator G and a handful of + application-specific bases): precompute a 3-bit windowed lookup table, + use conditional-add gadgets. ~255 constraints per 255-bit scalar for + the generator alone; table is a one-time compile-time cost. + +- **Variable-base scalar-mul** (arbitrary points — needed for Pedersen + public keys and generic EC ops): double-and-add over the scalar bits. + No windowing; ~3x more expensive than fixed-base but unavoidable for + runtime-chosen bases. + +Both use the unified addition formula; no exceptional cases in the +add-chain because `a = -1` and `d` is non-square. + +### 4. Subgroup check required on every untrusted point + +Jubjub has cofactor 8. An attacker-controlled point may lie outside the +prime-order subgroup, enabling invariant-subgroup attacks on EC-based +verification. Every point entering a circuit from an untrusted source +(signature R, public key, commitment) must pass an in-circuit +`isInSubgroup()` check. + +Subgroup check: `[l] · P == O` where `l` is the Jubjub scalar field order. +Cost: one variable-base scalar-mul, ~800 constraints. Cached when the +point is reused within a proof. + +### 5. EdDSA-Jubjub per RFC 8032 with strict encoding + +- **Signature**: `(R, S)` where `R` ∈ Jubjub and `S` ∈ [0, l). +- **Verification**: `[S]·G == R + [H(R, pk, msg)]·pk`, where H is a hash + function committed to below. +- **Hash function in-circuit**: Poseidon over the BLS12-381 scalar field + (from ADR-0015's `PoseidonParamsBLS12_381T3`). This gives a direct + in-circuit hash without needing SHA-512 emulation. +- **Malleability prevention**: reject `S ≥ l` (strict range check in + circuit via `S.assertInRange(253)` plus `S < l` conditional assert). +- **Subgroup enforcement**: both `R` and `pk` must pass `isInSubgroup()` + before entering the verification equation. + +Off-circuit signing (for test-vector generation and application code) uses +the same spec; compatibility is asserted via cross-check tests. + +### 6. Pedersen commitment — two-base, variable-scalar + +`Commit(v, r) = [v]·G + [r]·H` where `H = hashToCurve("zeroj.pedersen.H")` +is a second base point whose discrete log w.r.t. G is unknown (binding). + +`hashToCurve` procedure: Poseidon hash of the domain tag, reduced mod +the Jubjub base field, then mapped to a point via Elligator 2 (or the +simpler "try-and-increment" if performance permits, since this runs +once at library init). + +### 7. Optional — Jubjub-Merkle (M4 nice-to-have) + +For most applications Poseidon-Merkle is better (smaller circuits, +simpler cost model). Jubjub-Merkle makes sense only when the parent hash +needs homomorphic properties (e.g. proof-of-solvency Merkle sum trees +where a parent holds the Pedersen-committed sum of children). Scope is +narrow; implemented as an opt-in gadget in M4. + +### 8. BLS12-381 Poseidon `t=5` preset + +ADR-0015 deferred `BLS12_381_T5` to this ADR. M4 adds it via the existing +`PoseidonParamsCodegen` path (`Preset("PoseidonParamsBLS12_381T5", ..., t=5, rf=8, rp=60)`). +Used by Jubjub-Pedersen hashing that consumes `(x, y, cofactor_cleared, +domain_tag, ...)` tuples in a single compression. + +## Milestones + +| # | Scope | Tests / oracles | Est. effort | +|---|---|---|---| +| **M1** | Off-circuit `JubjubPoint` (extended coords), add/double/negate, scalar-mul, subgroup check, (de)compression | zkcrypto/jubjub test vectors | 3 days | +| **M2** | In-circuit point add + fixed-base scalar-mul gadget | Self-consistency vs M1 over 100+ random scalars | 4 days | +| **M3** | Variable-base scalar-mul gadget | Same | 3 days | +| **M4** | Pedersen commitment + `BLS12_381_T5` preset; optional Jubjub-Merkle | Cross-check Pedersen `Commit(v, r)` against off-circuit impl | 2 days | +| **M5** | EdDSA-Jubjub off-circuit sign/verify + in-circuit verify | zkcrypto/jubjub / zcash sapling test vectors; edge cases (S = l-1, R not in subgroup, signature malleability) | 1 week | +| **M6** | Consolidated cross-verification suite | External vectors + Sage-reference-Docker golden file (same pattern as ADR-0015) | 2 days | + +Usecase integration (identity-kyc EdDSA migration) happens after M5, with +end-to-end verification on yaci-devkit as a merge gate. + +## Consequences + +### Easier + +- W3C VC / DID / Atala PRISM interop paths open (issuer-signed credentials + become in-circuit verifiable). +- Pedersen-backed confidential amounts enable private voting, confidential + proof-of-reserves, sealed-bid auctions, privacy-preserving loyalty. +- Schnorr / EdDSA wallet-signature-in-circuit enables provable Cardano- + wallet-ownership gated features. +- **No onchain changes**: existing `Groth16BLS12381Verifier` and + `PlonkBLS12381FullVerifier` accept Jubjub-using proofs as-is. + +### Harder + +- Circuit compile times grow (each EdDSA verify adds ~3000 constraints). + Mitigations: gadget caches, parallel provers already shipped. +- Larger test surface; cross-verification against an external ecosystem + (zcash/zkcrypto) is a hard requirement. + +### Neutral + +- No new BLS12-381 onchain primitives needed. +- No backward-compatibility breaks to Poseidon callers; Jubjub is purely + additive. + +## Risks + +1. **Subgroup-check omission** in application code. If a caller forgets + to call `isInSubgroup()` on an incoming signature's `R` or public key, + malicious small-subgroup points can forge verifications. + Mitigation: the high-level `EdDSAJubjub.verify(api, sig, pk, msg)` + gadget *always* performs subgroup checks internally. Low-level point + ops are clearly documented as "no subgroup check — caller's + responsibility". + +2. **Encoding / endianness bugs** in off-circuit / in-circuit interop. + Jubjub points have a canonical compressed form (32 bytes, v-coord LSB + bit indicates sign). Any mismatch breaks cross-checks. + Mitigation: test vectors from zkcrypto/jubjub (`test_to_from_bytes` + vectors) are asserted byte-for-byte; a Sage reference computes the + same encoding. + +3. **EdDSA non-canonical S** — the classic malleability. Accept `S < l` + strictly; reject `S ≥ l` or the `S + l` "alternate signature". + Mitigation: `SignalBinary.assertInRange(253)` + `l.assertLessThan(S)` + both enforced in-circuit. + +4. **Unified-addition completeness assumption**. The unified formula is + complete for twisted-Edwards iff `a` is a square and `d` is non-square. + Jubjub satisfies this (`a = -1` is square in BLS12-381 scalar field; + `d` is non-square per the Zcash parameter verification). + Mitigation: `JubjubCurveTest.assertParameterSquareness` test pins the + assumption; regenerating from the upstream curve parameters would + surface any future divergence. + +5. **Performance: in-circuit EdDSA is ~3000 constraints per verify**. For + a batch verifier (aggregating k signatures), consider batched scalar-mul + tricks. Scope: out of this ADR; revisit if usecase proof times become + problematic. + +6. **No Cardano onchain Jubjub**. All Jubjub operations live inside the + SNARK. Any Plutus script that wants to *directly* compute a Jubjub + point op would have to emulate it in BigInteger arithmetic (impractical + for non-trivial ops). Mitigation: this is the same situation as any + non-builtin curve; the ADR's design explicitly assumes Jubjub stays + in-SNARK. + +## Open questions (resolved) + +- **Subgroup check cost**: in-circuit `[l]·P` ~800 constraints per check. + Acceptable. +- **M5 scope**: include EdDSA verify gadget in the primary ADR scope, not + defer. User explicitly authorized full scope. +- **Use case migration target**: `identity-kyc` — direct fit with ADR-0014 + motivation. + +## Implementation plan + +Sequenced per milestone above. After each milestone, **four-agent review** +(ADR conformance + crypto correctness + Java quality + Codex second +opinion) before moving on, matching the ADR-0015 pattern. Fixes applied +before the next milestone starts. + +Final phase: migrate `identity-kyc` usecase to EdDSA-Jubjub-signed +credentials. End-to-end verification on yaci-devkit as merge gate. +Comprehensive tutorial / user documentation ensures the API is +approachable by new developers. + +## References + +- zkcrypto/jubjub — +- Zcash Sapling spec — §5.4.8 +- Hisil, Wong, Carter, Dawson — *Twisted Edwards Curves Revisited*, 2008 +- RFC 8032 — *Edwards-Curve Digital Signature Algorithm (EdDSA)* +- Poseidon paper + hadeshash — pinned in ADR-0015 +- ADR-0014 — W3C Verifiable Credential Support (motivation for EdDSA) +- ADR-0015 — Standards-compatible Poseidon for BLS12-381 (prerequisite) diff --git a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPI.java b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPI.java index 948322d..b45ae23 100644 --- a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPI.java +++ b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPI.java @@ -97,4 +97,23 @@ public interface CircuitAPI { /** Look up a declared variable by name. */ Variable var(String name); + + // --- Field expectation (checked at compile/witness time) --- + + /** + * Declare that this circuit depends on constants tied to a specific scalar + * field (e.g. Poseidon round constants). Calling this from a gadget records + * the dependency on the circuit graph. At {@code compileR1CS(curve)} / + * {@code calculateWitness(..., curve)} time, if the compile curve's field + * differs from the recorded expectation, compilation throws. + * + *

Calling multiple times with the same {@code field} is fine; with + * conflicting fields within one circuit, throws immediately at define time. + * + *

Default implementation is a no-op — legacy gadgets that do not + * depend on field-specific constants need not implement it. + */ + default void requireField(FieldConfig field) { + // no-op by default + } } diff --git a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPIImpl.java b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPIImpl.java index b4a7866..af7d9c2 100644 --- a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPIImpl.java +++ b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitAPIImpl.java @@ -21,6 +21,7 @@ class CircuitAPIImpl implements CircuitAPI { private final List intermediateVars = new ArrayList<>(); private final Variable oneWire; private int nextId; + private FieldConfig expectedField; CircuitAPIImpl(List publicVarNames, List secretVarNames) { // Wire 0 = constant "1" @@ -51,7 +52,20 @@ private Variable newIntermediate() { ConstraintGraph buildGraph(String name) { return new ConstraintGraph(name, gates, oneWire, publicInputs, secretInputs, - intermediateVars, nextId); + intermediateVars, nextId, expectedField); + } + + @Override + public void requireField(FieldConfig field) { + java.util.Objects.requireNonNull(field, "field"); + if (expectedField == null) { + expectedField = field; + } else if (!expectedField.equals(field)) { + throw new IllegalStateException( + "Conflicting field expectations within one circuit: " + + expectedField.name() + " vs " + field.name() + + ". A circuit may only depend on constants for a single scalar field."); + } } // --- Core primitives --- diff --git a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitBuilder.java b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitBuilder.java index 187883c..c6bb0b6 100644 --- a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitBuilder.java +++ b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/CircuitBuilder.java @@ -98,28 +98,53 @@ public ConstraintGraph constraintGraph() { /** Compile to R1CS constraint system for Groth16. */ public R1CSConstraintSystem compileR1CS(CurveId curve) { requireDefined(); + checkExpectedField(curve); return R1CSCompiler.compile(graph, FieldConfig.forCurve(curve)); } /** Compile to PlonK constraint system. */ public PlonKConstraintSystem compilePlonK(CurveId curve) { requireDefined(); + checkExpectedField(curve); return PlonKCompiler.compile(graph, FieldConfig.forCurve(curve)); } /** Compile to Halo2 PLONKish circuit system. */ public Halo2CircuitSystem compileHalo2(CurveId curve) { requireDefined(); + checkExpectedField(curve); return Halo2Compiler.compile(graph, FieldConfig.forCurve(curve)); } /** Calculate witness for given inputs. */ public BigInteger[] calculateWitness(Map> inputs, CurveId curve) { requireDefined(); + checkExpectedField(curve); return WitnessCalculator.calculate(graph, inputs, FieldConfig.forCurve(curve)); } private void requireDefined() { if (graph == null) throw new IllegalStateException("Circuit not defined yet. Call define() first."); } + + /** + * If a gadget called {@link CircuitAPI#requireField} during {@code define()}, + * assert the compile curve's field matches that expectation. Prevents + * silently producing non-canonical outputs when, e.g., Poseidon params for + * BLS12-381 are paired with a BN254 compile curve. + */ + private void checkExpectedField(CurveId curve) { + FieldConfig expected = graph.expectedField(); + if (expected == null) return; + FieldConfig actual = FieldConfig.forCurve(curve); + if (!expected.equals(actual)) { + throw new IllegalStateException( + "Field mismatch: circuit declared expected field " + expected.name() + + " (via requireField) but compilation / witness calculation " + + "was requested for " + curve + " (" + actual.name() + "). " + + "Typical cause: a gadget was given PoseidonParams for a field " + + "that does not match the target curve. Either pass matching " + + "params or compile for the matching curve."); + } + } } diff --git a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/ConstraintGraph.java b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/ConstraintGraph.java index c362b38..d3ba463 100644 --- a/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/ConstraintGraph.java +++ b/zeroj-circuit-dsl/src/main/java/com/bloxbean/cardano/zeroj/circuit/ConstraintGraph.java @@ -16,6 +16,13 @@ *

  • Wires nPub+1..nPub+nSec: secret input variables
  • *
  • Remaining wires: intermediate variables
  • * + * + *

    {@code expectedField} (nullable): if set, any attempt to compile or + * calculate a witness for a curve whose field differs from this value + * throws. Gadgets that depend on field-specific constants (e.g. Poseidon) + * use {@link CircuitAPI#requireField} during {@code define()} to record + * this expectation and catch field-vs-curve mismatches at compile time + * rather than silently producing non-canonical outputs. */ public record ConstraintGraph( String name, @@ -24,7 +31,8 @@ public record ConstraintGraph( List publicInputs, List secretInputs, List intermediateVars, - int numWires + int numWires, + FieldConfig expectedField ) { public ConstraintGraph { gates = List.copyOf(gates); @@ -33,6 +41,13 @@ public record ConstraintGraph( intermediateVars = List.copyOf(intermediateVars); } + /** Convenience overload for callers that don't set an expected field. */ + public ConstraintGraph(String name, List gates, Variable oneWire, + List publicInputs, List secretInputs, + List intermediateVars, int numWires) { + this(name, gates, oneWire, publicInputs, secretInputs, intermediateVars, numWires, null); + } + /** Total number of input signals (public + secret). */ public int numInputs() { return publicInputs.size() + secretInputs.size(); } diff --git a/zeroj-circuit-lib/build.gradle b/zeroj-circuit-lib/build.gradle index ddaa232..eed460b 100644 --- a/zeroj-circuit-lib/build.gradle +++ b/zeroj-circuit-lib/build.gradle @@ -10,6 +10,25 @@ dependencies { testImplementation project(':zeroj-test-vectors') } +/** + * Regenerates the committed Poseidon parameter source files + * (PoseidonParamsBN254T3.java, PoseidonParamsBLS12_381T3.java) from the + * Grain LFSR reference implementation in PoseidonGrainLFSR. Output is + * written directly into src/main/java so the constants are version-controlled + * and reviewable. + * + * Usage: ./gradlew :zeroj-circuit-lib:generatePoseidonParams + * + * See docs/adr/0015-bls12-381-poseidon-constants.md for the rationale. + */ +tasks.register('generatePoseidonParams', JavaExec) { + group = 'codegen' + description = 'Regenerate Poseidon parameter presets from the Grain LFSR.' + mainClass = 'com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsCodegen' + classpath = sourceSets.main.runtimeClasspath + args projectDir.absolutePath +} + publishing { publications { mavenJava(MavenPublication) { diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/Poseidon.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/Poseidon.java index 35f3044..cad356f 100644 --- a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/Poseidon.java +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/Poseidon.java @@ -4,24 +4,37 @@ import com.bloxbean.cardano.zeroj.circuit.Signal; import com.bloxbean.cardano.zeroj.circuit.SignalBuilder; import com.bloxbean.cardano.zeroj.circuit.Variable; - -import java.math.BigInteger; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParams; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBN254T3; /** * Poseidon hash function circuit — the standard ZK-friendly hash used in circom. * - *

    Parameters for BN254 (matching circomlib exactly): + *

    Structurally supports {@code t=3, α=5, RF=8, RP=57}. Round constants and + * MDS matrix come from a {@link PoseidonParams} instance — pick the preset + * matching the scalar field you will compile the R1CS for: *

      - *
    • t = 3 (width: 2 inputs + 1 capacity)
    • - *
    • Rf = 8 full rounds (4 at start, 4 at end)
    • - *
    • Rp = 57 partial rounds
    • - *
    • S-box: x^5
    • - *
    • Constants from iden3/circomlibjs poseidon_constants.json
    • + *
    • {@link PoseidonParamsBN254T3#INSTANCE} — BN254, circomlib-compatible
    • + *
    • {@link com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3#INSTANCE} + * — BLS12-381, standards-compatible (paper spec)
    • *
    * - *

    Approximately 330 constraints for 2 inputs.

    + *

    The no-params overload defaults to {@link PoseidonParamsBN254T3#INSTANCE} + * for back-compat with circuits that pre-date parameterization. Callers + * targeting BLS12-381 must pass {@code PoseidonParamsBLS12_381T3.INSTANCE} + * explicitly — the no-params default does not auto-select by compile curve + * because the gadget is defined before the curve is known to the circuit. + * + *

    The gadget calls {@link CircuitAPI#requireField} with the preset's field + * during {@code define()}. If you subsequently compile or calculate a witness + * for a curve whose field differs (e.g. {@code BLS12_381_T3} params with + * {@code CurveId.BN254}), {@link com.bloxbean.cardano.zeroj.circuit.CircuitBuilder} + * throws at compile/witness time — this replaces what used to be a silent + * non-canonical output. * - *

    Test vectors (verified against circomlibjs): + *

    Approximately 330 constraints for 2 inputs. + * + *

    Test vectors for the BN254 default (from circomlibjs): *

      *
    • Poseidon(0, 0) = 14744269619966411208579211824598458697587494354926760081771325075741142829156
    • *
    • Poseidon(1, 2) = 7853200120776062878684798364095072458815029376092732009249414926327459813530
    • @@ -32,64 +45,78 @@ public final class Poseidon { private Poseidon() {} - private static final int T = 3; // width - private static final int RF = 8; // full rounds - private static final int RP = 57; // partial rounds - private static final int N_ROUNDS = RF + RP; // 65 - /** - * Poseidon hash of two field elements (CircuitAPI style). + * Poseidon hash of two field elements under the given {@link PoseidonParams}. + * The params must have {@code t=3} and {@code alpha=5}; other shapes are + * not yet supported by this gadget (use a different gadget for wider + * states or different S-boxes). */ - public static Variable hash(CircuitAPI api, Variable input0, Variable input1) { - // Initial state: [0, input0, input1] + public static Variable hash(CircuitAPI api, PoseidonParams params, Variable input0, Variable input1) { + requireT3Alpha5(params); + api.requireField(params.field()); + int t = params.t(); + int rf = params.rf(); + int rp = params.rp(); + int nRounds = rf + rp; + Variable s0 = api.constant(0); Variable s1 = input0; Variable s2 = input1; - for (int r = 0; r < N_ROUNDS; r++) { + for (int r = 0; r < nRounds; r++) { // AddRoundConstants - s0 = api.add(s0, api.constant(PoseidonConstants.C[r * 3])); - s1 = api.add(s1, api.constant(PoseidonConstants.C[r * 3 + 1])); - s2 = api.add(s2, api.constant(PoseidonConstants.C[r * 3 + 2])); + s0 = api.add(s0, api.constant(params.cAt(r, 0))); + s1 = api.add(s1, api.constant(params.cAt(r, 1))); + s2 = api.add(s2, api.constant(params.cAt(r, 2))); // S-box (x^5) - if (r < RF / 2 || r >= RF / 2 + RP) { - // Full round: apply S-box to all state elements + if (r < rf / 2 || r >= rf / 2 + rp) { s0 = sbox(api, s0); s1 = sbox(api, s1); s2 = sbox(api, s2); } else { - // Partial round: apply S-box only to s0 s0 = sbox(api, s0); } - // MDS matrix multiplication + // MDS matrix multiplication (3x3) Variable t0 = api.add(api.add( - api.mul(s0, api.constant(PoseidonConstants.M[0])), - api.mul(s1, api.constant(PoseidonConstants.M[1]))), - api.mul(s2, api.constant(PoseidonConstants.M[2]))); + api.mul(s0, api.constant(params.mAt(0, 0))), + api.mul(s1, api.constant(params.mAt(0, 1)))), + api.mul(s2, api.constant(params.mAt(0, 2)))); Variable t1 = api.add(api.add( - api.mul(s0, api.constant(PoseidonConstants.M[3])), - api.mul(s1, api.constant(PoseidonConstants.M[4]))), - api.mul(s2, api.constant(PoseidonConstants.M[5]))); + api.mul(s0, api.constant(params.mAt(1, 0))), + api.mul(s1, api.constant(params.mAt(1, 1)))), + api.mul(s2, api.constant(params.mAt(1, 2)))); Variable t2 = api.add(api.add( - api.mul(s0, api.constant(PoseidonConstants.M[6])), - api.mul(s1, api.constant(PoseidonConstants.M[7]))), - api.mul(s2, api.constant(PoseidonConstants.M[8]))); + api.mul(s0, api.constant(params.mAt(2, 0))), + api.mul(s1, api.constant(params.mAt(2, 1)))), + api.mul(s2, api.constant(params.mAt(2, 2)))); s0 = t0; s1 = t1; s2 = t2; } - return s0; // output is state[0] + return s0; + } + + /** Signal-API variant of {@link #hash(CircuitAPI, PoseidonParams, Variable, Variable)}. */ + public static Signal hash(SignalBuilder c, PoseidonParams params, Signal input0, Signal input1) { + Variable result = hash(c.api(), params, input0.variable(), input1.variable()); + return c.wrap(result); } /** - * Poseidon hash using Signal API. + * Poseidon hash using the back-compat default ({@link PoseidonParamsBN254T3#INSTANCE}). + * Prefer the explicit-params overload when targeting BLS12-381 or when + * interop with external Poseidon implementations matters. */ + public static Variable hash(CircuitAPI api, Variable input0, Variable input1) { + return hash(api, PoseidonParamsBN254T3.INSTANCE, input0, input1); + } + + /** Signal-API variant of {@link #hash(CircuitAPI, Variable, Variable)}. */ public static Signal hash(SignalBuilder c, Signal input0, Signal input1) { - Variable result = hash(c.api(), input0.variable(), input1.variable()); - return c.wrap(result); + return hash(c, PoseidonParamsBN254T3.INSTANCE, input0, input1); } /** @@ -100,4 +127,12 @@ private static Variable sbox(CircuitAPI api, Variable x) { Variable x4 = api.mul(x2, x2); return api.mul(x4, x); } + + private static void requireT3Alpha5(PoseidonParams params) { + if (params.t() != 3 || params.alpha() != 5) { + throw new IllegalArgumentException( + "Poseidon gadget supports only t=3, alpha=5 (got t=" + params.t() + + ", alpha=" + params.alpha() + ")"); + } + } } diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonConstants.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonConstants.java index 11d99fa..1734ef0 100644 --- a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonConstants.java +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonConstants.java @@ -5,7 +5,15 @@ /** * Poseidon hash constants for BN254 t=3 (from iden3/circomlibjs). * Generated from circomlibjs/src/poseidon_constants.json. + * + * @deprecated as of ADR-0015; prefer + * {@link com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBN254T3#INSTANCE} + * for BN254 and + * {@link com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3#INSTANCE} + * for BLS12-381. Direct reads of this class are legacy and will be removed in a + * future release. */ +@Deprecated public final class PoseidonConstants { private PoseidonConstants() {} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonN.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonN.java index c5b609c..dd07f51 100644 --- a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonN.java +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/PoseidonN.java @@ -4,63 +4,64 @@ import com.bloxbean.cardano.zeroj.circuit.Signal; import com.bloxbean.cardano.zeroj.circuit.SignalBuilder; import com.bloxbean.cardano.zeroj.circuit.Variable; - -import java.math.BigInteger; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParams; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBN254T3; /** - * Variable-arity Poseidon hash — supports 1 to 5 inputs (t = nInputs + 1). - * - *

      The standard Poseidon(2) uses t=3 (2 inputs + 1 capacity). This implementation - * generalizes to any width by varying the state size and corresponding constants.

      + * Variable-arity Poseidon hash — left-folds pairs through the two-input + * {@link Poseidon} gadget. * - *

      For t=3 (2 inputs), this delegates to the existing optimized {@link Poseidon} with - * pre-loaded constants from circomlibjs. For other arities, it uses a sequential - * approach: hash pairs using the 2-input Poseidon in a left-fold pattern.

      + *

      {@code PoseidonN(a, b, c, d) = Poseidon(Poseidon(Poseidon(a, b), c), d)} * - *

      This matches the practical approach used by most ZK applications: - * {@code PoseidonN(a, b, c, d) = Poseidon(Poseidon(Poseidon(a, b), c), d)}

      + *

      This is the practical approach used by most ZK applications: simple, + * widely compatible, and produces ~330 constraints per pair rather than per + * element. A true variable-width Poseidon would use separate MDS matrices + * and round constants per arity; we do not support that today. * - *

      Note: a true variable-width Poseidon would use different MDS matrices and - * round constants per arity. The folded approach is safe and widely used but - * produces ~330 constraints per pair (not per element).

      + *

      All overloads forward to {@link Poseidon}; see there for param/preset + * selection and interop notes. * - *

      Circom equivalent: {@code Poseidon(nInputs)} from circomlib.

      + *

      Circom equivalent: {@code Poseidon(nInputs)} from circomlib (folded). */ public final class PoseidonN { private PoseidonN() {} /** - * Hash N inputs using Poseidon (folded 2-input approach). + * Hash N inputs under the given {@link PoseidonParams} using folded two-input + * Poseidon. * - * @param api circuit API - * @param inputs 1 to N field elements - * @return hash output + *

      Single-input semantics: {@code PoseidonN(x) == Poseidon(x, 0)}. This + * is a ZeroJ-specific convention (no published Poseidon spec defines a + * 1-arity case). If spec interop with an external 1-input Poseidon is + * required, hash {@code (x, 0)} explicitly. */ - public static Variable hash(CircuitAPI api, Variable... inputs) { + public static Variable hash(CircuitAPI api, PoseidonParams params, Variable... inputs) { if (inputs.length == 0) throw new IllegalArgumentException("inputs must not be empty"); if (inputs.length == 1) { - // Single input: hash with zero - return Poseidon.hash(api, inputs[0], api.constant(0)); - } - if (inputs.length == 2) { - // Optimal: direct 2-input Poseidon - return Poseidon.hash(api, inputs[0], inputs[1]); + return Poseidon.hash(api, params, inputs[0], api.constant(0)); } - // N > 2: left-fold — Poseidon(Poseidon(...Poseidon(in[0], in[1])..., in[n-2]), in[n-1]) - Variable acc = Poseidon.hash(api, inputs[0], inputs[1]); + Variable acc = Poseidon.hash(api, params, inputs[0], inputs[1]); for (int i = 2; i < inputs.length; i++) { - acc = Poseidon.hash(api, acc, inputs[i]); + acc = Poseidon.hash(api, params, acc, inputs[i]); } return acc; } - /** - * Hash N inputs using Poseidon. Signal API wrapper. - */ - public static Signal hash(SignalBuilder c, Signal... inputs) { + /** Signal-API variant. */ + public static Signal hash(SignalBuilder c, PoseidonParams params, Signal... inputs) { Variable[] vars = new Variable[inputs.length]; for (int i = 0; i < inputs.length; i++) vars[i] = inputs[i].variable(); - return c.wrap(hash(c.api(), vars)); + return c.wrap(hash(c.api(), params, vars)); + } + + /** Hash N inputs under the back-compat default ({@link PoseidonParamsBN254T3#INSTANCE}). */ + public static Variable hash(CircuitAPI api, Variable... inputs) { + return hash(api, PoseidonParamsBN254T3.INSTANCE, inputs); + } + + /** Signal-API variant of the back-compat-default hash. */ + public static Signal hash(SignalBuilder c, Signal... inputs) { + return hash(c, PoseidonParamsBN254T3.INSTANCE, inputs); } } diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/SignalPoseidon.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/SignalPoseidon.java index d608315..a68d7a3 100644 --- a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/SignalPoseidon.java +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/SignalPoseidon.java @@ -2,21 +2,27 @@ import com.bloxbean.cardano.zeroj.circuit.Signal; import com.bloxbean.cardano.zeroj.circuit.SignalBuilder; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParams; /** - * Poseidon hash using the Signal API. + * Poseidon hash using the Signal API. Thin forwarder to {@link Poseidon}; see + * there for preset selection and compile-field interop notes. * *

      {@code
      - * Signal hash = SignalPoseidon.hash(c, left, right);
      + * Signal hash = SignalPoseidon.hash(c, params, left, right);
      + * Signal hash = SignalPoseidon.hash(c, left, right);  // BN254 default
        * }
      */ public final class SignalPoseidon { private SignalPoseidon() {} - /** - * Poseidon hash of two signals. - */ + /** Poseidon hash of two signals under the given {@link PoseidonParams}. */ + public static Signal hash(SignalBuilder c, PoseidonParams params, Signal input0, Signal input1) { + return Poseidon.hash(c, params, input0, input1); + } + + /** Poseidon hash of two signals under the back-compat BN254 default. */ public static Signal hash(SignalBuilder c, Signal input0, Signal input1) { return Poseidon.hash(c, input0, input1); } diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/EdDSAJubjub.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/EdDSAJubjub.java new file mode 100644 index 0000000..4ef45f3 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/EdDSAJubjub.java @@ -0,0 +1,157 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonHash; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Off-circuit EdDSA signature scheme over Jubjub, with Poseidon as the + * challenge hash. Used by {@link InCircuitEdDSAJubjub} as the in-circuit + * verifier's cryptographic oracle and by application code for issuing / + * signing / off-chain verification. + * + *

      Scheme

      + * + *

      Key generation

      + * {@code sk ∈ [1, l); pk = [sk]·G} where G is {@link JubjubPoint#SUBGROUP_GENERATOR} + * and {@code l = } {@link JubjubCurve#SUBGROUP_ORDER}. + * + *

      Sign (sk, msg)

      + *
        + *
      1. {@code r = Poseidon(sk, msg) mod l} — deterministic nonce (no RNG).
      2. + *
      3. {@code R = [r]·G}.
      4. + *
      5. {@code k = Poseidon(R.u, R.v, msg) mod l} — challenge.
      6. + *
      7. {@code S = (r + k·sk) mod l}.
      8. + *
      9. Signature = {@code (R, S)}.
      10. + *
      + * + *

      Verify (pk, msg, R, S)

      + *
        + *
      1. Reject if {@code R ∉ subgroup} or {@code pk ∉ subgroup}.
      2. + *
      3. Reject if {@code S ≥ l} (malleability prevention).
      4. + *
      5. {@code k = Poseidon(R.u, R.v, msg) mod l}.
      6. + *
      7. Check {@code [S]·G == R + [k]·pk}.
      8. + *
      + * + *

      Deviation from RFC 8032

      + * RFC 8032 uses SHA-512 for both the nonce derivation and challenge hash, + * and the public key is the output of a hash-to-curve rather than a plain + * scalar-mul. This implementation uses Poseidon so the verify equation can + * be emitted as a small number of constraints inside a BLS12-381 SNARK. The + * resulting scheme is not interoperable with Ed25519 or + * Sapling-EdDSA signatures; it is intended for in-SNARK credential + * verification on Cardano. + */ +public final class EdDSAJubjub { + + private EdDSAJubjub() {} + + /** A keypair: private scalar {@code sk} and public point {@code pk = [sk]·G}. */ + public record Keypair(BigInteger sk, JubjubPoint pk) { + public Keypair { + Objects.requireNonNull(sk, "sk"); + Objects.requireNonNull(pk, "pk"); + } + } + + /** + * A signature: curve point {@code R} and scalar {@code S}. Both are + * required to be canonical forms ({@code R} in the prime-order subgroup, + * {@code S ∈ [0, l)}) for verification to succeed. + */ + public record Signature(JubjubPoint r, BigInteger s) { + public Signature { + Objects.requireNonNull(r, "r"); + Objects.requireNonNull(s, "s"); + } + } + + /** + * Derives a keypair from a given secret scalar. + * + * @param sk secret key scalar; must satisfy {@code 0 < sk < l} + */ + public static Keypair keypairFromSecret(BigInteger sk) { + Objects.requireNonNull(sk, "sk"); + if (sk.signum() <= 0 || sk.compareTo(JubjubCurve.SUBGROUP_ORDER) >= 0) { + throw new IllegalArgumentException( + "Secret key must satisfy 0 < sk < l (= Jubjub subgroup order)"); + } + JubjubPoint pk = JubjubPoint.SUBGROUP_GENERATOR.scalarMul(sk); + return new Keypair(sk, pk); + } + + /** + * Signs a message digest {@code msg} with secret key {@code sk}. + * + *

      The {@code msg} argument is expected to be a pre-hashed field + * element (BLS12-381 scalar field). For byte-oriented messages, hash them + * to a field element via Poseidon or another field-friendly hash + * before calling. + */ + public static Signature sign(BigInteger sk, BigInteger msg) { + Objects.requireNonNull(sk, "sk"); + Objects.requireNonNull(msg, "msg"); + BigInteger l = JubjubCurve.SUBGROUP_ORDER; + JubjubPoint pk = JubjubPoint.SUBGROUP_GENERATOR.scalarMul(sk); + // r = Poseidon(sk, msg) mod l (deterministic; no secure RNG required). + // Note: mod-l over a 255-bit Poseidon output has ~2^-3 bias (p/l ≈ 8). + // For signers that issue many thousands of credentials under one key, + // this bias enables biased-nonce attacks (Bleichenbacher / HNP). For + // single-issuer credential systems with bounded signing volume, the + // bias is practically negligible. If volume grows: widen the hash. + BigInteger r = PoseidonHash.hash(PoseidonParamsBLS12_381T3.INSTANCE, sk, msg).mod(l); + JubjubPoint rPoint = JubjubPoint.SUBGROUP_GENERATOR.scalarMul(r); + // Challenge binds (R, pk, msg) — including pk defends against key- + // substitution / duplicate-signature attacks (standard Ed25519 / Schnorr). + BigInteger k = computeChallenge(rPoint, pk, msg); + // S = (r + k·sk) mod l + BigInteger s = r.add(k.multiply(sk)).mod(l); + return new Signature(rPoint, s); + } + + /** + * Verifies a signature. Returns {@code false} (does not throw) for + * malformed or invalid signatures. + * + * @param pk public key (must be in the Jubjub prime-order subgroup) + * @param msg message field element + * @param sig signature to verify + */ + public static boolean verify(JubjubPoint pk, BigInteger msg, Signature sig) { + Objects.requireNonNull(pk, "pk"); + Objects.requireNonNull(msg, "msg"); + Objects.requireNonNull(sig, "sig"); + BigInteger l = JubjubCurve.SUBGROUP_ORDER; + // 1. Subgroup checks (malleability / small-subgroup defenses). + if (!pk.isInSubgroup()) return false; + if (!sig.r.isInSubgroup()) return false; + // 2. S ∈ [0, l). + if (sig.s.signum() < 0 || sig.s.compareTo(l) >= 0) return false; + // 3. Challenge k = Poseidon(R.u, R.v, pk.u, pk.v, msg) mod l. + BigInteger k = computeChallenge(sig.r, pk, msg); + // 4. [S]·G == R + [k]·pk? + JubjubPoint lhs = JubjubPoint.SUBGROUP_GENERATOR.scalarMul(sig.s); + JubjubPoint rhs = sig.r.add(pk.scalarMul(k)); + return lhs.projectiveEquals(rhs); + } + + /** + * Computes the challenge scalar + * {@code k = Poseidon(R.u, R.v, pk.u, pk.v, msg) mod l}. + * + *

      Including {@code pk} in the challenge is a standard defense against + * key-substitution attacks (a malicious actor cannot claim a valid + * signature pair transfers to a different issuer key). + * + *

      Exposed for in-circuit gadgets that need to compute {@code k} the + * same way sign/verify do. + */ + public static BigInteger computeChallenge(JubjubPoint r, JubjubPoint pk, BigInteger msg) { + return PoseidonHash.hashN(PoseidonParamsBLS12_381T3.INSTANCE, + r.affineU(), r.affineV(), pk.affineU(), pk.affineV(), msg) + .mod(JubjubCurve.SUBGROUP_ORDER); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitEdDSAJubjub.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitEdDSAJubjub.java new file mode 100644 index 0000000..0aaa822 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitEdDSAJubjub.java @@ -0,0 +1,129 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import com.bloxbean.cardano.zeroj.circuit.CircuitAPI; +import com.bloxbean.cardano.zeroj.circuit.Variable; +import com.bloxbean.cardano.zeroj.circuit.lib.Comparators; +import com.bloxbean.cardano.zeroj.circuit.lib.PoseidonN; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3; + +/** + * In-circuit EdDSA-Jubjub verification gadget, matching the off-circuit + * {@link EdDSAJubjub} scheme. + * + *

      Use case: a credential holder proves "I have a signature {@code sig} + * from issuer {@code pk} over message {@code msg}", without revealing the + * signature itself. Inside the SNARK, the gadget emits the constraints + * {@code [S]·G == R + [k]·pk} where {@code k = Poseidon(R.u, R.v, msg) mod l}, + * plus range / malleability checks. + * + *

      Security checklist

      + * Enforced by this gadget: + *
        + *
      1. {@code S < l} — malleability prevention (rejects the {@code S + l} alias).
      2. + *
      3. Challenge {@code k} recomputed in-circuit via Poseidon.
      4. + *
      5. Verification equation {@code [S]·G == R + [k]·pk}.
      6. + *
      + * + *

      IMPORTANT — subgroup-check contract: this gadget does not + * perform in-circuit subgroup checks on {@code pk} or {@code R} (doing so + * would add ~5000 constraints per check — the cost of an {@code [l]·P} + * scalar-mul). Both points must therefore come from a trusted source: + *

        + *
      • {@code pk}: check {@link JubjubPoint#isInSubgroup} off-circuit + * at issuer-registration time. Afterwards treat pk as a trusted + * circuit constant or public input.
      • + *
      • {@code R}: comes from the issued signature. An attacker who controls + * the signer can substitute a small-order R and fabricate a matching + * S. For credential schemes where the issuer is trusted, this is + * acceptable. For adversarial-signer protocols, add an explicit + * subgroup-check gadget around this verify (see {@link #verify}).
      • + *
      + */ +public final class InCircuitEdDSAJubjub { + + private InCircuitEdDSAJubjub() {} + + /** + * Asserts that {@code (R, S)} is a valid EdDSA-Jubjub signature of + * {@code msg} under public key {@code pk}. Throws a witness-evaluation + * error if not. + * + *

      The {@code scalarBits} parameter is the width in bits used for + * fixed/variable-base scalar-mul. For Jubjub use {@code 252}. + * + * @param api circuit API + * @param pk issuer public key (asserted in-subgroup by caller) + * @param msg message digest as a field element + * @param rPoint signature component R (asserted in-subgroup by caller) + * @param s signature scalar S (asserted {@code < l} by this gadget) + * @param scalarBits bits for scalar-mul (typically 252) + */ + public static void verify(CircuitAPI api, + InCircuitJubjub.Point pk, + Variable msg, + InCircuitJubjub.Point rPoint, + Variable s, + Variable kModL, + Variable kQuotient) { + api.requireField(PoseidonParamsBLS12_381T3.INSTANCE.field()); + + Variable lConstant = api.constant(JubjubCurve.SUBGROUP_ORDER); + + // 1. Enforce S < l (malleability prevention). + Variable sLtL = Comparators.lessThan(api, s, lConstant, 252); + api.assertEqual(sLtL, api.constant(1)); + + // 2. Recompute k_raw = Poseidon(R.u, R.v, pk.u, pk.v, msg) and assert + // it equals kQuotient · l + kModL. Including pk in the challenge + // prevents key-substitution attacks; matching the 5-input Poseidon + // of the off-circuit EdDSAJubjub.computeChallenge. + Variable kRaw = PoseidonN.hash(api, PoseidonParamsBLS12_381T3.INSTANCE, + rPoint.u(), rPoint.v(), pk.u(), pk.v(), msg); + Variable reconstructed = api.add(api.mul(kQuotient, lConstant), kModL); + api.assertEqual(kRaw, reconstructed); + + // 3. Range checks: + // kModL < l (canonical reduction); + // kQuotient < 16 (4 bits). p / l ≈ 8.000028, so kRaw ∈ [0, p) admits + // q ∈ [0, 9] in the worst case; 4 bits (q < 16) gives safe headroom. + Variable kLtL = Comparators.lessThan(api, kModL, lConstant, 252); + api.assertEqual(kLtL, api.constant(1)); + api.toBinary(kQuotient, 4); // asserts kQuotient ∈ [0, 2^4) + + // 4. [S]·G (252 bits) and [kModL]·pk (252 bits). + InCircuitJubjub.Point sG = InCircuitJubjub.scalarMulFixedBase( + api, JubjubPoint.SUBGROUP_GENERATOR, s, 252); + InCircuitJubjub.Point kPk = InCircuitJubjub.scalarMulVariableBase( + api, pk, kModL, 252); + + // 5. R + [k]·pk + InCircuitJubjub.Point rPlusKPk = InCircuitJubjub.add(api, rPoint, kPk); + + // 6. Assert [S]·G == R + [k]·pk (projective equality). + api.assertEqual(api.mul(sG.u(), rPlusKPk.z()), api.mul(rPlusKPk.u(), sG.z())); + api.assertEqual(api.mul(sG.v(), rPlusKPk.z()), api.mul(rPlusKPk.v(), sG.z())); + } + + /** + * Witness-helper result from {@link #witnessComputeKReduction}: + * {@code kModL} is the challenge scalar (≡ {@code Poseidon(...) mod l}); + * {@code kQuotient} is the integer quotient needed by the in-circuit + * consistency assertion. + */ + public record KReduction(java.math.BigInteger kModL, java.math.BigInteger kQuotient) {} + + /** + * Helper for callers: computes the {@link KReduction} witnesses that + * {@link #verify} requires as secret inputs. {@code kQuotient} is + * guaranteed to be in {@code [0, 16)}; {@code kModL} is in {@code [0, l)}. + */ + public static KReduction witnessComputeKReduction( + JubjubPoint rPoint, JubjubPoint pk, java.math.BigInteger msg) { + java.math.BigInteger kRaw = com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonHash + .hashN(PoseidonParamsBLS12_381T3.INSTANCE, + rPoint.affineU(), rPoint.affineV(), + pk.affineU(), pk.affineV(), msg); + java.math.BigInteger[] qr = kRaw.divideAndRemainder(JubjubCurve.SUBGROUP_ORDER); + return new KReduction(qr[1], qr[0]); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitJubjub.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitJubjub.java new file mode 100644 index 0000000..4b9eef0 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitJubjub.java @@ -0,0 +1,276 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import com.bloxbean.cardano.zeroj.circuit.CircuitAPI; +import com.bloxbean.cardano.zeroj.circuit.Variable; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3; + +import java.math.BigInteger; + +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.A; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.TWO_D; + +/** + * In-circuit Jubjub gadgets. Each method takes a {@link CircuitAPI}, + * wires for the relevant point(s), and emits the constraints implementing + * the operation. + * + *

      Point representation inside the circuit

      + * A Jubjub point is four {@link Variable}s: {@code U}, {@code V}, + * {@code Z}, {@code T} — extended twisted Edwards coordinates with + * affine {@code (u, v) = (U/Z, V/Z)} and invariant {@code T = U·V/Z}. + * The {@link Point} record bundles them. + * + *

      Field binding

      + * All gadgets call {@code api.requireField(PoseidonParamsBLS12_381T3 + * .INSTANCE.field())} so that a subsequent compile or witness calculation + * for a non-BLS12-381 curve will fail at compile time. Jubjub is only + * meaningful over BLS12-381 scalar field; pairing with BN254 would produce + * a syntactically valid but cryptographically nonsense circuit. + * + *

      Security caveats

      + *
        + *
      • Input points from untrusted sources must be subgroup-checked + * before being passed to these gadgets. There is no implicit + * subgroup check here; adding one per operation would be + * unacceptably expensive.
      • + *
      • Scalar inputs used with {@link #scalarMulFixedBase} must be bit- + * decomposed with range-check (use {@link CircuitAPI#toBinary} + * on the scalar wire first).
      • + *
      • These gadgets do not enforce the scalar is reduced modulo + * {@link JubjubCurve#SUBGROUP_ORDER}. For EdDSA verification this + * must be enforced at the higher layer.
      • + *
      + */ +public final class InCircuitJubjub { + + private InCircuitJubjub() {} + + /** + * A Jubjub point as four circuit wires in extended-coordinate form. + */ + public record Point(Variable u, Variable v, Variable z, Variable t) {} + + /** + * Wraps an off-circuit {@link JubjubPoint} as four circuit constants. + * Useful for building fixed-base scalar-mul tables at compile time. + */ + public static Point constant(CircuitAPI api, JubjubPoint p) { + api.requireField(PoseidonParamsBLS12_381T3.INSTANCE.field()); + // Normalize to Z = 1 so constants have canonical form (affine lifted). + BigInteger uAff = p.affineU(); + BigInteger vAff = p.affineV(); + BigInteger tAff = uAff.multiply(vAff).mod(JubjubCurve.BASE_FIELD_PRIME); + return new Point(api.constant(uAff), api.constant(vAff), + api.constant(BigInteger.ONE), api.constant(tAff)); + } + + /** + * Unified twisted-Edwards addition {@code P + Q} per HWCD §3.2 for + * {@code a = -1}. Complete for Jubjub (no exceptional inputs). + * + *

      Cost: 9 constraint-level multiplications (some are + * constant-scalar mults which may fold into linear combinations + * depending on the backend; the R1CS compiler will emit ~9 + * multiplication gates). + */ + public static Point add(CircuitAPI api, Point p, Point q) { + api.requireField(PoseidonParamsBLS12_381T3.INSTANCE.field()); + // A = (V1 - U1)·(V2 - U2) + Variable vMinusU1 = api.sub(p.v, p.u); + Variable vMinusU2 = api.sub(q.v, q.u); + Variable aVal = api.mul(vMinusU1, vMinusU2); + + // B = (V1 + U1)·(V2 + U2) + Variable vPlusU1 = api.add(p.v, p.u); + Variable vPlusU2 = api.add(q.v, q.u); + Variable bVal = api.mul(vPlusU1, vPlusU2); + + // C = T1 · 2d · T2 + Variable t1Times2d = api.mul(p.t, api.constant(TWO_D)); + Variable cVal = api.mul(t1Times2d, q.t); + + // D = Z1 · 2 · Z2 + Variable z1Times2 = api.mul(p.z, api.constant(BigInteger.TWO)); + Variable dVal = api.mul(z1Times2, q.z); + + // Linear combinations + Variable eVal = api.sub(bVal, aVal); + Variable fVal = api.sub(dVal, cVal); + Variable gVal = api.add(dVal, cVal); + Variable hVal = api.add(bVal, aVal); + + // Output: U3 = E·F, V3 = G·H, T3 = E·H, Z3 = F·G + return new Point( + api.mul(eVal, fVal), + api.mul(gVal, hVal), + api.mul(fVal, gVal), + api.mul(eVal, hVal)); + } + + /** + * Twisted-Edwards doubling {@code 2·P} per HWCD §3.3 dedicated + * formula for {@code a = -1}. + */ + public static Point doubled(CircuitAPI api, Point p) { + api.requireField(PoseidonParamsBLS12_381T3.INSTANCE.field()); + // A = U^2 + Variable aVal = api.mul(p.u, p.u); + // B = V^2 + Variable bVal = api.mul(p.v, p.v); + // C = 2·Z^2 + Variable zSquared = api.mul(p.z, p.z); + Variable cVal = api.mul(zSquared, api.constant(BigInteger.TWO)); + // D = a·A = -A + Variable dVal = api.mul(aVal, api.constant(A)); + // E = (U + V)^2 - A - B + Variable uPlusV = api.add(p.u, p.v); + Variable uPlusVSquared = api.mul(uPlusV, uPlusV); + Variable eVal = api.sub(api.sub(uPlusVSquared, aVal), bVal); + // G = D + B + Variable gVal = api.add(dVal, bVal); + // F = G - C + Variable fVal = api.sub(gVal, cVal); + // H = D - B + Variable hVal = api.sub(dVal, bVal); + return new Point( + api.mul(eVal, fVal), + api.mul(gVal, hVal), + api.mul(fVal, gVal), + api.mul(eVal, hVal)); + } + + /** + * Conditional selection between two points. Returns {@code cond ? ifTrue : ifFalse} + * coordinate-wise. {@code cond} must be asserted boolean by the caller. + */ + public static Point select(CircuitAPI api, Variable cond, Point ifTrue, Point ifFalse) { + return new Point( + api.select(cond, ifTrue.u, ifFalse.u), + api.select(cond, ifTrue.v, ifFalse.v), + api.select(cond, ifTrue.z, ifFalse.z), + api.select(cond, ifTrue.t, ifFalse.t)); + } + + /** + * Conditional addition: {@code result = cond ? (acc + p) : acc}. + * {@code cond} must be asserted boolean. + */ + public static Point conditionalAdd(CircuitAPI api, Variable cond, Point acc, Point p) { + Point sum = add(api, acc, p); + return select(api, cond, sum, acc); + } + + /** + * In-circuit identity point {@code (0, 1, 1, 0)}. + */ + public static Point identity(CircuitAPI api) { + return new Point(api.constant(0), api.constant(1), api.constant(1), api.constant(0)); + } + + /** + * Fixed-base scalar multiplication {@code [k]·G} where {@code G} is + * an off-circuit point baked in at compile time. Uses a simple + * double-and-add over the bit decomposition of {@code k}. + * + *

      Cost: {@code numBits} conditional additions against pre-doubled + * copies of the base (precomputed at compile time). Each addition is + * ~8 multiplications plus 4 muxes; a 255-bit scalar-mul costs + * roughly 2500 constraints. + * + * @param api circuit API + * @param basePoint fixed off-circuit base point (e.g. Jubjub generator) + * @param scalarBits scalar as an LSB-first array of boolean variables + * (each must be asserted 0/1 by the caller) + * @return in-circuit {@code [k]·basePoint} in extended coords + */ + public static Point scalarMulFixedBase(CircuitAPI api, JubjubPoint basePoint, Variable[] scalarBits) { + api.requireField(PoseidonParamsBLS12_381T3.INSTANCE.field()); + if (scalarBits == null || scalarBits.length == 0) { + throw new IllegalArgumentException("scalarBits must not be empty"); + } + if (scalarBits.length > 255) { + throw new IllegalArgumentException( + "scalarBits.length must be at most 255; got " + scalarBits.length + + " (Jubjub scalar field is 252-bit; 255 tolerates slight overprovisioning)"); + } + // Precompute table[i] = [2^i] · basePoint off-circuit. + JubjubPoint[] table = new JubjubPoint[scalarBits.length]; + table[0] = basePoint; + for (int i = 1; i < scalarBits.length; i++) { + table[i] = table[i - 1].doubled(); + } + + Point acc = identity(api); + for (int i = 0; i < scalarBits.length; i++) { + Point addend = constant(api, table[i]); + acc = conditionalAdd(api, scalarBits[i], acc, addend); + } + return acc; + } + + /** + * Overload accepting the scalar as a single {@link Variable}; internally + * bit-decomposes to {@code numBits} bits (LSB-first, each bit asserted + * boolean). For a 252-bit Jubjub scalar pass {@code numBits = 252}. + */ + public static Point scalarMulFixedBase(CircuitAPI api, JubjubPoint basePoint, + Variable scalar, int numBits) { + Variable[] bits = api.toBinary(scalar, numBits); + return scalarMulFixedBase(api, basePoint, bits); + } + + /** + * Variable-base scalar multiplication {@code [k]·P} where {@code P} is + * an in-circuit point (not known at compile time). Uses double-and-add + * over {@code scalarBits} LSB-first. + * + *

      Cost per bit: one in-circuit doubling (~7 muls) + one conditional + * addition (~9 muls + 4 mux). For a 252-bit scalar this is roughly + * {@code 252 × 20 ≈ 5000 constraints} — about 3× more expensive than + * the fixed-base variant because in-circuit doublings are required each + * iteration. + * + *

      Caveats: + *

        + *
      • {@code base} must be in the prime-order subgroup; call + * {@code isInSubgroup()} off-circuit on every untrusted point + * before passing it into a witness.
      • + *
      • This does not enforce that the scalar is reduced modulo + * {@link JubjubCurve#SUBGROUP_ORDER}. For protocols that depend + * on that reduction (EdDSA), the caller must range-check the + * scalar separately.
      • + *
      + * + * @param api circuit API + * @param base in-circuit base point (extended coords) + * @param scalarBits scalar bits LSB-first (each asserted boolean by caller) + */ + public static Point scalarMulVariableBase(CircuitAPI api, Point base, Variable[] scalarBits) { + api.requireField(PoseidonParamsBLS12_381T3.INSTANCE.field()); + if (scalarBits == null || scalarBits.length == 0) { + throw new IllegalArgumentException("scalarBits must not be empty"); + } + if (scalarBits.length > 255) { + throw new IllegalArgumentException( + "scalarBits.length must be at most 255; got " + scalarBits.length); + } + Point acc = identity(api); + Point doubledBase = base; + for (int i = 0; i < scalarBits.length; i++) { + acc = conditionalAdd(api, scalarBits[i], acc, doubledBase); + if (i < scalarBits.length - 1) { + doubledBase = doubled(api, doubledBase); + } + } + return acc; + } + + /** + * Overload that bit-decomposes the scalar {@link Variable} first. + */ + public static Point scalarMulVariableBase(CircuitAPI api, Point base, + Variable scalar, int numBits) { + Variable[] bits = api.toBinary(scalar, numBits); + return scalarMulVariableBase(api, base, bits); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitPedersen.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitPedersen.java new file mode 100644 index 0000000..e670902 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/InCircuitPedersen.java @@ -0,0 +1,73 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import com.bloxbean.cardano.zeroj.circuit.CircuitAPI; +import com.bloxbean.cardano.zeroj.circuit.Variable; + +/** + * In-circuit Pedersen commitment gadgets. + * + *

      {@code Commit(v, r) = [v]·G + [r]·H} where G is the Jubjub subgroup + * generator and H is {@link PedersenCommitment#H}. Inside a SNARK, proves + * that a committed value and blinding scalar produce a particular committed + * point, without revealing either. + * + *

      Use cases

      + *
        + *
      • Confidential amounts: commit to transaction values; prove sum + * preservation via commitment homomorphism.
      • + *
      • Private voting: commit to vote, prove it is 0 or 1, homomorphically + * tally.
      • + *
      • Range proofs: Bulletproofs-style (out of M4 scope).
      • + *
      + * + *

      Performance

      + * One {@link #commit} call emits two fixed-base scalar-muls (~2·5000 = + * 10000 constraints at 252-bit scalars). This is the dominant cost in any + * Pedersen-heavy circuit; pre-commit wherever possible. + */ +public final class InCircuitPedersen { + + private InCircuitPedersen() {} + + /** + * Computes the Pedersen commitment {@code [v]·G + [r]·H} where G and H + * are Jubjub subgroup bases (G = {@link JubjubPoint#SUBGROUP_GENERATOR}, + * H = {@link PedersenCommitment#H}). + * + * @param api circuit API + * @param valueBits {@code v} as LSB-first boolean wires (each caller- + * asserted-boolean); length ≤ 252 + * @param blindBits {@code r} as LSB-first boolean wires; length ≤ 252 + * @return the commitment point in extended coords + */ + public static InCircuitJubjub.Point commit(CircuitAPI api, + Variable[] valueBits, + Variable[] blindBits) { + InCircuitJubjub.Point vG = InCircuitJubjub.scalarMulFixedBase( + api, JubjubPoint.SUBGROUP_GENERATOR, valueBits); + InCircuitJubjub.Point rH = InCircuitJubjub.scalarMulFixedBase( + api, PedersenCommitment.H, blindBits); + return InCircuitJubjub.add(api, vG, rH); + } + + /** + * Scalar-input overload: bit-decomposes {@code value} and {@code blinding} + * to {@code numBits} bits each, then commits. + * + *

      {@code numBits} is shared between both scalars — for Jubjub, + * {@code 252} is the natural choice. + */ + public static InCircuitJubjub.Point commit(CircuitAPI api, + Variable value, + Variable blinding, + int numBits) { + if (numBits <= 0 || numBits > 252) { + throw new IllegalArgumentException( + "numBits must be in (0, 252]; got " + numBits + + ". Jubjub scalars live in [0, l) where l is 252 bits."); + } + Variable[] valueBits = api.toBinary(value, numBits); + Variable[] blindBits = api.toBinary(blinding, numBits); + return commit(api, valueBits, blindBits); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/JubjubCurve.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/JubjubCurve.java new file mode 100644 index 0000000..4cd4751 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/JubjubCurve.java @@ -0,0 +1,93 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import java.math.BigInteger; + +/** + * Jubjub curve constants — twisted Edwards elliptic curve embedded in the + * BLS12-381 scalar field. Pinned to the Zcash / zkcrypto Jubjub parameters + * (see zkcrypto/jubjub). + * + *

      Naming convention

      + *
        + *
      • {@code Fq} — Jubjub base field (where point coordinates live) = + * BLS12-381 scalar field (prime {@link #BASE_FIELD_PRIME}).
      • + *
      • {@code Fr} — Jubjub scalar field (where scalar multipliers live) = + * prime-order subgroup modulus {@link #SUBGROUP_ORDER}.
      • + *
      + * + *

      Curve equation

      + * Twisted Edwards form: {@code -u² + v² = 1 + d·u²·v²}, with {@code a = -1}. + * Both {@code a = -1} (which is a square in {@code Fq}) and {@code d} + * (which is a non-square) are chosen so the unified addition formula + * (Hisil-Wong-Carter-Dawson 2008) is complete — no exceptional inputs. + * + *

      Full group vs prime-order subgroup

      + * The full Jubjub curve has order {@code 8 · l}. For cryptographic use + * (signatures, commitments, ZK circuits), points must live in the + * prime-order subgroup of order {@code l}. A point is in the subgroup iff + * {@code [l] · P == 0}. {@link #SUBGROUP_GENERATOR} is a fixed generator + * of that subgroup; it equals {@code [8] · FULL_GENERATOR}. + */ +public final class JubjubCurve { + + private JubjubCurve() {} + + /** + * Jubjub base field prime = BLS12-381 scalar field prime. + * {@code 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001} + */ + public static final BigInteger BASE_FIELD_PRIME = new BigInteger( + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16); + + /** + * Jubjub {@code a} parameter: {@code -1} (mod {@link #BASE_FIELD_PRIME}). + */ + public static final BigInteger A = BASE_FIELD_PRIME.subtract(BigInteger.ONE); + + /** + * Jubjub {@code d} parameter (Zcash/zkcrypto canonical value). + * {@code 0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1} + */ + public static final BigInteger D = new BigInteger( + "2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1", 16); + + /** {@code 2 · d} — pre-computed because the unified Edwards formula uses it. */ + public static final BigInteger TWO_D = D.shiftLeft(1).mod(BASE_FIELD_PRIME); + + /** + * Prime order of the Jubjub subgroup (= Jubjub {@code Fr} modulus, + * 252 bits). Matches zkcrypto/jubjub {@code FR_MODULUS_BYTES} byte + * for byte. + * {@code 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7} + */ + public static final BigInteger SUBGROUP_ORDER = new BigInteger( + "0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7", 16); + + /** Cofactor: {@code |E(Fq)| = 8 · SUBGROUP_ORDER}. */ + public static final int COFACTOR = 8; + + /** + * {@code FULL_GENERATOR} — a generator of the full curve group (order + * {@code 8 · l}). Not safe for cryptographic use without first + * cofactor-clearing (i.e. multiply by 8). Exposed for + * {@link #SUBGROUP_GENERATOR} derivation and for interop with zkcrypto + * test vectors. + */ + public static final BigInteger FULL_GENERATOR_U = new BigInteger( + "62edcbb8bf3787c88b0f03ddd60a8187caf55d1b29bf81afe4b3d35df1a7adfe", 16); + + /** {@code FULL_GENERATOR}'s v-coordinate ({@code 11}). */ + public static final BigInteger FULL_GENERATOR_V = BigInteger.valueOf(11); + + // The subgroup generator itself (as a JubjubPoint) lives on JubjubPoint to + // avoid a class-init ordering issue (computing [8] · FULL_GENERATOR needs + // JubjubPoint's methods). Access via {@link JubjubPoint#SUBGROUP_GENERATOR}. + + /** + * Compressed-point encoding length: 32 bytes (256 bits). + * Layout: little-endian {@code v}-coordinate in the low 255 bits; + * top bit encodes the sign of {@code u} (1 iff {@code u} is odd when + * represented as the least non-negative residue). + */ + public static final int COMPRESSED_POINT_BYTES = 32; +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/JubjubPoint.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/JubjubPoint.java new file mode 100644 index 0000000..3001622 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/JubjubPoint.java @@ -0,0 +1,415 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.A; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.BASE_FIELD_PRIME; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.COFACTOR; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.COMPRESSED_POINT_BYTES; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.D; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.FULL_GENERATOR_U; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.FULL_GENERATOR_V; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.SUBGROUP_ORDER; +import static com.bloxbean.cardano.zeroj.circuit.lib.jubjub.JubjubCurve.TWO_D; + +/** + * A Jubjub point in extended twisted-Edwards coordinates + * {@code (U, V, Z, T)} with affine {@code (u, v) = (U/Z, V/Z)} and + * {@code T = U·V/Z}. + * + *

      All arithmetic is done modulo {@link JubjubCurve#BASE_FIELD_PRIME}. + * Operations use the unified add / dedicated doubling formulas from + * Hisil–Wong–Carter–Dawson 2008 — complete for {@code a = -1} (Jubjub's + * choice) and non-square {@code d}. + * + *

      Subgroup safety

      + * Jubjub has cofactor 8. The full group {@code E(Fq)} contains points + * outside the prime-order subgroup; these points enable small-subgroup + * attacks in cryptographic protocols. Untrusted points must pass + * {@link #isInSubgroup} before use. The {@link #SUBGROUP_GENERATOR} + * constant and any point produced by {@link #mulByCofactor} are + * subgroup-safe by construction. + * + *

      Immutability

      + * Instances are immutable. Arithmetic returns new instances. + */ +public final class JubjubPoint { + + // ---------- Extended coordinates ---------- + private final BigInteger u; + private final BigInteger v; + private final BigInteger z; + private final BigInteger t; + + /** Identity element: {@code (0, 1, 1, 0)} in extended coords, = affine {@code (0, 1)}. */ + public static final JubjubPoint IDENTITY = new JubjubPoint( + BigInteger.ZERO, BigInteger.ONE, BigInteger.ONE, BigInteger.ZERO); + + /** + * Full curve generator (order {@code 8·l}). For cryptographic use prefer + * {@link #SUBGROUP_GENERATOR}, which is cofactor-cleared. + */ + public static final JubjubPoint FULL_GENERATOR = fromAffine(FULL_GENERATOR_U, FULL_GENERATOR_V); + + /** + * Prime-order subgroup generator = {@code [8] · FULL_GENERATOR}. Safe + * for EdDSA, Pedersen, and all downstream gadgets. + */ + public static final JubjubPoint SUBGROUP_GENERATOR = FULL_GENERATOR.mulByCofactor(); + + private JubjubPoint(BigInteger u, BigInteger v, BigInteger z, BigInteger t) { + this.u = u; + this.v = v; + this.z = z; + this.t = t; + } + + // ---------- Construction ---------- + + /** + * Builds a point from affine {@code (u, v)} coordinates. Throws if the + * point is not on the curve. + * + * @param u affine u-coordinate + * @param v affine v-coordinate + * @return extended-coord representation + * @throws IllegalArgumentException if {@code (u, v)} is not on the curve + */ + public static JubjubPoint fromAffine(BigInteger u, BigInteger v) { + BigInteger uRed = reduce(u); + BigInteger vRed = reduce(v); + if (!isOnCurveAffine(uRed, vRed)) { + throw new IllegalArgumentException( + "Point (" + uRed.toString(16) + ", " + vRed.toString(16) + ") is not on the Jubjub curve"); + } + BigInteger tRed = uRed.multiply(vRed).mod(BASE_FIELD_PRIME); + return new JubjubPoint(uRed, vRed, BigInteger.ONE, tRed); + } + + private static boolean isOnCurveAffine(BigInteger u, BigInteger v) { + // -u^2 + v^2 == 1 + d·u^2·v^2 + BigInteger uu = u.multiply(u).mod(BASE_FIELD_PRIME); + BigInteger vv = v.multiply(v).mod(BASE_FIELD_PRIME); + BigInteger lhs = vv.subtract(uu).mod(BASE_FIELD_PRIME); + BigInteger rhs = BigInteger.ONE.add(D.multiply(uu).multiply(vv)).mod(BASE_FIELD_PRIME); + return lhs.equals(rhs); + } + + // ---------- Accessors ---------- + + /** Extended coord {@code U}. */ + public BigInteger u() { return u; } + /** Extended coord {@code V}. */ + public BigInteger v() { return v; } + /** Extended coord {@code Z}. */ + public BigInteger z() { return z; } + /** Extended coord {@code T = U·V/Z}. */ + public BigInteger t() { return t; } + + /** Affine u-coordinate = {@code U/Z (mod p)}. */ + public BigInteger affineU() { + return u.multiply(z.modInverse(BASE_FIELD_PRIME)).mod(BASE_FIELD_PRIME); + } + + /** Affine v-coordinate = {@code V/Z (mod p)}. */ + public BigInteger affineV() { + return v.multiply(z.modInverse(BASE_FIELD_PRIME)).mod(BASE_FIELD_PRIME); + } + + /** {@code true} iff this point is the identity {@code (0, 1)}. */ + public boolean isIdentity() { + // In extended coords, identity has U = 0 and V = Z (so affineV = 1). + return u.signum() == 0 && v.equals(z); + } + + // ---------- Arithmetic ---------- + + /** + * Point addition per Hisil–Wong–Carter–Dawson §3.2 (unified formula, + * complete for {@code a = -1} and non-square {@code d}). + */ + public JubjubPoint add(JubjubPoint other) { + Objects.requireNonNull(other, "other"); + // HWCD §3.2 unified, a=-1. Locals named rA..rH to avoid shadowing + // the static imports JubjubCurve.A / D. + BigInteger rA = v.subtract(u).multiply(other.v.subtract(other.u)).mod(BASE_FIELD_PRIME); + BigInteger rB = v.add(u).multiply(other.v.add(other.u)).mod(BASE_FIELD_PRIME); + BigInteger rC = t.multiply(TWO_D).multiply(other.t).mod(BASE_FIELD_PRIME); + BigInteger rD = z.multiply(BigInteger.TWO).multiply(other.z).mod(BASE_FIELD_PRIME); + BigInteger rE = rB.subtract(rA).mod(BASE_FIELD_PRIME); + BigInteger rF = rD.subtract(rC).mod(BASE_FIELD_PRIME); + BigInteger rG = rD.add(rC).mod(BASE_FIELD_PRIME); + BigInteger rH = rB.add(rA).mod(BASE_FIELD_PRIME); + return new JubjubPoint( + rE.multiply(rF).mod(BASE_FIELD_PRIME), + rG.multiply(rH).mod(BASE_FIELD_PRIME), + rF.multiply(rG).mod(BASE_FIELD_PRIME), + rE.multiply(rH).mod(BASE_FIELD_PRIME)); + } + + /** + * Dedicated doubling formula per HWCD §3.3 for twisted Edwards with + * {@code a = -1}. Slightly cheaper than {@code add(this)}. + */ + public JubjubPoint doubled() { + // HWCD §3.3 dedicated doubling, a=-1. + BigInteger rA = u.multiply(u).mod(BASE_FIELD_PRIME); // A = U^2 + BigInteger rB = v.multiply(v).mod(BASE_FIELD_PRIME); // B = V^2 + BigInteger rC = z.multiply(z).shiftLeft(1).mod(BASE_FIELD_PRIME); // C = 2·Z^2 + BigInteger rD = A.multiply(rA).mod(BASE_FIELD_PRIME); // D = a·A = -A + BigInteger sum = u.add(v); + BigInteger rE = sum.multiply(sum) // E = (U+V)^2 - A - B + .subtract(rA).subtract(rB).mod(BASE_FIELD_PRIME); + BigInteger rG = rD.add(rB).mod(BASE_FIELD_PRIME); // G = D + B + BigInteger rF = rG.subtract(rC).mod(BASE_FIELD_PRIME); // F = G - C + BigInteger rH = rD.subtract(rB).mod(BASE_FIELD_PRIME); // H = D - B + return new JubjubPoint( + rE.multiply(rF).mod(BASE_FIELD_PRIME), + rG.multiply(rH).mod(BASE_FIELD_PRIME), + rF.multiply(rG).mod(BASE_FIELD_PRIME), + rE.multiply(rH).mod(BASE_FIELD_PRIME)); + } + + /** Point negation: {@code -P = (-U, V, Z, -T)}. */ + public JubjubPoint negate() { + return new JubjubPoint( + u.negate().mod(BASE_FIELD_PRIME), + v, + z, + t.negate().mod(BASE_FIELD_PRIME)); + } + + /** + * Scalar multiplication {@code [k] · P} via simple double-and-add. + * + *

      Does not pre-reduce {@code k} by {@link JubjubCurve#SUBGROUP_ORDER}. + * Pre-reduction is only valid when {@code P} is in the prime-order + * subgroup — for arbitrary curve points (e.g. inside a subgroup check + * that multiplies {@code FULL_GENERATOR} by {@code l}), reduction would + * silently produce the identity and mask a subgroup-membership failure. + * + *

      Negative {@code k} is handled by negating the result of + * {@code [|k|] · P}. + * + *

      Not constant-time — caller is responsible for side-channel + * considerations if applicable. + */ + public JubjubPoint scalarMul(BigInteger k) { + Objects.requireNonNull(k, "k"); + if (k.signum() == 0) return IDENTITY; + boolean negate = k.signum() < 0; + BigInteger scalar = negate ? k.negate() : k; + JubjubPoint result = IDENTITY; + JubjubPoint base = this; + int bits = scalar.bitLength(); + for (int i = 0; i < bits; i++) { + if (scalar.testBit(i)) { + result = result.add(base); + } + base = base.doubled(); + } + return negate ? result.negate() : result; + } + + /** Cofactor-clear: returns {@code [8] · P}, guaranteed to be in the prime-order subgroup. */ + public JubjubPoint mulByCofactor() { + // 8 = 2^3, so three doublings. + JubjubPoint r = this.doubled(); + r = r.doubled(); + r = r.doubled(); + return r; + } + + /** + * Prime-order subgroup membership check: {@code [l] · P == O}. + * + *

      Cryptographic protocols must reject untrusted points that fail + * this — they may lie in the 8-element kernel and bypass group- + * theoretic security assumptions. + */ + public boolean isInSubgroup() { + return scalarMul(SUBGROUP_ORDER).isIdentity(); + } + + // ---------- Equality ---------- + + /** + * Projective equality: compares affine coordinates after normalization. + * Two extended representations can have different {@code (U, V, Z, T)} + * but the same underlying affine point. + */ + public boolean projectiveEquals(JubjubPoint other) { + if (other == null) return false; + // U1·Z2 == U2·Z1 && V1·Z2 == V2·Z1 + BigInteger left1 = u.multiply(other.z).mod(BASE_FIELD_PRIME); + BigInteger right1 = other.u.multiply(z).mod(BASE_FIELD_PRIME); + if (!left1.equals(right1)) return false; + BigInteger left2 = v.multiply(other.z).mod(BASE_FIELD_PRIME); + BigInteger right2 = other.v.multiply(z).mod(BASE_FIELD_PRIME); + return left2.equals(right2); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof JubjubPoint p)) return false; + return projectiveEquals(p); + } + + @Override + public int hashCode() { + // Hash affine coordinates so equal points get equal hashes. + return Objects.hash(affineU(), affineV()); + } + + @Override + public String toString() { + return "JubjubPoint{u=" + affineU().toString(16) + ", v=" + affineV().toString(16) + "}"; + } + + // ---------- Encoding ---------- + + /** + * Compressed 32-byte encoding per Zcash/zkcrypto convention: + * 255 bits for {@code v} (little-endian) + top bit = sign of {@code u} + * (1 iff {@code u} is odd when encoded as the least non-negative + * residue in {@code [0, p)}). + */ + public byte[] toBytes() { + BigInteger uAff = affineU(); + BigInteger vAff = affineV(); + byte[] out = toFixedLE(vAff, COMPRESSED_POINT_BYTES); + if (uAff.testBit(0)) { + out[COMPRESSED_POINT_BYTES - 1] = (byte) (out[COMPRESSED_POINT_BYTES - 1] | 0x80); + } + return out; + } + + /** + * Inverse of {@link #toBytes}. Rejects if the decoded point is not on + * the curve. Does not check subgroup membership — call + * {@link #isInSubgroup} after decoding if you received the point from + * an untrusted source. + * + * @throws IllegalArgumentException if {@code bytes} does not decode + * to a valid curve point + */ + public static JubjubPoint fromBytes(byte[] bytes) { + if (bytes == null || bytes.length != COMPRESSED_POINT_BYTES) { + throw new IllegalArgumentException("Encoded point must be " + COMPRESSED_POINT_BYTES + " bytes"); + } + byte[] copy = bytes.clone(); + boolean uOdd = (copy[COMPRESSED_POINT_BYTES - 1] & 0x80) != 0; + copy[COMPRESSED_POINT_BYTES - 1] = (byte) (copy[COMPRESSED_POINT_BYTES - 1] & 0x7F); + + BigInteger vAff = fromLE(copy); + if (vAff.compareTo(BASE_FIELD_PRIME) >= 0) { + throw new IllegalArgumentException("v-coordinate out of range"); + } + + // Recover u from -u^2 + v^2 = 1 + d·u^2·v^2 + // u^2 · (d·v^2 + 1) = v^2 - 1 + // u^2 = (v^2 - 1) / (d·v^2 + 1) + BigInteger vv = vAff.multiply(vAff).mod(BASE_FIELD_PRIME); + BigInteger numerator = vv.subtract(BigInteger.ONE).mod(BASE_FIELD_PRIME); + BigInteger denominator = D.multiply(vv).add(BigInteger.ONE).mod(BASE_FIELD_PRIME); + if (denominator.signum() == 0) { + throw new IllegalArgumentException("Invalid encoded point (zero denominator)"); + } + BigInteger uSquared = numerator.multiply(denominator.modInverse(BASE_FIELD_PRIME)).mod(BASE_FIELD_PRIME); + + BigInteger uAff = modSqrt(uSquared, BASE_FIELD_PRIME); + if (uAff == null) { + throw new IllegalArgumentException("Invalid encoded point (no square root for u²)"); + } + // Reject non-canonical encoding: if u = 0, the sign bit is meaningless + // (there is no distinct -u), so encodings with u=0 AND sign bit=1 alias + // the same point as sign bit=0. Per RFC 8032 canonicalization, reject. + if (uAff.signum() == 0 && uOdd) { + throw new IllegalArgumentException("Non-canonical encoding: u = 0 with sign bit set"); + } + if (uAff.testBit(0) != uOdd) { + uAff = BASE_FIELD_PRIME.subtract(uAff); + } + return fromAffine(uAff, vAff); + } + + // ---------- Helpers ---------- + + private static BigInteger reduce(BigInteger x) { + BigInteger r = x.mod(BASE_FIELD_PRIME); + return r.signum() < 0 ? r.add(BASE_FIELD_PRIME) : r; + } + + private static byte[] toFixedLE(BigInteger v, int length) { + byte[] be = v.toByteArray(); // two's-complement big-endian, possibly with sign byte + // Strip leading zero sign byte if present. + int start = (be.length > 1 && be[0] == 0) ? 1 : 0; + int effLen = be.length - start; + byte[] out = new byte[length]; + if (effLen > length) { + throw new IllegalStateException("Value exceeds " + length + " bytes"); + } + for (int i = 0; i < effLen; i++) { + out[i] = be[be.length - 1 - i]; + } + return out; + } + + private static BigInteger fromLE(byte[] bytes) { + byte[] be = new byte[bytes.length + 1]; + be[0] = 0; // positive sign + for (int i = 0; i < bytes.length; i++) { + be[be.length - 1 - i] = bytes[i]; + } + return new BigInteger(be); + } + + /** + * Tonelli–Shanks for a generic prime, returning one square root (or + * null if {@code a} is not a quadratic residue). For Jubjub's base + * field {@code p ≡ 1 (mod 4)}, so the {@code (p+1)/4} shortcut does + * not apply — use full Tonelli–Shanks. + */ + private static BigInteger modSqrt(BigInteger a, BigInteger p) { + if (a.signum() == 0) return BigInteger.ZERO; + if (a.modPow(p.subtract(BigInteger.ONE).shiftRight(1), p).equals(p.subtract(BigInteger.ONE))) { + return null; // non-residue + } + // Tonelli–Shanks + BigInteger s = p.subtract(BigInteger.ONE); + int e = 0; + while (!s.testBit(0)) { s = s.shiftRight(1); e++; } + // Find a non-residue n + BigInteger n = BigInteger.TWO; + BigInteger pMinusOneHalf = p.subtract(BigInteger.ONE).shiftRight(1); + while (!n.modPow(pMinusOneHalf, p).equals(p.subtract(BigInteger.ONE))) { + n = n.add(BigInteger.ONE); + } + BigInteger x = a.modPow(s.add(BigInteger.ONE).shiftRight(1), p); + BigInteger b = a.modPow(s, p); + BigInteger g = n.modPow(s, p); + int r = e; + while (true) { + BigInteger tmp = b; + int m = 0; + while (!tmp.equals(BigInteger.ONE)) { + tmp = tmp.multiply(tmp).mod(p); + m++; + if (m == r) return null; + } + if (m == 0) return x; + BigInteger gs = g.modPow(BigInteger.TWO.modPow(BigInteger.valueOf(r - m - 1), p.subtract(BigInteger.ONE)), p); + g = gs.multiply(gs).mod(p); + x = x.multiply(gs).mod(p); + b = b.multiply(g).mod(p); + r = m; + } + } + + /** Byte-equal encoded-form comparison. Convenience for tests. */ + public boolean encodingEquals(byte[] expected) { + return Arrays.equals(toBytes(), expected); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/PedersenCommitment.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/PedersenCommitment.java new file mode 100644 index 0000000..f36dbc6 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/jubjub/PedersenCommitment.java @@ -0,0 +1,140 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.jubjub; + +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonHash; +import com.bloxbean.cardano.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +/** + * Off-circuit Pedersen commitment over Jubjub: + * {@code C(v, r) = [v]·G + [r]·H} + * + *

      G is {@link JubjubPoint#SUBGROUP_GENERATOR}. H is the first Jubjub + * subgroup point derived deterministically from a domain-separated Poseidon + * hash (see {@link #H} below). Because H is produced by a random oracle + * output with no known discrete-log relation to G, Pedersen commitments + * using (G, H) are binding. Hiding comes from the uniformly random + * blinding scalar {@code r}. + * + *

      Domain separation

      + * H is derived from the UTF-8 byte string {@code "zeroj.pedersen.v1.H"} + * hashed via Poseidon over BLS12-381 (ADR-0015 preset). The derivation is + * try-and-increment: hash the domain tag, reduce to a field element, check + * whether that value is a valid v-coordinate on Jubjub; if not, increment + * a counter and rehash. The first successful hit is taken, cofactor-cleared, + * and cached. + * + *

      Caveats

      + *
        + *
      • This implementation is deterministic — any two correct runs produce + * the same H.
      • + *
      • The domain tag {@code "zeroj.pedersen.v1.H"} is a protocol version. + * Changing it invalidates all commitments produced under the previous + * H; treat as a breaking change.
      • + *
      • H has no known discrete log w.r.t. G. If a future + * implementation hard-codes {@code H = [h]·G} for known h, binding is + * broken. The derivation recipe above avoids this trap by construction.
      • + *
      + */ +public final class PedersenCommitment { + + private PedersenCommitment() {} + + /** Domain tag for the second base derivation. Must not change. */ + public static final String H_DOMAIN_TAG = "zeroj.pedersen.v1.H"; + + /** + * Second Pedersen base, derived from {@link #H_DOMAIN_TAG} via a + * deterministic Poseidon-based try-and-increment. The discrete log of + * H w.r.t. {@link JubjubPoint#SUBGROUP_GENERATOR} is unknown. + */ + public static final JubjubPoint H = deriveSecondBase(); + + /** + * Commits to {@code v} with blinding {@code r}: + * {@code C(v, r) = [v]·G + [r]·H}. + * + * @param value committed value (typically small; must be reducible + * mod {@link JubjubCurve#SUBGROUP_ORDER}) + * @param blinding uniformly random blinding scalar (required for hiding) + * @return a Jubjub point representing the commitment + */ + public static JubjubPoint commit(BigInteger value, BigInteger blinding) { + JubjubPoint vG = JubjubPoint.SUBGROUP_GENERATOR.scalarMul(value); + JubjubPoint rH = H.scalarMul(blinding); + return vG.add(rH); + } + + /** + * Verifies an opening: returns {@code true} iff {@code C == [v]·G + [r]·H}. + */ + public static boolean verify(JubjubPoint commitment, BigInteger value, BigInteger blinding) { + return commit(value, blinding).projectiveEquals(commitment); + } + + private static JubjubPoint deriveSecondBase() { + BigInteger p = JubjubCurve.BASE_FIELD_PRIME; + // Hash the domain tag bytes into the field via Poseidon. + byte[] domainBytes = H_DOMAIN_TAG.getBytes(StandardCharsets.UTF_8); + BigInteger a = new BigInteger(1, domainBytes).mod(p); + for (int counter = 0; counter < 1_000_000; counter++) { + BigInteger b = BigInteger.valueOf(counter); + BigInteger seed = PoseidonHash.hash(PoseidonParamsBLS12_381T3.INSTANCE, a, b); + BigInteger vCandidate = seed.mod(p); + // Try to solve -u^2 + v^2 = 1 + d·u^2·v^2 for u. + // u^2 = (v^2 - 1) / (d·v^2 + 1). + BigInteger vv = vCandidate.multiply(vCandidate).mod(p); + BigInteger num = vv.subtract(BigInteger.ONE).mod(p); + BigInteger den = JubjubCurve.D.multiply(vv).add(BigInteger.ONE).mod(p); + if (den.signum() == 0) continue; + BigInteger uSquared = num.multiply(den.modInverse(p)).mod(p); + BigInteger u = modSqrtOrNull(uSquared, p); + if (u == null) continue; + // Deterministic sign choice: take the lexicographically-smaller root. + BigInteger altU = p.subtract(u); + if (altU.compareTo(u) < 0) u = altU; + // Cofactor-clear to ensure H is in the prime-order subgroup. + JubjubPoint candidate = JubjubPoint.fromAffine(u, vCandidate).mulByCofactor(); + if (candidate.isIdentity()) continue; // extremely unlikely, but safe + return candidate; + } + throw new IllegalStateException( + "PedersenCommitment second-base derivation did not converge after 1M tries — " + + "this should be cryptographically impossible; check Poseidon/Jubjub wiring"); + } + + private static BigInteger modSqrtOrNull(BigInteger a, BigInteger p) { + if (a.signum() == 0) return BigInteger.ZERO; + // Euler criterion: a is a QR iff a^((p-1)/2) == 1. + BigInteger exp = p.subtract(BigInteger.ONE).shiftRight(1); + if (!a.modPow(exp, p).equals(BigInteger.ONE)) return null; + // Tonelli-Shanks + BigInteger s = p.subtract(BigInteger.ONE); + int e = 0; + while (!s.testBit(0)) { s = s.shiftRight(1); e++; } + BigInteger n = BigInteger.TWO; + while (!n.modPow(exp, p).equals(p.subtract(BigInteger.ONE))) { + n = n.add(BigInteger.ONE); + } + BigInteger x = a.modPow(s.add(BigInteger.ONE).shiftRight(1), p); + BigInteger b = a.modPow(s, p); + BigInteger g = n.modPow(s, p); + int r = e; + while (true) { + BigInteger tmp = b; + int m = 0; + while (!tmp.equals(BigInteger.ONE)) { + tmp = tmp.multiply(tmp).mod(p); + m++; + if (m == r) return null; + } + if (m == 0) return x; + BigInteger gs = g.modPow(BigInteger.TWO.pow(r - m - 1), p); + g = gs.multiply(gs).mod(p); + x = x.multiply(gs).mod(p); + b = b.multiply(g).mod(p); + r = m; + } + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonCacheVersion.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonCacheVersion.java new file mode 100644 index 0000000..805ecaf --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonCacheVersion.java @@ -0,0 +1,141 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; +import java.util.List; + +/** + * Detects when persisted ZK caches (SRS, R1CS setup artifacts, Merkle tries) + * were generated under a different Poseidon parameter set than the current + * runtime and wipes them so the usecase regenerates fresh. + * + *

      Rationale: R1CS files bake Poseidon constants into their constraint + * systems. If the code's Poseidon parameters change (e.g. during an + * ADR-0015-style migration or a future parameter tweak), a cached R1CS is + * no longer correct for the current runtime. Without this marker, stale + * caches produce witness-evaluation errors at arbitrary points — the error + * at the top of the ADR-0015 end-to-end test was exactly this class of bug, + * manually worked around by {@code rm -rf data}. + * + *

      Version string: SHA-256(committed BN254 + BLS12-381 preset C and M + * arrays), truncated to 16 hex chars. Any parameter change flips the hash + * and triggers a wipe on next startup. + * + *

      Usage

      + *
      {@code
      + * // In your Spring @Configuration or app bootstrap:
      + * PoseidonCacheVersion.ensureFresh(
      + *     Path.of("./data"),
      + *     List.of("*.bin", "dpp-trie", "dpp-trie-minted"));
      + * }
      + * + *

      The caller owns the list of cache artifacts to wipe; this helper only + * handles version detection and deletion. Application state (H2 DB, RocksDB + * rows unrelated to hashing) should not be passed in. + */ +public final class PoseidonCacheVersion { + + /** Marker file name, placed at the root of the data dir. */ + public static final String MARKER = ".zeroj-poseidon-version"; + + /** + * Current Poseidon parameter-set version. Derived from committed BN254 + + * BLS12-381 preset constants; changes when either preset's constants + * change. + */ + public static final String CURRENT = computeCurrent(); + + private PoseidonCacheVersion() {} + + private static String computeCurrent() { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + hashPreset(md, PoseidonParamsBN254T3.C, PoseidonParamsBN254T3.M); + hashPreset(md, PoseidonParamsBLS12_381T3.C, PoseidonParamsBLS12_381T3.M); + byte[] digest = md.digest(); + return HexFormat.of().formatHex(digest).substring(0, 16); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-256 unavailable", e); + } + } + + private static void hashPreset(MessageDigest md, java.math.BigInteger[] c, java.math.BigInteger[] m) { + for (var v : c) md.update(v.toByteArray()); + for (var v : m) md.update(v.toByteArray()); + } + + /** + * Checks the marker file in {@code dataDir}. If missing or stale, wipes + * all paths matching the given {@code cachePatterns} (relative to + * {@code dataDir}; treated as literal filenames or directory names, not + * glob patterns) and writes a fresh marker. + * + *

      Safe to call on first run — if {@code dataDir} does not exist, it + * is created and the marker is written. + * + * @param dataDir directory containing ZK cache artifacts + * @param cachePatterns filename or directory-name matchers under + * {@code dataDir}; supports leading {@code "*."} and + * trailing {@code "*"} for simple prefix/suffix matching + * @return true if a wipe occurred, false if caches were already current + */ + public static boolean ensureFresh(Path dataDir, List cachePatterns) { + try { + Files.createDirectories(dataDir); + Path marker = dataDir.resolve(MARKER); + String stored = Files.exists(marker) + ? Files.readString(marker, StandardCharsets.UTF_8).trim() + : null; + if (CURRENT.equals(stored)) { + return false; + } + wipeMatching(dataDir, cachePatterns); + Files.writeString(marker, CURRENT, StandardCharsets.UTF_8); + return true; + } catch (IOException e) { + throw new UncheckedIOException("Failed to validate Poseidon cache version at " + dataDir, e); + } + } + + private static void wipeMatching(Path dataDir, List patterns) throws IOException { + if (!Files.exists(dataDir)) return; + try (DirectoryStream stream = Files.newDirectoryStream(dataDir)) { + for (Path entry : stream) { + String name = entry.getFileName().toString(); + if (name.equals(MARKER)) continue; + if (matchesAny(name, patterns)) { + deleteRecursively(entry); + } + } + } + } + + private static boolean matchesAny(String name, List patterns) { + for (String p : patterns) { + if (p.startsWith("*.")) { + if (name.endsWith(p.substring(1))) return true; + } else if (p.endsWith("*")) { + if (name.startsWith(p.substring(0, p.length() - 1))) return true; + } else if (name.equals(p)) { + return true; + } + } + return false; + } + + private static void deleteRecursively(Path path) throws IOException { + if (Files.isDirectory(path)) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path child : stream) deleteRecursively(child); + } + } + Files.deleteIfExists(path); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonGrainLFSR.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonGrainLFSR.java new file mode 100644 index 0000000..c1eb113 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonGrainLFSR.java @@ -0,0 +1,318 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import java.math.BigInteger; + +/** + * Java port of the authoritative Poseidon parameter generator from the + * Poseidon paper (Grassi et al., 2021), Sage reference implementation: + * {@code generate_parameters_grain.sage} in the IAIK hadeshash repository, + * pinned commit {@code 208b5a164c6a252b137997694d90931b2bb851c5} (2023-05-02). + * See {@code src/main/resources/poseidon/} for the pinned Sage script and + * reproduction instructions. + * + *

      This class is intentionally a direct, line-by-line port of the Sage + * script for verifiability. Outputs must match the Sage script byte-for-byte + * for equivalent parameter inputs. Correctness is enforced by a cross-check + * test against the known circomlibjs BN254 t=3 constants already shipped in + * {@code PoseidonConstants} — those were themselves produced by running this + * Sage script with the BN254 arguments. + * + *

      Currently supports GF(p) with S-box {@code x^alpha}. GF(2^n) and + * {@code x^{-1}} S-box are not implemented (not needed by any ZeroJ target). + * + *

      The Poseidon paper's MDS security Algorithms 1, 2, 3 are not ported: for + * every parameter set ZeroJ targets (BN254 t=3, BLS12-381 t=3 and t=5 with + * standard RF/RP), the first-generated Cauchy matrix passes all three + * algorithms, verified empirically by cross-checking against published + * reference implementations. Should a future target require retries, those + * algorithms would need to be added. + */ +public final class PoseidonGrainLFSR { + + /** Fixed LFSR register size, per Poseidon paper §5.1. */ + private static final int REG_SIZE = 80; + + /** Number of warm-up steps before the generator starts emitting bits. */ + private static final int WARMUP = 160; + + /** FIELD field value for GF(p) (the only kind supported). Used in init. */ + private static final int FIELD_KIND_GFP = 1; + + /** SBOX field value for x^α S-box (the only kind supported). Used in init. */ + private static final int SBOX_X_ALPHA = 0; + + /** + * Accept-list of {@code (fieldSize, t, rf, rp, primeHex)} tuples for which + * the first-sampled Cauchy MDS matrix has been empirically verified to pass + * the Poseidon paper's Algorithms 1/2/3 (so skipping them in Java is safe). + * See class-level Javadoc and ADR-0015 for details. + */ + private static final java.util.Set VETTED_PARAMS = java.util.Set.of( + // BN254 t=3 α=5 RF=8 RP=57 — verified against iden3/circomlibjs constants. + "254|3|8|57|0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + // BLS12-381 t=3 α=5 RF=8 RP=57 — verified via Sage script cross-check. + "255|3|8|57|0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + // BLS12-381 t=5 α=5 RF=8 RP=60 — ADR-0016 M4 (Pedersen / 4-ary Jubjub Merkle). + // Sage script confirmed first-pass Cauchy matrix passes Algorithms 1/2/3 (all True). + "255|5|8|60|0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" + ); + + /** Field size in bits (n): 254 for BN254, 255 for BLS12-381. */ + private final int fieldSize; + /** State width t (number of cells). */ + private final int t; + /** Full rounds (typically 8). */ + private final int rf; + /** Partial rounds. */ + private final int rp; + /** Scalar field prime modulus. */ + private final BigInteger prime; + + /** 80-bit LFSR register, as a circular buffer. */ + private final int[] register = new int[REG_SIZE]; + /** Head index: array position of LFSR logical position 0. */ + private int head; + + private PoseidonGrainLFSR(int fieldSize, int t, int rf, int rp, BigInteger prime) { + this.fieldSize = fieldSize; + this.t = t; + this.rf = rf; + this.rp = rp; + this.prime = prime; + initRegister(); + warmup(); + } + + /** + * Creates a generator for Poseidon parameters over {@code GF(p)} with + * {@code x^alpha} S-box. The tuple {@code (fieldSize, t, rf, rp, prime)} + * must appear in {@link #VETTED_PARAMS}: skipping the MDS security + * algorithms is only safe for parameter combinations we've empirically + * verified produce a secure first-pass matrix. + * + * @param fieldSize bit-length {@code n} of the scalar field (254 for BN254, 255 for BLS12-381) + * @param t state width (e.g. 3 for two-to-one hash) + * @param rf full rounds (typically 8) + * @param rp partial rounds + * @param prime scalar field modulus + * @throws UnsupportedOperationException for parameter tuples outside the accept-list + */ + public static PoseidonGrainLFSR forGFp(int fieldSize, int t, int rf, int rp, BigInteger prime) { + String key = fieldSize + "|" + t + "|" + rf + "|" + rp + "|0x" + prime.toString(16); + if (!VETTED_PARAMS.contains(key)) { + throw new UnsupportedOperationException( + "Parameter tuple not on ZeroJ's vetted list (MDS security algorithms 1/2/3 " + + "are not ported; first-pass Cauchy matrix is only trusted for " + + "explicitly vetted tuples). Requested: " + key + ". " + + "Adding a new tuple requires running the hadeshash Sage script " + + "to confirm first-pass acceptance and updating VETTED_PARAMS + ADR-0015."); + } + return new PoseidonGrainLFSR(fieldSize, t, rf, rp, prime); + } + + /** + * Populates {@link #register} with the initial 80-bit sequence from the + * concatenation of parameter bit-fields, exactly as {@code init_generator} + * in the Sage script: + * + *

      +     *   FIELD  (2 bits)  | SBOX  (4 bits)  | n (12 bits) | t (12 bits)
      +     *   R_F (10 bits)    | R_P (10 bits)   | 30 ones
      +     * 
      + * + * All fields are zero-left-padded to their designated widths. The leftmost + * bit of each field (its MSB) is placed at the lowest LFSR position; thus + * logical position 0 is the MSB of {@code FIELD}. + */ + private void initRegister() { + int pos = 0; + pos = appendBits(pos, FIELD_KIND_GFP, 2); + pos = appendBits(pos, SBOX_X_ALPHA, 4); + pos = appendBits(pos, fieldSize, 12); + pos = appendBits(pos, t, 12); + pos = appendBits(pos, rf, 10); + pos = appendBits(pos, rp, 10); + for (int i = 0; i < 30; i++) { + register[pos++] = 1; + } + if (pos != REG_SIZE) { + throw new IllegalStateException("Init sequence length != " + REG_SIZE + " (got " + pos + ")"); + } + this.head = 0; + } + + /** + * Writes {@code width} bits of {@code value} into {@link #register} starting + * at {@code pos}, MSB-first. Returns the next write position. {@code value} + * must fit in {@code width} bits; no overflow check is performed (all call + * sites pass values that fit by construction — widths are at most 12). + */ + private int appendBits(int pos, int value, int width) { + for (int i = width - 1; i >= 0; i--) { + register[pos++] = (value >>> i) & 1; + } + return pos; + } + + /** Runs {@link #WARMUP} LFSR steps whose outputs are discarded. */ + private void warmup() { + for (int i = 0; i < WARMUP; i++) { + stepRaw(); + } + } + + /** + * Advances the LFSR one step and returns the new feedback bit. Mirrors: + *
      +     *   new_bit = bit_sequence[62] XOR bit_sequence[51] XOR bit_sequence[38]
      +     *           XOR bit_sequence[23] XOR bit_sequence[13] XOR bit_sequence[0]
      +     *   bit_sequence.pop(0); bit_sequence.append(new_bit)
      +     * 
      + */ + private int stepRaw() { + int b0 = register[head]; + int b13 = register[(head + 13) % REG_SIZE]; + int b23 = register[(head + 23) % REG_SIZE]; + int b38 = register[(head + 38) % REG_SIZE]; + int b51 = register[(head + 51) % REG_SIZE]; + int b62 = register[(head + 62) % REG_SIZE]; + int newBit = b0 ^ b13 ^ b23 ^ b38 ^ b51 ^ b62; + // pop(0) + append(newBit): overwrite old position-0 slot, advance head. + register[head] = newBit; + head = (head + 1) % REG_SIZE; + return newBit; + } + + /** + * Produces one output bit using the self-shrinking generator rule from the + * Sage script. For each yielded bit, two raw LFSR bits are consumed; if + * the first is zero, the second is discarded and another pair is tried. + */ + public int nextBit() { + int first = stepRaw(); + while (first == 0) { + stepRaw(); // discard paired second bit + first = stepRaw(); // new "first" of next pair + } + return stepRaw(); // yield the second bit of the winning pair + } + + /** + * Produces a non-negative integer of exactly {@code numBits} bits by + * concatenating LFSR output bits MSB-first (first-generated bit is the + * most significant). Mirrors {@code grain_random_bits} in the Sage script. + */ + public BigInteger nextNBitInteger(int numBits) { + BigInteger result = BigInteger.ZERO; + for (int i = 0; i < numBits; i++) { + result = result.shiftLeft(1); + if (nextBit() == 1) { + result = result.or(BigInteger.ONE); + } + } + return result; + } + + /** + * Generates all {@code (RF + RP) * t} round constants, rejection-sampling + * until each value is in {@code [0, prime)}. Mirrors + * {@code generate_constants} in the Sage script for the GF(p) branch. + */ + public BigInteger[] generateRoundConstants() { + int num = (rf + rp) * t; + BigInteger[] constants = new BigInteger[num]; + for (int i = 0; i < num; i++) { + BigInteger v = nextNBitInteger(fieldSize); + while (v.compareTo(prime) >= 0) { + v = nextNBitInteger(fieldSize); + } + constants[i] = v; + } + return constants; + } + + /** + * Generates a {@code t x t} Cauchy MDS matrix as {@code create_mds_p} in + * the Sage script does. Samples {@code 2t} distinct field elements + * {@code [x_1..x_t, y_1..y_t]} (resampling all on any duplicate, where + * equality is taken modulo {@code prime}) and sets + * {@code M[i][j] = (x_i + y_j)^{-1} (mod prime)}. If any {@code x_i + y_j} + * is zero, the entire outer sampling loop restarts. + * + *

      Note: the Sage script wraps this in an outer + * {@code generate_matrix} that runs Algorithms 1/2/3 and resamples on + * security rejection. Those algorithms are not ported here — see the + * class-level Javadoc for the justification and limits. + * + * @return a row-major {@code t x t} matrix of field elements + */ + public BigInteger[][] generateMdsMatrix() { + while (true) { + BigInteger[] rand = sampleDistinctFieldElements(2 * t); + BigInteger[] xs = new BigInteger[t]; + BigInteger[] ys = new BigInteger[t]; + System.arraycopy(rand, 0, xs, 0, t); + System.arraycopy(rand, t, ys, 0, t); + + BigInteger[][] m = new BigInteger[t][t]; + boolean hadZeroSum = false; + // IMPORTANT: do NOT break on hadZeroSum. The Sage script + // (create_mds_p) also continues iterating once its flag is false — + // see comments there: after setting flag=False, the inner loops + // still execute the "else" branch-skip. Since neither Sage nor + // this port advances the LFSR inside the matrix construction, an + // early break would be observationally identical *today*, but + // could diverge if the inner body ever gains a grain_random_bits + // call (or a future Sage update changes semantics). Keep the + // structural mirror to Sage so any such change is caught by the + // regression tests rather than silently drifting. + for (int i = 0; i < t; i++) { + for (int j = 0; j < t; j++) { + BigInteger sum = xs[i].add(ys[j]).mod(prime); + if (sum.signum() == 0) { + hadZeroSum = true; + } else if (!hadZeroSum) { + m[i][j] = sum.modInverse(prime); + } + } + } + if (!hadZeroSum) { + return m; + } + // Outer while retries by drawing fresh samples — matches Sage's + // `continue` after `flag == False`. + } + } + + /** + * Draws {@code count} field elements (each reduced mod {@code prime}) + * whose field-sense values are pairwise distinct. Matches the Sage + * behavior: on any duplicate, resample the entire list (not just the + * duplicate) so the LFSR state advances identically. + */ + private BigInteger[] sampleDistinctFieldElements(int count) { + BigInteger[] rand = new BigInteger[count]; + drawAll(rand); + while (hasDuplicates(rand)) { + drawAll(rand); + } + return rand; + } + + private void drawAll(BigInteger[] buf) { + for (int i = 0; i < buf.length; i++) { + buf[i] = nextNBitInteger(fieldSize).mod(prime); + } + } + + private static boolean hasDuplicates(BigInteger[] arr) { + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + if (arr[i].equals(arr[j])) { + return true; + } + } + } + return false; + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonHash.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonHash.java new file mode 100644 index 0000000..1ec6d9a --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonHash.java @@ -0,0 +1,118 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Standalone off-circuit Poseidon hash over {@link PoseidonParams}. This is + * the reference implementation used by application code (hash computation + * outside the proof) and by tests (oracle to cross-check the in-circuit + * gadget output). + * + *

      Currently supports {@code t=3, alpha=5} (two-to-one hash). Wider states + * or different S-boxes require a different driver; the + * {@link PoseidonGrainLFSR} generator already supports them, but the hash + * function body here is intentionally specialized for the t=3 case that + * matches the {@link com.bloxbean.cardano.zeroj.circuit.lib.Poseidon} circuit + * gadget. + * + *

      All arithmetic is reduced modulo {@code params.field().prime()} — the + * inputs are reduced on entry, so callers may pass any non-negative + * BigInteger. + */ +public final class PoseidonHash { + + private PoseidonHash() {} + + /** + * Hashes two BigInteger inputs into a field element using the Poseidon + * permutation defined by {@code params}. + * + * @param params Poseidon parameters (must have t=3, alpha=5) + * @param a first input (reduced mod prime) + * @param b second input (reduced mod prime) + * @return hash output in {@code [0, prime)} + */ + public static BigInteger hash(PoseidonParams params, BigInteger a, BigInteger b) { + Objects.requireNonNull(params, "params"); + Objects.requireNonNull(a, "a"); + Objects.requireNonNull(b, "b"); + if (params.t() != 3 || params.alpha() != 5) { + throw new IllegalArgumentException( + "PoseidonHash supports only t=3, alpha=5 (got t=" + params.t() + + ", alpha=" + params.alpha() + ")"); + } + + BigInteger p = params.field().prime(); + int rf = params.rf(); + int rp = params.rp(); + int totalRounds = rf + rp; + + BigInteger s0 = BigInteger.ZERO; + BigInteger s1 = a.mod(p); + BigInteger s2 = b.mod(p); + + for (int r = 0; r < totalRounds; r++) { + // AddRoundConstants + s0 = s0.add(params.cAt(r, 0)).mod(p); + s1 = s1.add(params.cAt(r, 1)).mod(p); + s2 = s2.add(params.cAt(r, 2)).mod(p); + + // S-box (x^5) + if (r < rf / 2 || r >= rf / 2 + rp) { + s0 = sbox(s0, p); + s1 = sbox(s1, p); + s2 = sbox(s2, p); + } else { + s0 = sbox(s0, p); + } + + // MDS matrix multiplication (t=3) + BigInteger t0 = params.mAt(0, 0).multiply(s0) + .add(params.mAt(0, 1).multiply(s1)) + .add(params.mAt(0, 2).multiply(s2)) + .mod(p); + BigInteger t1 = params.mAt(1, 0).multiply(s0) + .add(params.mAt(1, 1).multiply(s1)) + .add(params.mAt(1, 2).multiply(s2)) + .mod(p); + BigInteger t2 = params.mAt(2, 0).multiply(s0) + .add(params.mAt(2, 1).multiply(s1)) + .add(params.mAt(2, 2).multiply(s2)) + .mod(p); + s0 = t0; + s1 = t1; + s2 = t2; + } + + return s0; + } + + /** + * Variable-arity hash matching the left-fold convention used by + * {@link com.bloxbean.cardano.zeroj.circuit.lib.PoseidonN}. For 0 inputs + * throws; for 1 input, hashes {@code (x, 0)} — this is a ZeroJ-specific + * convention, not a published Poseidon spec; see {@link PoseidonN} for + * the authoritative statement. For N inputs, + * {@code hash(...hash(hash(a, b), c), ...)}. + */ + public static BigInteger hashN(PoseidonParams params, BigInteger... inputs) { + Objects.requireNonNull(params, "params"); + Objects.requireNonNull(inputs, "inputs"); + if (inputs.length == 0) throw new IllegalArgumentException("inputs must not be empty"); + if (inputs.length == 1) { + return hash(params, inputs[0], BigInteger.ZERO); + } + BigInteger acc = hash(params, inputs[0], inputs[1]); + for (int i = 2; i < inputs.length; i++) { + acc = hash(params, acc, inputs[i]); + } + return acc; + } + + private static BigInteger sbox(BigInteger x, BigInteger p) { + BigInteger x2 = x.multiply(x).mod(p); + BigInteger x4 = x2.multiply(x2).mod(p); + return x4.multiply(x).mod(p); + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParams.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParams.java new file mode 100644 index 0000000..3cc5365 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParams.java @@ -0,0 +1,160 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import com.bloxbean.cardano.zeroj.circuit.FieldConfig; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +/** + * Parameter bundle for a specific Poseidon instantiation: scalar field, + * state width, S-box, round counts, and the {@code (C, M)} pair (round + * constants and MDS matrix) produced by the authoritative Grain LFSR + * generator defined in the Poseidon paper (see + * {@link PoseidonGrainLFSR}). + * + *

      A {@code PoseidonParams} instance is the unit of compatibility: two + * Poseidon implementations interoperate if and only if they use equal + * {@code PoseidonParams}. Named presets (e.g. {@code PoseidonParamsBN254T3}, + * {@code PoseidonParamsBLS12_381T3}) are generated from the Grain LFSR at + * build time via {@link PoseidonParamsCodegen} and committed as source for + * IDE ergonomics and GraalVM native-image compatibility. + * + *

      Immutability: the arrays {@code c} and {@code m} are cloned on + * construction and on every accessor call, so callers cannot mutate them. + * Because the accessor clones are O(n), gadget code on hot paths should + * prefer {@link #cAt(int, int)} and {@link #mAt(int, int)} which read the + * internal arrays directly. + * + *

      Equality: {@link #equals(Object)} and {@link #hashCode()} are overridden + * to compare the arrays by content (via {@link Arrays#equals}). This is + * critical because Java records' default {@code equals} uses reference + * equality for array components, which would make two independently + * constructed identical parameter sets compare unequal. + * + * @param field scalar field over which Poseidon operates + * @param t state width (number of cells) + * @param alpha S-box exponent; must be coprime to {@code p - 1} (not checked + * here — the known presets BN254 / BLS12-381 with {@code alpha=5} + * satisfy this; adding primes where {@code gcd(alpha, p-1) != 1} + * requires new analysis) + * @param rf full rounds (typically 8) + * @param rp partial rounds (depends on field, t, and security target) + * @param c round constants, length {@code (rf + rp) * t}, each in {@code [0, field.prime())} + * @param m MDS matrix, length {@code t * t}, row-major, each in {@code [0, field.prime())} + */ +public record PoseidonParams( + FieldConfig field, + int t, + int alpha, + int rf, + int rp, + BigInteger[] c, + BigInteger[] m +) { + + public PoseidonParams { + Objects.requireNonNull(field, "field"); + Objects.requireNonNull(c, "c"); + Objects.requireNonNull(m, "m"); + if (t < 2) { + throw new IllegalArgumentException("t must be >= 2, got " + t); + } + if (alpha < 3) { + throw new IllegalArgumentException("alpha must be >= 3, got " + alpha); + } + if (rf < 0 || rp < 0) { + throw new IllegalArgumentException("rf/rp must be non-negative"); + } + if (c.length != (rf + rp) * t) { + throw new IllegalArgumentException( + "c.length must be (rf + rp) * t = " + ((rf + rp) * t) + ", got " + c.length); + } + if (m.length != t * t) { + throw new IllegalArgumentException( + "m.length must be t * t = " + (t * t) + ", got " + m.length); + } + BigInteger prime = field.prime(); + validateElements(c, prime, "c"); + validateElements(m, prime, "m"); + c = c.clone(); + m = m.clone(); + } + + private static void validateElements(BigInteger[] arr, BigInteger prime, String name) { + for (int i = 0; i < arr.length; i++) { + BigInteger v = arr[i]; + if (v == null) { + throw new IllegalArgumentException(name + "[" + i + "] is null"); + } + if (v.signum() < 0) { + throw new IllegalArgumentException(name + "[" + i + "] is negative: " + v); + } + if (v.compareTo(prime) >= 0) { + throw new IllegalArgumentException(name + "[" + i + "] >= field prime"); + } + } + } + + /** Total round count, {@code rf + rp}. */ + public int totalRounds() { + return rf + rp; + } + + /** + * Defensive copy of the round constants. O(n); in hot paths prefer + * {@link #cAt(int, int)}. + */ + @Override + public BigInteger[] c() { + return c.clone(); + } + + /** + * Defensive copy of the MDS matrix (row-major). O(n); in hot paths prefer + * {@link #mAt(int, int)}. + */ + @Override + public BigInteger[] m() { + return m.clone(); + } + + /** Round constant at round {@code r}, cell {@code i}. */ + public BigInteger cAt(int r, int i) { + return c[r * t + i]; + } + + /** MDS entry at row {@code i}, column {@code j}. */ + public BigInteger mAt(int i, int j) { + return m[i * t + j]; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PoseidonParams other)) return false; + return t == other.t + && alpha == other.alpha + && rf == other.rf + && rp == other.rp + && field.equals(other.field) + && Arrays.equals(c, other.c) + && Arrays.equals(m, other.m); + } + + @Override + public int hashCode() { + int result = Objects.hash(field, t, alpha, rf, rp); + result = 31 * result + Arrays.hashCode(c); + result = 31 * result + Arrays.hashCode(m); + return result; + } + + @Override + public String toString() { + return "PoseidonParams{field=" + field.name() + + ", t=" + t + ", alpha=" + alpha + + ", rf=" + rf + ", rp=" + rp + + ", |c|=" + c.length + ", |m|=" + m.length + "}"; + } +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBLS12_381T3.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBLS12_381T3.java new file mode 100644 index 0000000..1e7b06b --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBLS12_381T3.java @@ -0,0 +1,238 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import com.bloxbean.cardano.zeroj.circuit.FieldConfig; + +import java.math.BigInteger; + +/** + * Poseidon parameters for BLS12-381 t=3 alpha=5 RF=8 RP=57. + * + *

      This file is generated by {@link PoseidonParamsCodegen} from the + * Grain LFSR in {@link PoseidonGrainLFSR}, which is a faithful port of the + * Poseidon paper's reference Sage script (hadeshash commit + * {@code 208b5a164c6a252b137997694d90931b2bb851c5}). Do not edit by hand; + * regenerate with {@code ./gradlew :zeroj-circuit-lib:generatePoseidonParams}. + */ +public final class PoseidonParamsBLS12_381T3 { + + private PoseidonParamsBLS12_381T3() {} + + /** Round constants: 195 values (65 rounds x 3 state width). */ + static final BigInteger[] C = new BigInteger[] { + new BigInteger("48991097081732275468845314168021420565497297775988823234113406403095118809216"), + new BigInteger("38385660029618165285848698857635215143135976511856402182142757680787979296154"), + new BigInteger("45664917788634056160947231182803089169570746657219074370482409200042991921246"), + new BigInteger("46611823467219910333349433978991031443945697128435279755908258896090196676828"), + new BigInteger("21239555800391983336673016232252577145979304597102502292785557024177155115319"), + new BigInteger("5444549814002252718699361548642546874417220826495496552290417094191494299797"), + new BigInteger("6120941817780228594851185625662354154126315032538247033968198498911791651970"), + new BigInteger("23268934541565483112488314239282439244757346303484537549209002605218913236536"), + new BigInteger("34778900561716047730386110499058136122597669775051061603711724688203374984731"), + new BigInteger("11866412958831620887953860204795878894545618212709331023611019011793447488176"), + new BigInteger("1292810553955081089139103033821163176614817808018762694232693357405135340213"), + new BigInteger("29829440149074940820671559824872937980763748927491238614065138142835318453671"), + new BigInteger("43007325278312980663982452106946226844964622384017700838855297379677047113384"), + new BigInteger("6207852559847946300667836829798951848361581084433525098597857899536657157132"), + new BigInteger("51263844854419207560514475863120683772532929850629546992690510884221364990253"), + new BigInteger("47537207485065031976374469967696134772574834313568026823983918780308518394040"), + new BigInteger("2221931791899303960239149702171682649773262449196140787838362753706579104592"), + new BigInteger("39456839086017037141295863080128693714705835125922448198802062180577619415688"), + new BigInteger("7307684192235537965831376311417883513796535701244096178785218530839409056523"), + new BigInteger("40363790847223872255995860144037894400158879326818322790255787884037990480527"), + new BigInteger("46370977865329511267956842930057959446221524060145738210680245530954549945015"), + new BigInteger("31963375456062604704511762940421329756212766442452555529101241339674782334039"), + new BigInteger("14931035994999669353073307088521670981122374648927581516990615825314462827897"), + new BigInteger("9146050314741225622437907700594105481623623087635695897868792721147700541623"), + new BigInteger("43028866523328004770172322384235815492694573248368601737155468843525625413279"), + new BigInteger("10642771813466087799681476709295362996886361934733270333728358675267521442184"), + new BigInteger("26204626472182247586446753357603232226235570940686295317661191583409532523578"), + new BigInteger("51764778305842182544341507127328333397682018984536762517144144495830254727692"), + new BigInteger("46323013798997081811959707047808149003166619133464450127989691277775183404349"), + new BigInteger("5482714761779403197336605367697000529513289823583027739458069397684408687717"), + new BigInteger("12801259943830582826718901632357112368256632783422449824889858551937326401170"), + new BigInteger("24705221370028061177410670936487461711735994635988936070623351799675117594850"), + new BigInteger("34818354068777339891091714877681898548352650337240481539567373888981659308099"), + new BigInteger("35437981511765462742605234803376772682840664204821301764084738573774616215109"), + new BigInteger("1433523918194521021731556457516832465819757187635645935518277720319249889445"), + new BigInteger("1786444825311968572352002116054188762971225383128313206702203805257523693888"), + new BigInteger("22232073076796622550494050910209988454596433174206874696362037700514082492276"), + new BigInteger("24042430109235922611027968831657325520072553641473321784508698720854180658031"), + new BigInteger("45406805567398680921065452923276055166961588153660261520529196040913487916279"), + new BigInteger("35053262861048825411061280559553895536192334830763062477277235807515959383150"), + new BigInteger("25108964803188800737437394246442073858261740146181095550988111856238954490309"), + new BigInteger("35192650141137106058577418514209092904214762437910434967540336800650620041958"), + new BigInteger("34220944794619662782589792809938215078980533657269200933482014763836254210880"), + new BigInteger("39884393792242132075258602070541114557272278571033974158755307717930033808078"), + new BigInteger("6528627567246138898338135471584665860403024864125846353758054588554049365178"), + new BigInteger("26135348890537017135058266369936506677345001674530050056494732502158573534651"), + new BigInteger("45940975099728729872716617510434185869788979733816569378448209603957649084497"), + new BigInteger("15421094974171181812057105309783852016087843260648209913425190920580878315912"), + new BigInteger("17821536801502538623431403481143359660601434134694528982404802873816360858943"), + new BigInteger("8010729838943058740614807905113741378835761166137481371357965047712306801123"), + new BigInteger("18699215163509883263304393673283276029620709331747651039747044003384506899917"), + new BigInteger("37045787943638220002917633921716309877792707850558591835874081145770158399128"), + new BigInteger("21575637935417645110089037900895429146838845113516284564671508366546944971174"), + new BigInteger("1788789771738709712587591109966362080868778924904243569200231458308784197447"), + new BigInteger("31893695366599021197812621371715665903315747385247436549810717167321695484766"), + new BigInteger("51153400179598348220410722401172031495931771158209082356586940118519763307990"), + new BigInteger("27065341612806387486757726552834268222391812301897865130062594135449450311205"), + new BigInteger("21631377794423816098233500204394685009343254816615902551641496756763638503963"), + new BigInteger("48126155452550090941025807356211843589751116110477652511672279566428926247148"), + new BigInteger("41945332685105951593851845839403181725987901258063429769257339995392450728766"), + new BigInteger("24296067579767080403247766323431204628341605710487447431323947636125286730412"), + new BigInteger("15881178462681378844988252603563609691162651204658664856493588769950563205407"), + new BigInteger("33027381395215663927148306470841421013404116814305740800948949823021554274098"), + new BigInteger("39278310473084767209787340524936392884387815060990743323143945308386189000820"), + new BigInteger("36914830105593239127583246606078015086694578878061417360363710472659792271157"), + new BigInteger("2471481831227881021689006198592503194795082772689986463565415296171852015386"), + new BigInteger("10133170919569185596470854926690039229735632740212998846069400800395437949818"), + new BigInteger("13713875128407368240685505357662717227751490836079655538057610707920043576169"), + new BigInteger("8342666644640774986634432327796294683569398370446186977217700283927741456745"), + new BigInteger("46601389125814748868096111624907238097032545985765609175268428943258314495300"), + new BigInteger("20955390743109511563797223108807741951396100480021156649651505770632943438749"), + new BigInteger("30784566406743698397200754777301033281231860349200935908047757137616877875074"), + new BigInteger("48343196439030272896030042717039190414055291776286919553358305329065060244544"), + new BigInteger("5454630884154432785537568532823077194524789618913833351503828005963129645447"), + new BigInteger("5929264687259766357446095238429932392315604113095822327000589827415320983004"), + new BigInteger("22075444908821639097706881947036304396835729534515628434816919715415538390017"), + new BigInteger("25941058816975140552446994550948593572939163972016393579803457030200129476973"), + new BigInteger("39776348414428957147819346902864822521632016599308432283712625663034427240337"), + new BigInteger("7416720880414633042939600412231360970614004283597614937824398530497243499212"), + new BigInteger("27759512177446113435859126093069895419463054324674208616122176370583357562941"), + new BigInteger("2693390255841122228782459820336527344026453452088174693463152401174043438469"), + new BigInteger("50367239350666539482528955684311280608817276753868085587890812549436189586564"), + new BigInteger("16174733649048109460569124327899128868049112853807486992529031028618670502840"), + new BigInteger("25032516686620026063532769674876936116496163673410980298313095252836905833243"), + new BigInteger("29144403930621998939944109351403497411548441156029659945515675350299265094466"), + new BigInteger("2003270776024057925128728348175382837282431082428047352264694823915738934597"), + new BigInteger("33363216671247018657387321397537436143187354110057266627888117938607035196831"), + new BigInteger("20203086474546098412356910533884833744816739556295954278635367853784856438617"), + new BigInteger("42960220771318412318176969631346524408076008158165832346168142557674200614679"), + new BigInteger("6311431299350400649257553117850994107778654765725553469026713480041524237057"), + new BigInteger("20356164198757608998824195662812920762417225019317083164408248459556033087792"), + new BigInteger("50934696509775059306730966013034554090787668615778167832259926621090584698298"), + new BigInteger("12540543785093585171832085015032615168496292565469198040103631290639480719638"), + new BigInteger("7087832377964131545651220267742883342179930832350845193376391176592931716961"), + new BigInteger("34984411233898940973869087861225504483500912780307024595154545196097892807889"), + new BigInteger("35766364158306764887416108757297765472332147961010533956614913565935878448984"), + new BigInteger("1765971701998656161486995693692800538505518481763639488010072221442068236951"), + new BigInteger("52296260704967533238281867983484652098827616020272035805695017707768629021210"), + new BigInteger("4935673489774322197628160742241883723281125866438378640636969542959380659457"), + new BigInteger("49493374663267588751846054378343301708694531580092984346087290317742537210902"), + new BigInteger("11234520985865325412206403291118519753189986845681526796638090446788348697652"), + new BigInteger("24240566602759984788029880030276085623682320979885122363103446030346976862554"), + new BigInteger("45173673056688650486124798353267048676515652881324846851443098010775612892322"), + new BigInteger("273339079894952168974065527137723282564095652951909656957160946114792896627"), + new BigInteger("4470325051640351957976738782642661997153601739638632363210829100051811744274"), + new BigInteger("35146154431885107533179241729875580217482204780231937987130147605583867466092"), + new BigInteger("5623976303155942456710618286519758761204923686926813378548021075733755166889"), + new BigInteger("24016465951530015578209275233668961482322584131459513288081598210134015257997"), + new BigInteger("17969920097176891022415687639709999939084490545645205326481661860931808113029"), + new BigInteger("45152206508674411747856285000257938228137174933577379726580072509850619926251"), + new BigInteger("38945634795250927360607537392732805897873100986379288027606175928019977509609"), + new BigInteger("32851666289693613044889283133849490343674968726730793059165429991055922454070"), + new BigInteger("31944620853700630151347751910587969550223781655480776781612692884058563662268"), + new BigInteger("25256966274452535017610572446887439115046074651331211781708168773655007778872"), + new BigInteger("9486939021502590608732001628331695421223550406038486802197261945175668785507"), + new BigInteger("39459143086960362426927505137137876218390935544236059938922871880000296175208"), + new BigInteger("31894450224048346260322339655447950546670422421242715439734122749915296243605"), + new BigInteger("26892539091318428420931225040417651442139701587930804697886023619431558542747"), + new BigInteger("2542844944718735302766446637202404427628413878092734865912744553984157161261"), + new BigInteger("31883859221346313107414474846252752604992097590133961842848913019073014153010"), + new BigInteger("51303361359653464050006771537341226976539604964205923399469614564706008834052"), + new BigInteger("51171387502764330562774849667033034283056080450385872897204773223645085369254"), + new BigInteger("7237091576916241695047293084522141336268656276386088021954481852199921973216"), + new BigInteger("25026554458962841467968682601680143746537618788336396538569095145280445662154"), + new BigInteger("16003513886762983460717836271035484656754723355114772159990269505739759600774"), + new BigInteger("20742179979178809796122395691368538694837598010689782796398715701486525085958"), + new BigInteger("44785832974715571208383539748048195425158621451201620091409304675643540484444"), + new BigInteger("40997683756979855969631370242290487603852436449608298499325558394715696204831"), + new BigInteger("24039577999618876159836452559464600377553684696598310542830185648570694947325"), + new BigInteger("214991500380221402745874275507138825943309188151683861156767017258335759518"), + new BigInteger("37648944229324812379904445632193391903358473357814505256571234492472677352375"), + new BigInteger("33262001091080721927187326829375441597312853742311915461357184164050334176171"), + new BigInteger("12889759088432190033171086881844675377815686311282488955569491035800531227592"), + new BigInteger("38889970121432469903433846063190552781925277874128916432889442865031400486457"), + new BigInteger("9686759546395317438502700818478291413888291261781927399197594299119600593872"), + new BigInteger("25228839869827315437841994432860023863461613471517457235105091951188556007171"), + new BigInteger("29251067411858749210993269168637503659802522399342640488863629751155422442084"), + new BigInteger("40912660681512278236165911366927220401330409827994264103091984300131586078341"), + new BigInteger("12796501909444494709088656380507035418412240267936921974592450125220369752821"), + new BigInteger("41489997591227135571666436387925119767986380278590920811343183082128452793080"), + new BigInteger("21497862265009693334292006570547451455021214638930393134366176167326805799325"), + new BigInteger("42759488993366187559528022270353477068325476435317366129099617149236057994173"), + new BigInteger("51812786435352958751631482409057671996557140765865434087196139886155873550638"), + new BigInteger("49668984917578993057336571483567900930503120626539459296975328351727319861276"), + new BigInteger("16647828498038646540925328826301561929374469486623027976723819473821480409681"), + new BigInteger("48148303340548214354795067112758174231010308760482898449349672592745234924387"), + new BigInteger("40514099213939369482769058963482609316155051560990264349668700968914554718236"), + new BigInteger("36567947302783543506732234132138195442155777559454242003814702099955749246290"), + new BigInteger("22396816925035795192842094319757131771178499933587237012855640944068186589937"), + new BigInteger("47761479716265566311036142819261705369735044145214592608213591050556455450430"), + new BigInteger("13277094590686127307617107451297268367321013828763858520220510028318248040673"), + new BigInteger("6273610774394348396010704017556554992266752629801490457323912355626787108751"), + new BigInteger("47394279615623798760617602748864924711531390489909756029248999925570450315302"), + new BigInteger("27952252793623580780344613559829677253211432925530630621608481053048520434744"), + new BigInteger("1683222943011658234228486862639342402730538635204883039431226239924268835592"), + new BigInteger("6849709550515639669397513895396396226183305237153796793058311861850242817732"), + new BigInteger("51524350017816629912679960748295545024593637560633508281874724597080573807830"), + new BigInteger("26590614177194547630006347843068513496427790322854759433492355517360208924714"), + new BigInteger("31548830001396651725711310298465958490865636855427227043617585502978053092924"), + new BigInteger("14291568473806392803367440164088272381690062239638560607879858528716058147676"), + new BigInteger("21146452903160991922099734199583866923318964586815062550024895407430164358523"), + new BigInteger("22961005724583382013438450487662047962072123198815308647967555251332825175693"), + new BigInteger("4752908842318626074338926279870993084957055641402767877988223199262408017438"), + new BigInteger("41544523600430331260332604149473035199994864893327747257504064038791086157408"), + new BigInteger("17323878296591859990733132832893641096022161936583121997952997880406237212813"), + new BigInteger("18014582744613086697405046476881081314871698927785490238333612330034405321202"), + new BigInteger("45325447140824171211209633262297712878556500592023247082629492785769121758434"), + new BigInteger("6192753434333002929210820794040779560623421075700800400752599138519650269040"), + new BigInteger("12937001546279985738495952624875312380127801527837660882855310431015537184413"), + new BigInteger("45991618799696924909840068913271150748052998998510820293768267349781597832497"), + new BigInteger("37441188106719457933929221474454571110916912448355945524409576665808556247872"), + new BigInteger("49875923679586708113406579244909793162425404239213510953269412337363307325571"), + new BigInteger("15051465698071304017966667797323113094420513709580063806706433232853573089040"), + new BigInteger("10338905189138871748742400929101717755982978259187828256039071250817040249017"), + new BigInteger("40261933448177008341539991920645739011692467645144896682394869561245899318641"), + new BigInteger("38346498339252184147870281431364733631809877281747451440216067081256241485418"), + new BigInteger("6209216396715641040468803949857167055175110420218294975303260728579180870134"), + new BigInteger("25923422290512595808420551575642237631007497169886590851128840338102194873726"), + new BigInteger("11953618934086915505672657493115697182858104796786340137294500949047339928290"), + new BigInteger("48506710952023206646326838201389789459004051035511888474426942257560405427104"), + new BigInteger("49584811575438811511092715559885015474424100729555178730940640525393341823572"), + new BigInteger("25222528947373923151054372702664425173210441980263130389325557963853429239320"), + new BigInteger("36212452941316997504575803214309342413443151488267891949906815090453746563323"), + new BigInteger("19548334171603533109137618032918088438321356008712800140019849908969476369140"), + new BigInteger("13369714008256347363334888026585995433724817786797528430136744458743428376798"), + new BigInteger("23153174875441426069922538845839074574095797738892298576581895020444392853731"), + new BigInteger("19950632315767750645780485212179021291844439659606854957365124208057044477001"), + new BigInteger("4990085320684307481424051057758258811192003289472239932032551966513564492664"), + new BigInteger("29810043862384409261569733347989054089853302964778668946432779952952625186706"), + new BigInteger("10937492441648375945337911315608624372433158520395209903090712138844575570844"), + new BigInteger("24981706249730491732129119057314109520549309496394969130105355950186024721860"), + new BigInteger("10498082524469215029826843019306692952360905490979497919767209022386939911216"), + new BigInteger("15682375221169428458922809183562392617423770660027773228464622792081026981791"), + new BigInteger("41914385147673242564111169184735297479310144571630342213035237856939024640011"), + new BigInteger("39667818743665708661866396692813914317148400284941420155363896112617842800421") + }; + + /** MDS matrix (3x3, row-major: M[i*3+j]). */ + static final BigInteger[] M = new BigInteger[] { + new BigInteger("27854988750630959170337239780597144027224715023811960992659706878268355039181"), // M[0][0] + new BigInteger("25146695260744508059100624982461970690166157722474767565243652164077487269055"), // M[0][1] + new BigInteger("20045359041216123667749848881863965260443684681509271093016182932435520519586"), // M[0][2] + new BigInteger("14489116502293865465195620705098702569149962166993518933952339786917836503875"), // M[1][0] + new BigInteger("13125423966940654332711887575940116829944663267413330181877013057693186361539"), // M[1][1] + new BigInteger("37781904496949962127477230973432217892379931214289750852498713884075794707207"), // M[1][2] + new BigInteger("13626913895298938265545264952401615832299228269982032679076937571883280705196"), // M[2][0] + new BigInteger("1961062001717124873779753860369853658060849384038305407377314938662537282272"), // M[2][1] + new BigInteger("39178371364179396693874733819376491076633720395229958100530484864695867731796") // M[2][2] + }; + + public static final PoseidonParams INSTANCE = new PoseidonParams( + FieldConfig.BLS12_381, + 3, 5, 8, 57, + C, + M + ); +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBLS12_381T5.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBLS12_381T5.java new file mode 100644 index 0000000..9607182 --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBLS12_381T5.java @@ -0,0 +1,399 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import com.bloxbean.cardano.zeroj.circuit.FieldConfig; + +import java.math.BigInteger; + +/** + * Poseidon parameters for BLS12-381 t=5 alpha=5 RF=8 RP=60. + * + *

      This file is generated by {@link PoseidonParamsCodegen} from the + * Grain LFSR in {@link PoseidonGrainLFSR}, which is a faithful port of the + * Poseidon paper's reference Sage script (hadeshash commit + * {@code 208b5a164c6a252b137997694d90931b2bb851c5}). Do not edit by hand; + * regenerate with {@code ./gradlew :zeroj-circuit-lib:generatePoseidonParams}. + */ +public final class PoseidonParamsBLS12_381T5 { + + private PoseidonParamsBLS12_381T5() {} + + /** Round constants: 340 values (68 rounds x 5 state width). */ + static final BigInteger[] C = new BigInteger[] { + new BigInteger("42922313792967571374976493829824820574484610841221983764825540534709773291864"), + new BigInteger("28302257740316577272401993595006404980124669828401302329643489918765776881889"), + new BigInteger("50842257806642118340622940401964374631198240049519817955848765262304709404132"), + new BigInteger("51730456923993647376872964298036393775025846564832238814795423143396105536981"), + new BigInteger("51902141483081918108034625450670181943008753401837093722919875746196087523089"), + new BigInteger("27814354740228244074103694188169683490224529252713561171498091227620998411924"), + new BigInteger("8745116577661864050591506084082972790622401037744651759187676942735129098123"), + new BigInteger("47236962896206828510506804048771638492073756905877493081689239700914886343858"), + new BigInteger("39880868708743396503408941700019998637865704495734270284608264775688995676955"), + new BigInteger("3360442358218264336835984848499675432561013227835687485381420209469757494580"), + new BigInteger("37597111698104920629539718508115044288451488585240123406885288642424580285586"), + new BigInteger("22047171185454483718688821282178859339391460628025678075863839592298125971573"), + new BigInteger("26215581583652814448434017231571968352069657926681778125371821293365890716422"), + new BigInteger("24007975154253752008465684842480863338180532874473255684684003236642874454069"), + new BigInteger("44981653772180681095376202889662159020663937386380323248230019134067559905846"), + new BigInteger("29928577727981185293285746779208032340792970201286503470169025029185236704741"), + new BigInteger("22882250826540003227967041479980241049132138149238270426823261228154653417491"), + new BigInteger("26895197360367542928993590374549907848856146276837476199243803227012060362125"), + new BigInteger("5090119893983729264898928910249940335988885302448466189334375590037225960457"), + new BigInteger("21216488385725881993428505181830406108657954965562744305115535868785461455119"), + new BigInteger("23576759345288136037637037331846063096364528618304237803709475537154211378509"), + new BigInteger("38866844583690186594072533301668847407451326680044402867126533388203361702494"), + new BigInteger("48490548647177861504308545283676343914944954996927045961727235674228264540956"), + new BigInteger("50881492192010291303139209767832817296956266557672386434690342844475267925842"), + new BigInteger("2674463449030802079341951899008438505319084350023265057054674545842081966642"), + new BigInteger("9741772150926613869744183808063425400896413203257550529559875829356415455386"), + new BigInteger("51151366925362208844500435413487387687992162897278086347937150095395009583084"), + new BigInteger("2360831377944841711838394341261074945801725660253097716328629931225542191844"), + new BigInteger("4072120023339457807962003019250528115198573394836778615751120471933495083029"), + new BigInteger("2296671502019365883558204050914447395599433712359604989556285876539407837122"), + new BigInteger("48728531491279102822712668471465636730440248287584189875946015031847346679752"), + new BigInteger("39582097867153630795215438375317799606140411474290901864241335292904142618117"), + new BigInteger("20587213631488186069322079396684624567994294205669245218863007501569007160360"), + new BigInteger("10324800063556213325940666127140346456582360042567841654046423143853525134522"), + new BigInteger("22390044438239629064373070298157628223071939098694311624721640894504361620036"), + new BigInteger("51338323093520214783930115835484026467602010469442418836803855475232062905026"), + new BigInteger("42041536388464832183824062215114041993946669046394527589330819356787896018958"), + new BigInteger("19681388861868946110206904459266992380871354486256833646609735012413957324964"), + new BigInteger("2421747160966461773506329982620875949574928175976526061985096424545499582743"), + new BigInteger("38046168655677407061644398871198020181078269633696546617148678758130787606986"), + new BigInteger("2601916569614938490283186144931960149569562837263575460063503430672797489175"), + new BigInteger("596518463053576999205112066221366612513752158722374374483202450445224071885"), + new BigInteger("47837476432180379114516508109167279325442473394297004897834159781449434243347"), + new BigInteger("40159077848181121123461319558159194387257670612193022057174481958737527608175"), + new BigInteger("3040089222839864986514018396059873693832358980714535787104774014738718383251"), + new BigInteger("23487828281443242755404312653858282108005183974697460509581250201582875539170"), + new BigInteger("11066039352938031612275842882141532679539333268725912332853003881744759306395"), + new BigInteger("26005811448181054974903110162127417588916563017078755074247299421399904177943"), + new BigInteger("21350530717186402682979389734680068162727937429737409681496893016488203309781"), + new BigInteger("2759996435314197240352849876586800565485937453748744197108490879099124247498"), + new BigInteger("34127833724731166765303933023872883885864663412821161651284270154514208540741"), + new BigInteger("38659881471066084287620511068528031706929142026320526492494264032908951230125"), + new BigInteger("36504719091827463314959808595556469781453473734723141808563678488712328652124"), + new BigInteger("5056105079010819486140736512899709633464381484580808733549383088685484939283"), + new BigInteger("32496321345787500946356002778346313156613758338829802424741096508892146731880"), + new BigInteger("44152871442986738620015794186648638006333983433835664302178389937455957216957"), + new BigInteger("49129202220269112001937505966441022765708337461009719335453990717502605531831"), + new BigInteger("14947216491974138506367033948284911794349185417684606156568805246544889843019"), + new BigInteger("35332622540613488236002229501358692686257735625153322086466582420650260867494"), + new BigInteger("47749975038009357155811514500545431869507638792611515020150506872509332138022"), + new BigInteger("18669829522044501667551325649219077592859523556156326567344527206751071240682"), + new BigInteger("38566520042758481784958907936843197611359282962847838825899759163651337633126"), + new BigInteger("46161730397980694880274720509622288642526046170336524714339256831495595920195"), + new BigInteger("25754310168348186638909499813874860736279257246504512642585743011138364981769"), + new BigInteger("41885674569668146781684398499169007889270241245151172193487008909135426516525"), + new BigInteger("22179575863522755469743261878462597021724966535770297827526829104459808718405"), + new BigInteger("51979077486840708335632993129256953587218524673970658815818394232582105768879"), + new BigInteger("47721134000437470757987543085172454376756544562117864928689389227252200667796"), + new BigInteger("1555945913188428140352268810038244547619124744510005738297949849765074294872"), + new BigInteger("35908988071466967832156251588349149741879526861327674955210389591894069789720"), + new BigInteger("48080237943642701200687739721198408654336928933995292740165900245479949453229"), + new BigInteger("24807461821521040000073205437941986000551937115920039738749598215616089063349"), + new BigInteger("11298379332190509259684145032521316243481231002749658584654771448699280655252"), + new BigInteger("24454599874398126009053780684441281788941144304724620091413944895676791611771"), + new BigInteger("26854445457945641633560695048305664457372888461778960133713585783959872506908"), + new BigInteger("29359871602496986239361299395525787429634283124727379357137708596239851493259"), + new BigInteger("21407729267846043783593078661008505148398397398807137004704812998679648094193"), + new BigInteger("170295029621550189189815502840120311007491069964982686602363042310703016300"), + new BigInteger("34074373285491382860069632885550401997580693238684418389826306383822033334497"), + new BigInteger("39174931627897472087072558564515547219501536349030970196004065929962884311188"), + new BigInteger("37767156522292878228077172647323700278471437577948962323236477240800622861529"), + new BigInteger("20511017889554842384958472956050035909265115458837606217269788609167898895522"), + new BigInteger("25235982884786000008288652218954858935168353170618732659888117794441258676783"), + new BigInteger("35354287655833801748347518860485469840001051922739813449624458674776112832828"), + new BigInteger("32272313857266221907314960665707906942862430387029188188210408172351567405707"), + new BigInteger("35283412699529798825882352660454131427126894883112745711388806768786354706638"), + new BigInteger("655499390994569150185618438852215747992381954940016412728165900655931580402"), + new BigInteger("15419036668355571667248129615300512179426650986495140828436421414999684988419"), + new BigInteger("10189475781468379129271985413776398201554753896480260831154012613907809630421"), + new BigInteger("7220465601634856929322337131000932964041017017982854771996503667933580760349"), + new BigInteger("31159166537240409107220180088039018370722588790901084338687032827913389291263"), + new BigInteger("17390363562633952428281929808768453787806866158599882148585202728793783921070"), + new BigInteger("13929654191277388416961231681877400303840710087299043065946964612827901928282"), + new BigInteger("22522247315789998756636151394250768825094037426822616368977012018438963784907"), + new BigInteger("42199962274964458657084739288454986111008296929988163583966501242428957584636"), + new BigInteger("15548852623448250727150880873349542048371784103998608119126761762183875541184"), + new BigInteger("4084039439014296265287546390503018540319579486413197184202909291748754261977"), + new BigInteger("37691020173853886226715078659192203619926712820697141785150233513797806005972"), + new BigInteger("47637925206450046849410791586105241393784946147280848482997303938856318611533"), + new BigInteger("829476784344609416475688761568114232271717875828290541682409622271114142790"), + new BigInteger("39590365178110083615132072959321992901527496321718004698015418068543108753703"), + new BigInteger("13280654285977271637283151626440310606155330795427225948374950037038108482871"), + new BigInteger("12395337734767939319698665782281026548370577119976024131949430051161486441850"), + new BigInteger("22570791010370552002276511848952835388103938184759257090200185994848744749037"), + new BigInteger("32732555400242911360273945492945333166092832864387408304989149327973793116576"), + new BigInteger("13694816385624517635571204411249598725977116104393082163959145934737009076708"), + new BigInteger("30808620132948846030281524132603287758302972956026753023650043417660303232776"), + new BigInteger("1194022800413643103687692516121636385533403900767982203231649895021835594764"), + new BigInteger("34685251243436726183406020860354493930175451108688491759584860200460429826718"), + new BigInteger("30878197658432444850813104614417962460626882297459226965750173452553300894820"), + new BigInteger("49468236777860920467952169463492057977364134538491962014129152209083140009007"), + new BigInteger("12474715039859091515512109574000992417829697747818247868055059749956913860464"), + new BigInteger("29493594315881366952055811980234463315193228812726258579723344474408768401172"), + new BigInteger("27755431864949765026871001212412531408007357091856278103722665279367156366493"), + new BigInteger("51492691737954525260621141273866655439534013798964147253066306086971364877312"), + new BigInteger("45422528452728122338840976025141891485260394983917093049687739902284834305527"), + new BigInteger("35969559635244183878112345845186412864355378641384736946814081929858475010477"), + new BigInteger("2487868378609222070279295289840787924745051389651227759929395659675907262891"), + new BigInteger("1080852957937516543035314602908491923207395663122705019438176907354054323713"), + new BigInteger("34721342669541308812242964429096769762742312350395584329090811575673090411106"), + new BigInteger("24745165683864603760080258791808269383782136723575034081581508580242668261485"), + new BigInteger("28810150834282459934293984894519052921880119622525953248920803405669215643980"), + new BigInteger("45002469148385301893551150615059542460738159169551621455387684693489733032058"), + new BigInteger("52348828534320747938729106688097438751143884555643038506616430151705765299948"), + new BigInteger("21844019994062238456395697602405268572226938615352642384379243375269397995117"), + new BigInteger("2935654935457678146318674448312682168905754705371942767342416059241046547942"), + new BigInteger("45915147854791271636159899446250725250488829091667680321566304808007671189095"), + new BigInteger("48939462087883530963701466012740104456280483493234373816554863608341626936644"), + new BigInteger("8133108212380706402124690860634608660284135270961504602703828100469903160547"), + new BigInteger("36486898010628143040273905456894882771310678686579628302629668770704761884085"), + new BigInteger("40876105037112678610862225611027970509565185623874252013259966883264584317140"), + new BigInteger("44683499119481829538635562444069661487380669909501762300480504684236215434041"), + new BigInteger("37112470940797318656467088866243729824462456945106387092394167971825467995779"), + new BigInteger("49593439820126464415668378579463175521259791072297140240150593045400011327635"), + new BigInteger("12504530028465532852319379020500952867661264922355400332631215615425761613979"), + new BigInteger("48270729688050350714555070298312655469265912692371357433243327119057135214244"), + new BigInteger("28675505982706643995137697655529274254406909772702335951601033388942076318498"), + new BigInteger("48026375510662295459657050034632185493560519002383395190101307102877910277178"), + new BigInteger("45442284689412650427625420153514804793366038983044438849904091056836635674081"), + new BigInteger("49521058752861140775587762339833780818167403932908531610860307195535702617768"), + new BigInteger("24925943660205755864449054573647686780870852776240853692786454343939595533041"), + new BigInteger("39157206360543483052594906813554810221884739372743940538192693493402015681464"), + new BigInteger("27257181408509081610482348519552705356758096496587384725791623203390632870046"), + new BigInteger("5880479923856643050375383970606914209098178027300220932480560531725682989146"), + new BigInteger("7441597819558551383175673898290775174026527551542415081974104033657368926721"), + new BigInteger("41767064718598239758648634898731317077313223835125952438141153864833698044640"), + new BigInteger("19159555721290022670084701391853324418609858498338745007068663410139582001279"), + new BigInteger("25153349729099811234356583920175127030917195879660728728896850465415844746951"), + new BigInteger("24518317665794754603348739999362941223698350066793324896912342342683835337858"), + new BigInteger("26599160547672927402871908713764383125219424658539152881232384752927438551553"), + new BigInteger("28655646506012789448846402780532116599595508237874377385835864355978189465333"), + new BigInteger("6025846994041888150840299685781559820379461089888739728152357781952726766747"), + new BigInteger("27169898416735769899697317078623395325716816186899331117064879346736563840406"), + new BigInteger("28081051397818844404667577505838889833745604739270414769257862420873188513461"), + new BigInteger("32179718281379333543553794329902909328682045288799295754518138407069925133067"), + new BigInteger("52398100698479246176814617500490112955223013503911337383866297447182901282450"), + new BigInteger("30111684343812815414259952069913249341523719181721307350268998527225717719103"), + new BigInteger("26095303211552252895906303696662733893308298837276978503085302260430336745040"), + new BigInteger("19649755871771820179933668352560641888293205022917555274749739020181620261820"), + new BigInteger("21901719219300983083128305192436323831753383615335716940965764371700703376032"), + new BigInteger("37329661298099443902487472735697274825373999780687701047295686671868982908948"), + new BigInteger("6088624897716336241451753685758125375570280779722476343924413376635815755020"), + new BigInteger("42748970105701660960253282164979057701441005634306247097166770591969593073333"), + new BigInteger("16467722625559225540008712142787868025264143252682210533589863531242829518670"), + new BigInteger("16154135142642868505554655305176661754050093605113069122785049234068042517460"), + new BigInteger("29457127550202170681211540145584479084550677438465052429036849500538204613824"), + new BigInteger("14199718288773909449288202573218716886343124357789201800910971780207859025307"), + new BigInteger("5157388820520790605431634999350942398953053674460017480683659964026802286649"), + new BigInteger("21949123692100398355622516709875483399402734221442011776361507741070508617935"), + new BigInteger("51569702408328847652617181375063880826461440927343958301804611432682567887507"), + new BigInteger("37924697719471396737652755920011302298169530181379105147688348676475039082063"), + new BigInteger("35617251332317317443420499430581224416005688202153940358692528969212699633144"), + new BigInteger("42925341528861570072502293877264239677663528404787652364216735185821083332197"), + new BigInteger("13327410574609322401644786933630090607942843503190162027001035024177655757134"), + new BigInteger("21099311596235992777454023082461539647641068368635455272705238527842394364362"), + new BigInteger("50531032857680667358302587898885534897104746930691536068629365818943115905118"), + new BigInteger("24296566469824942232344090971813183352446169620077932280052874595759629023778"), + new BigInteger("36595343826739924499154350873011461482823037089807273707861051908699163824157"), + new BigInteger("28845166882844413084101655275998238040142673311110453760031371374424226377202"), + new BigInteger("25119898814803922006238202500837072541835095235836996633024495613884951295412"), + new BigInteger("47659376729707675373710279544302119567124066727162357243805699421906390844285"), + new BigInteger("7366907781358617891942114393884201174548604740608773117750272028128693913218"), + new BigInteger("7919234226386340422172994317500116187927265684826031538137461521741544757156"), + new BigInteger("4316820823974139640638544359811306117312824697617601247689840995068644596113"), + new BigInteger("47849883877007746726625180763401830414272207703679015661772398172858801403425"), + new BigInteger("39372612014446894453897911634061788424205618818818638977932671292680793603395"), + new BigInteger("35805389516220135304662171330385504946855599453624931965343299680334556506074"), + new BigInteger("11313759568684708266209208487035773277188908295438831519219317797322621290383"), + new BigInteger("19703621862298658615656090057352676191172314794322642654259239013859079247465"), + new BigInteger("43845375928562544266328714166196418577811248975808284840785297333784850493626"), + new BigInteger("34648614823397328932042852286351976712611862145358224102223584372787934725419"), + new BigInteger("47448371881499703496214356591609646772124445722731058603132783964553778209035"), + new BigInteger("39296249522588191248599942317064010195252700269701187215104944783278925684044"), + new BigInteger("36846728068579514906377501181427568057180308916936085157587872448894956032491"), + new BigInteger("26544181462089906371490745579267197564453033300211124483692372523200000620312"), + new BigInteger("7287956652046780938132271313658813305472032671782313388304267927792314503830"), + new BigInteger("16965211956848387486838725074380709308263293911806264770971450521943515220773"), + new BigInteger("46064018004447293928964727235629881712479736223584170975087501816707381807258"), + new BigInteger("6043879161857537006317778662601963225767760287247255931925168763299650820374"), + new BigInteger("6428652967008301593264115663183314697350810859112894564728363293623383069726"), + new BigInteger("28607132102642514473826807562838350256207127606208442143254920050815112431760"), + new BigInteger("49470179893671197104959546709297687350237011435817718717282556704371336842855"), + new BigInteger("48427657916531736153738472565004495166466200244840282327017274155055019237148"), + new BigInteger("30737504299969550549304602880203946017378302263272066848324030626510012233213"), + new BigInteger("42340565620813948733057226303860606591377956906067356043980703250223152795394"), + new BigInteger("13886874619714088306786567700914636477304611137817127449581846318515602523369"), + new BigInteger("9763330237264794747261828670882391045402065845562439457921156160264290005312"), + new BigInteger("39597050751466550711248724604410871811561809487601357502028896926438936418530"), + new BigInteger("35230154261738802698375107603991248251177243383492194201559301395782936300221"), + new BigInteger("5254497706176587344113400026936225587052114438744342637534271590631303184558"), + new BigInteger("19044046856221092052159145895937167967150689466811150111743315795586851759449"), + new BigInteger("41778134632335923902345779301118007856844516690940747490753650291124174893399"), + new BigInteger("12153465758889464918046925719844039542403388531042742297225581423660843819875"), + new BigInteger("33113006918048558081356353301271523721126682608853986007385441108513481719929"), + new BigInteger("35567245240756521316817315623749099270839088239724760888148212593598187346542"), + new BigInteger("244386596788473055776472014558950272777145165316583348915467634093556197384"), + new BigInteger("48704785906634912723191632056149019895895793538987564011170548785601228143745"), + new BigInteger("22750619428211123569497479593155915712130836736167067843929706334446072089681"), + new BigInteger("28531181388654550471422179601209125744252466649689091928852476315474213290521"), + new BigInteger("6208035586336699304257159815998594114369017326372816225432017249408279130121"), + new BigInteger("16215269567456340123466373345419628916132198896580270581639398831267391304577"), + new BigInteger("48993376238832835343348131146605336528373970784303835800075887805415988969017"), + new BigInteger("39640788385989579506781019161081950702722179074772901754261957252733608264926"), + new BigInteger("9679021273703701630388545837215535661329927950159241249212038875756016191270"), + new BigInteger("12266275639894906271516958523273735875886691350832320220539021727276336647755"), + new BigInteger("36770876110173476886192400313833231500325662082768919673064306023053484592467"), + new BigInteger("10044611204148888423924990125355150123966095100028110088668333379680227368617"), + new BigInteger("41406398091072777438875606010694590967219636559942610748670645152213309659986"), + new BigInteger("37158214039556055925877363403298635287293629692430828820338852242214865790759"), + new BigInteger("45330654217757249484850505513740751453610266041233871615592826916173415277469"), + new BigInteger("43885900062879422430002456870527901470369679348293679011163721658599203099255"), + new BigInteger("41590038332672935771594022097558694578116823193152151576074205417832477375187"), + new BigInteger("17194741599738334705796039233790780400323919253329627960196222581794209082875"), + new BigInteger("7260209767834513223817424934190676480670708964544217788531907754982904644322"), + new BigInteger("6505053603402812227079582781800758284052010524169460913240154688199700661766"), + new BigInteger("39369109034790899135555840328741312841017475334877764025718110034536244123380"), + new BigInteger("11938371888583342546785009076150105280546046346273951786491218502429093445264"), + new BigInteger("4295309845730451296181308983351940492205939744465890881546717576423966541271"), + new BigInteger("13146030910644666932945447443626524380252577696916972134551113023480137938181"), + new BigInteger("28129901929298685350044428854007820798138881423971015831730232568221653456995"), + new BigInteger("19535774399128308829601201464451708479586983720198471538427149820041298674252"), + new BigInteger("19594432062915342025437755016855972511987651776815786204357275511224565136381"), + new BigInteger("10960819047440892557611077583496019848937993350447073672279379906824990280503"), + new BigInteger("35283762853119675554663376875072518315155452136695524532006971545135760260552"), + new BigInteger("37510421561266191766977832721635339579110255843137915280330210601210570927638"), + new BigInteger("22840681312934425080834669487812447928793565745649916934688148951691369318777"), + new BigInteger("27615943945816017367847477043866124800616936588477831566548974052792520649841"), + new BigInteger("13379275696676433943234387774896107678823990431683805579659414403018328432395"), + new BigInteger("16001753025710750108639598994122660055079138956709392264822320177580490046720"), + new BigInteger("16173492372586906483925706830912486305666235857698756679680917758516443247468"), + new BigInteger("13839576619421708041146430075944766604230116877707327594788037919698403512183"), + new BigInteger("30357785555444606274067511681297566261128913717819057298628598900943460966366"), + new BigInteger("38462879893637676910079282160672717554422955352401688882292555779826787935184"), + new BigInteger("8710518761932682065731906209775531189852894391144267316370858051896402163074"), + new BigInteger("50843738522289555769366963802969590663344415786802440067480249302608577948718"), + new BigInteger("18470843474833231409340338548473730084933948907196689646759519570693692403141"), + new BigInteger("47109501920799882114112468737886791091935240981272710102827737658089279077034"), + new BigInteger("38556255208528119099567010763557883728614163926515959770940130580769022486941"), + new BigInteger("29266603148391412314685667800337792222331718422170123247359994566039541462348"), + new BigInteger("34916968948584102405359327596524836249424678006070041558781982828028942994441"), + new BigInteger("43696301304688701163473513374985417711731072634178229445962689432980783102135"), + new BigInteger("23335115856847746658482410284274830440173003524359216570356197102366978599591"), + new BigInteger("27045943747276512234455970898894234407567484545065706821330181115428986389976"), + new BigInteger("8381840328831032639980118514764099119192202385436130199320675721561517600308"), + new BigInteger("19080805047121728498508031139918201904076935431544375516600288746481644454519"), + new BigInteger("25684793755704742472306573588966652018798081128103929132069187387831716723858"), + new BigInteger("29895319681943670243500129742925712289744095420355559458545112022763972771831"), + new BigInteger("22015165121324695786461704552056531015397778312591606053883606784723723971337"), + new BigInteger("29313014078958026681140867835984243293219138419040471920411688268959918045700"), + new BigInteger("36078778281979450346971523101145264400109792661176888388692661553700037291671"), + new BigInteger("28601105865886625655328255925494851828071439251763131273225908563741708559060"), + new BigInteger("8074372830533049447072343488001617075272284544224728417806885964175713953333"), + new BigInteger("18813587684898277054770964281522247564030871885636680221811923743868247704017"), + new BigInteger("34516920751731769200244520195260189678932531447036758664177071068904575908729"), + new BigInteger("44786320039717066949959784501078105332917786101687069277042961509170868232346"), + new BigInteger("31957784059142574763412897691817478532372920401321537541369787669505623224406"), + new BigInteger("29696210166726078345023672960109779653609898029745710777284484863484537726382"), + new BigInteger("21948986010375768584986718811921030843843947533427211640717763768184336760264"), + new BigInteger("9648909549305095713075802316638141962719613766573953131910309467029004394429"), + new BigInteger("29735496985590043043794450949549413590796345820149588753770210637568506611332"), + new BigInteger("544721243706578658965458269771793556308491650575540684812630904495864841497"), + new BigInteger("52118886587301679423411073677384417964080552896755481841492663801175049466341"), + new BigInteger("48137316764217621840063656399652697944034554508930918087258670202546445578084"), + new BigInteger("4734933816580210837325304059998229777515445040148346695346532449237381253152"), + new BigInteger("28486989070415612986197227221880447976072277136478943878239536159399550422164"), + new BigInteger("27160492546875167678696226156767309119855060319824617167390119318242436924546"), + new BigInteger("18105188892066410185382624969868222686221133916835722405837404275276039117566"), + new BigInteger("11481306491798271660081752052402210016243239421679607818031084293721501119019"), + new BigInteger("40393251777114491513378545438086792097061036722666914747971739133057036299839"), + new BigInteger("28114953386786590862747833094414051759246035819861052241337635435050949072615"), + new BigInteger("19261866466575572790091155421349684699324070517345856825261903608243515972930"), + new BigInteger("18894689598766885578199307969538153087802113550291192413517316624354348365196"), + new BigInteger("5314611972176607563480910584208883796405600214648831348676877064121009168508"), + new BigInteger("52369225078163225980054090962358663333208476840027259553771678164185579331188"), + new BigInteger("28927007026351852600969103168617435341865200275645868370938004175015791264117"), + new BigInteger("1364382317273512311083732944157451034862802386898673796172490831083968047908"), + new BigInteger("23355247730783220623369890467828870283280516883545019401528855692566899887987"), + new BigInteger("11021077027773443668377333156740641408373203125163932044804701998613584857218"), + new BigInteger("9264529035376136354651565273610160985220050492414859985609567001203265745403"), + new BigInteger("40220105168075602800583020496379140241253409254410931243213080502771396442969"), + new BigInteger("8637184087084065972542480998665633761308817780226817682065294826889661445545"), + new BigInteger("41525270223326716718450401551443286189518495686592966587195520498486983955799"), + new BigInteger("1095305749314941228484681584962027093136089804278123892929331040864225887424"), + new BigInteger("33152780121988796388656949678212356967933311458106042451455112548282630749286"), + new BigInteger("1773357973411122053907288771147904853612512676473156465187095418140878605266"), + new BigInteger("24493532543420357501986009393358763127298492843630490250957368283630459893333"), + new BigInteger("45200574577174367185647989997153382833186293373225776883717264313656115443429"), + new BigInteger("50824846896933763757155885251447824618292919081341551546079128373157122253025"), + new BigInteger("17284486241135003665858209437041100159825838887238706543640219259497272643825"), + new BigInteger("32343411324437790462813033199347116353823971987653105122309757293250188224654"), + new BigInteger("11520413810646435900689942613822844591447401345083670911643048885050234227334"), + new BigInteger("12774814663821279145348524402433551120774744479145349264538203656346757143093"), + new BigInteger("29771461804832255363806057125384848079350959087118737301254381187498091583299"), + new BigInteger("5531078036004245158987488598380328784011519537865982105712298962519021920696"), + new BigInteger("37503953324414933194821935202318874897425260188462363415660635174818744775502"), + new BigInteger("7700495694923966463249073825765016397038946473387866251866408902065822103159"), + new BigInteger("51504630226740052140605352869778214632030587332794836509602811790041597255887"), + new BigInteger("46267130220240641113289500222264503676444688341726986377472495336785918557344"), + new BigInteger("7299632882100116600897785537439234988149163589953233912406535963813923177575"), + new BigInteger("20500697411689148285705022919410161905965124087205833607626770061897451430898"), + new BigInteger("49571521322702870459373390149463746801489218717506921565896258341911714891265"), + new BigInteger("29909344512894001953004947714296743245967643420836489525552798567759012598529"), + new BigInteger("14252602509578357988020771443614238431076870083029697526630645864969296975556"), + new BigInteger("27012277783034320080299181661403499326150265830326471490511245824880834860221"), + new BigInteger("27750282419578450959190236992213007450294497911539434469966369421420363603229"), + new BigInteger("42614373576402187289022466339284177449405402376589455167970564500296594388585"), + new BigInteger("20006199296023759364617826125148108766005707432756388093109733858791197267441"), + new BigInteger("5420053916918232274210514723338743447926360145119696399708644438410278532948"), + new BigInteger("5190239522884226025676231287553440164705911022707418071341902470130232910018"), + new BigInteger("2053199993926037215306051276891716783489238540568418789353520811989981426927"), + new BigInteger("49679393088197704490747348596876258048640990025765228021255708282703031889600"), + new BigInteger("7760466581680969670458880432119074820234268099549010459347857465912965947068"), + new BigInteger("15144318554845541795085779455566972737840244662807028305981386151725283435633"), + new BigInteger("28767883386960215151999885062102867304459706855175587964545863568378041170809"), + new BigInteger("30142961368535895897624424420896206743894076178319495878518883300020227919494"), + new BigInteger("18661684335244525450863137880439284952752933903605241098911940295539539482316"), + new BigInteger("30985207421203458612870612913546028095159963080238543041350397070532048903115"), + new BigInteger("37828944015658704529393388922463626723788863429776394212426281357720792438718"), + new BigInteger("14693957365678181736684240167514062691294029910374460517633944716810061875149"), + new BigInteger("14914545936688999105770505226180934246473903378333938681449476850824349566154") + }; + + /** MDS matrix (5x5, row-major: M[i*5+j]). */ + static final BigInteger[] M = new BigInteger[] { + new BigInteger("24092972918862653083323576417625818422607503465576402267126821700189855072519"), // M[0][0] + new BigInteger("30968461747434443794758535245320757535621950569358274960353192815297842516051"), // M[0][1] + new BigInteger("40006600632842591273412997293374413722379234009593169103082780559738392303072"), // M[0][2] + new BigInteger("30700681840877782649728971856637017184537276283124209684014167799005728471146"), // M[0][3] + new BigInteger("3392768664815602318874546758394777071500828785884640529573807666214025153528"), // M[0][4] + new BigInteger("31588991388060922810675259191074453730559659471287020221190223196694684185297"), // M[1][0] + new BigInteger("3705925368981593339934770240122257785687880838694631813779284428648138538778"), // M[1][1] + new BigInteger("12537648423279909299980517665153308195615557227993412620969334604080805700720"), // M[1][2] + new BigInteger("1608643450558022778333143009840463808910800056485158255467512956986229423196"), // M[1][3] + new BigInteger("20734996960431302260620940905125893019911420599236570883734718491491769581611"), // M[1][4] + new BigInteger("34178659998518757657865859540997226795709054014291659241938411814965618628104"), // M[2][0] + new BigInteger("43589969788624983088529520055810011794179382777966574313030775664804389710196"), // M[2][1] + new BigInteger("7258768831209586444522930610739995999032653429443113424321232372486113883006"), // M[2][2] + new BigInteger("20468581904797107574385303727218791734470700833927394734670623884064665596011"), // M[2][3] + new BigInteger("28220252081277806808455947328867320496119967051151743372624713746318256287985"), // M[2][4] + new BigInteger("51324339565517678851929131183784782503100124711358325571125926202432733320400"), // M[3][0] + new BigInteger("38807823748721826562655970679390906340783577589095465575401807200690107678135"), // M[3][1] + new BigInteger("6792168445428615486802253633971037787575470695487830985970486131403438477957"), // M[3][2] + new BigInteger("44688488767017991522832293862978328704602091382265927559112168639284711867828"), // M[3][3] + new BigInteger("38890433888908689792438450212553171547233162384606781525859230566629702357713"), // M[3][4] + new BigInteger("31548368229875143438711930450386530123997421783931894644914479011174275245877"), // M[4][0] + new BigInteger("47946107661823090818360638896749751346303671622997721002300491520734116653551"), // M[4][1] + new BigInteger("141228630625056922390088557213393379872243083987647942541084575655893454168"), // M[4][2] + new BigInteger("4495743004724131218962934350469562268540689034091574648772657835433296146243"), // M[4][3] + new BigInteger("21458625847018303728559826760300696859599894289163915788753282280346777896455") // M[4][4] + }; + + public static final PoseidonParams INSTANCE = new PoseidonParams( + FieldConfig.BLS12_381, + 5, 5, 8, 60, + C, + M + ); +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBN254T3.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBN254T3.java new file mode 100644 index 0000000..f39842b --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsBN254T3.java @@ -0,0 +1,238 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import com.bloxbean.cardano.zeroj.circuit.FieldConfig; + +import java.math.BigInteger; + +/** + * Poseidon parameters for BN254 t=3 alpha=5 RF=8 RP=57. + * + *

      This file is generated by {@link PoseidonParamsCodegen} from the + * Grain LFSR in {@link PoseidonGrainLFSR}, which is a faithful port of the + * Poseidon paper's reference Sage script (hadeshash commit + * {@code 208b5a164c6a252b137997694d90931b2bb851c5}). Do not edit by hand; + * regenerate with {@code ./gradlew :zeroj-circuit-lib:generatePoseidonParams}. + */ +public final class PoseidonParamsBN254T3 { + + private PoseidonParamsBN254T3() {} + + /** Round constants: 195 values (65 rounds x 3 state width). */ + static final BigInteger[] C = new BigInteger[] { + new BigInteger("6745197990210204598374042828761989596302876299545964402857411729872131034734"), + new BigInteger("426281677759936592021316809065178817848084678679510574715894138690250139748"), + new BigInteger("4014188762916583598888942667424965430287497824629657219807941460227372577781"), + new BigInteger("21328925083209914769191926116470334003273872494252651254811226518870906634704"), + new BigInteger("19525217621804205041825319248827370085205895195618474548469181956339322154226"), + new BigInteger("1402547928439424661186498190603111095981986484908825517071607587179649375482"), + new BigInteger("18320863691943690091503704046057443633081959680694199244583676572077409194605"), + new BigInteger("17709820605501892134371743295301255810542620360751268064484461849423726103416"), + new BigInteger("15970119011175710804034336110979394557344217932580634635707518729185096681010"), + new BigInteger("9818625905832534778628436765635714771300533913823445439412501514317783880744"), + new BigInteger("6235167673500273618358172865171408902079591030551453531218774338170981503478"), + new BigInteger("12575685815457815780909564540589853169226710664203625668068862277336357031324"), + new BigInteger("7381963244739421891665696965695211188125933529845348367882277882370864309593"), + new BigInteger("14214782117460029685087903971105962785460806586237411939435376993762368956406"), + new BigInteger("13382692957873425730537487257409819532582973556007555550953772737680185788165"), + new BigInteger("2203881792421502412097043743980777162333765109810562102330023625047867378813"), + new BigInteger("2916799379096386059941979057020673941967403377243798575982519638429287573544"), + new BigInteger("4341714036313630002881786446132415875360643644216758539961571543427269293497"), + new BigInteger("2340590164268886572738332390117165591168622939528604352383836760095320678310"), + new BigInteger("5222233506067684445011741833180208249846813936652202885155168684515636170204"), + new BigInteger("7963328565263035669460582454204125526132426321764384712313576357234706922961"), + new BigInteger("1394121618978136816716817287892553782094854454366447781505650417569234586889"), + new BigInteger("20251767894547536128245030306810919879363877532719496013176573522769484883301"), + new BigInteger("141695147295366035069589946372747683366709960920818122842195372849143476473"), + new BigInteger("15919677773886738212551540894030218900525794162097204800782557234189587084981"), + new BigInteger("2616624285043480955310772600732442182691089413248613225596630696960447611520"), + new BigInteger("4740655602437503003625476760295930165628853341577914460831224100471301981787"), + new BigInteger("19201590924623513311141753466125212569043677014481753075022686585593991810752"), + new BigInteger("12116486795864712158501385780203500958268173542001460756053597574143933465696"), + new BigInteger("8481222075475748672358154589993007112877289817336436741649507712124418867136"), + new BigInteger("5181207870440376967537721398591028675236553829547043817076573656878024336014"), + new BigInteger("1576305643467537308202593927724028147293702201461402534316403041563704263752"), + new BigInteger("2555752030748925341265856133642532487884589978209403118872788051695546807407"), + new BigInteger("18840924862590752659304250828416640310422888056457367520753407434927494649454"), + new BigInteger("14593453114436356872569019099482380600010961031449147888385564231161572479535"), + new BigInteger("20826991704411880672028799007667199259549645488279985687894219600551387252871"), + new BigInteger("9159011389589751902277217485643457078922343616356921337993871236707687166408"), + new BigInteger("5605846325255071220412087261490782205304876403716989785167758520729893194481"), + new BigInteger("1148784255964739709393622058074925404369763692117037208398835319441214134867"), + new BigInteger("20945896491956417459309978192328611958993484165135279604807006821513499894540"), + new BigInteger("229312996389666104692157009189660162223783309871515463857687414818018508814"), + new BigInteger("21184391300727296923488439338697060571987191396173649012875080956309403646776"), + new BigInteger("21853424399738097885762888601689700621597911601971608617330124755808946442758"), + new BigInteger("12776298811140222029408960445729157525018582422120161448937390282915768616621"), + new BigInteger("7556638921712565671493830639474905252516049452878366640087648712509680826732"), + new BigInteger("19042212131548710076857572964084011858520620377048961573689299061399932349935"), + new BigInteger("12871359356889933725034558434803294882039795794349132643274844130484166679697"), + new BigInteger("3313271555224009399457959221795880655466141771467177849716499564904543504032"), + new BigInteger("15080780006046305940429266707255063673138269243146576829483541808378091931472"), + new BigInteger("21300668809180077730195066774916591829321297484129506780637389508430384679582"), + new BigInteger("20480395468049323836126447690964858840772494303543046543729776750771407319822"), + new BigInteger("10034492246236387932307199011778078115444704411143703430822959320969550003883"), + new BigInteger("19584962776865783763416938001503258436032522042569001300175637333222729790225"), + new BigInteger("20155726818439649091211122042505326538030503429443841583127932647435472711802"), + new BigInteger("13313554736139368941495919643765094930693458639277286513236143495391474916777"), + new BigInteger("14606609055603079181113315307204024259649959674048912770003912154260692161833"), + new BigInteger("5563317320536360357019805881367133322562055054443943486481491020841431450882"), + new BigInteger("10535419877021741166931390532371024954143141727751832596925779759801808223060"), + new BigInteger("12025323200952647772051708095132262602424463606315130667435888188024371598063"), + new BigInteger("2906495834492762782415522961458044920178260121151056598901462871824771097354"), + new BigInteger("19131970618309428864375891649512521128588657129006772405220584460225143887876"), + new BigInteger("8896386073442729425831367074375892129571226824899294414632856215758860965449"), + new BigInteger("7748212315898910829925509969895667732958278025359537472413515465768989125274"), + new BigInteger("422974903473869924285294686399247660575841594104291551918957116218939002865"), + new BigInteger("6398251826151191010634405259351528880538837895394722626439957170031528482771"), + new BigInteger("18978082967849498068717608127246258727629855559346799025101476822814831852169"), + new BigInteger("19150742296744826773994641927898928595714611370355487304294875666791554590142"), + new BigInteger("12896891575271590393203506752066427004153880610948642373943666975402674068209"), + new BigInteger("9546270356416926575977159110423162512143435321217584886616658624852959369669"), + new BigInteger("2159256158967802519099187112783460402410585039950369442740637803310736339200"), + new BigInteger("8911064487437952102278704807713767893452045491852457406400757953039127292263"), + new BigInteger("745203718271072817124702263707270113474103371777640557877379939715613501668"), + new BigInteger("19313999467876585876087962875809436559985619524211587308123441305315685710594"), + new BigInteger("13254105126478921521101199309550428567648131468564858698707378705299481802310"), + new BigInteger("1842081783060652110083740461228060164332599013503094142244413855982571335453"), + new BigInteger("9630707582521938235113899367442877106957117302212260601089037887382200262598"), + new BigInteger("5066637850921463603001689152130702510691309665971848984551789224031532240292"), + new BigInteger("4222575506342961001052323857466868245596202202118237252286417317084494678062"), + new BigInteger("2919565560395273474653456663643621058897649501626354982855207508310069954086"), + new BigInteger("6828792324689892364977311977277548750189770865063718432946006481461319858171"), + new BigInteger("2245543836264212411244499299744964607957732316191654500700776604707526766099"), + new BigInteger("19602444885919216544870739287153239096493385668743835386720501338355679311704"), + new BigInteger("8239538512351936341605373169291864076963368674911219628966947078336484944367"), + new BigInteger("15053013456316196458870481299866861595818749671771356646798978105863499965417"), + new BigInteger("7173615418515925804810790963571435428017065786053377450925733428353831789901"), + new BigInteger("8239211677777829016346247446855147819062679124993100113886842075069166957042"), + new BigInteger("15330855478780269194281285878526984092296288422420009233557393252489043181621"), + new BigInteger("10014883178425964324400942419088813432808659204697623248101862794157084619079"), + new BigInteger("14014440630268834826103915635277409547403899966106389064645466381170788813506"), + new BigInteger("3580284508947993352601712737893796312152276667249521401778537893620670305946"), + new BigInteger("2559754020964039399020874042785294258009596917335212876725104742182177996988"), + new BigInteger("14898657953331064524657146359621913343900897440154577299309964768812788279359"), + new BigInteger("2094037260225570753385567402013028115218264157081728958845544426054943497065"), + new BigInteger("18051086536715129874440142649831636862614413764019212222493256578581754875930"), + new BigInteger("21680659279808524976004872421382255670910633119979692059689680820959727969489"), + new BigInteger("13950668739013333802529221454188102772764935019081479852094403697438884885176"), + new BigInteger("9703845704528288130475698300068368924202959408694460208903346143576482802458"), + new BigInteger("12064310080154762977097567536495874701200266107682637369509532768346427148165"), + new BigInteger("16970760937630487134309762150133050221647250855182482010338640862111040175223"), + new BigInteger("9790997389841527686594908620011261506072956332346095631818178387333642218087"), + new BigInteger("16314772317774781682315680698375079500119933343877658265473913556101283387175"), + new BigInteger("82044870826814863425230825851780076663078706675282523830353041968943811739"), + new BigInteger("21696416499108261787701615667919260888528264686979598953977501999747075085778"), + new BigInteger("327771579314982889069767086599893095509690747425186236545716715062234528958"), + new BigInteger("4606746338794869835346679399457321301521448510419912225455957310754258695442"), + new BigInteger("64499140292086295251085369317820027058256893294990556166497635237544139149"), + new BigInteger("10455028514626281809317431738697215395754892241565963900707779591201786416553"), + new BigInteger("10421411526406559029881814534127830959833724368842872558146891658647152404488"), + new BigInteger("18848084335930758908929996602136129516563864917028006334090900573158639401697"), + new BigInteger("13844582069112758573505569452838731733665881813247931940917033313637916625267"), + new BigInteger("13488838454403536473492810836925746129625931018303120152441617863324950564617"), + new BigInteger("15742141787658576773362201234656079648895020623294182888893044264221895077688"), + new BigInteger("6756884846734501741323584200608866954194124526254904154220230538416015199997"), + new BigInteger("7860026400080412708388991924996537435137213401947704476935669541906823414404"), + new BigInteger("7871040688194276447149361970364037034145427598711982334898258974993423182255"), + new BigInteger("20758972836260983284101736686981180669442461217558708348216227791678564394086"), + new BigInteger("21723241881201839361054939276225528403036494340235482225557493179929400043949"), + new BigInteger("19428469330241922173653014973246050805326196062205770999171646238586440011910"), + new BigInteger("7969200143746252148180468265998213908636952110398450526104077406933642389443"), + new BigInteger("10950417916542216146808986264475443189195561844878185034086477052349738113024"), + new BigInteger("18149233917533571579549129116652755182249709970669448788972210488823719849654"), + new BigInteger("3729796741814967444466779622727009306670204996071028061336690366291718751463"), + new BigInteger("5172504399789702452458550583224415301790558941194337190035441508103183388987"), + new BigInteger("6686473297578275808822003704722284278892335730899287687997898239052863590235"), + new BigInteger("19426913098142877404613120616123695099909113097119499573837343516470853338513"), + new BigInteger("5120337081764243150760446206763109494847464512045895114970710519826059751800"), + new BigInteger("5055737465570446530938379301905385631528718027725177854815404507095601126720"), + new BigInteger("14235578612970484492268974539959119923625505766550088220840324058885914976980"), + new BigInteger("653592517890187950103239281291172267359747551606210609563961204572842639923"), + new BigInteger("5507360526092411682502736946959369987101940689834541471605074817375175870579"), + new BigInteger("7864202866011437199771472205361912625244234597659755013419363091895334445453"), + new BigInteger("21294659996736305811805196472076519801392453844037698272479731199885739891648"), + new BigInteger("13767183507040326119772335839274719411331242166231012705169069242737428254651"), + new BigInteger("810181532076738148308457416289197585577119693706380535394811298325092337781"), + new BigInteger("14232321930654703053193240133923161848171310212544136614525040874814292190478"), + new BigInteger("16796904728299128263054838299534612533844352058851230375569421467352578781209"), + new BigInteger("16256310366973209550759123431979563367001604350120872788217761535379268327259"), + new BigInteger("19791658638819031543640174069980007021961272701723090073894685478509001321817"), + new BigInteger("7046232469803978873754056165670086532908888046886780200907660308846356865119"), + new BigInteger("16001732848952745747636754668380555263330934909183814105655567108556497219752"), + new BigInteger("9737276123084413897604802930591512772593843242069849260396983774140735981896"), + new BigInteger("11410895086919039954381533622971292904413121053792570364694836768885182251535"), + new BigInteger("19098362474249267294548762387533474746422711206129028436248281690105483603471"), + new BigInteger("11013788190750472643548844759298623898218957233582881400726340624764440203586"), + new BigInteger("2206958256327295151076063922661677909471794458896944583339625762978736821035"), + new BigInteger("7171889270225471948987523104033632910444398328090760036609063776968837717795"), + new BigInteger("2510237900514902891152324520472140114359583819338640775472608119384714834368"), + new BigInteger("8825275525296082671615660088137472022727508654813239986303576303490504107418"), + new BigInteger("1481125575303576470988538039195271612778457110700618040436600537924912146613"), + new BigInteger("16268684562967416784133317570130804847322980788316762518215429249893668424280"), + new BigInteger("4681491452239189664806745521067158092729838954919425311759965958272644506354"), + new BigInteger("3131438137839074317765338377823608627360421824842227925080193892542578675835"), + new BigInteger("7930402370812046914611776451748034256998580373012248216998696754202474945793"), + new BigInteger("8973151117361309058790078507956716669068786070949641445408234962176963060145"), + new BigInteger("10223139291409280771165469989652431067575076252562753663259473331031932716923"), + new BigInteger("2232089286698717316374057160056566551249777684520809735680538268209217819725"), + new BigInteger("16930089744400890347392540468934821520000065594669279286854302439710657571308"), + new BigInteger("21739597952486540111798430281275997558482064077591840966152905690279247146674"), + new BigInteger("7508315029150148468008716674010060103310093296969466203204862163743615534994"), + new BigInteger("11418894863682894988747041469969889669847284797234703818032750410328384432224"), + new BigInteger("10895338268862022698088163806301557188640023613155321294365781481663489837917"), + new BigInteger("18644184384117747990653304688839904082421784959872380449968500304556054962449"), + new BigInteger("7414443845282852488299349772251184564170443662081877445177167932875038836497"), + new BigInteger("5391299369598751507276083947272874512197023231529277107201098701900193273851"), + new BigInteger("10329906873896253554985208009869159014028187242848161393978194008068001342262"), + new BigInteger("4711719500416619550464783480084256452493890461073147512131129596065578741786"), + new BigInteger("11943219201565014805519989716407790139241726526989183705078747065985453201504"), + new BigInteger("4298705349772984837150885571712355513879480272326239023123910904259614053334"), + new BigInteger("9999044003322463509208400801275356671266978396985433172455084837770460579627"), + new BigInteger("4908416131442887573991189028182614782884545304889259793974797565686968097291"), + new BigInteger("11963412684806827200577486696316210731159599844307091475104710684559519773777"), + new BigInteger("20129916000261129180023520480843084814481184380399868943565043864970719708502"), + new BigInteger("12884788430473747619080473633364244616344003003135883061507342348586143092592"), + new BigInteger("20286808211545908191036106582330883564479538831989852602050135926112143921015"), + new BigInteger("16282045180030846845043407450751207026423331632332114205316676731302016331498"), + new BigInteger("4332932669439410887701725251009073017227450696965904037736403407953448682093"), + new BigInteger("11105712698773407689561953778861118250080830258196150686012791790342360778288"), + new BigInteger("21853934471586954540926699232107176721894655187276984175226220218852955976831"), + new BigInteger("9807888223112768841912392164376763820266226276821186661925633831143729724792"), + new BigInteger("13411808896854134882869416756427789378942943805153730705795307450368858622668"), + new BigInteger("17906847067500673080192335286161014930416613104209700445088168479205894040011"), + new BigInteger("14554387648466176616800733804942239711702169161888492380425023505790070369632"), + new BigInteger("4264116751358967409634966292436919795665643055548061693088119780787376143967"), + new BigInteger("2401104597023440271473786738539405349187326308074330930748109868990675625380"), + new BigInteger("12251645483867233248963286274239998200789646392205783056343767189806123148785"), + new BigInteger("15331181254680049984374210433775713530849624954688899814297733641575188164316"), + new BigInteger("13108834590369183125338853868477110922788848506677889928217413952560148766472"), + new BigInteger("6843160824078397950058285123048455551935389277899379615286104657075620692224"), + new BigInteger("10151103286206275742153883485231683504642432930275602063393479013696349676320"), + new BigInteger("7074320081443088514060123546121507442501369977071685257650287261047855962224"), + new BigInteger("11413928794424774638606755585641504971720734248726394295158115188173278890938"), + new BigInteger("7312756097842145322667451519888915975561412209738441762091369106604423801080"), + new BigInteger("7181677521425162567568557182629489303281861794357882492140051324529826589361"), + new BigInteger("15123155547166304758320442783720138372005699143801247333941013553002921430306"), + new BigInteger("13409242754315411433193860530743374419854094495153957441316635981078068351329") + }; + + /** MDS matrix (3x3, row-major: M[i*3+j]). */ + static final BigInteger[] M = new BigInteger[] { + new BigInteger("7511745149465107256748700652201246547602992235352608707588321460060273774987"), // M[0][0] + new BigInteger("10370080108974718697676803824769673834027675643658433702224577712625900127200"), // M[0][1] + new BigInteger("19705173408229649878903981084052839426532978878058043055305024233888854471533"), // M[0][2] + new BigInteger("18732019378264290557468133440468564866454307626475683536618613112504878618481"), // M[1][0] + new BigInteger("20870176810702568768751421378473869562658540583882454726129544628203806653987"), // M[1][1] + new BigInteger("7266061498423634438633389053804536045105766754026813321943009179476902321146"), // M[1][2] + new BigInteger("9131299761947733513298312097611845208338517739621853568979632113419485819303"), // M[2][0] + new BigInteger("10595341252162738537912664445405114076324478519622938027420701542910180337937"), // M[2][1] + new BigInteger("11597556804922396090267472882856054602429588299176362916247939723151043581408") // M[2][2] + }; + + public static final PoseidonParams INSTANCE = new PoseidonParams( + FieldConfig.BN254, + 3, 5, 8, 57, + C, + M + ); +} diff --git a/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsCodegen.java b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsCodegen.java new file mode 100644 index 0000000..163482f --- /dev/null +++ b/zeroj-circuit-lib/src/main/java/com/bloxbean/cardano/zeroj/circuit/lib/poseidon/PoseidonParamsCodegen.java @@ -0,0 +1,156 @@ +package com.bloxbean.cardano.zeroj.circuit.lib.poseidon; + +import com.bloxbean.cardano.zeroj.circuit.FieldConfig; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * Code generator that invokes {@link PoseidonGrainLFSR} to produce + * {@code (C, M)} for each named Poseidon preset and writes the results as + * Java source files containing hardcoded {@link PoseidonParams} instances. + * + *

      Generated files are intended to be committed to source control. + * Regenerate with: + *

      + *   ./gradlew :zeroj-circuit-lib:generatePoseidonParams
      + * 
      + * + *

      The generated files are read-only at runtime; runtime generation is not + * supported because it would bloat GraalVM native-image binaries. All + * downstream code depends on the compiled-in constants. + * + *

      Supported CLI forms: + *

        + *
      • No args: writes into {@code src/main/java/...} of the current module + * (inferred from the working directory).
      • + *
      • One arg: target module root (e.g. absolute path to + * {@code zeroj-circuit-lib}).
      • + *
      + */ +public final class PoseidonParamsCodegen { + + private static final String PACKAGE = "com.bloxbean.cardano.zeroj.circuit.lib.poseidon"; + + private PoseidonParamsCodegen() {} + + private record Preset(String className, String displayName, FieldConfig field, + int fieldBitSize, int t, int alpha, int rf, int rp) { + Preset { + // Consistency guard: fieldBitSize must match the declared prime's bit length. + // Catches a typo like "255 for BN254" that would otherwise silently change LFSR output. + int expected = field.prime().bitLength(); + if (fieldBitSize != expected) { + throw new IllegalStateException( + "Preset " + className + ": fieldBitSize=" + fieldBitSize + + " but " + field.name() + " prime bitLength=" + expected); + } + } + } + + private static final List PRESETS = List.of( + new Preset("PoseidonParamsBN254T3", + "BN254 t=3 alpha=5 RF=8 RP=57", + FieldConfig.BN254, 254, 3, 5, 8, 57), + new Preset("PoseidonParamsBLS12_381T3", + "BLS12-381 t=3 alpha=5 RF=8 RP=57", + FieldConfig.BLS12_381, 255, 3, 5, 8, 57), + // ADR-0016 M4: t=5 for 4-ary Merkle / Pedersen-chunked hashing. + new Preset("PoseidonParamsBLS12_381T5", + "BLS12-381 t=5 alpha=5 RF=8 RP=60", + FieldConfig.BLS12_381, 255, 5, 5, 8, 60) + ); + + public static void main(String[] args) throws IOException { + Path moduleRoot = args.length > 0 ? Path.of(args[0]) : Path.of(".").toAbsolutePath().normalize(); + Path outDir = moduleRoot.resolve("src/main/java").resolve(PACKAGE.replace('.', '/')); + Files.createDirectories(outDir); + + for (Preset preset : PRESETS) { + String source = generate(preset); + Path outFile = outDir.resolve(preset.className + ".java"); + Files.writeString(outFile, source, StandardCharsets.UTF_8); + System.out.println("Generated " + outFile); + } + } + + static String generate(Preset p) { + PoseidonGrainLFSR gen = PoseidonGrainLFSR.forGFp(p.fieldBitSize, p.t, p.rf, p.rp, p.field.prime()); + BigInteger[] c = gen.generateRoundConstants(); + BigInteger[][] mMatrix = gen.generateMdsMatrix(); + + BigInteger[] m = new BigInteger[p.t * p.t]; + for (int i = 0; i < p.t; i++) { + for (int j = 0; j < p.t; j++) { + m[i * p.t + j] = mMatrix[i][j]; + } + } + + String fieldConstant = switch (p.field.curve()) { + case BN254 -> "FieldConfig.BN254"; + case BLS12_381 -> "FieldConfig.BLS12_381"; + case PALLAS -> throw new UnsupportedOperationException( + "Pallas (α, RP) not yet audited against the Poseidon paper for ZeroJ use; " + + "adding a Pallas preset requires a new ADR entry. See ADR-0015."); + }; + + StringBuilder out = new StringBuilder(); + out.append("package ").append(PACKAGE).append(";\n\n"); + out.append("import com.bloxbean.cardano.zeroj.circuit.FieldConfig;\n\n"); + out.append("import java.math.BigInteger;\n\n"); + out.append("/**\n"); + out.append(" * Poseidon parameters for ").append(p.displayName).append(".\n"); + out.append(" *\n"); + out.append(" *

      This file is generated by {@link PoseidonParamsCodegen} from the\n"); + out.append(" * Grain LFSR in {@link PoseidonGrainLFSR}, which is a faithful port of the\n"); + out.append(" * Poseidon paper's reference Sage script (hadeshash commit\n"); + out.append(" * {@code 208b5a164c6a252b137997694d90931b2bb851c5}). Do not edit by hand;\n"); + out.append(" * regenerate with {@code ./gradlew :zeroj-circuit-lib:generatePoseidonParams}.\n"); + out.append(" */\n"); + out.append("public final class ").append(p.className).append(" {\n\n"); + out.append(" private ").append(p.className).append("() {}\n\n"); + + out.append(" /** Round constants: ").append(c.length).append(" values (") + .append(p.rf + p.rp).append(" rounds x ").append(p.t).append(" state width). */\n"); + out.append(" static final BigInteger[] C = new BigInteger[] {\n"); + for (int i = 0; i < c.length; i++) { + out.append(" ").append(bigIntLiteral(c[i])); + if (i < c.length - 1) out.append(","); + out.append("\n"); + } + out.append(" };\n\n"); + + out.append(" /** MDS matrix (").append(p.t).append("x").append(p.t) + .append(", row-major: M[i*").append(p.t).append("+j]). */\n"); + out.append(" static final BigInteger[] M = new BigInteger[] {\n"); + for (int i = 0; i < m.length; i++) { + int row = i / p.t; + int col = i % p.t; + out.append(" ").append(bigIntLiteral(m[i])); + if (i < m.length - 1) out.append(","); + out.append(" // M[").append(row).append("][").append(col).append("]\n"); + } + out.append(" };\n\n"); + + out.append(" public static final PoseidonParams INSTANCE = new PoseidonParams(\n"); + out.append(" ").append(fieldConstant).append(",\n"); + out.append(" ").append(p.t).append(", "); + out.append(p.alpha).append(", "); + out.append(p.rf).append(", "); + out.append(p.rp).append(",\n"); + out.append(" C,\n"); + out.append(" M\n"); + out.append(" );\n"); + out.append("}\n"); + + return out.toString(); + } + + private static String bigIntLiteral(BigInteger v) { + return "new BigInteger(\"" + v.toString(10) + "\")"; + } +} diff --git a/zeroj-circuit-lib/src/main/resources/poseidon/README.md b/zeroj-circuit-lib/src/main/resources/poseidon/README.md new file mode 100644 index 0000000..fcae2a0 --- /dev/null +++ b/zeroj-circuit-lib/src/main/resources/poseidon/README.md @@ -0,0 +1,101 @@ +# Poseidon Parameter Generation — Authoritative Reference + +This directory contains the authoritative Sage script that generates Poseidon +round constants and MDS matrices. It is the **source of truth** for ZeroJ's +Poseidon parameters — the Java `PoseidonGrainLFSR` implementation ports this +script, and conformance is verified byte-for-byte against its output. + +## Source + +- Repository: `https://extgit.isec.tugraz.at/krypto/hadeshash` +- File: `code/generate_parameters_grain.sage` +- Pinned commit: `208b5a164c6a252b137997694d90931b2bb851c5` (2023-05-02) +- Paper: Grassi, Khovratovich, Rechberger, Roy, Schofnegger — *Poseidon: A New + Hash Function for Zero-Knowledge Proof Systems* (USENIX Security 2021). + +## Reproducing ZeroJ's Poseidon parameters + +### Prerequisites + +``` +brew install sagemath # or your platform's SageMath +``` + +### BN254 (scalar field), t=3, α=5, RF=8, RP=57 + +Primary sanity-check target — matches iden3/circomlibjs constants currently +committed at `zeroj-circuit-lib/.../PoseidonConstants.java`. + +``` +sage generate_parameters_grain.sage 1 0 254 3 8 57 \ + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +``` + +### BLS12-381 (scalar field), t=3, α=5, RF=8, RP=57 + +Target for `PoseidonParams.BLS12_381_T3` — the standards-compatible BLS Poseidon +used by arkworks, zkcrypto, and the Jubjub ecosystem. + +``` +sage generate_parameters_grain.sage 1 0 255 3 8 57 \ + 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +``` + +### BLS12-381 (scalar field), t=5, α=5, RF=8, RP=60 + +Reserved for ADR-0016 (Jubjub-in-circuit), 4-ary Merkle trees. + +``` +sage generate_parameters_grain.sage 1 0 255 5 8 60 \ + 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +``` + +## CLI argument legend + +``` +sage generate_parameters_grain.sage +``` + +- `field = 1` → GF(p) (always for ZeroJ targets) +- `sbox = 0` → x^α (standard Poseidon) +- `field_size` → bit-length of the prime (254 for BN254, 255 for BLS12-381) +- `num_cells` → state width `t` (2+1 = 3 for two-to-one hash) +- `R_F, R_P` → full / partial round counts +- `prime_hex` → scalar field modulus, hex-encoded + +## How this is consumed in ZeroJ + +1. **Java port**: `PoseidonGrainLFSR.java` implements the same algorithm as + this Sage script. Any divergence is a bug in the Java port. +2. **Cross-verification test** (`PoseidonGrainLFSRTest`): regenerates the BN254 + t=3 constants from the Grain LFSR and asserts byte-equality with the + committed `PoseidonConstants.C` and `PoseidonConstants.M`. This is a + regression test that catches any drift in the Java port. +3. **Generated BLS12-381 constants**: checked into source as + `PoseidonParamsBLS12_381.java`, produced by running the Java generator with + the BLS arguments listed above. + +## Security algorithms (algorithm_1/2/3) + +The Sage script includes three MDS-matrix security checks (Algorithms 1, 2, 3 +from the Poseidon paper). If the first-generated Cauchy matrix fails any check, +Sage resamples — advancing the LFSR state and changing subsequent outputs. + +For the parameter sets ZeroJ targets (BN254 t=3, BLS12-381 t=3, BLS12-381 t=5 +with RF=8 and standard RP values), the **first-generated Cauchy matrix passes +all three algorithms**. This is verifiable empirically: + +- The committed BN254 constants/MDS match the Java port's first-matrix output + (proving first-pass acceptance holds for BN254 t=3). +- The BLS12-381 outputs match arkworks, zkcrypto, and the Sage script output + when all three are cross-checked. + +Because of this, the Java port does **not** implement Algorithms 1/2/3. If we +ever target non-standard parameters where first-pass acceptance doesn't hold, +porting Algorithms 1/2/3 becomes required. Today it's not. + +## Audit trail + +Any change to committed constants must re-run the Sage script offline and +update the fixture files. The Java test asserts byte-equality, so drift is +caught at build time. diff --git a/zeroj-circuit-lib/src/main/resources/poseidon/generate_parameters_grain.sage b/zeroj-circuit-lib/src/main/resources/poseidon/generate_parameters_grain.sage new file mode 100644 index 0000000..2c4392e --- /dev/null +++ b/zeroj-circuit-lib/src/main/resources/poseidon/generate_parameters_grain.sage @@ -0,0 +1,373 @@ +# Remark: This script contains functionality for GF(2^n), but currently works only over GF(p)! A few small adaptations are needed for GF(2^n). +from sage.rings.polynomial.polynomial_gf2x import GF2X_BuildIrred_list + +# Note that R_P is increased to the closest multiple of t +# GF(p), alpha=3, N = 1536, n = 64, t = 24, R_F = 8, R_P = 42: sage generate_parameters_grain.sage 1 0 64 24 8 42 0xfffffffffffffeff +# GF(p), alpha=5, N = 1524, n = 254, t = 6, R_F = 8, R_P = 60: sage generate_parameters_grain.sage 1 0 254 6 8 60 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +# GF(p), x^(-1), N = 1518, n = 253, t = 6, R_F = 8, R_P = 60: sage generate_parameters_grain.sage 1 1 253 6 8 60 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed + +# GF(p), alpha=5, N = 765, n = 255, t = 3, R_F = 8, R_P = 57: sage generate_parameters_grain.sage 1 0 255 3 8 57 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +# GF(p), alpha=5, N = 1275, n = 255, t = 5, R_F = 8, R_P = 60: sage generate_parameters_grain.sage 1 0 255 5 8 60 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +# GF(p), alpha=5, N = 762, n = 254, t = 3, R_F = 8, R_P = 57: sage generate_parameters_grain.sage 1 0 254 3 8 57 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +# GF(p), alpha=5, N = 1270, n = 254, t = 5, R_F = 8, R_P = 60: sage generate_parameters_grain.sage 1 0 254 5 8 60 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + +# GF(2^n), alpha=5, N = 768, n = 256, t = 3, R_F = 8, R_P = 57: sage generate_parameters_grain.sage 0 0 256 3 8 57 0x0 + +if len(sys.argv) < 7: + print("Usage: