add fuzzing and v1.0.2
This commit is contained in:
parent
ffa8d84173
commit
c3782d1ae6
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,15 @@
|
||||
# Rust
|
||||
target
|
||||
|
||||
# cargo-fuzz / fuzzing
|
||||
fuzz/corpus/
|
||||
fuzz/artifacts/
|
||||
fuzz/target/
|
||||
fuzz/crashes/
|
||||
crash-*
|
||||
leak-*
|
||||
*.profraw
|
||||
|
||||
# Python / Maturin
|
||||
python/zkac/__pycache__
|
||||
python/zkac/*.cpython*
|
||||
|
||||
@ -26,3 +26,4 @@ pyo3 = { version = "0.25", features = ["extension-module"], optional = true }
|
||||
[features]
|
||||
default = []
|
||||
python = ["pyo3"]
|
||||
fuzz-expose = [] # `Session::new_fuzz` for fuzz builds only
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
## Documentation
|
||||
|
||||
- **[Python API](docs/PYTHON_API.md)** — types and usage for `import zkac`
|
||||
- **[Security model](SECURITY.md)** — threat model, assumptions, operational guidance
|
||||
- **[Security model](docs/SECURITY.md)** — threat model, assumptions, operational guidance
|
||||
- **[Fuzzing](docs/FUZZING.md)** — `cargo-fuzz` harnesses
|
||||
|
||||
## Rust
|
||||
|
||||
|
||||
106
docs/FUZZING.md
Normal file
106
docs/FUZZING.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Fuzzing ZKAC
|
||||
|
||||
This repository ships a **libFuzzer** harness via [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz). You can reuse `zkac_fuzz` from other fuzzers (e.g. AFL++, Honggfuzz) with a small custom binary.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Rust** toolchain (`rustc`, `cargo`).
|
||||
- **`cargo-fuzz`:** `cargo install cargo-fuzz`
|
||||
- **AddressSanitizer / default `cargo fuzz`:** requires **nightly** Rust (e.g. `rustup toolchain install nightly`). If you use a distro Rust **without** `rustup`, use sanitizer `none` (see below).
|
||||
|
||||
## Quick start
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/fuzz-libfuzzer.sh
|
||||
./scripts/fuzz-libfuzzer.sh
|
||||
```
|
||||
|
||||
Fuzz a single target for 5 minutes:
|
||||
|
||||
```bash
|
||||
FUZZ_TIME=300 ./scripts/fuzz-libfuzzer.sh handshake_respond
|
||||
```
|
||||
|
||||
### Stable Rust (no nightly)
|
||||
|
||||
Default `cargo fuzz` enables AddressSanitizer and needs nightly. To fuzz on **stable**, build and run with sanitizer `none`:
|
||||
|
||||
```bash
|
||||
cargo fuzz build -s none handshake_respond
|
||||
cargo fuzz run -s none handshake_respond -- -max_total_time=60
|
||||
```
|
||||
|
||||
The helper script sets `SANITIZER=none` by default for broad compatibility. It also prepends `~/.cargo/bin` to `PATH` so `cargo-fuzz` is found after `cargo install cargo-fuzz`.
|
||||
|
||||
### Did anything crash?
|
||||
|
||||
After a run:
|
||||
|
||||
1. **Exit code** — `0` means every target finished its time/run budget **without** libFuzzer treating the process as a crash. Non-zero means at least one target failed (crash, abort, or libFuzzer error).
|
||||
2. **Stdout** — On failure, libFuzzer prints `ERROR: libFuzzer: deadly signal` (or ASan messages if you use a sanitizer), the panic/backtrace, and a **reproduce** command.
|
||||
3. **Artifacts** — Crashes are written under `fuzz/artifacts/<target_name>/`, typically `crash-<hash>` (and sometimes `leak-*` with leak sanitizer). Re-run with:
|
||||
```bash
|
||||
cargo fuzz run -s none <target> fuzz/artifacts/<target>/crash-<hash>
|
||||
```
|
||||
4. **Minimize** — Shrink a crashing input with `cargo fuzz tmin -s none <target> <crash-file>`.
|
||||
|
||||
If the script stops early, scroll up to the last `=== cargo-fuzz: <name> ===` block: the lines after it show which target failed.
|
||||
|
||||
### Nightly + AddressSanitizer
|
||||
|
||||
```bash
|
||||
SANITIZER=address FUZZ_TIME=120 ./scripts/fuzz-libfuzzer.sh session_decrypt
|
||||
```
|
||||
|
||||
## Targets (`fuzz/fuzz_targets/`)
|
||||
|
||||
| Target | Exercises |
|
||||
|--------|-----------|
|
||||
| `handshake_respond` | X25519 responder (`handshake::respond`) |
|
||||
| `handshake_initiator_complete` | Initiator `complete` with arbitrary response bytes |
|
||||
| `session_decrypt` | ChaCha20-Poly1305 decrypt + replay guard |
|
||||
| `replay_sequence` | Sliding-window replay logic |
|
||||
| `crypto_deserialize` | Ristretto public key, Schnorr signature, BBS+ issuer key parsing |
|
||||
| `bbs_verify_presentation` | BBS+ proof parse + verify (heavy; keep corpora small) |
|
||||
|
||||
Shared logic lives in `fuzz/src/lib.rs` (`zkac_fuzz`).
|
||||
|
||||
## Crate feature `fuzz-expose`
|
||||
|
||||
Fuzz builds enable `zkac`’s **`fuzz-expose`** feature so harnesses can call `Session::new_fuzz`. Do not enable this in production binaries.
|
||||
|
||||
## corpora / artifacts
|
||||
|
||||
- **Inputs:** seed with `mkdir -p fuzz/corpus/<target>` and pass `-artifact_prefix=fuzz/artifacts/` if you want crash dumps.
|
||||
- **Gitignored:** `fuzz/corpus/`, `fuzz/artifacts/`, crash files (see `.gitignore`).
|
||||
|
||||
## AFL++ (optional)
|
||||
|
||||
1. Install [AFL++](https://github.com/AFLplusplus/AFLplusplus) and `cargo install cargo-afl`.
|
||||
2. Add a binary in `fuzz/` (or a separate crate) that calls into `zkac_fuzz::dispatch_all` or individual `zkac_fuzz::*` functions.
|
||||
3. Build with the `cargo-afl` wrapper (not plain `cargo build`):
|
||||
|
||||
```bash
|
||||
cd fuzz
|
||||
cargo afl build --release
|
||||
# then point cargo-afl at your harness binary, e.g.:
|
||||
# cargo afl fuzz -i in -o out target/release/your_afl_bin
|
||||
```
|
||||
|
||||
(`fuzz/Cargo.toml` already enables `zkac`’s `fuzz-expose` for the dependency.)
|
||||
|
||||
The `afl` crate’s `fuzz!` macro must be linked with AFL’s runtime; use `cargo afl build` as documented in [cargo-afl](https://github.com/rust-fuzz/afl.rs).
|
||||
|
||||
## Honggfuzz (optional)
|
||||
|
||||
Install the `honggfuzz` binary (distro package or source). Point it at a binary that exercises `zkac_fuzz` (similar to AFL). Honggfuzz does not require the `cargo-fuzz` project layout; you can compile a small `main` that loops over inputs.
|
||||
|
||||
## CI smoke
|
||||
|
||||
Short runs can use `-runs=1000` instead of time limits:
|
||||
|
||||
```bash
|
||||
cargo fuzz run -s none handshake_respond -- -runs=1000 -print_final_stats=1
|
||||
```
|
||||
@ -1,6 +1,6 @@
|
||||
# ZKAC Python API Reference
|
||||
|
||||
Version 0.1. Cryptographic stack: **BBS+** on BLS12-381 (credentials), **X25519** + **ChaCha20-Poly1305** (transport), **BLAKE2b** (role IDs).
|
||||
Version 0.2. Cryptographic stack: **BBS+** on BLS12-381 (credentials), **X25519** + **ChaCha20-Poly1305** (transport), **Schnorr/Ristretto255** (identity), **BLAKE2b** (role IDs, signatures).
|
||||
|
||||
```python
|
||||
import zkac
|
||||
@ -16,16 +16,16 @@ Upper bound on BBS+ proof size in an encrypted auth packet (256 KiB). Larger pro
|
||||
|
||||
### `Keypair()`
|
||||
|
||||
Generates a new random keypair.
|
||||
Generates a new random keypair (long-term identity).
|
||||
|
||||
### `Keypair.public_key() -> PublicKey`
|
||||
|
||||
Raises `ValueError` if consumed by `Node(...)`.
|
||||
- `public_key() -> PublicKey` — raises `ValueError` if consumed by `Node(...)`
|
||||
- `sign(msg: bytes) -> bytes` — 64-byte Schnorr signature over `msg`
|
||||
|
||||
### `PublicKey`
|
||||
|
||||
- `to_bytes() -> bytes` — 32 bytes
|
||||
- `from_bytes(bytes) -> PublicKey`
|
||||
- `verify(msg: bytes, signature: bytes) -> bool` — verify a 64-byte Schnorr signature
|
||||
- Equality, hash, `repr` supported
|
||||
|
||||
## BBS+ credentials
|
||||
@ -71,7 +71,8 @@ New random issuer. `from_secret_key(bytes)` restores from 32-byte secret.
|
||||
- `public_key() -> PublicKey`
|
||||
- `connect() -> (PendingConnect, bytes)` — 32-byte init message
|
||||
- `accept(init_msg) -> (Session, bytes)` — `init_msg` 32 bytes
|
||||
- `complete_connect(pending, response_msg, credential) -> (Session, bytes)` — `response_msg` 32 bytes
|
||||
- `prove_identity(session) -> bytes` — server produces encrypted identity proof (Schnorr signature over transcript)
|
||||
- `complete_connect(pending, response_msg, identity_proof, expected_server_pk, credential) -> (Session, bytes)` — verifies server identity, then produces BBS+ auth packet
|
||||
- `verify_auth(session, encrypted_auth, registry) -> bytes` — returns 32-byte `role_id`
|
||||
|
||||
### `Session`
|
||||
@ -84,13 +85,14 @@ New random issuer. `from_secret_key(bytes)` restores from 32-byte secret.
|
||||
|
||||
1. Issuer creates `BbsIssuer()`; server `register_role(role_id, issuer.public_key(), epoch)`.
|
||||
2. Member: `prepare_blind_request` → issuer `issue_blind` → `Credential.finalize`.
|
||||
3. Client: `connect` → server `accept` → client `complete_connect` → server `verify_auth`.
|
||||
4. Use `Session.encrypt` / `decrypt` for data.
|
||||
3. **Out-of-band:** client obtains server's `PublicKey` (static config, pinning, etc.).
|
||||
4. Client: `connect` → server `accept` → server `prove_identity` → client `complete_connect` (verifies server identity + sends BBS+ auth) → server `verify_auth`.
|
||||
5. Use `Session.encrypt` / `decrypt` for data.
|
||||
|
||||
## Errors
|
||||
|
||||
Raises `ValueError` with descriptive messages for crypto failures, replay, and bad inputs.
|
||||
Raises `ValueError` with descriptive messages for crypto failures, replay, identity verification, and bad inputs.
|
||||
|
||||
## Further reading
|
||||
|
||||
[Security model and assumptions](./SECURITY.md)
|
||||
[Security model and assumptions](../SECURITY.md)
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
# Security model and audit notes (ZKAC 0.1)
|
||||
# Security model and audit notes (ZKAC 0.2)
|
||||
|
||||
This document summarizes the design, residual risks, and recommendations for operators integrating **ZKAC**. It is not a substitute for independent review before high-assurance deployment.
|
||||
|
||||
## Goals
|
||||
|
||||
- **Authentication:** Only holders of a valid BBS+ credential for a registered role can complete `verify_auth` for that role.
|
||||
- **Server identity:** The server proves its long-term identity to the client via a Schnorr signature over the session transcript; clients verify against a pinned public key. This prevents MITM attacks without requiring TLS.
|
||||
- **Confidentiality & integrity:** Session payloads are authenticated-encrypted (ChaCha20-Poly1305) with a key derived from an ephemeral X25519 handshake.
|
||||
- **Replay resistance:** Duplicate ciphertexts in a direction are rejected (sliding window + monotonic counter).
|
||||
- **Unlinkability (credential layer):** BBS+ presentations are intended to be unlinkable across sessions when the presentation header (here, the session transcript hash) differs; the verifier learns only the disclosed attributes (opaque `role_id`, epoch) and validity.
|
||||
- **Unlinkability (credential layer):** BBS+ presentations are unlinkable across sessions when the presentation header (the session transcript hash) differs; the verifier learns only the disclosed attributes (opaque `role_id`, epoch) and validity. Client anonymity is preserved: the client never reveals its long-term key during the handshake.
|
||||
- **Server cannot forge credentials:** The server stores only the issuer **public** key per role; forging requires the issuer secret key.
|
||||
|
||||
## Cryptographic components
|
||||
@ -15,9 +16,26 @@ This document summarizes the design, residual risks, and recommendations for ope
|
||||
| Layer | Primitive | Purpose |
|
||||
|-------|-----------|---------|
|
||||
| Transport | X25519 ephemeral DH, HKDF-SHA256, ChaCha20-Poly1305 | Session keys, AEAD |
|
||||
| Identity | Schnorr on Ristretto255, BLAKE2b-512 challenge | Server identity binding |
|
||||
| Credentials | BBS+ on BLS12-381 (zkryptium), SHAKE256 ciphersuite | Blind issuance, ZK presentations |
|
||||
| Role IDs | BLAKE2b-512 (truncated to 32 bytes) | Opaque role identifiers |
|
||||
|
||||
## Protocol flow
|
||||
|
||||
```
|
||||
Client Server
|
||||
|--- init_msg (eph_pk) ------------>|
|
||||
| | accept()
|
||||
| | prove_identity() → sign(transcript)
|
||||
|<-- response_msg + identity_pkt ---|
|
||||
| complete DH |
|
||||
| decrypt + verify server sig |
|
||||
| encrypt BBS+ auth |
|
||||
|--- encrypted BBS+ auth ---------> |
|
||||
| | verify_auth()
|
||||
|===== encrypted session ===========>|
|
||||
```
|
||||
|
||||
## Threats considered
|
||||
|
||||
### Network attacker (passive)
|
||||
@ -26,8 +44,9 @@ This document summarizes the design, residual risks, and recommendations for ope
|
||||
|
||||
### Network attacker (active / MITM)
|
||||
|
||||
- **Without binding the handshake to authenticated long-term keys:** A MITM can run its own X25519 exchange with each side and decrypt/modify traffic unless an **out-of-band** binding exists (e.g. TLS with server authentication, or clients verifying server `PublicKey` through a trusted channel). The library’s `Keypair` / `PublicKey` are available for application-level identity display or future binding; **this release does not sign the handshake with long-term keys**.
|
||||
- **Recommendation:** Run the protocol inside **TLS 1.3** (or similar) for production, or add an explicit long-term key signing step over the transcript if you need standalone MITM resistance.
|
||||
- **Server impersonation:** The server signs the session transcript hash with its long-term Ristretto255 key (`prove_identity`). The client verifies this signature against the **pinned** server public key. A MITM running a separate DH exchange produces a different transcript; it cannot forge the server's signature. The client aborts on mismatch.
|
||||
- **Client impersonation:** The BBS+ presentation is bound to the session transcript hash. A MITM cannot relay a presentation from one session to another (different transcripts) or forge one (requires a valid credential from the issuer).
|
||||
- **Relay attack:** A MITM that relays the real server's identity proof to a client fails because the proof is encrypted under the MITM-to-server session keys (not the client-to-MITM keys), and the signature is over the wrong transcript.
|
||||
|
||||
### Malicious server
|
||||
|
||||
@ -35,10 +54,11 @@ This document summarizes the design, residual risks, and recommendations for ope
|
||||
- **Cannot** forge BBS+ credentials without the issuer secret key.
|
||||
- **Cannot** learn `member_secret` from presentations under the BBS+ security assumptions.
|
||||
- **Cannot** distinguish which specific member authenticated among valid credential holders (unlinkability holds against the verifier for distinct presentation headers).
|
||||
- **Cannot** learn the client's long-term public key — it is never transmitted.
|
||||
|
||||
### Malicious client
|
||||
|
||||
- Cannot decrypt others’ traffic without session keys.
|
||||
- Cannot decrypt others' traffic without session keys.
|
||||
- Cannot produce valid auth for a role without a valid credential + correct epoch + registry entry.
|
||||
|
||||
### Denial of service
|
||||
@ -47,29 +67,50 @@ This document summarizes the design, residual risks, and recommendations for ope
|
||||
- **Handshake:** Fixed 32-byte messages; no variable-length handshake parsing.
|
||||
- General packet limits should still be enforced at the application layer (total message size, rate limits).
|
||||
|
||||
## Key distribution
|
||||
|
||||
The server's long-term `PublicKey` (32-byte Ristretto255 point) functions as a **self-authenticating identity** — no certificate authority is required. The client must obtain and pin this key before connecting.
|
||||
|
||||
Recommended strategies:
|
||||
|
||||
1. **Static configuration** (default): embed the server public key in client config, environment variable, or CLI flag. Equivalent to WireGuard's `[Peer] PublicKey = ...`.
|
||||
2. **Trust On First Use (TOFU):** accept the server's key on first connection, pin it for subsequent sessions. Risk: first connection is vulnerable.
|
||||
3. **Out-of-band verification:** compare public key fingerprints over a trusted side channel (phone, in-person, encrypted messaging).
|
||||
4. **Key registry / directory:** a trusted service maps names to public keys. Shifts trust to the registry and its authentication channel.
|
||||
|
||||
## Operational requirements
|
||||
|
||||
1. **Issuer secret key:** Protect `BbsIssuer` secret material (HSM, KMS, or encrypted at rest). Compromise = ability to issue arbitrary credentials for that role.
|
||||
2. **Member storage:** `member_secret` and finalized `Credential` material must be protected; loss = re-enrollment required.
|
||||
3. **Epoch revocation:** On compromise or policy change, call `set_epoch` and re-issue credentials only to legitimate members; old credentials become invalid at verification time.
|
||||
4. **Registry integrity:** The server’s `(role_id → public key, epoch)` mapping must be integrity-protected (trusted storage or signed updates), or attackers could swap keys or epochs.
|
||||
5. **Role ID privacy:** `role_id` is a hash of the role name only if you use `role_id("myrole")`; treat role names as secrets if enumeration is a concern, or derive role IDs with an additional secret salt known to members.
|
||||
2. **Server long-term key:** Protect the `Node` `Keypair` secret. Compromise = ability to impersonate the server. Rotate the key and distribute the new public key to clients if compromised.
|
||||
3. **Member storage:** `member_secret` and finalized `Credential` material must be protected; loss = re-enrollment required.
|
||||
4. **Epoch revocation:** On compromise or policy change, call `set_epoch` and re-issue credentials only to legitimate members; old credentials become invalid at verification time.
|
||||
5. **Registry integrity:** The server's `(role_id → public key, epoch)` mapping must be integrity-protected (trusted storage or signed updates), or attackers could swap keys or epochs.
|
||||
6. **Role ID privacy:** `role_id` is a hash of the role name only if you use `role_id("myrole")`; treat role names as secrets if enumeration is a concern, or derive role IDs with an additional secret salt known to members.
|
||||
|
||||
## Implementation notes (audit checklist)
|
||||
|
||||
- [x] BBS+ proof verification uses the same header and presentation binding as proof generation (`verify_presentation` in Rust).
|
||||
- [x] Session transcript is included in the presentation via `present(transcript_hash)`.
|
||||
- [x] Server identity proof: Schnorr signature over `transcript_hash`, verified against pinned public key before BBS+ auth proceeds.
|
||||
- [x] Schnorr nonce is deterministic (`H(sk || msg)`) — no dependence on RNG quality at signing time.
|
||||
- [x] Replay protection is symmetric per direction in `Session`.
|
||||
- [x] Constant-time comparisons are used where critical in transport/replay paths (`subtle` crate).
|
||||
- [x] Client long-term key is never transmitted, preserving BBS+ unlinkability.
|
||||
- [ ] **External:** Python bindings surface raw bytes; callers must not log secrets (`secret_key_bytes`, `member_secret`, `prover_blind`).
|
||||
- [ ] **External:** Use secure randomness from the OS (library uses OS RNG for key generation paths exposed in Rust).
|
||||
|
||||
## Design decisions
|
||||
|
||||
- **Server-only identity proof:** Only the server signs the transcript. Adding client long-term signing would break BBS+ unlinkability (the server could correlate sessions by client public key). Client authentication is handled entirely by the anonymous BBS+ credential.
|
||||
- **Deterministic Schnorr nonces:** The signing nonce is derived as `H("zkac-schnorr-nonce" || sk || msg)`, eliminating a class of RNG-failure attacks (cf. PS3 ECDSA, Sony 2010). Same key + same message = same signature.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **No post-quantum** primitives: classical security assumptions only.
|
||||
- **Epoch granularity:** Revocation is coarse (epoch bump); plan issuance and rotation policy accordingly.
|
||||
- **zkryptium dependency:** Security follows the underlying crate and BLS12-381/BBS+ standards; keep dependencies updated.
|
||||
- **Key distribution:** The library provides the cryptographic mechanism; initial key distribution is an application-layer responsibility.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
Report security-sensitive findings through your project’s private disclosure channel (configure `SECURITY.md` contact or GitHub security advisories when the repository is public).
|
||||
Report security-sensitive findings through your project's private disclosure channel (configure `SECURITY.md` contact or GitHub security advisories when the repository is public).
|
||||
|
||||
762
fuzz/Cargo.lock
generated
Normal file
762
fuzz/Cargo.lock
generated
Normal file
@ -0,0 +1,762 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
|
||||
[[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 = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[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 = "cc"
|
||||
version = "1.2.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[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.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rand_core",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[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 = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[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.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
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 = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[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 = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[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.184"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f"
|
||||
dependencies = [
|
||||
"group",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[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 = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[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.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
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 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[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.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[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.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[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 = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[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"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zkac"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"chacha20poly1305",
|
||||
"curve25519-dalek",
|
||||
"digest",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"rand",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
"zkryptium",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zkac-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"libfuzzer-sys",
|
||||
"rand",
|
||||
"zkac",
|
||||
]
|
||||
|
||||
[[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"
|
||||
61
fuzz/Cargo.toml
Normal file
61
fuzz/Cargo.toml
Normal file
@ -0,0 +1,61 @@
|
||||
[package]
|
||||
name = "zkac-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
rand = { version = "0.8", features = ["std_rng"] }
|
||||
zkac = { path = "..", features = ["fuzz-expose"] }
|
||||
|
||||
[lib]
|
||||
name = "zkac_fuzz"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "handshake_respond"
|
||||
path = "fuzz_targets/handshake_respond.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "handshake_initiator_complete"
|
||||
path = "fuzz_targets/handshake_initiator_complete.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "session_decrypt"
|
||||
path = "fuzz_targets/session_decrypt.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "replay_sequence"
|
||||
path = "fuzz_targets/replay_sequence.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "crypto_deserialize"
|
||||
path = "fuzz_targets/crypto_deserialize.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "bbs_verify_presentation"
|
||||
path = "fuzz_targets/bbs_verify_presentation.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[workspace]
|
||||
8
fuzz/fuzz_targets/bbs_verify_presentation.rs
Normal file
8
fuzz/fuzz_targets/bbs_verify_presentation.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use zkac_fuzz::bbs_verify_presentation;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
bbs_verify_presentation(data);
|
||||
});
|
||||
8
fuzz/fuzz_targets/crypto_deserialize.rs
Normal file
8
fuzz/fuzz_targets/crypto_deserialize.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use zkac_fuzz::crypto_deserialize;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
crypto_deserialize(data);
|
||||
});
|
||||
8
fuzz/fuzz_targets/handshake_initiator_complete.rs
Normal file
8
fuzz/fuzz_targets/handshake_initiator_complete.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use zkac_fuzz::handshake_initiator_complete;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
handshake_initiator_complete(data);
|
||||
});
|
||||
8
fuzz/fuzz_targets/handshake_respond.rs
Normal file
8
fuzz/fuzz_targets/handshake_respond.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use zkac_fuzz::handshake_respond;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
handshake_respond(data);
|
||||
});
|
||||
8
fuzz/fuzz_targets/replay_sequence.rs
Normal file
8
fuzz/fuzz_targets/replay_sequence.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use zkac_fuzz::replay_sequence;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
replay_sequence(data);
|
||||
});
|
||||
8
fuzz/fuzz_targets/session_decrypt.rs
Normal file
8
fuzz/fuzz_targets/session_decrypt.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use zkac_fuzz::session_decrypt;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
session_decrypt(data);
|
||||
});
|
||||
142
fuzz/src/lib.rs
Normal file
142
fuzz/src/lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
//! Shared fuzz harnesses (`cargo-fuzz`); see `docs/FUZZING.md`. Inputs are capped for DoS bounds.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use zkac::credential::{
|
||||
role_id, verify_presentation, IssuerKeyPair, IssuerPublicKey, Presentation, PublicKey,
|
||||
Signature,
|
||||
};
|
||||
use zkac::transport::handshake::{self, InitiatorHandshake};
|
||||
use zkac::transport::{ReplayGuard, Session};
|
||||
|
||||
/// Maximum bytes passed into crypto entry points (DoS bound for fuzzing).
|
||||
pub const MAX_INPUT_LEN: usize = 1 << 20;
|
||||
|
||||
/// Max proof / issuer key blob size for BBS+ fuzz paths.
|
||||
pub const MAX_BBS_INPUT: usize = zkac::MAX_BBS_AUTH_PROOF_BYTES;
|
||||
|
||||
fn trim(data: &[u8]) -> &[u8] {
|
||||
if data.len() > MAX_INPUT_LEN {
|
||||
&data[..MAX_INPUT_LEN]
|
||||
} else {
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
/// Fuzz `handshake::respond` with arbitrary 32-byte initiator public key material.
|
||||
pub fn handshake_respond(data: &[u8]) {
|
||||
let data = trim(data);
|
||||
let mut init = [0u8; 32];
|
||||
let n = data.len().min(32);
|
||||
init[..n].copy_from_slice(&data[..n]);
|
||||
let _ = handshake::respond(rand::thread_rng(), &init);
|
||||
}
|
||||
|
||||
/// Fuzz initiator `complete` with seeded RNG and arbitrary response bytes.
|
||||
pub fn handshake_initiator_complete(data: &[u8]) {
|
||||
let data = trim(data);
|
||||
if data.len() < 8 + 32 {
|
||||
return;
|
||||
}
|
||||
let seed = u64::from_le_bytes(data[0..8].try_into().unwrap());
|
||||
let rng = StdRng::seed_from_u64(seed);
|
||||
let (pending, _init_msg) = InitiatorHandshake::begin(rng);
|
||||
let mut response = [0u8; 32];
|
||||
response.copy_from_slice(&data[8..40]);
|
||||
let _ = pending.complete(&response);
|
||||
}
|
||||
|
||||
/// Fuzz `Session::decrypt` with derived keys XOR-mixed from input (covers AEAD + replay).
|
||||
pub fn session_decrypt(data: &[u8]) {
|
||||
let data = trim(data);
|
||||
let mut send_key = [0x5au8; 32];
|
||||
let mut recv_key = [0xa5u8; 32];
|
||||
for (i, b) in data.iter().take(32).enumerate() {
|
||||
send_key[i] ^= b;
|
||||
recv_key[i] ^= b.rotate_left(1);
|
||||
}
|
||||
let mut transcript = [0u8; 32];
|
||||
let tail = data.len().saturating_sub(32);
|
||||
for (i, b) in data.iter().skip(32).take(32).enumerate() {
|
||||
transcript[i] ^= *b;
|
||||
}
|
||||
if tail > 0 {
|
||||
transcript[31] ^= (tail as u8).wrapping_mul(0x9d);
|
||||
}
|
||||
let mut recv_session = Session::new_fuzz(send_key, recv_key, transcript);
|
||||
let _ = recv_session.decrypt(data);
|
||||
}
|
||||
|
||||
/// Exercise `ReplayGuard` with a stream of little-endian counters.
|
||||
pub fn replay_sequence(data: &[u8]) {
|
||||
let data = trim(data);
|
||||
let mut guard = ReplayGuard::new();
|
||||
for chunk in data.chunks_exact(8) {
|
||||
let c = u64::from_le_bytes(chunk.try_into().unwrap());
|
||||
if guard.check(c).is_ok() {
|
||||
guard.accept(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fuzz deserialization: Ristretto public key, Schnorr signature, BBS+ issuer public key.
|
||||
pub fn crypto_deserialize(data: &[u8]) {
|
||||
let data = trim(data);
|
||||
if data.len() >= 32 {
|
||||
let mut pk = [0u8; 32];
|
||||
pk.copy_from_slice(&data[..32]);
|
||||
let _ = PublicKey::from_bytes(pk);
|
||||
}
|
||||
if data.len() >= 64 {
|
||||
let mut sig = [0u8; 64];
|
||||
sig.copy_from_slice(&data[..64]);
|
||||
let _ = Signature::from_bytes(&sig);
|
||||
}
|
||||
let blob = if data.len() > 4096 {
|
||||
&data[..4096]
|
||||
} else {
|
||||
data
|
||||
};
|
||||
let _ = IssuerPublicKey::from_bytes(blob);
|
||||
}
|
||||
|
||||
static BBS_SETUP: OnceLock<(IssuerPublicKey, [u8; 32])> = OnceLock::new();
|
||||
|
||||
fn bbs_setup() -> &'static (IssuerPublicKey, [u8; 32]) {
|
||||
BBS_SETUP.get_or_init(|| {
|
||||
let issuer = IssuerKeyPair::generate().expect("issuer generation");
|
||||
let rid = role_id("fuzz_target");
|
||||
(issuer.public_key(), rid)
|
||||
})
|
||||
}
|
||||
|
||||
/// Fuzz BBS+ proof parsing and verification (pairing-heavy; keep corpora small).
|
||||
pub fn bbs_verify_presentation(data: &[u8]) {
|
||||
let data = if data.len() > MAX_BBS_INPUT {
|
||||
&data[..MAX_BBS_INPUT]
|
||||
} else {
|
||||
data
|
||||
};
|
||||
let (pk, rid) = bbs_setup();
|
||||
let pres = Presentation::from_bytes(data.to_vec());
|
||||
let nonce = [0xabu8; 32];
|
||||
let _ = verify_presentation(pk, &pres, rid, 1, &nonce);
|
||||
}
|
||||
|
||||
/// Multiplexed entry for custom fuzz drivers: `data[0] % 6` selects the harness, rest is payload.
|
||||
pub fn dispatch_all(data: &[u8]) {
|
||||
if data.is_empty() {
|
||||
return;
|
||||
}
|
||||
let body = &data[1..];
|
||||
match data[0] % 6 {
|
||||
0 => handshake_respond(body),
|
||||
1 => handshake_initiator_complete(body),
|
||||
2 => session_decrypt(body),
|
||||
3 => replay_sequence(body),
|
||||
4 => crypto_deserialize(body),
|
||||
5 => bbs_verify_presentation(body),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
46
scripts/fuzz-libfuzzer.sh
Executable file
46
scripts/fuzz-libfuzzer.sh
Executable file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run libFuzzer targets via cargo-fuzz (LLVM coverage + mutation).
|
||||
# Usage:
|
||||
# ./scripts/fuzz-libfuzzer.sh # all targets, 60s each, sanitizer none (stable-friendly)
|
||||
# FUZZ_TIME=300 ./scripts/fuzz-libfuzzer.sh session_decrypt
|
||||
# SANITIZER=address ./scripts/fuzz-libfuzzer.sh # needs nightly rustc (rustup)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export PATH="${HOME}/.cargo/bin:${PATH}"
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
if ! command -v cargo-fuzz >/dev/null 2>&1; then
|
||||
echo "Install cargo-fuzz: cargo install cargo-fuzz" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FUZZ_TIME="${FUZZ_TIME:-60}"
|
||||
SANITIZER="${SANITIZER:-none}"
|
||||
|
||||
TARGETS=(
|
||||
handshake_respond
|
||||
handshake_initiator_complete
|
||||
session_decrypt
|
||||
replay_sequence
|
||||
crypto_deserialize
|
||||
bbs_verify_presentation
|
||||
)
|
||||
|
||||
run_one() {
|
||||
local name="$1"
|
||||
echo "=== cargo-fuzz: $name (max_total_time=${FUZZ_TIME}s, sanitizer=${SANITIZER}) ==="
|
||||
cargo fuzz run -s "$SANITIZER" "$name" -- -max_total_time="$FUZZ_TIME" -print_final_stats=1
|
||||
}
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
for name in "$@"; do
|
||||
run_one "$name"
|
||||
done
|
||||
else
|
||||
for name in "${TARGETS[@]}"; do
|
||||
run_one "$name"
|
||||
done
|
||||
fi
|
||||
@ -12,6 +12,9 @@ use crate::{Error, Result};
|
||||
|
||||
const ROLE_ID_DOMAIN: &[u8] = b"zkac-role-id-v1";
|
||||
const BBS_HEADER: &[u8] = b"zkac-bbs-credential-v1";
|
||||
/// zkryptium deserializers index fixed offsets; reject shorter inputs to avoid panics.
|
||||
const MIN_ISSUER_PUBKEY_BYTES: usize = 96;
|
||||
const MIN_POK_PROOF_BYTES: usize = 240;
|
||||
|
||||
type CS = BbsBls12381Shake256;
|
||||
|
||||
@ -119,6 +122,11 @@ impl IssuerPublicKey {
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
if bytes.len() < MIN_ISSUER_PUBKEY_BYTES {
|
||||
return Err(Error::CredentialError(
|
||||
"issuer public key: invalid length".into(),
|
||||
));
|
||||
}
|
||||
let inner = BBSplusPublicKey::from_bytes(bytes)
|
||||
.map_err(|e| Error::CredentialError(e.to_string()))?;
|
||||
Ok(Self { inner })
|
||||
@ -234,6 +242,10 @@ pub fn verify_presentation(
|
||||
epoch: u64,
|
||||
nonce: &[u8],
|
||||
) -> Result<()> {
|
||||
if presentation.proof_bytes.len() < MIN_POK_PROOF_BYTES {
|
||||
return Err(Error::InvalidPresentation);
|
||||
}
|
||||
|
||||
let proof = PoKSignature::<CS>::from_bytes(&presentation.proof_bytes)
|
||||
.map_err(|e| Error::CredentialError(format!("invalid proof: {e}")))?;
|
||||
|
||||
@ -386,6 +398,21 @@ mod tests {
|
||||
assert_eq!(issuer2.public_key().to_bytes(), pk_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issuer_pk_rejects_too_short() {
|
||||
assert!(IssuerPublicKey::from_bytes(&[]).is_err());
|
||||
assert!(IssuerPublicKey::from_bytes(&[0u8; 95]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_presentation_rejects_too_short_proof() {
|
||||
let issuer = IssuerKeyPair::generate().unwrap();
|
||||
let pk = issuer.public_key();
|
||||
let rid = role_id("short-proof");
|
||||
assert!(verify_presentation(&pk, &Presentation::from_bytes(vec![]), &rid, 1, b"n").is_err());
|
||||
assert!(verify_presentation(&pk, &Presentation::from_bytes(vec![0u8; 100]), &rid, 1, b"n").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_members_same_role() {
|
||||
let issuer = IssuerKeyPair::generate().unwrap();
|
||||
|
||||
@ -7,4 +7,4 @@ pub use bbs::{
|
||||
prepare_blind_request, role_id, verify_presentation,
|
||||
};
|
||||
pub use roles::RoleRegistry;
|
||||
pub use schnorr::{Keypair, PublicKey};
|
||||
pub use schnorr::{Keypair, PublicKey, Signature, SIGNATURE_LEN};
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
use blake2::Blake2b512;
|
||||
use curve25519_dalek::{
|
||||
constants::RISTRETTO_BASEPOINT_TABLE,
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
use digest::Digest;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
const SCHNORR_DOMAIN: &[u8] = b"zkac-schnorr-v1";
|
||||
|
||||
pub const SIGNATURE_LEN: usize = 64;
|
||||
|
||||
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct SecretKey {
|
||||
scalar: Scalar,
|
||||
@ -19,6 +25,11 @@ pub struct PublicKey {
|
||||
pub(crate) compressed: CompressedRistretto,
|
||||
}
|
||||
|
||||
pub struct Signature {
|
||||
r: CompressedRistretto,
|
||||
s: Scalar,
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct Keypair {
|
||||
secret: SecretKey,
|
||||
@ -26,6 +37,16 @@ pub struct Keypair {
|
||||
public: PublicKey,
|
||||
}
|
||||
|
||||
/// Hash (R || pk || msg) into a Scalar challenge.
|
||||
fn challenge(r: &CompressedRistretto, pk: &CompressedRistretto, msg: &[u8]) -> Scalar {
|
||||
let mut h = Blake2b512::new();
|
||||
h.update(SCHNORR_DOMAIN);
|
||||
h.update(r.as_bytes());
|
||||
h.update(pk.as_bytes());
|
||||
h.update(msg);
|
||||
Scalar::from_hash(h)
|
||||
}
|
||||
|
||||
impl Keypair {
|
||||
pub fn generate<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
|
||||
let scalar = Scalar::random(rng);
|
||||
@ -42,6 +63,23 @@ impl Keypair {
|
||||
pub fn public(&self) -> &PublicKey {
|
||||
&self.public
|
||||
}
|
||||
|
||||
pub fn sign(&self, msg: &[u8]) -> Signature {
|
||||
// Deterministic nonce: H("zkac-schnorr-nonce" || sk || msg) → scalar k.
|
||||
// Avoids dependency on RNG quality at signing time.
|
||||
let mut nh = Blake2b512::new();
|
||||
nh.update(b"zkac-schnorr-nonce");
|
||||
nh.update(self.secret.scalar.as_bytes());
|
||||
nh.update(msg);
|
||||
let k = Scalar::from_hash(nh);
|
||||
|
||||
let r_point = &k * RISTRETTO_BASEPOINT_TABLE;
|
||||
let r_compressed = r_point.compress();
|
||||
let e = challenge(&r_compressed, &self.public.compressed, msg);
|
||||
let s = k + e * self.secret.scalar;
|
||||
|
||||
Signature { r: r_compressed, s }
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
@ -61,6 +99,39 @@ impl PublicKey {
|
||||
pub fn as_compressed(&self) -> &CompressedRistretto {
|
||||
&self.compressed
|
||||
}
|
||||
|
||||
pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<()> {
|
||||
let r_point = sig.r
|
||||
.decompress()
|
||||
.ok_or(Error::DeserializationError("invalid signature R point"))?;
|
||||
let e = challenge(&sig.r, &self.compressed, msg);
|
||||
// Check: s * G == R + e * pk
|
||||
let lhs = &sig.s * RISTRETTO_BASEPOINT_TABLE;
|
||||
let rhs = r_point + e * self.point;
|
||||
if lhs == rhs {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::IdentityVerificationFailed("schnorr signature invalid"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
pub fn to_bytes(&self) -> [u8; SIGNATURE_LEN] {
|
||||
let mut buf = [0u8; SIGNATURE_LEN];
|
||||
buf[..32].copy_from_slice(self.r.as_bytes());
|
||||
buf[32..].copy_from_slice(self.s.as_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8; SIGNATURE_LEN]) -> Result<Self> {
|
||||
let r = CompressedRistretto::from_slice(&bytes[..32])
|
||||
.map_err(|_| Error::DeserializationError("invalid signature R"))?;
|
||||
let s_bytes: [u8; 32] = bytes[32..].try_into().unwrap();
|
||||
let s = Option::from(Scalar::from_canonical_bytes(s_bytes))
|
||||
.ok_or(Error::DeserializationError("invalid signature scalar"))?;
|
||||
Ok(Signature { r, s })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -75,4 +146,44 @@ mod tests {
|
||||
let pk2 = PublicKey::from_bytes(bytes).unwrap();
|
||||
assert_eq!(*kp.public(), pk2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_verify_roundtrip() {
|
||||
let kp = Keypair::generate(&mut OsRng);
|
||||
let msg = b"hello world";
|
||||
let sig = kp.sign(msg);
|
||||
kp.public().verify(msg, &sig).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_serialization() {
|
||||
let kp = Keypair::generate(&mut OsRng);
|
||||
let sig = kp.sign(b"test");
|
||||
let bytes = sig.to_bytes();
|
||||
let sig2 = Signature::from_bytes(&bytes).unwrap();
|
||||
kp.public().verify(b"test", &sig2).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_message_rejected() {
|
||||
let kp = Keypair::generate(&mut OsRng);
|
||||
let sig = kp.sign(b"correct");
|
||||
assert!(kp.public().verify(b"wrong", &sig).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_key_rejected() {
|
||||
let kp1 = Keypair::generate(&mut OsRng);
|
||||
let kp2 = Keypair::generate(&mut OsRng);
|
||||
let sig = kp1.sign(b"msg");
|
||||
assert!(kp2.public().verify(b"msg", &sig).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deterministic_signature() {
|
||||
let kp = Keypair::generate(&mut OsRng);
|
||||
let s1 = kp.sign(b"same msg");
|
||||
let s2 = kp.sign(b"same msg");
|
||||
assert_eq!(s1.to_bytes(), s2.to_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,9 @@ pub enum Error {
|
||||
|
||||
#[error("role not registered")]
|
||||
RoleNotRegistered,
|
||||
|
||||
#[error("identity verification failed: {0}")]
|
||||
IdentityVerificationFailed(&'static str),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
140
src/node.rs
140
src/node.rs
@ -1,8 +1,9 @@
|
||||
//! High-level client/server node: X25519 transport + BBS+ credential authentication.
|
||||
//! High-level client/server node: X25519 transport + BBS+ credential authentication
|
||||
//! with server identity binding (Schnorr signature over session transcript).
|
||||
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use crate::credential::{Keypair, PublicKey, RoleRegistry};
|
||||
use crate::credential::{Keypair, PublicKey, Signature, SIGNATURE_LEN, RoleRegistry};
|
||||
use crate::credential::bbs::{Credential, Presentation};
|
||||
use crate::transport::handshake::{self, InitiatorHandshake, HANDSHAKE_MSG_LEN};
|
||||
use crate::transport::Session;
|
||||
@ -11,8 +12,11 @@ use crate::{Error, Result};
|
||||
/// Maximum BBS+ proof size accepted in an auth packet (DoS bound).
|
||||
pub const MAX_BBS_AUTH_PROOF_BYTES: usize = 256 * 1024;
|
||||
|
||||
/// Identity proof payload: `[pubkey: 32] [signature: 64]`.
|
||||
const IDENTITY_PROOF_LEN: usize = 32 + SIGNATURE_LEN;
|
||||
|
||||
/// A node that can act as either client (initiator) or server (responder).
|
||||
/// Holds the node's long-term identity keypair used for the transport handshake.
|
||||
/// Holds the node's long-term identity keypair.
|
||||
pub struct Node {
|
||||
keypair: Keypair,
|
||||
}
|
||||
@ -43,20 +47,43 @@ impl Node {
|
||||
(PendingConnect { handshake: hs }, msg)
|
||||
}
|
||||
|
||||
/// Complete the connection using a BBS+ credential.
|
||||
/// Produces an encrypted session and an auth packet containing a
|
||||
/// ZK presentation bound to the session transcript.
|
||||
/// Complete the connection: verify the server's identity proof, then
|
||||
/// produce a BBS+ auth packet bound to the session transcript.
|
||||
///
|
||||
/// `identity_proof` is the encrypted packet from `prove_identity`.
|
||||
/// `expected_server_pk` is the pinned server public key the client trusts.
|
||||
///
|
||||
/// Auth payload layout: `[role_id: 32] [epoch: 8 LE] [proof_len: u32 LE] [proof]`
|
||||
/// (`epoch` is redundant with the credential; kept for forward compatibility.)
|
||||
pub fn complete_connect(
|
||||
&self,
|
||||
pending: PendingConnect,
|
||||
response_msg: &[u8; HANDSHAKE_MSG_LEN],
|
||||
identity_proof: &[u8],
|
||||
expected_server_pk: &PublicKey,
|
||||
credential: &Credential,
|
||||
) -> Result<(Session, Vec<u8>)> {
|
||||
let mut session = pending.handshake.complete(response_msg)?;
|
||||
|
||||
// --- Verify server identity ---
|
||||
let id_payload = session.decrypt(identity_proof)?;
|
||||
if id_payload.len() != IDENTITY_PROOF_LEN {
|
||||
return Err(Error::IdentityVerificationFailed("identity proof wrong length"));
|
||||
}
|
||||
|
||||
let mut pk_bytes = [0u8; 32];
|
||||
pk_bytes.copy_from_slice(&id_payload[..32]);
|
||||
let server_pk = PublicKey::from_bytes(pk_bytes)?;
|
||||
|
||||
if server_pk != *expected_server_pk {
|
||||
return Err(Error::IdentityVerificationFailed("server public key mismatch"));
|
||||
}
|
||||
|
||||
let mut sig_bytes = [0u8; SIGNATURE_LEN];
|
||||
sig_bytes.copy_from_slice(&id_payload[32..]);
|
||||
let sig = Signature::from_bytes(&sig_bytes)?;
|
||||
server_pk.verify(session.transcript_hash(), &sig)?;
|
||||
|
||||
// --- BBS+ credential auth ---
|
||||
let transcript = session.transcript_hash();
|
||||
let presentation = credential.present(transcript)?;
|
||||
let proof_bytes = presentation.to_bytes();
|
||||
@ -88,6 +115,19 @@ impl Node {
|
||||
Ok((resp.session, resp.response_msg))
|
||||
}
|
||||
|
||||
/// Produce an encrypted identity proof: the server signs the session
|
||||
/// transcript with its long-term key so the client can verify it is
|
||||
/// talking to the expected server.
|
||||
///
|
||||
/// Payload (encrypted): `[pubkey: 32] [schnorr_signature: 64]`
|
||||
pub fn prove_identity(&self, session: &mut Session) -> Result<Vec<u8>> {
|
||||
let sig = self.keypair.sign(session.transcript_hash());
|
||||
let mut payload = Vec::with_capacity(IDENTITY_PROOF_LEN);
|
||||
payload.extend_from_slice(&self.keypair.public().to_bytes());
|
||||
payload.extend_from_slice(&sig.to_bytes());
|
||||
session.encrypt(&payload)
|
||||
}
|
||||
|
||||
/// Verify the initiator's encrypted BBS+ auth packet.
|
||||
/// Returns the authenticated opaque `role_id` (32 bytes).
|
||||
pub fn verify_auth(
|
||||
@ -128,8 +168,7 @@ mod tests {
|
||||
use crate::credential::bbs::{self, IssuerKeyPair};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[test]
|
||||
fn full_handshake_with_bbs_auth() {
|
||||
fn test_credential() -> (Credential, RoleRegistry, [u8; 32]) {
|
||||
let issuer = IssuerKeyPair::generate().unwrap();
|
||||
let pk = issuer.public_key();
|
||||
let rid = bbs::role_id("admin");
|
||||
@ -143,15 +182,25 @@ mod tests {
|
||||
&sig, req.member_secret, req.prover_blind, rid, 1, &pk,
|
||||
).unwrap();
|
||||
|
||||
(cred, registry, rid)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_handshake_with_identity_and_bbs_auth() {
|
||||
let (cred, registry, rid) = test_credential();
|
||||
|
||||
let client_kp = Keypair::generate(&mut OsRng);
|
||||
let server_kp = Keypair::generate(&mut OsRng);
|
||||
let server_pk = *server_kp.public();
|
||||
let client = Node::new(client_kp);
|
||||
let server = Node::new(server_kp);
|
||||
|
||||
let (pending, init_msg) = client.connect(OsRng);
|
||||
let (mut server_session, response_msg) = server.accept(OsRng, &init_msg).unwrap();
|
||||
let identity_proof = server.prove_identity(&mut server_session).unwrap();
|
||||
|
||||
let (mut client_session, auth_packet) = client
|
||||
.complete_connect(pending, &response_msg, &cred)
|
||||
.complete_connect(pending, &response_msg, &identity_proof, &server_pk, &cred)
|
||||
.unwrap();
|
||||
|
||||
let role_id = server
|
||||
@ -166,6 +215,70 @@ mod tests {
|
||||
assert_eq!(client_session.decrypt(&pkt).unwrap(), b"response");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_server_key_rejected() {
|
||||
let (cred, _, _) = test_credential();
|
||||
|
||||
let client_kp = Keypair::generate(&mut OsRng);
|
||||
let server_kp = Keypair::generate(&mut OsRng);
|
||||
let wrong_pk = Keypair::generate(&mut OsRng);
|
||||
let client = Node::new(client_kp);
|
||||
let server = Node::new(server_kp);
|
||||
|
||||
let (pending, init_msg) = client.connect(OsRng);
|
||||
let (mut server_session, response_msg) = server.accept(OsRng, &init_msg).unwrap();
|
||||
let identity_proof = server.prove_identity(&mut server_session).unwrap();
|
||||
|
||||
// Client expects a different key than what the server actually has
|
||||
let result = client.complete_connect(
|
||||
pending, &response_msg, &identity_proof, wrong_pk.public(), &cred,
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mitm_separate_sessions_detected() {
|
||||
let (cred, _registry, _) = test_credential();
|
||||
|
||||
let client_kp = Keypair::generate(&mut OsRng);
|
||||
let server_kp = Keypair::generate(&mut OsRng);
|
||||
let mitm_kp = Keypair::generate(&mut OsRng);
|
||||
let server_pk = *server_kp.public();
|
||||
let client = Node::new(client_kp);
|
||||
let server = Node::new(server_kp);
|
||||
let mitm = Node::new(mitm_kp);
|
||||
|
||||
// Client initiates to MITM
|
||||
let (pending, init_msg) = client.connect(OsRng);
|
||||
|
||||
// MITM accepts from client, initiates to server
|
||||
let (mut mitm_client_session, mitm_response) = mitm.accept(OsRng, &init_msg).unwrap();
|
||||
let mitm_identity = mitm.prove_identity(&mut mitm_client_session).unwrap();
|
||||
|
||||
// Client tries to verify — MITM signed with wrong key
|
||||
let result = client.complete_connect(
|
||||
pending, &mitm_response, &mitm_identity, &server_pk, &cred,
|
||||
);
|
||||
assert!(result.is_err(), "MITM must be detected: wrong server key");
|
||||
|
||||
// Even if MITM relays the real server's response_msg, it cannot
|
||||
// forge the server's signature over the MITM's transcript.
|
||||
let (_mitm_pending, mitm_init) = mitm.connect(OsRng);
|
||||
let (mut real_server_session, _real_response) = server.accept(OsRng, &mitm_init).unwrap();
|
||||
let real_identity = server.prove_identity(&mut real_server_session).unwrap();
|
||||
|
||||
// The real server's identity proof is bound to the MITM<->server
|
||||
// transcript, not the client<->MITM transcript. The client cannot
|
||||
// use it because the sessions are different DH exchanges.
|
||||
let (pending2, init_msg2) = client.connect(OsRng);
|
||||
let (_mitm_session2, mitm_resp2) = mitm.accept(OsRng, &init_msg2).unwrap();
|
||||
// Relay real_identity (from server) to client — wrong session
|
||||
let result = client.complete_connect(
|
||||
pending2, &mitm_resp2, &real_identity, &server_pk, &cred,
|
||||
);
|
||||
assert!(result.is_err(), "relayed identity proof must fail: different transcript");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bbs_wrong_role_rejected() {
|
||||
let issuer = IssuerKeyPair::generate().unwrap();
|
||||
@ -184,13 +297,16 @@ mod tests {
|
||||
&sig, req.member_secret, req.prover_blind, rid, 1, &pk,
|
||||
).unwrap();
|
||||
|
||||
let server_kp = Keypair::generate(&mut OsRng);
|
||||
let server_pk = *server_kp.public();
|
||||
let client = Node::new(Keypair::generate(&mut OsRng));
|
||||
let server = Node::new(Keypair::generate(&mut OsRng));
|
||||
let server = Node::new(server_kp);
|
||||
|
||||
let (pending, init_msg) = client.connect(OsRng);
|
||||
let (mut server_session, response_msg) = server.accept(OsRng, &init_msg).unwrap();
|
||||
let identity_proof = server.prove_identity(&mut server_session).unwrap();
|
||||
let (_, auth_packet) = client
|
||||
.complete_connect(pending, &response_msg, &cred)
|
||||
.complete_connect(pending, &response_msg, &identity_proof, &server_pk, &cred)
|
||||
.unwrap();
|
||||
|
||||
let verified_rid = server
|
||||
|
||||
@ -3,7 +3,7 @@ use pyo3::prelude::*;
|
||||
use pyo3::types::PyBytes;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::credential::{self, bbs};
|
||||
use crate::credential::{self, bbs, SIGNATURE_LEN};
|
||||
use crate::transport::handshake::HANDSHAKE_MSG_LEN;
|
||||
|
||||
fn to_py_err(e: crate::Error) -> PyErr {
|
||||
@ -38,6 +38,14 @@ impl PyKeypair {
|
||||
inner: *kp.public(),
|
||||
})
|
||||
}
|
||||
|
||||
fn sign<'py>(&self, py: Python<'py>, msg: &[u8]) -> PyResult<Bound<'py, PyBytes>> {
|
||||
let kp = self.inner.as_ref().ok_or_else(|| {
|
||||
PyValueError::new_err("keypair was consumed by Node")
|
||||
})?;
|
||||
let sig = kp.sign(msg);
|
||||
Ok(PyBytes::new(py, &sig.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Ristretto PublicKey ──────────────────────────────────────────────
|
||||
@ -74,6 +82,18 @@ impl PyPublicKey {
|
||||
u64::from_le_bytes(b[..8].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn verify(&self, msg: &[u8], signature: &[u8]) -> PyResult<bool> {
|
||||
if signature.len() != SIGNATURE_LEN {
|
||||
return Err(PyValueError::new_err("signature must be 64 bytes"));
|
||||
}
|
||||
let sig_arr: [u8; SIGNATURE_LEN] = signature.try_into().unwrap();
|
||||
let sig = credential::Signature::from_bytes(&sig_arr).map_err(to_py_err)?;
|
||||
match self.inner.verify(msg, &sig) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
let hex = bytes_to_hex(&self.inner.to_bytes());
|
||||
format!("PublicKey({hex})")
|
||||
@ -369,11 +389,13 @@ impl PyNode {
|
||||
)
|
||||
}
|
||||
|
||||
/// Complete handshake with BBS+ credential authentication.
|
||||
/// Complete handshake: verify server identity, then produce BBS+ auth.
|
||||
fn complete_connect(
|
||||
&self,
|
||||
pending: &mut PyPendingConnect,
|
||||
response_msg: &[u8],
|
||||
identity_proof: &[u8],
|
||||
expected_server_pk: &PyPublicKey,
|
||||
credential: &PyCredential,
|
||||
) -> PyResult<(PySession, Vec<u8>)> {
|
||||
let p = pending
|
||||
@ -388,12 +410,28 @@ impl PyNode {
|
||||
|
||||
let (session, auth_packet) = self
|
||||
.inner
|
||||
.complete_connect(p, &msg, &credential.inner)
|
||||
.complete_connect(
|
||||
p,
|
||||
&msg,
|
||||
identity_proof,
|
||||
&expected_server_pk.inner,
|
||||
&credential.inner,
|
||||
)
|
||||
.map_err(to_py_err)?;
|
||||
|
||||
Ok((PySession { inner: session }, auth_packet))
|
||||
}
|
||||
|
||||
/// Produce encrypted identity proof (server signs transcript with long-term key).
|
||||
fn prove_identity<'py>(
|
||||
&self,
|
||||
py: Python<'py>,
|
||||
session: &mut PySession,
|
||||
) -> PyResult<Bound<'py, PyBytes>> {
|
||||
let proof = self.inner.prove_identity(&mut session.inner).map_err(to_py_err)?;
|
||||
Ok(PyBytes::new(py, &proof))
|
||||
}
|
||||
|
||||
fn accept(&self, init_msg: &[u8]) -> PyResult<(PySession, Vec<u8>)> {
|
||||
if init_msg.len() != HANDSHAKE_MSG_LEN {
|
||||
return Err(PyValueError::new_err("init_msg must be 32 bytes"));
|
||||
|
||||
@ -41,6 +41,16 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fuzz builds only (`fuzz-expose` feature).
|
||||
#[cfg(feature = "fuzz-expose")]
|
||||
pub fn new_fuzz(
|
||||
send_key: [u8; 32],
|
||||
recv_key: [u8; 32],
|
||||
transcript_hash: [u8; 32],
|
||||
) -> Self {
|
||||
Self::new(send_key, recv_key, transcript_hash)
|
||||
}
|
||||
|
||||
/// Transcript hash binding this session to a specific handshake.
|
||||
/// Sign this value for authentication proofs.
|
||||
pub fn transcript_hash(&self) -> &[u8; 32] {
|
||||
|
||||
@ -168,6 +168,31 @@ class TestBbsCredentials:
|
||||
assert cred.epoch() == 42
|
||||
|
||||
|
||||
class TestSchnorrSignature:
|
||||
def test_sign_verify(self):
|
||||
kp = zkac.Keypair()
|
||||
pk = kp.public_key()
|
||||
sig = kp.sign(b"hello")
|
||||
assert len(sig) == 64
|
||||
assert pk.verify(b"hello", sig)
|
||||
|
||||
def test_wrong_message(self):
|
||||
kp = zkac.Keypair()
|
||||
pk = kp.public_key()
|
||||
sig = kp.sign(b"correct")
|
||||
assert not pk.verify(b"wrong", sig)
|
||||
|
||||
def test_wrong_key(self):
|
||||
kp1 = zkac.Keypair()
|
||||
kp2 = zkac.Keypair()
|
||||
sig = kp1.sign(b"msg")
|
||||
assert not kp2.public_key().verify(b"msg", sig)
|
||||
|
||||
def test_deterministic(self):
|
||||
kp = zkac.Keypair()
|
||||
assert kp.sign(b"same") == kp.sign(b"same")
|
||||
|
||||
|
||||
class TestNodeHandshake:
|
||||
def _make_credential(self):
|
||||
issuer = zkac.BbsIssuer()
|
||||
@ -187,12 +212,15 @@ class TestNodeHandshake:
|
||||
reg.register_role(rid, pk, 1)
|
||||
|
||||
client = zkac.Node(zkac.Keypair())
|
||||
server = zkac.Node(zkac.Keypair())
|
||||
server_kp = zkac.Keypair()
|
||||
server_pk = server_kp.public_key()
|
||||
server = zkac.Node(server_kp)
|
||||
|
||||
pending, init_msg = client.connect()
|
||||
server_session, response_msg = server.accept(init_msg)
|
||||
identity_proof = server.prove_identity(server_session)
|
||||
client_session, auth_packet = client.complete_connect(
|
||||
pending, response_msg, cred
|
||||
pending, response_msg, identity_proof, server_pk, cred
|
||||
)
|
||||
|
||||
verified_rid = server.verify_auth(server_session, auth_packet, reg)
|
||||
@ -204,18 +232,37 @@ class TestNodeHandshake:
|
||||
pkt = server_session.encrypt(b"response")
|
||||
assert client_session.decrypt(pkt) == b"response"
|
||||
|
||||
def test_wrong_server_key_rejected(self):
|
||||
_, pk, rid, cred = self._make_credential()
|
||||
|
||||
client = zkac.Node(zkac.Keypair())
|
||||
server = zkac.Node(zkac.Keypair())
|
||||
wrong_pk = zkac.Keypair().public_key()
|
||||
|
||||
pending, init_msg = client.connect()
|
||||
server_session, response_msg = server.accept(init_msg)
|
||||
identity_proof = server.prove_identity(server_session)
|
||||
|
||||
with pytest.raises(ValueError, match="identity verification failed"):
|
||||
client.complete_connect(
|
||||
pending, response_msg, identity_proof, wrong_pk, cred
|
||||
)
|
||||
|
||||
def test_replay_rejected(self):
|
||||
_, pk, rid, cred = self._make_credential()
|
||||
reg = zkac.RoleRegistry()
|
||||
reg.register_role(rid, pk, 1)
|
||||
|
||||
client = zkac.Node(zkac.Keypair())
|
||||
server = zkac.Node(zkac.Keypair())
|
||||
server_kp = zkac.Keypair()
|
||||
server_pk = server_kp.public_key()
|
||||
server = zkac.Node(server_kp)
|
||||
|
||||
pending, init_msg = client.connect()
|
||||
server_session, response_msg = server.accept(init_msg)
|
||||
identity_proof = server.prove_identity(server_session)
|
||||
client_session, auth_packet = client.complete_connect(
|
||||
pending, response_msg, cred
|
||||
pending, response_msg, identity_proof, server_pk, cred
|
||||
)
|
||||
server.verify_auth(server_session, auth_packet, reg)
|
||||
|
||||
@ -230,12 +277,15 @@ class TestNodeHandshake:
|
||||
reg.register_role(rid, pk, 1)
|
||||
|
||||
client = zkac.Node(zkac.Keypair())
|
||||
server = zkac.Node(zkac.Keypair())
|
||||
server_kp = zkac.Keypair()
|
||||
server_pk = server_kp.public_key()
|
||||
server = zkac.Node(server_kp)
|
||||
|
||||
pending, init_msg = client.connect()
|
||||
server_session, response_msg = server.accept(init_msg)
|
||||
identity_proof = server.prove_identity(server_session)
|
||||
client_session, auth_packet = client.complete_connect(
|
||||
pending, response_msg, cred
|
||||
pending, response_msg, identity_proof, server_pk, cred
|
||||
)
|
||||
server.verify_auth(server_session, auth_packet, reg)
|
||||
|
||||
@ -248,11 +298,18 @@ class TestNodeHandshake:
|
||||
_, pk, rid, cred = self._make_credential()
|
||||
|
||||
client = zkac.Node(zkac.Keypair())
|
||||
server = zkac.Node(zkac.Keypair())
|
||||
server_kp = zkac.Keypair()
|
||||
server_pk = server_kp.public_key()
|
||||
server = zkac.Node(server_kp)
|
||||
|
||||
pending, init_msg = client.connect()
|
||||
_, response_msg = server.accept(init_msg)
|
||||
client.complete_connect(pending, response_msg, cred)
|
||||
server_session, response_msg = server.accept(init_msg)
|
||||
identity_proof = server.prove_identity(server_session)
|
||||
client.complete_connect(
|
||||
pending, response_msg, identity_proof, server_pk, cred
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="consumed"):
|
||||
client.complete_connect(pending, response_msg, cred)
|
||||
client.complete_connect(
|
||||
pending, response_msg, identity_proof, server_pk, cred
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user