mirror of https://github.com/zcash/orchard.git
Merge pull request #237 from zcash/orchard-mainnet-circuit
Orchard proposed mainnet circuit
This commit is contained in:
commit
54cdc051fe
|
@ -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" }
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -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(¶ms, &empty_circuit).expect("keygen_vk should not fail");
|
||||
let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail");
|
||||
|
||||
let prover_name = name.to_string() + "-prover";
|
||||
let verifier_name = name.to_string() + "-verifier";
|
||||
|
||||
let rng = OsRng;
|
||||
let message = (0..L)
|
||||
.map(|_| pallas::Base::random(rng))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let output = poseidon::Hash::<_, S, ConstantLength<L>, WIDTH, RATE>::init().hash(message);
|
||||
|
||||
let circuit = HashCircuit::<S, WIDTH, RATE, L> {
|
||||
message: Some(message),
|
||||
output: Some(output),
|
||||
_spec: PhantomData,
|
||||
};
|
||||
|
||||
c.bench_function(&prover_name, |b| {
|
||||
b.iter(|| {
|
||||
// Create a proof
|
||||
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
|
||||
create_proof(¶ms, &pk, &[circuit], &[&[]], &mut transcript)
|
||||
.expect("proof generation should not fail")
|
||||
})
|
||||
});
|
||||
|
||||
// Create a proof
|
||||
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
|
||||
create_proof(¶ms, &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(¶ms, pk.get_vk(), msm, &[&[]], &mut transcript).unwrap();
|
||||
let msm = guard.clone().use_challenges();
|
||||
assert!(msm.eval());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
bench_poseidon::<MySpec<3, 2>, 3, 2, 2>("WIDTH = 3, RATE = 2", c);
|
||||
bench_poseidon::<MySpec<9, 8>, 9, 8, 8>("WIDTH = 9, RATE = 8", c);
|
||||
bench_poseidon::<MySpec<12, 11>, 12, 11, 11>("WIDTH = 12, RATE = 11", c);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
|
@ -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)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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})}
|
||||
|
|
|
@ -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}).$$
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
140
src/circuit.rs
140
src/circuit.rs
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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_q−y_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
|
||||
};
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<_>>(),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()),
|
||||
],
|
||||
}
|
||||
])
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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))?;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -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
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
25
src/keys.rs
25
src/keys.rs
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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$.
|
||||
|
|
|
@ -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(¬e.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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
13
src/spec.rs
13
src/spec.rs
|
@ -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.
|
||||
|
|
25
src/tree.rs
25
src/tree.rs
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue