mirror of https://github.com/zcash/orchard.git
Merge pull request #217 from zcash/update-halo2-gadgets
Delete gadgets and introduce halo2_gadgets dependency.
This commit is contained in:
commit
40491385c6
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
||||
|
|
21
Cargo.toml
21
Cargo.toml
|
@ -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" }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(¶ms, &empty_circuit).expect("keygen_vk should not fail");
|
||||
let pk = keygen_pk(¶ms, 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(¶ms, &pk, &[circuit], &[&[]], &mut rng, &mut transcript)
|
||||
.expect("proof generation should not fail")
|
||||
})
|
||||
});
|
||||
|
||||
// Create a proof
|
||||
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
|
||||
create_proof(¶ms, &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(¶ms, 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);
|
|
@ -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);
|
|
@ -1 +1 @@
|
|||
1.51.0
|
||||
1.54.0
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(¶ms, &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(¶ms, &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)
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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_q−y_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_q−y_p)
|
||||
|
||||
// q_add ⋅(x_q − x_p)⋅((x_q − x_p)⋅λ − (y_q−y_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(())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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<_>>()
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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()),
|
||||
],
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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},
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(¬e.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)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,4 @@
|
|||
// - EphemeralPublicKey
|
||||
// - EphemeralSecretKey
|
||||
|
||||
pub mod poseidon;
|
||||
pub mod redpallas;
|
||||
pub mod sinsemilla;
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
16
src/spec.rs
16
src/spec.rs
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue