From c0b6b9171725f17bc3774770ff0d3a5663a9b795 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Wed, 22 Apr 2026 16:22:32 -0600 Subject: [PATCH 1/3] feat: add unstable-voting-circuits feature to widen internals Mirrors the existing `unstable-frost` pattern: flag off leaves the public API byte-identical to main; flag on promotes the `pub(crate)` items a downstream voting circuit needs to `pub`. Uses `#[cfg_attr(..., visibility::make(pub))]` for most items, with cfg twin-declarations where the `visibility` proc-macro can't apply on stable (non-inline modules, tuple-struct fields). --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/address.rs | 4 ++++ src/circuit.rs | 10 +++++++++- src/circuit/commit_ivk.rs | 7 +++++++ src/circuit/gadget.rs | 9 +++++++++ src/circuit/gadget/add_chip.rs | 8 ++++++++ src/circuit/note_commit.rs | 8 ++++++++ src/constants.rs | 6 +++--- src/keys.rs | 16 +++++++++++++++- src/lib.rs | 9 +++++++++ src/note.rs | 19 ++++++++++++++++++- src/note/commitment.rs | 8 ++++++++ src/note/nullifier.rs | 14 +++++++++++++- src/spec.rs | 2 ++ src/tree.rs | 2 ++ src/value.rs | 5 ++++- 17 files changed, 150 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a449a0b..6ae6cb5cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,36 @@ and this project adheres to Rust's notion of - `orchard::primitives::redpallas::testing::arb_valid_spendauth_keypair` (under the `test-dependencies` feature): a uniformly-distributed valid `(rsk, rk)` key pair with non-identity `rk`. +- `orchard::{L_ORCHARD_BASE, L_ORCHARD_SCALAR, L_VALUE}`, the bit-length + parameters of the Orchard base field, scalar field, and value encoding + as defined in the Zcash protocol specification. +- `orchard::value::NoteValue::zero`, equivalent to `NoteValue::from_raw(0)`. +- The following modules and APIs are available behind the + `unstable-voting-circuits` feature flag to support downstream + voting-circuit development. These temporary APIs are not covered by the + crate's semver stability guarantees and may change in any future release: + - Modules: `orchard::{constants, spec}`, + `orchard::circuit::{commit_ivk, commit_ivk::gadgets, note_commit, + note_commit::gadgets, gadget::add_chip}`, + `orchard::note::{commitment, nullifier}`. + - Address and circuit helpers: `Address::{g_d, pk_d}`, + `circuit::gadget::{AddInstruction, assign_free_advice, derive_nullifier, + commit_ivk, note_commit}`, + `circuit::gadget::add_chip::{AddConfig, AddChip}` and + `AddChip::{configure, construct}`, + `CommitIvkChip::{configure, construct}`, + `NoteCommitChip::{configure, construct}`. + - Key, note, tree, and value APIs: `SpendingKey::random`, + `SpendAuthorizingKey::derive_inner`, `NullifierDerivingKey` and + `CommitIvkRandomness` and their `inner` methods, + `FullViewingKey::{nk, rivk}`, + `DiversifiedTransmissionKey::{inner, to_bytes}`, + `orchard::note::NoteCommitTrapdoor` and `NoteCommitTrapdoor::inner`, + `Rho::{from_nf_old, into_inner}`, `RandomSeed::{psi, rcm}`, + `Note::{new, dummy}`, `NoteCommitment::inner`, + `ExtractedNoteCommitment::inner`, `Nullifier::{from_inner, inner}`, + `NonIdentityPallasPoint` and `NonIdentityPallasPoint::from_bytes`, + `MerklePath::dummy`, and `MerkleHashOrchard::inner`. ### Changed - MSRV is now 1.85.1 diff --git a/Cargo.toml b/Cargo.toml index 652c09c0d..9bcda2c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ default = ["circuit", "multicore", "std"] std = ["corez/std", "group/wnaf-memuse", "reddsa/std"] circuit = ["dep:halo2_gadgets", "dep:halo2_proofs", "std"] unstable-frost = [] +unstable-voting-circuits = [] multicore = ["halo2_proofs?/multicore"] dev-graph = ["halo2_proofs?/dev-graph", "image", "plotters"] test-dependencies = ["proptest", "rand/std"] diff --git a/src/address.rs b/src/address.rs index 6b643ad35..bc79e5027 100644 --- a/src/address.rs +++ b/src/address.rs @@ -35,10 +35,14 @@ impl Address { self.d } + /// Returns the diversified base point for this address. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn g_d(&self) -> NonIdentityPallasPoint { diversify_hash(self.d.as_array()) } + /// Returns the diversified transmission key for this address. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn pk_d(&self) -> &DiversifiedTransmissionKey { &self.pk_d } diff --git a/src/circuit.rs b/src/circuit.rs index 04667262a..ef9f458c5 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -58,9 +58,17 @@ use halo2_gadgets::{ utilities::lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, }; +#[cfg(not(feature = "unstable-voting-circuits"))] mod commit_ivk; +#[cfg(feature = "unstable-voting-circuits")] +#[allow(missing_docs)] +pub mod commit_ivk; pub mod gadget; +#[cfg(not(feature = "unstable-voting-circuits"))] mod note_commit; +#[cfg(feature = "unstable-voting-circuits")] +#[allow(missing_docs)] +pub mod note_commit; pub use crate::Proof; @@ -890,7 +898,7 @@ impl Instance { instance[ANCHOR] = self.anchor.inner(); instance[CV_NET_X] = self.cv_net.x(); instance[CV_NET_Y] = self.cv_net.y(); - instance[NF_OLD] = self.nf_old.0; + instance[NF_OLD] = self.nf_old.inner(); let rk = pallas::Point::from_bytes(&self.rk.clone().into()) .expect("the cached byte encoding of a VerificationKey<_> is canonical") diff --git a/src/circuit/commit_ivk.rs b/src/circuit/commit_ivk.rs index 351cb4681..60fef54d4 100644 --- a/src/circuit/commit_ivk.rs +++ b/src/circuit/commit_ivk.rs @@ -27,6 +27,8 @@ pub struct CommitIvkChip { } impl CommitIvkChip { + /// Configures the chip's gate and column assignments. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn configure( meta: &mut ConstraintSystem, advices: [Column; 10], @@ -222,11 +224,15 @@ impl CommitIvkChip { config } + /// Constructs the chip from a [`CommitIvkConfig`]. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn construct(config: CommitIvkConfig) -> Self { Self { config } } } +/// Gadget functions for `CommitIvk` operations. +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) mod gadgets { use halo2_gadgets::utilities::{ lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig}, @@ -241,6 +247,7 @@ pub(in crate::circuit) mod gadgets { /// [Section 5.4.8.4 Sinsemilla commitments]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillacommit #[allow(non_snake_case)] #[allow(clippy::type_complexity)] + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn commit_ivk( sinsemilla_chip: SinsemillaChip< OrchardHashDomains, diff --git a/src/circuit/gadget.rs b/src/circuit/gadget.rs index 12c3c90e6..34d7673fa 100644 --- a/src/circuit/gadget.rs +++ b/src/circuit/gadget.rs @@ -24,7 +24,11 @@ use halo2_proofs::{ plonk::{self, Advice, Assigned, Column}, }; +#[cfg(not(feature = "unstable-voting-circuits"))] pub(in crate::circuit) mod add_chip; +/// Addition chip for constraining `a + b = c` in-circuit. +#[cfg(feature = "unstable-voting-circuits")] +pub mod add_chip; impl super::Config { pub(super) fn add_chip(&self) -> add_chip::AddChip { @@ -77,6 +81,7 @@ impl super::Config { } /// An instruction set for adding two circuit words (field elements). +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) trait AddInstruction: Chip { /// Constraints `a + b` and returns the sum. fn add( @@ -92,6 +97,7 @@ pub(in crate::circuit) trait AddInstruction: Chip { /// Usages of this helper are technically superfluous, as the single-cell region is only /// ever used in equality constraints. We could eliminate them with a /// [write-on-copy abstraction](https://github.com/zcash/halo2/issues/334). +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn assign_free_advice( mut layouter: impl Layouter, column: Column, @@ -145,6 +151,7 @@ pub(in crate::circuit) fn value_commit_orchard< /// /// [Section 4.16: Note Commitments and Nullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers #[allow(clippy::too_many_arguments)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn derive_nullifier< PoseidonChip: PoseidonSpongeInstructions, 3, 2>, AddChip: AddInstruction, @@ -199,5 +206,7 @@ pub(in crate::circuit) fn derive_nullifier< .map(|res| res.extract_p()) } +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) use crate::circuit::commit_ivk::gadgets::commit_ivk; +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) use crate::circuit::note_commit::gadgets::note_commit; diff --git a/src/circuit/gadget/add_chip.rs b/src/circuit/gadget/add_chip.rs index 0973a3f3b..3d387f526 100644 --- a/src/circuit/gadget/add_chip.rs +++ b/src/circuit/gadget/add_chip.rs @@ -7,7 +7,9 @@ use pasta_curves::pallas; use super::AddInstruction; +/// Configuration for the addition chip. #[derive(Clone, Debug)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) struct AddConfig { a: Column, b: Column, @@ -16,6 +18,8 @@ pub(in crate::circuit) struct AddConfig { } /// A chip implementing a single addition constraint `c = a + b` on a single row. +#[derive(Debug)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) struct AddChip { config: AddConfig, } @@ -34,6 +38,8 @@ impl Chip for AddChip { } impl AddChip { + /// Configures the addition chip with the given advice columns. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn configure( meta: &mut ConstraintSystem, a: Column, @@ -53,6 +59,8 @@ impl AddChip { AddConfig { a, b, c, q_add } } + /// Constructs an addition chip from the given config. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn construct(config: AddConfig) -> Self { Self { config } } diff --git a/src/circuit/note_commit.rs b/src/circuit/note_commit.rs index bba540f5e..23f89a658 100644 --- a/src/circuit/note_commit.rs +++ b/src/circuit/note_commit.rs @@ -1437,8 +1437,10 @@ pub struct NoteCommitChip { } impl NoteCommitChip { + /// Configures the chip's gates, Sinsemilla instances, and canonicity checks. #[allow(non_snake_case)] #[allow(clippy::many_single_char_names)] + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn configure( meta: &mut ConstraintSystem, advices: [Column; 10], @@ -1558,19 +1560,25 @@ impl NoteCommitChip { } } + /// Constructs the chip from a [`NoteCommitConfig`]. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn construct(config: NoteCommitConfig) -> Self { Self { config } } } +/// Gadget functions for `NoteCommit` operations. +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) mod gadgets { use halo2_proofs::circuit::{Chip, Value}; use super::*; + /// Computes the note commitment in-circuit. #[allow(clippy::many_single_char_names)] #[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(in crate::circuit) fn note_commit( mut layouter: impl Layouter, chip: SinsemillaChip, diff --git a/src/constants.rs b/src/constants.rs index 62548f9c1..5931ceace 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -20,13 +20,13 @@ pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113; pub(crate) const T_P: u128 = 45560315531419706090280762371685220353; /// $\ell^\mathsf{Orchard}_\mathsf{base}$ -pub(crate) const L_ORCHARD_BASE: usize = 255; +pub const L_ORCHARD_BASE: usize = 255; /// $\ell^\mathsf{Orchard}_\mathsf{scalar}$ -pub(crate) const L_ORCHARD_SCALAR: usize = 255; +pub const L_ORCHARD_SCALAR: usize = 255; /// $\ell_\mathsf{value}$ -pub(crate) const L_VALUE: usize = 64; +pub const L_VALUE: usize = 64; /// SWU hash-to-curve personalization for the group hash for key diversification pub const KEY_DIVERSIFICATION_PERSONALIZATION: &str = "z.cash:Orchard-gd"; diff --git a/src/keys.rs b/src/keys.rs index 567a44a4d..6ae0df476 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -54,6 +54,7 @@ impl SpendingKey { /// derived according to [ZIP 32]. /// /// [ZIP 32]: https://zips.z.cash/zip-0032 + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn random(rng: &mut impl RngCore) -> Self { loop { let mut bytes = [0; 32]; @@ -121,7 +122,8 @@ pub struct SpendAuthorizingKey(redpallas::SigningKey); impl SpendAuthorizingKey { /// Derives ask from sk. Internal use only, does not enforce all constraints. - fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] + pub(crate) fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { to_scalar(PrfExpand::ORCHARD_ASK.with(&sk.0)) } @@ -226,9 +228,12 @@ impl SpendValidatingKey { /// [`Note`]: crate::note::Note /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) struct NullifierDerivingKey(pallas::Base); impl NullifierDerivingKey { + /// Returns the inner base field element. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> pallas::Base { self.0 } @@ -267,6 +272,7 @@ impl NullifierDerivingKey { /// /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents #[derive(Copy, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) struct CommitIvkRandomness(pallas::Scalar); impl From<&SpendingKey> for CommitIvkRandomness { @@ -276,6 +282,8 @@ impl From<&SpendingKey> for CommitIvkRandomness { } impl CommitIvkRandomness { + /// Returns the inner scalar value. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> pallas::Scalar { self.0 } @@ -334,11 +342,14 @@ impl From for SpendValidatingKey { } impl FullViewingKey { + /// Returns the nullifier deriving key for this full viewing key. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn nk(&self) -> &NullifierDerivingKey { &self.nk } /// Returns either `rivk` or `rivk_internal` based on `scope`. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn rivk(&self, scope: Scope) -> CommitIvkRandomness { match scope { Scope::External => self.rivk, @@ -745,6 +756,8 @@ impl AsRef<[u8; 32]> for OutgoingViewingKey { pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint); impl DiversifiedTransmissionKey { + /// Returns the inner `NonIdentityPallasPoint`. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> NonIdentityPallasPoint { self.0 } @@ -765,6 +778,7 @@ impl DiversifiedTransmissionKey { } /// $repr_P(self)$ + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn to_bytes(self) -> [u8; 32] { self.0.to_bytes() } diff --git a/src/lib.rs b/src/lib.rs index 960e1e7dc..87ce6720d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,13 +31,21 @@ pub mod builder; pub mod bundle; #[cfg(feature = "circuit")] pub mod circuit; +#[cfg(not(feature = "unstable-voting-circuits"))] mod constants; +#[cfg(feature = "unstable-voting-circuits")] +#[allow(missing_docs)] +pub mod constants; pub mod keys; pub mod note; pub mod note_encryption; pub mod pczt; pub mod primitives; +#[cfg(not(feature = "unstable-voting-circuits"))] mod spec; +#[cfg(feature = "unstable-voting-circuits")] +#[allow(missing_docs)] +pub mod spec; pub mod tree; pub mod value; pub mod zip32; @@ -49,6 +57,7 @@ pub use action::Action; pub use address::Address; pub use bundle::Bundle; pub use constants::MERKLE_DEPTH_ORCHARD as NOTE_COMMITMENT_TREE_DEPTH; +pub use constants::{L_ORCHARD_BASE, L_ORCHARD_SCALAR, L_VALUE}; pub use note::Note; pub use tree::Anchor; diff --git a/src/note.rs b/src/note.rs index 6a9c70b5a..53d04f5ec 100644 --- a/src/note.rs +++ b/src/note.rs @@ -15,10 +15,20 @@ use crate::{ Address, }; +#[cfg(not(feature = "unstable-voting-circuits"))] pub(crate) mod commitment; +#[cfg(feature = "unstable-voting-circuits")] +#[allow(missing_docs)] +pub mod commitment; +#[cfg(feature = "unstable-voting-circuits")] +pub use self::commitment::NoteCommitTrapdoor; pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment}; +#[cfg(not(feature = "unstable-voting-circuits"))] pub(crate) mod nullifier; +#[cfg(feature = "unstable-voting-circuits")] +#[allow(missing_docs)] +pub mod nullifier; pub use self::nullifier::Nullifier; /// The randomness used to construct a note. @@ -50,10 +60,13 @@ impl Rho { /// of the note being spent in the [`Action`] under construction. /// /// [`Action`]: crate::action::Action + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn from_nf_old(nf: Nullifier) -> Self { - Rho(nf.0) + Rho(nf.inner()) } + /// Consumes `self` and returns the inner field element. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn into_inner(self) -> pallas::Base { self.0 } @@ -92,6 +105,7 @@ impl RandomSeed { /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn psi(&self, rho: &Rho) -> pallas::Base { to_base(PrfExpand::PSI.with(&self.0, &rho.to_bytes())) } @@ -116,6 +130,7 @@ impl RandomSeed { /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn rcm(&self, rho: &Rho) -> commitment::NoteCommitTrapdoor { commitment::NoteCommitTrapdoor(to_scalar( PrfExpand::ORCHARD_RCM.with(&self.0, &rho.to_bytes()), @@ -186,6 +201,7 @@ impl Note { /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend]. /// /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn new( recipient: Address, value: NoteValue, @@ -205,6 +221,7 @@ impl Note { /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes]. /// /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn dummy( rng: &mut impl RngCore, rho: Option, diff --git a/src/note/commitment.rs b/src/note/commitment.rs index 5d0125874..6035bce91 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -11,10 +11,14 @@ use crate::{ value::NoteValue, }; +/// The trapdoor for a note commitment. #[derive(Clone, Debug)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) struct NoteCommitTrapdoor(pub(super) pallas::Scalar); impl NoteCommitTrapdoor { + /// Returns the inner scalar value. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> pallas::Scalar { self.0 } @@ -25,6 +29,8 @@ impl NoteCommitTrapdoor { pub struct NoteCommitment(pub(super) pallas::Point); impl NoteCommitment { + /// Returns the inner Pallas curve point. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> pallas::Point { self.0 } @@ -87,6 +93,8 @@ impl From for ExtractedNoteCommitment { } impl ExtractedNoteCommitment { + /// Returns the inner field element. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> pallas::Base { self.0 } diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs index 15ac3699f..efcc4ea72 100644 --- a/src/note/nullifier.rs +++ b/src/note/nullifier.rs @@ -12,12 +12,24 @@ use crate::{ /// A unique nullifier for a note. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Nullifier(pub(crate) pallas::Base); +pub struct Nullifier(pallas::Base); // We know that `pallas::Base` doesn't allocate internally. memuse::impl_no_dynamic_usage!(Nullifier); impl Nullifier { + /// Constructs a `Nullifier` from the given Pallas base field element. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] + pub(crate) fn from_inner(inner: pallas::Base) -> Self { + Self(inner) + } + + /// Returns the inner Pallas base field element. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] + pub(crate) fn inner(&self) -> pallas::Base { + self.0 + } + /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes. /// /// Nullifiers are required by consensus to be unique. For dummy output notes, we get diff --git a/src/spec.rs b/src/spec.rs index c7194562b..832bc8fc6 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -24,6 +24,7 @@ pub(crate) use zcash_spec::PrfExpand; /// A Pallas point that is guaranteed to not be the identity. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) struct NonIdentityPallasPoint(pallas::Point); impl Default for NonIdentityPallasPoint { @@ -39,6 +40,7 @@ impl ConditionallySelectable for NonIdentityPallasPoint { } impl NonIdentityPallasPoint { + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption { pallas::Point::from_bytes(bytes) .and_then(|p| CtOption::new(NonIdentityPallasPoint(p), !p.is_identity())) diff --git a/src/tree.rs b/src/tree.rs index dee5b3ac8..6037596f5 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -115,6 +115,7 @@ impl From> for MerklePa impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn dummy(mut rng: &mut impl RngCore) -> Self { MerklePath { position: rng.next_u32(), @@ -183,6 +184,7 @@ impl MerkleHashOrchard { } /// Only used in the circuit. + #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn inner(&self) -> pallas::Base { self.0 } diff --git a/src/value.rs b/src/value.rs index 053ad5838..061a36611 100644 --- a/src/value.rs +++ b/src/value.rs @@ -100,7 +100,10 @@ impl std::error::Error for BalanceError {} pub struct NoteValue(u64); impl NoteValue { - pub(crate) fn zero() -> Self { + /// Returns a zero note value. + /// + /// Equivalent to `NoteValue::from_raw(0)`. + pub fn zero() -> Self { // Default for u64 is zero. Default::default() } From f9f35ab7db23b80d6cae33671d09697e91eaf0ea Mon Sep 17 00:00:00 2001 From: Daira-Emma Hopwood Date: Thu, 23 Apr 2026 19:07:28 +0100 Subject: [PATCH 2/3] docs: replace `#[allow(missing_docs)]` with actual doc comments Remove the six `#[allow(missing_docs)]` attributes gating newly-public modules under `unstable-voting-circuits`, and document every item the lint was suppressing (modules, sub-configs, chip wrappers, enum variants, and the `generator()` entry points of each fixed-base table). The crate-wide `#![deny(missing_docs)]` policy now applies uniformly under the feature rather than being selectively relaxed. Co-authored-by: Claude Opus 4.7 (1M context) --- src/circuit.rs | 2 -- src/circuit/commit_ivk.rs | 9 +++++++++ src/circuit/note_commit.rs | 12 ++++++++++++ src/constants/fixed_bases.rs | 20 ++++++++++++++++++-- src/constants/fixed_bases/commit_ivk_r.rs | 1 + src/constants/fixed_bases/note_commit_r.rs | 1 + src/constants/fixed_bases/nullifier_k.rs | 1 + src/constants/fixed_bases/spend_auth_g.rs | 1 + src/constants/fixed_bases/value_commit_r.rs | 1 + src/constants/fixed_bases/value_commit_v.rs | 1 + src/constants/sinsemilla.rs | 7 +++++++ src/constants/util.rs | 2 ++ src/lib.rs | 2 -- src/note.rs | 2 -- src/note/commitment.rs | 8 ++++++++ src/note/nullifier.rs | 8 ++++++++ src/spec.rs | 3 +++ 17 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index ef9f458c5..69d416386 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -61,13 +61,11 @@ use halo2_gadgets::{ #[cfg(not(feature = "unstable-voting-circuits"))] mod commit_ivk; #[cfg(feature = "unstable-voting-circuits")] -#[allow(missing_docs)] pub mod commit_ivk; pub mod gadget; #[cfg(not(feature = "unstable-voting-circuits"))] mod note_commit; #[cfg(feature = "unstable-voting-circuits")] -#[allow(missing_docs)] pub mod note_commit; pub use crate::Proof; diff --git a/src/circuit/commit_ivk.rs b/src/circuit/commit_ivk.rs index 60fef54d4..8a4c35f63 100644 --- a/src/circuit/commit_ivk.rs +++ b/src/circuit/commit_ivk.rs @@ -1,3 +1,10 @@ +//! Sub-circuit implementing the `CommitIvk` gadget. +//! +//! `CommitIvk` is the Sinsemilla-based commitment that binds an incoming +//! viewing key `ivk` to the full viewing key `(ak, nk)` and the randomness +//! `rivk`. This module provides the Halo 2 chip that enforces that +//! commitment inside the Orchard Action circuit. + use core::iter; use group::ff::{Field, PrimeField}; @@ -15,12 +22,14 @@ use halo2_gadgets::{ utilities::{bool_check, RangeConstrained}, }; +/// Configuration for the [`CommitIvkChip`], including its selector and advice columns. #[derive(Clone, Debug)] pub struct CommitIvkConfig { q_commit_ivk: Selector, advices: [Column; 10], } +/// A Halo 2 chip that proves correct evaluation of the `CommitIvk` gadget. #[derive(Clone, Debug)] pub struct CommitIvkChip { config: CommitIvkConfig, diff --git a/src/circuit/note_commit.rs b/src/circuit/note_commit.rs index 23f89a658..73000b8f3 100644 --- a/src/circuit/note_commit.rs +++ b/src/circuit/note_commit.rs @@ -1,3 +1,11 @@ +//! Sub-circuit implementing the `NoteCommit` gadget. +//! +//! `NoteCommit` is the Sinsemilla-based commitment that binds the note's +//! diversified transmission key `(g_d, pk_d)`, value `v`, ρ, and ψ, with +//! canonicity checks on each component field element. This module provides +//! the Halo 2 chip that enforces that commitment inside the Orchard Action +//! circuit. + use core::iter; use group::ff::PrimeField; @@ -1412,6 +1420,9 @@ impl YCanonicity { } } +/// Configuration for the [`NoteCommitChip`], aggregating the per-field +/// decomposition and canonicity sub-configurations and the underlying +/// Sinsemilla configuration. #[allow(non_snake_case)] #[derive(Clone, Debug)] pub struct NoteCommitConfig { @@ -1431,6 +1442,7 @@ pub struct NoteCommitConfig { SinsemillaConfig, } +/// A Halo 2 chip that proves correct evaluation of the `NoteCommit` gadget. #[derive(Clone, Debug)] pub struct NoteCommitChip { config: NoteCommitConfig, diff --git a/src/constants/fixed_bases.rs b/src/constants/fixed_bases.rs index ee94571f0..31e58b683 100644 --- a/src/constants/fixed_bases.rs +++ b/src/constants/fixed_bases.rs @@ -14,11 +14,17 @@ use halo2_gadgets::ecc::{ #[cfg(feature = "circuit")] use pasta_curves::pallas; +/// Precomputed table for the `CommitIvk` commitment randomness base. pub mod commit_ivk_r; +/// Precomputed table for the `NoteCommit` commitment randomness base. pub mod note_commit_r; +/// Precomputed table for the nullifier base `K^Orchard`. pub mod nullifier_k; +/// Precomputed table for the spend authorization base `G^Orchard`. pub mod spend_auth_g; +/// Precomputed table for the value commitment randomness base. pub mod value_commit_r; +/// Precomputed table for the value commitment value base. pub mod value_commit_v; /// SWU hash-to-curve personalization for the spending key base point and @@ -52,12 +58,18 @@ pub const NUM_WINDOWS: usize = L_ORCHARD_SCALAR.div_ceil(FIXED_BASE_WINDOW_SIZE) /// Number of windows for a short signed scalar pub const NUM_WINDOWS_SHORT: usize = L_VALUE.div_ceil(FIXED_BASE_WINDOW_SIZE); +/// Enumeration of every fixed base used in the Orchard circuit. +/// +/// This enables the shared fixed-base scalar multiplication machinery in +/// `halo2_gadgets` to dispatch across full-width, base-field, and short-signed +/// bases using a single type. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -// A sum type for both full-width and short bases. This enables us to use the -// shared functionality of full-width and short fixed-base scalar multiplication. pub enum OrchardFixedBases { + /// A full-width scalar multiplication base. Full(OrchardFixedBasesFull), + /// The nullifier base `K^Orchard`, used with a base-field scalar. NullifierK, + /// The value commitment value base, used with a short signed scalar. ValueCommitV, } @@ -82,9 +94,13 @@ impl From for OrchardFixedBases { /// The Orchard fixed bases used in scalar mul with full-width scalars. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum OrchardFixedBasesFull { + /// Randomness base for the `CommitIvk` commitment. CommitIvkR, + /// Randomness base for the `NoteCommit` commitment. NoteCommitR, + /// Randomness base for value commitments. ValueCommitR, + /// Spend authorization base `G^Orchard`. SpendAuthG, } diff --git a/src/constants/fixed_bases/commit_ivk_r.rs b/src/constants/fixed_bases/commit_ivk_r.rs index 75ba5458b..9cb1c2ada 100644 --- a/src/constants/fixed_bases/commit_ivk_r.rs +++ b/src/constants/fixed_bases/commit_ivk_r.rs @@ -2920,6 +2920,7 @@ pub static U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; +/// Returns the affine generator point for this fixed base. pub fn generator() -> pallas::Affine { pallas::Affine::from_xy( pallas::Base::from_repr(GENERATOR.0).unwrap(), diff --git a/src/constants/fixed_bases/note_commit_r.rs b/src/constants/fixed_bases/note_commit_r.rs index 2d0e8e6b5..8342efdd7 100644 --- a/src/constants/fixed_bases/note_commit_r.rs +++ b/src/constants/fixed_bases/note_commit_r.rs @@ -2920,6 +2920,7 @@ pub static U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; +/// Returns the affine generator point for this fixed base. pub fn generator() -> pallas::Affine { pallas::Affine::from_xy( pallas::Base::from_repr(GENERATOR.0).unwrap(), diff --git a/src/constants/fixed_bases/nullifier_k.rs b/src/constants/fixed_bases/nullifier_k.rs index cecc0c26a..60bbd5df1 100644 --- a/src/constants/fixed_bases/nullifier_k.rs +++ b/src/constants/fixed_bases/nullifier_k.rs @@ -2920,6 +2920,7 @@ pub static U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; +/// Returns the affine generator point for this fixed base. pub fn generator() -> pallas::Affine { pallas::Affine::from_xy( pallas::Base::from_repr(GENERATOR.0).unwrap(), diff --git a/src/constants/fixed_bases/spend_auth_g.rs b/src/constants/fixed_bases/spend_auth_g.rs index 0a83fc363..bcba79968 100644 --- a/src/constants/fixed_bases/spend_auth_g.rs +++ b/src/constants/fixed_bases/spend_auth_g.rs @@ -2920,6 +2920,7 @@ pub static U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; +/// Returns the affine generator point for this fixed base. pub fn generator() -> pallas::Affine { pallas::Affine::from_xy( pallas::Base::from_repr(GENERATOR.0).unwrap(), diff --git a/src/constants/fixed_bases/value_commit_r.rs b/src/constants/fixed_bases/value_commit_r.rs index 859edb79f..63f1c5db9 100644 --- a/src/constants/fixed_bases/value_commit_r.rs +++ b/src/constants/fixed_bases/value_commit_r.rs @@ -2921,6 +2921,7 @@ pub static U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [ ], ]; +/// Returns the affine generator point for this fixed base. pub fn generator() -> pallas::Affine { pallas::Affine::from_xy( pallas::Base::from_repr(GENERATOR.0).unwrap(), diff --git a/src/constants/fixed_bases/value_commit_v.rs b/src/constants/fixed_bases/value_commit_v.rs index 30f79ba19..9d92f2e8d 100644 --- a/src/constants/fixed_bases/value_commit_v.rs +++ b/src/constants/fixed_bases/value_commit_v.rs @@ -774,6 +774,7 @@ pub const U_SHORT: [[[u8; 32]; super::H]; super::NUM_WINDOWS_SHORT] = [ ], ]; +/// Returns the affine generator point for this fixed base. pub fn generator() -> pallas::Affine { pallas::Affine::from_xy( pallas::Base::from_repr(GENERATOR.0).unwrap(), diff --git a/src/constants/sinsemilla.rs b/src/constants/sinsemilla.rs index 34843fcca..1f4fb9f28 100644 --- a/src/constants/sinsemilla.rs +++ b/src/constants/sinsemilla.rs @@ -78,10 +78,14 @@ pub(crate) fn i2lebsp_k(int: usize) -> [bool; K] { i2lebsp(int as u64) } +/// The Sinsemilla hash domains used in Orchard. #[derive(Clone, Debug, Eq, PartialEq)] pub enum OrchardHashDomains { + /// The domain used by the `NoteCommit` commitment's internal Sinsemilla hash. NoteCommit, + /// The domain used by the `CommitIvk` commitment's internal Sinsemilla hash. CommitIvk, + /// The domain used by the Orchard Merkle tree's `MerkleCRH^Orchard` hash. MerkleCrh, } @@ -109,9 +113,12 @@ impl HashDomains for OrchardHashDomains { } } +/// The Sinsemilla commitment domains used in Orchard. #[derive(Clone, Debug, Eq, PartialEq)] pub enum OrchardCommitDomains { + /// The `NoteCommit` commitment domain. NoteCommit, + /// The `CommitIvk` commitment domain. CommitIvk, } diff --git a/src/constants/util.rs b/src/constants/util.rs index e9876ca0d..8e754c2fb 100644 --- a/src/constants/util.rs +++ b/src/constants/util.rs @@ -1,3 +1,5 @@ +//! Small utility helpers used to build fixed-base scalar multiplication tables. + /// Takes in an FnMut closure and returns a constant-length array with elements of /// type `Output`. pub fn gen_const_array( diff --git a/src/lib.rs b/src/lib.rs index 87ce6720d..0c5387b65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,6 @@ pub mod circuit; #[cfg(not(feature = "unstable-voting-circuits"))] mod constants; #[cfg(feature = "unstable-voting-circuits")] -#[allow(missing_docs)] pub mod constants; pub mod keys; pub mod note; @@ -44,7 +43,6 @@ pub mod primitives; #[cfg(not(feature = "unstable-voting-circuits"))] mod spec; #[cfg(feature = "unstable-voting-circuits")] -#[allow(missing_docs)] pub mod spec; pub mod tree; pub mod value; diff --git a/src/note.rs b/src/note.rs index 53d04f5ec..366290fdc 100644 --- a/src/note.rs +++ b/src/note.rs @@ -18,7 +18,6 @@ use crate::{ #[cfg(not(feature = "unstable-voting-circuits"))] pub(crate) mod commitment; #[cfg(feature = "unstable-voting-circuits")] -#[allow(missing_docs)] pub mod commitment; #[cfg(feature = "unstable-voting-circuits")] pub use self::commitment::NoteCommitTrapdoor; @@ -27,7 +26,6 @@ pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment}; #[cfg(not(feature = "unstable-voting-circuits"))] pub(crate) mod nullifier; #[cfg(feature = "unstable-voting-circuits")] -#[allow(missing_docs)] pub mod nullifier; pub use self::nullifier::Nullifier; diff --git a/src/note/commitment.rs b/src/note/commitment.rs index 6035bce91..0a6001f7a 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -1,3 +1,11 @@ +//! Note commitments and their trapdoors. +//! +//! A [`NoteCommitment`] is the Sinsemilla commitment to the contents of a +//! note, binding the diversified transmission key, note value, ρ, and ψ. Its +//! x-coordinate is exposed as [`ExtractedNoteCommitment`] (the value +//! appearing in the commitment tree), and its randomness is +//! [`NoteCommitTrapdoor`]. + use core::iter; use bitvec::{array::BitArray, order::Lsb0}; diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs index efcc4ea72..ab4ef7607 100644 --- a/src/note/nullifier.rs +++ b/src/note/nullifier.rs @@ -1,3 +1,11 @@ +//! Orchard nullifiers. +//! +//! A [`Nullifier`] is the unique identifier revealed when a note is spent. +//! Nullifiers are derived deterministically from the note's ρ, the spender's +//! nullifier-deriving key `nk`, the note's ψ randomness, and the note +//! commitment, in a way that keeps them unlinkable without the viewing key +//! but uniquely tied to the note. + use group::{ff::PrimeField, Group}; use memuse::DynamicUsage; use pasta_curves::{arithmetic::CurveExt, pallas}; diff --git a/src/spec.rs b/src/spec.rs index 832bc8fc6..17ee92f7d 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -40,6 +40,9 @@ impl ConditionallySelectable for NonIdentityPallasPoint { } impl NonIdentityPallasPoint { + /// Decodes a non-identity Pallas point from its canonical 32-byte encoding, + /// returning `None` if the bytes do not decode to a valid curve point or if + /// they decode to the identity. #[cfg_attr(feature = "unstable-voting-circuits", visibility::make(pub))] pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption { pallas::Point::from_bytes(bytes) From 21d85e8dd893aa72dc79429053a278365b983904 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 24 Apr 2026 06:25:14 +0800 Subject: [PATCH 3/3] Restore visibility of zero() to pub(crate); Change visibility in a second PR as its not core to voting --- src/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value.rs b/src/value.rs index 061a36611..4b0ba462e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -103,7 +103,7 @@ impl NoteValue { /// Returns a zero note value. /// /// Equivalent to `NoteValue::from_raw(0)`. - pub fn zero() -> Self { + pub(crate) fn zero() -> Self { // Default for u64 is zero. Default::default() }