Merge pull request #237 from zcash/orchard-mainnet-circuit

Orchard proposed mainnet circuit
This commit is contained in:
str4d 2021-12-20 17:49:57 +00:00 committed by GitHub
commit 54cdc051fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 5142 additions and 4948 deletions

View File

@ -78,8 +78,15 @@ harness = false
name = "circuit"
harness = false
[[bench]]
name = "poseidon"
harness = false
[profile.release]
debug = true
[profile.bench]
debug = true
[patch.crates-io]
halo2 = { git = "https://github.com/zcash/halo2.git", rev = "afd7bc5469674cd08eae1634225fd02706a36a4f" }

View File

@ -73,7 +73,7 @@ fn criterion_benchmark(c: &mut Criterion) {
.unwrap()
.apply_signatures(rng, [0; 32], &[])
.unwrap();
assert_eq!(bundle.verify_proof(&vk), Ok(()));
assert!(bundle.verify_proof(&vk).is_ok());
group.bench_function(BenchmarkId::new("bundle", num_recipients), |b| {
b.iter(|| bundle.authorization().proof().verify(&vk, &instances));
});

253
benches/poseidon.rs Normal file
View File

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

View File

@ -21,7 +21,9 @@ fn bench_primitives(c: &mut Criterion) {
let message = [pallas::Base::random(rng), pallas::Base::random(rng)];
group.bench_function("2-to-1", |b| {
b.iter(|| poseidon::Hash::init(P128Pow5T3, ConstantLength).hash(message))
b.iter(|| {
poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message)
})
});
}

View File

@ -32,4 +32,5 @@
# Circuit constraint helper methods
\BoolCheck:{\texttt{bool\_check}({#1})}
\RangeCheck:{\texttt{range\_check}({#1, #2})}
\ShortLookupRangeCheck:{\texttt{short\_lookup\_range\_check}({#1})}

View File

@ -73,4 +73,4 @@ $$q_\mathit{lookup} \cdot \left(q_\mathit{running} \cdot (z_i - 2^K \cdot z_{i+1
where $z_i$ and $\textsf{word}$ are the same cell (but distinguished here for clarity of usage).
## Short range decomposition
For a short range (for instance, $[0, \texttt{range})$ where $\texttt{range} \leq 8$), we can range-constrain each word using a degree-$\texttt{range}$ polynomial constraint instead of a lookup: $$\texttt{range\_check(word, range)} = \texttt{word} \cdot (1 - \texttt{word}) \cdots (\texttt{range} - 1 - \texttt{word}).$$
For a short range (for instance, $[0, \texttt{range})$ where $\texttt{range} \leq 8$), we can range-constrain each word using a degree-$\texttt{range}$ polynomial constraint instead of a lookup: $$\RangeCheck{word}{range} = \texttt{word} \cdot (1 - \texttt{word}) \cdots (\texttt{range} - 1 - \texttt{word}).$$

View File

@ -28,10 +28,10 @@ $$
\begin{array}{|c|l|}
\hline
\text{Degree} & \text{Constraint} \\\hline
9 & q_\text{decompose-base-field} \cdot \texttt{range\_check}(\text{word}, 2^3) = 0 \\\hline
9 & q_\text{decompose-base-field} \cdot \RangeCheck{\text{word}}{2^3} = 0 \\\hline
\end{array}
$$
where $\texttt{range\_check}(\text{word}, \texttt{range}) = \text{word} \cdot (1 - \text{word}) \cdots (\texttt{range} - 1 - \text{word}).$
where $\RangeCheck{\text{word}}{\texttt{range}} = \text{word} \cdot (1 - \text{word}) \cdots (\texttt{range} - 1 - \text{word}).$
### Base field element
We support using a base field element as the scalar in fixed-base multiplication. This occurs, for example, in the scalar multiplication for the nullifier computation of the Action circuit $\mathsf{DeriveNullifier_{nk}} = \mathsf{Extract}_\mathbb{P}\left(\left[(\mathsf{PRF_{nk}^{nfOrchard}}(\rho) + \psi) \bmod{q_\mathbb{P}}\right]\mathcal{K}^\mathsf{Orchard} + \mathsf{cm}\right)$: here, the scalar $$\left[(\mathsf{PRF_{nk}^{nfOrchard}}(\rho) + \psi) \bmod{q_\mathbb{P}}\right]$$ is the result of a base field addition.
@ -47,8 +47,8 @@ $$
\begin{array}{|c|l|}
\hline
\text{Degree} & \text{Constraint} \\\hline
5 & q_\text{canon-base-field} \cdot \texttt{range\_check}(\alpha_1, 2^2) = 0 \\\hline
3 & q_\text{canon-base-field} \cdot \texttt{range\_check}(\alpha_2, 2^1) = 0 \\\hline
5 & q_\text{canon-base-field} \cdot \RangeCheck{\alpha_1}{2^2} = 0 \\\hline
3 & q_\text{canon-base-field} \cdot \RangeCheck{\alpha_2}{2^1} = 0 \\\hline
2 & q_\text{canon-base-field} \cdot \left(z_{84} - (\alpha_1 + \alpha_2 \cdot 2^2)\right) = 0 \\\hline
\end{array}
$$
@ -102,10 +102,11 @@ $$
\begin{array}{|c|l|l|}
\hline
\text{Degree} & \text{Constraint} & \text{Comment} \\\hline
3 & q_\text{scalar-fixed-short} \cdot \left(k_{21} \cdot (1 - k_{21})\right) = 0 & \text{The last window must be a single bit.}\\\hline
3 & q_\text{scalar-fixed-short} \cdot \BoolCheck{k_{21}} = 0 & \text{The last window must be a single bit.}\\\hline
3 & q_\text{scalar-fixed-short} \cdot \left(s^2 - 1\right) = 0 &\text{The sign must be $1$ or $-1$.}\\\hline
\end{array}
$$
where $\BoolCheck{x} = x \cdot (1 - x)$.
## Load fixed base
Then, we precompute multiples of the fixed base $B$ for each window. This takes the form of a window table: $M[0..W)[0..8)$ such that:

View File

@ -103,7 +103,7 @@ $\begin{array}{l}
\text{Initialize } A_{254} = [2] T. \\
\\
\text{for } i \text{ from } 254 \text{ down to } 4: \\
\hspace{1.5em} (\mathbf{k}_i)(\mathbf{k}_i-1) = 0 \\
\hspace{1.5em} \BoolCheck{\mathbf{k}_i} = 0 \\
\hspace{1.5em} \mathbf{z}_{i} = 2\mathbf{z}_{i+1} + \mathbf{k}_{i} \\
\hspace{1.5em} x_{P,i} = x_T \\
\hspace{1.5em} y_{P,i} = (2 \mathbf{k}_i - 1) \cdot y_T \hspace{2em}\text{(conditionally negate)} \\
@ -114,7 +114,8 @@ $\begin{array}{l}
\hspace{1.5em} \lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) = y_{A,i} + y_{A,i-1}, \\
\end{array}$
where $x_{R,i} = (\lambda_{1,i}^2 - x_{A,i} - x_T).$ After substitution of $x_{P,i}, y_{P,i}, x_{R,i}, y_{A,i}$, and $y_{A,i-1}$, this becomes:
where $x_{R,i} = (\lambda_{1,i}^2 - x_{A,i} - x_T).$ The helper $\BoolCheck{x} = x \cdot (1 - x)$.
After substitution of $x_{P,i}, y_{P,i}, x_{R,i}, y_{A,i}$, and $y_{A,i-1}$, this becomes:
$\begin{array}{l}
\text{Initialize } A_{254} = [2] T. \\
@ -122,7 +123,7 @@ $\begin{array}{l}
\text{for } i \text{ from } 254 \text{ down to } 4: \\
\hspace{1.5em} \text{// let } \mathbf{k}_{i} = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\
\hspace{1.5em} \text{// let } y_{A,i} = \frac{(\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - (\lambda_{1,i}^2 - x_{A,i} - x_T))}{2} \\[2ex]
\hspace{1.5em} (\mathbf{k}_i)(\mathbf{k}_i-1) = 0 \\
\hspace{1.5em} \BoolCheck{\mathbf{k}_i} = 0 \\
\hspace{1.5em} \lambda_{1,i} \cdot (x_{A,i} - x_T) = y_{A,i} - (2 \mathbf{k}_i - 1) \cdot y_T \\
\hspace{1.5em} \lambda_{2,i}^2 = x_{A,i-1} + \lambda_{1,i}^2 - x_T \\[1ex]
\hspace{1.5em} \begin{cases}
@ -138,7 +139,7 @@ The bits $\mathbf{k}_{3 \dots 1}$ are used in three further steps, using [comple
$\begin{array}{l}
\text{for } i \text{ from } 3 \text{ down to } 1: \\
\hspace{1.5em} \text{// let } \mathbf{k}_{i} = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\[0.5ex]
\hspace{1.5em} (\mathbf{k}_i)(\mathbf{k}_i-1) = 0 \\
\hspace{1.5em} \BoolCheck{\mathbf{k}_i} = 0 \\
\hspace{1.5em} (x_{A,i-1}, y_{A,i-1}) = \left((x_{A,i}, y_{A,i}) + (x_T, y_T)\right) + (x_{A,i}, y_{A,i})
\end{array}$
@ -202,7 +203,7 @@ $$
\text{Degree} & \text{Constraint} \\\hline
2 & q_2 \cdot \left(x_{T,cur} - x_{T,next}\right) = 0 \\\hline
2 & q_2 \cdot \left(y_{T,cur} - y_{T,next}\right) = 0 \\\hline
3 & q_2 \cdot \mathbf{k}_i \cdot (\mathbf{k}_i - 1) = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline
3 & q_2 \cdot \BoolCheck{\mathbf{k}_i} = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline
4 & q_2 \cdot \left(\lambda_{1,i} \cdot (x_{A,i} - x_{T,i}) - y_{A,i} + (2\mathbf{k}_i - 1) \cdot y_{T,i}\right) = 0 \\\hline
3 & q_2 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - \lambda_{1,i}^2 + x_{T,i}\right) = 0 \\\hline
3 & q_2 \cdot \left(\lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) - y_{A,i} - y_{A,i-1}\right) = 0 \\\hline
@ -222,7 +223,7 @@ $$
\begin{array}{|c|l|}
\hline
\text{Degree} & \text{Constraint} \\\hline
3 & q_3 \cdot \mathbf{k}_i \cdot (\mathbf{k}_i - 1) = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline
3 & q_3 \cdot \BoolCheck{\mathbf{k}_i} = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline
4 & q_3 \cdot \left(\lambda_{1,i} \cdot (x_{A,i} - x_{T,i}) - y_{A,i} + (2\mathbf{k}_i - 1) \cdot y_{T,i}\right) = 0 \\\hline
3 & q_3 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - \lambda_{1,i}^2 + x_{T,i}\right) = 0 \\\hline
3 & q_3 \cdot \left(\lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) - y_{A,i} - y_{A,i-1}^\text{witnessed}\right) = 0 \\\hline

View File

@ -2,16 +2,13 @@
use group::{Curve, GroupEncoding};
use halo2::{
circuit::{floor_planner, Layouter},
circuit::{floor_planner, AssignedCell, Layouter},
plonk::{self, Advice, Column, Expression, Instance as InstanceColumn, Selector},
poly::Rotation,
transcript::{Blake2bRead, Blake2bWrite},
};
use memuse::DynamicUsage;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas, vesta,
};
use pasta_curves::{arithmetic::CurveAffine, pallas, vesta};
use crate::{
constants::{
@ -40,10 +37,7 @@ use gadget::{
chip::{EccChip, EccConfig},
FixedPoint, FixedPointBaseField, FixedPointShort, NonIdentityPoint, Point,
},
poseidon::{
Hash as PoseidonHash, Pow5T3Chip as PoseidonChip, Pow5T3Config as PoseidonConfig,
StateWord, Word,
},
poseidon::{Hash as PoseidonHash, Pow5Chip as PoseidonChip, Pow5Config as PoseidonConfig},
sinsemilla::{
chip::{SinsemillaChip, SinsemillaConfig, SinsemillaHashDomains},
commit_ivk::CommitIvkConfig,
@ -53,14 +47,14 @@ use gadget::{
},
note_commit::NoteCommitConfig,
},
utilities::{copy, CellValue, UtilitiesInstructions, Var},
utilities::UtilitiesInstructions,
};
use std::convert::TryInto;
use self::gadget::utilities::lookup_range_check::LookupRangeCheckConfig;
pub(crate) mod gadget;
pub mod gadget;
/// Size of the Orchard circuit.
const K: u32 = 11;
@ -85,7 +79,7 @@ pub struct Config {
q_add: Selector,
advices: [Column<Advice>; 10],
ecc_config: EccConfig,
poseidon_config: PoseidonConfig<pallas::Base>,
poseidon_config: PoseidonConfig<pallas::Base, 3, 2>,
merkle_config_1: MerkleConfig,
merkle_config_2: MerkleConfig,
sinsemilla_config_1: SinsemillaConfig,
@ -120,7 +114,7 @@ pub struct Circuit {
}
impl UtilitiesInstructions<pallas::Base> for Circuit {
type Var = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl plonk::Circuit<pallas::Base> for Circuit {
@ -239,12 +233,11 @@ impl plonk::Circuit<pallas::Base> for Circuit {
// Configuration for curve point operations.
// This uses 10 advice columns and spans the whole circuit.
let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check.clone());
let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check);
// Configuration for the Poseidon hash.
let poseidon_config = PoseidonChip::configure(
let poseidon_config = PoseidonChip::configure::<poseidon::P128Pow5T3>(
meta,
poseidon::P128Pow5T3,
// We place the state columns after the partial_sbox column so that the
// pad-and-add region can be layed out more efficiently.
advices[6..9].try_into().unwrap(),
@ -264,7 +257,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
advices[6],
lagrange_coeffs[0],
lookup,
range_check.clone(),
range_check,
);
let merkle_config_1 = MerkleChip::configure(meta, sinsemilla_config_1.clone());
@ -381,16 +374,14 @@ impl plonk::Circuit<pallas::Base> for Circuit {
let v_old = self.load_private(
layouter.namespace(|| "witness v_old"),
config.advices[0],
self.v_old
.map(|v_old| pallas::Base::from_u64(v_old.inner())),
self.v_old.map(|v_old| pallas::Base::from(v_old.inner())),
)?;
// Witness v_new.
let v_new = self.load_private(
layouter.namespace(|| "witness v_new"),
config.advices[0],
self.v_new
.map(|v_new| pallas::Base::from_u64(v_new.inner())),
self.v_new.map(|v_new| pallas::Base::from(v_new.inner())),
)?;
(psi_old, rho_old, cm_old, g_d_old, ak, nk, v_old, v_new)
@ -409,7 +400,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
leaf_pos: self.pos,
path,
};
let leaf = *cm_old.extract_p().inner();
let leaf = cm_old.extract_p().inner().clone();
merkle_inputs.calculate_root(layouter.namespace(|| "MerkleCRH"), leaf)?
};
@ -419,12 +410,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
let v_net = {
// v_old, v_new are guaranteed to be 64-bit values. Therefore, we can
// move them into the base field.
let v_old = self
.v_old
.map(|v_old| pallas::Base::from_u64(v_old.inner()));
let v_new = self
.v_new
.map(|v_new| pallas::Base::from_u64(v_new.inner()));
let v_old = self.v_old.map(|v_old| pallas::Base::from(v_old.inner()));
let v_new = self.v_new.map(|v_new| pallas::Base::from(v_new.inner()));
let magnitude_sign = v_old.zip(v_new).map(|(v_old, v_new)| {
let is_negative = v_old < v_new;
@ -458,7 +445,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
let (commitment, _) = {
let value_commit_v = ValueCommitV::get();
let value_commit_v = FixedPointShort::from_inner(ecc_chip.clone(), value_commit_v);
value_commit_v.mul(layouter.namespace(|| "[v_net] ValueCommitV"), v_net)?
value_commit_v.mul(layouter.namespace(|| "[v_net] ValueCommitV"), v_net.clone())?
};
// blind = [rcv] ValueCommitR
@ -485,40 +472,16 @@ impl plonk::Circuit<pallas::Base> for Circuit {
let nf_old = {
// hash_old = poseidon_hash(nk, rho_old)
let hash_old = {
let message = [nk, rho_old];
let poseidon_message = layouter.assign_region(
|| "load message",
|mut region| {
let mut message_word = |i: usize| {
let value = message[i].value();
let var = region.assign_advice(
|| format!("load message_{}", i),
config.poseidon_config.state[i],
0,
|| value.ok_or(plonk::Error::SynthesisError),
)?;
region.constrain_equal(var, message[i].cell())?;
Ok(Word::<_, _, poseidon::P128Pow5T3, 3, 2>::from_inner(
StateWord::new(var, value),
))
};
Ok([message_word(0)?, message_word(1)?])
},
)?;
let poseidon_hasher = PoseidonHash::init(
config.poseidon_chip(),
layouter.namespace(|| "Poseidon init"),
ConstantLength::<2>,
)?;
let poseidon_output = poseidon_hasher.hash(
let poseidon_message = [nk.clone(), rho_old.clone()];
let poseidon_hasher =
PoseidonHash::<_, _, poseidon::P128Pow5T3, ConstantLength<2>, 3, 2>::init(
config.poseidon_chip(),
layouter.namespace(|| "Poseidon init"),
)?;
poseidon_hasher.hash(
layouter.namespace(|| "Poseidon hash (nk, rho_old)"),
poseidon_message,
)?;
let poseidon_output: CellValue<pallas::Base> = poseidon_output.inner().into();
poseidon_output
)?
};
// Add hash output to psi.
@ -529,32 +492,19 @@ impl plonk::Circuit<pallas::Base> for Circuit {
|mut region| {
config.q_add.enable(&mut region, 0)?;
copy(
&mut region,
|| "copy hash_old",
config.advices[7],
0,
&hash_old,
)?;
copy(
&mut region,
|| "copy psi_old",
config.advices[8],
0,
&psi_old,
)?;
hash_old.copy_advice(|| "copy hash_old", &mut region, config.advices[7], 0)?;
psi_old.copy_advice(|| "copy psi_old", &mut region, config.advices[8], 0)?;
let scalar_val = hash_old
.value()
.zip(psi_old.value())
.map(|(hash_old, psi_old)| hash_old + psi_old);
let cell = region.assign_advice(
region.assign_advice(
|| "poseidon_hash(nk, rho_old) + psi_old",
config.advices[6],
0,
|| scalar_val.ok_or(plonk::Error::SynthesisError),
)?;
Ok(CellValue::new(cell, scalar_val))
|| scalar_val.ok_or(plonk::Error::Synthesis),
)
},
)?;
@ -609,7 +559,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
config.sinsemilla_chip_1(),
ecc_chip.clone(),
layouter.namespace(|| "CommitIvk"),
*ak.extract_p().inner(),
ak.extract_p().inner().clone(),
nk,
rivk,
)?
@ -647,7 +597,7 @@ impl plonk::Circuit<pallas::Base> for Circuit {
config.ecc_chip(),
g_d_old.inner(),
pk_d_old.inner(),
v_old,
v_old.clone(),
rho_old,
psi_old,
rcm_old,
@ -703,8 +653,8 @@ impl plonk::Circuit<pallas::Base> for Circuit {
config.ecc_chip(),
g_d_new.inner(),
pk_d_new.inner(),
v_new,
*nf_old.inner(),
v_new.clone(),
nf_old.inner().clone(),
psi_new,
rcm_new,
)?;
@ -720,19 +670,13 @@ impl plonk::Circuit<pallas::Base> for Circuit {
layouter.assign_region(
|| "v_old - v_new = magnitude * sign",
|mut region| {
copy(&mut region, || "v_old", config.advices[0], 0, &v_old)?;
copy(&mut region, || "v_new", config.advices[1], 0, &v_new)?;
let (magnitude, sign) = v_net;
copy(
&mut region,
|| "v_net magnitude",
config.advices[2],
0,
&magnitude,
)?;
copy(&mut region, || "v_net sign", config.advices[3], 0, &sign)?;
v_old.copy_advice(|| "v_old", &mut region, config.advices[0], 0)?;
v_new.copy_advice(|| "v_new", &mut region, config.advices[1], 0)?;
let (magnitude, sign) = v_net.clone();
magnitude.copy_advice(|| "v_net magnitude", &mut region, config.advices[2], 0)?;
sign.copy_advice(|| "v_net sign", &mut region, config.advices[3], 0)?;
copy(&mut region, || "anchor", config.advices[4], 0, &anchor)?;
anchor.copy_advice(|| "anchor", &mut region, config.advices[4], 0)?;
region.assign_advice_from_instance(
|| "pub input anchor",
config.primary,
@ -861,8 +805,8 @@ impl Instance {
instance[RK_X] = *rk.x();
instance[RK_Y] = *rk.y();
instance[CMX] = self.cmx.inner();
instance[ENABLE_SPEND] = vesta::Scalar::from_u64(self.enable_spend.into());
instance[ENABLE_OUTPUT] = vesta::Scalar::from_u64(self.enable_output.into());
instance[ENABLE_SPEND] = vesta::Scalar::from(u64::from(self.enable_spend));
instance[ENABLE_OUTPUT] = vesta::Scalar::from(u64::from(self.enable_output));
[instance]
}
@ -1094,7 +1038,7 @@ mod tests {
halo2::dev::CircuitLayout::default()
.show_labels(false)
.view_height(0..(1 << 11))
.render(K as usize, &circuit, &root)
.render(K, &circuit, &root)
.unwrap();
}
}

View File

@ -1,13 +1,15 @@
//! Gadgets used in the Orchard circuit.
use pasta_curves::pallas;
use ecc::chip::EccChip;
use poseidon::Pow5T3Chip as PoseidonChip;
use poseidon::Pow5Chip as PoseidonChip;
use sinsemilla::{chip::SinsemillaChip, merkle::chip::MerkleChip};
pub(crate) mod ecc;
pub(crate) mod poseidon;
pub mod poseidon;
pub(crate) mod sinsemilla;
pub(crate) mod utilities;
pub mod utilities;
impl super::Config {
pub(super) fn ecc_chip(&self) -> EccChip {
@ -30,7 +32,7 @@ impl super::Config {
MerkleChip::construct(self.merkle_config_2.clone())
}
pub(super) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base> {
pub(super) fn poseidon_chip(&self) -> PoseidonChip<pallas::Base, 3, 2> {
PoseidonChip::construct(self.poseidon_config.clone())
}
}

View File

@ -508,7 +508,9 @@ mod tests {
use super::chip::{EccChip, EccConfig};
use crate::circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig;
struct MyCircuit {}
struct MyCircuit {
test_errors: bool,
}
#[allow(non_snake_case)]
impl Circuit<pallas::Base> for MyCircuit {
@ -516,7 +518,7 @@ mod tests {
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
MyCircuit {}
MyCircuit { test_errors: false }
}
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
@ -634,6 +636,7 @@ mod tests {
q_val,
&q,
&p_neg,
self.test_errors,
)?;
}
@ -678,7 +681,7 @@ mod tests {
#[test]
fn ecc_chip() {
let k = 13;
let circuit = MyCircuit {};
let circuit = MyCircuit { test_errors: true };
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
@ -692,7 +695,7 @@ mod tests {
root.fill(&WHITE).unwrap();
let root = root.titled("Ecc Chip Layout", ("sans-serif", 60)).unwrap();
let circuit = MyCircuit {};
let circuit = MyCircuit { test_errors: false };
halo2::dev::CircuitLayout::default()
.render(13, &circuit, &root)
.unwrap();

View File

@ -1,21 +1,23 @@
use super::EccInstructions;
use crate::{
circuit::gadget::utilities::{
copy, decompose_running_sum::RunningSumConfig, lookup_range_check::LookupRangeCheckConfig,
CellValue, UtilitiesInstructions, Var,
lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions,
},
constants::{self, NullifierK, OrchardFixedBasesFull, ValueCommitV},
primitives::sinsemilla,
};
use arrayvec::ArrayVec;
use ff::Field;
use group::prime::PrimeCurveAffine;
use halo2::{
circuit::{Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector},
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(super) mod mul;
@ -25,12 +27,12 @@ pub(super) mod witness_point;
/// A curve point represented in affine (x, y) coordinates, or the
/// identity represented as (0, 0).
/// Each coordinate is assigned to a cell.
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct EccPoint {
/// x-coordinate
x: CellValue<pallas::Base>,
x: AssignedCell<pallas::Base, pallas::Base>,
/// y-coordinate
y: CellValue<pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
}
impl EccPoint {
@ -39,8 +41,8 @@ impl EccPoint {
/// 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: CellValue<pallas::Base>,
y: CellValue<pallas::Base>,
x: AssignedCell<pallas::Base, pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
) -> Self {
EccPoint { x, y }
}
@ -49,10 +51,10 @@ impl EccPoint {
pub fn point(&self) -> Option<pallas::Affine> {
match (self.x.value(), self.y.value()) {
(Some(x), Some(y)) => {
if x == pallas::Base::zero() && y == pallas::Base::zero() {
if x.is_zero_vartime() && y.is_zero_vartime() {
Some(pallas::Affine::identity())
} else {
Some(pallas::Affine::from_xy(x, y).unwrap())
Some(pallas::Affine::from_xy(*x, *y).unwrap())
}
}
_ => None,
@ -60,29 +62,29 @@ impl EccPoint {
}
/// The cell containing the affine short-Weierstrass x-coordinate,
/// or 0 for the zero point.
pub fn x(&self) -> CellValue<pallas::Base> {
self.x
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) -> CellValue<pallas::Base> {
self.y
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 == pallas::Base::zero())
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(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct NonIdentityEccPoint {
/// x-coordinate
x: CellValue<pallas::Base>,
x: AssignedCell<pallas::Base, pallas::Base>,
/// y-coordinate
y: CellValue<pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
}
impl NonIdentityEccPoint {
@ -91,8 +93,8 @@ impl NonIdentityEccPoint {
/// 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: CellValue<pallas::Base>,
y: CellValue<pallas::Base>,
x: AssignedCell<pallas::Base, pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
) -> Self {
NonIdentityEccPoint { x, y }
}
@ -101,19 +103,19 @@ impl NonIdentityEccPoint {
pub fn point(&self) -> Option<pallas::Affine> {
match (self.x.value(), self.y.value()) {
(Some(x), Some(y)) => {
assert!(x != pallas::Base::zero() && y != pallas::Base::zero());
Some(pallas::Affine::from_xy(x, y).unwrap())
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) -> CellValue<pallas::Base> {
self.x
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) -> CellValue<pallas::Base> {
self.y
pub fn y(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.y.clone()
}
}
@ -133,47 +135,27 @@ pub struct EccConfig {
/// Advice columns needed by instructions in the ECC chip.
pub advices: [Column<Advice>; 10],
/// Coefficients of interpolation polynomials for x-coordinates (used in fixed-base scalar multiplication)
pub lagrange_coeffs: [Column<Fixed>; constants::H],
/// Fixed z such that y + z = u^2 some square, and -y + z is a non-square. (Used in fixed-base scalar multiplication)
pub fixed_z: Column<Fixed>,
/// Incomplete addition
pub q_add_incomplete: Selector,
add_incomplete: add_incomplete::Config,
/// Complete addition
pub q_add: Selector,
add: add::Config,
/// Variable-base scalar multiplication (hi half)
pub q_mul_hi: (Selector, Selector, Selector),
/// Variable-base scalar multiplication (lo half)
pub q_mul_lo: (Selector, Selector, Selector),
/// Selector used to enforce boolean decomposition in variable-base scalar mul
pub q_mul_decompose_var: Selector,
/// Selector used to enforce switching logic on LSB in variable-base scalar mul
pub q_mul_lsb: Selector,
/// Variable-base scalar multiplication (overflow check)
pub q_mul_overflow: Selector,
/// Variable-base scalar multiplication
mul: mul::Config,
/// Fixed-base full-width scalar multiplication
pub q_mul_fixed_full: Selector,
mul_fixed_full: mul_fixed::full_width::Config,
/// Fixed-base signed short scalar multiplication
pub q_mul_fixed_short: Selector,
/// Canonicity checks on base field element used as scalar in fixed-base mul
pub q_mul_fixed_base_field: Selector,
/// Running sum decomposition of a scalar used in fixed-base mul. This is used
/// when the scalar is a signed short exponent or a base-field element.
pub q_mul_fixed_running_sum: Selector,
mul_fixed_short: mul_fixed::short::Config,
/// Fixed-base mul using a base field element as a scalar
mul_fixed_base_field: mul_fixed::base_field_elem::Config,
/// Witness point (can be identity)
pub q_point: Selector,
/// Witness non-identity point
pub q_point_non_id: Selector,
/// Witness point
witness_point: witness_point::Config,
/// Lookup range check using 10-bit lookup table
pub lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
/// Running sum decomposition.
pub running_sum_config: RunningSumConfig<pallas::Base, { constants::FIXED_BASE_WINDOW_SIZE }>,
}
/// A chip implementing EccInstructions
@ -196,7 +178,7 @@ impl Chip<pallas::Base> for EccChip {
}
impl UtilitiesInstructions<pallas::Base> for EccChip {
type Var = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl EccChip {
@ -214,113 +196,59 @@ impl EccChip {
lagrange_coeffs: [Column<Fixed>; 8],
range_check: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
) -> <Self as Chip<pallas::Base>>::Config {
// The following columns need to be equality-enabled for their use in sub-configs:
//
// add::Config and add_incomplete::Config:
// - advices[0]: x_p,
// - advices[1]: y_p,
// - advices[2]: x_qr,
// - advices[3]: y_qr,
//
// mul_fixed::Config:
// - advices[4]: window
// - advices[5]: u
//
// mul_fixed::base_field_element::Config:
// - [advices[6], advices[7], advices[8]]: canon_advices
//
// mul::overflow::Config:
// - [advices[0], advices[1], advices[2]]: advices
//
// mul::incomplete::Config
// - advices[4]: lambda1
// - advices[9]: z
//
// mul::complete::Config:
// - advices[9]: z_complete
//
// TODO: Refactor away from `impl From<EccConfig> for _` so that sub-configs can
// equality-enable the columns they need to.
for column in &advices {
meta.enable_equality((*column).into());
}
let q_mul_fixed_running_sum = meta.selector();
let running_sum_config =
RunningSumConfig::configure(meta, q_mul_fixed_running_sum, advices[4]);
let config = EccConfig {
advices,
lagrange_coeffs,
fixed_z: meta.fixed_column(),
q_add_incomplete: meta.selector(),
q_add: meta.selector(),
q_mul_hi: (meta.selector(), meta.selector(), meta.selector()),
q_mul_lo: (meta.selector(), meta.selector(), meta.selector()),
q_mul_decompose_var: meta.selector(),
q_mul_overflow: meta.selector(),
q_mul_lsb: meta.selector(),
q_mul_fixed_full: meta.selector(),
q_mul_fixed_short: meta.selector(),
q_mul_fixed_base_field: meta.selector(),
q_mul_fixed_running_sum,
q_point: meta.selector(),
q_point_non_id: meta.selector(),
lookup_config: range_check,
running_sum_config,
};
// Create witness point gate
{
let config: witness_point::Config = (&config).into();
config.create_gate(meta);
}
let witness_point = witness_point::Config::configure(meta, advices[0], advices[1]);
// Create incomplete point addition gate
{
let config: add_incomplete::Config = (&config).into();
config.create_gate(meta);
}
let add_incomplete =
add_incomplete::Config::configure(meta, advices[0], advices[1], advices[2], advices[3]);
// Create complete point addition gate
{
let add_config: add::Config = (&config).into();
add_config.create_gate(meta);
}
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_config: mul::Config = (&config).into();
mul_config.create_gate(meta);
}
let mul = mul::Config::configure(meta, add, range_check, advices);
// Create gate that is used both in fixed-base mul using a short signed exponent,
// and fixed-base mul using a base field element.
{
// The const generic does not matter when creating gates.
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
mul_fixed_config.running_sum_coords_gate(meta);
}
// Create config that is shared across short, base-field, and full-width
// fixed-base scalar mul.
let mul_fixed = mul_fixed::Config::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_config: mul_fixed::full_width::Config = (&config).into();
mul_fixed_full_config.create_gate(meta);
}
let mul_fixed_full = mul_fixed::full_width::Config::configure(meta, mul_fixed);
// Create gate that is only used in short fixed-base scalar mul.
{
let short_config: mul_fixed::short::Config = (&config).into();
short_config.create_gate(meta);
}
let mul_fixed_short = mul_fixed::short::Config::configure(meta, mul_fixed);
// Create gate that is only used in fixed-base mul using a base field element.
{
let base_field_config: mul_fixed::base_field_elem::Config = (&config).into();
base_field_config.create_gate(meta);
}
let mul_fixed_base_field = mul_fixed::base_field_elem::Config::configure(
meta,
advices[6..9].try_into().unwrap(),
range_check,
mul_fixed,
);
config
EccConfig {
advices,
add_incomplete,
add,
mul,
mul_fixed_full,
mul_fixed_short,
mul_fixed_base_field,
witness_point,
lookup_config: range_check,
}
}
}
@ -332,9 +260,15 @@ impl EccChip {
#[derive(Clone, Debug)]
pub struct EccScalarFixed {
value: Option<pallas::Scalar>,
windows: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS }>,
windows: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { constants::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.
@ -349,9 +283,10 @@ pub struct EccScalarFixed {
/// k_21 must be a single bit, i.e. 0 or 1.
#[derive(Clone, Debug)]
pub struct EccScalarFixedShort {
magnitude: CellValue<pallas::Base>,
sign: CellValue<pallas::Base>,
running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS_SHORT + 1 }>,
magnitude: MagnitudeCell,
sign: SignCell,
running_sum:
ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { constants::NUM_WINDOWS_SHORT + 1 }>,
}
/// A base field element used for fixed-base scalar multiplication.
@ -365,23 +300,23 @@ pub struct EccScalarFixedShort {
/// `base_field_elem`.
#[derive(Clone, Debug)]
struct EccBaseFieldElemFixed {
base_field_elem: CellValue<pallas::Base>,
running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS + 1 }>,
base_field_elem: AssignedCell<pallas::Base, pallas::Base>,
running_sum: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { constants::NUM_WINDOWS + 1 }>,
}
impl EccBaseFieldElemFixed {
fn base_field_elem(&self) -> CellValue<pallas::Base> {
self.base_field_elem
fn base_field_elem(&self) -> AssignedCell<pallas::Base, pallas::Base> {
self.base_field_elem.clone()
}
}
impl EccInstructions<pallas::Affine> for EccChip {
type ScalarFixed = EccScalarFixed;
type ScalarFixedShort = EccScalarFixedShort;
type ScalarVar = CellValue<pallas::Base>;
type ScalarVar = AssignedCell<pallas::Base, pallas::Base>;
type Point = EccPoint;
type NonIdentityPoint = NonIdentityEccPoint;
type X = CellValue<pallas::Base>;
type X = AssignedCell<pallas::Base, pallas::Base>;
type FixedPoints = OrchardFixedBasesFull;
type FixedPointsBaseField = NullifierK;
type FixedPointsShort = ValueCommitV;
@ -408,7 +343,7 @@ impl EccInstructions<pallas::Affine> for EccChip {
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Affine>,
) -> Result<Self::Point, Error> {
let config: witness_point::Config = self.config().into();
let config = self.config().witness_point;
layouter.assign_region(
|| "witness point",
|mut region| config.point(value, 0, &mut region),
@ -420,7 +355,7 @@ impl EccInstructions<pallas::Affine> for EccChip {
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Affine>,
) -> Result<Self::NonIdentityPoint, Error> {
let config: witness_point::Config = self.config().into();
let config = self.config().witness_point;
layouter.assign_region(
|| "witness non-identity point",
|mut region| config.point_non_id(value, 0, &mut region),
@ -438,7 +373,7 @@ impl EccInstructions<pallas::Affine> for EccChip {
a: &Self::NonIdentityPoint,
b: &Self::NonIdentityPoint,
) -> Result<Self::NonIdentityPoint, Error> {
let config: add_incomplete::Config = self.config().into();
let config = self.config().add_incomplete;
layouter.assign_region(
|| "incomplete point addition",
|mut region| config.assign_region(a, b, 0, &mut region),
@ -451,7 +386,7 @@ impl EccInstructions<pallas::Affine> for EccChip {
a: &A,
b: &B,
) -> Result<Self::Point, Error> {
let config: add::Config = self.config().into();
let config = self.config().add;
layouter.assign_region(
|| "complete point addition",
|mut region| {
@ -466,10 +401,10 @@ impl EccInstructions<pallas::Affine> for EccChip {
scalar: &Self::Var,
base: &Self::NonIdentityPoint,
) -> Result<(Self::Point, Self::ScalarVar), Error> {
let config: mul::Config = self.config().into();
let config = self.config().mul;
config.assign(
layouter.namespace(|| "variable-base scalar mul"),
*scalar,
scalar.clone(),
base,
)
}
@ -480,7 +415,7 @@ impl EccInstructions<pallas::Affine> for EccChip {
scalar: Option<pallas::Scalar>,
base: &Self::FixedPoints,
) -> Result<(Self::Point, Self::ScalarFixed), Error> {
let config: mul_fixed::full_width::Config = self.config().into();
let config = self.config().mul_fixed_full;
config.assign(
layouter.namespace(|| format!("fixed-base mul of {:?}", base)),
scalar,
@ -491,10 +426,10 @@ impl EccInstructions<pallas::Affine> for EccChip {
fn mul_fixed_short(
&self,
layouter: &mut impl Layouter<pallas::Base>,
magnitude_sign: (CellValue<pallas::Base>, CellValue<pallas::Base>),
magnitude_sign: MagnitudeSign,
base: &Self::FixedPointsShort,
) -> Result<(Self::Point, Self::ScalarFixedShort), Error> {
let config: mul_fixed::short::Config = self.config().into();
let config: mul_fixed::short::Config = self.config().mul_fixed_short;
config.assign(
layouter.namespace(|| format!("short fixed-base mul of {:?}", base)),
magnitude_sign,
@ -505,10 +440,10 @@ impl EccInstructions<pallas::Affine> for EccChip {
fn mul_fixed_base_field_elem(
&self,
layouter: &mut impl Layouter<pallas::Base>,
base_field_elem: CellValue<pallas::Base>,
base_field_elem: AssignedCell<pallas::Base, pallas::Base>,
base: &Self::FixedPointsBaseField,
) -> Result<Self::Point, Error> {
let config: mul_fixed::base_field_elem::Config = self.config().into();
let config = self.config().mul_fixed_base_field;
config.assign(
layouter.namespace(|| format!("base-field elem fixed-base mul of {:?}", base)),
base_field_elem,

View File

@ -1,9 +1,8 @@
use std::array;
use super::{copy, CellValue, EccConfig, EccPoint, Var};
use ff::Field;
use super::EccPoint;
use ff::{BatchInvert, Field};
use halo2::{
arithmetic::BatchInvert,
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
@ -11,7 +10,7 @@ use halo2::{
use pasta_curves::{arithmetic::FieldExt, pallas};
use std::collections::HashSet;
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
q_add: Selector,
// lambda
@ -34,24 +33,43 @@ pub struct Config {
delta: Column<Advice>,
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self {
q_add: ecc_config.q_add,
x_p: ecc_config.advices[0],
y_p: ecc_config.advices[1],
x_qr: ecc_config.advices[2],
y_qr: ecc_config.advices[3],
lambda: ecc_config.advices[4],
alpha: ecc_config.advices[5],
beta: ecc_config.advices[6],
gamma: ecc_config.advices[7],
delta: ecc_config.advices[8],
}
}
}
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.into());
meta.enable_equality(y_p.into());
meta.enable_equality(x_qr.into());
meta.enable_equality(y_qr.into());
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,
@ -71,7 +89,7 @@ impl Config {
core::array::IntoIter::new([self.x_qr, self.y_qr]).collect()
}
pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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());
@ -103,8 +121,8 @@ impl Config {
// Useful constants
let one = Expression::Constant(pallas::Base::one());
let two = Expression::Constant(pallas::Base::from_u64(2));
let three = Expression::Constant(pallas::Base::from_u64(3));
let two = Expression::Constant(pallas::Base::from(2));
let three = Expression::Constant(pallas::Base::from(3));
// (x_q x_p)⋅((x_q x_p)⋅λ (y_qy_p)) = 0
let poly1 = {
@ -205,12 +223,12 @@ impl Config {
self.q_add.enable(region, offset)?;
// Copy point `p` into `x_p`, `y_p` columns
copy(region, || "x_p", self.x_p, offset, &p.x)?;
copy(region, || "y_p", self.y_p, offset, &p.y)?;
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
copy(region, || "x_q", self.x_qr, offset, &q.x)?;
copy(region, || "y_q", self.y_qr, offset, &q.y)?;
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());
@ -230,7 +248,7 @@ impl Config {
let gamma = x_q;
let delta = y_q + y_p;
let mut inverses = [alpha, beta, gamma, delta];
let mut inverses = [alpha, *beta, *gamma, delta];
inverses.batch_invert();
inverses
});
@ -243,28 +261,13 @@ impl Config {
};
// Assign α = inv0(x_q - x_p)
region.assign_advice(
|| "α",
self.alpha,
offset,
|| alpha.ok_or(Error::SynthesisError),
)?;
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::SynthesisError),
)?;
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::SynthesisError),
)?;
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(
@ -272,11 +275,11 @@ impl Config {
self.delta,
offset,
|| {
let x_p = x_p.ok_or(Error::SynthesisError)?;
let x_q = x_q.ok_or(Error::SynthesisError)?;
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::SynthesisError)
delta.ok_or(Error::Synthesis)
} else {
Ok(pallas::Base::zero())
}
@ -297,9 +300,9 @@ impl Config {
// know that x_q != x_p in this branch.
(y_q - y_p) * alpha
} else {
if y_p != pallas::Base::zero() {
if !y_p.is_zero_vartime() {
// 3(x_p)^2
let three_x_p_sq = pallas::Base::from_u64(3) * x_p.square();
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)
@ -313,7 +316,7 @@ impl Config {
|| "λ",
self.lambda,
offset,
|| lambda.ok_or(Error::SynthesisError),
|| lambda.ok_or(Error::Synthesis),
)?;
// Calculate (x_r, y_r)
@ -324,13 +327,13 @@ impl Config {
.zip(lambda)
.map(|((((x_p, y_p), x_q), y_q), lambda)| {
{
if x_p == pallas::Base::zero() {
if x_p.is_zero_vartime() {
// 0 + Q = Q
(x_q, y_q)
} else if x_q == pallas::Base::zero() {
(*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) {
(*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 {
@ -349,7 +352,7 @@ impl Config {
|| "x_r",
self.x_qr,
offset + 1,
|| x_r.ok_or(Error::SynthesisError),
|| x_r.ok_or(Error::Synthesis),
)?;
// Assign y_r
@ -358,12 +361,12 @@ impl Config {
|| "y_r",
self.y_qr,
offset + 1,
|| y_r.ok_or(Error::SynthesisError),
|| y_r.ok_or(Error::Synthesis),
)?;
let result = EccPoint {
x: CellValue::<pallas::Base>::new(x_r_cell, x_r),
y: CellValue::<pallas::Base>::new(y_r_cell, y_r),
x: x_r_cell,
y: y_r_cell,
};
#[cfg(test)]
@ -411,7 +414,9 @@ pub mod tests {
// Check complete addition P + (-P)
let zero = {
let result = p.add(layouter.namespace(|| "P + (-P)"), p_neg)?;
assert!(result.inner().is_identity().unwrap());
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
};

View File

@ -1,6 +1,7 @@
use std::{array, collections::HashSet};
use super::{copy, CellValue, EccConfig, NonIdentityEccPoint, Var};
use super::NonIdentityEccPoint;
use ff::Field;
use group::Curve;
use halo2::{
circuit::Region,
@ -9,7 +10,7 @@ use halo2::{
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
q_add_incomplete: Selector,
// x-coordinate of P in P + Q = R
@ -22,24 +23,37 @@ pub struct Config {
pub y_qr: Column<Advice>,
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self {
q_add_incomplete: ecc_config.q_add_incomplete,
x_p: ecc_config.advices[0],
y_p: ecc_config.advices[1],
x_qr: ecc_config.advices[2],
y_qr: ecc_config.advices[3],
}
}
}
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.into());
meta.enable_equality(y_p.into());
meta.enable_equality(x_qr.into());
meta.enable_equality(y_qr.into());
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()
}
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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());
@ -83,13 +97,13 @@ impl Config {
.zip(y_q)
.map(|(((x_p, y_p), x_q), y_q)| {
// P is point at infinity
if (x_p == pallas::Base::zero() && y_p == pallas::Base::zero())
if (x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_q == pallas::Base::zero() && y_q == pallas::Base::zero())
|| (x_q.is_zero_vartime() && y_q.is_zero_vartime())
// x_p = x_q
|| (x_p == x_q)
{
Err(Error::SynthesisError)
Err(Error::Synthesis)
} else {
Ok(())
}
@ -97,12 +111,12 @@ impl Config {
.transpose()?;
// Copy point `p` into `x_p`, `y_p` columns
copy(region, || "x_p", self.x_p, offset, &p.x)?;
copy(region, || "y_p", self.y_p, offset, &p.y)?;
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
copy(region, || "x_q", self.x_qr, offset, &q.x)?;
copy(region, || "y_q", self.y_qr, offset, &q.y)?;
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 = {
@ -123,7 +137,7 @@ impl Config {
|| "x_r",
self.x_qr,
offset + 1,
|| x_r.ok_or(Error::SynthesisError),
|| x_r.ok_or(Error::Synthesis),
)?;
let y_r = r.1;
@ -131,12 +145,12 @@ impl Config {
|| "y_r",
self.y_qr,
offset + 1,
|| y_r.ok_or(Error::SynthesisError),
|| y_r.ok_or(Error::Synthesis),
)?;
let result = NonIdentityEccPoint {
x: CellValue::<pallas::Base>::new(x_r_var, x_r),
y: CellValue::<pallas::Base>::new(y_r_var, y_r),
x: x_r_var,
y: y_r_var,
};
Ok(result)
@ -162,6 +176,7 @@ pub mod tests {
q_val: pallas::Affine,
q: &NonIdentityPoint<pallas::Affine, EccChip>,
p_neg: &NonIdentityPoint<pallas::Affine, EccChip>,
test_errors: bool,
) -> Result<(), Error> {
// P + Q
{
@ -174,13 +189,15 @@ pub mod tests {
result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?;
}
// P + P should return an error
p.add_incomplete(layouter.namespace(|| "P + P"), p)
.expect_err("P + P should return an error");
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");
// P + (-P) should return an error
p.add_incomplete(layouter.namespace(|| "P + (-P)"), p_neg)
.expect_err("P + (-P) should return an error");
}
Ok(())
}

View File

@ -1,13 +1,20 @@
use super::{add, CellValue, EccConfig, EccPoint, NonIdentityEccPoint, Var};
use crate::{circuit::gadget::utilities::copy, constants::T_Q};
use std::ops::{Deref, Range};
use super::{add, EccPoint, NonIdentityEccPoint};
use crate::{
circuit::gadget::utilities::{bool_check, lookup_range_check::LookupRangeCheckConfig, ternary},
constants::T_Q,
primitives::sinsemilla,
};
use std::{
convert::TryInto,
ops::{Deref, Range},
};
use bigint::U256;
use ff::PrimeField;
use halo2::{
arithmetic::FieldExt,
circuit::{Layouter, Region},
plonk::{ConstraintSystem, Error, Expression, Selector},
circuit::{AssignedCell, Layouter, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
@ -30,41 +37,61 @@ const INCOMPLETE_RANGE: Range<usize> = 0..INCOMPLETE_LEN;
// (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_LEN / 2)..INCOMPLETE_LEN;
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::HiConfig,
hi_config: incomplete::Config<INCOMPLETE_HI_LEN>,
// Configuration used for `lo` bits of the scalar
lo_config: incomplete::LoConfig,
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 From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
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: ecc_config.q_mul_lsb,
add_config: ecc_config.into(),
hi_config: ecc_config.into(),
lo_config: ecc_config.into(),
complete_config: ecc_config.into(),
overflow_config: ecc_config.into(),
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."
@ -78,23 +105,31 @@ impl From<&EccConfig> for 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();
for config in [&(*config.hi_config), &(*config.lo_config)].iter() {
{
assert!(
!add_config_outputs.contains(&config.z),
!add_config_outputs.contains(&config.hi_config.z),
"incomplete config z cannot overlap with complete addition columns."
);
assert!(
!add_config_outputs.contains(&config.lambda1),
!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
}
}
impl Config {
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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);
@ -108,15 +143,14 @@ impl Config {
// z_0 = 2 * z_1 + k_0
// => k_0 = z_0 - 2 * z_1
let lsb = z_0 - z_1 * pallas::Base::from_u64(2);
let one_minus_lsb = Expression::Constant(pallas::Base::one()) - lsb.clone();
let lsb = z_0 - z_1 * pallas::Base::from(2);
let bool_check = lsb.clone() * one_minus_lsb.clone();
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 = (lsb.clone() * x_p.clone()) + one_minus_lsb.clone() * (x_p - base_x);
let lsb_y = (lsb * y_p.clone()) + one_minus_lsb * (y_p + base_y);
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),
@ -125,26 +159,21 @@ impl Config {
])
.map(move |(name, poly)| (name, q_mul_lsb.clone() * poly))
});
self.hi_config.create_gate(meta);
self.lo_config.create_gate(meta);
self.complete_config.create_gate(meta);
self.overflow_config.create_gate(meta);
}
pub(super) fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
alpha: CellValue<pallas::Base>,
alpha: AssignedCell<pallas::Base, pallas::Base>,
base: &NonIdentityEccPoint,
) -> Result<(EccPoint, CellValue<pallas::Base>), Error> {
) -> 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).into();
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());
@ -163,16 +192,12 @@ impl Config {
let offset = offset + 1;
// Initialize the running sum for scalar decomposition to zero
let z_init = {
let z_init_cell = region.assign_advice_from_constant(
|| "z_init = 0",
self.hi_config.z,
offset,
pallas::Base::zero(),
)?;
Z(CellValue::new(z_init_cell, Some(pallas::Base::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(
@ -180,7 +205,7 @@ impl Config {
offset,
base,
bits_incomplete_hi,
(X(acc.x), Y(acc.y), z_init),
(X(acc.x), Y(acc.y), z_init.clone()),
)?;
// Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition
@ -190,7 +215,7 @@ impl Config {
offset,
base,
bits_incomplete_lo,
(x_a, y_a, *z),
(x_a, y_a, z.clone()),
)?;
// Move from incomplete addition to complete addition.
@ -214,7 +239,7 @@ impl Config {
&base_point,
x_a,
y_a,
*z,
z.clone(),
)?
};
@ -222,8 +247,8 @@ impl Config {
let offset = offset + COMPLETE_RANGE.len() * 2;
// Process the least significant bit
let z_1 = zs_complete.last().unwrap();
let (result, z_0) = self.process_lsb(&mut region, offset, base, acc, *z_1, lsb)?;
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.
@ -233,7 +258,7 @@ impl Config {
let base = base.point();
let alpha = alpha
.value()
.map(|alpha| pallas::Scalar::from_bytes(&alpha.to_bytes()).unwrap());
.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();
@ -261,8 +286,11 @@ impl Config {
},
)?;
self.overflow_config
.overflow_check(layouter.namespace(|| "overflow check"), alpha, &zs)?;
self.overflow_config.overflow_check(
layouter.namespace(|| "overflow check"),
alpha.clone(),
&zs,
)?;
Ok((result, alpha))
}
@ -298,39 +326,29 @@ impl Config {
// 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_u64(lsb as u64);
z_1 * pallas::Base::from_u64(2) + 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::SynthesisError),
|| z_0_val.ok_or(Error::Synthesis),
)?;
Z(CellValue::new(z_0_cell, z_0_val))
Z(z_0_cell)
};
// Copy in `base_x`, `base_y` to use in the LSB gate
copy(
region,
|| "copy base_x",
self.add_config.x_p,
offset + 1,
&base.x(),
)?;
copy(
region,
|| "copy base_y",
self.add_config.y_p,
offset + 1,
&base.y(),
)?;
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()
base.x.value().cloned()
} else {
Some(pallas::Base::zero())
}
@ -352,19 +370,19 @@ impl Config {
|| "x",
self.add_config.x_p,
offset,
|| x.ok_or(Error::SynthesisError),
|| x.ok_or(Error::Synthesis),
)?;
let y_cell = region.assign_advice(
|| "y",
self.add_config.y_p,
offset,
|| y.ok_or(Error::SynthesisError),
|| y.ok_or(Error::Synthesis),
)?;
let p = EccPoint {
x: CellValue::<pallas::Base>::new(x_cell, x),
y: CellValue::<pallas::Base>::new(y_cell, y),
x: x_cell,
y: y_cell,
};
// Return the result of the final complete addition as `[scalar]B`
@ -376,44 +394,44 @@ impl Config {
#[derive(Clone, Debug)]
// `x`-coordinate of the accumulator.
struct X<F: FieldExt>(CellValue<F>);
struct X<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> Deref for X<F> {
type Target = CellValue<F>;
type Target = AssignedCell<F, F>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
// `y`-coordinate of the accumulator.
struct Y<F: FieldExt>(CellValue<F>);
struct Y<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> Deref for Y<F> {
type Target = CellValue<F>;
type Target = AssignedCell<F, F>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
// Cumulative sum `z` used to decompose the scalar.
struct Z<F: FieldExt>(CellValue<F>);
struct Z<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> Deref for Z<F> {
type Target = CellValue<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>> {
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_bytes());
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;
@ -445,7 +463,7 @@ fn decompose_for_scalar_mul(scalar: Option<pallas::Base>) -> Vec<Option<bool>> {
#[cfg(test)]
pub mod tests {
use group::Curve;
use group::{ff::PrimeField, Curve};
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
@ -479,7 +497,7 @@ pub mod tests {
) -> Result<(), Error> {
// Move scalar from base field into scalar field (which always fits
// for Pallas).
let scalar = pallas::Scalar::from_bytes(&scalar_val.to_bytes()).unwrap();
let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap();
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
@ -517,7 +535,9 @@ pub mod tests {
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_val))?;
p.mul(layouter.namespace(|| "[0]B"), &scalar)?
};
assert!(result.inner().is_identity().unwrap());
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
// [-1]B (the largest possible base field element)

View File

@ -1,5 +1,6 @@
use super::super::{add, copy, CellValue, EccConfig, EccPoint, Var};
use super::super::{add, EccPoint};
use super::{COMPLETE_RANGE, X, Y, Z};
use crate::circuit::gadget::utilities::{bool_check, ternary};
use halo2::{
circuit::Region,
@ -7,8 +8,9 @@ use halo2::{
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
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,
@ -18,30 +20,30 @@ pub struct Config {
add_config: add::Config,
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
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.into());
let config = Self {
q_mul_decompose_var: ecc_config.q_mul_decompose_var,
z_complete: ecc_config.advices[9],
add_config: ecc_config.into(),
q_mul_decompose_var: meta.selector(),
z_complete,
add_config,
};
let add_config_advices = config.add_config.advice_columns();
assert!(
!add_config_advices.contains(&config.z_complete),
"z_complete cannot overlap with complete addition columns."
);
config.create_gate(meta);
config
}
}
impl 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.
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// | y_p | z_complete |
// --------------------
// | y_p | z_{i + 1} |
@ -57,10 +59,9 @@ impl Config {
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_u64(2)) * z_prev;
let k_minus_one = k.clone() - Expression::Constant(pallas::Base::one());
// (k_i) ⋅ (k_i - 1) = 0
let bool_check = k.clone() * k_minus_one.clone();
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());
@ -69,7 +70,7 @@ impl Config {
// k_i = 0 => y_p = -base_y
// k_i = 1 => y_p = base_y
let y_switch = k_minus_one * (base_y.clone() + y_p.clone()) + k * (base_y - y_p);
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))
@ -105,16 +106,15 @@ impl Config {
}
// Use x_a, y_a output from incomplete addition
let mut acc = EccPoint { x: *x_a, y: *y_a };
let mut acc = EccPoint { x: x_a.0, y: y_a.0 };
// Copy running sum `z` from incomplete addition
let mut z = {
let z = copy(
region,
let z = z.copy_advice(
|| "Copy `z` running sum from incomplete addition",
region,
self.z_complete,
offset,
&z,
)?;
Z(z)
};
@ -137,45 +137,48 @@ impl Config {
z = {
// z_next = z_cur * 2 + k_next
let z_val = z.value().zip(k.as_ref()).map(|(z_val, k)| {
pallas::Base::from_u64(2) * z_val + pallas::Base::from_u64(*k as u64)
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::SynthesisError),
|| z_val.ok_or(Error::Synthesis),
)?;
Z(CellValue::new(z_cell, z_val))
Z(z_cell)
};
zs.push(z);
zs.push(z.clone());
// Assign `y_p` for complete addition.
let y_p = {
let base_y = copy(
region,
let base_y = base.y.copy_advice(
|| "Copy `base.y`",
region,
self.z_complete,
row + offset + 1,
&base.y,
)?;
// If the bit is set, use `y`; if the bit is not set, use `-y`
let y_p = base_y
.value()
.zip(k.as_ref())
.map(|(base_y, k)| if !k { -base_y } else { base_y });
let y_p =
base_y
.value()
.cloned()
.zip(k.as_ref())
.map(|(base_y, k)| if !k { -base_y } else { base_y });
let y_p_cell = region.assign_advice(
region.assign_advice(
|| "y_p",
self.add_config.y_p,
row + offset,
|| y_p.ok_or(Error::SynthesisError),
)?;
CellValue::<pallas::Base>::new(y_p_cell, y_p)
|| 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, y: y_p };
let U = EccPoint {
x: base.x.clone(),
y: y_p,
};
// Acc + U
let tmp_acc = self

View File

@ -1,7 +1,6 @@
use std::ops::Deref;
use super::super::{copy, CellValue, EccConfig, NonIdentityEccPoint, Var};
use super::{INCOMPLETE_HI_RANGE, INCOMPLETE_LO_RANGE, X, Y, Z};
use super::super::NonIdentityEccPoint;
use super::{X, Y, Z};
use crate::circuit::gadget::utilities::bool_check;
use ff::Field;
use halo2::{
circuit::Region,
@ -11,10 +10,8 @@ use halo2::{
use pasta_curves::{arithmetic::FieldExt, pallas};
#[derive(Copy, Clone)]
pub(super) struct Config {
// Number of bits covered by this incomplete range.
num_bits: usize,
#[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.
@ -31,61 +28,36 @@ pub(super) struct Config {
pub(super) lambda2: Column<Advice>,
}
// Columns used in processing the `hi` bits of the scalar.
// `x_p, y_p` are shared across the `hi` and `lo` halves.
pub(super) struct HiConfig(Config);
impl From<&EccConfig> for HiConfig {
fn from(ecc_config: &EccConfig) -> Self {
let config = Config {
num_bits: INCOMPLETE_HI_RANGE.len(),
q_mul: ecc_config.q_mul_hi,
x_p: ecc_config.advices[0],
y_p: ecc_config.advices[1],
z: ecc_config.advices[9],
x_a: ecc_config.advices[3],
lambda1: ecc_config.advices[4],
lambda2: ecc_config.advices[5],
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.into());
meta.enable_equality(lambda1.into());
let config = Self {
q_mul: (meta.selector(), meta.selector(), meta.selector()),
z,
x_a,
x_p,
y_p,
lambda1,
lambda2,
};
Self(config)
}
}
impl Deref for HiConfig {
type Target = Config;
fn deref(&self) -> &Config {
&self.0
}
}
config.create_gate(meta);
// Columns used in processing the `lo` bits of the scalar.
// `x_p, y_p` are shared across the `hi` and `lo` halves.
pub(super) struct LoConfig(Config);
impl From<&EccConfig> for LoConfig {
fn from(ecc_config: &EccConfig) -> Self {
let config = Config {
num_bits: INCOMPLETE_LO_RANGE.len(),
q_mul: ecc_config.q_mul_lo,
x_p: ecc_config.advices[0],
y_p: ecc_config.advices[1],
z: ecc_config.advices[6],
x_a: ecc_config.advices[7],
lambda1: ecc_config.advices[8],
lambda2: ecc_config.advices[2],
};
Self(config)
config
}
}
impl Deref for LoConfig {
type Target = Config;
fn deref(&self) -> &Config {
&self.0
}
}
impl Config {
// Gate for incomplete addition part of variable-base scalar multiplication.
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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);
@ -131,13 +103,13 @@ impl Config {
// 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_u64(2);
let k = z_cur - z_prev * pallas::Base::from(2);
// Check booleanity of decomposition.
let bool_check = k.clone() * (one.clone() - k.clone());
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_u64(2) - one) * y_p_cur;
+ (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()
@ -215,21 +187,21 @@ impl Config {
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(), self.num_bits);
assert_eq!(bits.len(), NUM_BITS);
// Handle exceptional cases
let (x_p, y_p) = (base.x.value(), base.y.value());
let (x_a, y_a) = (acc.0.value(), acc.1.value());
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 == pallas::Base::zero() && y_p == pallas::Base::zero())
if (x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_a == pallas::Base::zero() && y_a == pallas::Base::zero())
|| (x_a.is_zero_vartime() && y_a.is_zero_vartime())
// x_p = x_a
|| (x_p == x_a)
{
return Err(Error::SynthesisError);
return Err(Error::Synthesis);
}
}
@ -240,24 +212,28 @@ impl Config {
let offset = offset + 1;
// q_mul_2 = 1 on all rows after offset 0, excluding the last row.
for idx in 0..(self.num_bits - 1) {
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 + self.num_bits - 1)?;
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 = copy(region, || "starting z", self.z, offset, &acc.2)?;
let z = acc.2.copy_advice(|| "starting z", region, self.z, offset)?;
// Initialise acc
let x_a = copy(region, || "starting x_a", self.x_a, offset + 1, &acc.0)?;
let y_a = copy(region, || "starting y_a", self.lambda1, offset, &acc.1)?;
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(), z)
(x_a, y_a.value().cloned(), z)
};
// Increase offset by 1; we used row 0 for initializing `z`.
@ -269,30 +245,30 @@ impl Config {
// 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_u64(2) * z_val + pallas::Base::from_u64(*k as u64)
});
let z_cell = region.assign_advice(
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::SynthesisError),
|| z_val.ok_or(Error::Synthesis),
)?;
z = CellValue::new(z_cell, z_val);
zs.push(Z(z));
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::SynthesisError),
|| x_p.ok_or(Error::Synthesis),
)?;
region.assign_advice(
|| "y_p",
self.y_p,
row + offset,
|| y_p.ok_or(Error::SynthesisError),
|| y_p.ok_or(Error::Synthesis),
)?;
// If the bit is set, use `y`; if the bit is not set, use `-y`
@ -310,7 +286,7 @@ impl Config {
|| "lambda1",
self.lambda1,
row + offset,
|| lambda1.ok_or(Error::SynthesisError),
|| lambda1.ok_or(Error::Synthesis),
)?;
// x_R = λ1^2 - x_A - x_P
@ -326,13 +302,13 @@ impl Config {
.zip(x_a.value())
.zip(x_r)
.map(|(((lambda1, y_a), x_a), x_r)| {
pallas::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1
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::SynthesisError),
|| lambda2.ok_or(Error::Synthesis),
)?;
// Compute and assign `x_a` for the next row
@ -341,30 +317,26 @@ impl Config {
.zip(x_r)
.map(|((lambda2, x_a), x_r)| lambda2.square() - x_a - x_r);
y_a = lambda2
.zip(x_a.value())
.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;
let x_a_cell = region.assign_advice(
x_a = region.assign_advice(
|| "x_a",
self.x_a,
row + offset + 1,
|| x_a_val.ok_or(Error::SynthesisError),
|| x_a_val.ok_or(Error::Synthesis),
)?;
x_a = CellValue::new(x_a_cell, x_a_val);
}
// Witness final y_a
let y_a = {
let cell = region.assign_advice(
|| "y_a",
self.lambda1,
offset + self.num_bits,
|| y_a.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, y_a)
};
let y_a = region.assign_advice(
|| "y_a",
self.lambda1,
offset + NUM_BITS,
|| y_a.ok_or(Error::Synthesis),
)?;
Ok((X(x_a), Y(y_a), zs))
}

View File

@ -1,9 +1,9 @@
use super::super::{copy, CellValue, EccConfig, Var};
use super::Z;
use crate::{
circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, constants::T_Q,
primitives::sinsemilla,
};
use halo2::circuit::AssignedCell;
use halo2::{
circuit::Layouter,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
@ -15,6 +15,7 @@ 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,
@ -24,24 +25,28 @@ pub struct Config {
advices: [Column<Advice>; 3],
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self {
q_mul_overflow: ecc_config.q_mul_overflow,
lookup_config: ecc_config.lookup_config.clone(),
// Use advice columns that don't conflict with the either the incomplete
// additions in fixed-base scalar mul, or the lookup range checks.
advices: [
ecc_config.advices[6],
ecc_config.advices[7],
ecc_config.advices[8],
],
}
}
}
impl Config {
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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).into());
}
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);
@ -94,7 +99,7 @@ impl Config {
pub(super) fn overflow_check(
&self,
mut layouter: impl Layouter<pallas::Base>,
alpha: CellValue<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
@ -102,7 +107,7 @@ impl Config {
// In the overflow check gate, we check that s is properly derived
// from alpha and k_254.
let s = {
let k_254 = *zs[254];
let k_254 = zs[254].clone();
let s_val = alpha
.value()
.zip(k_254.value())
@ -111,13 +116,12 @@ impl Config {
layouter.assign_region(
|| "s = alpha + k_254 ⋅ 2^130",
|mut region| {
let s_cell = region.assign_advice(
region.assign_advice(
|| "s = alpha + k_254 ⋅ 2^130",
self.advices[0],
0,
|| s_val.ok_or(Error::SynthesisError),
)?;
Ok(CellValue::new(s_cell, s_val))
|| s_val.ok_or(Error::Synthesis),
)
},
)?
};
@ -125,7 +129,7 @@ impl Config {
// 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)?;
self.s_minus_lo_130(layouter.namespace(|| "decompose s_{0..=129}"), s.clone())?;
layouter.assign_region(
|| "overflow check",
@ -136,21 +140,15 @@ impl Config {
self.q_mul_overflow.enable(&mut region, offset + 1)?;
// Copy `z_0`
copy(&mut region, || "copy z_0", self.advices[0], offset, &*zs[0])?;
zs[0].copy_advice(|| "copy z_0", &mut region, self.advices[0], offset)?;
// Copy `z_130`
copy(
&mut region,
|| "copy z_130",
self.advices[0],
offset + 1,
&*zs[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 == pallas::Base::zero() {
if z_130.is_zero_vartime() {
pallas::Base::zero()
} else {
z_130.invert().unwrap()
@ -160,39 +158,31 @@ impl Config {
|| "η = inv0(z_130)",
self.advices[0],
offset + 2,
|| eta.ok_or(Error::SynthesisError),
|| eta.ok_or(Error::Synthesis),
)?;
}
// Copy `k_254` = z_254
copy(
&mut region,
|| "copy k_254",
self.advices[1],
offset,
&*zs[254],
)?;
zs[254].copy_advice(|| "copy k_254", &mut region, self.advices[1], offset)?;
// Copy original alpha
copy(
&mut region,
alpha.copy_advice(
|| "copy original alpha",
&mut region,
self.advices[1],
offset + 1,
&alpha,
)?;
// Copy weighted sum of the decomposition of s = alpha + k_254 ⋅ 2^130.
copy(
&mut region,
s_minus_lo_130.copy_advice(
|| "copy s_minus_lo_130",
&mut region,
self.advices[1],
offset + 2,
&s_minus_lo_130,
)?;
// Copy witnessed s to check that it was properly derived from alpha and k_254.
copy(&mut region, || "copy s", self.advices[2], offset + 1, &s)?;
s.copy_advice(|| "copy s", &mut region, self.advices[2], offset + 1)?;
Ok(())
},
@ -204,8 +194,8 @@ impl Config {
fn s_minus_lo_130(
&self,
mut layouter: impl Layouter<pallas::Base>,
s: CellValue<pallas::Base>,
) -> Result<CellValue<pallas::Base>, Error> {
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);
@ -218,6 +208,6 @@ impl Config {
false,
)?;
// (s - (2^0 s_0 + 2^1 s_1 + ... + 2^129 s_129)) / 2^130
Ok(zs[zs.len() - 1])
Ok(zs[zs.len() - 1].clone())
}
}

View File

@ -1,15 +1,16 @@
use super::{
add, add_incomplete, CellValue, EccBaseFieldElemFixed, EccConfig, EccScalarFixed,
EccScalarFixedShort, NonIdentityEccPoint, Var,
add, add_incomplete, EccBaseFieldElemFixed, EccScalarFixed, EccScalarFixedShort,
NonIdentityEccPoint,
};
use crate::circuit::gadget::utilities::decompose_running_sum::RunningSumConfig;
use crate::constants::{
self,
load::{NullifierK, OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV, WindowUs},
};
use group::Curve;
use group::{ff::PrimeField, Curve};
use halo2::{
circuit::Region,
circuit::{AssignedCell, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, VirtualCells},
poly::Rotation,
};
@ -24,10 +25,10 @@ pub mod full_width;
pub mod short;
lazy_static! {
static ref TWO_SCALAR: pallas::Scalar = pallas::Scalar::from_u64(2);
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_u64(constants::H as u64);
static ref H_BASE: pallas::Base = pallas::Base::from_u64(constants::H as u64);
static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from(constants::H as u64);
static ref H_BASE: pallas::Base = pallas::Base::from(constants::H as u64);
}
// A sum type for both full-width and short bases. This enables us to use the
@ -75,9 +76,9 @@ impl OrchardFixedBases {
}
}
#[derive(Clone, Debug)]
pub struct Config<const NUM_WINDOWS: usize> {
q_mul_fixed_running_sum: Selector,
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
running_sum_config: RunningSumConfig<pallas::Base, { constants::FIXED_BASE_WINDOW_SIZE }>,
// The fixed Lagrange interpolation coefficients for `x_p`.
lagrange_coeffs: [Column<Fixed>; constants::H],
// The fixed `z` for each window such that `y + z = u^2`.
@ -97,18 +98,34 @@ pub struct Config<const NUM_WINDOWS: usize> {
add_incomplete_config: add_incomplete::Config,
}
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
fn from(ecc_config: &EccConfig) -> Self {
impl Config {
#[allow(clippy::too_many_arguments)]
pub(super) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
lagrange_coeffs: [Column<Fixed>; constants::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.into());
meta.enable_equality(u.into());
let q_running_sum = meta.selector();
let running_sum_config = RunningSumConfig::configure(meta, q_running_sum, window);
let config = Self {
q_mul_fixed_running_sum: ecc_config.q_mul_fixed_running_sum,
lagrange_coeffs: ecc_config.lagrange_coeffs,
fixed_z: ecc_config.fixed_z,
x_p: ecc_config.advices[0],
y_p: ecc_config.advices[1],
window: ecc_config.advices[4],
u: ecc_config.advices[5],
add_config: ecc_config.into(),
add_incomplete_config: ecc_config.into(),
running_sum_config,
lagrange_coeffs,
fixed_z: meta.fixed_column(),
window,
x_p,
y_p,
u,
add_config,
add_incomplete_config,
};
// Check relationships between this config and `add_config`.
@ -141,11 +158,11 @@ impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
);
}
config.running_sum_coords_gate(meta);
config
}
}
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
/// Check that each window in the running sum decomposition uses the correct y_p
/// and interpolated x_p.
///
@ -155,16 +172,17 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
/// 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.
pub(crate) fn running_sum_coords_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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.q_mul_fixed_running_sum);
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_u64(constants::H as u64);
let word = z_cur - z_next * pallas::Base::from(constants::H as u64);
self.coords_check(meta, q_mul_fixed_running_sum, word)
});
@ -213,7 +231,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
}
#[allow(clippy::type_complexity)]
fn assign_region_inner(
fn assign_region_inner<const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
@ -222,7 +240,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
coords_check_toggle: Selector,
) -> Result<(NonIdentityEccPoint, NonIdentityEccPoint), Error> {
// Assign fixed columns for given fixed base
self.assign_fixed_constants(region, offset, base, coords_check_toggle)?;
self.assign_fixed_constants::<NUM_WINDOWS>(region, offset, base, coords_check_toggle)?;
// Initialize accumulator
let acc = self.initialize_accumulator(region, offset, base, scalar)?;
@ -231,12 +249,12 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
let acc = self.add_incomplete(region, offset, acc, base, scalar)?;
// Process most significant window using complete addition
let mul_b = self.process_msb(region, offset, base, scalar)?;
let mul_b = self.process_msb::<NUM_WINDOWS>(region, offset, base, scalar)?;
Ok((acc, mul_b))
}
fn assign_fixed_constants(
fn assign_fixed_constants<const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
@ -335,38 +353,31 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
assert!(x != pallas::Base::zero());
x
});
let x_cell = region.assign_advice(
let x = region.assign_advice(
|| format!("mul_b_x, window {}", w),
self.x_p,
offset + w,
|| x.ok_or(Error::SynthesisError),
|| x.ok_or(Error::Synthesis),
)?;
let x = CellValue::new(x_cell, x);
let y = mul_b.map(|mul_b| {
let y = *mul_b.y();
assert!(y != pallas::Base::zero());
y
});
let y_cell = region.assign_advice(
let y = region.assign_advice(
|| format!("mul_b_y, window {}", w),
self.y_p,
offset + w,
|| y.ok_or(Error::SynthesisError),
|| y.ok_or(Error::Synthesis),
)?;
let y = CellValue::new(y_cell, y);
NonIdentityEccPoint { x, y }
};
// Assign u = (y_p + z_w).sqrt()
let u_val = k_usize.map(|k| base_u[w].0[k]);
region.assign_advice(
|| "u",
self.u,
offset + w,
|| u_val.ok_or(Error::SynthesisError),
)?;
region.assign_advice(|| "u", self.u, offset + w, || u_val.ok_or(Error::Synthesis))?;
Ok(mul_b)
}
@ -416,7 +427,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
Ok(acc)
}
fn process_msb(
fn process_msb<const NUM_WINDOWS: usize>(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
@ -431,7 +442,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
|| "u",
self.u,
offset + NUM_WINDOWS - 1,
|| u_val.ok_or(Error::SynthesisError),
|| u_val.ok_or(Error::Synthesis),
)?;
}
@ -458,27 +469,24 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
assert!(x != pallas::Base::zero());
x
});
let x_cell = region.assign_advice(
let x = region.assign_advice(
|| format!("mul_b_x, window {}", NUM_WINDOWS - 1),
self.x_p,
offset + NUM_WINDOWS - 1,
|| x.ok_or(Error::SynthesisError),
|| x.ok_or(Error::Synthesis),
)?;
let x = CellValue::new(x_cell, x);
let y = mul_b.map(|mul_b| {
let y = *mul_b.y();
assert!(y != pallas::Base::zero());
y
});
let y_cell = region.assign_advice(
let y = region.assign_advice(
|| format!("mul_b_y, window {}", NUM_WINDOWS - 1),
self.y_p,
offset + NUM_WINDOWS - 1,
|| y.ok_or(Error::SynthesisError),
|| y.ok_or(Error::Synthesis),
)?;
let y = CellValue::new(y_cell, y);
NonIdentityEccPoint { x, y }
};
@ -515,7 +523,7 @@ 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<CellValue<pallas::Base>>| {
let running_sum_to_windows = |zs: Vec<AssignedCell<pallas::Base, pallas::Base>>| {
(0..(zs.len() - 1))
.map(|idx| {
let z_cur = zs[idx].value();
@ -523,7 +531,7 @@ impl ScalarFixed {
let word = z_cur
.zip(z_next)
.map(|(z_cur, z_next)| z_cur - z_next * *H_BASE);
word.map(|word| pallas::Scalar::from_bytes(&word.to_bytes()).unwrap())
word.map(|word| pallas::Scalar::from_repr(word.to_repr()).unwrap())
})
.collect::<Vec<_>>()
};
@ -535,7 +543,7 @@ impl ScalarFixed {
.iter()
.map(|bits| {
bits.value()
.map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap())
.map(|value| pallas::Scalar::from_repr(value.to_repr()).unwrap())
})
.collect::<Vec<_>>(),
}

View File

@ -1,14 +1,14 @@
use super::super::{EccBaseFieldElemFixed, EccConfig, EccPoint, NullifierK};
use super::super::{EccBaseFieldElemFixed, EccPoint, NullifierK};
use super::H_BASE;
use crate::{
circuit::gadget::utilities::{
bitrange_subset, copy, decompose_running_sum::RunningSumConfig,
lookup_range_check::LookupRangeCheckConfig, range_check, CellValue, Var,
bitrange_subset, lookup_range_check::LookupRangeCheckConfig, range_check,
},
constants::{self, T_P},
primitives::sinsemilla,
};
use halo2::circuit::AssignedCell;
use halo2::{
circuit::Layouter,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
@ -18,24 +18,30 @@ use pasta_curves::{arithmetic::FieldExt, pallas};
use std::convert::TryInto;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Config {
q_mul_fixed_running_sum: Selector,
q_mul_fixed_base_field: Selector,
canon_advices: [Column<Advice>; 3],
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
running_sum_config: RunningSumConfig<pallas::Base, { constants::FIXED_BASE_WINDOW_SIZE }>,
super_config: super::Config<{ constants::NUM_WINDOWS }>,
super_config: super::Config,
}
impl From<&EccConfig> for Config {
fn from(config: &EccConfig) -> Self {
impl Config {
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,
) -> Self {
for advice in canon_advices.iter() {
meta.enable_equality((*advice).into());
}
let config = Self {
q_mul_fixed_running_sum: config.q_mul_fixed_running_sum,
q_mul_fixed_base_field: config.q_mul_fixed_base_field,
canon_advices: [config.advices[6], config.advices[7], config.advices[8]],
lookup_config: config.lookup_config.clone(),
running_sum_config: config.running_sum_config.clone(),
super_config: config.into(),
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();
@ -46,14 +52,12 @@ impl From<&EccConfig> for Config {
);
}
assert_eq!(config.running_sum_config.z, config.super_config.window);
config.create_gate(meta);
config
}
}
impl Config {
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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);
@ -85,7 +89,7 @@ impl Config {
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_u64(1 << 2));
- (alpha_1.clone() + alpha_2.clone() * pallas::Base::from(1 << 2));
std::iter::empty()
.chain(Some(("alpha_1_range_check", alpha_1_range_check)))
@ -156,7 +160,7 @@ impl Config {
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
scalar: CellValue<pallas::Base>,
scalar: AssignedCell<pallas::Base, pallas::Base>,
base: NullifierK,
) -> Result<EccPoint, Error> {
let (scalar, acc, mul_b) = layouter.assign_region(
@ -166,27 +170,29 @@ impl Config {
// Decompose scalar
let scalar = {
let running_sum = self.running_sum_config.copy_decompose(
let running_sum = self.super_config.running_sum_config.copy_decompose(
&mut region,
offset,
scalar,
scalar.clone(),
true,
constants::L_ORCHARD_BASE,
constants::NUM_WINDOWS,
)?;
EccBaseFieldElemFixed {
base_field_elem: running_sum[0],
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(
&mut region,
offset,
&(&scalar).into(),
base.into(),
self.q_mul_fixed_running_sum,
)?;
let (acc, mul_b) = self
.super_config
.assign_region_inner::<{ constants::NUM_WINDOWS }>(
&mut region,
offset,
&(&scalar).into(),
base.into(),
self.super_config.running_sum_config.q_range_check,
)?;
Ok((scalar, acc, mul_b))
},
@ -197,8 +203,8 @@ impl Config {
|| "Base-field elem fixed-base mul (complete addition)",
|mut region| {
self.super_config.add_config.assign_region(
&mul_b.into(),
&acc.into(),
&mul_b.clone().into(),
&acc.clone().into(),
0,
&mut region,
)
@ -208,13 +214,13 @@ impl Config {
#[cfg(test)]
// Check that the correct multiple is obtained.
{
use group::Curve;
use group::{ff::PrimeField, Curve};
let base: super::OrchardFixedBases = base.into();
let scalar = &scalar
.base_field_elem()
.value()
.map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap());
.map(|scalar| pallas::Scalar::from_repr(scalar.to_repr()).unwrap());
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let result = result.point();
@ -243,9 +249,9 @@ impl Config {
// => z_13_alpha_0_prime = 0
//
let (alpha, running_sum) = (scalar.base_field_elem, &scalar.running_sum);
let z_43_alpha = running_sum[43];
let z_44_alpha = running_sum[44];
let z_84_alpha = running_sum[84];
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
@ -269,9 +275,9 @@ impl Config {
13,
false,
)?;
let alpha_0_prime = zs[0];
let alpha_0_prime = zs[0].clone();
(alpha_0_prime, zs[13])
(alpha_0_prime, zs[13].clone())
};
layouter.assign_region(
@ -285,21 +291,14 @@ impl Config {
let offset = 0;
// Copy α
copy(
&mut region,
|| "Copy α",
self.canon_advices[0],
offset,
&alpha,
)?;
alpha.copy_advice(|| "Copy α", &mut region, self.canon_advices[0], offset)?;
// z_84_alpha = the top three bits of alpha.
copy(
&mut region,
z_84_alpha.copy_advice(
|| "Copy z_84_alpha",
&mut region,
self.canon_advices[2],
offset,
&z_84_alpha,
)?;
}
@ -308,12 +307,11 @@ impl Config {
let offset = 1;
// Copy alpha_0_prime = alpha_0 + 2^130 - t_p.
// We constrain this in the custom gate to be derived correctly.
copy(
&mut region,
alpha_0_prime.copy_advice(
|| "Copy α_0 + 2^130 - t_p",
&mut region,
self.canon_advices[0],
offset,
&alpha_0_prime,
)?;
// Decompose α into three pieces,
@ -325,7 +323,7 @@ impl Config {
|| "α_1 = α[252..=253]",
self.canon_advices[1],
offset,
|| alpha_1.ok_or(Error::SynthesisError),
|| alpha_1.ok_or(Error::Synthesis),
)?;
// Witness the MSB α_2 = α[254]
@ -334,7 +332,7 @@ impl Config {
|| "α_2 = α[254]",
self.canon_advices[2],
offset,
|| alpha_2.ok_or(Error::SynthesisError),
|| alpha_2.ok_or(Error::Synthesis),
)?;
}
@ -342,30 +340,27 @@ impl Config {
{
let offset = 2;
// Copy z_13_alpha_0_prime
copy(
&mut region,
z_13_alpha_0_prime.copy_advice(
|| "Copy z_13_alpha_0_prime",
&mut region,
self.canon_advices[0],
offset,
&z_13_alpha_0_prime,
)?;
// Copy z_44_alpha
copy(
&mut region,
z_44_alpha.copy_advice(
|| "Copy z_44_alpha",
&mut region,
self.canon_advices[1],
offset,
&z_44_alpha,
)?;
// Copy z_43_alpha
copy(
&mut region,
z_43_alpha.copy_advice(
|| "Copy z_43_alpha",
&mut region,
self.canon_advices[2],
offset,
&z_43_alpha,
)?;
}
@ -379,7 +374,7 @@ impl Config {
#[cfg(test)]
pub mod tests {
use group::Curve;
use group::{ff::PrimeField, Curve};
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
@ -426,7 +421,7 @@ pub mod tests {
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_bytes(&scalar_val.to_bytes()).unwrap();
let scalar = pallas::Scalar::from_repr(scalar_val.to_repr()).unwrap();
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
@ -460,11 +455,11 @@ pub mod tests {
// (There is another *non-canonical* sequence
// 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.)
{
let h = pallas::Base::from_u64(constants::H as u64);
let h = pallas::Base::from(constants::H as u64);
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
.chars()
.fold(pallas::Base::zero(), |acc, c| {
acc * &h + &pallas::Base::from_u64(c.to_digit(8).unwrap().into())
acc * &h + &pallas::Base::from(c.to_digit(8).unwrap() as u64)
});
let result = {
let scalar_fixed = chip.load_private(
@ -492,7 +487,9 @@ pub mod tests {
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_fixed))?;
base.mul(layouter.namespace(|| "mul by zero"), scalar_fixed)?
};
assert!(result.inner().is_identity().unwrap());
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
// [-1]B is the largest base field element

View File

@ -1,33 +1,39 @@
use super::super::{EccConfig, EccPoint, EccScalarFixed, OrchardFixedBasesFull};
use super::super::{EccPoint, EccScalarFixed, OrchardFixedBasesFull};
use crate::{
circuit::gadget::utilities::{range_check, CellValue, Var},
circuit::gadget::utilities::range_check,
constants::{self, util, L_ORCHARD_SCALAR, NUM_WINDOWS},
};
use arrayvec::ArrayVec;
use halo2::{
circuit::{Layouter, Region},
circuit::{AssignedCell, Layouter, Region},
plonk::{ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use pasta_curves::pallas;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
q_mul_fixed_full: Selector,
super_config: super::Config<NUM_WINDOWS>,
}
impl From<&EccConfig> for Config {
fn from(config: &EccConfig) -> Self {
Self {
q_mul_fixed_full: config.q_mul_fixed_full,
super_config: config.into(),
}
}
super_config: super::Config,
}
impl Config {
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
pub(crate) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
super_config: super::Config,
) -> 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);
@ -70,7 +76,7 @@ impl Config {
scalar: Option<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS>, Error> {
) -> 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)?;
@ -79,20 +85,21 @@ impl Config {
// Decompose scalar into `k-bit` windows
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
util::decompose_word::<pallas::Scalar>(
scalar,
&scalar,
SCALAR_NUM_BITS,
constants::FIXED_BASE_WINDOW_SIZE,
)
});
// Store the scalar decomposition
let mut windows: ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS> = ArrayVec::new();
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_u64(window as u64)))
.map(|window| Some(pallas::Base::from(window as u64)))
.collect()
} else {
vec![None; NUM_WINDOWS]
@ -103,9 +110,9 @@ impl Config {
|| format!("k[{:?}]", offset + idx),
self.super_config.window,
offset + idx,
|| window.ok_or(Error::SynthesisError),
|| window.ok_or(Error::Synthesis),
)?;
windows.push(CellValue::new(window_cell, window));
windows.push(window_cell);
}
Ok(windows)
@ -124,13 +131,15 @@ impl Config {
let scalar = self.witness(&mut region, offset, scalar)?;
let (acc, mul_b) = self.super_config.assign_region_inner(
&mut region,
offset,
&(&scalar).into(),
base.into(),
self.q_mul_fixed_full,
)?;
let (acc, mul_b) = self
.super_config
.assign_region_inner::<{ constants::NUM_WINDOWS }>(
&mut region,
offset,
&(&scalar).into(),
base.into(),
self.q_mul_fixed_full,
)?;
Ok((scalar, acc, mul_b))
},
@ -141,8 +150,8 @@ impl Config {
|| "Full-width fixed-base mul (last window, complete addition)",
|mut region| {
self.super_config.add_config.assign_region(
&mul_b.into(),
&acc.into(),
&mul_b.clone().into(),
&acc.clone().into(),
0,
&mut region,
)
@ -263,11 +272,11 @@ pub mod tests {
// (There is another *non-canonical* sequence
// 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.)
{
let h = pallas::Scalar::from_u64(constants::H as u64);
let h = pallas::Scalar::from(constants::H as u64);
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
.chars()
.fold(pallas::Scalar::zero(), |acc, c| {
acc * &h + &pallas::Scalar::from_u64(c.to_digit(8).unwrap().into())
acc * &h + &pallas::Scalar::from(c.to_digit(8).unwrap() as u64)
});
let (result, _) =
base.mul(layouter.namespace(|| "mul with double"), Some(scalar_fixed))?;
@ -286,7 +295,9 @@ pub mod tests {
{
let scalar_fixed = pallas::Scalar::zero();
let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), Some(scalar_fixed))?;
assert!(result.inner().is_identity().unwrap());
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
// [-1]B is the largest scalar field element.

View File

@ -1,9 +1,9 @@
use std::{array, convert::TryInto};
use super::super::{EccConfig, EccPoint, EccScalarFixedShort};
use super::super::{EccPoint, EccScalarFixedShort};
use crate::{
circuit::gadget::utilities::{copy, decompose_running_sum::RunningSumConfig, CellValue, Var},
constants::{ValueCommitV, FIXED_BASE_WINDOW_SIZE, L_VALUE, NUM_WINDOWS_SHORT},
circuit::gadget::{ecc::chip::MagnitudeSign, utilities::bool_check},
constants::{ValueCommitV, L_VALUE, NUM_WINDOWS_SHORT},
};
use halo2::{
@ -13,28 +13,29 @@ use halo2::{
};
use pasta_curves::pallas;
#[derive(Clone)]
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
pub struct Config {
// Selector used for fixed-base scalar mul with short signed exponent.
q_mul_fixed_short: Selector,
q_mul_fixed_running_sum: Selector,
running_sum_config: RunningSumConfig<pallas::Base, { FIXED_BASE_WINDOW_SIZE }>,
super_config: super::Config<NUM_WINDOWS_SHORT>,
}
impl From<&EccConfig> for Config {
fn from(config: &EccConfig) -> Self {
Self {
q_mul_fixed_short: config.q_mul_fixed_short,
q_mul_fixed_running_sum: config.q_mul_fixed_running_sum,
running_sum_config: config.running_sum_config.clone(),
super_config: config.into(),
}
}
super_config: super::Config,
}
impl Config {
pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
pub(crate) fn configure(
meta: &mut ConstraintSystem<pallas::Base>,
super_config: super::Config,
) -> 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());
@ -46,7 +47,7 @@ impl Config {
let one = Expression::Constant(pallas::Base::one());
// Check that last window is either 0 or 1.
let last_window_check = last_window.clone() * (one.clone() - last_window);
let last_window_check = bool_check(last_window);
// Check that sign is either 1 or -1.
let sign_check = sign.clone() * sign.clone() - one;
@ -73,15 +74,15 @@ impl Config {
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
magnitude_sign: (CellValue<pallas::Base>, CellValue<pallas::Base>),
magnitude_sign: MagnitudeSign,
) -> Result<EccScalarFixedShort, Error> {
let (magnitude, sign) = magnitude_sign;
// Decompose magnitude
let running_sum = self.running_sum_config.copy_decompose(
let running_sum = self.super_config.running_sum_config.copy_decompose(
region,
offset,
magnitude,
magnitude.clone(),
true,
L_VALUE,
NUM_WINDOWS_SHORT,
@ -97,7 +98,7 @@ impl Config {
pub fn assign(
&self,
mut layouter: impl Layouter<pallas::Base>,
magnitude_sign: (CellValue<pallas::Base>, CellValue<pallas::Base>),
magnitude_sign: MagnitudeSign,
base: &ValueCommitV,
) -> Result<(EccPoint, EccScalarFixedShort), Error> {
let (scalar, acc, mul_b) = layouter.assign_region(
@ -106,14 +107,14 @@ impl Config {
let offset = 0;
// Decompose the scalar
let scalar = self.decompose(&mut region, offset, magnitude_sign)?;
let scalar = self.decompose(&mut region, offset, magnitude_sign.clone())?;
let (acc, mul_b) = self.super_config.assign_region_inner(
let (acc, mul_b) = self.super_config.assign_region_inner::<NUM_WINDOWS_SHORT>(
&mut region,
offset,
&(&scalar).into(),
base.clone().into(),
self.q_mul_fixed_running_sum,
self.super_config.running_sum_config.q_range_check,
)?;
Ok((scalar, acc, mul_b))
@ -127,8 +128,8 @@ impl Config {
let offset = 0;
// Add to the cumulative sum to get `[magnitude]B`.
let magnitude_mul = self.super_config.add_config.assign_region(
&mul_b.into(),
&acc.into(),
&mul_b.clone().into(),
&acc.clone().into(),
offset,
&mut region,
)?;
@ -137,32 +138,25 @@ impl Config {
let offset = offset + 1;
// Copy sign to `window` column
let sign = copy(
&mut region,
let sign = scalar.sign.copy_advice(
|| "sign",
&mut region,
self.super_config.window,
offset,
&scalar.sign,
)?;
// 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];
copy(
&mut region,
|| "last_window",
self.super_config.u,
offset,
&z_21,
)?;
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().map(|y: pallas::Base| -y)
if sign == &-pallas::Base::one() {
magnitude_mul.y.value().cloned().map(|y: pallas::Base| -y)
} else {
magnitude_mul.y.value()
magnitude_mul.y.value().cloned()
}
} else {
None
@ -176,12 +170,12 @@ impl Config {
|| "y_var",
self.super_config.y_p,
offset,
|| y_val.ok_or(Error::SynthesisError),
|| y_val.ok_or(Error::Synthesis),
)?;
Ok(EccPoint {
x: magnitude_mul.x,
y: CellValue::new(y_var, y_val),
y: y_var,
})
},
)?;
@ -193,12 +187,10 @@ impl Config {
// Invalid values result in constraint failures which are
// tested at the circuit-level.
{
use group::Curve;
use pasta_curves::arithmetic::FieldExt;
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_u64(0xFFFF_FFFF_FFFF_FFFFu64);
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 base: super::OrchardFixedBases = base.clone().into();
@ -207,10 +199,9 @@ impl Config {
|(magnitude, sign)| {
// Move magnitude from base field into scalar field (which always fits
// for Pallas).
let magnitude =
pallas::Scalar::from_bytes(&magnitude.to_bytes()).unwrap();
let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap();
let sign = if sign == pallas::Base::one() {
let sign = if sign == &pallas::Base::one() {
pallas::Scalar::one()
} else {
-pallas::Scalar::one()
@ -236,16 +227,20 @@ impl Config {
#[cfg(test)]
pub mod tests {
use group::Curve;
use group::{ff::PrimeField, Curve};
use halo2::{
circuit::{Chip, Layouter},
arithmetic::CurveAffine,
circuit::{AssignedCell, Chip, Layouter},
plonk::{Any, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::circuit::gadget::{
ecc::{chip::EccChip, FixedPointShort, NonIdentityPoint, Point},
utilities::{lookup_range_check::LookupRangeCheckConfig, CellValue, UtilitiesInstructions},
ecc::{
chip::{EccChip, MagnitudeSign},
FixedPointShort, NonIdentityPoint, Point,
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
};
use crate::constants::load::ValueCommitV;
@ -264,7 +259,7 @@ pub mod tests {
mut layouter: impl Layouter<pallas::Base>,
magnitude: pallas::Base,
sign: pallas::Base,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
) -> Result<MagnitudeSign, Error> {
let column = chip.config().advices[0];
let magnitude =
chip.load_private(layouter.namespace(|| "magnitude"), column, Some(magnitude))?;
@ -289,25 +284,21 @@ pub mod tests {
}
let magnitude_signs = [
(
"random [a]B",
pallas::Base::from_u64(rand::random::<u64>()),
{
let mut random_sign = pallas::Base::one();
if rand::random::<bool>() {
random_sign = -random_sign;
}
random_sign
},
),
("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_u64(0xFFFF_FFFF_FFFF_FFFFu64),
pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64),
pallas::Base::one(),
),
(
"-[2^64 - 1]B",
pallas::Base::from_u64(0xFFFF_FFFF_FFFF_FFFFu64),
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:
@ -315,12 +306,12 @@ pub mod tests {
// [0xB6DB_6DB6_DB6D_B6DC] B
(
"mul_with_double",
pallas::Base::from_u64(0xB6DB_6DB6_DB6D_B6DCu64),
pallas::Base::from(0xB6DB_6DB6_DB6D_B6DCu64),
pallas::Base::one(),
),
(
"mul_with_double negative",
pallas::Base::from_u64(0xB6DB_6DB6_DB6D_B6DCu64),
pallas::Base::from(0xB6DB_6DB6_DB6D_B6DCu64),
-pallas::Base::one(),
),
];
@ -337,7 +328,7 @@ pub mod tests {
};
// Move from base field into scalar field
let scalar = {
let magnitude = pallas::Scalar::from_bytes(&magnitude.to_bytes()).unwrap();
let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap();
let sign = if *sign == pallas::Base::one() {
pallas::Scalar::one()
} else {
@ -369,7 +360,9 @@ pub mod tests {
)?;
value_commit_v.mul(layouter.namespace(|| *name), magnitude_sign)?
};
assert!(result.inner().is_identity().unwrap());
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
}
Ok(())
@ -377,10 +370,7 @@ pub mod tests {
#[test]
fn invalid_magnitude_sign() {
use crate::circuit::gadget::{
ecc::chip::EccConfig,
utilities::{CellValue, UtilitiesInstructions},
};
use crate::circuit::gadget::{ecc::chip::EccConfig, utilities::UtilitiesInstructions};
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::{MockProver, VerifyFailure},
@ -391,10 +381,12 @@ pub mod tests {
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 = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl Circuit<pallas::Base> for MyCircuit {
@ -445,7 +437,7 @@ pub mod tests {
) -> Result<(), Error> {
let column = config.advices[0];
let short_config: super::Config = (&config).into();
let short_config = config.mul_fixed_short;
let magnitude_sign = {
let magnitude = self.load_private(
layouter.namespace(|| "load magnitude"),
@ -463,6 +455,25 @@ pub mod tests {
}
}
// 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 = [
@ -470,31 +481,41 @@ pub mod tests {
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),
),
},
];
@ -510,7 +531,11 @@ pub mod tests {
"last_window_check"
)
.into(),
row: 26
row: 26,
cell_values: vec![(
((Any::Advice, 5).into(), 0).into(),
format_value(circuit.magnitude_error.unwrap()),
)],
},
VerifyFailure::Permutation {
column: (Any::Fixed, 9).into(),
@ -527,9 +552,19 @@ pub mod tests {
// Sign that is not +/- 1 should fail
{
let magnitude_u64 = rand::random::<u64>();
let circuit = MyCircuit {
magnitude: Some(pallas::Base::from_u64(rand::random::<u64>())),
magnitude: Some(pallas::Base::from(magnitude_u64)),
sign: Some(pallas::Base::zero()),
magnitude_error: None,
};
let negation_check_y = {
*(ValueCommitV::get().generator * pallas::Scalar::from(magnitude_u64))
.to_affine()
.coordinates()
.unwrap()
.y()
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@ -539,7 +574,8 @@ pub mod tests {
VerifyFailure::ConstraintNotSatisfied {
constraint: ((17, "Short fixed-base mul gate").into(), 1, "sign_check")
.into(),
row: 26
row: 26,
cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())],
},
VerifyFailure::ConstraintNotSatisfied {
constraint: (
@ -548,7 +584,18 @@ pub mod tests {
"negation_check"
)
.into(),
row: 26
row: 26,
cell_values: vec![
(
((Any::Advice, 1).into(), 0).into(),
format_value(negation_check_y),
),
(
((Any::Advice, 3).into(), 0).into(),
format_value(negation_check_y),
),
(((Any::Advice, 4).into(), 0).into(), "0".to_string()),
],
}
])
);

View File

@ -1,15 +1,20 @@
use super::{CellValue, EccConfig, EccPoint, NonIdentityEccPoint, Var};
use super::{EccPoint, NonIdentityEccPoint};
use group::prime::PrimeCurveAffine;
use halo2::{
circuit::Region,
circuit::{AssignedCell, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector, VirtualCells},
poly::Rotation,
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
#[derive(Clone, Debug)]
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,
@ -19,19 +24,25 @@ pub struct Config {
pub y: Column<Advice>,
}
impl From<&EccConfig> for Config {
fn from(ecc_config: &EccConfig) -> Self {
Self {
q_point: ecc_config.q_point,
q_point_non_id: ecc_config.q_point_non_id,
x: ecc_config.advices[0],
y: ecc_config.advices[1],
}
}
}
impl Config {
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
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());
@ -70,29 +81,18 @@ impl Config {
value: Option<(pallas::Base, pallas::Base)>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
) -> 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::SynthesisError),
)?;
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::SynthesisError),
)?;
let y_var =
region.assign_advice(|| "y", self.y, offset, || y_val.ok_or(Error::Synthesis))?;
Ok((
CellValue::<pallas::Base>::new(x_var, x_val),
CellValue::<pallas::Base>::new(y_var, y_val),
))
Ok((x_var, y_var))
}
/// Assigns a point that can be the identity.
@ -132,7 +132,7 @@ impl Config {
if let Some(value) = value {
// Return an error if the point is the identity.
if value == pallas::Affine::identity() {
return Err(Error::SynthesisError);
return Err(Error::Synthesis);
}
};

View File

@ -1,25 +1,39 @@
//! 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::{Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter},
plonk::Error,
};
mod pow5t3;
pub use pow5t3::{Pow5T3Chip, Pow5T3Config, StateWord};
mod pow5;
pub use pow5::{Pow5Chip, Pow5Config, StateWord};
use crate::primitives::poseidon::{ConstantLength, Domain, Spec, Sponge, SpongeState, State};
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: Copy + fmt::Debug;
type Word: Clone + fmt::Debug + From<AssignedCell<F, F>> + Into<AssignedCell<F, F>>;
/// Applies the Poseidon permutation to the given state.
fn permute(
@ -29,37 +43,35 @@ pub trait PoseidonInstructions<F: FieldExt, S: Spec<F, T, RATE>, const T: usize,
) -> Result<State<Self::Word, T>, Error>;
}
/// The set of circuit instructions required to use the [`Duplex`] and [`Hash`] gadgets.
/// The set of circuit instructions required to use the [`Sponge`] and [`Hash`] gadgets.
///
/// [`Hash`]: self::Hash
pub trait PoseidonDuplexInstructions<
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>,
domain: &impl Domain<F, T, RATE>,
) -> Result<State<Self::Word, T>, Error>;
fn initial_state(&self, layouter: &mut impl Layouter<F>)
-> Result<State<Self::Word, T>, Error>;
/// Pads the given input (according to the specified domain) and adds it to the state.
fn pad_and_add(
/// Adds the given input to the state.
fn add_input(
&self,
layouter: &mut impl Layouter<F>,
domain: &impl Domain<F, T, RATE>,
initial_state: &State<Self::Word, T>,
input: &SpongeState<Self::Word, RATE>,
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>) -> SpongeState<Self::Word, RATE>;
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>,
@ -78,168 +90,187 @@ impl<
const RATE: usize,
> Word<F, PoseidonChip, S, T, RATE>
{
pub(crate) fn inner(&self) -> PoseidonChip::Word {
self.inner
/// The word contained in this gadget.
pub fn inner(&self) -> PoseidonChip::Word {
self.inner.clone()
}
pub(crate) fn from_inner(inner: PoseidonChip::Word) -> Self {
/// Construct a [`Word`] gadget from the inner word.
pub fn from_inner(inner: PoseidonChip::Word) -> Self {
Self { inner }
}
}
fn poseidon_duplex<
fn poseidon_sponge<
F: FieldExt,
PoseidonChip: PoseidonDuplexInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
>(
chip: &PoseidonChip,
mut layouter: impl Layouter<F>,
domain: &D,
state: &mut State<PoseidonChip::Word, T>,
input: &SpongeState<PoseidonChip::Word, RATE>,
) -> Result<SpongeState<PoseidonChip::Word, RATE>, Error> {
*state = chip.pad_and_add(&mut layouter, domain, state, input)?;
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 duplex sponge.
pub struct Duplex<
/// A Poseidon sponge.
#[derive(Debug)]
pub struct Sponge<
F: FieldExt,
PoseidonChip: PoseidonDuplexInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
M: SpongeMode,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> {
chip: PoseidonChip,
sponge: Sponge<PoseidonChip::Word, RATE>,
mode: M,
state: State<PoseidonChip::Word, T>,
domain: D,
_marker: PhantomData<D>,
}
impl<
F: FieldExt,
PoseidonChip: PoseidonDuplexInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> Duplex<F, PoseidonChip, S, D, T, RATE>
> 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>,
domain: D,
) -> Result<Self, Error> {
chip.initial_state(&mut layouter, &domain)
.map(|state| Duplex {
chip,
sponge: Sponge::Absorbing([None; RATE]),
state,
domain,
})
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: Word<F, PoseidonChip, S, T, RATE>,
value: PaddedWord<F>,
) -> Result<(), Error> {
match self.sponge {
Sponge::Absorbing(ref mut input) => {
for entry in input.iter_mut() {
if entry.is_none() {
*entry = Some(value.inner);
return Ok(());
}
}
// We've already absorbed as many elements as we can
let _ = poseidon_duplex(
&self.chip,
layouter.namespace(|| "PoseidonDuplex"),
&self.domain,
&mut self.state,
input,
)?;
self.sponge = Sponge::absorb(value.inner);
}
Sponge::Squeezing(_) => {
// Drop the remaining output elements
self.sponge = Sponge::absorb(value.inner);
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(())
}
/// Squeezes an element from the sponge.
pub fn squeeze(
&mut self,
/// Transitions the sponge into its squeezing state.
#[allow(clippy::type_complexity)]
pub fn finish_absorbing(
mut self,
mut layouter: impl Layouter<F>,
) -> Result<Word<F, PoseidonChip, S, T, RATE>, Error> {
loop {
match self.sponge {
Sponge::Absorbing(ref input) => {
self.sponge = Sponge::Squeezing(poseidon_duplex(
&self.chip,
layouter.namespace(|| "PoseidonDuplex"),
&self.domain,
&mut self.state,
input,
)?);
}
Sponge::Squeezing(ref mut output) => {
for entry in output.iter_mut() {
if let Some(inner) = entry.take() {
return Ok(Word { inner });
}
}
) -> 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),
)?;
// We've already squeezed out all available elements
self.sponge = Sponge::Absorbing([None; RATE]);
}
}
}
Ok(Sponge {
chip: self.chip,
mode,
state: self.state,
_marker: PhantomData::default(),
})
}
}
/// A Poseidon hash function, built around a duplex sponge.
pub struct Hash<
F: FieldExt,
PoseidonChip: PoseidonDuplexInstructions<F, S, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
const RATE: usize,
> {
duplex: Duplex<F, PoseidonChip, S, D, T, RATE>,
}
impl<
F: FieldExt,
PoseidonChip: PoseidonDuplexInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, D, T, RATE>,
S: Spec<F, T, RATE>,
D: Domain<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>, domain: D) -> Result<Self, Error> {
Duplex::new(chip, layouter, domain).map(|duplex| Hash { duplex })
pub fn init(chip: PoseidonChip, layouter: impl Layouter<F>) -> Result<Self, Error> {
Sponge::new(chip, layouter).map(|sponge| Hash { sponge })
}
}
impl<
F: FieldExt,
PoseidonChip: PoseidonDuplexInstructions<F, S, T, RATE>,
PoseidonChip: PoseidonSpongeInstructions<F, S, ConstantLength<L>, T, RATE>,
S: Spec<F, T, RATE>,
const T: usize,
const RATE: usize,
@ -250,12 +281,18 @@ impl<
pub fn hash(
mut self,
mut layouter: impl Layouter<F>,
message: [Word<F, PoseidonChip, S, T, RATE>; L],
) -> Result<Word<F, PoseidonChip, S, T, RATE>, Error> {
for (i, value) in array::IntoIter::new(message).enumerate() {
self.duplex
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.duplex.squeeze(layouter.namespace(|| "squeeze"))
self.sponge
.finish_absorbing(layouter.namespace(|| "finish absorbing"))?
.squeeze(layouter.namespace(|| "squeeze"))
}
}

View File

@ -0,0 +1,886 @@
use std::convert::TryInto;
use std::iter;
use halo2::{
arithmetic::FieldExt,
circuit::{AssignedCell, Cell, Chip, Layouter, Region},
plonk::{Advice, 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(|c| c.into()))
.chain(rc_b.iter().cloned().map(|c| c.into()))
{
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 ff::PrimeField;
use halo2::{
arithmetic::FieldExt,
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
pasta::Fp,
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::pallas;
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 message = [Fp::rand(), Fp::rand()];
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 message = [Fp::rand(), Fp::rand(), Fp::rand()];
let output =
poseidon::Hash::<_, OrchardNullifier, ConstantLength<3>, 3, 2>::init().hash(message);
let k = 7;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 3> {
message: Some(message),
output: Some(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[test]
fn hash_test_vectors() {
for tv in crate::primitives::poseidon::test_vectors::fp::hash() {
let message = [
pallas::Base::from_repr(tv.input[0]).unwrap(),
pallas::Base::from_repr(tv.input[1]).unwrap(),
];
let output = poseidon::Hash::<_, OrchardNullifier, ConstantLength<2>, 3, 2>::init()
.hash(message);
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: Some(message),
output: Some(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
}
}
#[cfg(feature = "dev-graph")]
#[test]
fn print_poseidon_chip() {
use plotters::prelude::*;
let root = BitMapBackend::new("poseidon-chip-layout.png", (1024, 768)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root
.titled("Poseidon Chip Layout", ("sans-serif", 60))
.unwrap();
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: None,
output: None,
_spec: PhantomData,
};
halo2::dev::CircuitLayout::default()
.render(6, &circuit, &root)
.unwrap();
}
}

View File

@ -1,872 +0,0 @@
use std::iter;
use halo2::{
arithmetic::FieldExt,
circuit::{Cell, Chip, Layouter, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector},
poly::Rotation,
};
use super::{PoseidonDuplexInstructions, PoseidonInstructions};
use crate::circuit::gadget::utilities::{CellValue, Var};
use crate::primitives::poseidon::{Domain, Mds, Spec, SpongeState, State};
const WIDTH: usize = 3;
/// Configuration for an [`Pow5T3Chip`].
#[derive(Clone, Debug)]
pub struct Pow5T3Config<F: FieldExt> {
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, with a width of 3, suitable for a 2:1 reduction.
#[derive(Debug)]
pub struct Pow5T3Chip<F: FieldExt> {
config: Pow5T3Config<F>,
}
impl<F: FieldExt> Pow5T3Chip<F> {
/// 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, 2>>(
meta: &mut ConstraintSystem<F>,
spec: S,
state: [Column<Advice>; WIDTH],
partial_sbox: Column<Advice>,
rc_a: [Column<Fixed>; WIDTH],
rc_b: [Column<Fixed>; WIDTH],
) -> Pow5T3Config<F> {
// 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) = spec.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(|c| c.into()))
.chain(rc_b.iter().cloned().map(|c| c.into()))
{
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 cur_0 = meta.query_advice(state[0], Rotation::cur());
let cur_1 = meta.query_advice(state[1], Rotation::cur());
let cur_2 = meta.query_advice(state[2], Rotation::cur());
let next = [
meta.query_advice(state[0], Rotation::next()),
meta.query_advice(state[1], Rotation::next()),
meta.query_advice(state[2], Rotation::next()),
];
let rc_0 = meta.query_fixed(rc_a[0], Rotation::cur());
let rc_1 = meta.query_fixed(rc_a[1], Rotation::cur());
let rc_2 = meta.query_fixed(rc_a[2], Rotation::cur());
let s_full = meta.query_selector(s_full);
let full_round = |next_idx: usize| {
s_full.clone()
* (pow_5(cur_0.clone() + rc_0.clone()) * m_reg[next_idx][0]
+ pow_5(cur_1.clone() + rc_1.clone()) * m_reg[next_idx][1]
+ pow_5(cur_2.clone() + rc_2.clone()) * m_reg[next_idx][2]
- next[next_idx].clone())
};
vec![
("state[0]", full_round(0)),
("state[1]", full_round(1)),
("state[2]", full_round(2)),
]
});
meta.create_gate("partial rounds", |meta| {
let cur_0 = meta.query_advice(state[0], Rotation::cur());
let cur_1 = meta.query_advice(state[1], Rotation::cur());
let cur_2 = meta.query_advice(state[2], Rotation::cur());
let mid_0 = meta.query_advice(partial_sbox, Rotation::cur());
let next_0 = meta.query_advice(state[0], Rotation::next());
let next_1 = meta.query_advice(state[1], Rotation::next());
let next_2 = meta.query_advice(state[2], Rotation::next());
let rc_a0 = meta.query_fixed(rc_a[0], Rotation::cur());
let rc_a1 = meta.query_fixed(rc_a[1], Rotation::cur());
let rc_a2 = meta.query_fixed(rc_a[2], Rotation::cur());
let rc_b0 = meta.query_fixed(rc_b[0], Rotation::cur());
let rc_b1 = meta.query_fixed(rc_b[1], Rotation::cur());
let rc_b2 = meta.query_fixed(rc_b[2], Rotation::cur());
let s_partial = meta.query_selector(s_partial);
let partial_round_linear = |idx: usize, rc_b: Expression<F>| {
s_partial.clone()
* (mid_0.clone() * m_reg[idx][0]
+ (cur_1.clone() + rc_a1.clone()) * m_reg[idx][1]
+ (cur_2.clone() + rc_a2.clone()) * m_reg[idx][2]
+ rc_b
- (next_0.clone() * m_inv[idx][0]
+ next_1.clone() * m_inv[idx][1]
+ next_2.clone() * m_inv[idx][2]))
};
vec![
(
"state[0] round a",
s_partial.clone() * (pow_5(cur_0 + rc_a0) - mid_0.clone()),
),
(
"state[0] round b",
s_partial.clone()
* (pow_5(
mid_0.clone() * m_reg[0][0]
+ (cur_1.clone() + rc_a1.clone()) * m_reg[0][1]
+ (cur_2.clone() + rc_a2.clone()) * m_reg[0][2]
+ rc_b0,
) - (next_0.clone() * m_inv[0][0]
+ next_1.clone() * m_inv[0][1]
+ next_2.clone() * m_inv[0][2])),
),
("state[1]", partial_round_linear(1, rc_b1)),
("state[2]", partial_round_linear(2, rc_b2)),
]
});
meta.create_gate("pad-and-add", |meta| {
let initial_state_0 = meta.query_advice(state[0], Rotation::prev());
let initial_state_1 = meta.query_advice(state[1], Rotation::prev());
let initial_state_2 = meta.query_advice(state[2], Rotation::prev());
let input_0 = meta.query_advice(state[0], Rotation::cur());
let input_1 = meta.query_advice(state[1], Rotation::cur());
let output_state_0 = meta.query_advice(state[0], Rotation::next());
let output_state_1 = meta.query_advice(state[1], Rotation::next());
let output_state_2 = meta.query_advice(state[2], Rotation::next());
let s_pad_and_add = meta.query_selector(s_pad_and_add);
let pad_and_add = |initial_state, input, output_state| {
// 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)
};
vec![
(
"state[0]",
pad_and_add(initial_state_0, input_0, output_state_0),
),
(
"state[1]",
pad_and_add(initial_state_1, input_1, output_state_1),
),
// The capacity element is never altered by the input.
(
"state[2]",
s_pad_and_add * (initial_state_2 - output_state_2),
),
]
});
Pow5T3Config {
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,
}
}
pub fn construct(config: Pow5T3Config<F>) -> Self {
Pow5T3Chip { config }
}
}
impl<F: FieldExt> Chip<F> for Pow5T3Chip<F> {
type Config = Pow5T3Config<F>;
type Loaded = ();
fn config(&self) -> &Self::Config {
&self.config
}
fn loaded(&self) -> &Self::Loaded {
&()
}
}
impl<F: FieldExt, S: Spec<F, WIDTH, 2>> PoseidonInstructions<F, S, WIDTH, 2> for Pow5T3Chip<F> {
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 = Pow5T3State::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, 2>> PoseidonDuplexInstructions<F, S, WIDTH, 2>
for Pow5T3Chip<F>
{
fn initial_state(
&self,
layouter: &mut impl Layouter<F>,
domain: &impl Domain<F, WIDTH, 2>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
layouter.assign_region(
|| format!("initial state for domain {:?}", domain),
|mut region| {
let mut load_state_word = |i: usize, value: F| {
let var = region.assign_advice_from_constant(
|| format!("state_{}", i),
config.state[i],
0,
value,
)?;
Ok(StateWord {
var,
value: Some(value),
})
};
Ok([
load_state_word(0, F::zero())?,
load_state_word(1, F::zero())?,
load_state_word(2, domain.initial_capacity_element())?,
])
},
)
}
fn pad_and_add(
&self,
layouter: &mut impl Layouter<F>,
domain: &impl Domain<F, WIDTH, 2>,
initial_state: &State<Self::Word, WIDTH>,
input: &SpongeState<Self::Word, 2>,
) -> Result<State<Self::Word, WIDTH>, Error> {
let config = self.config();
layouter.assign_region(
|| format!("pad-and-add for domain {:?}", domain),
|mut region| {
config.s_pad_and_add.enable(&mut region, 1)?;
// Load the initial state into this region.
let mut load_state_word = |i: usize| {
let value = initial_state[i].value;
let var = region.assign_advice(
|| format!("load state_{}", i),
config.state[i],
0,
|| value.ok_or(Error::SynthesisError),
)?;
region.constrain_equal(initial_state[i].var, var)?;
Ok(StateWord { var, value })
};
let initial_state = [
load_state_word(0)?,
load_state_word(1)?,
load_state_word(2)?,
];
let padding_values = domain.padding();
// Load the input and padding into this region.
let mut load_input_word = |i: usize| {
let (constraint_var, value) = match (input[i], padding_values[i]) {
(Some(word), None) => (word.var, word.value),
(None, Some(padding_value)) => {
let padding_var = region.assign_fixed(
|| format!("load pad_{}", i),
config.rc_b[i],
1,
|| Ok(padding_value),
)?;
(padding_var, Some(padding_value))
}
_ => panic!("Input and padding don't match"),
};
let var = region.assign_advice(
|| format!("load input_{}", i),
config.state[i],
1,
|| value.ok_or(Error::SynthesisError),
)?;
region.constrain_equal(constraint_var, var)?;
Ok(StateWord { var, value })
};
let input = [load_input_word(0)?, load_input_word(1)?];
// Constrain the output.
let mut constrain_output_word = |i: usize| {
let value = initial_state[i].value.and_then(|initial_word| {
input
.get(i)
.map(|word| word.value)
// The capacity element is never altered by the input.
.unwrap_or_else(|| Some(F::zero()))
.map(|input_word| initial_word + input_word)
});
let var = region.assign_advice(
|| format!("load output_{}", i),
config.state[i],
2,
|| value.ok_or(Error::SynthesisError),
)?;
Ok(StateWord { var, value })
};
Ok([
constrain_output_word(0)?,
constrain_output_word(1)?,
constrain_output_word(2)?,
])
},
)
}
fn get_output(state: &State<Self::Word, WIDTH>) -> SpongeState<Self::Word, 2> {
[Some(state[0]), Some(state[1])]
}
}
#[derive(Clone, Copy, Debug)]
pub struct StateWord<F: FieldExt> {
var: Cell,
value: Option<F>,
}
impl<F: FieldExt> StateWord<F> {
pub fn new(var: Cell, value: Option<F>) -> Self {
Self { var, value }
}
}
impl<F: FieldExt> From<StateWord<F>> for CellValue<F> {
fn from(state_word: StateWord<F>) -> CellValue<F> {
CellValue::new(state_word.var, state_word.value)
}
}
#[derive(Debug)]
struct Pow5T3State<F: FieldExt>([StateWord<F>; WIDTH]);
impl<F: FieldExt> Pow5T3State<F> {
fn full_round(
self,
region: &mut Region<F>,
config: &Pow5T3Config<F>,
round: usize,
offset: usize,
) -> Result<Self, Error> {
Self::round(region, config, round, offset, config.s_full, |_| {
let q_0 = self.0[0]
.value
.map(|v| v + config.round_constants[round][0]);
let q_1 = self.0[1]
.value
.map(|v| v + config.round_constants[round][1]);
let q_2 = self.0[2]
.value
.map(|v| v + config.round_constants[round][2]);
let r_0 = q_0.map(|v| v.pow(&config.alpha));
let r_1 = q_1.map(|v| v.pow(&config.alpha));
let r_2 = q_2.map(|v| v.pow(&config.alpha));
let m = &config.m_reg;
let r = r_0.and_then(|r_0| r_1.and_then(|r_1| r_2.map(|r_2| [r_0, r_1, r_2])));
Ok((
round + 1,
[
r.map(|r| m[0][0] * r[0] + m[0][1] * r[1] + m[0][2] * r[2]),
r.map(|r| m[1][0] * r[0] + m[1][1] * r[1] + m[1][2] * r[2]),
r.map(|r| m[2][0] * r[0] + m[2][1] * r[1] + m[2][2] * r[2]),
],
))
})
}
fn partial_round(
self,
region: &mut Region<F>,
config: &Pow5T3Config<F>,
round: usize,
offset: usize,
) -> Result<Self, Error> {
Self::round(region, config, round, offset, config.s_partial, |region| {
let m = &config.m_reg;
let p = self.0[0].value.and_then(|p_0| {
self.0[1]
.value
.and_then(|p_1| self.0[2].value.map(|p_2| [p_0, p_1, p_2]))
});
let r = p.map(|p| {
[
(p[0] + config.round_constants[round][0]).pow(&config.alpha),
p[1] + config.round_constants[round][1],
p[2] + config.round_constants[round][2],
]
});
region.assign_advice(
|| format!("round_{} partial_sbox", round),
config.partial_sbox,
offset,
|| r.map(|r| r[0]).ok_or(Error::SynthesisError),
)?;
let p_mid = r.map(|r| {
[
m[0][0] * r[0] + m[0][1] * r[1] + m[0][2] * r[2],
m[1][0] * r[0] + m[1][1] * r[1] + m[1][2] * r[2],
m[2][0] * r[0] + m[2][1] * r[1] + m[2][2] * r[2],
]
});
// 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 = p_mid.map(|p| {
[
(p[0] + config.round_constants[round + 1][0]).pow(&config.alpha),
p[1] + config.round_constants[round + 1][1],
p[2] + config.round_constants[round + 1][2],
]
});
Ok((
round + 2,
[
r_mid.map(|r| m[0][0] * r[0] + m[0][1] * r[1] + m[0][2] * r[2]),
r_mid.map(|r| m[1][0] * r[0] + m[1][1] * r[1] + m[1][2] * r[2]),
r_mid.map(|r| m[2][0] * r[0] + m[2][1] * r[1] + m[2][2] * r[2]),
],
))
})
}
fn load(
region: &mut Region<F>,
config: &Pow5T3Config<F>,
initial_state: &State<StateWord<F>, WIDTH>,
) -> Result<Self, Error> {
let mut load_state_word = |i: usize| {
let value = initial_state[i].value;
let var = region.assign_advice(
|| format!("load state_{}", i),
config.state[i],
0,
|| value.ok_or(Error::SynthesisError),
)?;
region.constrain_equal(initial_state[i].var, var)?;
Ok(StateWord { var, value })
};
Ok(Pow5T3State([
load_state_word(0)?,
load_state_word(1)?,
load_state_word(2)?,
]))
}
fn round(
region: &mut Region<F>,
config: &Pow5T3Config<F>,
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 mut 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::SynthesisError),
)?;
Ok(StateWord { var, value })
};
Ok(Pow5T3State([
next_state_word(0)?,
next_state_word(1)?,
next_state_word(2)?,
]))
}
}
#[cfg(test)]
mod tests {
use ff::PrimeField;
use halo2::{
arithmetic::FieldExt,
circuit::{Layouter, SimpleFloorPlanner},
dev::MockProver,
pasta::Fp,
plonk::{Circuit, ConstraintSystem, Error},
};
use pasta_curves::pallas;
use super::{PoseidonInstructions, Pow5T3Chip, Pow5T3Config, StateWord, WIDTH};
use crate::{
circuit::gadget::poseidon::{Hash, Word},
primitives::poseidon::{self, ConstantLength, P128Pow5T3 as OrchardNullifier, Spec},
};
struct PermuteCircuit {}
impl Circuit<Fp> for PermuteCircuit {
type Config = Pow5T3Config<Fp>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
PermuteCircuit {}
}
fn configure(meta: &mut ConstraintSystem<Fp>) -> Pow5T3Config<Fp> {
let state = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let partial_sbox = meta.advice_column();
let rc_a = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
let rc_b = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
Pow5T3Chip::configure(meta, OrchardNullifier, state, partial_sbox, rc_a, rc_b)
}
fn synthesize(
&self,
config: Pow5T3Config<Fp>,
mut layouter: impl Layouter<Fp>,
) -> Result<(), Error> {
let initial_state = layouter.assign_region(
|| "prepare initial state",
|mut region| {
let mut 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::SynthesisError),
)?;
Ok(StateWord { var, value })
};
Ok([state_word(0)?, state_word(1)?, state_word(2)?])
},
)?;
let chip = Pow5T3Chip::construct(config.clone());
let final_state = <Pow5T3Chip<_> as PoseidonInstructions<
Fp,
OrchardNullifier,
WIDTH,
2,
>>::permute(&chip, &mut layouter, &initial_state)?;
// For the purpose of this test, compute the real final state inline.
let mut expected_final_state = [Fp::zero(), Fp::one(), Fp::from_u64(2)];
let (round_constants, mds, _) = OrchardNullifier.constants();
poseidon::permute::<_, OrchardNullifier, WIDTH, 2>(
&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].var, var)
};
final_state_word(0)?;
final_state_word(1)?;
final_state_word(2)
},
)
}
}
#[test]
fn poseidon_permute() {
let k = 6;
let circuit = PermuteCircuit {};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()))
}
#[derive(Default)]
struct HashCircuit {
message: Option<[Fp; 2]>,
// For the purpose of this test, witness the result.
// TODO: Move this into an instance column.
output: Option<Fp>,
}
impl Circuit<Fp> for HashCircuit {
type Config = Pow5T3Config<Fp>;
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self::default()
}
fn configure(meta: &mut ConstraintSystem<Fp>) -> Pow5T3Config<Fp> {
let state = [
meta.advice_column(),
meta.advice_column(),
meta.advice_column(),
];
let partial_sbox = meta.advice_column();
let rc_a = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
let rc_b = [
meta.fixed_column(),
meta.fixed_column(),
meta.fixed_column(),
];
meta.enable_constant(rc_b[0]);
Pow5T3Chip::configure(meta, OrchardNullifier, state, partial_sbox, rc_a, rc_b)
}
fn synthesize(
&self,
config: Pow5T3Config<Fp>,
mut layouter: impl Layouter<Fp>,
) -> Result<(), Error> {
let chip = Pow5T3Chip::construct(config.clone());
let message = layouter.assign_region(
|| "load message",
|mut region| {
let mut message_word = |i: usize| {
let value = self.message.map(|message_vals| message_vals[i]);
let var = region.assign_advice(
|| format!("load message_{}", i),
config.state[i],
0,
|| value.ok_or(Error::SynthesisError),
)?;
Ok(Word::<_, _, OrchardNullifier, WIDTH, 2> {
inner: StateWord { var, value },
})
};
Ok([message_word(0)?, message_word(1)?])
},
)?;
let hasher = Hash::init(chip, layouter.namespace(|| "init"), ConstantLength::<2>)?;
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::SynthesisError),
)?;
let word: StateWord<_> = output.inner;
region.constrain_equal(word.var, expected_var)
},
)
}
}
#[test]
fn poseidon_hash() {
let message = [Fp::rand(), Fp::rand()];
let output = poseidon::Hash::init(OrchardNullifier, ConstantLength::<2>).hash(message);
let k = 6;
let circuit = HashCircuit {
message: Some(message),
output: Some(output),
};
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::init(OrchardNullifier, ConstantLength).hash(message);
let k = 6;
let circuit = HashCircuit {
message: Some(message),
output: Some(output),
};
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 {
message: None,
output: None,
};
halo2::dev::CircuitLayout::default()
.render(6, &circuit, &root)
.unwrap();
}
}

View File

@ -3,10 +3,10 @@ use crate::circuit::gadget::{
ecc::{self, EccInstructions},
utilities::Var,
};
use ff::PrimeField;
use group::ff::{Field, PrimeField};
use halo2::{circuit::Layouter, plonk::Error};
use pasta_curves::arithmetic::{CurveAffine, FieldExt};
use std::{convert::TryInto, fmt::Debug};
use pasta_curves::arithmetic::CurveAffine;
use std::fmt::Debug;
pub mod chip;
pub mod commit_ivk;
@ -28,11 +28,11 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
/// 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::NUM_BITS`.
/// `N*K <= C::Base::CAPACITY`.
///
/// For example, in the case `K = 10`, `NUM_BITS = 255`, we can fit
/// 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: Copy + Clone + Debug;
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
@ -118,8 +118,8 @@ where
// Message must have at most `MAX_WORDS` words.
assert!(bitstring.len() / K <= MAX_WORDS);
// Message piece must be at most `ceil(C::NUM_BITS / K)` bits
let piece_num_words = C::Base::NUM_BITS as usize / K;
// 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()
@ -170,7 +170,7 @@ where
SinsemillaChip: SinsemillaInstructions<C, K, MAX_WORDS> + Clone + Debug + Eq,
{
fn inner(&self) -> SinsemillaChip::MessagePiece {
self.inner
self.inner.clone()
}
}
@ -188,26 +188,23 @@ where
assert_eq!(bitstring.len() % K, 0);
let num_words = bitstring.len() / K;
// Message piece must be at most `ceil(C::Base::NUM_BITS / K)` bits
let piece_max_num_words = C::Base::NUM_BITS as usize / 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> {
assert!(bits.len() <= C::Base::NUM_BITS as usize);
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
let bytes: Option<Vec<u8>> = bits.map(|bits| {
// Pad bits to 256 bits
let pad_len = 256 - bits.len();
let mut bits = bits;
bits.extend_from_slice(&vec![false; pad_len]);
bits.chunks_exact(8)
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
.collect()
});
bytes.map(|bytes| C::Base::from_bytes(&bytes.try_into().unwrap()).unwrap())
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);
@ -487,8 +484,7 @@ mod tests {
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
let ecc_config =
EccChip::configure(meta, advices, lagrange_coeffs, range_check.clone());
let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check);
let config1 = SinsemillaChip::configure(
meta,
@ -496,7 +492,7 @@ mod tests {
advices[2],
lagrange_coeffs[0],
lookup,
range_check.clone(),
range_check,
);
let config2 = SinsemillaChip::configure(
meta,

View File

@ -4,8 +4,7 @@ use super::{
};
use crate::{
circuit::gadget::{
ecc::chip::NonIdentityEccPoint,
utilities::{lookup_range_check::LookupRangeCheckConfig, CellValue, Var},
ecc::chip::NonIdentityEccPoint, utilities::lookup_range_check::LookupRangeCheckConfig,
},
constants::OrchardFixedBasesFull,
primitives::sinsemilla::{
@ -13,9 +12,10 @@ use crate::{
},
};
use group::ff::PrimeField;
use halo2::{
arithmetic::{CurveAffine, FieldExt},
circuit::{Chip, Layouter},
arithmetic::CurveAffine,
circuit::{AssignedCell, Chip, Layouter},
plonk::{
Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector, TableColumn,
VirtualCells,
@ -146,7 +146,7 @@ impl SinsemillaChip {
// Set up lookup argument
GeneratorTableConfig::configure(meta, config.clone());
let two = pallas::Base::from_u64(2);
let two = pallas::Base::from(2);
// Closures for expressions that are derived multiple times
// x_r = lambda_1^2 - x_a - x_p
@ -211,7 +211,7 @@ impl SinsemillaChip {
// - 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_u64(4) * (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 = {
@ -239,14 +239,14 @@ impl SinsemillaChip {
impl SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }>
for SinsemillaChip
{
type CellValue = CellValue<pallas::Base>;
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 = CellValue<pallas::Base>;
type X = AssignedCell<pallas::Base, pallas::Base>;
type NonIdentityPoint = NonIdentityEccPoint;
type FixedPoints = OrchardFixedBasesFull;
@ -268,11 +268,11 @@ impl SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }
|| "witness message piece",
config.witness_pieces,
0,
|| field_elem.ok_or(Error::SynthesisError),
|| field_elem.ok_or(Error::Synthesis),
)
},
)?;
Ok(MessagePiece::new(cell, field_elem, num_words))
Ok(MessagePiece::new(cell, num_words))
}
#[allow(non_snake_case)]
@ -306,18 +306,18 @@ impl HashDomains<pallas::Affine> for SinsemillaHashDomains {
fn Q(&self) -> pallas::Affine {
match self {
SinsemillaHashDomains::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(),
SinsemillaHashDomains::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(),
SinsemillaHashDomains::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(),
}

View File

@ -42,7 +42,7 @@ impl GeneratorTableConfig {
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_u64(1 << sinsemilla::K))
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());
@ -84,7 +84,7 @@ impl GeneratorTableConfig {
|| "table_idx",
self.table_idx,
index,
|| Ok(pallas::Base::from_u64(index as u64)),
|| 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))?;

View File

@ -1,12 +1,13 @@
use super::super::SinsemillaInstructions;
use super::{CellValue, NonIdentityEccPoint, SinsemillaChip, Var};
use super::{NonIdentityEccPoint, SinsemillaChip};
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 ff::{Field, PrimeFieldBits};
use group::ff::{Field, PrimeField, PrimeFieldBits};
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
@ -26,7 +27,13 @@ impl SinsemillaChip {
{ sinsemilla::K },
{ sinsemilla::C },
>>::Message,
) -> Result<(NonIdentityEccPoint, Vec<Vec<CellValue<pallas::Base>>>), Error> {
) -> Result<
(
NonIdentityEccPoint,
Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>>,
),
Error,
> {
let config = self.config().clone();
let mut offset = 0;
@ -46,16 +53,13 @@ impl SinsemillaChip {
// 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 = {
let cell =
region.assign_advice_from_constant(|| "fixed x_q", config.x_a, offset, x_q)?;
CellValue::new(cell, Some(x_q))
};
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<CellValue<pallas::Base>>> = Vec::new();
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() {
@ -81,7 +85,7 @@ impl SinsemillaChip {
|| "y_a",
config.lambda_1,
offset,
|| y_a.ok_or(Error::SynthesisError),
|| y_a.ok_or(Error::Synthesis),
)?;
// Assign lambda_2 and x_p zero values since they are queried
@ -102,7 +106,7 @@ impl SinsemillaChip {
)?;
}
CellValue::new(y_a_cell, y_a.0)
y_a_cell
};
#[cfg(test)]
@ -142,15 +146,15 @@ impl SinsemillaChip {
.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();
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 == pallas::Base::zero() || y_a == pallas::Base::zero() {
return Err(Error::SynthesisError);
if x_a.is_zero_vartime() || y_a.is_zero_vartime() {
return Err(Error::Synthesis);
}
}
}
@ -183,7 +187,7 @@ impl SinsemillaChip {
(
X<pallas::Base>,
Y<pallas::Base>,
Vec<CellValue<pallas::Base>>,
Vec<AssignedCell<pallas::Base, pallas::Base>>,
),
Error,
> {
@ -220,7 +224,7 @@ impl SinsemillaChip {
offset + piece.num_words() - 1,
|| {
Ok(if final_piece {
pallas::Base::from_u64(2)
pallas::Base::from(2)
} else {
pallas::Base::zero()
})
@ -264,14 +268,13 @@ impl SinsemillaChip {
let mut zs = Vec::with_capacity(piece.num_words() + 1);
// Copy message and initialize running sum `z` to decompose message in-circuit
let cell = region.assign_advice(
let initial_z = piece.cell_value().copy_advice(
|| "z_0 (copy of message piece)",
region,
config.bits,
offset,
|| piece.field_elem().ok_or(Error::SynthesisError),
)?;
region.constrain_equal(piece.cell(), cell)?;
zs.push(CellValue::new(cell, piece.field_elem()));
zs.push(initial_z);
// Assign cumulative sum such that for 0 <= i < n,
// z_i = 2^K * z_{i + 1} + m_{i + 1}
@ -281,21 +284,21 @@ impl SinsemillaChip {
// 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_bytes(&INV_TWO_POW_K).unwrap();
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_u64(word as u64)) * inv_2_k);
.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::SynthesisError),
|| z.ok_or(Error::Synthesis),
)?;
zs.push(CellValue::new(cell, z))
zs.push(cell)
}
zs
@ -320,7 +323,7 @@ impl SinsemillaChip {
|| "x_p",
config.x_p,
offset + row,
|| x_p.ok_or(Error::SynthesisError),
|| x_p.ok_or(Error::Synthesis),
)?;
// Compute and assign `lambda_1`
@ -337,7 +340,7 @@ impl SinsemillaChip {
|| "lambda_1",
config.lambda_1,
offset + row,
|| lambda_1.ok_or(Error::SynthesisError),
|| lambda_1.ok_or(Error::Synthesis),
)?;
lambda_1
@ -353,7 +356,7 @@ impl SinsemillaChip {
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_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda_1
pallas::Base::from(2) * y_a * (x_a - x_r).invert().unwrap() - lambda_1
},
);
@ -361,7 +364,7 @@ impl SinsemillaChip {
|| "lambda_2",
config.lambda_2,
offset + row,
|| lambda_2.ok_or(Error::SynthesisError),
|| lambda_2.ok_or(Error::Synthesis),
)?;
lambda_2
@ -378,10 +381,10 @@ impl SinsemillaChip {
|| "x_a",
config.x_a,
offset + row + 1,
|| x_a_new.ok_or(Error::SynthesisError),
|| x_a_new.ok_or(Error::Synthesis),
)?;
CellValue::new(x_a_cell, x_a_new).into()
x_a_cell.into()
};
// Compute y_a for the next row.
@ -402,18 +405,18 @@ impl SinsemillaChip {
}
/// The x-coordinate of the accumulator in a Sinsemilla hash instance.
struct X<F: FieldExt>(CellValue<F>);
struct X<F: FieldExt>(AssignedCell<F, F>);
impl<F: FieldExt> From<CellValue<F>> for X<F> {
fn from(cell_value: CellValue<F>) -> Self {
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 = CellValue<F>;
type Target = AssignedCell<F, F>;
fn deref(&self) -> &CellValue<F> {
fn deref(&self) -> &AssignedCell<F, F> {
&self.0
}
}

View File

@ -1,5 +1,5 @@
use halo2::{
circuit::Layouter,
circuit::{AssignedCell, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
@ -8,7 +8,7 @@ use pasta_curves::{arithmetic::FieldExt, pallas};
use crate::{
circuit::gadget::{
ecc::{chip::EccChip, X},
utilities::{bitrange_subset, bool_check, copy, CellValue, Var},
utilities::{bitrange_subset, bool_check},
},
constants::T_P,
};
@ -67,8 +67,8 @@ impl CommitIvkConfig {
let q_commit_ivk = meta.query_selector(config.q_commit_ivk);
// Useful constants
let two_pow_4 = pallas::Base::from_u64(1 << 4);
let two_pow_5 = pallas::Base::from_u64(1 << 5);
let two_pow_4 = pallas::Base::from(1 << 4);
let two_pow_5 = pallas::Base::from(1 << 5);
let two_pow_9 = two_pow_4 * two_pow_5;
let two_pow_250 = pallas::Base::from_u128(1 << 125).square();
let two_pow_254 = two_pow_250 * two_pow_4;
@ -119,7 +119,7 @@ impl CommitIvkConfig {
// Check that nk = b_2 (5 bits) || c (240 bits) || d_0 (9 bits) || d_1 (1 bit)
let nk_decomposition_check = {
let two_pow_245 = pallas::Base::from_u64(1 << 49).pow(&[5, 0, 0, 0]);
let two_pow_245 = pallas::Base::from(1 << 49).pow(&[5, 0, 0, 0]);
b_2.clone()
+ c.clone() * two_pow_5
@ -181,7 +181,7 @@ impl CommitIvkConfig {
// Check that b2_c_prime = b_2 + c * 2^5 + 2^140 - t_P.
// This is checked regardless of the value of d_1.
let b2_c_prime_check = {
let two_pow_5 = pallas::Base::from_u64(1 << 5);
let two_pow_5 = pallas::Base::from(1 << 5);
let two_pow_140 =
Expression::Constant(pallas::Base::from_u128(1 << 70).square());
let t_p = Expression::Constant(pallas::Base::from_u128(T_P));
@ -225,8 +225,8 @@ impl CommitIvkConfig {
sinsemilla_chip: SinsemillaChip,
ecc_chip: EccChip,
mut layouter: impl Layouter<pallas::Base>,
ak: CellValue<pallas::Base>,
nk: CellValue<pallas::Base>,
ak: AssignedCell<pallas::Base, pallas::Base>,
nk: AssignedCell<pallas::Base, pallas::Base>,
rivk: Option<pallas::Scalar>,
) -> Result<X<pallas::Affine, EccChip>, Error> {
// <https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
@ -257,8 +257,8 @@ impl CommitIvkConfig {
let b_2 = nk.value().map(|value| bitrange_subset(value, 0..5));
let b = b_0.zip(b_1).zip(b_2).map(|((b_0, b_1), b_2)| {
let b1_shifted = b_1 * pallas::Base::from_u64(1 << 4);
let b2_shifted = b_2 * pallas::Base::from_u64(1 << 5);
let b1_shifted = b_1 * pallas::Base::from(1 << 4);
let b2_shifted = b_2 * pallas::Base::from(1 << 5);
b_0 + b1_shifted + b2_shifted
});
@ -304,7 +304,7 @@ impl CommitIvkConfig {
let d = d_0
.zip(d_1)
.map(|(d_0, d_1)| d_0 + d_1 * pallas::Base::from_u64(1 << 9));
.map(|(d_0, d_1)| d_0 + d_1 * pallas::Base::from(1 << 9));
// Constrain d_0 to be 9 bits.
let d_0 = self.sinsemilla_config.lookup_config.witness_short_check(
@ -337,8 +337,8 @@ impl CommitIvkConfig {
domain.short_commit(layouter.namespace(|| "Hash ak||nk"), message, rivk)?
};
let z13_a = zs[0][13];
let z13_c = zs[2][13];
let z13_a = zs[0][13].clone();
let z13_c = zs[2][13].clone();
let (a_prime, z13_a_prime) = self.ak_canonicity(
layouter.namespace(|| "ak canonicity"),
@ -347,7 +347,7 @@ impl CommitIvkConfig {
let (b2_c_prime, z14_b2_c_prime) = self.nk_canonicity(
layouter.namespace(|| "nk canonicity"),
b_2,
b_2.clone(),
c.inner().cell_value(),
)?;
@ -384,8 +384,14 @@ impl CommitIvkConfig {
fn ak_canonicity(
&self,
mut layouter: impl Layouter<pallas::Base>,
a: CellValue<pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
a: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<
(
AssignedCell<pallas::Base, pallas::Base>,
AssignedCell<pallas::Base, pallas::Base>,
),
Error,
> {
// `ak` = `a (250 bits) || b_0 (4 bits) || b_1 (1 bit)`
// - b_1 = 1 => b_0 = 0
// - b_1 = 1 => a < t_P
@ -406,10 +412,10 @@ impl CommitIvkConfig {
13,
false,
)?;
let a_prime = zs[0];
let a_prime = zs[0].clone();
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z13_a]
Ok((a_prime, zs[13]))
Ok((a_prime, zs[13].clone()))
}
#[allow(clippy::type_complexity)]
@ -417,9 +423,15 @@ impl CommitIvkConfig {
fn nk_canonicity(
&self,
mut layouter: impl Layouter<pallas::Base>,
b_2: CellValue<pallas::Base>,
c: CellValue<pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
b_2: AssignedCell<pallas::Base, pallas::Base>,
c: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<
(
AssignedCell<pallas::Base, pallas::Base>,
AssignedCell<pallas::Base, pallas::Base>,
),
Error,
> {
// `nk` = `b_2 (5 bits) || c (240 bits) || d_0 (9 bits) || d_1 (1 bit)
// - d_1 = 1 => d_0 = 0
// - d_1 = 1 => b_2 + c * 2^5 < t_P
@ -432,7 +444,7 @@ impl CommitIvkConfig {
// Decompose the low 140 bits of b2_c_prime = b_2 + c * 2^5 + 2^140 - t_P, and output
// the running sum at the end of it. If b2_c_prime < 2^140, the running sum will be 0.
let b2_c_prime = b_2.value().zip(c.value()).map(|(b_2, c)| {
let two_pow_5 = pallas::Base::from_u64(1 << 5);
let two_pow_5 = pallas::Base::from(1 << 5);
let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square();
let t_p = pallas::Base::from_u128(T_P);
b_2 + c * two_pow_5 + two_pow_140 - t_p
@ -443,10 +455,10 @@ impl CommitIvkConfig {
14,
false,
)?;
let b2_c_prime = zs[0];
let b2_c_prime = zs[0].clone();
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z14]
Ok((b2_c_prime, zs[14]))
Ok((b2_c_prime, zs[14].clone()))
}
// Assign cells for the canonicity gate.
@ -474,71 +486,60 @@ impl CommitIvkConfig {
{
let offset = 0;
// Copy in `ak`
copy(
&mut region,
|| "ak",
self.advices[0],
offset,
&gate_cells.ak,
)?;
gate_cells
.ak
.copy_advice(|| "ak", &mut region, self.advices[0], offset)?;
// Copy in `a`
copy(&mut region, || "a", self.advices[1], offset, &gate_cells.a)?;
gate_cells
.a
.copy_advice(|| "a", &mut region, self.advices[1], offset)?;
// Copy in `b`
copy(&mut region, || "b", self.advices[2], offset, &gate_cells.b)?;
gate_cells
.b
.copy_advice(|| "b", &mut region, self.advices[2], offset)?;
// Copy in `b_0`
copy(
&mut region,
|| "b_0",
self.advices[3],
offset,
&gate_cells.b_0,
)?;
gate_cells
.b_0
.copy_advice(|| "b_0", &mut region, self.advices[3], offset)?;
// Witness `b_1`
region.assign_advice(
|| "Witness b_1",
self.advices[4],
offset,
|| gate_cells.b_1.ok_or(Error::SynthesisError),
|| gate_cells.b_1.ok_or(Error::Synthesis),
)?;
// Copy in `b_2`
copy(
&mut region,
|| "b_2",
self.advices[5],
offset,
&gate_cells.b_2,
)?;
gate_cells
.b_2
.copy_advice(|| "b_2", &mut region, self.advices[5], offset)?;
// Copy in z13_a
copy(
&mut region,
gate_cells.z13_a.copy_advice(
|| "z13_a",
&mut region,
self.advices[6],
offset,
&gate_cells.z13_a,
)?;
// Copy in a_prime
copy(
&mut region,
gate_cells.a_prime.copy_advice(
|| "a_prime",
&mut region,
self.advices[7],
offset,
&gate_cells.a_prime,
)?;
// Copy in z13_a_prime
copy(
&mut region,
gate_cells.z13_a_prime.copy_advice(
|| "z13_a_prime",
&mut region,
self.advices[8],
offset,
&gate_cells.z13_a_prime,
)?;
}
@ -547,62 +548,55 @@ impl CommitIvkConfig {
let offset = 1;
// Copy in `nk`
copy(
&mut region,
|| "nk",
self.advices[0],
offset,
&gate_cells.nk,
)?;
gate_cells
.nk
.copy_advice(|| "nk", &mut region, self.advices[0], offset)?;
// Copy in `c`
copy(&mut region, || "c", self.advices[1], offset, &gate_cells.c)?;
gate_cells
.c
.copy_advice(|| "c", &mut region, self.advices[1], offset)?;
// Copy in `d`
copy(&mut region, || "d", self.advices[2], offset, &gate_cells.d)?;
gate_cells
.d
.copy_advice(|| "d", &mut region, self.advices[2], offset)?;
// Copy in `d_0`
copy(
&mut region,
|| "d_0",
self.advices[3],
offset,
&gate_cells.d_0,
)?;
gate_cells
.d_0
.copy_advice(|| "d_0", &mut region, self.advices[3], offset)?;
// Witness `d_1`
region.assign_advice(
|| "Witness d_1",
self.advices[4],
offset,
|| gate_cells.d_1.ok_or(Error::SynthesisError),
|| gate_cells.d_1.ok_or(Error::Synthesis),
)?;
// Copy in z13_c
copy(
&mut region,
gate_cells.z13_c.copy_advice(
|| "z13_c",
&mut region,
self.advices[6],
offset,
&gate_cells.z13_c,
)?;
// Copy in b2_c_prime
copy(
&mut region,
gate_cells.b2_c_prime.copy_advice(
|| "b2_c_prime",
&mut region,
self.advices[7],
offset,
&gate_cells.b2_c_prime,
)?;
// Copy in z14_b2_c_prime
copy(
&mut region,
gate_cells.z14_b2_c_prime.copy_advice(
|| "z14_b2_c_prime",
&mut region,
self.advices[8],
offset,
&gate_cells.z14_b2_c_prime,
)?;
}
@ -614,23 +608,23 @@ impl CommitIvkConfig {
// Cells used in the canonicity gate.
struct GateCells {
a: CellValue<pallas::Base>,
b: CellValue<pallas::Base>,
c: CellValue<pallas::Base>,
d: CellValue<pallas::Base>,
ak: CellValue<pallas::Base>,
nk: CellValue<pallas::Base>,
b_0: CellValue<pallas::Base>,
a: AssignedCell<pallas::Base, pallas::Base>,
b: AssignedCell<pallas::Base, pallas::Base>,
c: AssignedCell<pallas::Base, pallas::Base>,
d: AssignedCell<pallas::Base, pallas::Base>,
ak: AssignedCell<pallas::Base, pallas::Base>,
nk: AssignedCell<pallas::Base, pallas::Base>,
b_0: AssignedCell<pallas::Base, pallas::Base>,
b_1: Option<pallas::Base>,
b_2: CellValue<pallas::Base>,
d_0: CellValue<pallas::Base>,
b_2: AssignedCell<pallas::Base, pallas::Base>,
d_0: AssignedCell<pallas::Base, pallas::Base>,
d_1: Option<pallas::Base>,
z13_a: CellValue<pallas::Base>,
a_prime: CellValue<pallas::Base>,
z13_a_prime: CellValue<pallas::Base>,
z13_c: CellValue<pallas::Base>,
b2_c_prime: CellValue<pallas::Base>,
z14_b2_c_prime: CellValue<pallas::Base>,
z13_a: AssignedCell<pallas::Base, pallas::Base>,
a_prime: AssignedCell<pallas::Base, pallas::Base>,
z13_a_prime: AssignedCell<pallas::Base, pallas::Base>,
z13_c: AssignedCell<pallas::Base, pallas::Base>,
b2_c_prime: AssignedCell<pallas::Base, pallas::Base>,
z14_b2_c_prime: AssignedCell<pallas::Base, pallas::Base>,
}
#[cfg(test)]
@ -640,16 +634,14 @@ mod tests {
circuit::gadget::{
ecc::chip::{EccChip, EccConfig},
sinsemilla::chip::SinsemillaChip,
utilities::{
lookup_range_check::LookupRangeCheckConfig, CellValue, UtilitiesInstructions, Var,
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
},
constants::{COMMIT_IVK_PERSONALIZATION, L_ORCHARD_BASE, T_Q},
primitives::sinsemilla::CommitDomain,
};
use ff::PrimeFieldBits;
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{AssignedCell, Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
@ -666,7 +658,7 @@ mod tests {
}
impl UtilitiesInstructions<pallas::Base> for MyCircuit {
type Var = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl Circuit<pallas::Base> for MyCircuit {
@ -722,7 +714,7 @@ mod tests {
advices[2],
lagrange_coeffs[0],
lookup,
range_check.clone(),
range_check,
);
let commit_ivk_config =
@ -803,7 +795,7 @@ mod tests {
.unwrap()
};
assert_eq!(expected_ivk, ivk.inner().value().unwrap());
assert_eq!(&expected_ivk, ivk.inner().value().unwrap());
Ok(())
}

View File

@ -139,13 +139,14 @@ pub mod tests {
use crate::{
circuit::gadget::{
sinsemilla::chip::{SinsemillaChip, SinsemillaHashDomains},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions, Var},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
},
constants::MERKLE_DEPTH_ORCHARD,
note::commitment::ExtractedNoteCommitment,
tree,
};
use group::ff::PrimeField;
use halo2::{
arithmetic::FieldExt,
circuit::{Layouter, SimpleFloorPlanner},
@ -210,7 +211,7 @@ pub mod tests {
advices[7],
fixed_y_q_1,
lookup,
range_check.clone(),
range_check,
);
let config1 = MerkleChip::configure(meta, sinsemilla_config_1);
@ -260,13 +261,13 @@ pub mod tests {
// 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_bytes())
.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());
assert_eq!(computed_final_root.value().unwrap(), &final_root.inner());
}
Ok(())

View File

@ -1,5 +1,5 @@
use halo2::{
circuit::{Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
@ -15,7 +15,7 @@ use crate::{
circuit::gadget::utilities::{
bitrange_subset,
cond_swap::{CondSwapChip, CondSwapConfig, CondSwapInstructions},
copy, CellValue, UtilitiesInstructions, Var,
UtilitiesInstructions,
},
constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD},
primitives::sinsemilla,
@ -82,7 +82,7 @@ impl MerkleChip {
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_u64(1 << 5);
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.
@ -101,7 +101,7 @@ impl MerkleChip {
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_u64(1 << 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
@ -185,12 +185,12 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
let a = {
let a = {
// a_0 = l
let a_0 = bitrange_subset(pallas::Base::from_u64(l as u64), 0..10);
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_u64(1 << 10))
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)?
@ -233,8 +233,7 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
.zip(b_1.value())
.zip(b_2.value())
.map(|((b_0, b_1), b_2)| {
b_0 + b_1 * pallas::Base::from_u64(1 << 10)
+ b_2 * pallas::Base::from_u64(1 << 15)
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"),
@ -257,10 +256,10 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
let (point, zs) = self.hash_to_point(
layouter.namespace(|| format!("hash at l = {}", l)),
Q,
vec![a, b, c].into(),
vec![a.clone(), b.clone(), c.clone()].into(),
)?;
let z1_a = zs[0][1];
let z1_b = zs[1][1];
let z1_a = zs[0][1].clone();
let z1_b = zs[1][1].clone();
// Check that the pieces have been decomposed properly.
/*
@ -282,48 +281,33 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
|| format!("l {}", l),
config.advices[4],
1,
pallas::Base::from_u64(l as u64),
pallas::Base::from(l as u64),
)?;
// Offset 0
// Copy and assign `a` at the correct position.
copy(
&mut region,
|| "copy a",
config.advices[0],
0,
&a.cell_value(),
)?;
a.cell_value()
.copy_advice(|| "copy a", &mut region, config.advices[0], 0)?;
// Copy and assign `b` at the correct position.
copy(
&mut region,
|| "copy b",
config.advices[1],
0,
&b.cell_value(),
)?;
b.cell_value()
.copy_advice(|| "copy b", &mut region, config.advices[1], 0)?;
// Copy and assign `c` at the correct position.
copy(
&mut region,
|| "copy c",
config.advices[2],
0,
&c.cell_value(),
)?;
c.cell_value()
.copy_advice(|| "copy c", &mut region, config.advices[2], 0)?;
// Copy and assign the left node at the correct position.
copy(&mut region, || "left", config.advices[3], 0, &left)?;
left.copy_advice(|| "left", &mut region, config.advices[3], 0)?;
// Copy and assign the right node at the correct position.
copy(&mut region, || "right", config.advices[4], 0, &right)?;
right.copy_advice(|| "right", &mut region, config.advices[4], 0)?;
// Offset 1
// Copy and assign z_1 of SinsemillaHash(a) = a_1
copy(&mut region, || "z1_a", config.advices[0], 1, &z1_a)?;
z1_a.copy_advice(|| "z1_a", &mut region, config.advices[0], 1)?;
// Copy and assign z_1 of SinsemillaHash(b) = b_1
copy(&mut region, || "z1_b", config.advices[1], 1, &z1_b)?;
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
copy(&mut region, || "b_1", config.advices[2], 1, &b_1)?;
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
copy(&mut region, || "b_2", config.advices[3], 1, &b_2)?;
b_2.copy_advice(|| "b_2", &mut region, config.advices[3], 1)?;
Ok(())
},
@ -339,7 +323,7 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
constants::MERKLE_CRH_PERSONALIZATION, primitives::sinsemilla::HashDomain,
spec::i2lebsp,
};
use ff::PrimeFieldBits;
use group::ff::{PrimeField, PrimeFieldBits};
if let (Some(left), Some(right)) = (left.value(), right.value()) {
let l = i2lebsp::<10>(l as u64);
@ -363,7 +347,7 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
let expected = merkle_crh.hash(message.into_iter()).unwrap();
assert_eq!(expected.to_bytes(), result.value().unwrap().to_bytes());
assert_eq!(expected.to_repr(), result.value().unwrap().to_repr());
}
}
@ -372,7 +356,7 @@ impl MerkleInstructions<pallas::Affine, MERKLE_DEPTH_ORCHARD, { sinsemilla::K },
}
impl UtilitiesInstructions<pallas::Base> for MerkleChip {
type Var = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl CondSwapInstructions<pallas::Base> for MerkleChip {

View File

@ -1,7 +1,9 @@
//! Gadget and chips for the Sinsemilla hash function.
use crate::circuit::gadget::utilities::{CellValue, Var};
use ff::PrimeFieldBits;
use halo2::{arithmetic::FieldExt, circuit::Cell};
use halo2::{
arithmetic::FieldExt,
circuit::{AssignedCell, Cell},
};
use std::fmt::Debug;
/// A [`Message`] composed of several [`MessagePiece`]s.
@ -32,17 +34,16 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize, const MAX_WORDS: usize> std::
///
/// The piece must fit within a base field element, which means its length
/// cannot exceed the base field's `NUM_BITS`.
#[derive(Copy, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct MessagePiece<F: FieldExt, const K: usize> {
cell_value: CellValue<F>,
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: Cell, field_elem: Option<F>, num_words: usize) -> Self {
pub fn new(cell_value: AssignedCell<F, F>, num_words: usize) -> Self {
assert!(num_words * K < F::NUM_BITS as usize);
let cell_value = CellValue::new(cell, field_elem);
Self {
cell_value,
num_words,
@ -58,10 +59,10 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> MessagePiece<F, K> {
}
pub fn field_elem(&self) -> Option<F> {
self.cell_value.value()
self.cell_value.value().cloned()
}
pub fn cell_value(&self) -> CellValue<F> {
self.cell_value
pub fn cell_value(&self) -> AssignedCell<F, F> {
self.cell_value.clone()
}
}

View File

@ -1,5 +1,5 @@
use halo2::{
circuit::Layouter,
circuit::{AssignedCell, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
poly::Rotation,
};
@ -11,7 +11,7 @@ use crate::{
chip::{EccChip, NonIdentityEccPoint},
Point,
},
utilities::{bitrange_subset, bool_check, copy, CellValue, Var},
utilities::{bitrange_subset, bool_check},
},
constants::T_P,
};
@ -21,6 +21,13 @@ use super::{
CommitDomain, Message, MessagePiece,
};
/// The values of the running sum at the start and end of the range being used for a
/// canonicity check.
type CanonicityBounds = (
AssignedCell<pallas::Base, pallas::Base>,
AssignedCell<pallas::Base, pallas::Base>,
);
/*
<https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit>
We need to hash g_d || pk_d || i2lebsp_{64}(v) || rho || psi,
@ -89,15 +96,15 @@ impl NoteCommitConfig {
};
// Useful constants
let two = pallas::Base::from_u64(2);
let two_pow_2 = pallas::Base::from_u64(1 << 2);
let two = pallas::Base::from(2);
let two_pow_2 = pallas::Base::from(1 << 2);
let two_pow_4 = two_pow_2.square();
let two_pow_5 = two_pow_4 * two;
let two_pow_6 = two_pow_5 * two;
let two_pow_8 = two_pow_4.square();
let two_pow_9 = two_pow_8 * two;
let two_pow_10 = two_pow_9 * two;
let two_pow_58 = pallas::Base::from_u64(1 << 58);
let two_pow_58 = pallas::Base::from(1 << 58);
let two_pow_130 = Expression::Constant(pallas::Base::from_u128(1 << 65).square());
let two_pow_140 = Expression::Constant(pallas::Base::from_u128(1 << 70).square());
let two_pow_249 = pallas::Base::from_u128(1 << 124).square() * two;
@ -525,13 +532,16 @@ impl NoteCommitConfig {
ecc_chip: EccChip,
g_d: &NonIdentityEccPoint,
pk_d: &NonIdentityEccPoint,
value: CellValue<pallas::Base>,
rho: CellValue<pallas::Base>,
psi: CellValue<pallas::Base>,
// TODO: Set V to Orchard value type
value: AssignedCell<pallas::Base, pallas::Base>,
rho: AssignedCell<pallas::Base, pallas::Base>,
psi: AssignedCell<pallas::Base, pallas::Base>,
rcm: Option<pallas::Scalar>,
) -> Result<Point<pallas::Affine, EccChip>, Error> {
let (gd_x, gd_y) = (g_d.x().value(), g_d.y().value());
let (pkd_x, pkd_y) = (pk_d.x().value(), pk_d.y().value());
let (gd_x, gd_y) = (g_d.x(), g_d.y());
let (pkd_x, pkd_y) = (pk_d.x(), pk_d.y());
let (gd_x, gd_y) = (gd_x.value(), gd_y.value());
let (pkd_x, pkd_y) = (pkd_x.value(), pkd_y.value());
let value_val = value.value();
let rho_val = rho.value();
let psi_val = psi.value();
@ -569,9 +579,9 @@ impl NoteCommitConfig {
let b = b_0.value().zip(b_1).zip(b_2).zip(b_3.value()).map(
|(((b_0, b_1), b_2), b_3)| {
let b1_shifted = b_1 * pallas::Base::from_u64(1 << 4);
let b2_shifted = b_2 * pallas::Base::from_u64(1 << 5);
let b3_shifted = b_3 * pallas::Base::from_u64(1 << 6);
let b1_shifted = b_1 * pallas::Base::from(1 << 4);
let b2_shifted = b_2 * pallas::Base::from(1 << 5);
let b3_shifted = b_3 * pallas::Base::from(1 << 6);
b_0 + b1_shifted + b2_shifted + b3_shifted
},
);
@ -611,9 +621,9 @@ impl NoteCommitConfig {
.zip(d_2.value())
.zip(d_3)
.map(|(((d_0, d_1), d_2), d_3)| {
let d1_shifted = d_1 * pallas::Base::from_u64(2);
let d2_shifted = d_2 * pallas::Base::from_u64(1 << 2);
let d3_shifted = d_3 * pallas::Base::from_u64(1 << 10);
let d1_shifted = d_1 * pallas::Base::from(2);
let d2_shifted = d_2 * pallas::Base::from(1 << 2);
let d3_shifted = d_3 * pallas::Base::from(1 << 10);
d_0 + d1_shifted + d2_shifted + d3_shifted
});
@ -644,7 +654,7 @@ impl NoteCommitConfig {
let e = e_0
.value()
.zip(e_1.value())
.map(|(e_0, e_1)| e_0 + e_1 * pallas::Base::from_u64(1 << 6));
.map(|(e_0, e_1)| e_0 + e_1 * pallas::Base::from(1 << 6));
let e = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "e"), e, 1)?;
(e_0, e_1, e)
@ -674,8 +684,8 @@ impl NoteCommitConfig {
// g_2 = z1_g from the SinsemillaHash(g) running sum output.
let g = g_0.zip(g_1.value()).zip(g_2).map(|((g_0, g_1), g_2)| {
let g1_shifted = g_1 * pallas::Base::from_u64(2);
let g2_shifted = g_2 * pallas::Base::from_u64(1 << 10);
let g1_shifted = g_1 * pallas::Base::from(2);
let g2_shifted = g_2 * pallas::Base::from(1 << 10);
g_0 + g1_shifted + g2_shifted
});
let g = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "g"), g, 25)?;
@ -701,7 +711,7 @@ impl NoteCommitConfig {
let h = h_0
.value()
.zip(h_1)
.map(|(h_0, h_1)| h_0 + h_1 * pallas::Base::from_u64(1 << 5));
.map(|(h_0, h_1)| h_0 + h_1 * pallas::Base::from(1 << 5));
let h = MessagePiece::from_field_elem(chip.clone(), layouter.namespace(|| "h"), h, 1)?;
(h_0, h_1, h)
@ -738,13 +748,13 @@ impl NoteCommitConfig {
)?
};
let z13_a = zs[0][13];
let z13_c = zs[2][13];
let z1_d = zs[3][1];
let z13_f = zs[5][13];
let z1_g = zs[6][1];
let g_2 = z1_g;
let z13_g = zs[6][13];
let z13_a = zs[0][13].clone();
let z13_c = zs[2][13].clone();
let z1_d = zs[3][1].clone();
let z13_f = zs[5][13].clone();
let z1_g = zs[6][1].clone();
let g_2 = z1_g.clone();
let z13_g = zs[6][13].clone();
let (a_prime, z13_a_prime) = self.canon_bitshift_130(
layouter.namespace(|| "x(g_d) canonicity"),
@ -753,18 +763,18 @@ impl NoteCommitConfig {
let (b3_c_prime, z14_b3_c_prime) = self.pkd_x_canonicity(
layouter.namespace(|| "x(pk_d) canonicity"),
b_3,
b_3.clone(),
c.inner().cell_value(),
)?;
let (e1_f_prime, z14_e1_f_prime) = self.rho_canonicity(
layouter.namespace(|| "rho canonicity"),
e_1,
e_1.clone(),
f.inner().cell_value(),
)?;
let (g1_g2_prime, z13_g1_g2_prime) =
self.psi_canonicity(layouter.namespace(|| "psi canonicity"), g_1, g_2)?;
self.psi_canonicity(layouter.namespace(|| "psi canonicity"), g_1.clone(), g_2)?;
let gate_cells = GateCells {
a: a.inner().cell_value(),
@ -814,13 +824,12 @@ impl NoteCommitConfig {
Ok(cm)
}
#[allow(clippy::type_complexity)]
// A canonicity check helper used in checking x(g_d), y(g_d), and y(pk_d).
fn canon_bitshift_130(
&self,
mut layouter: impl Layouter<pallas::Base>,
a: CellValue<pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
a: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<CanonicityBounds, Error> {
// element = `a (250 bits) || b_0 (4 bits) || b_1 (1 bit)`
// - b_1 = 1 => b_0 = 0
// - b_1 = 1 => a < t_P
@ -841,19 +850,19 @@ impl NoteCommitConfig {
13,
false,
)?;
let a_prime = zs[0];
let a_prime = zs[0].clone();
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z_13]
Ok((a_prime, zs[13]))
Ok((a_prime, zs[13].clone()))
}
// Check canonicity of `x(pk_d)` encoding
fn pkd_x_canonicity(
&self,
mut layouter: impl Layouter<pallas::Base>,
b_3: CellValue<pallas::Base>,
c: CellValue<pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
b_3: AssignedCell<pallas::Base, pallas::Base>,
c: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<CanonicityBounds, Error> {
// `x(pk_d)` = `b_3 (4 bits) || c (250 bits) || d_0 (1 bit)`
// - d_0 = 1 => b_3 + 2^4 c < t_P
// - 0 ≤ b_3 + 2^4 c < 2^134
@ -868,7 +877,7 @@ impl NoteCommitConfig {
// and output the running sum at the end of it.
// If b3_c_prime < 2^140, the running sum will be 0.
let b3_c_prime = b_3.value().zip(c.value()).map(|(b_3, c)| {
let two_pow_4 = pallas::Base::from_u64(1u64 << 4);
let two_pow_4 = pallas::Base::from(1u64 << 4);
let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square();
let t_p = pallas::Base::from_u128(T_P);
b_3 + (two_pow_4 * c) + two_pow_140 - t_p
@ -880,20 +889,19 @@ impl NoteCommitConfig {
14,
false,
)?;
let b3_c_prime = zs[0];
let b3_c_prime = zs[0].clone();
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14]
Ok((b3_c_prime, zs[14]))
Ok((b3_c_prime, zs[14].clone()))
}
#[allow(clippy::type_complexity)]
// Check canonicity of `rho` encoding
fn rho_canonicity(
&self,
mut layouter: impl Layouter<pallas::Base>,
e_1: CellValue<pallas::Base>,
f: CellValue<pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
e_1: AssignedCell<pallas::Base, pallas::Base>,
f: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<CanonicityBounds, Error> {
// `rho` = `e_1 (4 bits) || f (250 bits) || g_0 (1 bit)`
// - g_0 = 1 => e_1 + 2^4 f < t_P
// - 0 ≤ e_1 + 2^4 f < 2^134
@ -905,7 +913,7 @@ impl NoteCommitConfig {
// - 0 ≤ e_1 + 2^4 f + 2^140 - t_P < 2^140 (14 ten-bit lookups)
let e1_f_prime = e_1.value().zip(f.value()).map(|(e_1, f)| {
let two_pow_4 = pallas::Base::from_u64(1u64 << 4);
let two_pow_4 = pallas::Base::from(1u64 << 4);
let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square();
let t_p = pallas::Base::from_u128(T_P);
e_1 + (two_pow_4 * f) + two_pow_140 - t_p
@ -920,19 +928,19 @@ impl NoteCommitConfig {
14,
false,
)?;
let e1_f_prime = zs[0];
let e1_f_prime = zs[0].clone();
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14]
Ok((e1_f_prime, zs[14]))
Ok((e1_f_prime, zs[14].clone()))
}
// Check canonicity of `psi` encoding
fn psi_canonicity(
&self,
mut layouter: impl Layouter<pallas::Base>,
g_1: CellValue<pallas::Base>,
g_2: CellValue<pallas::Base>,
) -> Result<(CellValue<pallas::Base>, CellValue<pallas::Base>), Error> {
g_1: AssignedCell<pallas::Base, pallas::Base>,
g_2: AssignedCell<pallas::Base, pallas::Base>,
) -> Result<CanonicityBounds, Error> {
// `psi` = `g_1 (9 bits) || g_2 (240 bits) || h_0 (5 bits) || h_1 (1 bit)`
// - h_1 = 1 => (h_0 = 0) ∧ (g_1 + 2^9 g_2 < t_P)
// - 0 ≤ g_1 + 2^9 g_2 < 2^130
@ -945,7 +953,7 @@ impl NoteCommitConfig {
// and output the running sum at the end of it.
// If g1_g2_prime < 2^130, the running sum will be 0.
let g1_g2_prime = g_1.value().zip(g_2.value()).map(|(g_1, g_2)| {
let two_pow_9 = pallas::Base::from_u64(1u64 << 9);
let two_pow_9 = pallas::Base::from(1u64 << 9);
let two_pow_130 = pallas::Base::from_u128(1u128 << 65).square();
let t_p = pallas::Base::from_u128(T_P);
g_1 + (two_pow_9 * g_2) + two_pow_130 - t_p
@ -957,10 +965,10 @@ impl NoteCommitConfig {
13,
false,
)?;
let g1_g2_prime = zs[0];
let g1_g2_prime = zs[0].clone();
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z_13]
Ok((g1_g2_prime, zs[13]))
Ok((g1_g2_prime, zs[13].clone()))
}
// Check canonicity of y-coordinate given its LSB as a value.
@ -968,9 +976,9 @@ impl NoteCommitConfig {
fn y_canonicity(
&self,
mut layouter: impl Layouter<pallas::Base>,
y: CellValue<pallas::Base>,
y: AssignedCell<pallas::Base, pallas::Base>,
lsb: Option<pallas::Base>,
) -> Result<CellValue<pallas::Base>, Error> {
) -> Result<AssignedCell<pallas::Base, pallas::Base>, Error> {
// Decompose the field element
// y = LSB || k_0 || k_1 || k_2 || k_3
// = (bit 0) || (bits 1..=9) || (bits 10..=249) || (bits 250..=253) || (bit 254)
@ -1000,8 +1008,8 @@ impl NoteCommitConfig {
// Decompose j = LSB + (2)k_0 + (2^10)k_1 using 25 ten-bit lookups.
let (j, z1_j, z13_j) = {
let j = lsb.zip(k_0.value()).zip(k_1).map(|((lsb, k_0), k_1)| {
let two = pallas::Base::from_u64(2);
let two_pow_10 = pallas::Base::from_u64(1 << 10);
let two = pallas::Base::from(2);
let two_pow_10 = pallas::Base::from(1 << 10);
lsb + two * k_0 + two_pow_10 * k_1
});
let zs = self.sinsemilla_config.lookup_config.witness_check(
@ -1010,13 +1018,15 @@ impl NoteCommitConfig {
25,
true,
)?;
(zs[0], zs[1], zs[13])
(zs[0].clone(), zs[1].clone(), zs[13].clone())
};
// Decompose j_prime = j + 2^130 - t_P using 13 ten-bit lookups.
// We can reuse the canon_bitshift_130 logic here.
let (j_prime, z13_j_prime) =
self.canon_bitshift_130(layouter.namespace(|| "j_prime = j + 2^130 - t_P"), j)?;
let (j_prime, z13_j_prime) = self.canon_bitshift_130(
layouter.namespace(|| "j_prime = j + 2^130 - t_P"),
j.clone(),
)?;
/*
@ -1037,27 +1047,24 @@ impl NoteCommitConfig {
let offset = 0;
// Copy y.
copy(&mut region, || "copy y", self.advices[5], offset, &y)?;
y.copy_advice(|| "copy y", &mut region, self.advices[5], offset)?;
// Witness LSB.
let lsb = {
let cell = region.assign_advice(
|| "witness LSB",
self.advices[6],
offset,
|| lsb.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, lsb)
};
let lsb = region.assign_advice(
|| "witness LSB",
self.advices[6],
offset,
|| lsb.ok_or(Error::Synthesis),
)?;
// Witness k_0.
copy(&mut region, || "copy k_0", self.advices[7], offset, &k_0)?;
k_0.copy_advice(|| "copy k_0", &mut region, self.advices[7], offset)?;
// Copy k_2.
copy(&mut region, || "copy k_2", self.advices[8], offset, &k_2)?;
k_2.copy_advice(|| "copy k_2", &mut region, self.advices[8], offset)?;
// Witness k_3.
region.assign_advice(
|| "witness k_3",
self.advices[9],
offset,
|| k_3.ok_or(Error::SynthesisError),
|| k_3.ok_or(Error::Synthesis),
)?;
lsb
@ -1068,32 +1075,19 @@ impl NoteCommitConfig {
let offset = 1;
// Copy j.
copy(&mut region, || "copy j", self.advices[5], offset, &j)?;
j.copy_advice(|| "copy j", &mut region, self.advices[5], offset)?;
// Copy z1_j.
copy(&mut region, || "copy z1_j", self.advices[6], offset, &z1_j)?;
z1_j.copy_advice(|| "copy z1_j", &mut region, self.advices[6], offset)?;
// Copy z13_j.
copy(
&mut region,
|| "copy z13_j",
self.advices[7],
offset,
&z13_j,
)?;
z13_j.copy_advice(|| "copy z13_j", &mut region, self.advices[7], offset)?;
// Copy j_prime.
copy(
&mut region,
|| "copy j_prime",
self.advices[8],
offset,
&j_prime,
)?;
j_prime.copy_advice(|| "copy j_prime", &mut region, self.advices[8], offset)?;
// Copy z13_j_prime.
copy(
&mut region,
z13_j_prime.copy_advice(
|| "copy z13_j_prime",
&mut region,
self.advices[9],
offset,
&z13_j_prime,
)?;
}
@ -1122,20 +1116,23 @@ impl NoteCommitConfig {
|mut region| {
self.q_notecommit_b.enable(&mut region, 0)?;
copy(&mut region, || "b", col_l, 0, &gate_cells.b)?;
copy(&mut region, || "b_0", col_m, 0, &gate_cells.b_0)?;
let b_1 = {
let cell = region.assign_advice(
|| "b_1",
col_r,
0,
|| gate_cells.b_1.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, gate_cells.b_1)
};
gate_cells.b.copy_advice(|| "b", &mut region, col_l, 0)?;
gate_cells
.b_0
.copy_advice(|| "b_0", &mut region, col_m, 0)?;
let b_1 = region.assign_advice(
|| "b_1",
col_r,
0,
|| gate_cells.b_1.ok_or(Error::Synthesis),
)?;
copy(&mut region, || "b_2", col_m, 1, &gate_cells.b_2)?;
copy(&mut region, || "b_3", col_r, 1, &gate_cells.b_3)?;
gate_cells
.b_2
.copy_advice(|| "b_2", &mut region, col_m, 1)?;
gate_cells
.b_3
.copy_advice(|| "b_3", &mut region, col_r, 1)?;
Ok(b_1)
},
@ -1150,20 +1147,23 @@ impl NoteCommitConfig {
|mut region| {
self.q_notecommit_d.enable(&mut region, 0)?;
copy(&mut region, || "d", col_l, 0, &gate_cells.d)?;
let d_0 = {
let cell = region.assign_advice(
|| "d_0",
col_m,
0,
|| gate_cells.d_0.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, gate_cells.d_0)
};
copy(&mut region, || "d_1", col_r, 0, &gate_cells.d_1)?;
gate_cells.d.copy_advice(|| "d", &mut region, col_l, 0)?;
let d_0 = region.assign_advice(
|| "d_0",
col_m,
0,
|| gate_cells.d_0.ok_or(Error::Synthesis),
)?;
gate_cells
.d_1
.copy_advice(|| "d_1", &mut region, col_r, 0)?;
copy(&mut region, || "d_2", col_m, 1, &gate_cells.d_2)?;
copy(&mut region, || "d_3 = z1_d", col_r, 1, &gate_cells.z1_d)?;
gate_cells
.d_2
.copy_advice(|| "d_2", &mut region, col_m, 1)?;
gate_cells
.z1_d
.copy_advice(|| "d_3 = z1_d", &mut region, col_r, 1)?;
Ok(d_0)
},
@ -1177,9 +1177,13 @@ impl NoteCommitConfig {
|mut region| {
self.q_notecommit_e.enable(&mut region, 0)?;
copy(&mut region, || "e", col_l, 0, &gate_cells.e)?;
copy(&mut region, || "e_0", col_m, 0, &gate_cells.e_0)?;
copy(&mut region, || "e_1", col_r, 0, &gate_cells.e_1)?;
gate_cells.e.copy_advice(|| "e", &mut region, col_l, 0)?;
gate_cells
.e_0
.copy_advice(|| "e_0", &mut region, col_m, 0)?;
gate_cells
.e_1
.copy_advice(|| "e_1", &mut region, col_r, 0)?;
Ok(())
},
@ -1194,19 +1198,20 @@ impl NoteCommitConfig {
|mut region| {
self.q_notecommit_g.enable(&mut region, 0)?;
copy(&mut region, || "g", col_l, 0, &gate_cells.g)?;
let g_0 = {
let cell = region.assign_advice(
|| "g_0",
col_m,
0,
|| gate_cells.g_0.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, gate_cells.g_0)
};
gate_cells.g.copy_advice(|| "g", &mut region, col_l, 0)?;
let g_0 = region.assign_advice(
|| "g_0",
col_m,
0,
|| gate_cells.g_0.ok_or(Error::Synthesis),
)?;
copy(&mut region, || "g_1", col_l, 1, &gate_cells.g_1)?;
copy(&mut region, || "g_2 = z1_g", col_m, 1, &gate_cells.z1_g)?;
gate_cells
.g_1
.copy_advice(|| "g_1", &mut region, col_l, 1)?;
gate_cells
.z1_g
.copy_advice(|| "g_2 = z1_g", &mut region, col_m, 1)?;
Ok(g_0)
},
@ -1220,17 +1225,16 @@ impl NoteCommitConfig {
|mut region| {
self.q_notecommit_h.enable(&mut region, 0)?;
copy(&mut region, || "h", col_l, 0, &gate_cells.h)?;
copy(&mut region, || "h_0", col_m, 0, &gate_cells.h_0)?;
let h_1 = {
let cell = region.assign_advice(
|| "h_1",
col_r,
0,
|| gate_cells.h_1.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, gate_cells.h_1)
};
gate_cells.h.copy_advice(|| "h", &mut region, col_l, 0)?;
gate_cells
.h_0
.copy_advice(|| "h_0", &mut region, col_m, 0)?;
let h_1 = region.assign_advice(
|| "h_1",
col_r,
0,
|| gate_cells.h_1.ok_or(Error::Synthesis),
)?;
Ok(h_1)
},
@ -1243,22 +1247,26 @@ impl NoteCommitConfig {
layouter.assign_region(
|| "NoteCommit input g_d",
|mut region| {
copy(&mut region, || "gd_x", col_l, 0, &gate_cells.gd_x)?;
gate_cells
.gd_x
.copy_advice(|| "gd_x", &mut region, col_l, 0)?;
copy(&mut region, || "b_0", col_m, 0, &gate_cells.b_0)?;
copy(&mut region, || "b_1", col_m, 1, &b_1)?;
gate_cells
.b_0
.copy_advice(|| "b_0", &mut region, col_m, 0)?;
b_1.copy_advice(|| "b_1", &mut region, col_m, 1)?;
copy(&mut region, || "a", col_r, 0, &gate_cells.a)?;
copy(&mut region, || "a_prime", col_r, 1, &gate_cells.a_prime)?;
gate_cells.a.copy_advice(|| "a", &mut region, col_r, 0)?;
gate_cells
.a_prime
.copy_advice(|| "a_prime", &mut region, col_r, 1)?;
copy(&mut region, || "z13_a", col_z, 0, &gate_cells.z13_a)?;
copy(
&mut region,
|| "z13_a_prime",
col_z,
1,
&gate_cells.z13_a_prime,
)?;
gate_cells
.z13_a
.copy_advice(|| "z13_a", &mut region, col_z, 0)?;
gate_cells
.z13_a_prime
.copy_advice(|| "z13_a_prime", &mut region, col_z, 1)?;
self.q_notecommit_g_d.enable(&mut region, 0)
},
@ -1271,27 +1279,28 @@ impl NoteCommitConfig {
layouter.assign_region(
|| "NoteCommit input pk_d",
|mut region| {
copy(&mut region, || "pkd_x", col_l, 0, &gate_cells.pkd_x)?;
gate_cells
.pkd_x
.copy_advice(|| "pkd_x", &mut region, col_l, 0)?;
copy(&mut region, || "b_3", col_m, 0, &gate_cells.b_3)?;
copy(&mut region, || "d_0", col_m, 1, &d_0)?;
gate_cells
.b_3
.copy_advice(|| "b_3", &mut region, col_m, 0)?;
d_0.copy_advice(|| "d_0", &mut region, col_m, 1)?;
copy(&mut region, || "c", col_r, 0, &gate_cells.c)?;
copy(
&mut region,
|| "b3_c_prime",
col_r,
1,
&gate_cells.b3_c_prime,
)?;
gate_cells.c.copy_advice(|| "c", &mut region, col_r, 0)?;
gate_cells
.b3_c_prime
.copy_advice(|| "b3_c_prime", &mut region, col_r, 1)?;
copy(&mut region, || "z13_c", col_z, 0, &gate_cells.z13_c)?;
copy(
&mut region,
gate_cells
.z13_c
.copy_advice(|| "z13_c", &mut region, col_z, 0)?;
gate_cells.z14_b3_c_prime.copy_advice(
|| "z14_b3_c_prime",
&mut region,
col_z,
1,
&gate_cells.z14_b3_c_prime,
)?;
self.q_notecommit_pk_d.enable(&mut region, 0)
@ -1302,10 +1311,18 @@ impl NoteCommitConfig {
layouter.assign_region(
|| "NoteCommit input value",
|mut region| {
copy(&mut region, || "value", col_l, 0, &gate_cells.value)?;
copy(&mut region, || "d_2", col_m, 0, &gate_cells.d_2)?;
copy(&mut region, || "d3 = z1_d", col_r, 0, &gate_cells.z1_d)?;
copy(&mut region, || "e_0", col_z, 0, &gate_cells.e_0)?;
gate_cells
.value
.copy_advice(|| "value", &mut region, col_l, 0)?;
gate_cells
.d_2
.copy_advice(|| "d_2", &mut region, col_m, 0)?;
gate_cells
.z1_d
.copy_advice(|| "d3 = z1_d", &mut region, col_r, 0)?;
gate_cells
.e_0
.copy_advice(|| "e_0", &mut region, col_z, 0)?;
self.q_notecommit_value.enable(&mut region, 0)
},
@ -1318,27 +1335,28 @@ impl NoteCommitConfig {
layouter.assign_region(
|| "NoteCommit input rho",
|mut region| {
copy(&mut region, || "rho", col_l, 0, &gate_cells.rho)?;
gate_cells
.rho
.copy_advice(|| "rho", &mut region, col_l, 0)?;
copy(&mut region, || "e_1", col_m, 0, &gate_cells.e_1)?;
copy(&mut region, || "g_0", col_m, 1, &g_0)?;
gate_cells
.e_1
.copy_advice(|| "e_1", &mut region, col_m, 0)?;
g_0.copy_advice(|| "g_0", &mut region, col_m, 1)?;
copy(&mut region, || "f", col_r, 0, &gate_cells.f)?;
copy(
&mut region,
|| "e1_f_prime",
col_r,
1,
&gate_cells.e1_f_prime,
)?;
gate_cells.f.copy_advice(|| "f", &mut region, col_r, 0)?;
gate_cells
.e1_f_prime
.copy_advice(|| "e1_f_prime", &mut region, col_r, 1)?;
copy(&mut region, || "z13_f", col_z, 0, &gate_cells.z13_f)?;
copy(
&mut region,
gate_cells
.z13_f
.copy_advice(|| "z13_f", &mut region, col_z, 0)?;
gate_cells.z14_e1_f_prime.copy_advice(
|| "z14_e1_f_prime",
&mut region,
col_z,
1,
&gate_cells.z14_e1_f_prime,
)?;
self.q_notecommit_rho.enable(&mut region, 0)
@ -1352,28 +1370,33 @@ impl NoteCommitConfig {
layouter.assign_region(
|| "NoteCommit input psi",
|mut region| {
copy(&mut region, || "psi", col_l, 0, &gate_cells.psi)?;
copy(&mut region, || "h_0", col_l, 1, &gate_cells.h_0)?;
gate_cells
.psi
.copy_advice(|| "psi", &mut region, col_l, 0)?;
gate_cells
.h_0
.copy_advice(|| "h_0", &mut region, col_l, 1)?;
copy(&mut region, || "g_1", col_m, 0, &gate_cells.g_1)?;
copy(&mut region, || "h_1", col_m, 1, &h_1)?;
gate_cells
.g_1
.copy_advice(|| "g_1", &mut region, col_m, 0)?;
h_1.copy_advice(|| "h_1", &mut region, col_m, 1)?;
copy(&mut region, || "g_2 = z1_g", col_r, 0, &gate_cells.z1_g)?;
copy(
&mut region,
|| "g1_g2_prime",
col_r,
1,
&gate_cells.g1_g2_prime,
)?;
gate_cells
.z1_g
.copy_advice(|| "g_2 = z1_g", &mut region, col_r, 0)?;
gate_cells
.g1_g2_prime
.copy_advice(|| "g1_g2_prime", &mut region, col_r, 1)?;
copy(&mut region, || "z13_g", col_z, 0, &gate_cells.z13_g)?;
copy(
&mut region,
gate_cells
.z13_g
.copy_advice(|| "z13_g", &mut region, col_z, 0)?;
gate_cells.z13_g1_g2_prime.copy_advice(
|| "z13_g1_g2_prime",
&mut region,
col_z,
1,
&gate_cells.z13_g1_g2_prime,
)?;
self.q_notecommit_psi.enable(&mut region, 0)
@ -1383,46 +1406,46 @@ impl NoteCommitConfig {
}
struct GateCells {
a: CellValue<pallas::Base>,
b: CellValue<pallas::Base>,
b_0: CellValue<pallas::Base>,
a: AssignedCell<pallas::Base, pallas::Base>,
b: AssignedCell<pallas::Base, pallas::Base>,
b_0: AssignedCell<pallas::Base, pallas::Base>,
b_1: Option<pallas::Base>,
b_2: CellValue<pallas::Base>,
b_3: CellValue<pallas::Base>,
c: CellValue<pallas::Base>,
d: CellValue<pallas::Base>,
b_2: AssignedCell<pallas::Base, pallas::Base>,
b_3: AssignedCell<pallas::Base, pallas::Base>,
c: AssignedCell<pallas::Base, pallas::Base>,
d: AssignedCell<pallas::Base, pallas::Base>,
d_0: Option<pallas::Base>,
d_1: CellValue<pallas::Base>,
d_2: CellValue<pallas::Base>,
z1_d: CellValue<pallas::Base>,
e: CellValue<pallas::Base>,
e_0: CellValue<pallas::Base>,
e_1: CellValue<pallas::Base>,
f: CellValue<pallas::Base>,
g: CellValue<pallas::Base>,
d_1: AssignedCell<pallas::Base, pallas::Base>,
d_2: AssignedCell<pallas::Base, pallas::Base>,
z1_d: AssignedCell<pallas::Base, pallas::Base>,
e: AssignedCell<pallas::Base, pallas::Base>,
e_0: AssignedCell<pallas::Base, pallas::Base>,
e_1: AssignedCell<pallas::Base, pallas::Base>,
f: AssignedCell<pallas::Base, pallas::Base>,
g: AssignedCell<pallas::Base, pallas::Base>,
g_0: Option<pallas::Base>,
g_1: CellValue<pallas::Base>,
z1_g: CellValue<pallas::Base>,
h: CellValue<pallas::Base>,
h_0: CellValue<pallas::Base>,
g_1: AssignedCell<pallas::Base, pallas::Base>,
z1_g: AssignedCell<pallas::Base, pallas::Base>,
h: AssignedCell<pallas::Base, pallas::Base>,
h_0: AssignedCell<pallas::Base, pallas::Base>,
h_1: Option<pallas::Base>,
gd_x: CellValue<pallas::Base>,
pkd_x: CellValue<pallas::Base>,
value: CellValue<pallas::Base>,
rho: CellValue<pallas::Base>,
psi: CellValue<pallas::Base>,
a_prime: CellValue<pallas::Base>,
b3_c_prime: CellValue<pallas::Base>,
e1_f_prime: CellValue<pallas::Base>,
g1_g2_prime: CellValue<pallas::Base>,
z13_a_prime: CellValue<pallas::Base>,
z14_b3_c_prime: CellValue<pallas::Base>,
z14_e1_f_prime: CellValue<pallas::Base>,
z13_g1_g2_prime: CellValue<pallas::Base>,
z13_a: CellValue<pallas::Base>,
z13_c: CellValue<pallas::Base>,
z13_f: CellValue<pallas::Base>,
z13_g: CellValue<pallas::Base>,
gd_x: AssignedCell<pallas::Base, pallas::Base>,
pkd_x: AssignedCell<pallas::Base, pallas::Base>,
value: AssignedCell<pallas::Base, pallas::Base>,
rho: AssignedCell<pallas::Base, pallas::Base>,
psi: AssignedCell<pallas::Base, pallas::Base>,
a_prime: AssignedCell<pallas::Base, pallas::Base>,
b3_c_prime: AssignedCell<pallas::Base, pallas::Base>,
e1_f_prime: AssignedCell<pallas::Base, pallas::Base>,
g1_g2_prime: AssignedCell<pallas::Base, pallas::Base>,
z13_a_prime: AssignedCell<pallas::Base, pallas::Base>,
z14_b3_c_prime: AssignedCell<pallas::Base, pallas::Base>,
z14_e1_f_prime: AssignedCell<pallas::Base, pallas::Base>,
z13_g1_g2_prime: AssignedCell<pallas::Base, pallas::Base>,
z13_a: AssignedCell<pallas::Base, pallas::Base>,
z13_c: AssignedCell<pallas::Base, pallas::Base>,
z13_f: AssignedCell<pallas::Base, pallas::Base>,
z13_g: AssignedCell<pallas::Base, pallas::Base>,
}
#[cfg(test)]
@ -1435,9 +1458,7 @@ mod tests {
NonIdentityPoint,
},
sinsemilla::chip::SinsemillaChip,
utilities::{
lookup_range_check::LookupRangeCheckConfig, CellValue, UtilitiesInstructions,
},
utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions},
},
constants::{L_ORCHARD_BASE, L_VALUE, NOTE_COMMITMENT_PERSONALIZATION, T_Q},
primitives::sinsemilla::CommitDomain,
@ -1446,7 +1467,7 @@ mod tests {
use ff::{Field, PrimeField, PrimeFieldBits};
use group::Curve;
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{AssignedCell, Layouter, SimpleFloorPlanner},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
@ -1471,7 +1492,7 @@ mod tests {
}
impl UtilitiesInstructions<pallas::Base> for MyCircuit {
type Var = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
impl Circuit<pallas::Base> for MyCircuit {
@ -1528,7 +1549,7 @@ mod tests {
advices[2],
lagrange_coeffs[0],
lookup,
range_check.clone(),
range_check,
);
let note_commit_config =
NoteCommitConfig::configure(meta, advices, sinsemilla_config);
@ -1595,7 +1616,7 @@ mod tests {
// A note value cannot be negative.
let value = {
let mut rng = OsRng;
pallas::Base::from_u64(rng.next_u64())
pallas::Base::from(rng.next_u64())
};
let value_var = {
self.load_private(

View File

@ -1,45 +1,42 @@
//! Utility gadgets.
use ff::PrimeFieldBits;
use halo2::{
circuit::{Cell, Layouter, Region},
circuit::{AssignedCell, Cell, Layouter},
plonk::{Advice, Column, Error, Expression},
};
use pasta_curves::arithmetic::FieldExt;
use std::{array, convert::TryInto, ops::Range};
use std::{array, ops::Range};
pub(crate) mod cond_swap;
pub(crate) mod decompose_running_sum;
pub(crate) mod lookup_range_check;
/// A variable representing a field element.
#[derive(Copy, Clone, Debug)]
pub struct CellValue<F: FieldExt> {
cell: Cell,
value: Option<F>,
}
pub trait Var<F: FieldExt>: Copy + Clone + std::fmt::Debug {
fn new(cell: Cell, value: Option<F>) -> Self;
/// 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 CellValue<F> {
fn new(cell: Cell, value: Option<F>) -> Self {
Self { cell, value }
}
impl<F: FieldExt> Var<F> for AssignedCell<F, F> {
fn cell(&self) -> Cell {
self.cell
self.cell()
}
fn value(&self) -> Option<F> {
self.value
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>,
@ -49,46 +46,20 @@ pub trait UtilitiesInstructions<F: FieldExt> {
layouter.assign_region(
|| "load private",
|mut region| {
let cell = region.assign_advice(
|| "load private",
column,
0,
|| value.ok_or(Error::SynthesisError),
)?;
Ok(Var::new(cell, value))
region
.assign_advice(
|| "load private",
column,
0,
|| value.ok_or(Error::Synthesis),
)
.map(Self::Var::from)
},
)
}
}
/// Assigns a cell at a specific offset within the given region, constraining it
/// to the same value as another cell (which may be in any region).
///
/// Returns an error if either `column` or `copy` is not in a column that was passed to
/// [`ConstraintSystem::enable_equality`] during circuit configuration.
///
/// [`ConstraintSystem::enable_equality`]: halo2::plonk::ConstraintSystem::enable_equality
pub fn copy<A, AR, F: FieldExt>(
region: &mut Region<'_, F>,
annotation: A,
column: Column<Advice>,
offset: usize,
copy: &CellValue<F>,
) -> Result<CellValue<F>, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
let cell = region.assign_advice(annotation, column, offset, || {
copy.value.ok_or(Error::SynthesisError)
})?;
region.constrain_equal(cell, copy.cell)?;
Ok(CellValue::new(cell, copy.value))
}
pub fn transpose_option_array<T: Copy + std::fmt::Debug, const LEN: usize>(
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];
@ -102,36 +73,45 @@ pub fn transpose_option_array<T: Copy + std::fmt::Debug, const LEN: usize>(
/// Checks that an expresssion is either 1 or 0.
pub fn bool_check<F: FieldExt>(value: Expression<F>) -> Expression<F> {
value.clone() * (Expression::Constant(F::one()) - value)
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: FieldExt + PrimeFieldBits>(field_elem: F, bitrange: Range<usize>) -> F {
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);
let bits: Vec<bool> = field_elem
field_elem
.to_le_bits()
.iter()
.by_val()
.skip(bitrange.start)
.take(bitrange.end - bitrange.start)
.chain(std::iter::repeat(false))
.take(256)
.collect();
let bytearray: Vec<u8> = bits
.chunks_exact(8)
.map(|byte| byte.iter().rev().fold(0u8, |acc, bit| acc * 2 + *bit as u8))
.collect();
F::from_bytes(&bytearray.try_into().unwrap()).unwrap()
.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 * (word.clone() - Expression::Constant(F::from_u64(i as u64)))
acc * (Expression::Constant(F::from(i as u64)) - word.clone())
})
}
@ -143,7 +123,7 @@ mod tests {
use halo2::{
circuit::{Layouter, SimpleFloorPlanner},
dev::{MockProver, VerifyFailure},
plonk::{Circuit, ConstraintSystem, Error, Selector},
plonk::{Any, Circuit, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::pallas;
@ -153,7 +133,7 @@ mod tests {
struct MyCircuit<const RANGE: usize>(u8);
impl<const RANGE: usize> UtilitiesInstructions<pallas::Base> for MyCircuit<RANGE> {
type Var = CellValue<pallas::Base>;
type Var = AssignedCell<pallas::Base, pallas::Base>;
}
#[derive(Clone)]
@ -197,7 +177,7 @@ mod tests {
|| format!("witness {}", self.0),
config.advice,
0,
|| Ok(pallas::Base::from_u64(self.0.into())),
|| Ok(pallas::Base::from(self.0 as u64)),
)?;
Ok(())
@ -219,7 +199,8 @@ mod tests {
prover.verify(),
Err(vec![VerifyFailure::ConstraintNotSatisfied {
constraint: ((0, "range check").into(), 0, "").into(),
row: 0
row: 0,
cell_values: vec![(((Any::Advice, 0).into(), 0).into(), "0x8".to_string())],
}])
);
}
@ -231,7 +212,7 @@ mod tests {
{
let field_elem = pallas::Base::rand();
let bitrange = 0..(pallas::Base::NUM_BITS as usize);
let subset = bitrange_subset(field_elem, bitrange);
let subset = bitrange_subset(&field_elem, bitrange);
assert_eq!(field_elem, subset);
}
@ -239,7 +220,7 @@ mod tests {
{
let field_elem = pallas::Base::rand();
let bitrange = 0..0;
let subset = bitrange_subset(field_elem, bitrange);
let subset = bitrange_subset(&field_elem, bitrange);
assert_eq!(pallas::Base::zero(), subset);
}
@ -266,7 +247,7 @@ mod tests {
let subsets = ranges
.iter()
.map(|range| bitrange_subset(field_elem, range.clone()))
.map(|range| bitrange_subset(&field_elem, range.clone()))
.collect::<Vec<_>>();
let mut sum = subsets[0];
@ -281,7 +262,7 @@ mod tests {
.to_little_endian(&mut range_shift);
range_shift
};
sum += subset * pallas::Base::from_bytes(&range_shift).unwrap();
sum += subset * pallas::Base::from_repr(range_shift).unwrap();
}
assert_eq!(field_elem, sum);
};

View File

@ -1,7 +1,7 @@
use super::{copy, CellValue, UtilitiesInstructions, Var};
use super::{bool_check, ternary, UtilitiesInstructions};
use halo2::{
circuit::{Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
circuit::{AssignedCell, Chip, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use pasta_curves::arithmetic::FieldExt;
@ -53,7 +53,7 @@ pub struct CondSwapConfig {
}
impl<F: FieldExt> UtilitiesInstructions<F> for CondSwapChip<F> {
type Var = CellValue<F>;
type Var = AssignedCell<F, F>;
}
impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
@ -73,64 +73,55 @@ impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
config.q_swap.enable(&mut region, 0)?;
// Copy in `a` value
let a = copy(&mut region, || "copy a", config.a, 0, &pair.0)?;
let a = pair.0.copy_advice(|| "copy a", &mut region, config.a, 0)?;
// Witness `b` value
let b = {
let cell = region.assign_advice(
|| "witness b",
config.b,
0,
|| pair.1.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, pair.1)
};
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_u64(swap as u64));
let swap_val = swap.map(|swap| F::from(swap as u64));
region.assign_advice(
|| "swap",
config.swap,
0,
|| swap_val.ok_or(Error::SynthesisError),
|| swap_val.ok_or(Error::Synthesis),
)?;
// Conditionally swap a
let a_swapped = {
let a_swapped = a
.value
.zip(b.value)
.value()
.zip(b.value())
.zip(swap)
.map(|((a, b), swap)| if swap { b } else { a });
let a_swapped_cell = region.assign_advice(
.map(|((a, b), swap)| if swap { b } else { a })
.cloned();
region.assign_advice(
|| "a_swapped",
config.a_swapped,
0,
|| a_swapped.ok_or(Error::SynthesisError),
)?;
CellValue {
cell: a_swapped_cell,
value: a_swapped,
}
|| a_swapped.ok_or(Error::Synthesis),
)?
};
// Conditionally swap b
let b_swapped = {
let b_swapped = a
.value
.zip(b.value)
.value()
.zip(b.value())
.zip(swap)
.map(|((a, b), swap)| if swap { a } else { b });
let b_swapped_cell = region.assign_advice(
.map(|((a, b), swap)| if swap { a } else { b })
.cloned();
region.assign_advice(
|| "b_swapped",
config.b_swapped,
0,
|| b_swapped.ok_or(Error::SynthesisError),
)?;
CellValue {
cell: b_swapped_cell,
value: b_swapped,
}
|| b_swapped.ok_or(Error::Synthesis),
)?
};
// Return swapped pair
@ -176,21 +167,16 @@ impl<F: FieldExt> CondSwapChip<F> {
let b_swapped = meta.query_advice(config.b_swapped, Rotation::cur());
let swap = meta.query_advice(config.swap, Rotation::cur());
let one = Expression::Constant(F::one());
// a_swapped - b ⋅ swap - a ⋅ (1-swap) = 0
// This checks that `a_swapped` is equal to `y` when `swap` is set,
// 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 - b.clone() * swap.clone() - a.clone() * (one.clone() - swap.clone());
let a_check = a_swapped - ternary(swap.clone(), b.clone(), a.clone());
// b_swapped - a ⋅ swap - b ⋅ (1-swap) = 0
// 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 - a * swap.clone() - b * (one.clone() - swap.clone());
let b_check = b_swapped - ternary(swap.clone(), a, b);
// Check `swap` is boolean.
let bool_check = swap.clone() * (one - swap);
let bool_check = bool_check(swap);
array::IntoIter::new([a_check, b_check, bool_check])
.map(move |poly| q_swap.clone() * poly)
@ -257,18 +243,21 @@ mod tests {
// 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, self.b), self.swap)?;
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());
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());
assert_eq!(swapped_pair.0.value().unwrap(), a.value().unwrap());
assert_eq!(swapped_pair.1.value().unwrap(), &self.b.unwrap());
}
}

View File

@ -24,29 +24,29 @@
use ff::PrimeFieldBits;
use halo2::{
circuit::Region,
circuit::{AssignedCell, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
};
use super::{copy, range_check, CellValue, Var};
use super::range_check;
use crate::constants::util::decompose_word;
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<CellValue<F>>);
pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<AssignedCell<F, F>>);
impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
type Target = Vec<CellValue<F>>;
type Target = Vec<AssignedCell<F, F>>;
fn deref(&self) -> &Vec<CellValue<F>> {
fn deref(&self) -> &Vec<AssignedCell<F, F>> {
&self.0
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct RunningSumConfig<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize> {
q_range_check: Selector,
pub q_range_check: Selector,
pub z: Column<Advice>,
_marker: PhantomData<F>,
}
@ -84,7 +84,7 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
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_u64(1 << WINDOW_NUM_BITS);
let word = z_cur - z_next * F::from(1 << WINDOW_NUM_BITS);
vec![q_range_check * range_check(word, 1 << WINDOW_NUM_BITS)]
});
@ -105,15 +105,12 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
word_num_bits: usize,
num_windows: usize,
) -> Result<RunningSum<F>, Error> {
let z_0 = {
let cell = region.assign_advice(
|| "z_0 = alpha",
self.z,
offset,
|| alpha.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, alpha)
};
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)
}
@ -125,12 +122,12 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
&self,
region: &mut Region<'_, F>,
offset: usize,
alpha: CellValue<F>,
alpha: AssignedCell<F, F>,
strict: bool,
word_num_bits: usize,
num_windows: usize,
) -> Result<RunningSum<F>, Error> {
let z_0 = copy(region, || "copy z_0 = alpha", self.z, offset, &alpha)?;
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)
}
@ -143,7 +140,7 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
&self,
region: &mut Region<'_, F>,
offset: usize,
z_0: CellValue<F>,
z_0: AssignedCell<F, F>,
strict: bool,
word_num_bits: usize,
num_windows: usize,
@ -179,33 +176,32 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
};
// Initialize empty vector to store running sum values [z_0, ..., z_W].
let mut zs: Vec<CellValue<F>> = vec![z_0];
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_u64(1 << WINDOW_NUM_BITS as u64).invert().unwrap();
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_u64(word as u64));
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);
let cell = region.assign_advice(
.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::SynthesisError),
)?;
CellValue::new(cell, z_next_val)
|| z_next_val.ok_or(Error::Synthesis),
)?
};
// Update `z`.
z = z_next;
zs.push(z);
zs.push(z.clone());
}
assert_eq!(zs.len(), num_windows + 1);
@ -284,7 +280,7 @@ mod tests {
WORD_NUM_BITS,
NUM_WINDOWS,
)?;
let alpha = zs[0];
let alpha = zs[0].clone();
let offset = offset + NUM_WINDOWS + 1;
@ -323,7 +319,7 @@ mod tests {
// Random 64-bit word
{
let alpha = pallas::Base::from_u64(rand::random());
let alpha = pallas::Base::from(rand::random::<u64>());
// Strict full decomposition should pass.
let circuit: MyCircuit<

View File

@ -3,7 +3,7 @@
use crate::spec::lebs2ip;
use halo2::{
circuit::{Layouter, Region},
circuit::{AssignedCell, Layouter, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Selector, TableColumn},
poly::Rotation,
};
@ -14,16 +14,16 @@ 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<CellValue<F>>);
pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<AssignedCell<F, F>>);
impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
type Target = Vec<CellValue<F>>;
type Target = Vec<AssignedCell<F, F>>;
fn deref(&self) -> &Vec<CellValue<F>> {
fn deref(&self) -> &Vec<AssignedCell<F, F>> {
&self.0
}
}
#[derive(Eq, PartialEq, Debug, Clone)]
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
pub struct LookupRangeCheckConfig<F: FieldExt + PrimeFieldBits, const K: usize> {
pub q_lookup: Selector,
pub q_running: Selector,
@ -76,7 +76,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
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_u64(1 << K)
z_cur.clone() - z_next * F::from(1 << K)
};
q_running.clone() * running_sum_word
@ -104,7 +104,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
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_u64(1 << K);
let two_pow_k = F::from(1 << K);
// shifted_word = word * 2^{K-s}
// = word * 2^K * inv_two_pow_s
@ -128,7 +128,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|| "table_idx",
self.table_idx,
index,
|| Ok(F::from_u64(index as u64)),
|| Ok(F::from(index as u64)),
)?;
}
Ok(())
@ -143,7 +143,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
pub fn copy_check(
&self,
mut layouter: impl Layouter<F>,
element: CellValue<F>,
element: AssignedCell<F, F>,
num_words: usize,
strict: bool,
) -> Result<RunningSum<F>, Error> {
@ -151,7 +151,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|| format!("{:?} words range check", num_words),
|mut region| {
// Copy `element` and initialize running sum `z_0 = element` to decompose it.
let z_0 = copy(&mut region, || "z_0", self.running_sum, 0, &element)?;
let z_0 = element.copy_advice(|| "z_0", &mut region, self.running_sum, 0)?;
self.range_check(&mut region, z_0, num_words, strict)
},
)
@ -168,15 +168,12 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
layouter.assign_region(
|| "Witness element",
|mut region| {
let z_0 = {
let cell = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| value.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, value)
};
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)
},
)
@ -192,7 +189,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
fn range_check(
&self,
region: &mut Region<'_, F>,
element: CellValue<F>,
element: AssignedCell<F, F>,
num_words: usize,
strict: bool,
) -> Result<RunningSum<F>, Error> {
@ -213,7 +210,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
let words: Option<Vec<F>> = bits.map(|bits| {
bits.chunks_exact(K)
.map(|word| F::from_u64(lebs2ip::<K>(&(word.try_into().unwrap()))))
.map(|word| F::from(lebs2ip::<K>(&(word.try_into().unwrap()))))
.collect::<Vec<_>>()
});
if let Some(words) = words {
@ -223,7 +220,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
}
};
let mut zs = vec![element];
let mut zs = vec![element.clone()];
// Assign cumulative sum such that
// z_i = 2^{K}⋅z_{i + 1} + a_i
@ -232,7 +229,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, 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_u64(1u64 << K).invert().unwrap();
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)?;
@ -244,19 +241,17 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
let z_val = z
.value()
.zip(*word)
.map(|(z, word)| (z - word) * inv_two_pow_k);
.map(|(z, word)| (*z - word) * inv_two_pow_k);
// Assign z_next
let z_cell = region.assign_advice(
region.assign_advice(
|| format!("z_{:?}", idx + 1),
self.running_sum,
idx + 1,
|| z_val.ok_or(Error::SynthesisError),
)?;
CellValue::new(z_cell, z_val)
|| z_val.ok_or(Error::Synthesis),
)?
};
zs.push(z);
zs.push(z.clone());
}
if strict {
@ -275,7 +270,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
pub fn copy_short_check(
&self,
mut layouter: impl Layouter<F>,
element: CellValue<F>,
element: AssignedCell<F, F>,
num_bits: usize,
) -> Result<(), Error> {
assert!(num_bits < K);
@ -283,7 +278,8 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|| format!("Range check {:?} bits", num_bits),
|mut region| {
// Copy `element` to use in the k-bit lookup.
let element = copy(&mut region, || "element", self.running_sum, 0, &element)?;
let element =
element.copy_advice(|| "element", &mut region, self.running_sum, 0)?;
self.short_range_check(&mut region, element, num_bits)
},
@ -300,23 +296,20 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
mut layouter: impl Layouter<F>,
element: Option<F>,
num_bits: usize,
) -> Result<CellValue<F>, Error> {
) -> 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 = {
let cell = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| element.ok_or(Error::SynthesisError),
)?;
CellValue::new(cell, element)
};
let element = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| element.ok_or(Error::Synthesis),
)?;
self.short_range_check(&mut region, element, num_bits)?;
self.short_range_check(&mut region, element.clone(), num_bits)?;
Ok(element)
},
@ -329,7 +322,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
fn short_range_check(
&self,
region: &mut Region<'_, F>,
element: CellValue<F>,
element: AssignedCell<F, F>,
num_bits: usize,
) -> Result<(), Error> {
// Enable lookup for `element`, to constrain it to 10 bits.
@ -343,19 +336,19 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
// Assign shifted `element * 2^{K - num_bits}`
let shifted = element.value().map(|element| {
let shift = F::from_u64(1 << (K - num_bits));
element * shift
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::SynthesisError),
|| shifted.ok_or(Error::Synthesis),
)?;
// Assign 2^{-num_bits} from a fixed column.
let inv_two_pow_s = F::from_u64(1 << num_bits).invert().unwrap();
let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap();
region.assign_advice_from_constant(
|| format!("2^(-{})", num_bits),
self.running_sum,
@ -369,10 +362,9 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
#[cfg(test)]
mod tests {
use super::super::Var;
use super::LookupRangeCheckConfig;
use crate::primitives::sinsemilla::{INV_TWO_POW_K, K};
use crate::primitives::sinsemilla::K;
use crate::spec::lebs2ip;
use ff::{Field, PrimeFieldBits};
use halo2::{
@ -419,12 +411,8 @@ mod tests {
// Lookup constraining element to be no longer than num_words * K bits.
let elements_and_expected_final_zs = [
(
F::from_u64((1 << (self.num_words * K)) - 1),
F::zero(),
true,
), // a word that is within self.num_words * K bits long
(F::from_u64(1 << (self.num_words * K)), F::one(), false), // a word that is just over self.num_words * K bits long
(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>(
@ -439,11 +427,11 @@ mod tests {
.take(num_words * K)
.collect::<Vec<_>>()
.chunks_exact(K)
.map(|chunk| F::from_u64(lebs2ip::<K>(chunk.try_into().unwrap())))
.map(|chunk| F::from(lebs2ip::<K>(chunk.try_into().unwrap())))
.collect::<Vec<_>>()
};
let expected_zs = {
let inv_two_pow_k = F::from_bytes(&INV_TWO_POW_K).unwrap();
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;
@ -468,7 +456,7 @@ mod tests {
for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) {
if let Some(z) = z.value() {
assert_eq!(expected_z, z);
assert_eq!(&expected_z, z);
}
}
}
@ -546,7 +534,7 @@ mod tests {
// Edge case: K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from_u64((1 << K) - 1)),
element: Some(pallas::Base::from((1 << K) - 1)),
num_bits: K,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@ -556,7 +544,7 @@ mod tests {
// Element within `num_bits`
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from_u64((1 << 6) - 1)),
element: Some(pallas::Base::from((1 << 6) - 1)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@ -566,7 +554,7 @@ mod tests {
// Element larger than `num_bits` but within K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from_u64(1 << 6)),
element: Some(pallas::Base::from(1 << 6)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@ -582,7 +570,7 @@ mod tests {
// Element larger than K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from_u64(1 << K)),
element: Some(pallas::Base::from(1 << K)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@ -605,11 +593,11 @@ mod tests {
// num_bits
{
let num_bits = 6;
let shifted = pallas::Base::from_u64((1 << num_bits) - 1);
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_u64(1 << (K as u64 - num_bits))
* pallas::Base::from(1 << (K as u64 - num_bits))
.invert()
.unwrap();
let circuit: MyCircuit<pallas::Base> = MyCircuit {

File diff suppressed because it is too large Load Diff

View File

@ -100,8 +100,8 @@ fn compute_window_table<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C;
(0..H)
.map(|k| {
// scalar = (k+2)*(8^w)
let scalar = C::ScalarExt::from_u64(k as u64 + 2)
* C::ScalarExt::from_u64(H as u64).pow(&[w as u64, 0, 0, 0]);
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>>()
@ -114,19 +114,14 @@ fn compute_window_table<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C;
// 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::ScalarExt::zero(), |acc, j| {
acc + C::ScalarExt::from_u64(2).pow(&[
FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1,
0,
0,
0,
])
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::ScalarExt::from_u64(k as u64)
* C::ScalarExt::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
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()
})
@ -142,7 +137,7 @@ fn compute_window_table<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C;
/// Here, we pre-compute and store the coefficients of the interpolation polynomial.
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_u64(i as u64)).collect();
let points: Vec<_> = (0..H).map(|i| C::Base::from(i as u64)).collect();
let window_table = compute_window_table(base, num_windows);
@ -183,8 +178,8 @@ fn find_zs_and_us<C: CurveAffine>(base: C, num_windows: usize) -> Option<Vec<(u6
(0..(1000 * (1 << (2 * H)))).find_map(|z| {
ys.iter()
.map(|&y| {
if (-y + C::Base::from_u64(z)).sqrt().is_none().into() {
(y + C::Base::from_u64(z)).sqrt().into()
if (-y + C::Base::from(z)).sqrt().is_none().into() {
(y + C::Base::from(z)).sqrt().into()
} else {
None
}
@ -217,8 +212,8 @@ fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
// Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B.
let point = base
* C::Scalar::from_u64(bits as u64 + 2)
* C::Scalar::from_u64(H as u64).pow(&[idx as u64, 0, 0, 0]);
* 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.
@ -235,15 +230,10 @@ fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
// 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_u64(2).pow(&[
FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1,
0,
0,
0,
])
acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0])
});
let scalar = C::Scalar::from_u64(bits as u64)
* C::Scalar::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 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();
@ -258,15 +248,15 @@ fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
// 1. z + y = u^2,
// 2. z - y is not a square
// for the y-coordinate of each fixed-base multiple in each window.
fn test_zs_and_us<C: CurveAffine>(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) {
fn test_zs_and_us(base: pallas::Affine, 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()));
let u = pallas::Base::from_repr(*u).unwrap();
assert_eq!(pallas::Base::from(*z) + y, u * u); // allow either square root
assert!(bool::from((pallas::Base::from(*z) - y).sqrt().is_none()));
}
}
}

View File

@ -1,7 +1,5 @@
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
/// Generator used in SinsemillaCommit randomness for IVK commitment
pub const GENERATOR: ([u8; 32], [u8; 32]) = (
@ -2922,8 +2920,8 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [
pub fn generator() -> pallas::Affine {
pallas::Affine::from_xy(
pallas::Base::from_bytes(&GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&GENERATOR.1).unwrap(),
pallas::Base::from_repr(GENERATOR.0).unwrap(),
pallas::Base::from_repr(GENERATOR.1).unwrap(),
)
.unwrap()
}
@ -2936,10 +2934,7 @@ mod tests {
use super::*;
use crate::primitives::sinsemilla::CommitDomain;
use group::Curve;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
#[test]
fn generator() {
@ -2947,8 +2942,8 @@ mod tests {
let point = domain.R();
let coords = point.to_affine().coordinates().unwrap();
assert_eq!(*coords.x(), pallas::Base::from_bytes(&GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_bytes(&GENERATOR.1).unwrap());
assert_eq!(*coords.x(), pallas::Base::from_repr(GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_repr(GENERATOR.1).unwrap());
}
#[test]

View File

@ -1,7 +1,8 @@
use std::convert::TryInto;
use crate::constants::{self, compute_lagrange_coeffs, H, NUM_WINDOWS, NUM_WINDOWS_SHORT};
use pasta_curves::{arithmetic::FieldExt, pallas};
use group::ff::PrimeField;
use pasta_curves::pallas;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OrchardFixedBasesFull {
@ -177,7 +178,7 @@ impl From<[u64; NUM_WINDOWS]> for Z {
fn from(zs: [u64; NUM_WINDOWS]) -> Self {
Self(
zs.iter()
.map(|z| pallas::Base::from_u64(*z))
.map(|z| pallas::Base::from(*z))
.collect::<Vec<_>>()
.into_boxed_slice()
.try_into()
@ -194,7 +195,7 @@ impl From<[u64; NUM_WINDOWS_SHORT]> for ZShort {
fn from(zs: [u64; NUM_WINDOWS_SHORT]) -> Self {
Self(
zs.iter()
.map(|z| pallas::Base::from_u64(*z))
.map(|z| pallas::Base::from(*z))
.collect::<Vec<_>>()
.into_boxed_slice()
.try_into()
@ -212,7 +213,7 @@ impl From<&[[u8; 32]; H]> for WindowUs {
Self(
window_us
.iter()
.map(|u| pallas::Base::from_bytes(u).unwrap())
.map(|u| pallas::Base::from_repr(*u).unwrap())
.collect::<Vec<_>>()
.into_boxed_slice()
.try_into()

View File

@ -1,7 +1,5 @@
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
/// Generator used in SinsemillaCommit randomness for note commitment
pub const GENERATOR: ([u8; 32], [u8; 32]) = (
@ -2922,8 +2920,8 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [
pub fn generator() -> pallas::Affine {
pallas::Affine::from_xy(
pallas::Base::from_bytes(&GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&GENERATOR.1).unwrap(),
pallas::Base::from_repr(GENERATOR.0).unwrap(),
pallas::Base::from_repr(GENERATOR.1).unwrap(),
)
.unwrap()
}
@ -2936,10 +2934,7 @@ mod tests {
use super::*;
use crate::primitives::sinsemilla::CommitDomain;
use group::Curve;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
#[test]
fn generator() {
@ -2947,8 +2942,8 @@ mod tests {
let point = domain.R();
let coords = point.to_affine().coordinates().unwrap();
assert_eq!(*coords.x(), pallas::Base::from_bytes(&GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_bytes(&GENERATOR.1).unwrap());
assert_eq!(*coords.x(), pallas::Base::from_repr(GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_repr(GENERATOR.1).unwrap());
}
#[test]

View File

@ -1,7 +1,5 @@
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
pub const GENERATOR: ([u8; 32], [u8; 32]) = (
[
@ -2921,8 +2919,8 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [
pub fn generator() -> pallas::Affine {
pallas::Affine::from_xy(
pallas::Base::from_bytes(&GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&GENERATOR.1).unwrap(),
pallas::Base::from_repr(GENERATOR.0).unwrap(),
pallas::Base::from_repr(GENERATOR.1).unwrap(),
)
.unwrap()
}
@ -2934,10 +2932,7 @@ mod tests {
};
use super::*;
use group::Curve;
use pasta_curves::{
arithmetic::{CurveExt, FieldExt},
pallas,
};
use pasta_curves::{arithmetic::CurveExt, pallas};
#[test]
fn generator() {
@ -2945,8 +2940,8 @@ mod tests {
let point = hasher(b"K");
let coords = point.to_affine().coordinates().unwrap();
assert_eq!(*coords.x(), pallas::Base::from_bytes(&GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_bytes(&GENERATOR.1).unwrap());
assert_eq!(*coords.x(), pallas::Base::from_repr(GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_repr(GENERATOR.1).unwrap());
}
#[test]

View File

@ -1,7 +1,5 @@
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
/// The value commitment is used to check balance between inputs and outputs. The value is
/// placed over this generator.
@ -2923,8 +2921,8 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [
pub fn generator() -> pallas::Affine {
pallas::Affine::from_xy(
pallas::Base::from_bytes(&GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&GENERATOR.1).unwrap(),
pallas::Base::from_repr(GENERATOR.0).unwrap(),
pallas::Base::from_repr(GENERATOR.1).unwrap(),
)
.unwrap()
}
@ -2937,7 +2935,7 @@ mod tests {
use super::*;
use group::Curve;
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt, FieldExt},
arithmetic::{CurveAffine, CurveExt},
pallas,
};
@ -2947,8 +2945,8 @@ mod tests {
let point = hasher(b"G");
let coords = point.to_affine().coordinates().unwrap();
assert_eq!(*coords.x(), pallas::Base::from_bytes(&GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_bytes(&GENERATOR.1).unwrap());
assert_eq!(*coords.x(), pallas::Base::from_repr(GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_repr(GENERATOR.1).unwrap());
}
#[test]

View File

@ -1,5 +1,5 @@
use ff::{Field, PrimeFieldBits};
use halo2::arithmetic::{CurveAffine, FieldExt};
use halo2::arithmetic::CurveAffine;
/// 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`
@ -10,7 +10,7 @@ use halo2::arithmetic::{CurveAffine, FieldExt};
/// 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: &F,
word_num_bits: usize,
window_num_bits: usize,
) -> Vec<u8> {
@ -33,7 +33,7 @@ pub fn decompose_word<F: PrimeFieldBits>(
/// Evaluate y = f(x) given the coefficients of f(x)
pub fn evaluate<C: CurveAffine>(x: u8, coeffs: &[C::Base]) -> C::Base {
let x = C::Base::from_u64(x as u64);
let x = C::Base::from(x as u64);
coeffs
.iter()
.rev()
@ -86,7 +86,7 @@ mod tests {
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);
let decomposed = decompose_word(&scalar, pallas::Scalar::NUM_BITS as usize, window_num_bits as usize);
// Flatten bits
let bits = decomposed
@ -102,7 +102,7 @@ mod tests {
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_bytes(&bytes.try_into().unwrap()).unwrap());
assert_eq!(scalar, pallas::Scalar::from_repr(bytes.try_into().unwrap()).unwrap());
}
}
}

View File

@ -1,7 +1,5 @@
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
/// The value commitment is used to check balance between inputs and outputs. The value is
/// placed over this generator.
@ -2923,8 +2921,8 @@ pub const U: [[[u8; 32]; super::H]; super::NUM_WINDOWS] = [
pub fn generator() -> pallas::Affine {
pallas::Affine::from_xy(
pallas::Base::from_bytes(&GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&GENERATOR.1).unwrap(),
pallas::Base::from_repr(GENERATOR.0).unwrap(),
pallas::Base::from_repr(GENERATOR.1).unwrap(),
)
.unwrap()
}
@ -2937,7 +2935,7 @@ mod tests {
use super::*;
use group::Curve;
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt, FieldExt},
arithmetic::{CurveAffine, CurveExt},
pallas,
};
@ -2947,8 +2945,8 @@ mod tests {
let point = hasher(b"r");
let coords = point.to_affine().coordinates().unwrap();
assert_eq!(*coords.x(), pallas::Base::from_bytes(&GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_bytes(&GENERATOR.1).unwrap());
assert_eq!(*coords.x(), pallas::Base::from_repr(GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_repr(GENERATOR.1).unwrap());
}
#[test]

View File

@ -1,7 +1,5 @@
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
};
use group::ff::PrimeField;
use pasta_curves::{arithmetic::CurveAffine, pallas};
/// The value commitment is used to check balance between inputs and outputs. The value is
/// placed over this generator.
@ -776,8 +774,8 @@ pub const U_SHORT: [[[u8; 32]; super::H]; super::NUM_WINDOWS_SHORT] = [
pub fn generator() -> pallas::Affine {
pallas::Affine::from_xy(
pallas::Base::from_bytes(&GENERATOR.0).unwrap(),
pallas::Base::from_bytes(&GENERATOR.1).unwrap(),
pallas::Base::from_repr(GENERATOR.0).unwrap(),
pallas::Base::from_repr(GENERATOR.1).unwrap(),
)
.unwrap()
}
@ -790,7 +788,7 @@ mod tests {
use super::*;
use group::Curve;
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt, FieldExt},
arithmetic::{CurveAffine, CurveExt},
pallas,
};
@ -800,8 +798,8 @@ mod tests {
let point = hasher(b"v");
let coords = point.to_affine().coordinates().unwrap();
assert_eq!(*coords.x(), pallas::Base::from_bytes(&GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_bytes(&GENERATOR.1).unwrap());
assert_eq!(*coords.x(), pallas::Base::from_repr(GENERATOR.0).unwrap());
assert_eq!(*coords.y(), pallas::Base::from_repr(GENERATOR.1).unwrap());
}
#[test]

View File

@ -7,8 +7,11 @@ use std::mem;
use aes::Aes256;
use blake2b_simd::{Hash as Blake2bHash, Params};
use fpe::ff1::{BinaryNumeralString, FF1};
use group::{ff::Field, prime::PrimeCurveAffine, Curve, GroupEncoding};
use halo2::arithmetic::FieldExt;
use group::{
ff::{Field, PrimeField},
prime::PrimeCurveAffine,
Curve, GroupEncoding,
};
use pasta_curves::pallas;
use rand::RngCore;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
@ -124,10 +127,10 @@ impl From<&SpendingKey> for SpendAuthorizingKey {
// SpendingKey cannot be constructed such that this assertion would fail.
assert!(!bool::from(ask.is_zero()));
// TODO: Add TryFrom<S::Scalar> for SpendAuthorizingKey.
let ret = SpendAuthorizingKey(ask.to_bytes().try_into().unwrap());
let ret = SpendAuthorizingKey(ask.to_repr().try_into().unwrap());
// If the last bit of repr_P(ak) is 1, negate ask.
if (<[u8; 32]>::from(SpendValidatingKey::from(&ret).0)[31] >> 7) == 1 {
SpendAuthorizingKey((-ask).to_bytes().try_into().unwrap())
SpendAuthorizingKey((-ask).to_repr().try_into().unwrap())
} else {
ret
}
@ -230,7 +233,7 @@ impl NullifierDerivingKey {
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
let nk_bytes = <[u8; 32]>::try_from(bytes).ok()?;
let nk = pallas::Base::from_bytes(&nk_bytes).map(NullifierDerivingKey);
let nk = pallas::Base::from_repr(nk_bytes).map(NullifierDerivingKey);
if nk.is_some().into() {
Some(nk.unwrap())
} else {
@ -265,7 +268,7 @@ impl CommitIvkRandomness {
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
let rivk_bytes = <[u8; 32]>::try_from(bytes).ok()?;
let rivk = pallas::Scalar::from_bytes(&rivk_bytes).map(CommitIvkRandomness);
let rivk = pallas::Scalar::from_repr(rivk_bytes).map(CommitIvkRandomness);
if rivk.is_some().into() {
Some(rivk.unwrap())
} else {
@ -324,8 +327,8 @@ impl FullViewingKey {
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
fn derive_dk_ovk(&self) -> (DiversifierKey, OutgoingViewingKey) {
let k = self.rivk.0.to_bytes();
let b = [(&self.ak.0).into(), self.nk.0.to_bytes()];
let k = self.rivk.0.to_repr();
let b = [(&self.ak.0).into(), self.nk.0.to_repr()];
let r = PrfExpand::OrchardDkOvk.with_ad_slices(&k, &[&b[0][..], &b[1][..]]);
(
DiversifierKey(r[..32].try_into().unwrap()),
@ -355,8 +358,8 @@ impl FullViewingKey {
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
let ak_raw: [u8; 32] = self.ak.0.clone().into();
writer.write_all(&ak_raw)?;
writer.write_all(&self.nk.0.to_bytes())?;
writer.write_all(&self.rivk.0.to_bytes())?;
writer.write_all(&self.nk.0.to_repr())?;
writer.write_all(&self.rivk.0.to_repr())?;
Ok(())
}
@ -560,7 +563,7 @@ impl IncomingViewingKey {
pub fn to_bytes(&self) -> [u8; 64] {
let mut result = [0u8; 64];
result[..32].copy_from_slice(self.dk.to_bytes());
result[32..].copy_from_slice(&self.ivk.0.to_bytes());
result[32..].copy_from_slice(&self.ivk.0.to_repr());
result
}

View File

@ -1,8 +1,8 @@
use std::iter;
use bitvec::{array::BitArray, order::Lsb0};
use ff::PrimeFieldBits;
use pasta_curves::{arithmetic::FieldExt, pallas};
use group::ff::{PrimeField, PrimeFieldBits};
use pasta_curves::pallas;
use subtle::{ConstantTimeEq, CtOption};
use crate::{
@ -72,12 +72,12 @@ impl ExtractedNoteCommitment {
///
/// [cmxcanon]: https://zips.z.cash/protocol/protocol.pdf#actionencodingandconsensus
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment)
pallas::Base::from_repr(*bytes).map(ExtractedNoteCommitment)
}
/// Serialize the value commitment to its canonical byte representation.
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_bytes()
self.0.to_repr()
}
}

View File

@ -1,6 +1,6 @@
use group::Group;
use group::{ff::PrimeField, Group};
use halo2::arithmetic::CurveExt;
use pasta_curves::{arithmetic::FieldExt, pallas};
use pasta_curves::pallas;
use rand::RngCore;
use subtle::CtOption;
@ -33,12 +33,12 @@ impl Nullifier {
/// Deserialize the nullifier from a byte array.
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_bytes(bytes).map(Nullifier)
pallas::Base::from_repr(*bytes).map(Nullifier)
}
/// Serialize the nullifier to its canonical byte representation.
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_bytes()
self.0.to_repr()
}
/// $DeriveNullifier$.

View File

@ -3,7 +3,7 @@
use std::{convert::TryInto, fmt};
use blake2b_simd::{Hash, Params};
use halo2::arithmetic::FieldExt;
use group::ff::PrimeField;
use zcash_note_encryption::{
BatchDomain, Domain, EphemeralKeyBytes, NotePlaintextBytes, OutPlaintextBytes,
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE,
@ -171,7 +171,7 @@ impl Domain for OrchardDomain {
) -> OutPlaintextBytes {
let mut op = [0; OUT_PLAINTEXT_SIZE];
op[..32].copy_from_slice(&note.recipient().pk_d().to_bytes());
op[32..].copy_from_slice(&esk.0.to_bytes());
op[32..].copy_from_slice(&esk.0.to_repr());
OutPlaintextBytes(op)
}

View File

@ -1,6 +1,7 @@
//! The Poseidon algebraic hash function.
use std::array;
use std::convert::TryInto;
use std::fmt;
use std::iter;
use std::marker::PhantomData;
@ -23,14 +24,14 @@ 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 duplex sponge state.
pub(crate) type SpongeState<F, const RATE: usize> = [Option<F>; RATE];
/// 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> {
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.
@ -47,10 +48,10 @@ pub trait Spec<F: FieldExt, const T: usize, const RATE: usize> {
///
/// 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(&self) -> usize;
fn secure_mds() -> usize;
/// Generates `(round_constants, mds, mds^-1)` corresponding to this specification.
fn constants(&self) -> (Vec<[F; T]>, Mds<F, T>, Mds<F, T>) {
fn constants() -> (Vec<[F; T]>, Mds<F, T>, Mds<F, T>) {
let r_f = Self::full_rounds();
let r_p = Self::partial_rounds();
@ -69,7 +70,7 @@ pub trait Spec<F: FieldExt, const T: usize, const RATE: usize> {
})
.collect();
let (mds, mds_inv) = mds::generate_mds::<F, T>(&mut grain, self.secure_mds());
let (mds, mds_inv) = mds::generate_mds::<F, T>(&mut grain, Self::secure_mds());
(round_constants, mds, mds_inv)
}
@ -123,14 +124,19 @@ pub(crate) fn permute<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RA
});
}
fn poseidon_duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>(
fn poseidon_sponge<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize>(
state: &mut State<F, T>,
input: &SpongeState<F, RATE>,
pad_and_add: &dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>),
input: Option<&Absorbing<F, RATE>>,
mds_matrix: &Mds<F, T>,
round_constants: &[[F; T]],
) -> SpongeState<F, RATE> {
pad_and_add(state, input);
) -> 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);
@ -138,49 +144,70 @@ fn poseidon_duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE:
for (word, value) in output.iter_mut().zip(state.iter()) {
*word = Some(*value);
}
output
Squeezing(output)
}
pub(crate) enum Sponge<F, const RATE: usize> {
Absorbing(SpongeState<F, RATE>),
Squeezing(SpongeState<F, RATE>),
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> {}
}
impl<F: Copy, const RATE: usize> Sponge<F, RATE> {
pub(crate) fn absorb(val: F) -> Self {
let mut input = [None; RATE];
input[0] = Some(val);
Sponge::Absorbing(input)
/// 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 duplex sponge.
pub(crate) struct Duplex<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> {
sponge: Sponge<F, RATE>,
/// 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>,
pad_and_add: Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>,
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> Duplex<F, S, T, RATE> {
/// Constructs a new duplex sponge for the given Poseidon specification.
pub(crate) fn new(
spec: S,
initial_capacity_element: F,
pad_and_add: Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>,
) -> Self {
let (round_constants, mds_matrix, _) = spec.constants();
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 input = [None; RATE];
let mode = Absorbing([None; RATE]);
let mut state = [F::zero(); T];
state[RATE] = initial_capacity_element;
Duplex {
sponge: Sponge::Absorbing(input),
Sponge {
mode,
state,
pad_and_add,
mds_matrix,
round_constants,
_marker: PhantomData::default(),
@ -189,71 +216,78 @@ impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize> Duplex
/// Absorbs an element into the sponge.
pub(crate) fn absorb(&mut self, value: F) {
match self.sponge {
Sponge::Absorbing(ref mut input) => {
for entry in input.iter_mut() {
if entry.is_none() {
*entry = Some(value);
return;
}
}
// We've already absorbed as many elements as we can
let _ = poseidon_duplex::<F, S, T, RATE>(
&mut self.state,
input,
&self.pad_and_add,
&self.mds_matrix,
&self.round_constants,
);
self.sponge = Sponge::absorb(value);
}
Sponge::Squeezing(_) => {
// Drop the remaining output elements
self.sponge = Sponge::absorb(value);
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 {
match self.sponge {
Sponge::Absorbing(ref input) => {
self.sponge = Sponge::Squeezing(poseidon_duplex::<F, S, T, RATE>(
&mut self.state,
input,
&self.pad_and_add,
&self.mds_matrix,
&self.round_constants,
));
}
Sponge::Squeezing(ref mut output) => {
for entry in output.iter_mut() {
if let Some(e) = entry.take() {
return e;
}
}
// We've already squeezed out all available elements
self.sponge = Sponge::Absorbing([None; RATE]);
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 T: usize, const RATE: usize>: Copy + fmt::Debug {
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(&self) -> F;
fn initial_capacity_element() -> F;
/// The padding that will be added to each state word by [`Domain::pad_and_add`].
fn padding(&self) -> SpongeState<F, RATE>;
/// Returns a function that will update the given state with the given input to a
/// duplex permutation round, applying padding according to this domain specification.
fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)>;
/// 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.
@ -262,59 +296,44 @@ pub trait Domain<F: FieldExt, const T: usize, const RATE: usize>: Copy + fmt::De
#[derive(Clone, Copy, Debug)]
pub struct ConstantLength<const L: usize>;
impl<F: FieldExt, const T: usize, const RATE: usize, const L: usize> Domain<F, T, RATE>
for ConstantLength<L>
{
fn initial_capacity_element(&self) -> F {
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(&self) -> SpongeState<F, RATE> {
// For constant-input-length hashing, padding consists of the field elements being
// zero.
let mut padding = [None; RATE];
for word in padding.iter_mut().skip(L) {
*word = Some(F::zero());
}
padding
}
fn pad_and_add(&self) -> Box<dyn Fn(&mut State<F, T>, &SpongeState<F, RATE>)> {
Box::new(|state, 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()) {
// For constant-input-length hashing, padding consists of the field
// elements being zero, so we don't add anything to the state.
if let Some(value) = value {
*word += value;
}
}
})
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 duplex sponge.
/// A Poseidon hash function, built around a sponge.
pub struct Hash<
F: FieldExt,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
D: Domain<F, RATE>,
const T: usize,
const RATE: usize,
> {
duplex: Duplex<F, S, T, RATE>,
domain: D,
sponge: Sponge<F, S, Absorbing<F, RATE>, T, RATE>,
_domain: PhantomData<D>,
}
impl<
F: FieldExt,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
const RATE: usize,
> fmt::Debug for Hash<F, S, D, T, RATE>
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")
@ -322,28 +341,19 @@ impl<
.field("rate", &RATE)
.field("R_F", &S::full_rounds())
.field("R_P", &S::partial_rounds())
.field("domain", &self.domain)
.field("domain", &D::name())
.finish()
}
}
impl<
F: FieldExt,
S: Spec<F, T, RATE>,
D: Domain<F, T, RATE>,
const T: usize,
const RATE: usize,
> Hash<F, S, D, T, RATE>
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(spec: S, domain: D) -> Self {
pub fn init() -> Self {
Hash {
duplex: Duplex::new(
spec,
domain.initial_capacity_element(),
domain.pad_and_add(),
),
domain,
sponge: Sponge::new(D::initial_capacity_element()),
_domain: PhantomData::default(),
}
}
}
@ -353,10 +363,12 @@ impl<F: FieldExt, S: Spec<F, T, RATE>, const T: usize, const RATE: usize, const
{
/// Hashes the given input.
pub fn hash(mut self, message: [F; L]) -> F {
for value in array::IntoIter::new(message) {
self.duplex.absorb(value);
for value in
array::IntoIter::new(message).chain(<ConstantLength<L> as Domain<F, RATE>>::padding(L))
{
self.sponge.absorb(value);
}
self.duplex.squeeze()
self.sponge.finish_absorbing().squeeze()
}
}
@ -369,11 +381,11 @@ mod tests {
#[test]
fn orchard_spec_equivalence() {
let message = [pallas::Base::from_u64(6), pallas::Base::from_u64(42)];
let message = [pallas::Base::from(6), pallas::Base::from(42)];
let (round_constants, mds, _) = OrchardNullifier.constants();
let (round_constants, mds, _) = OrchardNullifier::constants();
let hasher = Hash::init(OrchardNullifier, ConstantLength);
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

View File

@ -25,11 +25,11 @@ impl Spec<Fp, 3, 2> for P128Pow5T3 {
val.pow_vartime(&[5])
}
fn secure_mds(&self) -> usize {
fn secure_mds() -> usize {
unimplemented!()
}
fn constants(&self) -> (Vec<[Fp; 3]>, Mds<Fp, 3>, Mds<Fp, 3>) {
fn constants() -> (Vec<[Fp; 3]>, Mds<Fp, 3>, Mds<Fp, 3>) {
(
super::fp::ROUND_CONSTANTS[..].to_vec(),
super::fp::MDS,
@ -51,11 +51,11 @@ impl Spec<Fq, 3, 2> for P128Pow5T3 {
val.pow_vartime(&[5])
}
fn secure_mds(&self) -> usize {
fn secure_mds() -> usize {
unimplemented!()
}
fn constants(&self) -> (Vec<[Fq; 3]>, Mds<Fq, 3>, Mds<Fq, 3>) {
fn constants() -> (Vec<[Fq; 3]>, Mds<Fq, 3>, Mds<Fq, 3>) {
(
super::fq::ROUND_CONSTANTS[..].to_vec(),
super::fq::MDS,
@ -80,21 +80,15 @@ mod tests {
/// 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> {
secure_mds: usize,
_field: PhantomData<F>,
}
pub struct P128Pow5T3Gen<F: FieldExt, const SECURE_MDS: usize>(PhantomData<F>);
impl<F: FieldExt> P128Pow5T3Gen<F> {
pub fn new(secure_mds: usize) -> Self {
P128Pow5T3Gen {
secure_mds,
_field: PhantomData::default(),
}
impl<F: FieldExt, const SECURE_MDS: usize> P128Pow5T3Gen<F, SECURE_MDS> {
pub fn new() -> Self {
P128Pow5T3Gen(PhantomData::default())
}
}
impl<F: FieldExt> Spec<F, 3, 2> for P128Pow5T3Gen<F> {
impl<F: FieldExt, const SECURE_MDS: usize> Spec<F, 3, 2> for P128Pow5T3Gen<F, SECURE_MDS> {
fn full_rounds() -> usize {
8
}
@ -107,8 +101,8 @@ mod tests {
val.pow_vartime(&[5])
}
fn secure_mds(&self) -> usize {
self.secure_mds
fn secure_mds() -> usize {
SECURE_MDS
}
}
@ -119,8 +113,7 @@ mod tests {
expected_mds: [[F; 3]; 3],
expected_mds_inv: [[F; 3]; 3],
) {
let poseidon = P128Pow5T3Gen::<F>::new(0);
let (round_constants, mds, mds_inv) = poseidon.constants();
let (round_constants, mds, mds_inv) = P128Pow5T3Gen::<F, 0>::constants();
for (actual, expected) in round_constants
.iter()
@ -196,7 +189,7 @@ mod tests {
]),
];
permute::<Fp, P128Pow5T3Gen<Fp>, 3, 2>(&mut input, &fp::MDS, &fp::ROUND_CONSTANTS);
permute::<Fp, P128Pow5T3Gen<Fp, 0>, 3, 2>(&mut input, &fp::MDS, &fp::ROUND_CONSTANTS);
assert_eq!(input, expected_output);
}
@ -247,7 +240,7 @@ mod tests {
]),
];
permute::<Fq, P128Pow5T3Gen<Fq>, 3, 2>(&mut input, &fq::MDS, &fq::ROUND_CONSTANTS);
permute::<Fq, P128Pow5T3Gen<Fq, 0>, 3, 2>(&mut input, &fq::MDS, &fq::ROUND_CONSTANTS);
assert_eq!(input, expected_output);
}
}
@ -255,7 +248,7 @@ mod tests {
#[test]
fn permute_test_vectors() {
{
let (round_constants, mds, _) = super::P128Pow5T3.constants();
let (round_constants, mds, _) = super::P128Pow5T3::constants();
for tv in crate::primitives::poseidon::test_vectors::fp::permute() {
let mut state = [
@ -273,7 +266,7 @@ mod tests {
}
{
let (round_constants, mds, _) = super::P128Pow5T3.constants();
let (round_constants, mds, _) = super::P128Pow5T3::constants();
for tv in crate::primitives::poseidon::test_vectors::fq::permute() {
let mut state = [
@ -299,7 +292,8 @@ mod tests {
Fp::from_repr(tv.input[1]).unwrap(),
];
let result = Hash::init(super::P128Pow5T3, ConstantLength).hash(message);
let result =
Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
assert_eq!(result.to_repr(), tv.output);
}
@ -310,7 +304,8 @@ mod tests {
Fq::from_repr(tv.input[1]).unwrap(),
];
let result = Hash::init(super::P128Pow5T3, ConstantLength).hash(message);
let result =
Hash::<_, super::P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
assert_eq!(result.to_repr(), tv.output);
}

View File

@ -66,8 +66,8 @@ mod tests {
use crate::constants::{
COMMIT_IVK_PERSONALIZATION, MERKLE_CRH_PERSONALIZATION, NOTE_COMMITMENT_PERSONALIZATION,
};
use group::Curve;
use halo2::arithmetic::{CurveAffine, CurveExt, FieldExt};
use group::{ff::PrimeField, Curve};
use halo2::arithmetic::{CurveAffine, CurveExt};
use halo2::pasta::pallas;
#[test]
@ -93,11 +93,11 @@ mod tests {
assert_eq!(
*coords.x(),
pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap()
pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap()
);
assert_eq!(
*coords.y(),
pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap()
pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap()
);
}
@ -109,11 +109,11 @@ mod tests {
assert_eq!(
*coords.x(),
pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.0).unwrap()
pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap()
);
assert_eq!(
*coords.y(),
pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.1).unwrap()
pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap()
);
}
@ -125,18 +125,18 @@ mod tests {
assert_eq!(
*coords.x(),
pallas::Base::from_bytes(&Q_MERKLE_CRH.0).unwrap()
pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap()
);
assert_eq!(
*coords.y(),
pallas::Base::from_bytes(&Q_MERKLE_CRH.1).unwrap()
pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap()
);
}
#[test]
fn inv_two_pow_k() {
let two_pow_k = pallas::Base::from_u64(1u64 << K);
let inv_two_pow_k = pallas::Base::from_bytes(&INV_TWO_POW_K).unwrap();
let two_pow_k = pallas::Base::from(1u64 << K);
let inv_two_pow_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap();
assert_eq!(two_pow_k * inv_two_pow_k, pallas::Base::one());
}

View File

@ -70,11 +70,11 @@ impl ConditionallySelectable for NonZeroPallasBase {
impl NonZeroPallasBase {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_bytes(bytes).and_then(NonZeroPallasBase::from_base)
pallas::Base::from_repr(*bytes).and_then(NonZeroPallasBase::from_base)
}
pub(crate) fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
self.0.to_repr()
}
pub(crate) fn from_base(b: pallas::Base) -> CtOption<Self> {
@ -116,7 +116,7 @@ impl ConditionallySelectable for NonZeroPallasScalar {
impl NonZeroPallasScalar {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Scalar::from_bytes(bytes).and_then(NonZeroPallasScalar::from_scalar)
pallas::Scalar::from_repr(*bytes).and_then(NonZeroPallasScalar::from_scalar)
}
pub(crate) fn from_scalar(s: pallas::Scalar) -> CtOption<Self> {
@ -212,7 +212,8 @@ pub(crate) fn diversify_hash(d: &[u8; 11]) -> NonIdentityPallasPoint {
///
/// [concreteprfs]: https://zips.z.cash/protocol/nu5.pdf#concreteprfs
pub(crate) fn prf_nf(nk: pallas::Base, rho: pallas::Base) -> pallas::Base {
poseidon::Hash::init(poseidon::P128Pow5T3, poseidon::ConstantLength).hash([nk, rho])
poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<2>, 3, 2>::init()
.hash([nk, rho])
}
/// Defined in [Zcash Protocol Spec § 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
@ -250,8 +251,8 @@ pub(crate) fn extract_p_bottom(point: CtOption<pallas::Point>) -> CtOption<palla
/// The field element representation of a u64 integer represented by
/// an L-bit little-endian bitstring.
pub fn lebs2ip_field<F: FieldExt, const L: usize>(bits: &[bool; L]) -> F {
F::from_u64(lebs2ip::<L>(bits))
pub fn lebs2ip_field<F: PrimeField, const L: usize>(bits: &[bool; L]) -> F {
F::from(lebs2ip::<L>(bits))
}
/// The u64 integer represented by an L-bit little-endian bitstring.

View File

@ -9,7 +9,7 @@ use crate::{
primitives::sinsemilla::{i2lebsp_k, HashDomain},
};
use incrementalmerkletree::{Altitude, Hashable};
use pasta_curves::{arithmetic::FieldExt, pallas};
use pasta_curves::pallas;
use ff::{Field, PrimeField, PrimeFieldBits};
use lazy_static::lazy_static;
@ -23,7 +23,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
// The uncommitted leaf is defined as pallas::Base(2).
// <https://zips.z.cash/protocol/protocol.pdf#thmuncommittedorchard>
lazy_static! {
static ref UNCOMMITTED_ORCHARD: pallas::Base = pallas::Base::from_u64(2);
static ref UNCOMMITTED_ORCHARD: pallas::Base = pallas::Base::from(2);
pub(crate) static ref EMPTY_ROOTS: Vec<MerkleHashOrchard> = {
iter::empty()
.chain(Some(MerkleHashOrchard::empty_leaf()))
@ -168,7 +168,7 @@ impl MerkleHashOrchard {
/// Convert this digest to its canonical byte representation.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
self.0.to_repr()
}
/// Parses a incremental tree leaf digest from the bytes of
@ -177,7 +177,7 @@ impl MerkleHashOrchard {
/// Returns the empty `CtOption` if the provided bytes represent
/// a non-canonical encoding.
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
pallas::Base::from_bytes(bytes).map(MerkleHashOrchard)
pallas::Base::from_repr(*bytes).map(MerkleHashOrchard)
}
}
@ -195,7 +195,7 @@ impl std::cmp::Eq for MerkleHashOrchard {}
impl std::hash::Hash for MerkleHashOrchard {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
<Option<pallas::Base>>::from(self.0)
.map(|b| b.to_bytes())
.map(|b| b.to_repr())
.hash(state)
}
}
@ -272,7 +272,9 @@ pub mod testing {
#[cfg(test)]
use crate::tree::{MerkleHashOrchard, EMPTY_ROOTS};
#[cfg(test)]
use pasta_curves::{arithmetic::FieldExt, pallas};
use group::ff::PrimeField;
#[cfg(test)]
use pasta_curves::pallas;
#[cfg(test)]
use std::convert::TryInto;
@ -293,7 +295,7 @@ pub mod testing {
tree.append(&cmx);
tree.witness();
assert_eq!(tree.root().0, pallas::Base::from_bytes(&tv.root).unwrap());
assert_eq!(tree.root().0, pallas::Base::from_repr(tv.root).unwrap());
// Check paths for all leaves up to this point. The test vectors include paths
// for not-yet-appended leaves (using UNCOMMITTED_ORCHARD as the leaf value),
@ -324,7 +326,7 @@ pub mod testing {
assert_eq!(
MerkleHashOrchard::empty_root(Altitude::from(altitude as u8))
.0
.to_bytes(),
.to_repr(),
*tv_root,
"Empty root mismatch at altitude {}",
altitude
@ -375,12 +377,9 @@ pub mod testing {
let mut frontier = BridgeFrontier::<MerkleHashOrchard, 32>::empty();
for commitment in commitments.iter() {
let cmx = MerkleHashOrchard(pallas::Base::from_bytes(commitment).unwrap());
let cmx = MerkleHashOrchard(pallas::Base::from_repr(*commitment).unwrap());
frontier.append(&cmx);
}
assert_eq!(
frontier.root().0,
pallas::Base::from_bytes(&anchor).unwrap()
);
assert_eq!(frontier.root().0, pallas::Base::from_repr(anchor).unwrap());
}
}

View File

@ -44,7 +44,7 @@ use bitvec::{array::BitArray, order::Lsb0};
use ff::{Field, PrimeField};
use group::{Curve, Group, GroupEncoding};
use pasta_curves::{
arithmetic::{CurveAffine, CurveExt, FieldExt},
arithmetic::{CurveAffine, CurveExt},
pallas,
};
use rand::RngCore;
@ -265,9 +265,9 @@ impl ValueCommitment {
let abs_value = u64::try_from(value.0.abs()).expect("value must be in valid range");
let value = if value.0.is_negative() {
-pallas::Scalar::from_u64(abs_value)
-pallas::Scalar::from(abs_value)
} else {
pallas::Scalar::from_u64(abs_value)
pallas::Scalar::from(abs_value)
};
ValueCommitment(V * value + R * rcv.0)