#[cfg(feature = "circuit")]
use alloc::vec::Vec;
use super::{L_ORCHARD_SCALAR, L_VALUE};
#[cfg(feature = "circuit")]
use halo2_gadgets::ecc::{
chip::{BaseFieldElem, FixedPoint, FullScalar, ShortScalar},
FixedPoints,
};
#[cfg(feature = "circuit")]
use pasta_curves::pallas;
pub mod commit_ivk_r;
pub mod note_commit_r;
pub mod nullifier_k;
pub mod spend_auth_g;
pub mod value_commit_r;
pub mod value_commit_v;
pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard";
pub const VALUE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-cv";
pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v";
pub const VALUE_COMMITMENT_R_BYTES: [u8; 1] = *b"r";
pub const NOTE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-NoteCommit";
pub const COMMIT_IVK_PERSONALIZATION: &str = "z.cash:Orchard-CommitIvk";
pub const FIXED_BASE_WINDOW_SIZE: usize = 3;
pub const H: usize = 1 << FIXED_BASE_WINDOW_SIZE;
pub const NUM_WINDOWS: usize = L_ORCHARD_SCALAR.div_ceil(FIXED_BASE_WINDOW_SIZE);
pub const NUM_WINDOWS_SHORT: usize = L_VALUE.div_ceil(FIXED_BASE_WINDOW_SIZE);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OrchardBaseFieldBases {
NullifierK,
SpendAuthGBase,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum OrchardShortScalarBases {
ValueCommitV,
SpendAuthGShort,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrchardFixedBases {
Full(OrchardFixedBasesFull),
Base(OrchardBaseFieldBases),
Short(OrchardShortScalarBases),
}
impl From<OrchardFixedBasesFull> for OrchardFixedBases {
fn from(full_width_base: OrchardFixedBasesFull) -> Self {
Self::Full(full_width_base)
}
}
impl From<ValueCommitV> for OrchardFixedBases {
fn from(_: ValueCommitV) -> Self {
Self::Short(OrchardShortScalarBases::ValueCommitV)
}
}
impl From<NullifierK> for OrchardFixedBases {
fn from(_: NullifierK) -> Self {
Self::Base(OrchardBaseFieldBases::NullifierK)
}
}
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(_: NullifierK) -> Self {
Self::NullifierK
}
}
impl From<ValueCommitV> for OrchardShortScalarBases {
fn from(_: ValueCommitV) -> Self {
Self::ValueCommitV
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrchardFixedBasesFull {
CommitIvkR,
NoteCommitR,
ValueCommitR,
SpendAuthG,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct NullifierK;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct ValueCommitV;
#[cfg(feature = "circuit")]
impl FixedPoints<pallas::Affine> for OrchardFixedBases {
type FullScalar = OrchardFixedBasesFull;
type Base = OrchardBaseFieldBases;
type ShortScalar = OrchardShortScalarBases;
}
#[cfg(feature = "circuit")]
impl FixedPoint<pallas::Affine> for OrchardFixedBasesFull {
type FixedScalarKind = FullScalar;
fn generator(&self) -> pallas::Affine {
match self {
Self::CommitIvkR => commit_ivk_r::generator(),
Self::NoteCommitR => note_commit_r::generator(),
Self::ValueCommitR => value_commit_r::generator(),
Self::SpendAuthG => spend_auth_g::generator(),
}
}
fn u(&self) -> Vec<[[u8; 32]; H]> {
match self {
Self::CommitIvkR => commit_ivk_r::U.to_vec(),
Self::NoteCommitR => note_commit_r::U.to_vec(),
Self::ValueCommitR => value_commit_r::U.to_vec(),
Self::SpendAuthG => spend_auth_g::U.to_vec(),
}
}
fn z(&self) -> Vec<u64> {
match self {
Self::CommitIvkR => commit_ivk_r::Z.to_vec(),
Self::NoteCommitR => note_commit_r::Z.to_vec(),
Self::ValueCommitR => value_commit_r::Z.to_vec(),
Self::SpendAuthG => spend_auth_g::Z.to_vec(),
}
}
}
#[cfg(feature = "circuit")]
impl FixedPoint<pallas::Affine> for NullifierK {
type FixedScalarKind = BaseFieldElem;
fn generator(&self) -> pallas::Affine {
nullifier_k::generator()
}
fn u(&self) -> Vec<[[u8; 32]; H]> {
nullifier_k::U.to_vec()
}
fn z(&self) -> Vec<u64> {
nullifier_k::Z.to_vec()
}
}
#[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(),
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;
fn generator(&self) -> pallas::Affine {
value_commit_v::generator()
}
fn u(&self) -> Vec<[[u8; 32]; H]> {
value_commit_v::U_SHORT.to_vec()
}
fn z(&self) -> Vec<u64> {
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::*;
#[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"
);
}
#[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
);
}
#[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"
);
let short_u = short.u();
let full_u = full.u();
let short_z = short.z();
let full_z = full.z();
assert_eq!(
short_u[..NUM_WINDOWS_SHORT - 1],
full_u[..NUM_WINDOWS_SHORT - 1],
"SpendAuthGShort's first NUM_WINDOWS_SHORT - 1 U windows must equal SpendAuthG's"
);
assert_eq!(
short_z[..NUM_WINDOWS_SHORT - 1],
full_z[..NUM_WINDOWS_SHORT - 1],
"SpendAuthGShort's first NUM_WINDOWS_SHORT - 1 Z windows must equal SpendAuthG's"
);
assert_ne!(
short_u[NUM_WINDOWS_SHORT - 1],
full_u[NUM_WINDOWS_SHORT - 1],
"SpendAuthGShort's final U window must differ from the full-scalar table"
);
assert_ne!(
short_z[NUM_WINDOWS_SHORT - 1],
full_z[NUM_WINDOWS_SHORT - 1],
"SpendAuthGShort's final Z window must differ from the full-scalar table"
);
}
#[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
);
}
#[derive(Copy, Clone, Debug)]
enum SpendAuthGCase {
BaseField,
Short,
}
fn run_spend_auth_g_fixed_base_mul_e2e(case: SpendAuthGCase) {
use group::{ff::PrimeField, Curve};
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
FixedPointBaseField, FixedPointShort, NonIdentityPoint, ScalarFixedShort,
},
utilities::{
lookup_range_check::{LookupRangeCheck, LookupRangeCheckConfig},
UtilitiesInstructions,
},
};
use halo2_proofs::{
circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error, TableColumn},
};
#[derive(Clone, Debug)]
struct MyConfig {
ecc: EccConfig<OrchardFixedBases>,
table_idx: TableColumn,
}
struct MyCircuit {
case: SpendAuthGCase,
}
impl UtilitiesInstructions<pallas::Base> for MyCircuit {
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl Circuit<pallas::Base> for MyCircuit {
type Config = MyConfig;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
MyCircuit { case: self.case }
}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
let advices = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
for advice in advices.iter() {
meta.enable_equality(*advice);
}
let lagrange_coeffs = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
let constants = meta.fixed_column();
meta.enable_constant(constants);
let table_idx = meta.lookup_table_column();
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
let ecc = EccChip::<OrchardFixedBases>::configure(
meta,
advices,
lagrange_coeffs,
range_check,
);
MyConfig { ecc, table_idx }
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
let chip = EccChip::construct(config.ecc.clone());
layouter.assign_table(
|| "10-bit range-check table",
|mut table| {
for index in 0..(1 << 10) {
table.assign_cell(
|| "range-check value",
config.table_idx,
index,
|| Value::known(pallas::Base::from(index as u64)),
)?;
}
Ok(())
},
)?;
match self.case {
SpendAuthGCase::BaseField => {
let base = FixedPointBaseField::from_inner(
chip.clone(),
OrchardBaseFieldBases::SpendAuthGBase,
);
let scalar_val = pallas::Base::from(7u64);
let scalar_cell = chip.load_private(
layouter.namespace(|| "SpendAuthGBase scalar"),
config.ecc.advices[0],
Value::known(scalar_val),
)?;
let result =
base.mul(layouter.namespace(|| "SpendAuthGBase mul"), scalar_cell)?;
let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap();
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "SpendAuthGBase expected"),
Value::known((spend_auth_g::generator() * scalar).to_affine()),
)?;
result.constrain_equal(
layouter.namespace(|| "SpendAuthGBase constrain"),
&expected,
)?;
}
SpendAuthGCase::Short => {
let base = FixedPointShort::from_inner(
chip.clone(),
OrchardShortScalarBases::SpendAuthGShort,
);
let magnitude = chip.load_private(
layouter.namespace(|| "SpendAuthGShort magnitude"),
config.ecc.advices[0],
Value::known(pallas::Base::from(42u64)),
)?;
let sign = chip.load_private(
layouter.namespace(|| "SpendAuthGShort sign"),
config.ecc.advices[0],
Value::known(pallas::Base::one()),
)?;
let scalar = ScalarFixedShort::new(
chip.clone(),
layouter.namespace(|| "SpendAuthGShort scalar"),
(magnitude, sign),
)?;
let (result, _) =
base.mul(layouter.namespace(|| "SpendAuthGShort mul"), scalar)?;
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "SpendAuthGShort expected"),
Value::known(
(spend_auth_g::generator() * pallas::Scalar::from(42u64))
.to_affine(),
),
)?;
result.constrain_equal(
layouter.namespace(|| "SpendAuthGShort constrain"),
&expected,
)?;
}
}
Ok(())
}
}
let prover = MockProver::<pallas::Base>::run(11, &MyCircuit { case }, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
#[test]
fn spend_auth_g_base_field_mul_e2e() {
run_spend_auth_g_fixed_base_mul_e2e(SpendAuthGCase::BaseField);
}
#[test]
fn spend_auth_g_short_mul_e2e() {
run_spend_auth_g_fixed_base_mul_e2e(SpendAuthGCase::Short);
}
}