Merge pull request #217 from zcash/update-halo2-gadgets

Delete gadgets and introduce halo2_gadgets dependency.
This commit is contained in:
str4d 2022-02-03 21:23:42 +00:00 committed by GitHub
commit 40491385c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 158 additions and 31360 deletions

View File

@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
override: true
- name: Run benchmark
run: cargo bench -- --output-format bencher | tee output.txt

View File

@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
override: true
- name: Run tests
uses: actions-rs/cargo@v1
@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
override: true
# Build benchmarks to prevent bitrot
- name: Build benchmarks
@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
override: true
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
@ -89,7 +89,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
override: true
- name: cargo fetch
uses: actions-rs/cargo@v1
@ -112,7 +112,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1

View File

@ -5,19 +5,19 @@ on: pull_request
jobs:
clippy:
name: Clippy (1.51.0)
name: Clippy (1.54.0)
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.51.0
toolchain: 1.54.0
components: clippy
override: true
- name: Run Clippy
uses: actions-rs/clippy-check@v1
with:
name: Clippy (1.51.0)
name: Clippy (1.54.0)
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all-targets -- -D warnings

View File

@ -8,7 +8,13 @@ and this project adheres to Rust's notion of
## [Unreleased]
### Changed
- pass in an additional `rng: impl RngCore` argument to `builder::InProgress::create_proof`, `builder::Bundle::create_proof`, `circuit::Proof::create`.
- MSRV is now 1.54.0.
- Bumped dependencies to `pasta_curves 0.3`.
- The following methods now have an additional `rng: impl RngCore` argument:
- `orchard::builder::Bundle::create_proof`
- `orchard::builder::InProgress::create_proof`
- `orchard::circuit::Proof::create`
### Removed
- `orchard::value::ValueSum::from_raw`

View File

@ -30,13 +30,14 @@ blake2b_simd = "1"
ff = "0.11"
fpe = "0.5"
group = "0.11"
halo2 = "=0.1.0-beta.1"
halo2_gadgets = "0.0"
halo2_proofs = "=0.1.0-beta.1"
lazy_static = "1"
memuse = { version = "0.2", features = ["nonempty"] }
pasta_curves = "0.2.1"
pasta_curves = "0.3"
proptest = { version = "1.0.0", optional = true }
rand = "0.8"
reddsa = "0.1"
reddsa = "0.2"
nonempty = "0.7"
serde = { version = "1.0", features = ["derive"] }
subtle = "2.3"
@ -48,6 +49,7 @@ plotters = { version = "0.3.0", optional = true }
[dev-dependencies]
criterion = "0.3"
halo2_gadgets = { version = "0.0", features = ["test-dependencies"] }
hex = "0.4"
proptest = "1.0.0"
zcash_note_encryption = { version = "0.1", features = ["pre-zip-212"] }
@ -59,17 +61,13 @@ pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] }
bench = false
[features]
dev-graph = ["halo2/dev-graph", "plotters"]
dev-graph = ["halo2_proofs/dev-graph", "plotters"]
test-dependencies = ["proptest"]
[[bench]]
name = "note_decryption"
harness = false
[[bench]]
name = "primitives"
harness = false
[[bench]]
name = "small"
harness = false
@ -78,10 +76,6 @@ harness = false
name = "circuit"
harness = false
[[bench]]
name = "poseidon"
harness = false
[profile.release]
debug = true
@ -89,4 +83,5 @@ debug = true
debug = true
[patch.crates-io]
halo2 = { git = "https://github.com/zcash/halo2.git", rev = "f9b3ff2aef09a5a3cb5489d0e7e747e9523d2e6e" }
halo2_proofs = { git = "https://github.com/zcash/halo2.git", rev = "5312343e6d68b1bdd141337b52a5b73cce752bff" }
halo2_gadgets = { git = "https://github.com/zcash/halo2.git", rev = "5312343e6d68b1bdd141337b52a5b73cce752bff" }

View File

@ -2,7 +2,7 @@
**IMPORTANT**: This library is being actively developed and should not be used in production software.
Requires Rust 1.51+.
Requires Rust 1.54+.
## Documentation

View File

@ -1,253 +0,0 @@
use ff::Field;
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
pasta::Fp,
plonk::{
create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column,
ConstraintSystem, Error,
},
poly::commitment::Params,
transcript::{Blake2bRead, Blake2bWrite, Challenge255},
};
use pasta_curves::{pallas, vesta};
use orchard::{
circuit::gadget::poseidon::{Hash, Pow5Chip, Pow5Config},
primitives::poseidon::{self, ConstantLength, Spec},
};
use std::convert::TryInto;
use std::marker::PhantomData;
use criterion::{criterion_group, criterion_main, Criterion};
use rand::rngs::OsRng;
#[derive(Clone, Copy)]
struct HashCircuit<S, const WIDTH: usize, const RATE: usize, const L: usize>
where
S: Spec<Fp, WIDTH, RATE> + Clone + Copy,
{
message: Option<[Fp; L]>,
// For the purpose of this test, witness the result.
// TODO: Move this into an instance column.
output: Option<Fp>,
_spec: PhantomData<S>,
}
#[derive(Debug, Clone)]
struct MyConfig<const WIDTH: usize, const RATE: usize, const L: usize> {
input: [Column<Advice>; L],
poseidon_config: Pow5Config<Fp, WIDTH, RATE>,
}
impl<S, const WIDTH: usize, const RATE: usize, const L: usize> Circuit<Fp>
for HashCircuit<S, WIDTH, RATE, L>
where
S: Spec<Fp, WIDTH, RATE> + Copy + Clone,
{
type Config = MyConfig<WIDTH, RATE, L>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self {
message: None,
output: None,
_spec: PhantomData,
}
}
fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
let state = (0..WIDTH).map(|_| meta.advice_column()).collect::<Vec<_>>();
let partial_sbox = meta.advice_column();
let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::<Vec<_>>();
let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::<Vec<_>>();
meta.enable_constant(rc_b[0]);
Self::Config {
input: state[..RATE].try_into().unwrap(),
poseidon_config: Pow5Chip::configure::<S>(
meta,
state.try_into().unwrap(),
partial_sbox,
rc_a.try_into().unwrap(),
rc_b.try_into().unwrap(),
),
}
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Fp>,
) -> Result<(), Error> {
let chip = Pow5Chip::construct(config.poseidon_config.clone());
let message = layouter.assign_region(
|| "load message",
|mut region| {
let message_word = |i: usize| {
let value = self.message.map(|message_vals| message_vals[i]);
region.assign_advice(
|| format!("load message_{}", i),
config.input[i],
0,
|| value.ok_or(Error::Synthesis),
)
};
let message: Result<Vec<_>, Error> = (0..L).map(message_word).collect();
Ok(message?.try_into().unwrap())
},
)?;
let hasher = Hash::<_, _, S, ConstantLength<L>, WIDTH, RATE>::init(
chip,
layouter.namespace(|| "init"),
)?;
let output = hasher.hash(layouter.namespace(|| "hash"), message)?;
layouter.assign_region(
|| "constrain output",
|mut region| {
let expected_var = region.assign_advice(
|| "load output",
config.input[0],
0,
|| self.output.ok_or(Error::Synthesis),
)?;
region.constrain_equal(output.cell(), expected_var.cell())
},
)
}
}
#[derive(Debug, Clone, Copy)]
struct MySpec<const WIDTH: usize, const RATE: usize>;
impl Spec<Fp, 3, 2> for MySpec<3, 2> {
fn full_rounds() -> usize {
8
}
fn partial_rounds() -> usize {
56
}
fn sbox(val: Fp) -> Fp {
val.pow_vartime(&[5])
}
fn secure_mds() -> usize {
0
}
}
impl Spec<Fp, 9, 8> for MySpec<9, 8> {
fn full_rounds() -> usize {
8
}
fn partial_rounds() -> usize {
56
}
fn sbox(val: Fp) -> Fp {
val.pow_vartime(&[5])
}
fn secure_mds() -> usize {
0
}
}
impl Spec<Fp, 12, 11> for MySpec<12, 11> {
fn full_rounds() -> usize {
8
}
fn partial_rounds() -> usize {
56
}
fn sbox(val: Fp) -> Fp {
val.pow_vartime(&[5])
}
fn secure_mds() -> usize {
0
}
}
const K: u32 = 6;
fn bench_poseidon<S, const WIDTH: usize, const RATE: usize, const L: usize>(
name: &str,
c: &mut Criterion,
) where
S: Spec<Fp, WIDTH, RATE> + Copy + Clone,
{
// Initialize the polynomial commitment parameters
let params: Params<vesta::Affine> = Params::new(K);
let empty_circuit = HashCircuit::<S, WIDTH, RATE, L> {
message: None,
output: None,
_spec: PhantomData,
};
// Initialize the proving key
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &empty_circuit).expect("keygen_pk should not fail");
let prover_name = name.to_string() + "-prover";
let verifier_name = name.to_string() + "-verifier";
let mut rng = OsRng;
let message = (0..L)
.map(|_| pallas::Base::random(rng))
.collect::<Vec<_>>()
.try_into()
.unwrap();
let output = poseidon::Hash::<_, S, ConstantLength<L>, WIDTH, RATE>::init().hash(message);
let circuit = HashCircuit::<S, WIDTH, RATE, L> {
message: Some(message),
output: Some(output),
_spec: PhantomData,
};
c.bench_function(&prover_name, |b| {
b.iter(|| {
// Create a proof
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
create_proof(&params, &pk, &[circuit], &[&[]], &mut rng, &mut transcript)
.expect("proof generation should not fail")
})
});
// Create a proof
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
create_proof(&params, &pk, &[circuit], &[&[]], &mut rng, &mut transcript)
.expect("proof generation should not fail");
let proof = transcript.finalize();
c.bench_function(&verifier_name, |b| {
b.iter(|| {
let msm = params.empty_msm();
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
let guard = verify_proof(&params, pk.get_vk(), msm, &[&[]], &mut transcript).unwrap();
let msm = guard.clone().use_challenges();
assert!(msm.eval());
});
});
}
fn criterion_benchmark(c: &mut Criterion) {
bench_poseidon::<MySpec<3, 2>, 3, 2, 2>("WIDTH = 3, RATE = 2", c);
bench_poseidon::<MySpec<9, 8>, 9, 8, 8>("WIDTH = 9, RATE = 8", c);
bench_poseidon::<MySpec<12, 11>, 12, 11, 11>("WIDTH = 12, RATE = 11", c);
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -1,70 +0,0 @@
use std::array;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use ff::Field;
use orchard::primitives::{
poseidon::{self, ConstantLength, P128Pow5T3},
sinsemilla,
};
use pasta_curves::pallas;
#[cfg(unix)]
use pprof::criterion::{Output, PProfProfiler};
use rand::{rngs::OsRng, Rng};
fn bench_primitives(c: &mut Criterion) {
let mut rng = OsRng;
{
let mut group = c.benchmark_group("Poseidon");
let message = [pallas::Base::random(rng), pallas::Base::random(rng)];
group.bench_function("2-to-1", |b| {
b.iter(|| {
poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message)
})
});
}
{
let mut group = c.benchmark_group("Sinsemilla");
let hasher = sinsemilla::HashDomain::new("hasher");
let committer = sinsemilla::CommitDomain::new("committer");
let bits: Vec<bool> = (0..1086).map(|_| rng.gen()).collect();
let r = pallas::Scalar::random(rng);
// Benchmark the input sizes we use in Orchard:
// - 510 bits for Commit^ivk
// - 520 bits for MerkleCRH
// - 1086 bits for NoteCommit
for size in array::IntoIter::new([510, 520, 1086]) {
group.bench_function(BenchmarkId::new("hash-to-point", size), |b| {
b.iter(|| hasher.hash_to_point(bits[..size].iter().cloned()))
});
group.bench_function(BenchmarkId::new("hash", size), |b| {
b.iter(|| hasher.hash(bits[..size].iter().cloned()))
});
group.bench_function(BenchmarkId::new("commit", size), |b| {
b.iter(|| committer.commit(bits[..size].iter().cloned(), &r))
});
group.bench_function(BenchmarkId::new("short-commit", size), |b| {
b.iter(|| committer.commit(bits[..size].iter().cloned(), &r))
});
}
}
}
#[cfg(unix)]
criterion_group! {
name = benches;
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
targets = bench_primitives
}
#[cfg(not(unix))]
criterion_group!(benches, bench_primitives);
criterion_main!(benches);

View File

@ -1 +1 @@
1.51.0
1.54.0

View File

@ -31,14 +31,14 @@ pub enum Error {
/// A bundle could not be built because required signatures were missing.
MissingSignatures,
/// An error occurred in the process of producing a proof for a bundle.
Proof(halo2::plonk::Error),
Proof(halo2_proofs::plonk::Error),
/// An overflow error occurred while attempting to construct the value
/// for a bundle.
ValueSum(value::OverflowError),
}
impl From<halo2::plonk::Error> for Error {
fn from(e: halo2::plonk::Error) -> Self {
impl From<halo2_proofs::plonk::Error> for Error {
fn from(e: halo2_proofs::plonk::Error) -> Self {
Error::Proof(e)
}
}
@ -392,7 +392,7 @@ impl<S: InProgressSignatures> InProgress<Unproven, S> {
pk: &ProvingKey,
instances: &[Instance],
rng: impl RngCore,
) -> Result<Proof, halo2::plonk::Error> {
) -> Result<Proof, halo2_proofs::plonk::Error> {
Proof::create(pk, &self.proof.circuits, instances, rng)
}
}

View File

@ -461,7 +461,7 @@ impl<V> Bundle<Authorized, V> {
}
/// Verifies the proof for this bundle.
pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2::plonk::Error> {
pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> {
self.authorization()
.proof()
.verify(vk, &self.to_instances())

View File

@ -55,9 +55,9 @@ where
.unwrap();
}
h.write_all(&ch.finalize().as_bytes()).unwrap();
h.write_all(&mh.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
h.write_all(ch.finalize().as_bytes()).unwrap();
h.write_all(mh.finalize().as_bytes()).unwrap();
h.write_all(nh.finalize().as_bytes()).unwrap();
h.write_all(&[bundle.flags().to_byte()]).unwrap();
h.write_all(&<i64>::from(bundle.value_balance()).to_le_bytes())
.unwrap();

View File

@ -1,9 +1,11 @@
//! The Orchard Action circuit implementation.
use group::{Curve, GroupEncoding};
use halo2::{
use halo2_proofs::{
circuit::{floor_planner, AssignedCell, Layouter},
plonk::{self, Advice, Column, Expression, Instance as InstanceColumn, Selector},
plonk::{
self, Advice, Column, Expression, Instance as InstanceColumn, Selector, SingleVerifier,
},
poly::Rotation,
transcript::{Blake2bRead, Blake2bWrite},
};
@ -12,21 +14,6 @@ use pasta_curves::{arithmetic::CurveAffine, pallas, vesta};
use rand::RngCore;
use self::commit_ivk::CommitIvkConfig;
use self::gadget::{
ecc::{
chip::{EccChip, EccConfig},
FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point,
},
poseidon::{Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
merkle::{
chip::{MerkleChip, MerkleConfig},
MerklePath,
},
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use self::note_commit::NoteCommitConfig;
use crate::{
constants::{
@ -41,14 +28,27 @@ use crate::{
nullifier::Nullifier,
ExtractedNoteCommitment,
},
primitives::{
poseidon::{self, ConstantLength},
redpallas::{SpendAuth, VerificationKey},
},
primitives::redpallas::{SpendAuth, VerificationKey},
spec::NonIdentityPallasPoint,
tree::{Anchor, MerkleHashOrchard},
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
};
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point,
},
poseidon::{Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
primitives::poseidon::{self, ConstantLength},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
merkle::{
chip::{MerkleChip, MerkleConfig},
MerklePath,
},
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use std::convert::TryInto;
@ -396,13 +396,13 @@ impl plonk::Circuit<pallas::Base> for Circuit {
// TODO: Replace with array::map once MSRV is 1.55.0.
gen_const_array(|i| typed_path[i].inner())
});
let merkle_inputs = MerklePath {
chip_1: config.merkle_chip_1(),
chip_2: config.merkle_chip_2(),
domain: OrchardHashDomains::MerkleCrh,
leaf_pos: self.pos,
let merkle_inputs = MerklePath::construct(
config.merkle_chip_1(),
config.merkle_chip_2(),
OrchardHashDomains::MerkleCrh,
self.pos,
path,
};
);
let leaf = cm_old.extract_p().inner().clone();
merkle_inputs.calculate_root(layouter.namespace(|| "MerkleCRH"), leaf)?
};
@ -715,14 +715,14 @@ impl plonk::Circuit<pallas::Base> for Circuit {
/// The verifying key for the Orchard Action circuit.
#[derive(Debug)]
pub struct VerifyingKey {
params: halo2::poly::commitment::Params<vesta::Affine>,
params: halo2_proofs::poly::commitment::Params<vesta::Affine>,
vk: plonk::VerifyingKey<vesta::Affine>,
}
impl VerifyingKey {
/// Builds the verifying key.
pub fn build() -> Self {
let params = halo2::poly::commitment::Params::new(K);
let params = halo2_proofs::poly::commitment::Params::new(K);
let circuit: Circuit = Default::default();
let vk = plonk::keygen_vk(&params, &circuit).unwrap();
@ -734,14 +734,14 @@ impl VerifyingKey {
/// The proving key for the Orchard Action circuit.
#[derive(Debug)]
pub struct ProvingKey {
params: halo2::poly::commitment::Params<vesta::Affine>,
params: halo2_proofs::poly::commitment::Params<vesta::Affine>,
pk: plonk::ProvingKey<vesta::Affine>,
}
impl ProvingKey {
/// Builds the proving key.
pub fn build() -> Self {
let params = halo2::poly::commitment::Params::new(K);
let params = halo2_proofs::poly::commitment::Params::new(K);
let circuit: Circuit = Default::default();
let vk = plonk::keygen_vk(&params, &circuit).unwrap();
@ -873,15 +873,9 @@ impl Proof {
.collect();
let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect();
let msm = vk.params.empty_msm();
let strategy = SingleVerifier::new(&vk.params);
let mut transcript = Blake2bRead::init(&self.0[..]);
let guard = plonk::verify_proof(&vk.params, &vk.vk, msm, &instances, &mut transcript)?;
let msm = guard.clone().use_challenges();
if msm.eval() {
Ok(())
} else {
Err(plonk::Error::ConstraintSystemFailure)
}
plonk::verify_proof(&vk.params, &vk.vk, strategy, &instances, &mut transcript)
}
/// Constructs a new Proof value.
@ -894,7 +888,7 @@ impl Proof {
mod tests {
use ff::Field;
use group::GroupEncoding;
use halo2::dev::MockProver;
use halo2_proofs::dev::MockProver;
use pasta_curves::pallas;
use rand::rngs::OsRng;
use std::iter;
@ -982,10 +976,11 @@ mod tests {
// Test that the proof size is as expected.
let expected_proof_size = {
let circuit_cost = halo2::dev::CircuitCost::<pasta_curves::vesta::Point, _>::measure(
K as usize,
&circuits[0],
);
let circuit_cost =
halo2_proofs::dev::CircuitCost::<pasta_curves::vesta::Point, _>::measure(
K as usize,
&circuits[0],
);
assert_eq!(usize::from(circuit_cost.proof_size(1)), 4992);
assert_eq!(usize::from(circuit_cost.proof_size(2)), 7264);
usize::from(circuit_cost.proof_size(instances.len()))
@ -1046,7 +1041,7 @@ mod tests {
rcm_new: None,
rcv: None,
};
halo2::dev::CircuitLayout::default()
halo2_proofs::dev::CircuitLayout::default()
.show_labels(false)
.view_height(0..(1 << 11))
.render(K, &circuit, &root)

View File

@ -1,20 +1,18 @@
use halo2::{
use halo2_proofs::{
circuit::{AssignedCell, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::{
circuit::gadget::{
ecc::{chip::EccChip, X},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
CommitDomain, Message, MessagePiece,
},
utilities::{bitrange_subset, bool_check},
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, T_P};
use halo2_gadgets::{
ecc::{chip::EccChip, X},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
CommitDomain, Message, MessagePiece,
},
constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, T_P},
utilities::{bitrange_subset, bool_check},
};
#[derive(Clone, Debug)]
@ -635,20 +633,18 @@ struct GateCells {
#[cfg(test)]
mod tests {
use super::CommitIvkConfig;
use crate::{
circuit::gadget::{
ecc::chip::{EccChip, EccConfig},
sinsemilla::chip::SinsemillaChip,
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
},
constants::{
fixed_bases::COMMIT_IVK_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases,
OrchardHashDomains, L_ORCHARD_BASE, T_Q,
},
primitives::sinsemilla::CommitDomain,
use crate::constants::{
fixed_bases::COMMIT_IVK_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases,
OrchardHashDomains, L_ORCHARD_BASE, T_Q,
};
use group::ff::{Field, PrimeFieldBits};
use halo2::{
use halo2_gadgets::{
ecc::chip::{EccChip, EccConfig},
primitives::sinsemilla::CommitDomain,
sinsemilla::chip::SinsemillaChip,
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use halo2_proofs::{
circuit::{AssignedCell, Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},

View File

@ -3,14 +3,11 @@
use pasta_curves::pallas;
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains};
use ecc::chip::EccChip;
use poseidon::Pow5Chip as PoseidonChip;
use sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip};
pub(crate) mod ecc;
pub mod poseidon;
pub(crate) mod sinsemilla;
pub mod utilities;
use halo2_gadgets::{
ecc::chip::EccChip,
poseidon::Pow5Chip as PoseidonChip,
sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip},
};
impl super::Config {
pub(super) fn ecc_chip(&self) -> EccChip<OrchardFixedBases> {

View File

@ -1,695 +0,0 @@
//! Gadgets for elliptic curve operations.
use std::fmt::Debug;
use halo2::{
arithmetic::CurveAffine,
circuit::{Chip, Layouter},
plonk::Error,
};
use crate::circuit::gadget::utilities::UtilitiesInstructions;
pub mod chip;
/// The set of circuit instructions required to use the ECC gadgets.
pub trait EccInstructions<C: CurveAffine>:
Chip<C::Base> + UtilitiesInstructions<C::Base> + Clone + Debug + Eq
{
/// Variable representing an element of the elliptic curve's base field, that
/// is used as a scalar in variable-base scalar mul.
///
/// It is not true in general that a scalar field element fits in a curve's
/// base field, and in particular it is untrue for the Pallas curve, whose
/// scalar field `Fq` is larger than its base field `Fp`.
///
/// However, the only use of variable-base scalar mul in the Orchard protocol
/// is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed
/// to be in the base field of the curve. (See non-normative notes in
/// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.)
type ScalarVar: Clone + Debug;
/// Variable representing a full-width element of the elliptic curve's
/// scalar field, to be used for fixed-base scalar mul.
type ScalarFixed: Clone + Debug;
/// Variable representing a signed short element of the elliptic curve's
/// scalar field, to be used for fixed-base scalar mul.
///
/// A `ScalarFixedShort` must be in the range [-(2^64 - 1), 2^64 - 1].
type ScalarFixedShort: Clone + Debug;
/// Variable representing an elliptic curve point.
type Point: From<Self::NonIdentityPoint> + Clone + Debug;
/// Variable representing a non-identity elliptic curve point.
type NonIdentityPoint: Clone + Debug;
/// Variable representing the affine short Weierstrass x-coordinate of an
/// elliptic curve point.
type X: Clone + Debug;
/// Enumeration of the set of fixed bases to be used in scalar mul.
/// TODO: When associated consts can be used as const generics, introduce
/// `Self::NUM_WINDOWS`, `Self::NUM_WINDOWS_BASE_FIELD`, `Self::NUM_WINDOWS_SHORT`
/// and use them to differentiate `FixedPoints` types.
type FixedPoints: FixedPoints<C>;
/// Constrains point `a` to be equal in value to point `b`.
fn constrain_equal(
&self,
layouter: &mut impl Layouter<C::Base>,
a: &Self::Point,
b: &Self::Point,
) -> Result<(), Error>;
/// Witnesses the given point as a private input to the circuit.
/// This allows the point to be the identity, mapped to (0, 0) in
/// affine coordinates.
fn witness_point(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C>,
) -> Result<Self::Point, Error>;
/// Witnesses the given point as a private input to the circuit.
/// This returns an error if the point is the identity.
fn witness_point_non_id(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C>,
) -> Result<Self::NonIdentityPoint, Error>;
/// Extracts the x-coordinate of a point.
fn extract_p<Point: Into<Self::Point> + Clone>(point: &Point) -> Self::X;
/// Performs incomplete point addition, returning `a + b`.
///
/// This returns an error in exceptional cases.
fn add_incomplete(
&self,
layouter: &mut impl Layouter<C::Base>,
a: &Self::NonIdentityPoint,
b: &Self::NonIdentityPoint,
) -> Result<Self::NonIdentityPoint, Error>;
/// Performs complete point addition, returning `a + b`.
fn add<A: Into<Self::Point> + Clone, B: Into<Self::Point> + Clone>(
&self,
layouter: &mut impl Layouter<C::Base>,
a: &A,
b: &B,
) -> Result<Self::Point, Error>;
/// Performs variable-base scalar multiplication, returning `[scalar] base`.
fn mul(
&self,
layouter: &mut impl Layouter<C::Base>,
scalar: &Self::Var,
base: &Self::NonIdentityPoint,
) -> Result<(Self::Point, Self::ScalarVar), Error>;
/// Performs fixed-base scalar multiplication using a full-width scalar, returning `[scalar] base`.
fn mul_fixed(
&self,
layouter: &mut impl Layouter<C::Base>,
scalar: Option<C::Scalar>,
base: &<Self::FixedPoints as FixedPoints<C>>::FullScalar,
) -> Result<(Self::Point, Self::ScalarFixed), Error>;
/// Performs fixed-base scalar multiplication using a short signed scalar, returning
/// `[magnitude * sign] base`.
fn mul_fixed_short(
&self,
layouter: &mut impl Layouter<C::Base>,
magnitude_sign: (Self::Var, Self::Var),
base: &<Self::FixedPoints as FixedPoints<C>>::ShortScalar,
) -> Result<(Self::Point, Self::ScalarFixedShort), Error>;
/// Performs fixed-base scalar multiplication using a base field element as the scalar.
/// In the current implementation, this base field element must be output from another
/// instruction.
fn mul_fixed_base_field_elem(
&self,
layouter: &mut impl Layouter<C::Base>,
base_field_elem: Self::Var,
base: &<Self::FixedPoints as FixedPoints<C>>::Base,
) -> Result<Self::Point, Error>;
}
/// Defines the fixed points for a given instantiation of the ECC chip.
pub trait FixedPoints<C: CurveAffine>: Debug + Eq + Clone {
type FullScalar: Debug + Eq + Clone;
type ShortScalar: Debug + Eq + Clone;
type Base: Debug + Eq + Clone;
}
/// An element of the given elliptic curve's base field, that is used as a scalar
/// in variable-base scalar mul.
///
/// It is not true in general that a scalar field element fits in a curve's
/// base field, and in particular it is untrue for the Pallas curve, whose
/// scalar field `Fq` is larger than its base field `Fp`.
///
/// However, the only use of variable-base scalar mul in the Orchard protocol
/// is in deriving diversified addresses `[ivk] g_d`, and `ivk` is guaranteed
/// to be in the base field of the curve. (See non-normative notes in
/// https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents.)
#[derive(Debug)]
pub struct ScalarVar<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: EccChip::ScalarVar,
}
/// A full-width element of the given elliptic curve's scalar field, to be used for fixed-base scalar mul.
#[derive(Debug)]
pub struct ScalarFixed<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: EccChip::ScalarFixed,
}
/// A signed short element of the given elliptic curve's scalar field, to be used for fixed-base scalar mul.
#[derive(Debug)]
pub struct ScalarFixedShort<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: EccChip::ScalarFixedShort,
}
/// A non-identity elliptic curve point over the given curve.
#[derive(Copy, Clone, Debug)]
pub struct NonIdentityPoint<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: EccChip::NonIdentityPoint,
}
impl<C: CurveAffine, EccChip: EccInstructions<C>> NonIdentityPoint<C, EccChip> {
/// Constructs a new point with the given value.
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C>,
) -> Result<Self, Error> {
let point = chip.witness_point_non_id(&mut layouter, value);
point.map(|inner| NonIdentityPoint { chip, inner })
}
/// Constrains this point to be equal in value to another point.
pub fn constrain_equal<Other: Into<Point<C, EccChip>> + Clone>(
&self,
mut layouter: impl Layouter<C::Base>,
other: &Other,
) -> Result<(), Error> {
let other: Point<C, EccChip> = (other.clone()).into();
self.chip.constrain_equal(
&mut layouter,
&Point::<C, EccChip>::from(self.clone()).inner,
&other.inner,
)
}
/// Returns the inner point.
pub fn inner(&self) -> &EccChip::NonIdentityPoint {
&self.inner
}
/// Extracts the x-coordinate of a point.
pub fn extract_p(&self) -> X<C, EccChip> {
X::from_inner(self.chip.clone(), EccChip::extract_p(&self.inner))
}
/// Wraps the given point (obtained directly from an instruction) in a gadget.
pub fn from_inner(chip: EccChip, inner: EccChip::NonIdentityPoint) -> Self {
NonIdentityPoint { chip, inner }
}
/// Returns `self + other` using complete addition.
pub fn add<Other: Into<Point<C, EccChip>> + Clone>(
&self,
mut layouter: impl Layouter<C::Base>,
other: &Other,
) -> Result<Point<C, EccChip>, Error> {
let other: Point<C, EccChip> = (other.clone()).into();
assert_eq!(self.chip, other.chip);
self.chip
.add(&mut layouter, &self.inner, &other.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
})
}
/// Returns `self + other` using incomplete addition.
/// The arguments are type-constrained not to be the identity point,
/// and since exceptional cases return an Error, the result also cannot
/// be the identity point.
pub fn add_incomplete(
&self,
mut layouter: impl Layouter<C::Base>,
other: &Self,
) -> Result<Self, Error> {
assert_eq!(self.chip, other.chip);
self.chip
.add_incomplete(&mut layouter, &self.inner, &other.inner)
.map(|inner| NonIdentityPoint {
chip: self.chip.clone(),
inner,
})
}
/// Returns `[by] self`.
#[allow(clippy::type_complexity)]
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
by: &EccChip::Var,
) -> Result<(Point<C, EccChip>, ScalarVar<C, EccChip>), Error> {
self.chip
.mul(&mut layouter, by, &self.inner.clone())
.map(|(point, scalar)| {
(
Point {
chip: self.chip.clone(),
inner: point,
},
ScalarVar {
chip: self.chip.clone(),
inner: scalar,
},
)
})
}
}
impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq>
From<NonIdentityPoint<C, EccChip>> for Point<C, EccChip>
{
fn from(non_id_point: NonIdentityPoint<C, EccChip>) -> Self {
Self {
chip: non_id_point.chip,
inner: non_id_point.inner.into(),
}
}
}
/// An elliptic curve point over the given curve.
#[derive(Copy, Clone, Debug)]
pub struct Point<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> {
chip: EccChip,
inner: EccChip::Point,
}
impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> Point<C, EccChip> {
/// Constructs a new point with the given value.
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C>,
) -> Result<Self, Error> {
let point = chip.witness_point(&mut layouter, value);
point.map(|inner| Point { chip, inner })
}
/// Constrains this point to be equal in value to another point.
pub fn constrain_equal<Other: Into<Point<C, EccChip>> + Clone>(
&self,
mut layouter: impl Layouter<C::Base>,
other: &Other,
) -> Result<(), Error> {
let other: Point<C, EccChip> = (other.clone()).into();
self.chip
.constrain_equal(&mut layouter, &self.inner, &other.inner)
}
/// Returns the inner point.
pub fn inner(&self) -> &EccChip::Point {
&self.inner
}
/// Extracts the x-coordinate of a point.
pub fn extract_p(&self) -> X<C, EccChip> {
X::from_inner(self.chip.clone(), EccChip::extract_p(&self.inner))
}
/// Wraps the given point (obtained directly from an instruction) in a gadget.
pub fn from_inner(chip: EccChip, inner: EccChip::Point) -> Self {
Point { chip, inner }
}
/// Returns `self + other` using complete addition.
pub fn add<Other: Into<Point<C, EccChip>> + Clone>(
&self,
mut layouter: impl Layouter<C::Base>,
other: &Other,
) -> Result<Point<C, EccChip>, Error> {
let other: Point<C, EccChip> = (other.clone()).into();
assert_eq!(self.chip, other.chip);
self.chip
.add(&mut layouter, &self.inner, &other.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
})
}
}
/// The affine short Weierstrass x-coordinate of an elliptic curve point over the
/// given curve.
#[derive(Debug)]
pub struct X<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: EccChip::X,
}
impl<C: CurveAffine, EccChip: EccInstructions<C>> X<C, EccChip> {
/// Wraps the given x-coordinate (obtained directly from an instruction) in a gadget.
pub fn from_inner(chip: EccChip, inner: EccChip::X) -> Self {
X { chip, inner }
}
/// Returns the inner x-coordinate.
pub fn inner(&self) -> &EccChip::X {
&self.inner
}
}
/// A constant elliptic curve point over the given curve, for which window tables have
/// been provided to make scalar multiplication more efficient.
///
/// Used in scalar multiplication with full-width scalars.
#[derive(Clone, Debug)]
pub struct FixedPoint<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: <EccChip::FixedPoints as FixedPoints<C>>::FullScalar,
}
/// A constant elliptic curve point over the given curve, used in scalar multiplication
/// with a base field element
#[derive(Clone, Debug)]
pub struct FixedPointBaseField<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: <EccChip::FixedPoints as FixedPoints<C>>::Base,
}
/// A constant elliptic curve point over the given curve, used in scalar multiplication
/// with a short signed exponent
#[derive(Clone, Debug)]
pub struct FixedPointShort<C: CurveAffine, EccChip: EccInstructions<C>> {
chip: EccChip,
inner: <EccChip::FixedPoints as FixedPoints<C>>::ShortScalar,
}
impl<C: CurveAffine, EccChip: EccInstructions<C>> FixedPoint<C, EccChip> {
#[allow(clippy::type_complexity)]
/// Returns `[by] self`.
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
by: Option<C::Scalar>,
) -> Result<(Point<C, EccChip>, ScalarFixed<C, EccChip>), Error> {
self.chip
.mul_fixed(&mut layouter, by, &self.inner)
.map(|(point, scalar)| {
(
Point {
chip: self.chip.clone(),
inner: point,
},
ScalarFixed {
chip: self.chip.clone(),
inner: scalar,
},
)
})
}
/// Wraps the given fixed base (obtained directly from an instruction) in a gadget.
pub fn from_inner(
chip: EccChip,
inner: <EccChip::FixedPoints as FixedPoints<C>>::FullScalar,
) -> Self {
Self { chip, inner }
}
}
impl<C: CurveAffine, EccChip: EccInstructions<C>> FixedPointBaseField<C, EccChip> {
#[allow(clippy::type_complexity)]
/// Returns `[by] self`.
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
by: EccChip::Var,
) -> Result<Point<C, EccChip>, Error> {
self.chip
.mul_fixed_base_field_elem(&mut layouter, by, &self.inner)
.map(|inner| Point {
chip: self.chip.clone(),
inner,
})
}
/// Wraps the given fixed base (obtained directly from an instruction) in a gadget.
pub fn from_inner(
chip: EccChip,
inner: <EccChip::FixedPoints as FixedPoints<C>>::Base,
) -> Self {
Self { chip, inner }
}
}
impl<C: CurveAffine, EccChip: EccInstructions<C>> FixedPointShort<C, EccChip> {
#[allow(clippy::type_complexity)]
/// Returns `[by] self`.
pub fn mul(
&self,
mut layouter: impl Layouter<C::Base>,
magnitude_sign: (EccChip::Var, EccChip::Var),
) -> Result<(Point<C, EccChip>, ScalarFixedShort<C, EccChip>), Error> {
self.chip
.mul_fixed_short(&mut layouter, magnitude_sign, &self.inner)
.map(|(point, scalar)| {
(
Point {
chip: self.chip.clone(),
inner: point,
},
ScalarFixedShort {
chip: self.chip.clone(),
inner: scalar,
},
)
})
}
/// Wraps the given fixed base (obtained directly from an instruction) in a gadget.
pub fn from_inner(
chip: EccChip,
inner: <EccChip::FixedPoints as FixedPoints<C>>::ShortScalar,
) -> Self {
Self { chip, inner }
}
}
#[cfg(test)]
mod tests {
use group::{prime::PrimeCurveAffine, Curve, Group};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::pallas;
use super::chip::{EccChip, EccConfig};
use crate::circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig;
use crate::constants::OrchardFixedBases;
struct MyCircuit {
test_errors: bool,
}
#[allow(non_snake_case)]
impl Circuit<pallas::Base> for MyCircuit {
type Config = EccConfig<OrchardFixedBases>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
MyCircuit { test_errors: false }
}
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(),
];
let lookup_table = meta.lookup_table_column();
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(),
];
// Shared fixed column for loading constants
let constants = meta.fixed_column();
meta.enable_constant(constants);
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table);
EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
let chip = EccChip::construct(config.clone());
// Load 10-bit lookup table. In the Action circuit, this will be
// provided by the Sinsemilla chip.
config.lookup_config.load(&mut layouter)?;
// Generate a random non-identity point P
let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // P
let p = super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "P"),
Some(p_val),
)?;
let p_neg = -p_val;
let p_neg = super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "-P"),
Some(p_neg),
)?;
// Generate a random non-identity point Q
let q_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // Q
let q = super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "Q"),
Some(q_val),
)?;
// Make sure P and Q are not the same point.
assert_ne!(p_val, q_val);
// Test that we can witness the identity as a point, but not as a non-identity point.
{
let _ = super::Point::new(
chip.clone(),
layouter.namespace(|| "identity"),
Some(pallas::Affine::identity()),
)?;
super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "identity"),
Some(pallas::Affine::identity()),
)
.expect_err("Trying to witness the identity should return an error");
}
// Test witness non-identity point
{
super::chip::witness_point::tests::test_witness_non_id(
chip.clone(),
layouter.namespace(|| "witness non-identity point"),
)
}
// Test complete addition
{
super::chip::add::tests::test_add(
chip.clone(),
layouter.namespace(|| "complete addition"),
p_val,
&p,
q_val,
&q,
&p_neg,
)?;
}
// Test incomplete addition
{
super::chip::add_incomplete::tests::test_add_incomplete(
chip.clone(),
layouter.namespace(|| "incomplete addition"),
p_val,
&p,
q_val,
&q,
&p_neg,
self.test_errors,
)?;
}
// Test variable-base scalar multiplication
{
super::chip::mul::tests::test_mul(
chip.clone(),
layouter.namespace(|| "variable-base scalar mul"),
&p,
p_val,
)?;
}
// Test full-width fixed-base scalar multiplication
{
super::chip::mul_fixed::full_width::tests::test_mul_fixed(
chip.clone(),
layouter.namespace(|| "full-width fixed-base scalar mul"),
)?;
}
// Test signed short fixed-base scalar multiplication
{
super::chip::mul_fixed::short::tests::test_mul_fixed_short(
chip.clone(),
layouter.namespace(|| "signed short fixed-base scalar mul"),
)?;
}
// Test fixed-base scalar multiplication with a base field element
{
super::chip::mul_fixed::base_field_elem::tests::test_mul_fixed_base_field(
chip,
layouter.namespace(|| "fixed-base scalar mul with base field element"),
)?;
}
Ok(())
}
}
#[test]
fn ecc_chip() {
let k = 13;
let circuit = MyCircuit { test_errors: true };
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[cfg(feature = "dev-graph")]
#[test]
fn print_ecc_chip() {
use plotters::prelude::*;
let root = BitMapBackend::new("ecc-chip-layout.png", (1024, 7680)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root.titled("Ecc Chip Layout", ("sans-serif", 60)).unwrap();
let circuit = MyCircuit { test_errors: false };
halo2::dev::CircuitLayout::default()
.render(13, &circuit, &root)
.unwrap();
}
}

View File

@ -1,509 +0,0 @@
use super::{EccInstructions, FixedPoints};
use crate::{
circuit::gadget::utilities::{
lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions,
},
primitives::sinsemilla,
};
use arrayvec::ArrayVec;
use ff::Field;
use group::prime::PrimeCurveAffine;
use halo2::{
circuit::{AssignedCell, Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed},
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
use std::convert::TryInto;
pub(super) mod add;
pub(super) mod add_incomplete;
pub mod constants;
pub(super) mod mul;
pub(super) mod mul_fixed;
pub(super) mod witness_point;
pub use constants::*;
/// A curve point represented in affine (x, y) coordinates, or the
/// identity represented as (0, 0).
/// Each coordinate is assigned to a cell.
#[derive(Clone, Debug)]
pub struct EccPoint {
/// x-coordinate
x: AssignedCell<pallas::Base, pallas::Base>,
/// y-coordinate
y: AssignedCell<pallas::Base, pallas::Base>,
}
impl EccPoint {
/// Constructs a point from its coordinates, without checking they are on the curve.
///
/// This is an internal API that we only use where we know we have a valid curve point
/// (specifically inside Sinsemilla).
pub(in crate::circuit::gadget) fn from_coordinates_unchecked(
x: AssignedCell<pallas::Base, pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
) -> Self {
EccPoint { x, y }
}
/// Returns the value of this curve point, if known.
pub fn point(&self) -> Option<pallas::Affine> {
match (self.x.value(), self.y.value()) {
(Some(x), Some(y)) => {
if x.is_zero_vartime() && y.is_zero_vartime() {
Some(pallas::Affine::identity())
} else {
Some(pallas::Affine::from_xy(*x, *y).unwrap())
}
}
_ => None,
}
}
/// The cell containing the affine short-Weierstrass x-coordinate,
/// or 0 for the zero point.
pub fn x(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.x.clone()
}
/// The cell containing the affine short-Weierstrass y-coordinate,
/// or 0 for the zero point.
pub fn y(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.y.clone()
}
#[cfg(test)]
fn is_identity(&self) -> Option<bool> {
self.x.value().map(|x| x.is_zero_vartime())
}
}
/// A non-identity point represented in affine (x, y) coordinates.
/// Each coordinate is assigned to a cell.
#[derive(Clone, Debug)]
pub struct NonIdentityEccPoint {
/// x-coordinate
x: AssignedCell<pallas::Base, pallas::Base>,
/// y-coordinate
y: AssignedCell<pallas::Base, pallas::Base>,
}
impl NonIdentityEccPoint {
/// Constructs a point from its coordinates, without checking they are on the curve.
///
/// This is an internal API that we only use where we know we have a valid non-identity
/// curve point (specifically inside Sinsemilla).
pub(in crate::circuit::gadget) fn from_coordinates_unchecked(
x: AssignedCell<pallas::Base, pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
) -> Self {
NonIdentityEccPoint { x, y }
}
/// Returns the value of this curve point, if known.
pub fn point(&self) -> Option<pallas::Affine> {
match (self.x.value(), self.y.value()) {
(Some(x), Some(y)) => {
assert!(!x.is_zero_vartime() && !y.is_zero_vartime());
Some(pallas::Affine::from_xy(*x, *y).unwrap())
}
_ => None,
}
}
/// The cell containing the affine short-Weierstrass x-coordinate.
pub fn x(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.x.clone()
}
/// The cell containing the affine short-Weierstrass y-coordinate.
pub fn y(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.y.clone()
}
}
impl From<NonIdentityEccPoint> for EccPoint {
fn from(non_id_point: NonIdentityEccPoint) -> Self {
Self {
x: non_id_point.x,
y: non_id_point.y,
}
}
}
/// Configuration for the ECC chip
#[derive(Clone, Debug, Eq, PartialEq)]
#[allow(non_snake_case)]
pub struct EccConfig<FixedPoints: super::FixedPoints<pallas::Affine>> {
/// Advice columns needed by instructions in the ECC chip.
pub advices: [Column<Advice>; 10],
/// Incomplete addition
add_incomplete: add_incomplete::Config,
/// Complete addition
add: add::Config,
/// Variable-base scalar multiplication
mul: mul::Config,
/// Fixed-base full-width scalar multiplication
mul_fixed_full: mul_fixed::full_width::Config<FixedPoints>,
/// Fixed-base signed short scalar multiplication
mul_fixed_short: mul_fixed::short::Config<FixedPoints>,
/// Fixed-base mul using a base field element as a scalar
mul_fixed_base_field: mul_fixed::base_field_elem::Config<FixedPoints>,
/// Witness point
witness_point: witness_point::Config,
/// Lookup range check using 10-bit lookup table
pub lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
}
/// A trait representing the kind of scalar used with a particular `FixedPoint`.
///
/// This trait exists because of limitations around const generics.
pub trait ScalarKind {
const NUM_WINDOWS: usize;
}
/// Type marker representing a full-width scalar for use in fixed-base scalar
/// multiplication.
pub enum FullScalar {}
impl ScalarKind for FullScalar {
const NUM_WINDOWS: usize = NUM_WINDOWS;
}
/// Type marker representing a signed 64-bit scalar for use in fixed-base scalar
/// multiplication.
pub enum ShortScalar {}
impl ScalarKind for ShortScalar {
const NUM_WINDOWS: usize = NUM_WINDOWS_SHORT;
}
/// Type marker representing a base field element being used as a scalar in fixed-base
/// scalar multiplication.
pub enum BaseFieldElem {}
impl ScalarKind for BaseFieldElem {
const NUM_WINDOWS: usize = NUM_WINDOWS;
}
/// Returns information about a fixed point.
///
/// TODO: When associated consts can be used as const generics, introduce a
/// `const NUM_WINDOWS: usize` associated const, and return `NUM_WINDOWS`-sized
/// arrays instead of `Vec`s.
pub trait FixedPoint<C: CurveAffine>: std::fmt::Debug + Eq + Clone {
type ScalarKind: ScalarKind;
fn generator(&self) -> C;
fn u(&self) -> Vec<[[u8; 32]; H]>;
fn z(&self) -> Vec<u64>;
fn lagrange_coeffs(&self) -> Vec<[C::Base; H]> {
compute_lagrange_coeffs(self.generator(), Self::ScalarKind::NUM_WINDOWS)
}
}
/// A chip implementing EccInstructions
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EccChip<FixedPoints: super::FixedPoints<pallas::Affine>> {
config: EccConfig<FixedPoints>,
}
impl<FixedPoints: super::FixedPoints<pallas::Affine>> Chip<pallas::Base> for EccChip<FixedPoints> {
type Config = EccConfig<FixedPoints>;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<Fixed: super::FixedPoints<pallas::Affine>> UtilitiesInstructions<pallas::Base>
for EccChip<Fixed>
{
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl<FixedPoints: super::FixedPoints<pallas::Affine>> EccChip<FixedPoints> {
pub fn construct(config: <Self as Chip<pallas::Base>>::Config) -> Self {
Self { config }
}
/// # Side effects
///
/// All columns in `advices` will be equality-enabled.
#[allow(non_snake_case)]
pub fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
advices: [Column<Advice>; 10],
lagrange_coeffs: [Column<Fixed>; 8],
range_check: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
) -> <Self as Chip<pallas::Base>>::Config {
// Create witness point gate
let witness_point = witness_point::Config::configure(meta, advices[0], advices[1]);
// Create incomplete point addition gate
let add_incomplete =
add_incomplete::Config::configure(meta, advices[0], advices[1], advices[2], advices[3]);
// Create complete point addition gate
let add = add::Config::configure(
meta, advices[0], advices[1], advices[2], advices[3], advices[4], advices[5],
advices[6], advices[7], advices[8],
);
// Create variable-base scalar mul gates
let mul = mul::Config::configure(meta, add, range_check, advices);
// Create config that is shared across short, base-field, and full-width
// fixed-base scalar mul.
let mul_fixed = mul_fixed::Config::<FixedPoints>::configure(
meta,
lagrange_coeffs,
advices[4],
advices[0],
advices[1],
advices[5],
add,
add_incomplete,
);
// Create gate that is only used in full-width fixed-base scalar mul.
let mul_fixed_full =
mul_fixed::full_width::Config::<FixedPoints>::configure(meta, mul_fixed.clone());
// Create gate that is only used in short fixed-base scalar mul.
let mul_fixed_short =
mul_fixed::short::Config::<FixedPoints>::configure(meta, mul_fixed.clone());
// Create gate that is only used in fixed-base mul using a base field element.
let mul_fixed_base_field = mul_fixed::base_field_elem::Config::<FixedPoints>::configure(
meta,
advices[6..9].try_into().unwrap(),
range_check,
mul_fixed,
);
EccConfig {
advices,
add_incomplete,
add,
mul,
mul_fixed_full,
mul_fixed_short,
mul_fixed_base_field,
witness_point,
lookup_config: range_check,
}
}
}
/// A full-width scalar used for fixed-base scalar multiplication.
/// This is decomposed into 85 3-bit windows in little-endian order,
/// i.e. `windows` = [k_0, k_1, ..., k_84] (for a 255-bit scalar)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// each `k_i` is in the range [0..2^3).
#[derive(Clone, Debug)]
pub struct EccScalarFixed {
value: Option<pallas::Scalar>,
windows: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { NUM_WINDOWS }>,
}
// TODO: Make V a `u64`
type MagnitudeCell = AssignedCell<pallas::Base, pallas::Base>;
// TODO: Make V an enum Sign { Positive, Negative }
type SignCell = AssignedCell<pallas::Base, pallas::Base>;
type MagnitudeSign = (MagnitudeCell, SignCell);
/// A signed short scalar used for fixed-base scalar multiplication.
/// A short scalar must have magnitude in the range [0..2^64), with
/// a sign of either 1 or -1.
/// This is decomposed into 3-bit windows in little-endian order
/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3)
/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}.
/// Each `a_i` is in the range [0..2^3).
///
/// `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// each `k_i` is in the range [0..2^3).
/// k_21 must be a single bit, i.e. 0 or 1.
#[derive(Clone, Debug)]
pub struct EccScalarFixedShort {
magnitude: MagnitudeCell,
sign: SignCell,
running_sum: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { NUM_WINDOWS_SHORT + 1 }>,
}
/// A base field element used for fixed-base scalar multiplication.
/// This is decomposed into 3-bit windows in little-endian order
/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3)
/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}.
/// Each `a_i` is in the range [0..2^3).
///
/// `running_sum` = [z_0, ..., z_85], where we expect z_85 = 0.
/// Since z_0 is initialized as the scalar α, we store it as
/// `base_field_elem`.
#[derive(Clone, Debug)]
struct EccBaseFieldElemFixed {
base_field_elem: AssignedCell<pallas::Base, pallas::Base>,
running_sum: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { NUM_WINDOWS + 1 }>,
}
impl EccBaseFieldElemFixed {
fn base_field_elem(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.base_field_elem.clone()
}
}
impl<Fixed: FixedPoints<pallas::Affine>> EccInstructions<pallas::Affine> for EccChip<Fixed>
where
<Fixed as FixedPoints<pallas::Affine>>::Base:
FixedPoint<pallas::Affine, ScalarKind = BaseFieldElem>,
<Fixed as FixedPoints<pallas::Affine>>::FullScalar:
FixedPoint<pallas::Affine, ScalarKind = FullScalar>,
<Fixed as FixedPoints<pallas::Affine>>::ShortScalar:
FixedPoint<pallas::Affine, ScalarKind = ShortScalar>,
{
type ScalarFixed = EccScalarFixed;
type ScalarFixedShort = EccScalarFixedShort;
type ScalarVar = AssignedCell<pallas::Base, pallas::Base>;
type Point = EccPoint;
type NonIdentityPoint = NonIdentityEccPoint;
type X = AssignedCell<pallas::Base, pallas::Base>;
type FixedPoints = Fixed;
fn constrain_equal(
&self,
layouter: &mut impl Layouter<pallas::Base>,
a: &Self::Point,
b: &Self::Point,
) -> Result<(), Error> {
layouter.assign_region(
|| "constrain equal",
|mut region| {
// Constrain x-coordinates
region.constrain_equal(a.x().cell(), b.x().cell())?;
// Constrain x-coordinates
region.constrain_equal(a.y().cell(), b.y().cell())
},
)
}
fn witness_point(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Affine>,
) -> Result<Self::Point, Error> {
let config = self.config().witness_point;
layouter.assign_region(
|| "witness point",
|mut region| config.point(value, 0, &mut region),
)
}
fn witness_point_non_id(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Affine>,
) -> Result<Self::NonIdentityPoint, Error> {
let config = self.config().witness_point;
layouter.assign_region(
|| "witness non-identity point",
|mut region| config.point_non_id(value, 0, &mut region),
)
}
fn extract_p<Point: Into<Self::Point> + Clone>(point: &Point) -> Self::X {
let point: EccPoint = (point.clone()).into();
point.x()
}
fn add_incomplete(
&self,
layouter: &mut impl Layouter<pallas::Base>,
a: &Self::NonIdentityPoint,
b: &Self::NonIdentityPoint,
) -> Result<Self::NonIdentityPoint, Error> {
let config = self.config().add_incomplete;
layouter.assign_region(
|| "incomplete point addition",
|mut region| config.assign_region(a, b, 0, &mut region),
)
}
fn add<A: Into<Self::Point> + Clone, B: Into<Self::Point> + Clone>(
&self,
layouter: &mut impl Layouter<pallas::Base>,
a: &A,
b: &B,
) -> Result<Self::Point, Error> {
let config = self.config().add;
layouter.assign_region(
|| "complete point addition",
|mut region| {
config.assign_region(&(a.clone()).into(), &(b.clone()).into(), 0, &mut region)
},
)
}
fn mul(
&self,
layouter: &mut impl Layouter<pallas::Base>,
scalar: &Self::Var,
base: &Self::NonIdentityPoint,
) -> Result<(Self::Point, Self::ScalarVar), Error> {
let config = self.config().mul;
config.assign(
layouter.namespace(|| "variable-base scalar mul"),
scalar.clone(),
base,
)
}
fn mul_fixed(
&self,
layouter: &mut impl Layouter<pallas::Base>,
scalar: Option<pallas::Scalar>,
base: &<Self::FixedPoints as FixedPoints<pallas::Affine>>::FullScalar,
) -> Result<(Self::Point, Self::ScalarFixed), Error> {
let config = self.config().mul_fixed_full.clone();
config.assign(
layouter.namespace(|| format!("fixed-base mul of {:?}", base)),
scalar,
base,
)
}
fn mul_fixed_short(
&self,
layouter: &mut impl Layouter<pallas::Base>,
magnitude_sign: MagnitudeSign,
base: &<Self::FixedPoints as FixedPoints<pallas::Affine>>::ShortScalar,
) -> Result<(Self::Point, Self::ScalarFixedShort), Error> {
let config = self.config().mul_fixed_short.clone();
config.assign(
layouter.namespace(|| format!("short fixed-base mul of {:?}", base)),
magnitude_sign,
base,
)
}
fn mul_fixed_base_field_elem(
&self,
layouter: &mut impl Layouter<pallas::Base>,
base_field_elem: AssignedCell<pallas::Base, pallas::Base>,
base: &<Self::FixedPoints as FixedPoints<pallas::Affine>>::Base,
) -> Result<Self::Point, Error> {
let config = self.config().mul_fixed_base_field.clone();
config.assign(
layouter.namespace(|| format!("base-field elem fixed-base mul of {:?}", base)),
base_field_elem,
base,
)
}
}

View File

@ -1,501 +0,0 @@
use std::array;
use super::EccPoint;
use ff::{BatchInvert, Field};
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use std::collections::HashSet;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
q_add: Selector,
// lambda
lambda: Column<Advice>,
// x-coordinate of P in P + Q = R
pub x_p: Column<Advice>,
// y-coordinate of P in P + Q = R
pub y_p: Column<Advice>,
// x-coordinate of Q or R in P + Q = R
pub x_qr: Column<Advice>,
// y-coordinate of Q or R in P + Q = R
pub y_qr: Column<Advice>,
// α = inv0(x_q - x_p)
alpha: Column<Advice>,
// β = inv0(x_p)
beta: Column<Advice>,
// γ = inv0(x_q)
gamma: Column<Advice>,
// δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise
delta: Column<Advice>,
}
impl Config {
#[allow(clippy::too_many_arguments)]
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
x_p: Column<Advice>,
y_p: Column<Advice>,
x_qr: Column<Advice>,
y_qr: Column<Advice>,
lambda: Column<Advice>,
alpha: Column<Advice>,
beta: Column<Advice>,
gamma: Column<Advice>,
delta: Column<Advice>,
) -> Self {
meta.enable_equality(x_p);
meta.enable_equality(y_p);
meta.enable_equality(x_qr);
meta.enable_equality(y_qr);
let config = Self {
q_add: meta.selector(),
x_p,
y_p,
x_qr,
y_qr,
lambda,
alpha,
beta,
gamma,
delta,
};
config.create_gate(meta);
config
}
pub(crate) fn advice_columns(&self) -> HashSet<Column<Advice>> {
core::array::IntoIter::new([
self.x_p,
self.y_p,
self.x_qr,
self.y_qr,
self.lambda,
self.alpha,
self.beta,
self.gamma,
self.delta,
])
.collect()
}
pub(crate) fn output_columns(&self) -> HashSet<Column<Advice>> {
core::array::IntoIter::new([self.x_qr, self.y_qr]).collect()
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("complete addition gates", |meta| {
let q_add = meta.query_selector(self.q_add);
let x_p = meta.query_advice(self.x_p, Rotation::cur());
let y_p = meta.query_advice(self.y_p, Rotation::cur());
let x_q = meta.query_advice(self.x_qr, Rotation::cur());
let y_q = meta.query_advice(self.y_qr, Rotation::cur());
let x_r = meta.query_advice(self.x_qr, Rotation::next());
let y_r = meta.query_advice(self.y_qr, Rotation::next());
let lambda = meta.query_advice(self.lambda, Rotation::cur());
// α = inv0(x_q - x_p)
let alpha = meta.query_advice(self.alpha, Rotation::cur());
// β = inv0(x_p)
let beta = meta.query_advice(self.beta, Rotation::cur());
// γ = inv0(x_q)
let gamma = meta.query_advice(self.gamma, Rotation::cur());
// δ = inv0(y_p + y_q) if x_q = x_p, 0 otherwise
let delta = meta.query_advice(self.delta, Rotation::cur());
// Useful composite expressions
// α ⋅(x_q - x_p)
let if_alpha = (x_q.clone() - x_p.clone()) * alpha;
// β ⋅ x_p
let if_beta = x_p.clone() * beta;
// γ ⋅ x_q
let if_gamma = x_q.clone() * gamma;
// δ ⋅(y_p + y_q)
let if_delta = (y_q.clone() + y_p.clone()) * delta;
// Useful constants
let one = Expression::Constant(pallas::Base::one());
let two = Expression::Constant(pallas::Base::from(2));
let three = Expression::Constant(pallas::Base::from(3));
// (x_q x_p)⋅((x_q x_p)⋅λ (y_qy_p)) = 0
let poly1 = {
let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q x_p)
let y_q_minus_y_p = y_q.clone() - y_p.clone(); // (y_q y_p)
let incomplete = x_q_minus_x_p.clone() * lambda.clone() - y_q_minus_y_p; // (x_q x_p)⋅λ (y_qy_p)
// q_add ⋅(x_q x_p)⋅((x_q x_p)⋅λ (y_qy_p))
x_q_minus_x_p * incomplete
};
// (1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2) = 0
let poly2 = {
let three_x_p_sq = three * x_p.clone().square(); // 3x_p^2
let two_y_p = two * y_p.clone(); // 2y_p
let tangent_line = two_y_p * lambda.clone() - three_x_p_sq; // (2y_p ⋅λ - 3x_p^2)
// q_add ⋅(1 - (x_q - x_p)⋅α)⋅(2y_p ⋅λ - 3x_p^2)
(one.clone() - if_alpha.clone()) * tangent_line
};
// x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r) = 0
let secant_line = lambda.clone().square() - x_p.clone() - x_q.clone() - x_r.clone(); // (λ^2 - x_p - x_q - x_r)
let poly3 = {
let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p)
// x_p⋅x_q⋅(x_q - x_p)⋅(λ^2 - x_p - x_q - x_r)
x_p.clone() * x_q.clone() * x_q_minus_x_p * secant_line.clone()
};
// x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0
let poly4 = {
let x_q_minus_x_p = x_q.clone() - x_p.clone(); // (x_q - x_p)
let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r)
// x_p⋅x_q⋅(x_q - x_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r)
x_p.clone()
* x_q.clone()
* x_q_minus_x_p
* (lambda.clone() * x_p_minus_x_r - y_p.clone() - y_r.clone())
};
// x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r) = 0
let poly5 = {
let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p)
// x_p⋅x_q⋅(y_q + y_p)⋅(λ^2 - x_p - x_q - x_r)
x_p.clone() * x_q.clone() * y_q_plus_y_p * secant_line
};
// x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r) = 0
let poly6 = {
let y_q_plus_y_p = y_q.clone() + y_p.clone(); // (y_q + y_p)
let x_p_minus_x_r = x_p.clone() - x_r.clone(); // (x_p - x_r)
// x_p⋅x_q⋅(y_q + y_p)⋅(λ ⋅(x_p - x_r) - y_p - y_r)
x_p.clone()
* x_q.clone()
* y_q_plus_y_p
* (lambda * x_p_minus_x_r - y_p.clone() - y_r.clone())
};
// (1 - x_p * β) * (x_r - x_q) = 0
let poly7 = (one.clone() - if_beta.clone()) * (x_r.clone() - x_q);
// (1 - x_p * β) * (y_r - y_q) = 0
let poly8 = (one.clone() - if_beta) * (y_r.clone() - y_q);
// (1 - x_q * γ) * (x_r - x_p) = 0
let poly9 = (one.clone() - if_gamma.clone()) * (x_r.clone() - x_p);
// (1 - x_q * γ) * (y_r - y_p) = 0
let poly10 = (one.clone() - if_gamma) * (y_r.clone() - y_p);
// ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * x_r
let poly11 = (one.clone() - if_alpha.clone() - if_delta.clone()) * x_r;
// ((1 - (x_q - x_p) * α - (y_q + y_p) * δ)) * y_r
let poly12 = (one - if_alpha - if_delta) * y_r;
array::IntoIter::new([
poly1, poly2, poly3, poly4, poly5, poly6, poly7, poly8, poly9, poly10, poly11,
poly12,
])
.map(move |poly| q_add.clone() * poly)
});
}
pub(super) fn assign_region(
&self,
p: &EccPoint,
q: &EccPoint,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> {
// Enable `q_add` selector
self.q_add.enable(region, offset)?;
// Copy point `p` into `x_p`, `y_p` columns
p.x.copy_advice(|| "x_p", region, self.x_p, offset)?;
p.y.copy_advice(|| "y_p", region, self.y_p, offset)?;
// Copy point `q` into `x_qr`, `y_qr` columns
q.x.copy_advice(|| "x_q", region, self.x_qr, offset)?;
q.y.copy_advice(|| "y_q", region, self.y_qr, offset)?;
let (x_p, y_p) = (p.x.value(), p.y.value());
let (x_q, y_q) = (q.x.value(), q.y.value());
// [alpha, beta, gamma, delta]
// = [inv0(x_q - x_p), inv0(x_p), inv0(x_q), inv0(y_q + y_p)]
// where inv0(x) = 0 if x = 0, 1/x otherwise.
//
let (alpha, beta, gamma, delta) = {
let inverses = x_p
.zip(x_q)
.zip(y_p)
.zip(y_q)
.map(|(((x_p, x_q), y_p), y_q)| {
let alpha = x_q - x_p;
let beta = x_p;
let gamma = x_q;
let delta = y_q + y_p;
let mut inverses = [alpha, *beta, *gamma, delta];
inverses.batch_invert();
inverses
});
if let Some([alpha, beta, gamma, delta]) = inverses {
(Some(alpha), Some(beta), Some(gamma), Some(delta))
} else {
(None, None, None, None)
}
};
// Assign α = inv0(x_q - x_p)
region.assign_advice(|| "α", self.alpha, offset, || alpha.ok_or(Error::Synthesis))?;
// Assign β = inv0(x_p)
region.assign_advice(|| "β", self.beta, offset, || beta.ok_or(Error::Synthesis))?;
// Assign γ = inv0(x_q)
region.assign_advice(|| "γ", self.gamma, offset, || gamma.ok_or(Error::Synthesis))?;
// Assign δ = inv0(y_q + y_p) if x_q = x_p, 0 otherwise
region.assign_advice(
|| "δ",
self.delta,
offset,
|| {
let x_p = x_p.ok_or(Error::Synthesis)?;
let x_q = x_q.ok_or(Error::Synthesis)?;
if x_q == x_p {
delta.ok_or(Error::Synthesis)
} else {
Ok(pallas::Base::zero())
}
},
)?;
#[allow(clippy::collapsible_else_if)]
// Assign lambda
let lambda =
x_p.zip(y_p)
.zip(x_q)
.zip(y_q)
.zip(alpha)
.map(|((((x_p, y_p), x_q), y_q), alpha)| {
if x_q != x_p {
// λ = (y_q - y_p)/(x_q - x_p)
// Here, alpha = inv0(x_q - x_p), which suffices since we
// know that x_q != x_p in this branch.
(y_q - y_p) * alpha
} else {
if !y_p.is_zero_vartime() {
// 3(x_p)^2
let three_x_p_sq = pallas::Base::from(3) * x_p.square();
// 1 / 2(y_p)
let inv_two_y_p = y_p.invert().unwrap() * pallas::Base::TWO_INV;
// λ = 3(x_p)^2 / 2(y_p)
three_x_p_sq * inv_two_y_p
} else {
pallas::Base::zero()
}
}
});
region.assign_advice(
|| "λ",
self.lambda,
offset,
|| lambda.ok_or(Error::Synthesis),
)?;
// Calculate (x_r, y_r)
let r =
x_p.zip(y_p)
.zip(x_q)
.zip(y_q)
.zip(lambda)
.map(|((((x_p, y_p), x_q), y_q), lambda)| {
{
if x_p.is_zero_vartime() {
// 0 + Q = Q
(*x_q, *y_q)
} else if x_q.is_zero_vartime() {
// P + 0 = P
(*x_p, *y_p)
} else if (x_q == x_p) && (*y_q == -y_p) {
// P + (-P) maps to (0,0)
(pallas::Base::zero(), pallas::Base::zero())
} else {
// x_r = λ^2 - x_p - x_q
let x_r = lambda.square() - x_p - x_q;
// y_r = λ(x_p - x_r) - y_p
let y_r = lambda * (x_p - x_r) - y_p;
(x_r, y_r)
}
}
});
// Assign x_r
let x_r = r.map(|r| r.0);
let x_r_cell = region.assign_advice(
|| "x_r",
self.x_qr,
offset + 1,
|| x_r.ok_or(Error::Synthesis),
)?;
// Assign y_r
let y_r = r.map(|r| r.1);
let y_r_cell = region.assign_advice(
|| "y_r",
self.y_qr,
offset + 1,
|| y_r.ok_or(Error::Synthesis),
)?;
let result = EccPoint {
x: x_r_cell,
y: y_r_cell,
};
#[cfg(test)]
// Check that the correct sum is obtained.
{
use group::Curve;
let p = p.point();
let q = q.point();
let real_sum = p.zip(q).map(|(p, q)| p + q);
let result = result.point();
if let (Some(real_sum), Some(result)) = (real_sum, result) {
assert_eq!(real_sum.to_affine(), result);
}
}
Ok(result)
}
}
#[cfg(test)]
pub mod tests {
use group::{prime::PrimeCurveAffine, Curve};
use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::{arithmetic::CurveExt, pallas};
use crate::circuit::gadget::ecc::{chip::EccPoint, EccInstructions, NonIdentityPoint};
#[allow(clippy::too_many_arguments)]
pub fn test_add<
EccChip: EccInstructions<pallas::Affine, Point = EccPoint> + Clone + Eq + std::fmt::Debug,
>(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
p_val: pallas::Affine,
p: &NonIdentityPoint<pallas::Affine, EccChip>,
q_val: pallas::Affine,
q: &NonIdentityPoint<pallas::Affine, EccChip>,
p_neg: &NonIdentityPoint<pallas::Affine, EccChip>,
) -> Result<(), Error> {
// Make sure P and Q are not the same point.
assert_ne!(p_val, q_val);
// Check complete addition P + (-P)
let zero = {
let result = p.add(layouter.namespace(|| "P + (-P)"), p_neg)?;
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
};
// Check complete addition 𝒪 + 𝒪
{
let result = zero.add(layouter.namespace(|| "𝒪 + 𝒪"), &zero)?;
result.constrain_equal(layouter.namespace(|| "𝒪 + 𝒪 = 𝒪"), &zero)?;
}
// Check P + Q
{
let result = p.add(layouter.namespace(|| "P + Q"), q)?;
let witnessed_result = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "witnessed P + Q"),
Some((p_val + q_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?;
}
// P + P
{
let result = p.add(layouter.namespace(|| "P + P"), p)?;
let witnessed_result = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "witnessed P + P"),
Some((p_val + p_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain P + P"), &witnessed_result)?;
}
// P + 𝒪
{
let result = p.add(layouter.namespace(|| "P + 𝒪"), &zero)?;
result.constrain_equal(layouter.namespace(|| "P + 𝒪 = P"), p)?;
}
// 𝒪 + P
{
let result = zero.add(layouter.namespace(|| "𝒪 + P"), p)?;
result.constrain_equal(layouter.namespace(|| "𝒪 + P = P"), p)?;
}
// (x, y) + (ζx, y) should behave like normal P + Q.
let endo_p = p_val.to_curve().endo();
let endo_p = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "endo(P)"),
Some(endo_p.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?;
// (x, y) + (ζx, -y) should also behave like normal P + Q.
let endo_p_neg = (-p_val).to_curve().endo();
let endo_p_neg = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "endo(-P)"),
Some(endo_p_neg.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo(-P)"), &endo_p_neg)?;
// (x, y) + ((ζ^2)x, y)
let endo_2_p = p_val.to_curve().endo().endo();
let endo_2_p = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "endo^2(P)"),
Some(endo_2_p.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo^2(P)"), &endo_2_p)?;
// (x, y) + ((ζ^2)x, -y)
let endo_2_p_neg = (-p_val).to_curve().endo().endo();
let endo_2_p_neg = NonIdentityPoint::new(
chip,
layouter.namespace(|| "endo^2(-P)"),
Some(endo_2_p_neg.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo^2(-P)"), &endo_2_p_neg)?;
Ok(())
}
}

View File

@ -1,204 +0,0 @@
use std::{array, collections::HashSet};
use super::NonIdentityEccPoint;
use ff::Field;
use group::Curve;
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
q_add_incomplete: Selector,
// x-coordinate of P in P + Q = R
pub x_p: Column<Advice>,
// y-coordinate of P in P + Q = R
pub y_p: Column<Advice>,
// x-coordinate of Q or R in P + Q = R
pub x_qr: Column<Advice>,
// y-coordinate of Q or R in P + Q = R
pub y_qr: Column<Advice>,
}
impl Config {
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
x_p: Column<Advice>,
y_p: Column<Advice>,
x_qr: Column<Advice>,
y_qr: Column<Advice>,
) -> Self {
meta.enable_equality(x_p);
meta.enable_equality(y_p);
meta.enable_equality(x_qr);
meta.enable_equality(y_qr);
let config = Self {
q_add_incomplete: meta.selector(),
x_p,
y_p,
x_qr,
y_qr,
};
config.create_gate(meta);
config
}
pub(crate) fn advice_columns(&self) -> HashSet<Column<Advice>> {
core::array::IntoIter::new([self.x_p, self.y_p, self.x_qr, self.y_qr]).collect()
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("incomplete addition gates", |meta| {
let q_add_incomplete = meta.query_selector(self.q_add_incomplete);
let x_p = meta.query_advice(self.x_p, Rotation::cur());
let y_p = meta.query_advice(self.y_p, Rotation::cur());
let x_q = meta.query_advice(self.x_qr, Rotation::cur());
let y_q = meta.query_advice(self.y_qr, Rotation::cur());
let x_r = meta.query_advice(self.x_qr, Rotation::next());
let y_r = meta.query_advice(self.y_qr, Rotation::next());
// (x_r + x_q + x_p)⋅(x_p x_q)^2 (y_p y_q)^2 = 0
let poly1 = {
(x_r.clone() + x_q.clone() + x_p.clone())
* (x_p.clone() - x_q.clone())
* (x_p.clone() - x_q.clone())
- (y_p.clone() - y_q.clone()).square()
};
// (y_r + y_q)(x_p x_q) (y_p y_q)(x_q x_r) = 0
let poly2 = (y_r + y_q.clone()) * (x_p - x_q.clone()) - (y_p - y_q) * (x_q - x_r);
array::IntoIter::new([("x_r", poly1), ("y_r", poly2)])
.map(move |(name, poly)| (name, q_add_incomplete.clone() * poly))
});
}
pub(super) fn assign_region(
&self,
p: &NonIdentityEccPoint,
q: &NonIdentityEccPoint,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<NonIdentityEccPoint, Error> {
// Enable `q_add_incomplete` selector
self.q_add_incomplete.enable(region, offset)?;
// Handle exceptional cases
let (x_p, y_p) = (p.x.value(), p.y.value());
let (x_q, y_q) = (q.x.value(), q.y.value());
x_p.zip(y_p)
.zip(x_q)
.zip(y_q)
.map(|(((x_p, y_p), x_q), y_q)| {
// P is point at infinity
if (x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_q.is_zero_vartime() && y_q.is_zero_vartime())
// x_p = x_q
|| (x_p == x_q)
{
Err(Error::Synthesis)
} else {
Ok(())
}
})
.transpose()?;
// Copy point `p` into `x_p`, `y_p` columns
p.x.copy_advice(|| "x_p", region, self.x_p, offset)?;
p.y.copy_advice(|| "y_p", region, self.y_p, offset)?;
// Copy point `q` into `x_qr`, `y_qr` columns
q.x.copy_advice(|| "x_q", region, self.x_qr, offset)?;
q.y.copy_advice(|| "y_q", region, self.y_qr, offset)?;
// Compute the sum `P + Q = R`
let r = {
let p = p.point();
let q = q.point();
let r = p
.zip(q)
.map(|(p, q)| (p + q).to_affine().coordinates().unwrap());
let r_x = r.map(|r| *r.x());
let r_y = r.map(|r| *r.y());
(r_x, r_y)
};
// Assign the sum to `x_qr`, `y_qr` columns in the next row
let x_r = r.0;
let x_r_var = region.assign_advice(
|| "x_r",
self.x_qr,
offset + 1,
|| x_r.ok_or(Error::Synthesis),
)?;
let y_r = r.1;
let y_r_var = region.assign_advice(
|| "y_r",
self.y_qr,
offset + 1,
|| y_r.ok_or(Error::Synthesis),
)?;
let result = NonIdentityEccPoint {
x: x_r_var,
y: y_r_var,
};
Ok(result)
}
}
#[cfg(test)]
pub mod tests {
use group::Curve;
use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::pallas;
use crate::circuit::gadget::ecc::{EccInstructions, NonIdentityPoint};
#[allow(clippy::too_many_arguments)]
pub fn test_add_incomplete<
EccChip: EccInstructions<pallas::Affine> + Clone + Eq + std::fmt::Debug,
>(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
p_val: pallas::Affine,
p: &NonIdentityPoint<pallas::Affine, EccChip>,
q_val: pallas::Affine,
q: &NonIdentityPoint<pallas::Affine, EccChip>,
p_neg: &NonIdentityPoint<pallas::Affine, EccChip>,
test_errors: bool,
) -> Result<(), Error> {
// P + Q
{
let result = p.add_incomplete(layouter.namespace(|| "P + Q"), q)?;
let witnessed_result = NonIdentityPoint::new(
chip,
layouter.namespace(|| "witnessed P + Q"),
Some((p_val + q_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?;
}
if test_errors {
// P + P should return an error
p.add_incomplete(layouter.namespace(|| "P + P"), p)
.expect_err("P + P should return an error");
// P + (-P) should return an error
p.add_incomplete(layouter.namespace(|| "P + (-P)"), p_neg)
.expect_err("P + (-P) should return an error");
}
Ok(())
}
}

View File

@ -1,274 +0,0 @@
use arrayvec::ArrayVec;
use group::{
ff::{Field, PrimeField},
Curve,
};
use halo2::arithmetic::lagrange_interpolate;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
/// Window size for fixed-base scalar multiplication
pub const FIXED_BASE_WINDOW_SIZE: usize = 3;
/// $2^{`FIXED_BASE_WINDOW_SIZE`}$
pub const H: usize = 1 << FIXED_BASE_WINDOW_SIZE;
/// Number of windows for a full-width scalar
pub const NUM_WINDOWS: usize =
(pallas::Scalar::NUM_BITS as usize + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
/// Number of windows for a short signed scalar
pub const NUM_WINDOWS_SHORT: usize =
(L_SCALAR_SHORT + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
/// $\ell_\mathsf{value}$
/// Number of bits in an unsigned short scalar.
pub(crate) const L_SCALAR_SHORT: usize = 64;
/// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$.
/// <https://github.com/zcash/pasta>
pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113;
/// The Pallas base field modulus is $p = 2^{254} + \mathsf{t_p}$.
/// <https://github.com/zcash/pasta>
pub(crate) const T_P: u128 = 45560315531419706090280762371685220353;
/// For each fixed base, we calculate its scalar multiples in three-bit windows.
/// Each window will have $2^3 = 8$ points. The tables are computed as described in
/// [the Orchard book](https://zcash.github.io/orchard/design/circuit/gadgets/ecc/fixed-base-scalar-mul.html#load-fixed-base).
fn compute_window_table<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C; H]> {
let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows);
// Generate window table entries for all windows but the last.
// For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B.
// Here, w ranges from [0..`num_windows - 1`)
for w in 0..(num_windows - 1) {
window_table.push(
(0..H)
.map(|k| {
// scalar = (k+2)*(8^w)
let scalar = C::Scalar::from(k as u64 + 2)
* C::Scalar::from(H as u64).pow(&[w as u64, 0, 0, 0]);
(base * scalar).to_affine()
})
.collect::<ArrayVec<C, H>>()
.into_inner()
.unwrap(),
);
}
// Generate window table entries for the last window, w = `num_windows - 1`.
// For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined
// as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1}
let sum = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, j| {
acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, 0, 0, 0])
});
window_table.push(
(0..H)
.map(|k| {
// scalar = k * (2^3)^w - sum, where w = `num_windows - 1`
let scalar = C::Scalar::from(k as u64)
* C::Scalar::from(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
- sum;
(base * scalar).to_affine()
})
.collect::<ArrayVec<C, H>>()
.into_inner()
.unwrap(),
);
window_table
}
/// For each window, we interpolate the $x$-coordinate.
/// Here, we pre-compute and store the coefficients of the interpolation polynomial.
pub fn compute_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C::Base; H]> {
// We are interpolating over the 3-bit window, k \in [0..8)
let points: Vec<_> = (0..H).map(|i| C::Base::from(i as u64)).collect();
let window_table = compute_window_table(base, num_windows);
window_table
.iter()
.map(|window_points| {
let x_window_points: Vec<_> = window_points
.iter()
.map(|point| *point.coordinates().unwrap().x())
.collect();
lagrange_interpolate(&points, &x_window_points)
.into_iter()
.collect::<ArrayVec<C::Base, H>>()
.into_inner()
.unwrap()
})
.collect()
}
/// For each window, $z$ is a field element such that for each point $(x, y)$ in the window:
/// - $z + y = u^2$ (some square in the field); and
/// - $z - y$ is not a square.
/// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window.
///
/// This function was used to generate the `z`s and `u`s for the Orchard fixed
/// bases. The outputs of this function have been stored as constants, and it
/// is not called anywhere in this codebase. However, we keep this function here
/// as a utility for those who wish to use it with different parameters.
pub fn find_zs_and_us<C: CurveAffine>(
base: C,
num_windows: usize,
) -> Option<Vec<(u64, [C::Base; H])>> {
// Closure to find z and u's for one window
let find_z_and_us = |window_points: &[C]| {
assert_eq!(H, window_points.len());
let ys: Vec<_> = window_points
.iter()
.map(|point| *point.coordinates().unwrap().y())
.collect();
(0..(1000 * (1 << (2 * H)))).find_map(|z| {
ys.iter()
.map(|&y| {
if (-y + C::Base::from(z)).sqrt().is_none().into() {
(y + C::Base::from(z)).sqrt().into()
} else {
None
}
})
.collect::<Option<ArrayVec<C::Base, H>>>()
.map(|us| (z, us.into_inner().unwrap()))
})
};
let window_table = compute_window_table(base, num_windows);
window_table
.iter()
.map(|window_points| find_z_and_us(window_points))
.collect()
}
// Test that the z-values and u-values satisfy the conditions:
// 1. z + y = u^2,
// 2. z - y is not a square
// for the y-coordinate of each fixed-base multiple in each window.
pub fn test_zs_and_us<C: CurveAffine>(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) {
let window_table = compute_window_table(base, num_windows);
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
for (u, point) in u.iter().zip(window_points.iter()) {
let y = *point.coordinates().unwrap().y();
let u = C::Base::from_bytes(u).unwrap();
assert_eq!(C::Base::from_u64(*z) + y, u * u); // allow either square root
assert!(bool::from((C::Base::from_u64(*z) - y).sqrt().is_none()));
}
}
}
// Test that Lagrange interpolation coefficients reproduce the correct x-coordinate
// for each fixed-base multiple in each window.
pub fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
/// Evaluate y = f(x) given the coefficients of f(x)
fn evaluate<C: CurveAffine>(x: u8, coeffs: &[C::Base]) -> C::Base {
let x = C::Base::from(x as u64);
coeffs
.iter()
.rev()
.cloned()
.reduce(|acc, coeff| acc * x + coeff)
.unwrap_or_else(C::Base::zero)
}
let lagrange_coeffs = compute_lagrange_coeffs(base, num_windows);
// Check first 84 windows, i.e. `k_0, k_1, ..., k_83`
for (idx, coeffs) in lagrange_coeffs[0..(num_windows - 1)].iter().enumerate() {
// Test each three-bit chunk in this window.
for bits in 0..(H as u8) {
{
// Interpolate the x-coordinate using this window's coefficients
let interpolated_x = evaluate::<C>(bits, coeffs);
// Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B.
let point = base
* C::Scalar::from(bits as u64 + 2)
* C::Scalar::from(H as u64).pow(&[idx as u64, 0, 0, 0]);
let x = *point.to_affine().coordinates().unwrap().x();
// Check that the interpolated x-coordinate matches the actual one.
assert_eq!(x, interpolated_x);
}
}
}
// Check last window.
for bits in 0..(H as u8) {
// Interpolate the x-coordinate using the last window's coefficients
let interpolated_x = evaluate::<C>(bits, &lagrange_coeffs[num_windows - 1]);
// Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B,
// where offset = \sum_{j = 0}^{83} 2^{3j+1}
let offset = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, w| {
acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0])
});
let scalar = C::Scalar::from(bits as u64)
* C::Scalar::from(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
- offset;
let point = base * scalar;
let x = *point.to_affine().coordinates().unwrap().x();
// Check that the interpolated x-coordinate matches the actual one.
assert_eq!(x, interpolated_x);
}
}
#[cfg(test)]
mod tests {
use group::{ff::Field, Curve, Group};
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use proptest::prelude::*;
use super::{compute_window_table, find_zs_and_us, test_lagrange_coeffs, H, NUM_WINDOWS};
prop_compose! {
/// Generate an arbitrary Pallas point.
pub fn arb_point()(bytes in prop::array::uniform32(0u8..)) -> pallas::Point {
// Instead of rejecting out-of-range bytes, let's reduce them.
let mut buf = [0; 64];
buf[..32].copy_from_slice(&bytes);
let scalar = pallas::Scalar::from_bytes_wide(&buf);
pallas::Point::generator() * scalar
}
}
proptest! {
#[test]
fn lagrange_coeffs(
base in arb_point(),
) {
test_lagrange_coeffs(base.to_affine(), NUM_WINDOWS);
}
}
#[test]
fn zs_and_us() {
let base = pallas::Point::random(rand::rngs::OsRng);
let (z, u): (Vec<u64>, Vec<[pallas::Base; H]>) =
find_zs_and_us(base.to_affine(), NUM_WINDOWS)
.unwrap()
.into_iter()
.unzip();
let window_table = compute_window_table(base.to_affine(), NUM_WINDOWS);
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
for (u, point) in u.iter().zip(window_points.iter()) {
let y = *point.coordinates().unwrap().y();
assert_eq!(pallas::Base::from(*z) + y, u * u); // allow either square root
assert!(bool::from((pallas::Base::from(*z) - y).sqrt().is_none()));
}
}
}
}

View File

@ -1,566 +0,0 @@
use super::{add, EccPoint, NonIdentityEccPoint, T_Q};
use crate::{
circuit::gadget::utilities::{bool_check, lookup_range_check::LookupRangeCheckConfig, ternary},
primitives::sinsemilla,
};
use std::{
convert::TryInto,
ops::{Deref, Range},
};
use bigint::U256;
use ff::PrimeField;
use halo2::{
arithmetic::FieldExt,
circuit::{AssignedCell, Layouter, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
mod complete;
mod incomplete;
mod overflow;
/// Number of bits for which complete addition needs to be used in variable-base
/// scalar multiplication
const NUM_COMPLETE_BITS: usize = 3;
// Bits used in incomplete addition. k_{254} to k_{4} inclusive
const INCOMPLETE_LEN: usize = pallas::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS;
const INCOMPLETE_RANGE: Range<usize> = 0..INCOMPLETE_LEN;
// Bits k_{254} to k_{4} inclusive are used in incomplete addition.
// The `hi` half is k_{254} to k_{130} inclusive (length 125 bits).
// (It is a coincidence that k_{130} matches the boundary of the
// overflow check described in [the book](https://zcash.github.io/halo2/design/gadgets/ecc/var-base-scalar-mul.html#overflow-check).)
const INCOMPLETE_HI_RANGE: Range<usize> = 0..(INCOMPLETE_LEN / 2);
const INCOMPLETE_HI_LEN: usize = INCOMPLETE_LEN / 2;
// Bits k_{254} to k_{4} inclusive are used in incomplete addition.
// The `lo` half is k_{129} to k_{4} inclusive (length 126 bits).
const INCOMPLETE_LO_RANGE: Range<usize> = INCOMPLETE_HI_LEN..INCOMPLETE_LEN;
const INCOMPLETE_LO_LEN: usize = INCOMPLETE_LEN - INCOMPLETE_HI_LEN;
// Bits k_{3} to k_{1} inclusive are used in complete addition.
// Bit k_{0} is handled separately.
const COMPLETE_RANGE: Range<usize> = INCOMPLETE_LEN..(INCOMPLETE_LEN + NUM_COMPLETE_BITS);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Config {
// Selector used to check switching logic on LSB
q_mul_lsb: Selector,
// Configuration used in complete addition
add_config: add::Config,
// Configuration used for `hi` bits of the scalar
hi_config: incomplete::Config<INCOMPLETE_HI_LEN>,
// Configuration used for `lo` bits of the scalar
lo_config: incomplete::Config<INCOMPLETE_LO_LEN>,
// Configuration used for complete addition part of double-and-add algorithm
complete_config: complete::Config,
// Configuration used to check for overflow
overflow_config: overflow::Config,
}
impl Config {
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
add_config: add::Config,
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
advices: [Column<Advice>; 10],
) -> Self {
let hi_config = incomplete::Config::configure(
meta, advices[9], advices[3], advices[0], advices[1], advices[4], advices[5],
);
let lo_config = incomplete::Config::configure(
meta, advices[6], advices[7], advices[0], advices[1], advices[8], advices[2],
);
let complete_config = complete::Config::configure(meta, advices[9], add_config);
let overflow_config =
overflow::Config::configure(meta, lookup_config, advices[6..9].try_into().unwrap());
let config = Self {
q_mul_lsb: meta.selector(),
add_config,
hi_config,
lo_config,
complete_config,
overflow_config,
};
config.create_gate(meta);
assert_eq!(
config.hi_config.x_p, config.lo_config.x_p,
"x_p is shared across hi and lo halves."
);
assert_eq!(
config.hi_config.y_p, config.lo_config.y_p,
"y_p is shared across hi and lo halves."
);
// For both hi_config and lo_config:
// z and lambda1 are assigned on the same row as the add_config output.
// Therefore, z and lambda1 must not overlap with add_config.x_qr, add_config.y_qr.
let add_config_outputs = config.add_config.output_columns();
{
assert!(
!add_config_outputs.contains(&config.hi_config.z),
"incomplete config z cannot overlap with complete addition columns."
);
assert!(
!add_config_outputs.contains(&config.hi_config.lambda1),
"incomplete config lambda1 cannot overlap with complete addition columns."
);
}
{
assert!(
!add_config_outputs.contains(&config.lo_config.z),
"incomplete config z cannot overlap with complete addition columns."
);
assert!(
!add_config_outputs.contains(&config.lo_config.lambda1),
"incomplete config lambda1 cannot overlap with complete addition columns."
);
}
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// If `lsb` is 0, (x, y) = (x_p, -y_p). If `lsb` is 1, (x, y) = (0,0).
meta.create_gate("LSB check", |meta| {
let q_mul_lsb = meta.query_selector(self.q_mul_lsb);
let z_1 = meta.query_advice(self.complete_config.z_complete, Rotation::cur());
let z_0 = meta.query_advice(self.complete_config.z_complete, Rotation::next());
let x_p = meta.query_advice(self.add_config.x_p, Rotation::cur());
let y_p = meta.query_advice(self.add_config.y_p, Rotation::cur());
let base_x = meta.query_advice(self.add_config.x_p, Rotation::next());
let base_y = meta.query_advice(self.add_config.y_p, Rotation::next());
// z_0 = 2 * z_1 + k_0
// => k_0 = z_0 - 2 * z_1
let lsb = z_0 - z_1 * pallas::Base::from(2);
let bool_check = bool_check(lsb.clone());
// `lsb` = 0 => (x_p, y_p) = (x, -y)
// `lsb` = 1 => (x_p, y_p) = (0,0)
let lsb_x = ternary(lsb.clone(), x_p.clone(), x_p - base_x);
let lsb_y = ternary(lsb, y_p.clone(), y_p + base_y);
std::array::IntoIter::new([
("bool_check", bool_check),
("lsb_x", lsb_x),
("lsb_y", lsb_y),
])
.map(move |(name, poly)| (name, q_mul_lsb.clone() * poly))
});
}
pub(super) fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
alpha: AssignedCell<pallas::Base, pallas::Base>,
base: &NonIdentityEccPoint,
) -> Result<(EccPoint, AssignedCell<pallas::Base, pallas::Base>), Error> {
let (result, zs): (EccPoint, Vec<Z<pallas::Base>>) = layouter.assign_region(
|| "variable-base scalar mul",
|mut region| {
let offset = 0;
// Case `base` into an `EccPoint` for later use.
let base_point: EccPoint = base.clone().into();
// Decompose `k = alpha + t_q` bitwise (big-endian bit order).
let bits = decompose_for_scalar_mul(alpha.value());
// Define ranges for each part of the algorithm.
let bits_incomplete_hi = &bits[INCOMPLETE_HI_RANGE];
let bits_incomplete_lo = &bits[INCOMPLETE_LO_RANGE];
let lsb = bits[pallas::Scalar::NUM_BITS as usize - 1];
// Initialize the accumulator `acc = [2]base`
let acc =
self.add_config
.assign_region(&base_point, &base_point, offset, &mut region)?;
// Increase the offset by 1 after complete addition.
let offset = offset + 1;
// Initialize the running sum for scalar decomposition to zero
let z_init = Z(region.assign_advice_from_constant(
|| "z_init = 0",
self.hi_config.z,
offset,
pallas::Base::zero(),
)?);
// Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition
let (x_a, y_a, zs_incomplete_hi) = self.hi_config.double_and_add(
&mut region,
offset,
base,
bits_incomplete_hi,
(X(acc.x), Y(acc.y), z_init.clone()),
)?;
// Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition
let z = zs_incomplete_hi.last().expect("should not be empty");
let (x_a, y_a, zs_incomplete_lo) = self.lo_config.double_and_add(
&mut region,
offset,
base,
bits_incomplete_lo,
(x_a, y_a, z.clone()),
)?;
// Move from incomplete addition to complete addition.
// Inside incomplete::double_and_add, the offset was increased once after initialization
// of the running sum.
// Then, the final assignment of double-and-add was made on row + offset + 1.
// Outside of incomplete addition, we must account for these offset increases by adding
// 2 to the incomplete addition length.
let offset = offset + INCOMPLETE_LO_RANGE.len() + 2;
// Complete addition
let (acc, zs_complete) = {
let z = zs_incomplete_lo.last().expect("should not be empty");
// Bits used in complete addition. k_{3} to k_{1} inclusive
// The LSB k_{0} is handled separately.
let bits_complete = &bits[COMPLETE_RANGE];
self.complete_config.assign_region(
&mut region,
offset,
bits_complete,
&base_point,
x_a,
y_a,
z.clone(),
)?
};
// Each iteration of the complete addition uses two rows.
let offset = offset + COMPLETE_RANGE.len() * 2;
// Process the least significant bit
let z_1 = zs_complete.last().unwrap().clone();
let (result, z_0) = self.process_lsb(&mut region, offset, base, acc, z_1, lsb)?;
#[cfg(test)]
// Check that the correct multiple is obtained.
{
use group::Curve;
let base = base.point();
let alpha = alpha
.value()
.map(|alpha| pallas::Scalar::from_repr(alpha.to_repr()).unwrap());
let real_mul = base.zip(alpha).map(|(base, alpha)| base * alpha);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
}
let zs = {
let mut zs = std::iter::empty()
.chain(Some(z_init))
.chain(zs_incomplete_hi.into_iter())
.chain(zs_incomplete_lo.into_iter())
.chain(zs_complete.into_iter())
.chain(Some(z_0))
.collect::<Vec<_>>();
assert_eq!(zs.len(), pallas::Scalar::NUM_BITS as usize + 1);
// This reverses zs to give us [z_0, z_1, ..., z_{254}, z_{255}].
zs.reverse();
zs
};
Ok((result, zs))
},
)?;
self.overflow_config.overflow_check(
layouter.namespace(|| "overflow check"),
alpha.clone(),
&zs,
)?;
Ok((result, alpha))
}
/// Processes the final scalar bit `k_0`.
///
/// Assumptions for this sub-region:
/// - `acc_x` and `acc_y` are assigned in row `offset` by the previous complete
/// addition. They will be copied into themselves.
/// - `z_1 is assigned in row `offset` by the mul::complete region assignment. We only
/// use its value here.
///
/// `x_p` and `y_p` are assigned here, and then copied into themselves by the complete
/// addition subregion.
///
/// ```text
/// | x_p | y_p | acc_x | acc_y | complete addition | z_1 | q_mul_lsb = 1
/// |base_x|base_y| res_x | res_y | | | | | | z_0 |
/// ```
fn process_lsb(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &NonIdentityEccPoint,
acc: EccPoint,
z_1: Z<pallas::Base>,
lsb: Option<bool>,
) -> Result<(EccPoint, Z<pallas::Base>), Error> {
// Enforce switching logic on LSB using a custom gate
self.q_mul_lsb.enable(region, offset)?;
// z_1 has been assigned at (z_complete, offset).
// Assign z_0 = 2⋅z_1 + k_0
let z_0 = {
let z_0_val = z_1.value().zip(lsb).map(|(z_1, lsb)| {
let lsb = pallas::Base::from(lsb as u64);
z_1 * pallas::Base::from(2) + lsb
});
let z_0_cell = region.assign_advice(
|| "z_0",
self.complete_config.z_complete,
offset + 1,
|| z_0_val.ok_or(Error::Synthesis),
)?;
Z(z_0_cell)
};
// Copy in `base_x`, `base_y` to use in the LSB gate
base.x()
.copy_advice(|| "copy base_x", region, self.add_config.x_p, offset + 1)?;
base.y()
.copy_advice(|| "copy base_y", region, self.add_config.y_p, offset + 1)?;
// If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`.
let x = if let Some(lsb) = lsb {
if !lsb {
base.x.value().cloned()
} else {
Some(pallas::Base::zero())
}
} else {
None
};
let y = if let Some(lsb) = lsb {
if !lsb {
base.y.value().map(|y_p| -y_p)
} else {
Some(pallas::Base::zero())
}
} else {
None
};
let x_cell = region.assign_advice(
|| "x",
self.add_config.x_p,
offset,
|| x.ok_or(Error::Synthesis),
)?;
let y_cell = region.assign_advice(
|| "y",
self.add_config.y_p,
offset,
|| y.ok_or(Error::Synthesis),
)?;
let p = EccPoint {
x: x_cell,
y: y_cell,
};
// Return the result of the final complete addition as `[scalar]B`
let result = self.add_config.assign_region(&p, &acc, offset, region)?;
Ok((result, z_0))
}
}
#[derive(Clone, Debug)]
// `x`-coordinate of the accumulator.
struct X<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> Deref for X<F> {
type Target = AssignedCell<F, F>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
// `y`-coordinate of the accumulator.
struct Y<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> Deref for Y<F> {
type Target = AssignedCell<F, F>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
// Cumulative sum `z` used to decompose the scalar.
struct Z<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> Deref for Z<F> {
type Target = AssignedCell<F, F>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn decompose_for_scalar_mul(scalar: Option<&pallas::Base>) -> Vec<Option<bool>> {
let bitstring = scalar.map(|scalar| {
// We use `k = scalar + t_q` in the double-and-add algorithm, where
// the scalar field `F_q = 2^254 + t_q`.
// Note that the addition `scalar + t_q` is not reduced.
//
let scalar = U256::from_little_endian(&scalar.to_repr());
let t_q = U256::from_little_endian(&T_Q.to_le_bytes());
let k = scalar + t_q;
// Big-endian bit representation of `k`.
let bitstring: Vec<bool> = {
let mut le_bytes = [0u8; 32];
k.to_little_endian(&mut le_bytes);
le_bytes.iter().fold(Vec::new(), |mut bitstring, byte| {
let bits = (0..8)
.map(|shift| (byte >> shift) % 2 == 1)
.collect::<Vec<_>>();
bitstring.extend_from_slice(&bits);
bitstring
})
};
// Take the first 255 bits.
let mut bitstring = bitstring[0..pallas::Scalar::NUM_BITS as usize].to_vec();
bitstring.reverse();
bitstring
});
if let Some(bitstring) = bitstring {
bitstring.into_iter().map(Some).collect()
} else {
vec![None; pallas::Scalar::NUM_BITS as usize]
}
}
#[cfg(test)]
pub mod tests {
use group::{
ff::{Field, PrimeField},
Curve,
};
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
};
use pasta_curves::pallas;
use rand::rngs::OsRng;
use crate::circuit::gadget::{
ecc::{
chip::{EccChip, EccPoint},
EccInstructions, NonIdentityPoint, Point,
},
utilities::UtilitiesInstructions,
};
use crate::constants::OrchardFixedBases;
pub fn test_mul(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
p: &NonIdentityPoint<pallas::Affine, EccChip<OrchardFixedBases>>,
p_val: pallas::Affine,
) -> Result<(), Error> {
let column = chip.config().advices[0];
fn constrain_equal_non_id<
EccChip: EccInstructions<pallas::Affine, Point = EccPoint> + Clone + Eq + std::fmt::Debug,
>(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
base_val: pallas::Affine,
scalar_val: pallas::Base,
result: Point<pallas::Affine, EccChip>,
) -> Result<(), Error> {
// Move scalar from base field into scalar field (which always fits
// for Pallas).
let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap();
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
// [a]B
{
let scalar_val = pallas::Base::random(OsRng);
let (result, _) = {
let scalar = chip.load_private(
layouter.namespace(|| "random scalar"),
column,
Some(scalar_val),
)?;
p.mul(layouter.namespace(|| "random [a]B"), &scalar)?
};
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "random [a]B"),
p_val,
scalar_val,
result,
)?;
}
// [0]B should return (0,0) since variable-base scalar multiplication
// uses complete addition for the final bits of the scalar.
{
let scalar_val = pallas::Base::zero();
let (result, _) = {
let scalar =
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_val))?;
p.mul(layouter.namespace(|| "[0]B"), &scalar)?
};
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
// [-1]B (the largest possible base field element)
{
let scalar_val = -pallas::Base::one();
let (result, _) = {
let scalar =
chip.load_private(layouter.namespace(|| "-1"), column, Some(scalar_val))?;
p.mul(layouter.namespace(|| "[-1]B"), &scalar)?
};
constrain_equal_non_id(
chip,
layouter.namespace(|| "[-1]B"),
p_val,
scalar_val,
result,
)?;
}
Ok(())
}
}

View File

@ -1,195 +0,0 @@
use super::super::{add, EccPoint};
use super::{COMPLETE_RANGE, X, Y, Z};
use crate::circuit::gadget::utilities::{bool_check, ternary};
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Config {
// Selector used to constrain the cells used in complete addition.
q_mul_decompose_var: Selector,
// Advice column used to decompose scalar in complete addition.
pub z_complete: Column<Advice>,
// Configuration used in complete addition
add_config: add::Config,
}
impl Config {
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
z_complete: Column<Advice>,
add_config: add::Config,
) -> Self {
meta.enable_equality(z_complete);
let config = Self {
q_mul_decompose_var: meta.selector(),
z_complete,
add_config,
};
config.create_gate(meta);
config
}
/// Gate used to check scalar decomposition is correct.
/// This is used to check the bits used in complete addition, since the incomplete
/// addition gate (controlled by `q_mul`) already checks scalar decomposition for
/// the other bits.
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// | y_p | z_complete |
// --------------------
// | y_p | z_{i + 1} |
// | | base_y |
// | | z_i |
meta.create_gate(
"Decompose scalar for complete bits of variable-base mul",
|meta| {
let q_mul_decompose_var = meta.query_selector(self.q_mul_decompose_var);
// z_{i + 1}
let z_prev = meta.query_advice(self.z_complete, Rotation::prev());
// z_i
let z_next = meta.query_advice(self.z_complete, Rotation::next());
// k_{i} = z_{i} - 2⋅z_{i+1}
let k = z_next - Expression::Constant(pallas::Base::from(2)) * z_prev;
// (k_i) ⋅ (1 - k_i) = 0
let bool_check = bool_check(k.clone());
// base_y
let base_y = meta.query_advice(self.z_complete, Rotation::cur());
// y_p
let y_p = meta.query_advice(self.add_config.y_p, Rotation::prev());
// k_i = 0 => y_p = -base_y
// k_i = 1 => y_p = base_y
let y_switch = ternary(k, base_y.clone() - y_p.clone(), base_y + y_p);
std::array::IntoIter::new([("bool_check", bool_check), ("y_switch", y_switch)])
.map(move |(name, poly)| (name, q_mul_decompose_var.clone() * poly))
},
);
}
#[allow(clippy::type_complexity)]
#[allow(non_snake_case)]
#[allow(clippy::too_many_arguments)]
pub(super) fn assign_region(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
bits: &[Option<bool>],
base: &EccPoint,
x_a: X<pallas::Base>,
y_a: Y<pallas::Base>,
z: Z<pallas::Base>,
) -> Result<(EccPoint, Vec<Z<pallas::Base>>), Error> {
// Make sure we have the correct number of bits for the complete addition
// part of variable-base scalar mul.
assert_eq!(bits.len(), COMPLETE_RANGE.len());
// Enable selectors for complete range
for row in 0..COMPLETE_RANGE.len() {
// Each iteration uses 2 rows (two complete additions)
let row = 2 * row;
// Check scalar decomposition for each iteration. Since the gate enabled by
// `q_mul_decompose_var` queries the previous row, we enable the selector on
// `row + offset + 1` (instead of `row + offset`).
self.q_mul_decompose_var.enable(region, row + offset + 1)?;
}
// Use x_a, y_a output from incomplete addition
let mut acc = EccPoint { x: x_a.0, y: y_a.0 };
// Copy running sum `z` from incomplete addition
let mut z = {
let z = z.copy_advice(
|| "Copy `z` running sum from incomplete addition",
region,
self.z_complete,
offset,
)?;
Z(z)
};
// Store interstitial running sum `z`s in vector
let mut zs: Vec<Z<pallas::Base>> = Vec::with_capacity(bits.len());
// Complete addition
for (iter, k) in bits.iter().enumerate() {
// Each iteration uses 2 rows (two complete additions)
let row = 2 * iter;
// | x_p | y_p | x_qr | y_qr | z_complete |
// ------------------------------------------
// | U_x | U_y | acc_x | acc_y | z_{i + 1} | row + offset
// |acc_x|acc_y|acc+U_x|acc+U_y| base_y |
// | | | res_x | res_y | z_i |
// Update `z`.
z = {
// z_next = z_cur * 2 + k_next
let z_val = z.value().zip(k.as_ref()).map(|(z_val, k)| {
pallas::Base::from(2) * z_val + pallas::Base::from(*k as u64)
});
let z_cell = region.assign_advice(
|| "z",
self.z_complete,
row + offset + 2,
|| z_val.ok_or(Error::Synthesis),
)?;
Z(z_cell)
};
zs.push(z.clone());
// Assign `y_p` for complete addition.
let y_p = {
let base_y = base.y.copy_advice(
|| "Copy `base.y`",
region,
self.z_complete,
row + offset + 1,
)?;
// If the bit is set, use `y`; if the bit is not set, use `-y`
let y_p =
base_y
.value()
.cloned()
.zip(k.as_ref())
.map(|(base_y, k)| if !k { -base_y } else { base_y });
region.assign_advice(
|| "y_p",
self.add_config.y_p,
row + offset,
|| y_p.ok_or(Error::Synthesis),
)?
};
// U = P if the bit is set; U = -P is the bit is not set.
let U = EccPoint {
x: base.x.clone(),
y: y_p,
};
// Acc + U
let tmp_acc = self
.add_config
.assign_region(&U, &acc, row + offset, region)?;
// Acc + U + Acc
acc = self
.add_config
.assign_region(&acc, &tmp_acc, row + offset + 1, region)?;
}
Ok((acc, zs))
}
}

View File

@ -1,343 +0,0 @@
use super::super::NonIdentityEccPoint;
use super::{X, Y, Z};
use crate::circuit::gadget::utilities::bool_check;
use ff::Field;
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, VirtualCells},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Config<const NUM_BITS: usize> {
// Selectors used to constrain the cells used in incomplete addition.
pub(super) q_mul: (Selector, Selector, Selector),
// Cumulative sum used to decompose the scalar.
pub(super) z: Column<Advice>,
// x-coordinate of the accumulator in each double-and-add iteration.
pub(super) x_a: Column<Advice>,
// x-coordinate of the point being added in each double-and-add iteration.
pub(super) x_p: Column<Advice>,
// y-coordinate of the point being added in each double-and-add iteration.
pub(super) y_p: Column<Advice>,
// lambda1 in each double-and-add iteration.
pub(super) lambda1: Column<Advice>,
// lambda2 in each double-and-add iteration.
pub(super) lambda2: Column<Advice>,
}
impl<const NUM_BITS: usize> Config<NUM_BITS> {
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
z: Column<Advice>,
x_a: Column<Advice>,
x_p: Column<Advice>,
y_p: Column<Advice>,
lambda1: Column<Advice>,
lambda2: Column<Advice>,
) -> Self {
meta.enable_equality(z);
meta.enable_equality(lambda1);
let config = Self {
q_mul: (meta.selector(), meta.selector(), meta.selector()),
z,
x_a,
x_p,
y_p,
lambda1,
lambda2,
};
config.create_gate(meta);
config
}
// Gate for incomplete addition part of variable-base scalar multiplication.
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Closure to compute x_{R,i} = λ_{1,i}^2 - x_{A,i} - x_{P,i}
let x_r = |meta: &mut VirtualCells<pallas::Base>, rotation: Rotation| {
let x_a = meta.query_advice(self.x_a, rotation);
let x_p = meta.query_advice(self.x_p, rotation);
let lambda_1 = meta.query_advice(self.lambda1, rotation);
lambda_1.square() - x_a - x_p
};
// Closure to compute y_{A,i} = (λ_{1,i} + λ_{2,i}) * (x_{A,i} - x_{R,i}) / 2
let y_a = |meta: &mut VirtualCells<pallas::Base>, rotation: Rotation| {
let x_a = meta.query_advice(self.x_a, rotation);
let lambda_1 = meta.query_advice(self.lambda1, rotation);
let lambda_2 = meta.query_advice(self.lambda2, rotation);
(lambda_1 + lambda_2) * (x_a - x_r(meta, rotation)) * pallas::Base::TWO_INV
};
// Constraints used for q_mul_{2, 3} == 1
let for_loop = |meta: &mut VirtualCells<pallas::Base>,
q_mul: Expression<pallas::Base>,
y_a_next: Expression<pallas::Base>| {
let one = Expression::Constant(pallas::Base::one());
// z_i
let z_cur = meta.query_advice(self.z, Rotation::cur());
// z_{i+1}
let z_prev = meta.query_advice(self.z, Rotation::prev());
// x_{A,i}
let x_a_cur = meta.query_advice(self.x_a, Rotation::cur());
// x_{A,i-1}
let x_a_next = meta.query_advice(self.x_a, Rotation::next());
// x_{P,i}
let x_p_cur = meta.query_advice(self.x_p, Rotation::cur());
// y_{P,i}
let y_p_cur = meta.query_advice(self.y_p, Rotation::cur());
// λ_{1,i}
let lambda1_cur = meta.query_advice(self.lambda1, Rotation::cur());
// λ_{2,i}
let lambda2_cur = meta.query_advice(self.lambda2, Rotation::cur());
let y_a_cur = y_a(meta, Rotation::cur());
// The current bit in the scalar decomposition, k_i = z_i - 2⋅z_{i+1}.
// Recall that we assigned the cumulative variable `z_i` in descending order,
// i from n down to 0. So z_{i+1} corresponds to the `z_prev` query.
let k = z_cur - z_prev * pallas::Base::from(2);
// Check booleanity of decomposition.
let bool_check = bool_check(k.clone());
// λ_{1,i}⋅(x_{A,i} x_{P,i}) y_{A,i} + (2k_i - 1) y_{P,i} = 0
let gradient_1 = lambda1_cur * (x_a_cur.clone() - x_p_cur) - y_a_cur.clone()
+ (k * pallas::Base::from(2) - one) * y_p_cur;
// λ_{2,i}^2 x_{A,i-1} x_{R,i} x_{A,i} = 0
let secant_line = lambda2_cur.clone().square()
- x_a_next.clone()
- x_r(meta, Rotation::cur())
- x_a_cur.clone();
// λ_{2,i}⋅(x_{A,i} x_{A,i-1}) y_{A,i} y_{A,i-1} = 0
let gradient_2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next;
std::iter::empty()
.chain(Some(("bool_check", q_mul.clone() * bool_check)))
.chain(Some(("gradient_1", q_mul.clone() * gradient_1)))
.chain(Some(("secant_line", q_mul.clone() * secant_line)))
.chain(Some(("gradient_2", q_mul * gradient_2)))
};
// q_mul_1 == 1 checks
meta.create_gate("q_mul_1 == 1 checks", |meta| {
let q_mul_1 = meta.query_selector(self.q_mul.0);
let y_a_next = y_a(meta, Rotation::next());
let y_a_witnessed = meta.query_advice(self.lambda1, Rotation::cur());
vec![("init y_a", q_mul_1 * (y_a_witnessed - y_a_next))]
});
// q_mul_2 == 1 checks
meta.create_gate("q_mul_2 == 1 checks", |meta| {
let q_mul_2 = meta.query_selector(self.q_mul.1);
let y_a_next = y_a(meta, Rotation::next());
// x_{P,i}
let x_p_cur = meta.query_advice(self.x_p, Rotation::cur());
// x_{P,i-1}
let x_p_next = meta.query_advice(self.x_p, Rotation::next());
// y_{P,i}
let y_p_cur = meta.query_advice(self.y_p, Rotation::cur());
// y_{P,i-1}
let y_p_next = meta.query_advice(self.y_p, Rotation::next());
// The base used in double-and-add remains constant. We check that its
// x- and y- coordinates are the same throughout.
let x_p_check = x_p_cur - x_p_next;
let y_p_check = y_p_cur - y_p_next;
std::iter::empty()
.chain(Some(("x_p_check", q_mul_2.clone() * x_p_check)))
.chain(Some(("y_p_check", q_mul_2.clone() * y_p_check)))
.chain(for_loop(meta, q_mul_2, y_a_next))
});
// q_mul_3 == 1 checks
meta.create_gate("q_mul_3 == 1 checks", |meta| {
let q_mul_3 = meta.query_selector(self.q_mul.2);
let y_a_final = meta.query_advice(self.lambda1, Rotation::next());
for_loop(meta, q_mul_3, y_a_final)
});
}
/// We perform incomplete addition on all but the last three bits of the
/// decomposed scalar.
/// We split the bits in the incomplete addition range into "hi" and "lo"
/// halves and process them side by side, using the same rows but with
/// non-overlapping columns. The base is never the identity point even at
/// the boundary between halves.
/// Returns (x, y, z).
#[allow(clippy::type_complexity)]
pub(super) fn double_and_add(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &NonIdentityEccPoint,
bits: &[Option<bool>],
acc: (X<pallas::Base>, Y<pallas::Base>, Z<pallas::Base>),
) -> Result<(X<pallas::Base>, Y<pallas::Base>, Vec<Z<pallas::Base>>), Error> {
// Check that we have the correct number of bits for this double-and-add.
assert_eq!(bits.len(), NUM_BITS);
// Handle exceptional cases
let (x_p, y_p) = (base.x.value().cloned(), base.y.value().cloned());
let (x_a, y_a) = (acc.0.value().cloned(), acc.1.value().cloned());
if let (Some(x_a), Some(y_a), Some(x_p), Some(y_p)) = (x_a, y_a, x_p, y_p) {
// A is point at infinity
if (x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_a.is_zero_vartime() && y_a.is_zero_vartime())
// x_p = x_a
|| (x_p == x_a)
{
return Err(Error::Synthesis);
}
}
// Set q_mul values
{
// q_mul_1 = 1 on offset 0
self.q_mul.0.enable(region, offset)?;
let offset = offset + 1;
// q_mul_2 = 1 on all rows after offset 0, excluding the last row.
for idx in 0..(NUM_BITS - 1) {
self.q_mul.1.enable(region, offset + idx)?;
}
// q_mul_3 = 1 on the last row.
self.q_mul.2.enable(region, offset + NUM_BITS - 1)?;
}
// Initialise double-and-add
let (mut x_a, mut y_a, mut z) = {
// Initialise the running `z` sum for the scalar bits.
let z = acc.2.copy_advice(|| "starting z", region, self.z, offset)?;
// Initialise acc
let x_a = acc
.0
.copy_advice(|| "starting x_a", region, self.x_a, offset + 1)?;
let y_a = acc
.1
.copy_advice(|| "starting y_a", region, self.lambda1, offset)?;
(x_a, y_a.value().cloned(), z)
};
// Increase offset by 1; we used row 0 for initializing `z`.
let offset = offset + 1;
// Initialise vector to store all interstitial `z` running sum values.
let mut zs: Vec<Z<pallas::Base>> = Vec::with_capacity(bits.len());
// Incomplete addition
for (row, k) in bits.iter().enumerate() {
// z_{i} = 2 * z_{i+1} + k_i
let z_val = z
.value()
.zip(k.as_ref())
.map(|(z_val, k)| pallas::Base::from(2) * z_val + pallas::Base::from(*k as u64));
z = region.assign_advice(
|| "z",
self.z,
row + offset,
|| z_val.ok_or(Error::Synthesis),
)?;
zs.push(Z(z.clone()));
// Assign `x_p`, `y_p`
region.assign_advice(
|| "x_p",
self.x_p,
row + offset,
|| x_p.ok_or(Error::Synthesis),
)?;
region.assign_advice(
|| "y_p",
self.y_p,
row + offset,
|| y_p.ok_or(Error::Synthesis),
)?;
// If the bit is set, use `y`; if the bit is not set, use `-y`
let y_p = y_p
.zip(k.as_ref())
.map(|(y_p, k)| if !k { -y_p } else { y_p });
// Compute and assign λ1⋅(x_A x_P) = y_A y_P
let lambda1 = y_a
.zip(y_p)
.zip(x_a.value())
.zip(x_p)
.map(|(((y_a, y_p), x_a), x_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap());
region.assign_advice(
|| "lambda1",
self.lambda1,
row + offset,
|| lambda1.ok_or(Error::Synthesis),
)?;
// x_R = λ1^2 - x_A - x_P
let x_r = lambda1
.zip(x_a.value())
.zip(x_p)
.map(|((lambda1, x_a), x_p)| lambda1 * lambda1 - x_a - x_p);
// λ2 = (2(y_A) / (x_A - x_R)) - λ1
let lambda2 =
lambda1
.zip(y_a)
.zip(x_a.value())
.zip(x_r)
.map(|(((lambda1, y_a), x_a), x_r)| {
pallas::Base::from(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1
});
region.assign_advice(
|| "lambda2",
self.lambda2,
row + offset,
|| lambda2.ok_or(Error::Synthesis),
)?;
// Compute and assign `x_a` for the next row
let x_a_new = lambda2
.zip(x_a.value())
.zip(x_r)
.map(|((lambda2, x_a), x_r)| lambda2.square() - x_a - x_r);
y_a = lambda2
.zip(x_a.value().cloned())
.zip(x_a_new)
.zip(y_a)
.map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a);
let x_a_val = x_a_new;
x_a = region.assign_advice(
|| "x_a",
self.x_a,
row + offset + 1,
|| x_a_val.ok_or(Error::Synthesis),
)?;
}
// Witness final y_a
let y_a = region.assign_advice(
|| "y_a",
self.lambda1,
offset + NUM_BITS,
|| y_a.ok_or(Error::Synthesis),
)?;
Ok((X(x_a), Y(y_a), zs))
}
}

View File

@ -1,212 +0,0 @@
use super::{T_Q, Z};
use crate::{
circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, primitives::sinsemilla,
};
use halo2::circuit::AssignedCell;
use halo2::{
circuit::Layouter,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use ff::Field;
use pasta_curves::{arithmetic::FieldExt, pallas};
use std::iter;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Config {
// Selector to check z_0 = alpha + t_q (mod p)
q_mul_overflow: Selector,
// 10-bit lookup table
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
// Advice columns
advices: [Column<Advice>; 3],
}
impl Config {
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
advices: [Column<Advice>; 3],
) -> Self {
for advice in advices.iter() {
meta.enable_equality(*advice);
}
let config = Self {
q_mul_overflow: meta.selector(),
lookup_config,
advices,
};
config.create_gate(meta);
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("overflow checks", |meta| {
let q_mul_overflow = meta.query_selector(self.q_mul_overflow);
// Constant expressions
let one = Expression::Constant(pallas::Base::one());
let two_pow_124 = Expression::Constant(pallas::Base::from_u128(1 << 124));
let two_pow_130 =
two_pow_124.clone() * Expression::Constant(pallas::Base::from_u128(1 << 6));
let z_0 = meta.query_advice(self.advices[0], Rotation::prev());
let z_130 = meta.query_advice(self.advices[0], Rotation::cur());
let eta = meta.query_advice(self.advices[0], Rotation::next());
let k_254 = meta.query_advice(self.advices[1], Rotation::prev());
let alpha = meta.query_advice(self.advices[1], Rotation::cur());
// s_minus_lo_130 = s - sum_{i = 0}^{129} 2^i ⋅ s_i
let s_minus_lo_130 = meta.query_advice(self.advices[1], Rotation::next());
let s = meta.query_advice(self.advices[2], Rotation::cur());
let s_check = s - (alpha.clone() + k_254.clone() * two_pow_130);
// q = 2^254 + t_q is the Pallas scalar field modulus.
// We cast t_q into the base field to check alpha + t_q (mod p).
let t_q = Expression::Constant(pallas::Base::from_u128(T_Q));
// z_0 - alpha - t_q = 0 (mod p)
let recovery = z_0 - alpha - t_q;
// k_254 * (z_130 - 2^124) = 0
let lo_zero = k_254.clone() * (z_130.clone() - two_pow_124);
// k_254 * s_minus_lo_130 = 0
let s_minus_lo_130_check = k_254.clone() * s_minus_lo_130.clone();
// (1 - k_254) * (1 - z_130 * eta) * s_minus_lo_130 = 0
let canonicity = (one.clone() - k_254) * (one - z_130 * eta) * s_minus_lo_130;
iter::empty()
.chain(Some(("s_check", s_check)))
.chain(Some(("recovery", recovery)))
.chain(Some(("lo_zero", lo_zero)))
.chain(Some(("s_minus_lo_130_check", s_minus_lo_130_check)))
.chain(Some(("canonicity", canonicity)))
.map(|(name, poly)| (name, q_mul_overflow.clone() * poly))
.collect::<Vec<_>>()
});
}
pub(super) fn overflow_check(
&self,
mut layouter: impl Layouter<pallas::Base>,
alpha: AssignedCell<pallas::Base, pallas::Base>,
zs: &[Z<pallas::Base>], // [z_0, z_1, ..., z_{254}, z_{255}]
) -> Result<(), Error> {
// s = alpha + k_254 ⋅ 2^130 is witnessed here, and then copied into
// the decomposition as well as the overflow check gate.
// In the overflow check gate, we check that s is properly derived
// from alpha and k_254.
let s = {
let k_254 = zs[254].clone();
let s_val = alpha
.value()
.zip(k_254.value())
.map(|(alpha, k_254)| alpha + k_254 * pallas::Base::from_u128(1 << 65).square());
layouter.assign_region(
|| "s = alpha + k_254 ⋅ 2^130",
|mut region| {
region.assign_advice(
|| "s = alpha + k_254 ⋅ 2^130",
self.advices[0],
0,
|| s_val.ok_or(Error::Synthesis),
)
},
)?
};
// Subtract the first 130 low bits of s = alpha + k_254 ⋅ 2^130
// using thirteen 10-bit lookups, s_{0..=129}
let s_minus_lo_130 =
self.s_minus_lo_130(layouter.namespace(|| "decompose s_{0..=129}"), s.clone())?;
layouter.assign_region(
|| "overflow check",
|mut region| {
let offset = 0;
// Enable overflow check gate
self.q_mul_overflow.enable(&mut region, offset + 1)?;
// Copy `z_0`
zs[0].copy_advice(|| "copy z_0", &mut region, self.advices[0], offset)?;
// Copy `z_130`
zs[130].copy_advice(|| "copy z_130", &mut region, self.advices[0], offset + 1)?;
// Witness η = inv0(z_130), where inv0(x) = 0 if x = 0, 1/x otherwise
{
let eta = zs[130].value().map(|z_130| {
if z_130.is_zero_vartime() {
pallas::Base::zero()
} else {
z_130.invert().unwrap()
}
});
region.assign_advice(
|| "η = inv0(z_130)",
self.advices[0],
offset + 2,
|| eta.ok_or(Error::Synthesis),
)?;
}
// Copy `k_254` = z_254
zs[254].copy_advice(|| "copy k_254", &mut region, self.advices[1], offset)?;
// Copy original alpha
alpha.copy_advice(
|| "copy original alpha",
&mut region,
self.advices[1],
offset + 1,
)?;
// Copy weighted sum of the decomposition of s = alpha + k_254 ⋅ 2^130.
s_minus_lo_130.copy_advice(
|| "copy s_minus_lo_130",
&mut region,
self.advices[1],
offset + 2,
)?;
// Copy witnessed s to check that it was properly derived from alpha and k_254.
s.copy_advice(|| "copy s", &mut region, self.advices[2], offset + 1)?;
Ok(())
},
)?;
Ok(())
}
fn s_minus_lo_130(
&self,
mut layouter: impl Layouter<pallas::Base>,
s: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<AssignedCell<pallas::Base, pallas::Base>, Error> {
// Number of k-bit words we can use in the lookup decomposition.
let num_words = 130 / sinsemilla::K;
assert!(num_words * sinsemilla::K == 130);
// Decompose the low 130 bits of `s` using thirteen 10-bit lookups.
let zs = self.lookup_config.copy_check(
layouter.namespace(|| "Decompose low 130 bits of s"),
s,
num_words,
false,
)?;
// (s - (2^0 s_0 + 2^1 s_1 + ... + 2^129 s_129)) / 2^130
Ok(zs[zs.len() - 1].clone())
}
}

View File

@ -1,503 +0,0 @@
use super::{
add, add_incomplete, EccBaseFieldElemFixed, EccScalarFixed, EccScalarFixedShort, FixedPoint,
NonIdentityEccPoint, FIXED_BASE_WINDOW_SIZE, H,
};
use crate::circuit::gadget::utilities::decompose_running_sum::RunningSumConfig;
use std::marker::PhantomData;
use group::{ff::PrimeField, Curve};
use halo2::{
circuit::{AssignedCell, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, VirtualCells},
poly::Rotation,
};
use lazy_static::lazy_static;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
pub mod base_field_elem;
pub mod full_width;
pub mod short;
lazy_static! {
static ref TWO_SCALAR: pallas::Scalar = pallas::Scalar::from(2);
// H = 2^3 (3-bit window)
static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from(H as u64);
static ref H_BASE: pallas::Base = pallas::Base::from(H as u64);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config<FixedPoints: super::FixedPoints<pallas::Affine>> {
running_sum_config: RunningSumConfig<pallas::Base, FIXED_BASE_WINDOW_SIZE>,
// The fixed Lagrange interpolation coefficients for `x_p`.
lagrange_coeffs: [Column<Fixed>; H],
// The fixed `z` for each window such that `y + z = u^2`.
fixed_z: Column<Fixed>,
// Decomposition of an `n-1`-bit scalar into `k`-bit windows:
// a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1})
window: Column<Advice>,
// x-coordinate of the multiple of the fixed base at the current window.
x_p: Column<Advice>,
// y-coordinate of the multiple of the fixed base at the current window.
y_p: Column<Advice>,
// y-coordinate of accumulator (only used in the final row).
u: Column<Advice>,
// Configuration for `add`
add_config: add::Config,
// Configuration for `add_incomplete`
add_incomplete_config: add_incomplete::Config,
_marker: PhantomData<FixedPoints>,
}
impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
#[allow(clippy::too_many_arguments)]
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
lagrange_coeffs: [Column<Fixed>; H],
window: Column<Advice>,
x_p: Column<Advice>,
y_p: Column<Advice>,
u: Column<Advice>,
add_config: add::Config,
add_incomplete_config: add_incomplete::Config,
) -> Self {
meta.enable_equality(window);
meta.enable_equality(u);
let q_running_sum = meta.selector();
let running_sum_config = RunningSumConfig::configure(meta, q_running_sum, window);
let config = Self {
running_sum_config,
lagrange_coeffs,
fixed_z: meta.fixed_column(),
window,
x_p,
y_p,
u,
add_config,
add_incomplete_config,
_marker: PhantomData,
};
// Check relationships between this config and `add_config`.
assert_eq!(
config.x_p, config.add_config.x_p,
"add is used internally in mul_fixed."
);
assert_eq!(
config.y_p, config.add_config.y_p,
"add is used internally in mul_fixed."
);
// Check relationships between this config and `add_incomplete_config`.
assert_eq!(
config.x_p, config.add_incomplete_config.x_p,
"add_incomplete is used internally in mul_fixed."
);
assert_eq!(
config.y_p, config.add_incomplete_config.y_p,
"add_incomplete is used internally in mul_fixed."
);
for advice in [config.x_p, config.y_p, config.window, config.u].iter() {
assert_ne!(
*advice, config.add_config.x_qr,
"Do not overlap with output columns of add."
);
assert_ne!(
*advice, config.add_config.y_qr,
"Do not overlap with output columns of add."
);
}
config.running_sum_coords_gate(meta);
config
}
/// Check that each window in the running sum decomposition uses the correct y_p
/// and interpolated x_p.
///
/// This gate is used both in the mul_fixed::base_field_elem and mul_fixed::short
/// helpers, which decompose the scalar using a running sum.
///
/// This gate is not used in the mul_fixed::full_width helper, since the full-width
/// scalar is witnessed directly as three-bit windows instead of being decomposed
/// via a running sum.
fn running_sum_coords_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("Running sum coordinates check", |meta| {
let q_mul_fixed_running_sum =
meta.query_selector(self.running_sum_config.q_range_check);
let z_cur = meta.query_advice(self.window, Rotation::cur());
let z_next = meta.query_advice(self.window, Rotation::next());
// z_{i+1} = (z_i - a_i) / 2^3
// => a_i = z_i - z_{i+1} * 2^3
let word = z_cur - z_next * pallas::Base::from(H as u64);
self.coords_check(meta, q_mul_fixed_running_sum, word)
});
}
#[allow(clippy::op_ref)]
fn coords_check(
&self,
meta: &mut VirtualCells<'_, pallas::Base>,
toggle: Expression<pallas::Base>,
window: Expression<pallas::Base>,
) -> Vec<(&'static str, Expression<pallas::Base>)> {
let y_p = meta.query_advice(self.y_p, Rotation::cur());
let x_p = meta.query_advice(self.x_p, Rotation::cur());
let z = meta.query_fixed(self.fixed_z, Rotation::cur());
let u = meta.query_advice(self.u, Rotation::cur());
let window_pow: Vec<Expression<pallas::Base>> = (0..H)
.map(|pow| {
(0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| {
acc * window.clone()
})
})
.collect();
let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold(
Expression::Constant(pallas::Base::zero()),
|acc, (window_pow, coeff)| {
acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur()))
},
);
// Check interpolation of x-coordinate
let x_check = interpolated_x - x_p.clone();
// Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed
let y_check = u.square() - y_p.clone() - z;
// Check that (x, y) is on the curve
let on_curve =
y_p.square() - x_p.clone().square() * x_p - Expression::Constant(pallas::Affine::b());
vec![
("check x", toggle.clone() * x_check),
("check y", toggle.clone() * y_check),
("on-curve", toggle * on_curve),
]
}
#[allow(clippy::type_complexity)]
fn assign_region_inner<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
scalar: &ScalarFixed,
base: &F,
coords_check_toggle: Selector,
) -> Result<(NonIdentityEccPoint, NonIdentityEccPoint), Error> {
// Assign fixed columns for given fixed base
self.assign_fixed_constants::<F, NUM_WINDOWS>(region, offset, base, coords_check_toggle)?;
// Initialize accumulator
let acc = self.initialize_accumulator::<F, NUM_WINDOWS>(region, offset, base, scalar)?;
// Process all windows excluding least and most significant windows
let acc = self.add_incomplete::<F, NUM_WINDOWS>(region, offset, acc, base, scalar)?;
// Process most significant window using complete addition
let mul_b = self.process_msb::<F, NUM_WINDOWS>(region, offset, base, scalar)?;
Ok((acc, mul_b))
}
fn assign_fixed_constants<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &F,
coords_check_toggle: Selector,
) -> Result<(), Error> {
let mut constants = None;
let build_constants = || {
let lagrange_coeffs = base.lagrange_coeffs();
assert_eq!(lagrange_coeffs.len(), NUM_WINDOWS);
let z = base.z();
assert_eq!(z.len(), NUM_WINDOWS);
(lagrange_coeffs, z)
};
// Assign fixed columns for given fixed base
for window in 0..NUM_WINDOWS {
coords_check_toggle.enable(region, window + offset)?;
// Assign x-coordinate Lagrange interpolation coefficients
for k in 0..H {
region.assign_fixed(
|| {
format!(
"Lagrange interpolation coeff for window: {:?}, k: {:?}",
window, k
)
},
self.lagrange_coeffs[k],
window + offset,
|| {
if constants.as_ref().is_none() {
constants = Some(build_constants());
}
let lagrange_coeffs = &constants.as_ref().unwrap().0;
Ok(lagrange_coeffs[window][k])
},
)?;
}
// Assign z-values for each window
region.assign_fixed(
|| format!("z-value for window: {:?}", window),
self.fixed_z,
window + offset,
|| {
let z = &constants.as_ref().unwrap().1;
Ok(pallas::Base::from(z[window]))
},
)?;
}
Ok(())
}
fn process_window<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
w: usize,
k: Option<pallas::Scalar>,
k_usize: Option<usize>,
base: &F,
) -> Result<NonIdentityEccPoint, Error> {
let base_value = base.generator();
let base_u = base.u();
assert_eq!(base_u.len(), NUM_WINDOWS);
// Compute [(k_w + 2) ⋅ 8^w]B
let mul_b = {
let mul_b =
k.map(|k| base_value * (k + *TWO_SCALAR) * H_SCALAR.pow(&[w as u64, 0, 0, 0]));
let mul_b = mul_b.map(|mul_b| mul_b.to_affine().coordinates().unwrap());
let x = mul_b.map(|mul_b| {
let x = *mul_b.x();
assert!(x != pallas::Base::zero());
x
});
let x = region.assign_advice(
|| format!("mul_b_x, window {}", w),
self.x_p,
offset + w,
|| x.ok_or(Error::Synthesis),
)?;
let y = mul_b.map(|mul_b| {
let y = *mul_b.y();
assert!(y != pallas::Base::zero());
y
});
let y = region.assign_advice(
|| format!("mul_b_y, window {}", w),
self.y_p,
offset + w,
|| y.ok_or(Error::Synthesis),
)?;
NonIdentityEccPoint { x, y }
};
// Assign u = (y_p + z_w).sqrt()
let u_val = k_usize.map(|k| pallas::Base::from_bytes(&base_u[w][k]).unwrap());
region.assign_advice(|| "u", self.u, offset + w, || u_val.ok_or(Error::Synthesis))?;
Ok(mul_b)
}
fn initialize_accumulator<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &F,
scalar: &ScalarFixed,
) -> Result<NonIdentityEccPoint, Error> {
// Recall that the message at each window `w` is represented as
// `m_w = [(k_w + 2) ⋅ 8^w]B`.
// When `w = 0`, we have `m_0 = [(k_0 + 2)]B`.
let w = 0;
let k0 = scalar.windows_field()[0];
let k0_usize = scalar.windows_usize()[0];
self.process_window::<_, NUM_WINDOWS>(region, offset, w, k0, k0_usize, base)
}
fn add_incomplete<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
mut acc: NonIdentityEccPoint,
base: &F,
scalar: &ScalarFixed,
) -> Result<NonIdentityEccPoint, Error> {
let scalar_windows_field = scalar.windows_field();
let scalar_windows_usize = scalar.windows_usize();
for (w, (k, k_usize)) in scalar_windows_field[..(scalar_windows_field.len() - 1)]
.iter()
.zip(scalar_windows_usize[..(scalar_windows_field.len() - 1)].iter())
.enumerate()
// Skip k_0 (already processed).
.skip(1)
{
// Compute [(k_w + 2) ⋅ 8^w]B
let mul_b =
self.process_window::<_, NUM_WINDOWS>(region, offset, w, *k, *k_usize, base)?;
// Add to the accumulator
acc = self
.add_incomplete_config
.assign_region(&mul_b, &acc, offset + w, region)?;
}
Ok(acc)
}
fn process_msb<F: FixedPoint<pallas::Affine>, const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &F,
scalar: &ScalarFixed,
) -> Result<NonIdentityEccPoint, Error> {
// Assign u = (y_p + z_w).sqrt() for the most significant window
{
let u_val = scalar.windows_usize()[NUM_WINDOWS - 1]
.map(|k| pallas::Base::from_bytes(&base.u()[NUM_WINDOWS - 1][k]).unwrap());
region.assign_advice(
|| "u",
self.u,
offset + NUM_WINDOWS - 1,
|| u_val.ok_or(Error::Synthesis),
)?;
}
// offset_acc = \sum_{j = 0}^{NUM_WINDOWS - 2} 2^{FIXED_BASE_WINDOW_SIZE*j + 1}
let offset_acc = (0..(NUM_WINDOWS - 1)).fold(pallas::Scalar::zero(), |acc, w| {
acc + (*TWO_SCALAR).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0])
});
// `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 2^{FIXED_BASE_WINDOW_SIZE*j + 1}`.
let scalar = scalar.windows_field()[scalar.windows_field().len() - 1]
.map(|k| k * (*H_SCALAR).pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc);
let mul_b = {
let mul_b = scalar.map(|scalar| base.generator() * scalar);
let mul_b = mul_b.map(|mul_b| mul_b.to_affine().coordinates().unwrap());
let x = mul_b.map(|mul_b| {
let x = *mul_b.x();
assert!(x != pallas::Base::zero());
x
});
let x = region.assign_advice(
|| format!("mul_b_x, window {}", NUM_WINDOWS - 1),
self.x_p,
offset + NUM_WINDOWS - 1,
|| x.ok_or(Error::Synthesis),
)?;
let y = mul_b.map(|mul_b| {
let y = *mul_b.y();
assert!(y != pallas::Base::zero());
y
});
let y = region.assign_advice(
|| format!("mul_b_y, window {}", NUM_WINDOWS - 1),
self.y_p,
offset + NUM_WINDOWS - 1,
|| y.ok_or(Error::Synthesis),
)?;
NonIdentityEccPoint { x, y }
};
Ok(mul_b)
}
}
enum ScalarFixed {
FullWidth(EccScalarFixed),
Short(EccScalarFixedShort),
BaseFieldElem(EccBaseFieldElemFixed),
}
impl From<&EccScalarFixed> for ScalarFixed {
fn from(scalar_fixed: &EccScalarFixed) -> Self {
Self::FullWidth(scalar_fixed.clone())
}
}
impl From<&EccScalarFixedShort> for ScalarFixed {
fn from(scalar_fixed: &EccScalarFixedShort) -> Self {
Self::Short(scalar_fixed.clone())
}
}
impl From<&EccBaseFieldElemFixed> for ScalarFixed {
fn from(base_field_elem: &EccBaseFieldElemFixed) -> Self {
Self::BaseFieldElem(base_field_elem.clone())
}
}
impl ScalarFixed {
// The scalar decomposition was done in the base field. For computation
// outside the circuit, we now convert them back into the scalar field.
fn windows_field(&self) -> Vec<Option<pallas::Scalar>> {
let running_sum_to_windows = |zs: Vec<AssignedCell<pallas::Base, pallas::Base>>| {
(0..(zs.len() - 1))
.map(|idx| {
let z_cur = zs[idx].value();
let z_next = zs[idx + 1].value();
let word = z_cur
.zip(z_next)
.map(|(z_cur, z_next)| z_cur - z_next * *H_BASE);
word.map(|word| pallas::Scalar::from_repr(word.to_repr()).unwrap())
})
.collect::<Vec<_>>()
};
match self {
Self::BaseFieldElem(scalar) => running_sum_to_windows(scalar.running_sum.to_vec()),
Self::Short(scalar) => running_sum_to_windows(scalar.running_sum.to_vec()),
Self::FullWidth(scalar) => scalar
.windows
.iter()
.map(|bits| {
bits.value()
.map(|value| pallas::Scalar::from_repr(value.to_repr()).unwrap())
})
.collect::<Vec<_>>(),
}
}
// The scalar decomposition is guaranteed to be in three-bit windows,
// so we also cast the least significant 4 bytes in their serialisation
// into usize for convenient indexing into `u`-values
fn windows_usize(&self) -> Vec<Option<usize>> {
self.windows_field()
.iter()
.map(|window| {
if let Some(window) = window {
let window = window.get_lower_32() as usize;
assert!(window < H);
Some(window)
} else {
None
}
})
.collect::<Vec<_>>()
}
}

View File

@ -1,520 +0,0 @@
use super::super::{EccBaseFieldElemFixed, EccPoint, FixedPoints, NUM_WINDOWS, T_P};
use super::H_BASE;
use crate::{
circuit::gadget::utilities::{
bitrange_subset, lookup_range_check::LookupRangeCheckConfig, range_check,
},
primitives::sinsemilla,
};
use group::ff::PrimeField;
use halo2::{
circuit::{AssignedCell, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use std::convert::TryInto;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config<Fixed: FixedPoints<pallas::Affine>> {
q_mul_fixed_base_field: Selector,
canon_advices: [Column<Advice>; 3],
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
super_config: super::Config<Fixed>,
}
impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
pub(crate) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
canon_advices: [Column<Advice>; 3],
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
super_config: super::Config<Fixed>,
) -> Self {
for advice in canon_advices.iter() {
meta.enable_equality(*advice);
}
let config = Self {
q_mul_fixed_base_field: meta.selector(),
canon_advices,
lookup_config,
super_config,
};
let add_incomplete_advices = config.super_config.add_incomplete_config.advice_columns();
for canon_advice in config.canon_advices.iter() {
assert!(
!add_incomplete_advices.contains(canon_advice),
"Deconflict canon_advice columns with incomplete addition columns."
);
}
config.create_gate(meta);
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that the base field element is canonical.
meta.create_gate("Canonicity checks", |meta| {
let q_mul_fixed_base_field = meta.query_selector(self.q_mul_fixed_base_field);
let alpha = meta.query_advice(self.canon_advices[0], Rotation::prev());
// The last three bits of α.
let z_84_alpha = meta.query_advice(self.canon_advices[2], Rotation::prev());
// Decompose α into three pieces, in little-endian order:
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
//
// α_0 is derived, not witnessed.
let alpha_0 = {
let two_pow_252 = pallas::Base::from_u128(1 << 126).square();
alpha - (z_84_alpha.clone() * two_pow_252)
};
let alpha_1 = meta.query_advice(self.canon_advices[1], Rotation::cur());
let alpha_2 = meta.query_advice(self.canon_advices[2], Rotation::cur());
let alpha_0_prime = meta.query_advice(self.canon_advices[0], Rotation::cur());
let z_13_alpha_0_prime = meta.query_advice(self.canon_advices[0], Rotation::next());
let z_44_alpha = meta.query_advice(self.canon_advices[1], Rotation::next());
let z_43_alpha = meta.query_advice(self.canon_advices[2], Rotation::next());
let decomposition_checks = {
// Range-constrain α_1 to be 2 bits
let alpha_1_range_check = range_check(alpha_1.clone(), 1 << 2);
// Boolean-constrain α_2
let alpha_2_range_check = range_check(alpha_2.clone(), 1 << 1);
// Check that α_1 + 2^2 α_2 = z_84_alpha
let z_84_alpha_check = z_84_alpha.clone()
- (alpha_1.clone() + alpha_2.clone() * pallas::Base::from(1 << 2));
std::iter::empty()
.chain(Some(("alpha_1_range_check", alpha_1_range_check)))
.chain(Some(("alpha_2_range_check", alpha_2_range_check)))
.chain(Some(("z_84_alpha_check", z_84_alpha_check)))
};
// Check α_0_prime = α_0 + 2^130 - t_p
let alpha_0_prime_check = {
let two_pow_130 = Expression::Constant(pallas::Base::from_u128(1 << 65).square());
let t_p = Expression::Constant(pallas::Base::from_u128(T_P));
alpha_0_prime - (alpha_0 + two_pow_130 - t_p)
};
// We want to enforce canonicity of a 255-bit base field element, α.
// That is, we want to check that 0 ≤ α < p, where p is Pallas base
// field modulus p = 2^254 + t_p
// = 2^254 + 45560315531419706090280762371685220353.
// Note that t_p < 2^130.
//
// α has been decomposed into three pieces in little-endian order:
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
// = α_0 + 2^252 α_1 + 2^254 α_2.
//
// If the MSB α_2 = 1, then:
// - α_2 = 1 => α_1 = 0, and
// - α_2 = 1 => α_0 < t_p. To enforce this:
// - α_2 = 1 => 0 ≤ α_0 < 2^130
// - alpha_0_hi_120 = 0 (constrain α_0 to be 132 bits)
// - a_43 = 0 or 1 (constrain α_0[130..=131] to be 0)
// - α_2 = 1 => 0 ≤ α_0 + 2^130 - t_p < 2^130
// => 13 ten-bit lookups of α_0 + 2^130 - t_p
// => z_13_alpha_0_prime = 0
//
let canon_checks = {
// alpha_0_hi_120 = z_44 - 2^120 z_84
let alpha_0_hi_120 = {
let two_pow_120 =
Expression::Constant(pallas::Base::from_u128(1 << 60).square());
z_44_alpha.clone() - z_84_alpha * two_pow_120
};
// a_43 = z_43 - (2^3)z_44
let a_43 = z_43_alpha - z_44_alpha * *H_BASE;
std::iter::empty()
.chain(Some(("MSB = 1 => alpha_1 = 0", alpha_2.clone() * alpha_1)))
.chain(Some((
"MSB = 1 => alpha_0_hi_120 = 0",
alpha_2.clone() * alpha_0_hi_120,
)))
.chain(Some((
"MSB = 1 => a_43 = 0 or 1",
alpha_2.clone() * range_check(a_43, 2),
)))
.chain(Some((
"MSB = 1 => z_13_alpha_0_prime = 0",
alpha_2 * z_13_alpha_0_prime,
)))
};
canon_checks
.chain(decomposition_checks)
.chain(Some(("alpha_0_prime check", alpha_0_prime_check)))
.map(move |(name, poly)| (name, q_mul_fixed_base_field.clone() * poly))
});
}
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
scalar: AssignedCell<pallas::Base, pallas::Base>,
base: &<Fixed as FixedPoints<pallas::Affine>>::Base,
) -> Result<EccPoint, Error>
where
<Fixed as FixedPoints<pallas::Affine>>::Base: super::super::FixedPoint<pallas::Affine>,
{
let (scalar, acc, mul_b) = layouter.assign_region(
|| "Base-field elem fixed-base mul (incomplete addition)",
|mut region| {
let offset = 0;
// Decompose scalar
let scalar = {
let running_sum = self.super_config.running_sum_config.copy_decompose(
&mut region,
offset,
scalar.clone(),
true,
pallas::Base::NUM_BITS as usize,
NUM_WINDOWS,
)?;
EccBaseFieldElemFixed {
base_field_elem: running_sum[0].clone(),
running_sum: (*running_sum).as_slice().try_into().unwrap(),
}
};
let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>(
&mut region,
offset,
&(&scalar).into(),
base,
self.super_config.running_sum_config.q_range_check,
)?;
Ok((scalar, acc, mul_b))
},
)?;
// Add to the accumulator and return the final result as `[scalar]B`.
let result = layouter.assign_region(
|| "Base-field elem fixed-base mul (complete addition)",
|mut region| {
self.super_config.add_config.assign_region(
&mul_b.clone().into(),
&acc.clone().into(),
0,
&mut region,
)
},
)?;
#[cfg(test)]
// Check that the correct multiple is obtained.
{
use super::super::FixedPoint;
use group::Curve;
let scalar = &scalar
.base_field_elem()
.value()
.map(|scalar| pallas::Scalar::from_repr(scalar.to_repr()).unwrap());
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
}
// We want to enforce canonicity of a 255-bit base field element, α.
// That is, we want to check that 0 ≤ α < p, where p is Pallas base
// field modulus p = 2^254 + t_p
// = 2^254 + 45560315531419706090280762371685220353.
// Note that t_p < 2^130.
//
// α has been decomposed into three pieces in little-endian order:
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
// = α_0 + 2^252 α_1 + 2^254 α_2.
//
// If the MSB α_2 = 1, then:
// - α_2 = 1 => α_1 = 0, and
// - α_2 = 1 => α_0 < t_p. To enforce this:
// - α_2 = 1 => 0 ≤ α_0 < 2^130
// => 13 ten-bit lookups of α_0
// - α_2 = 1 => 0 ≤ α_0 + 2^130 - t_p < 2^130
// => 13 ten-bit lookups of α_0 + 2^130 - t_p
// => z_13_alpha_0_prime = 0
//
let (alpha, running_sum) = (scalar.base_field_elem, &scalar.running_sum);
let z_43_alpha = running_sum[43].clone();
let z_44_alpha = running_sum[44].clone();
let z_84_alpha = running_sum[84].clone();
// α_0 = α - z_84_alpha * 2^252
let alpha_0 = alpha
.value()
.zip(z_84_alpha.value())
.map(|(alpha, z_84_alpha)| {
let two_pow_252 = pallas::Base::from_u128(1 << 126).square();
alpha - z_84_alpha * two_pow_252
});
let (alpha_0_prime, z_13_alpha_0_prime) = {
// alpha_0_prime = alpha + 2^130 - t_p.
let alpha_0_prime = alpha_0.map(|alpha_0| {
let two_pow_130 = pallas::Base::from_u128(1 << 65).square();
let t_p = pallas::Base::from_u128(T_P);
alpha_0 + two_pow_130 - t_p
});
let zs = self.lookup_config.witness_check(
layouter.namespace(|| "Lookup range check alpha_0 + 2^130 - t_p"),
alpha_0_prime,
13,
false,
)?;
let alpha_0_prime = zs[0].clone();
(alpha_0_prime, zs[13].clone())
};
layouter.assign_region(
|| "Canonicity checks",
|mut region| {
// Activate canonicity check gate
self.q_mul_fixed_base_field.enable(&mut region, 1)?;
// Offset 0
{
let offset = 0;
// Copy α
alpha.copy_advice(|| "Copy α", &mut region, self.canon_advices[0], offset)?;
// z_84_alpha = the top three bits of alpha.
z_84_alpha.copy_advice(
|| "Copy z_84_alpha",
&mut region,
self.canon_advices[2],
offset,
)?;
}
// Offset 1
{
let offset = 1;
// Copy alpha_0_prime = alpha_0 + 2^130 - t_p.
// We constrain this in the custom gate to be derived correctly.
alpha_0_prime.copy_advice(
|| "Copy α_0 + 2^130 - t_p",
&mut region,
self.canon_advices[0],
offset,
)?;
// Decompose α into three pieces,
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
// We only need to witness α_1 and α_2. α_0 is derived in the gate.
// Witness α_1 = α[252..=253]
let alpha_1 = alpha.value().map(|alpha| bitrange_subset(alpha, 252..254));
region.assign_advice(
|| "α_1 = α[252..=253]",
self.canon_advices[1],
offset,
|| alpha_1.ok_or(Error::Synthesis),
)?;
// Witness the MSB α_2 = α[254]
let alpha_2 = alpha.value().map(|alpha| bitrange_subset(alpha, 254..255));
region.assign_advice(
|| "α_2 = α[254]",
self.canon_advices[2],
offset,
|| alpha_2.ok_or(Error::Synthesis),
)?;
}
// Offset 2
{
let offset = 2;
// Copy z_13_alpha_0_prime
z_13_alpha_0_prime.copy_advice(
|| "Copy z_13_alpha_0_prime",
&mut region,
self.canon_advices[0],
offset,
)?;
// Copy z_44_alpha
z_44_alpha.copy_advice(
|| "Copy z_44_alpha",
&mut region,
self.canon_advices[1],
offset,
)?;
// Copy z_43_alpha
z_43_alpha.copy_advice(
|| "Copy z_43_alpha",
&mut region,
self.canon_advices[2],
offset,
)?;
}
Ok(())
},
)?;
Ok(result)
}
}
#[cfg(test)]
pub mod tests {
use group::{
ff::{Field, PrimeField},
Curve,
};
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
};
use pasta_curves::pallas;
use rand::rngs::OsRng;
use crate::circuit::gadget::{
ecc::{
chip::{EccChip, FixedPoint, H},
FixedPointBaseField, NonIdentityPoint, Point,
},
utilities::UtilitiesInstructions,
};
use crate::constants::{NullifierK, OrchardFixedBases};
pub fn test_mul_fixed_base_field(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
// nullifier_k
test_single_base(
chip.clone(),
layouter.namespace(|| "nullifier_k"),
FixedPointBaseField::from_inner(chip, NullifierK),
NullifierK.generator(),
)
}
#[allow(clippy::op_ref)]
fn test_single_base(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base: FixedPointBaseField<pallas::Affine, EccChip<OrchardFixedBases>>,
base_val: pallas::Affine,
) -> Result<(), Error> {
let rng = OsRng;
let column = chip.config().advices[0];
fn constrain_equal_non_id(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base_val: pallas::Affine,
scalar_val: pallas::Base,
result: Point<pallas::Affine, EccChip<OrchardFixedBases>>,
) -> Result<(), Error> {
// Move scalar from base field into scalar field (which always fits for Pallas).
let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap();
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
// [a]B
{
let scalar_fixed = pallas::Base::random(rng);
let result = {
let scalar_fixed = chip.load_private(
layouter.namespace(|| "random base field element"),
column,
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "random [a]B"), scalar_fixed)?
};
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "random [a]B"),
base_val,
scalar_fixed,
result,
)?;
}
// There is a single canonical sequence of window values for which a doubling occurs on the last step:
// 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal.
// (There is another *non-canonical* sequence
// 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.)
{
let h = pallas::Base::from(H as u64);
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
.chars()
.fold(pallas::Base::zero(), |acc, c| {
acc * &h + &pallas::Base::from(c.to_digit(8).unwrap() as u64)
});
let result = {
let scalar_fixed = chip.load_private(
layouter.namespace(|| "mul with double"),
column,
Some(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul with double"), scalar_fixed)?
};
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "mul with double"),
base_val,
scalar_fixed,
result,
)?;
}
// [0]B should return (0,0) since it uses complete addition
// on the last step.
{
let scalar_fixed = pallas::Base::zero();
let result = {
let scalar_fixed =
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_fixed))?;
base.mul(layouter.namespace(|| "mul by zero"), scalar_fixed)?
};
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
// [-1]B is the largest base field element
{
let scalar_fixed = -pallas::Base::one();
let result = {
let scalar_fixed =
chip.load_private(layouter.namespace(|| "-1"), column, Some(scalar_fixed))?;
base.mul(layouter.namespace(|| "mul by -1"), scalar_fixed)?
};
constrain_equal_non_id(
chip,
layouter.namespace(|| "mul by -1"),
base_val,
scalar_fixed,
result,
)?;
}
Ok(())
}
}

View File

@ -1,317 +0,0 @@
use super::super::{EccPoint, EccScalarFixed, FixedPoints, FIXED_BASE_WINDOW_SIZE, H, NUM_WINDOWS};
use crate::circuit::gadget::utilities::{decompose_word, range_check};
use arrayvec::ArrayVec;
use ff::PrimeField;
use halo2::{
circuit::{AssignedCell, Layouter, Region},
plonk::{ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config<Fixed: FixedPoints<pallas::Affine>> {
q_mul_fixed_full: Selector,
super_config: super::Config<Fixed>,
}
impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
pub(crate) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
super_config: super::Config<Fixed>,
) -> Self {
let config = Self {
q_mul_fixed_full: meta.selector(),
super_config,
};
config.create_gate(meta);
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that each window `k` is within 3 bits
meta.create_gate("Full-width fixed-base scalar mul", |meta| {
let q_mul_fixed_full = meta.query_selector(self.q_mul_fixed_full);
let window = meta.query_advice(self.super_config.window, Rotation::cur());
self.super_config
.coords_check(meta, q_mul_fixed_full.clone(), window.clone())
.into_iter()
// Constrain each window to a 3-bit value:
// 1 * (window - 0) * (window - 1) * ... * (window - 7)
.chain(Some((
"window range check",
q_mul_fixed_full * range_check(window, H),
)))
});
}
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
///
/// The scalar is allowed to be non-canonical.
fn witness(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
scalar: Option<pallas::Scalar>,
) -> Result<EccScalarFixed, Error> {
let windows = self.decompose_scalar_fixed::<{ pallas::Scalar::NUM_BITS as usize }>(
scalar, offset, region,
)?;
Ok(EccScalarFixed {
value: scalar,
windows,
})
}
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
///
/// The scalar is allowed to be non-canonical.
fn decompose_scalar_fixed<const SCALAR_NUM_BITS: usize>(
&self,
scalar: Option<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<AssignedCell<pallas::Base, pallas::Base>, NUM_WINDOWS>, Error> {
// Enable `q_mul_fixed_full` selector
for idx in 0..NUM_WINDOWS {
self.q_mul_fixed_full.enable(region, offset + idx)?;
}
// Decompose scalar into `k-bit` windows
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
decompose_word::<pallas::Scalar>(&scalar, SCALAR_NUM_BITS, FIXED_BASE_WINDOW_SIZE)
});
// Store the scalar decomposition
let mut windows: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, NUM_WINDOWS> =
ArrayVec::new();
let scalar_windows: Vec<Option<pallas::Base>> = if let Some(windows) = scalar_windows {
assert_eq!(windows.len(), NUM_WINDOWS);
windows
.into_iter()
.map(|window| Some(pallas::Base::from(window as u64)))
.collect()
} else {
vec![None; NUM_WINDOWS]
};
for (idx, window) in scalar_windows.into_iter().enumerate() {
let window_cell = region.assign_advice(
|| format!("k[{:?}]", offset + idx),
self.super_config.window,
offset + idx,
|| window.ok_or(Error::Synthesis),
)?;
windows.push(window_cell);
}
Ok(windows)
}
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
scalar: Option<pallas::Scalar>,
base: &<Fixed as FixedPoints<pallas::Affine>>::FullScalar,
) -> Result<(EccPoint, EccScalarFixed), Error>
where
<Fixed as FixedPoints<pallas::Affine>>::FullScalar:
super::super::FixedPoint<pallas::Affine>,
{
let (scalar, acc, mul_b) = layouter.assign_region(
|| "Full-width fixed-base mul (incomplete addition)",
|mut region| {
let offset = 0;
let scalar = self.witness(&mut region, offset, scalar)?;
let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>(
&mut region,
offset,
&(&scalar).into(),
base,
self.q_mul_fixed_full,
)?;
Ok((scalar, acc, mul_b))
},
)?;
// Add to the accumulator and return the final result as `[scalar]B`.
let result = layouter.assign_region(
|| "Full-width fixed-base mul (last window, complete addition)",
|mut region| {
self.super_config.add_config.assign_region(
&mul_b.clone().into(),
&acc.clone().into(),
0,
&mut region,
)
},
)?;
#[cfg(test)]
// Check that the correct multiple is obtained.
{
use super::super::FixedPoint;
use group::Curve;
let real_mul = scalar.value.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
}
Ok((result, scalar))
}
}
#[cfg(test)]
pub mod tests {
use group::{ff::Field, Curve};
use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::pallas;
use rand::rngs::OsRng;
use crate::circuit::gadget::ecc::{
chip::{EccChip, FixedPoint as _, H},
FixedPoint, NonIdentityPoint, Point,
};
use crate::constants::{OrchardFixedBases, OrchardFixedBasesFull};
pub fn test_mul_fixed(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
// commit_ivk_r
let commit_ivk_r = OrchardFixedBasesFull::CommitIvkR;
test_single_base(
chip.clone(),
layouter.namespace(|| "commit_ivk_r"),
FixedPoint::from_inner(chip.clone(), commit_ivk_r),
commit_ivk_r.generator(),
)?;
// note_commit_r
let note_commit_r = OrchardFixedBasesFull::NoteCommitR;
test_single_base(
chip.clone(),
layouter.namespace(|| "note_commit_r"),
FixedPoint::from_inner(chip.clone(), note_commit_r),
note_commit_r.generator(),
)?;
// value_commit_r
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
test_single_base(
chip.clone(),
layouter.namespace(|| "value_commit_r"),
FixedPoint::from_inner(chip.clone(), value_commit_r),
value_commit_r.generator(),
)?;
// spend_auth_g
let spend_auth_g = OrchardFixedBasesFull::SpendAuthG;
test_single_base(
chip.clone(),
layouter.namespace(|| "spend_auth_g"),
FixedPoint::from_inner(chip, spend_auth_g),
spend_auth_g.generator(),
)?;
Ok(())
}
#[allow(clippy::op_ref)]
fn test_single_base(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base: FixedPoint<pallas::Affine, EccChip<OrchardFixedBases>>,
base_val: pallas::Affine,
) -> Result<(), Error> {
fn constrain_equal_non_id(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base_val: pallas::Affine,
scalar_val: pallas::Scalar,
result: Point<pallas::Affine, EccChip<OrchardFixedBases>>,
) -> Result<(), Error> {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
// [a]B
{
let scalar_fixed = pallas::Scalar::random(OsRng);
let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), Some(scalar_fixed))?;
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "random [a]B"),
base_val,
scalar_fixed,
result,
)?;
}
// There is a single canonical sequence of window values for which a doubling occurs on the last step:
// 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal.
// (There is another *non-canonical* sequence
// 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.)
{
let h = pallas::Scalar::from(H as u64);
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
.chars()
.fold(pallas::Scalar::zero(), |acc, c| {
acc * &h + &pallas::Scalar::from(c.to_digit(8).unwrap() as u64)
});
let (result, _) =
base.mul(layouter.namespace(|| "mul with double"), Some(scalar_fixed))?;
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| "mul with double"),
base_val,
scalar_fixed,
result,
)?;
}
// [0]B should return (0,0) since it uses complete addition
// on the last step.
{
let scalar_fixed = pallas::Scalar::zero();
let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), Some(scalar_fixed))?;
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
// [-1]B is the largest scalar field element.
{
let scalar_fixed = -pallas::Scalar::one();
let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), Some(scalar_fixed))?;
constrain_equal_non_id(
chip,
layouter.namespace(|| "mul by -1"),
base_val,
scalar_fixed,
result,
)?;
}
Ok(())
}
}

View File

@ -1,617 +0,0 @@
use std::{array, convert::TryInto};
use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_SCALAR_SHORT, NUM_WINDOWS_SHORT};
use crate::circuit::gadget::{ecc::chip::MagnitudeSign, utilities::bool_check};
use halo2::{
circuit::{Layouter, Region},
plonk::{ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config<Fixed: FixedPoints<pallas::Affine>> {
// Selector used for fixed-base scalar mul with short signed exponent.
q_mul_fixed_short: Selector,
super_config: super::Config<Fixed>,
}
impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
pub(crate) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
super_config: super::Config<Fixed>,
) -> Self {
let config = Self {
q_mul_fixed_short: meta.selector(),
super_config,
};
config.create_gate(meta);
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
meta.create_gate("Short fixed-base mul gate", |meta| {
let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short);
let y_p = meta.query_advice(self.super_config.y_p, Rotation::cur());
let y_a = meta.query_advice(self.super_config.add_config.y_qr, Rotation::cur());
// z_21
let last_window = meta.query_advice(self.super_config.u, Rotation::cur());
let sign = meta.query_advice(self.super_config.window, Rotation::cur());
let one = Expression::Constant(pallas::Base::one());
// Check that last window is either 0 or 1.
let last_window_check = bool_check(last_window);
// Check that sign is either 1 or -1.
let sign_check = sign.clone() * sign.clone() - one;
// `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude.
// We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign.
// Check that the final `y_p = y_a` or `y_p = -y_a`
let y_check = (y_p.clone() - y_a.clone()) * (y_p.clone() + y_a.clone());
// Check that the correct sign is witnessed s.t. sign * y_p = y_a
let negation_check = sign * y_p - y_a;
array::IntoIter::new([
("last_window_check", last_window_check),
("sign_check", sign_check),
("y_check", y_check),
("negation_check", negation_check),
])
.map(move |(name, poly)| (name, q_mul_fixed_short.clone() * poly))
});
}
fn decompose(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
magnitude_sign: MagnitudeSign,
) -> Result<EccScalarFixedShort, Error> {
let (magnitude, sign) = magnitude_sign;
// Decompose magnitude
let running_sum = self.super_config.running_sum_config.copy_decompose(
region,
offset,
magnitude.clone(),
true,
L_SCALAR_SHORT,
NUM_WINDOWS_SHORT,
)?;
Ok(EccScalarFixedShort {
magnitude,
sign,
running_sum: (*running_sum).as_slice().try_into().unwrap(),
})
}
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
magnitude_sign: MagnitudeSign,
base: &<Fixed as FixedPoints<pallas::Affine>>::ShortScalar,
) -> Result<(EccPoint, EccScalarFixedShort), Error>
where
<Fixed as FixedPoints<pallas::Affine>>::ShortScalar:
super::super::FixedPoint<pallas::Affine>,
{
let (scalar, acc, mul_b) = layouter.assign_region(
|| "Short fixed-base mul (incomplete addition)",
|mut region| {
let offset = 0;
// Decompose the scalar
let scalar = self.decompose(&mut region, offset, magnitude_sign.clone())?;
let (acc, mul_b) = self
.super_config
.assign_region_inner::<_, NUM_WINDOWS_SHORT>(
&mut region,
offset,
&(&scalar).into(),
base,
self.super_config.running_sum_config.q_range_check,
)?;
Ok((scalar, acc, mul_b))
},
)?;
// Last window
let result = layouter.assign_region(
|| "Short fixed-base mul (most significant word)",
|mut region| {
let offset = 0;
// Add to the cumulative sum to get `[magnitude]B`.
let magnitude_mul = self.super_config.add_config.assign_region(
&mul_b.clone().into(),
&acc.clone().into(),
offset,
&mut region,
)?;
// Increase offset by 1 after complete addition
let offset = offset + 1;
// Copy sign to `window` column
let sign = scalar.sign.copy_advice(
|| "sign",
&mut region,
self.super_config.window,
offset,
)?;
// Copy last window to `u` column.
// (Although the last window is not a `u` value; we are copying it into the `u`
// column because there is an available cell there.)
let z_21 = scalar.running_sum[21].clone();
z_21.copy_advice(|| "last_window", &mut region, self.super_config.u, offset)?;
// Conditionally negate `y`-coordinate
let y_val = if let Some(sign) = sign.value() {
if sign == &-pallas::Base::one() {
magnitude_mul.y.value().cloned().map(|y: pallas::Base| -y)
} else {
magnitude_mul.y.value().cloned()
}
} else {
None
};
// Enable mul_fixed_short selector on final row
self.q_mul_fixed_short.enable(&mut region, offset)?;
// Assign final `y` to `y_p` column and return final point
let y_var = region.assign_advice(
|| "y_var",
self.super_config.y_p,
offset,
|| y_val.ok_or(Error::Synthesis),
)?;
Ok(EccPoint {
x: magnitude_mul.x,
y: y_var,
})
},
)?;
#[cfg(test)]
// Check that the correct multiple is obtained.
// This inlined test is only done for valid 64-bit magnitudes
// and valid +/- 1 signs.
// Invalid values result in constraint failures which are
// tested at the circuit-level.
{
use super::super::FixedPoint;
use group::{ff::PrimeField, Curve};
if let (Some(magnitude), Some(sign)) = (scalar.magnitude.value(), scalar.sign.value()) {
let magnitude_is_valid = magnitude <= &pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64);
let sign_is_valid = sign * sign == pallas::Base::one();
if magnitude_is_valid && sign_is_valid {
let scalar = scalar.magnitude.value().zip(scalar.sign.value()).map(
|(magnitude, sign)| {
// Move magnitude from base field into scalar field (which always fits
// for Pallas).
let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap();
let sign = if sign == &pallas::Base::one() {
pallas::Scalar::one()
} else {
-pallas::Scalar::one()
};
magnitude * sign
},
);
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
}
}
}
Ok((result, scalar))
}
}
#[cfg(test)]
pub mod tests {
use group::{ff::PrimeField, Curve};
use halo2::{
arithmetic::CurveAffine,
circuit::{AssignedCell, Chip, Layouter},
plonk::{Any, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::circuit::gadget::{
ecc::{
chip::{EccChip, FixedPoint, MagnitudeSign},
FixedPointShort, NonIdentityPoint, Point,
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use crate::constants::{OrchardFixedBases, ValueCommitV};
#[allow(clippy::op_ref)]
pub fn test_mul_fixed_short(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
// value_commit_v
let base_val = ValueCommitV.generator();
let value_commit_v = FixedPointShort::from_inner(chip.clone(), ValueCommitV);
fn load_magnitude_sign(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
magnitude: pallas::Base,
sign: pallas::Base,
) -> Result<MagnitudeSign, Error> {
let column = chip.config().advices[0];
let magnitude =
chip.load_private(layouter.namespace(|| "magnitude"), column, Some(magnitude))?;
let sign = chip.load_private(layouter.namespace(|| "sign"), column, Some(sign))?;
Ok((magnitude, sign))
}
fn constrain_equal_non_id(
chip: EccChip<OrchardFixedBases>,
mut layouter: impl Layouter<pallas::Base>,
base_val: pallas::Affine,
scalar_val: pallas::Scalar,
result: Point<pallas::Affine, EccChip<OrchardFixedBases>>,
) -> Result<(), Error> {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
let magnitude_signs = [
("random [a]B", pallas::Base::from(rand::random::<u64>()), {
let mut random_sign = pallas::Base::one();
if rand::random::<bool>() {
random_sign = -random_sign;
}
random_sign
}),
(
"[2^64 - 1]B",
pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64),
pallas::Base::one(),
),
(
"-[2^64 - 1]B",
pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64),
-pallas::Base::one(),
),
// There is a single canonical sequence of window values for which a doubling occurs on the last step:
// 1333333333333333333334 in octal.
// [0xB6DB_6DB6_DB6D_B6DC] B
(
"mul_with_double",
pallas::Base::from(0xB6DB_6DB6_DB6D_B6DCu64),
pallas::Base::one(),
),
(
"mul_with_double negative",
pallas::Base::from(0xB6DB_6DB6_DB6D_B6DCu64),
-pallas::Base::one(),
),
];
for (name, magnitude, sign) in magnitude_signs.iter() {
let (result, _) = {
let magnitude_sign = load_magnitude_sign(
chip.clone(),
layouter.namespace(|| *name),
*magnitude,
*sign,
)?;
value_commit_v.mul(layouter.namespace(|| *name), magnitude_sign)?
};
// Move from base field into scalar field
let scalar = {
let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap();
let sign = if *sign == pallas::Base::one() {
pallas::Scalar::one()
} else {
-pallas::Scalar::one()
};
magnitude * sign
};
constrain_equal_non_id(
chip.clone(),
layouter.namespace(|| *name),
base_val,
scalar,
result,
)?;
}
let zero_magnitude_signs = [
("mul by +zero", pallas::Base::zero(), pallas::Base::one()),
("mul by -zero", pallas::Base::zero(), -pallas::Base::one()),
];
for (name, magnitude, sign) in zero_magnitude_signs.iter() {
let (result, _) = {
let magnitude_sign = load_magnitude_sign(
chip.clone(),
layouter.namespace(|| *name),
*magnitude,
*sign,
)?;
value_commit_v.mul(layouter.namespace(|| *name), magnitude_sign)?
};
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
Ok(())
}
#[test]
fn invalid_magnitude_sign() {
use crate::circuit::gadget::{
ecc::chip::{EccConfig, FixedPoint},
utilities::UtilitiesInstructions,
};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::{FailureLocation, MockProver, VerifyFailure},
plonk::{Circuit, ConstraintSystem, Error},
};
#[derive(Default)]
struct MyCircuit {
magnitude: Option<pallas::Base>,
sign: Option<pallas::Base>,
// For test checking
magnitude_error: Option<pallas::Base>,
}
impl UtilitiesInstructions<pallas::Base> for MyCircuit {
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl Circuit<pallas::Base> for MyCircuit {
type Config = EccConfig<OrchardFixedBases>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
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(),
];
let lookup_table = meta.lookup_table_column();
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(),
];
// Shared fixed column for loading constants
let constants = meta.fixed_column();
meta.enable_constant(constants);
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table);
EccChip::<OrchardFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
let column = config.advices[0];
let short_config = config.mul_fixed_short;
let magnitude_sign = {
let magnitude = self.load_private(
layouter.namespace(|| "load magnitude"),
column,
self.magnitude,
)?;
let sign =
self.load_private(layouter.namespace(|| "load sign"), column, self.sign)?;
(magnitude, sign)
};
short_config.assign(layouter, magnitude_sign, &ValueCommitV)?;
Ok(())
}
}
// Copied from halo2::dev::util
fn format_value(v: pallas::Base) -> String {
use ff::Field;
if v.is_zero_vartime() {
"0".into()
} else if v == pallas::Base::one() {
"1".into()
} else if v == -pallas::Base::one() {
"-1".into()
} else {
// Format value as hex.
let s = format!("{:?}", v);
// Remove leading zeroes.
let s = s.strip_prefix("0x").unwrap();
let s = s.trim_start_matches('0');
format!("0x{}", s)
}
}
// Magnitude larger than 64 bits should fail
{
let circuits = [
// 2^64
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 64)),
sign: Some(pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 1)),
},
// -2^64
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 64)),
sign: Some(-pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 1)),
},
// 2^66
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 66)),
sign: Some(pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 3)),
},
// -2^66
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 66)),
sign: Some(-pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 3)),
},
// 2^254
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 127).square()),
sign: Some(pallas::Base::one()),
magnitude_error: Some(
pallas::Base::from_u128(1 << 95).square() * pallas::Base::from(2),
),
},
// -2^254
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 127).square()),
sign: Some(-pallas::Base::one()),
magnitude_error: Some(
pallas::Base::from_u128(1 << 95).square() * pallas::Base::from(2),
),
},
];
for circuit in circuits.iter() {
let prover = MockProver::<pallas::Base>::run(11, circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![
VerifyFailure::ConstraintNotSatisfied {
constraint: (
(17, "Short fixed-base mul gate").into(),
0,
"last_window_check"
)
.into(),
location: FailureLocation::InRegion {
region: (3, "Short fixed-base mul (most significant word)").into(),
offset: 1,
},
cell_values: vec![(
((Any::Advice, 5).into(), 0).into(),
format_value(circuit.magnitude_error.unwrap()),
)],
},
VerifyFailure::Permutation {
column: (Any::Fixed, 9).into(),
row: 0
},
VerifyFailure::Permutation {
column: (Any::Advice, 4).into(),
row: 24
}
])
);
}
}
// Sign that is not +/- 1 should fail
{
let magnitude_u64 = rand::random::<u64>();
let circuit = MyCircuit {
magnitude: Some(pallas::Base::from(magnitude_u64)),
sign: Some(pallas::Base::zero()),
magnitude_error: None,
};
let negation_check_y = {
*(ValueCommitV.generator() * pallas::Scalar::from(magnitude_u64))
.to_affine()
.coordinates()
.unwrap()
.y()
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![
VerifyFailure::ConstraintNotSatisfied {
constraint: ((17, "Short fixed-base mul gate").into(), 1, "sign_check")
.into(),
location: FailureLocation::InRegion {
region: (3, "Short fixed-base mul (most significant word)").into(),
offset: 1,
},
cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())],
},
VerifyFailure::ConstraintNotSatisfied {
constraint: (
(17, "Short fixed-base mul gate").into(),
3,
"negation_check"
)
.into(),
location: FailureLocation::InRegion {
region: (3, "Short fixed-base mul (most significant word)").into(),
offset: 1,
},
cell_values: vec![
(
((Any::Advice, 1).into(), 0).into(),
format_value(negation_check_y),
),
(
((Any::Advice, 3).into(), 0).into(),
format_value(negation_check_y),
),
(((Any::Advice, 4).into(), 0).into(), "0".to_string()),
],
}
])
);
}
}
}

View File

@ -1,171 +0,0 @@
use super::{EccPoint, NonIdentityEccPoint};
use group::prime::PrimeCurveAffine;
use halo2::{
circuit::{AssignedCell, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, VirtualCells},
poly::Rotation,
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
type Coordinates = (
AssignedCell<pallas::Base, pallas::Base>,
AssignedCell<pallas::Base, pallas::Base>,
);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
q_point: Selector,
q_point_non_id: Selector,
// x-coordinate
pub x: Column<Advice>,
// y-coordinate
pub y: Column<Advice>,
}
impl Config {
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
x: Column<Advice>,
y: Column<Advice>,
) -> Self {
let config = Self {
q_point: meta.selector(),
q_point_non_id: meta.selector(),
x,
y,
};
config.create_gate(meta);
config
}
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
let curve_eqn = |meta: &mut VirtualCells<pallas::Base>| {
let x = meta.query_advice(self.x, Rotation::cur());
let y = meta.query_advice(self.y, Rotation::cur());
// y^2 = x^3 + b
y.square() - (x.clone().square() * x) - Expression::Constant(pallas::Affine::b())
};
meta.create_gate("witness point", |meta| {
// Check that the point being witnessed is either:
// - the identity, which is mapped to (0, 0) in affine coordinates; or
// - a valid curve point y^2 = x^3 + b, where b = 5 in the Pallas equation
let q_point = meta.query_selector(self.q_point);
let x = meta.query_advice(self.x, Rotation::cur());
let y = meta.query_advice(self.y, Rotation::cur());
vec![
("x == 0 v on_curve", q_point.clone() * x * curve_eqn(meta)),
("y == 0 v on_curve", q_point * y * curve_eqn(meta)),
]
});
meta.create_gate("witness non-identity point", |meta| {
// Check that the point being witnessed is a valid curve point y^2 = x^3 + b,
// where b = 5 in the Pallas equation
let q_point_non_id = meta.query_selector(self.q_point_non_id);
vec![("on_curve", q_point_non_id * curve_eqn(meta))]
});
}
fn assign_xy(
&self,
value: Option<(pallas::Base, pallas::Base)>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<Coordinates, Error> {
// Assign `x` value
let x_val = value.map(|value| value.0);
let x_var =
region.assign_advice(|| "x", self.x, offset, || x_val.ok_or(Error::Synthesis))?;
// Assign `y` value
let y_val = value.map(|value| value.1);
let y_var =
region.assign_advice(|| "y", self.y, offset, || y_val.ok_or(Error::Synthesis))?;
Ok((x_var, y_var))
}
/// Assigns a point that can be the identity.
pub(super) fn point(
&self,
value: Option<pallas::Affine>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> {
// Enable `q_point` selector
self.q_point.enable(region, offset)?;
let value = value.map(|value| {
// Map the identity to (0, 0).
if value == pallas::Affine::identity() {
(pallas::Base::zero(), pallas::Base::zero())
} else {
let value = value.coordinates().unwrap();
(*value.x(), *value.y())
}
});
self.assign_xy(value, offset, region)
.map(|(x, y)| EccPoint { x, y })
}
/// Assigns a non-identity point.
pub(super) fn point_non_id(
&self,
value: Option<pallas::Affine>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<NonIdentityEccPoint, Error> {
// Enable `q_point_non_id` selector
self.q_point_non_id.enable(region, offset)?;
if let Some(value) = value {
// Return an error if the point is the identity.
if value == pallas::Affine::identity() {
return Err(Error::Synthesis);
}
};
let value = value.map(|value| {
let value = value.coordinates().unwrap();
(*value.x(), *value.y())
});
self.assign_xy(value, offset, region)
.map(|(x, y)| NonIdentityEccPoint { x, y })
}
}
#[cfg(test)]
pub mod tests {
use halo2::circuit::Layouter;
use pasta_curves::pallas;
use super::*;
use crate::circuit::gadget::ecc::{EccInstructions, NonIdentityPoint};
pub fn test_witness_non_id<
EccChip: EccInstructions<pallas::Affine> + Clone + Eq + std::fmt::Debug,
>(
chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
) {
// Witnessing the identity should return an error.
NonIdentityPoint::new(
chip,
layouter.namespace(|| "witness identity"),
Some(pallas::Affine::identity()),
)
.expect_err("witnessing 𝒪 should return an error");
}
}

View File

@ -1,298 +0,0 @@
//! Gadget and chips for the Poseidon algebraic hash function.
use std::array;
use std::convert::TryInto;
use std::fmt;
use std::marker::PhantomData;
use group::ff::Field;
use halo2::{
arithmetic::FieldExt,
circuit::{AssignedCell, Chip, Layouter},
plonk::Error,
};
mod pow5;
pub use pow5::{Pow5Chip, Pow5Config, StateWord};
use crate::primitives::poseidon::{
Absorbing, ConstantLength, Domain, Spec, SpongeMode, Squeezing, State,
};
/// A word from the padded input to a Poseidon sponge.
#[derive(Clone, Debug)]
pub enum PaddedWord<F: Field> {
/// A message word provided by the prover.
Message(AssignedCell<F, F>),
/// A padding word, that will be fixed in the circuit parameters.
Padding(F),
}
/// The set of circuit instructions required to use the Poseidon permutation.
pub trait PoseidonInstructions<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>:
Chip<F>
{
/// Variable representing the word over which the Poseidon permutation operates.
type Word: Clone + fmt::Debug + From<AssignedCell<F, F>> + Into<AssignedCell<F, F>>;
/// Applies the Poseidon permutation to the given state.
fn permute(
&self,
layouter: &mut impl Layouter<F>,
initial_state: &State<Self::Word, T>,
) -> Result<State<Self::Word, T>, Error>;
}
/// The set of circuit instructions required to use the [`Sponge`] and [`Hash`] gadgets.
///
/// [`Hash`]: self::Hash
pub trait PoseidonSpongeInstructions<
F: FieldExt,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
>: PoseidonInstructions<F, S, T, RATE>
{
/// Returns the initial empty state for the given domain.
fn initial_state(&self, layouter: &mut impl Layouter<F>)
-> Result<State<Self::Word, T>, Error>;
/// Adds the given input to the state.
fn add_input(
&self,
layouter: &mut impl Layouter<F>,
initial_state: &State<Self::Word, T>,
input: &Absorbing<PaddedWord<F>, RATE>,
) -> Result<State<Self::Word, T>, Error>;
/// Extracts sponge output from the given state.
fn get_output(state: &State<Self::Word, T>) -> Squeezing<Self::Word, RATE>;
}
/// A word over which the Poseidon permutation operates.
#[derive(Debug)]
pub struct Word<
F: FieldExt,
PoseidonChip: PoseidonInstructions<F, S, T, RATE>,
S: Spec<F, T, RATE>,
const T: usize,
const RATE: usize,
> {
inner: PoseidonChip::Word,
}
impl<
F: FieldExt,
PoseidonChip: PoseidonInstructions<F, S, T, RATE>,
S: Spec<F, T, RATE>,
const T: usize,
const RATE: usize,
> Word<F, PoseidonChip, S, T, RATE>
{
/// The word contained in this gadget.
pub fn inner(&self) -> PoseidonChip::Word {
self.inner.clone()
}
/// Construct a [`Word`] gadget from the inner word.
pub fn from_inner(inner: PoseidonChip::Word) -> Self {
Self { inner }
}
}
fn poseidon_sponge<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
>(
chip: &PoseidonChip,
mut layouter: impl Layouter<F>,
state: &mut State<PoseidonChip::Word, T>,
input: Option<&Absorbing<PaddedWord<F>, RATE>>,
) -> Result<Squeezing<PoseidonChip::Word, RATE>, Error> {
if let Some(input) = input {
*state = chip.add_input(&mut layouter, state, input)?;
}
*state = chip.permute(&mut layouter, state)?;
Ok(PoseidonChip::get_output(state))
}
/// A Poseidon sponge.
#[derive(Debug)]
pub struct Sponge<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
M: SpongeMode,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> {
chip: PoseidonChip,
mode: M,
state: State<PoseidonChip::Word, T>,
_marker: PhantomData<D>,
}
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> Sponge<F, PoseidonChip, S, Absorbing<PaddedWord<F>, RATE>, D, T, RATE>
{
/// Constructs a new duplex sponge for the given Poseidon specification.
pub fn new(chip: PoseidonChip, mut layouter: impl Layouter<F>) -> Result<Self, Error> {
chip.initial_state(&mut layouter).map(|state| Sponge {
chip,
mode: Absorbing(
(0..RATE)
.map(|_| None)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
),
state,
_marker: PhantomData::default(),
})
}
/// Absorbs an element into the sponge.
pub fn absorb(
&mut self,
mut layouter: impl Layouter<F>,
value: PaddedWord<F>,
) -> Result<(), Error> {
for entry in self.mode.0.iter_mut() {
if entry.is_none() {
*entry = Some(value);
return Ok(());
}
}
// We've already absorbed as many elements as we can
let _ = poseidon_sponge(
&self.chip,
layouter.namespace(|| "PoseidonSponge"),
&mut self.state,
Some(&self.mode),
)?;
self.mode = Absorbing::init_with(value);
Ok(())
}
/// Transitions the sponge into its squeezing state.
#[allow(clippy::type_complexity)]
pub fn finish_absorbing(
mut self,
mut layouter: impl Layouter<F>,
) -> Result<Sponge<F, PoseidonChip, S, Squeezing<PoseidonChip::Word, RATE>, D, T, RATE>, Error>
{
let mode = poseidon_sponge(
&self.chip,
layouter.namespace(|| "PoseidonSponge"),
&mut self.state,
Some(&self.mode),
)?;
Ok(Sponge {
chip: self.chip,
mode,
state: self.state,
_marker: PhantomData::default(),
})
}
}
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> Sponge<F, PoseidonChip, S, Squeezing<PoseidonChip::Word, RATE>, D, T, RATE>
{
/// Squeezes an element from the sponge.
pub fn squeeze(&mut self, mut layouter: impl Layouter<F>) -> Result<AssignedCell<F, F>, Error> {
loop {
for entry in self.mode.0.iter_mut() {
if let Some(inner) = entry.take() {
return Ok(inner.into());
}
}
// We've already squeezed out all available elements
self.mode = poseidon_sponge(
&self.chip,
layouter.namespace(|| "PoseidonSponge"),
&mut self.state,
None,
)?;
}
}
}
/// A Poseidon hash function, built around a sponge.
#[derive(Debug)]
pub struct Hash<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> {
sponge: Sponge<F, PoseidonChip, S, Absorbing<PaddedWord<F>, RATE>, D, T, RATE>,
}
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> Hash<F, PoseidonChip, S, D, T, RATE>
{
/// Initializes a new hasher.
pub fn init(chip: PoseidonChip, layouter: impl Layouter<F>) -> Result<Self, Error> {
Sponge::new(chip, layouter).map(|sponge| Hash { sponge })
}
}
impl<
F: FieldExt,
PoseidonChip: PoseidonSpongeInstructions<F, S, ConstantLength<L>, T, RATE>,
S: Spec<F, T, RATE>,
const T: usize,
const RATE: usize,
const L: usize,
> Hash<F, PoseidonChip, S, ConstantLength<L>, T, RATE>
{
/// Hashes the given input.
pub fn hash(
mut self,
mut layouter: impl Layouter<F>,
message: [AssignedCell<F, F>; L],
) -> Result<AssignedCell<F, F>, Error> {
for (i, value) in array::IntoIter::new(message)
.map(PaddedWord::Message)
.chain(<ConstantLength<L> as Domain<F, RATE>>::padding(L).map(PaddedWord::Padding))
.enumerate()
{
self.sponge
.absorb(layouter.namespace(|| format!("absorb_{}", i)), value)?;
}
self.sponge
.finish_absorbing(layouter.namespace(|| "finish absorbing"))?
.squeeze(layouter.namespace(|| "squeeze"))
}
}

View File

@ -1,890 +0,0 @@
use std::convert::TryInto;
use std::iter;
use halo2::{
arithmetic::FieldExt,
circuit::{AssignedCell, Cell, Chip, Layouter, Region},
plonk::{Advice, Any, Column, ConstraintSystem, Error, Expression, Fixed, Selector},
poly::Rotation,
};
use super::{PaddedWord, PoseidonInstructions, PoseidonSpongeInstructions};
use crate::primitives::poseidon::{Domain, Mds, Spec, State};
use crate::{
circuit::gadget::utilities::Var,
primitives::poseidon::{Absorbing, Squeezing},
};
/// Configuration for a [`Pow5Chip`].
#[derive(Clone, Debug)]
pub struct Pow5Config<F: FieldExt, const WIDTH: usize, const RATE: usize> {
pub(in crate::circuit) state: [Column<Advice>; WIDTH],
partial_sbox: Column<Advice>,
rc_a: [Column<Fixed>; WIDTH],
rc_b: [Column<Fixed>; WIDTH],
s_full: Selector,
s_partial: Selector,
s_pad_and_add: Selector,
half_full_rounds: usize,
half_partial_rounds: usize,
alpha: [u64; 4],
round_constants: Vec<[F; WIDTH]>,
m_reg: Mds<F, WIDTH>,
m_inv: Mds<F, WIDTH>,
}
/// A Poseidon chip using an $x^5$ S-Box.
///
/// The chip is implemented using a single round per row for full rounds, and two rounds
/// per row for partial rounds.
#[derive(Debug)]
pub struct Pow5Chip<F: FieldExt, const WIDTH: usize, const RATE: usize> {
config: Pow5Config<F, WIDTH, RATE>,
}
impl<F: FieldExt, const WIDTH: usize, const RATE: usize> Pow5Chip<F, WIDTH, RATE> {
/// Configures this chip for use in a circuit.
///
/// # Side-effects
///
/// All columns in `state` will be equality-enabled.
//
// TODO: Does the rate need to be hard-coded here, or only the width? It probably
// needs to be known wherever we implement the hashing gadget, but it isn't strictly
// necessary for the permutation.
pub fn configure<S: Spec<F, WIDTH, RATE>>(
meta: &mut ConstraintSystem<F>,
state: [Column<Advice>; WIDTH],
partial_sbox: Column<Advice>,
rc_a: [Column<Fixed>; WIDTH],
rc_b: [Column<Fixed>; WIDTH],
) -> Pow5Config<F, WIDTH, RATE> {
assert_eq!(RATE, WIDTH - 1);
// Generate constants for the Poseidon permutation.
// This gadget requires R_F and R_P to be even.
assert!(S::full_rounds() & 1 == 0);
assert!(S::partial_rounds() & 1 == 0);
let half_full_rounds = S::full_rounds() / 2;
let half_partial_rounds = S::partial_rounds() / 2;
let (round_constants, m_reg, m_inv) = S::constants();
// This allows state words to be initialized (by constraining them equal to fixed
// values), and used in a permutation from an arbitrary region. rc_a is used in
// every permutation round, while rc_b is empty in the initial and final full
// rounds, so we use rc_b as "scratch space" for fixed values (enabling potential
// layouter optimisations).
for column in iter::empty()
.chain(state.iter().cloned().map(Column::<Any>::from))
.chain(rc_b.iter().cloned().map(Column::<Any>::from))
{
meta.enable_equality(column);
}
let s_full = meta.selector();
let s_partial = meta.selector();
let s_pad_and_add = meta.selector();
let alpha = [5, 0, 0, 0];
let pow_5 = |v: Expression<F>| {
let v2 = v.clone() * v.clone();
v2.clone() * v2 * v
};
meta.create_gate("full round", |meta| {
let s_full = meta.query_selector(s_full);
(0..WIDTH)
.map(|next_idx| {
let state_next = meta.query_advice(state[next_idx], Rotation::next());
let expr = (0..WIDTH)
.map(|idx| {
let state_cur = meta.query_advice(state[idx], Rotation::cur());
let rc_a = meta.query_fixed(rc_a[idx], Rotation::cur());
pow_5(state_cur + rc_a) * m_reg[next_idx][idx]
})
.reduce(|acc, term| acc + term)
.expect("WIDTH > 0");
s_full.clone() * (expr - state_next)
})
.collect::<Vec<_>>()
});
meta.create_gate("partial rounds", |meta| {
let cur_0 = meta.query_advice(state[0], Rotation::cur());
let mid_0 = meta.query_advice(partial_sbox, Rotation::cur());
let rc_a0 = meta.query_fixed(rc_a[0], Rotation::cur());
let rc_b0 = meta.query_fixed(rc_b[0], Rotation::cur());
let s_partial = meta.query_selector(s_partial);
use halo2::plonk::VirtualCells;
let mid = |idx: usize, meta: &mut VirtualCells<F>| {
let mid = mid_0.clone() * m_reg[idx][0];
(1..WIDTH).fold(mid, |acc, cur_idx| {
let cur = meta.query_advice(state[cur_idx], Rotation::cur());
let rc_a = meta.query_fixed(rc_a[cur_idx], Rotation::cur());
acc + (cur + rc_a) * m_reg[idx][cur_idx]
})
};
let next = |idx: usize, meta: &mut VirtualCells<F>| {
(0..WIDTH)
.map(|next_idx| {
let next = meta.query_advice(state[next_idx], Rotation::next());
next * m_inv[idx][next_idx]
})
.reduce(|acc, next| acc + next)
.expect("WIDTH > 0")
};
let partial_round_linear = |idx: usize, meta: &mut VirtualCells<F>| {
let expr = {
let rc_b = meta.query_fixed(rc_b[idx], Rotation::cur());
mid(idx, meta) + rc_b - next(idx, meta)
};
s_partial.clone() * expr
};
std::iter::empty()
// state[0] round a
.chain(Some(
s_partial.clone() * (pow_5(cur_0 + rc_a0) - mid_0.clone()),
))
// state[0] round b
.chain(Some(
s_partial.clone() * (pow_5(mid(0, meta) + rc_b0) - next(0, meta)),
))
.chain((1..WIDTH).map(|idx| partial_round_linear(idx, meta)))
.collect::<Vec<_>>()
});
meta.create_gate("pad-and-add", |meta| {
let initial_state_rate = meta.query_advice(state[RATE], Rotation::prev());
let output_state_rate = meta.query_advice(state[RATE], Rotation::next());
let s_pad_and_add = meta.query_selector(s_pad_and_add);
let pad_and_add = |idx: usize| {
let initial_state = meta.query_advice(state[idx], Rotation::prev());
let input = meta.query_advice(state[idx], Rotation::cur());
let output_state = meta.query_advice(state[idx], Rotation::next());
// We pad the input by storing the required padding in fixed columns and
// then constraining the corresponding input columns to be equal to it.
s_pad_and_add.clone() * (initial_state + input - output_state)
};
(0..RATE)
.map(pad_and_add)
// The capacity element is never altered by the input.
.chain(Some(
s_pad_and_add.clone() * (initial_state_rate - output_state_rate),
))
.collect::<Vec<_>>()
});
Pow5Config {
state,
partial_sbox,
rc_a,
rc_b,
s_full,
s_partial,
s_pad_and_add,
half_full_rounds,
half_partial_rounds,
alpha,
round_constants,
m_reg,
m_inv,
}
}
/// Construct a [`Pow5Chip`].
pub fn construct(config: Pow5Config<F, WIDTH, RATE>) -> Self {
Pow5Chip { config }
}
}
impl<F: FieldExt, const WIDTH: usize, const RATE: usize> Chip<F> for Pow5Chip<F, WIDTH, RATE> {
type Config = Pow5Config<F, WIDTH, RATE>;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<F: FieldExt, S: Spec<F, WIDTH, RATE>, const WIDTH: usize, const RATE: usize>
PoseidonInstructions<F, S, WIDTH, RATE> for Pow5Chip<F, WIDTH, RATE>
{
type Word = StateWord<F>;
fn permute(
&self,
layouter: &mut impl Layouter<F>,
initial_state: &State<Self::Word, WIDTH>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
layouter.assign_region(
|| "permute state",
|mut region| {
// Load the initial state into this region.
let state = Pow5State::load(&mut region, config, initial_state)?;
let state = (0..config.half_full_rounds).fold(Ok(state), |res, r| {
res.and_then(|state| state.full_round(&mut region, config, r, r))
})?;
let state = (0..config.half_partial_rounds).fold(Ok(state), |res, r| {
res.and_then(|state| {
state.partial_round(
&mut region,
config,
config.half_full_rounds + 2 * r,
config.half_full_rounds + r,
)
})
})?;
let state = (0..config.half_full_rounds).fold(Ok(state), |res, r| {
res.and_then(|state| {
state.full_round(
&mut region,
config,
config.half_full_rounds + 2 * config.half_partial_rounds + r,
config.half_full_rounds + config.half_partial_rounds + r,
)
})
})?;
Ok(state.0)
},
)
}
}
impl<
F: FieldExt,
S: Spec<F, WIDTH, RATE>,
D: Domain<F, RATE>,
const WIDTH: usize,
const RATE: usize,
> PoseidonSpongeInstructions<F, S, D, WIDTH, RATE> for Pow5Chip<F, WIDTH, RATE>
{
fn initial_state(
&self,
layouter: &mut impl Layouter<F>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
let state = layouter.assign_region(
|| format!("initial state for domain {}", D::name()),
|mut region| {
let mut state = Vec::with_capacity(WIDTH);
let mut load_state_word = |i: usize, value: F| -> Result<_, Error> {
let var = region.assign_advice_from_constant(
|| format!("state_{}", i),
config.state[i],
0,
value,
)?;
state.push(StateWord(var));
Ok(())
};
for i in 0..RATE {
load_state_word(i, F::zero())?;
}
load_state_word(RATE, D::initial_capacity_element())?;
Ok(state)
},
)?;
Ok(state.try_into().unwrap())
}
fn add_input(
&self,
layouter: &mut impl Layouter<F>,
initial_state: &State<Self::Word, WIDTH>,
input: &Absorbing<PaddedWord<F>, RATE>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
layouter.assign_region(
|| format!("add input for domain {}", D::name()),
|mut region| {
config.s_pad_and_add.enable(&mut region, 1)?;
// Load the initial state into this region.
let load_state_word = |i: usize| {
initial_state[i]
.0
.copy_advice(
|| format!("load state_{}", i),
&mut region,
config.state[i],
0,
)
.map(StateWord)
};
let initial_state: Result<Vec<_>, Error> =
(0..WIDTH).map(load_state_word).collect();
let initial_state = initial_state?;
// Load the input into this region.
let load_input_word = |i: usize| {
let constraint_var = match input.0[i].clone() {
Some(PaddedWord::Message(word)) => word,
Some(PaddedWord::Padding(padding_value)) => region.assign_fixed(
|| format!("load pad_{}", i),
config.rc_b[i],
1,
|| Ok(padding_value),
)?,
_ => panic!("Input is not padded"),
};
constraint_var
.copy_advice(
|| format!("load input_{}", i),
&mut region,
config.state[i],
1,
)
.map(StateWord)
};
let input: Result<Vec<_>, Error> = (0..RATE).map(load_input_word).collect();
let input = input?;
// Constrain the output.
let constrain_output_word = |i: usize| {
let value = initial_state[i].0.value().and_then(|initial_word| {
input
.get(i)
.map(|word| word.0.value().cloned())
// The capacity element is never altered by the input.
.unwrap_or_else(|| Some(F::zero()))
.map(|input_word| *initial_word + input_word)
});
region
.assign_advice(
|| format!("load output_{}", i),
config.state[i],
2,
|| value.ok_or(Error::Synthesis),
)
.map(StateWord)
};
let output: Result<Vec<_>, Error> = (0..WIDTH).map(constrain_output_word).collect();
output.map(|output| output.try_into().unwrap())
},
)
}
fn get_output(state: &State<Self::Word, WIDTH>) -> Squeezing<Self::Word, RATE> {
Squeezing(
state[..RATE]
.iter()
.map(|word| Some(word.clone()))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
)
}
}
/// A word in the Poseidon state.
#[derive(Clone, Debug)]
pub struct StateWord<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> From<StateWord<F>> for AssignedCell<F, F> {
fn from(state_word: StateWord<F>) -> AssignedCell<F, F> {
state_word.0
}
}
impl<F: FieldExt> From<AssignedCell<F, F>> for StateWord<F> {
fn from(cell_value: AssignedCell<F, F>) -> StateWord<F> {
StateWord(cell_value)
}
}
impl<F: FieldExt> Var<F> for StateWord<F> {
fn cell(&self) -> Cell {
self.0.cell()
}
fn value(&self) -> Option<F> {
self.0.value().cloned()
}
}
#[derive(Debug)]
struct Pow5State<F: FieldExt, const WIDTH: usize>([StateWord<F>; WIDTH]);
impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
fn full_round<const RATE: usize>(
self,
region: &mut Region<F>,
config: &Pow5Config<F, WIDTH, RATE>,
round: usize,
offset: usize,
) -> Result<Self, Error> {
Self::round(region, config, round, offset, config.s_full, |_| {
let q = self.0.iter().enumerate().map(|(idx, word)| {
word.0
.value()
.map(|v| *v + config.round_constants[round][idx])
});
let r: Option<Vec<F>> = q.map(|q| q.map(|q| q.pow(&config.alpha))).collect();
let m = &config.m_reg;
let state = m.iter().map(|m_i| {
r.as_ref().map(|r| {
r.iter()
.enumerate()
.fold(F::zero(), |acc, (j, r_j)| acc + m_i[j] * r_j)
})
});
Ok((round + 1, state.collect::<Vec<_>>().try_into().unwrap()))
})
}
fn partial_round<const RATE: usize>(
self,
region: &mut Region<F>,
config: &Pow5Config<F, WIDTH, RATE>,
round: usize,
offset: usize,
) -> Result<Self, Error> {
Self::round(region, config, round, offset, config.s_partial, |region| {
let m = &config.m_reg;
let p: Option<Vec<_>> = self.0.iter().map(|word| word.0.value().cloned()).collect();
let r: Option<Vec<_>> = p.map(|p| {
let r_0 = (p[0] + config.round_constants[round][0]).pow(&config.alpha);
let r_i = p[1..]
.iter()
.enumerate()
.map(|(i, p_i)| *p_i + config.round_constants[round][i + 1]);
std::iter::empty().chain(Some(r_0)).chain(r_i).collect()
});
region.assign_advice(
|| format!("round_{} partial_sbox", round),
config.partial_sbox,
offset,
|| r.as_ref().map(|r| r[0]).ok_or(Error::Synthesis),
)?;
let p_mid: Option<Vec<_>> = m
.iter()
.map(|m_i| {
r.as_ref().map(|r| {
m_i.iter()
.zip(r.iter())
.fold(F::zero(), |acc, (m_ij, r_j)| acc + *m_ij * r_j)
})
})
.collect();
// Load the second round constants.
let mut load_round_constant = |i: usize| {
region.assign_fixed(
|| format!("round_{} rc_{}", round + 1, i),
config.rc_b[i],
offset,
|| Ok(config.round_constants[round + 1][i]),
)
};
for i in 0..WIDTH {
load_round_constant(i)?;
}
let r_mid: Option<Vec<_>> = p_mid.map(|p| {
let r_0 = (p[0] + config.round_constants[round + 1][0]).pow(&config.alpha);
let r_i = p[1..]
.iter()
.enumerate()
.map(|(i, p_i)| *p_i + config.round_constants[round + 1][i + 1]);
std::iter::empty().chain(Some(r_0)).chain(r_i).collect()
});
let state: Vec<Option<_>> = m
.iter()
.map(|m_i| {
r_mid.as_ref().map(|r| {
m_i.iter()
.zip(r.iter())
.fold(F::zero(), |acc, (m_ij, r_j)| acc + *m_ij * r_j)
})
})
.collect();
Ok((round + 2, state.try_into().unwrap()))
})
}
fn load<const RATE: usize>(
region: &mut Region<F>,
config: &Pow5Config<F, WIDTH, RATE>,
initial_state: &State<StateWord<F>, WIDTH>,
) -> Result<Self, Error> {
let load_state_word = |i: usize| {
initial_state[i]
.0
.copy_advice(|| format!("load state_{}", i), region, config.state[i], 0)
.map(StateWord)
};
let state: Result<Vec<_>, _> = (0..WIDTH).map(load_state_word).collect();
state.map(|state| Pow5State(state.try_into().unwrap()))
}
fn round<const RATE: usize>(
region: &mut Region<F>,
config: &Pow5Config<F, WIDTH, RATE>,
round: usize,
offset: usize,
round_gate: Selector,
round_fn: impl FnOnce(&mut Region<F>) -> Result<(usize, [Option<F>; WIDTH]), Error>,
) -> Result<Self, Error> {
// Enable the required gate.
round_gate.enable(region, offset)?;
// Load the round constants.
let mut load_round_constant = |i: usize| {
region.assign_fixed(
|| format!("round_{} rc_{}", round, i),
config.rc_a[i],
offset,
|| Ok(config.round_constants[round][i]),
)
};
for i in 0..WIDTH {
load_round_constant(i)?;
}
// Compute the next round's state.
let (next_round, next_state) = round_fn(region)?;
let next_state_word = |i: usize| {
let value = next_state[i];
let var = region.assign_advice(
|| format!("round_{} state_{}", next_round, i),
config.state[i],
offset + 1,
|| value.ok_or(Error::Synthesis),
)?;
Ok(StateWord(var))
};
let next_state: Result<Vec<_>, _> = (0..WIDTH).map(next_state_word).collect();
next_state.map(|next_state| Pow5State(next_state.try_into().unwrap()))
}
}
#[cfg(test)]
mod tests {
use group::ff::{Field, PrimeField};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
pasta::Fp,
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::pallas;
use rand::rngs::OsRng;
use super::{PoseidonInstructions, Pow5Chip, Pow5Config, StateWord};
use crate::{
circuit::gadget::poseidon::Hash,
primitives::poseidon::{self, ConstantLength, P128Pow5T3 as OrchardNullifier, Spec},
};
use std::convert::TryInto;
use std::marker::PhantomData;
struct PermuteCircuit<S: Spec<Fp, WIDTH, RATE>, const WIDTH: usize, const RATE: usize>(
PhantomData<S>,
);
impl<S: Spec<Fp, WIDTH, RATE>, const WIDTH: usize, const RATE: usize> Circuit<Fp>
for PermuteCircuit<S, WIDTH, RATE>
{
type Config = Pow5Config<Fp, WIDTH, RATE>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
PermuteCircuit::<S, WIDTH, RATE>(PhantomData)
}
fn configure(meta: &mut ConstraintSystem<Fp>) -> Pow5Config<Fp, WIDTH, RATE> {
let state = (0..WIDTH).map(|_| meta.advice_column()).collect::<Vec<_>>();
let partial_sbox = meta.advice_column();
let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::<Vec<_>>();
let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::<Vec<_>>();
Pow5Chip::configure::<S>(
meta,
state.try_into().unwrap(),
partial_sbox,
rc_a.try_into().unwrap(),
rc_b.try_into().unwrap(),
)
}
fn synthesize(
&self,
config: Pow5Config<Fp, WIDTH, RATE>,
mut layouter: impl Layouter<Fp>,
) -> Result<(), Error> {
let initial_state = layouter.assign_region(
|| "prepare initial state",
|mut region| {
let state_word = |i: usize| {
let value = Some(Fp::from(i as u64));
let var = region.assign_advice(
|| format!("load state_{}", i),
config.state[i],
0,
|| value.ok_or(Error::Synthesis),
)?;
Ok(StateWord(var))
};
let state: Result<Vec<_>, Error> = (0..WIDTH).map(state_word).collect();
Ok(state?.try_into().unwrap())
},
)?;
let chip = Pow5Chip::construct(config.clone());
let final_state = <Pow5Chip<_, WIDTH, RATE> as PoseidonInstructions<
Fp,
S,
WIDTH,
RATE,
>>::permute(&chip, &mut layouter, &initial_state)?;
// For the purpose of this test, compute the real final state inline.
let mut expected_final_state = (0..WIDTH)
.map(|idx| Fp::from(idx as u64))
.collect::<Vec<_>>()
.try_into()
.unwrap();
let (round_constants, mds, _) = S::constants();
poseidon::permute::<_, S, WIDTH, RATE>(
&mut expected_final_state,
&mds,
&round_constants,
);
layouter.assign_region(
|| "constrain final state",
|mut region| {
let mut final_state_word = |i: usize| {
let var = region.assign_advice(
|| format!("load final_state_{}", i),
config.state[i],
0,
|| Ok(expected_final_state[i]),
)?;
region.constrain_equal(final_state[i].0.cell(), var.cell())
};
for i in 0..(WIDTH) {
final_state_word(i)?;
}
Ok(())
},
)
}
}
#[test]
fn poseidon_permute() {
let k = 6;
let circuit = PermuteCircuit::<OrchardNullifier, 3, 2>(PhantomData);
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
struct HashCircuit<
S: Spec<Fp, WIDTH, RATE>,
const WIDTH: usize,
const RATE: usize,
const L: usize,
> {
message: Option<[Fp; L]>,
// For the purpose of this test, witness the result.
// TODO: Move this into an instance column.
output: Option<Fp>,
_spec: PhantomData<S>,
}
impl<S: Spec<Fp, WIDTH, RATE>, const WIDTH: usize, const RATE: usize, const L: usize>
Circuit<Fp> for HashCircuit<S, WIDTH, RATE, L>
{
type Config = Pow5Config<Fp, WIDTH, RATE>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self {
message: None,
output: None,
_spec: PhantomData,
}
}
fn configure(meta: &mut ConstraintSystem<Fp>) -> Pow5Config<Fp, WIDTH, RATE> {
let state = (0..WIDTH).map(|_| meta.advice_column()).collect::<Vec<_>>();
let partial_sbox = meta.advice_column();
let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::<Vec<_>>();
let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::<Vec<_>>();
meta.enable_constant(rc_b[0]);
Pow5Chip::configure::<S>(
meta,
state.try_into().unwrap(),
partial_sbox,
rc_a.try_into().unwrap(),
rc_b.try_into().unwrap(),
)
}
fn synthesize(
&self,
config: Pow5Config<Fp, WIDTH, RATE>,
mut layouter: impl Layouter<Fp>,
) -> Result<(), Error> {
let chip = Pow5Chip::construct(config.clone());
let message = layouter.assign_region(
|| "load message",
|mut region| {
let message_word = |i: usize| {
let value = self.message.map(|message_vals| message_vals[i]);
region.assign_advice(
|| format!("load message_{}", i),
config.state[i],
0,
|| value.ok_or(Error::Synthesis),
)
};
let message: Result<Vec<_>, Error> = (0..L).map(message_word).collect();
Ok(message?.try_into().unwrap())
},
)?;
let hasher = Hash::<_, _, S, ConstantLength<L>, WIDTH, RATE>::init(
chip,
layouter.namespace(|| "init"),
)?;
let output = hasher.hash(layouter.namespace(|| "hash"), message)?;
layouter.assign_region(
|| "constrain output",
|mut region| {
let expected_var = region.assign_advice(
|| "load output",
config.state[0],
0,
|| self.output.ok_or(Error::Synthesis),
)?;
region.constrain_equal(output.cell(), expected_var.cell())
},
)
}
}
#[test]
fn poseidon_hash() {
let rng = OsRng;
let message = [Fp::random(rng), Fp::random(rng)];
let output =
poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init().hash(message);
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: Some(message),
output: Some(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[test]
fn poseidon_hash_longer_input() {
let rng = OsRng;
let message = [Fp::random(rng), Fp::random(rng), Fp::random(rng)];
let output =
poseidon::Hash::<_, OrchardNullifier, ConstantLength<3>, 3, 2>::init().hash(message);
let k = 7;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 3> {
message: Some(message),
output: Some(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[test]
fn hash_test_vectors() {
for tv in crate::primitives::poseidon::test_vectors::fp::hash() {
let message = [
pallas::Base::from_repr(tv.input[0]).unwrap(),
pallas::Base::from_repr(tv.input[1]).unwrap(),
];
let output = poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init()
.hash(message);
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: Some(message),
output: Some(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}
#[cfg(feature = "dev-graph")]
#[test]
fn print_poseidon_chip() {
use plotters::prelude::*;
let root = BitMapBackend::new("poseidon-chip-layout.png", (1024, 768)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root
.titled("Poseidon Chip Layout", ("sans-serif", 60))
.unwrap();
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: None,
output: None,
_spec: PhantomData,
};
halo2::dev::CircuitLayout::default()
.render(6, &circuit, &root)
.unwrap();
}
}

View File

@ -1,686 +0,0 @@
//! Gadget and chips for the Sinsemilla hash function.
use crate::circuit::gadget::{
ecc::{self, EccInstructions, FixedPoints},
utilities::Var,
};
use group::ff::{Field, PrimeField};
use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::arithmetic::CurveAffine;
use std::fmt::Debug;
pub mod chip;
pub mod merkle;
mod message;
/// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget.
/// This trait is bounded on two constant parameters: `K`, the number of bits
/// in each word accepted by the Sinsemilla hash, and `MAX_WORDS`, the maximum
/// number of words that a single hash instance can process.
pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS: usize> {
/// A variable in the circuit.
type CellValue: Var<C::Base>;
/// A message composed of [`Self::MessagePiece`]s.
type Message: From<Vec<Self::MessagePiece>>;
/// A piece in a message containing a number of `K`-bit words.
/// A [`Self::MessagePiece`] fits in a single base field element,
/// which means it can only contain up to `N` words, where
/// `N*K <= C::Base::CAPACITY`.
///
/// For example, in the case `K = 10`, `CAPACITY = 254`, we can fit
/// up to `N = 25` words in a single base field element.
type MessagePiece: Clone + Debug;
/// A cumulative sum `z` is used to decompose a Sinsemilla message. It
/// produces intermediate values for each word in the message, such
/// that `z_next` = (`z_cur` - `word_next`) / `2^K`.
///
/// These intermediate values are useful for range checks on subsets
/// of the Sinsemilla message. Sinsemilla messages in the Orchard
/// protocol are composed of field elements, and we need to check
/// the canonicity of the field element encodings in certain cases.
type RunningSum;
/// The x-coordinate of a point output of [`Self::hash_to_point`].
type X;
/// A point output of [`Self::hash_to_point`].
type NonIdentityPoint: Clone + Debug;
/// A type enumerating the fixed points used in `CommitDomains`.
type FixedPoints: FixedPoints<C>;
/// HashDomains used in this instruction.
type HashDomains: HashDomains<C>;
/// CommitDomains used in this instruction.
type CommitDomains: CommitDomains<C, Self::FixedPoints, Self::HashDomains>;
/// Witness a message piece given a field element. Returns a [`Self::MessagePiece`]
/// encoding the given message.
///
/// # Panics
///
/// Panics if `num_words` exceed the maximum number of `K`-bit words that
/// can fit into a single base field element.
fn witness_message_piece(
&self,
layouter: impl Layouter<C::Base>,
value: Option<C::Base>,
num_words: usize,
) -> Result<Self::MessagePiece, Error>;
/// Hashes a message to an ECC curve point.
/// This returns both the resulting point, as well as the message
/// decomposition in the form of intermediate values in a cumulative
/// sum.
///
#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
fn hash_to_point(
&self,
layouter: impl Layouter<C::Base>,
Q: C,
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error>;
/// Extracts the x-coordinate of the output of a Sinsemilla hash.
fn extract(point: &Self::NonIdentityPoint) -> Self::X;
}
/// A message to be hashed.
///
/// Composed of [`MessagePiece`]s with bitlength some multiple of `K`.
///
/// [`MessagePiece`]: SinsemillaInstructions::MessagePiece
#[derive(Clone, Debug)]
pub struct Message<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
chip: SinsemillaChip,
inner: SinsemillaChip::Message,
}
impl<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
Message<C, SinsemillaChip, K, MAX_WORDS>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
fn from_bitstring(
chip: SinsemillaChip,
mut layouter: impl Layouter<C::Base>,
bitstring: Vec<Option<bool>>,
) -> Result<Self, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(bitstring.len() % K, 0);
// Message must have at most `MAX_WORDS` words.
assert!(bitstring.len() / K <= MAX_WORDS);
// Each message piece must have at most `floor(C::CAPACITY / K)` words.
let piece_num_words = C::Base::CAPACITY as usize / K;
let pieces: Result<Vec<_>, _> = bitstring
.chunks(piece_num_words * K)
.enumerate()
.map(
|(i, piece)| -> Result<MessagePiece<C, SinsemillaChip, K, MAX_WORDS>, Error> {
MessagePiece::from_bitstring(
chip.clone(),
layouter.namespace(|| format!("message piece {}", i)),
piece,
)
},
)
.collect();
pieces.map(|pieces| Self::from_pieces(chip, pieces))
}
/// Constructs a message from a vector of [`MessagePiece`]s.
///
/// [`MessagePiece`]: SinsemillaInstructions::MessagePiece
pub fn from_pieces(
chip: SinsemillaChip,
pieces: Vec<MessagePiece<C, SinsemillaChip, K, MAX_WORDS>>,
) -> Self {
Self {
chip,
inner: pieces
.into_iter()
.map(|piece| piece.inner)
.collect::<Vec<_>>()
.into(),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct MessagePiece<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
chip: SinsemillaChip,
inner: SinsemillaChip::MessagePiece,
}
impl<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
MessagePiece<C, SinsemillaChip, K, MAX_WORDS>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
/// Returns the inner MessagePiece contained in this gadget.
pub fn inner(&self) -> SinsemillaChip::MessagePiece {
self.inner.clone()
}
}
impl<C: CurveAffine, SinsemillaChip, const K: usize, const MAX_WORDS: usize>
MessagePiece<C, SinsemillaChip, K, MAX_WORDS>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
fn from_bitstring(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
bitstring: &[Option<bool>],
) -> Result<Self, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(bitstring.len() % K, 0);
let num_words = bitstring.len() / K;
// Each message piece must have at most `floor(C::Base::CAPACITY / K)` words.
// This ensures that the all-ones bitstring is canonical in the field.
let piece_max_num_words = C::Base::CAPACITY as usize / K;
assert!(num_words <= piece_max_num_words as usize);
// Closure to parse a bitstring (little-endian) into a base field element.
let to_base_field = |bits: &[Option<bool>]| -> Option<C::Base> {
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
bits.map(|bits| {
bits.into_iter().rev().fold(C::Base::zero(), |acc, bit| {
if bit {
acc.double() + C::Base::one()
} else {
acc.double()
}
})
})
};
let piece_value = to_base_field(bitstring);
Self::from_field_elem(chip, layouter, piece_value, num_words)
}
/// Constructs a MessagePiece from a field element.
pub fn from_field_elem(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
field_elem: Option<C::Base>,
num_words: usize,
) -> Result<Self, Error> {
let inner = chip.witness_message_piece(layouter, field_elem, num_words)?;
Ok(Self { chip, inner })
}
}
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can
/// be used.
#[allow(non_snake_case)]
pub struct HashDomain<
C: CurveAffine,
SinsemillaChip,
EccChip,
const K: usize,
const MAX_WORDS: usize,
> where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
EccChip: EccInstructions<
C,
NonIdentityPoint = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
FixedPoints = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::FixedPoints,
> + Clone
+ Debug
+ Eq,
{
sinsemilla_chip: SinsemillaChip,
ecc_chip: EccChip,
Q: C,
}
impl<C: CurveAffine, SinsemillaChip, EccChip, const K: usize, const MAX_WORDS: usize>
HashDomain<C, SinsemillaChip, EccChip, K, MAX_WORDS>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
EccChip: EccInstructions<
C,
NonIdentityPoint = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
FixedPoints = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::FixedPoints,
> + Clone
+ Debug
+ Eq,
{
#[allow(non_snake_case)]
/// Constructs a new `HashDomain` for the given domain.
pub fn new(
sinsemilla_chip: SinsemillaChip,
ecc_chip: EccChip,
domain: &SinsemillaChip::HashDomains,
) -> Self {
HashDomain {
sinsemilla_chip,
ecc_chip,
Q: domain.Q(),
}
}
#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash].
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
pub fn hash_to_point(
&self,
layouter: impl Layouter<C::Base>,
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
) -> Result<(ecc::NonIdentityPoint<C, EccChip>, Vec<SinsemillaChip::RunningSum>), Error> {
assert_eq!(self.sinsemilla_chip, message.chip);
self.sinsemilla_chip
.hash_to_point(layouter, self.Q, message.inner)
.map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs))
}
/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
#[allow(clippy::type_complexity)]
pub fn hash(
&self,
layouter: impl Layouter<C::Base>,
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
) -> Result<(ecc::X<C, EccChip>, Vec<SinsemillaChip::RunningSum>), Error> {
assert_eq!(self.sinsemilla_chip, message.chip);
let (p, zs) = self.hash_to_point(layouter, message)?;
Ok((p.extract_p(), zs))
}
}
/// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated.
pub trait CommitDomains<C: CurveAffine, F: FixedPoints<C>, H: HashDomains<C>>:
Clone + Debug
{
/// Returns the fixed point corresponding to the R constant used for
/// randomization in this CommitDomain.
fn r(&self) -> F::FullScalar;
/// Returns the HashDomain contained in this CommitDomain
fn hash_domain(&self) -> H;
}
/// Trait allowing circuit's Sinsemilla HashDomains to be enumerated.
#[allow(non_snake_case)]
pub trait HashDomains<C: CurveAffine>: Clone + Debug {
fn Q(&self) -> C;
}
#[allow(non_snake_case)]
pub struct CommitDomain<
C: CurveAffine,
SinsemillaChip,
EccChip,
const K: usize,
const MAX_WORDS: usize,
> where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
EccChip: EccInstructions<
C,
NonIdentityPoint = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
FixedPoints = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::FixedPoints,
> + Clone
+ Debug
+ Eq,
{
M: HashDomain<C, SinsemillaChip, EccChip, K, MAX_WORDS>,
R: ecc::FixedPoint<C, EccChip>,
}
impl<C: CurveAffine, SinsemillaChip, EccChip, const K: usize, const MAX_WORDS: usize>
CommitDomain<C, SinsemillaChip, EccChip, K, MAX_WORDS>
where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
EccChip: EccInstructions<
C,
NonIdentityPoint = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
FixedPoints = <SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::FixedPoints,
> + Clone
+ Debug
+ Eq,
{
/// Constructs a new `CommitDomain` for the given domain.
pub fn new(
sinsemilla_chip: SinsemillaChip,
ecc_chip: EccChip,
// TODO: Instead of using SinsemilllaChip::CommitDomains, just use something that implements a CommitDomains trait
domain: &SinsemillaChip::CommitDomains,
) -> Self {
CommitDomain {
M: HashDomain::new(sinsemilla_chip, ecc_chip.clone(), &domain.hash_domain()),
R: ecc::FixedPoint::from_inner(ecc_chip, domain.r()),
}
}
#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
pub fn commit(
&self,
mut layouter: impl Layouter<C::Base>,
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
r: Option<C::Scalar>,
) -> Result<
(
ecc::Point<C, EccChip>,
Vec<SinsemillaChip::RunningSum>,
),
Error,
> {
assert_eq!(self.M.sinsemilla_chip, message.chip);
let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?;
let (p, zs) = self.M.hash_to_point(layouter.namespace(|| "M"), message)?;
let commitment = p.add(layouter.namespace(|| "M + [r] R"), &blind)?;
Ok((commitment, zs))
}
#[allow(clippy::type_complexity)]
/// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
pub fn short_commit(
&self,
mut layouter: impl Layouter<C::Base>,
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
r: Option<C::Scalar>,
) -> Result<(ecc::X<C, EccChip>, Vec<SinsemillaChip::RunningSum>), Error> {
assert_eq!(self.M.sinsemilla_chip, message.chip);
let (p, zs) = self.commit(layouter.namespace(|| "commit"), message, r)?;
Ok((p.extract_p(), zs))
}
}
#[cfg(test)]
mod tests {
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
use rand::rngs::OsRng;
use super::{
chip::{SinsemillaChip, SinsemillaConfig},
CommitDomain, HashDomain, Message, MessagePiece,
};
use crate::{
circuit::gadget::{
ecc::{
chip::{EccChip, EccConfig},
NonIdentityPoint,
},
utilities::lookup_range_check::LookupRangeCheckConfig,
},
constants::{
fixed_bases::COMMIT_IVK_PERSONALIZATION, sinsemilla::MERKLE_CRH_PERSONALIZATION,
OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains,
},
primitives::sinsemilla::{self, K},
};
use group::{ff::Field, Curve};
use pasta_curves::pallas;
use std::convert::TryInto;
struct MyCircuit {}
impl Circuit<pallas::Base> for MyCircuit {
#[allow(clippy::type_complexity)]
type Config = (
EccConfig<OrchardFixedBases>,
SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
SinsemillaConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
);
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
MyCircuit {}
}
#[allow(non_snake_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(),
];
// Shared fixed column for loading constants
let constants = meta.fixed_column();
meta.enable_constant(constants);
let table_idx = meta.lookup_table_column();
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(),
];
// Fixed columns for the Sinsemilla generator lookup table
let lookup = (
table_idx,
meta.lookup_table_column(),
meta.lookup_table_column(),
);
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
let ecc_config = EccChip::<OrchardFixedBases>::configure(
meta,
advices,
lagrange_coeffs,
range_check,
);
let config1 = SinsemillaChip::configure(
meta,
advices[..5].try_into().unwrap(),
advices[2],
lagrange_coeffs[0],
lookup,
range_check,
);
let config2 = SinsemillaChip::configure(
meta,
advices[5..].try_into().unwrap(),
advices[7],
lagrange_coeffs[1],
lookup,
range_check,
);
(ecc_config, config1, config2)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
let rng = OsRng;
let ecc_chip = EccChip::construct(config.0);
// The two `SinsemillaChip`s share the same lookup table.
SinsemillaChip::<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>::load(
config.1.clone(),
&mut layouter,
)?;
// This MerkleCRH example is purely for illustrative purposes.
// It is not an implementation of the Orchard protocol spec.
{
let chip1 = SinsemillaChip::construct(config.1);
let merkle_crh = HashDomain::new(
chip1.clone(),
ecc_chip.clone(),
&OrchardHashDomains::MerkleCrh,
);
// Layer 31, l = MERKLE_DEPTH - 1 - layer = 0
let l_bitstring = vec![Some(false); K];
let l = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "l"),
&l_bitstring,
)?;
// Left leaf
let left_bitstring: Vec<Option<bool>> =
(0..250).map(|_| Some(rand::random::<bool>())).collect();
let left = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "left"),
&left_bitstring,
)?;
// Right leaf
let right_bitstring: Vec<Option<bool>> =
(0..250).map(|_| Some(rand::random::<bool>())).collect();
let right = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "right"),
&right_bitstring,
)?;
let l_bitstring: Option<Vec<bool>> = l_bitstring.into_iter().collect();
let left_bitstring: Option<Vec<bool>> = left_bitstring.into_iter().collect();
let right_bitstring: Option<Vec<bool>> = right_bitstring.into_iter().collect();
// Witness expected parent
let expected_parent = {
let expected_parent = if let (Some(l), Some(left), Some(right)) =
(l_bitstring, left_bitstring, right_bitstring)
{
let merkle_crh = sinsemilla::HashDomain::new(MERKLE_CRH_PERSONALIZATION);
let point = merkle_crh
.hash_to_point(
l.into_iter()
.chain(left.into_iter())
.chain(right.into_iter()),
)
.unwrap();
Some(point.to_affine())
} else {
None
};
NonIdentityPoint::new(
ecc_chip.clone(),
layouter.namespace(|| "Witness expected parent"),
expected_parent,
)?
};
// Parent
let (parent, _) = {
let message = Message::from_pieces(chip1, vec![l, left, right]);
merkle_crh.hash_to_point(layouter.namespace(|| "parent"), message)?
};
parent.constrain_equal(
layouter.namespace(|| "parent == expected parent"),
&expected_parent,
)?;
}
{
let chip2 = SinsemillaChip::construct(config.2);
let commit_ivk = CommitDomain::new(
chip2.clone(),
ecc_chip.clone(),
&OrchardCommitDomains::CommitIvk,
);
let r_val = pallas::Scalar::random(rng);
let message: Vec<Option<bool>> =
(0..500).map(|_| Some(rand::random::<bool>())).collect();
let (result, _) = {
let message = Message::from_bitstring(
chip2,
layouter.namespace(|| "witness message"),
message.clone(),
)?;
commit_ivk.commit(layouter.namespace(|| "commit"), message, Some(r_val))?
};
// Witness expected result.
let expected_result = {
let message: Option<Vec<bool>> = message.into_iter().collect();
let expected_result = if let Some(message) = message {
let domain = sinsemilla::CommitDomain::new(COMMIT_IVK_PERSONALIZATION);
let point = domain.commit(message.into_iter(), &r_val).unwrap();
Some(point.to_affine())
} else {
None
};
NonIdentityPoint::new(
ecc_chip,
layouter.namespace(|| "Witness expected result"),
expected_result,
)?
};
result.constrain_equal(
layouter.namespace(|| "result == expected result"),
&expected_result,
)
}
}
}
#[test]
fn sinsemilla_chip() {
let k = 11;
let circuit = MyCircuit {};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[cfg(feature = "dev-graph")]
#[test]
fn print_sinsemilla_chip() {
use plotters::prelude::*;
let root =
BitMapBackend::new("sinsemilla-hash-layout.png", (1024, 7680)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root.titled("SinsemillaHash", ("sans-serif", 60)).unwrap();
let circuit = MyCircuit {};
halo2::dev::CircuitLayout::default()
.render(11, &circuit, &root)
.unwrap();
}
}

View File

@ -1,328 +0,0 @@
use super::{
message::{Message, MessagePiece},
CommitDomains, HashDomains, SinsemillaInstructions,
};
use crate::{
circuit::gadget::{
ecc::{chip::NonIdentityEccPoint, FixedPoints},
utilities::lookup_range_check::LookupRangeCheckConfig,
},
primitives::sinsemilla,
};
use std::marker::PhantomData;
use halo2::{
circuit::{AssignedCell, Chip, Layouter},
plonk::{
Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, TableColumn,
VirtualCells,
},
poly::Rotation,
};
use pasta_curves::pallas;
mod generator_table;
use generator_table::GeneratorTableConfig;
mod hash_to_point;
/// Configuration for the Sinsemilla hash chip
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct SinsemillaConfig<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
/// Binary selector used in lookup argument and in the body of the Sinsemilla hash.
q_sinsemilla1: Selector,
/// Non-binary selector used in lookup argument and in the body of the Sinsemilla hash.
q_sinsemilla2: Column<Fixed>,
/// q_sinsemilla2 is used to define a synthetic selector,
/// q_sinsemilla3 = (q_sinsemilla2) ⋅ (q_sinsemilla2 - 1)
/// Simple selector used to constrain hash initialization to be consistent with
/// the y-coordinate of the domain $Q$.
q_sinsemilla4: Selector,
/// Fixed column used to load the y-coordinate of the domain $Q$.
fixed_y_q: Column<Fixed>,
/// Advice column used to store the x-coordinate of the accumulator at each
/// iteration of the hash.
x_a: Column<Advice>,
/// Advice column used to store the x-coordinate of the generator corresponding
/// to the message word at each iteration of the hash. This is looked up in the
/// generator table.
x_p: Column<Advice>,
/// Advice column used to load the message.
bits: Column<Advice>,
/// Advice column used to store the $\lambda_1$ intermediate value at each
/// iteration.
lambda_1: Column<Advice>,
/// Advice column used to store the $\lambda_2$ intermediate value at each
/// iteration.
lambda_2: Column<Advice>,
/// Advice column used to witness message pieces. This may or may not be the same
/// column as `bits`.
witness_pieces: Column<Advice>,
/// The lookup table where $(\mathsf{idx}, x_p, y_p)$ are loaded for the $2^K$
/// generators of the Sinsemilla hash.
pub(super) generator_table: GeneratorTableConfig,
/// An advice column configured to perform lookup range checks.
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
_marker: PhantomData<(Hash, Commit, F)>,
}
impl<Hash, Commit, F> SinsemillaConfig<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
/// Returns an array of all advice columns in this config, in arbitrary order.
pub(super) fn advices(&self) -> [Column<Advice>; 5] {
[self.x_a, self.x_p, self.bits, self.lambda_1, self.lambda_2]
}
/// Returns the lookup range check config used in this config.
pub fn lookup_config(&self) -> LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }> {
self.lookup_config
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct SinsemillaChip<Hash, Commit, Fixed>
where
Hash: HashDomains<pallas::Affine>,
Fixed: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, Fixed, Hash>,
{
config: SinsemillaConfig<Hash, Commit, Fixed>,
}
impl<Hash, Commit, Fixed> Chip<pallas::Base> for SinsemillaChip<Hash, Commit, Fixed>
where
Hash: HashDomains<pallas::Affine>,
Fixed: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, Fixed, Hash>,
{
type Config = SinsemillaConfig<Hash, Commit, Fixed>;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<Hash, Commit, F> SinsemillaChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
pub fn construct(config: <Self as Chip<pallas::Base>>::Config) -> Self {
Self { config }
}
pub fn load(
config: SinsemillaConfig<Hash, Commit, F>,
layouter: &mut impl Layouter<pallas::Base>,
) -> Result<<Self as Chip<pallas::Base>>::Loaded, Error> {
// Load the lookup table.
config.generator_table.load(layouter)
}
/// # Side-effects
///
/// All columns in `advices` and will be equality-enabled.
#[allow(clippy::too_many_arguments)]
#[allow(non_snake_case)]
pub fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
advices: [Column<Advice>; 5],
witness_pieces: Column<Advice>,
fixed_y_q: Column<Fixed>,
lookup: (TableColumn, TableColumn, TableColumn),
range_check: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
) -> <Self as Chip<pallas::Base>>::Config {
// Enable equality on all advice columns
for advice in advices.iter() {
meta.enable_equality(*advice);
}
let config = SinsemillaConfig::<Hash, Commit, F> {
q_sinsemilla1: meta.complex_selector(),
q_sinsemilla2: meta.fixed_column(),
q_sinsemilla4: meta.selector(),
fixed_y_q,
x_a: advices[0],
x_p: advices[1],
bits: advices[2],
lambda_1: advices[3],
lambda_2: advices[4],
witness_pieces,
generator_table: GeneratorTableConfig {
table_idx: lookup.0,
table_x: lookup.1,
table_y: lookup.2,
},
lookup_config: range_check,
_marker: PhantomData,
};
// Set up lookup argument
GeneratorTableConfig::configure(meta, config.clone());
let two = pallas::Base::from(2);
// Closures for expressions that are derived multiple times
// x_r = lambda_1^2 - x_a - x_p
let x_r = |meta: &mut VirtualCells<pallas::Base>, rotation| {
let x_a = meta.query_advice(config.x_a, rotation);
let x_p = meta.query_advice(config.x_p, rotation);
let lambda_1 = meta.query_advice(config.lambda_1, rotation);
lambda_1.square() - x_a - x_p
};
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
let Y_A = |meta: &mut VirtualCells<pallas::Base>, rotation| {
let x_a = meta.query_advice(config.x_a, rotation);
let lambda_1 = meta.query_advice(config.lambda_1, rotation);
let lambda_2 = meta.query_advice(config.lambda_2, rotation);
(lambda_1 + lambda_2) * (x_a - x_r(meta, rotation))
};
// Check that the initial x_A, x_P, lambda_1, lambda_2 are consistent with y_Q.
meta.create_gate("Initial y_Q", |meta| {
let q_s4 = meta.query_selector(config.q_sinsemilla4);
let y_q = meta.query_fixed(config.fixed_y_q, Rotation::cur());
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
let Y_A_cur = Y_A(meta, Rotation::cur());
// 2 * y_q - Y_{A,0} = 0
let init_y_q_check = y_q * two - Y_A_cur;
vec![q_s4 * init_y_q_check]
});
meta.create_gate("Sinsemilla gate", |meta| {
let q_s1 = meta.query_selector(config.q_sinsemilla1);
// q_s3 = (q_s2) * (q_s2 - 1)
let q_s3 = {
let one = Expression::Constant(pallas::Base::one());
let q_s2 = meta.query_fixed(config.q_sinsemilla2, Rotation::cur());
q_s2.clone() * (q_s2 - one)
};
let lambda_1_next = meta.query_advice(config.lambda_1, Rotation::next());
let lambda_2_cur = meta.query_advice(config.lambda_2, Rotation::cur());
let x_a_cur = meta.query_advice(config.x_a, Rotation::cur());
let x_a_next = meta.query_advice(config.x_a, Rotation::next());
// x_r = lambda_1^2 - x_a_cur - x_p
let x_r = x_r(meta, Rotation::cur());
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
let Y_A_cur = Y_A(meta, Rotation::cur());
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
let Y_A_next = Y_A(meta, Rotation::next());
// lambda2^2 - (x_a_next + x_r + x_a_cur) = 0
let secant_line =
lambda_2_cur.clone().square() - (x_a_next.clone() + x_r + x_a_cur.clone());
// lhs - rhs = 0, where
// - lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next)
// - rhs = (2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final)
let y_check = {
// lhs = 4 * lambda_2_cur * (x_a_cur - x_a_next)
let lhs = lambda_2_cur * pallas::Base::from(4) * (x_a_cur - x_a_next);
// rhs = 2 * Y_A_cur + (2 - q_s3) * Y_A_next + 2 * q_s3 * y_a_final
let rhs = {
// y_a_final is assigned to the lambda1 column on the next offset.
let y_a_final = lambda_1_next;
Y_A_cur * two
+ (Expression::Constant(two) - q_s3.clone()) * Y_A_next
+ q_s3 * two * y_a_final
};
lhs - rhs
};
vec![
("Secant line", q_s1.clone() * secant_line),
("y check", q_s1 * y_check),
]
});
config
}
}
// Implement `SinsemillaInstructions` for `SinsemillaChip`
impl<Hash, Commit, F> SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }>
for SinsemillaChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
type CellValue = AssignedCell<pallas::Base, pallas::Base>;
type Message = Message<pallas::Base, { sinsemilla::K }, { sinsemilla::C }>;
type MessagePiece = MessagePiece<pallas::Base, { sinsemilla::K }>;
type RunningSum = Vec<Self::CellValue>;
type X = AssignedCell<pallas::Base, pallas::Base>;
type NonIdentityPoint = NonIdentityEccPoint;
type FixedPoints = F;
type HashDomains = Hash;
type CommitDomains = Commit;
fn witness_message_piece(
&self,
mut layouter: impl Layouter<pallas::Base>,
field_elem: Option<pallas::Base>,
num_words: usize,
) -> Result<Self::MessagePiece, Error> {
let config = self.config().clone();
let cell = layouter.assign_region(
|| "witness message piece",
|mut region| {
region.assign_advice(
|| "witness message piece",
config.witness_pieces,
0,
|| field_elem.ok_or(Error::Synthesis),
)
},
)?;
Ok(MessagePiece::new(cell, num_words))
}
#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
fn hash_to_point(
&self,
mut layouter: impl Layouter<pallas::Base>,
Q: pallas::Affine,
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error> {
layouter.assign_region(
|| "hash_to_point",
|mut region| self.hash_message(&mut region, Q, &message),
)
}
fn extract(point: &Self::NonIdentityPoint) -> Self::X {
point.x()
}
}

View File

@ -1,104 +0,0 @@
use crate::primitives::sinsemilla::{self, SINSEMILLA_S};
use halo2::{
circuit::Layouter,
plonk::{ConstraintSystem, Error, Expression, TableColumn},
poly::Rotation,
};
use super::{CommitDomains, FixedPoints, HashDomains};
use pasta_curves::{arithmetic::FieldExt, pallas};
/// Table containing independent generators S[0..2^k]
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub struct GeneratorTableConfig {
pub table_idx: TableColumn,
pub table_x: TableColumn,
pub table_y: TableColumn,
}
impl GeneratorTableConfig {
#[allow(clippy::too_many_arguments)]
#[allow(non_snake_case)]
/// Even though the lookup table can be used in other parts of the circuit,
/// this specific configuration sets up Sinsemilla-specific constraints
/// controlled by `q_sinsemilla`, and would likely not apply to other chips.
pub fn configure<Hash, Commit, F>(
meta: &mut ConstraintSystem<pallas::Base>,
config: super::SinsemillaConfig<Hash, Commit, F>,
) where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
let (table_idx, table_x, table_y) = (
config.generator_table.table_idx,
config.generator_table.table_x,
config.generator_table.table_y,
);
meta.lookup(|meta| {
let q_s1 = meta.query_selector(config.q_sinsemilla1);
let q_s2 = meta.query_fixed(config.q_sinsemilla2, Rotation::cur());
let q_s3 = {
let one = Expression::Constant(pallas::Base::one());
q_s2.clone() * (q_s2.clone() - one)
};
// m_{i+1} = z_{i} - 2^K * (q_s2 - q_s3) * z_{i + 1}
// Note that the message words m_i's are 1-indexed while the
// running sum z_i's are 0-indexed.
let word = {
let z_cur = meta.query_advice(config.bits, Rotation::cur());
let z_next = meta.query_advice(config.bits, Rotation::next());
z_cur - ((q_s2 - q_s3) * z_next * pallas::Base::from(1 << sinsemilla::K))
};
let x_p = meta.query_advice(config.x_p, Rotation::cur());
// y_{p,i} = (Y_{A,i} / 2) - lambda1 * (x_{A,i} - x_{P,i}),
// where Y_{A,i} = (lambda1_i + lambda2_i) * (x_{A,i} - x_{R,i}),
// x_{R,i} = lambda1^2 - x_{A,i} - x_{P,i}
//
let y_p = {
let lambda1 = meta.query_advice(config.lambda_1, Rotation::cur());
let lambda2 = meta.query_advice(config.lambda_2, Rotation::cur());
let x_a = meta.query_advice(config.x_a, Rotation::cur());
let x_r = lambda1.clone().square() - x_a.clone() - x_p.clone();
let Y_A = (lambda1.clone() + lambda2) * (x_a.clone() - x_r);
(Y_A * pallas::Base::TWO_INV) - (lambda1 * (x_a - x_p.clone()))
};
// Lookup expressions default to the first entry when `q_s1`
// is not enabled.
let (init_x, init_y) = SINSEMILLA_S[0];
let not_q_s1 = Expression::Constant(pallas::Base::one()) - q_s1.clone();
let m = q_s1.clone() * word; // The first table index is 0.
let x_p = q_s1.clone() * x_p + not_q_s1.clone() * init_x;
let y_p = q_s1 * y_p + not_q_s1 * init_y;
vec![(m, table_idx), (x_p, table_x), (y_p, table_y)]
});
}
pub fn load(&self, layouter: &mut impl Layouter<pallas::Base>) -> Result<(), Error> {
layouter.assign_table(
|| "generator_table",
|mut table| {
for (index, (x, y)) in SINSEMILLA_S.iter().enumerate() {
table.assign_cell(
|| "table_idx",
self.table_idx,
index,
|| Ok(pallas::Base::from(index as u64)),
)?;
table.assign_cell(|| "table_x", self.table_x, index, || Ok(*x))?;
table.assign_cell(|| "table_y", self.table_y, index, || Ok(*y))?;
}
Ok(())
},
)
}
}

View File

@ -1,450 +0,0 @@
use super::super::{CommitDomains, HashDomains, SinsemillaInstructions};
use super::{NonIdentityEccPoint, SinsemillaChip};
use crate::circuit::gadget::ecc::FixedPoints;
use crate::primitives::sinsemilla::{self, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S};
use halo2::circuit::AssignedCell;
use halo2::{
circuit::{Chip, Region},
plonk::Error,
};
use group::ff::{Field, PrimeField, PrimeFieldBits};
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use std::ops::Deref;
impl<Hash, Commit, Fixed> SinsemillaChip<Hash, Commit, Fixed>
where
Hash: HashDomains<pallas::Affine>,
Fixed: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, Fixed, Hash>,
{
#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
pub(super) fn hash_message(
&self,
region: &mut Region<'_, pallas::Base>,
Q: pallas::Affine,
message: &<Self as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::Message,
) -> Result<
(
NonIdentityEccPoint,
Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>>,
),
Error,
> {
let config = self.config().clone();
let mut offset = 0;
// Get the `x`- and `y`-coordinates of the starting `Q` base.
let x_q = *Q.coordinates().unwrap().x();
let y_q = *Q.coordinates().unwrap().y();
// Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4
// selector.
let mut y_a: Y<pallas::Base> = {
// Enable `q_sinsemilla4` on the first row.
config.q_sinsemilla4.enable(region, offset)?;
region.assign_fixed(|| "fixed y_q", config.fixed_y_q, offset, || Ok(y_q))?;
(Some(y_q)).into()
};
// Constrain the initial x_q to equal the x-coordinate of the domain's `Q`.
let mut x_a: X<pallas::Base> = {
let x_a =
region.assign_advice_from_constant(|| "fixed x_q", config.x_a, offset, x_q)?;
x_a.into()
};
let mut zs_sum: Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>> = Vec::new();
// Hash each piece in the message.
for (idx, piece) in message.iter().enumerate() {
let final_piece = idx == message.len() - 1;
// The value of the accumulator after this piece is processed.
let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?;
// Since each message word takes one row to process, we increase
// the offset by `piece.num_words` on each iteration.
offset += piece.num_words();
// Update the accumulator to the latest value.
x_a = x;
y_a = y;
zs_sum.push(zs);
}
// Assign the final y_a.
let y_a = {
// Assign the final y_a.
let y_a_cell = region.assign_advice(
|| "y_a",
config.lambda_1,
offset,
|| y_a.ok_or(Error::Synthesis),
)?;
// Assign lambda_2 and x_p zero values since they are queried
// in the gate. (The actual values do not matter since they are
// multiplied by zero.)
{
region.assign_advice(
|| "dummy lambda2",
config.lambda_2,
offset,
|| Ok(pallas::Base::zero()),
)?;
region.assign_advice(
|| "dummy x_p",
config.x_p,
offset,
|| Ok(pallas::Base::zero()),
)?;
}
y_a_cell
};
#[cfg(test)]
#[allow(non_snake_case)]
// Check equivalence to result from primitives::sinsemilla::hash_to_point
{
use crate::circuit::gadget::sinsemilla::message::MessagePiece;
use crate::primitives::sinsemilla::{K, S_PERSONALIZATION};
use group::{prime::PrimeCurveAffine, Curve};
use pasta_curves::arithmetic::CurveExt;
let field_elems: Option<Vec<pallas::Base>> =
message.iter().map(|piece| piece.field_elem()).collect();
if field_elems.is_some() && x_a.value().is_some() && y_a.value().is_some() {
// Get message as a bitstring.
let bitstring: Vec<bool> = message
.iter()
.map(|piece: &MessagePiece<pallas::Base, K>| {
piece
.field_elem()
.unwrap()
.to_le_bits()
.into_iter()
.take(K * piece.num_words())
.collect::<Vec<_>>()
})
.flatten()
.collect();
let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());
// We can use complete addition here because it differs from
// incomplete addition with negligible probability.
let expected_point = bitstring
.chunks(K)
.fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc);
let actual_point =
pallas::Affine::from_xy(*x_a.value().unwrap(), *y_a.value().unwrap()).unwrap();
assert_eq!(expected_point.to_affine(), actual_point);
}
}
if let Some(x_a) = x_a.value() {
if let Some(y_a) = y_a.value() {
if x_a.is_zero_vartime() || y_a.is_zero_vartime() {
return Err(Error::Synthesis);
}
}
}
Ok((
NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a),
zs_sum,
))
}
#[allow(clippy::type_complexity)]
/// Hashes a message piece containing `piece.length` number of `K`-bit words.
///
/// To avoid a duplicate assignment, the accumulator x-coordinate provided
/// by the caller is not copied. This only works because `hash_piece()` is
/// an internal API. Before this call to `hash_piece()`, x_a MUST have been
/// already assigned within this region at the correct offset.
fn hash_piece(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
piece: &<Self as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::MessagePiece,
mut x_a: X<pallas::Base>,
mut y_a: Y<pallas::Base>,
final_piece: bool,
) -> Result<
(
X<pallas::Base>,
Y<pallas::Base>,
Vec<AssignedCell<pallas::Base, pallas::Base>>,
),
Error,
> {
let config = self.config().clone();
// Selector assignments
{
// Enable `q_sinsemilla1` selector on every row.
for row in 0..piece.num_words() {
config.q_sinsemilla1.enable(region, offset + row)?;
}
// Set `q_sinsemilla2` fixed column to 1 on every row but the last.
for row in 0..(piece.num_words() - 1) {
region.assign_fixed(
|| "q_s2 = 1",
config.q_sinsemilla2,
offset + row,
|| Ok(pallas::Base::one()),
)?;
}
// Set `q_sinsemilla2` fixed column to 0 on the last row if this is
// not the final piece, or to 2 on the last row of the final piece.
region.assign_fixed(
|| {
if final_piece {
"q_s2 for final piece"
} else {
"q_s2 between pieces"
}
},
config.q_sinsemilla2,
offset + piece.num_words() - 1,
|| {
Ok(if final_piece {
pallas::Base::from(2)
} else {
pallas::Base::zero()
})
},
)?;
}
// Message piece as K * piece.length bitstring
let bitstring: Option<Vec<bool>> = piece.field_elem().map(|value| {
value
.to_le_bits()
.into_iter()
.take(sinsemilla::K * piece.num_words())
.collect()
});
let words: Option<Vec<u32>> = bitstring.map(|bitstring| {
bitstring
.chunks_exact(sinsemilla::K)
.map(|word| lebs2ip_k(word))
.collect()
});
// Get (x_p, y_p) for each word.
let generators: Option<Vec<(pallas::Base, pallas::Base)>> = words.clone().map(|words| {
words
.iter()
.map(|word| SINSEMILLA_S[*word as usize])
.collect()
});
// Convert `words` from `Option<Vec<u32>>` to `Vec<Option<u32>>`
let words: Vec<Option<u32>> = if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; piece.num_words()]
};
// Decompose message piece into `K`-bit pieces with a running sum `z`.
let zs = {
let mut zs = Vec::with_capacity(piece.num_words() + 1);
// Copy message and initialize running sum `z` to decompose message in-circuit
let initial_z = piece.cell_value().copy_advice(
|| "z_0 (copy of message piece)",
region,
config.bits,
offset,
)?;
zs.push(initial_z);
// Assign cumulative sum such that for 0 <= i < n,
// z_i = 2^K * z_{i + 1} + m_{i + 1}
// => z_{i + 1} = (z_i - m_{i + 1}) / 2^K
//
// For a message piece m = m_1 + 2^K m_2 + ... + 2^{K(n-1)} m_n}, initialize z_0 = m.
// We end up with z_n = 0. (z_n is not directly encoded as a cell value;
// it is implicitly taken as 0 by adjusting the definition of m_{i+1}.)
let mut z = piece.field_elem();
let inv_2_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap();
// We do not assign the final z_n as it is constrained to be zero.
for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() {
// z_{i + 1} = (z_i - m_{i + 1}) / 2^K
z = z
.zip(*word)
.map(|(z, word)| (z - pallas::Base::from(word as u64)) * inv_2_k);
let cell = region.assign_advice(
|| format!("z_{:?}", idx + 1),
config.bits,
offset + idx + 1,
|| z.ok_or(Error::Synthesis),
)?;
zs.push(cell)
}
zs
};
// The accumulator x-coordinate provided by the caller MUST have been assigned
// within this region.
let generators: Vec<Option<(pallas::Base, pallas::Base)>> =
if let Some(generators) = generators {
generators.into_iter().map(Some).collect()
} else {
vec![None; piece.num_words()]
};
for (row, gen) in generators.iter().enumerate() {
let x_p = gen.map(|gen| gen.0);
let y_p = gen.map(|gen| gen.1);
// Assign `x_p`
region.assign_advice(
|| "x_p",
config.x_p,
offset + row,
|| x_p.ok_or(Error::Synthesis),
)?;
// Compute and assign `lambda_1`
let lambda_1 = {
let lambda_1 = x_a
.value()
.zip(y_a.0)
.zip(x_p)
.zip(y_p)
.map(|(((x_a, y_a), x_p), y_p)| (y_a - y_p) * (x_a - x_p).invert().unwrap());
// Assign lambda_1
region.assign_advice(
|| "lambda_1",
config.lambda_1,
offset + row,
|| lambda_1.ok_or(Error::Synthesis),
)?;
lambda_1
};
// Compute `x_r`
let x_r = lambda_1
.zip(x_a.value())
.zip(x_p)
.map(|((lambda_1, x_a), x_p)| lambda_1.square() - x_a - x_p);
// Compute and assign `lambda_2`
let lambda_2 = {
let lambda_2 = x_a.value().zip(y_a.0).zip(x_r).zip(lambda_1).map(
|(((x_a, y_a), x_r), lambda_1)| {
pallas::Base::from(2) * y_a * (x_a - x_r).invert().unwrap() - lambda_1
},
);
region.assign_advice(
|| "lambda_2",
config.lambda_2,
offset + row,
|| lambda_2.ok_or(Error::Synthesis),
)?;
lambda_2
};
// Compute and assign `x_a` for the next row.
let x_a_new: X<pallas::Base> = {
let x_a_new = lambda_2
.zip(x_a.value())
.zip(x_r)
.map(|((lambda_2, x_a), x_r)| lambda_2.square() - x_a - x_r);
let x_a_cell = region.assign_advice(
|| "x_a",
config.x_a,
offset + row + 1,
|| x_a_new.ok_or(Error::Synthesis),
)?;
x_a_cell.into()
};
// Compute y_a for the next row.
let y_a_new: Y<pallas::Base> = lambda_2
.zip(x_a.value())
.zip(x_a_new.value())
.zip(y_a.0)
.map(|(((lambda_2, x_a), x_a_new), y_a)| lambda_2 * (x_a - x_a_new) - y_a)
.into();
// Update the mutable `x_a`, `y_a` variables.
x_a = x_a_new;
y_a = y_a_new;
}
Ok((x_a, y_a, zs))
}
}
/// The x-coordinate of the accumulator in a Sinsemilla hash instance.
struct X<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> From<AssignedCell<F, F>> for X<F> {
fn from(cell_value: AssignedCell<F, F>) -> Self {
X(cell_value)
}
}
impl<F: FieldExt> Deref for X<F> {
type Target = AssignedCell<F, F>;
fn deref(&self) -> &AssignedCell<F, F> {
&self.0
}
}
/// The y-coordinate of the accumulator in a Sinsemilla hash instance.
///
/// This is never actually witnessed until the last round, since it
/// can be derived from other variables. Thus it only exists as a field
/// element, not a `CellValue`.
struct Y<F: FieldExt>(Option<F>);
impl<F: FieldExt> From<Option<F>> for Y<F> {
fn from(value: Option<F>) -> Self {
Y(value)
}
}
impl<F: FieldExt> Deref for Y<F> {
type Target = Option<F>;
fn deref(&self) -> &Option<F> {
&self.0
}
}

View File

@ -1,324 +0,0 @@
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
};
use pasta_curves::arithmetic::CurveAffine;
use super::{HashDomains, SinsemillaInstructions};
use crate::circuit::gadget::utilities::{
cond_swap::CondSwapInstructions, i2lebsp, transpose_option_array, UtilitiesInstructions,
};
use std::iter;
pub(in crate::circuit) mod chip;
/// SWU hash-to-curve personalization for the Merkle CRH generator
pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH";
/// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`.
/// The hash function used is a Sinsemilla instance with `K`-bit words.
/// The hash function can process `MAX_WORDS` words.
pub trait MerkleInstructions<
C: CurveAffine,
const PATH_LENGTH: usize,
const K: usize,
const MAX_WORDS: usize,
>:
SinsemillaInstructions<C, K, MAX_WORDS>
+ CondSwapInstructions<C::Base>
+ UtilitiesInstructions<C::Base>
+ Chip<C::Base>
{
/// Compute MerkleCRH for a given `layer`. The hash that computes the root
/// is at layer 0, and the hashes that are applied to two leaves are at
/// layer `MERKLE_DEPTH - 1` = layer 31.
#[allow(non_snake_case)]
fn hash_layer(
&self,
layouter: impl Layouter<C::Base>,
Q: C,
l: usize,
left: Self::Var,
right: Self::Var,
) -> Result<Self::Var, Error>;
}
#[derive(Clone, Debug)]
pub struct MerklePath<
C: CurveAffine,
MerkleChip,
const PATH_LENGTH: usize,
const K: usize,
const MAX_WORDS: usize,
> where
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
{
pub(in crate::circuit) chip_1: MerkleChip,
pub(in crate::circuit) chip_2: MerkleChip,
pub(in crate::circuit) domain: MerkleChip::HashDomains,
pub(in crate::circuit) leaf_pos: Option<u32>,
// The Merkle path is ordered from leaves to root.
pub(in crate::circuit) path: Option<[C::Base; PATH_LENGTH]>,
}
#[allow(non_snake_case)]
impl<
C: CurveAffine,
MerkleChip,
const PATH_LENGTH: usize,
const K: usize,
const MAX_WORDS: usize,
> MerklePath<C, MerkleChip, PATH_LENGTH, K, MAX_WORDS>
where
MerkleChip: MerkleInstructions<C, PATH_LENGTH, K, MAX_WORDS> + Clone,
{
/// Calculates the root of the tree containing the given leaf at this Merkle path.
pub(in crate::circuit) fn calculate_root(
&self,
mut layouter: impl Layouter<C::Base>,
leaf: MerkleChip::Var,
) -> Result<MerkleChip::Var, Error> {
// A Sinsemilla chip uses 5 advice columns, but the full Orchard action circuit
// uses 10 advice columns. We distribute the path hashing across two Sinsemilla
// chips to make better use of the available circuit area.
let chips = iter::empty()
.chain(iter::repeat(self.chip_1.clone()).take(PATH_LENGTH / 2))
.chain(iter::repeat(self.chip_2.clone()));
// The Merkle path is ordered from leaves to root, which is consistent with the
// little-endian representation of `pos` below.
let path = transpose_option_array(self.path);
// Get position as a PATH_LENGTH-bit bitstring (little-endian bit order).
let pos: [Option<bool>; PATH_LENGTH] = {
let pos: Option<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64));
transpose_option_array(pos)
};
let Q = self.domain.Q();
let mut node = leaf;
for (l, ((sibling, pos), chip)) in path.iter().zip(pos.iter()).zip(chips).enumerate() {
// `l` = MERKLE_DEPTH - layer - 1, which is the index obtained from
// enumerating this Merkle path (going from leaf to root).
// For example, when `layer = 31` (the first sibling on the Merkle path),
// we have `l` = 32 - 31 - 1 = 0.
// On the other hand, when `layer = 0` (the final sibling on the Merkle path),
// we have `l` = 32 - 0 - 1 = 31.
let pair = {
let pair = (node, *sibling);
// Swap node and sibling if needed
chip.swap(layouter.namespace(|| "node position"), pair, *pos)?
};
// Each `hash_layer` consists of 52 Sinsemilla words:
// - l (10 bits) = 1 word
// - left (255 bits) || right (255 bits) = 51 words (510 bits)
node = chip.hash_layer(
layouter.namespace(|| format!("hash l {}", l)),
Q,
l,
pair.0,
pair.1,
)?;
}
Ok(node)
}
}
#[cfg(test)]
pub mod tests {
use super::{
chip::{MerkleChip, MerkleConfig},
MerklePath,
};
use crate::{
circuit::gadget::{
sinsemilla::chip::SinsemillaChip,
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
},
constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains},
note::commitment::ExtractedNoteCommitment,
tree,
};
use group::ff::{Field, PrimeField};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
pasta::pallas,
plonk::{Circuit, ConstraintSystem, Error},
};
use rand::{rngs::OsRng, RngCore};
use std::convert::TryInto;
const MERKLE_DEPTH: usize = 32;
#[derive(Default)]
struct MyCircuit {
leaf: Option<pallas::Base>,
leaf_pos: Option<u32>,
merkle_path: Option<[pallas::Base; MERKLE_DEPTH]>,
}
impl Circuit<pallas::Base> for MyCircuit {
type Config = (
MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
MerkleConfig<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>,
);
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
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(),
];
// Shared fixed column for loading constants
let constants = meta.fixed_column();
meta.enable_constant(constants);
// NB: In the actual Action circuit, these fixed columns will be reused
// by other chips. For this test, we are creating new fixed columns.
let fixed_y_q_1 = meta.fixed_column();
let fixed_y_q_2 = meta.fixed_column();
// Fixed columns for the Sinsemilla generator lookup table
let lookup = (
meta.lookup_table_column(),
meta.lookup_table_column(),
meta.lookup_table_column(),
);
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup.0);
let sinsemilla_config_1 = SinsemillaChip::configure(
meta,
advices[5..].try_into().unwrap(),
advices[7],
fixed_y_q_1,
lookup,
range_check,
);
let config1 = MerkleChip::configure(meta, sinsemilla_config_1);
let sinsemilla_config_2 = SinsemillaChip::configure(
meta,
advices[..5].try_into().unwrap(),
advices[2],
fixed_y_q_2,
lookup,
range_check,
);
let config2 = MerkleChip::configure(meta, sinsemilla_config_2);
(config1, config2)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
// Load generator table (shared across both configs)
SinsemillaChip::<OrchardHashDomains, OrchardCommitDomains, OrchardFixedBases>::load(
config.0.sinsemilla_config.clone(),
&mut layouter,
)?;
// Construct Merkle chips which will be placed side-by-side in the circuit.
let chip_1 = MerkleChip::construct(config.0.clone());
let chip_2 = MerkleChip::construct(config.1.clone());
let leaf = chip_1.load_private(
layouter.namespace(|| ""),
config.0.cond_swap_config.a,
self.leaf,
)?;
let path = MerklePath {
chip_1,
chip_2,
domain: OrchardHashDomains::MerkleCrh,
leaf_pos: self.leaf_pos,
path: self.merkle_path,
};
let computed_final_root =
path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?;
if let Some(leaf_pos) = self.leaf_pos {
// The expected final root
let final_root = {
let path = tree::MerklePath::new(leaf_pos, self.merkle_path.unwrap());
let leaf =
ExtractedNoteCommitment::from_bytes(&self.leaf.unwrap().to_repr()).unwrap();
path.root(leaf)
};
// Check the computed final root against the expected final root.
assert_eq!(computed_final_root.value().unwrap(), &final_root.inner());
}
Ok(())
}
}
#[test]
fn merkle_chip() {
let mut rng = OsRng;
// Choose a random leaf and position
let leaf = pallas::Base::random(rng);
let pos = rng.next_u32();
// Choose a path of random inner nodes
let path: Vec<_> = (0..(MERKLE_DEPTH))
.map(|_| pallas::Base::random(rng))
.collect();
// The root is provided as a public input in the Orchard circuit.
let circuit = MyCircuit {
leaf: Some(leaf),
leaf_pos: Some(pos),
merkle_path: Some(path.try_into().unwrap()),
};
let prover = MockProver::run(11, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[cfg(feature = "dev-graph")]
#[test]
fn print_merkle_chip() {
use plotters::prelude::*;
let root = BitMapBackend::new("merkle-path-layout.png", (1024, 7680)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root.titled("MerkleCRH Path", ("sans-serif", 60)).unwrap();
let circuit = MyCircuit::default();
halo2::dev::CircuitLayout::default()
.show_labels(false)
.render(11, &circuit, &root)
.unwrap();
}
}

View File

@ -1,493 +0,0 @@
use halo2::{
circuit::{AssignedCell, Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use super::MerkleInstructions;
use crate::{
circuit::gadget::{
ecc::FixedPoints,
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
CommitDomains, HashDomains, SinsemillaInstructions,
},
utilities::{
bitrange_subset,
cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions},
UtilitiesInstructions,
},
},
primitives::sinsemilla,
};
use group::ff::PrimeField;
use std::array;
#[derive(Clone, Debug)]
pub struct MerkleConfig<Hash, Commit, Fixed>
where
Hash: HashDomains<pallas::Affine>,
Fixed: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, Fixed, Hash>,
{
advices: [Column<Advice>; 5],
q_decompose: Selector,
pub(super) cond_swap_config: CondSwapConfig,
pub(super) sinsemilla_config: SinsemillaConfig<Hash, Commit, Fixed>,
}
#[derive(Clone, Debug)]
pub struct MerkleChip<Hash, Commit, Fixed>
where
Hash: HashDomains<pallas::Affine>,
Fixed: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, Fixed, Hash>,
{
config: MerkleConfig<Hash, Commit, Fixed>,
}
impl<Hash, Commit, Fixed> Chip<pallas::Base> for MerkleChip<Hash, Commit, Fixed>
where
Hash: HashDomains<pallas::Affine>,
Fixed: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, Fixed, Hash>,
{
type Config = MerkleConfig<Hash, Commit, Fixed>;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<Hash, Commit, F> MerkleChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
pub fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
sinsemilla_config: SinsemillaConfig<Hash, Commit, F>,
) -> MerkleConfig<Hash, Commit, F> {
// All five advice columns are equality-enabled by SinsemillaConfig.
let advices = sinsemilla_config.advices();
let cond_swap_config = CondSwapChip::configure(meta, advices);
// This selector enables the decomposition gate.
let q_decompose = meta.selector();
// Check that pieces have been decomposed correctly for Sinsemilla hash.
// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
//
// a = a_0||a_1 = l || (bits 0..=239 of left)
// b = b_0||b_1||b_2
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
// c = bits 5..=254 of right
//
// The message pieces `a`, `b`, `c` are constrained by Sinsemilla to be
// 250 bits, 20 bits, and 250 bits respectively.
//
/*
The pieces and subpieces are arranged in the following configuration:
| A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose |
-------------------------------------------------------
| a | b | c | left | right | 1 |
| z1_a | z1_b | b_1 | b_2 | l | |
*/
meta.create_gate("Decomposition check", |meta| {
let q_decompose = meta.query_selector(q_decompose);
let l_whole = meta.query_advice(advices[4], Rotation::next());
let two_pow_5 = pallas::Base::from(1 << 5);
let two_pow_10 = two_pow_5.square();
// a_whole is constrained by Sinsemilla to be 250 bits.
let a_whole = meta.query_advice(advices[0], Rotation::cur());
// b_whole is constrained by Sinsemilla to be 20 bits.
let b_whole = meta.query_advice(advices[1], Rotation::cur());
// c_whole is constrained by Sinsemilla to be 250 bits.
let c_whole = meta.query_advice(advices[2], Rotation::cur());
let left_node = meta.query_advice(advices[3], Rotation::cur());
let right_node = meta.query_advice(advices[4], Rotation::cur());
// a = a_0||a_1 = l || (bits 0..=239 of left)
// Check that a_0 = l
//
// z_1 of SinsemillaHash(a) = a_1
let z1_a = meta.query_advice(advices[0], Rotation::next());
let a_1 = z1_a;
// a_0 = a - (a_1 * 2^10)
let a_0 = a_whole - a_1.clone() * pallas::Base::from(1 << 10);
let l_check = a_0 - l_whole;
// b = b_0||b_1||b_2
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
// The Orchard specification allows this representation to be non-canonical.
// <https://zips.z.cash/protocol/protocol.pdf#merklepath>
//
// z_1 of SinsemillaHash(b) = b_1 + 2^5 b_2
// => b_0 = b - (z1_b * 2^10)
let z1_b = meta.query_advice(advices[1], Rotation::next());
// b_1 has been constrained to be 5 bits outside this gate.
let b_1 = meta.query_advice(advices[2], Rotation::next());
// b_2 has been constrained to be 5 bits outside this gate.
let b_2 = meta.query_advice(advices[3], Rotation::next());
// Constrain b_1 + 2^5 b_2 = z1_b
let b1_b2_check = z1_b.clone() - (b_1.clone() + b_2.clone() * two_pow_5);
// Derive b_0 (constrained by SinsemillaHash to be 10 bits)
let b_0 = b_whole - (z1_b * two_pow_10);
// Check that left = a_1 (240 bits) || b_0 (10 bits) || b_1 (5 bits)
let left_check = {
let reconstructed = {
let two_pow_240 = pallas::Base::from_u128(1 << 120).square();
a_1 + (b_0 + b_1 * two_pow_10) * two_pow_240
};
reconstructed - left_node
};
// Check that right = b_2 (5 bits) || c (250 bits)
// The Orchard specification allows this representation to be non-canonical.
// <https://zips.z.cash/protocol/protocol.pdf#merklepath>
let right_check = b_2 + c_whole * two_pow_5 - right_node;
array::IntoIter::new([
("l_check", l_check),
("left_check", left_check),
("right_check", right_check),
("b1_b2_check", b1_b2_check),
])
.map(move |(name, poly)| (name, q_decompose.clone() * poly))
});
MerkleConfig {
advices,
q_decompose,
cond_swap_config,
sinsemilla_config,
}
}
pub fn construct(config: MerkleConfig<Hash, Commit, F>) -> Self {
MerkleChip { config }
}
}
impl<Hash, Commit, F, const MERKLE_DEPTH: usize>
MerkleInstructions<pallas::Affine, MERKLE_DEPTH, { sinsemilla::K }, { sinsemilla::C }>
for MerkleChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
#[allow(non_snake_case)]
fn hash_layer(
&self,
mut layouter: impl Layouter<pallas::Base>,
Q: pallas::Affine,
// l = MERKLE_DEPTH - layer - 1
l: usize,
left: Self::Var,
right: Self::Var,
) -> Result<Self::Var, Error> {
let config = self.config().clone();
// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
// We need to hash `l || left || right`, where `l` is a 10-bit value.
// We allow `left` and `right` to be non-canonical 255-bit encodings.
//
// a = a_0||a_1 = l || (bits 0..=239 of left)
// b = b_0||b_1||b_2
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
// c = bits 5..=254 of right
// `a = a_0||a_1` = `l` || (bits 0..=239 of `left`)
let a = {
let a = {
// a_0 = l
let a_0 = bitrange_subset(&pallas::Base::from(l as u64), 0..10);
// a_1 = (bits 0..=239 of `left`)
let a_1 = left.value().map(|value| bitrange_subset(value, 0..240));
a_1.map(|a_1| a_0 + a_1 * pallas::Base::from(1 << 10))
};
self.witness_message_piece(layouter.namespace(|| "Witness a = a_0 || a_1"), a, 25)?
};
// b = b_0 || b_1 || b_2
// = (bits 240..=249 of left) || (bits 250..=254 of left) || (bits 0..=4 of right)
let (b_1, b_2, b) = {
// b_0 = (bits 240..=249 of `left`)
let b_0 = left.value().map(|value| bitrange_subset(value, 240..250));
// b_1 = (bits 250..=254 of `left`)
// Constrain b_1 to 5 bits.
let b_1 = {
let b_1 = left
.value()
.map(|value| bitrange_subset(value, 250..(pallas::Base::NUM_BITS as usize)));
config
.sinsemilla_config
.lookup_config()
.witness_short_check(layouter.namespace(|| "Constrain b_1 to 5 bits"), b_1, 5)?
};
// b_2 = (bits 0..=4 of `right`)
// Constrain b_2 to 5 bits.
let b_2 = {
let b_2 = right.value().map(|value| bitrange_subset(value, 0..5));
config
.sinsemilla_config
.lookup_config()
.witness_short_check(layouter.namespace(|| "Constrain b_2 to 5 bits"), b_2, 5)?
};
let b = {
let b = b_0
.zip(b_1.value())
.zip(b_2.value())
.map(|((b_0, b_1), b_2)| {
b_0 + b_1 * pallas::Base::from(1 << 10) + b_2 * pallas::Base::from(1 << 15)
});
self.witness_message_piece(
layouter.namespace(|| "Witness b = b_0 || b_1 || b_2"),
b,
2,
)?
};
(b_1, b_2, b)
};
let c = {
// `c = bits 5..=254 of `right`
let c = right
.value()
.map(|value| bitrange_subset(value, 5..(pallas::Base::NUM_BITS as usize)));
self.witness_message_piece(layouter.namespace(|| "Witness c"), c, 25)?
};
let (point, zs) = self.hash_to_point(
layouter.namespace(|| format!("hash at l = {}", l)),
Q,
vec![a.clone(), b.clone(), c.clone()].into(),
)?;
let z1_a = zs[0][1].clone();
let z1_b = zs[1][1].clone();
// Check that the pieces have been decomposed properly.
/*
The pieces and subpieces are arranged in the following configuration:
| A_0 | A_1 | A_2 | A_3 | A_4 | q_decompose |
-------------------------------------------------------
| a | b | c | left | right | 1 |
| z1_a | z1_b | b_1 | b_2 | l | |
*/
{
layouter.assign_region(
|| "Check piece decomposition",
|mut region| {
// Set the fixed column `l` to the current l.
// Recall that l = MERKLE_DEPTH - layer - 1.
// The layer with 2^n nodes is called "layer n".
config.q_decompose.enable(&mut region, 0)?;
region.assign_advice_from_constant(
|| format!("l {}", l),
config.advices[4],
1,
pallas::Base::from(l as u64),
)?;
// Offset 0
// Copy and assign `a` at the correct position.
a.cell_value()
.copy_advice(|| "copy a", &mut region, config.advices[0], 0)?;
// Copy and assign `b` at the correct position.
b.cell_value()
.copy_advice(|| "copy b", &mut region, config.advices[1], 0)?;
// Copy and assign `c` at the correct position.
c.cell_value()
.copy_advice(|| "copy c", &mut region, config.advices[2], 0)?;
// Copy and assign the left node at the correct position.
left.copy_advice(|| "left", &mut region, config.advices[3], 0)?;
// Copy and assign the right node at the correct position.
right.copy_advice(|| "right", &mut region, config.advices[4], 0)?;
// Offset 1
// Copy and assign z_1 of SinsemillaHash(a) = a_1
z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?;
// Copy and assign z_1 of SinsemillaHash(b) = b_1
z1_b.copy_advice(|| "z1_b", &mut region, config.advices[1], 1)?;
// Copy `b_1`, which has been constrained to be a 5-bit value
b_1.copy_advice(|| "b_1", &mut region, config.advices[2], 1)?;
// Copy `b_2`, which has been constrained to be a 5-bit value
b_2.copy_advice(|| "b_2", &mut region, config.advices[3], 1)?;
Ok(())
},
)?;
}
let result = Self::extract(&point);
// Check layer hash output against Sinsemilla primitives hash
#[cfg(test)]
{
use super::MERKLE_CRH_PERSONALIZATION;
use crate::{primitives::sinsemilla::HashDomain, spec::i2lebsp};
use group::ff::PrimeFieldBits;
if let (Some(left), Some(right)) = (left.value(), right.value()) {
let l = i2lebsp::<10>(l as u64);
let left: Vec<_> = left
.to_le_bits()
.iter()
.by_val()
.take(pallas::Base::NUM_BITS as usize)
.collect();
let right: Vec<_> = right
.to_le_bits()
.iter()
.by_val()
.take(pallas::Base::NUM_BITS as usize)
.collect();
let merkle_crh = HashDomain::new(MERKLE_CRH_PERSONALIZATION);
let mut message = l.to_vec();
message.extend_from_slice(&left);
message.extend_from_slice(&right);
let expected = merkle_crh.hash(message.into_iter()).unwrap();
assert_eq!(expected.to_repr(), result.value().unwrap().to_repr());
}
}
Ok(result)
}
}
impl<Hash, Commit, F> UtilitiesInstructions<pallas::Base> for MerkleChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl<Hash, Commit, F> CondSwapInstructions<pallas::Base> for MerkleChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
#[allow(clippy::type_complexity)]
fn swap(
&self,
layouter: impl Layouter<pallas::Base>,
pair: (Self::Var, Option<pallas::Base>),
swap: Option<bool>,
) -> Result<(Self::Var, Self::Var), Error> {
let config = self.config().cond_swap_config.clone();
let chip = CondSwapChip::<pallas::Base>::construct(config);
chip.swap(layouter, pair, swap)
}
}
impl<Hash, Commit, F> SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }>
for MerkleChip<Hash, Commit, F>
where
Hash: HashDomains<pallas::Affine>,
F: FixedPoints<pallas::Affine>,
Commit: CommitDomains<pallas::Affine, F, Hash>,
{
type CellValue = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::CellValue;
type Message = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::Message;
type MessagePiece = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::MessagePiece;
type RunningSum = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::RunningSum;
type X = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::X;
type NonIdentityPoint = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::NonIdentityPoint;
type FixedPoints = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::FixedPoints;
type HashDomains = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::HashDomains;
type CommitDomains = <SinsemillaChip<Hash, Commit, F> as SinsemillaInstructions<
pallas::Affine,
{ sinsemilla::K },
{ sinsemilla::C },
>>::CommitDomains;
fn witness_message_piece(
&self,
layouter: impl Layouter<pallas::Base>,
value: Option<pallas::Base>,
num_words: usize,
) -> Result<Self::MessagePiece, Error> {
let config = self.config().sinsemilla_config.clone();
let chip = SinsemillaChip::<Hash, Commit, F>::construct(config);
chip.witness_message_piece(layouter, value, num_words)
}
#[allow(non_snake_case)]
#[allow(clippy::type_complexity)]
fn hash_to_point(
&self,
layouter: impl Layouter<pallas::Base>,
Q: pallas::Affine,
message: Self::Message,
) -> Result<(Self::NonIdentityPoint, Vec<Vec<Self::CellValue>>), Error> {
let config = self.config().sinsemilla_config.clone();
let chip = SinsemillaChip::<Hash, Commit, F>::construct(config);
chip.hash_to_point(layouter, Q, message)
}
fn extract(point: &Self::NonIdentityPoint) -> Self::X {
SinsemillaChip::<Hash, Commit, F>::extract(point)
}
}

View File

@ -1,68 +0,0 @@
//! Gadget and chips for the Sinsemilla hash function.
use ff::PrimeFieldBits;
use halo2::{
arithmetic::FieldExt,
circuit::{AssignedCell, Cell},
};
use std::fmt::Debug;
/// A [`Message`] composed of several [`MessagePiece`]s.
#[derive(Clone, Debug)]
pub struct Message<F: FieldExt, const K: usize, const MAX_WORDS: usize>(Vec<MessagePiece<F, K>>);
impl<F: FieldExt + PrimeFieldBits, const K: usize, const MAX_WORDS: usize>
From<Vec<MessagePiece<F, K>>> for Message<F, K, MAX_WORDS>
{
fn from(pieces: Vec<MessagePiece<F, K>>) -> Self {
// A message cannot contain more than `MAX_WORDS` words.
assert!(pieces.iter().map(|piece| piece.num_words()).sum::<usize>() < MAX_WORDS);
Message(pieces)
}
}
impl<F: FieldExt + PrimeFieldBits, const K: usize, const MAX_WORDS: usize> std::ops::Deref
for Message<F, K, MAX_WORDS>
{
type Target = [MessagePiece<F, K>];
fn deref(&self) -> &[MessagePiece<F, K>] {
&self.0
}
}
/// A [`MessagePiece`] of some bitlength.
///
/// The piece must fit within a base field element, which means its length
/// cannot exceed the base field's `NUM_BITS`.
#[derive(Clone, Debug)]
pub struct MessagePiece<F: FieldExt, const K: usize> {
cell_value: AssignedCell<F, F>,
/// The number of K-bit words in this message piece.
num_words: usize,
}
impl<F: FieldExt + PrimeFieldBits, const K: usize> MessagePiece<F, K> {
pub fn new(cell_value: AssignedCell<F, F>, num_words: usize) -> Self {
assert!(num_words * K < F::NUM_BITS as usize);
Self {
cell_value,
num_words,
}
}
pub fn num_words(&self) -> usize {
self.num_words
}
pub fn cell(&self) -> Cell {
self.cell_value.cell()
}
pub fn field_elem(&self) -> Option<F> {
self.cell_value.value().cloned()
}
pub fn cell_value(&self) -> AssignedCell<F, F> {
self.cell_value.clone()
}
}

View File

@ -1,432 +0,0 @@
//! Utility gadgets.
use ff::PrimeFieldBits;
use halo2::{
circuit::{AssignedCell, Cell, Layouter},
plonk::{Advice, Column, Error, Expression},
};
use pasta_curves::arithmetic::FieldExt;
use std::{array, ops::Range};
pub(crate) mod cond_swap;
pub(crate) mod decompose_running_sum;
pub(crate) mod lookup_range_check;
/// Trait for a variable in the circuit.
pub trait Var<F: FieldExt>: Clone + std::fmt::Debug + From<AssignedCell<F, F>> {
/// The cell at which this variable was allocated.
fn cell(&self) -> Cell;
/// The value allocated to this variable.
fn value(&self) -> Option<F>;
}
impl<F: FieldExt> Var<F> for AssignedCell<F, F> {
fn cell(&self) -> Cell {
self.cell()
}
fn value(&self) -> Option<F> {
self.value().cloned()
}
}
/// Trait for utilities used across circuits.
pub trait UtilitiesInstructions<F: FieldExt> {
/// Variable in the circuit.
type Var: Var<F>;
/// Load a variable.
fn load_private(
&self,
mut layouter: impl Layouter<F>,
column: Column<Advice>,
value: Option<F>,
) -> Result<Self::Var, Error> {
layouter.assign_region(
|| "load private",
|mut region| {
region
.assign_advice(
|| "load private",
column,
0,
|| value.ok_or(Error::Synthesis),
)
.map(Self::Var::from)
},
)
}
}
pub(crate) fn transpose_option_array<T: Copy + std::fmt::Debug, const LEN: usize>(
option_array: Option<[T; LEN]>,
) -> [Option<T>; LEN] {
let mut ret = [None; LEN];
if let Some(arr) = option_array {
for (entry, value) in ret.iter_mut().zip(array::IntoIter::new(arr)) {
*entry = Some(value);
}
}
ret
}
/// Checks that an expresssion is either 1 or 0.
pub fn bool_check<F: FieldExt>(value: Expression<F>) -> Expression<F> {
range_check(value, 2)
}
/// If `a` then `b`, else `c`. Returns (a * b) + (1 - a) * c.
///
/// `a` must be a boolean-constrained expression.
pub fn ternary<F: FieldExt>(a: Expression<F>, b: Expression<F>, c: Expression<F>) -> Expression<F> {
let one_minus_a = Expression::Constant(F::one()) - a.clone();
a * b + one_minus_a * c
}
/// Takes a specified subsequence of the little-endian bit representation of a field element.
/// The bits are numbered from 0 for the LSB.
pub fn bitrange_subset<F: PrimeFieldBits>(field_elem: &F, bitrange: Range<usize>) -> F {
// We can allow a subsequence of length NUM_BITS, because
// field_elem.to_le_bits() returns canonical bitstrings.
assert!(bitrange.end <= F::NUM_BITS as usize);
field_elem
.to_le_bits()
.iter()
.by_val()
.skip(bitrange.start)
.take(bitrange.end - bitrange.start)
.rev()
.fold(F::zero(), |acc, bit| {
if bit {
acc.double() + F::one()
} else {
acc.double()
}
})
}
/// Check that an expression is in the small range [0..range),
/// i.e. 0 ≤ word < range.
pub fn range_check<F: FieldExt>(word: Expression<F>, range: usize) -> Expression<F> {
(1..range).fold(word.clone(), |acc, i| {
acc * (Expression::Constant(F::from(i as u64)) - word.clone())
})
}
/// Decompose a word `alpha` into `window_num_bits` bits (little-endian)
/// For a window size of `w`, this returns [k_0, ..., k_n] where each `k_i`
/// is a `w`-bit value, and `scalar = k_0 + k_1 * w + k_n * w^n`.
///
/// # Panics
///
/// We are returning a `Vec<u8>` which means the window size is limited to
/// <= 8 bits.
pub fn decompose_word<F: PrimeFieldBits>(
word: &F,
word_num_bits: usize,
window_num_bits: usize,
) -> Vec<u8> {
assert!(window_num_bits <= 8);
// Pad bits to multiple of window_num_bits
let padding = (window_num_bits - (word_num_bits % window_num_bits)) % window_num_bits;
let bits: Vec<bool> = word
.to_le_bits()
.into_iter()
.take(word_num_bits)
.chain(std::iter::repeat(false).take(padding))
.collect();
assert_eq!(bits.len(), word_num_bits + padding);
bits.chunks_exact(window_num_bits)
.map(|chunk| chunk.iter().rev().fold(0, |acc, b| (acc << 1) + (*b as u8)))
.collect()
}
/// The u64 integer represented by an L-bit little-endian bitstring.
///
/// # Panics
///
/// Panics if the bitstring is longer than 64 bits.
pub fn lebs2ip<const L: usize>(bits: &[bool; L]) -> u64 {
assert!(L <= 64);
bits.iter()
.enumerate()
.fold(0u64, |acc, (i, b)| acc + if *b { 1 << i } else { 0 })
}
/// The sequence of bits representing a u64 in little-endian order.
///
/// # Panics
///
/// Panics if the expected length of the sequence `NUM_BITS` exceeds
/// 64.
pub fn i2lebsp<const NUM_BITS: usize>(int: u64) -> [bool; NUM_BITS] {
/// Takes in an FnMut closure and returns a constant-length array with elements of
/// type `Output`.
fn gen_const_array<Output: Copy + Default, const LEN: usize>(
mut closure: impl FnMut(usize) -> Output,
) -> [Output; LEN] {
let mut ret: [Output; LEN] = [Default::default(); LEN];
for (bit, val) in ret.iter_mut().zip((0..LEN).map(|idx| closure(idx))) {
*bit = val;
}
ret
}
assert!(NUM_BITS <= 64);
gen_const_array(|mask: usize| (int & (1 << mask)) != 0)
}
#[cfg(test)]
mod tests {
use super::*;
use bigint::U256;
use group::ff::{Field, PrimeField};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::{FailureLocation, MockProver, VerifyFailure},
plonk::{Any, Circuit, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use proptest::prelude::*;
use rand::rngs::OsRng;
use std::convert::TryInto;
use std::iter;
#[test]
fn test_range_check() {
struct MyCircuit<const RANGE: usize>(u8);
impl<const RANGE: usize> UtilitiesInstructions<pallas::Base> for MyCircuit<RANGE> {
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
#[derive(Clone)]
struct Config {
selector: Selector,
advice: Column<Advice>,
}
impl<const RANGE: usize> Circuit<pallas::Base> for MyCircuit<RANGE> {
type Config = Config;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
MyCircuit(self.0)
}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
let selector = meta.selector();
let advice = meta.advice_column();
meta.create_gate("range check", |meta| {
let selector = meta.query_selector(selector);
let advice = meta.query_advice(advice, Rotation::cur());
vec![selector * range_check(advice, RANGE)]
});
Config { selector, advice }
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<pallas::Base>,
) -> Result<(), Error> {
layouter.assign_region(
|| "range constrain",
|mut region| {
config.selector.enable(&mut region, 0)?;
region.assign_advice(
|| format!("witness {}", self.0),
config.advice,
0,
|| Ok(pallas::Base::from(self.0 as u64)),
)?;
Ok(())
},
)
}
}
for i in 0..8 {
let circuit: MyCircuit<8> = MyCircuit(i);
let prover = MockProver::<pallas::Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
{
let circuit: MyCircuit<8> = MyCircuit(8);
let prover = MockProver::<pallas::Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::ConstraintNotSatisfied {
constraint: ((0, "range check").into(), 0, "").into(),
location: FailureLocation::InRegion {
region: (0, "range constrain").into(),
offset: 0,
},
cell_values: vec![(((Any::Advice, 0).into(), 0).into(), "0x8".to_string())],
}])
);
}
}
#[test]
fn test_bitrange_subset() {
let rng = OsRng;
// Subset full range.
{
let field_elem = pallas::Base::random(rng);
let bitrange = 0..(pallas::Base::NUM_BITS as usize);
let subset = bitrange_subset(&field_elem, bitrange);
assert_eq!(field_elem, subset);
}
// Subset zero bits
{
let field_elem = pallas::Base::random(rng);
let bitrange = 0..0;
let subset = bitrange_subset(&field_elem, bitrange);
assert_eq!(pallas::Base::zero(), subset);
}
// Closure to decompose field element into pieces using consecutive ranges,
// and check that we recover the original.
let decompose = |field_elem: pallas::Base, ranges: &[Range<usize>]| {
assert_eq!(
ranges.iter().map(|range| range.len()).sum::<usize>(),
pallas::Base::NUM_BITS as usize
);
assert_eq!(ranges[0].start, 0);
assert_eq!(ranges.last().unwrap().end, pallas::Base::NUM_BITS as usize);
// Check ranges are contiguous
#[allow(unused_assignments)]
{
let mut ranges = ranges.iter();
let mut range = ranges.next().unwrap();
if let Some(next_range) = ranges.next() {
assert_eq!(range.end, next_range.start);
range = next_range;
}
}
let subsets = ranges
.iter()
.map(|range| bitrange_subset(&field_elem, range.clone()))
.collect::<Vec<_>>();
let mut sum = subsets[0];
let mut num_bits = 0;
for (idx, subset) in subsets.iter().skip(1).enumerate() {
// 2^num_bits
let range_shift: [u8; 32] = {
num_bits += ranges[idx].len();
let mut range_shift = [0u8; 32];
U256([2, 0, 0, 0])
.pow(U256([num_bits as u64, 0, 0, 0]))
.to_little_endian(&mut range_shift);
range_shift
};
sum += subset * pallas::Base::from_repr(range_shift).unwrap();
}
assert_eq!(field_elem, sum);
};
decompose(pallas::Base::random(rng), &[0..255]);
decompose(pallas::Base::random(rng), &[0..1, 1..255]);
decompose(pallas::Base::random(rng), &[0..254, 254..255]);
decompose(pallas::Base::random(rng), &[0..127, 127..255]);
decompose(pallas::Base::random(rng), &[0..128, 128..255]);
decompose(
pallas::Base::random(rng),
&[0..50, 50..100, 100..150, 150..200, 200..255],
);
}
prop_compose! {
fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> pallas::Scalar {
// Instead of rejecting out-of-range bytes, let's reduce them.
let mut buf = [0; 64];
buf[..32].copy_from_slice(&bytes);
pallas::Scalar::from_bytes_wide(&buf)
}
}
proptest! {
#[test]
fn test_decompose_word(
scalar in arb_scalar(),
window_num_bits in 1u8..9
) {
// Get decomposition into `window_num_bits` bits
let decomposed = decompose_word(&scalar, pallas::Scalar::NUM_BITS as usize, window_num_bits as usize);
// Flatten bits
let bits = decomposed
.iter()
.flat_map(|window| (0..window_num_bits).map(move |mask| (window & (1 << mask)) != 0));
// Ensure this decomposition contains 256 or fewer set bits.
assert!(!bits.clone().skip(32*8).any(|b| b));
// Pad or truncate bits to 32 bytes
let bits: Vec<bool> = bits.chain(iter::repeat(false)).take(32*8).collect();
let bytes: Vec<u8> = bits.chunks_exact(8).map(|chunk| chunk.iter().rev().fold(0, |acc, b| (acc << 1) + (*b as u8))).collect();
// Check that original scalar is recovered from decomposition
assert_eq!(scalar, pallas::Scalar::from_repr(bytes.try_into().unwrap()).unwrap());
}
}
#[test]
fn lebs2ip_round_trip() {
use rand::rngs::OsRng;
let mut rng = OsRng;
{
let int = rng.next_u64();
assert_eq!(lebs2ip::<64>(&i2lebsp(int)), int);
}
assert_eq!(lebs2ip::<64>(&i2lebsp(0)), 0);
assert_eq!(
lebs2ip::<64>(&i2lebsp(0xFFFFFFFFFFFFFFFF)),
0xFFFFFFFFFFFFFFFF
);
}
#[test]
fn i2lebsp_round_trip() {
{
let bitstring = (0..64).map(|_| rand::random()).collect::<Vec<_>>();
assert_eq!(
i2lebsp::<64>(lebs2ip::<64>(&bitstring.clone().try_into().unwrap())).to_vec(),
bitstring
);
}
{
let bitstring = [false; 64];
assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring);
}
{
let bitstring = [true; 64];
assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring);
}
{
let bitstring = [];
assert_eq!(i2lebsp(lebs2ip(&bitstring)), bitstring);
}
}
}

View File

@ -1,294 +0,0 @@
use super::{bool_check, ternary, UtilitiesInstructions};
use halo2::{
circuit::{AssignedCell, Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::arithmetic::FieldExt;
use std::{array, marker::PhantomData};
pub trait CondSwapInstructions<F: FieldExt>: UtilitiesInstructions<F> {
#[allow(clippy::type_complexity)]
/// Given an input pair (a,b) and a `swap` boolean flag, returns
/// (b,a) if `swap` is set, else (a,b) if `swap` is not set.
///
/// The second element of the pair is required to be a witnessed
/// value, not a variable that already exists in the circuit.
fn swap(
&self,
layouter: impl Layouter<F>,
pair: (Self::Var, Option<F>),
swap: Option<bool>,
) -> Result<(Self::Var, Self::Var), Error>;
}
/// A chip implementing a conditional swap.
#[derive(Clone, Debug)]
pub struct CondSwapChip<F> {
config: CondSwapConfig,
_marker: PhantomData<F>,
}
impl<F: FieldExt> Chip<F> for CondSwapChip<F> {
type Config = CondSwapConfig;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
#[derive(Clone, Debug)]
pub struct CondSwapConfig {
pub q_swap: Selector,
pub a: Column<Advice>,
pub b: Column<Advice>,
pub a_swapped: Column<Advice>,
pub b_swapped: Column<Advice>,
pub swap: Column<Advice>,
}
impl<F: FieldExt> UtilitiesInstructions<F> for CondSwapChip<F> {
type Var = AssignedCell<F, F>;
}
impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
#[allow(clippy::type_complexity)]
fn swap(
&self,
mut layouter: impl Layouter<F>,
pair: (Self::Var, Option<F>),
swap: Option<bool>,
) -> Result<(Self::Var, Self::Var), Error> {
let config = self.config();
layouter.assign_region(
|| "swap",
|mut region| {
// Enable `q_swap` selector
config.q_swap.enable(&mut region, 0)?;
// Copy in `a` value
let a = pair.0.copy_advice(|| "copy a", &mut region, config.a, 0)?;
// Witness `b` value
let b = region.assign_advice(
|| "witness b",
config.b,
0,
|| pair.1.ok_or(Error::Synthesis),
)?;
// Witness `swap` value
let swap_val = swap.map(|swap| F::from(swap as u64));
region.assign_advice(
|| "swap",
config.swap,
0,
|| swap_val.ok_or(Error::Synthesis),
)?;
// Conditionally swap a
let a_swapped = {
let a_swapped = a
.value()
.zip(b.value())
.zip(swap)
.map(|((a, b), swap)| if swap { b } else { a })
.cloned();
region.assign_advice(
|| "a_swapped",
config.a_swapped,
0,
|| a_swapped.ok_or(Error::Synthesis),
)?
};
// Conditionally swap b
let b_swapped = {
let b_swapped = a
.value()
.zip(b.value())
.zip(swap)
.map(|((a, b), swap)| if swap { a } else { b })
.cloned();
region.assign_advice(
|| "b_swapped",
config.b_swapped,
0,
|| b_swapped.ok_or(Error::Synthesis),
)?
};
// Return swapped pair
Ok((a_swapped, b_swapped))
},
)
}
}
impl<F: FieldExt> CondSwapChip<F> {
/// Configures this chip for use in a circuit.
///
/// # Side-effects
///
/// `advices[0]` will be equality-enabled.
pub fn configure(
meta: &mut ConstraintSystem<F>,
advices: [Column<Advice>; 5],
) -> CondSwapConfig {
let a = advices[0];
// Only column a is used in an equality constraint directly by this chip.
meta.enable_equality(a);
let q_swap = meta.selector();
let config = CondSwapConfig {
q_swap,
a,
b: advices[1],
a_swapped: advices[2],
b_swapped: advices[3],
swap: advices[4],
};
// TODO: optimise shape of gate for Merkle path validation
meta.create_gate("a' = b ⋅ swap + a ⋅ (1-swap)", |meta| {
let q_swap = meta.query_selector(q_swap);
let a = meta.query_advice(config.a, Rotation::cur());
let b = meta.query_advice(config.b, Rotation::cur());
let a_swapped = meta.query_advice(config.a_swapped, Rotation::cur());
let b_swapped = meta.query_advice(config.b_swapped, Rotation::cur());
let swap = meta.query_advice(config.swap, Rotation::cur());
// This checks that `a_swapped` is equal to `b` when `swap` is set,
// but remains as `a` when `swap` is not set.
let a_check = a_swapped - ternary(swap.clone(), b.clone(), a.clone());
// This checks that `b_swapped` is equal to `a` when `swap` is set,
// but remains as `b` when `swap` is not set.
let b_check = b_swapped - ternary(swap.clone(), a, b);
// Check `swap` is boolean.
let bool_check = bool_check(swap);
array::IntoIter::new([a_check, b_check, bool_check])
.map(move |poly| q_swap.clone() * poly)
});
config
}
pub fn construct(config: CondSwapConfig) -> Self {
CondSwapChip {
config,
_marker: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::super::UtilitiesInstructions;
use super::{CondSwapChip, CondSwapConfig, CondSwapInstructions};
use group::ff::Field;
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas::Base};
use rand::rngs::OsRng;
#[test]
fn cond_swap() {
#[derive(Default)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
b: Option<F>,
swap: Option<bool>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
type Config = CondSwapConfig;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let advices = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
CondSwapChip::<F>::configure(meta, advices)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let chip = CondSwapChip::<F>::construct(config.clone());
// Load the pair and the swap flag into the circuit.
let a = chip.load_private(layouter.namespace(|| "a"), config.a, self.a)?;
// Return the swapped pair.
let swapped_pair = chip.swap(
layouter.namespace(|| "swap"),
(a.clone(), self.b),
self.swap,
)?;
if let Some(swap) = self.swap {
if swap {
// Check that `a` and `b` have been swapped
assert_eq!(swapped_pair.0.value().unwrap(), &self.b.unwrap());
assert_eq!(swapped_pair.1.value().unwrap(), a.value().unwrap());
} else {
// Check that `a` and `b` have not been swapped
assert_eq!(swapped_pair.0.value().unwrap(), a.value().unwrap());
assert_eq!(swapped_pair.1.value().unwrap(), &self.b.unwrap());
}
}
Ok(())
}
}
let rng = OsRng;
// Test swap case
{
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::random(rng)),
b: Some(Base::random(rng)),
swap: Some(true),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Test non-swap case
{
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::random(rng)),
b: Some(Base::random(rng)),
swap: Some(false),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}
}

View File

@ -1,391 +0,0 @@
//! Decomposes an $n$-bit field element $\alpha$ into $W$ windows, each window
//! being a $K$-bit word, using a running sum $z$.
//! We constrain $K \leq 3$ for this helper.
//! $$\alpha = k_0 + (2^K) k_1 + (2^{2K}) k_2 + ... + (2^{(W-1)K}) k_{W-1}$$
//!
//! $z_0$ is initialized as $\alpha$. Each successive $z_{i+1}$ is computed as
//! $$z_{i+1} = (z_{i} - k_i) / (2^K).$$
//! $z_W$ is constrained to be zero.
//! The difference between each interstitial running sum output is constrained
//! to be $K$ bits, i.e.
//! `range_check`($k_i$, $2^K$),
//! where
//! ```text
//! range_check(word, range)
//! = word * (1 - word) * (2 - word) * ... * ((range - 1) - word)
//! ```
//!
//! Given that the `range_check` constraint will be toggled by a selector, in
//! practice we will have a `selector * range_check(word, range)` expression
//! of degree `range + 1`.
//!
//! This means that $2^K$ has to be at most `degree_bound - 1` in order for
//! the range check constraint to stay within the degree bound.
use ff::PrimeFieldBits;
use halo2::{
circuit::{AssignedCell, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use super::range_check;
use pasta_curves::arithmetic::FieldExt;
use std::marker::PhantomData;
/// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$.
pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<AssignedCell<F, F>>);
impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
type Target = Vec<AssignedCell<F, F>>;
fn deref(&self) -> &Vec<AssignedCell<F, F>> {
&self.0
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct RunningSumConfig<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize> {
pub q_range_check: Selector,
pub z: Column<Advice>,
_marker: PhantomData<F>,
}
impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
RunningSumConfig<F, WINDOW_NUM_BITS>
{
/// `perm` MUST include the advice column `z`.
///
/// # Panics
///
/// Panics if WINDOW_NUM_BITS > 3.
///
/// # Side-effects
///
/// `z` will be equality-enabled.
pub fn configure(
meta: &mut ConstraintSystem<F>,
q_range_check: Selector,
z: Column<Advice>,
) -> Self {
assert!(WINDOW_NUM_BITS <= 3);
meta.enable_equality(z);
let config = Self {
q_range_check,
z,
_marker: PhantomData,
};
meta.create_gate("range check", |meta| {
let q_range_check = meta.query_selector(config.q_range_check);
let z_cur = meta.query_advice(config.z, Rotation::cur());
let z_next = meta.query_advice(config.z, Rotation::next());
// z_i = 2^{K}⋅z_{i + 1} + k_i
// => k_i = z_i - 2^{K}⋅z_{i + 1}
let word = z_cur - z_next * F::from(1 << WINDOW_NUM_BITS);
vec![q_range_check * range_check(word, 1 << WINDOW_NUM_BITS)]
});
config
}
/// Decompose a field element alpha that is witnessed in this helper.
///
/// `strict` = true constrains the final running sum to be zero, i.e.
/// constrains alpha to be within WINDOW_NUM_BITS * num_windows bits.
pub fn witness_decompose(
&self,
region: &mut Region<'_, F>,
offset: usize,
alpha: Option<F>,
strict: bool,
word_num_bits: usize,
num_windows: usize,
) -> Result<RunningSum<F>, Error> {
let z_0 = region.assign_advice(
|| "z_0 = alpha",
self.z,
offset,
|| alpha.ok_or(Error::Synthesis),
)?;
self.decompose(region, offset, z_0, strict, word_num_bits, num_windows)
}
/// Decompose an existing variable alpha that is copied into this helper.
///
/// `strict` = true constrains the final running sum to be zero, i.e.
/// constrains alpha to be within WINDOW_NUM_BITS * num_windows bits.
pub fn copy_decompose(
&self,
region: &mut Region<'_, F>,
offset: usize,
alpha: AssignedCell<F, F>,
strict: bool,
word_num_bits: usize,
num_windows: usize,
) -> Result<RunningSum<F>, Error> {
let z_0 = alpha.copy_advice(|| "copy z_0 = alpha", region, self.z, offset)?;
self.decompose(region, offset, z_0, strict, word_num_bits, num_windows)
}
/// `z_0` must be the cell at `(self.z, offset)` in `region`.
///
/// # Panics
///
/// Panics if there are too many windows for the given word size.
fn decompose(
&self,
region: &mut Region<'_, F>,
offset: usize,
z_0: AssignedCell<F, F>,
strict: bool,
word_num_bits: usize,
num_windows: usize,
) -> Result<RunningSum<F>, Error> {
// Make sure that we do not have more windows than required for the number
// of bits in the word. In other words, every window must contain at least
// one bit of the word (no empty windows).
//
// For example, let:
// - word_num_bits = 64
// - WINDOW_NUM_BITS = 3
// In this case, the maximum allowed num_windows is 22:
// 3 * 22 < 64 + 3
//
assert!(WINDOW_NUM_BITS * num_windows < word_num_bits + WINDOW_NUM_BITS);
// Enable selectors
for idx in 0..num_windows {
self.q_range_check.enable(region, offset + idx)?;
}
// Decompose base field element into K-bit words.
let words: Vec<Option<u8>> = {
let words = z_0
.value()
.map(|word| super::decompose_word::<F>(word, word_num_bits, WINDOW_NUM_BITS));
if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; num_windows]
}
};
// Initialize empty vector to store running sum values [z_0, ..., z_W].
let mut zs: Vec<AssignedCell<F, F>> = vec![z_0.clone()];
let mut z = z_0;
// Assign running sum `z_{i+1}` = (z_i - k_i) / (2^K) for i = 0..=n-1.
// Outside of this helper, z_0 = alpha must have already been loaded into the
// `z` column at `offset`.
let two_pow_k_inv = F::from(1 << WINDOW_NUM_BITS as u64).invert().unwrap();
for (i, word) in words.iter().enumerate() {
// z_next = (z_cur - word) / (2^K)
let z_next = {
let word = word.map(|word| F::from(word as u64));
let z_next_val = z
.value()
.zip(word)
.map(|(z_cur_val, word)| (*z_cur_val - word) * two_pow_k_inv);
region.assign_advice(
|| format!("z_{:?}", i + 1),
self.z,
offset + i + 1,
|| z_next_val.ok_or(Error::Synthesis),
)?
};
// Update `z`.
z = z_next;
zs.push(z.clone());
}
assert_eq!(zs.len(), num_windows + 1);
if strict {
// Constrain the final running sum output to be zero.
region.constrain_constant(zs.last().unwrap().cell(), F::zero())?;
}
Ok(RunningSum(zs))
}
}
#[cfg(test)]
mod tests {
use super::*;
use group::ff::{Field, PrimeField};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::{MockProver, VerifyFailure},
plonk::{Any, Circuit, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use rand::rngs::OsRng;
use crate::circuit::gadget::ecc::chip::{
FIXED_BASE_WINDOW_SIZE, L_SCALAR_SHORT as L_SHORT, NUM_WINDOWS, NUM_WINDOWS_SHORT,
};
const L_BASE: usize = pallas::Base::NUM_BITS as usize;
#[test]
fn test_running_sum() {
struct MyCircuit<
F: FieldExt + PrimeFieldBits,
const WORD_NUM_BITS: usize,
const WINDOW_NUM_BITS: usize,
const NUM_WINDOWS: usize,
> {
alpha: Option<F>,
strict: bool,
}
impl<
F: FieldExt + PrimeFieldBits,
const WORD_NUM_BITS: usize,
const WINDOW_NUM_BITS: usize,
const NUM_WINDOWS: usize,
> Circuit<F> for MyCircuit<F, WORD_NUM_BITS, WINDOW_NUM_BITS, NUM_WINDOWS>
{
type Config = RunningSumConfig<F, WINDOW_NUM_BITS>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self {
alpha: None,
strict: self.strict,
}
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let z = meta.advice_column();
let q_range_check = meta.selector();
let constants = meta.fixed_column();
meta.enable_constant(constants);
RunningSumConfig::<F, WINDOW_NUM_BITS>::configure(meta, q_range_check, z)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
layouter.assign_region(
|| "decompose",
|mut region| {
let offset = 0;
let zs = config.witness_decompose(
&mut region,
offset,
self.alpha,
self.strict,
WORD_NUM_BITS,
NUM_WINDOWS,
)?;
let alpha = zs[0].clone();
let offset = offset + NUM_WINDOWS + 1;
config.copy_decompose(
&mut region,
offset,
alpha,
self.strict,
WORD_NUM_BITS,
NUM_WINDOWS,
)?;
Ok(())
},
)
}
}
// Random base field element
{
let alpha = pallas::Base::random(OsRng);
// Strict full decomposition should pass.
let circuit: MyCircuit<pallas::Base, L_BASE, FIXED_BASE_WINDOW_SIZE, { NUM_WINDOWS }> =
MyCircuit {
alpha: Some(alpha),
strict: true,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Random 64-bit word
{
let alpha = pallas::Base::from(rand::random::<u64>());
// Strict full decomposition should pass.
let circuit: MyCircuit<
pallas::Base,
L_SHORT,
FIXED_BASE_WINDOW_SIZE,
{ NUM_WINDOWS_SHORT },
> = MyCircuit {
alpha: Some(alpha),
strict: true,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// 2^66
{
let alpha = pallas::Base::from_u128(1 << 66);
// Strict partial decomposition should fail.
let circuit: MyCircuit<
pallas::Base,
L_SHORT,
FIXED_BASE_WINDOW_SIZE,
{ NUM_WINDOWS_SHORT },
> = MyCircuit {
alpha: Some(alpha),
strict: true,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![
VerifyFailure::Permutation {
column: (Any::Fixed, 0).into(),
row: 0
},
VerifyFailure::Permutation {
column: (Any::Fixed, 0).into(),
row: 1
},
VerifyFailure::Permutation {
column: (Any::Advice, 0).into(),
row: 22
},
VerifyFailure::Permutation {
column: (Any::Advice, 0).into(),
row: 45
},
])
);
// Non-strict partial decomposition should pass.
let circuit: MyCircuit<
pallas::Base,
{ L_SHORT },
FIXED_BASE_WINDOW_SIZE,
{ NUM_WINDOWS_SHORT },
> = MyCircuit {
alpha: Some(alpha),
strict: false,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}
}

View File

@ -1,628 +0,0 @@
//! Make use of a K-bit lookup table to decompose a field element into K-bit
//! words.
use halo2::{
circuit::{AssignedCell, Layouter, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Selector, TableColumn},
poly::Rotation,
};
use std::{convert::TryInto, marker::PhantomData};
use ff::PrimeFieldBits;
use super::*;
/// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$.
pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<AssignedCell<F, F>>);
impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
type Target = Vec<AssignedCell<F, F>>;
fn deref(&self) -> &Vec<AssignedCell<F, F>> {
&self.0
}
}
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct LookupRangeCheckConfig<F: FieldExt + PrimeFieldBits, const K: usize> {
pub q_lookup: Selector,
pub q_running: Selector,
pub q_bitshift: Selector,
pub running_sum: Column<Advice>,
table_idx: TableColumn,
_marker: PhantomData<F>,
}
impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
/// The `running_sum` advice column breaks the field element into `K`-bit
/// words. It is used to construct the input expression to the lookup
/// argument.
///
/// The `table_idx` fixed column contains values from [0..2^K). Looking up
/// a value in `table_idx` constrains it to be within this range. The table
/// can be loaded outside this helper.
///
/// # Side-effects
///
/// Both the `running_sum` and `constants` columns will be equality-enabled.
pub fn configure(
meta: &mut ConstraintSystem<F>,
running_sum: Column<Advice>,
table_idx: TableColumn,
) -> Self {
meta.enable_equality(running_sum);
let q_lookup = meta.complex_selector();
let q_running = meta.complex_selector();
let q_bitshift = meta.selector();
let config = LookupRangeCheckConfig {
q_lookup,
q_running,
q_bitshift,
running_sum,
table_idx,
_marker: PhantomData,
};
meta.lookup(|meta| {
let q_lookup = meta.query_selector(config.q_lookup);
let q_running = meta.query_selector(config.q_running);
let z_cur = meta.query_advice(config.running_sum, Rotation::cur());
// In the case of a running sum decomposition, we recover the word from
// the difference of the running sums:
// z_i = 2^{K}⋅z_{i + 1} + a_i
// => a_i = z_i - 2^{K}⋅z_{i + 1}
let running_sum_lookup = {
let running_sum_word = {
let z_next = meta.query_advice(config.running_sum, Rotation::next());
z_cur.clone() - z_next * F::from(1 << K)
};
q_running.clone() * running_sum_word
};
// In the short range check, the word is directly witnessed.
let short_lookup = {
let short_word = z_cur;
let q_short = Expression::Constant(F::one()) - q_running;
q_short * short_word
};
// Combine the running sum and short lookups:
vec![(
q_lookup * (running_sum_lookup + short_lookup),
config.table_idx,
)]
});
// For short lookups, check that the word has been shifted by the correct number of bits.
meta.create_gate("Short lookup bitshift", |meta| {
let q_bitshift = meta.query_selector(config.q_bitshift);
let word = meta.query_advice(config.running_sum, Rotation::prev());
let shifted_word = meta.query_advice(config.running_sum, Rotation::cur());
let inv_two_pow_s = meta.query_advice(config.running_sum, Rotation::next());
let two_pow_k = F::from(1 << K);
// shifted_word = word * 2^{K-s}
// = word * 2^K * inv_two_pow_s
vec![q_bitshift * (word * two_pow_k * inv_two_pow_s - shifted_word)]
});
config
}
#[cfg(test)]
// Loads the values [0..2^K) into `table_idx`. This is only used in testing
// for now, since the Sinsemilla chip provides a pre-loaded table in the
// Orchard context.
pub fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
layouter.assign_table(
|| "table_idx",
|mut table| {
// We generate the row values lazily (we only need them during keygen).
for index in 0..(1 << K) {
table.assign_cell(
|| "table_idx",
self.table_idx,
index,
|| Ok(F::from(index as u64)),
)?;
}
Ok(())
},
)
}
/// Range check on an existing cell that is copied into this helper.
///
/// Returns an error if `element` is not in a column that was passed to
/// [`ConstraintSystem::enable_equality`] during circuit configuration.
pub fn copy_check(
&self,
mut layouter: impl Layouter<F>,
element: AssignedCell<F, F>,
num_words: usize,
strict: bool,
) -> Result<RunningSum<F>, Error> {
layouter.assign_region(
|| format!("{:?} words range check", num_words),
|mut region| {
// Copy `element` and initialize running sum `z_0 = element` to decompose it.
let z_0 = element.copy_advice(|| "z_0", &mut region, self.running_sum, 0)?;
self.range_check(&mut region, z_0, num_words, strict)
},
)
}
/// Range check on a value that is witnessed in this helper.
pub fn witness_check(
&self,
mut layouter: impl Layouter<F>,
value: Option<F>,
num_words: usize,
strict: bool,
) -> Result<RunningSum<F>, Error> {
layouter.assign_region(
|| "Witness element",
|mut region| {
let z_0 = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| value.ok_or(Error::Synthesis),
)?;
self.range_check(&mut region, z_0, num_words, strict)
},
)
}
/// If `strict` is set to "true", the field element must fit into
/// `num_words * K` bits. In other words, the the final cumulative sum `z_{num_words}`
/// must be zero.
///
/// If `strict` is set to "false", the final `z_{num_words}` is not constrained.
///
/// `element` must have been assigned to `self.running_sum` at offset 0.
fn range_check(
&self,
region: &mut Region<'_, F>,
element: AssignedCell<F, F>,
num_words: usize,
strict: bool,
) -> Result<RunningSum<F>, Error> {
// `num_words` must fit into a single field element.
assert!(num_words * K <= F::CAPACITY as usize);
let num_bits = num_words * K;
// Chunk the first num_bits bits into K-bit words.
let words = {
// Take first num_bits bits of `element`.
let bits = element.value().map(|element| {
element
.to_le_bits()
.into_iter()
.take(num_bits)
.collect::<Vec<_>>()
});
let words: Option<Vec<F>> = bits.map(|bits| {
bits.chunks_exact(K)
.map(|word| F::from(lebs2ip::<K>(&(word.try_into().unwrap()))))
.collect::<Vec<_>>()
});
if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; num_words]
}
};
let mut zs = vec![element.clone()];
// Assign cumulative sum such that
// z_i = 2^{K}⋅z_{i + 1} + a_i
// => z_{i + 1} = (z_i - a_i) / (2^K)
//
// For `element` = a_0 + 2^10 a_1 + ... + 2^{120} a_{12}}, initialize z_0 = `element`.
// If `element` fits in 130 bits, we end up with z_{13} = 0.
let mut z = element;
let inv_two_pow_k = F::from(1u64 << K).invert().unwrap();
for (idx, word) in words.iter().enumerate() {
// Enable q_lookup on this row
self.q_lookup.enable(region, idx)?;
// Enable q_running on this row
self.q_running.enable(region, idx)?;
// z_next = (z_cur - m_cur) / 2^K
z = {
let z_val = z
.value()
.zip(*word)
.map(|(z, word)| (*z - word) * inv_two_pow_k);
// Assign z_next
region.assign_advice(
|| format!("z_{:?}", idx + 1),
self.running_sum,
idx + 1,
|| z_val.ok_or(Error::Synthesis),
)?
};
zs.push(z.clone());
}
if strict {
// Constrain the final `z` to be zero.
region.constrain_constant(zs.last().unwrap().cell(), F::zero())?;
}
Ok(RunningSum(zs))
}
/// Short range check on an existing cell that is copied into this helper.
///
/// # Panics
///
/// Panics if NUM_BITS is equal to or larger than K.
pub fn copy_short_check(
&self,
mut layouter: impl Layouter<F>,
element: AssignedCell<F, F>,
num_bits: usize,
) -> Result<(), Error> {
assert!(num_bits < K);
layouter.assign_region(
|| format!("Range check {:?} bits", num_bits),
|mut region| {
// Copy `element` to use in the k-bit lookup.
let element =
element.copy_advice(|| "element", &mut region, self.running_sum, 0)?;
self.short_range_check(&mut region, element, num_bits)
},
)
}
/// Short range check on value that is witnessed in this helper.
///
/// # Panics
///
/// Panics if num_bits is larger than K.
pub fn witness_short_check(
&self,
mut layouter: impl Layouter<F>,
element: Option<F>,
num_bits: usize,
) -> Result<AssignedCell<F, F>, Error> {
assert!(num_bits <= K);
layouter.assign_region(
|| format!("Range check {:?} bits", num_bits),
|mut region| {
// Witness `element` to use in the k-bit lookup.
let element = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| element.ok_or(Error::Synthesis),
)?;
self.short_range_check(&mut region, element.clone(), num_bits)?;
Ok(element)
},
)
}
/// Constrain `x` to be a NUM_BITS word.
///
/// `element` must have been assigned to `self.running_sum` at offset 0.
fn short_range_check(
&self,
region: &mut Region<'_, F>,
element: AssignedCell<F, F>,
num_bits: usize,
) -> Result<(), Error> {
// Enable lookup for `element`, to constrain it to 10 bits.
self.q_lookup.enable(region, 0)?;
// Enable lookup for shifted element, to constrain it to 10 bits.
self.q_lookup.enable(region, 1)?;
// Check element has been shifted by the correct number of bits.
self.q_bitshift.enable(region, 1)?;
// Assign shifted `element * 2^{K - num_bits}`
let shifted = element.value().map(|element| {
let shift = F::from(1 << (K - num_bits));
*element * shift
});
region.assign_advice(
|| format!("element * 2^({}-{})", K, num_bits),
self.running_sum,
1,
|| shifted.ok_or(Error::Synthesis),
)?;
// Assign 2^{-num_bits} from a fixed column.
let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap();
region.assign_advice_from_constant(
|| format!("2^(-{})", num_bits),
self.running_sum,
2,
inv_two_pow_s,
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::LookupRangeCheckConfig;
use super::super::lebs2ip;
use crate::primitives::sinsemilla::K;
use ff::{Field, PrimeFieldBits};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::{FailureLocation, MockProver, VerifyFailure},
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use std::{convert::TryInto, marker::PhantomData};
#[test]
fn lookup_range_check() {
#[derive(Clone, Copy)]
struct MyCircuit<F: FieldExt + PrimeFieldBits> {
num_words: usize,
_marker: PhantomData<F>,
}
impl<F: FieldExt + PrimeFieldBits> Circuit<F> for MyCircuit<F> {
type Config = LookupRangeCheckConfig<F, K>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
*self
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let running_sum = meta.advice_column();
let table_idx = meta.lookup_table_column();
let constants = meta.fixed_column();
meta.enable_constant(constants);
LookupRangeCheckConfig::<F, K>::configure(meta, running_sum, table_idx)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
// Load table_idx
config.load(&mut layouter)?;
// Lookup constraining element to be no longer than num_words * K bits.
let elements_and_expected_final_zs = [
(F::from((1 << (self.num_words * K)) - 1), F::zero(), true), // a word that is within self.num_words * K bits long
(F::from(1 << (self.num_words * K)), F::one(), false), // a word that is just over self.num_words * K bits long
];
fn expected_zs<F: FieldExt + PrimeFieldBits, const K: usize>(
element: F,
num_words: usize,
) -> Vec<F> {
let chunks = {
element
.to_le_bits()
.iter()
.by_val()
.take(num_words * K)
.collect::<Vec<_>>()
.chunks_exact(K)
.map(|chunk| F::from(lebs2ip::<K>(chunk.try_into().unwrap())))
.collect::<Vec<_>>()
};
let expected_zs = {
let inv_two_pow_k = F::from(1 << K).invert().unwrap();
chunks.iter().fold(vec![element], |mut zs, a_i| {
// z_{i + 1} = (z_i - a_i) / 2^{K}
let z = (zs[zs.len() - 1] - a_i) * inv_two_pow_k;
zs.push(z);
zs
})
};
expected_zs
}
for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() {
let expected_zs = expected_zs::<F, K>(*element, self.num_words);
let zs = config.witness_check(
layouter.namespace(|| format!("Lookup {:?}", self.num_words)),
Some(*element),
self.num_words,
*strict,
)?;
assert_eq!(*expected_zs.last().unwrap(), *expected_final_z);
for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) {
if let Some(z) = z.value() {
assert_eq!(&expected_z, z);
}
}
}
Ok(())
}
}
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
num_words: 6,
_marker: PhantomData,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}
#[test]
fn short_range_check() {
struct MyCircuit<F: FieldExt + PrimeFieldBits> {
element: Option<F>,
num_bits: usize,
}
impl<F: FieldExt + PrimeFieldBits> Circuit<F> for MyCircuit<F> {
type Config = LookupRangeCheckConfig<F, K>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
MyCircuit {
element: None,
num_bits: self.num_bits,
}
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let running_sum = meta.advice_column();
let table_idx = meta.lookup_table_column();
let constants = meta.fixed_column();
meta.enable_constant(constants);
LookupRangeCheckConfig::<F, K>::configure(meta, running_sum, table_idx)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
// Load table_idx
config.load(&mut layouter)?;
// Lookup constraining element to be no longer than num_bits.
config.witness_short_check(
layouter.namespace(|| format!("Lookup {:?} bits", self.num_bits)),
self.element,
self.num_bits,
)?;
Ok(())
}
}
// Edge case: zero bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::zero()),
num_bits: 0,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Edge case: K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from((1 << K) - 1)),
num_bits: K,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Element within `num_bits`
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from((1 << 6) - 1)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
// Element larger than `num_bits` but within K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from(1 << 6)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::Lookup {
lookup_index: 0,
location: FailureLocation::InRegion {
region: (1, "Range check 6 bits").into(),
offset: 1,
},
}])
);
}
// Element larger than K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from(1 << K)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![
VerifyFailure::Lookup {
lookup_index: 0,
location: FailureLocation::InRegion {
region: (1, "Range check 6 bits").into(),
offset: 0,
},
},
VerifyFailure::Lookup {
lookup_index: 0,
location: FailureLocation::InRegion {
region: (1, "Range check 6 bits").into(),
offset: 1,
},
},
])
);
}
// Element which is not within `num_bits`, but which has a shifted value within
// num_bits
{
let num_bits = 6;
let shifted = pallas::Base::from((1 << num_bits) - 1);
// Recall that shifted = element * 2^{K-s}
// => element = shifted * 2^{s-K}
let element = shifted
* pallas::Base::from(1 << (K as u64 - num_bits))
.invert()
.unwrap();
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(element),
num_bits: num_bits as usize,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![VerifyFailure::Lookup {
lookup_index: 0,
location: FailureLocation::InRegion {
region: (1, "Range check 6 bits").into(),
offset: 0,
},
}])
);
}
}
}

View File

@ -1,23 +1,21 @@
use halo2::{
use halo2_proofs::{
circuit::{AssignedCell, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::{
circuit::gadget::{
ecc::{
chip::{EccChip, NonIdentityEccPoint},
Point,
},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
CommitDomain, Message, MessagePiece,
},
utilities::{bitrange_subset, bool_check},
use crate::constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, T_P};
use halo2_gadgets::{
ecc::{
chip::{EccChip, NonIdentityEccPoint},
Point,
},
constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, T_P},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig},
CommitDomain, Message, MessagePiece,
},
utilities::{bitrange_subset, bool_check},
};
/// The values of the running sum at the start and end of the range being used for a
@ -1455,25 +1453,23 @@ struct GateCells {
#[cfg(test)]
mod tests {
use super::NoteCommitConfig;
use crate::{
circuit::gadget::{
ecc::{
chip::{EccChip, EccConfig},
NonIdentityPoint,
},
sinsemilla::chip::SinsemillaChip,
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
},
constants::{
fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases,
OrchardHashDomains, L_ORCHARD_BASE, L_VALUE, T_Q,
use crate::constants::{
fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases,
OrchardHashDomains, L_ORCHARD_BASE, L_VALUE, T_Q,
};
use halo2_gadgets::{
ecc::{
chip::{EccChip, EccConfig},
NonIdentityPoint,
},
primitives::sinsemilla::CommitDomain,
sinsemilla::chip::SinsemillaChip,
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use ff::{Field, PrimeField, PrimeFieldBits};
use group::Curve;
use halo2::{
use halo2_proofs::{
circuit::{AssignedCell, Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},

View File

@ -1,6 +1,6 @@
//! Orchard fixed bases.
use super::{L_ORCHARD_SCALAR, L_VALUE};
use crate::circuit::gadget::ecc::{
use halo2_gadgets::ecc::{
chip::{BaseFieldElem, FixedPoint, FullScalar, ShortScalar},
FixedPoints,
};

View File

@ -2930,9 +2930,11 @@ pub fn generator() -> pallas::Affine {
mod tests {
use super::super::{COMMIT_IVK_PERSONALIZATION, NUM_WINDOWS};
use super::*;
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use crate::primitives::sinsemilla::CommitDomain;
use group::Curve;
use halo2_gadgets::{
ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us},
primitives::sinsemilla::CommitDomain,
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
#[test]

View File

@ -2930,8 +2930,10 @@ pub fn generator() -> pallas::Affine {
mod tests {
use super::super::{NOTE_COMMITMENT_PERSONALIZATION, NUM_WINDOWS};
use super::*;
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use crate::primitives::sinsemilla::CommitDomain;
use halo2_gadgets::{
ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us},
primitives::sinsemilla::CommitDomain,
};
use group::Curve;
use pasta_curves::{arithmetic::CurveAffine, pallas};

View File

@ -2929,8 +2929,8 @@ pub fn generator() -> pallas::Affine {
mod tests {
use super::super::{NUM_WINDOWS, ORCHARD_PERSONALIZATION};
use super::*;
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use group::Curve;
use halo2_gadgets::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use pasta_curves::{arithmetic::CurveExt, pallas};
#[test]

View File

@ -2931,8 +2931,8 @@ pub fn generator() -> pallas::Affine {
mod tests {
use super::super::{NUM_WINDOWS, ORCHARD_PERSONALIZATION};
use super::*;
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use group::Curve;
use halo2_gadgets::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt},
pallas,

View File

@ -2931,8 +2931,8 @@ pub fn generator() -> pallas::Affine {
mod tests {
use super::super::{NUM_WINDOWS, VALUE_COMMITMENT_PERSONALIZATION};
use super::*;
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use group::Curve;
use halo2_gadgets::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt},
pallas,

View File

@ -784,8 +784,8 @@ pub fn generator() -> pallas::Affine {
mod tests {
use super::super::{NUM_WINDOWS_SHORT, VALUE_COMMITMENT_PERSONALIZATION};
use super::*;
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use group::Curve;
use halo2_gadgets::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt},
pallas,

View File

@ -1,12 +1,10 @@
//! Sinsemilla generators
use super::{OrchardFixedBases, OrchardFixedBasesFull};
use crate::circuit::gadget::sinsemilla::{CommitDomains, HashDomains};
use crate::spec::i2lebsp;
use halo2_gadgets::sinsemilla::{CommitDomains, HashDomains};
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$
pub const K: usize = 10;
@ -89,18 +87,18 @@ impl HashDomains<pallas::Affine> for OrchardHashDomains {
fn Q(&self) -> pallas::Affine {
match self {
OrchardHashDomains::CommitIvk => pallas::Affine::from_xy(
pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.1).unwrap(),
pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap(),
pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap(),
)
.unwrap(),
OrchardHashDomains::NoteCommit => pallas::Affine::from_xy(
pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(),
pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(),
pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(),
)
.unwrap(),
OrchardHashDomains::MerkleCrh => pallas::Affine::from_xy(
pallas::Base::from_bytes(&Q_MERKLE_CRH.0).unwrap(),
pallas::Base::from_bytes(&Q_MERKLE_CRH.1).unwrap(),
pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap(),
pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap(),
)
.unwrap(),
}
@ -136,10 +134,10 @@ mod tests {
fixed_bases::{COMMIT_IVK_PERSONALIZATION, NOTE_COMMITMENT_PERSONALIZATION},
sinsemilla::MERKLE_CRH_PERSONALIZATION,
};
use crate::primitives::sinsemilla::{CommitDomain, HashDomain};
use group::{ff::PrimeField, Curve};
use halo2::arithmetic::CurveAffine;
use halo2::pasta::pallas;
use halo2_gadgets::primitives::sinsemilla::{CommitDomain, HashDomain};
use halo2_proofs::arithmetic::CurveAffine;
use halo2_proofs::pasta::pallas;
use rand::{self, rngs::OsRng, Rng};
#[test]

View File

@ -227,7 +227,7 @@ impl NullifierDerivingKey {
}
/// Converts this nullifier deriving key to its serialized form.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
pub(crate) fn to_bytes(self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
@ -262,7 +262,7 @@ impl CommitIvkRandomness {
}
/// Converts this nullifier deriving key to its serialized form.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
pub(crate) fn to_bytes(self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}

View File

@ -39,7 +39,7 @@ impl RandomSeed {
CtOption::new(rseed, esk.is_some())
}
pub(crate) fn to_bytes(&self) -> &[u8; 32] {
pub(crate) fn as_bytes(&self) -> &[u8; 32] {
&self.0
}

View File

@ -7,10 +7,10 @@ use subtle::{ConstantTimeEq, CtOption};
use crate::{
constants::{fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, L_ORCHARD_BASE},
primitives::sinsemilla,
spec::extract_p,
value::NoteValue,
};
use halo2_gadgets::primitives::sinsemilla;
#[derive(Debug)]
pub(crate) struct NoteCommitTrapdoor(pub(super) pallas::Scalar);

View File

@ -1,5 +1,5 @@
use group::{ff::PrimeField, Group};
use halo2::arithmetic::CurveExt;
use halo2_proofs::arithmetic::CurveExt;
use pasta_curves::pallas;
use rand::RngCore;
use subtle::CtOption;

View File

@ -151,7 +151,7 @@ impl Domain for OrchardDomain {
np[0] = 0x02;
np[1..12].copy_from_slice(note.recipient().diversifer().as_array());
np[12..20].copy_from_slice(&note.value().to_bytes());
np[20..52].copy_from_slice(note.rseed().to_bytes());
np[20..52].copy_from_slice(note.rseed().as_bytes());
np[52..].copy_from_slice(memo);
NotePlaintextBytes(np)
}

View File

@ -4,6 +4,4 @@
// - EphemeralPublicKey
// - EphemeralSecretKey
pub mod poseidon;
pub mod redpallas;
pub mod sinsemilla;

View File

@ -1,397 +0,0 @@
//! The Poseidon algebraic hash function.
use std::array;
use std::convert::TryInto;
use std::fmt;
use std::iter;
use std::marker::PhantomData;
use halo2::arithmetic::FieldExt;
pub(crate) mod fp;
pub(crate) mod fq;
pub(crate) mod grain;
pub(crate) mod mds;
#[cfg(test)]
pub(crate) mod test_vectors;
mod p128pow5t3;
pub use p128pow5t3::P128Pow5T3;
use grain::SboxType;
/// The type used to hold permutation state.
pub(crate) type State<F, const T: usize> = [F; T];
/// The type used to hold sponge rate.
pub(crate) type SpongeRate<F, const RATE: usize> = [Option<F>; RATE];
/// The type used to hold the MDS matrix and its inverse.
pub(crate) type Mds<F, const T: usize> = [[F; T]; T];
/// A specification for a Poseidon permutation.
pub trait Spec<F: FieldExt, const T: usize, const RATE: usize>: fmt::Debug {
/// The number of full rounds for this specification.
///
/// This must be an even number.
fn full_rounds() -> usize;
/// The number of partial rounds for this specification.
fn partial_rounds() -> usize;
/// The S-box for this specification.
fn sbox(val: F) -> F;
/// Side-loaded index of the first correct and secure MDS that will be generated by
/// the reference implementation.
///
/// This is used by the default implementation of [`Spec::constants`]. If you are
/// hard-coding the constants, you may leave this unimplemented.
fn secure_mds() -> usize;
/// Generates `(round_constants, mds, mds^-1)` corresponding to this specification.
fn constants() -> (Vec<[F; T]>, Mds<F, T>, Mds<F, T>) {
let r_f = Self::full_rounds();
let r_p = Self::partial_rounds();
let mut grain = grain::Grain::new(SboxType::Pow, T as u16, r_f as u16, r_p as u16);
let round_constants = (0..(r_f + r_p))
.map(|_| {
let mut rc_row = [F::zero(); T];
for (rc, value) in rc_row
.iter_mut()
.zip((0..T).map(|_| grain.next_field_element()))
{
*rc = value;
}
rc_row
})
.collect();
let (mds, mds_inv) = mds::generate_mds::<F, T>(&mut grain, Self::secure_mds());
(round_constants, mds, mds_inv)
}
}
/// Runs the Poseidon permutation on the given state.
pub(crate) fn permute<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>(
state: &mut State<F, T>,
mds: &Mds<F, T>,
round_constants: &[[F; T]],
) {
let r_f = S::full_rounds() / 2;
let r_p = S::partial_rounds();
let apply_mds = |state: &mut State<F, T>| {
let mut new_state = [F::zero(); T];
// Matrix multiplication
#[allow(clippy::needless_range_loop)]
for i in 0..T {
for j in 0..T {
new_state[i] += mds[i][j] * state[j];
}
}
*state = new_state;
};
let full_round = |state: &mut State<F, T>, rcs: &[F; T]| {
for (word, rc) in state.iter_mut().zip(rcs.iter()) {
*word = S::sbox(*word + rc);
}
apply_mds(state);
};
let part_round = |state: &mut State<F, T>, rcs: &[F; T]| {
for (word, rc) in state.iter_mut().zip(rcs.iter()) {
*word += rc;
}
// In a partial round, the S-box is only applied to the first state word.
state[0] = S::sbox(state[0]);
apply_mds(state);
};
iter::empty()
.chain(iter::repeat(&full_round as &dyn Fn(&mut State<F, T>, &[F; T])).take(r_f))
.chain(iter::repeat(&part_round as &dyn Fn(&mut State<F, T>, &[F; T])).take(r_p))
.chain(iter::repeat(&full_round as &dyn Fn(&mut State<F, T>, &[F; T])).take(r_f))
.zip(round_constants.iter())
.fold(state, |state, (round, rcs)| {
round(state, rcs);
state
});
}
fn poseidon_sponge<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>(
state: &mut State<F, T>,
input: Option<&Absorbing<F, RATE>>,
mds_matrix: &Mds<F, T>,
round_constants: &[[F; T]],
) -> Squeezing<F, RATE> {
if let Some(Absorbing(input)) = input {
// `Iterator::zip` short-circuits when one iterator completes, so this will only
// mutate the rate portion of the state.
for (word, value) in state.iter_mut().zip(input.iter()) {
*word += value.expect("poseidon_sponge is called with a padded input");
}
}
permute::<F, S, T, RATE>(state, mds_matrix, round_constants);
let mut output = [None; RATE];
for (word, value) in output.iter_mut().zip(state.iter()) {
*word = Some(*value);
}
Squeezing(output)
}
mod private {
pub trait SealedSpongeMode {}
impl<F, const RATE: usize> SealedSpongeMode for super::Absorbing<F, RATE> {}
impl<F, const RATE: usize> SealedSpongeMode for super::Squeezing<F, RATE> {}
}
/// The state of the `Sponge`.
pub trait SpongeMode: private::SealedSpongeMode {}
/// The absorbing state of the `Sponge`.
#[derive(Debug)]
pub struct Absorbing<F, const RATE: usize>(pub(crate) SpongeRate<F, RATE>);
/// The squeezing state of the `Sponge`.
#[derive(Debug)]
pub struct Squeezing<F, const RATE: usize>(pub(crate) SpongeRate<F, RATE>);
impl<F, const RATE: usize> SpongeMode for Absorbing<F, RATE> {}
impl<F, const RATE: usize> SpongeMode for Squeezing<F, RATE> {}
impl<F: fmt::Debug, const RATE: usize> Absorbing<F, RATE> {
pub(crate) fn init_with(val: F) -> Self {
Self(
iter::once(Some(val))
.chain((1..RATE).map(|_| None))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
)
}
}
/// A Poseidon sponge.
pub(crate) struct Sponge<
F: FieldExt,
S: Spec<F, T, RATE>,
M: SpongeMode,
const T: usize,
const RATE: usize,
> {
mode: M,
state: State<F, T>,
mds_matrix: Mds<F, T>,
round_constants: Vec<[F; T]>,
_marker: PhantomData<S>,
}
impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>
Sponge<F, S, Absorbing<F, RATE>, T, RATE>
{
/// Constructs a new sponge for the given Poseidon specification.
pub(crate) fn new(initial_capacity_element: F) -> Self {
let (round_constants, mds_matrix, _) = S::constants();
let mode = Absorbing([None; RATE]);
let mut state = [F::zero(); T];
state[RATE] = initial_capacity_element;
Sponge {
mode,
state,
mds_matrix,
round_constants,
_marker: PhantomData::default(),
}
}
/// Absorbs an element into the sponge.
pub(crate) fn absorb(&mut self, value: F) {
for entry in self.mode.0.iter_mut() {
if entry.is_none() {
*entry = Some(value);
return;
}
}
// We've already absorbed as many elements as we can
let _ = poseidon_sponge::<F, S, T, RATE>(
&mut self.state,
Some(&self.mode),
&self.mds_matrix,
&self.round_constants,
);
self.mode = Absorbing::init_with(value);
}
/// Transitions the sponge into its squeezing state.
pub(crate) fn finish_absorbing(mut self) -> Sponge<F, S, Squeezing<F, RATE>, T, RATE> {
let mode = poseidon_sponge::<F, S, T, RATE>(
&mut self.state,
Some(&self.mode),
&self.mds_matrix,
&self.round_constants,
);
Sponge {
mode,
state: self.state,
mds_matrix: self.mds_matrix,
round_constants: self.round_constants,
_marker: PhantomData::default(),
}
}
}
impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>
Sponge<F, S, Squeezing<F, RATE>, T, RATE>
{
/// Squeezes an element from the sponge.
pub(crate) fn squeeze(&mut self) -> F {
loop {
for entry in self.mode.0.iter_mut() {
if let Some(e) = entry.take() {
return e;
}
}
// We've already squeezed out all available elements
self.mode = poseidon_sponge::<F, S, T, RATE>(
&mut self.state,
None,
&self.mds_matrix,
&self.round_constants,
);
}
}
}
/// A domain in which a Poseidon hash function is being used.
pub trait Domain<F: FieldExt, const RATE: usize> {
/// Iterator that outputs padding field elements.
type Padding: IntoIterator<Item = F>;
/// The name of this domain, for debug formatting purposes.
fn name() -> String;
/// The initial capacity element, encoding this domain.
fn initial_capacity_element() -> F;
/// Returns the padding to be appended to the input.
fn padding(input_len: usize) -> Self::Padding;
}
/// A Poseidon hash function used with constant input length.
///
/// Domain specified in section 4.2 of https://eprint.iacr.org/2019/458.pdf
#[derive(Clone, Copy, Debug)]
pub struct ConstantLength<const L: usize>;
impl<F: FieldExt, const RATE: usize, const L: usize> Domain<F, RATE> for ConstantLength<L> {
type Padding = iter::Take<iter::Repeat<F>>;
fn name() -> String {
format!("ConstantLength<{}>", L)
}
fn initial_capacity_element() -> F {
// Capacity value is $length \cdot 2^64 + (o-1)$ where o is the output length.
// We hard-code an output length of 1.
F::from_u128((L as u128) << 64)
}
fn padding(input_len: usize) -> Self::Padding {
assert_eq!(input_len, L);
// For constant-input-length hashing, we pad the input with zeroes to a multiple
// of RATE. On its own this would not be sponge-compliant padding, but the
// Poseidon authors encode the constant length into the capacity element, ensuring
// that inputs of different lengths do not share the same permutation.
let k = (L + RATE - 1) / RATE;
iter::repeat(F::zero()).take(k * RATE - L)
}
}
/// A Poseidon hash function, built around a sponge.
pub struct Hash<
F: FieldExt,
S: Spec<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> {
sponge: Sponge<F, S, Absorbing<F, RATE>, T, RATE>,
_domain: PhantomData<D>,
}
impl<F: FieldExt, S: Spec<F, T, RATE>, D: Domain<F, RATE>, const T: usize, const RATE: usize>
fmt::Debug for Hash<F, S, D, T, RATE>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Hash")
.field("width", &T)
.field("rate", &RATE)
.field("R_F", &S::full_rounds())
.field("R_P", &S::partial_rounds())
.field("domain", &D::name())
.finish()
}
}
impl<F: FieldExt, S: Spec<F, T, RATE>, D: Domain<F, RATE>, const T: usize, const RATE: usize>
Hash<F, S, D, T, RATE>
{
/// Initializes a new hasher.
pub fn init() -> Self {
Hash {
sponge: Sponge::new(D::initial_capacity_element()),
_domain: PhantomData::default(),
}
}
}
impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize, const L: usize>
Hash<F, S, ConstantLength<L>, T, RATE>
{
/// Hashes the given input.
pub fn hash(mut self, message: [F; L]) -> F {
for value in
array::IntoIter::new(message).chain(<ConstantLength<L> as Domain<F, RATE>>::padding(L))
{
self.sponge.absorb(value);
}
self.sponge.finish_absorbing().squeeze()
}
}
#[cfg(test)]
mod tests {
use halo2::arithmetic::FieldExt;
use pasta_curves::pallas;
use super::{permute, ConstantLength, Hash, P128Pow5T3 as OrchardNullifier, Spec};
#[test]
fn orchard_spec_equivalence() {
let message = [pallas::Base::from(6), pallas::Base::from(42)];
let (round_constants, mds, _) = OrchardNullifier::constants();
let hasher = Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init();
let result = hasher.hash(message);
// The result should be equivalent to just directly applying the permutation and
// taking the first state element as the output.
let mut state = [message[0], message[1], pallas::Base::from_u128(2 << 64)];
permute::<_, OrchardNullifier, 3, 2>(&mut state, &mds, &round_constants);
assert_eq!(state[0], result);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,193 +0,0 @@
//! The Grain LFSR in self-shrinking mode, as used by Poseidon.
use std::marker::PhantomData;
use bitvec::prelude::*;
use halo2::arithmetic::FieldExt;
const STATE: usize = 80;
#[derive(Debug, Clone, Copy)]
pub(super) enum FieldType {
/// GF(2^n)
#[allow(dead_code)]
Binary,
/// GF(p)
PrimeOrder,
}
impl FieldType {
fn tag(&self) -> u8 {
match self {
FieldType::Binary => 0,
FieldType::PrimeOrder => 1,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(super) enum SboxType {
/// x^alpha
Pow,
/// x^(-1)
#[allow(dead_code)]
Inv,
}
impl SboxType {
fn tag(&self) -> u8 {
match self {
SboxType::Pow => 0,
SboxType::Inv => 1,
}
}
}
pub(super) struct Grain<F: FieldExt> {
state: BitArr!(for 80, in Msb0, u8),
next_bit: usize,
_field: PhantomData<F>,
}
impl<F: FieldExt> Grain<F> {
pub(super) fn new(sbox: SboxType, t: u16, r_f: u16, r_p: u16) -> Self {
// Initialize the LFSR state.
let mut state = bitarr![Msb0, u8; 1; STATE];
let mut set_bits = |offset: usize, len, value| {
// Poseidon reference impl sets initial state bits in MSB order.
for i in 0..len {
*state.get_mut(offset + len - 1 - i).unwrap() = (value >> i) & 1 != 0;
}
};
set_bits(0, 2, FieldType::PrimeOrder.tag() as u16);
set_bits(2, 4, sbox.tag() as u16);
set_bits(6, 12, F::NUM_BITS as u16);
set_bits(18, 12, t);
set_bits(30, 10, r_f);
set_bits(40, 10, r_p);
let mut grain = Grain {
state,
next_bit: STATE,
_field: PhantomData::default(),
};
// Discard the first 160 bits.
for _ in 0..20 {
grain.load_next_8_bits();
grain.next_bit = STATE;
}
grain
}
fn load_next_8_bits(&mut self) {
let mut new_bits = 0u8;
for i in 0..8 {
new_bits |= ((self.state[i + 62]
^ self.state[i + 51]
^ self.state[i + 38]
^ self.state[i + 23]
^ self.state[i + 13]
^ self.state[i]) as u8)
<< i;
}
self.state.rotate_left(8);
self.next_bit -= 8;
for i in 0..8 {
*self.state.get_mut(self.next_bit + i).unwrap() = (new_bits >> i) & 1 != 0;
}
}
fn get_next_bit(&mut self) -> bool {
if self.next_bit == STATE {
self.load_next_8_bits();
}
let ret = self.state[self.next_bit];
self.next_bit += 1;
ret
}
/// Returns the next field element from this Grain instantiation.
pub(super) fn next_field_element(&mut self) -> F {
// Loop until we get an element in the field.
loop {
let mut bytes = F::Repr::default();
// Poseidon reference impl interprets the bits as a repr in MSB order, because
// it's easy to do that in Python. Meanwhile, our field elements all use LSB
// order. There's little motivation to diverge from the reference impl; these
// are all constants, so we aren't introducing big-endianness into the rest of
// the circuit (assuming unkeyed Poseidon, but we probably wouldn't want to
// implement Grain inside a circuit, so we'd use a different round constant
// derivation function there).
let view = bytes.as_mut();
for (i, bit) in self.take(F::NUM_BITS as usize).enumerate() {
// If we diverged from the reference impl and interpreted the bits in LSB
// order, we would remove this line.
let i = F::NUM_BITS as usize - 1 - i;
view[i / 8] |= if bit { 1 << (i % 8) } else { 0 };
}
if let Some(f) = F::from_repr_vartime(bytes) {
break f;
}
}
}
/// Returns the next field element from this Grain instantiation, without using
/// rejection sampling.
pub(super) fn next_field_element_without_rejection(&mut self) -> F {
let mut bytes = [0u8; 64];
// Poseidon reference impl interprets the bits as a repr in MSB order, because
// it's easy to do that in Python. Additionally, it does not use rejection
// sampling in cases where the constants don't specifically need to be uniformly
// random for security. We do not provide APIs that take a field-element-sized
// array and reduce it modulo the field order, because those are unsafe APIs to
// offer generally (accidentally using them can lead to divergence in consensus
// systems due to not rejecting canonical forms).
//
// Given that we don't want to diverge from the reference implementation, we hack
// around this restriction by serializing the bits into a 64-byte array and then
// calling F::from_bytes_wide. PLEASE DO NOT COPY THIS INTO YOUR OWN CODE!
let view = bytes.as_mut();
for (i, bit) in self.take(F::NUM_BITS as usize).enumerate() {
// If we diverged from the reference impl and interpreted the bits in LSB
// order, we would remove this line.
let i = F::NUM_BITS as usize - 1 - i;
view[i / 8] |= if bit { 1 << (i % 8) } else { 0 };
}
F::from_bytes_wide(&bytes)
}
}
impl<F: FieldExt> Iterator for Grain<F> {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
// Evaluate bits in pairs:
// - If the first bit is a 1, output the second bit.
// - If the first bit is a 0, discard the second bit.
while !self.get_next_bit() {
self.get_next_bit();
}
Some(self.get_next_bit())
}
}
#[cfg(test)]
mod tests {
use pasta_curves::Fp;
use super::{Grain, SboxType};
#[test]
fn grain() {
let mut grain = Grain::<Fp>::new(SboxType::Pow, 3, 8, 56);
let _f = grain.next_field_element();
}
}

View File

@ -1,123 +0,0 @@
use halo2::arithmetic::FieldExt;
use super::{grain::Grain, Mds};
pub(super) fn generate_mds<F: FieldExt, const T: usize>(
grain: &mut Grain<F>,
mut select: usize,
) -> (Mds<F, T>, Mds<F, T>) {
let (xs, ys, mds) = loop {
// Generate two [F; T] arrays of unique field elements.
let (xs, ys) = loop {
let mut vals: Vec<_> = (0..2 * T)
.map(|_| grain.next_field_element_without_rejection())
.collect();
// Check that we have unique field elements.
let mut unique = vals.clone();
unique.sort_unstable();
unique.dedup();
if vals.len() == unique.len() {
let rhs = vals.split_off(T);
break (vals, rhs);
}
};
// We need to ensure that the MDS is secure. Instead of checking the MDS against
// the relevant algorithms directly, we witness a fixed number of MDS matrices
// that we need to sample from the given Grain state before obtaining a secure
// matrix. This can be determined out-of-band via the reference implementation in
// Sage.
if select != 0 {
select -= 1;
continue;
}
// Generate a Cauchy matrix, with elements a_ij in the form:
// a_ij = 1/(x_i + y_j); x_i + y_j != 0
//
// It would be much easier to use the alternate definition:
// a_ij = 1/(x_i - y_j); x_i - y_j != 0
//
// These are clearly equivalent on `y <- -y`, but it is easier to work with the
// negative formulation, because ensuring that xs ys is unique implies that
// x_i - y_j != 0 by construction (whereas the positive case does not hold). It
// also makes computation of the matrix inverse simpler below (the theorem used
// was formulated for the negative definition).
//
// However, the Poseidon paper and reference impl use the positive formulation,
// and we want to rely on the reference impl for MDS security, so we use the same
// formulation.
let mut mds = [[F::zero(); T]; T];
#[allow(clippy::needless_range_loop)]
for i in 0..T {
for j in 0..T {
let sum = xs[i] + ys[j];
// We leverage the secure MDS selection counter to also check this.
assert!(!sum.is_zero_vartime());
mds[i][j] = sum.invert().unwrap();
}
}
break (xs, ys, mds);
};
// Compute the inverse. All square Cauchy matrices have a non-zero determinant and
// thus are invertible. The inverse for a Cauchy matrix of the form:
//
// a_ij = 1/(x_i - y_j); x_i - y_j != 0
//
// has elements b_ij given by:
//
// b_ij = (x_j - y_i) A_j(y_i) B_i(x_j) (Schechter 1959, Theorem 1)
//
// where A_i(x) and B_i(x) are the Lagrange polynomials for xs and ys respectively.
//
// We adapt this to the positive Cauchy formulation by negating ys.
let mut mds_inv = [[F::zero(); T]; T];
let l = |xs: &[F], j, x: F| {
let x_j = xs[j];
xs.iter().enumerate().fold(F::one(), |acc, (m, x_m)| {
if m == j {
acc
} else {
// We can invert freely; by construction, the elements of xs are distinct.
acc * (x - x_m) * (x_j - x_m).invert().unwrap()
}
})
};
let neg_ys: Vec<_> = ys.iter().map(|y| -*y).collect();
for i in 0..T {
for j in 0..T {
mds_inv[i][j] = (xs[j] - neg_ys[i]) * l(&xs, j, neg_ys[i]) * l(&neg_ys, i, xs[j]);
}
}
(mds, mds_inv)
}
#[cfg(test)]
mod tests {
use pasta_curves::Fp;
use super::{generate_mds, Grain};
#[test]
fn poseidon_mds() {
const T: usize = 3;
let mut grain = Grain::new(super::super::grain::SboxType::Pow, T as u16, 8, 56);
let (mds, mds_inv) = generate_mds::<Fp, T>(&mut grain, 0);
// Verify that MDS * MDS^-1 = I.
#[allow(clippy::needless_range_loop)]
for i in 0..T {
for j in 0..T {
let expected = if i == j { Fp::one() } else { Fp::zero() };
assert_eq!(
(0..T).fold(Fp::zero(), |acc, k| acc + (mds[i][k] * mds_inv[k][j])),
expected
);
}
}
}
}

View File

@ -1,313 +0,0 @@
use halo2::arithmetic::Field;
use pasta_curves::{pallas::Base as Fp, vesta::Base as Fq};
use super::{Mds, Spec};
/// Poseidon-128 using the $x^5$ S-box, with a width of 3 field elements, and the
/// standard number of rounds for 128-bit security "with margin".
///
/// The standard specification for this set of parameters (on either of the Pasta
/// fields) uses $R_F = 8, R_P = 56$. This is conveniently an even number of
/// partial rounds, making it easier to construct a Halo 2 circuit.
#[derive(Debug)]
pub struct P128Pow5T3;
impl Spec<Fp, 3, 2> for P128Pow5T3 {
fn full_rounds() -> usize {
8
}
fn partial_rounds() -> usize {
56
}
fn sbox(val: Fp) -> Fp {
val.pow_vartime(&[5])
}
fn secure_mds() -> usize {
unimplemented!()
}
fn constants() -> (Vec<[Fp; 3]>, Mds<Fp, 3>, Mds<Fp, 3>) {
(
super::fp::ROUND_CONSTANTS[..].to_vec(),
super::fp::MDS,
super::fp::MDS_INV,
)
}
}
impl Spec<Fq, 3, 2> for P128Pow5T3 {
fn full_rounds() -> usize {
8
}
fn partial_rounds() -> usize {
56
}
fn sbox(val: Fq) -> Fq {
val.pow_vartime(&[5])
}
fn secure_mds() -> usize {
unimplemented!()
}
fn constants() -> (Vec<[Fq; 3]>, Mds<Fq, 3>, Mds<Fq, 3>) {
(
super::fq::ROUND_CONSTANTS[..].to_vec(),
super::fq::MDS,
super::fq::MDS_INV,
)
}
}
#[cfg(test)]
mod tests {
use ff::PrimeField;
use std::marker::PhantomData;
use pasta_curves::arithmetic::FieldExt;
use super::{
super::{fp, fq},
Fp, Fq,
};
use crate::primitives::poseidon::{permute, ConstantLength, Hash, Spec};
/// The same Poseidon specification as poseidon::P128Pow5T3, but constructed
/// such that its constants will be generated at runtime.
#[derive(Debug)]
pub struct P128Pow5T3Gen<F: FieldExt, const SECURE_MDS: usize>(PhantomData<F>);
impl<F: FieldExt, const SECURE_MDS: usize> P128Pow5T3Gen<F, SECURE_MDS> {
pub fn new() -> Self {
P128Pow5T3Gen(PhantomData::default())
}
}
impl<F: FieldExt, const SECURE_MDS: usize> Spec<F, 3, 2> for P128Pow5T3Gen<F, SECURE_MDS> {
fn full_rounds() -> usize {
8
}
fn partial_rounds() -> usize {
56
}
fn sbox(val: F) -> F {
val.pow_vartime(&[5])
}
fn secure_mds() -> usize {
SECURE_MDS
}
}
#[test]
fn verify_constants() {
fn verify_constants_helper<F: FieldExt>(
expected_round_constants: [[F; 3]; 64],
expected_mds: [[F; 3]; 3],
expected_mds_inv: [[F; 3]; 3],
) {
let (round_constants, mds, mds_inv) = P128Pow5T3Gen::<F, 0>::constants();
for (actual, expected) in round_constants
.iter()
.flatten()
.zip(expected_round_constants.iter().flatten())
{
assert_eq!(actual, expected);
}
for (actual, expected) in mds.iter().flatten().zip(expected_mds.iter().flatten()) {
assert_eq!(actual, expected);
}
for (actual, expected) in mds_inv
.iter()
.flatten()
.zip(expected_mds_inv.iter().flatten())
{
assert_eq!(actual, expected);
}
}
verify_constants_helper(fp::ROUND_CONSTANTS, fp::MDS, fp::MDS_INV);
verify_constants_helper(fq::ROUND_CONSTANTS, fq::MDS, fq::MDS_INV);
}
#[test]
fn test_against_reference() {
{
// <https://github.com/daira/pasta-hadeshash>, using parameters from
// `generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001`.
// The test vector is generated by `sage poseidonperm_x5_pallas_3.sage --rust`
let mut input = [
Fp::from_raw([
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
Fp::from_raw([
0x0000_0000_0000_0001,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
Fp::from_raw([
0x0000_0000_0000_0002,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
];
let expected_output = [
Fp::from_raw([
0xaeb1_bc02_4aec_a456,
0xf7e6_9a71_d0b6_42a0,
0x94ef_b364_f966_240f,
0x2a52_6acd_0b64_b453,
]),
Fp::from_raw([
0x012a_3e96_28e5_b82a,
0xdcd4_2e7f_bed9_dafe,
0x76ff_7dae_343d_5512,
0x13c5_d156_8b4a_a430,
]),
Fp::from_raw([
0x3590_29a1_d34e_9ddd,
0xf7cf_dfe1_bda4_2c7b,
0x256f_cd59_7984_561a,
0x0a49_c868_c697_6544,
]),
];
permute::<Fp, P128Pow5T3Gen<Fp, 0>, 3, 2>(&mut input, &fp::MDS, &fp::ROUND_CONSTANTS);
assert_eq!(input, expected_output);
}
{
// <https://github.com/daira/pasta-hadeshash>, using parameters from
// `generate_parameters_grain.sage 1 0 255 3 8 56 0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001`.
// The test vector is generated by `sage poseidonperm_x5_vesta_3.sage --rust`
let mut input = [
Fq::from_raw([
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
Fq::from_raw([
0x0000_0000_0000_0001,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
Fq::from_raw([
0x0000_0000_0000_0002,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]),
];
let expected_output = [
Fq::from_raw([
0x0eb0_8ea8_13be_be59,
0x4d43_d197_3dd3_36c6,
0xeddd_74f2_2f8f_2ff7,
0x315a_1f4c_db94_2f7c,
]),
Fq::from_raw([
0xf9f1_26e6_1ea1_65f1,
0x413e_e0eb_7bbd_2198,
0x642a_dee0_dd13_aa48,
0x3be4_75f2_d764_2bde,
]),
Fq::from_raw([
0x14d5_4237_2a7b_a0d9,
0x5019_bfd4_e042_3fa0,
0x117f_db24_20d8_ea60,
0x25ab_8aec_e953_7168,
]),
];
permute::<Fq, P128Pow5T3Gen<Fq, 0>, 3, 2>(&mut input, &fq::MDS, &fq::ROUND_CONSTANTS);
assert_eq!(input, expected_output);
}
}
#[test]
fn permute_test_vectors() {
{
let (round_constants, mds, _) = super::P128Pow5T3::constants();
for tv in crate::primitives::poseidon::test_vectors::fp::permute() {
let mut state = [
Fp::from_repr(tv.initial_state[0]).unwrap(),
Fp::from_repr(tv.initial_state[1]).unwrap(),
Fp::from_repr(tv.initial_state[2]).unwrap(),
];
permute::<Fp, super::P128Pow5T3, 3, 2>(&mut state, &mds, &round_constants);
for (expected, actual) in tv.final_state.iter().zip(state.iter()) {
assert_eq!(&actual.to_repr(), expected);
}
}
}
{
let (round_constants, mds, _) = super::P128Pow5T3::constants();
for tv in crate::primitives::poseidon::test_vectors::fq::permute() {
let mut state = [
Fq::from_repr(tv.initial_state[0]).unwrap(),
Fq::from_repr(tv.initial_state[1]).unwrap(),
Fq::from_repr(tv.initial_state[2]).unwrap(),
];
permute::<Fq, super::P128Pow5T3, 3, 2>(&mut state, &mds, &round_constants);
for (expected, actual) in tv.final_state.iter().zip(state.iter()) {
assert_eq!(&actual.to_repr(), expected);
}
}
}
}
#[test]
fn hash_test_vectors() {
for tv in crate::primitives::poseidon::test_vectors::fp::hash() {
let message = [
Fp::from_repr(tv.input[0]).unwrap(),
Fp::from_repr(tv.input[1]).unwrap(),
];
let result =
Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
assert_eq!(result.to_repr(), tv.output);
}
for tv in crate::primitives::poseidon::test_vectors::fq::hash() {
let message = [
Fq::from_repr(tv.input[0]).unwrap(),
Fq::from_repr(tv.input[1]).unwrap(),
];
let result =
Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
assert_eq!(result.to_repr(), tv.output);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,293 +0,0 @@
//! The Sinsemilla hash function and commitment scheme.
use group::{Curve, Wnaf};
use halo2::arithmetic::{CurveAffine, CurveExt};
use pasta_curves::pallas;
use subtle::CtOption;
mod addition;
use self::addition::IncompletePoint;
mod sinsemilla_s;
pub use sinsemilla_s::SINSEMILLA_S;
/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$
pub const K: usize = 10;
/// $\frac{1}{2^K}$
pub const INV_TWO_POW_K: [u8; 32] = [
1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 240, 63,
];
/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order
/// of Pallas.
pub const C: usize = 253;
// Sinsemilla Q generators
/// SWU hash-to-curve personalization for Sinsemilla $Q$ generators.
pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ";
// Sinsemilla S generators
/// SWU hash-to-curve personalization for Sinsemilla $S$ generators.
pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS";
pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 {
assert!(bits.len() == K);
bits.iter()
.enumerate()
.fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 })
}
/// Coordinate extractor for Pallas.
///
/// Defined in [Zcash Protocol Spec § 5.4.9.7: Coordinate Extractor for Pallas][concreteextractorpallas].
///
/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas
fn extract_p_bottom(point: CtOption<pallas::Point>) -> CtOption<pallas::Base> {
point.map(|p| {
p.to_affine()
.coordinates()
.map(|c| *c.x())
.unwrap_or_else(pallas::Base::zero)
})
}
/// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a
/// multiple of $K$ bits.
struct Pad<I: Iterator<Item = bool>> {
/// The iterator we are padding.
inner: I,
/// The measured length of the inner iterator.
///
/// This starts as a lower bound, and will be accurate once `padding_left.is_some()`.
len: usize,
/// The amount of padding that remains to be emitted.
padding_left: Option<usize>,
}
impl<I: Iterator<Item = bool>> Pad<I> {
fn new(inner: I) -> Self {
Pad {
inner,
len: 0,
padding_left: None,
}
}
}
impl<I: Iterator<Item = bool>> Iterator for Pad<I> {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
loop {
// If we have identified the required padding, the inner iterator has ended,
// and we will never poll it again.
if let Some(n) = self.padding_left.as_mut() {
if *n == 0 {
// Either we already emitted all necessary padding, or there was no
// padding required.
break None;
} else {
// Emit the next padding bit.
*n -= 1;
break Some(false);
}
} else if let Some(ret) = self.inner.next() {
// We haven't reached the end of the inner iterator yet.
self.len += 1;
assert!(self.len <= K * C);
break Some(ret);
} else {
// Inner iterator just ended, so we now know its length.
let rem = self.len % K;
if rem > 0 {
// The inner iterator requires padding in the range [1,K).
self.padding_left = Some(K - rem);
} else {
// No padding required.
self.padding_left = Some(0);
}
}
}
}
}
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can
/// be used.
#[derive(Debug, Clone)]
#[allow(non_snake_case)]
pub struct HashDomain {
Q: pallas::Point,
}
impl HashDomain {
/// Constructs a new `HashDomain` with a specific prefix string.
pub fn new(domain: &str) -> Self {
HashDomain {
Q: pallas::Point::hash_to_curve(Q_PERSONALIZATION)(domain.as_bytes()),
}
}
/// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash].
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
pub fn hash_to_point(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Point> {
self.hash_to_point_inner(msg).into()
}
#[allow(non_snake_case)]
fn hash_to_point_inner(&self, msg: impl Iterator<Item = bool>) -> IncompletePoint {
let padded: Vec<_> = Pad::new(msg).collect();
padded
.chunks(K)
.fold(IncompletePoint::from(self.Q), |acc, chunk| {
let (S_x, S_y) = SINSEMILLA_S[lebs2ip_k(chunk) as usize];
let S_chunk = pallas::Affine::from_xy(S_x, S_y).unwrap();
(acc + S_chunk) + acc
})
}
/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
///
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
///
/// # Panics
///
/// This panics if the message length is greater than [`K`] * [`C`]
pub fn hash(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Base> {
extract_p_bottom(self.hash_to_point(msg))
}
/// Returns the Sinsemilla $Q$ constant for this domain.
#[cfg(test)]
#[allow(non_snake_case)]
pub(crate) fn Q(&self) -> pallas::Point {
self.Q
}
}
/// A domain in which $\mathsf{SinsemillaCommit}$ and $\mathsf{SinsemillaShortCommit}$ can
/// be used.
#[derive(Debug)]
#[allow(non_snake_case)]
pub struct CommitDomain {
M: HashDomain,
R: pallas::Point,
}
impl CommitDomain {
/// Constructs a new `CommitDomain` with a specific prefix string.
pub fn new(domain: &str) -> Self {
let m_prefix = format!("{}-M", domain);
let r_prefix = format!("{}-r", domain);
let hasher_r = pallas::Point::hash_to_curve(&r_prefix);
CommitDomain {
M: HashDomain::new(&m_prefix),
R: hasher_r(&[]),
}
}
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
#[allow(non_snake_case)]
pub fn commit(
&self,
msg: impl Iterator<Item = bool>,
r: &pallas::Scalar,
) -> CtOption<pallas::Point> {
// We use complete addition for the blinding factor.
CtOption::<pallas::Point>::from(self.M.hash_to_point_inner(msg))
.map(|p| p + Wnaf::new().scalar(r).base(self.R))
}
/// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
///
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
pub fn short_commit(
&self,
msg: impl Iterator<Item = bool>,
r: &pallas::Scalar,
) -> CtOption<pallas::Base> {
extract_p_bottom(self.commit(msg, r))
}
/// Returns the Sinsemilla $R$ constant for this domain.
#[cfg(test)]
#[allow(non_snake_case)]
pub(crate) fn R(&self) -> pallas::Point {
self.R
}
/// Returns the Sinsemilla $Q$ constant for this domain.
#[cfg(test)]
#[allow(non_snake_case)]
pub(crate) fn Q(&self) -> pallas::Point {
self.M.Q
}
}
#[cfg(test)]
mod tests {
use super::{Pad, K};
use pasta_curves::{arithmetic::CurveExt, pallas};
#[test]
fn pad() {
assert_eq!(Pad::new([].iter().cloned()).collect::<Vec<_>>(), vec![]);
assert_eq!(
Pad::new([true].iter().cloned()).collect::<Vec<_>>(),
vec![true, false, false, false, false, false, false, false, false, false]
);
assert_eq!(
Pad::new([true, true].iter().cloned()).collect::<Vec<_>>(),
vec![true, true, false, false, false, false, false, false, false, false]
);
assert_eq!(
Pad::new([true, true, true].iter().cloned()).collect::<Vec<_>>(),
vec![true, true, true, false, false, false, false, false, false, false]
);
assert_eq!(
Pad::new(
[true, true, false, true, false, true, false, true, false, true]
.iter()
.cloned()
)
.collect::<Vec<_>>(),
vec![true, true, false, true, false, true, false, true, false, true]
);
assert_eq!(
Pad::new(
[true, true, false, true, false, true, false, true, false, true, true]
.iter()
.cloned()
)
.collect::<Vec<_>>(),
vec![
true, true, false, true, false, true, false, true, false, true, true, false, false,
false, false, false, false, false, false, false
]
);
}
#[test]
fn sinsemilla_s() {
use super::sinsemilla_s::SINSEMILLA_S;
use group::Curve;
use pasta_curves::arithmetic::CurveAffine;
let hasher = pallas::Point::hash_to_curve(super::S_PERSONALIZATION);
for j in 0..(1u32 << K) {
let computed = {
let point = hasher(&j.to_le_bytes()).to_affine().coordinates().unwrap();
(*point.x(), *point.y())
};
let actual = SINSEMILLA_S[j as usize];
assert_eq!(computed, actual);
}
}
}

View File

@ -1,73 +0,0 @@
use std::ops::Add;
use group::{cofactor::CofactorCurveAffine, Group};
use pasta_curves::pallas;
use subtle::{ConstantTimeEq, CtOption};
/// P {⊥}
///
/// Simulated incomplete addition built over complete addition.
#[derive(Clone, Copy, Debug)]
pub(super) struct IncompletePoint(CtOption<pallas::Point>);
impl From<pallas::Point> for IncompletePoint {
fn from(p: pallas::Point) -> Self {
IncompletePoint(CtOption::new(p, 1.into()))
}
}
impl From<IncompletePoint> for CtOption<pallas::Point> {
fn from(p: IncompletePoint) -> Self {
p.0
}
}
impl Add for IncompletePoint {
type Output = IncompletePoint;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self::Output {
// ⊥ ⸭ ⊥ = ⊥
// ⊥ ⸭ P = ⊥
IncompletePoint(self.0.and_then(|p| {
// P ⸭ ⊥ = ⊥
rhs.0.and_then(|q| {
// 0 ⸭ 0 = ⊥
// 0 ⸭ P = ⊥
// P ⸭ 0 = ⊥
// (x, y) ⸭ (x', y') = ⊥ if x == x'
// (x, y) ⸭ (x', y') = (x, y) + (x', y') if x != x'
CtOption::new(
p + q,
!(p.is_identity() | q.is_identity() | p.ct_eq(&q) | p.ct_eq(&-q)),
)
})
}))
}
}
impl Add<pallas::Affine> for IncompletePoint {
type Output = IncompletePoint;
/// Specialisation of incomplete addition for mixed addition.
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: pallas::Affine) -> Self::Output {
// ⊥ ⸭ ⊥ = ⊥
// ⊥ ⸭ P = ⊥
IncompletePoint(self.0.and_then(|p| {
// P ⸭ ⊥ = ⊥ is satisfied by definition.
let q = rhs.to_curve();
// 0 ⸭ 0 = ⊥
// 0 ⸭ P = ⊥
// P ⸭ 0 = ⊥
// (x, y) ⸭ (x', y') = ⊥ if x == x'
// (x, y) ⸭ (x', y') = (x, y) + (x', y') if x != x'
CtOption::new(
// Use mixed addition for efficiency.
p + rhs,
!(p.is_identity() | q.is_identity() | p.ct_eq(&q) | p.ct_eq(&-q)),
)
}))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,16 +6,14 @@ use std::ops::Deref;
use ff::{Field, PrimeField, PrimeFieldBits};
use group::GroupEncoding;
use group::{Curve, Group};
use halo2::arithmetic::{CurveAffine, CurveExt, FieldExt};
use halo2_gadgets::primitives::{poseidon, sinsemilla};
use halo2_proofs::arithmetic::{CurveAffine, CurveExt, FieldExt};
use pasta_curves::pallas;
use subtle::{ConditionallySelectable, CtOption};
use crate::{
constants::{
fixed_bases::COMMIT_IVK_PERSONALIZATION, util::gen_const_array,
KEY_DIVERSIFICATION_PERSONALIZATION, L_ORCHARD_BASE,
},
primitives::{poseidon, sinsemilla},
use crate::constants::{
fixed_bases::COMMIT_IVK_PERSONALIZATION, util::gen_const_array,
KEY_DIVERSIFICATION_PERSONALIZATION, L_ORCHARD_BASE,
};
mod prf_expand;
@ -73,7 +71,7 @@ impl NonZeroPallasBase {
pallas::Base::from_repr(*bytes).and_then(NonZeroPallasBase::from_base)
}
pub(crate) fn to_bytes(&self) -> [u8; 32] {
pub(crate) fn to_bytes(self) -> [u8; 32] {
self.0.to_repr()
}
@ -283,7 +281,7 @@ mod tests {
use super::{i2lebsp, lebs2ip};
use group::Group;
use halo2::arithmetic::CurveExt;
use halo2_proofs::arithmetic::CurveExt;
use pasta_curves::pallas;
use rand::{rngs::OsRng, RngCore};
use std::convert::TryInto;

View File

@ -7,8 +7,8 @@ use crate::{
MERKLE_DEPTH_ORCHARD,
},
note::commitment::ExtractedNoteCommitment,
primitives::sinsemilla::HashDomain,
};
use halo2_gadgets::primitives::sinsemilla::HashDomain;
use incrementalmerkletree::{Altitude, Hashable};
use pasta_curves::pallas;