Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/circuit/gadget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use pasta_curves::pallas;

use super::{commit_ivk::CommitIvkChip, note_commit::NoteCommitChip};
use crate::constants::{
NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull, OrchardHashDomains,
ValueCommitV,
NullifierK, OrchardCommitDomains, OrchardFixedBases, OrchardFixedBasesFull,
OrchardHashDomains, ValueCommitV,
};
use halo2_gadgets::{
ecc::{
Expand Down Expand Up @@ -137,7 +137,7 @@ pub(in crate::circuit) fn value_commit_orchard<
// commitment = [v] ValueCommitV
let (commitment, _) = {
let value_commit_v = ValueCommitV;
let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v);
let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v.into());
value_commit_v.mul(layouter.namespace(|| "[v] ValueCommitV"), v)?
};

Expand Down Expand Up @@ -199,7 +199,8 @@ pub fn derive_nullifier<
// `product` = [poseidon_hash(nk, rho) + psi] NullifierK.
//
let product = {
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip, NullifierK);
let nullifier_k = NullifierK;
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip, nullifier_k.into());
nullifier_k.mul(
layouter.namespace(|| "[poseidon_output + psi] NullifierK"),
scalar,
Expand Down
5 changes: 4 additions & 1 deletion src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ pub mod util;
#[cfg(feature = "circuit")]
pub use self::sinsemilla::{OrchardCommitDomains, OrchardHashDomains};
#[cfg(feature = "circuit")]
pub use fixed_bases::{NullifierK, OrchardFixedBases, OrchardFixedBasesFull, ValueCommitV};
pub use fixed_bases::{
NullifierK, OrchardBaseFieldBases, OrchardFixedBases, OrchardFixedBasesFull,
OrchardShortScalarBases, ValueCommitV,
};

/// $\mathsf{MerkleDepth^{Orchard}}$
pub const MERKLE_DEPTH_ORCHARD: usize = 32;
Expand Down
230 changes: 224 additions & 6 deletions src/constants/fixed_bases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,42 @@ pub const NUM_WINDOWS: usize =
pub const NUM_WINDOWS_SHORT: usize =
(L_VALUE + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;

/// Fixed bases used in scalar mul where the scalar is a base field element.
///
/// The ECC chip's `FixedPoints::Base` associated type must be a single type,
/// so both `NullifierK` and `SpendAuthGBase` are wrapped in this enum.
/// `SpendAuthGBase` reuses the same generator and U/Z tables as
/// `OrchardFixedBasesFull::SpendAuthG` (same 85-window structure over the
/// 255-bit pallas base field), allowing `FixedPointBaseField::mul` to accept
/// an `AssignedCell` directly — no variable-base NonIdentityPoint witness needed.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrchardBaseFieldBases {
NullifierK,
SpendAuthGBase,
}

/// Fixed bases used in scalar mul where the scalar is a short (64-bit) signed value.
///
/// `FixedPoints::ShortScalar` must be a single type, so both `ValueCommitV`
/// and `SpendAuthGShort` are wrapped in this enum. `SpendAuthGShort` uses the
/// same generator as `SpendAuthG` but with 22-window precomputed tables, enabling
/// `FixedPointShort::mul` for vote-share values bounded to 30 bits by condition 9.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrchardShortScalarBases {
/// Value commitment generator for Orchard (original short base).
ValueCommitV,
/// SpendAuthG with short (22-window) tables, used for `[v_i]*G` in ElGamal
/// encryption where `v_i` is range-checked to 30 bits by condition 9.
SpendAuthGShort,
}

#[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 {
Full(OrchardFixedBasesFull),
NullifierK,
ValueCommitV,
Base(OrchardBaseFieldBases),
Short(OrchardShortScalarBases),
}

impl From<OrchardFixedBasesFull> for OrchardFixedBases {
Expand All @@ -70,17 +99,41 @@ impl From<OrchardFixedBasesFull> for OrchardFixedBases {
}

impl From<ValueCommitV> for OrchardFixedBases {
fn from(_value_commit_v: ValueCommitV) -> Self {
Self::ValueCommitV
fn from(value_commit_v: ValueCommitV) -> Self {
Self::Short(value_commit_v.into())
}
}

impl From<NullifierK> for OrchardFixedBases {
fn from(nullifier_k: NullifierK) -> Self {
Self::Base(nullifier_k.into())
}
}

impl From<OrchardBaseFieldBases> for OrchardFixedBases {
fn from(b: OrchardBaseFieldBases) -> Self {
Self::Base(b)
}
}

impl From<OrchardShortScalarBases> for OrchardFixedBases {
fn from(b: OrchardShortScalarBases) -> Self {
Self::Short(b)
}
}

impl From<NullifierK> for OrchardBaseFieldBases {
fn from(_nullifier_k: NullifierK) -> Self {
Self::NullifierK
}
}

impl From<ValueCommitV> for OrchardShortScalarBases {
fn from(_value_commit_v: ValueCommitV) -> Self {
Self::ValueCommitV
}
}

/// The Orchard fixed bases used in scalar mul with full-width scalars.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrchardFixedBasesFull {
Expand All @@ -101,8 +154,8 @@ pub struct ValueCommitV;
#[cfg(feature = "circuit")]
impl FixedPoints<pallas::Affine> for OrchardFixedBases {
type FullScalar = OrchardFixedBasesFull;
type Base = NullifierK;
type ShortScalar = ValueCommitV;
type Base = OrchardBaseFieldBases;
type ShortScalar = OrchardShortScalarBases;
}

#[cfg(feature = "circuit")]
Expand Down Expand Up @@ -154,6 +207,36 @@ impl FixedPoint<pallas::Affine> for NullifierK {
}
}

#[cfg(feature = "circuit")]
impl FixedPoint<pallas::Affine> for OrchardBaseFieldBases {
type FixedScalarKind = BaseFieldElem;

fn generator(&self) -> pallas::Affine {
match self {
Self::NullifierK => nullifier_k::generator(),
Self::SpendAuthGBase => spend_auth_g::generator(),
}
}

fn u(&self) -> Vec<[[u8; 32]; H]> {
match self {
Self::NullifierK => nullifier_k::U.to_vec(),
// SpendAuthG's full-scalar U/Z tables have the same 85-window
// structure as the base-field-element variant (pallas::Base and
// pallas::Scalar are both 255-bit); the precomputed values depend
// only on the generator and window layout, not the scalar kind.
Self::SpendAuthGBase => spend_auth_g::U.to_vec(),
}
}

fn z(&self) -> Vec<u64> {
match self {
Self::NullifierK => nullifier_k::Z.to_vec(),
Self::SpendAuthGBase => spend_auth_g::Z.to_vec(),
}
}
}

#[cfg(feature = "circuit")]
impl FixedPoint<pallas::Affine> for ValueCommitV {
type FixedScalarKind = ShortScalar;
Expand All @@ -170,3 +253,138 @@ impl FixedPoint<pallas::Affine> for ValueCommitV {
value_commit_v::Z_SHORT.to_vec()
}
}

#[cfg(feature = "circuit")]
impl FixedPoint<pallas::Affine> for OrchardShortScalarBases {
type FixedScalarKind = ShortScalar;

fn generator(&self) -> pallas::Affine {
match self {
Self::ValueCommitV => value_commit_v::generator(),
Self::SpendAuthGShort => spend_auth_g::generator(),
}
}

fn u(&self) -> Vec<[[u8; 32]; H]> {
match self {
Self::ValueCommitV => value_commit_v::U_SHORT.to_vec(),
Self::SpendAuthGShort => spend_auth_g::U_SHORT.to_vec(),
}
}

fn z(&self) -> Vec<u64> {
match self {
Self::ValueCommitV => value_commit_v::Z_SHORT.to_vec(),
Self::SpendAuthGShort => spend_auth_g::Z_SHORT.to_vec(),
}
}
}

#[cfg(all(test, feature = "circuit"))]
mod tests {
use super::*;

/// Ensures that `OrchardBaseFieldBases::SpendAuthGBase` routes to the
/// correct generator and tables via the `FixedPoint` trait. The U/Z data
/// is identical to `OrchardFixedBasesFull::SpendAuthG` (same generator,
/// same 85-window structure); this test makes the dispatch wiring explicit.
#[test]
fn spend_auth_g_base_field_routes_correctly() {
let full = OrchardFixedBasesFull::SpendAuthG;
let base = OrchardBaseFieldBases::SpendAuthGBase;

assert_eq!(
full.generator(),
base.generator(),
"SpendAuthGBase must share the SpendAuthG generator"
);
assert_eq!(
full.u(),
base.u(),
"SpendAuthGBase U tables must match SpendAuthG full-scalar U tables"
);
assert_eq!(
full.z(),
base.z(),
"SpendAuthGBase Z tables must match SpendAuthG full-scalar Z tables"
);
}

/// Ensures that `OrchardBaseFieldBases::NullifierK` still routes to the
/// NullifierK generator and tables (regression guard for the enum refactor).
#[test]
fn nullifier_k_base_field_routes_correctly() {
let base = OrchardBaseFieldBases::NullifierK;

assert_eq!(
base.generator(),
nullifier_k::generator(),
"OrchardBaseFieldBases::NullifierK must use the NullifierK generator"
);
assert_eq!(
base.u(),
nullifier_k::U.to_vec(),
"OrchardBaseFieldBases::NullifierK U tables must match"
);
assert_eq!(
base.z(),
nullifier_k::Z.to_vec(),
"OrchardBaseFieldBases::NullifierK Z tables must match"
);
}

#[test]
fn nullifier_k_converts_to_base_field_enum() {
assert_eq!(OrchardBaseFieldBases::from(NullifierK), OrchardBaseFieldBases::NullifierK);
}

/// Ensures that `OrchardShortScalarBases::SpendAuthGShort` routes to the
/// SpendAuthG generator and the 22-window short tables.
#[test]
fn spend_auth_g_short_routes_correctly() {
let short = OrchardShortScalarBases::SpendAuthGShort;
let full = OrchardFixedBasesFull::SpendAuthG;

assert_eq!(
short.generator(),
full.generator(),
"SpendAuthGShort must share the SpendAuthG generator"
);
assert_eq!(
short.u().len(),
NUM_WINDOWS_SHORT,
"SpendAuthGShort U table must have NUM_WINDOWS_SHORT entries"
);
assert_eq!(
short.z().len(),
NUM_WINDOWS_SHORT,
"SpendAuthGShort Z table must have NUM_WINDOWS_SHORT entries"
);
// Short tables must differ from the full 85-window tables.
assert_ne!(
short.u(),
full.u()[..NUM_WINDOWS_SHORT].to_vec(),
"SpendAuthGShort U table must use the short-scalar window structure"
);
}

/// Ensures that `OrchardShortScalarBases::ValueCommitV` still routes to
/// the ValueCommitV generator and tables (regression guard for the enum change).
#[test]
fn value_commit_v_short_routes_correctly() {
let short = OrchardShortScalarBases::ValueCommitV;
let legacy = ValueCommitV;

assert_eq!(short.generator(), legacy.generator());
assert_eq!(short.u(), legacy.u());
assert_eq!(short.z(), legacy.z());
}

#[test]
fn value_commit_v_converts_to_short_scalar_enum() {
assert_eq!(
OrchardShortScalarBases::from(ValueCommitV),
OrchardShortScalarBases::ValueCommitV
);
}
}
Loading
Loading