diff --git a/.gitignore b/.gitignore index 5fe490f..5b0ca67 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,9 @@ incubator/zeroj-prover-rapidsnark/src/main/resources/native/*/librapidsnark.* ### Rust build artifacts (Halo2) ### incubator/zeroj-verifier-halo2/halo2-rust/target/ +zeroj-bbs/rust/target/ +zeroj-bls12381-wasm/rust/target/ +zeroj-bbs-wasm/rust/target/ ### Go compiled binaries ### zeroj-prover-gnark/gnark-wrapper/gentestvectors diff --git a/docs/adr/0018-shared-bls12381-primitives-and-wasm-provider.md b/docs/adr/0018-shared-bls12381-primitives-and-wasm-provider.md new file mode 100644 index 0000000..7706dd6 --- /dev/null +++ b/docs/adr/0018-shared-bls12381-primitives-and-wasm-provider.md @@ -0,0 +1,264 @@ +# ADR-0018: Shared BLS12-381 Primitives and Optional WASM Provider + +## Status +Accepted + +## Date +2026-05-07 + +## Context + +ZeroJ already contains BLS12-381 functionality, but it is not exposed as one +neutral primitive module: + +- `zeroj-crypto` contains optimized pure Java Montgomery field arithmetic and + Jacobian G1/G2 point arithmetic for BLS12-381 provers. +- `zeroj-verifier-groth16` contains pure Java BLS12-381 affine field towers, + G1/G2 point types, and optimal Ate pairing checks under Groth16 verifier + packages. +- `zeroj-blst` contains an optional native-backed wrapper for BLS12-381 pairing + and G1 operations through `foundation.icon:blst-java`. + +This split works for Groth16 and PlonK, but it is awkward for BBS. CFRG BBS +needs reusable BLS12-381 primitives beyond a verifier-specific pairing check: +compressed point codecs, subgroup checks, scalar arithmetic, hash-to-curve, +message-to-scalar hashing, generator derivation, and pairing product checks. + +ADR-0017 introduced BBS support through Rust WASM, but later review showed that +ZeroJ's existing pure Java BLS12-381 code makes a Java-native CFRG BBS +implementation feasible. At the same time, a small compatibility spike showed +that popular pure Rust BLS12-381 crates such as `zkcrypto/bls12_381` and +`ark-bls12-381` can compile to `wasm32-unknown-unknown` and run under Chicory +1.7.5 with no host imports. Native `blst` remains attractive for speed, but its +Rust crate introduces C/assembly build complexity for `wasm32-unknown-unknown`. + +ZeroJ therefore needs a shared BLS12-381 primitive boundary before returning to +the CFRG BBS implementation. + +## Decision + +### 1. Add `zeroj-bls12381` + +Create a neutral pure Java module named: + +``` +zeroj-bls12381 +``` + +This module is the default BLS12-381 primitive provider for ZeroJ. It should +expose reusable primitives needed by Groth16, PlonK, BBS, KZG, and future +BLS12-381 protocols. + +The target API surface includes: + +- base field and scalar field helpers for `Fp`, `Fp2`, `Fp6`, `Fp12`, and `Fr` +- G1 and G2 point operations: add, negate, scalar multiplication, identity, + equality, and subgroup validation +- pairing operations: Miller loop, final exponentiation, and pairing product + check +- compressed and uncompressed point serialization compatible with the relevant + standards +- RFC9380 hash-to-curve helpers for BLS12-381 G1/G2 +- scalar hashing and canonical byte helpers needed by higher-level protocols + +This module must reuse or move existing ZeroJ pure Java implementations. It is +not a second independent pure Java BLS12-381 implementation. + +### 2. Prefer a clean pre-release extraction + +ZeroJ has not been released yet. The current BLS12-381 classes are only used +inside ZeroJ modules, examples, and use-case documentation, so package-level +source compatibility is not a primary constraint. + +Correctness and standards compatibility are more important than preserving the +current Groth16 package locations. If moving implementation code into +`zeroj-bls12381` produces a cleaner and less duplicated design, update the +internal Groth16, PlonK, examples, and use-case imports directly. + +The preferred migration path is: + +1. introduce `zeroj-bls12381` with neutral APIs +2. move reusable implementation behind those APIs where practical +3. update Groth16/PlonK internals to use the neutral module +4. update `zeroj-examples` and `zeroj-usecases` imports directly +5. add compatibility wrappers only if they materially reduce migration risk + +### 3. Add a provider SPI + +Define a small BLS12-381 provider boundary so higher-level protocols can choose +between implementations. + +The default provider is the pure Java provider from `zeroj-bls12381`. +Alternative providers must be explicit opt-ins. ZeroJ must not silently switch +to native or WASM providers just because an optional module is on the classpath. + +Provider implementations must pass the same conformance tests and vector suites +before they are marked supported. + +### 4. Add `zeroj-bls12381-wasm` as optional + +Create an optional module named: + +``` +zeroj-bls12381-wasm +``` + +This module wraps a Rust BLS12-381 implementation compiled to +`wasm32-unknown-unknown` and executed through Chicory. The first candidate is +`zkcrypto/bls12_381` because it is pure Rust and compiled cleanly in the spike. +`ark-bls12-381` remains a backup candidate. Native `blst`-to-WASM is deferred +until its build pipeline can be made reliable. + +The WASM provider is not the default. It is an optional provider and benchmark +target. + +The WASM ABI should avoid very small cross-boundary calls such as individual +field additions. Prefer coarse operations such as: + +- point decode/encode +- hash-to-G1 or hash-to-G2 +- G1/G2 scalar multiplication +- G1 multi-scalar multiplication +- pairing product check +- batched generator derivation + +For protocols such as BBS, a high-level WASM backend that performs full +`sign`, `verify`, `deriveProof`, and `verifyProof` inside WASM may still be +faster than a low-level primitive provider. This ADR does not require BBS to use +the low-level WASM provider. + +### 5. Keep `blst` support explicit + +Native `blst` remains valuable for performance. ZeroJ already has a +`zeroj-blst` module for verifier pairing operations, so native provider support +can live there as an explicit `Bls12381Provider` implementation instead of +adding another module immediately. It remains an explicit user opt-in because +native loading has platform and packaging implications. + +## Consequences + +### Easier + +- BBS can target a clean BLS12-381 provider boundary instead of depending on + Groth16 package internals. +- Groth16, PlonK, KZG, and BBS can share one standards-oriented primitive + module. +- Pure Java remains the portable default with no native or WASM runtime + requirement. +- WASM and native providers can be added and benchmarked without changing BBS + public APIs. + +### Harder + +- Extracting shared code from verifier/prover modules requires broad internal + import updates. +- The provider SPI must be small enough to maintain but complete enough for + CFRG BBS and future protocols. +- Multiple providers increase the conformance test burden. +- WASM performance is not guaranteed to beat optimized JVM code, especially if + calls are too fine-grained. + +### Neutral + +- Existing BBS WASM work from ADR-0017 remains valid as an incubating backend. +- Existing `zeroj-blst` can expose native-backed BLS12-381 provider operations + while remaining an explicit optional dependency. +- On-chain verifier modules are unaffected by this ADR. + +## Test Plan + +- Unit tests for `zeroj-bls12381`: + - field arithmetic against existing ZeroJ tests + - G1/G2 generator, identity, addition, scalar multiplication, and subgroup + checks + - pairing product checks, including `e(P, Q) * e(-P, Q) == 1` + - compressed and uncompressed point encode/decode round trips + - RFC9380 hash-to-curve vectors for BLS12-381 G1 and G2 +- Integration migration tests: + - existing Groth16 and PlonK BLS12-381 tests continue to pass + - examples and use-case modules compile against the neutral module +- WASM provider tests: + - Chicory 1.7.5 loads the WASM artifact + - the module exports memory and the expected ABI version + - exported operations have no unexpected host imports + - outputs match the pure Java provider for shared vectors +- BBS follow-up tests: + - CFRG BBS draft vectors pass with the pure Java provider first + - optional WASM/native providers must pass the same vectors before being + advertised as supported for BBS + +## Implementation Plan + +1. Create `zeroj-bls12381` and move the existing pure Java BLS12-381 + primitives behind neutral packages where practical. +2. Update current Groth16/PlonK imports to use the new packages directly. +3. Update Groth16/PlonK internals to depend on `zeroj-bls12381`. +4. Add the provider SPI and make pure Java the default provider. +5. Add `zeroj-bls12381-wasm` with a minimal Chicory smoke test and vector + equality tests against the pure Java provider. +6. Resume CFRG BBS implementation on top of the provider boundary. + +## Implementation Status + +Implemented on 2026-05-07: + +- `zeroj-bls12381` owns the shared pure Java BLS12-381 field, curve, pairing, + generator, codec, and provider SPI classes. +- Groth16, PlonK, KZG, examples, and on-chain test utilities use the neutral + BLS12-381 packages instead of Groth16 verifier or `zeroj-crypto` internals. +- `zeroj-bls12381-wasm` provides an explicit Chicory-backed provider using + Rust `zkcrypto/bls12_381` compiled to `wasm32-unknown-unknown`. +- The WASM ABI currently exposes generator retrieval, G1/G2 scalar + multiplication, and pairing product checks as coarse operations. +- Shared codecs validate uncompressed points for curve membership and + prime-order subgroup membership by default, with explicit unchecked decode + helpers for trusted internal boundaries. +- Shared codecs support compressed and uncompressed G1/G2 encodings with + round-trip tests, infinity handling, curve-membership checks, and subgroup + rejection tests. +- The provider SPI covers the BBS-required low-level boundary: G1/G2 identity, + add, negate, scalar multiplication, subgroup validation, compressed and + uncompressed codecs, RFC9380 hash-to-curve helpers, encode-to-curve helpers, + and scalar hashing. +- `zeroj-bls12381` implements RFC9380 hash-to-curve and encode-to-curve for + BLS12-381 G1/G2 with official vector coverage for the suites required by + CFRG BBS. +- Provider scalar multiplication reduces signed `BigInteger` inputs modulo the + BLS12-381 scalar-field order so pure Java and WASM providers have the same + scalar-domain behavior. +- `zeroj-bls12381-wasm` has explicit ABI conformance tests for no host imports, + expected exports, wrong-length inputs, invalid point bytes, and repeated + error handling. +- The WASM Rust crate is built from source with a committed Cargo lockfile and + a pinned Rust toolchain file; the generated `.wasm` is packaged by Gradle + rather than checked in. +- `zeroj-blst` exposes `BlstBls12381Provider`, an explicit native-backed + provider that implements the shared BLS12-381 provider SPI. +- BBS provider conformance tests exercise official draft-10 vectors through the + pure Java, WASM, and blst BLS providers. +- The BOM and Gradle settings include both new modules. + +Future BLS12-381 providers must pass the same provider and BBS conformance +vectors before being advertised as BBS-capable. + +## Risks + +| Risk | Severity | Mitigation | +|---|---:|---| +| Accidental behavior change during extraction | High | Move code in small slices and keep existing Groth16/PlonK tests passing | +| Provider SPI becomes too low-level and slow for WASM | Medium | Use batched/coarse primitive operations; allow high-level protocol backends | +| WASM provider is slower than pure Java | Medium | Keep pure Java default; benchmark before recommending WASM | +| Hash-to-curve incompatibility | High | Require RFC9380 vectors before using providers for BBS | +| Native/WASM provider auto-selection surprises users | Medium | Require explicit provider selection | +| Refactor delays BBS | Medium | Start with direct internal moves; use adapters only if extraction grows too large | + +## References + +- ADR-0007: Multi-Module Structure and Boundaries +- ADR-0012: Pure Java Provers for Groth16 and PlonK +- ADR-0017: BBS Selective Disclosure via Rust WASM and Chicory +- Rust `bls12_381` crate: +- Rust `ark-bls12-381` crate: +- Rust `blst` crate: +- Chicory docs: +- RFC 9380 hash-to-curve: diff --git a/docs/adr/0019-cfrg-bbs-pure-java-and-wasm-providers.md b/docs/adr/0019-cfrg-bbs-pure-java-and-wasm-providers.md new file mode 100644 index 0000000..7dbe37b --- /dev/null +++ b/docs/adr/0019-cfrg-bbs-pure-java-and-wasm-providers.md @@ -0,0 +1,398 @@ +# ADR-0019: CFRG BBS Pure Java and Optional WASM Providers + +## Status +Accepted + +## Date +2026-05-07 + +## Context + +ADR-0017 planned BBS selective disclosure as a WASM-first feature backed by an +arkworks `bbs_plus` implementation. That direction is no longer the right +default. ADR-0018 has extracted ZeroJ's reusable BLS12-381 primitives into +`zeroj-bls12381`, including point encodings, subgroup checks, pairing product +checks, scalar hashing, and RFC9380 hash-to-curve support. This makes a +standards-oriented pure Java BBS implementation feasible. + +The existing `zeroj-bbs/` scaffold is also not the target shape. It uses a +ZeroJ-specific BBS+ suite and CBOR ABI, not the CFRG BBS interface and octet +serialization from `draft-irtf-cfrg-bbs-signatures-10`. We should replace that +incubating code instead of evolving it. + +The target standard is pinned to: + +``` +draft-irtf-cfrg-bbs-signatures-10 +``` + +That draft defines the high-level interface operations `KeyGen`, `SkToPk`, +`Sign`, `Verify`, `ProofGen`, and `ProofVerify`, plus the core operations and +utility operations needed to make those interfaces vector-compatible. It also +defines BLS12-381 SHAKE-256 and SHA-256 ciphersuites and includes test vectors. + +## Decision + +### 1. Replace the current BBS scaffold + +Remove the existing experimental `zeroj-bbs/` directory and its nested +`wasm/` and `rust/` layout during implementation. Create new BBS modules with +the same shape as the BLS modules: + +``` +zeroj-bbs/ + build.gradle + src/main/java/com/bloxbean/cardano/zeroj/bbs/... + src/test/resources/cfrg-bbs/draft10/... + +zeroj-bbs-wasm/ + build.gradle + rust/ + src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/... +``` + +`zeroj-bbs` is the default portable implementation. `zeroj-bbs-wasm` is an +optional provider and benchmark target. + +### 2. Make pure Java the default BBS provider + +`zeroj-bbs` depends on `zeroj-bls12381` and implements CFRG BBS directly in +Java. It must not depend on the WASM module. + +The public Java API should be standards-oriented and provider-backed: + +- `BbsCiphersuite` +- `BbsSecretKey` +- `BbsPublicKey` +- `BbsKeyPair` +- `BbsSignature` +- `BbsProof` +- `BbsPresentation` +- `BbsProvider` +- `BbsProviders` +- `BbsService` + +`BbsProviders.pureJava()` is the default. Alternate providers are explicit +opt-ins. ZeroJ must not silently switch to WASM because `zeroj-bbs-wasm` is on +the classpath. + +### 3. Implement the CFRG draft-10 interface, not the older BBS+ API + +The implementation target is the draft-10 interface algorithms: + +- `KeyGen` +- `SkToPk` +- `Sign` +- `Verify` +- `ProofGen` +- `ProofVerify` + +The implementation must also expose or test the draft utility operations needed +for vector compatibility: + +- secret-key derivation from key material and key info +- public-key derivation +- message-to-scalar mapping +- generator derivation +- domain calculation +- challenge calculation +- random scalar derivation for test vectors +- scalar, point, signature, proof, and public-key octet serialization + +Core cryptographic bytes must follow the CFRG draft octet formats. ZeroJ may +define a small CBOR envelope for `ZkProofEnvelope` integration, but that +envelope must wrap draft-compatible BBS proof bytes rather than replacing the +draft serialization. + +### 4. Ciphersuite support + +Implement the BLS12-381 SHA-256 ciphersuite first: + +``` +BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_ +``` + +This is the lowest-risk first target because `zeroj-bls12381` already has the +required RFC9380 XMD SHA-256 hash-to-G1 support and test vectors. + +Support the SHAKE-256 ciphersuite as a second milestone: + +``` +BBS_BLS12381G1_XOF:SHAKE-256_SSWU_RO_ +``` + +ZeroJ must not advertise full draft-10 ciphersuite coverage until both +ciphersuites pass the corresponding draft vectors. If SHAKE-256 requires new +XOF support in `zeroj-bls12381`, add that as a prerequisite before enabling +the suite. + +### 5. Constant-time secret-scalar boundary + +BBS uses secret keys, proof randomness, and hidden-message blinding scalars. +The implementation must not route those through provider methods documented +only for public scalar multiplication. + +Before production use, `zeroj-bbs` must either: + +- add constant-time secret-scalar operations to `zeroj-bls12381` and use them + for BBS secret-dependent scalar multiplication, or +- keep the BBS module clearly marked experimental until a side-channel review + is complete. + +Correct vector compatibility is required first, but a constant-time contract is +required before advertising the pure Java implementation as production-ready +for secret-bearing BBS workflows. + +Implementation note: the follow-up implementation added explicit +`g1SecretScalarMul` and `g2SecretScalarMul` provider methods, wired BBS +`SkToPk`, signing, proof blinding, proof randomness, and hidden-message +commitments through those methods, and replaced BBS secret scalar inversions +with Montgomery-form scalar-field inversion. The JVM implementation uses a +fixed-schedule Jacobian multiplication path; high-value deployments should +still perform an environment-specific side-channel review. + +### 6. BLS-provider swapping happens in `zeroj-bbs`, not in separate modules + +Native `blst` is exposed as a BLS12-381 provider from `zeroj-blst`. The WASM +BLS12-381 primitives are exposed from `zeroj-bls12381-wasm`. BBS can use either +through explicit BLS provider selection without adding a separate +`zeroj-bbs-blst` or `zeroj-bbs-wasm-bls` module: + +```java +// Java BBS + WASM BLS primitives (hybrid) +BbsProviders.withBlsProvider(ciphersuite, WasmBls12381Provider.createDefault()) + +// Java BBS + native blst BLS primitives +BbsProviders.withBlsProvider(ciphersuite, BlstBls12381Provider.createDefault()) +``` + +The main `zeroj-bbs` module must continue to depend only on the shared +`zeroj-bls12381` API; WASM and blst remain user-selected optional dependencies +that callers add to their classpath when they want them. The BLS provider +conformance suite in `zeroj-bbs` already parameterizes across `(pure-java BLS, +WASM BLS, blst BLS) × (SHA-256, SHAKE-256)` to gate any new BLS backend on the +same draft-10 fixtures. + +A separate "hybrid Java BBS + WASM BLS" module would only re-export a one-line +factory that callers can write themselves via `withBlsProvider`. It would add +audit surface and packaging cost without adding capability. We therefore do not +ship such a module. + +### 7. `zeroj-bbs-wasm` is reserved for the full Rust-WASM BBS provider + +`zeroj-bbs-wasm` implements the same `BbsProvider` SPI as an explicit opt-in, +but the algorithm itself runs entirely inside WebAssembly. The Rust BBS crate +is compiled to `wasm32-unknown-unknown` and executed through Chicory; ZeroJ's +Java code only marshals requests, results, and errors across the WASM boundary. +This eliminates the cross-boundary calls that the hybrid path incurs per +pairing or per scalar multiplication. + +The coarse Rust ABI is: + +- `zeroj_bbs_keygen` +- `zeroj_bbs_sk_to_pk` +- `zeroj_bbs_sign` +- `zeroj_bbs_verify` +- `zeroj_bbs_proof_gen` +- `zeroj_bbs_proof_verify` + +The first Rust candidate is `zkryptium` because it implements CFRG draft-10 +directly and supports both BLS12-381-SHA-256 and BLS12-381-SHAKE-256 +ciphersuites. It must compile cleanly to `wasm32-unknown-unknown`, must not +introduce unexpected host imports (no `getrandom`/`wasm-bindgen` shims), and +must pass the pinned draft-10 vectors byte-for-byte. If it does not meet those +gates, implement the Rust provider against a lower-level BLS12-381 crate +instead. Do not patch the older `bbs_plus` crate or `mattrglobal/pairing_crypto` +(BBS draft-03 / BBS+) into a draft-10 shape. + +The WASM ABI must mirror the hardened `zeroj-bls12381-wasm` pattern: + +- committed `Cargo.lock` +- pinned `rust-toolchain.toml` +- generated `.wasm` built by Gradle, not checked in +- exported memory plus explicit `alloc` and `dealloc` +- typed Java exceptions for malformed responses +- tests for alloc/dealloc balance on error paths +- response allocation length captured before length validation, so malformed + responses still get freed + +The full Rust-WASM BBS provider requires a real RNG for `proof_gen` (BBS +proof randomness is essential to the zero-knowledge property — a deterministic +seed would leak the secret across two presentations of the same signature). The +zkryptium 0.6.1 candidate does not expose a public API for caller-supplied +random scalars, so the WASM module exposes exactly **one** named host import: + +``` +env.zeroj_host_getrandom(ptr: i32, len: i32) -> i32 // 0 = ok, !=0 = error +``` + +The Java side wires this to `java.security.SecureRandom` via Chicory. No other +host imports are permitted; tests must assert that exactly this one import is +present and nothing else. + +CFRG mocked-RNG proof byte-equality is retained only on the pure-Java provider +(`PureJavaBbsProvider`), where `BbsBlsProviderConformanceTest` already gates +all 30 proof × 2 ciphersuite fixtures with deterministic scalars. The +full-WASM provider verifies proof correctness via roundtrip (`proof_gen` then +`proof_verify`) plus byte-exact tests on the deterministic operations +(`keygen`, `sk_to_pk`, `sign`, `verify`, `proof_verify` on a known fixture +proof). This keeps one byte-exact algorithm gate upstream without doubling +fixture maintenance. + +### 8. ZeroJ verifier integration + +`zeroj-bbs` should provide a BBS verifier for ZeroJ's verifier APIs: + +- proof system: `ProofSystemId.BBS` +- curve: `CurveId.BLS12_381` +- proof format: `bbs-cfrg-draft10-presentation-cbor-v1` + +The verifier checks only cryptographic correctness. Issuer trust, schema +semantics, expiration, revocation, holder binding, and disclosure policy remain +application policy concerns, consistent with ADR-0006. + +Provider selection remains explicit. ServiceLoader may discover the +`ZkVerifier`, but it must not silently switch the underlying BBS provider from +pure Java to WASM. + +## Consequences + +### Easier + +- BBS follows the same architecture as BLS: portable pure Java default plus an + optional WASM backend. +- CFRG vector compatibility becomes the primary correctness gate. +- `zeroj-bbs` can be used without Rust, WASM, native code, or Chicory. +- WASM can be benchmarked and improved independently. + +### Harder + +- The pure Java implementation must implement the full draft algorithm instead + of wrapping an existing BBS+ library. +- Constant-time secret-scalar handling must be addressed explicitly. +- Supporting both SHA-256 and SHAKE-256 ciphersuites adds hash/XOF work. +- Multiple BLS providers increase the conformance-test burden. + +### Neutral + +- No Cardano on-chain verifier is implied. +- W3C Data Integrity `bbs-2023` packaging can be layered later on top of the + CFRG-compatible BBS core. +- Blind BBS signatures and per-verifier linkability are out of scope for this + ADR. + +## Test Plan + +### Pure Java tests + +- Import draft-10 vectors for: + - key generation + - public key derivation + - message-to-scalar mapping + - generator derivation + - signature generation + - signature verification + - proof generation + - proof verification + - hash-to-scalar cases + - Appendix D.1 SHAKE-256 signature and proof fixtures from the official draft + JSON set + - Appendix D.2 SHA-256 signature and proof fixtures from the official draft + JSON set +- Add negative tests for: + - tampered signature + - tampered proof + - wrong public key + - wrong header + - wrong presentation header + - wrong revealed message + - duplicate, unsorted, and out-of-range revealed indexes + - invalid scalar, G1, and G2 encodings + - subgroup rejection on public keys and proof points +- Add deterministic mocked-random scalar tests for draft proof vectors. +- Add randomized round-trip tests for sign, verify, proof generation, and proof + verification after the draft vectors pass. + +### Provider conformance tests + +- Define one shared conformance suite in `zeroj-bbs`. +- Run the same suite against BBS with selected BLS providers: + - pure Java BLS from `zeroj-bls12381` + - WASM BLS from `zeroj-bls12381-wasm` + - native blst BLS from `zeroj-blst` +- A provider cannot be advertised as BBS-capable until it passes the shared + draft vectors. + +### WASM tests + +- Chicory loads the generated WASM artifact. +- The WASM module exports the expected ABI version and operations. +- The module imports exactly one host function (`env.zeroj_host_getrandom`) + and nothing else. +- Deterministic operations (`KeyGen`, `SkToPk`, `Sign`) match the official + CFRG draft-10 fixture bytes byte-for-byte for both ciphersuites. +- `proof_verify` accepts the official CFRG draft-10 proof bytes for both + ciphersuites; tampered/modified fixtures are rejected. +- `proof_gen` is verified by roundtrip (the generated proof must verify under + the same WASM provider) rather than by byte-equality, because the full-WASM + path consumes real entropy through the host RNG import. +- Malformed requests and malformed responses map to typed Java exceptions. +- Repeated WASM errors do not leak response buffers. +- The host RNG import is the only crossing where the Rust crate can obtain + entropy; the per-call `SecureRandom` supplied to `BbsProvider.proofGen` + must drive that crossing for each invocation. + +### Integration tests + +- `BbsService` signs, verifies, derives a presentation, and verifies the + presentation using the pure Java provider. +- `ZkProofEnvelope` can carry a BBS presentation. +- `VerifierOrchestrator` verifies a BBS presentation envelope. +- Wrong proof system, curve, proof format, verification material, and public + inputs reject cleanly. + +## Implementation Plan + +1. Supersede ADR-0017 and delete the old experimental `zeroj-bbs/` scaffold. +2. Create `zeroj-bbs` with the public API, `BbsProvider` SPI, and pure Java + provider shell. +3. Add CFRG draft-10 vector resources and a vector loader. +4. Implement draft serialization utilities, scalar utilities, and ciphersuite + metadata. +5. Implement `KeyGen`, `SkToPk`, message-to-scalar mapping, and generator + derivation; pass draft vectors. +6. Implement `Sign` and `Verify`; pass signature vectors and negative tests. +7. Implement `ProofGen` and `ProofVerify`; pass proof vectors and negative + tests. +8. Add `BbsService`, presentation wrappers, and ZeroJ verifier integration. +9. Run the shared conformance suite against pure Java, WASM, and blst BLS + providers using `BbsService.withBlsProvider(...)` from `zeroj-bbs`. +10. Create `zeroj-bbs-wasm` as the full Rust-WASM BBS provider: Rust candidate + (`zkryptium` first) compiled to `wasm32-unknown-unknown`, coarse `zeroj_bbs_*` + ABI, Java client mirroring the hardened `zeroj-bls12381-wasm` pattern, + no-host-imports + alloc/dealloc balance + malformed-response leak tests, + and extend the conformance suite to gate on the new path. +11. Add documentation and examples for selective disclosure workflows. + +## Risks + +| Risk | Severity | Mitigation | +|---|---:|---| +| Draft-10 changes before RFC publication | Medium | Pin proof format and vectors to draft-10; add a new suite for later drafts | +| Pure Java side-channel leakage | Medium | Secret-scalar provider boundary is in place; require environment-specific side-channel review for high-value deployments | +| Incorrect generator/domain/challenge serialization | High | Gate each utility on draft vectors before implementing higher layers | +| Non-default providers drift from pure Java | Medium | Shared provider conformance suite and exact same CFRG vectors | +| Rust crate claims draft-10 but differs in details | Medium | Treat Rust crates as candidates only; vectors decide support | +| Rust crate pulls in additional host imports beyond `env.zeroj_host_getrandom` | Medium | Single-import assertion in the WASM hardening test rejects any extra import; if a candidate cannot be configured to call only `getrandom` on the host side, drop to a lower-level BLS crate per §7. | +| Host RNG quality on caller's JVM is weaker than expected | Low | Per-call `SecureRandom` is injectable through the `BbsProvider.proofGen` SPI; production callers can pass a `SecureRandom.getInstanceStrong()` or a hardware-backed instance. | +| Users confuse CFRG core with W3C Data Integrity packaging | Medium | Keep proof format names explicit and document policy/package boundaries | + +## References + +- ADR-0006: Separation of Crypto and Policy Verification +- ADR-0018: Shared BLS12-381 Primitives and Optional WASM Provider +- BBS draft-10: +- BBS draft datatracker entry: +- RFC 9380 hash-to-curve: +- ZKryptium Rust crate candidate: +- `mattrglobal/pairing_crypto` (BBS draft-03, BBS+ flavor — NOT a draft-10 candidate): diff --git a/docs/pure-java-prover-guide.md b/docs/pure-java-prover-guide.md index 28a57ce..68130fa 100644 --- a/docs/pure-java-prover-guide.md +++ b/docs/pure-java-prover-guide.md @@ -75,7 +75,8 @@ public class SecretMultiplierCircuit implements CircuitSpec { import com.bloxbean.cardano.zeroj.api.CurveId; import com.bloxbean.cardano.zeroj.crypto.groth16.*; import com.bloxbean.cardano.zeroj.crypto.setup.*; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; // Compile circuit var circuit = SecretMultiplierCircuit.build(); diff --git a/settings.gradle b/settings.gradle index 055484b..54def92 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ include 'zeroj-backend-spi' include 'zeroj-verifier-core' include 'zeroj-verifier-groth16' include 'zeroj-verifier-plonk' +include 'zeroj-bls12381' include 'zeroj-blst' include 'zeroj-test-vectors' include 'zeroj-submission' @@ -22,6 +23,10 @@ include 'zeroj-onchain-julc' include 'zeroj-examples' include 'zeroj-bom' +include 'zeroj-bls12381-wasm' +include 'zeroj-bbs-wasm' +include 'zeroj-bbs' + // === Incubator modules (experimental, alternative backends) === include 'zeroj-prover-rapidsnark' project(':zeroj-prover-rapidsnark').projectDir = file('incubator/zeroj-prover-rapidsnark') diff --git a/zeroj-api/src/main/java/com/bloxbean/cardano/zeroj/api/ProofSystemId.java b/zeroj-api/src/main/java/com/bloxbean/cardano/zeroj/api/ProofSystemId.java index 2a1d1cf..d09cda2 100644 --- a/zeroj-api/src/main/java/com/bloxbean/cardano/zeroj/api/ProofSystemId.java +++ b/zeroj-api/src/main/java/com/bloxbean/cardano/zeroj/api/ProofSystemId.java @@ -7,7 +7,8 @@ public enum ProofSystemId { GROTH16("groth16"), PLONK("plonk"), FFLONK("fflonk"), - HALO2("halo2"); + HALO2("halo2"), + BBS("bbs"); private final String value; diff --git a/zeroj-bbs-wasm/README.md b/zeroj-bbs-wasm/README.md new file mode 100644 index 0000000..7cf8667 --- /dev/null +++ b/zeroj-bbs-wasm/README.md @@ -0,0 +1,131 @@ +# zeroj-bbs-wasm + +Full Rust-WASM CFRG BBS draft-10 provider. The entire BBS algorithm runs +inside WebAssembly via [zkryptium 0.6.1](https://docs.rs/zkryptium) compiled +to `wasm32-unknown-unknown`, executed through Chicory. ZeroJ's Java layer +only serializes requests, parses responses, and supplies entropy via a single +documented host import. + +See [ADR-0019](../docs/adr/0019-cfrg-bbs-pure-java-and-wasm-providers.md) §7 +for the design rationale. + +## When to use this module + +Use the full Rust-WASM provider when you want the BBS algorithm to run +end-to-end inside a Rust-derived WebAssembly module and are OK with shipping +a small WebAssembly artifact. Signing and verification use coarse WASM calls +against a long-lived instance. Proof generation also uses a coarse call, but +ZeroJ intentionally creates a fresh WASM instance for each `proofGen` call so +the caller-supplied `SecureRandom` is honored for every proof. + +```java +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsService; +import com.bloxbean.cardano.zeroj.bbs.wasm.WasmBbsProvider; + +var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); +var service = new BbsService(provider); + +var keyPair = service.keyPair(keyMaterial, keyInfo); +var signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); +var presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, header, presentationHeader, + new int[]{0, 2}); +boolean valid = service.verifyPresentation(keyPair.publicKey(), presentation); +``` + +## When NOT to use this module + +If you only need to swap the BLS12-381 primitive layer (pairings, scalar +mul) — for example to benchmark WASM-backed BLS while keeping the BBS +algorithm in Java — use the hybrid path directly from `zeroj-bbs`: + +```java +// Java BBS algorithm + WASM BLS primitives: +var service = BbsService.withBlsProvider( + BbsCiphersuite.BLS12381_SHA256, + WasmBls12381Provider.createDefault()); + +// Java BBS algorithm + native blst BLS primitives: +var service = BbsService.withBlsProvider( + BbsCiphersuite.BLS12381_SHA256, + BlstBls12381Provider.createDefault()); +``` + +`zeroj-bbs/src/test/java/.../BbsBlsProviderConformanceTest` already runs the +full CFRG draft-10 fixture suite (10 signatures + 15 proofs + keypair + h2s ++ generators + MapMessageToScalar + mockedRng, both ciphersuites) across all +three BLS providers via `withBlsProvider`. No separate hybrid module exists. + +## ABI + +The WASM module exposes: + +- `zeroj_bbs_version() -> i32` (ABI version 1) +- `alloc(len) -> *mut u8` +- `dealloc(ptr, len)` +- `zeroj_bbs_keygen(req_ptr, req_len) -> *mut u8` +- `zeroj_bbs_sk_to_pk(req_ptr, req_len) -> *mut u8` +- `zeroj_bbs_sign(req_ptr, req_len) -> *mut u8` +- `zeroj_bbs_verify(req_ptr, req_len) -> *mut u8` +- `zeroj_bbs_proof_gen(req_ptr, req_len) -> *mut u8` +- `zeroj_bbs_proof_verify(req_ptr, req_len) -> *mut u8` + +Every response is laid out as `[u32 LE length | status byte | payload]`. +Status `0` = success, status `1` = error (payload is UTF-8 error message). +Callers free the response buffer with `dealloc(ptr, length + 4)`. + +The module imports exactly one host function: + +- `env.zeroj_host_getrandom(ptr: i32, len: i32) -> i32` — `0` on success, + non-zero on error. Java wires this to `java.security.SecureRandom`. + +A test (`wasmModule_hasExactlyOneImportAndExpectedExports`) asserts that no +other imports are present. + +## Runtime behavior + +`keyGen`, `skToPk`, `sign`, `verify`, and `proofVerify` run on a long-lived +WASM instance owned by the provider. These operations are deterministic and +should never call the host RNG import. + +`proofGen` needs fresh zero-knowledge blinding randomness. The Rust dependency +uses an internal thread-local RNG, so ZeroJ builds a transient WASM instance +per `proofGen` call and wires that instance's `env.zeroj_host_getrandom` +import to the `SecureRandom` supplied to the Java API call. This avoids +silently reusing or bypassing the per-call random source after the first +proof. + +The tradeoff is performance: `proofGen` includes WASM instantiation overhead +in addition to the BBS proof computation. This is correctness-first behavior, +not a protocol limitation. For high-throughput proof issuance, benchmark this +provider under the target workload and compare it with `zeroj-bbs` using the +native `zeroj-blst` BLS provider. A future optimization can remove the +per-proof instantiation cost if the Rust dependency exposes an API that +accepts caller-provided randomness directly. + +## Building + +The `.wasm` artifact is built on demand by Gradle during `processResources`. +Install Rust with the pinned toolchain (`rust-toolchain.toml` selects +`rustc 1.94.0` with the `wasm32-unknown-unknown` target). Gradle uses +`~/.cargo/bin/cargo` by default; override via the `CARGO` environment +variable if needed. + +```bash +./gradlew :zeroj-bbs-wasm:build +./gradlew :zeroj-bbs-wasm:test +``` + +## Trust boundary + +The CFRG draft-10 algorithm correctness is owned by zkryptium upstream and +gated on the ZeroJ side by the pure-Java `PureJavaBbsProvider` running all +30 CFRG mocked-RNG proof fixtures × 2 ciphersuites byte-for-byte in +`BbsBlsProviderConformanceTest`. The WASM tests in this module focus on the +ZeroJ-owned boundary: request encoding, response framing, error mapping, +alloc/dealloc balance under failure, no-unexpected-host-imports, and a +small "trust ladder" of byte-exact CFRG fixtures (keygen, sk_to_pk, sign) +plus a proof_gen → proof_verify roundtrip. CFRG mocked-RNG proof +byte-equality is not retestable in this module because `proof_gen` uses +real RNG via the host import. diff --git a/zeroj-bbs-wasm/build.gradle b/zeroj-bbs-wasm/build.gradle new file mode 100644 index 0000000..3e404e2 --- /dev/null +++ b/zeroj-bbs-wasm/build.gradle @@ -0,0 +1,53 @@ +plugins { + id 'java-library' +} + +description = 'ZeroJ full Rust-WASM CFRG BBS provider via zkryptium and Chicory' + +def bbsRustDir = project.file('rust') +def bbsWasmTarget = bbsRustDir.toPath() + .resolve('target/wasm32-unknown-unknown/release/zeroj_bbs.wasm') + .toFile() +def generatedWasmDir = layout.buildDirectory.dir('generated-resources/wasm') + +dependencies { + api project(':zeroj-bbs') + + implementation 'com.dylibso.chicory:runtime:1.7.5' + implementation 'com.dylibso.chicory:wasm:1.7.5' +} + +def cargoBinary = System.getenv('CARGO') ?: (System.getProperty('user.home') + '/.cargo/bin/cargo') + +tasks.register('buildBbsWasm', Exec) { + description = 'Builds the zkryptium CFRG BBS Rust crate as wasm32-unknown-unknown' + group = 'build' + workingDir = bbsRustDir + commandLine cargoBinary, 'build', '--release', '--target', 'wasm32-unknown-unknown' + inputs.files(fileTree(bbsRustDir) { + include 'Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', 'src/**/*.rs' + }) + outputs.file(bbsWasmTarget) +} + +tasks.register('copyBbsWasm', Copy) { + dependsOn tasks.named('buildBbsWasm') + from(bbsWasmTarget) + into(generatedWasmDir.map { it.dir('zeroj-bbs-wasm') }) +} + +processResources { + dependsOn tasks.named('copyBbsWasm') + from(generatedWasmDir) +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + name = 'ZeroJ BBS WASM' + description = 'Optional CFRG BBS provider with the entire BBS algorithm running inside WebAssembly via zkryptium and Chicory' + } + } + } +} diff --git a/zeroj-bbs-wasm/rust/Cargo.lock b/zeroj-bbs-wasm/rust/Cargo.lock new file mode 100644 index 0000000..3066ade --- /dev/null +++ b/zeroj-bbs-wasm/rust/Cargo.lock @@ -0,0 +1,475 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bls12_381_plus" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa37cf2a8c96054d2dc3d708efe35cc0347014af0d30b86c736b4388ff8491c" +dependencies = [ + "arrayref", + "elliptic-curve", + "ff", + "group", + "hex", + "pairing", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zeroj-bbs-wasm" +version = "0.1.0" +dependencies = [ + "elliptic-curve", + "getrandom", + "zkryptium", +] + +[[package]] +name = "zkryptium" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39159a1cd33b28bad7c3502528f77da679dd6f45a055581f5ac56954c458c6e5" +dependencies = [ + "bls12_381_plus", + "digest", + "elliptic-curve", + "ff", + "group", + "hex", + "rand", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/zeroj-bbs-wasm/rust/Cargo.toml b/zeroj-bbs-wasm/rust/Cargo.toml new file mode 100644 index 0000000..a6c7366 --- /dev/null +++ b/zeroj-bbs-wasm/rust/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "zeroj-bbs-wasm" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "zeroj_bbs" +crate-type = ["cdylib", "rlib"] + +[dependencies] +zkryptium = { version = "0.6.1", default-features = false, features = ["bbsplus"] } +getrandom = { version = "0.2", features = ["custom"] } +elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"] } + +[profile.release] +opt-level = "z" +lto = true +panic = "abort" +codegen-units = 1 diff --git a/zeroj-bbs-wasm/rust/rust-toolchain.toml b/zeroj-bbs-wasm/rust/rust-toolchain.toml new file mode 100644 index 0000000..6596900 --- /dev/null +++ b/zeroj-bbs-wasm/rust/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.94.0" +targets = ["wasm32-unknown-unknown"] +profile = "minimal" diff --git a/zeroj-bbs-wasm/rust/src/lib.rs b/zeroj-bbs-wasm/rust/src/lib.rs new file mode 100644 index 0000000..8e78bf6 --- /dev/null +++ b/zeroj-bbs-wasm/rust/src/lib.rs @@ -0,0 +1,453 @@ +//! ZeroJ CFRG BBS draft-10 provider — full BBS algorithm running inside +//! WebAssembly via zkryptium 0.6.1. +//! +//! ABI: see ADR-0019 §7. +//! +//! Memory model: every response is laid out as +//! [u32 LE total_payload_len | status_byte | payload_bytes] +//! Status byte is 0 for success, 1 for error. On error, payload is a UTF-8 +//! error message. Caller reads the 4-byte length, then reads the status + +//! payload, then frees the buffer with `dealloc(ptr, length + 4)`. +//! +//! Single host import: `env.zeroj_host_getrandom(ptr, len) -> i32` (0 = ok). +//! All other operations are self-contained. + +use std::{mem, slice}; + +use elliptic_curve::hash2curve::ExpandMsg; +use zkryptium::{ + bbsplus::{ + ciphersuites::{BbsCiphersuite, Bls12381Sha256, Bls12381Shake256}, + keys::{BBSplusPublicKey, BBSplusSecretKey}, + }, + keys::pair::KeyPair, + schemes::{ + algorithms::BBSplus, + generics::{PoKSignature, Signature}, + }, +}; + +// ---------- Host import --------------------------------------------------- + +extern "C" { + fn zeroj_host_getrandom(ptr: *mut u8, len: usize) -> i32; +} + +fn host_backed_getrandom(buf: &mut [u8]) -> Result<(), getrandom::Error> { + if buf.is_empty() { + return Ok(()); + } + let rc = unsafe { zeroj_host_getrandom(buf.as_mut_ptr(), buf.len()) }; + if rc == 0 { + Ok(()) + } else { + Err(getrandom::Error::FAILED_RDRAND) + } +} +getrandom::register_custom_getrandom!(host_backed_getrandom); + +// ---------- Memory primitives --------------------------------------------- + +#[no_mangle] +pub extern "C" fn alloc(len: usize) -> *mut u8 { + let mut buf = Vec::with_capacity(len); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + ptr +} + +#[no_mangle] +pub extern "C" fn dealloc(ptr: *mut u8, len: usize) { + if ptr.is_null() || len == 0 { + return; + } + unsafe { + let _ = Vec::from_raw_parts(ptr, len, len); + } +} + +#[no_mangle] +pub extern "C" fn zeroj_bbs_version() -> u32 { + 1 +} + +// ---------- ABI entrypoints ---------------------------------------------- + +#[no_mangle] +pub extern "C" fn zeroj_bbs_keygen(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, op_keygen) +} + +#[no_mangle] +pub extern "C" fn zeroj_bbs_sk_to_pk(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, op_sk_to_pk) +} + +#[no_mangle] +pub extern "C" fn zeroj_bbs_sign(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, op_sign) +} + +#[no_mangle] +pub extern "C" fn zeroj_bbs_verify(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, op_verify) +} + +#[no_mangle] +pub extern "C" fn zeroj_bbs_proof_gen(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, op_proof_gen) +} + +#[no_mangle] +pub extern "C" fn zeroj_bbs_proof_verify(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, op_proof_verify) +} + +// ---------- Framing ------------------------------------------------------- + +fn handle(ptr: *const u8, len: usize, op: F) -> *mut u8 +where + F: FnOnce(&[u8]) -> Result, String>, +{ + if ptr.is_null() { + return respond(Err("request pointer is null".into())); + } + let input = unsafe { slice::from_raw_parts(ptr, len) }; + respond(op(input)) +} + +fn respond(result: Result, String>) -> *mut u8 { + let mut payload = Vec::new(); + match result { + Ok(bytes) => { + payload.push(0); + payload.extend_from_slice(&bytes); + } + Err(message) => { + payload.push(1); + payload.extend_from_slice(message.as_bytes()); + } + } + leak_response(payload) +} + +fn leak_response(payload: Vec) -> *mut u8 { + let len = payload.len(); + let mut buf = Vec::with_capacity(len + 4); + buf.extend_from_slice(&(len as u32).to_le_bytes()); + buf.extend_from_slice(&payload); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + ptr +} + +// ---------- Ciphersuite dispatch ------------------------------------------ + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Suite { + Sha256, + Shake256, +} + +fn read_suite(input: &[u8]) -> Result<(Suite, &[u8]), String> { + if input.is_empty() { + return Err("request is empty (expected ciphersuite byte)".into()); + } + let suite = match input[0] { + 0 => Suite::Sha256, + 1 => Suite::Shake256, + other => return Err(format!("unknown ciphersuite byte: {other}")), + }; + Ok((suite, &input[1..])) +} + +macro_rules! with_suite { + ($suite:expr, $body:ident, $input:expr) => { + match $suite { + Suite::Sha256 => $body::($input), + Suite::Shake256 => $body::($input), + } + }; +} + +// ---------- Request decoder helpers --------------------------------------- + +struct Cursor<'a> { + buf: &'a [u8], + off: usize, +} + +impl<'a> Cursor<'a> { + fn new(buf: &'a [u8]) -> Self { + Self { buf, off: 0 } + } + + fn remaining(&self) -> usize { + self.buf.len() - self.off + } + + fn need(&self, n: usize, label: &str) -> Result<(), String> { + if self.remaining() < n { + Err(format!( + "{label}: need {n} bytes, only {} remaining", + self.remaining() + )) + } else { + Ok(()) + } + } + + fn take(&mut self, n: usize, label: &str) -> Result<&'a [u8], String> { + self.need(n, label)?; + let out = &self.buf[self.off..self.off + n]; + self.off += n; + Ok(out) + } + + fn u32_le(&mut self, label: &str) -> Result { + let bytes = self.take(4, label)?; + Ok(u32::from_le_bytes(bytes.try_into().unwrap())) + } + + fn var_bytes(&mut self, label: &str) -> Result<&'a [u8], String> { + let len = self.u32_le(&format!("{label} length"))? as usize; + self.take(len, label) + } + + fn message_list(&mut self) -> Result>, String> { + let count = self.u32_le("message count")? as usize; + if count > 1024 { + return Err(format!("message count {count} exceeds cap 1024")); + } + let mut out = Vec::with_capacity(count); + for i in 0..count { + let msg = self.var_bytes(&format!("message[{i}]"))?; + out.push(msg.to_vec()); + } + Ok(out) + } + + fn index_list(&mut self) -> Result, String> { + let count = self.u32_le("disclosed index count")? as usize; + if count > 1024 { + return Err(format!("disclosed index count {count} exceeds cap 1024")); + } + let mut out = Vec::with_capacity(count); + for i in 0..count { + let idx = self.u32_le(&format!("disclosed_index[{i}]"))? as usize; + out.push(idx); + } + Ok(out) + } + + fn expect_eof(&self, label: &str) -> Result<(), String> { + if self.remaining() != 0 { + return Err(format!( + "{label}: {} unexpected trailing bytes", + self.remaining() + )); + } + Ok(()) + } +} + +fn read_sk(bytes: &[u8]) -> Result { + let arr: [u8; 32] = bytes + .try_into() + .map_err(|_| "secret key must be 32 bytes".to_string())?; + BBSplusSecretKey::from_bytes(&arr).map_err(err) +} + +fn read_pk(bytes: &[u8]) -> Result { + let arr: [u8; 96] = bytes + .try_into() + .map_err(|_| "public key must be 96 bytes".to_string())?; + BBSplusPublicKey::from_bytes(&arr).map_err(err) +} + +fn header_opt<'a>(bytes: &'a [u8]) -> Option<&'a [u8]> { + if bytes.is_empty() { + None + } else { + Some(bytes) + } +} + +fn messages_opt<'a>(messages: &'a [Vec]) -> Option<&'a [Vec]> { + if messages.is_empty() { + None + } else { + Some(messages) + } +} + +fn indexes_opt<'a>(idx: &'a [usize]) -> Option<&'a [usize]> { + if idx.is_empty() { + None + } else { + Some(idx) + } +} + +// ---------- KeyGen -------------------------------------------------------- + +fn op_keygen(input: &[u8]) -> Result, String> { + let (suite, rest) = read_suite(input)?; + with_suite!(suite, keygen_typed, rest) +} + +fn keygen_typed(input: &[u8]) -> Result, String> +where + CS: BbsCiphersuite, + CS::Expander: for<'a> ExpandMsg<'a>, +{ + let mut c = Cursor::new(input); + let key_material = c.var_bytes("key_material")?.to_vec(); + let key_info_bytes = c.var_bytes("key_info")?.to_vec(); + c.expect_eof("keygen request")?; + let key_info = header_opt(&key_info_bytes); + let kp = KeyPair::>::generate(&key_material, key_info, None).map_err(err)?; + Ok(kp.private_key().to_bytes().to_vec()) +} + +// ---------- SkToPk -------------------------------------------------------- + +fn op_sk_to_pk(input: &[u8]) -> Result, String> { + let (suite, rest) = read_suite(input)?; + with_suite!(suite, sk_to_pk_typed, rest) +} + +fn sk_to_pk_typed(input: &[u8]) -> Result, String> +where + CS: BbsCiphersuite, +{ + let mut c = Cursor::new(input); + let sk = read_sk(c.take(32, "secret key")?)?; + c.expect_eof("sk_to_pk request")?; + let pk = sk.public_key(); + Ok(pk.to_bytes().to_vec()) +} + +// ---------- Sign / Verify ------------------------------------------------- + +fn op_sign(input: &[u8]) -> Result, String> { + let (suite, rest) = read_suite(input)?; + with_suite!(suite, sign_typed, rest) +} + +fn sign_typed(input: &[u8]) -> Result, String> +where + CS: BbsCiphersuite, + CS::Expander: for<'a> ExpandMsg<'a>, +{ + let mut c = Cursor::new(input); + let sk = read_sk(c.take(32, "secret key")?)?; + let pk = read_pk(c.take(96, "public key")?)?; + let header = c.var_bytes("header")?.to_vec(); + let messages = c.message_list()?; + c.expect_eof("sign request")?; + let sig = Signature::>::sign( + messages_opt(&messages), + &sk, + &pk, + header_opt(&header), + ) + .map_err(err)?; + Ok(sig.to_bytes().to_vec()) +} + +fn op_verify(input: &[u8]) -> Result, String> { + let (suite, rest) = read_suite(input)?; + with_suite!(suite, verify_typed, rest) +} + +fn verify_typed(input: &[u8]) -> Result, String> +where + CS: BbsCiphersuite, + CS::Expander: for<'a> ExpandMsg<'a>, +{ + let mut c = Cursor::new(input); + let pk = read_pk(c.take(96, "public key")?)?; + let sig_bytes = c.take(80, "signature")?.to_vec(); + let header = c.var_bytes("header")?.to_vec(); + let messages = c.message_list()?; + c.expect_eof("verify request")?; + let sig_arr: &[u8; 80] = sig_bytes + .as_slice() + .try_into() + .map_err(|_| "signature must be 80 bytes".to_string())?; + let sig = Signature::>::from_bytes(sig_arr).map_err(err)?; + let ok = sig + .verify(&pk, messages_opt(&messages), header_opt(&header)) + .is_ok(); + Ok(vec![if ok { 1 } else { 0 }]) +} + +// ---------- ProofGen / ProofVerify ---------------------------------------- + +fn op_proof_gen(input: &[u8]) -> Result, String> { + let (suite, rest) = read_suite(input)?; + with_suite!(suite, proof_gen_typed, rest) +} + +fn proof_gen_typed(input: &[u8]) -> Result, String> +where + CS: BbsCiphersuite, + CS::Expander: for<'a> ExpandMsg<'a>, +{ + let mut c = Cursor::new(input); + let pk = read_pk(c.take(96, "public key")?)?; + let sig_bytes = c.take(80, "signature")?.to_vec(); + let header = c.var_bytes("header")?.to_vec(); + let ph = c.var_bytes("presentation header")?.to_vec(); + let messages = c.message_list()?; + let disclosed = c.index_list()?; + c.expect_eof("proof_gen request")?; + let proof = PoKSignature::>::proof_gen( + &pk, + &sig_bytes, + header_opt(&header), + header_opt(&ph), + messages_opt(&messages), + indexes_opt(&disclosed), + ) + .map_err(err)?; + Ok(proof.to_bytes()) +} + +fn op_proof_verify(input: &[u8]) -> Result, String> { + let (suite, rest) = read_suite(input)?; + with_suite!(suite, proof_verify_typed, rest) +} + +fn proof_verify_typed(input: &[u8]) -> Result, String> +where + CS: BbsCiphersuite, + CS::Expander: for<'a> ExpandMsg<'a>, +{ + let mut c = Cursor::new(input); + let pk = read_pk(c.take(96, "public key")?)?; + let proof_bytes = c.var_bytes("proof")?.to_vec(); + let header = c.var_bytes("header")?.to_vec(); + let ph = c.var_bytes("presentation header")?.to_vec(); + let disclosed_messages = c.message_list()?; + let disclosed_indexes = c.index_list()?; + c.expect_eof("proof_verify request")?; + let proof = PoKSignature::>::from_bytes(&proof_bytes).map_err(err)?; + let ok = proof + .proof_verify( + &pk, + messages_opt(&disclosed_messages), + indexes_opt(&disclosed_indexes), + header_opt(&header), + header_opt(&ph), + ) + .is_ok(); + Ok(vec![if ok { 1 } else { 0 }]) +} + +// ---------- Error mapping ------------------------------------------------- + +fn err(e: E) -> String { + format!("{e:?}") +} diff --git a/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/Bbs12381WasmClient.java b/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/Bbs12381WasmClient.java new file mode 100644 index 0000000..aea847e --- /dev/null +++ b/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/Bbs12381WasmClient.java @@ -0,0 +1,518 @@ +package com.bloxbean.cardano.zeroj.bbs.wasm; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.dylibso.chicory.runtime.HostFunction; +import com.dylibso.chicory.runtime.ImportValues; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.runtime.Memory; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.ValueType; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Chicory client for the ZeroJ CFRG BBS Rust WASM module. + * + *

Exposes the coarse {@code zeroj_bbs_*} ABI: keygen, sk_to_pk, sign, verify, + * proof_gen, proof_verify. The module imports exactly one host function, + * {@code env.zeroj_host_getrandom}, which this client wires to + * {@link SecureRandom}. See ADR-0019 §7.

+ */ +public final class Bbs12381WasmClient { + public static final String DEFAULT_RESOURCE = "/zeroj-bbs-wasm/zeroj_bbs.wasm"; + + private static final int MAX_RESPONSE_LEN = 16 * 1024 * 1024; + private static final int MAX_HOST_GETRANDOM_LEN = 16 * 1024; + + /** Maximum number of signed messages or revealed indexes per request (matches the Rust cap). */ + static final int MAX_MESSAGES = 1024; + /** Maximum bytes per individual message. */ + static final int MAX_MESSAGE_BYTES = 64 * 1024; + /** Maximum bytes for header / presentation header. */ + static final int MAX_HEADER_BYTES = 64 * 1024; + /** Maximum bytes for key material / key info. */ + static final int MAX_KEY_INPUT_BYTES = 64 * 1024; + /** Maximum bytes for an opaque proof. */ + static final int MAX_PROOF_BYTES = 3 * 96 + (4 + MAX_MESSAGES) * 32; + /** Maximum total serialized request size. */ + static final int MAX_REQUEST_BYTES = 16 * 1024 * 1024; + + static final byte SUITE_SHA256 = 0; + static final byte SUITE_SHAKE256 = 1; + + private final byte[] wasmBytes; + private final Instance instance; + private final Memory memory; + private final SecureRandom defaultRandom; + + public Bbs12381WasmClient(byte[] wasmBytes, SecureRandom random) { + Objects.requireNonNull(wasmBytes, "wasmBytes required"); + Objects.requireNonNull(random, "random required"); + if (wasmBytes.length == 0) { + throw new IllegalArgumentException("wasmBytes must not be empty"); + } + this.wasmBytes = wasmBytes.clone(); + this.defaultRandom = random; + try { + // Persistent instance for ops that consume no entropy (keygen, + // sk_to_pk, sign, verify, proof_verify) — bound to defaultRandom + // since they should never reach the host RNG anyway. + this.instance = buildInstance(this.wasmBytes, this.defaultRandom); + this.memory = Objects.requireNonNull(instance.memory(), "BBS WASM module must export memory"); + long version = instance.export("zeroj_bbs_version").apply()[0]; + if (version != 1L) { + throw new Bbs12381WasmException("Unsupported BBS WASM ABI version: " + version); + } + } catch (Bbs12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bbs12381WasmException("Failed to initialize BBS WASM module", e); + } + } + + /** + * Build a fresh Chicory Instance whose {@code env.zeroj_host_getrandom} + * import draws from the given {@link SecureRandom}. A fresh instance is + * required for every {@code proof_gen} call because zkryptium's internal + * {@code rand::thread_rng()} keeps a cached chacha-state per thread that + * only re-seeds from {@code getrandom} on the first use — so without a + * fresh instance, calls 2..N silently bypass the per-call SecureRandom and + * derive bytes from the cached state seeded by call 1. + */ + private static Instance buildInstance(byte[] wasmBytes, SecureRandom random) { + HostFunction hostGetrandom = new HostFunction( + "env", + "zeroj_host_getrandom", + List.of(ValueType.I32, ValueType.I32), + List.of(ValueType.I32), + (inst, args) -> { + int ptr = (int) args[0]; + int len = (int) args[1]; + if (len < 0 || len > MAX_HOST_GETRANDOM_LEN) { + return new long[]{1L}; + } + if (len == 0) { + return new long[]{0L}; + } + byte[] buf = new byte[len]; + random.nextBytes(buf); + inst.memory().write(ptr, buf); + return new long[]{0L}; + }); + ImportValues imports = ImportValues.builder().addFunction(hostGetrandom).build(); + return Instance.builder(Parser.parse(wasmBytes)).withImportValues(imports).build(); + } + + public static Bbs12381WasmClient fromPath(Path wasmPath) throws IOException { + return new Bbs12381WasmClient(Files.readAllBytes(wasmPath), new SecureRandom()); + } + + public static Bbs12381WasmClient createDefault() { + return createDefault(new SecureRandom()); + } + + public static Bbs12381WasmClient createDefault(SecureRandom random) { + try (var in = Bbs12381WasmClient.class.getResourceAsStream(DEFAULT_RESOURCE)) { + if (in == null) { + throw new Bbs12381WasmException("BBS WASM resource not found: " + DEFAULT_RESOURCE); + } + return new Bbs12381WasmClient(in.readAllBytes(), random); + } catch (IOException e) { + throw new Bbs12381WasmException("Failed to read BBS WASM resource", e); + } + } + + // ----- typed entry points ----- + + public byte[] keyGen(BbsCiphersuite ciphersuite, byte[] keyMaterial, byte[] keyInfo) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + requireMaxLength(keyMaterial, MAX_KEY_INPUT_BYTES, "keyMaterial"); + requireMaxLength(keyInfo, MAX_KEY_INPUT_BYTES, "keyInfo"); + long size = 1L + 4 + keyMaterial.length + 4 + keyInfo.length; + ByteBuffer req = allocateRequest(size, "keyGen"); + req.put(suite(ciphersuite)); + putVarBytes(req, keyMaterial); + putVarBytes(req, keyInfo); + return invoke("zeroj_bbs_keygen", req.array()); + } + + public byte[] skToPk(BbsCiphersuite ciphersuite, byte[] sk) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + Objects.requireNonNull(sk, "sk required"); + if (sk.length != 32) { + throw new IllegalArgumentException("BBS secret key must be 32 bytes, got " + sk.length); + } + ByteBuffer req = ByteBuffer.allocate(1 + 32).order(ByteOrder.LITTLE_ENDIAN); + req.put(suite(ciphersuite)); + req.put(sk); + return invoke("zeroj_bbs_sk_to_pk", req.array()); + } + + public byte[] sign(BbsCiphersuite ciphersuite, byte[] sk, byte[] pk, byte[] header, List messages) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + Objects.requireNonNull(sk, "sk required"); + if (sk.length != 32) { + throw new IllegalArgumentException("BBS secret key must be 32 bytes, got " + sk.length); + } + requireLength(pk, 96, "BBS public key"); + requireMaxLength(header, MAX_HEADER_BYTES, "header"); + long messagesBytes = validateMessageList(messages, "messages"); + long size = 1L + 32 + 96 + 4 + header.length + 4 + messagesBytes; + ByteBuffer req = allocateRequest(size, "sign"); + req.put(suite(ciphersuite)); + req.put(sk); + req.put(pk); + putVarBytes(req, header); + putMessageList(req, messages); + return invoke("zeroj_bbs_sign", req.array()); + } + + public boolean verify( + BbsCiphersuite ciphersuite, byte[] pk, byte[] signature, byte[] header, List messages) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + requireLength(pk, 96, "BBS public key"); + requireLength(signature, 80, "BBS signature"); + requireMaxLength(header, MAX_HEADER_BYTES, "header"); + long messagesBytes = validateMessageList(messages, "messages"); + long size = 1L + 96 + 80 + 4 + header.length + 4 + messagesBytes; + ByteBuffer req = allocateRequest(size, "verify"); + req.put(suite(ciphersuite)); + req.put(pk); + req.put(signature); + putVarBytes(req, header); + putMessageList(req, messages); + byte[] response = invoke("zeroj_bbs_verify", req.array()); + return decodeBool(response, "verify"); + } + + public byte[] proofGen( + BbsCiphersuite ciphersuite, + byte[] pk, + byte[] signature, + byte[] header, + byte[] presentationHeader, + List messages, + int[] disclosedIndexes, + SecureRandom random) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + Objects.requireNonNull(random, "random required"); + requireLength(pk, 96, "BBS public key"); + requireLength(signature, 80, "BBS signature"); + requireMaxLength(header, MAX_HEADER_BYTES, "header"); + requireMaxLength(presentationHeader, MAX_HEADER_BYTES, "presentationHeader"); + long messagesBytes = validateMessageList(messages, "messages"); + validateIndexList(disclosedIndexes, messages.size(), "disclosedIndexes"); + long size = 1L + 96 + 80 + 4 + header.length + 4 + presentationHeader.length + + 4 + messagesBytes + 4L + 4L * disclosedIndexes.length; + ByteBuffer req = allocateRequest(size, "proofGen"); + req.put(suite(ciphersuite)); + req.put(pk); + req.put(signature); + putVarBytes(req, header); + putVarBytes(req, presentationHeader); + putMessageList(req, messages); + req.putInt(disclosedIndexes.length); + for (int idx : disclosedIndexes) { + req.putInt(idx); + } + return invokeOnTransientInstance("zeroj_bbs_proof_gen", req.array(), random); + } + + public boolean proofVerify( + BbsCiphersuite ciphersuite, + byte[] pk, + byte[] proof, + byte[] header, + byte[] presentationHeader, + List disclosedMessages, + int[] disclosedIndexes) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + requireLength(pk, 96, "BBS public key"); + requireMaxLength(proof, MAX_PROOF_BYTES, "proof"); + requireMaxLength(header, MAX_HEADER_BYTES, "header"); + requireMaxLength(presentationHeader, MAX_HEADER_BYTES, "presentationHeader"); + long messagesBytes = validateMessageList(disclosedMessages, "disclosedMessages"); + validateDisclosedIndexesForVerify(disclosedIndexes, "disclosedIndexes"); + if (disclosedIndexes.length != disclosedMessages.size()) { + throw new IllegalArgumentException( + "disclosedIndexes (" + disclosedIndexes.length + + ") and disclosedMessages (" + disclosedMessages.size() + ") must have equal length"); + } + long size = 1L + 96 + 4 + proof.length + 4 + header.length + 4 + presentationHeader.length + + 4 + messagesBytes + 4L + 4L * disclosedIndexes.length; + ByteBuffer req = allocateRequest(size, "proofVerify"); + req.put(suite(ciphersuite)); + req.put(pk); + putVarBytes(req, proof); + putVarBytes(req, header); + putVarBytes(req, presentationHeader); + putMessageList(req, disclosedMessages); + req.putInt(disclosedIndexes.length); + for (int idx : disclosedIndexes) { + req.putInt(idx); + } + byte[] response = invoke("zeroj_bbs_proof_verify", req.array()); + return decodeBool(response, "proof_verify"); + } + + // ----- test hooks ----- + + byte[] invokeRawForTesting(String exportName, byte[] request) { + return invoke(exportName, request); + } + + byte[] invokeNoArgRawForTesting(String exportName) { + return invokeNoArg(exportName); + } + + long invokeExportForTesting(String exportName, long... args) { + return instance.export(exportName).apply(args)[0]; + } + + // ----- internal ----- + + /** + * Run {@code exportName} on a freshly-built Chicory instance whose + * getrandom import draws from {@code random}. The instance is discarded + * after the call. See {@link #buildInstance} for the rationale: this is + * what guarantees the per-call SecureRandom drives every proof generation, + * not just the first one per shared instance. + */ + private byte[] invokeOnTransientInstance(String exportName, byte[] request, SecureRandom random) { + Instance transient_ = buildInstance(wasmBytes, random); + Memory transientMemory = Objects.requireNonNull( + transient_.memory(), "BBS WASM module must export memory"); + int requestPtr = 0; + int responsePtr = 0; + long responseAllocationLen = 0; + try { + requestPtr = (int) transient_.export("alloc").apply(request.length)[0]; + transientMemory.write(requestPtr, request); + responsePtr = (int) transient_.export(exportName).apply(requestPtr, request.length)[0]; + long responseLen = readResponseLenHeader(transientMemory, responsePtr); + responseAllocationLen = responseAllocationLen(responseLen); + requireValidResponseLen(responseLen); + return readResponsePayload(transientMemory, exportName, responsePtr, (int) responseLen); + } catch (Bbs12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bbs12381WasmException("BBS WASM invocation failed: " + exportName, e); + } finally { + if (requestPtr != 0) { + transient_.export("dealloc").apply(requestPtr, request.length); + } + if (responsePtr != 0 && responseAllocationLen > 0) { + transient_.export("dealloc").apply(responsePtr, responseAllocationLen); + } + } + } + + private synchronized byte[] invoke(String exportName, byte[] request) { + int requestPtr = 0; + int responsePtr = 0; + long responseAllocationLen = 0; + try { + requestPtr = (int) instance.export("alloc").apply(request.length)[0]; + memory.write(requestPtr, request); + responsePtr = (int) instance.export(exportName).apply(requestPtr, request.length)[0]; + long responseLen = readResponseLenHeader(memory, responsePtr); + responseAllocationLen = responseAllocationLen(responseLen); + requireValidResponseLen(responseLen); + return readResponsePayload(memory, exportName, responsePtr, (int) responseLen); + } catch (Bbs12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bbs12381WasmException("BBS WASM invocation failed: " + exportName, e); + } finally { + if (requestPtr != 0) { + instance.export("dealloc").apply(requestPtr, request.length); + } + if (responsePtr != 0 && responseAllocationLen > 0) { + instance.export("dealloc").apply(responsePtr, responseAllocationLen); + } + } + } + + private synchronized byte[] invokeNoArg(String exportName) { + int responsePtr = 0; + long responseAllocationLen = 0; + try { + responsePtr = (int) instance.export(exportName).apply()[0]; + long responseLen = readResponseLenHeader(memory, responsePtr); + responseAllocationLen = responseAllocationLen(responseLen); + requireValidResponseLen(responseLen); + return readResponsePayload(memory, exportName, responsePtr, (int) responseLen); + } catch (Bbs12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bbs12381WasmException("BBS WASM invocation failed: " + exportName, e); + } finally { + if (responsePtr != 0 && responseAllocationLen > 0) { + instance.export("dealloc").apply(responsePtr, responseAllocationLen); + } + } + } + + private long readResponseLenHeader(Memory mem, int responsePtr) { + byte[] lenBytes = mem.readBytes(responsePtr, 4); + return Integer.toUnsignedLong(ByteBuffer.wrap(lenBytes).order(ByteOrder.LITTLE_ENDIAN).getInt()); + } + + private void requireValidResponseLen(long responseLen) { + if (responseLen == 0 || responseLen > MAX_RESPONSE_LEN) { + throw new Bbs12381WasmException("Invalid BBS WASM response length: " + responseLen); + } + } + + private long responseAllocationLen(long responseLen) { + long maxWasmAllocationLen = Integer.toUnsignedLong(-1); + return responseLen <= maxWasmAllocationLen - 4 ? responseLen + 4 : 0; + } + + private byte[] readResponsePayload(Memory mem, String exportName, int responsePtr, int responseLen) { + byte[] response = mem.readBytes(responsePtr + 4, responseLen); + if (response[0] != 0) { + String message = new String(response, 1, response.length - 1, StandardCharsets.UTF_8); + throw new Bbs12381WasmException("BBS WASM error from " + exportName + ": " + message); + } + return Arrays.copyOfRange(response, 1, response.length); + } + + private static byte suite(BbsCiphersuite ciphersuite) { + return switch (ciphersuite) { + case BLS12381_SHA256 -> SUITE_SHA256; + case BLS12381_SHAKE256 -> SUITE_SHAKE256; + }; + } + + private static void putVarBytes(ByteBuffer buf, byte[] bytes) { + buf.putInt(bytes.length); + buf.put(bytes); + } + + private static void putMessageList(ByteBuffer buf, List messages) { + buf.putInt(messages.size()); + for (byte[] msg : messages) { + putVarBytes(buf, msg); + } + } + + private static long validateMessageList(List messages, String label) { + Objects.requireNonNull(messages, label + " required"); + if (messages.size() > MAX_MESSAGES) { + throw new IllegalArgumentException( + label + " count " + messages.size() + " exceeds " + MAX_MESSAGES); + } + long total = 0L; + for (int i = 0; i < messages.size(); i++) { + byte[] m = messages.get(i); + if (m == null) { + throw new IllegalArgumentException(label + "[" + i + "] must not be null"); + } + if (m.length > MAX_MESSAGE_BYTES) { + throw new IllegalArgumentException( + label + "[" + i + "] length " + m.length + " exceeds " + MAX_MESSAGE_BYTES); + } + total = Math.addExact(total, 4L + m.length); + } + return total; + } + + private static void validateIndexList(int[] indexes, int messageCount, String label) { + Objects.requireNonNull(indexes, label + " required"); + if (indexes.length > MAX_MESSAGES) { + throw new IllegalArgumentException( + label + " count " + indexes.length + " exceeds " + MAX_MESSAGES); + } + int previous = -1; + for (int i = 0; i < indexes.length; i++) { + int idx = indexes[i]; + if (idx < 0 || idx >= messageCount) { + throw new IllegalArgumentException( + label + "[" + i + "] = " + idx + " out of range [0, " + messageCount + ")"); + } + if (idx <= previous) { + throw new IllegalArgumentException( + label + " must be strictly ascending; " + + label + "[" + i + "] = " + idx + " is not greater than previous " + previous); + } + previous = idx; + } + } + + /** + * Like {@link #validateIndexList} but for the proof_verify path where the + * total message count is not known to the caller (only the disclosed + * subset is). Validates strict-ascending, no-duplicate, non-negative. + */ + private static void validateDisclosedIndexesForVerify(int[] indexes, String label) { + Objects.requireNonNull(indexes, label + " required"); + if (indexes.length > MAX_MESSAGES) { + throw new IllegalArgumentException( + label + " count " + indexes.length + " exceeds " + MAX_MESSAGES); + } + int previous = -1; + for (int i = 0; i < indexes.length; i++) { + int idx = indexes[i]; + if (idx < 0) { + throw new IllegalArgumentException( + label + "[" + i + "] = " + idx + " must be non-negative"); + } + if (idx <= previous) { + throw new IllegalArgumentException( + label + " must be strictly ascending; " + + label + "[" + i + "] = " + idx + " is not greater than previous " + previous); + } + previous = idx; + } + } + + private static void requireLength(byte[] arr, int length, String label) { + Objects.requireNonNull(arr, label + " required"); + if (arr.length != length) { + throw new IllegalArgumentException(label + " must be " + length + " bytes, got " + arr.length); + } + } + + private static void requireMaxLength(byte[] arr, int max, String label) { + Objects.requireNonNull(arr, label + " required"); + if (arr.length > max) { + throw new IllegalArgumentException( + label + " length " + arr.length + " exceeds " + max); + } + } + + private static ByteBuffer allocateRequest(long size, String op) { + if (size > MAX_REQUEST_BYTES) { + throw new IllegalArgumentException( + "BBS WASM " + op + " request size " + size + " exceeds " + MAX_REQUEST_BYTES); + } + return ByteBuffer.allocate(Math.toIntExact(size)).order(ByteOrder.LITTLE_ENDIAN); + } + + private static boolean decodeBool(byte[] response, String exportName) { + if (response.length != 1) { + throw new Bbs12381WasmException( + "Invalid BBS WASM " + exportName + " response length: " + response.length); + } + byte b = response[0]; + if (b == 0) { + return false; + } + if (b == 1) { + return true; + } + throw new Bbs12381WasmException( + "Invalid BBS WASM " + exportName + " boolean response byte: 0x" + + String.format("%02x", b & 0xff)); + } +} diff --git a/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/Bbs12381WasmException.java b/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/Bbs12381WasmException.java new file mode 100644 index 0000000..52a255d --- /dev/null +++ b/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/Bbs12381WasmException.java @@ -0,0 +1,14 @@ +package com.bloxbean.cardano.zeroj.bbs.wasm; + +/** + * Runtime exception raised by the Chicory-backed CFRG BBS WASM provider. + */ +public class Bbs12381WasmException extends RuntimeException { + public Bbs12381WasmException(String message) { + super(message); + } + + public Bbs12381WasmException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/WasmBbsProvider.java b/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/WasmBbsProvider.java new file mode 100644 index 0000000..93cd34d --- /dev/null +++ b/zeroj-bbs-wasm/src/main/java/com/bloxbean/cardano/zeroj/bbs/wasm/WasmBbsProvider.java @@ -0,0 +1,152 @@ +package com.bloxbean.cardano.zeroj.bbs.wasm; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsProof; +import com.bloxbean.cardano.zeroj.bbs.BbsPublicKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSecretKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSignature; +import com.bloxbean.cardano.zeroj.bbs.internal.BbsCodec; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProvider; + +import java.security.SecureRandom; +import java.util.List; +import java.util.Objects; + +/** + * Full Rust-WASM CFRG BBS provider. The entire BBS algorithm runs inside + * WebAssembly (zkryptium 0.6.1 compiled to {@code wasm32-unknown-unknown}) + * executed through Chicory. ZeroJ's Java layer only serializes requests, + * parses responses, and supplies entropy via the single named host import + * {@code env.zeroj_host_getrandom}. + * + *

This provider is per-{@link BbsCiphersuite}. Construct one instance per + * ciphersuite you intend to use. The underlying {@link Bbs12381WasmClient} is + * thread-safe; the wrapper does no further synchronization.

+ */ +public final class WasmBbsProvider implements BbsProvider { + private final BbsCiphersuite ciphersuite; + private final Bbs12381WasmClient client; + + public WasmBbsProvider(BbsCiphersuite ciphersuite, Bbs12381WasmClient client) { + this.ciphersuite = Objects.requireNonNull(ciphersuite, "ciphersuite required"); + this.client = Objects.requireNonNull(client, "client required"); + } + + public static WasmBbsProvider createDefault() { + return createDefault(BbsCiphersuite.BLS12381_SHA256); + } + + public static WasmBbsProvider createDefault(BbsCiphersuite ciphersuite) { + return new WasmBbsProvider(ciphersuite, Bbs12381WasmClient.createDefault()); + } + + public static WasmBbsProvider createDefault(BbsCiphersuite ciphersuite, SecureRandom random) { + return new WasmBbsProvider(ciphersuite, Bbs12381WasmClient.createDefault(random)); + } + + @Override + public String id() { + return "zeroj-bbs-wasm-zkryptium"; + } + + @Override + public BbsCiphersuite ciphersuite() { + return ciphersuite; + } + + @Override + public BbsSecretKey keyGen(byte[] keyMaterial, byte[] keyInfo) { + byte[] sk = client.keyGen(ciphersuite, keyMaterial, keyInfo); + return new BbsSecretKey(BbsCodec.scalarFromBytes(sk, "BBS secret key"), ciphersuite); + } + + @Override + public BbsPublicKey skToPk(BbsSecretKey secretKey) { + requireSuite(secretKey); + byte[] pk = client.skToPk(ciphersuite, secretKey.toBytes()); + return new BbsPublicKey(pk, ciphersuite); + } + + @Override + public BbsSignature sign( + BbsSecretKey secretKey, BbsPublicKey publicKey, List messages, byte[] header) { + requireSuite(secretKey); + requireSuite(publicKey); + byte[] sig = client.sign(ciphersuite, secretKey.toBytes(), publicKey.bytes(), header, messages); + return new BbsSignature(sig, ciphersuite); + } + + @Override + public boolean verify( + BbsPublicKey publicKey, BbsSignature signature, List messages, byte[] header) { + if (publicKey.ciphersuite() != ciphersuite || signature.ciphersuite() != ciphersuite) { + return false; + } + return client.verify(ciphersuite, publicKey.bytes(), signature.bytes(), header, messages); + } + + @Override + public BbsProof proofGen( + BbsPublicKey publicKey, + BbsSignature signature, + List messages, + byte[] header, + byte[] presentationHeader, + int[] disclosedIndexes, + SecureRandom random) { + // Per-call SecureRandom drives the host getrandom import for the + // duration of this synchronized invocation, matching the contract + // honored by PureJavaBbsProvider. + requireSuite(publicKey); + requireSuite(signature); + byte[] proof = client.proofGen( + ciphersuite, + publicKey.bytes(), + signature.bytes(), + header, + presentationHeader, + messages, + disclosedIndexes, + Objects.requireNonNull(random, "random required")); + return new BbsProof(proof, ciphersuite); + } + + @Override + public boolean proofVerify( + BbsPublicKey publicKey, + BbsProof proof, + byte[] header, + byte[] presentationHeader, + List disclosedMessages, + int[] disclosedIndexes) { + if (publicKey.ciphersuite() != ciphersuite || proof.ciphersuite() != ciphersuite) { + return false; + } + return client.proofVerify( + ciphersuite, + publicKey.bytes(), + proof.bytes(), + header, + presentationHeader, + disclosedMessages, + disclosedIndexes); + } + + private void requireSuite(BbsSecretKey secretKey) { + if (Objects.requireNonNull(secretKey, "secret key required").ciphersuite() != ciphersuite) { + throw new IllegalArgumentException("BBS secret key ciphersuite mismatch"); + } + } + + private void requireSuite(BbsPublicKey publicKey) { + if (Objects.requireNonNull(publicKey, "public key required").ciphersuite() != ciphersuite) { + throw new IllegalArgumentException("BBS public key ciphersuite mismatch"); + } + } + + private void requireSuite(BbsSignature signature) { + if (Objects.requireNonNull(signature, "signature required").ciphersuite() != ciphersuite) { + throw new IllegalArgumentException("BBS signature ciphersuite mismatch"); + } + } +} diff --git a/zeroj-bbs-wasm/src/main/resources/META-INF/native-image/com.bloxbean.cardano.zeroj/zeroj-bbs-wasm/resource-config.json b/zeroj-bbs-wasm/src/main/resources/META-INF/native-image/com.bloxbean.cardano.zeroj/zeroj-bbs-wasm/resource-config.json new file mode 100644 index 0000000..6dac94f --- /dev/null +++ b/zeroj-bbs-wasm/src/main/resources/META-INF/native-image/com.bloxbean.cardano.zeroj/zeroj-bbs-wasm/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qzeroj-bbs-wasm/zeroj_bbs.wasm\\E" + } + ] + } +} diff --git a/zeroj-bbs-wasm/src/test/java/com/bloxbean/cardano/zeroj/bbs/wasm/WasmBbsProviderTest.java b/zeroj-bbs-wasm/src/test/java/com/bloxbean/cardano/zeroj/bbs/wasm/WasmBbsProviderTest.java new file mode 100644 index 0000000..72d5d50 --- /dev/null +++ b/zeroj-bbs-wasm/src/test/java/com/bloxbean/cardano/zeroj/bbs/wasm/WasmBbsProviderTest.java @@ -0,0 +1,684 @@ +package com.bloxbean.cardano.zeroj.bbs.wasm; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsKeyPair; +import com.bloxbean.cardano.zeroj.bbs.BbsProof; +import com.bloxbean.cardano.zeroj.bbs.BbsPublicKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSecretKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSignature; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.ExternalType; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class WasmBbsProviderTest { + + private static final byte[] KEY_MATERIAL = hex("746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579"); + private static final byte[] KEY_INFO = hex("746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e"); + private static final byte[] EXPECTED_SK = hex("60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc"); + private static final byte[] EXPECTED_PK = hex("a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c"); + private static final byte[] HEADER = hex("11223344556677889900aabbccddeeff"); + private static final byte[] PRESENTATION_HEADER = hex("bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501"); + private static final byte[] SINGLE_MSG = hex("9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02"); + private static final byte[] EXPECTED_SIG_SHA256 = hex("84773160b824e194073a57493dac1a20b667af70cd2352d8af241c77658da5253aa8458317cca0eae615690d55b1f27164657dcafee1d5c1973947aa70e2cfbb4c892340be5969920d0916067b4565a0"); + + // SHAKE-256 ciphersuite fixtures (same key_material + key_info, different ciphersuite). + private static final byte[] EXPECTED_SK_SHAKE = hex("2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079"); + private static final byte[] EXPECTED_PK_SHAKE = hex("92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5"); + private static final byte[] EXPECTED_SIG_SHAKE_SINGLE = hex("b9a622a4b404e6ca4c85c15739d2124a1deb16df750be202e2430e169bc27fb71c44d98e6d40792033e1c452145ada95030832c5dc778334f2f1b528eced21b0b97a12025a283d78b7136bb9825d04ef"); + private static final byte[] EXPECTED_SIG_SHAKE_MULTI = hex("956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e"); + + // SHA-256 multi-message signature (signature004.json, 10 messages, with header). + private static final byte[] EXPECTED_SIG_SHA256_MULTI = hex("8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8"); + + // SHA-256 no-header signature (signature010.json, 10 messages, empty header). + private static final byte[] EXPECTED_SIG_SHA256_NOHEADER = hex("8c87e2080859a97299c148427cd2fcf390d24bea850103a9748879039262ecf4f42206f6ef767f298b6a96b424c1e86c26f8fba62212d0e05b95261c2cc0e5fdc63a32731347e810fd12e9c58355aa0d"); + + private static List tenFixtureMessages() { + return List.of( + hex("9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02"), + hex("c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80"), + hex("7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73"), + hex("77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c"), + hex("496694774c5604ab1b2544eababcf0f53278ff50"), + hex("515ae153e22aae04ad16f759e07237b4"), + hex("d183ddc6e2665aa4e2f088af"), + hex("ac55fb33a75909ed"), + hex("96012096"), + new byte[0]); + } + + // SHA-256 proof001: single-message revealed proof; CFRG mockedRng-derived + // proof bytes; proof_verify must accept. + private static final byte[] PROOF_SHA256_PROOF001 = hex("94916292a7a6bade28456c601d3af33fcf39278d6594b467e128a3f83686a104ef2b2fcf72df0215eeaf69262ffe8194a19fab31a82ddbe06908985abc4c9825788b8a1610942d12b7f5debbea8985296361206dbace7af0cc834c80f33e0aadaeea5597befbb651827b5eed5a66f1a959bb46cfd5ca1a817a14475960f69b32c54db7587b5ee3ab665fbd37b506830a49f21d592f5e634f47cee05a025a2f8f94e73a6c15f02301d1178a92873b6e8634bafe4983c3e15a663d64080678dbf29417519b78af042be2b3e1c4d08b8d520ffab008cbaaca5671a15b22c239b38e940cfeaa5e72104576a9ec4a6fad78c532381aeaa6fb56409cef56ee5c140d455feeb04426193c57086c9b6d397d9418"); + + // SHAKE-256 proof001. + private static final byte[] PROOF_SHAKE_PROOF001 = hex("89e4ab0c160880e0c2f12a754b9c051ed7f5fccfee3d5cbbb62e1239709196c737fff4303054660f8fcd08267a5de668a2e395ebe8866bdcb0dff9786d7014fa5e3c8cf7b41f8d7510e27d307f18032f6b788e200b9d6509f40ce1d2f962ceedb023d58ee44d660434e6ba60ed0da1a5d2cde031b483684cd7c5b13295a82f57e209b584e8fe894bcc964117bf3521b43d8e2eb59ce31f34d68b39f05bb2c625e4de5e61e95ff38bfd62ab07105d016414b45b01625c69965ad3c8a933e7b25d93daeb777302b966079827a99178240e6c3f13b7db2fb1f14790940e239d775ab32f539bdf9f9b582b250b05882996832652f7f5d3b6e04744c73ada1702d6791940ccbd75e719537f7ace6ee817298d"); + + // SHA-256 proof003: 10-message signature, disclosed {0, 2, 4, 6}, 6 hidden. + // Exercises the core BBS selective-disclosure path against an official + // CFRG mockedRng-derived proof. + private static final byte[] PROOF_SHA256_PROOF003 = hex("a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a"); + + @Test + void wasmModule_hasExactlyOneImportAndExpectedExports() throws IOException { + var module = Parser.parse(loadDefaultWasm()); + + assertEquals(1, module.importSection().importCount()); + var imp = module.importSection().getImport(0); + assertEquals("env", imp.module()); + assertEquals("zeroj_host_getrandom", imp.name()); + assertEquals(ExternalType.FUNCTION, imp.importType()); + + Set exports = new HashSet<>(); + for (int i = 0; i < module.exportSection().exportCount(); i++) { + var export = module.exportSection().getExport(i); + if (export.exportType() == ExternalType.FUNCTION) { + exports.add(export.name()); + } + } + assertTrue(exports.contains("zeroj_bbs_version")); + assertTrue(exports.contains("zeroj_bbs_keygen")); + assertTrue(exports.contains("zeroj_bbs_sk_to_pk")); + assertTrue(exports.contains("zeroj_bbs_sign")); + assertTrue(exports.contains("zeroj_bbs_verify")); + assertTrue(exports.contains("zeroj_bbs_proof_gen")); + assertTrue(exports.contains("zeroj_bbs_proof_verify")); + assertTrue(exports.contains("alloc")); + assertTrue(exports.contains("dealloc")); + } + + @Test + void keygenAndSkToPk_matchDraft10ShaFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + + BbsSecretKey sk = provider.keyGen(KEY_MATERIAL, KEY_INFO); + assertArrayEquals(EXPECTED_SK, sk.toBytes()); + + BbsPublicKey pk = provider.skToPk(sk); + assertArrayEquals(EXPECTED_PK, pk.bytes()); + } + + @Test + void sign_matchesDraft10ShaSingleMessageFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), List.of(SINGLE_MSG), HEADER); + + assertArrayEquals(EXPECTED_SIG_SHA256, sig.bytes()); + assertTrue(provider.verify(kp.publicKey(), sig, List.of(SINGLE_MSG), HEADER)); + } + + @Test + void verify_rejectsTamperedSignature() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), List.of(SINGLE_MSG), HEADER); + + byte[] bad = sig.bytes(); + bad[bad.length - 1] ^= 1; + + assertFalse(provider.verify( + kp.publicKey(), + new BbsSignature(bad, BbsCiphersuite.BLS12381_SHA256), + List.of(SINGLE_MSG), + HEADER)); + } + + @Test + void proofGen_roundtripsViaProofVerify() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = List.of(SINGLE_MSG); + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, HEADER); + + BbsProof proof = provider.proofGen( + kp.publicKey(), + sig, + messages, + HEADER, + PRESENTATION_HEADER, + new int[]{0}, + new SecureRandom()); + + assertTrue(provider.proofVerify( + kp.publicKey(), + proof, + HEADER, + PRESENTATION_HEADER, + messages, + new int[]{0})); + } + + @Test + void proofGen_honorsPerCallSecureRandomOnEveryInvocation() { + // zkryptium's internal rand::thread_rng() seeds itself from getrandom + // exactly once per thread and then derives bytes from a cached chacha + // state. To honor ADR-0019 §7's per-call SecureRandom contract on + // every invocation, Bbs12381WasmClient.proofGen builds a *fresh* + // Chicory instance per call so ThreadRng has to re-seed each time. + // + // This test asserts the contract holds across N successive calls, not + // just the first one (which was a real Codex finding against the + // earlier shared-instance design). + var defaultCounter = new CountingSecureRandom(); + var provider = new WasmBbsProvider( + BbsCiphersuite.BLS12381_SHA256, + Bbs12381WasmClient.createDefault(defaultCounter)); + + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = List.of(SINGLE_MSG); + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, HEADER); + + // Deterministic ops must NOT touch the host RNG. + assertEquals(0, defaultCounter.bytesRead, "deterministic ops must not call host getrandom"); + + int callCount = 5; + for (int i = 0; i < callCount; i++) { + var perCall = new CountingSecureRandom(); + BbsProof proof = provider.proofGen( + kp.publicKey(), sig, messages, HEADER, PRESENTATION_HEADER, + new int[]{0}, perCall); + assertTrue(perCall.bytesRead > 0, + "per-call SecureRandom must drive host getrandom on call " + i + + " (read " + perCall.bytesRead + " bytes)"); + assertTrue(provider.proofVerify( + kp.publicKey(), proof, HEADER, PRESENTATION_HEADER, messages, new int[]{0})); + } + assertEquals(0, defaultCounter.bytesRead, + "defaultRandom must NOT be read across " + + callCount + " proofGen calls with per-call SecureRandoms"); + } + + /** SecureRandom subclass that records how many bytes have been consumed via nextBytes. */ + private static final class CountingSecureRandom extends SecureRandom { + volatile int bytesRead = 0; + + CountingSecureRandom() { + super(new java.security.SecureRandomSpi() { + @Override protected void engineSetSeed(byte[] seed) {} + @Override protected void engineNextBytes(byte[] bytes) { + // Fill with arbitrary deterministic bytes so proof_gen still + // succeeds (zkryptium will reject out-of-range scalars, + // 0x42... is well below r). + java.util.Arrays.fill(bytes, (byte) 0x42); + } + @Override protected byte[] engineGenerateSeed(int numBytes) { + byte[] b = new byte[numBytes]; + java.util.Arrays.fill(b, (byte) 0x42); + return b; + } + }, null); + } + + @Override + public void nextBytes(byte[] bytes) { + super.nextBytes(bytes); + bytesRead += bytes.length; + } + } + + @Test + void proofGen_isNonDeterministicAcrossCalls() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = List.of(SINGLE_MSG); + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, HEADER); + + BbsProof first = provider.proofGen( + kp.publicKey(), sig, messages, HEADER, PRESENTATION_HEADER, new int[]{0}, new SecureRandom()); + BbsProof second = provider.proofGen( + kp.publicKey(), sig, messages, HEADER, PRESENTATION_HEADER, new int[]{0}, new SecureRandom()); + + assertFalse(java.util.Arrays.equals(first.bytes(), second.bytes()), + "host RNG must produce distinct proofs across calls"); + assertTrue(provider.proofVerify( + kp.publicKey(), first, HEADER, PRESENTATION_HEADER, messages, new int[]{0})); + assertTrue(provider.proofVerify( + kp.publicKey(), second, HEADER, PRESENTATION_HEADER, messages, new int[]{0})); + } + + @Test + void shake256_signRoundtripsViaVerify() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHAKE256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), List.of(SINGLE_MSG), HEADER); + assertTrue(provider.verify(kp.publicKey(), sig, List.of(SINGLE_MSG), HEADER)); + } + + @Test + void keygenAndSkToPk_matchDraft10ShakeFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHAKE256); + + BbsSecretKey sk = provider.keyGen(KEY_MATERIAL, KEY_INFO); + assertArrayEquals(EXPECTED_SK_SHAKE, sk.toBytes()); + + BbsPublicKey pk = provider.skToPk(sk); + assertArrayEquals(EXPECTED_PK_SHAKE, pk.bytes()); + } + + @Test + void sign_matchesDraft10ShakeSingleMessageFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHAKE256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), List.of(SINGLE_MSG), HEADER); + + assertArrayEquals(EXPECTED_SIG_SHAKE_SINGLE, sig.bytes()); + assertTrue(provider.verify(kp.publicKey(), sig, List.of(SINGLE_MSG), HEADER)); + } + + @Test + void sign_matchesDraft10ShakeMultiMessageFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHAKE256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = tenFixtureMessages(); + + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, HEADER); + + assertArrayEquals(EXPECTED_SIG_SHAKE_MULTI, sig.bytes()); + assertTrue(provider.verify(kp.publicKey(), sig, messages, HEADER)); + } + + @Test + void sign_matchesDraft10ShaMultiMessageFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = tenFixtureMessages(); + + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, HEADER); + + assertArrayEquals(EXPECTED_SIG_SHA256_MULTI, sig.bytes()); + assertTrue(provider.verify(kp.publicKey(), sig, messages, HEADER)); + } + + @Test + void sign_matchesDraft10ShaNoHeaderFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = tenFixtureMessages(); + byte[] emptyHeader = new byte[0]; + + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, emptyHeader); + + assertArrayEquals(EXPECTED_SIG_SHA256_NOHEADER, sig.bytes()); + assertTrue(provider.verify(kp.publicKey(), sig, messages, emptyHeader)); + } + + @Test + void proofVerify_acceptsOfficialDraft10ShaProofFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsPublicKey pk = new BbsPublicKey(EXPECTED_PK, BbsCiphersuite.BLS12381_SHA256); + BbsProof proof = new BbsProof(PROOF_SHA256_PROOF001, BbsCiphersuite.BLS12381_SHA256); + List disclosedMessages = List.of(SINGLE_MSG); + int[] disclosedIndexes = {0}; + + assertTrue(provider.proofVerify( + pk, proof, HEADER, PRESENTATION_HEADER, disclosedMessages, disclosedIndexes)); + } + + @Test + void proofVerify_acceptsOfficialDraft10ShakeProofFixture() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHAKE256); + BbsPublicKey pk = new BbsPublicKey(EXPECTED_PK_SHAKE, BbsCiphersuite.BLS12381_SHAKE256); + BbsProof proof = new BbsProof(PROOF_SHAKE_PROOF001, BbsCiphersuite.BLS12381_SHAKE256); + List disclosedMessages = List.of(SINGLE_MSG); + int[] disclosedIndexes = {0}; + + assertTrue(provider.proofVerify( + pk, proof, HEADER, PRESENTATION_HEADER, disclosedMessages, disclosedIndexes)); + } + + @Test + void proofGen_hiddenMessageSelectiveDisclosureRoundtrip() { + // Selective disclosure: 10 messages, reveal {0, 2, 4, 6} = 4 disclosed, + // 6 hidden. Exercises the core BBS selective-disclosure path through + // the WASM provider end-to-end. + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List allMessages = tenFixtureMessages(); + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), allMessages, HEADER); + + int[] disclosed = {0, 2, 4, 6}; + BbsProof proof = provider.proofGen( + kp.publicKey(), sig, allMessages, HEADER, PRESENTATION_HEADER, + disclosed, new SecureRandom()); + + List disclosedMessages = List.of( + allMessages.get(0), allMessages.get(2), allMessages.get(4), allMessages.get(6)); + assertTrue(provider.proofVerify( + kp.publicKey(), proof, HEADER, PRESENTATION_HEADER, disclosedMessages, disclosed)); + + // Same proof with disclosed messages from a different signature must reject. + List mangled = List.of( + allMessages.get(1), allMessages.get(2), allMessages.get(4), allMessages.get(6)); + assertFalse(provider.proofVerify( + kp.publicKey(), proof, HEADER, PRESENTATION_HEADER, mangled, disclosed)); + } + + @Test + void proofVerify_acceptsOfficialDraft10ShaHiddenMessageProof() { + // proof003.json: 10-message signature with 4 revealed (disclosedIndexes + // [0, 2, 4, 6]) and 6 hidden. The official CFRG mockedRng-derived + // proof bytes; proof_verify must accept. + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsPublicKey pk = new BbsPublicKey(EXPECTED_PK, BbsCiphersuite.BLS12381_SHA256); + BbsProof proof = new BbsProof(PROOF_SHA256_PROOF003, BbsCiphersuite.BLS12381_SHA256); + List allMessages = tenFixtureMessages(); + int[] disclosed = {0, 2, 4, 6}; + List disclosedMessages = List.of( + allMessages.get(0), allMessages.get(2), allMessages.get(4), allMessages.get(6)); + + assertTrue(provider.proofVerify( + pk, proof, HEADER, PRESENTATION_HEADER, disclosedMessages, disclosed)); + } + + @Test + void proofGen_rejectsDuplicateDisclosedIndexes() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsKeyPair kp = provider.keyPair(KEY_MATERIAL, KEY_INFO); + List messages = tenFixtureMessages(); + BbsSignature sig = provider.sign(kp.secretKey(), kp.publicKey(), messages, HEADER); + + IllegalArgumentException dup = assertThrows(IllegalArgumentException.class, () -> + provider.proofGen( + kp.publicKey(), sig, messages, HEADER, PRESENTATION_HEADER, + new int[]{0, 0}, new SecureRandom())); + assertTrue(dup.getMessage().contains("strictly ascending"), dup.getMessage()); + + IllegalArgumentException unsorted = assertThrows(IllegalArgumentException.class, () -> + provider.proofGen( + kp.publicKey(), sig, messages, HEADER, PRESENTATION_HEADER, + new int[]{2, 0}, new SecureRandom())); + assertTrue(unsorted.getMessage().contains("strictly ascending"), unsorted.getMessage()); + } + + @Test + void proofVerify_rejectsDuplicateDisclosedIndexes() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsPublicKey pk = new BbsPublicKey(EXPECTED_PK, BbsCiphersuite.BLS12381_SHA256); + BbsProof proof = new BbsProof(PROOF_SHA256_PROOF001, BbsCiphersuite.BLS12381_SHA256); + List dup = List.of(SINGLE_MSG, SINGLE_MSG); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + provider.proofVerify( + pk, proof, HEADER, PRESENTATION_HEADER, dup, new int[]{0, 0})); + assertTrue(ex.getMessage().contains("strictly ascending"), ex.getMessage()); + } + + @Test + void proofVerify_rejectsOfficialProofWithWrongPresentationHeader() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + BbsPublicKey pk = new BbsPublicKey(EXPECTED_PK, BbsCiphersuite.BLS12381_SHA256); + BbsProof proof = new BbsProof(PROOF_SHA256_PROOF001, BbsCiphersuite.BLS12381_SHA256); + List disclosedMessages = List.of(SINGLE_MSG); + int[] disclosedIndexes = {0}; + byte[] wrongPh = new byte[PRESENTATION_HEADER.length]; + System.arraycopy(PRESENTATION_HEADER, 0, wrongPh, 0, PRESENTATION_HEADER.length); + wrongPh[0] ^= 1; + + assertFalse(provider.proofVerify( + pk, proof, HEADER, wrongPh, disclosedMessages, disclosedIndexes)); + } + + @Test + void rawInvocation_reportsTypedExceptionOnShortInput() { + var client = Bbs12381WasmClient.createDefault(); + + assertThrows(Bbs12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bbs_sign", new byte[1])); + assertThrows(Bbs12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bbs_verify", new byte[]{})); + } + + @Test + void rawInvocation_reportsTypedExceptionOnInvalidPublicKey() { + var client = Bbs12381WasmClient.createDefault(); + + byte[] req = new byte[1 + 32]; + req[0] = Bbs12381WasmClient.SUITE_SHA256; + assertThrows(Bbs12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bbs_sk_to_pk", + java.util.Arrays.copyOf(req, req.length - 1))); + } + + @Test + void repeatedErrors_doNotPoisonClient() { + var provider = WasmBbsProvider.createDefault(BbsCiphersuite.BLS12381_SHA256); + for (int i = 0; i < 50; i++) { + assertThrows(Bbs12381WasmException.class, + () -> provider.keyGen(new byte[0], new byte[0])); + } + BbsSecretKey sk = provider.keyGen(KEY_MATERIAL, KEY_INFO); + assertArrayEquals(EXPECTED_SK, sk.toBytes()); + } + + @Test + void malformedResponseLength_freesResponseAllocationOnInvoke() { + SecureRandom rng = new SecureRandom(); + var malformed = new Bbs12381WasmClient(malformedResponseWasm(), rng); + + assertThrows(Bbs12381WasmException.class, + () -> malformed.invokeRawForTesting("malformed_response", new byte[]{1, 2, 3})); + + assertEquals(2, malformed.invokeExportForTesting("dealloc_count")); + assertEquals(4, malformed.invokeExportForTesting("last_dealloc_len")); + } + + @Test + void malformedResponseLength_freesResponseAllocationOnInvokeNoArg() { + SecureRandom rng = new SecureRandom(); + var malformed = new Bbs12381WasmClient(malformedResponseWasm(), rng); + + assertThrows(Bbs12381WasmException.class, + () -> malformed.invokeNoArgRawForTesting("malformed_noarg")); + + assertEquals(1, malformed.invokeExportForTesting("dealloc_count")); + assertEquals(4, malformed.invokeExportForTesting("last_dealloc_len")); + } + + @Test + void verify_rejectsNonCanonicalBooleanResponse() { + SecureRandom rng = new SecureRandom(); + var bad = new Bbs12381WasmClient(badBoolResponseWasm(), rng); + + byte[] pk = new byte[96]; + byte[] sig = new byte[80]; + Bbs12381WasmException ex = assertThrows( + Bbs12381WasmException.class, + () -> bad.verify(BbsCiphersuite.BLS12381_SHA256, pk, sig, new byte[0], List.of())); + assertTrue(ex.getMessage().contains("boolean response byte"), + "expected strict bool decode error, got: " + ex.getMessage()); + } + + private static byte[] loadDefaultWasm() throws IOException { + try (var in = Bbs12381WasmClient.class.getResourceAsStream(Bbs12381WasmClient.DEFAULT_RESOURCE)) { + assertNotNull(in, "BBS WASM resource must be present on the classpath"); + return in.readAllBytes(); + } + } + + // Hand-built synthetic WASM module exporting only the version-1 ABI shape + // we need to exercise the response-buffer cleanup path. Mirrors the + // technique in Bls12381WasmClientTest.malformedResponseWasm. Notably this + // synthetic module declares no imports, so it remains compatible with the + // Bbs12381WasmClient constructor (which always supplies the + // env.zeroj_host_getrandom import — Chicory ignores unused-import slots). + private static byte[] malformedResponseWasm() { + var wasm = new ByteArrayOutputStream(); + write(wasm, 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00); + section(wasm, 1, out -> { + u32(out, 4); + funcType(out, 0, true); + funcType(out, 1, true); + funcType(out, 2, false); + funcType(out, 2, true); + }); + section(wasm, 3, out -> { + u32(out, 7); + write(out, 0, 1, 2, 3, 0, 0, 0); + }); + section(wasm, 5, out -> write(out, 1, 0, 1)); + section(wasm, 7, out -> { + u32(out, 8); + export(out, "memory", 2, 0); + export(out, "zeroj_bbs_version", 0, 0); + export(out, "alloc", 0, 1); + export(out, "dealloc", 0, 2); + export(out, "malformed_response", 0, 3); + export(out, "malformed_noarg", 0, 4); + export(out, "dealloc_count", 0, 5); + export(out, "last_dealloc_len", 0, 6); + }); + section(wasm, 10, out -> { + u32(out, 7); + // zeroj_bbs_version: return 1 + code(out, 0x00, 0x41, 0x01, 0x0b); + // alloc: return 0x400 (1024) + code(out, 0x00, 0x41, 0x80, 0x08, 0x0b); + // dealloc: count++, last_len = arg1 + code(out, + 0x00, + 0x41, 0x00, + 0x41, 0x00, + 0x28, 0x02, 0x00, + 0x41, 0x01, + 0x6a, + 0x36, 0x02, 0x00, + 0x41, 0x04, + 0x20, 0x01, + 0x36, 0x02, 0x00, + 0x0b); + // malformed_response: write 0 at addr 8, return 8 + code(out, 0x00, 0x41, 0x08, 0x41, 0x00, 0x36, 0x02, 0x00, 0x41, 0x08, 0x0b); + // malformed_noarg: same + code(out, 0x00, 0x41, 0x08, 0x41, 0x00, 0x36, 0x02, 0x00, 0x41, 0x08, 0x0b); + // dealloc_count: load addr 0 + code(out, 0x00, 0x41, 0x00, 0x28, 0x02, 0x00, 0x0b); + // last_dealloc_len: load addr 4 + code(out, 0x00, 0x41, 0x04, 0x28, 0x02, 0x00, 0x0b); + }); + return wasm.toByteArray(); + } + + // Synthetic WASM that always returns a "success" response with a bool + // payload byte of 0x02 (instead of the legal 0x00 or 0x01). Used to verify + // that Bbs12381WasmClient.decodeBool rejects non-canonical truthy values. + private static byte[] badBoolResponseWasm() { + var wasm = new ByteArrayOutputStream(); + write(wasm, 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00); + section(wasm, 1, out -> { + u32(out, 4); + funcType(out, 0, true); // () -> i32 + funcType(out, 1, true); // (i32) -> i32 + funcType(out, 2, false); // (i32, i32) -> void + funcType(out, 2, true); // (i32, i32) -> i32 + }); + section(wasm, 3, out -> { + u32(out, 4); + write(out, 0, 1, 2, 3); + }); + section(wasm, 5, out -> write(out, 1, 0, 1)); + section(wasm, 7, out -> { + u32(out, 5); + export(out, "memory", 2, 0); + export(out, "zeroj_bbs_version", 0, 0); + export(out, "alloc", 0, 1); + export(out, "dealloc", 0, 2); + export(out, "zeroj_bbs_verify", 0, 3); + }); + section(wasm, 10, out -> { + u32(out, 4); + // zeroj_bbs_version -> 1 + code(out, 0x00, 0x41, 0x01, 0x0b); + // alloc -> 0x400 (1024) + code(out, 0x00, 0x41, 0x80, 0x08, 0x0b); + // dealloc -> no-op + code(out, 0x00, 0x0b); + // zeroj_bbs_verify: write [u32 LE 2 | 0x00 | 0x02] at addr 8, return 8. + // addr 8..12: response length = 2 + // addr 12: status = 0 (success) + // addr 13: payload byte = 0x02 (illegal bool) + code(out, + 0x00, + 0x41, 0x08, 0x41, 0x02, 0x36, 0x02, 0x00, // i32.store [8] = 2 + 0x41, 0x0c, 0x41, 0x00, 0x3a, 0x00, 0x00, // i32.store8 [12] = 0 + 0x41, 0x0d, 0x41, 0x02, 0x3a, 0x00, 0x00, // i32.store8 [13] = 2 + 0x41, 0x08, 0x0b); // return 8 + }); + return wasm.toByteArray(); + } + + private static void funcType(ByteArrayOutputStream out, int paramCount, boolean hasResult) { + write(out, 0x60); + u32(out, paramCount); + for (int i = 0; i < paramCount; i++) { + write(out, 0x7f); + } + u32(out, hasResult ? 1 : 0); + if (hasResult) { + write(out, 0x7f); + } + } + + private static void export(ByteArrayOutputStream out, String name, int kind, int index) { + byte[] nameBytes = name.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + u32(out, nameBytes.length); + out.writeBytes(nameBytes); + write(out, kind); + u32(out, index); + } + + private static void code(ByteArrayOutputStream out, int... body) { + u32(out, body.length); + write(out, body); + } + + private static void section(ByteArrayOutputStream wasm, int id, SectionWriter writer) { + var body = new ByteArrayOutputStream(); + writer.write(body); + write(wasm, id); + u32(wasm, body.size()); + wasm.writeBytes(body.toByteArray()); + } + + private static void u32(ByteArrayOutputStream out, int value) { + int remaining = value; + do { + int b = remaining & 0x7f; + remaining >>>= 7; + if (remaining != 0) { + b |= 0x80; + } + write(out, b); + } while (remaining != 0); + } + + private static void write(ByteArrayOutputStream out, int... bytes) { + for (int b : bytes) { + out.write(b); + } + } + + private static byte[] hex(String hex) { + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } + + private interface SectionWriter { + void write(ByteArrayOutputStream out); + } +} diff --git a/zeroj-bbs/README.md b/zeroj-bbs/README.md new file mode 100644 index 0000000..733ac2c --- /dev/null +++ b/zeroj-bbs/README.md @@ -0,0 +1,129 @@ +# zeroj-bbs + +CFRG BBS draft-10 signatures and selective disclosure for ZeroJ. + +This module implements the BLS12-381 ciphersuites from +`draft-irtf-cfrg-bbs-signatures-10`: + +```text +BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_ +BBS_BLS12381G1_XOF:SHAKE-256_SSWU_RO_ +``` + +Implemented operations: + +- `KeyGen` +- `SkToPk` +- `Sign` +- `Verify` +- `ProofGen` +- `ProofVerify` + +The implementation is vector-tested against the official draft-10 SHA-256 and +SHAKE-256 vectors for key generation, public-key derivation, message scalar +mapping, generator derivation, signatures, proof generation, proof verification, +hash-to-scalar, and mocked random scalars. The tests cover the draft fixture JSON +for both ciphersuites: 10 signature cases and 15 proof cases per ciphersuite. + +## Basic Use + +```java +var service = BbsService.pureJava(); + +// Optional SHAKE-256 ciphersuite: +var shakeService = BbsService.pureJava(BbsCiphersuite.BLS12381_SHAKE256); + +// Optional explicit BLS12-381 provider selection: +var serviceWithBlsProvider = BbsService.withBlsProvider( + BbsCiphersuite.BLS12381_SHA256, + blsProvider); + +var keyPair = service.keyPair(keyMaterial, keyInfo); +var signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + +boolean signatureValid = service.verify(keyPair.publicKey(), signature, messages, header); + +var presentation = service.derivePresentation( + keyPair.publicKey(), + signature, + messages, + header, + presentationHeader, + new int[]{0, 2}); + +boolean proofValid = service.verifyPresentation(keyPair.publicKey(), presentation); +``` + +`messages`, `header`, and `presentationHeader` are byte arrays. Revealed indexes +are zero-based and must be strictly ascending. + +## Presentation Encoding + +`BbsPresentationCodec` wraps draft-compatible proof bytes in a small +deterministic CBOR envelope: + +```java +byte[] cbor = BbsPresentationCodec.encode(presentation); +BbsPresentation decoded = BbsPresentationCodec.decode(cbor); +``` + +Use proof format: + +```text +bbs-cfrg-draft10-presentation-cbor-v1 +``` + +## ZeroJ Verifier + +`BbsZkVerifier` verifies `ZkProofEnvelope` values with: + +- `ProofSystemId.BBS` +- `CurveId.BLS12_381` +- `proofFormat = bbs-cfrg-draft10-presentation-cbor-v1` +- verification material `vkBytes = issuer BBS public key` + +The verifier checks cryptographic validity only. Issuer trust, schema checks, +expiration, revocation, holder binding, and disclosure policy remain application +policy. + +## WASM Provider + +`zeroj-bbs-wasm` provides an explicit opt-in provider: + +```java +var provider = com.bloxbean.cardano.zeroj.bbs.wasm.WasmBbsProvider.createDefault(); +var service = new BbsService(provider); +``` + +It uses the same BBS draft implementation with BLS12-381 operations backed by +the Rust/Chicory `zeroj-bls12381-wasm` module. + +## Native blst BLS Provider + +`zeroj-blst` exposes a native-backed BLS12-381 provider that can be selected +without changing the BBS API: + +```java +var bls = com.bloxbean.cardano.zeroj.blst.BlstBls12381Provider.createDefault(); +var service = BbsService.withBlsProvider(BbsCiphersuite.BLS12381_SHA256, bls); +``` + +The `zeroj-bbs` conformance tests run the same official draft-10 signature and +proof vectors against the pure Java, WASM, and blst BLS providers. + +## Production Hardening + +- SHA-256 and SHAKE-256 ciphersuites are implemented and pass official + draft-10 fixture vectors. +- BBS secret-key, signature, proof-randomness, and hidden-message scalar + multiplications go through the explicit `Bls12381Provider` secret-scalar + boundary. +- The pure Java provider backs that boundary with fixed-schedule Jacobian scalar + multiplication and Montgomery-form scalar inversion. As with any JVM + cryptographic implementation, high-value deployments should still run an + environment-specific side-channel review. + +References: + +- +- diff --git a/zeroj-bbs/build.gradle b/zeroj-bbs/build.gradle new file mode 100644 index 0000000..169954a --- /dev/null +++ b/zeroj-bbs/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'java-library' +} + +description = 'ZeroJ CFRG BBS signatures and selective disclosure' + +dependencies { + api project(':zeroj-api') + api project(':zeroj-backend-spi') + api project(':zeroj-bls12381') + implementation 'co.nstant.in:cbor:0.9' + + testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2' + testImplementation project(':zeroj-bls12381-wasm') + testImplementation project(':zeroj-blst') +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + name = 'ZeroJ BBS' + description = 'Pure Java CFRG BBS signatures and selective disclosure' + } + } + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsCiphersuite.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsCiphersuite.java new file mode 100644 index 0000000..7ef0e99 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsCiphersuite.java @@ -0,0 +1,81 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; + +import java.nio.charset.StandardCharsets; + +/** + * CFRG BBS ciphersuites supported by ZeroJ. + */ +public enum BbsCiphersuite { + BLS12381_SHA256( + "BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_", + "a8ce256102840821a3e94ea9025e4662b205762f9776b3a766c872b948f1fd225e7c59698588e70d11406d161b4e28c9"), + BLS12381_SHAKE256( + "BBS_BLS12381G1_XOF:SHAKE-256_SSWU_RO_", + "8929dfbc7e6642c4ed9cba0856e493f8b9d7d5fcb0c31ef8fdcd34d50648a56c795e106e9eada6e0bda386b414150755"); + + public static final String DEFAULT_PROOF_FORMAT = "bbs-cfrg-draft10-presentation-cbor-v1"; + + private final String ciphersuiteId; + private final byte[] ciphersuiteIdBytes; + private final byte[] apiId; + private final G1Point p1; + + BbsCiphersuite(String ciphersuiteId, String p1CompressedHex) { + this.ciphersuiteId = ciphersuiteId; + this.ciphersuiteIdBytes = ciphersuiteId.getBytes(StandardCharsets.US_ASCII); + this.apiId = (ciphersuiteId + "H2G_HM2S_").getBytes(StandardCharsets.US_ASCII); + this.p1 = Bls12381Codecs.g1FromCompressed(hexToBytes(p1CompressedHex)); + } + + public String ciphersuiteId() { + return ciphersuiteId; + } + + public byte[] ciphersuiteIdBytes() { + return ciphersuiteIdBytes.clone(); + } + + public byte[] apiId() { + return apiId.clone(); + } + + public G1Point p1() { + return p1; + } + + public int scalarBytes() { + return Bls12381Codecs.SCALAR_BYTES; + } + + public int g1Bytes() { + return Bls12381Codecs.G1_COMPRESSED_BYTES; + } + + public int g2Bytes() { + return Bls12381Codecs.G2_COMPRESSED_BYTES; + } + + public int expandLen() { + return 48; + } + + public static BbsCiphersuite fromCiphersuiteId(String ciphersuiteId) { + for (BbsCiphersuite ciphersuite : values()) { + if (ciphersuite.ciphersuiteId.equals(ciphersuiteId)) { + return ciphersuite; + } + } + throw new IllegalArgumentException("Unknown BBS ciphersuite: " + ciphersuiteId); + } + + private static byte[] hexToBytes(String hex) { + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsException.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsException.java new file mode 100644 index 0000000..5f64323 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsException.java @@ -0,0 +1,14 @@ +package com.bloxbean.cardano.zeroj.bbs; + +/** + * Runtime exception raised by CFRG BBS operations. + */ +public class BbsException extends RuntimeException { + public BbsException(String message) { + super(message); + } + + public BbsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsKeyPair.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsKeyPair.java new file mode 100644 index 0000000..861af00 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsKeyPair.java @@ -0,0 +1,16 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.util.Objects; + +/** + * CFRG BBS key pair. + */ +public record BbsKeyPair(BbsSecretKey secretKey, BbsPublicKey publicKey) { + public BbsKeyPair { + Objects.requireNonNull(secretKey, "secretKey required"); + Objects.requireNonNull(publicKey, "publicKey required"); + if (secretKey.ciphersuite() != publicKey.ciphersuite()) { + throw new IllegalArgumentException("secret and public key ciphersuites differ"); + } + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPresentation.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPresentation.java new file mode 100644 index 0000000..dbc26ba --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPresentation.java @@ -0,0 +1,31 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.util.List; +import java.util.Objects; + +/** + * A CFRG BBS selective-disclosure presentation. + */ +public record BbsPresentation( + BbsProof proof, + byte[] header, + byte[] presentationHeader, + List revealedMessages +) { + public BbsPresentation { + Objects.requireNonNull(proof, "proof required"); + header = header != null ? header.clone() : new byte[0]; + presentationHeader = presentationHeader != null ? presentationHeader.clone() : new byte[0]; + revealedMessages = List.copyOf(Objects.requireNonNull(revealedMessages, "revealedMessages required")); + } + + @Override + public byte[] header() { + return header.clone(); + } + + @Override + public byte[] presentationHeader() { + return presentationHeader.clone(); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPresentationCodec.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPresentationCodec.java new file mode 100644 index 0000000..898d720 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPresentationCodec.java @@ -0,0 +1,264 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import co.nstant.in.cbor.CborBuilder; +import co.nstant.in.cbor.CborDecoder; +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.ByteString; +import co.nstant.in.cbor.model.DataItem; +import co.nstant.in.cbor.model.UnicodeString; +import co.nstant.in.cbor.model.UnsignedInteger; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Deterministic CBOR wrapper for draft-compatible BBS proof presentations. + * + *

Map keys are fixed:

+ *
+ * 1: version (uint)
+ * 2: ciphersuite id (text)
+ * 3: draft BBS proof bytes (bstr)
+ * 4: header (bstr)
+ * 5: presentation header (bstr)
+ * 6: revealed messages ([[index, message], ...])
+ * 
+ */ +public final class BbsPresentationCodec { + private static final int VERSION = 1; + private static final int MAX_ENVELOPE_BYTES = 1024 * 1024; + private static final int MAX_HEADER_BYTES = 65_535; + private static final int MAX_PRESENTATION_HEADER_BYTES = 65_535; + private static final int MAX_REVEALED_MESSAGE_BYTES = 65_535; + private static final int MAX_MESSAGES = 1024; + private static final int MAX_CIPHERSUITE_ID_CHARS = 128; + + private BbsPresentationCodec() {} + + public static byte[] encode(BbsPresentation presentation) { + Objects.requireNonNull(presentation, "presentation required"); + try { + BbsProof proof = presentation.proof(); + validateProofLength(proof.bytes(), proof.ciphersuite()); + requireMaxLength(presentation.header(), MAX_HEADER_BYTES, "BBS header"); + requireMaxLength(presentation.presentationHeader(), MAX_PRESENTATION_HEADER_BYTES, "BBS presentation header"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CborEncoder(baos).encode(new CborBuilder() + .addMap() + .put(new UnsignedInteger(1), new UnsignedInteger(VERSION)) + .put(new UnsignedInteger(2), new UnicodeString(proof.ciphersuite().ciphersuiteId())) + .put(new UnsignedInteger(3), new ByteString(proof.bytes())) + .put(new UnsignedInteger(4), new ByteString(presentation.header())) + .put(new UnsignedInteger(5), new ByteString(presentation.presentationHeader())) + .put(new UnsignedInteger(6), encodeRevealedMessages(presentation.revealedMessages())) + .end() + .build()); + return baos.toByteArray(); + } catch (CborException e) { + throw new BbsException("Failed to encode BBS presentation CBOR", e); + } + } + + /** + * Decode a canonical deterministic CBOR BBS presentation. + * + *

The decoder rejects byte-distinct non-canonical encodings of the same + * logical presentation by decoding and re-encoding with the canonical encoder. + * Callers that hash, sign, or content-address presentation envelopes can rely + * on this method accepting only ZeroJ's canonical envelope form.

+ */ + public static BbsPresentation decode(byte[] cbor) { + return decodeStrict(cbor); + } + + public static BbsPresentation decodeStrict(byte[] cbor) { + return decodeInternal(cbor, true); + } + + private static BbsPresentation decodeInternal(byte[] cbor, boolean strict) { + try { + validateEnvelopeBytes(cbor); + CborDecoder decoder = new CborDecoder(new ByteArrayInputStream(cbor)); + decoder.setRejectDuplicateKeys(true); + decoder.setMaxPreallocationSize(MAX_ENVELOPE_BYTES); + List items = decoder.decode(); + if (items.size() != 1 || !(items.getFirst() instanceof co.nstant.in.cbor.model.Map map)) { + throw new BbsException("BBS presentation CBOR must be a map"); + } + if (map.getKeys().size() != 6) { + throw new BbsException("BBS presentation CBOR map must contain exactly 6 keys"); + } + int version = intAt(map, 1); + if (version != VERSION) { + throw new BbsException("Unsupported BBS presentation CBOR version: " + version); + } + BbsCiphersuite ciphersuite = BbsCiphersuite.fromCiphersuiteId(textAt(map, 2)); + byte[] proofBytes = bytesAt(map, 3, "BBS proof", maxProofBytes(ciphersuite)); + BbsProof proof = new BbsProof(proofBytes, ciphersuite); + byte[] header = bytesAt(map, 4, "BBS header", MAX_HEADER_BYTES); + byte[] presentationHeader = bytesAt(map, 5, "BBS presentation header", MAX_PRESENTATION_HEADER_BYTES); + List revealedMessages = decodeRevealedMessages(arrayAt(map, 6)); + validateProofAndRevealedMessageCount(proof.bytes(), ciphersuite, revealedMessages.size()); + BbsPresentation presentation = new BbsPresentation(proof, header, presentationHeader, revealedMessages); + if (strict && !Arrays.equals(cbor, encode(presentation))) { + throw new BbsException("BBS presentation CBOR must be canonical"); + } + return presentation; + } catch (BbsException e) { + throw e; + } catch (Exception e) { + throw new BbsException("Failed to decode BBS presentation CBOR", e); + } + } + + private static Array encodeRevealedMessages(List messages) { + Objects.requireNonNull(messages, "revealed messages required"); + if (messages.size() > MAX_MESSAGES) { + throw new BbsException("BBS revealed message count exceeds " + MAX_MESSAGES); + } + Array outer = new Array(); + int previous = -1; + for (BbsRevealedMessage message : messages) { + if (message.index() <= previous) { + throw new BbsException("BBS revealed message indexes must be strictly ascending"); + } + previous = message.index(); + requireMaxLength(message.message(), MAX_REVEALED_MESSAGE_BYTES, "BBS revealed message"); + Array item = new Array(); + item.add(new UnsignedInteger(message.index())); + item.add(new ByteString(message.message())); + outer.add(item); + } + return outer; + } + + private static List decodeRevealedMessages(Array array) { + if (array.getDataItems().size() > MAX_MESSAGES) { + throw new BbsException("BBS revealed message count exceeds " + MAX_MESSAGES); + } + List out = new ArrayList<>(); + int previous = -1; + for (DataItem item : array.getDataItems()) { + if (!(item instanceof Array pair) || pair.getDataItems().size() != 2) { + throw new BbsException("BBS revealed message must be [index, message]"); + } + int index = intFromItem(pair.getDataItems().get(0), "BBS revealed message index"); + if (index <= previous) { + throw new BbsException("BBS revealed message indexes must be strictly ascending"); + } + previous = index; + byte[] message = bytesFromItem( + pair.getDataItems().get(1), + "BBS revealed message", + MAX_REVEALED_MESSAGE_BYTES); + out.add(new BbsRevealedMessage(index, message)); + } + return List.copyOf(out); + } + + private static int intAt(co.nstant.in.cbor.model.Map map, int key) { + return intFromItem(itemAt(map, key), "BBS presentation CBOR map key " + key); + } + + private static String textAt(co.nstant.in.cbor.model.Map map, int key) { + DataItem item = itemAt(map, key); + if (!(item instanceof UnicodeString text)) { + throw new BbsException("BBS presentation CBOR map key " + key + " must be text"); + } + String value = text.getString(); + if (value.length() > MAX_CIPHERSUITE_ID_CHARS) { + throw new BbsException("BBS ciphersuite id is too long"); + } + return value; + } + + private static byte[] bytesAt(co.nstant.in.cbor.model.Map map, int key, String label, int maxLength) { + return bytesFromItem(itemAt(map, key), label, maxLength); + } + + private static Array arrayAt(co.nstant.in.cbor.model.Map map, int key) { + DataItem item = itemAt(map, key); + if (!(item instanceof Array array)) { + throw new BbsException("BBS presentation CBOR map key " + key + " must be an array"); + } + return array; + } + + private static DataItem itemAt(co.nstant.in.cbor.model.Map map, int key) { + DataItem item = map.get(new UnsignedInteger(key)); + if (item == null) { + throw new BbsException("Missing BBS presentation CBOR map key: " + key); + } + return item; + } + + private static int intFromItem(DataItem item, String label) { + if (!(item instanceof UnsignedInteger number)) { + throw new BbsException(label + " must be an unsigned integer"); + } + BigInteger value = number.getValue(); + if (value.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) { + throw new BbsException(label + " is too large"); + } + return value.intValueExact(); + } + + private static byte[] bytesFromItem(DataItem item, String label, int maxLength) { + if (!(item instanceof ByteString bytes)) { + throw new BbsException(label + " must be a byte string"); + } + byte[] value = bytes.getBytes(); + requireMaxLength(value, maxLength, label); + return value.clone(); + } + + private static void validateEnvelopeBytes(byte[] cbor) { + Objects.requireNonNull(cbor, "CBOR bytes required"); + requireMaxLength(cbor, MAX_ENVELOPE_BYTES, "BBS presentation CBOR"); + } + + private static void requireMaxLength(byte[] bytes, int maxLength, String label) { + Objects.requireNonNull(bytes, label + " required"); + if (bytes.length > maxLength) { + throw new BbsException(label + " exceeds " + maxLength + " bytes"); + } + } + + private static void validateProofLength(byte[] proofBytes, BbsCiphersuite ciphersuite) { + requireMaxLength(proofBytes, maxProofBytes(ciphersuite), "BBS proof"); + hiddenMessageCount(proofBytes, ciphersuite); + } + + private static void validateProofAndRevealedMessageCount( + byte[] proofBytes, + BbsCiphersuite ciphersuite, + int revealedCount) { + int hiddenCount = hiddenMessageCount(proofBytes, ciphersuite); + if (hiddenCount + revealedCount > MAX_MESSAGES) { + throw new BbsException("BBS presentation message count exceeds " + MAX_MESSAGES); + } + } + + private static int hiddenMessageCount(byte[] proofBytes, BbsCiphersuite ciphersuite) { + int floor = 3 * ciphersuite.g1Bytes() + 4 * ciphersuite.scalarBytes(); + if (proofBytes.length < floor) { + throw new BbsException("BBS proof is too short: " + proofBytes.length); + } + int scalarBytes = proofBytes.length - 3 * ciphersuite.g1Bytes(); + if (scalarBytes % ciphersuite.scalarBytes() != 0) { + throw new BbsException("BBS proof scalar section is not aligned to 32-byte scalars"); + } + return scalarBytes / ciphersuite.scalarBytes() - 4; + } + + private static int maxProofBytes(BbsCiphersuite ciphersuite) { + return 3 * ciphersuite.g1Bytes() + (4 + MAX_MESSAGES) * ciphersuite.scalarBytes(); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsProof.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsProof.java new file mode 100644 index 0000000..37cb470 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsProof.java @@ -0,0 +1,46 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.util.Arrays; +import java.util.Objects; + +/** + * CFRG BBS proof octets. + */ +public final class BbsProof { + private final byte[] bytes; + private final BbsCiphersuite ciphersuite; + + public BbsProof(byte[] bytes, BbsCiphersuite ciphersuite) { + Objects.requireNonNull(bytes, "proof bytes required"); + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + int floor = 3 * ciphersuite.g1Bytes() + 4 * ciphersuite.scalarBytes(); + if (bytes.length < floor) { + throw new IllegalArgumentException("BBS proof is too short: " + bytes.length); + } + if ((bytes.length - floor) % ciphersuite.scalarBytes() != 0) { + throw new IllegalArgumentException("BBS proof scalar section is not aligned to 32-byte scalars"); + } + this.bytes = bytes.clone(); + this.ciphersuite = ciphersuite; + } + + public byte[] bytes() { + return bytes.clone(); + } + + public BbsCiphersuite ciphersuite() { + return ciphersuite; + } + + @Override + public boolean equals(Object other) { + return other instanceof BbsProof p + && ciphersuite == p.ciphersuite + && Arrays.equals(bytes, p.bytes); + } + + @Override + public int hashCode() { + return 31 * Arrays.hashCode(bytes) + ciphersuite.hashCode(); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPublicKey.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPublicKey.java new file mode 100644 index 0000000..42d4335 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsPublicKey.java @@ -0,0 +1,49 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.util.Arrays; +import java.util.Objects; + +/** + * CFRG BBS public key octets. + */ +public final class BbsPublicKey { + private final byte[] bytes; + private final BbsCiphersuite ciphersuite; + + public BbsPublicKey(byte[] bytes, BbsCiphersuite ciphersuite) { + this.bytes = requireNonEmpty(bytes, "public key").clone(); + this.ciphersuite = Objects.requireNonNull(ciphersuite, "ciphersuite required"); + if (this.bytes.length != ciphersuite.g2Bytes()) { + throw new IllegalArgumentException("BBS public key must be " + ciphersuite.g2Bytes() + + " bytes, got " + this.bytes.length); + } + } + + public byte[] bytes() { + return bytes.clone(); + } + + public BbsCiphersuite ciphersuite() { + return ciphersuite; + } + + private static byte[] requireNonEmpty(byte[] bytes, String label) { + Objects.requireNonNull(bytes, label + " required"); + if (bytes.length == 0) { + throw new IllegalArgumentException(label + " must not be empty"); + } + return bytes; + } + + @Override + public boolean equals(Object other) { + return other instanceof BbsPublicKey k + && ciphersuite == k.ciphersuite + && Arrays.equals(bytes, k.bytes); + } + + @Override + public int hashCode() { + return 31 * Arrays.hashCode(bytes) + ciphersuite.hashCode(); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsRevealedMessage.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsRevealedMessage.java new file mode 100644 index 0000000..2f5b02c --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsRevealedMessage.java @@ -0,0 +1,41 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Revealed message and original message index for a BBS presentation. + */ +public final class BbsRevealedMessage { + private final int index; + private final byte[] message; + + public BbsRevealedMessage(int index, byte[] message) { + if (index < 0) { + throw new IllegalArgumentException("index must be non-negative"); + } + Objects.requireNonNull(message, "message required"); + this.index = index; + this.message = message.clone(); + } + + public int index() { + return index; + } + + public byte[] message() { + return message.clone(); + } + + @Override + public boolean equals(Object other) { + return other instanceof BbsRevealedMessage m + && index == m.index + && Arrays.equals(message, m.message); + } + + @Override + public int hashCode() { + return 31 * index + Arrays.hashCode(message); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsSecretKey.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsSecretKey.java new file mode 100644 index 0000000..3324bbd --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsSecretKey.java @@ -0,0 +1,19 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * CFRG BBS secret key scalar. + */ +public record BbsSecretKey(BigInteger value, BbsCiphersuite ciphersuite) { + public BbsSecretKey { + Objects.requireNonNull(value, "value required"); + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + com.bloxbean.cardano.zeroj.bbs.internal.BbsCodec.scalarToBytes(value); + } + + public byte[] toBytes() { + return com.bloxbean.cardano.zeroj.bbs.internal.BbsCodec.scalarToBytes(value); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsService.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsService.java new file mode 100644 index 0000000..714cf2c --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsService.java @@ -0,0 +1,134 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import com.bloxbean.cardano.zeroj.bbs.internal.CfrgBbsCore; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProvider; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProviders; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; + +import java.security.SecureRandom; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +/** + * High-level CFRG BBS draft-10 service. + */ +public final class BbsService { + private final BbsProvider provider; + private final SecureRandom random; + + public BbsService(BbsProvider provider) { + this(provider, new SecureRandom()); + } + + public BbsService(BbsProvider provider, SecureRandom random) { + this.provider = Objects.requireNonNull(provider, "provider required"); + this.random = Objects.requireNonNull(random, "secure random required"); + } + + public static BbsService pureJava() { + return new BbsService(BbsProviders.pureJava()); + } + + public static BbsService pureJava(BbsCiphersuite ciphersuite) { + return new BbsService(BbsProviders.pureJava(ciphersuite)); + } + + public static BbsService withBlsProvider(BbsCiphersuite ciphersuite, Bls12381Provider bls) { + return new BbsService(BbsProviders.withBlsProvider(ciphersuite, bls)); + } + + public BbsProvider provider() { + return provider; + } + + public BbsKeyPair keyPair(byte[] keyMaterial, byte[] keyInfo) { + return provider.keyPair(keyMaterial, keyInfo); + } + + /** + * Sign messages with the ZeroJ argument order: messages before header. + * + *

For the draft-10 order, use {@link #sign(BbsSecretKey, BbsPublicKey, byte[], List)}.

+ */ + public BbsSignature sign(BbsSecretKey secretKey, BbsPublicKey publicKey, List messages, byte[] header) { + return provider.sign(secretKey, publicKey, messages, header); + } + + /** + * Sign messages with the draft-10 argument order: header before messages. + */ + public BbsSignature sign(BbsSecretKey secretKey, BbsPublicKey publicKey, byte[] header, List messages) { + return sign(secretKey, publicKey, messages, header); + } + + /** + * Verify a signature with the ZeroJ argument order: messages before header. + * + *

For the draft-10 order, use {@link #verify(BbsPublicKey, BbsSignature, byte[], List)}.

+ */ + public boolean verify(BbsPublicKey publicKey, BbsSignature signature, List messages, byte[] header) { + return provider.verify(publicKey, signature, messages, header); + } + + /** + * Verify a signature with the draft-10 argument order: header before messages. + */ + public boolean verify(BbsPublicKey publicKey, BbsSignature signature, byte[] header, List messages) { + return verify(publicKey, signature, messages, header); + } + + public BbsPresentation derivePresentation( + BbsPublicKey publicKey, + BbsSignature signature, + List messages, + byte[] header, + byte[] presentationHeader, + int[] disclosedIndexes) { + Objects.requireNonNull(messages, "messages required"); + int[] indexes = validateDisclosedIndexes(disclosedIndexes, messages.size()); + BbsProof proof = provider.proofGen( + publicKey, signature, messages, header, presentationHeader, indexes, random); + List revealedMessages = revealedMessages(messages, indexes); + return new BbsPresentation(proof, header, presentationHeader, revealedMessages); + } + + public boolean verifyPresentation(BbsPublicKey publicKey, BbsPresentation presentation) { + Objects.requireNonNull(presentation, "presentation required"); + List revealed = presentation.revealedMessages().stream() + .sorted(Comparator.comparingInt(BbsRevealedMessage::index)) + .toList(); + int[] indexes = revealed.stream().mapToInt(BbsRevealedMessage::index).toArray(); + validateDisclosedIndexes(indexes, hiddenMessageCountFromProof(presentation.proof()) + revealed.size()); + List messages = revealed.stream().map(BbsRevealedMessage::message).toList(); + return provider.proofVerify( + publicKey, + presentation.proof(), + presentation.header(), + presentation.presentationHeader(), + messages, + indexes); + } + + private static List revealedMessages(List messages, int[] indexes) { + Objects.requireNonNull(messages, "messages required"); + return java.util.Arrays.stream(indexes) + .mapToObj(index -> new BbsRevealedMessage(index, messages.get(index))) + .toList(); + } + + private static int[] validateDisclosedIndexes(int[] indexes, int messageCount) { + try { + return CfrgBbsCore.validateDisclosedIndexes(indexes, messageCount); + } catch (RuntimeException e) { + throw new BbsException("Invalid BBS disclosed indexes", e); + } + } + + private static int hiddenMessageCountFromProof(BbsProof proof) { + BbsCiphersuite ciphersuite = proof.ciphersuite(); + int scalarBytes = proof.bytes().length - 3 * ciphersuite.g1Bytes(); + int scalarCount = scalarBytes / ciphersuite.scalarBytes(); + return scalarCount - 4; + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsSignature.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsSignature.java new file mode 100644 index 0000000..8e22d48 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/BbsSignature.java @@ -0,0 +1,43 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import java.util.Arrays; +import java.util.Objects; + +/** + * CFRG BBS signature octets. + */ +public final class BbsSignature { + private final byte[] bytes; + private final BbsCiphersuite ciphersuite; + + public BbsSignature(byte[] bytes, BbsCiphersuite ciphersuite) { + Objects.requireNonNull(bytes, "signature bytes required"); + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + if (bytes.length != ciphersuite.g1Bytes() + ciphersuite.scalarBytes()) { + throw new IllegalArgumentException("BBS signature must be " + + (ciphersuite.g1Bytes() + ciphersuite.scalarBytes()) + " bytes, got " + bytes.length); + } + this.bytes = bytes.clone(); + this.ciphersuite = ciphersuite; + } + + public byte[] bytes() { + return bytes.clone(); + } + + public BbsCiphersuite ciphersuite() { + return ciphersuite; + } + + @Override + public boolean equals(Object other) { + return other instanceof BbsSignature s + && ciphersuite == s.ciphersuite + && Arrays.equals(bytes, s.bytes); + } + + @Override + public int hashCode() { + return 31 * Arrays.hashCode(bytes) + ciphersuite.hashCode(); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/internal/BbsCodec.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/internal/BbsCodec.java new file mode 100644 index 0000000..8c597ac --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/internal/BbsCodec.java @@ -0,0 +1,298 @@ +package com.bloxbean.cardano.zeroj.bbs.internal; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Draft-10 BBS octet serialization helpers. + */ +public final class BbsCodec { + public static final BigInteger R = Bls12381Generators.SCALAR_FIELD_ORDER; + + private BbsCodec() {} + + public record SignatureParts(G1Point a, BigInteger e) { + public SignatureParts { + requireNonIdentity(a, "signature A"); + e = requireNonZeroScalar(e, "signature e"); + } + } + + public record ProofParts( + G1Point aBar, + G1Point bBar, + G1Point d, + BigInteger eHat, + BigInteger r1Hat, + BigInteger r3Hat, + List mHats, + BigInteger challenge + ) { + public ProofParts { + requireNonIdentity(aBar, "proof Abar"); + requireNonIdentity(bBar, "proof Bbar"); + requireNonIdentity(d, "proof D"); + eHat = requireScalar(eHat, "proof eHat"); + r1Hat = requireScalar(r1Hat, "proof r1Hat"); + r3Hat = requireScalar(r3Hat, "proof r3Hat"); + mHats = List.copyOf(Objects.requireNonNull(mHats, "proof commitments required")); + for (BigInteger mHat : mHats) { + requireScalar(mHat, "proof hidden message commitment"); + } + challenge = requireScalar(challenge, "proof challenge"); + } + } + + public static byte[] scalarToBytes(BigInteger scalar) { + return fixedBigEndian(requireNonZeroScalar(scalar, "scalar"), Bls12381Codecs.SCALAR_BYTES); + } + + public static byte[] scalarToBytesAllowZero(BigInteger scalar) { + return fixedBigEndian(requireScalar(scalar, "scalar"), Bls12381Codecs.SCALAR_BYTES); + } + + public static BigInteger scalarFromBytes(byte[] bytes, String label) { + requireLength(bytes, Bls12381Codecs.SCALAR_BYTES, label); + BigInteger scalar = new BigInteger(1, bytes); + if (scalar.compareTo(R) >= 0) { + throw new IllegalArgumentException(label + " is outside BLS12-381 Fr"); + } + return scalar; + } + + public static BigInteger nonZeroScalarFromBytes(byte[] bytes, String label) { + return requireNonZeroScalar(scalarFromBytes(bytes, label), label); + } + + public static byte[] publicKeyToOctets(G2Point point) { + requireNonIdentity(point, "public key"); + return Bls12381Codecs.g2ToCompressed(Bls12381Codecs.requireValid(point)); + } + + public static G2Point octetsToPublicKey(byte[] bytes) { + requireLength(bytes, Bls12381Codecs.G2_COMPRESSED_BYTES, "public key"); + G2Point point = Bls12381Codecs.g2FromCompressed(bytes); + if (point.isInfinity()) { + throw new IllegalArgumentException("BBS public key must not be identity"); + } + return point; + } + + public static byte[] signatureToOctets(SignatureParts signature) { + Objects.requireNonNull(signature, "signature required"); + return concat( + Bls12381Codecs.g1ToCompressed(signature.a()), + scalarToBytes(signature.e())); + } + + public static SignatureParts octetsToSignature(byte[] bytes, BbsCiphersuite ciphersuite) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + requireLength(bytes, ciphersuite.g1Bytes() + ciphersuite.scalarBytes(), "signature"); + G1Point a = Bls12381Codecs.g1FromCompressed(Arrays.copyOfRange(bytes, 0, ciphersuite.g1Bytes())); + if (a.isInfinity()) { + throw new IllegalArgumentException("BBS signature A must not be identity"); + } + BigInteger e = nonZeroScalarFromBytes( + Arrays.copyOfRange(bytes, ciphersuite.g1Bytes(), bytes.length), "signature e"); + return new SignatureParts(a, e); + } + + public static byte[] proofToOctets(ProofParts proof) { + Objects.requireNonNull(proof, "proof required"); + List parts = new ArrayList<>(); + parts.add(Bls12381Codecs.g1ToCompressed(proof.aBar())); + parts.add(Bls12381Codecs.g1ToCompressed(proof.bBar())); + parts.add(Bls12381Codecs.g1ToCompressed(proof.d())); + parts.add(scalarToBytesAllowZero(proof.eHat())); + parts.add(scalarToBytesAllowZero(proof.r1Hat())); + parts.add(scalarToBytesAllowZero(proof.r3Hat())); + for (BigInteger mHat : proof.mHats()) { + parts.add(scalarToBytesAllowZero(mHat)); + } + parts.add(scalarToBytesAllowZero(proof.challenge())); + return concat(parts.toArray(byte[][]::new)); + } + + public static ProofParts octetsToProof(byte[] bytes, BbsCiphersuite ciphersuite) { + Objects.requireNonNull(bytes, "proof bytes required"); + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + int floor = 3 * ciphersuite.g1Bytes() + 4 * ciphersuite.scalarBytes(); + if (bytes.length < floor) { + throw new IllegalArgumentException("BBS proof must be at least " + floor + " bytes, got " + bytes.length); + } + int scalarBytesLen = bytes.length - 3 * ciphersuite.g1Bytes(); + if (scalarBytesLen % ciphersuite.scalarBytes() != 0) { + throw new IllegalArgumentException("BBS proof scalar section is not aligned to 32-byte scalars"); + } + int scalarCount = scalarBytesLen / ciphersuite.scalarBytes(); + if (scalarCount < 4) { + throw new IllegalArgumentException("BBS proof must contain at least 4 scalars"); + } + + int offset = 0; + G1Point aBar = readNonIdentityG1(bytes, offset, "proof Abar"); + offset += ciphersuite.g1Bytes(); + G1Point bBar = readNonIdentityG1(bytes, offset, "proof Bbar"); + offset += ciphersuite.g1Bytes(); + G1Point d = readNonIdentityG1(bytes, offset, "proof D"); + offset += ciphersuite.g1Bytes(); + + List scalars = new ArrayList<>(scalarCount); + while (offset < bytes.length) { + scalars.add(scalarFromBytes(Arrays.copyOfRange(bytes, offset, offset + ciphersuite.scalarBytes()), + "proof scalar")); + offset += ciphersuite.scalarBytes(); + } + + List commitments = scalarCount > 4 + ? scalars.subList(3, scalarCount - 1) + : List.of(); + return new ProofParts( + aBar, + bBar, + d, + scalars.get(0), + scalars.get(1), + scalars.get(2), + commitments, + scalars.get(scalarCount - 1)); + } + + public static byte[] serialize(Object... values) { + List parts = new ArrayList<>(values.length); + for (Object value : values) { + if (value instanceof G1Point point) { + parts.add(Bls12381Codecs.g1ToCompressed(Bls12381Codecs.requireValid(point))); + } else if (value instanceof G2Point point) { + parts.add(Bls12381Codecs.g2ToCompressed(Bls12381Codecs.requireValid(point))); + } else if (value instanceof BigInteger scalar) { + parts.add(scalarToBytesAllowZero(scalar)); + } else if (value instanceof Integer integer) { + parts.add(i2osp(integer.longValue(), 8)); + } else if (value instanceof Long longValue) { + parts.add(i2osp(longValue, 8)); + } else if (value instanceof String string) { + parts.add(string.getBytes(StandardCharsets.US_ASCII)); + } else { + throw new IllegalArgumentException("Unsupported BBS serialization type: " + value); + } + } + return concat(parts.toArray(byte[][]::new)); + } + + public static byte[] i2osp(long value, int length) { + if (value < 0) { + throw new IllegalArgumentException("I2OSP value must be non-negative"); + } + if (length <= 0 || length > 8) { + throw new IllegalArgumentException("I2OSP length must be between 1 and 8"); + } + byte[] out = new byte[length]; + long v = value; + for (int i = length - 1; i >= 0; i--) { + out[i] = (byte) v; + v >>>= 8; + } + if (v != 0) { + throw new IllegalArgumentException("I2OSP value does not fit in " + length + " bytes"); + } + return out; + } + + public static byte[] concat(byte[]... chunks) { + int len = 0; + for (byte[] chunk : chunks) { + len += chunk.length; + } + byte[] out = new byte[len]; + int offset = 0; + for (byte[] chunk : chunks) { + System.arraycopy(chunk, 0, out, offset, chunk.length); + offset += chunk.length; + } + return out; + } + + public static byte[] copy(byte[] bytes) { + return bytes != null ? bytes.clone() : new byte[0]; + } + + public static List copyMessages(List messages) { + Objects.requireNonNull(messages, "messages required"); + List copy = new ArrayList<>(messages.size()); + for (byte[] message : messages) { + copy.add(copy(message)); + } + return List.copyOf(copy); + } + + static BigInteger requireScalar(BigInteger scalar, String label) { + Objects.requireNonNull(scalar, label + " required"); + if (scalar.signum() < 0 || scalar.compareTo(R) >= 0) { + throw new IllegalArgumentException(label + " must be in [0, r)"); + } + return scalar; + } + + static BigInteger requireNonZeroScalar(BigInteger scalar, String label) { + requireScalar(scalar, label); + if (scalar.signum() == 0) { + throw new IllegalArgumentException(label + " must be non-zero"); + } + return scalar; + } + + static G1Point requireNonIdentity(G1Point point, String label) { + Bls12381Codecs.requireValid(point); + if (point.isInfinity()) { + throw new IllegalArgumentException(label + " must not be identity"); + } + return point; + } + + static G2Point requireNonIdentity(G2Point point, String label) { + Bls12381Codecs.requireValid(point); + if (point.isInfinity()) { + throw new IllegalArgumentException(label + " must not be identity"); + } + return point; + } + + private static G1Point readNonIdentityG1(byte[] bytes, int offset, String label) { + G1Point point = Bls12381Codecs.g1FromCompressed( + Arrays.copyOfRange(bytes, offset, offset + Bls12381Codecs.G1_COMPRESSED_BYTES)); + if (point.isInfinity()) { + throw new IllegalArgumentException(label + " must not be identity"); + } + return point; + } + + private static void requireLength(byte[] bytes, int expected, String label) { + Objects.requireNonNull(bytes, label + " bytes required"); + if (bytes.length != expected) { + throw new IllegalArgumentException(label + " must be " + expected + " bytes, got " + bytes.length); + } + } + + private static byte[] fixedBigEndian(BigInteger value, int length) { + byte[] raw = value.toByteArray(); + int rawStart = raw.length > 1 && raw[0] == 0 ? 1 : 0; + int rawLen = raw.length - rawStart; + if (rawLen > length) { + throw new IllegalArgumentException("Value does not fit in " + length + " bytes"); + } + byte[] out = new byte[length]; + System.arraycopy(raw, rawStart, out, length - rawLen, rawLen); + return out; + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/internal/CfrgBbsCore.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/internal/CfrgBbsCore.java new file mode 100644 index 0000000..7c85951 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/internal/CfrgBbsCore.java @@ -0,0 +1,679 @@ +package com.bloxbean.cardano.zeroj.bbs.internal; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Hash; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * CFRG BBS draft-10 algorithms for supported BLS12-381 ciphersuites. + */ +public final class CfrgBbsCore { + private static final BigInteger R = BbsCodec.R; + + private CfrgBbsCore() {} + + public record Generators(G1Point q1, List h) { + public Generators { + BbsCodec.requireNonIdentity(q1, "Q_1"); + h = List.copyOf(Objects.requireNonNull(h, "H generators required")); + for (G1Point point : h) { + BbsCodec.requireNonIdentity(point, "H generator"); + } + } + + public int count() { + return h.size() + 1; + } + } + + record ProofInitResult( + G1Point aBar, + G1Point bBar, + G1Point d, + G1Point t1, + G1Point t2, + BigInteger domain + ) {} + + public static BigInteger keyGen(BbsCiphersuite ciphersuite, byte[] keyMaterial, byte[] keyInfo) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + Objects.requireNonNull(keyMaterial, "key material required"); + keyInfo = BbsCodec.copy(keyInfo); + if (keyMaterial.length < 32) { + throw new IllegalArgumentException("BBS key material must be at least 32 bytes"); + } + if (keyInfo.length > 65535) { + throw new IllegalArgumentException("BBS key info must be at most 65535 bytes"); + } + + byte[] deriveInput = BbsCodec.concat(keyMaterial, BbsCodec.i2osp(keyInfo.length, 2), keyInfo); + // Draft-10 interface vectors bind KeyGen to the BBS API identifier, not only the ciphersuite id. + BigInteger sk = hashToScalar(deriveInput, dst(ciphersuite.apiId(), "KEYGEN_DST_"), ciphersuite); + return BbsCodec.requireNonZeroScalar(sk, "BBS secret key"); + } + + public static byte[] skToPk(BigInteger secretKey, Bls12381Provider bls) { + BbsCodec.requireNonZeroScalar(secretKey, "BBS secret key"); + G2Point publicKey = Objects.requireNonNull(bls, "BLS provider required") + .g2SecretScalarMul(bls.g2Generator(), secretKey); + return BbsCodec.publicKeyToOctets(publicKey); + } + + public static List messagesToScalars(List messages, BbsCiphersuite ciphersuite) { + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + List copiedMessages = BbsCodec.copyMessages(messages); + byte[] mapDst = dst(ciphersuite.apiId(), "MAP_MSG_TO_SCALAR_AS_HASH_"); + List scalars = new ArrayList<>(copiedMessages.size()); + for (byte[] message : copiedMessages) { + scalars.add(hashToScalar(message, mapDst, ciphersuite)); + } + return List.copyOf(scalars); + } + + public static Generators createGenerators(int count, BbsCiphersuite ciphersuite, Bls12381Provider bls) { + if (count < 1) { + throw new IllegalArgumentException("BBS generator count must be at least 1"); + } + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + Objects.requireNonNull(bls, "BLS provider required"); + + byte[] apiId = ciphersuite.apiId(); + byte[] seedDst = dst(apiId, "SIG_GENERATOR_SEED_"); + byte[] generatorDst = dst(apiId, "SIG_GENERATOR_DST_"); + byte[] generatorSeed = dst(apiId, "MESSAGE_GENERATOR_SEED"); + + byte[] v = expandMessage(generatorSeed, seedDst, ciphersuite.expandLen(), ciphersuite); + List points = new ArrayList<>(count); + for (int i = 1; i <= count; i++) { + v = expandMessage(BbsCodec.concat(v, BbsCodec.i2osp(i, 8)), seedDst, ciphersuite.expandLen(), ciphersuite); + points.add(BbsCodec.requireNonIdentity(hashToG1(v, generatorDst, ciphersuite, bls), "BBS generator")); + } + return new Generators(points.getFirst(), points.subList(1, points.size())); + } + + public static BigInteger calculateDomain( + byte[] publicKey, + Generators generators, + byte[] header, + BbsCiphersuite ciphersuite + ) { + Objects.requireNonNull(publicKey, "public key required"); + Objects.requireNonNull(generators, "generators required"); + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + header = BbsCodec.copy(header); + + List domArray = new ArrayList<>(generators.count() + 1); + domArray.add((long) generators.h().size()); + domArray.add(generators.q1()); + domArray.addAll(generators.h()); + byte[] domOcts = BbsCodec.concat(BbsCodec.serialize(domArray.toArray()), ciphersuite.apiId()); + byte[] domInput = BbsCodec.concat(publicKey, domOcts, BbsCodec.i2osp(header.length, 8), header); + return hashToScalar(domInput, dst(ciphersuite.apiId(), "H2S_"), ciphersuite); + } + + public static G1Point calculateB( + Generators generators, + BigInteger domain, + List messages, + BbsCiphersuite ciphersuite + ) { + return computeB(generators, domain, messages, ciphersuite); + } + + public static byte[] sign( + BigInteger secretKey, + byte[] publicKey, + List messages, + byte[] header, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + BbsCodec.requireNonZeroScalar(secretKey, "BBS secret key"); + BbsCodec.octetsToPublicKey(publicKey); + List messageScalars = messagesToScalars(messages, ciphersuite); + Generators generators = createGenerators(messageScalars.size() + 1, ciphersuite, bls); + BbsCodec.SignatureParts signature = coreSign( + secretKey, publicKey, generators, BbsCodec.copy(header), messageScalars, ciphersuite, bls); + return BbsCodec.signatureToOctets(signature); + } + + public static boolean verify( + byte[] publicKey, + byte[] signature, + List messages, + byte[] header, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + try { + List messageScalars = messagesToScalars(messages, ciphersuite); + Generators generators = createGenerators(messageScalars.size() + 1, ciphersuite, bls); + return coreVerify(publicKey, signature, generators, BbsCodec.copy(header), messageScalars, ciphersuite, bls); + } catch (RuntimeException e) { + return false; + } + } + + public static byte[] proofGen( + byte[] publicKey, + byte[] signature, + List messages, + byte[] header, + byte[] presentationHeader, + int[] disclosedIndexes, + BbsCiphersuite ciphersuite, + Bls12381Provider bls, + SecureRandom random + ) { + Objects.requireNonNull(random, "secure random required"); + int messageCount = Objects.requireNonNull(messages, "messages required").size(); + int[] indexes = validateDisclosedIndexes(disclosedIndexes, messageCount); + int undisclosedCount = messageCount - indexes.length; + return proofGenWithRandomScalars( + publicKey, + signature, + messages, + header, + presentationHeader, + indexes, + ciphersuite, + bls, + randomScalars(5 + undisclosedCount, ciphersuite, random)); + } + + public static byte[] proofGenWithRandomScalars( + byte[] publicKey, + byte[] signature, + List messages, + byte[] header, + byte[] presentationHeader, + int[] disclosedIndexes, + BbsCiphersuite ciphersuite, + Bls12381Provider bls, + List randomScalars + ) { + List messageScalars = messagesToScalars(messages, ciphersuite); + int[] indexes = validateDisclosedIndexes(disclosedIndexes, messageScalars.size()); + Generators generators = createGenerators(messageScalars.size() + 1, ciphersuite, bls); + if (!coreVerify(publicKey, signature, generators, BbsCodec.copy(header), messageScalars, ciphersuite, bls, true)) { + throw new IllegalArgumentException("Cannot generate BBS proof for an invalid signature"); + } + + BbsCodec.SignatureParts signatureParts = BbsCodec.octetsToSignature(signature, ciphersuite); + int[] undisclosedIndexes = undisclosedIndexes(messageScalars.size(), indexes); + List disclosedMessages = select(messageScalars, indexes); + List undisclosedMessages = select(messageScalars, undisclosedIndexes); + List proofRandomScalars = validateRandomScalars(randomScalars, 5 + undisclosedIndexes.length); + + ProofInitResult initResult = proofInit( + publicKey, + signatureParts, + generators, + proofRandomScalars, + BbsCodec.copy(header), + messageScalars, + undisclosedIndexes, + ciphersuite, + bls); + BigInteger challenge = proofChallengeCalculate( + initResult, + disclosedMessages, + indexes, + BbsCodec.copy(presentationHeader), + ciphersuite); + return proofFinalize(initResult, challenge, signatureParts.e(), proofRandomScalars, undisclosedMessages); + } + + public static boolean proofVerify( + byte[] publicKey, + byte[] proof, + byte[] header, + byte[] presentationHeader, + List disclosedMessages, + int[] disclosedIndexes, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + try { + BbsCodec.ProofParts proofParts = BbsCodec.octetsToProof(proof, ciphersuite); + int undisclosedCount = proofParts.mHats().size(); + int revealedCount = Objects.requireNonNull(disclosedMessages, "disclosed messages required").size(); + int messageCount = undisclosedCount + revealedCount; + int[] indexes = validateDisclosedIndexes(disclosedIndexes, messageCount); + if (indexes.length != revealedCount) { + return false; + } + List disclosedScalars = messagesToScalars(disclosedMessages, ciphersuite); + Generators generators = createGenerators(messageCount + 1, ciphersuite, bls); + return coreProofVerify( + publicKey, + proofParts, + generators, + BbsCodec.copy(header), + BbsCodec.copy(presentationHeader), + disclosedScalars, + indexes, + ciphersuite, + bls); + } catch (RuntimeException e) { + return false; + } + } + + public static List seededRandomScalars( + byte[] seed, + byte[] dst, + int count, + BbsCiphersuite ciphersuite + ) { + Objects.requireNonNull(seed, "seed required"); + Objects.requireNonNull(dst, "dst required"); + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + if (count < 0) { + throw new IllegalArgumentException("random scalar count must be non-negative"); + } + int outLen = ciphersuite.expandLen() * count; + if (outLen > 65535) { + throw new IllegalArgumentException("mocked random scalar output exceeds 65535 bytes"); + } + byte[] uniform = expandMessage(seed, dst, outLen, ciphersuite); + List scalars = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + scalars.add(new BigInteger(1, Arrays.copyOfRange( + uniform, i * ciphersuite.expandLen(), (i + 1) * ciphersuite.expandLen())).mod(R)); + } + return List.copyOf(scalars); + } + + public static BigInteger hashToScalar(byte[] message, byte[] dst, BbsCiphersuite ciphersuite) { + Objects.requireNonNull(message, "message required"); + Objects.requireNonNull(dst, "dst required"); + byte[] uniform = expandMessage(message, dst, ciphersuite.expandLen(), ciphersuite); + return new BigInteger(1, uniform).mod(R); + } + + public static List randomScalars(int count, BbsCiphersuite ciphersuite, SecureRandom random) { + if (count < 0) { + throw new IllegalArgumentException("random scalar count must be non-negative"); + } + Objects.requireNonNull(ciphersuite, "ciphersuite required"); + Objects.requireNonNull(random, "secure random required"); + byte[] buffer = new byte[ciphersuite.expandLen()]; + List scalars = new ArrayList<>(count); + while (scalars.size() < count) { + random.nextBytes(buffer); + BigInteger scalar = new BigInteger(1, buffer).mod(R); + if (scalar.signum() != 0) { + scalars.add(scalar); + } + } + return List.copyOf(scalars); + } + + private static BbsCodec.SignatureParts coreSign( + BigInteger secretKey, + byte[] publicKey, + Generators generators, + byte[] header, + List messageScalars, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + BigInteger domain = calculateDomain(publicKey, generators, header, ciphersuite); + List eInput = new ArrayList<>(messageScalars.size() + 2); + eInput.add(secretKey); + eInput.addAll(messageScalars); + eInput.add(domain); + BigInteger e = hashToScalar(BbsCodec.serialize(eInput.toArray()), dst(ciphersuite.apiId(), "H2S_"), ciphersuite); + BbsCodec.requireNonZeroScalar(e, "signature e"); + + BigInteger denominator = mod(secretKey.add(e)); + if (denominator.signum() == 0) { + throw new IllegalArgumentException("BBS signature denominator is zero"); + } + G1Point b = computeBSecretMessages(generators, domain, messageScalars, ciphersuite, bls); + G1Point a = bls.g1SecretScalarMul(b, secretScalarInverse(denominator)); + return new BbsCodec.SignatureParts(a, e); + } + + private static boolean coreVerify( + byte[] publicKey, + byte[] signature, + Generators generators, + byte[] header, + List messageScalars, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + return coreVerify(publicKey, signature, generators, header, messageScalars, ciphersuite, bls, false); + } + + private static boolean coreVerify( + byte[] publicKey, + byte[] signature, + Generators generators, + byte[] header, + List messageScalars, + BbsCiphersuite ciphersuite, + Bls12381Provider bls, + boolean secretMessages + ) { + try { + BbsCodec.SignatureParts signatureParts = BbsCodec.octetsToSignature(signature, ciphersuite); + G2Point w = BbsCodec.octetsToPublicKey(publicKey); + BigInteger domain = calculateDomain(publicKey, generators, header, ciphersuite); + G1Point b = secretMessages + ? computeBSecretMessages(generators, domain, messageScalars, ciphersuite, bls) + : computeBPublicMessages(generators, domain, messageScalars, ciphersuite, bls); + G1Point left2 = bls.g1Add(bls.g1ScalarMul(signatureParts.a(), signatureParts.e()), bls.g1Negate(b)); + return bls.pairingProductIsIdentity( + new G1Point[]{signatureParts.a(), left2}, + new G2Point[]{w, bls.g2Generator()}); + } catch (RuntimeException e) { + return false; + } + } + + private static ProofInitResult proofInit( + byte[] publicKey, + BbsCodec.SignatureParts signature, + Generators generators, + List randomScalars, + byte[] header, + List messages, + int[] undisclosedIndexes, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + BigInteger r1 = randomScalars.get(0); + BigInteger r2 = randomScalars.get(1); + BigInteger eTilde = randomScalars.get(2); + BigInteger r1Tilde = randomScalars.get(3); + BigInteger r3Tilde = randomScalars.get(4); + + BigInteger domain = calculateDomain(publicKey, generators, header, ciphersuite); + G1Point b = computeBSecretMessages(generators, domain, messages, ciphersuite, bls); + G1Point d = bls.g1SecretScalarMul(b, r2); + G1Point aBar = bls.g1SecretScalarMul(signature.a(), mod(r1.multiply(r2))); + G1Point bBar = bls.g1Add( + bls.g1SecretScalarMul(d, r1), + bls.g1Negate(bls.g1ScalarMul(aBar, signature.e()))); + G1Point t1 = bls.g1Add(bls.g1SecretScalarMul(aBar, eTilde), bls.g1SecretScalarMul(d, r1Tilde)); + G1Point t2 = bls.g1SecretScalarMul(d, r3Tilde); + for (int u = 0; u < undisclosedIndexes.length; u++) { + t2 = bls.g1Add( + t2, + bls.g1SecretScalarMul(generators.h().get(undisclosedIndexes[u]), randomScalars.get(5 + u))); + } + return new ProofInitResult(aBar, bBar, d, t1, t2, domain); + } + + private static byte[] proofFinalize( + ProofInitResult initResult, + BigInteger challenge, + BigInteger e, + List randomScalars, + List undisclosedMessages + ) { + BigInteger r1 = randomScalars.get(0); + BigInteger r2 = randomScalars.get(1); + BigInteger eTilde = randomScalars.get(2); + BigInteger r1Tilde = randomScalars.get(3); + BigInteger r3Tilde = randomScalars.get(4); + BigInteger r3 = secretScalarInverse(r2); + + BigInteger eHat = mod(eTilde.add(e.multiply(challenge))); + BigInteger r1Hat = mod(r1Tilde.subtract(r1.multiply(challenge))); + BigInteger r3Hat = mod(r3Tilde.subtract(r3.multiply(challenge))); + List mHats = new ArrayList<>(undisclosedMessages.size()); + for (int i = 0; i < undisclosedMessages.size(); i++) { + mHats.add(mod(randomScalars.get(5 + i).add(undisclosedMessages.get(i).multiply(challenge)))); + } + + return BbsCodec.proofToOctets(new BbsCodec.ProofParts( + initResult.aBar(), + initResult.bBar(), + initResult.d(), + eHat, + r1Hat, + r3Hat, + mHats, + challenge)); + } + + private static boolean coreProofVerify( + byte[] publicKey, + BbsCodec.ProofParts proof, + Generators generators, + byte[] header, + byte[] presentationHeader, + List disclosedMessages, + int[] disclosedIndexes, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + G2Point w = BbsCodec.octetsToPublicKey(publicKey); + ProofInitResult initResult = proofVerifyInit( + publicKey, proof, generators, header, disclosedMessages, disclosedIndexes, ciphersuite, bls); + BigInteger challenge = proofChallengeCalculate( + initResult, disclosedMessages, disclosedIndexes, presentationHeader, ciphersuite); + if (!proof.challenge().equals(challenge)) { + return false; + } + return bls.pairingProductIsIdentity( + new G1Point[]{proof.aBar(), proof.bBar()}, + new G2Point[]{w, bls.g2Generator().negate()}); + } + + private static ProofInitResult proofVerifyInit( + byte[] publicKey, + BbsCodec.ProofParts proof, + Generators generators, + byte[] header, + List disclosedMessages, + int[] disclosedIndexes, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + int hiddenCount = proof.mHats().size(); + int messageCount = hiddenCount + disclosedIndexes.length; + int[] undisclosedIndexes = undisclosedIndexes(messageCount, disclosedIndexes); + BigInteger domain = calculateDomain(publicKey, generators, header, ciphersuite); + + G1Point t1 = bls.g1Add( + bls.g1Add( + bls.g1ScalarMul(proof.bBar(), proof.challenge()), + bls.g1ScalarMul(proof.aBar(), proof.eHat())), + bls.g1ScalarMul(proof.d(), proof.r1Hat())); + + G1Point bv = bls.g1Add(ciphersuite.p1(), bls.g1ScalarMul(generators.q1(), domain)); + for (int i = 0; i < disclosedIndexes.length; i++) { + bv = bls.g1Add(bv, bls.g1ScalarMul(generators.h().get(disclosedIndexes[i]), disclosedMessages.get(i))); + } + + G1Point t2 = bls.g1Add(bls.g1ScalarMul(bv, proof.challenge()), bls.g1ScalarMul(proof.d(), proof.r3Hat())); + for (int u = 0; u < undisclosedIndexes.length; u++) { + t2 = bls.g1Add(t2, bls.g1ScalarMul(generators.h().get(undisclosedIndexes[u]), proof.mHats().get(u))); + } + return new ProofInitResult(proof.aBar(), proof.bBar(), proof.d(), t1, t2, domain); + } + + private static BigInteger proofChallengeCalculate( + ProofInitResult initResult, + List disclosedMessages, + int[] disclosedIndexes, + byte[] presentationHeader, + BbsCiphersuite ciphersuite + ) { + if (disclosedMessages.size() != disclosedIndexes.length) { + throw new IllegalArgumentException("Disclosed message count must match disclosed index count"); + } + List values = new ArrayList<>(1 + disclosedIndexes.length * 2 + 6); + values.add((long) disclosedIndexes.length); + for (int i = 0; i < disclosedIndexes.length; i++) { + values.add((long) disclosedIndexes[i]); + values.add(disclosedMessages.get(i)); + } + values.add(initResult.aBar()); + values.add(initResult.bBar()); + values.add(initResult.d()); + values.add(initResult.t1()); + values.add(initResult.t2()); + values.add(initResult.domain()); + + byte[] cOctets = BbsCodec.concat( + BbsCodec.serialize(values.toArray()), + BbsCodec.i2osp(presentationHeader.length, 8), + presentationHeader); + return hashToScalar(cOctets, dst(ciphersuite.apiId(), "H2S_"), ciphersuite); + } + + private static G1Point computeB( + Generators generators, + BigInteger domain, + List messages, + BbsCiphersuite ciphersuite + ) { + return computeB(generators, domain, messages, ciphersuite, null, false); + } + + private static G1Point computeBPublicMessages( + Generators generators, + BigInteger domain, + List messages, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + return computeB(generators, domain, messages, ciphersuite, bls, false); + } + + private static G1Point computeBSecretMessages( + Generators generators, + BigInteger domain, + List messages, + BbsCiphersuite ciphersuite, + Bls12381Provider bls + ) { + return computeB(generators, domain, messages, ciphersuite, bls, true); + } + + private static G1Point computeB( + Generators generators, + BigInteger domain, + List messages, + BbsCiphersuite ciphersuite, + Bls12381Provider bls, + boolean secretMessages + ) { + if (messages.size() != generators.h().size()) { + throw new IllegalArgumentException("Message count must match BBS message generator count"); + } + G1Point b = bls == null + ? ciphersuite.p1().add(generators.q1().scalarMul(domain)) + : bls.g1Add(ciphersuite.p1(), bls.g1ScalarMul(generators.q1(), domain)); + for (int i = 0; i < messages.size(); i++) { + G1Point term = bls == null + ? generators.h().get(i).scalarMul(messages.get(i)) + : secretMessages + ? bls.g1SecretScalarMul(generators.h().get(i), messages.get(i)) + : bls.g1ScalarMul(generators.h().get(i), messages.get(i)); + b = bls == null ? b.add(term) : bls.g1Add(b, term); + } + return b; + } + + private static List validateRandomScalars(List randomScalars, int expected) { + Objects.requireNonNull(randomScalars, "random scalars required"); + if (randomScalars.size() != expected) { + throw new IllegalArgumentException("Expected " + expected + " random scalars, got " + randomScalars.size()); + } + List scalars = new ArrayList<>(expected); + for (BigInteger scalar : randomScalars) { + scalars.add(BbsCodec.requireNonZeroScalar(scalar, "proof random scalar")); + } + return List.copyOf(scalars); + } + + public static int[] validateDisclosedIndexes(int[] disclosedIndexes, int messageCount) { + Objects.requireNonNull(disclosedIndexes, "disclosed indexes required"); + if (messageCount < 0) { + throw new IllegalArgumentException("message count must be non-negative"); + } + int[] copy = disclosedIndexes.clone(); + int previous = -1; + for (int index : copy) { + if (index < 0 || index >= messageCount) { + throw new IllegalArgumentException("BBS disclosed index out of range: " + index); + } + if (index <= previous) { + throw new IllegalArgumentException("BBS disclosed indexes must be strictly ascending"); + } + previous = index; + } + return copy; + } + + private static int[] undisclosedIndexes(int messageCount, int[] disclosedIndexes) { + int[] out = new int[messageCount - disclosedIndexes.length]; + int disclosedOffset = 0; + int outOffset = 0; + for (int i = 0; i < messageCount; i++) { + if (disclosedOffset < disclosedIndexes.length && disclosedIndexes[disclosedOffset] == i) { + disclosedOffset++; + } else { + out[outOffset++] = i; + } + } + return out; + } + + private static List select(List values, int[] indexes) { + List selected = new ArrayList<>(indexes.length); + for (int index : indexes) { + selected.add(values.get(index)); + } + return List.copyOf(selected); + } + + private static BigInteger mod(BigInteger value) { + BigInteger out = value.mod(R); + return out.signum() >= 0 ? out : out.add(R); + } + + private static BigInteger secretScalarInverse(BigInteger scalar) { + BbsCodec.requireNonZeroScalar(scalar, "secret scalar"); + return MontFr381.fromBigInteger(scalar).inverse().toBigInteger(); + } + + private static byte[] expandMessage(byte[] message, byte[] dst, int len, BbsCiphersuite ciphersuite) { + if (dst.length > 255) { + throw new IllegalArgumentException("BBS DST must be at most 255 bytes"); + } + return switch (ciphersuite) { + case BLS12381_SHA256 -> Bls12381Hash.expandMessageXmdSha256(message, dst, len); + case BLS12381_SHAKE256 -> Bls12381Hash.expandMessageXofShake256(message, dst, len); + }; + } + + private static G1Point hashToG1(byte[] message, byte[] dst, BbsCiphersuite ciphersuite, Bls12381Provider bls) { + return switch (ciphersuite) { + case BLS12381_SHA256 -> bls.g1HashToCurve(message, dst); + case BLS12381_SHAKE256 -> bls.g1HashToCurveXofShake256(message, dst); + }; + } + + private static byte[] dst(byte[] prefix, String suffix) { + return BbsCodec.concat(prefix, suffix.getBytes(StandardCharsets.US_ASCII)); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/BbsProvider.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/BbsProvider.java new file mode 100644 index 0000000..2a82dc0 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/BbsProvider.java @@ -0,0 +1,58 @@ +package com.bloxbean.cardano.zeroj.bbs.spi; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsKeyPair; +import com.bloxbean.cardano.zeroj.bbs.BbsProof; +import com.bloxbean.cardano.zeroj.bbs.BbsPublicKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSecretKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSignature; + +import java.security.SecureRandom; +import java.util.List; + +/** + * Provider boundary for CFRG BBS draft-10 implementations. + */ +public interface BbsProvider { + String id(); + + BbsCiphersuite ciphersuite(); + + BbsSecretKey keyGen(byte[] keyMaterial, byte[] keyInfo); + + BbsPublicKey skToPk(BbsSecretKey secretKey); + + default BbsKeyPair keyPair(byte[] keyMaterial, byte[] keyInfo) { + BbsSecretKey secretKey = keyGen(keyMaterial, keyInfo); + return new BbsKeyPair(secretKey, skToPk(secretKey)); + } + + BbsSignature sign(BbsSecretKey secretKey, BbsPublicKey publicKey, List messages, byte[] header); + + default BbsSignature sign(BbsSecretKey secretKey, BbsPublicKey publicKey, byte[] header, List messages) { + return sign(secretKey, publicKey, messages, header); + } + + boolean verify(BbsPublicKey publicKey, BbsSignature signature, List messages, byte[] header); + + default boolean verify(BbsPublicKey publicKey, BbsSignature signature, byte[] header, List messages) { + return verify(publicKey, signature, messages, header); + } + + BbsProof proofGen( + BbsPublicKey publicKey, + BbsSignature signature, + List messages, + byte[] header, + byte[] presentationHeader, + int[] disclosedIndexes, + SecureRandom random); + + boolean proofVerify( + BbsPublicKey publicKey, + BbsProof proof, + byte[] header, + byte[] presentationHeader, + List disclosedMessages, + int[] disclosedIndexes); +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/BbsProviders.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/BbsProviders.java new file mode 100644 index 0000000..dc45885 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/BbsProviders.java @@ -0,0 +1,38 @@ +package com.bloxbean.cardano.zeroj.bbs.spi; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; + +import java.util.Objects; + +/** + * Built-in BBS provider factories. + */ +public final class BbsProviders { + private static final PureJavaBbsProvider SHA256 = new PureJavaBbsProvider( + BbsCiphersuite.BLS12381_SHA256, + Bls12381Providers.pureJava()); + private static final PureJavaBbsProvider SHAKE256 = new PureJavaBbsProvider( + BbsCiphersuite.BLS12381_SHAKE256, + Bls12381Providers.pureJava()); + + private BbsProviders() {} + + public static BbsProvider pureJava() { + return SHA256; + } + + public static BbsProvider pureJava(BbsCiphersuite ciphersuite) { + return switch (Objects.requireNonNull(ciphersuite, "ciphersuite required")) { + case BLS12381_SHA256 -> SHA256; + case BLS12381_SHAKE256 -> SHAKE256; + }; + } + + public static BbsProvider withBlsProvider(BbsCiphersuite ciphersuite, Bls12381Provider bls) { + return new PureJavaBbsProvider( + Objects.requireNonNull(ciphersuite, "ciphersuite required"), + Objects.requireNonNull(bls, "BLS provider required")); + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/PureJavaBbsProvider.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/PureJavaBbsProvider.java new file mode 100644 index 0000000..a7ec8e3 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/spi/PureJavaBbsProvider.java @@ -0,0 +1,126 @@ +package com.bloxbean.cardano.zeroj.bbs.spi; + +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsProof; +import com.bloxbean.cardano.zeroj.bbs.BbsPublicKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSecretKey; +import com.bloxbean.cardano.zeroj.bbs.BbsSignature; +import com.bloxbean.cardano.zeroj.bbs.internal.CfrgBbsCore; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; + +import java.security.SecureRandom; +import java.util.List; +import java.util.Objects; + +/** + * Pure Java CFRG BBS draft-10 provider backed by ZeroJ BLS12-381 primitives. + */ +public final class PureJavaBbsProvider implements BbsProvider { + private final BbsCiphersuite ciphersuite; + private final Bls12381Provider bls; + + public PureJavaBbsProvider(BbsCiphersuite ciphersuite, Bls12381Provider bls) { + this.ciphersuite = Objects.requireNonNull(ciphersuite, "ciphersuite required"); + this.bls = Objects.requireNonNull(bls, "BLS provider required"); + } + + @Override + public String id() { + return "zeroj-bbs-pure-java-" + ciphersuite.name().toLowerCase(java.util.Locale.ROOT); + } + + @Override + public BbsCiphersuite ciphersuite() { + return ciphersuite; + } + + @Override + public BbsSecretKey keyGen(byte[] keyMaterial, byte[] keyInfo) { + return new BbsSecretKey(CfrgBbsCore.keyGen(ciphersuite, keyMaterial, keyInfo), ciphersuite); + } + + @Override + public BbsPublicKey skToPk(BbsSecretKey secretKey) { + requireSuite(secretKey); + return new BbsPublicKey(CfrgBbsCore.skToPk(secretKey.value(), bls), ciphersuite); + } + + @Override + public BbsSignature sign(BbsSecretKey secretKey, BbsPublicKey publicKey, List messages, byte[] header) { + requireSuite(secretKey); + requireSuite(publicKey); + return new BbsSignature(CfrgBbsCore.sign( + secretKey.value(), publicKey.bytes(), messages, header, ciphersuite, bls), ciphersuite); + } + + @Override + public boolean verify(BbsPublicKey publicKey, BbsSignature signature, List messages, byte[] header) { + if (publicKey.ciphersuite() != ciphersuite || signature.ciphersuite() != ciphersuite) { + return false; + } + return CfrgBbsCore.verify(publicKey.bytes(), signature.bytes(), messages, header, ciphersuite, bls); + } + + @Override + public BbsProof proofGen( + BbsPublicKey publicKey, + BbsSignature signature, + List messages, + byte[] header, + byte[] presentationHeader, + int[] disclosedIndexes, + SecureRandom random) { + requireSuite(publicKey); + requireSuite(signature); + return new BbsProof(CfrgBbsCore.proofGen( + publicKey.bytes(), + signature.bytes(), + messages, + header, + presentationHeader, + disclosedIndexes, + ciphersuite, + bls, + random), ciphersuite); + } + + @Override + public boolean proofVerify( + BbsPublicKey publicKey, + BbsProof proof, + byte[] header, + byte[] presentationHeader, + List disclosedMessages, + int[] disclosedIndexes) { + if (publicKey.ciphersuite() != ciphersuite || proof.ciphersuite() != ciphersuite) { + return false; + } + return CfrgBbsCore.proofVerify( + publicKey.bytes(), + proof.bytes(), + header, + presentationHeader, + disclosedMessages, + disclosedIndexes, + ciphersuite, + bls); + } + + private void requireSuite(BbsSecretKey secretKey) { + if (Objects.requireNonNull(secretKey, "secret key required").ciphersuite() != ciphersuite) { + throw new IllegalArgumentException("BBS secret key ciphersuite mismatch"); + } + } + + private void requireSuite(BbsPublicKey publicKey) { + if (Objects.requireNonNull(publicKey, "public key required").ciphersuite() != ciphersuite) { + throw new IllegalArgumentException("BBS public key ciphersuite mismatch"); + } + } + + private void requireSuite(BbsSignature signature) { + if (Objects.requireNonNull(signature, "signature required").ciphersuite() != ciphersuite) { + throw new IllegalArgumentException("BBS signature ciphersuite mismatch"); + } + } +} diff --git a/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/verifier/BbsZkVerifier.java b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/verifier/BbsZkVerifier.java new file mode 100644 index 0000000..5b7fb95 --- /dev/null +++ b/zeroj-bbs/src/main/java/com/bloxbean/cardano/zeroj/bbs/verifier/BbsZkVerifier.java @@ -0,0 +1,72 @@ +package com.bloxbean.cardano.zeroj.bbs.verifier; + +import com.bloxbean.cardano.zeroj.api.CurveId; +import com.bloxbean.cardano.zeroj.api.ProofSystemId; +import com.bloxbean.cardano.zeroj.api.VerificationMaterial; +import com.bloxbean.cardano.zeroj.api.VerificationResult; +import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope; +import com.bloxbean.cardano.zeroj.backend.spi.BackendDescriptor; +import com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier; +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsPresentationCodec; +import com.bloxbean.cardano.zeroj.bbs.BbsPublicKey; +import com.bloxbean.cardano.zeroj.bbs.BbsService; + +/** + * ZeroJ verifier adapter for CFRG BBS draft-10 presentations. + */ +public final class BbsZkVerifier implements ZkVerifier { + private static final BackendDescriptor DESCRIPTOR = + new BackendDescriptor(ProofSystemId.BBS, CurveId.BLS12_381, "bbs-bls12381-java"); + + private final BbsService service; + + public BbsZkVerifier() { + this(BbsService.pureJava()); + } + + public BbsZkVerifier(BbsService service) { + this.service = java.util.Objects.requireNonNull(service, "BBS service required"); + } + + @Override + public VerificationResult verify(ZkProofEnvelope envelope, VerificationMaterial material) { + try { + if (envelope.proofSystem() != ProofSystemId.BBS || material.proofSystemId() != ProofSystemId.BBS) { + return VerificationResult.error( + VerificationResult.ReasonCode.UNSUPPORTED_PROOF_SYSTEM, + "BBS verifier only supports proof system bbs"); + } + if (envelope.curve() != CurveId.BLS12_381 || material.curveId() != CurveId.BLS12_381) { + return VerificationResult.error( + VerificationResult.ReasonCode.UNSUPPORTED_CURVE, + "BBS verifier only supports BLS12-381"); + } + if (envelope.proofFormat().isPresent() + && !BbsCiphersuite.DEFAULT_PROOF_FORMAT.equals(envelope.proofFormat().get())) { + return VerificationResult.error( + VerificationResult.ReasonCode.MALFORMED_ENVELOPE, + "Unsupported BBS proof format: " + envelope.proofFormat().get()); + } + + var presentation = BbsPresentationCodec.decode(envelope.proofBytes()); + BbsCiphersuite ciphersuite = presentation.proof().ciphersuite(); + BbsService verifierService = service.provider().ciphersuite() == ciphersuite + ? service + : BbsService.pureJava(ciphersuite); + BbsPublicKey publicKey = new BbsPublicKey(material.vkBytes(), ciphersuite); + boolean valid = verifierService.verifyPresentation(publicKey, presentation); + return valid ? VerificationResult.cryptoValid() + : VerificationResult.proofInvalid("BBS proof verification failed"); + } catch (Exception e) { + return VerificationResult.error( + VerificationResult.ReasonCode.INTERNAL_ERROR, + "BBS verification error: " + e.getMessage()); + } + } + + @Override + public BackendDescriptor descriptor() { + return DESCRIPTOR; + } +} diff --git a/zeroj-bbs/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bbs/reflect-config.json b/zeroj-bbs/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bbs/reflect-config.json new file mode 100644 index 0000000..3a739d1 --- /dev/null +++ b/zeroj-bbs/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bbs/reflect-config.json @@ -0,0 +1,13 @@ +[ + { + "name": "com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bbs.verifier.BbsZkVerifier", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] diff --git a/zeroj-bbs/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bbs/resource-config.json b/zeroj-bbs/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bbs/resource-config.json new file mode 100644 index 0000000..2cd94b3 --- /dev/null +++ b/zeroj-bbs/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bbs/resource-config.json @@ -0,0 +1,7 @@ +{ + "resources": { + "includes": [ + {"pattern": "META-INF/services/.*"} + ] + } +} diff --git a/zeroj-bbs/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier b/zeroj-bbs/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier new file mode 100644 index 0000000..98d3bf1 --- /dev/null +++ b/zeroj-bbs/src/main/resources/META-INF/services/com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier @@ -0,0 +1 @@ +com.bloxbean.cardano.zeroj.bbs.verifier.BbsZkVerifier diff --git a/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/BbsBlsProviderConformanceTest.java b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/BbsBlsProviderConformanceTest.java new file mode 100644 index 0000000..2b05e4b --- /dev/null +++ b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/BbsBlsProviderConformanceTest.java @@ -0,0 +1,188 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import com.bloxbean.cardano.zeroj.bbs.internal.CfrgBbsCore; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProvider; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProviders; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; +import com.bloxbean.cardano.zeroj.bls12381.wasm.WasmBls12381Provider; +import com.bloxbean.cardano.zeroj.blst.BlstBls12381Provider; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class BbsBlsProviderConformanceTest { + private static final ObjectMapper JSON = new ObjectMapper(); + private static final List SUITES = List.of( + new SuiteFixture("/cfrg-bbs/draft10/official/bls12-381-sha-256", BbsCiphersuite.BLS12381_SHA256), + new SuiteFixture("/cfrg-bbs/draft10/official/bls12-381-shake-256", BbsCiphersuite.BLS12381_SHAKE256)); + + @ParameterizedTest(name = "{0} {1}") + @MethodSource("providerSuites") + void selectedBlsProviderMatchesOfficialKeySignatureAndProofVectors( + ProviderFixture providerFixture, + SuiteFixture suite) { + BbsProvider provider = BbsProviders.withBlsProvider(suite.ciphersuite(), providerFixture.bls()); + + assertKeyPairFixture(provider, suite); + assertSignatureFixture(provider, suite); + assertProofFixture(providerFixture.bls(), provider, suite); + } + + private static void assertKeyPairFixture(BbsProvider provider, SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/keypair.json"); + + BbsSecretKey secretKey = provider.keyGen(hex(fixture, "keyMaterial"), hex(fixture, "keyInfo")); + BbsPublicKey publicKey = provider.skToPk(secretKey); + + assertArrayEquals(hex(fixture.at("/keyPair/secretKey")), secretKey.toBytes()); + assertArrayEquals(hex(fixture.at("/keyPair/publicKey")), publicKey.bytes()); + } + + private static void assertSignatureFixture(BbsProvider provider, SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/signature/signature001.json"); + BbsSecretKey secretKey = new BbsSecretKey( + new BigInteger(1, hex(fixture.at("/signerKeyPair/secretKey"))), + suite.ciphersuite()); + BbsPublicKey publicKey = new BbsPublicKey(hex(fixture.at("/signerKeyPair/publicKey")), suite.ciphersuite()); + BbsSignature expected = new BbsSignature(hex(fixture, "signature"), suite.ciphersuite()); + List messages = byteArrayList(fixture.get("messages")); + byte[] header = hex(fixture, "header"); + + BbsSignature generated = provider.sign(secretKey, publicKey, messages, header); + + assertArrayEquals(expected.bytes(), generated.bytes()); + assertTrue(provider.verify(publicKey, expected, messages, header)); + assertTrue(provider.verify(publicKey, generated, messages, header)); + } + + private static void assertProofFixture(Bls12381Provider bls, BbsProvider provider, SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/proof/proof001.json"); + BbsPublicKey publicKey = new BbsPublicKey(hex(fixture, "signerPublicKey"), suite.ciphersuite()); + BbsProof expected = new BbsProof(hex(fixture, "proof"), suite.ciphersuite()); + byte[] header = hex(fixture, "header"); + byte[] presentationHeader = hex(fixture, "presentationHeader"); + List messages = byteArrayList(fixture.get("messages")); + int[] disclosedIndexes = intArray(fixture.get("disclosedIndexes")); + List disclosedMessages = select(messages, disclosedIndexes); + + assertTrue(provider.proofVerify( + publicKey, + expected, + header, + presentationHeader, + disclosedMessages, + disclosedIndexes)); + + byte[] generated = CfrgBbsCore.proofGenWithRandomScalars( + publicKey.bytes(), + hex(fixture, "signature"), + messages, + header, + presentationHeader, + disclosedIndexes, + suite.ciphersuite(), + bls, + proofRandomScalars(fixture.at("/trace/random_scalars"))); + assertArrayEquals(expected.bytes(), generated); + } + + private static List proofRandomScalars(JsonNode randomScalars) { + List out = new ArrayList<>(); + out.add(scalar(randomScalars, "r1")); + out.add(scalar(randomScalars, "r2")); + out.add(scalar(randomScalars, "e_tilde")); + out.add(scalar(randomScalars, "r1_tilde")); + out.add(scalar(randomScalars, "r3_tilde")); + for (JsonNode mTilde : randomScalars.get("m_tilde_scalars")) { + out.add(new BigInteger(1, hex(mTilde))); + } + return List.copyOf(out); + } + + private static BigInteger scalar(JsonNode node, String fieldName) { + return new BigInteger(1, hex(node, fieldName)); + } + + private static List select(List values, int[] indexes) { + List out = new ArrayList<>(indexes.length); + for (int index : indexes) { + out.add(values.get(index)); + } + return List.copyOf(out); + } + + private static List byteArrayList(JsonNode array) { + List out = new ArrayList<>(); + for (JsonNode item : array) { + out.add(hex(item)); + } + return List.copyOf(out); + } + + private static int[] intArray(JsonNode array) { + int[] out = new int[array.size()]; + for (int i = 0; i < array.size(); i++) { + out[i] = array.get(i).asInt(); + } + return out; + } + + private static byte[] hex(JsonNode node, String fieldName) { + return hex(node.get(fieldName)); + } + + private static byte[] hex(JsonNode node) { + String hex = node.asText(); + if (hex.isEmpty()) { + return new byte[0]; + } + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } + + private static JsonNode read(String resource) { + try (var in = BbsBlsProviderConformanceTest.class.getResourceAsStream(resource)) { + assertNotNull(in, "Missing fixture resource: " + resource); + return JSON.readTree(in); + } catch (IOException e) { + throw new IllegalStateException("Unable to read fixture resource: " + resource, e); + } + } + + private static Stream providerSuites() { + List providers = List.of( + new ProviderFixture("pure-java", Bls12381Providers.pureJava()), + new ProviderFixture("wasm-zkcrypto", WasmBls12381Provider.createDefault()), + new ProviderFixture("blst", BlstBls12381Provider.createDefault())); + return providers.stream() + .flatMap(provider -> SUITES.stream().map(suite -> Arguments.of(provider, suite))); + } + + private record ProviderFixture(String name, Bls12381Provider bls) { + @Override + public String toString() { + return name; + } + } + + private record SuiteFixture(String resourceBase, BbsCiphersuite ciphersuite) { + @Override + public String toString() { + return ciphersuite.name(); + } + } +} diff --git a/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/BbsServiceTest.java b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/BbsServiceTest.java new file mode 100644 index 0000000..f2d7359 --- /dev/null +++ b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/BbsServiceTest.java @@ -0,0 +1,355 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import co.nstant.in.cbor.CborBuilder; +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.ByteString; +import co.nstant.in.cbor.model.UnicodeString; +import co.nstant.in.cbor.model.UnsignedInteger; +import com.bloxbean.cardano.zeroj.api.CircuitId; +import com.bloxbean.cardano.zeroj.api.CurveId; +import com.bloxbean.cardano.zeroj.api.ProofSystemId; +import com.bloxbean.cardano.zeroj.api.PublicInputs; +import com.bloxbean.cardano.zeroj.api.VerificationKeyRef; +import com.bloxbean.cardano.zeroj.api.VerificationMaterial; +import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope; +import com.bloxbean.cardano.zeroj.bbs.verifier.BbsZkVerifier; +import com.bloxbean.cardano.zeroj.bbs.spi.PureJavaBbsProvider; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class BbsServiceTest { + + @Test + void serviceSignsVerifiesAndDerivesPresentation() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("01234567890123456789012345678901"), bytes("issuer-key")); + List messages = List.of(bytes("name:alice"), bytes("age:42"), bytes("member:true")); + byte[] header = bytes("credential-v1"); + byte[] presentationHeader = bytes("verifier-session-123"); + + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + assertTrue(service.verify(keyPair.publicKey(), signature, messages, header)); + + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, header, presentationHeader, new int[]{0, 2}); + + assertEquals(List.of(new BbsRevealedMessage(0, messages.get(0)), new BbsRevealedMessage(2, messages.get(2))), + presentation.revealedMessages()); + assertTrue(service.verifyPresentation(keyPair.publicKey(), presentation)); + } + + @Test + void presentationCborRoundTrips() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("abcdefghijklmnopqrstuvwxyz123456"), bytes("issuer-key")); + List messages = List.of(bytes("status:active"), bytes("role:member")); + byte[] header = bytes("credential-v1"); + byte[] presentationHeader = bytes("session"); + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, header, presentationHeader, new int[]{1}); + + byte[] cbor = BbsPresentationCodec.encode(presentation); + BbsPresentation decoded = BbsPresentationCodec.decode(cbor); + + assertEquals(presentation.proof(), decoded.proof()); + assertArrayEquals(presentation.header(), decoded.header()); + assertArrayEquals(presentation.presentationHeader(), decoded.presentationHeader()); + assertEquals(presentation.revealedMessages(), decoded.revealedMessages()); + assertArrayEquals(cbor, BbsPresentationCodec.encode(decoded)); + assertTrue(service.verifyPresentation(keyPair.publicKey(), decoded)); + } + + @Test + void specOrderSignAndVerifyOverloadsWork() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("abcdefghijklmnopqrstuvwxyzabcdef"), bytes("issuer-key")); + List messages = List.of(bytes("status:active"), bytes("role:member")); + byte[] header = bytes("credential-v1"); + + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), header, messages); + + assertTrue(service.verify(keyPair.publicKey(), signature, header, messages)); + } + + @Test + void verifyRejectsTamperedSignatureWrongPublicKeyAndModifiedMessage() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789ij"), bytes("issuer-key")); + BbsKeyPair wrongKeyPair = service.keyPair(bytes("012345678901234567890123456789kl"), bytes("issuer-key")); + List messages = List.of(bytes("given:Alice"), bytes("family:Liddell")); + byte[] header = bytes("credential-v1"); + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + byte[] tamperedSignatureBytes = signature.bytes(); + tamperedSignatureBytes[tamperedSignatureBytes.length - 1] ^= 0x01; + + assertFalse(service.verify( + keyPair.publicKey(), + new BbsSignature(tamperedSignatureBytes, BbsCiphersuite.BLS12381_SHA256), + messages, + header)); + assertFalse(service.verify(wrongKeyPair.publicKey(), signature, messages, header)); + assertFalse(service.verify( + keyPair.publicKey(), + signature, + List.of(bytes("given:Alice"), bytes("family:Changed")), + header)); + } + + @Test + void presentationIndexValidationRejectsInvalidServiceInputs() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789mn"), bytes("issuer-key")); + List messages = List.of(bytes("given:Alice"), bytes("family:Liddell")); + byte[] header = bytes("credential-v1"); + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + + assertThrows(BbsException.class, () -> service.derivePresentation( + keyPair.publicKey(), signature, messages, header, bytes("nonce"), new int[]{2})); + assertThrows(BbsException.class, () -> service.derivePresentation( + keyPair.publicKey(), signature, messages, header, bytes("nonce"), new int[]{0, 0})); + + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, header, bytes("nonce"), new int[]{0}); + BbsPresentation duplicateRevealed = new BbsPresentation( + presentation.proof(), + presentation.header(), + presentation.presentationHeader(), + List.of(new BbsRevealedMessage(0, messages.get(0)), new BbsRevealedMessage(0, messages.get(0)))); + + assertThrows(BbsException.class, () -> service.verifyPresentation(keyPair.publicKey(), duplicateRevealed)); + } + + @Test + void presentationCodecRejectsMalformedNonCanonicalAndDuplicateIndexCbor() throws Exception { + BbsPresentation presentation = samplePresentation(); + + assertThrows(BbsException.class, () -> BbsPresentationCodec.decode(new byte[]{0})); + assertThrows(BbsException.class, () -> BbsPresentationCodec.decode(envelope(presentation, 2, false, false))); + assertThrows(BbsException.class, () -> BbsPresentationCodec.decode(envelope(presentation, 1, true, false))); + assertThrows(BbsException.class, () -> BbsPresentationCodec.decode(envelope(presentation, 1, false, true))); + } + + @Test + void presentationCodecAppliesFieldLengthCaps() { + BbsPresentation presentation = samplePresentation(); + byte[] oversizedHeader = new byte[65_536]; + BbsPresentation oversized = new BbsPresentation( + presentation.proof(), + oversizedHeader, + presentation.presentationHeader(), + presentation.revealedMessages()); + + assertThrows(BbsException.class, () -> BbsPresentationCodec.encode(oversized)); + } + + @Test + void zkVerifierAcceptsPresentationEnvelope() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789ab"), bytes("issuer-key")); + List messages = List.of(bytes("given:Alice"), bytes("family:Liddell"), bytes("age:20")); + byte[] header = bytes("credential-v1"); + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, header, bytes("verifier-nonce"), new int[]{0}); + + ZkProofEnvelope envelope = ZkProofEnvelope.builder() + .proofSystem(ProofSystemId.BBS) + .curve(CurveId.BLS12_381) + .circuitId(new CircuitId("bbs-selective-disclosure")) + .proofBytes(BbsPresentationCodec.encode(presentation)) + .publicInputs(new PublicInputs(List.of())) + .vkRef(new VerificationKeyRef.ById("issuer-key")) + .proofFormat(BbsCiphersuite.DEFAULT_PROOF_FORMAT) + .build(); + VerificationMaterial material = VerificationMaterial.of( + keyPair.publicKey().bytes(), + ProofSystemId.BBS, + CurveId.BLS12_381, + new CircuitId("bbs-selective-disclosure")); + + var result = new BbsZkVerifier(service).verify(envelope, material); + + assertTrue(result.proofValid(), result.message().orElse("")); + } + + @Test + void zkVerifierAcceptsShake256PresentationEnvelope() { + BbsService service = BbsService.pureJava(BbsCiphersuite.BLS12381_SHAKE256); + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789cd"), bytes("issuer-key")); + List messages = List.of(bytes("given:Alice"), bytes("family:Liddell"), bytes("age:20")); + byte[] header = bytes("credential-v1"); + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, header, bytes("verifier-nonce"), new int[]{0, 2}); + + ZkProofEnvelope envelope = ZkProofEnvelope.builder() + .proofSystem(ProofSystemId.BBS) + .curve(CurveId.BLS12_381) + .circuitId(new CircuitId("bbs-selective-disclosure-shake")) + .proofBytes(BbsPresentationCodec.encode(presentation)) + .publicInputs(new PublicInputs(List.of())) + .vkRef(new VerificationKeyRef.ById("issuer-key")) + .proofFormat(BbsCiphersuite.DEFAULT_PROOF_FORMAT) + .build(); + VerificationMaterial material = VerificationMaterial.of( + keyPair.publicKey().bytes(), + ProofSystemId.BBS, + CurveId.BLS12_381, + new CircuitId("bbs-selective-disclosure-shake")); + + var result = new BbsZkVerifier().verify(envelope, material); + + assertTrue(result.proofValid(), result.message().orElse("")); + } + + @Test + void signingAndProofGenerationUseSecretScalarProviderBoundary() { + var bls = new CountingBlsProvider(Bls12381Providers.pureJava()); + var provider = new PureJavaBbsProvider(BbsCiphersuite.BLS12381_SHA256, bls); + BbsService service = new BbsService(provider, new SecureRandom(new byte[]{1, 2, 3, 4})); + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789ef"), bytes("issuer-key")); + List messages = List.of(bytes("given:Alice"), bytes("family:Liddell"), bytes("age:20")); + + assertTrue(bls.g2SecretScalarMulCalls > 0, "SkToPk must use the secret G2 scalar boundary"); + + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, bytes("credential-v1")); + assertTrue(bls.g1SecretScalarMulCalls > 0, "Sign must use the secret G1 scalar boundary"); + int afterSignSecretCalls = bls.g1SecretScalarMulCalls; + + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), signature, messages, bytes("credential-v1"), bytes("verifier-nonce"), new int[]{0}); + + assertTrue(bls.g1SecretScalarMulCalls > afterSignSecretCalls, + "ProofGen must use the secret G1 scalar boundary"); + assertTrue(service.verifyPresentation(keyPair.publicKey(), presentation)); + } + + @Test + void serviceFactoryAcceptsExplicitBlsProvider() { + var bls = new CountingBlsProvider(Bls12381Providers.pureJava()); + BbsService service = BbsService.withBlsProvider(BbsCiphersuite.BLS12381_SHAKE256, bls); + + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789gh"), bytes("issuer-key")); + + assertEquals(BbsCiphersuite.BLS12381_SHAKE256, service.provider().ciphersuite()); + assertEquals(BbsCiphersuite.BLS12381_SHAKE256, keyPair.publicKey().ciphersuite()); + assertTrue(bls.g2SecretScalarMulCalls > 0, "Selected BLS provider must be used for SkToPk"); + } + + private static byte[] bytes(String text) { + return text.getBytes(StandardCharsets.UTF_8); + } + + private static BbsPresentation samplePresentation() { + BbsService service = BbsService.pureJava(); + BbsKeyPair keyPair = service.keyPair(bytes("012345678901234567890123456789op"), bytes("issuer-key")); + List messages = List.of(bytes("given:Alice"), bytes("family:Liddell"), bytes("age:20")); + byte[] header = bytes("credential-v1"); + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + return service.derivePresentation(keyPair.publicKey(), signature, messages, header, bytes("nonce"), new int[]{0, 2}); + } + + private static byte[] envelope( + BbsPresentation presentation, + int version, + boolean reversedKeys, + boolean duplicateRevealedIndex) throws CborException { + co.nstant.in.cbor.model.Array revealed = new co.nstant.in.cbor.model.Array(); + for (BbsRevealedMessage message : presentation.revealedMessages()) { + co.nstant.in.cbor.model.Array pair = new co.nstant.in.cbor.model.Array(); + pair.add(new UnsignedInteger(duplicateRevealedIndex ? 0 : message.index())); + pair.add(new ByteString(message.message())); + revealed.add(pair); + } + + co.nstant.in.cbor.model.Map map = new co.nstant.in.cbor.model.Map(); + if (reversedKeys) { + map.put(new UnsignedInteger(6), revealed); + map.put(new UnsignedInteger(5), new ByteString(presentation.presentationHeader())); + map.put(new UnsignedInteger(4), new ByteString(presentation.header())); + map.put(new UnsignedInteger(3), new ByteString(presentation.proof().bytes())); + map.put(new UnsignedInteger(2), new UnicodeString(presentation.proof().ciphersuite().ciphersuiteId())); + map.put(new UnsignedInteger(1), new UnsignedInteger(version)); + } else { + map.put(new UnsignedInteger(1), new UnsignedInteger(version)); + map.put(new UnsignedInteger(2), new UnicodeString(presentation.proof().ciphersuite().ciphersuiteId())); + map.put(new UnsignedInteger(3), new ByteString(presentation.proof().bytes())); + map.put(new UnsignedInteger(4), new ByteString(presentation.header())); + map.put(new UnsignedInteger(5), new ByteString(presentation.presentationHeader())); + map.put(new UnsignedInteger(6), revealed); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CborEncoder encoder = new CborEncoder(baos); + if (reversedKeys) { + encoder.nonCanonical(); + } + encoder.encode(new CborBuilder().add(map).build()); + return baos.toByteArray(); + } + + private static final class CountingBlsProvider implements Bls12381Provider { + private final Bls12381Provider delegate; + private int g1SecretScalarMulCalls; + private int g2SecretScalarMulCalls; + + private CountingBlsProvider(Bls12381Provider delegate) { + this.delegate = delegate; + } + + @Override + public String id() { + return "counting-" + delegate.id(); + } + + @Override + public G1Point g1Generator() { + return delegate.g1Generator(); + } + + @Override + public G2Point g2Generator() { + return delegate.g2Generator(); + } + + @Override + public G1Point g1ScalarMul(G1Point point, BigInteger scalar) { + return delegate.g1ScalarMul(point, scalar); + } + + @Override + public G2Point g2ScalarMul(G2Point point, BigInteger scalar) { + return delegate.g2ScalarMul(point, scalar); + } + + @Override + public G1Point g1SecretScalarMul(G1Point point, BigInteger scalar) { + g1SecretScalarMulCalls++; + return delegate.g1SecretScalarMul(point, scalar); + } + + @Override + public G2Point g2SecretScalarMul(G2Point point, BigInteger scalar) { + g2SecretScalarMulCalls++; + return delegate.g2SecretScalarMul(point, scalar); + } + + @Override + public boolean pairingProductIsIdentity(G1Point[] g1Points, G2Point[] g2Points) { + return delegate.pairingProductIsIdentity(g1Points, g2Points); + } + } +} diff --git a/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/CfrgBbsDraft10VectorTest.java b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/CfrgBbsDraft10VectorTest.java new file mode 100644 index 0000000..00cabf5 --- /dev/null +++ b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/CfrgBbsDraft10VectorTest.java @@ -0,0 +1,252 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import com.bloxbean.cardano.zeroj.bbs.internal.CfrgBbsCore; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProviders; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; + +class CfrgBbsDraft10VectorTest { + private static final BbsCiphersuite SUITE = BbsCiphersuite.BLS12381_SHA256; + private static final Properties VECTORS = loadVectors(); + + @Test + void keyGenAndSkToPk_matchDraft10Vector() { + var provider = BbsProviders.pureJava(); + + BbsSecretKey secretKey = provider.keyGen(hex("key_material"), hex("key_info")); + BbsPublicKey publicKey = provider.skToPk(secretKey); + + assertArrayEquals(hex("sk"), secretKey.toBytes()); + assertArrayEquals(hex("pk"), publicKey.bytes()); + } + + @Test + void messagesToScalars_matchDraft10Vector() { + List messages = messages(); + List actual = CfrgBbsCore.messagesToScalars(messages, SUITE); + List expected = csv("message_scalars"); + + assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertArrayEquals(hexString(expected.get(i)), com.bloxbean.cardano.zeroj.bbs.internal.BbsCodec.scalarToBytesAllowZero(actual.get(i))); + } + } + + @Test + void generators_matchDraft10Vector() { + var generators = CfrgBbsCore.createGenerators(11, SUITE, Bls12381Providers.pureJava()); + List expected = csv("generators"); + + assertArrayEquals(hexString(expected.get(0)), Bls12381Codecs.g1ToCompressed(generators.q1())); + for (int i = 0; i < generators.h().size(); i++) { + assertArrayEquals(hexString(expected.get(i + 1)), Bls12381Codecs.g1ToCompressed(generators.h().get(i))); + } + } + + @Test + void signAndVerify_matchDraft10SingleMessageVector() { + var provider = BbsProviders.pureJava(); + BbsSecretKey secretKey = new BbsSecretKey(new BigInteger(1, hex("sk")), SUITE); + BbsPublicKey publicKey = new BbsPublicKey(hex("pk"), SUITE); + List oneMessage = messages().subList(0, 1); + + BbsSignature signature = provider.sign(secretKey, publicKey, oneMessage, hex("header")); + + assertArrayEquals(hex("single_message_signature"), signature.bytes()); + assertTrue(provider.verify(publicKey, signature, oneMessage, hex("header"))); + assertFalse(provider.verify(publicKey, tamper(signature), oneMessage, hex("header"))); + assertFalse(provider.verify(publicKey, signature, oneMessage, hexString("00"))); + } + + @Test + void signAndVerify_matchDraft10MultiMessageVector() { + var provider = BbsProviders.pureJava(); + BbsSecretKey secretKey = new BbsSecretKey(new BigInteger(1, hex("sk")), SUITE); + BbsPublicKey publicKey = new BbsPublicKey(hex("pk"), SUITE); + List messages = messages(); + + BbsSignature signature = provider.sign(secretKey, publicKey, messages, hex("header")); + + assertArrayEquals(hex("multi_message_signature"), signature.bytes()); + assertTrue(provider.verify(publicKey, signature, messages, hex("header"))); + assertFalse(provider.verify(publicKey, signature, replace(messages, 2, hexString("01")), hex("header"))); + } + + @Test + void seededRandomScalars_matchDraft10MockVector() { + List scalars = CfrgBbsCore.seededRandomScalars(hex("mock_seed"), hex("mock_dst"), 10, SUITE); + List expected = csv("mock_scalars"); + + assertEquals(expected.size(), scalars.size()); + for (int i = 0; i < expected.size(); i++) { + assertArrayEquals(hexString(expected.get(i)), com.bloxbean.cardano.zeroj.bbs.internal.BbsCodec.scalarToBytesAllowZero(scalars.get(i))); + } + } + + @Test + void proofGenAndVerify_matchDraft10SingleMessageProofVector() { + List randomScalars = csv("single_proof_random_scalars").stream() + .map(hex -> new BigInteger(1, hexString(hex))) + .toList(); + byte[] proof = CfrgBbsCore.proofGenWithRandomScalars( + hex("pk"), + hex("single_message_signature"), + messages().subList(0, 1), + hex("header"), + hex("presentation_header"), + new int[]{0}, + SUITE, + Bls12381Providers.pureJava(), + randomScalars); + + assertArrayEquals(hex("single_proof"), proof); + assertTrue(CfrgBbsCore.proofVerify( + hex("pk"), + proof, + hex("header"), + hex("presentation_header"), + messages().subList(0, 1), + new int[]{0}, + SUITE, + Bls12381Providers.pureJava())); + } + + @Test + void proofGenAndVerify_matchDraft10AllDisclosedProofVector() { + List messages = messages(); + int[] allIndexes = java.util.stream.IntStream.range(0, messages.size()).toArray(); + List randomScalars = CfrgBbsCore.seededRandomScalars(hex("mock_seed"), hex("mock_dst"), 5, SUITE); + + byte[] proof = CfrgBbsCore.proofGenWithRandomScalars( + hex("pk"), + hex("multi_message_signature"), + messages, + hex("header"), + hex("presentation_header"), + allIndexes, + SUITE, + Bls12381Providers.pureJava(), + randomScalars); + + assertArrayEquals(hex("all_disclosed_proof"), proof); + assertTrue(CfrgBbsCore.proofVerify( + hex("pk"), + proof, + hex("header"), + hex("presentation_header"), + messages, + allIndexes, + SUITE, + Bls12381Providers.pureJava())); + } + + @Test + void proofGenAndVerify_matchDraft10SomeDisclosedProofVector() { + List messages = messages(); + int[] disclosedIndexes = {0, 2, 4, 6}; + List disclosedMessages = Arrays.stream(disclosedIndexes) + .mapToObj(messages::get) + .toList(); + List randomScalars = CfrgBbsCore.seededRandomScalars(hex("mock_seed"), hex("mock_dst"), 11, SUITE); + + byte[] proof = CfrgBbsCore.proofGenWithRandomScalars( + hex("pk"), + hex("multi_message_signature"), + messages, + hex("header"), + hex("presentation_header"), + disclosedIndexes, + SUITE, + Bls12381Providers.pureJava(), + randomScalars); + + assertArrayEquals(hex("some_disclosed_proof"), proof); + assertTrue(CfrgBbsCore.proofVerify( + hex("pk"), + proof, + hex("header"), + hex("presentation_header"), + disclosedMessages, + disclosedIndexes, + SUITE, + Bls12381Providers.pureJava())); + assertFalse(CfrgBbsCore.proofVerify( + hex("pk"), + proof, + hex("header"), + hexString("00"), + disclosedMessages, + disclosedIndexes, + SUITE, + Bls12381Providers.pureJava())); + } + + @Test + void invalidDisclosedIndexesReject() { + var provider = BbsProviders.pureJava(); + BbsPublicKey publicKey = new BbsPublicKey(hex("pk"), SUITE); + BbsSignature signature = new BbsSignature(hex("multi_message_signature"), SUITE); + List messages = messages(); + + assertThrows(IllegalArgumentException.class, + () -> provider.proofGen(publicKey, signature, messages, hex("header"), new byte[0], new int[]{2, 2}, new java.security.SecureRandom())); + assertThrows(IllegalArgumentException.class, + () -> provider.proofGen(publicKey, signature, messages, hex("header"), new byte[0], new int[]{3, 1}, new java.security.SecureRandom())); + assertFalse(provider.proofVerify(publicKey, new BbsProof(hex("some_disclosed_proof"), SUITE), + hex("header"), hex("presentation_header"), List.of(messages.get(0), messages.get(2)), new int[]{0, 0})); + } + + private static BbsSignature tamper(BbsSignature signature) { + byte[] bytes = signature.bytes(); + bytes[bytes.length - 1] ^= 1; + return new BbsSignature(bytes, signature.ciphersuite()); + } + + private static List replace(List messages, int index, byte[] replacement) { + java.util.ArrayList copy = new java.util.ArrayList<>(messages); + copy.set(index, replacement); + return List.copyOf(copy); + } + + private static List messages() { + return csv("messages").stream().map(CfrgBbsDraft10VectorTest::hexString).toList(); + } + + private static byte[] hex(String property) { + return hexString(VECTORS.getProperty(property)); + } + + private static byte[] hexString(String hex) { + if (hex.isEmpty()) { + return new byte[0]; + } + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } + + private static List csv(String property) { + return Arrays.asList(VECTORS.getProperty(property).split(",", -1)); + } + + private static Properties loadVectors() { + try (var in = CfrgBbsDraft10VectorTest.class.getResourceAsStream("/cfrg-bbs/draft10/sha256.properties")) { + Properties properties = new Properties(); + properties.load(in); + return properties; + } catch (IOException e) { + throw new IllegalStateException("Unable to load CFRG BBS draft-10 vectors", e); + } + } +} diff --git a/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/CfrgBbsOfficialJsonFixtureTest.java b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/CfrgBbsOfficialJsonFixtureTest.java new file mode 100644 index 0000000..6e1cc94 --- /dev/null +++ b/zeroj-bbs/src/test/java/com/bloxbean/cardano/zeroj/bbs/CfrgBbsOfficialJsonFixtureTest.java @@ -0,0 +1,251 @@ +package com.bloxbean.cardano.zeroj.bbs; + +import com.bloxbean.cardano.zeroj.bbs.internal.BbsCodec; +import com.bloxbean.cardano.zeroj.bbs.internal.CfrgBbsCore; +import com.bloxbean.cardano.zeroj.bbs.spi.BbsProviders; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class CfrgBbsOfficialJsonFixtureTest { + private static final List SUITES = List.of( + new SuiteFixture("/cfrg-bbs/draft10/official/bls12-381-sha-256", BbsCiphersuite.BLS12381_SHA256), + new SuiteFixture("/cfrg-bbs/draft10/official/bls12-381-shake-256", BbsCiphersuite.BLS12381_SHAKE256)); + private static final ObjectMapper JSON = new ObjectMapper(); + + @ParameterizedTest(name = "{0}") + @MethodSource("suites") + void officialKeyPairFixtureMatches(SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/keypair.json"); + var provider = BbsProviders.pureJava(suite.ciphersuite()); + + BbsSecretKey secretKey = provider.keyGen(hex(fixture, "keyMaterial"), hex(fixture, "keyInfo")); + BbsPublicKey publicKey = provider.skToPk(secretKey); + + assertArrayEquals(hex(fixture.at("/keyPair/secretKey")), secretKey.toBytes()); + assertArrayEquals(hex(fixture.at("/keyPair/publicKey")), publicKey.bytes()); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("suites") + void officialMapMessageToScalarFixtureMatches(SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/MapMessageToScalarAsHash.json"); + for (JsonNode c : fixture.get("cases")) { + BigInteger scalar = CfrgBbsCore.messagesToScalars(List.of(hex(c, "message")), suite.ciphersuite()).getFirst(); + + assertArrayEquals(hex(c, "scalar"), BbsCodec.scalarToBytesAllowZero(scalar)); + } + } + + @ParameterizedTest(name = "{0}") + @MethodSource("suites") + void officialGeneratorFixtureMatches(SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/generators.json"); + var generators = CfrgBbsCore.createGenerators(11, suite.ciphersuite(), Bls12381Providers.pureJava()); + + assertArrayEquals(hex(fixture, "P1"), Bls12381Codecs.g1ToCompressed(suite.ciphersuite().p1())); + assertArrayEquals(hex(fixture, "Q1"), Bls12381Codecs.g1ToCompressed(generators.q1())); + for (int i = 0; i < fixture.get("MsgGenerators").size(); i++) { + assertArrayEquals(hex(fixture.get("MsgGenerators").get(i)), + Bls12381Codecs.g1ToCompressed(generators.h().get(i))); + } + } + + @ParameterizedTest(name = "{0}") + @MethodSource("suites") + void officialHashToScalarFixtureMatches(SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/h2s.json"); + BigInteger scalar = CfrgBbsCore.hashToScalar(hex(fixture, "message"), hex(fixture, "dst"), suite.ciphersuite()); + + assertArrayEquals(hex(fixture, "scalar"), BbsCodec.scalarToBytesAllowZero(scalar)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("suites") + void officialMockedRngFixtureMatches(SuiteFixture suite) { + JsonNode fixture = read(suite.resourceBase() + "/mockedRng.json"); + List scalars = CfrgBbsCore.seededRandomScalars( + hex(fixture, "seed"), hex(fixture, "dst"), fixture.get("count").asInt(), suite.ciphersuite()); + + assertEquals(fixture.get("mockedScalars").size(), scalars.size()); + for (int i = 0; i < scalars.size(); i++) { + assertArrayEquals(hex(fixture.get("mockedScalars").get(i)), BbsCodec.scalarToBytesAllowZero(scalars.get(i))); + } + } + + @ParameterizedTest(name = "{0} {1}") + @MethodSource("signatureFixtures") + void officialSignatureFixturesVerifyAndValidCasesSign(SuiteFixture suite, String resource) { + JsonNode fixture = read(resource); + var provider = BbsProviders.pureJava(suite.ciphersuite()); + BbsSecretKey secretKey = new BbsSecretKey(new BigInteger(1, hex(fixture.at("/signerKeyPair/secretKey"))), suite.ciphersuite()); + BbsPublicKey publicKey = new BbsPublicKey(hex(fixture.at("/signerKeyPair/publicKey")), suite.ciphersuite()); + BbsSignature signature = new BbsSignature(hex(fixture, "signature"), suite.ciphersuite()); + List messages = byteArrayList(fixture.get("messages")); + byte[] header = hex(fixture, "header"); + boolean expected = fixture.at("/result/valid").asBoolean(); + + assertEquals(expected, provider.verify(publicKey, signature, messages, header), fixture.get("caseName").asText()); + + if (expected) { + BbsSignature generated = provider.sign(secretKey, publicKey, messages, header); + assertArrayEquals(signature.bytes(), generated.bytes()); + assertSignatureTrace(fixture, publicKey.bytes(), messages, header, suite.ciphersuite()); + } + } + + @ParameterizedTest(name = "{0} {1}") + @MethodSource("proofFixtures") + void officialProofFixturesVerifyAndValidCasesGenerate(SuiteFixture suite, String resource) { + JsonNode fixture = read(resource); + byte[] publicKey = hex(fixture, "signerPublicKey"); + byte[] proof = hex(fixture, "proof"); + byte[] header = hex(fixture, "header"); + byte[] presentationHeader = hex(fixture, "presentationHeader"); + List messages = byteArrayList(fixture.get("messages")); + int[] disclosedIndexes = intArray(fixture.get("disclosedIndexes")); + List disclosedMessages = select(messages, disclosedIndexes); + boolean expected = fixture.at("/result/valid").asBoolean(); + + assertEquals(expected, CfrgBbsCore.proofVerify( + publicKey, + proof, + header, + presentationHeader, + disclosedMessages, + disclosedIndexes, + suite.ciphersuite(), + Bls12381Providers.pureJava()), fixture.get("caseName").asText()); + + if (expected) { + byte[] generated = CfrgBbsCore.proofGenWithRandomScalars( + publicKey, + hex(fixture, "signature"), + messages, + header, + presentationHeader, + disclosedIndexes, + suite.ciphersuite(), + Bls12381Providers.pureJava(), + proofRandomScalars(fixture.at("/trace/random_scalars"))); + assertArrayEquals(proof, generated); + } + } + + private static void assertSignatureTrace( + JsonNode fixture, + byte[] publicKey, + List messages, + byte[] header, + BbsCiphersuite ciphersuite) { + List scalars = CfrgBbsCore.messagesToScalars(messages, ciphersuite); + var generators = CfrgBbsCore.createGenerators(scalars.size() + 1, ciphersuite, Bls12381Providers.pureJava()); + BigInteger domain = CfrgBbsCore.calculateDomain(publicKey, generators, header, ciphersuite); + + assertArrayEquals(hex(fixture.at("/trace/domain")), BbsCodec.scalarToBytesAllowZero(domain)); + assertArrayEquals(hex(fixture.at("/trace/B")), + Bls12381Codecs.g1ToCompressed(CfrgBbsCore.calculateB(generators, domain, scalars, ciphersuite))); + } + + private static List proofRandomScalars(JsonNode randomScalars) { + List out = new ArrayList<>(); + out.add(scalar(randomScalars, "r1")); + out.add(scalar(randomScalars, "r2")); + out.add(scalar(randomScalars, "e_tilde")); + out.add(scalar(randomScalars, "r1_tilde")); + out.add(scalar(randomScalars, "r3_tilde")); + for (JsonNode mTilde : randomScalars.get("m_tilde_scalars")) { + out.add(new BigInteger(1, hex(mTilde))); + } + return List.copyOf(out); + } + + private static BigInteger scalar(JsonNode node, String fieldName) { + return new BigInteger(1, hex(node, fieldName)); + } + + private static List select(List values, int[] indexes) { + List out = new ArrayList<>(indexes.length); + for (int index : indexes) { + out.add(values.get(index)); + } + return List.copyOf(out); + } + + private static List byteArrayList(JsonNode array) { + List out = new ArrayList<>(); + for (JsonNode item : array) { + out.add(hex(item)); + } + return List.copyOf(out); + } + + private static int[] intArray(JsonNode array) { + int[] out = new int[array.size()]; + for (int i = 0; i < array.size(); i++) { + out[i] = array.get(i).asInt(); + } + return out; + } + + private static byte[] hex(JsonNode node, String fieldName) { + return hex(node.get(fieldName)); + } + + private static byte[] hex(JsonNode node) { + String hex = node.asText(); + if (hex.isEmpty()) { + return new byte[0]; + } + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } + + private static JsonNode read(String resource) { + try (var in = CfrgBbsOfficialJsonFixtureTest.class.getResourceAsStream(resource)) { + assertNotNull(in, "Missing fixture resource: " + resource); + return JSON.readTree(in); + } catch (IOException e) { + throw new IllegalStateException("Unable to read fixture resource: " + resource, e); + } + } + + private static Stream suites() { + return SUITES.stream(); + } + + private static Stream signatureFixtures() { + return SUITES.stream() + .flatMap(suite -> IntStream.rangeClosed(1, 10) + .mapToObj(i -> Arguments.of(suite, suite.resourceBase() + "/signature/signature%03d.json".formatted(i)))); + } + + private static Stream proofFixtures() { + return SUITES.stream() + .flatMap(suite -> IntStream.rangeClosed(1, 15) + .mapToObj(i -> Arguments.of(suite, suite.resourceBase() + "/proof/proof%03d.json".formatted(i)))); + } + + private record SuiteFixture(String resourceBase, BbsCiphersuite ciphersuite) { + @Override + public String toString() { + return ciphersuite.name(); + } + } +} diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/MapMessageToScalarAsHash.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/MapMessageToScalarAsHash.json new file mode 100644 index 0000000..cfa7413 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/MapMessageToScalarAsHash.json @@ -0,0 +1,46 @@ +{ + "caseName": "MapMessageToScalar fixture", + "dst": "4242535f424c53313233383147315f584d443a5348412d3235365f535357555f524f5f4832475f484d32535f4d41505f4d53475f544f5f5343414c41525f41535f484153485f", + "cases": [ + { + "message": "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "scalar": "1cb5bb86114b34dc438a911617655a1db595abafac92f47c5001799cf624b430" + }, + { + "message": "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "scalar": "154249d503c093ac2df516d4bb88b510d54fd97e8d7121aede420a25d9521952" + }, + { + "message": "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "scalar": "0c7c4c85cdab32e6fdb0de267b16fa3212733d4e3a3f0d0f751657578b26fe22" + }, + { + "message": "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "scalar": "4a196deafee5c23f630156ae13be3e46e53b7e39094d22877b8cba7f14640888" + }, + { + "message": "496694774c5604ab1b2544eababcf0f53278ff50", + "scalar": "34c5ea4f2ba49117015a02c711bb173c11b06b3f1571b88a2952b93d0ed4cf7e" + }, + { + "message": "515ae153e22aae04ad16f759e07237b4", + "scalar": "4045b39b83055cd57a4d0203e1660800fabe434004dbdc8730c21ce3f0048b08" + }, + { + "message": "d183ddc6e2665aa4e2f088af", + "scalar": "064621da4377b6b1d05ecc37cf3b9dfc94b9498d7013dc5c4a82bf3bb1750743" + }, + { + "message": "ac55fb33a75909ed", + "scalar": "34ac9196ace0a37e147e32319ea9b3d8cc7d21870d3c3ba071246859cca49b02" + }, + { + "message": "96012096", + "scalar": "57eb93f417c43200e9784fa5ea5a59168d3dbc38df707a13bb597c871b2a5f74" + }, + { + "message": "", + "scalar": "08e3afeb2b4f2b5f907924ef42856616e6f2d5f1fb373736db1cca32707a7d16" + } + ] +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/generators.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/generators.json new file mode 100644 index 0000000..967b6f2 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/generators.json @@ -0,0 +1,16 @@ +{ + "P1": "a8ce256102840821a3e94ea9025e4662b205762f9776b3a766c872b948f1fd225e7c59698588e70d11406d161b4e28c9", + "Q1": "a9ec65b70a7fbe40c874c9eb041c2cb0a7af36ccec1bea48fa2ba4c2eb67ef7f9ecb17ed27d38d27cdeddff44c8137be", + "MsgGenerators": [ + "98cd5313283aaf5db1b3ba8611fe6070d19e605de4078c38df36019fbaad0bd28dd090fd24ed27f7f4d22d5ff5dea7d4", + "a31fbe20c5c135bcaa8d9fc4e4ac665cc6db0226f35e737507e803044093f37697a9d452490a970eea6f9ad6c3dcaa3a", + "b479263445f4d2108965a9086f9d1fdc8cde77d14a91c856769521ad3344754cc5ce90d9bc4c696dffbc9ef1d6ad1b62", + "ac0401766d2128d4791d922557c7b4d1ae9a9b508ce266575244a8d6f32110d7b0b7557b77604869633bb49afbe20035", + "b95d2898370ebc542857746a316ce32fa5151c31f9b57915e308ee9d1de7db69127d919e984ea0747f5223821b596335", + "8f19359ae6ee508157492c06765b7df09e2e5ad591115742f2de9c08572bb2845cbf03fd7e23b7f031ed9c7564e52f39", + "abc914abe2926324b2c848e8a411a2b6df18cbe7758db8644145fefb0bf0a2d558a8c9946bd35e00c69d167aadf304c1", + "80755b3eb0dd4249cbefd20f177cee88e0761c066b71794825c9997b551f24051c352567ba6c01e57ac75dff763eaa17", + "82701eb98070728e1769525e73abff1783cedc364adb20c05c897a62f2ab2927f86f118dcb7819a7b218d8f3fee4bd7f", + "a1f229540474f4d6f1134761b92b788128c7ac8dc9b0c52d59493132679673032ac7db3fb3d79b46b13c1c41ee495bca" + ] +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/h2s.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/h2s.json new file mode 100644 index 0000000..c116f76 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/h2s.json @@ -0,0 +1,6 @@ +{ + "caseName": "Hash to scalar output", + "message": "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "dst": "4242535f424c53313233383147315f584d443a5348412d3235365f535357555f524f5f4832475f484d32535f4832535f", + "scalar": "0f90cbee27beb214e6545becb8404640d3612da5d6758dffeccd77ed7169807c" +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/keypair.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/keypair.json new file mode 100644 index 0000000..4b203c7 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/keypair.json @@ -0,0 +1,10 @@ +{ + "caseName": "key pair fixture", + "keyMaterial": "746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579", + "keyInfo": "746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e", + "keyDst": "4242535f424c53313233383147315f584d443a5348412d3235365f535357555f524f5f4832475f484d32535f4b455947454e5f4453545f", + "keyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/mockedRng.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/mockedRng.json new file mode 100644 index 0000000..b12218c --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/mockedRng.json @@ -0,0 +1,18 @@ +{ + "caseName": "mocked random scalars", + "seed": "332e313431353932363533353839373933323338343632363433333833323739", + "dst": "4242535f424c53313233383147315f584d443a5348412d3235365f535357555f524f5f4832475f484d32535f4d4f434b5f52414e444f4d5f5343414c4152535f4453545f", + "count": 10, + "mockedScalars": [ + "04f8e2518993c4383957ad14eb13a023c4ad0c67d01ec86eeb902e732ed6df3f", + "5d87c1ba64c320ad601d227a1b74188a41a100325cecf00223729863966392b1", + "0444607600ac70482e9c983b4b063214080b9e808300aa4cc02a91b3a92858fe", + "548cd11eae4318e88cda10b4cd31ae29d41c3a0b057196ee9cf3a69d471e4e94", + "2264b06a08638b69b4627756a62f08e0dc4d8240c1b974c9c7db779a769892f4", + "4d99352986a9f8978b93485d21525244b21b396cf61f1d71f7c48e3fbc970a42", + "5ed8be91662386243a6771fbdd2c627de31a44220e8d6f745bad5d99821a4880", + "62ff1734b939ddd87beeb37a7bbcafa0a274cbc1b07384198f0e88398272208d", + "05c2a0af016df58e844db8944082dcaf434de1b1e2e7136ec8a99b939b716223", + "485e2adab17b76f5334c95bf36c03ccf91cef77dcfcdc6b8a69e2090b3156663" + ] +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof001.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof001.json new file mode 100644 index 0000000..a5816bb --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof001.json @@ -0,0 +1,34 @@ +{ + "caseName": "valid single message signature, single-message revealed proof", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "84773160b824e194073a57493dac1a20b667af70cd2352d8af241c77658da5253aa8458317cca0eae615690d55b1f27164657dcafee1d5c1973947aa70e2cfbb4c892340be5969920d0916067b4565a0", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02" + ], + "disclosedIndexes": [ + 0 + ], + "proof": "94916292a7a6bade28456c601d3af33fcf39278d6594b467e128a3f83686a104ef2b2fcf72df0215eeaf69262ffe8194a19fab31a82ddbe06908985abc4c9825788b8a1610942d12b7f5debbea8985296361206dbace7af0cc834c80f33e0aadaeea5597befbb651827b5eed5a66f1a959bb46cfd5ca1a817a14475960f69b32c54db7587b5ee3ab665fbd37b506830a49f21d592f5e634f47cee05a025a2f8f94e73a6c15f02301d1178a92873b6e8634bafe4983c3e15a663d64080678dbf29417519b78af042be2b3e1c4d08b8d520ffab008cbaaca5671a15b22c239b38e940cfeaa5e72104576a9ec4a6fad78c532381aeaa6fb56409cef56ee5c140d455feeb04426193c57086c9b6d397d9418", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "60ca409f6b0563f687fc471c63d2819f446f39c23bb540925d9d4254ac58f337", + "r2": "2ceff4982de0c913090f75f081df5ec594c310bb48c17cfdaab5332a682ef811", + "e_tilde": "6101c4404895f3dff87ab39c34cb995af07e7139e6b3847180ffdd1bc8c313cd", + "r1_tilde": "0dfcffd97a6ecdebef3c9c114b99d7a030c998d938905f357df62822dee072e8", + "r3_tilde": "639e3417007d38e5d34ba8c511e836768ddc2669fdd3faff5c14ad27ac2b2da1", + "m_tilde_scalars": [] + }, + "A_bar": "94916292a7a6bade28456c601d3af33fcf39278d6594b467e128a3f83686a104ef2b2fcf72df0215eeaf69262ffe8194", + "B_bar": "a19fab31a82ddbe06908985abc4c9825788b8a1610942d12b7f5debbea8985296361206dbace7af0cc834c80f33e0aad", + "D": "aeea5597befbb651827b5eed5a66f1a959bb46cfd5ca1a817a14475960f69b32c54db7587b5ee3ab665fbd37b506830a", + "T1": "a862fa5d3ab4c264c22b8a02636fd4030e8b14ac20dee14e08fdb6cfc445432c08abb49ec111c1eb9d90abef50134a60", + "T2": "ab9543a6b04303e997621d3d5cbd85924e7e69da498a2a9e9d3a8b01f39259c9c5920bd530de1d3b0afb99eb0c549d5a", + "domain": "25d57fab92a8274c68fde5c3f16d4b275e4a156f211ae34b3ab32fbaf506ed5c", + "challenge": "32381aeaa6fb56409cef56ee5c140d455feeb04426193c57086c9b6d397d9418" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof002.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof002.json new file mode 100644 index 0000000..d0b868b --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof002.json @@ -0,0 +1,52 @@ +{ + "caseName": "valid multi-message signature, all messages revealed proof", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "proof": "b1f468aec2001c4f54cb56f707c6222a43e5803a25b2253e67b2210ab2ef9eab52db2d4b379935c4823281eaf767fd37b08ce80dc65de8f9769d27099ae649ad4c9b4bd2cc23edcba52073a298087d2495e6d57aaae051ef741adf1cbce65c64a73c8c97264177a76c4a03341956d2ae45ed3438ce598d5cda4f1bf9507fecef47855480b7b30b5e4052c92a4360110c67327365763f5aa9fb85ddcbc2975449b8c03db1216ca66b310f07d0ccf12ab460cdc6003b677fed36d0a23d0818a9d4d098d44f749e91008cf50e8567ef936704c8277b7710f41ab7e6e16408ab520edc290f9801349aee7b7b4e318e6a76e028e1dea911e2e7baec6a6a174da1a22362717fbae1cd961d7bf4adce1d31c2ab", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "60ca409f6b0563f687fc471c63d2819f446f39c23bb540925d9d4254ac58f337", + "r2": "2ceff4982de0c913090f75f081df5ec594c310bb48c17cfdaab5332a682ef811", + "e_tilde": "6101c4404895f3dff87ab39c34cb995af07e7139e6b3847180ffdd1bc8c313cd", + "r1_tilde": "0dfcffd97a6ecdebef3c9c114b99d7a030c998d938905f357df62822dee072e8", + "r3_tilde": "639e3417007d38e5d34ba8c511e836768ddc2669fdd3faff5c14ad27ac2b2da1", + "m_tilde_scalars": [] + }, + "A_bar": "b1f468aec2001c4f54cb56f707c6222a43e5803a25b2253e67b2210ab2ef9eab52db2d4b379935c4823281eaf767fd37", + "B_bar": "b08ce80dc65de8f9769d27099ae649ad4c9b4bd2cc23edcba52073a298087d2495e6d57aaae051ef741adf1cbce65c64", + "D": "a73c8c97264177a76c4a03341956d2ae45ed3438ce598d5cda4f1bf9507fecef47855480b7b30b5e4052c92a4360110c", + "T1": "9881efa96b2411626d490e399eb1c06badf23c2c0760bd403f50f45a6b470c5a9dbeef53a27916f2f165085a3878f1f4", + "T2": "b9f8cf9271d10a04ae7116ad021f4b69c435d20a5af10ddd8f5b1ec6b9b8b91605aca76a140241784b7f161e21dfc3e7", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "28e1dea911e2e7baec6a6a174da1a22362717fbae1cd961d7bf4adce1d31c2ab" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof003.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof003.json new file mode 100644 index 0000000..9e66110 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof003.json @@ -0,0 +1,53 @@ +{ + "caseName": "valid multi-message signature, multiple messages revealed proof", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof004.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof004.json new file mode 100644 index 0000000..5e8514b --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof004.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (different presentation header)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "011594ba7f95b3b470ea4102dd5899de3a042e5104d3ea01d15e6780d831d2be", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "different presentation header" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof005.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof005.json new file mode 100644 index 0000000..ec3f77d --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof005.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (wrong public key)", + "signerPublicKey": "b064bd8d1ba99503cbb7f9d7ea00bce877206a85b1750e5583dd9399828a4d20610cb937ea928d90404c239b2835ffb104220a9c66a4c9ed3b54c0cac9ea465d0429556b438ceefb59650ddf67e7a8f103677561b7ef7fe3c3357ec6b94d41c6", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "wrong public key" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof006.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof006.json new file mode 100644 index 0000000..7d523a1 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof006.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (modified messages)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "7385ee1a722e00e173b4cdb1c1e0c3fb379403a31b337d3778c447d9da664ac876b0f7c5587d9e994c51f9e2b6de09c0f1d0f3b39b275a96da4926c22e55166998b8c4e90372820c007ceb27bd34ec4ebfab63fea4dcc88d95f58b25ffd35b041f3fe994", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "modified messages" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof007.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof007.json new file mode 100644 index 0000000..943dae1 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof007.json @@ -0,0 +1,56 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (extra message un-revealed in proof)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6, + 9 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "extra message un-revealed in proof" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof008.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof008.json new file mode 100644 index 0000000..242d3c1 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof008.json @@ -0,0 +1,56 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (extra message invalid message un-revealed in proof)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + "96012096" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6, + 9 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "extra message invalid message un-revealed in proof" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof009.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof009.json new file mode 100644 index 0000000..9491590 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof009.json @@ -0,0 +1,52 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (missing message revealed in proof)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "missing message revealed in proof" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof010.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof010.json new file mode 100644 index 0000000..3574f13 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof010.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (re-ordered messages)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 4, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "re-ordered messages" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof011.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof011.json new file mode 100644 index 0000000..786efb2 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof011.json @@ -0,0 +1,56 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (extra valid message, modified total message count)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6, + 9 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "extra valid message, modified total message count" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof012.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof012.json new file mode 100644 index 0000000..6d32527 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof012.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (truncated proof, one less undisclosed message)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870", + "result": { + "valid": false, + "reason": "truncated proof, one less undisclosed message" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof013.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof013.json new file mode 100644 index 0000000..53b0ba2 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof013.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (different header)", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "ffeeddccbbaa00998877665544332211", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a", + "result": { + "valid": false, + "reason": "different header" + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof014.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof014.json new file mode 100644 index 0000000..a23c728 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof014.json @@ -0,0 +1,53 @@ +{ + "caseName": "valid multi-message signature, multiple messages revealed proof, no header", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8c87e2080859a97299c148427cd2fcf390d24bea850103a9748879039262ecf4f42206f6ef767f298b6a96b424c1e86c26f8fba62212d0e05b95261c2cc0e5fdc63a32731347e810fd12e9c58355aa0d", + "header": "", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "81925c2e525d9fbb0ba95b438b5a13fff5874c7c0515c193628d7d143ddc3bb487771ad73658895997a88dd5b254ed29abc019bfca62c09b8dafb37e5f09b1d380e084ec3623d071ec38d6b8602af93aa0ddbada307c9309cca86be16db53dc7ac310574f509c712bb1a181d64ea3c1ee075c018a2bc773e2480b5c033ccb9bfea5af347a88ab83746c9342ba76db3675ff70ce9006d166fd813a81b448a632216521c864594f3f92965974914992f8d1845230915b11680cf44b25886c5670904ac2d88255c8c31aea7b072e9c4eb7e4c3fdd38836ae9d2e9fa271c8d9fd42f669a9938aeeba9d8ae613bf11f489ce947616f5cbaee95511dfaa5c73d85e4ddd2f29340f821dc2fb40db3eae5f5bc08467eb195e38d7d436b63e556ea653168282a23b53d5792a107f85b1203f82aab46f6940650760e5b320261ffc0ca5f15917b51e7d2ad4bcbec94de792e229db663abff23af392a5e73ce115c27e8492ec24a0815091c69874dbd9dae2d2eed000810c748a798a78a804a39034c6e745cee455812cc982eea7105948b2cb55b82278a77237fcbec4748e2d2255af0994dd09dba8ac60515a39b24632a2c1c840c4a70506add5b2eb0be9ff66e3ea8deae666f198edfbb1391c6834e6df4f1026d", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "81925c2e525d9fbb0ba95b438b5a13fff5874c7c0515c193628d7d143ddc3bb487771ad73658895997a88dd5b254ed29", + "B_bar": "abc019bfca62c09b8dafb37e5f09b1d380e084ec3623d071ec38d6b8602af93aa0ddbada307c9309cca86be16db53dc7", + "D": "ac310574f509c712bb1a181d64ea3c1ee075c018a2bc773e2480b5c033ccb9bfea5af347a88ab83746c9342ba76db367", + "T1": "ada552bd7ee0d6914b89eaa0e9426b3bdbdfa7ecac26b3c118aefefc577095e894c1b4a828c184e091a563e09763f3a9", + "T2": "818dd907bf0321cf982648f91d7201b357358d3b2f6f7678afa722d89bbe5eba4415e4a65567a03292d9c7859da20cad", + "domain": "41c5fe0290d0da734ce9bba57bfe0dfc14f3f9cfef18a0d7438cf2075fd71cc7", + "challenge": "4a70506add5b2eb0be9ff66e3ea8deae666f198edfbb1391c6834e6df4f1026d" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof015.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof015.json new file mode 100644 index 0000000..f61fbe6 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/proof/proof015.json @@ -0,0 +1,53 @@ +{ + "caseName": "valid multi-message signature, multiple messages revealed proof, no presentation header", + "signerPublicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c", + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135672556358e78b5398f1a547a2a98dfe16230f244ba742dea737e4f810b4d94e03ac068ef840aaadf12b2ed51d3fb774c2a0a620019fd1f39c52c6f89a0e6067e3039413a91129791b2af215a82ad2356b6bc305c1d7a828fe519619dd026eaaf07ea81cee52b21aab3e8320519bf37c2bb228a8b580f899d84327bdc5e84a66000e8bac17d2fa039bb2246c8eacc623ccd9eb26e184a96a9e3a6702e1dbafe194772394b05251f72bcd2d20f542b15b2406f899791f6f285c7b469e7c7b9624147f305c38c903273a949f6e85b9774aeeccfafa432e2cdd7c8f97d1687741ed30d725444428dd87d9884711d9a46baaf0c04b03a2a228b7033be0841880134b03b15f698756eca5f37503a0411a9586d3027a8b8b9118e95a9949b2719e85e4a669d9e4b7bb6d4544c8cc558c30d79f9c85a87e1a95611400b7c7dac5673d800", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "44679831fe60eca50938ef0e812e2a9284ad7971b6932a38c7303538b712e457", + "r2": "6481692f89086cce11779e847ff884db8eebb85a13e81b2d0c79d6c1062069d8", + "e_tilde": "721ce4c4c148a1d5826f326af6fd6ac2844f29533ba4127c3a43d222d51b7081", + "r1_tilde": "1ecfaf5a079b0504b00a1f0d6fe8857291dd798291d7ad7454b398114393f37f", + "r3_tilde": "0a4b3d59b34707bb9999bc6e2a6d382a2d2e214bff36ecd88639a14124b1622e", + "m_tilde_scalars": [ + "7217411a9e329c7a5705e8db552274646e2949d62c288d7537dd62bc284715e4", + "67d4d43660746759f598caac106a2b5f58ccd1c3eefaec31841a4f77d2548870", + "715d965b1c3912d20505b381470ff1a528700b673e50ba89fd287e13171cc137", + "4d3281a149674e58c9040fc7a10dd92cb9c7f76f6f0815a1afc3b09d74b92fe4", + "438feebaa5894ca0da49992df2c97d872bf153eab07e08ff73b28131c46ff415", + "602b723c8bbaec1b057d70f18269ae5e6de6197a5884967b03b933fa80006121" + ] + }, + "A_bar": "a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc4151", + "B_bar": "99462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f", + "D": "897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac48135", + "T1": "84719c2b5bb275ee74913dbf95fb9054f690c8e4035f1259e184e9024544bc4bbea9c244e7897f9db7c82b7b14b27d28", + "T2": "8f5f191c956aefd5c960e57d2dfbab6761eb0ebc5efdba1aca1403dcc19e05296b16c9feb7636cb4ef2a360c5a148483", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47", + "challenge": "669d9e4b7bb6d4544c8cc558c30d79f9c85a87e1a95611400b7c7dac5673d800" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature001.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature001.json new file mode 100644 index 0000000..5c2254d --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature001.json @@ -0,0 +1,19 @@ +{ + "caseName": "valid single message signature", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02" + ], + "signature": "84773160b824e194073a57493dac1a20b667af70cd2352d8af241c77658da5253aa8458317cca0eae615690d55b1f27164657dcafee1d5c1973947aa70e2cfbb4c892340be5969920d0916067b4565a0", + "result": { + "valid": true + }, + "trace": { + "B": "92d264aed02bf23de022ebe778c4f929fddf829f504e451d011ed89a313b8167ac947332e1648157ceffc6e6e41ab255", + "domain": "25d57fab92a8274c68fde5c3f16d4b275e4a156f211ae34b3ab32fbaf506ed5c" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature002.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature002.json new file mode 100644 index 0000000..ca9ce3b --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature002.json @@ -0,0 +1,20 @@ +{ + "caseName": "invalid single message signature (modified message)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "" + ], + "signature": "84773160b824e194073a57493dac1a20b667af70cd2352d8af241c77658da5253aa8458317cca0eae615690d55b1f27164657dcafee1d5c1973947aa70e2cfbb4c892340be5969920d0916067b4565a0", + "result": { + "valid": false, + "reason": "modified message" + }, + "trace": { + "B": "92d264aed02bf23de022ebe778c4f929fddf829f504e451d011ed89a313b8167ac947332e1648157ceffc6e6e41ab255", + "domain": "25d57fab92a8274c68fde5c3f16d4b275e4a156f211ae34b3ab32fbaf506ed5c" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature003.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature003.json new file mode 100644 index 0000000..4642e8c --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature003.json @@ -0,0 +1,21 @@ +{ + "caseName": "invalid single message signature (extra unsigned message)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80" + ], + "signature": "84773160b824e194073a57493dac1a20b667af70cd2352d8af241c77658da5253aa8458317cca0eae615690d55b1f27164657dcafee1d5c1973947aa70e2cfbb4c892340be5969920d0916067b4565a0", + "result": { + "valid": false, + "reason": "extra unsigned message" + }, + "trace": { + "B": "92d264aed02bf23de022ebe778c4f929fddf829f504e451d011ed89a313b8167ac947332e1648157ceffc6e6e41ab255", + "domain": "25d57fab92a8274c68fde5c3f16d4b275e4a156f211ae34b3ab32fbaf506ed5c" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature004.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature004.json new file mode 100644 index 0000000..d603b00 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature004.json @@ -0,0 +1,28 @@ +{ + "caseName": "valid multi-message signature", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "result": { + "valid": true + }, + "trace": { + "B": "84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature005.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature005.json new file mode 100644 index 0000000..17adc78 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature005.json @@ -0,0 +1,21 @@ +{ + "caseName": "invalid multi-message signature (missing messages)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80" + ], + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "result": { + "valid": false, + "reason": "missing messages" + }, + "trace": { + "B": "84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature006.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature006.json new file mode 100644 index 0000000..236b35a --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature006.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (re-ordered messages)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "", + "96012096", + "ac55fb33a75909ed", + "d183ddc6e2665aa4e2f088af", + "515ae153e22aae04ad16f759e07237b4", + "496694774c5604ab1b2544eababcf0f53278ff50", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02" + ], + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "result": { + "valid": false, + "reason": "re-ordered messages" + }, + "trace": { + "B": "84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature007.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature007.json new file mode 100644 index 0000000..abeee40 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature007.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (wrong public key)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "b064bd8d1ba99503cbb7f9d7ea00bce877206a85b1750e5583dd9399828a4d20610cb937ea928d90404c239b2835ffb104220a9c66a4c9ed3b54c0cac9ea465d0429556b438ceefb59650ddf67e7a8f103677561b7ef7fe3c3357ec6b94d41c6" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "result": { + "valid": false, + "reason": "wrong public key" + }, + "trace": { + "B": "84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature008.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature008.json new file mode 100644 index 0000000..a8e7c08 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature008.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (different header)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "ffeeddccbbaa00998877665544332211", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "result": { + "valid": false, + "reason": "different header" + }, + "trace": { + "B": "84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature009.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature009.json new file mode 100644 index 0000000..e9778b2 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature009.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (re-ordered(randomly shuffled) messages)", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "ac55fb33a75909ed", + "", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "d183ddc6e2665aa4e2f088af", + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "96012096", + "515ae153e22aae04ad16f759e07237b4", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50" + ], + "signature": "8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8", + "result": { + "valid": false, + "reason": "re-ordered(randomly shuffled) messages" + }, + "trace": { + "B": "84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522", + "domain": "6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature010.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature010.json new file mode 100644 index 0000000..49b5124 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-sha-256/signature/signature010.json @@ -0,0 +1,28 @@ +{ + "caseName": "valid multi-message signature, no header", + "signerKeyPair": { + "secretKey": "60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc", + "publicKey": "a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c" + }, + "header": "", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "8c87e2080859a97299c148427cd2fcf390d24bea850103a9748879039262ecf4f42206f6ef767f298b6a96b424c1e86c26f8fba62212d0e05b95261c2cc0e5fdc63a32731347e810fd12e9c58355aa0d", + "result": { + "valid": true + }, + "trace": { + "B": "98e38eadb6a2232cf91f41861089cda14d7e3ddef0c6eaba4d11a2732f66408f394d58301ffcc8fcfb3c89bb75136f61", + "domain": "41c5fe0290d0da734ce9bba57bfe0dfc14f3f9cfef18a0d7438cf2075fd71cc7" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/MapMessageToScalarAsHash.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/MapMessageToScalarAsHash.json new file mode 100644 index 0000000..da81b8c --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/MapMessageToScalarAsHash.json @@ -0,0 +1,46 @@ +{ + "caseName": "MapMessageToScalar fixture", + "dst": "4242535f424c53313233383147315f584f463a5348414b452d3235365f535357555f524f5f4832475f484d32535f4d41505f4d53475f544f5f5343414c41525f41535f484153485f", + "cases": [ + { + "message": "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "scalar": "1e0dea6c9ea8543731d331a0ab5f64954c188542b33c5bbc8ae5b3a830f2d99f" + }, + { + "message": "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "scalar": "3918a40fb277b4c796805d1371931e08a314a8bf8200a92463c06054d2c56a9f" + }, + { + "message": "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "scalar": "6642b981edf862adf34214d933c5d042bfa8f7ef343165c325131e2ffa32fa94" + }, + { + "message": "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "scalar": "33c021236956a2006f547e22ff8790c9d2d40c11770c18cce6037786c6f23512" + }, + { + "message": "496694774c5604ab1b2544eababcf0f53278ff50", + "scalar": "52b249313abbe323e7d84230550f448d99edfb6529dec8c4e783dbd6dd2a8471" + }, + { + "message": "515ae153e22aae04ad16f759e07237b4", + "scalar": "2a50bdcbe7299e47e1046100aadffe35b4247bf3f059d525f921537484dd54fc" + }, + { + "message": "d183ddc6e2665aa4e2f088af", + "scalar": "0e92550915e275f8cfd6da5e08e334d8ef46797ee28fa29de40a1ebccd9d95d3" + }, + { + "message": "ac55fb33a75909ed", + "scalar": "4c28f612e6c6f82f51f95e1e4faaf597547f93f6689827a6dcda3cb94971d356" + }, + { + "message": "96012096", + "scalar": "1db51bedc825b85efe1dab3e3ab0274fa82bbd39732be3459525faf70f197650" + }, + { + "message": "", + "scalar": "27878da72f7775e709bb693d81b819dc4e9fa60711f4ea927740e40073489e78" + } + ] +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/generators.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/generators.json new file mode 100644 index 0000000..0655962 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/generators.json @@ -0,0 +1,16 @@ +{ + "P1": "8929dfbc7e6642c4ed9cba0856e493f8b9d7d5fcb0c31ef8fdcd34d50648a56c795e106e9eada6e0bda386b414150755", + "Q1": "a9d40131066399fd41af51d883f4473b0dcd7d028d3d34ef17f3241d204e28507d7ecae032afa1d5490849b7678ec1f8", + "MsgGenerators": [ + "903c7ca0b7e78a2017d0baf74103bd00ca8ff9bf429f834f071c75ffe6bfdec6d6dca15417e4ac08ca4ae1e78b7adc0e", + "84321f5855bfb6b001f0dfcb47ac9b5cc68f1a4edd20f0ec850e0563b27d2accee6edff1a26b357762fb24e8ddbb6fcb", + "b3060dff0d12a32819e08da00e61810676cc9185fdd750e5ef82b1a9798c7d76d63de3b6225d6c9a479d6c21a7c8bf93", + "8f1093d1e553cdead3c70ce55b6d664e5d1912cc9edfdd37bf1dad11ca396a0a8bb062092d391ebf8790ea5722413f68", + "990824e00b48a68c3d9a308e8c52a57b1bc84d1cf5d3c0f8c6fb6b1230e4e5b8eb752fb374da0b1ef687040024868140", + "b86d1c6ab8ce22bc53f625d1ce9796657f18060fcb1893ce8931156ef992fe56856199f8fa6c998e5d855a354a26b0dd", + "b4cdd98c5c1e64cb324e0c57954f719d5c5f9e8d991fd8e159b31c8d079c76a67321a30311975c706578d3a0ddc313b7", + "8311492d43ec9182a5fc44a75419b09547e311251fe38b6864dc1e706e29446cb3ea4d501634eb13327245fd8a574f77", + "ac00b493f92d17837a28d1f5b07991ca5ab9f370ae40d4f9b9f2711749ca200110ce6517dc28400d4ea25dddc146cacc", + "965a6c62451d4be6cb175dec39727dc665762673ee42bf0ac13a37a74784fbd61e84e0915277a6f59863b2bb4f5f6005" + ] +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/h2s.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/h2s.json new file mode 100644 index 0000000..c21054f --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/h2s.json @@ -0,0 +1,6 @@ +{ + "caseName": "Hash to scalar output", + "message": "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "dst": "4242535f424c53313233383147315f584f463a5348414b452d3235365f535357555f524f5f4832475f484d32535f4832535f", + "scalar": "0500031f786fde5326aa9370dd7ffe9535ec7a52cf2b8f432cad5d9acfb73cd3" +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/keypair.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/keypair.json new file mode 100644 index 0000000..5016524 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/keypair.json @@ -0,0 +1,10 @@ +{ + "caseName": "key pair fixture", + "keyMaterial": "746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579", + "keyInfo": "746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e", + "keyDst": "4242535f424c53313233383147315f584f463a5348414b452d3235365f535357555f524f5f4832475f484d32535f4b455947454e5f4453545f", + "keyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/mockedRng.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/mockedRng.json new file mode 100644 index 0000000..ebb2305 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/mockedRng.json @@ -0,0 +1,18 @@ +{ + "caseName": "mocked random scalars", + "seed": "332e313431353932363533353839373933323338343632363433333833323739", + "dst": "4242535f424c53313233383147315f584f463a5348414b452d3235365f535357555f524f5f4832475f484d32535f4d4f434b5f52414e444f4d5f5343414c4152535f4453545f", + "count": 10, + "mockedScalars": [ + "1004262112c3eaa95941b2b0d1311c09c845db0099a50e67eda628ad26b43083", + "6da7f145a94c1fa7f116b2482d59e4d466fe49c955ae8726e79453065156a9a4", + "05017919b3607e78c51e8ec34329955d49c8c90e4488079c43e74824e98f1306", + "4d451dad519b6a226bba79e11b44c441f1a74800eecfec6a2e2d79ea65b9d32d", + "5e7e4894e6dbe68023bc92ef15c410b01f3828109fc72b3b5ab159fc427b3f51", + "646e3014f49accb375253d268eb6c7f3289a1510f1e9452b612dd73a06ec5dd4", + "363ecc4c1f9d6d9144374de8f1f7991405e3345a3ec49dd485a39982753c11a4", + "12e592fe28d91d7b92a198c29afaa9d5329a4dcfdaf8b08557807412faeb4ac6", + "513325acdcdec7ea572360587b350a8b095ca19bdd8258c5c69d375e8706141a", + "6474fceba35e7e17365dde1a0284170180e446ae96c82943290d7baa3a6ed429" + ] +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof001.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof001.json new file mode 100644 index 0000000..6636632 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof001.json @@ -0,0 +1,34 @@ +{ + "caseName": "valid single message signature, single-message revealed proof", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "b9a622a4b404e6ca4c85c15739d2124a1deb16df750be202e2430e169bc27fb71c44d98e6d40792033e1c452145ada95030832c5dc778334f2f1b528eced21b0b97a12025a283d78b7136bb9825d04ef", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02" + ], + "disclosedIndexes": [ + 0 + ], + "proof": "89e4ab0c160880e0c2f12a754b9c051ed7f5fccfee3d5cbbb62e1239709196c737fff4303054660f8fcd08267a5de668a2e395ebe8866bdcb0dff9786d7014fa5e3c8cf7b41f8d7510e27d307f18032f6b788e200b9d6509f40ce1d2f962ceedb023d58ee44d660434e6ba60ed0da1a5d2cde031b483684cd7c5b13295a82f57e209b584e8fe894bcc964117bf3521b43d8e2eb59ce31f34d68b39f05bb2c625e4de5e61e95ff38bfd62ab07105d016414b45b01625c69965ad3c8a933e7b25d93daeb777302b966079827a99178240e6c3f13b7db2fb1f14790940e239d775ab32f539bdf9f9b582b250b05882996832652f7f5d3b6e04744c73ada1702d6791940ccbd75e719537f7ace6ee817298d", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "1308e6f945f663b96de1c76461cf7d7f88b92eb99a9034685150db443d733881", + "r2": "25f81cb69a8fac6fb55d44a084557258575d1003be2bd94f1922dad2c3e447fd", + "e_tilde": "5e8041a7ab02976ee50226c4b062b47d38829bbf42ee7eb899b29720377a584c", + "r1_tilde": "3bbf1d5dc2904dbb7b2ba75c5dce8a5ad2d56a359c13ff0fa5fcb1339cd2fe58", + "r3_tilde": "016b1460eee7707c524a86a4aedeb826ce9597b42906dccaa96c6b49a8ea7da2", + "m_tilde_scalars": [] + }, + "A_bar": "89e4ab0c160880e0c2f12a754b9c051ed7f5fccfee3d5cbbb62e1239709196c737fff4303054660f8fcd08267a5de668", + "B_bar": "a2e395ebe8866bdcb0dff9786d7014fa5e3c8cf7b41f8d7510e27d307f18032f6b788e200b9d6509f40ce1d2f962ceed", + "D": "b023d58ee44d660434e6ba60ed0da1a5d2cde031b483684cd7c5b13295a82f57e209b584e8fe894bcc964117bf3521b4", + "T1": "91a10e73cf4090812e8ea25f31aaa61be53fcb42ce86e9f0e5df6f6dac4c3eee62ac846b0b83a5cfcbe78315175a4961", + "T2": "988f3d473186634e41478dc4527cf240e64de23a763037454d39a876862ebc617738ba6c458142e3746b01eab58ca8d7", + "domain": "2f18dd269c11c512256a9d1d57e61a7d2de6ebcf41cac3053f37afedc4e650a9", + "challenge": "2652f7f5d3b6e04744c73ada1702d6791940ccbd75e719537f7ace6ee817298d" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof002.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof002.json new file mode 100644 index 0000000..30270cd --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof002.json @@ -0,0 +1,52 @@ +{ + "caseName": "valid multi-message signature, all messages revealed proof", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "proof": "91b0f598268c57b67bc9e55327c3c2b9b1654be89a0cf963ab392fa9e1637c565241d71fd6d7bbd7dfe243de85a9bac8b7461575c1e13b5055fed0b51fd0ec1433096607755b2f2f9ba6dc614dfa456916ca0d7fc6482b39c679cfb747a50ea1b3dd7ed57aaadc348361e2501a17317352e555a333e014e8e7d71eef808ae4f8fbdf45cd19fde45038bb310d5135f5205fc550b077e381fb3a3543dca31a0d8bba97bc0b660a5aa239eb74921e184aa3035fa01eaba32f52029319ec3df4fa4a4f716edb31a6ce19a19dbb971380099345070bd0fdeecf7c4774a33e0a116e069d5e215992fb637984802066dee6919146ae50b70ea52332dfe57f6e05c66e99f1764d8b890d121d65bfcc2984886ee0", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "1308e6f945f663b96de1c76461cf7d7f88b92eb99a9034685150db443d733881", + "r2": "25f81cb69a8fac6fb55d44a084557258575d1003be2bd94f1922dad2c3e447fd", + "e_tilde": "5e8041a7ab02976ee50226c4b062b47d38829bbf42ee7eb899b29720377a584c", + "r1_tilde": "3bbf1d5dc2904dbb7b2ba75c5dce8a5ad2d56a359c13ff0fa5fcb1339cd2fe58", + "r3_tilde": "016b1460eee7707c524a86a4aedeb826ce9597b42906dccaa96c6b49a8ea7da2", + "m_tilde_scalars": [] + }, + "A_bar": "91b0f598268c57b67bc9e55327c3c2b9b1654be89a0cf963ab392fa9e1637c565241d71fd6d7bbd7dfe243de85a9bac8", + "B_bar": "b7461575c1e13b5055fed0b51fd0ec1433096607755b2f2f9ba6dc614dfa456916ca0d7fc6482b39c679cfb747a50ea1", + "D": "b3dd7ed57aaadc348361e2501a17317352e555a333e014e8e7d71eef808ae4f8fbdf45cd19fde45038bb310d5135f520", + "T1": "8890adfc78da24768d59dbfdb3f380e2793e9018b20c23e9ba05baa60f1b21456bc047a5d27049dab5dc6a94696ce711", + "T2": "a49f953636d3651a3ae6fe45a99a2e4fec079eef3be8b8a6a4ba70885d7e028642f7224e9f451529915c88a7edc59fbe", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "46ae50b70ea52332dfe57f6e05c66e99f1764d8b890d121d65bfcc2984886ee0" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof003.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof003.json new file mode 100644 index 0000000..99384c3 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof003.json @@ -0,0 +1,53 @@ +{ + "caseName": "valid multi-message signature, multiple messages revealed proof", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof004.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof004.json new file mode 100644 index 0000000..d664696 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof004.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (different presentation header)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "011594ba7f95b3b470ea4102dd5899de3a042e5104d3ea01d15e6780d831d2be", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "different presentation header" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof005.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof005.json new file mode 100644 index 0000000..43ddedb --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof005.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (wrong public key)", + "signerPublicKey": "b24c723803f84e210f7a95f6265c5cbfa4ecc51488bf7acf24b921807801c0798b725b9a2dcfa29953efcdfef03328720196c78b2e613727fd6e085302a0cc2d8d7e1d820cf1d36b20e79eee78c13a1a5da51a298f1aef86f07bc33388f089d8", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "wrong public key" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof006.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof006.json new file mode 100644 index 0000000..68c03b5 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof006.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (modified messages)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "b3e4043a1e148028b85dfbf39d1e44d7bfc8277fd310aeda5deb4a6eb7b3d1293c86788288e86b1819caa0b11a4f2c6330abda72b1bcb082d660dc78b5271f6a047bb96c250f2ca877cc72464d363c3bd0bfc4d4b4de7233419234e94f16ec24359e13b6", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "modified messages" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof007.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof007.json new file mode 100644 index 0000000..60ed2e0 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof007.json @@ -0,0 +1,56 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (extra message un-revealed in proof)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6, + 9 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "extra message un-revealed in proof" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof008.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof008.json new file mode 100644 index 0000000..af4916a --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof008.json @@ -0,0 +1,56 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (extra message invalid message un-revealed in proof)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + "96012096" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6, + 9 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "extra message invalid message un-revealed in proof" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof009.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof009.json new file mode 100644 index 0000000..3e33a95 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof009.json @@ -0,0 +1,52 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (missing message revealed in proof)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "missing message revealed in proof" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof010.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof010.json new file mode 100644 index 0000000..96499ab --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof010.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (re-ordered messages)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 4, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "re-ordered messages" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof011.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof011.json new file mode 100644 index 0000000..dc3623e --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof011.json @@ -0,0 +1,56 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (extra valid message, modified total message count)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6, + 9 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "extra valid message, modified total message count" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof012.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof012.json new file mode 100644 index 0000000..996f597 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof012.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (truncated proof, one less undisclosed message)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d1", + "result": { + "valid": false, + "reason": "truncated proof, one less undisclosed message" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof013.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof013.json new file mode 100644 index 0000000..4e310e6 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof013.json @@ -0,0 +1,54 @@ +{ + "caseName": "invalid multi-message signature, all messages revealed proof (different header)", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "ffeeddccbbaa00998877665544332211", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af356dd39bf8bcbfd41bf95d913f4c9b2979e1ed2ca10ac7e881bb6a271722549681e398d29e9ba4eac8848b168eddd5e4acec7df4103e2ed165e6e32edc80f0a3b28c36fb39ca19b4b8acee570deadba2da9ec20d1f236b571e0d4c2ea3b826fe924175ed4dfffbf18a9cfa98546c241efb9164c444d970e8c89849bc8601e96cf228fdefe38ab3b7e289cac859e68d9cbb0e648faf692b27df5ff6539c30da17e5444a65143de02ca64cee7b0823be65865cdc310be038ec6b594b99280072ae067bad1117b0ff3201a5506a8533b925c7ffae9cdb64558857db0ac5f5e0f18e750ae77ec9cf35263474fef3f78138c7a1ef5cfbc878975458239824fad3ce05326ba3969b1f5451bd82bd1f8075f3d32ece2d61d89a064ab4804c3c892d651d11bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558", + "result": { + "valid": false, + "reason": "different header" + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "1bc325464a71cd7aacc2d956a811aaff13ea4c35cef7842b656e8ba4758e7558" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof014.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof014.json new file mode 100644 index 0000000..8a636f9 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof014.json @@ -0,0 +1,53 @@ +{ + "caseName": "valid multi-message signature, multiple messages revealed proof, no header", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "88beeb970f803160d3058eacde505207c576a8c9e4e5dc7c5249cbcf2a046c15f8df047031eef3436e04b779d92a9cdb1fe4c6cc035ba1634f1740f9dd49816d3ca745ecbe39f655ea61fb700137fded", + "header": "", + "presentationHeader": "bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "8ac336eea1d278656372d9914483c3d3b3069dfa4a7862293ac021dfeeebca93cadd7eb2b818f7b89719cdeffa5aa85989a7d691be11b1929a2bf089bfe9f2adc2c06788edc30585546efb74877f34ad91f0d6923b4ed7a53c49051dda8d056a95644ee738810772d90c1033f1dfe45c0b1b453d131170aafa8a99f812f3b90a5d1d9e6bd05a4dee6a50dd277ffc646f2429372f3ad9d5946ffeb53f24d41ffcc83c32cbb68afc9b6e0b64eebd24c69c6a7bd3bca8a6394ed8ae315abd555a6996f34d9da7680447947b3f35f54c38b562e990ee4d17a21569af4fc02f2991e6db78cc32d3ef9f6069fc5c2d47c8d8ff116dfb8a59641641961b854427f67649df14ab6e63f2d0d2a0cba2b2e1e835d20cd45e41f274532e9d50f31a690e5fef1c1456b65c668b80d8ec17b09bd5fb3b2c4edd6d6f5f790a5d6da22eb9a1aa2196d1a607f3c753813ba2bc6ece15d35263218fc7667c5f0fabfffe74745a8000e0415c8dafd5654ce6850ac2c6485d02433fdaebd9993f8b86a2eebb3beb10b4cc7735330384a3f4dfd4d5b21998ad0227b37e736cf9c144a0386f28cccf27a01e50aab45dda8275eb877728e77d2055309dba8c6604e7cff0d2c46ce6026b8e232c192955f909da6e47c2130c7e3f4f", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "8ac336eea1d278656372d9914483c3d3b3069dfa4a7862293ac021dfeeebca93cadd7eb2b818f7b89719cdeffa5aa859", + "B_bar": "89a7d691be11b1929a2bf089bfe9f2adc2c06788edc30585546efb74877f34ad91f0d6923b4ed7a53c49051dda8d056a", + "D": "95644ee738810772d90c1033f1dfe45c0b1b453d131170aafa8a99f812f3b90a5d1d9e6bd05a4dee6a50dd277ffc646f", + "T1": "a5405cc2c5965dda18714ab35f4d4a7ae4024f388fa7a5ba71202d4455b50b316ec37b360659e3012234562fa8989980", + "T2": "9827a40454cdc90a70e9c927f097019dbdd84768babb10ebcb460c2d918e1ce1c0512bf2cc49ed7ec476dfcde7a6a10c", + "domain": "333d8686761cff65a3a2ef20bfa217d37bdf19105e87c210e9ce64ea1210a157", + "challenge": "309dba8c6604e7cff0d2c46ce6026b8e232c192955f909da6e47c2130c7e3f4f" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof015.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof015.json new file mode 100644 index 0000000..73e5a66 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/proof/proof015.json @@ -0,0 +1,53 @@ +{ + "caseName": "valid multi-message signature, multiple messages revealed proof, no presentation header", + "signerPublicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5", + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "header": "11223344556677889900aabbccddeeff", + "presentationHeader": "", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "disclosedIndexes": [ + 0, + 2, + 4, + 6 + ], + "proof": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af33fda9e14ba4cc0fcad8015bce3fecc4704799bef9924ab19688fc04f760c4da35017072a3e295788eff1b0dc2311bb199c186f86ea0540379d5a2ac8b7bd02d22487f2acc0e299115e16097b970badea802752a6fcb56cfbbcc2569916a8d3fe6d2d0fb1ae801cfc5ce056699adf23e3cd16b1fdf197deac099ab093da049a5b4451d038c71b7cc69e8390967594f6777a855c7f5d301f0f0573211ac85e2e165ea196f78c33f54092645a51341b777f0f5342301991f3da276c04b0224f7308090ae0b290d428a0570a71605a27977e7daf01d42dfbdcec252686c3060a73d81f6e151e23e3df2473b322da389f15a55cb2cd8a2bf29ef0d83d4876117735465fae956d8df56ec9eb0e4748ad3ef5587797368c51a0ccd67eb6da38602a1c2d4fd411214efc6932334ba0bcbf562626e7c0e1ae0db912c28d99f194fa3cd3a2", + "result": { + "valid": true + }, + "trace": { + "random_scalars": { + "r1": "5ee9426ae206e3a127eb53c79044bc9ed1b71354f8354b01bf410a02220be7d0", + "r2": "280d4fcc38376193ffc777b68459ed7ba897e2857f938581acf95ae5a68988f3", + "e_tilde": "39966b00042fc43906297d692ebb41de08e36aada8d9504d4e0ae02ad59e9230", + "r1_tilde": "61f5c273999b0b50be8f84d2380eb9220fc5a88afe144efc4007545f0ab9c089", + "r3_tilde": "63af117e0c8b7d2f1f3e375fcf5d9430e136ff0f7e879423e49dadc401a50089", + "m_tilde_scalars": [ + "020b83ca2ab319cba0744d6d58da75ac3dfb6ba682bfce2587c5a6d86a4e4e7b", + "5bf565343611c08f83e4420e8b1577ace8cc4df5d5303aeb3c4e425f1080f836", + "049d77949af1192534da28975f76d4f211315dce1e36f93ffcf2a555de516b28", + "407e5a952f145de7da53533de8366bbd2e0c854721a204f03906dc82fde10f48", + "1c925d9052849edddcf04d5f1f0d4ff183a66b66eb820f59b675aee121cfc63c", + "07d7c41b02158a9c5eac212ed6d7c2cddeb8e38baea6e93e1a00b2e83e2a0995" + ] + }, + "A_bar": "b1f8bf99a11c39f04e2a032183c1ead12956ad322dd06799c50f20fb8cf6b0ac279210ef5a2920a7be3ec2aa0911ace7", + "B_bar": "b96811a98f3c1cceba4a2147ae763b3ba036f47bc21c39179f2b395e0ab1ac49017ea5b27848547bedd27be481c1dfc0", + "D": "b73372346feb94ab16189d4c525652b8d3361bab43463700720ecfb0ee75e595ea1b13330615011050a0dfcffdb21af3", + "T1": "8b497dd4dcdcf7eb58c9b43e57e06bcea3468a223ae2fc015d7a86506a952d68055e73f5a5847e58f133ea154256d0da", + "T2": "8655584d3da1313f881f48c239384a5623d2d292f08dae7ac1d8129c19a02a89b82fa45de3f6c2c439510fce5919656f", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b", + "challenge": "4fd411214efc6932334ba0bcbf562626e7c0e1ae0db912c28d99f194fa3cd3a2" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature001.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature001.json new file mode 100644 index 0000000..77ace19 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature001.json @@ -0,0 +1,19 @@ +{ + "caseName": "valid single message signature", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02" + ], + "signature": "b9a622a4b404e6ca4c85c15739d2124a1deb16df750be202e2430e169bc27fb71c44d98e6d40792033e1c452145ada95030832c5dc778334f2f1b528eced21b0b97a12025a283d78b7136bb9825d04ef", + "result": { + "valid": true + }, + "trace": { + "B": "8bbc8c123d3f128f206dd0d2dae490e82af08b84e8d70af3dc291d32a6e98f635beefcc4533b2599804a164aabe68d7c", + "domain": "2f18dd269c11c512256a9d1d57e61a7d2de6ebcf41cac3053f37afedc4e650a9" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature002.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature002.json new file mode 100644 index 0000000..948574f --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature002.json @@ -0,0 +1,20 @@ +{ + "caseName": "invalid single message signature (modified message)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "" + ], + "signature": "b9a622a4b404e6ca4c85c15739d2124a1deb16df750be202e2430e169bc27fb71c44d98e6d40792033e1c452145ada95030832c5dc778334f2f1b528eced21b0b97a12025a283d78b7136bb9825d04ef", + "result": { + "valid": false, + "reason": "modified message" + }, + "trace": { + "B": "8bbc8c123d3f128f206dd0d2dae490e82af08b84e8d70af3dc291d32a6e98f635beefcc4533b2599804a164aabe68d7c", + "domain": "2f18dd269c11c512256a9d1d57e61a7d2de6ebcf41cac3053f37afedc4e650a9" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature003.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature003.json new file mode 100644 index 0000000..1bb7d60 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature003.json @@ -0,0 +1,21 @@ +{ + "caseName": "invalid single message signature (extra unsigned message)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80" + ], + "signature": "b9a622a4b404e6ca4c85c15739d2124a1deb16df750be202e2430e169bc27fb71c44d98e6d40792033e1c452145ada95030832c5dc778334f2f1b528eced21b0b97a12025a283d78b7136bb9825d04ef", + "result": { + "valid": false, + "reason": "extra unsigned message" + }, + "trace": { + "B": "8bbc8c123d3f128f206dd0d2dae490e82af08b84e8d70af3dc291d32a6e98f635beefcc4533b2599804a164aabe68d7c", + "domain": "2f18dd269c11c512256a9d1d57e61a7d2de6ebcf41cac3053f37afedc4e650a9" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature004.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature004.json new file mode 100644 index 0000000..e3b3be5 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature004.json @@ -0,0 +1,28 @@ +{ + "caseName": "valid multi-message signature", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "result": { + "valid": true + }, + "trace": { + "B": "ae8d4ebe248b9ad9c933d5661bfb46c56721fba2a1182ddda7e8fb443bda3c0a571ad018ad31d0b6d1f4e8b985e6c58d", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature005.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature005.json new file mode 100644 index 0000000..f29df89 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature005.json @@ -0,0 +1,21 @@ +{ + "caseName": "invalid multi-message signature (missing messages)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80" + ], + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "result": { + "valid": false, + "reason": "missing messages" + }, + "trace": { + "B": "ae8d4ebe248b9ad9c933d5661bfb46c56721fba2a1182ddda7e8fb443bda3c0a571ad018ad31d0b6d1f4e8b985e6c58d", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature006.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature006.json new file mode 100644 index 0000000..02e02db --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature006.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (re-ordered messages)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "", + "96012096", + "ac55fb33a75909ed", + "d183ddc6e2665aa4e2f088af", + "515ae153e22aae04ad16f759e07237b4", + "496694774c5604ab1b2544eababcf0f53278ff50", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02" + ], + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "result": { + "valid": false, + "reason": "re-ordered messages" + }, + "trace": { + "B": "ae8d4ebe248b9ad9c933d5661bfb46c56721fba2a1182ddda7e8fb443bda3c0a571ad018ad31d0b6d1f4e8b985e6c58d", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature007.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature007.json new file mode 100644 index 0000000..dbb55c3 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature007.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (wrong public key)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "b24c723803f84e210f7a95f6265c5cbfa4ecc51488bf7acf24b921807801c0798b725b9a2dcfa29953efcdfef03328720196c78b2e613727fd6e085302a0cc2d8d7e1d820cf1d36b20e79eee78c13a1a5da51a298f1aef86f07bc33388f089d8" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "result": { + "valid": false, + "reason": "wrong public key" + }, + "trace": { + "B": "ae8d4ebe248b9ad9c933d5661bfb46c56721fba2a1182ddda7e8fb443bda3c0a571ad018ad31d0b6d1f4e8b985e6c58d", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature008.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature008.json new file mode 100644 index 0000000..b409db4 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature008.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (different header)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "ffeeddccbbaa00998877665544332211", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "result": { + "valid": false, + "reason": "different header" + }, + "trace": { + "B": "ae8d4ebe248b9ad9c933d5661bfb46c56721fba2a1182ddda7e8fb443bda3c0a571ad018ad31d0b6d1f4e8b985e6c58d", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature009.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature009.json new file mode 100644 index 0000000..fc43ec5 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature009.json @@ -0,0 +1,29 @@ +{ + "caseName": "invalid multi-message signature (re-ordered(randomly shuffled) messages)", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "11223344556677889900aabbccddeeff", + "messages": [ + "", + "96012096", + "496694774c5604ab1b2544eababcf0f53278ff50", + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "ac55fb33a75909ed", + "d183ddc6e2665aa4e2f088af", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "515ae153e22aae04ad16f759e07237b4" + ], + "signature": "956a3427b1b8e3642e60e6a7990b67626811adeec7a0a6cb4f770cdd7c20cf08faabb913ac94d18e1e92832e924cb6e202912b624261fc6c59b0fea801547f67fb7d3253e1e2acbcf90ef59a6911931e", + "result": { + "valid": false, + "reason": "re-ordered(randomly shuffled) messages" + }, + "trace": { + "B": "ae8d4ebe248b9ad9c933d5661bfb46c56721fba2a1182ddda7e8fb443bda3c0a571ad018ad31d0b6d1f4e8b985e6c58d", + "domain": "6f7ee8de30835599bb540d2cb4dd02fd0c6cf8246f14c9ee9a8463f7fd400f7b" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature010.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature010.json new file mode 100644 index 0000000..c099757 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/bls12-381-shake-256/signature/signature010.json @@ -0,0 +1,28 @@ +{ + "caseName": "valid multi-message signature, no header", + "signerKeyPair": { + "secretKey": "2eee0f60a8a3a8bec0ee942bfd46cbdae9a0738ee68f5a64e7238311cf09a079", + "publicKey": "92d37d1d6cd38fea3a873953333eab23a4c0377e3e049974eb62bd45949cdeb18fb0490edcd4429adff56e65cbce42cf188b31bddbd619e419b99c2c41b38179eb001963bc3decaae0d9f702c7a8c004f207f46c734a5eae2e8e82833f3e7ea5" + }, + "header": "", + "messages": [ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" + ], + "signature": "88beeb970f803160d3058eacde505207c576a8c9e4e5dc7c5249cbcf2a046c15f8df047031eef3436e04b779d92a9cdb1fe4c6cc035ba1634f1740f9dd49816d3ca745ecbe39f655ea61fb700137fded", + "result": { + "valid": true + }, + "trace": { + "B": "8607ebc413b397c1e27ce591d1daa39f73da329018bda0f90bf996355cc28c3cdba19feeb81e35be9e1503a018e4086e", + "domain": "333d8686761cff65a3a2ef20bfa217d37bdf19105e87c210e9ce64ea1210a157" + } +} \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/messages.json b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/messages.json new file mode 100644 index 0000000..b674216 --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/official/messages.json @@ -0,0 +1,12 @@ +[ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "" +] \ No newline at end of file diff --git a/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/sha256.properties b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/sha256.properties new file mode 100644 index 0000000..540435c --- /dev/null +++ b/zeroj-bbs/src/test/resources/cfrg-bbs/draft10/sha256.properties @@ -0,0 +1,24 @@ +# CFRG BBS draft-irtf-cfrg-bbs-signatures-10, Section 8.4 BLS12-381 SHA-256 vectors. +ciphersuite_id=BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_ +key_material=746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579 +key_info=746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e +sk=60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc +pk=a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c +header=11223344556677889900aabbccddeeff +presentation_header=bed231d880675ed101ead304512e043ade9958dd0241ea70b4b3957fba941501 +messages=9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02,c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80,7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73,77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c,496694774c5604ab1b2544eababcf0f53278ff50,515ae153e22aae04ad16f759e07237b4,d183ddc6e2665aa4e2f088af,ac55fb33a75909ed,96012096, +message_scalars=1cb5bb86114b34dc438a911617655a1db595abafac92f47c5001799cf624b430,154249d503c093ac2df516d4bb88b510d54fd97e8d7121aede420a25d9521952,0c7c4c85cdab32e6fdb0de267b16fa3212733d4e3a3f0d0f751657578b26fe22,4a196deafee5c23f630156ae13be3e46e53b7e39094d22877b8cba7f14640888,34c5ea4f2ba49117015a02c711bb173c11b06b3f1571b88a2952b93d0ed4cf7e,4045b39b83055cd57a4d0203e1660800fabe434004dbdc8730c21ce3f0048b08,064621da4377b6b1d05ecc37cf3b9dfc94b9498d7013dc5c4a82bf3bb1750743,34ac9196ace0a37e147e32319ea9b3d8cc7d21870d3c3ba071246859cca49b02,57eb93f417c43200e9784fa5ea5a59168d3dbc38df707a13bb597c871b2a5f74,08e3afeb2b4f2b5f907924ef42856616e6f2d5f1fb373736db1cca32707a7d16 +generators=a9ec65b70a7fbe40c874c9eb041c2cb0a7af36ccec1bea48fa2ba4c2eb67ef7f9ecb17ed27d38d27cdeddff44c8137be,98cd5313283aaf5db1b3ba8611fe6070d19e605de4078c38df36019fbaad0bd28dd090fd24ed27f7f4d22d5ff5dea7d4,a31fbe20c5c135bcaa8d9fc4e4ac665cc6db0226f35e737507e803044093f37697a9d452490a970eea6f9ad6c3dcaa3a,b479263445f4d2108965a9086f9d1fdc8cde77d14a91c856769521ad3344754cc5ce90d9bc4c696dffbc9ef1d6ad1b62,ac0401766d2128d4791d922557c7b4d1ae9a9b508ce266575244a8d6f32110d7b0b7557b77604869633bb49afbe20035,b95d2898370ebc542857746a316ce32fa5151c31f9b57915e308ee9d1de7db69127d919e984ea0747f5223821b596335,8f19359ae6ee508157492c06765b7df09e2e5ad591115742f2de9c08572bb2845cbf03fd7e23b7f031ed9c7564e52f39,abc914abe2926324b2c848e8a411a2b6df18cbe7758db8644145fefb0bf0a2d558a8c9946bd35e00c69d167aadf304c1,80755b3eb0dd4249cbefd20f177cee88e0761c066b71794825c9997b551f24051c352567ba6c01e57ac75dff763eaa17,82701eb98070728e1769525e73abff1783cedc364adb20c05c897a62f2ab2927f86f118dcb7819a7b218d8f3fee4bd7f,a1f229540474f4d6f1134761b92b788128c7ac8dc9b0c52d59493132679673032ac7db3fb3d79b46b13c1c41ee495bca +single_message_b=92d264aed02bf23de022ebe778c4f929fddf829f504e451d011ed89a313b8167ac947332e1648157ceffc6e6e41ab255 +single_message_domain=25d57fab92a8274c68fde5c3f16d4b275e4a156f211ae34b3ab32fbaf506ed5c +single_message_signature=84773160b824e194073a57493dac1a20b667af70cd2352d8af241c77658da5253aa8458317cca0eae615690d55b1f27164657dcafee1d5c1973947aa70e2cfbb4c892340be5969920d0916067b4565a0 +multi_message_b=84f48376f7df6af40bc329cf484cdbfd0b19d0b326fccab4e9d8f00d1dbcf48139d498b19667f203cf8a1d1f8340c522 +multi_message_domain=6272832582a0ac96e6fe53e879422f24c51680b25fbf17bad22a35ea93ce5b47 +multi_message_signature=8339b285a4acd89dec7777c09543a43e3cc60684b0a6f8ab335da4825c96e1463e28f8c5f4fd0641d19cec5920d3a8ff4bedb6c9691454597bbd298288abed3632078557b2ace7d44caed846e1a0a1e8 +mock_seed=332e313431353932363533353839373933323338343632363433333833323739 +mock_dst=4242535f424c53313233383147315f584d443a5348412d3235365f535357555f524f5f4832475f484d32535f4d4f434b5f52414e444f4d5f5343414c4152535f4453545f +mock_scalars=04f8e2518993c4383957ad14eb13a023c4ad0c67d01ec86eeb902e732ed6df3f,5d87c1ba64c320ad601d227a1b74188a41a100325cecf00223729863966392b1,0444607600ac70482e9c983b4b063214080b9e808300aa4cc02a91b3a92858fe,548cd11eae4318e88cda10b4cd31ae29d41c3a0b057196ee9cf3a69d471e4e94,2264b06a08638b69b4627756a62f08e0dc4d8240c1b974c9c7db779a769892f4,4d99352986a9f8978b93485d21525244b21b396cf61f1d71f7c48e3fbc970a42,5ed8be91662386243a6771fbdd2c627de31a44220e8d6f745bad5d99821a4880,62ff1734b939ddd87beeb37a7bbcafa0a274cbc1b07384198f0e88398272208d,05c2a0af016df58e844db8944082dcaf434de1b1e2e7136ec8a99b939b716223,485e2adab17b76f5334c95bf36c03ccf91cef77dcfcdc6b8a69e2090b3156663 +single_proof_random_scalars=60ca409f6b0563f687fc471c63d2819f446f39c23bb540925d9d4254ac58f337,2ceff4982de0c913090f75f081df5ec594c310bb48c17cfdaab5332a682ef811,6101c4404895f3dff87ab39c34cb995af07e7139e6b3847180ffdd1bc8c313cd,0dfcffd97a6ecdebef3c9c114b99d7a030c998d938905f357df62822dee072e8,639e3417007d38e5d34ba8c511e836768ddc2669fdd3faff5c14ad27ac2b2da1 +single_proof=94916292a7a6bade28456c601d3af33fcf39278d6594b467e128a3f83686a104ef2b2fcf72df0215eeaf69262ffe8194a19fab31a82ddbe06908985abc4c9825788b8a1610942d12b7f5debbea8985296361206dbace7af0cc834c80f33e0aadaeea5597befbb651827b5eed5a66f1a959bb46cfd5ca1a817a14475960f69b32c54db7587b5ee3ab665fbd37b506830a49f21d592f5e634f47cee05a025a2f8f94e73a6c15f02301d1178a92873b6e8634bafe4983c3e15a663d64080678dbf29417519b78af042be2b3e1c4d08b8d520ffab008cbaaca5671a15b22c239b38e940cfeaa5e72104576a9ec4a6fad78c532381aeaa6fb56409cef56ee5c140d455feeb04426193c57086c9b6d397d9418 +all_disclosed_proof=b1f468aec2001c4f54cb56f707c6222a43e5803a25b2253e67b2210ab2ef9eab52db2d4b379935c4823281eaf767fd37b08ce80dc65de8f9769d27099ae649ad4c9b4bd2cc23edcba52073a298087d2495e6d57aaae051ef741adf1cbce65c64a73c8c97264177a76c4a03341956d2ae45ed3438ce598d5cda4f1bf9507fecef47855480b7b30b5e4052c92a4360110c67327365763f5aa9fb85ddcbc2975449b8c03db1216ca66b310f07d0ccf12ab460cdc6003b677fed36d0a23d0818a9d4d098d44f749e91008cf50e8567ef936704c8277b7710f41ab7e6e16408ab520edc290f9801349aee7b7b4e318e6a76e028e1dea911e2e7baec6a6a174da1a22362717fbae1cd961d7bf4adce1d31c2ab +some_disclosed_proof=a2ed608e8e12ed21abc2bf154e462d744a367c7f1f969bdbf784a2a134c7db2d340394223a5397a3011b1c340ebc415199462ba6f31106d8a6da8b513b37a47afe93c9b3474d0d7a354b2edc1b88818b063332df774c141f7a07c48fe50d452f897739228c88afc797916dca01e8f03bd9c5375c7a7c59996e514bb952a436afd24457658acbaba5ddac2e693ac481356918cd38025d86b28650e909defe9604a7259f44386b861608be742af7775a2e71a6070e5836f5f54dc43c60096834a5b6da295bf8f081f72b7cdf7f3b4347fb3ff19edaa9e74055c8ba46dbcb7594fb2b06633bb5324192eb9be91be0d33e453b4d3127459de59a5e2193c900816f049a02cb9127dac894418105fa1641d5a206ec9c42177af9316f433417441478276ca0303da8f941bf2e0222a43251cf5c2bf6eac1961890aa740534e519c1767e1223392a3a286b0f4d91f7f25217a7862b8fcc1810cdcfddde2a01c80fcc90b632585fec12dc4ae8fea1918e9ddeb9414623a457e88f53f545841f9d5dcb1f8e160d1560770aa79d65e2eca8edeaecb73fb7e995608b820c4a64de6313a370ba05dc25ed7c1d185192084963652f2870341bdaa4b1a37f8c06348f38a4f80c5a2650a21d59f09e8305dcd3fc3ac30e2a diff --git a/zeroj-bls12381-wasm/README.md b/zeroj-bls12381-wasm/README.md new file mode 100644 index 0000000..f377691 --- /dev/null +++ b/zeroj-bls12381-wasm/README.md @@ -0,0 +1,25 @@ +# zeroj-bls12381-wasm + +Optional BLS12-381 provider backed by the Rust `bls12_381` crate compiled to +`wasm32-unknown-unknown` and loaded through Chicory. + +## Reproducible build inputs + +- Rust is pinned by `rust/rust-toolchain.toml`. +- The WASM target is declared in the pinned toolchain file. +- Rust dependencies are locked by `rust/Cargo.lock`. +- The generated `zeroj_bls12381.wasm` artifact is not checked in. Gradle builds + it from source and packages it as `zeroj-bls12381/zeroj_bls12381.wasm`. + +Build and test the module with: + +```bash +./gradlew :zeroj-bls12381-wasm:test +``` + +Build only the Rust artifact with: + +```bash +cd zeroj-bls12381-wasm/rust +cargo build --release --target wasm32-unknown-unknown +``` diff --git a/zeroj-bls12381-wasm/build.gradle b/zeroj-bls12381-wasm/build.gradle new file mode 100644 index 0000000..b3e6b5d --- /dev/null +++ b/zeroj-bls12381-wasm/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'java-library' +} + +description = 'ZeroJ BLS12-381 WASM provider — zkcrypto Rust WASM via Chicory' + +def bls12381RustDir = project.file('rust') +def bls12381WasmTarget = bls12381RustDir.toPath() + .resolve('target/wasm32-unknown-unknown/release/zeroj_bls12381.wasm') + .toFile() +def generatedWasmDir = layout.buildDirectory.dir('generated-resources/wasm') + +dependencies { + api project(':zeroj-bls12381') + + implementation 'com.dylibso.chicory:runtime:1.7.5' + implementation 'com.dylibso.chicory:wasm:1.7.5' +} + +tasks.register('buildBls12381Wasm', Exec) { + description = 'Builds the zkcrypto BLS12-381 Rust crate as wasm32-unknown-unknown' + group = 'build' + workingDir = bls12381RustDir + commandLine 'cargo', 'build', '--release', '--target', 'wasm32-unknown-unknown' + inputs.files(fileTree(bls12381RustDir) { + include 'Cargo.toml', 'Cargo.lock', 'src/**/*.rs' + }) + outputs.file(bls12381WasmTarget) +} + +tasks.register('copyBls12381Wasm', Copy) { + dependsOn tasks.named('buildBls12381Wasm') + from(bls12381WasmTarget) + into(generatedWasmDir.map { it.dir('zeroj-bls12381') }) +} + +processResources { + dependsOn tasks.named('copyBls12381Wasm') + from(generatedWasmDir) +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + name = 'ZeroJ BLS12-381 WASM' + description = 'Optional BLS12-381 primitive provider using zkcrypto Rust WASM and Chicory' + } + } + } +} diff --git a/zeroj-bls12381-wasm/rust/Cargo.lock b/zeroj-bls12381-wasm/rust/Cargo.lock new file mode 100644 index 0000000..c420645 --- /dev/null +++ b/zeroj-bls12381-wasm/rust/Cargo.lock @@ -0,0 +1,105 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroj-bls12381-wasm" +version = "0.1.0" +dependencies = [ + "bls12_381", +] diff --git a/zeroj-bls12381-wasm/rust/Cargo.toml b/zeroj-bls12381-wasm/rust/Cargo.toml new file mode 100644 index 0000000..35d6238 --- /dev/null +++ b/zeroj-bls12381-wasm/rust/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "zeroj-bls12381-wasm" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "zeroj_bls12381" +crate-type = ["cdylib", "rlib"] + +[dependencies] +bls12_381 = "0.8.0" + +[profile.release] +opt-level = "z" +lto = true +panic = "abort" +codegen-units = 1 diff --git a/zeroj-bls12381-wasm/rust/rust-toolchain.toml b/zeroj-bls12381-wasm/rust/rust-toolchain.toml new file mode 100644 index 0000000..6596900 --- /dev/null +++ b/zeroj-bls12381-wasm/rust/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.94.0" +targets = ["wasm32-unknown-unknown"] +profile = "minimal" diff --git a/zeroj-bls12381-wasm/rust/src/lib.rs b/zeroj-bls12381-wasm/rust/src/lib.rs new file mode 100644 index 0000000..f6e86b8 --- /dev/null +++ b/zeroj-bls12381-wasm/rust/src/lib.rs @@ -0,0 +1,194 @@ +use bls12_381::{ + multi_miller_loop, G1Affine, G2Affine, G2Prepared, Gt, Scalar, +}; +use std::{mem, slice}; + +const G1_BYTES: usize = 96; +const G2_BYTES: usize = 192; +const SCALAR_BYTES: usize = 32; + +#[no_mangle] +pub extern "C" fn alloc(len: usize) -> *mut u8 { + let mut buf = Vec::with_capacity(len); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + ptr +} + +#[no_mangle] +pub extern "C" fn dealloc(ptr: *mut u8, len: usize) { + if ptr.is_null() || len == 0 { + return; + } + unsafe { + let _ = Vec::from_raw_parts(ptr, len, len); + } +} + +#[no_mangle] +pub extern "C" fn zeroj_bls12381_version() -> u32 { + 1 +} + +#[no_mangle] +pub extern "C" fn zeroj_bls12381_g1_generator() -> *mut u8 { + respond(Ok(G1Affine::generator().to_uncompressed().to_vec())) +} + +#[no_mangle] +pub extern "C" fn zeroj_bls12381_g2_generator() -> *mut u8 { + respond(Ok(G2Affine::generator().to_uncompressed().to_vec())) +} + +#[no_mangle] +pub extern "C" fn zeroj_bls12381_g1_scalar_mul(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, g1_scalar_mul) +} + +#[no_mangle] +pub extern "C" fn zeroj_bls12381_g2_scalar_mul(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, g2_scalar_mul) +} + +#[no_mangle] +pub extern "C" fn zeroj_bls12381_pairing_check(ptr: *const u8, len: usize) -> *mut u8 { + handle(ptr, len, pairing_check) +} + +fn handle(ptr: *const u8, len: usize, op: F) -> *mut u8 +where + F: FnOnce(&[u8]) -> Result, String>, +{ + if ptr.is_null() { + return respond(Err("request pointer is null".into())); + } + let input = unsafe { slice::from_raw_parts(ptr, len) }; + respond(op(input)) +} + +fn respond(result: Result, String>) -> *mut u8 { + let mut payload = Vec::new(); + match result { + Ok(bytes) => { + payload.push(0); + payload.extend_from_slice(&bytes); + } + Err(message) => { + payload.push(1); + payload.extend_from_slice(message.as_bytes()); + } + } + leak_response(payload) +} + +fn leak_response(payload: Vec) -> *mut u8 { + let len = payload.len(); + let mut buf = Vec::with_capacity(len + 4); + buf.extend_from_slice(&(len as u32).to_le_bytes()); + buf.extend_from_slice(&payload); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + ptr +} + +fn g1_scalar_mul(input: &[u8]) -> Result, String> { + require_len(input, G1_BYTES + SCALAR_BYTES, "G1 scalar multiplication request")?; + let point = read_g1(&input[..G1_BYTES])?; + let scalar = read_scalar(&input[G1_BYTES..])?; + let result = G1Affine::from(&point * &scalar); + Ok(result.to_uncompressed().to_vec()) +} + +fn g2_scalar_mul(input: &[u8]) -> Result, String> { + require_len(input, G2_BYTES + SCALAR_BYTES, "G2 scalar multiplication request")?; + let point = read_g2(&input[..G2_BYTES])?; + let scalar = read_scalar(&input[G2_BYTES..])?; + let result = G2Affine::from(&point * &scalar); + Ok(result.to_uncompressed().to_vec()) +} + +fn pairing_check(input: &[u8]) -> Result, String> { + if input.len() < 4 { + return Err("pairing check request is too short".into()); + } + let count = u32::from_le_bytes(input[..4].try_into().unwrap()) as usize; + let pair_len = G1_BYTES + G2_BYTES; + let expected = 4usize + .checked_add(count.checked_mul(pair_len).ok_or("pairing request is too large")?) + .ok_or("pairing request is too large")?; + require_len(input, expected, "pairing check request")?; + + let mut g1_points = Vec::with_capacity(count); + let mut g2_prepared = Vec::with_capacity(count); + let mut offset = 4; + for _ in 0..count { + g1_points.push(read_g1(&input[offset..offset + G1_BYTES])?); + offset += G1_BYTES; + let g2 = read_g2(&input[offset..offset + G2_BYTES])?; + offset += G2_BYTES; + g2_prepared.push(G2Prepared::from(g2)); + } + + let terms: Vec<_> = g1_points.iter().zip(g2_prepared.iter()).collect(); + let is_identity = multi_miller_loop(&terms).final_exponentiation() == Gt::identity(); + Ok(vec![if is_identity { 1 } else { 0 }]) +} + +fn read_g1(bytes: &[u8]) -> Result { + let arr: [u8; G1_BYTES] = bytes.try_into().map_err(|_| "invalid G1 byte length")?; + let point = G1Affine::from_uncompressed(&arr); + if bool::from(point.is_some()) { + Ok(point.unwrap()) + } else { + Err("invalid G1 point encoding".into()) + } +} + +fn read_g2(bytes: &[u8]) -> Result { + let arr: [u8; G2_BYTES] = bytes.try_into().map_err(|_| "invalid G2 byte length")?; + let point = G2Affine::from_uncompressed(&arr); + if bool::from(point.is_some()) { + Ok(point.unwrap()) + } else { + Err("invalid G2 point encoding".into()) + } +} + +fn read_scalar(bytes: &[u8]) -> Result { + let arr: [u8; SCALAR_BYTES] = bytes.try_into().map_err(|_| "invalid scalar byte length")?; + let scalar = Scalar::from_bytes(&arr); + if bool::from(scalar.is_some()) { + Ok(scalar.unwrap()) + } else { + Err("invalid BLS12-381 scalar encoding".into()) + } +} + +fn require_len(input: &[u8], expected: usize, label: &str) -> Result<(), String> { + if input.len() != expected { + return Err(format!("{label} must be {expected} bytes, got {}", input.len())); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generator_pairing_is_not_identity() { + let g1 = G1Affine::generator(); + let g2 = G2Prepared::from(G2Affine::generator()); + let terms = vec![(&g1, &g2)]; + assert_ne!(multi_miller_loop(&terms).final_exponentiation(), Gt::identity()); + } + + #[test] + fn generator_plus_neg_generator_pairing_is_identity() { + let g1 = G1Affine::generator(); + let neg_g1 = -G1Affine::generator(); + let g2 = G2Prepared::from(G2Affine::generator()); + let terms = vec![(&g1, &g2), (&neg_g1, &g2)]; + assert_eq!(multi_miller_loop(&terms).final_exponentiation(), Gt::identity()); + } +} diff --git a/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmClient.java b/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmClient.java new file mode 100644 index 0000000..1e223af --- /dev/null +++ b/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmClient.java @@ -0,0 +1,197 @@ +package com.bloxbean.cardano.zeroj.bls12381.wasm; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.runtime.Memory; +import com.dylibso.chicory.wasm.Parser; + +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +/** + * Chicory client for the ZeroJ BLS12-381 Rust WASM module. + */ +public final class Bls12381WasmClient { + public static final String DEFAULT_RESOURCE = "/zeroj-bls12381/zeroj_bls12381.wasm"; + + private static final int MAX_RESPONSE_LEN = 16 * 1024 * 1024; + + private final Instance instance; + private final Memory memory; + + public Bls12381WasmClient(byte[] wasmBytes) { + Objects.requireNonNull(wasmBytes, "wasmBytes required"); + if (wasmBytes.length == 0) { + throw new IllegalArgumentException("wasmBytes must not be empty"); + } + try { + this.instance = Instance.builder(Parser.parse(wasmBytes)).build(); + this.memory = Objects.requireNonNull(instance.memory(), "BLS12-381 WASM module must export memory"); + long version = instance.export("zeroj_bls12381_version").apply()[0]; + if (version != 1L) { + throw new Bls12381WasmException("Unsupported BLS12-381 WASM ABI version: " + version); + } + } catch (Bls12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bls12381WasmException("Failed to initialize BLS12-381 WASM module", e); + } + } + + public static Bls12381WasmClient fromPath(Path wasmPath) throws IOException { + return new Bls12381WasmClient(Files.readAllBytes(wasmPath)); + } + + public static Bls12381WasmClient createDefault() { + try (var in = Bls12381WasmClient.class.getResourceAsStream(DEFAULT_RESOURCE)) { + if (in == null) { + throw new Bls12381WasmException("BLS12-381 WASM resource not found: " + DEFAULT_RESOURCE); + } + return new Bls12381WasmClient(in.readAllBytes()); + } catch (IOException e) { + throw new Bls12381WasmException("Failed to read BLS12-381 WASM resource", e); + } + } + + public G1Point g1Generator() { + return Bls12381Codecs.g1FromUncompressed(invokeNoArg("zeroj_bls12381_g1_generator")); + } + + public G2Point g2Generator() { + return Bls12381Codecs.g2FromUncompressed(invokeNoArg("zeroj_bls12381_g2_generator")); + } + + public G1Point g1ScalarMul(G1Point point, BigInteger scalar) { + byte[] request = concat( + Bls12381Codecs.g1ToUncompressed(Bls12381Codecs.requireValid(point)), + Bls12381Codecs.scalarToLittleEndian32Reduced(Objects.requireNonNull(scalar, "scalar required"))); + return Bls12381Codecs.g1FromUncompressed(invoke("zeroj_bls12381_g1_scalar_mul", request)); + } + + public G2Point g2ScalarMul(G2Point point, BigInteger scalar) { + byte[] request = concat( + Bls12381Codecs.g2ToUncompressed(Bls12381Codecs.requireValid(point)), + Bls12381Codecs.scalarToLittleEndian32Reduced(Objects.requireNonNull(scalar, "scalar required"))); + return Bls12381Codecs.g2FromUncompressed(invoke("zeroj_bls12381_g2_scalar_mul", request)); + } + + public boolean pairingProductIsIdentity(G1Point[] g1Points, G2Point[] g2Points) { + Objects.requireNonNull(g1Points, "g1Points required"); + Objects.requireNonNull(g2Points, "g2Points required"); + if (g1Points.length != g2Points.length) { + throw new IllegalArgumentException("Arrays must have equal length"); + } + ByteBuffer request = ByteBuffer + .allocate(4 + g1Points.length * (Bls12381Codecs.G1_UNCOMPRESSED_BYTES + Bls12381Codecs.G2_UNCOMPRESSED_BYTES)) + .order(ByteOrder.LITTLE_ENDIAN); + request.putInt(g1Points.length); + for (int i = 0; i < g1Points.length; i++) { + request.put(Bls12381Codecs.g1ToUncompressed(Bls12381Codecs.requireValid(g1Points[i]))); + request.put(Bls12381Codecs.g2ToUncompressed(Bls12381Codecs.requireValid(g2Points[i]))); + } + byte[] response = invoke("zeroj_bls12381_pairing_check", request.array()); + if (response.length != 1) { + throw new Bls12381WasmException("Invalid BLS12-381 pairing response length: " + response.length); + } + return response[0] != 0; + } + + byte[] invokeRawForTesting(String exportName, byte[] request) { + return invoke(exportName, request); + } + + byte[] invokeNoArgRawForTesting(String exportName) { + return invokeNoArg(exportName); + } + + long invokeExportForTesting(String exportName, long... args) { + return instance.export(exportName).apply(args)[0]; + } + + private synchronized byte[] invoke(String exportName, byte[] request) { + int requestPtr = 0; + int responsePtr = 0; + long responseAllocationLen = 0; + try { + requestPtr = (int) instance.export("alloc").apply(request.length)[0]; + memory.write(requestPtr, request); + responsePtr = (int) instance.export(exportName).apply(requestPtr, request.length)[0]; + long responseLen = readResponseLenHeader(responsePtr); + responseAllocationLen = responseAllocationLen(responseLen); + requireValidResponseLen(responseLen); + return readResponsePayload(exportName, responsePtr, (int) responseLen); + } catch (Bls12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bls12381WasmException("BLS12-381 WASM invocation failed: " + exportName, e); + } finally { + if (requestPtr != 0) { + instance.export("dealloc").apply(requestPtr, request.length); + } + if (responsePtr != 0 && responseAllocationLen > 0) { + instance.export("dealloc").apply(responsePtr, responseAllocationLen); + } + } + } + + private synchronized byte[] invokeNoArg(String exportName) { + int responsePtr = 0; + long responseAllocationLen = 0; + try { + responsePtr = (int) instance.export(exportName).apply()[0]; + long responseLen = readResponseLenHeader(responsePtr); + responseAllocationLen = responseAllocationLen(responseLen); + requireValidResponseLen(responseLen); + return readResponsePayload(exportName, responsePtr, (int) responseLen); + } catch (Bls12381WasmException e) { + throw e; + } catch (Exception e) { + throw new Bls12381WasmException("BLS12-381 WASM invocation failed: " + exportName, e); + } finally { + if (responsePtr != 0 && responseAllocationLen > 0) { + instance.export("dealloc").apply(responsePtr, responseAllocationLen); + } + } + } + + private long readResponseLenHeader(int responsePtr) { + byte[] lenBytes = memory.readBytes(responsePtr, 4); + return Integer.toUnsignedLong(ByteBuffer.wrap(lenBytes).order(ByteOrder.LITTLE_ENDIAN).getInt()); + } + + private void requireValidResponseLen(long responseLen) { + if (responseLen == 0 || responseLen > MAX_RESPONSE_LEN) { + throw new Bls12381WasmException("Invalid BLS12-381 WASM response length: " + responseLen); + } + } + + private long responseAllocationLen(long responseLen) { + long maxWasmAllocationLen = Integer.toUnsignedLong(-1); + return responseLen <= maxWasmAllocationLen - 4 ? responseLen + 4 : 0; + } + + private byte[] readResponsePayload(String exportName, int responsePtr, int responseLen) { + byte[] response = memory.readBytes(responsePtr + 4, responseLen); + if (response[0] != 0) { + String message = new String(response, 1, response.length - 1, StandardCharsets.UTF_8); + throw new Bls12381WasmException("BLS12-381 WASM error from " + exportName + ": " + message); + } + return Arrays.copyOfRange(response, 1, response.length); + } + + private static byte[] concat(byte[] left, byte[] right) { + byte[] out = Arrays.copyOf(left, left.length + right.length); + System.arraycopy(right, 0, out, left.length, right.length); + return out; + } + +} diff --git a/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmException.java b/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmException.java new file mode 100644 index 0000000..dd32ed7 --- /dev/null +++ b/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmException.java @@ -0,0 +1,14 @@ +package com.bloxbean.cardano.zeroj.bls12381.wasm; + +/** + * Runtime exception raised by the Chicory-backed BLS12-381 provider. + */ +public class Bls12381WasmException extends RuntimeException { + public Bls12381WasmException(String message) { + super(message); + } + + public Bls12381WasmException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/WasmBls12381Provider.java b/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/WasmBls12381Provider.java new file mode 100644 index 0000000..59558be --- /dev/null +++ b/zeroj-bls12381-wasm/src/main/java/com/bloxbean/cardano/zeroj/bls12381/wasm/WasmBls12381Provider.java @@ -0,0 +1,63 @@ +package com.bloxbean.cardano.zeroj.bls12381.wasm; + +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * BLS12-381 provider backed by zkcrypto/bls12_381 compiled to WASM and executed by Chicory. + */ +public final class WasmBls12381Provider implements Bls12381Provider { + private final Bls12381WasmClient client; + + public WasmBls12381Provider(Bls12381WasmClient client) { + this.client = Objects.requireNonNull(client, "client required"); + } + + public static WasmBls12381Provider createDefault() { + return new WasmBls12381Provider(Bls12381WasmClient.createDefault()); + } + + @Override + public String id() { + return "zeroj-bls12381-wasm-zkcrypto"; + } + + @Override + public G1Point g1Generator() { + return client.g1Generator(); + } + + @Override + public G2Point g2Generator() { + return client.g2Generator(); + } + + @Override + public G1Point g1ScalarMul(G1Point point, BigInteger scalar) { + return client.g1ScalarMul(point, scalar); + } + + @Override + public G2Point g2ScalarMul(G2Point point, BigInteger scalar) { + return client.g2ScalarMul(point, scalar); + } + + @Override + public G1Point g1SecretScalarMul(G1Point point, BigInteger scalar) { + return client.g1ScalarMul(point, scalar); + } + + @Override + public G2Point g2SecretScalarMul(G2Point point, BigInteger scalar) { + return client.g2ScalarMul(point, scalar); + } + + @Override + public boolean pairingProductIsIdentity(G1Point[] g1Points, G2Point[] g2Points) { + return client.pairingProductIsIdentity(g1Points, g2Points); + } +} diff --git a/zeroj-bls12381-wasm/src/main/resources/META-INF/native-image/com.bloxbean.cardano.zeroj/zeroj-bls12381-wasm/resource-config.json b/zeroj-bls12381-wasm/src/main/resources/META-INF/native-image/com.bloxbean.cardano.zeroj/zeroj-bls12381-wasm/resource-config.json new file mode 100644 index 0000000..8d47fe2 --- /dev/null +++ b/zeroj-bls12381-wasm/src/main/resources/META-INF/native-image/com.bloxbean.cardano.zeroj/zeroj-bls12381-wasm/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources": { + "includes": [ + { + "pattern": "\\Qzeroj-bls12381/zeroj_bls12381.wasm\\E" + } + ] + } +} diff --git a/zeroj-bls12381-wasm/src/test/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmClientTest.java b/zeroj-bls12381-wasm/src/test/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmClientTest.java new file mode 100644 index 0000000..16b2e4e --- /dev/null +++ b/zeroj-bls12381-wasm/src/test/java/com/bloxbean/cardano/zeroj/bls12381/wasm/Bls12381WasmClientTest.java @@ -0,0 +1,274 @@ +package com.bloxbean.cardano.zeroj.bls12381.wasm; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.ExternalType; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class Bls12381WasmClientTest { + + private final Bls12381WasmClient client = Bls12381WasmClient.createDefault(); + + @Test + void wasmModule_hasNoHostImportsAndExpectedExports() throws IOException { + var module = Parser.parse(loadDefaultWasm()); + assertEquals(0, module.importSection().importCount()); + + Set exports = new HashSet<>(); + for (int i = 0; i < module.exportSection().exportCount(); i++) { + var export = module.exportSection().getExport(i); + if (export.exportType() == ExternalType.FUNCTION) { + exports.add(export.name()); + } + } + assertTrue(exports.contains("zeroj_bls12381_version")); + assertTrue(exports.contains("zeroj_bls12381_g1_generator")); + assertTrue(exports.contains("zeroj_bls12381_g2_generator")); + assertTrue(exports.contains("zeroj_bls12381_g1_scalar_mul")); + assertTrue(exports.contains("zeroj_bls12381_g2_scalar_mul")); + assertTrue(exports.contains("zeroj_bls12381_pairing_check")); + assertTrue(exports.contains("alloc")); + assertTrue(exports.contains("dealloc")); + } + + @Test + void generators_matchPureJavaConstants() { + assertEquals(Bls12381Generators.G1, client.g1Generator()); + assertEquals(Bls12381Generators.G2, client.g2Generator()); + } + + @Test + void scalarMul_matchesPureJavaProvider() { + var pure = Bls12381Providers.pureJava(); + BigInteger scalar = BigInteger.valueOf(42); + + assertEquals(pure.g1ScalarMul(Bls12381Generators.G1, scalar), + client.g1ScalarMul(Bls12381Generators.G1, scalar)); + assertEquals(pure.g2ScalarMul(Bls12381Generators.G2, scalar), + client.g2ScalarMul(Bls12381Generators.G2, scalar)); + } + + @Test + void scalarMul_byZero_returnsInfinity() { + assertEquals(G1Point.INFINITY, client.g1ScalarMul(Bls12381Generators.G1, BigInteger.ZERO)); + assertEquals(G2Point.INFINITY, client.g2ScalarMul(Bls12381Generators.G2, BigInteger.ZERO)); + } + + @Test + void scalarMul_reducesScalarsLikePureJavaProvider() { + var pure = Bls12381Providers.pureJava(); + var r = Bls12381Generators.SCALAR_FIELD_ORDER; + + for (BigInteger scalar : new BigInteger[]{r, r.add(BigInteger.ONE), BigInteger.valueOf(-1)}) { + assertEquals(pure.g1ScalarMul(Bls12381Generators.G1, scalar), + client.g1ScalarMul(Bls12381Generators.G1, scalar)); + assertEquals(pure.g2ScalarMul(Bls12381Generators.G2, scalar), + client.g2ScalarMul(Bls12381Generators.G2, scalar)); + } + } + + @Test + void scalarMul_rejectsInvalidPointsBeforeWasmCall() { + assertThrows(IllegalArgumentException.class, + () -> client.g1ScalarMul(new G1Point(Fp.ZERO, Fp.ZERO), BigInteger.ONE)); + assertThrows(IllegalArgumentException.class, + () -> client.g2ScalarMul(new G2Point(Fp2.ZERO, Fp2.ZERO), BigInteger.ONE)); + } + + @Test + void rawWasmInvocation_reportsWrongLengthAndInvalidPointErrors() { + assertThrows(Bls12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bls12381_g1_scalar_mul", new byte[1])); + assertThrows(Bls12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bls12381_pairing_check", new byte[3])); + + byte[] invalidG1ScalarMul = new byte[128]; + assertThrows(Bls12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bls12381_g1_scalar_mul", invalidG1ScalarMul)); + } + + @Test + void rawWasmInvocation_repeatedErrorsDoNotPoisonClient() { + for (int i = 0; i < 100; i++) { + assertThrows(Bls12381WasmException.class, + () -> client.invokeRawForTesting("zeroj_bls12381_g2_scalar_mul", new byte[7])); + } + assertEquals(Bls12381Generators.G1, client.g1Generator()); + } + + @Test + void malformedResponseLengthStillFreesResponseAllocation() { + var malformed = new Bls12381WasmClient(malformedResponseWasm()); + + assertThrows(Bls12381WasmException.class, + () -> malformed.invokeRawForTesting("malformed_response", new byte[]{1, 2, 3})); + + assertEquals(2, malformed.invokeExportForTesting("dealloc_count")); + assertEquals(4, malformed.invokeExportForTesting("last_dealloc_len")); + } + + @Test + void malformedNoArgResponseLengthStillFreesResponseAllocation() { + var malformed = new Bls12381WasmClient(malformedResponseWasm()); + + assertThrows(Bls12381WasmException.class, + () -> malformed.invokeNoArgRawForTesting("malformed_noarg")); + + assertEquals(1, malformed.invokeExportForTesting("dealloc_count")); + assertEquals(4, malformed.invokeExportForTesting("last_dealloc_len")); + } + + @Test + void pairingProduct_matchesExpectedIdentityResult() { + G1Point g1 = Bls12381Generators.G1; + G2Point g2 = Bls12381Generators.G2; + + assertFalse(client.pairingProductIsIdentity(new G1Point[]{g1}, new G2Point[]{g2})); + assertTrue(client.pairingProductIsIdentity(new G1Point[]{g1, g1.negate()}, new G2Point[]{g2, g2})); + } + + @Test + void provider_implementsSharedSpi() { + var provider = WasmBls12381Provider.createDefault(); + + assertEquals("zeroj-bls12381-wasm-zkcrypto", provider.id()); + assertEquals(Bls12381Generators.G1, provider.g1Generator()); + assertEquals(Bls12381Generators.G1, + provider.g1FromCompressed(provider.g1ToCompressed(Bls12381Generators.G1))); + assertEquals(provider.g1ScalarMulGenerator(BigInteger.valueOf(42)), + provider.g1SecretScalarMulGenerator(BigInteger.valueOf(42))); + assertEquals(provider.g2ScalarMulGenerator(BigInteger.valueOf(42)), + provider.g2SecretScalarMulGenerator(BigInteger.valueOf(42))); + assertTrue(provider.g1HashToCurve( + "abc".getBytes(java.nio.charset.StandardCharsets.US_ASCII), + "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_".getBytes(java.nio.charset.StandardCharsets.US_ASCII)).isValid()); + assertTrue(provider.pairingProductIsIdentity( + new G1Point[]{Bls12381Generators.G1, Bls12381Generators.G1.negate()}, + new G2Point[]{Bls12381Generators.G2, Bls12381Generators.G2})); + } + + private static byte[] loadDefaultWasm() throws IOException { + try (var in = Bls12381WasmClient.class.getResourceAsStream(Bls12381WasmClient.DEFAULT_RESOURCE)) { + assertNotNull(in); + return in.readAllBytes(); + } + } + + private static byte[] malformedResponseWasm() { + var wasm = new ByteArrayOutputStream(); + write(wasm, 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00); + section(wasm, 1, out -> { + u32(out, 4); + funcType(out, 0, true); + funcType(out, 1, true); + funcType(out, 2, false); + funcType(out, 2, true); + }); + section(wasm, 3, out -> { + u32(out, 7); + write(out, 0, 1, 2, 3, 0, 0, 0); + }); + section(wasm, 5, out -> write(out, 1, 0, 1)); + section(wasm, 7, out -> { + u32(out, 8); + export(out, "memory", 2, 0); + export(out, "zeroj_bls12381_version", 0, 0); + export(out, "alloc", 0, 1); + export(out, "dealloc", 0, 2); + export(out, "malformed_response", 0, 3); + export(out, "malformed_noarg", 0, 4); + export(out, "dealloc_count", 0, 5); + export(out, "last_dealloc_len", 0, 6); + }); + section(wasm, 10, out -> { + u32(out, 7); + code(out, 0x00, 0x41, 0x01, 0x0b); + code(out, 0x00, 0x41, 0x80, 0x08, 0x0b); + code(out, + 0x00, + 0x41, 0x00, + 0x41, 0x00, + 0x28, 0x02, 0x00, + 0x41, 0x01, + 0x6a, + 0x36, 0x02, 0x00, + 0x41, 0x04, + 0x20, 0x01, + 0x36, 0x02, 0x00, + 0x0b); + code(out, 0x00, 0x41, 0x08, 0x41, 0x00, 0x36, 0x02, 0x00, 0x41, 0x08, 0x0b); + code(out, 0x00, 0x41, 0x08, 0x41, 0x00, 0x36, 0x02, 0x00, 0x41, 0x08, 0x0b); + code(out, 0x00, 0x41, 0x00, 0x28, 0x02, 0x00, 0x0b); + code(out, 0x00, 0x41, 0x04, 0x28, 0x02, 0x00, 0x0b); + }); + return wasm.toByteArray(); + } + + private static void funcType(ByteArrayOutputStream out, int paramCount, boolean hasResult) { + write(out, 0x60); + u32(out, paramCount); + for (int i = 0; i < paramCount; i++) { + write(out, 0x7f); + } + u32(out, hasResult ? 1 : 0); + if (hasResult) { + write(out, 0x7f); + } + } + + private static void export(ByteArrayOutputStream out, String name, int kind, int index) { + byte[] nameBytes = name.getBytes(java.nio.charset.StandardCharsets.US_ASCII); + u32(out, nameBytes.length); + out.writeBytes(nameBytes); + write(out, kind); + u32(out, index); + } + + private static void code(ByteArrayOutputStream out, int... body) { + u32(out, body.length); + write(out, body); + } + + private static void section(ByteArrayOutputStream wasm, int id, SectionWriter writer) { + var body = new ByteArrayOutputStream(); + writer.write(body); + write(wasm, id); + u32(wasm, body.size()); + wasm.writeBytes(body.toByteArray()); + } + + private static void u32(ByteArrayOutputStream out, int value) { + int remaining = value; + do { + int b = remaining & 0x7f; + remaining >>>= 7; + if (remaining != 0) { + b |= 0x80; + } + write(out, b); + } while (remaining != 0); + } + + private static void write(ByteArrayOutputStream out, int... bytes) { + for (int b : bytes) { + out.write(b); + } + } + + private interface SectionWriter { + void write(ByteArrayOutputStream out); + } +} diff --git a/zeroj-bls12381/build.gradle b/zeroj-bls12381/build.gradle new file mode 100644 index 0000000..e61c99f --- /dev/null +++ b/zeroj-bls12381/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +description = 'ZeroJ shared pure Java BLS12-381 primitives' + +dependencies { +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + name = 'ZeroJ BLS12-381' + description = 'Shared pure Java BLS12-381 field, curve, pairing, and provider primitives' + } + } + } +} diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Codecs.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Codecs.java new file mode 100644 index 0000000..40874c4 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Codecs.java @@ -0,0 +1,293 @@ +package com.bloxbean.cardano.zeroj.bls12381; + +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +/** + * BLS12-381 byte encodings used by ZeroJ providers. + * + *

G1 uncompressed encoding is {@code x || y}. G2 uncompressed encoding is + * {@code x.c1 || x.c0 || y.c1 || y.c0}, matching the standard BLS12-381 + * serialization used by zkcrypto/blst.

+ */ +public final class Bls12381Codecs { + public static final int FP_BYTES = 48; + public static final int SCALAR_BYTES = 32; + public static final int G1_COMPRESSED_BYTES = FP_BYTES; + public static final int G2_COMPRESSED_BYTES = FP_BYTES * 2; + public static final int G1_UNCOMPRESSED_BYTES = FP_BYTES * 2; + public static final int G2_UNCOMPRESSED_BYTES = FP_BYTES * 4; + + private static final int COMPRESSED_FLAG = 0x80; + private static final int INFINITY_FLAG = 0x40; + private static final int SORT_FLAG = 0x20; + private static final int FLAG_MASK = 0xe0; + + private Bls12381Codecs() {} + + public static byte[] scalarToLittleEndian32(BigInteger scalar) { + Objects.requireNonNull(scalar, "scalar required"); + if (scalar.signum() < 0 || scalar.compareTo(Bls12381Generators.SCALAR_FIELD_ORDER) >= 0) { + throw new IllegalArgumentException("Scalar is outside BLS12-381 Fr"); + } + byte[] be = fixedBigEndian(scalar, SCALAR_BYTES); + reverse(be); + return be; + } + + public static byte[] scalarToLittleEndian32Reduced(BigInteger scalar) { + Objects.requireNonNull(scalar, "scalar required"); + return scalarToLittleEndian32(scalar.mod(Bls12381Generators.SCALAR_FIELD_ORDER)); + } + + public static byte[] g1ToUncompressed(G1Point point) { + Objects.requireNonNull(point, "point required"); + byte[] out = new byte[G1_UNCOMPRESSED_BYTES]; + if (point.isInfinity()) { + out[0] = INFINITY_FLAG; + return out; + } + writeFp(out, 0, point.x().value()); + writeFp(out, FP_BYTES, point.y().value()); + return out; + } + + public static byte[] g1ToCompressed(G1Point point) { + Objects.requireNonNull(point, "point required"); + byte[] out = new byte[G1_COMPRESSED_BYTES]; + if (point.isInfinity()) { + out[0] = (byte) (COMPRESSED_FLAG | INFINITY_FLAG); + return out; + } + writeFp(out, 0, point.x().value()); + out[0] |= (byte) COMPRESSED_FLAG; + if (point.y().lexicographicallyLargest()) { + out[0] |= SORT_FLAG; + } + return out; + } + + public static G1Point g1FromUncompressed(byte[] bytes) { + return requireValid(g1FromUncompressedUnchecked(bytes)); + } + + public static G1Point g1FromCompressed(byte[] bytes) { + return requireValid(g1FromCompressedUnchecked(bytes)); + } + + public static G1Point g1FromCompressedUnchecked(byte[] bytes) { + requireLength(bytes, G1_COMPRESSED_BYTES, "G1 compressed"); + byte[] copy = bytes.clone(); + boolean compressed = (copy[0] & COMPRESSED_FLAG) != 0; + boolean infinity = (copy[0] & INFINITY_FLAG) != 0; + boolean sorted = (copy[0] & SORT_FLAG) != 0; + copy[0] &= ~FLAG_MASK; + if (!compressed) { + throw new IllegalArgumentException("Invalid G1 compressed flags"); + } + if (infinity) { + if (sorted || !allZero(copy)) { + throw new IllegalArgumentException("Invalid G1 compressed infinity encoding"); + } + return G1Point.INFINITY; + } + + var x = Fp.of(readFp(copy, 0)); + var y = x.square().mul(x).add(Fp.of(4)).sqrt() + .orElseThrow(() -> new IllegalArgumentException("Invalid G1 compressed point")); + if (y.lexicographicallyLargest() != sorted) { + y = y.neg(); + } + return new G1Point(x, y); + } + + public static G1Point g1FromUncompressedUnchecked(byte[] bytes) { + requireLength(bytes, G1_UNCOMPRESSED_BYTES, "G1 uncompressed"); + byte[] copy = bytes.clone(); + boolean compressed = (copy[0] & COMPRESSED_FLAG) != 0; + boolean infinity = (copy[0] & INFINITY_FLAG) != 0; + boolean sorted = (copy[0] & SORT_FLAG) != 0; + copy[0] &= ~FLAG_MASK; + if (compressed || sorted) { + throw new IllegalArgumentException("Invalid G1 uncompressed flags"); + } + if (infinity) { + if (!allZero(copy)) { + throw new IllegalArgumentException("Invalid G1 infinity encoding"); + } + return G1Point.INFINITY; + } + return new G1Point( + Fp.of(readFp(copy, 0)), + Fp.of(readFp(copy, FP_BYTES))); + } + + public static byte[] g2ToUncompressed(G2Point point) { + Objects.requireNonNull(point, "point required"); + byte[] out = new byte[G2_UNCOMPRESSED_BYTES]; + if (point.isInfinity()) { + out[0] = INFINITY_FLAG; + return out; + } + writeFp(out, 0, point.x().c1().value()); + writeFp(out, FP_BYTES, point.x().c0().value()); + writeFp(out, FP_BYTES * 2, point.y().c1().value()); + writeFp(out, FP_BYTES * 3, point.y().c0().value()); + return out; + } + + public static byte[] g2ToCompressed(G2Point point) { + Objects.requireNonNull(point, "point required"); + byte[] out = new byte[G2_COMPRESSED_BYTES]; + if (point.isInfinity()) { + out[0] = (byte) (COMPRESSED_FLAG | INFINITY_FLAG); + return out; + } + writeFp(out, 0, point.x().c1().value()); + writeFp(out, FP_BYTES, point.x().c0().value()); + out[0] |= (byte) COMPRESSED_FLAG; + if (point.y().lexicographicallyLargest()) { + out[0] |= SORT_FLAG; + } + return out; + } + + public static G2Point g2FromUncompressed(byte[] bytes) { + return requireValid(g2FromUncompressedUnchecked(bytes)); + } + + public static G2Point g2FromCompressed(byte[] bytes) { + return requireValid(g2FromCompressedUnchecked(bytes)); + } + + public static G2Point g2FromCompressedUnchecked(byte[] bytes) { + requireLength(bytes, G2_COMPRESSED_BYTES, "G2 compressed"); + byte[] copy = bytes.clone(); + boolean compressed = (copy[0] & COMPRESSED_FLAG) != 0; + boolean infinity = (copy[0] & INFINITY_FLAG) != 0; + boolean sorted = (copy[0] & SORT_FLAG) != 0; + copy[0] &= ~FLAG_MASK; + if (!compressed) { + throw new IllegalArgumentException("Invalid G2 compressed flags"); + } + if (infinity) { + if (sorted || !allZero(copy)) { + throw new IllegalArgumentException("Invalid G2 compressed infinity encoding"); + } + return G2Point.INFINITY; + } + + var xc1 = Fp.of(readFp(copy, 0)); + var xc0 = Fp.of(readFp(copy, FP_BYTES)); + var x = Fp2.of(xc0, xc1); + var twistB = Fp2.of(Fp.of(4), Fp.of(4)); + var y = x.square().mul(x).add(twistB).sqrt() + .orElseThrow(() -> new IllegalArgumentException("Invalid G2 compressed point")); + if (y.lexicographicallyLargest() != sorted) { + y = y.neg(); + } + return new G2Point(x, y); + } + + public static G2Point g2FromUncompressedUnchecked(byte[] bytes) { + requireLength(bytes, G2_UNCOMPRESSED_BYTES, "G2 uncompressed"); + byte[] copy = bytes.clone(); + boolean compressed = (copy[0] & COMPRESSED_FLAG) != 0; + boolean infinity = (copy[0] & INFINITY_FLAG) != 0; + boolean sorted = (copy[0] & SORT_FLAG) != 0; + copy[0] &= ~FLAG_MASK; + if (compressed || sorted) { + throw new IllegalArgumentException("Invalid G2 uncompressed flags"); + } + if (infinity) { + if (!allZero(copy)) { + throw new IllegalArgumentException("Invalid G2 infinity encoding"); + } + return G2Point.INFINITY; + } + var xc1 = Fp.of(readFp(copy, 0)); + var xc0 = Fp.of(readFp(copy, FP_BYTES)); + var yc1 = Fp.of(readFp(copy, FP_BYTES * 2)); + var yc0 = Fp.of(readFp(copy, FP_BYTES * 3)); + return new G2Point(Fp2.of(xc0, xc1), Fp2.of(yc0, yc1)); + } + + public static G1Point requireValid(G1Point point) { + Objects.requireNonNull(point, "G1 point required"); + if (!point.isOnCurve()) { + throw new IllegalArgumentException("G1 point is not on BLS12-381"); + } + if (!point.isInSubgroup()) { + throw new IllegalArgumentException("G1 point is not in the prime-order subgroup"); + } + return point; + } + + public static G2Point requireValid(G2Point point) { + Objects.requireNonNull(point, "G2 point required"); + if (!point.isOnCurve()) { + throw new IllegalArgumentException("G2 point is not on BLS12-381 twist"); + } + if (!point.isInSubgroup()) { + throw new IllegalArgumentException("G2 point is not in the prime-order subgroup"); + } + return point; + } + + private static void writeFp(byte[] buf, int offset, BigInteger value) { + byte[] bytes = fixedBigEndian(value, FP_BYTES); + System.arraycopy(bytes, 0, buf, offset, FP_BYTES); + } + + private static BigInteger readFp(byte[] bytes, int offset) { + BigInteger value = new BigInteger(1, Arrays.copyOfRange(bytes, offset, offset + FP_BYTES)); + if (value.compareTo(Fp.P) >= 0) { + throw new IllegalArgumentException("Field element is outside BLS12-381 Fp"); + } + return value; + } + + private static byte[] fixedBigEndian(BigInteger value, int length) { + if (value.signum() < 0) { + throw new IllegalArgumentException("Value must be non-negative"); + } + byte[] raw = value.toByteArray(); + int rawStart = raw.length > 1 && raw[0] == 0 ? 1 : 0; + int rawLen = raw.length - rawStart; + if (rawLen > length) { + throw new IllegalArgumentException("Value does not fit in " + length + " bytes"); + } + byte[] out = new byte[length]; + System.arraycopy(raw, rawStart, out, length - rawLen, rawLen); + return out; + } + + private static void requireLength(byte[] bytes, int expected, String label) { + Objects.requireNonNull(bytes, label + " bytes required"); + if (bytes.length != expected) { + throw new IllegalArgumentException(label + " must be " + expected + " bytes, got " + bytes.length); + } + } + + private static boolean allZero(byte[] bytes) { + int acc = 0; + for (byte b : bytes) { + acc |= b & 0xff; + } + return acc == 0; + } + + private static void reverse(byte[] bytes) { + for (int i = 0, j = bytes.length - 1; i < j; i++, j--) { + byte tmp = bytes[i]; + bytes[i] = bytes[j]; + bytes[j] = tmp; + } + } +} diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Generators.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Generators.java new file mode 100644 index 0000000..ce41cb2 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Generators.java @@ -0,0 +1,29 @@ +package com.bloxbean.cardano.zeroj.bls12381; + +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; + +import java.math.BigInteger; + +/** + * Standard BLS12-381 generator points and scalar-field constants. + */ +public final class Bls12381Generators { + private Bls12381Generators() {} + + public static final BigInteger SCALAR_FIELD_ORDER = G1Point.R; + + public static final G1Point G1 = new G1Point( + Fp.of(new BigInteger("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", 16)), + Fp.of(new BigInteger("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", 16))); + + public static final G2Point G2 = new G2Point( + Fp2.of( + Fp.of(new BigInteger("024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", 16)), + Fp.of(new BigInteger("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e", 16))), + Fp2.of( + Fp.of(new BigInteger("0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801", 16)), + Fp.of(new BigInteger("0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", 16)))); +} diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Hash.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Hash.java new file mode 100644 index 0000000..5d93554 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381Hash.java @@ -0,0 +1,284 @@ +package com.bloxbean.cardano.zeroj.bls12381; + +import java.math.BigInteger; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Objects; + +/** + * Hash helpers shared by BLS12-381 providers. + */ +public final class Bls12381Hash { + private static final int SHA256_BYTES = 32; + private static final int SHA256_BLOCK_BYTES = 64; + private static final int SHAKE256_RATE_BYTES = 136; + private static final int SHAKE256_OVERSIZE_DST_BYTES = 32; + private static final int SCALAR_HASH_BYTES = 48; + private static final byte[] OVERSIZE_DST_PREFIX = "H2C-OVERSIZE-DST-".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + private static final int[] KECCAK_ROTATION = { + 0, 1, 62, 28, 27, + 36, 44, 6, 55, 20, + 3, 10, 43, 25, 39, + 41, 45, 15, 21, 8, + 18, 2, 61, 56, 14 + }; + private static final long[] KECCAK_ROUND_CONSTANTS = { + 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808aL, 0x8000000080008000L, + 0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L, 0x8000000000008009L, + 0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL, + 0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L, + 0x8000000000008002L, 0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL, + 0x8000000080008081L, 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L + }; + + private Bls12381Hash() {} + + public static BigInteger hashToScalar(byte[] message, byte[] dst) { + byte[] uniform = expandMessageXmdSha256(message, dst, SCALAR_HASH_BYTES); + return new BigInteger(1, uniform).mod(Bls12381Generators.SCALAR_FIELD_ORDER); + } + + public static BigInteger hashToScalarXofShake256(byte[] message, byte[] dst) { + byte[] uniform = expandMessageXofShake256(message, dst, SCALAR_HASH_BYTES); + return new BigInteger(1, uniform).mod(Bls12381Generators.SCALAR_FIELD_ORDER); + } + + public static G1Point hashToG1(byte[] message, byte[] dst) { + return Bls12381HashToCurve.hashToG1(message, dst); + } + + public static G1Point encodeToG1(byte[] message, byte[] dst) { + return Bls12381HashToCurve.encodeToG1(message, dst); + } + + public static G1Point hashToG1XofShake256(byte[] message, byte[] dst) { + return Bls12381HashToCurve.hashToG1XofShake256(message, dst); + } + + public static G1Point encodeToG1XofShake256(byte[] message, byte[] dst) { + return Bls12381HashToCurve.encodeToG1XofShake256(message, dst); + } + + public static G2Point hashToG2(byte[] message, byte[] dst) { + return Bls12381HashToCurve.hashToG2(message, dst); + } + + public static G2Point encodeToG2(byte[] message, byte[] dst) { + return Bls12381HashToCurve.encodeToG2(message, dst); + } + + public static G2Point hashToG2XofShake256(byte[] message, byte[] dst) { + return Bls12381HashToCurve.hashToG2XofShake256(message, dst); + } + + public static G2Point encodeToG2XofShake256(byte[] message, byte[] dst) { + return Bls12381HashToCurve.encodeToG2XofShake256(message, dst); + } + + public static byte[] expandMessageXmdSha256(byte[] message, byte[] dst, int lenInBytes) { + Objects.requireNonNull(message, "message required"); + Objects.requireNonNull(dst, "dst required"); + if (lenInBytes < 0 || lenInBytes > 255 * SHA256_BYTES) { + throw new IllegalArgumentException("Invalid expand_message_xmd length: " + lenInBytes); + } + if (lenInBytes == 0) { + return new byte[0]; + } + byte[] effectiveDst = dst; + if (effectiveDst.length > 255) { + effectiveDst = sha256(concat(OVERSIZE_DST_PREFIX, effectiveDst)); + } + + int ell = (lenInBytes + SHA256_BYTES - 1) / SHA256_BYTES; + byte[] dstPrime = concat(effectiveDst, new byte[]{(byte) effectiveDst.length}); + byte[] zPad = new byte[SHA256_BLOCK_BYTES]; + byte[] lenBytes = new byte[]{(byte) (lenInBytes >>> 8), (byte) lenInBytes}; + + byte[] b0 = sha256(concat(zPad, message, lenBytes, new byte[]{0}, dstPrime)); + byte[] bi = sha256(concat(b0, new byte[]{1}, dstPrime)); + byte[] uniform = new byte[ell * SHA256_BYTES]; + System.arraycopy(bi, 0, uniform, 0, SHA256_BYTES); + + for (int i = 2; i <= ell; i++) { + bi = sha256(concat(xor(b0, bi), new byte[]{(byte) i}, dstPrime)); + System.arraycopy(bi, 0, uniform, (i - 1) * SHA256_BYTES, SHA256_BYTES); + } + return Arrays.copyOf(uniform, lenInBytes); + } + + public static byte[] expandMessageXofShake256(byte[] message, byte[] dst, int lenInBytes) { + Objects.requireNonNull(message, "message required"); + Objects.requireNonNull(dst, "dst required"); + if (lenInBytes < 0 || lenInBytes > 0xffff) { + throw new IllegalArgumentException("Invalid expand_message_xof length: " + lenInBytes); + } + if (lenInBytes == 0) { + return new byte[0]; + } + byte[] effectiveDst = dst; + if (effectiveDst.length > 255) { + effectiveDst = shake256(concat(OVERSIZE_DST_PREFIX, effectiveDst), SHAKE256_OVERSIZE_DST_BYTES); + } + + byte[] dstPrime = concat(effectiveDst, new byte[]{(byte) effectiveDst.length}); + byte[] lenBytes = new byte[]{(byte) (lenInBytes >>> 8), (byte) lenInBytes}; + return shake256(concat(message, lenBytes, dstPrime), lenInBytes); + } + + static BigInteger[] hashToFp(byte[] message, byte[] dst, int count) { + byte[] uniform = expandMessageXmdSha256(message, dst, count * 64); + BigInteger[] out = new BigInteger[count]; + for (int i = 0; i < count; i++) { + out[i] = new BigInteger(1, Arrays.copyOfRange(uniform, i * 64, (i + 1) * 64)) + .mod(com.bloxbean.cardano.zeroj.bls12381.field.Fp.P); + } + return out; + } + + static BigInteger[] hashToFpXofShake256(byte[] message, byte[] dst, int count) { + byte[] uniform = expandMessageXofShake256(message, dst, count * 64); + BigInteger[] out = new BigInteger[count]; + for (int i = 0; i < count; i++) { + out[i] = new BigInteger(1, Arrays.copyOfRange(uniform, i * 64, (i + 1) * 64)) + .mod(com.bloxbean.cardano.zeroj.bls12381.field.Fp.P); + } + return out; + } + + static BigInteger[][] hashToFp2(byte[] message, byte[] dst, int count) { + byte[] uniform = expandMessageXmdSha256(message, dst, count * 2 * 64); + BigInteger[][] out = new BigInteger[count][2]; + for (int i = 0; i < count; i++) { + int offset = i * 128; + out[i][0] = new BigInteger(1, Arrays.copyOfRange(uniform, offset, offset + 64)) + .mod(com.bloxbean.cardano.zeroj.bls12381.field.Fp.P); + out[i][1] = new BigInteger(1, Arrays.copyOfRange(uniform, offset + 64, offset + 128)) + .mod(com.bloxbean.cardano.zeroj.bls12381.field.Fp.P); + } + return out; + } + + static BigInteger[][] hashToFp2XofShake256(byte[] message, byte[] dst, int count) { + byte[] uniform = expandMessageXofShake256(message, dst, count * 2 * 64); + BigInteger[][] out = new BigInteger[count][2]; + for (int i = 0; i < count; i++) { + int offset = i * 128; + out[i][0] = new BigInteger(1, Arrays.copyOfRange(uniform, offset, offset + 64)) + .mod(com.bloxbean.cardano.zeroj.bls12381.field.Fp.P); + out[i][1] = new BigInteger(1, Arrays.copyOfRange(uniform, offset + 64, offset + 128)) + .mod(com.bloxbean.cardano.zeroj.bls12381.field.Fp.P); + } + return out; + } + + private static byte[] sha256(byte[] input) { + try { + return MessageDigest.getInstance("SHA-256").digest(input); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-256 is not available", e); + } + } + + private static byte[] shake256(byte[] input, int lenInBytes) { + if (lenInBytes < 0) { + throw new IllegalArgumentException("Invalid SHAKE256 output length: " + lenInBytes); + } + long[] state = new long[25]; + int offset = 0; + while (input.length - offset >= SHAKE256_RATE_BYTES) { + absorbBlock(state, input, offset); + keccakF1600(state); + offset += SHAKE256_RATE_BYTES; + } + + byte[] finalBlock = new byte[SHAKE256_RATE_BYTES]; + int remaining = input.length - offset; + System.arraycopy(input, offset, finalBlock, 0, remaining); + finalBlock[remaining] ^= 0x1f; + finalBlock[SHAKE256_RATE_BYTES - 1] ^= (byte) 0x80; + absorbBlock(state, finalBlock, 0); + keccakF1600(state); + + byte[] out = new byte[lenInBytes]; + int outOffset = 0; + while (outOffset < lenInBytes) { + int blockLen = Math.min(SHAKE256_RATE_BYTES, lenInBytes - outOffset); + squeezeBlock(state, out, outOffset, blockLen); + outOffset += blockLen; + if (outOffset < lenInBytes) { + keccakF1600(state); + } + } + return out; + } + + private static void absorbBlock(long[] state, byte[] block, int offset) { + for (int i = 0; i < SHAKE256_RATE_BYTES; i++) { + state[i >>> 3] ^= (block[offset + i] & 0xffL) << (8 * (i & 7)); + } + } + + private static void squeezeBlock(long[] state, byte[] out, int offset, int len) { + for (int i = 0; i < len; i++) { + out[offset + i] = (byte) (state[i >>> 3] >>> (8 * (i & 7))); + } + } + + private static void keccakF1600(long[] state) { + long[] b = new long[25]; + long[] c = new long[5]; + long[] d = new long[5]; + for (long roundConstant : KECCAK_ROUND_CONSTANTS) { + for (int x = 0; x < 5; x++) { + c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20]; + } + for (int x = 0; x < 5; x++) { + d[x] = c[(x + 4) % 5] ^ Long.rotateLeft(c[(x + 1) % 5], 1); + } + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + state[x + 5 * y] ^= d[x]; + } + } + + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + b[y + 5 * ((2 * x + 3 * y) % 5)] = + Long.rotateLeft(state[x + 5 * y], KECCAK_ROTATION[x + 5 * y]); + } + } + for (int x = 0; x < 5; x++) { + for (int y = 0; y < 5; y++) { + state[x + 5 * y] = b[x + 5 * y] + ^ ((~b[((x + 1) % 5) + 5 * y]) & b[((x + 2) % 5) + 5 * y]); + } + } + state[0] ^= roundConstant; + } + } + + private static byte[] xor(byte[] left, byte[] right) { + byte[] out = new byte[left.length]; + for (int i = 0; i < left.length; i++) { + out[i] = (byte) (left[i] ^ right[i]); + } + return out; + } + + private static byte[] concat(byte[]... chunks) { + int len = 0; + for (byte[] chunk : chunks) { + len += chunk.length; + } + byte[] out = new byte[len]; + int offset = 0; + for (byte[] chunk : chunks) { + System.arraycopy(chunk, 0, out, offset, chunk.length); + offset += chunk.length; + } + return out; + } +} diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381HashToCurve.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381HashToCurve.java new file mode 100644 index 0000000..592a5ec --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381HashToCurve.java @@ -0,0 +1,306 @@ +package com.bloxbean.cardano.zeroj.bls12381; + +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * RFC 9380 BLS12-381 hash-to-curve support. + */ +final class Bls12381HashToCurve { + private static final BigInteger G1_H_EFF = new BigInteger("d201000000010001", 16); + private static final BigInteger G2_H_EFF = new BigInteger( + "bc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551", 16); + + private static final Fp G1_Z = Fp.of(11); + private static final Fp G1_A = fp("144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d"); + private static final Fp G1_B = fp("12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0"); + + private static final Fp2 G2_Z = Fp2.of(Fp.of(BigInteger.valueOf(-2)), Fp.of(BigInteger.valueOf(-1))); + private static final Fp2 G2_A = Fp2.of(Fp.ZERO, Fp.of(240)); + private static final Fp2 G2_B = Fp2.of(Fp.of(1012), Fp.of(1012)); + + private static final Fp[] G1_X_NUM = fpArray( + "11a05f2b1e833340b809101dd99815856b303e88a2d7005ff2627b56cdb4e2c85610c2d5f2e62d6eaeac1662734649b7", + "17294ed3e943ab2f0588bab22147a81c7c17e75b2f6a8417f565e33c70d1e86b4838f2a6f318c356e834eef1b3cb83bb", + "0d54005db97678ec1d1048c5d10a9a1bce032473295983e56878e501ec68e25c958c3e3d2a09729fe0179f9dac9edcb0", + "1778e7166fcc6db74e0609d307e55412d7f5e4656a8dbf25f1b33289f1b330835336e25ce3107193c5b388641d9b6861", + "0e99726a3199f4436642b4b3e4118e5499db995a1257fb3f086eeb65982fac18985a286f301e77c451154ce9ac8895d9", + "1630c3250d7313ff01d1201bf7a74ab5db3cb17dd952799b9ed3ab9097e68f90a0870d2dcae73d19cd13c1c66f652983", + "0d6ed6553fe44d296a3726c38ae652bfb11586264f0f8ce19008e218f9c86b2a8da25128c1052ecaddd7f225a139ed84", + "17b81e7701abdbe2e8743884d1117e53356de5ab275b4db1a682c62ef0f2753339b7c8f8c8f475af9ccb5618e3f0c88e", + "080d3cf1f9a78fc47b90b33563be990dc43b756ce79f5574a2c596c928c5d1de4fa295f296b74e956d71986a8497e317", + "169b1f8e1bcfa7c42e0c37515d138f22dd2ecb803a0c5c99676314baf4bb1b7fa3190b2edc0327797f241067be390c9e", + "10321da079ce07e272d8ec09d2565b0dfa7dccdde6787f96d50af36003b14866f69b771f8c285decca67df3f1605fb7b", + "06e08c248e260e70bd1e962381edee3d31d79d7e22c837bc23c0bf1bc24c6b68c24b1b80b64d391fa9c8ba2e8ba2d229"); + private static final Fp[] G1_X_DEN = fpArray( + "08ca8d548cff19ae18b2e62f4bd3fa6f01d5ef4ba35b48ba9c9588617fc8ac62b558d681be343df8993cf9fa40d21b1c", + "12561a5deb559c4348b4711298e536367041e8ca0cf0800c0126c2588c48bf5713daa8846cb026e9e5c8276ec82b3bff", + "0b2962fe57a3225e8137e629bff2991f6f89416f5a718cd1fca64e00b11aceacd6a3d0967c94fedcfcc239ba5cb83e19", + "03425581a58ae2fec83aafef7c40eb545b08243f16b1655154cca8abc28d6fd04976d5243eecf5c4130de8938dc62cd8", + "13a8e162022914a80a6f1d5f43e7a07dffdfc759a12062bb8d6b44e833b306da9bd29ba81f35781d539d395b3532a21e", + "0e7355f8e4e667b955390f7f0506c6e9395735e9ce9cad4d0a43bcef24b8982f7400d24bc4228f11c02df9a29f6304a5", + "0772caacf16936190f3e0c63e0596721570f5799af53a1894e2e073062aede9cea73b3538f0de06cec2574496ee84a3a", + "14a7ac2a9d64a8b230b3f5b074cf01996e7f63c21bca68a81996e1cdf9822c580fa5b9489d11e2d311f7d99bbdcc5a5e", + "0a10ecf6ada54f825e920b3dafc7a3cce07f8d1d7161366b74100da67f39883503826692abba43704776ec3a79a1d641", + "095fc13ab9e92ad4476d6e3eb3a56680f682b4ee96f7d03776df533978f31c1593174e4b4b7865002d6384d168ecdd0a"); + private static final Fp[] G1_Y_NUM = fpArray( + "090d97c81ba24ee0259d1f094980dcfa11ad138e48a869522b52af6c956543d3cd0c7aee9b3ba3c2be9845719707bb33", + "134996a104ee5811d51036d776fb46831223e96c254f383d0f906343eb67ad34d6c56711962fa8bfe097e75a2e41c696", + "0cc786baa966e66f4a384c86a3b49942552e2d658a31ce2c344be4b91400da7d26d521628b00523b8dfe240c72de1f6", + "01f86376e8981c217898751ad8746757d42aa7b90eeb791c09e4a3ec03251cf9de405aba9ec61deca6355c77b0e5f4cb", + "08cc03fdefe0ff135caf4fe2a21529c4195536fbe3ce50b879833fd221351adc2ee7f8dc099040a841b6daecf2e8fedb", + "16603fca40634b6a2211e11db8f0a6a074a7d0d4afadb7bd76505c3d3ad5544e203f6326c95a807299b23ab13633a5f0", + "04ab0b9bcfac1bbcb2c977d027796b3ce75bb8ca2be184cb5231413c4d634f3747a87ac2460f415ec961f8855fe9d6f2", + "0987c8d5333ab86fde9926bd2ca6c674170a05bfe3bdd81ffd038da6c26c842642f64550fedfe935a15e4ca31870fb29", + "09fc4018bd96684be88c9e221e4da1bb8f3abd16679dc26c1e8b6e6a1f20cabe69d65201c78607a360370e577bdba587", + "0e1bba7a1186bdb5223abde7ada14a23c42a0ca7915af6fe06985e7ed1e4d43b9b3f7055dd4eba6f2bafaaebca731c30", + "19713e47937cd1be0dfd0b8f1d43fb93cd2fcbcb6caf493fd1183e416389e61031bf3a5cce3fbafce813711ad011c132", + "18b46a908f36f6deb918c143fed2edcc523559b8aaf0c2462e6bfe7f911f643249d9cdf41b44d606ce07c8a4d0074d8e", + "0b182cac101b9399d155096004f53f447aa7b12a3426b08ec02710e807b4633f06c851c1919211f20d4c04f00b971ef8", + "0245a394ad1eca9b72fc00ae7be315dc757b3b080d4c158013e6632d3c40659cc6cf90ad1c232a6442d9d3f5db980133", + "05c129645e44cf1102a159f748c4a3fc5e673d81d7e86568d9ab0f5d396a7ce46ba1049b6579afb7866b1e715475224b", + "15e6be4e990f03ce4ea50b3b42df2eb5cb181d8f84965a3957add4fa95af01b2b665027efec01c7704b456be69c8b604"); + private static final Fp[] G1_Y_DEN = fpArray( + "16112c4c3a9c98b252181140fad0eae9601a6de578980be6eec3232b5be72e7a07f3688ef60c206d01479253b03663c1", + "1962d75c2381201e1a0cbd6c43c348b885c84ff731c4d59ca4a10356f453e01f78a4260763529e3532f6102c2e49a03d", + "058df3306640da276faaae7d6e8eb15778c4855551ae7f310c35a5dd279cd2eca6757cd636f96f891e2538b53dbf67f2", + "16b7d288798e5395f20d23bf89edb4d1d115c5dbddbcd30e123da489e726af41727364f2c28297ada8d26d98445f5416", + "0be0e079545f43e4b00cc912f8228ddcc6d19c9f0f69bbb0542eda0fc9dec916a20b15dc0fd2ededda39142311a5001d", + "08d9e5297186db2d9fb266eaac783182b70152c65550d881c5ecd87b6f0f5a6449f38db9dfa9cce202c6477faaf9b7ac", + "166007c08a99db2fc3ba8734ace9824b5eecfdfa8d0cf8ef5dd365bc400a0051d5fa9c01a58b1fb93d1a1399126a775c", + "16a3ef08be3ea7ea03bcddfabba6ff6ee5a4375efa1f4fd7feb34fd206357132b920f5b00801dee460ee415a15812ed9", + "1866c8ed336c61231a1be54fd1d74cc4f9fb0ce4c6af5920abc5750c4bf39b4852cfe2f7bb9248836b233d9d55535d4a", + "167a55cda70a6e1cea820597d94a84903216f763e13d87bb5308592e7ea7d4fbc7385ea3d529b35e346ef48bb8913f55", + "04d2f259eea405bd48f010a01ad2911d9c6dd039bb61a6290e591b36e636a5c871a5c29f4f83060400f8b49cba8f6aa8", + "0accbb67481d033ff5852c1e48c50c477f94ff8aefce42d28c0f9a88cea7913516f968986f7ebbea9684b529e2561092", + "0ad6b9514c767fe3c3613144b45f1496543346d98adf02267d5ceef9a00d9b8693000763e3b90ac11e99b138573345cc", + "02660400eb2e4f3b628bdd0d53cd76f2bf565b94e72927c1cb748df27942480e420517bd8714cc80d1fadc1326ed06f7", + "0e0fa1d816ddc03e6b24255e0d7819c171c40f65e273b853324efcd6356caa205ca2f570f13497804415473a1d634b8f"); + + private static final Fp2[] G2_X_NUM = fp2Array( + fp2("5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6", "5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97d6"), + fp2("0", "11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71a"), + fp2("11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71e", "8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38d"), + fp2("171d6541fa38ccfaed6dea691f5fb614cb14b4e7f4e810aa22d6108f142b85757098e38d0f671c7188e2aaaaaaaa5ed1", "0")); + private static final Fp2[] G2_X_DEN = fp2Array( + fp2("0", "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa63"), + fp2("0c", "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa9f")); + private static final Fp2[] G2_Y_NUM = fp2Array( + fp2("1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706", "1530477c7ab4113b59a4c18b076d11930f7da5d4a07f649bf54439d87d27e500fc8c25ebf8c92f6812cfc71c71c6d706"), + fp2("0", "5c759507e8e333ebb5b7a9a47d7ed8532c52d39fd3a042a88b58423c50ae15d5c2638e343d9c71c6238aaaaaaaa97be"), + fp2("11560bf17baa99bc32126fced787c88f984f87adf7ae0c7f9a208c6b4f20a4181472aaa9cb8d555526a9ffffffffc71c", "8ab05f8bdd54cde190937e76bc3e447cc27c3d6fbd7063fcd104635a790520c0a395554e5c6aaaa9354ffffffffe38f"), + fp2("124c9ad43b6cf79bfbf7043de3811ad0761b0f37a1e26286b0e977c69aa274524e79097a56dc4bd9e1b371c71c718b10", "0")); + private static final Fp2[] G2_Y_DEN = fp2Array( + fp2("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb", "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa8fb"), + fp2("0", "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffa9d3"), + fp2("12", "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa99")); + + private Bls12381HashToCurve() {} + + static G1Point hashToG1(byte[] message, byte[] dst) { + BigInteger[] u = Bls12381Hash.hashToFp(message, dst, 2); + return clearG1(isoMapG1(simpleSwuG1(Fp.of(u[0]))).add(isoMapG1(simpleSwuG1(Fp.of(u[1]))))); + } + + static G1Point encodeToG1(byte[] message, byte[] dst) { + BigInteger[] u = Bls12381Hash.hashToFp(message, dst, 1); + return clearG1(isoMapG1(simpleSwuG1(Fp.of(u[0])))); + } + + static G1Point hashToG1XofShake256(byte[] message, byte[] dst) { + BigInteger[] u = Bls12381Hash.hashToFpXofShake256(message, dst, 2); + return clearG1(isoMapG1(simpleSwuG1(Fp.of(u[0]))).add(isoMapG1(simpleSwuG1(Fp.of(u[1]))))); + } + + static G1Point encodeToG1XofShake256(byte[] message, byte[] dst) { + BigInteger[] u = Bls12381Hash.hashToFpXofShake256(message, dst, 1); + return clearG1(isoMapG1(simpleSwuG1(Fp.of(u[0])))); + } + + static G2Point hashToG2(byte[] message, byte[] dst) { + BigInteger[][] u = Bls12381Hash.hashToFp2(message, dst, 2); + var u0 = Fp2.of(Fp.of(u[0][0]), Fp.of(u[0][1])); + var u1 = Fp2.of(Fp.of(u[1][0]), Fp.of(u[1][1])); + return clearG2(isoMapG2(simpleSwuG2(u0)).add(isoMapG2(simpleSwuG2(u1)))); + } + + static G2Point encodeToG2(byte[] message, byte[] dst) { + BigInteger[][] u = Bls12381Hash.hashToFp2(message, dst, 1); + var u0 = Fp2.of(Fp.of(u[0][0]), Fp.of(u[0][1])); + return clearG2(isoMapG2(simpleSwuG2(u0))); + } + + static G2Point hashToG2XofShake256(byte[] message, byte[] dst) { + BigInteger[][] u = Bls12381Hash.hashToFp2XofShake256(message, dst, 2); + var u0 = Fp2.of(Fp.of(u[0][0]), Fp.of(u[0][1])); + var u1 = Fp2.of(Fp.of(u[1][0]), Fp.of(u[1][1])); + return clearG2(isoMapG2(simpleSwuG2(u0)).add(isoMapG2(simpleSwuG2(u1)))); + } + + static G2Point encodeToG2XofShake256(byte[] message, byte[] dst) { + BigInteger[][] u = Bls12381Hash.hashToFp2XofShake256(message, dst, 1); + var u0 = Fp2.of(Fp.of(u[0][0]), Fp.of(u[0][1])); + return clearG2(isoMapG2(simpleSwuG2(u0))); + } + + private static G1Point clearG1(G1Point point) { + return Bls12381Codecs.requireValid(point.scalarMul(G1_H_EFF)); + } + + private static G2Point clearG2(G2Point point) { + return Bls12381Codecs.requireValid(point.scalarMul(G2_H_EFF)); + } + + private static FpPoint simpleSwuG1(Fp u) { + var tv1 = G1_Z.mul(u.square()); + var tv2 = tv1.square().add(tv1); + var tv3 = G1_B.mul(tv2.add(Fp.ONE)); + var tv4 = (tv2.isZero() ? G1_Z : tv2.neg()).mul(G1_A); + var tv6 = tv4.square(); + tv2 = tv3.square().add(G1_A.mul(tv6)); + tv2 = tv2.mul(tv3); + tv6 = tv6.mul(tv4); + tv2 = tv2.add(G1_B.mul(tv6)); + var x = tv1.mul(tv3); + var sqrtRatio = sqrtRatio(tv2, tv6, G1_Z); + var y = tv1.mul(u).mul(sqrtRatio.value()); + if (sqrtRatio.wasSquare()) { + x = tv3; + y = sqrtRatio.value(); + } + if (u.sgn0() != y.sgn0()) { + y = y.neg(); + } + x = x.div(tv4); + return new FpPoint(x, y); + } + + private static Fp2Point simpleSwuG2(Fp2 u) { + var tv1 = G2_Z.mul(u.square()); + var tv2 = tv1.square().add(tv1); + var tv3 = G2_B.mul(tv2.add(Fp2.ONE)); + var tv4 = (tv2.isZero() ? G2_Z : tv2.neg()).mul(G2_A); + var tv6 = tv4.square(); + tv2 = tv3.square().add(G2_A.mul(tv6)); + tv2 = tv2.mul(tv3); + tv6 = tv6.mul(tv4); + tv2 = tv2.add(G2_B.mul(tv6)); + var x = tv1.mul(tv3); + var sqrtRatio = sqrtRatio(tv2, tv6, G2_Z); + var y = tv1.mul(u).mul(sqrtRatio.value()); + if (sqrtRatio.wasSquare()) { + x = tv3; + y = sqrtRatio.value(); + } + if (u.sgn0() != y.sgn0()) { + y = y.neg(); + } + x = x.div(tv4); + return new Fp2Point(x, y); + } + + private static G1Point isoMapG1(FpPoint point) { + var x = point.x(); + var xNum = eval(G1_X_NUM, x); + var xDen = evalWithLeadingOne(G1_X_DEN, x); + var yNum = eval(G1_Y_NUM, x); + var yDen = evalWithLeadingOne(G1_Y_DEN, x); + if (xDen.isZero() || yDen.isZero()) { + return G1Point.INFINITY; + } + return new G1Point(xNum.div(xDen), point.y().mul(yNum).div(yDen)); + } + + private static G2Point isoMapG2(Fp2Point point) { + var x = point.x(); + var xNum = eval(G2_X_NUM, x); + var xDen = evalWithLeadingOne(G2_X_DEN, x); + var yNum = eval(G2_Y_NUM, x); + var yDen = evalWithLeadingOne(G2_Y_DEN, x); + if (xDen.isZero() || yDen.isZero()) { + return G2Point.INFINITY; + } + return new G2Point(xNum.div(xDen), point.y().mul(yNum).div(yDen)); + } + + private static SqrtRatioFp sqrtRatio(Fp u, Fp v, Fp z) { + var direct = u.div(v).sqrt(); + if (direct.isPresent()) { + return new SqrtRatioFp(true, direct.get()); + } + return new SqrtRatioFp(false, z.mul(u).div(v).sqrt() + .orElseThrow(() -> new IllegalStateException("sqrt_ratio failed"))); + } + + private static SqrtRatioFp2 sqrtRatio(Fp2 u, Fp2 v, Fp2 z) { + var direct = u.div(v).sqrt(); + if (direct.isPresent()) { + return new SqrtRatioFp2(true, direct.get()); + } + return new SqrtRatioFp2(false, z.mul(u).div(v).sqrt() + .orElseThrow(() -> new IllegalStateException("sqrt_ratio failed"))); + } + + private static Fp eval(Fp[] coeffs, Fp x) { + var result = coeffs[coeffs.length - 1]; + for (int i = coeffs.length - 2; i >= 0; i--) { + result = result.mul(x).add(coeffs[i]); + } + return result; + } + + private static Fp evalWithLeadingOne(Fp[] coeffs, Fp x) { + var result = Fp.ONE; + for (int i = coeffs.length - 1; i >= 0; i--) { + result = result.mul(x).add(coeffs[i]); + } + return result; + } + + private static Fp2 eval(Fp2[] coeffs, Fp2 x) { + var result = coeffs[coeffs.length - 1]; + for (int i = coeffs.length - 2; i >= 0; i--) { + result = result.mul(x).add(coeffs[i]); + } + return result; + } + + private static Fp2 evalWithLeadingOne(Fp2[] coeffs, Fp2 x) { + var result = Fp2.ONE; + for (int i = coeffs.length - 1; i >= 0; i--) { + result = result.mul(x).add(coeffs[i]); + } + return result; + } + + private static Fp fp(String hex) { + Objects.requireNonNull(hex, "hex required"); + return Fp.of(new BigInteger(hex, 16)); + } + + private static Fp[] fpArray(String... hexValues) { + var out = new Fp[hexValues.length]; + for (int i = 0; i < hexValues.length; i++) { + out[i] = fp(hexValues[i]); + } + return out; + } + + private static Fp2 fp2(String c0Hex, String c1Hex) { + return Fp2.of(fp(c0Hex), fp(c1Hex)); + } + + private static Fp2[] fp2Array(Fp2... values) { + return values; + } + + private record FpPoint(Fp x, Fp y) {} + private record Fp2Point(Fp2 x, Fp2 y) {} + private record SqrtRatioFp(boolean wasSquare, Fp value) {} + private record SqrtRatioFp2(boolean wasSquare, Fp2 value) {} +} diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/G1Point.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/G1Point.java similarity index 64% rename from zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/G1Point.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/G1Point.java index a600b70..3199269 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/G1Point.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/G1Point.java @@ -1,4 +1,6 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; +package com.bloxbean.cardano.zeroj.bls12381.ec; + +import com.bloxbean.cardano.zeroj.bls12381.field.*; import java.math.BigInteger; @@ -8,13 +10,33 @@ public record G1Point(Fp x, Fp y) { public static final G1Point INFINITY = new G1Point(null, null); + private static final Fp B = Fp.of(4); /** Scalar field order r. */ public static final BigInteger R = new BigInteger( "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16); + public G1Point { + if ((x == null) != (y == null)) { + throw new IllegalArgumentException("G1 infinity must have both coordinates null"); + } + } + public boolean isInfinity() { return x == null; } + public boolean isOnCurve() { + if (isInfinity()) return true; + return y.square().equals(x.square().mul(x).add(B)); + } + + public boolean isInSubgroup() { + return isInfinity() || scalarMul(R).isInfinity(); + } + + public boolean isValid() { + return isOnCurve() && isInSubgroup(); + } + /** Create from snarkjs projective [x, y, z] coordinates. */ public static G1Point fromProjective(BigInteger px, BigInteger py, BigInteger pz) { if (pz.signum() == 0) return INFINITY; @@ -61,4 +83,19 @@ public G1Point scalarMul(BigInteger scalar) { } return result; } + + /** + * Fixed-schedule scalar multiplication for secret-scalar callers. + */ + public G1Point ctScalarMul(BigInteger scalar) { + if (scalar.signum() == 0 || isInfinity()) return INFINITY; + if (scalar.signum() < 0) return negate().ctScalarMul(scalar.negate()); + var affine = JacobianG1BLS381.fromAffine(x.value(), y.value()) + .ctScalarMul(scalar) + .toAffine(); + if (affine.isInfinity()) { + return INFINITY; + } + return new G1Point(Fp.of(affine.xBigInt()), Fp.of(affine.yBigInt())); + } } diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/G2Point.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/G2Point.java new file mode 100644 index 0000000..5642b0b --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/G2Point.java @@ -0,0 +1,108 @@ +package com.bloxbean.cardano.zeroj.bls12381.ec; + +import com.bloxbean.cardano.zeroj.bls12381.field.*; + +import java.math.BigInteger; + +/** + * BLS12-381 G2 point in affine coordinates on the twisted curve y^2 = x^3 + 4(1+u) over Fp2. + */ +public record G2Point(Fp2 x, Fp2 y) { + + public static final G2Point INFINITY = new G2Point(null, null); + private static final Fp2 B_TWIST = Fp2.of(Fp.of(4), Fp.of(4)); + + public G2Point { + if ((x == null) != (y == null)) { + throw new IllegalArgumentException("G2 infinity must have both coordinates null"); + } + } + + public boolean isInfinity() { return x == null; } + + public boolean isOnCurve() { + if (isInfinity()) return true; + return y.square().equals(x.square().mul(x).add(B_TWIST)); + } + + public boolean isInSubgroup() { + return isInfinity() || scalarMul(G1Point.R).isInfinity(); + } + + public boolean isValid() { + return isOnCurve() && isInSubgroup(); + } + + /** Create from snarkjs projective [[x_c0,x_c1],[y_c0,y_c1],[z_c0,z_c1]]. */ + public static G2Point fromProjective(BigInteger xc0, BigInteger xc1, + BigInteger yc0, BigInteger yc1, + BigInteger zc0, BigInteger zc1) { + var z = Fp2.of(Fp.of(zc0), Fp.of(zc1)); + if (z.isZero()) return INFINITY; + if (z.c0().equals(Fp.ONE) && z.c1().isZero()) { + return new G2Point(Fp2.of(Fp.of(xc0), Fp.of(xc1)), Fp2.of(Fp.of(yc0), Fp.of(yc1))); + } + var zInv = z.inv(); + return new G2Point( + Fp2.of(Fp.of(xc0), Fp.of(xc1)).mul(zInv), + Fp2.of(Fp.of(yc0), Fp.of(yc1)).mul(zInv)); + } + + public G2Point negate() { + return isInfinity() ? this : new G2Point(x, y.neg()); + } + + public G2Point add(G2Point o) { + if (this.isInfinity()) return o; + if (o.isInfinity()) return this; + if (this.x.equals(o.x)) { + return this.y.equals(o.y) ? this.doublePoint() : INFINITY; + } + var lambda = o.y.sub(this.y).mul(o.x.sub(this.x).inv()); + var x3 = lambda.square().sub(this.x).sub(o.x); + var y3 = lambda.mul(this.x.sub(x3)).sub(this.y); + return new G2Point(x3, y3); + } + + public G2Point doublePoint() { + if (isInfinity() || y.isZero()) return INFINITY; + var three = Fp2.of(Fp.of(3), Fp.ZERO); + var lambda = x.square().mul(three).mul(y.add(y).inv()); + var x3 = lambda.square().sub(x).sub(x); + var y3 = lambda.mul(x.sub(x3)).sub(y); + return new G2Point(x3, y3); + } + + public G2Point scalarMul(BigInteger scalar) { + if (scalar.signum() == 0) return INFINITY; + if (scalar.signum() < 0) return negate().scalarMul(scalar.negate()); + var result = INFINITY; + var base = this; + var s = scalar; + while (s.signum() > 0) { + if (s.testBit(0)) result = result.add(base); + base = base.doublePoint(); + s = s.shiftRight(1); + } + return result; + } + + /** + * Fixed-schedule scalar multiplication for secret-scalar callers. + */ + public G2Point ctScalarMul(BigInteger scalar) { + if (scalar.signum() == 0 || isInfinity()) return INFINITY; + if (scalar.signum() < 0) return negate().ctScalarMul(scalar.negate()); + var affine = JacobianG2BLS381.fromAffine( + MontFp2_381.of(x.c0().value(), x.c1().value()), + MontFp2_381.of(y.c0().value(), y.c1().value())) + .ctScalarMul(scalar) + .toAffine(); + if (affine.isInfinity()) { + return INFINITY; + } + return new G2Point( + Fp2.of(Fp.of(affine.x().reBigInt()), Fp.of(affine.x().imBigInt())), + Fp2.of(Fp.of(affine.y().reBigInt()), Fp.of(affine.y().imBigInt()))); + } +} diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG1BLS381.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG1BLS381.java similarity index 93% rename from zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG1BLS381.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG1BLS381.java index 909d052..4f8fee3 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG1BLS381.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG1BLS381.java @@ -1,6 +1,6 @@ -package com.bloxbean.cardano.zeroj.crypto.ec; +package com.bloxbean.cardano.zeroj.bls12381.ec; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; import java.math.BigInteger; @@ -172,21 +172,20 @@ public JacobianG1BLS381 ctScalarMul(BigInteger scalar) { if (scalar.signum() < 0) return negate().ctScalarMul(scalar.negate()); if (this.isInfinity()) return INFINITY; - // Montgomery ladder: R0 = infinity, R1 = P - // For each bit (MSB to LSB): - // if bit=0: R1 = R0 + R1, R0 = 2*R0 - // if bit=1: R0 = R0 + R1, R1 = 2*R1 - // Always perform both operations to maintain constant-time behavior. + // Montgomery ladder with a fixed operation schedule per bit. JacobianG1BLS381 r0 = INFINITY; JacobianG1BLS381 r1 = this; for (int i = 255; i >= 0; i--) { + JacobianG1BLS381 sum = r0.add(r1); + JacobianG1BLS381 double0 = r0.doublePoint(); + JacobianG1BLS381 double1 = r1.doublePoint(); if (scalar.testBit(i)) { - r0 = r0.add(r1); - r1 = r1.doublePoint(); + r0 = sum; + r1 = double1; } else { - r1 = r0.add(r1); - r0 = r0.doublePoint(); + r0 = double0; + r1 = sum; } } return r0; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG2BLS381.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG2BLS381.java similarity index 92% rename from zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG2BLS381.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG2BLS381.java index 0fd0e89..0da4eb6 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG2BLS381.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG2BLS381.java @@ -1,7 +1,7 @@ -package com.bloxbean.cardano.zeroj.crypto.ec; +package com.bloxbean.cardano.zeroj.bls12381.ec; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp2_381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp2_381; import java.math.BigInteger; @@ -134,12 +134,15 @@ public JacobianG2BLS381 ctScalarMul(BigInteger scalar) { JacobianG2BLS381 r1 = this; for (int i = 255; i >= 0; i--) { + JacobianG2BLS381 sum = r0.add(r1); + JacobianG2BLS381 double0 = r0.doublePoint(); + JacobianG2BLS381 double1 = r1.doublePoint(); if (scalar.testBit(i)) { - r0 = r0.add(r1); - r1 = r1.doublePoint(); + r0 = sum; + r1 = double1; } else { - r1 = r0.add(r1); - r0 = r0.doublePoint(); + r0 = double0; + r1 = sum; } } return r0; diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp.java similarity index 70% rename from zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp.java index afaca22..8314bab 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp.java @@ -1,6 +1,7 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import java.math.BigInteger; +import java.util.Optional; /** * BLS12-381 base field element — arithmetic modulo the base field prime p. @@ -12,6 +13,8 @@ public record Fp(BigInteger value) { /** BLS12-381 base field prime (381 bits). */ public static final BigInteger P = new BigInteger( "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16); + private static final BigInteger SQRT_EXPONENT = P.add(BigInteger.ONE).shiftRight(2); + private static final BigInteger LEXICOGRAPHIC_LIMIT = P.subtract(BigInteger.ONE).shiftRight(1); public static final Fp ZERO = new Fp(BigInteger.ZERO); public static final Fp ONE = new Fp(BigInteger.ONE); @@ -25,11 +28,18 @@ public record Fp(BigInteger value) { public Fp add(Fp o) { return new Fp(value.add(o.value)); } public Fp sub(Fp o) { return new Fp(value.subtract(o.value).add(P)); } public Fp mul(Fp o) { return new Fp(value.multiply(o.value)); } + public Fp div(Fp o) { return mul(o.inv()); } public Fp neg() { return value.signum() == 0 ? this : new Fp(P.subtract(value)); } public Fp inv() { return new Fp(value.modInverse(P)); } public Fp square() { return new Fp(value.multiply(value)); } public Fp pow(BigInteger exp) { return new Fp(value.modPow(exp, P)); } + public Optional sqrt() { + var root = pow(SQRT_EXPONENT); + return root.square().equals(this) ? Optional.of(root) : Optional.empty(); + } public boolean isZero() { return value.signum() == 0; } + public boolean sgn0() { return value.testBit(0); } + public boolean lexicographicallyLargest() { return value.compareTo(LEXICOGRAPHIC_LIMIT) > 0; } @Override public String toString() { return value.toString(); } } diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp12.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp12.java similarity index 96% rename from zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp12.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp12.java index adbba59..2bdcedd 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp12.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp12.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import java.math.BigInteger; diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp2.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp2.java similarity index 52% rename from zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp2.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp2.java index 5358757..70ba331 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp2.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp2.java @@ -1,4 +1,7 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; +package com.bloxbean.cardano.zeroj.bls12381.field; + +import java.math.BigInteger; +import java.util.Optional; /** * BLS12-381 quadratic extension field element (Fp2 = Fp[u] / (u^2 + 1)). @@ -7,6 +10,9 @@ */ public record Fp2(Fp c0, Fp c1) { + private static final BigInteger P_MINUS_THREE_OVER_FOUR = Fp.P.subtract(BigInteger.valueOf(3)).shiftRight(2); + private static final BigInteger P_MINUS_ONE_OVER_TWO = Fp.P.subtract(BigInteger.ONE).shiftRight(1); + public static final Fp2 ZERO = new Fp2(Fp.ZERO, Fp.ZERO); public static final Fp2 ONE = new Fp2(Fp.ONE, Fp.ZERO); @@ -32,7 +38,34 @@ public Fp2 square() { public Fp2 conjugate() { return new Fp2(c0, c1.neg()); } public Fp norm() { return c0.square().add(c1.square()); } public Fp2 inv() { var n = norm().inv(); return new Fp2(c0.mul(n), c1.neg().mul(n)); } + public Fp2 div(Fp2 o) { return mul(o.inv()); } public Fp2 mulScalar(Fp s) { return new Fp2(c0.mul(s), c1.mul(s)); } + public Fp2 pow(BigInteger exp) { + if (exp.signum() < 0) return inv().pow(exp.negate()); + var result = ONE; + var base = this; + var e = exp; + while (e.signum() > 0) { + if (e.testBit(0)) result = result.mul(base); + base = base.square(); + e = e.shiftRight(1); + } + return result; + } + public Optional sqrt() { + if (isZero()) return Optional.of(ZERO); + + var a1 = pow(P_MINUS_THREE_OVER_FOUR); + var alpha = a1.square().mul(this); + var x0 = a1.mul(this); + Fp2 root; + if (alpha.equals(ONE.neg())) { + root = new Fp2(x0.c1.neg(), x0.c0); + } else { + root = alpha.add(ONE).pow(P_MINUS_ONE_OVER_TWO).mul(x0); + } + return root.square().equals(this) ? Optional.of(root) : Optional.empty(); + } /** * Multiply by the non-residue xi = 1 + u used for BLS12-381 Fp6 tower. @@ -43,6 +76,10 @@ public Fp2 mulByNonResidue() { } public boolean isZero() { return c0.isZero() && c1.isZero(); } + public boolean sgn0() { return c0.sgn0() || (c0.isZero() && c1.sgn0()); } + public boolean lexicographicallyLargest() { + return c1.lexicographicallyLargest() || (c1.isZero() && c0.lexicographicallyLargest()); + } @Override public String toString() { return "(" + c0 + " + " + c1 + "*u)"; } } diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp6.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp6.java similarity index 96% rename from zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp6.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp6.java index 1d67086..075d43f 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/Fp6.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp6.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; +package com.bloxbean.cardano.zeroj.bls12381.field; /** * BLS12-381 sextic extension field element (Fp6 = Fp2[v] / (v^3 - xi)). diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp2_381.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp2_381.java similarity index 98% rename from zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp2_381.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp2_381.java index 6d87e01..5edabe5 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp2_381.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp2_381.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp381.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp381.java similarity index 99% rename from zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp381.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp381.java index 4c74ce0..325f2d2 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp381.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp381.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFr381.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFr381.java similarity index 99% rename from zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFr381.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFr381.java index 5f3f5a6..cc2332e 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/field/MontFr381.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFr381.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import java.math.BigInteger; diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontUtil.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontUtil.java new file mode 100644 index 0000000..422c175 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/field/MontUtil.java @@ -0,0 +1,40 @@ +package com.bloxbean.cardano.zeroj.bls12381.field; + +import java.math.BigInteger; + +/** + * Shared utilities for Montgomery-form field element conversions. + */ +final class MontUtil { + + private MontUtil() {} + + /** + * Convert 4 unsigned 64-bit limbs (little-endian) to BigInteger. + */ + static BigInteger limbsToBigInteger(long l0, long l1, long l2, long l3) { + BigInteger result = toUnsignedBigInteger(l3); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l2)); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l1)); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l0)); + return result; + } + + /** + * Convert 6 unsigned 64-bit limbs (little-endian) to BigInteger. + */ + static BigInteger limbsToBigInteger(long l0, long l1, long l2, long l3, long l4, long l5) { + BigInteger result = toUnsignedBigInteger(l5); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l4)); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l3)); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l2)); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l1)); + result = result.shiftLeft(64).or(toUnsignedBigInteger(l0)); + return result; + } + + static BigInteger toUnsignedBigInteger(long v) { + if (v >= 0) return BigInteger.valueOf(v); + return BigInteger.valueOf(v >>> 1).shiftLeft(1).or(BigInteger.valueOf(v & 1)); + } +} diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/BLS12381Pairing.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/pairing/BLS12381Pairing.java similarity index 97% rename from zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/BLS12381Pairing.java rename to zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/pairing/BLS12381Pairing.java index 45b3ac2..544ec38 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/BLS12381Pairing.java +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/pairing/BLS12381Pairing.java @@ -1,4 +1,7 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; +package com.bloxbean.cardano.zeroj.bls12381.pairing; + +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; import java.math.BigInteger; diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/Bls12381Provider.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/Bls12381Provider.java new file mode 100644 index 0000000..5d63a89 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/Bls12381Provider.java @@ -0,0 +1,168 @@ +package com.bloxbean.cardano.zeroj.bls12381.spi; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Hash; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Provider boundary for BLS12-381 primitive implementations. + * + *

Scalar multiplication methods reduce the scalar modulo the BLS12-381 + * scalar-field order. They are for public scalars. Protocol code that multiplies + * by secret scalars must use a backend with an explicit constant-time contract.

+ */ +public interface Bls12381Provider { + String id(); + + G1Point g1Generator(); + + G2Point g2Generator(); + + default G1Point g1Identity() { + return G1Point.INFINITY; + } + + default G2Point g2Identity() { + return G2Point.INFINITY; + } + + default G1Point g1Add(G1Point left, G1Point right) { + return Bls12381Codecs.requireValid(left).add(Bls12381Codecs.requireValid(right)); + } + + default G2Point g2Add(G2Point left, G2Point right) { + return Bls12381Codecs.requireValid(left).add(Bls12381Codecs.requireValid(right)); + } + + default G1Point g1Negate(G1Point point) { + return Bls12381Codecs.requireValid(point).negate(); + } + + default G2Point g2Negate(G2Point point) { + return Bls12381Codecs.requireValid(point).negate(); + } + + G1Point g1ScalarMul(G1Point point, BigInteger scalar); + + G2Point g2ScalarMul(G2Point point, BigInteger scalar); + + /** + * Multiply a G1 point by a secret scalar using this provider's side-channel hardened path. + */ + default G1Point g1SecretScalarMul(G1Point point, BigInteger scalar) { + throw new UnsupportedOperationException(id() + " does not declare a secret-scalar G1 multiplication contract"); + } + + /** + * Multiply a G2 point by a secret scalar using this provider's side-channel hardened path. + */ + default G2Point g2SecretScalarMul(G2Point point, BigInteger scalar) { + throw new UnsupportedOperationException(id() + " does not declare a secret-scalar G2 multiplication contract"); + } + + boolean pairingProductIsIdentity(G1Point[] g1Points, G2Point[] g2Points); + + default boolean g1IsValid(G1Point point) { + return point != null && point.isValid(); + } + + default boolean g2IsValid(G2Point point) { + return point != null && point.isValid(); + } + + default byte[] g1ToCompressed(G1Point point) { + return Bls12381Codecs.g1ToCompressed(Bls12381Codecs.requireValid(point)); + } + + default G1Point g1FromCompressed(byte[] bytes) { + return Bls12381Codecs.g1FromCompressed(bytes); + } + + default byte[] g1ToUncompressed(G1Point point) { + return Bls12381Codecs.g1ToUncompressed(Bls12381Codecs.requireValid(point)); + } + + default G1Point g1FromUncompressed(byte[] bytes) { + return Bls12381Codecs.g1FromUncompressed(bytes); + } + + default byte[] g2ToCompressed(G2Point point) { + return Bls12381Codecs.g2ToCompressed(Bls12381Codecs.requireValid(point)); + } + + default G2Point g2FromCompressed(byte[] bytes) { + return Bls12381Codecs.g2FromCompressed(bytes); + } + + default byte[] g2ToUncompressed(G2Point point) { + return Bls12381Codecs.g2ToUncompressed(Bls12381Codecs.requireValid(point)); + } + + default G2Point g2FromUncompressed(byte[] bytes) { + return Bls12381Codecs.g2FromUncompressed(bytes); + } + + default BigInteger hashToScalar(byte[] message, byte[] dst) { + Objects.requireNonNull(message, "message required"); + Objects.requireNonNull(dst, "dst required"); + return Bls12381Hash.hashToScalar(message, dst); + } + + default BigInteger hashToScalarXofShake256(byte[] message, byte[] dst) { + Objects.requireNonNull(message, "message required"); + Objects.requireNonNull(dst, "dst required"); + return Bls12381Hash.hashToScalarXofShake256(message, dst); + } + + default G1Point g1HashToCurve(byte[] message, byte[] dst) { + return Bls12381Hash.hashToG1(message, dst); + } + + default G1Point g1EncodeToCurve(byte[] message, byte[] dst) { + return Bls12381Hash.encodeToG1(message, dst); + } + + default G1Point g1HashToCurveXofShake256(byte[] message, byte[] dst) { + return Bls12381Hash.hashToG1XofShake256(message, dst); + } + + default G1Point g1EncodeToCurveXofShake256(byte[] message, byte[] dst) { + return Bls12381Hash.encodeToG1XofShake256(message, dst); + } + + default G2Point g2HashToCurve(byte[] message, byte[] dst) { + return Bls12381Hash.hashToG2(message, dst); + } + + default G2Point g2EncodeToCurve(byte[] message, byte[] dst) { + return Bls12381Hash.encodeToG2(message, dst); + } + + default G2Point g2HashToCurveXofShake256(byte[] message, byte[] dst) { + return Bls12381Hash.hashToG2XofShake256(message, dst); + } + + default G2Point g2EncodeToCurveXofShake256(byte[] message, byte[] dst) { + return Bls12381Hash.encodeToG2XofShake256(message, dst); + } + + default G1Point g1ScalarMulGenerator(BigInteger scalar) { + return g1ScalarMul(g1Generator(), scalar); + } + + default G2Point g2ScalarMulGenerator(BigInteger scalar) { + return g2ScalarMul(g2Generator(), scalar); + } + + default G1Point g1SecretScalarMulGenerator(BigInteger scalar) { + return g1SecretScalarMul(g1Generator(), scalar); + } + + default G2Point g2SecretScalarMulGenerator(BigInteger scalar) { + return g2SecretScalarMul(g2Generator(), scalar); + } +} diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/Bls12381Providers.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/Bls12381Providers.java new file mode 100644 index 0000000..53b8a10 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/Bls12381Providers.java @@ -0,0 +1,12 @@ +package com.bloxbean.cardano.zeroj.bls12381.spi; + +/** + * Built-in BLS12-381 provider factories. + */ +public final class Bls12381Providers { + private Bls12381Providers() {} + + public static Bls12381Provider pureJava() { + return PureJavaBls12381Provider.INSTANCE; + } +} diff --git a/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/PureJavaBls12381Provider.java b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/PureJavaBls12381Provider.java new file mode 100644 index 0000000..d274d42 --- /dev/null +++ b/zeroj-bls12381/src/main/java/com/bloxbean/cardano/zeroj/bls12381/spi/PureJavaBls12381Provider.java @@ -0,0 +1,76 @@ +package com.bloxbean.cardano.zeroj.bls12381.spi; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * Default provider backed by ZeroJ's pure Java BLS12-381 implementation. + */ +public final class PureJavaBls12381Provider implements Bls12381Provider { + public static final PureJavaBls12381Provider INSTANCE = new PureJavaBls12381Provider(); + + private PureJavaBls12381Provider() {} + + @Override + public String id() { + return "zeroj-bls12381-pure-java"; + } + + @Override + public G1Point g1Generator() { + return Bls12381Generators.G1; + } + + @Override + public G2Point g2Generator() { + return Bls12381Generators.G2; + } + + @Override + public G1Point g1ScalarMul(G1Point point, BigInteger scalar) { + return Bls12381Codecs.requireValid(point) + .scalarMul(reduceScalar(scalar)); + } + + @Override + public G2Point g2ScalarMul(G2Point point, BigInteger scalar) { + return Bls12381Codecs.requireValid(point) + .scalarMul(reduceScalar(scalar)); + } + + @Override + public G1Point g1SecretScalarMul(G1Point point, BigInteger scalar) { + return Bls12381Codecs.requireValid(point) + .ctScalarMul(reduceScalar(scalar)); + } + + @Override + public G2Point g2SecretScalarMul(G2Point point, BigInteger scalar) { + return Bls12381Codecs.requireValid(point) + .ctScalarMul(reduceScalar(scalar)); + } + + @Override + public boolean pairingProductIsIdentity(G1Point[] g1Points, G2Point[] g2Points) { + Objects.requireNonNull(g1Points, "g1Points required"); + Objects.requireNonNull(g2Points, "g2Points required"); + for (G1Point point : g1Points) { + Bls12381Codecs.requireValid(point); + } + for (G2Point point : g2Points) { + Bls12381Codecs.requireValid(point); + } + return BLS12381Pairing.pairingCheck(g1Points, g2Points); + } + + private static BigInteger reduceScalar(BigInteger scalar) { + return Objects.requireNonNull(scalar, "scalar required") + .mod(Bls12381Generators.SCALAR_FIELD_ORDER); + } +} diff --git a/zeroj-bls12381/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bls12381/reflect-config.json b/zeroj-bls12381/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bls12381/reflect-config.json new file mode 100644 index 0000000..b787474 --- /dev/null +++ b/zeroj-bls12381/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bls12381/reflect-config.json @@ -0,0 +1,110 @@ +[ + { + "name": "com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.Bls12381Hash", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.ec.G1Point", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.ec.G2Point", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381$AffineG1", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381$AffineG2", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.Fp", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.Fp2", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.Fp6", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.Fp12", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.MontFp381", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.MontFp2_381", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.field.MontFr381", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.bls12381.spi.PureJavaBls12381Provider", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + } +] diff --git a/zeroj-bls12381/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bls12381/resource-config.json b/zeroj-bls12381/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bls12381/resource-config.json new file mode 100644 index 0000000..bf2f940 --- /dev/null +++ b/zeroj-bls12381/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-bls12381/resource-config.json @@ -0,0 +1,5 @@ +{ + "resources": { + "includes": [] + } +} diff --git a/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381CodecsTest.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381CodecsTest.java new file mode 100644 index 0000000..1f1d9eb --- /dev/null +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381CodecsTest.java @@ -0,0 +1,135 @@ +package com.bloxbean.cardano.zeroj.bls12381; + +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; + +import static org.junit.jupiter.api.Assertions.*; + +class Bls12381CodecsTest { + + @Test + void g1Uncompressed_roundTripsGenerator() { + byte[] encoded = Bls12381Codecs.g1ToUncompressed(Bls12381Generators.G1); + assertEquals(Bls12381Codecs.G1_UNCOMPRESSED_BYTES, encoded.length); + assertEquals(Bls12381Generators.G1, Bls12381Codecs.g1FromUncompressed(encoded)); + } + + @Test + void g1Compressed_roundTripsGenerator() { + byte[] encoded = Bls12381Codecs.g1ToCompressed(Bls12381Generators.G1); + assertEquals(Bls12381Codecs.G1_COMPRESSED_BYTES, encoded.length); + assertEquals(Bls12381Generators.G1, Bls12381Codecs.g1FromCompressed(encoded)); + } + + @Test + void g1Compressed_generatorMatchesZcashEncoding() { + assertArrayEquals( + hexToBytes("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"), + Bls12381Codecs.g1ToCompressed(Bls12381Generators.G1)); + } + + @Test + void g2Uncompressed_roundTripsGenerator() { + byte[] encoded = Bls12381Codecs.g2ToUncompressed(Bls12381Generators.G2); + assertEquals(Bls12381Codecs.G2_UNCOMPRESSED_BYTES, encoded.length); + assertEquals(Bls12381Generators.G2, Bls12381Codecs.g2FromUncompressed(encoded)); + } + + @Test + void g2Compressed_roundTripsGenerator() { + byte[] encoded = Bls12381Codecs.g2ToCompressed(Bls12381Generators.G2); + assertEquals(Bls12381Codecs.G2_COMPRESSED_BYTES, encoded.length); + assertEquals(Bls12381Generators.G2, Bls12381Codecs.g2FromCompressed(encoded)); + } + + @Test + void g2Compressed_generatorMatchesZcashEncoding() { + assertArrayEquals( + hexToBytes("93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"), + Bls12381Codecs.g2ToCompressed(Bls12381Generators.G2)); + } + + @Test + void infinity_roundTrips() { + assertEquals(G1Point.INFINITY, Bls12381Codecs.g1FromUncompressed(Bls12381Codecs.g1ToUncompressed(G1Point.INFINITY))); + assertEquals(G2Point.INFINITY, Bls12381Codecs.g2FromUncompressed(Bls12381Codecs.g2ToUncompressed(G2Point.INFINITY))); + assertEquals(G1Point.INFINITY, Bls12381Codecs.g1FromCompressed(Bls12381Codecs.g1ToCompressed(G1Point.INFINITY))); + assertEquals(G2Point.INFINITY, Bls12381Codecs.g2FromCompressed(Bls12381Codecs.g2ToCompressed(G2Point.INFINITY))); + } + + @Test + void scalarToLittleEndian32_encodesOne() { + byte[] scalar = Bls12381Codecs.scalarToLittleEndian32(BigInteger.ONE); + assertEquals(1, scalar[0]); + for (int i = 1; i < scalar.length; i++) { + assertEquals(0, scalar[i]); + } + } + + @Test + void scalarToLittleEndian32Reduced_reducesSignedScalars() { + assertArrayEquals( + Bls12381Codecs.scalarToLittleEndian32(BigInteger.ZERO), + Bls12381Codecs.scalarToLittleEndian32Reduced(Bls12381Generators.SCALAR_FIELD_ORDER)); + assertArrayEquals( + Bls12381Codecs.scalarToLittleEndian32(Bls12381Generators.SCALAR_FIELD_ORDER.subtract(BigInteger.ONE)), + Bls12381Codecs.scalarToLittleEndian32Reduced(BigInteger.valueOf(-1))); + } + + @Test + void g1Uncompressed_rejectsOffCurvePoint() { + byte[] encoded = Bls12381Codecs.g1ToUncompressed(Bls12381Generators.G1); + encoded[encoded.length - 1] ^= 1; + + assertThrows(IllegalArgumentException.class, () -> Bls12381Codecs.g1FromUncompressed(encoded)); + assertFalse(Bls12381Codecs.g1FromUncompressedUnchecked(encoded).isOnCurve()); + } + + @Test + void g1Codecs_rejectPrimeFieldTorsionPoint() { + var torsion = new G1Point(Fp.ZERO, Fp.of(2)); + assertTrue(torsion.isOnCurve()); + assertFalse(torsion.isInSubgroup()); + + assertThrows(IllegalArgumentException.class, + () -> Bls12381Codecs.g1FromUncompressed(Bls12381Codecs.g1ToUncompressed(torsion))); + assertThrows(IllegalArgumentException.class, + () -> Bls12381Codecs.g1FromCompressed(Bls12381Codecs.g1ToCompressed(torsion))); + } + + @Test + void g2Uncompressed_rejectsOffCurvePoint() { + byte[] encoded = Bls12381Codecs.g2ToUncompressed(Bls12381Generators.G2); + encoded[encoded.length - 1] ^= 1; + + assertThrows(IllegalArgumentException.class, () -> Bls12381Codecs.g2FromUncompressed(encoded)); + assertFalse(Bls12381Codecs.g2FromUncompressedUnchecked(encoded).isOnCurve()); + } + + @Test + void g2Codecs_rejectTwistTorsionPoint() { + var x = Fp2.of(Fp.ZERO, Fp.ONE); + var y = x.square().mul(x).add(Fp2.of(Fp.of(4), Fp.of(4))).sqrt().orElseThrow(); + var torsion = new G2Point(x, y); + assertTrue(torsion.isOnCurve()); + assertFalse(torsion.isInSubgroup()); + + assertThrows(IllegalArgumentException.class, + () -> Bls12381Codecs.g2FromUncompressed(Bls12381Codecs.g2ToUncompressed(torsion))); + assertThrows(IllegalArgumentException.class, + () -> Bls12381Codecs.g2FromCompressed(Bls12381Codecs.g2ToCompressed(torsion))); + } + + private static byte[] hexToBytes(String hex) { + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } +} diff --git a/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381HashToCurveTest.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381HashToCurveTest.java new file mode 100644 index 0000000..057b366 --- /dev/null +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/Bls12381HashToCurveTest.java @@ -0,0 +1,128 @@ +package com.bloxbean.cardano.zeroj.bls12381; + +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +class Bls12381HashToCurveTest { + private static final byte[] G1_RO_DST = + "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_".getBytes(StandardCharsets.US_ASCII); + private static final byte[] G2_RO_DST = + "QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_".getBytes(StandardCharsets.US_ASCII); + private static final byte[] G1_NU_DST = + "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_NU_".getBytes(StandardCharsets.US_ASCII); + private static final byte[] G2_NU_DST = + "QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_NU_".getBytes(StandardCharsets.US_ASCII); + + @Test + void hashToG1_matchesRfc9380EmptyMessageVector() { + assertG1( + Bls12381Hash.hashToG1(new byte[0], G1_RO_DST), + "052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a1", + "08ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265"); + } + + @Test + void hashToG1_matchesRfc9380AbcVector() { + assertG1( + Bls12381Hash.hashToG1("abc".getBytes(StandardCharsets.US_ASCII), G1_RO_DST), + "03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903", + "0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d"); + } + + @Test + void encodeToG1_matchesRfc9380AbcVector() { + assertG1( + Bls12381Hash.encodeToG1("abc".getBytes(StandardCharsets.US_ASCII), G1_NU_DST), + "009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d", + "1532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c"); + } + + @Test + void expandMessageXmd_allowsZeroLengthOutput() { + assertArrayEquals(new byte[0], + Bls12381Hash.expandMessageXmdSha256("abc".getBytes(StandardCharsets.US_ASCII), G1_RO_DST, 0)); + } + + @Test + void expandMessageXmd_usesOversizeDstReduction() { + byte[] msg = "abc".getBytes(StandardCharsets.US_ASCII); + byte[] oversizedDst = ("QUUX-V01-CS02-with-expander-SHA256-128-long-DST-" + "1".repeat(256)) + .getBytes(StandardCharsets.US_ASCII); + byte[] reducedDst = sha256(concat("H2C-OVERSIZE-DST-".getBytes(StandardCharsets.US_ASCII), oversizedDst)); + + assertArrayEquals( + Bls12381Hash.expandMessageXmdSha256(msg, reducedDst, 32), + Bls12381Hash.expandMessageXmdSha256(msg, oversizedDst, 32)); + } + + @Test + void hashToG2_matchesRfc9380EmptyMessageVector() { + assertG2( + Bls12381Hash.hashToG2(new byte[0], G2_RO_DST), + "0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a", + "05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d", + "0503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92", + "12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d6"); + } + + @Test + void hashToG2_matchesRfc9380AbcVector() { + assertG2( + Bls12381Hash.hashToG2("abc".getBytes(StandardCharsets.US_ASCII), G2_RO_DST), + "02c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6", + "139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd8", + "1787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48", + "00aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd16"); + } + + @Test + void encodeToG2_matchesRfc9380AbcVector() { + assertG2( + Bls12381Hash.encodeToG2("abc".getBytes(StandardCharsets.US_ASCII), G2_NU_DST), + "108ed59fd9fae381abfd1d6bce2fd2fa220990f0f837fa30e0f27914ed6e1454db0d1ee957b219f61da6ff8be0d6441f", + "0296238ea82c6d4adb3c838ee3cb2346049c90b96d602d7bb1b469b905c9228be25c627bffee872def773d5b2a2eb57d", + "033f90f6057aadacae7963b0a0b379dd46750c1c94a6357c99b65f63b79e321ff50fe3053330911c56b6ceea08fee656", + "153606c417e59fb331b7ae6bce4fbf7c5190c33ce9402b5ebe2b70e44fca614f3f1382a3625ed5493843d0b0a652fc3f"); + } + + private static void assertG1(G1Point point, String x, String y) { + assertTrue(point.isValid()); + assertEquals(fp(x), point.x()); + assertEquals(fp(y), point.y()); + } + + private static void assertG2(G2Point point, String xc0, String xc1, String yc0, String yc1) { + assertTrue(point.isValid()); + assertEquals(Fp2.of(fp(xc0), fp(xc1)), point.x()); + assertEquals(Fp2.of(fp(yc0), fp(yc1)), point.y()); + } + + private static Fp fp(String hex) { + return Fp.of(new BigInteger(hex, 16)); + } + + private static byte[] sha256(byte[] input) { + try { + return MessageDigest.getInstance("SHA-256").digest(input); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + private static byte[] concat(byte[] left, byte[] right) { + byte[] out = Arrays.copyOf(left, left.length + right.length); + System.arraycopy(right, 0, out, left.length, right.length); + return out; + } +} diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG1BLS381Test.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG1BLS381Test.java similarity index 89% rename from zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG1BLS381Test.java rename to zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG1BLS381Test.java index 676ea34..71fbc77 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG1BLS381Test.java +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG1BLS381Test.java @@ -1,6 +1,6 @@ -package com.bloxbean.cardano.zeroj.crypto.ec; +package com.bloxbean.cardano.zeroj.bls12381.ec; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -87,9 +87,9 @@ void ctScalarMul_byOrder_returnsInfinity() { @Test void crossValidate_withVerifierG1Point() { var g = JacobianG1BLS381.GENERATOR.toAffine(); - var verG1 = new com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.G1Point( - com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.of(g.xBigInt()), - com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.of(g.yBigInt())); + var verG1 = new com.bloxbean.cardano.zeroj.bls12381.ec.G1Point( + com.bloxbean.cardano.zeroj.bls12381.field.Fp.of(g.xBigInt()), + com.bloxbean.cardano.zeroj.bls12381.field.Fp.of(g.yBigInt())); // Double using verifier var verDbl = verG1.doublePoint(); diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG2BLS381Test.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG2BLS381Test.java similarity index 97% rename from zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG2BLS381Test.java rename to zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG2BLS381Test.java index f913e04..707b66f 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/ec/JacobianG2BLS381Test.java +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/ec/JacobianG2BLS381Test.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.ec; +package com.bloxbean.cardano.zeroj.bls12381.ec; import org.junit.jupiter.api.Test; diff --git a/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp2Test.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp2Test.java new file mode 100644 index 0000000..1eac0cb --- /dev/null +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/Fp2Test.java @@ -0,0 +1,18 @@ +package com.bloxbean.cardano.zeroj.bls12381.field; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class Fp2Test { + + @Test + void sqrtMinusOne_usesFp2SpecialCaseBranch() { + Fp2 minusOne = Fp2.ONE.neg(); + + Fp2 root = minusOne.sqrt().orElseThrow(); + + assertEquals(minusOne, root.square()); + assertTrue(root.equals(Fp2.of(Fp.ZERO, Fp.ONE)) || root.equals(Fp2.of(Fp.ZERO, Fp.ONE.neg()))); + } +} diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp2_381Test.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp2_381Test.java similarity index 83% rename from zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp2_381Test.java rename to zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp2_381Test.java index 8c4c395..83dcc5a 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp2_381Test.java +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp2_381Test.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.RepeatedTest; @@ -93,13 +93,13 @@ void crossValidate_withVerifierFp2() { var montA = MontFp2_381.of(a0, a1); var montB = MontFp2_381.of(b0, b1); - var verFp = com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.class; - var verA = com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp2.of( - com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.of(a0), - com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.of(a1)); - var verB = com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp2.of( - com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.of(b0), - com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.of(b1)); + var verFp = com.bloxbean.cardano.zeroj.bls12381.field.Fp.class; + var verA = com.bloxbean.cardano.zeroj.bls12381.field.Fp2.of( + com.bloxbean.cardano.zeroj.bls12381.field.Fp.of(a0), + com.bloxbean.cardano.zeroj.bls12381.field.Fp.of(a1)); + var verB = com.bloxbean.cardano.zeroj.bls12381.field.Fp2.of( + com.bloxbean.cardano.zeroj.bls12381.field.Fp.of(b0), + com.bloxbean.cardano.zeroj.bls12381.field.Fp.of(b1)); var montResult = montA.mul(montB); var verResult = verA.mul(verB); diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp381Test.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp381Test.java similarity index 93% rename from zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp381Test.java rename to zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp381Test.java index 12f41b5..4e6f576 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFp381Test.java +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFp381Test.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.RepeatedTest; @@ -114,15 +114,15 @@ void square_random_matchesMul() { @Test void crossValidate_withVerifierFp() { // Cross-validate against the verifier's BigInteger-based Fp - var vFp = com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp.class; + var vFp = com.bloxbean.cardano.zeroj.bls12381.field.Fp.class; BigInteger a = new BigInteger("123456789012345678901234567890123456789012345678901234567890"); BigInteger b = new BigInteger("987654321098765432109876543210987654321098765432109876543210"); var montA = MontFp381.fromBigInteger(a); var montB = MontFp381.fromBigInteger(b); - var verA = new com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp(a); - var verB = new com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.Fp(b); + var verA = new com.bloxbean.cardano.zeroj.bls12381.field.Fp(a); + var verB = new com.bloxbean.cardano.zeroj.bls12381.field.Fp(b); assertEquals(verA.mul(verB).value(), montA.mul(montB).toBigInteger(), "mul cross-validate"); assertEquals(verA.add(verB).value(), montA.add(montB).toBigInteger(), "add cross-validate"); diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFr381Test.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFr381Test.java similarity index 98% rename from zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFr381Test.java rename to zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFr381Test.java index e28f972..67c155e 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/field/MontFr381Test.java +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/field/MontFr381Test.java @@ -1,4 +1,4 @@ -package com.bloxbean.cardano.zeroj.crypto.field; +package com.bloxbean.cardano.zeroj.bls12381.field; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.RepeatedTest; diff --git a/zeroj-verifier-groth16/src/test/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/BLS12381PairingTest.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/pairing/BLS12381PairingTest.java similarity index 94% rename from zeroj-verifier-groth16/src/test/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/BLS12381PairingTest.java rename to zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/pairing/BLS12381PairingTest.java index 6ed3acb..ff02cd9 100644 --- a/zeroj-verifier-groth16/src/test/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/BLS12381PairingTest.java +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/pairing/BLS12381PairingTest.java @@ -1,6 +1,7 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381; +package com.bloxbean.cardano.zeroj.bls12381.pairing; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; import org.junit.jupiter.api.Test; import java.math.BigInteger; diff --git a/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/spi/PureJavaBls12381ProviderTest.java b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/spi/PureJavaBls12381ProviderTest.java new file mode 100644 index 0000000..e1f3c4b --- /dev/null +++ b/zeroj-bls12381/src/test/java/com/bloxbean/cardano/zeroj/bls12381/spi/PureJavaBls12381ProviderTest.java @@ -0,0 +1,107 @@ +package com.bloxbean.cardano.zeroj.bls12381.spi; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp; +import com.bloxbean.cardano.zeroj.bls12381.field.Fp2; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; + +import static org.junit.jupiter.api.Assertions.*; + +class PureJavaBls12381ProviderTest { + + private final Bls12381Provider provider = Bls12381Providers.pureJava(); + + @Test + void exposesStandardGenerators() { + assertEquals(Bls12381Generators.G1, provider.g1Generator()); + assertEquals(Bls12381Generators.G2, provider.g2Generator()); + } + + @Test + void exposesGroupOperationsAndCodecs() { + assertEquals(G1Point.INFINITY, provider.g1Identity()); + assertEquals(G2Point.INFINITY, provider.g2Identity()); + assertEquals(Bls12381Generators.G1.add(Bls12381Generators.G1), provider.g1Add(Bls12381Generators.G1, Bls12381Generators.G1)); + assertEquals(Bls12381Generators.G2.add(Bls12381Generators.G2), provider.g2Add(Bls12381Generators.G2, Bls12381Generators.G2)); + assertEquals(Bls12381Generators.G1.negate(), provider.g1Negate(Bls12381Generators.G1)); + assertEquals(Bls12381Generators.G2.negate(), provider.g2Negate(Bls12381Generators.G2)); + assertEquals(Bls12381Generators.G1, provider.g1FromCompressed(provider.g1ToCompressed(Bls12381Generators.G1))); + assertEquals(Bls12381Generators.G2, provider.g2FromCompressed(provider.g2ToCompressed(Bls12381Generators.G2))); + assertEquals(Bls12381Generators.G1, provider.g1FromUncompressed(provider.g1ToUncompressed(Bls12381Generators.G1))); + assertEquals(Bls12381Generators.G2, provider.g2FromUncompressed(provider.g2ToUncompressed(Bls12381Generators.G2))); + } + + @Test + void scalarMul_matchesAffineImplementation() { + assertEquals( + Bls12381Generators.G1.scalarMul(BigInteger.valueOf(42)), + provider.g1ScalarMulGenerator(BigInteger.valueOf(42))); + assertEquals( + Bls12381Generators.G2.scalarMul(BigInteger.valueOf(42)), + provider.g2ScalarMulGenerator(BigInteger.valueOf(42))); + } + + @Test + void scalarMul_reducesScalarsModuloGroupOrder() { + var r = Bls12381Generators.SCALAR_FIELD_ORDER; + + assertEquals(G1Point.INFINITY, provider.g1ScalarMulGenerator(r)); + assertEquals(G2Point.INFINITY, provider.g2ScalarMulGenerator(r)); + assertEquals(Bls12381Generators.G1, provider.g1ScalarMulGenerator(r.add(BigInteger.ONE))); + assertEquals(Bls12381Generators.G2, provider.g2ScalarMulGenerator(r.add(BigInteger.ONE))); + assertEquals(Bls12381Generators.G1.negate(), provider.g1ScalarMulGenerator(BigInteger.valueOf(-1))); + assertEquals(Bls12381Generators.G2.negate(), provider.g2ScalarMulGenerator(BigInteger.valueOf(-1))); + } + + @Test + void secretScalarMul_matchesPublicScalarMul() { + var scalar = new BigInteger("12345678901234567890123456789012345678901234567890"); + + assertEquals(provider.g1ScalarMulGenerator(scalar), provider.g1SecretScalarMulGenerator(scalar)); + assertEquals(provider.g2ScalarMulGenerator(scalar), provider.g2SecretScalarMulGenerator(scalar)); + } + + @Test + void rejectsInvalidPoints() { + assertThrows(IllegalArgumentException.class, + () -> provider.g1ScalarMul(new G1Point(Fp.ZERO, Fp.ZERO), BigInteger.ONE)); + assertThrows(IllegalArgumentException.class, + () -> provider.g2ScalarMul(new G2Point(Fp2.ZERO, Fp2.ZERO), BigInteger.ONE)); + assertThrows(IllegalArgumentException.class, + () -> provider.g1SecretScalarMul(new G1Point(Fp.ZERO, Fp.ZERO), BigInteger.ONE)); + assertThrows(IllegalArgumentException.class, + () -> provider.g2SecretScalarMul(new G2Point(Fp2.ZERO, Fp2.ZERO), BigInteger.ONE)); + } + + @Test + void hashToScalar_isDeterministicAndInRange() { + byte[] msg = "abc".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + byte[] dst = "ZEROJ-BLS12381-SCALAR".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + + BigInteger scalar = provider.hashToScalar(msg, dst); + assertEquals(scalar, provider.hashToScalar(msg, dst)); + assertTrue(scalar.signum() >= 0); + assertTrue(scalar.compareTo(Bls12381Generators.SCALAR_FIELD_ORDER) < 0); + } + + @Test + void hashToCurve_returnsValidSubgroupPoints() { + byte[] msg = "abc".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + byte[] g1Dst = "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + byte[] g2Dst = "QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_".getBytes(java.nio.charset.StandardCharsets.US_ASCII); + + assertTrue(provider.g1HashToCurve(msg, g1Dst).isValid()); + assertTrue(provider.g2HashToCurve(msg, g2Dst).isValid()); + } + + @Test + void pairingProductIdentityForGeneratorAndNegation() { + assertTrue(provider.pairingProductIsIdentity( + new G1Point[]{Bls12381Generators.G1, Bls12381Generators.G1.negate()}, + new G2Point[]{Bls12381Generators.G2, Bls12381Generators.G2})); + } +} diff --git a/zeroj-blst/README.md b/zeroj-blst/README.md index 56b25f2..68e86b4 100644 --- a/zeroj-blst/README.md +++ b/zeroj-blst/README.md @@ -2,12 +2,13 @@ BLS12-381 cryptographic operations via the [blst](https://github.com/supranational/blst) native library. -This module wraps the `blst-java` library to provide BLS12-381 pairing operations used by `zeroj-verifier-groth16` for high-performance Groth16 verification. BLS12-381 is the curve used by Cardano's Plutus V3 native BLS primitives. +This module wraps the `blst-java` library to provide BLS12-381 pairing operations used by `zeroj-verifier-groth16` for high-performance Groth16 verification. It also exposes an explicit `Bls12381Provider` implementation for protocols such as BBS that can opt in to native-backed BLS12-381 operations. BLS12-381 is the curve used by Cardano's Plutus V3 native BLS primitives. ## Key Types | Type | Description | |------|-------------| +| `BlstBls12381Provider` | Explicit native-backed `Bls12381Provider` implementation | | `BlstPairing` | Wrapper for blst FFM pairing operations (multi-pairing, point validation) | ## Why blst? @@ -28,3 +29,12 @@ dependencies { ``` Most users don't depend on this module directly — it is pulled in transitively by `zeroj-verifier-groth16`. + +Provider selection remains explicit: + +```java +var bls = com.bloxbean.cardano.zeroj.blst.BlstBls12381Provider.createDefault(); +var bbs = com.bloxbean.cardano.zeroj.bbs.BbsService.withBlsProvider( + com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite.BLS12381_SHA256, + bls); +``` diff --git a/zeroj-blst/build.gradle b/zeroj-blst/build.gradle index f18e3a2..cf1ba69 100644 --- a/zeroj-blst/build.gradle +++ b/zeroj-blst/build.gradle @@ -6,6 +6,7 @@ description = 'ZeroJ BLS12-381 operations via blst native library (FFM/JNI)' dependencies { api project(':zeroj-api') + api project(':zeroj-bls12381') api 'foundation.icon:blst-java:0.3.2' } diff --git a/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/BlstBls12381Provider.java b/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/BlstBls12381Provider.java new file mode 100644 index 0000000..e85e183 --- /dev/null +++ b/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/BlstBls12381Provider.java @@ -0,0 +1,171 @@ +package com.bloxbean.cardano.zeroj.blst; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; +import supranational.blst.P1; +import supranational.blst.P1_Affine; +import supranational.blst.P2; +import supranational.blst.P2_Affine; +import supranational.blst.PT; + +import java.math.BigInteger; +import java.util.Objects; + +/** + * BLS12-381 provider backed by the blst native library. + */ +public final class BlstBls12381Provider implements Bls12381Provider { + private static final BlstBls12381Provider INSTANCE = new BlstBls12381Provider(); + + private BlstBls12381Provider() {} + + public static BlstBls12381Provider createDefault() { + return INSTANCE; + } + + @Override + public String id() { + return "zeroj-bls12381-blst"; + } + + @Override + public G1Point g1Generator() { + return decodeG1(P1.generator().serialize()); + } + + @Override + public G2Point g2Generator() { + return decodeG2(P2.generator().serialize()); + } + + @Override + public G1Point g1Add(G1Point left, G1Point right) { + left = Bls12381Codecs.requireValid(left); + right = Bls12381Codecs.requireValid(right); + if (left.isInfinity()) { + return right; + } + if (right.isInfinity()) { + return left; + } + P1 result = new P1(encodeG1(left)); + result.add(new P1_Affine(encodeG1(right))); + return decodeG1(result.serialize()); + } + + @Override + public G2Point g2Add(G2Point left, G2Point right) { + left = Bls12381Codecs.requireValid(left); + right = Bls12381Codecs.requireValid(right); + if (left.isInfinity()) { + return right; + } + if (right.isInfinity()) { + return left; + } + P2 result = new P2(encodeG2(left)); + result.add(new P2_Affine(encodeG2(right))); + return decodeG2(result.serialize()); + } + + @Override + public G1Point g1Negate(G1Point point) { + point = Bls12381Codecs.requireValid(point); + if (point.isInfinity()) { + return point; + } + P1 result = new P1(encodeG1(point)); + result.neg(); + return decodeG1(result.serialize()); + } + + @Override + public G2Point g2Negate(G2Point point) { + point = Bls12381Codecs.requireValid(point); + if (point.isInfinity()) { + return point; + } + P2 result = new P2(encodeG2(point)); + result.neg(); + return decodeG2(result.serialize()); + } + + @Override + public G1Point g1ScalarMul(G1Point point, BigInteger scalar) { + point = Bls12381Codecs.requireValid(point); + BigInteger reduced = reduceScalar(scalar); + if (point.isInfinity() || reduced.signum() == 0) { + return G1Point.INFINITY; + } + P1 result = new P1(encodeG1(point)); + result.mult(reduced); + return decodeG1(result.serialize()); + } + + @Override + public G2Point g2ScalarMul(G2Point point, BigInteger scalar) { + point = Bls12381Codecs.requireValid(point); + BigInteger reduced = reduceScalar(scalar); + if (point.isInfinity() || reduced.signum() == 0) { + return G2Point.INFINITY; + } + P2 result = new P2(encodeG2(point)); + result.mult(reduced); + return decodeG2(result.serialize()); + } + + @Override + public G1Point g1SecretScalarMul(G1Point point, BigInteger scalar) { + return g1ScalarMul(point, scalar); + } + + @Override + public G2Point g2SecretScalarMul(G2Point point, BigInteger scalar) { + return g2ScalarMul(point, scalar); + } + + @Override + public boolean pairingProductIsIdentity(G1Point[] g1Points, G2Point[] g2Points) { + Objects.requireNonNull(g1Points, "g1Points required"); + Objects.requireNonNull(g2Points, "g2Points required"); + if (g1Points.length != g2Points.length) { + throw new IllegalArgumentException("Pairing point arrays must have the same length"); + } + + PT accumulated = null; + for (int i = 0; i < g1Points.length; i++) { + G1Point g1 = Bls12381Codecs.requireValid(g1Points[i]); + G2Point g2 = Bls12381Codecs.requireValid(g2Points[i]); + if (g1.isInfinity() || g2.isInfinity()) { + continue; + } + PT term = new PT(new P1_Affine(encodeG1(g1)), new P2_Affine(encodeG2(g2))); + accumulated = accumulated == null ? term : BlstPairing.mulMlResult(accumulated, term); + } + return accumulated == null || BlstPairing.finalVerify(accumulated); + } + + private static byte[] encodeG1(G1Point point) { + return Bls12381Codecs.g1ToUncompressed(point); + } + + private static byte[] encodeG2(G2Point point) { + return Bls12381Codecs.g2ToUncompressed(point); + } + + private static G1Point decodeG1(byte[] bytes) { + return Bls12381Codecs.g1FromUncompressed(bytes); + } + + private static G2Point decodeG2(byte[] bytes) { + return Bls12381Codecs.g2FromUncompressed(bytes); + } + + private static BigInteger reduceScalar(BigInteger scalar) { + return Objects.requireNonNull(scalar, "scalar required") + .mod(Bls12381Generators.SCALAR_FIELD_ORDER); + } +} diff --git a/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/package-info.java b/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/package-info.java index 89a20fc..4294a92 100644 --- a/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/package-info.java +++ b/zeroj-blst/src/main/java/com/bloxbean/cardano/zeroj/blst/package-info.java @@ -1,4 +1,4 @@ /** - * BLS12-381 elliptic curve operations via the blst native library. + * BLS12-381 provider and pairing operations via the blst native library. */ package com.bloxbean.cardano.zeroj.blst; diff --git a/zeroj-blst/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-blst/reflect-config.json b/zeroj-blst/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-blst/reflect-config.json index fe51488..1ee4733 100644 --- a/zeroj-blst/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-blst/reflect-config.json +++ b/zeroj-blst/src/main/resources/META-INF/native-image/com.bloxbean.cardano/zeroj-blst/reflect-config.json @@ -1 +1,14 @@ -[] +[ + { + "name": "com.bloxbean.cardano.zeroj.blst.BlstBls12381Provider", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "com.bloxbean.cardano.zeroj.blst.BlstPairing", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + } +] diff --git a/zeroj-blst/src/test/java/com/bloxbean/cardano/zeroj/blst/BlstBls12381ProviderTest.java b/zeroj-blst/src/test/java/com/bloxbean/cardano/zeroj/blst/BlstBls12381ProviderTest.java new file mode 100644 index 0000000..6e005ae --- /dev/null +++ b/zeroj-blst/src/test/java/com/bloxbean/cardano/zeroj/blst/BlstBls12381ProviderTest.java @@ -0,0 +1,100 @@ +package com.bloxbean.cardano.zeroj.blst; + +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Codecs; +import com.bloxbean.cardano.zeroj.bls12381.Bls12381Generators; +import com.bloxbean.cardano.zeroj.bls12381.ec.G1Point; +import com.bloxbean.cardano.zeroj.bls12381.ec.G2Point; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Provider; +import com.bloxbean.cardano.zeroj.bls12381.spi.Bls12381Providers; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class BlstBls12381ProviderTest { + private static final Bls12381Provider PURE = Bls12381Providers.pureJava(); + private static final Bls12381Provider BLST = BlstBls12381Provider.createDefault(); + private static final List SCALARS = List.of( + BigInteger.ZERO, + BigInteger.ONE, + BigInteger.TWO, + BigInteger.valueOf(17), + Bls12381Generators.SCALAR_FIELD_ORDER.subtract(BigInteger.ONE), + Bls12381Generators.SCALAR_FIELD_ORDER, + Bls12381Generators.SCALAR_FIELD_ORDER.add(BigInteger.ONE), + BigInteger.valueOf(-1)); + + @Test + void generatorsMatchPureJavaBytes() { + assertEquals("zeroj-bls12381-blst", BLST.id()); + assertArrayEquals( + Bls12381Codecs.g1ToUncompressed(PURE.g1Generator()), + Bls12381Codecs.g1ToUncompressed(BLST.g1Generator())); + assertArrayEquals( + Bls12381Codecs.g2ToUncompressed(PURE.g2Generator()), + Bls12381Codecs.g2ToUncompressed(BLST.g2Generator())); + assertArrayEquals( + Bls12381Codecs.g1ToCompressed(PURE.g1Generator()), + BLST.g1ToCompressed(BLST.g1Generator())); + assertArrayEquals( + Bls12381Codecs.g2ToCompressed(PURE.g2Generator()), + BLST.g2ToCompressed(BLST.g2Generator())); + } + + @Test + void g1ArithmeticMatchesPureJava() { + G1Point g = BLST.g1Generator(); + G1Point hashed = BLST.g1HashToCurve( + "blst-g1-provider-test".getBytes(StandardCharsets.US_ASCII), + "ZEROJ-BLS12381-BLST-G1-TEST".getBytes(StandardCharsets.US_ASCII)); + + assertEquals(PURE.g1Add(PURE.g1Generator(), hashed), BLST.g1Add(g, hashed)); + assertEquals(PURE.g1Negate(hashed), BLST.g1Negate(hashed)); + assertEquals(hashed, BLST.g1Add(hashed, BLST.g1Identity())); + assertEquals(BLST.g1Identity(), BLST.g1Add(hashed, BLST.g1Negate(hashed))); + for (BigInteger scalar : SCALARS) { + assertEquals(PURE.g1ScalarMul(hashed, scalar), BLST.g1ScalarMul(hashed, scalar), scalar.toString()); + assertEquals(BLST.g1ScalarMul(hashed, scalar), BLST.g1SecretScalarMul(hashed, scalar), scalar.toString()); + } + } + + @Test + void g2ArithmeticMatchesPureJava() { + G2Point g = BLST.g2Generator(); + G2Point hashed = BLST.g2HashToCurve( + "blst-g2-provider-test".getBytes(StandardCharsets.US_ASCII), + "ZEROJ-BLS12381-BLST-G2-TEST".getBytes(StandardCharsets.US_ASCII)); + + assertEquals(PURE.g2Add(PURE.g2Generator(), hashed), BLST.g2Add(g, hashed)); + assertEquals(PURE.g2Negate(hashed), BLST.g2Negate(hashed)); + assertEquals(hashed, BLST.g2Add(hashed, BLST.g2Identity())); + assertEquals(BLST.g2Identity(), BLST.g2Add(hashed, BLST.g2Negate(hashed))); + for (BigInteger scalar : SCALARS) { + assertEquals(PURE.g2ScalarMul(hashed, scalar), BLST.g2ScalarMul(hashed, scalar), scalar.toString()); + assertEquals(BLST.g2ScalarMul(hashed, scalar), BLST.g2SecretScalarMul(hashed, scalar), scalar.toString()); + } + } + + @Test + void pairingProductMatchesPureJava() { + G1Point g1 = BLST.g1Generator(); + G2Point g2 = BLST.g2Generator(); + G1Point negG1 = BLST.g1Negate(g1); + + assertTrue(BLST.pairingProductIsIdentity(new G1Point[]{g1, negG1}, new G2Point[]{g2, g2})); + assertFalse(BLST.pairingProductIsIdentity(new G1Point[]{g1}, new G2Point[]{g2})); + assertTrue(BLST.pairingProductIsIdentity(new G1Point[]{BLST.g1Identity()}, new G2Point[]{g2})); + assertEquals( + PURE.pairingProductIsIdentity(new G1Point[]{g1, negG1}, new G2Point[]{g2, g2}), + BLST.pairingProductIsIdentity(new G1Point[]{g1, negG1}, new G2Point[]{g2, g2})); + } + + @Test + void pairingProductRejectsMismatchedArrayLengths() { + assertThrows(IllegalArgumentException.class, + () -> BLST.pairingProductIsIdentity(new G1Point[]{BLST.g1Generator()}, new G2Point[0])); + } +} diff --git a/zeroj-bom/build.gradle b/zeroj-bom/build.gradle index 64b5261..e8335e0 100644 --- a/zeroj-bom/build.gradle +++ b/zeroj-bom/build.gradle @@ -19,6 +19,8 @@ dependencies { api project(':zeroj-verifier-core') api project(':zeroj-verifier-groth16') api project(':zeroj-verifier-plonk') + api project(':zeroj-bls12381') + api project(':zeroj-bls12381-wasm') api project(':zeroj-blst') api project(':zeroj-submission') api project(':zeroj-ingestion') @@ -29,6 +31,8 @@ dependencies { api project(':zeroj-circuit-lib') api project(':zeroj-prover-gnark') api project(':zeroj-onchain-julc') + api project(':zeroj-bbs') + api project(':zeroj-bbs-wasm') // Incubator modules api project(':zeroj-prover-sidecar') diff --git a/zeroj-crypto/build.gradle b/zeroj-crypto/build.gradle index 64f5a5a..e21ee02 100644 --- a/zeroj-crypto/build.gradle +++ b/zeroj-crypto/build.gradle @@ -5,6 +5,8 @@ plugins { description = 'Pure Java optimized cryptographic primitives — Montgomery field arithmetic, EC operations, FFT, MSM' dependencies { + api project(':zeroj-bls12381') + // PlonK prover uses the Fiat-Shamir transcript from the verifier module implementation project(':zeroj-verifier-plonk') diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProofBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProofBLS381.java index 8c50e5f..f75cac4 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProofBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProofBLS381.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.crypto.groth16; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; /** * A Groth16 proof for BLS12-381: three group elements (A in G1, B in G2, C in G1). diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProverBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProverBLS381.java index 0719b84..71fd9c9 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProverBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProverBLS381.java @@ -1,11 +1,11 @@ package com.bloxbean.cardano.zeroj.crypto.groth16; import com.bloxbean.cardano.zeroj.api.R1CSConstraint; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.msm.PippengerBLS381; import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProvingKeyBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProvingKeyBLS381.java index 39413a7..3eac45d 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProvingKeyBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16ProvingKeyBLS381.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.crypto.groth16; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; /** * Groth16 proving key for BLS12-381. diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/ZkeyImporterBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/ZkeyImporterBLS381.java index 4702f50..468d74a 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/ZkeyImporterBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/groth16/ZkeyImporterBLS381.java @@ -1,10 +1,10 @@ package com.bloxbean.cardano.zeroj.crypto.groth16; import com.bloxbean.cardano.zeroj.api.R1CSConstraint; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp2_381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp2_381; import java.io.IOException; import java.io.InputStream; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381.java index f9b937e..8f29d58 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381.java @@ -1,8 +1,8 @@ package com.bloxbean.cardano.zeroj.crypto.kzg; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.msm.PippengerBLS381; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381.java index 06537f3..2aec28d 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.crypto.msm; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProofBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProofBLS381.java index b215cfb..cca61eb 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProofBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProofBLS381.java @@ -1,6 +1,6 @@ package com.bloxbean.cardano.zeroj.crypto.plonk; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java index 87c675d..b32fc7c 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProverBLS381.java @@ -1,8 +1,8 @@ package com.bloxbean.cardano.zeroj.crypto.plonk; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.kzg.KZGCommitmentBLS381; import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381; import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProvingKeyBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProvingKeyBLS381.java index e2e79fd..141c35d 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProvingKeyBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKProvingKeyBLS381.java @@ -1,8 +1,8 @@ package com.bloxbean.cardano.zeroj.crypto.plonk; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKSetupBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKSetupBLS381.java index ed81c94..324dd00 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKSetupBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKSetupBLS381.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.crypto.plonk; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.kzg.KZGCommitmentBLS381; import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKZkeyImporterBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKZkeyImporterBLS381.java index a1cb93c..976aae4 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKZkeyImporterBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKZkeyImporterBLS381.java @@ -1,10 +1,10 @@ package com.bloxbean.cardano.zeroj.crypto.plonk; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp2_381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp2_381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381; import java.io.IOException; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PtauImporterBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PtauImporterBLS381.java index 19e8d77..a3c9edb 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PtauImporterBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/plonk/PtauImporterBLS381.java @@ -1,9 +1,9 @@ package com.bloxbean.cardano.zeroj.crypto.plonk; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp2_381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp2_381; import java.io.IOException; import java.io.InputStream; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381.java index 276aabc..1ae9993 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381.java @@ -1,6 +1,6 @@ package com.bloxbean.cardano.zeroj.crypto.poly; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/Groth16SetupBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/Groth16SetupBLS381.java index c75a306..77168b2 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/Groth16SetupBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/Groth16SetupBLS381.java @@ -1,11 +1,11 @@ package com.bloxbean.cardano.zeroj.crypto.setup; import com.bloxbean.cardano.zeroj.api.R1CSConstraint; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProvingKeyBLS381; import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauBLS381.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauBLS381.java index 52dcccc..24d1eff 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauBLS381.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/PowersOfTauBLS381.java @@ -1,10 +1,10 @@ package com.bloxbean.cardano.zeroj.crypto.setup; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.plonk.PtauImporterBLS381; import java.math.BigInteger; diff --git a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/SetupCache.java b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/SetupCache.java index fe0b0bd..f564d79 100644 --- a/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/SetupCache.java +++ b/zeroj-crypto/src/main/java/com/bloxbean/cardano/zeroj/crypto/setup/SetupCache.java @@ -1,9 +1,9 @@ package com.bloxbean.cardano.zeroj.crypto.setup; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp2_381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFp381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp2_381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFp381; import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProvingKeyBLS381; import com.bloxbean.cardano.zeroj.crypto.plonk.PtauImporterBLS381; diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381EndToEndTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381EndToEndTest.java index cf288a0..f9961c3 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381EndToEndTest.java +++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381EndToEndTest.java @@ -1,11 +1,13 @@ package com.bloxbean.cardano.zeroj.crypto.groth16; import com.bloxbean.cardano.zeroj.api.R1CSConstraint; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import org.junit.jupiter.api.Test; import java.math.BigInteger; diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381ZkeyEndToEndTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381ZkeyEndToEndTest.java index b90ac63..8fbac2f 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381ZkeyEndToEndTest.java +++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/groth16/Groth16BLS381ZkeyEndToEndTest.java @@ -1,9 +1,11 @@ package com.bloxbean.cardano.zeroj.crypto.groth16; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381Test.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381Test.java index 5a7030e..4db97a6 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381Test.java +++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/kzg/KZGCommitmentBLS381Test.java @@ -1,8 +1,8 @@ package com.bloxbean.cardano.zeroj.crypto.kzg; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.poly.FieldFFTBLS381; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381Test.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381Test.java index 5c3547b..c743d10 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381Test.java +++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/msm/PippengerBLS381Test.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.crypto.msm; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1; import org.junit.jupiter.api.Test; import java.math.BigInteger; diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java index 29d0d17..f33c5c8 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java +++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/plonk/PlonKBLS381EndToEndTest.java @@ -2,11 +2,13 @@ import com.bloxbean.cardano.zeroj.api.CurveId; import com.bloxbean.cardano.zeroj.circuit.CircuitBuilder; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import com.bloxbean.cardano.zeroj.verifier.plonk.FiatShamirTranscript; import org.junit.jupiter.api.Test; diff --git a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381Test.java b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381Test.java index b9080bd..dd08c23 100644 --- a/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381Test.java +++ b/zeroj-crypto/src/test/java/com/bloxbean/cardano/zeroj/crypto/poly/FieldFFTBLS381Test.java @@ -1,6 +1,6 @@ package com.bloxbean.cardano.zeroj.crypto.poly; -import com.bloxbean.cardano.zeroj.crypto.field.MontFr381; +import com.bloxbean.cardano.zeroj.bls12381.field.MontFr381; import org.junit.jupiter.api.Test; import java.math.BigInteger; diff --git a/zeroj-examples/build.gradle b/zeroj-examples/build.gradle index 578f31e..f4852d0 100644 --- a/zeroj-examples/build.gradle +++ b/zeroj-examples/build.gradle @@ -38,6 +38,7 @@ dependencies { implementation project(':zeroj-ccl') implementation project(':zeroj-patterns') implementation project(':zeroj-test-vectors') + implementation project(':zeroj-bbs') // On-chain verifier module (Julc-based Plutus V3 validators) implementation project(':zeroj-onchain-julc') diff --git a/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/bbs/BbsSelectiveDisclosureExample.java b/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/bbs/BbsSelectiveDisclosureExample.java new file mode 100644 index 0000000..742a2ff --- /dev/null +++ b/zeroj-examples/src/main/java/com/bloxbean/cardano/zeroj/examples/bbs/BbsSelectiveDisclosureExample.java @@ -0,0 +1,74 @@ +package com.bloxbean.cardano.zeroj.examples.bbs; + +import com.bloxbean.cardano.zeroj.api.CircuitId; +import com.bloxbean.cardano.zeroj.api.CurveId; +import com.bloxbean.cardano.zeroj.api.ProofSystemId; +import com.bloxbean.cardano.zeroj.api.PublicInputs; +import com.bloxbean.cardano.zeroj.api.VerificationKeyRef; +import com.bloxbean.cardano.zeroj.api.VerificationMaterial; +import com.bloxbean.cardano.zeroj.api.ZkProofEnvelope; +import com.bloxbean.cardano.zeroj.bbs.BbsCiphersuite; +import com.bloxbean.cardano.zeroj.bbs.BbsPresentation; +import com.bloxbean.cardano.zeroj.bbs.BbsPresentationCodec; +import com.bloxbean.cardano.zeroj.bbs.BbsService; +import com.bloxbean.cardano.zeroj.bbs.BbsSignature; +import com.bloxbean.cardano.zeroj.bbs.verifier.BbsZkVerifier; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Minimal BBS selective-disclosure flow. + */ +public final class BbsSelectiveDisclosureExample { + private BbsSelectiveDisclosureExample() {} + + public static void main(String[] args) { + var service = BbsService.pureJava(); + var keyPair = service.keyPair(bytes("01234567890123456789012345678901"), bytes("issuer-key-v1")); + + List messages = List.of( + bytes("given_name:Alice"), + bytes("family_name:Liddell"), + bytes("age_over_18:true"), + bytes("membership:gold")); + byte[] header = bytes("example-credential-v1"); + byte[] presentationHeader = bytes("verifier-session-123"); + + BbsSignature signature = service.sign(keyPair.secretKey(), keyPair.publicKey(), messages, header); + boolean signatureValid = service.verify(keyPair.publicKey(), signature, messages, header); + + BbsPresentation presentation = service.derivePresentation( + keyPair.publicKey(), + signature, + messages, + header, + presentationHeader, + new int[]{0, 2}); + boolean presentationValid = service.verifyPresentation(keyPair.publicKey(), presentation); + + ZkProofEnvelope envelope = ZkProofEnvelope.builder() + .proofSystem(ProofSystemId.BBS) + .curve(CurveId.BLS12_381) + .circuitId(new CircuitId("bbs-selective-disclosure")) + .proofBytes(BbsPresentationCodec.encode(presentation)) + .publicInputs(new PublicInputs(List.of())) + .vkRef(new VerificationKeyRef.ById("issuer-key-v1")) + .proofFormat(BbsCiphersuite.DEFAULT_PROOF_FORMAT) + .build(); + VerificationMaterial material = VerificationMaterial.of( + keyPair.publicKey().bytes(), + ProofSystemId.BBS, + CurveId.BLS12_381, + new CircuitId("bbs-selective-disclosure")); + boolean envelopeValid = new BbsZkVerifier(service).verify(envelope, material).proofValid(); + + System.out.println("signatureValid=" + signatureValid); + System.out.println("presentationValid=" + presentationValid); + System.out.println("envelopeValid=" + envelopeValid); + } + + private static byte[] bytes(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } +} diff --git a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/auction/SealedBidPureJavaE2ETest.java b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/auction/SealedBidPureJavaE2ETest.java index 1317fb0..c29dab7 100644 --- a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/auction/SealedBidPureJavaE2ETest.java +++ b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/auction/SealedBidPureJavaE2ETest.java @@ -5,7 +5,9 @@ import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381; import com.bloxbean.cardano.zeroj.examples.dsl.common.MiMCHash; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -115,12 +117,12 @@ private boolean verifyOffChain(com.bloxbean.cardano.zeroj.crypto.groth16.Groth16 new G2Point[]{piB, beta, gamma, delta}); } - private static G1Point toG1(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1 p) { + private static G1Point toG1(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1 p) { if (p.isInfinity()) return G1Point.INFINITY; return new G1Point(Fp.of(p.xBigInt()), Fp.of(p.yBigInt())); } - private static G2Point toG2(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2 p) { + private static G2Point toG2(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2 p) { if (p.isInfinity()) return G2Point.INFINITY; return new G2Point( Fp2.of(Fp.of(p.x().reBigInt()), Fp.of(p.x().imBigInt())), diff --git a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/balance/BalanceThresholdPureJavaE2ETest.java b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/balance/BalanceThresholdPureJavaE2ETest.java index 9a85a92..03b08da 100644 --- a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/balance/BalanceThresholdPureJavaE2ETest.java +++ b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/balance/BalanceThresholdPureJavaE2ETest.java @@ -4,7 +4,9 @@ import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProverBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -93,12 +95,12 @@ private boolean verifyOffChain(com.bloxbean.cardano.zeroj.crypto.groth16.Groth16 new G2Point[]{piB, toG2(pk.betaG2()), toG2(setupResult.gammaG2()), toG2(pk.deltaG2())}); } - private static G1Point toG1(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1 p) { + private static G1Point toG1(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1 p) { if (p.isInfinity()) return G1Point.INFINITY; return new G1Point(Fp.of(p.xBigInt()), Fp.of(p.yBigInt())); } - private static G2Point toG2(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2 p) { + private static G2Point toG2(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2 p) { if (p.isInfinity()) return G2Point.INFINITY; return new G2Point( Fp2.of(Fp.of(p.x().reBigInt()), Fp.of(p.x().imBigInt())), diff --git a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/templates/ParameterizedCircuitE2ETest.java b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/templates/ParameterizedCircuitE2ETest.java index 18ab938..11a4cdf 100644 --- a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/templates/ParameterizedCircuitE2ETest.java +++ b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/templates/ParameterizedCircuitE2ETest.java @@ -7,7 +7,9 @@ import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381; import com.bloxbean.cardano.zeroj.examples.dsl.common.MiMCHash; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -263,12 +265,12 @@ private static int nextPowerOf2Log(int n) { return p; } - private static G1Point toG1(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1 p) { + private static G1Point toG1(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1 p) { if (p.isInfinity()) return G1Point.INFINITY; return new G1Point(Fp.of(p.xBigInt()), Fp.of(p.yBigInt())); } - private static G2Point toG2(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2 p) { + private static G2Point toG2(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2 p) { if (p.isInfinity()) return G2Point.INFINITY; return new G2Point( Fp2.of(Fp.of(p.x().reBigInt()), Fp.of(p.x().imBigInt())), diff --git a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/voting/AnonymousVotingPureJavaE2ETest.java b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/voting/AnonymousVotingPureJavaE2ETest.java index 3d8df97..f257331 100644 --- a/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/voting/AnonymousVotingPureJavaE2ETest.java +++ b/zeroj-examples/src/test/java/com/bloxbean/cardano/zeroj/examples/dsl/voting/AnonymousVotingPureJavaE2ETest.java @@ -5,7 +5,9 @@ import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.PowersOfTauBLS381; import com.bloxbean.cardano.zeroj.examples.dsl.common.MiMCHash; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import org.junit.jupiter.api.Test; import java.math.BigInteger; @@ -106,12 +108,12 @@ private boolean verifyOffChain(com.bloxbean.cardano.zeroj.crypto.groth16.Groth16 new G2Point[]{toG2(proof.b()), toG2(pk.betaG2()), toG2(setupResult.gammaG2()), toG2(pk.deltaG2())}); } - private static G1Point toG1(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381.AffineG1 p) { + private static G1Point toG1(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381.AffineG1 p) { if (p.isInfinity()) return G1Point.INFINITY; return new G1Point(Fp.of(p.xBigInt()), Fp.of(p.yBigInt())); } - private static G2Point toG2(com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381.AffineG2 p) { + private static G2Point toG2(com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381.AffineG2 p) { if (p.isInfinity()) return G2Point.INFINITY; return new G2Point( Fp2.of(Fp.of(p.x().reBigInt()), Fp.of(p.x().imBigInt())), diff --git a/zeroj-onchain-julc/build.gradle b/zeroj-onchain-julc/build.gradle index b9fe4f1..bd37920 100644 --- a/zeroj-onchain-julc/build.gradle +++ b/zeroj-onchain-julc/build.gradle @@ -26,8 +26,9 @@ dependencies { api project(':zeroj-api') implementation project(':zeroj-blst') - // Pure Java prover types — used by ProverToCardano to compress prover output - implementation project(':zeroj-crypto') + // Pure Java prover and BLS point types — used by ProverToCardano to compress prover output + api project(':zeroj-crypto') + api project(':zeroj-bls12381') // JSON parsing for SnarkjsToCardano implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2' diff --git a/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ProverToCardano.java b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ProverToCardano.java index ed14159..38326ce 100644 --- a/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ProverToCardano.java +++ b/zeroj-onchain-julc/src/main/java/com/bloxbean/cardano/zeroj/onchain/julc/ProverToCardano.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.onchain.julc; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProofBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; import supranational.blst.P1_Affine; diff --git a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/CircomToOnChainE2ETest.java b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/CircomToOnChainE2ETest.java index f6b36d8..d2e6bf5 100644 --- a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/CircomToOnChainE2ETest.java +++ b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/CircomToOnChainE2ETest.java @@ -1,7 +1,7 @@ package com.bloxbean.cardano.zeroj.onchain.julc; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProverBLS381; import com.bloxbean.cardano.zeroj.crypto.groth16.ZkeyImporterBLS381; import com.bloxbean.cardano.julc.core.PlutusData; diff --git a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/Groth16BLS12381PureJavaProverTest.java b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/Groth16BLS12381PureJavaProverTest.java index 383bcfd..e5682c1 100644 --- a/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/Groth16BLS12381PureJavaProverTest.java +++ b/zeroj-onchain-julc/src/test/java/com/bloxbean/cardano/zeroj/onchain/julc/Groth16BLS12381PureJavaProverTest.java @@ -2,8 +2,8 @@ import com.bloxbean.cardano.zeroj.api.CurveId; import com.bloxbean.cardano.zeroj.circuit.CircuitBuilder; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG1BLS381; -import com.bloxbean.cardano.zeroj.crypto.ec.JacobianG2BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG1BLS381; +import com.bloxbean.cardano.zeroj.bls12381.ec.JacobianG2BLS381; import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProverBLS381; import com.bloxbean.cardano.zeroj.crypto.groth16.Groth16ProofBLS381; import com.bloxbean.cardano.zeroj.crypto.setup.Groth16SetupBLS381; diff --git a/zeroj-verifier-groth16/build.gradle b/zeroj-verifier-groth16/build.gradle index 17ed6be..700e988 100644 --- a/zeroj-verifier-groth16/build.gradle +++ b/zeroj-verifier-groth16/build.gradle @@ -7,6 +7,7 @@ description = 'ZeroJ Groth16 verification — BN254 (pure Java) and BLS12-381 (v dependencies { api project(':zeroj-backend-spi') implementation project(':zeroj-codec') + implementation project(':zeroj-bls12381') implementation project(':zeroj-blst') testImplementation project(':zeroj-test-vectors') diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/Groth16BLS12381PureJavaVerifier.java b/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/Groth16BLS12381PureJavaVerifier.java index d5c0e0f..d0471b5 100644 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/Groth16BLS12381PureJavaVerifier.java +++ b/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/Groth16BLS12381PureJavaVerifier.java @@ -4,7 +4,9 @@ import com.bloxbean.cardano.zeroj.backend.spi.BackendDescriptor; import com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier; import com.bloxbean.cardano.zeroj.codec.SnarkjsJsonCodec; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import java.math.BigInteger; import java.util.List; @@ -13,7 +15,7 @@ * Pure Java Groth16 verifier for BLS12-381 — no native dependencies. * *

Uses the pure Java BLS12-381 field arithmetic and pairing from - * {@link com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field}.

+ * {@link com.bloxbean.cardano.zeroj.bls12381}.

* *

Verification equation: e(A, B) * e(-alpha, beta) * e(-vk_x, gamma) * e(-C, delta) == 1

* diff --git a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/G2Point.java b/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/G2Point.java deleted file mode 100644 index 8c20311..0000000 --- a/zeroj-verifier-groth16/src/main/java/com/bloxbean/cardano/zeroj/verifier/groth16/bls12381/field/G2Point.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field; - -import java.math.BigInteger; - -/** - * BLS12-381 G2 point in affine coordinates on the twisted curve y^2 = x^3 + 4(1+u) over Fp2. - */ -public record G2Point(Fp2 x, Fp2 y) { - - public static final G2Point INFINITY = new G2Point(null, null); - - public boolean isInfinity() { return x == null; } - - /** Create from snarkjs projective [[x_c0,x_c1],[y_c0,y_c1],[z_c0,z_c1]]. */ - public static G2Point fromProjective(BigInteger xc0, BigInteger xc1, - BigInteger yc0, BigInteger yc1, - BigInteger zc0, BigInteger zc1) { - var z = Fp2.of(Fp.of(zc0), Fp.of(zc1)); - if (z.isZero()) return INFINITY; - if (z.c0().equals(Fp.ONE) && z.c1().isZero()) { - return new G2Point(Fp2.of(Fp.of(xc0), Fp.of(xc1)), Fp2.of(Fp.of(yc0), Fp.of(yc1))); - } - var zInv = z.inv(); - return new G2Point( - Fp2.of(Fp.of(xc0), Fp.of(xc1)).mul(zInv), - Fp2.of(Fp.of(yc0), Fp.of(yc1)).mul(zInv)); - } - - public G2Point negate() { - return isInfinity() ? this : new G2Point(x, y.neg()); - } - - public G2Point add(G2Point o) { - if (this.isInfinity()) return o; - if (o.isInfinity()) return this; - if (this.x.equals(o.x)) { - return this.y.equals(o.y) ? this.doublePoint() : INFINITY; - } - var lambda = o.y.sub(this.y).mul(o.x.sub(this.x).inv()); - var x3 = lambda.square().sub(this.x).sub(o.x); - var y3 = lambda.mul(this.x.sub(x3)).sub(this.y); - return new G2Point(x3, y3); - } - - public G2Point doublePoint() { - if (isInfinity() || y.isZero()) return INFINITY; - var three = Fp2.of(Fp.of(3), Fp.ZERO); - var lambda = x.square().mul(three).mul(y.add(y).inv()); - var x3 = lambda.square().sub(x).sub(x); - var y3 = lambda.mul(x.sub(x3)).sub(y); - return new G2Point(x3, y3); - } -} diff --git a/zeroj-verifier-plonk/build.gradle b/zeroj-verifier-plonk/build.gradle index 287e36f..c9cdef2 100644 --- a/zeroj-verifier-plonk/build.gradle +++ b/zeroj-verifier-plonk/build.gradle @@ -7,6 +7,7 @@ description = 'ZeroJ PlonK verification — pure Java for BLS12-381 and BN254, n dependencies { api project(':zeroj-backend-spi') implementation project(':zeroj-codec') + implementation project(':zeroj-bls12381') implementation project(':zeroj-verifier-groth16') // BN254 field arithmetic reuse testImplementation project(':zeroj-test-vectors') diff --git a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java b/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java index 5f73a92..8d1359a 100644 --- a/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java +++ b/zeroj-verifier-plonk/src/main/java/com/bloxbean/cardano/zeroj/verifier/plonk/PlonkBLS12381Verifier.java @@ -3,7 +3,9 @@ import com.bloxbean.cardano.zeroj.api.*; import com.bloxbean.cardano.zeroj.backend.spi.BackendDescriptor; import com.bloxbean.cardano.zeroj.backend.spi.ZkVerifier; -import com.bloxbean.cardano.zeroj.verifier.groth16.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.ec.*; +import com.bloxbean.cardano.zeroj.bls12381.field.*; +import com.bloxbean.cardano.zeroj.bls12381.pairing.BLS12381Pairing; import java.math.BigInteger; import java.util.List;