Add FROST threshold signing for Orchard spends#150
Open
zmanian wants to merge 8 commits intozcash:mainfrom
Open
Add FROST threshold signing for Orchard spends#150zmanian wants to merge 8 commits intozcash:mainfrom
zmanian wants to merge 8 commits intozcash:mainfrom
Conversation
Add 13 tests to frost_serde.rs covering the full FROST protocol: - DKG ceremony (2-of-3) with serde round-trips on all messages - Signing ceremony with rerandomization and full JSON round-trips - Standalone round-trip tests for all serde types (IdHex, KeyPackageStore, PublicKeyPackageStore, DkgRound1PackageStore, DkgRound2PackageStore, SigningCommitmentsStore, SigningPackageStore) - Error-path tests for invalid hex, wrong lengths, truncated fields Add doc/frost-walkthrough.md with end-to-end usage guide covering DKG setup, address generation, chain scanning, threshold signing ceremony, file layout, security considerations, and troubleshooting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nversion Test the two critical integration boundaries between the FROST protocol and the Zcash wallet: DKG group key to Orchard FullViewingKey (with ak sign bit retry logic), and FROST aggregate signature to orchard_redpallas::Signature<SpendAuth>. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a Testing section to the FROST walkthrough cataloguing all 17 tests across protocol/serde round-trips, error handling, and the new Zcash wallet bridge tests (FVK construction and signature conversion). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement the three CLI commands behind the `frost` feature flag: - `wallet frost-dkg`: Interactive distributed key generation ceremony. Participants cooperate over three rounds to produce FROST key shares and a shared Orchard Full Viewing Key (ak from DKG, nk/rivk from coordinator). The resulting UFVK is imported as a view-only wallet account and key material is stored in frost.toml (encrypted with age). - `pczt frost-sign`: Coordinator-side signing ceremony. Reads a PCZT, extracts sighash and spend auth randomizers, orchestrates two rounds of message exchange with participants, aggregates signature shares, converts to orchard_redpallas::Signature<SpendAuth>, and applies signatures to the PCZT. - `pczt frost-participate`: Participant-side signing. Loads the encrypted key package from frost.toml, generates nonce commitments (round 1), and computes signature shares (round 2). Supporting modules: - frost_config.rs: FROST configuration file (frost.toml) management with age encryption/decryption for key material at rest. - Cargo.toml: frost feature flag gating frost-core, frost-rerandomized, reddsa, and orchard/unstable-frost dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntation Critical: - Clear high bit on nk/rivk to ensure valid Pallas field elements (~75% DKG failure rate without this) - Check ak sign bit after DKG and fail with clear message instead of cryptic FVK construction error - Participant now verifies sighash in round 2 signing packages matches round 1 request (prevents malicious coordinator substitution) - Alpha byte extraction returns proper error instead of silent corruption via unwrap_or(0) Security: - Encrypt nk/rivk with age before storing in frost.toml (same as key_package) - Write frost.toml with 0600 permissions on Unix - Remove unused --identity flag from frost-sign (was accepted but never verified) Correctness: - Duplicate participant detection in all DKG and signing collection loops - Cross-round participant set validation before signature aggregation - UUID formatted with expose_uuid() instead of Debug formatting - Upper-bound validation on num_signers vs max_signers - Birthday=0 rejected instead of causing underflow - Round 2 from_id validated against known round 1 participants - Duplicate account UUID check before writing to frost.toml - PublicKeyPackageStore uses BTreeMap for deterministic serialization Robustness: - Replace expect() with proper error on block height and UTF-8 encoding - Eliminate TOCTOU in FrostConfig::read; add fsync on write - Surface age identity parse errors instead of silently dropping - Trim input lines before JSON parsing in all interactive rounds - IdHex inner field restricted to pub(crate) - Config round-trip test now verifies all fields Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove alpha_hex from ActionSigningData (H2): the spend auth randomizer scalar was unnecessarily leaked to participants in the signing request. Participants only need the randomizer point (alpha * G) sent in Round 2. - Reduce PCZT parsing from 3x to 2x (H5): extract alpha values from pczt.orchard().actions() before consuming into Signer, eliminating one redundant Pczt::parse call. - Add frost_signing_sighash_mismatch_detected test: exercises the participant-side sighash verification from frost_participate.rs. - Add frost_aggregate_wrong_signer_set_fails test: verifies aggregation rejects shares from signers not in the commitment set. - Add frost_production_fvk_path test: covers the exact FVK construction, age encryption round-trip, and key package storage path from frost_dkg.rs. - Update walkthrough test count from 17 to 20. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- H1: frost_sign reads PCZT from a file argument instead of stdin, fixing the broken mixed async/blocking stdin pattern where read_to_end consumed stdin to EOF before interactive JSON reads. - H4: Document the brittle alpha extraction via serde JSON introspection with a HACK comment explaining the pczt crate limitation (no public getter for Spend.alpha). - H8: Add security warnings to DKG Round 2 output labeling each package as a SECRET SHARE that must be sent privately. - M9: Remove unnecessary async from frost_participate (all I/O is blocking std::io, no async operations). - Extract duplicate account selection logic into FrostConfig::resolve_account(), used by both frost_sign and frost_participate. - Skip empty/whitespace-only lines in all interactive input loops (frost_sign, frost_dkg) to prevent confusing JSON parse errors from accidental Enter presses. - Update walkthrough for new frost-sign file argument syntax. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add zeroize dependency (gated behind frost feature) and implement Drop for KeyPackageStore to securely erase the signing_share field - Fix FVK share input to skip empty lines, matching other interactive loops - Add explanatory comment for AccountPurpose::ViewOnly on FROST accounts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add FROST (Flexible Round-Optimized Schnorr Threshold Signatures) support for Zcash Orchard spends, behind the
frostfeature flag. This enables t-of-n threshold signing for shielded transactions.Implementation
Three new CLI commands:
wallet frost-dkg-- Interactive distributed key generation ceremony. All participants cooperate over three rounds to produce FROST key shares and a shared Orchard Full Viewing Key (akfrom DKG,nk/rivkfrom coordinator). The UFVK is imported as a view-only wallet account; key material is stored infrost.toml(encrypted withage).pczt frost-sign-- Coordinator-side signing ceremony. Reads a PCZT from stdin, extracts sighash and spend auth randomizers (alpha), orchestrates two rounds of message exchange, aggregates signature shares intoorchard_redpallas::Signature<SpendAuth>, and applies them to the PCZT viaSigner::apply_orchard_signature().pczt frost-participate-- Participant-side signing. Loads the encrypted key package fromfrost.toml, generates nonce commitments (round 1), and computes signature shares with rerandomization (round 2).Supporting modules
frost_serde.rs-- Serialization layer for all FROST protocol messages (DKG rounds, signing rounds, key packages) via hex-encoded JSON, sincePallasBlake2b512doesn't implement serde directly.frost_config.rs-- FROST configuration file (frost.toml) management withageencryption/decryption for key material at rest.Cargo.toml--frostfeature flag gatingfrost-core,frost-rerandomized,reddsa, andorchard/unstable-frostdependencies.Documentation
doc/frost-walkthrough.mdcovers the full workflow end-to-end:ak/nk/rivkcompose the Orchard FVK)frost.tomlstructureTest coverage
17 tests pass (
cargo test --features frost -- frost):Protocol and serde round-trips (11 tests):
dkg_full_ceremony_2_of_3frost_signing_with_rerandomizationidentifier_hex_round_tripIdHexserde for identifiers 1-5key_package_store_round_tripKeyPackageStoreJSON round-trippublic_key_package_store_round_tripPublicKeyPackageStoreJSON round-tripdkg_round1_package_store_round_tripDkgRound1PackageStoreJSON round-tripdkg_round2_package_store_round_tripDkgRound2PackageStoreJSON round-tripsigning_commitments_store_round_tripSigningCommitmentsStoreJSON round-tripsigning_package_store_round_tripSigningPackageStoreJSON round-tripfrost_config_round_tripFrostConfigTOML round-tripencrypt_decrypt_round_tripError handling (4 tests):
id_hex_rejects_invalid_hexid_hex_rejects_wrong_lengthkey_package_store_rejects_truncated_fieldssigning_commitments_store_rejects_bad_hexZcash wallet bridge (2 tests):
frost_dkg_to_orchard_fvk_and_addressFullViewingKey, address derivation,UnifiedFullViewingKeywrapping, and FVK round-trip (includesaksign bit retry logic)frost_signature_to_orchard_spendauthorchard_redpallas::Signature<SpendAuth>conversion and byte-level round-tripTest plan
cargo test --features frost -- frostpasses (17/17)cargo check --features frostcompiles clean (no warnings)Generated with Claude Code