2021-06-21 05:38:43 -07:00
|
|
|
//! The Orchard Action circuit implementation.
|
|
|
|
|
2021-06-22 08:06:49 -07:00
|
|
|
use std::mem;
|
|
|
|
|
2021-06-07 03:45:03 -07:00
|
|
|
use group::{Curve, GroupEncoding};
|
2021-04-14 21:35:48 -07:00
|
|
|
use halo2::{
|
2021-07-21 09:19:15 -07:00
|
|
|
circuit::{floor_planner, Layouter},
|
2021-07-21 09:20:39 -07:00
|
|
|
plonk::{self, Advice, Column, Expression, Instance as InstanceColumn, Selector},
|
2021-07-15 04:52:15 -07:00
|
|
|
poly::Rotation,
|
2021-04-14 21:35:48 -07:00
|
|
|
transcript::{Blake2bRead, Blake2bWrite},
|
|
|
|
};
|
2021-07-19 09:42:22 -07:00
|
|
|
use pasta_curves::{
|
|
|
|
arithmetic::{CurveAffine, FieldExt},
|
|
|
|
pallas, vesta,
|
|
|
|
};
|
2021-04-14 21:35:48 -07:00
|
|
|
|
|
|
|
use crate::{
|
2021-06-06 04:44:24 -07:00
|
|
|
constants::{
|
2021-06-06 05:05:51 -07:00
|
|
|
load::{NullifierK, OrchardFixedBasesFull, ValueCommitV},
|
2021-06-06 04:44:24 -07:00
|
|
|
MERKLE_DEPTH_ORCHARD,
|
|
|
|
},
|
2021-06-06 04:21:27 -07:00
|
|
|
keys::{
|
|
|
|
CommitIvkRandomness, DiversifiedTransmissionKey, NullifierDerivingKey, SpendValidatingKey,
|
|
|
|
},
|
|
|
|
note::{
|
|
|
|
commitment::{NoteCommitTrapdoor, NoteCommitment},
|
|
|
|
nullifier::Nullifier,
|
|
|
|
ExtractedNoteCommitment,
|
|
|
|
},
|
2021-06-06 04:26:37 -07:00
|
|
|
primitives::{
|
2021-06-06 05:05:51 -07:00
|
|
|
poseidon::{self, ConstantLength},
|
2021-06-06 04:26:37 -07:00
|
|
|
redpallas::{SpendAuth, VerificationKey},
|
|
|
|
},
|
2021-06-06 04:21:27 -07:00
|
|
|
spec::NonIdentityPallasPoint,
|
2021-04-14 21:35:48 -07:00
|
|
|
tree::Anchor,
|
2021-06-06 04:21:27 -07:00
|
|
|
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
|
2021-04-14 21:35:48 -07:00
|
|
|
};
|
2021-06-06 04:26:37 -07:00
|
|
|
use gadget::{
|
2021-06-06 21:26:02 -07:00
|
|
|
ecc::{
|
|
|
|
chip::{EccChip, EccConfig},
|
2021-06-06 05:05:51 -07:00
|
|
|
FixedPoint, FixedPointBaseField, FixedPointShort, Point,
|
|
|
|
},
|
|
|
|
poseidon::{
|
|
|
|
Hash as PoseidonHash, Pow5T3Chip as PoseidonChip, Pow5T3Config as PoseidonConfig,
|
|
|
|
StateWord, Word,
|
2021-06-06 21:26:02 -07:00
|
|
|
},
|
2021-06-06 04:26:37 -07:00
|
|
|
sinsemilla::{
|
2021-06-07 03:24:58 -07:00
|
|
|
chip::{SinsemillaChip, SinsemillaConfig, SinsemillaHashDomains},
|
2021-06-06 10:00:24 -07:00
|
|
|
commit_ivk::CommitIvkConfig,
|
2021-06-06 04:32:59 -07:00
|
|
|
merkle::{
|
|
|
|
chip::{MerkleChip, MerkleConfig},
|
|
|
|
MerklePath,
|
|
|
|
},
|
2021-06-07 03:24:58 -07:00
|
|
|
note_commit::NoteCommitConfig,
|
2021-06-06 04:26:37 -07:00
|
|
|
},
|
2021-07-23 02:58:37 -07:00
|
|
|
utilities::{copy, CellValue, UtilitiesInstructions, Var},
|
2021-06-06 04:26:37 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
use std::convert::TryInto;
|
2021-04-14 21:35:48 -07:00
|
|
|
|
2021-07-21 07:59:08 -07:00
|
|
|
use self::gadget::utilities::lookup_range_check::LookupRangeCheckConfig;
|
|
|
|
|
2021-02-25 10:11:46 -08:00
|
|
|
pub(crate) mod gadget;
|
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
/// Size of the Orchard circuit.
|
2021-07-21 09:19:15 -07:00
|
|
|
const K: u32 = 11;
|
2021-04-14 21:35:48 -07:00
|
|
|
|
2021-07-19 09:42:22 -07:00
|
|
|
// Absolute offsets for public inputs.
|
2021-07-20 01:22:08 -07:00
|
|
|
const ANCHOR: usize = 0;
|
2021-07-22 07:14:34 -07:00
|
|
|
const CV_NET_X: usize = 1;
|
|
|
|
const CV_NET_Y: usize = 2;
|
|
|
|
const NF_OLD: usize = 3;
|
2021-07-20 01:22:08 -07:00
|
|
|
const RK_X: usize = 4;
|
|
|
|
const RK_Y: usize = 5;
|
|
|
|
const CMX: usize = 6;
|
|
|
|
const ENABLE_SPEND: usize = 7;
|
|
|
|
const ENABLE_OUTPUT: usize = 8;
|
2021-07-19 09:42:22 -07:00
|
|
|
|
2021-06-06 04:26:37 -07:00
|
|
|
/// Configuration needed to use the Orchard Action circuit.
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Config {
|
|
|
|
primary: Column<InstanceColumn>,
|
2021-07-19 09:42:22 -07:00
|
|
|
q_orchard: Selector,
|
2021-07-23 02:58:37 -07:00
|
|
|
// Selector for the field addition gate poseidon_hash(nk, rho_old) + psi_old.
|
|
|
|
q_add: Selector,
|
2021-06-06 04:26:37 -07:00
|
|
|
advices: [Column<Advice>; 10],
|
|
|
|
ecc_config: EccConfig,
|
|
|
|
poseidon_config: PoseidonConfig<pallas::Base>,
|
|
|
|
merkle_config_1: MerkleConfig,
|
|
|
|
merkle_config_2: MerkleConfig,
|
|
|
|
sinsemilla_config_1: SinsemillaConfig,
|
|
|
|
sinsemilla_config_2: SinsemillaConfig,
|
2021-06-06 10:00:24 -07:00
|
|
|
commit_ivk_config: CommitIvkConfig,
|
2021-06-07 03:24:58 -07:00
|
|
|
old_note_commit_config: NoteCommitConfig,
|
2021-06-07 03:45:03 -07:00
|
|
|
new_note_commit_config: NoteCommitConfig,
|
2021-06-06 04:26:37 -07:00
|
|
|
}
|
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
/// The Orchard Action circuit.
|
2021-06-06 04:21:27 -07:00
|
|
|
#[derive(Debug, Default)]
|
|
|
|
pub struct Circuit {
|
|
|
|
pub(crate) path: Option<[pallas::Base; MERKLE_DEPTH_ORCHARD]>,
|
|
|
|
pub(crate) pos: Option<u32>,
|
|
|
|
pub(crate) g_d_old: Option<NonIdentityPallasPoint>,
|
|
|
|
pub(crate) pk_d_old: Option<DiversifiedTransmissionKey>,
|
|
|
|
pub(crate) v_old: Option<NoteValue>,
|
|
|
|
pub(crate) rho_old: Option<Nullifier>,
|
|
|
|
pub(crate) psi_old: Option<pallas::Base>,
|
|
|
|
pub(crate) rcm_old: Option<NoteCommitTrapdoor>,
|
|
|
|
pub(crate) cm_old: Option<NoteCommitment>,
|
|
|
|
pub(crate) alpha: Option<pallas::Scalar>,
|
|
|
|
pub(crate) ak: Option<SpendValidatingKey>,
|
|
|
|
pub(crate) nk: Option<NullifierDerivingKey>,
|
|
|
|
pub(crate) rivk: Option<CommitIvkRandomness>,
|
|
|
|
pub(crate) g_d_new_star: Option<[u8; 32]>,
|
|
|
|
pub(crate) pk_d_new_star: Option<[u8; 32]>,
|
|
|
|
pub(crate) v_new: Option<NoteValue>,
|
|
|
|
pub(crate) psi_new: Option<pallas::Base>,
|
|
|
|
pub(crate) rcm_new: Option<NoteCommitTrapdoor>,
|
|
|
|
pub(crate) rcv: Option<ValueCommitTrapdoor>,
|
|
|
|
}
|
2021-04-14 21:35:48 -07:00
|
|
|
|
2021-06-06 21:26:02 -07:00
|
|
|
impl UtilitiesInstructions<pallas::Base> for Circuit {
|
|
|
|
type Var = CellValue<pallas::Base>;
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
impl plonk::Circuit<pallas::Base> for Circuit {
|
2021-06-06 04:26:37 -07:00
|
|
|
type Config = Config;
|
2021-07-21 09:19:15 -07:00
|
|
|
type FloorPlanner = floor_planner::V1;
|
2021-07-08 18:56:27 -07:00
|
|
|
|
|
|
|
fn without_witnesses(&self) -> Self {
|
2021-06-06 04:26:37 -07:00
|
|
|
Self::default()
|
2021-07-08 18:56:27 -07:00
|
|
|
}
|
2021-04-14 21:35:48 -07:00
|
|
|
|
|
|
|
fn configure(meta: &mut plonk::ConstraintSystem<pallas::Base>) -> Self::Config {
|
2021-06-06 04:26:37 -07:00
|
|
|
// Advice columns used in the Orchard circuit.
|
|
|
|
let advices = [
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
meta.advice_column(),
|
|
|
|
];
|
|
|
|
|
2021-06-06 04:44:24 -07:00
|
|
|
// Constrain v_old - v_new = magnitude * sign
|
2021-07-19 09:42:22 -07:00
|
|
|
// Either v_old = 0, or anchor equals public input
|
2021-07-19 10:01:39 -07:00
|
|
|
// Constrain v_old = 0 or enable_spends = 1.
|
|
|
|
// Constrain v_new = 0 or enable_outputs = 1.
|
2021-07-19 09:42:22 -07:00
|
|
|
let q_orchard = meta.selector();
|
|
|
|
meta.create_gate("Orchard circuit checks", |meta| {
|
|
|
|
let q_orchard = meta.query_selector(q_orchard);
|
2021-06-06 04:44:24 -07:00
|
|
|
let v_old = meta.query_advice(advices[0], Rotation::cur());
|
|
|
|
let v_new = meta.query_advice(advices[1], Rotation::cur());
|
|
|
|
let magnitude = meta.query_advice(advices[2], Rotation::cur());
|
|
|
|
let sign = meta.query_advice(advices[3], Rotation::cur());
|
|
|
|
|
2021-07-19 09:42:22 -07:00
|
|
|
let anchor = meta.query_advice(advices[4], Rotation::cur());
|
|
|
|
let pub_input_anchor = meta.query_advice(advices[5], Rotation::cur());
|
|
|
|
|
2021-07-19 10:01:39 -07:00
|
|
|
let one = Expression::Constant(pallas::Base::one());
|
|
|
|
let not_enable_spends = one.clone() - meta.query_advice(advices[6], Rotation::cur());
|
|
|
|
let not_enable_outputs = one - meta.query_advice(advices[7], Rotation::cur());
|
|
|
|
|
2021-07-19 09:42:22 -07:00
|
|
|
std::array::IntoIter::new([
|
|
|
|
(
|
|
|
|
"v_old - v_new = magnitude * sign",
|
2021-07-19 10:01:39 -07:00
|
|
|
v_old.clone() - v_new.clone() - magnitude * sign,
|
2021-07-19 09:42:22 -07:00
|
|
|
),
|
|
|
|
(
|
|
|
|
"Either v_old = 0, or anchor equals public input",
|
2021-07-19 10:01:39 -07:00
|
|
|
v_old.clone() * (anchor - pub_input_anchor),
|
|
|
|
),
|
|
|
|
("v_old = 0 or enable_spends = 1", v_old * not_enable_spends),
|
|
|
|
(
|
|
|
|
"v_new = 0 or enable_outputs = 1",
|
|
|
|
v_new * not_enable_outputs,
|
2021-07-19 09:42:22 -07:00
|
|
|
),
|
|
|
|
])
|
|
|
|
.map(move |(name, poly)| (name, q_orchard.clone() * poly))
|
2021-06-06 04:44:24 -07:00
|
|
|
});
|
|
|
|
|
2021-07-23 02:58:37 -07:00
|
|
|
// Addition of two field elements poseidon_hash(nk, rho_old) + psi_old.
|
|
|
|
let q_add = meta.selector();
|
|
|
|
meta.create_gate("poseidon_hash(nk, rho_old) + psi_old", |meta| {
|
|
|
|
let q_add = meta.query_selector(q_add);
|
|
|
|
let sum = meta.query_advice(advices[0], Rotation::cur());
|
|
|
|
let nk_rho_old = meta.query_advice(advices[1], Rotation::cur());
|
|
|
|
let psi_old = meta.query_advice(advices[2], Rotation::cur());
|
|
|
|
|
|
|
|
vec![q_add * (nk_rho_old + psi_old - sum)]
|
|
|
|
});
|
|
|
|
|
2021-06-06 04:26:37 -07:00
|
|
|
// Fixed columns for the Sinsemilla generator lookup table
|
|
|
|
let table_idx = meta.fixed_column();
|
|
|
|
let lookup = (table_idx, meta.fixed_column(), meta.fixed_column());
|
|
|
|
|
2021-07-20 01:22:08 -07:00
|
|
|
// Instance column used for public inputs
|
2021-07-19 09:42:22 -07:00
|
|
|
let primary = meta.instance_column();
|
|
|
|
meta.enable_equality(primary.into());
|
|
|
|
|
2021-07-20 01:22:08 -07:00
|
|
|
// Permutation over all advice columns.
|
2021-06-06 04:26:37 -07:00
|
|
|
for advice in advices.iter() {
|
|
|
|
meta.enable_equality((*advice).into());
|
|
|
|
}
|
|
|
|
|
2021-07-21 04:13:47 -07:00
|
|
|
// Poseidon requires four advice columns, while ECC incomplete addition requires
|
|
|
|
// six, so we could choose to configure them in parallel. However, we only use a
|
|
|
|
// single Poseidon invocation, and we have the rows to accomodate it serially.
|
|
|
|
// Instead, we reduce the proof size by sharing fixed columns between the ECC and
|
|
|
|
// Poseidon chips.
|
|
|
|
let lagrange_coeffs = [
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
meta.fixed_column(),
|
|
|
|
];
|
|
|
|
let rc_a = lagrange_coeffs[2..5].try_into().unwrap();
|
|
|
|
let rc_b = lagrange_coeffs[5..8].try_into().unwrap();
|
|
|
|
|
2021-07-21 09:20:39 -07:00
|
|
|
// Also use the first Lagrange coefficient column for loading global constants.
|
|
|
|
// It's free real estate :)
|
|
|
|
meta.enable_constant(lagrange_coeffs[0]);
|
|
|
|
|
2021-07-21 07:59:08 -07:00
|
|
|
// We have a lot of free space in the right-most advice columns; use one of them
|
|
|
|
// for all of our range checks.
|
|
|
|
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
|
|
|
|
|
2021-06-06 04:26:37 -07:00
|
|
|
// Configuration for curve point operations.
|
|
|
|
// This uses 10 advice columns and spans the whole circuit.
|
2021-07-21 07:59:08 -07:00
|
|
|
let ecc_config = EccChip::configure(meta, advices, lagrange_coeffs, range_check.clone());
|
2021-06-06 04:26:37 -07:00
|
|
|
|
|
|
|
// Configuration for the Poseidon hash.
|
|
|
|
let poseidon_config = PoseidonChip::configure(
|
|
|
|
meta,
|
|
|
|
poseidon::OrchardNullifier,
|
2021-07-23 15:13:10 -07:00
|
|
|
// 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(),
|
|
|
|
advices[5],
|
2021-07-21 04:13:47 -07:00
|
|
|
rc_a,
|
|
|
|
rc_b,
|
2021-06-06 04:26:37 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// Configuration for a Sinsemilla hash instantiation and a
|
|
|
|
// Merkle hash instantiation using this Sinsemilla instance.
|
|
|
|
// Since the Sinsemilla config uses only 5 advice columns,
|
|
|
|
// we can fit two instances side-by-side.
|
|
|
|
let (sinsemilla_config_1, merkle_config_1) = {
|
2021-07-21 07:59:08 -07:00
|
|
|
let sinsemilla_config_1 = SinsemillaChip::configure(
|
|
|
|
meta,
|
|
|
|
advices[..5].try_into().unwrap(),
|
2021-07-21 08:47:49 -07:00
|
|
|
advices[6],
|
2021-07-23 05:25:02 -07:00
|
|
|
lagrange_coeffs[0],
|
2021-07-21 07:59:08 -07:00
|
|
|
lookup,
|
|
|
|
range_check.clone(),
|
|
|
|
);
|
2021-06-06 04:26:37 -07:00
|
|
|
let merkle_config_1 = MerkleChip::configure(meta, sinsemilla_config_1.clone());
|
|
|
|
|
|
|
|
(sinsemilla_config_1, merkle_config_1)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Configuration for a Sinsemilla hash instantiation and a
|
|
|
|
// Merkle hash instantiation using this Sinsemilla instance.
|
|
|
|
// Since the Sinsemilla config uses only 5 advice columns,
|
|
|
|
// we can fit two instances side-by-side.
|
|
|
|
let (sinsemilla_config_2, merkle_config_2) = {
|
2021-07-21 07:59:08 -07:00
|
|
|
let sinsemilla_config_2 = SinsemillaChip::configure(
|
|
|
|
meta,
|
|
|
|
advices[5..].try_into().unwrap(),
|
2021-07-21 08:47:49 -07:00
|
|
|
advices[7],
|
2021-07-23 05:25:02 -07:00
|
|
|
lagrange_coeffs[1],
|
2021-07-21 07:59:08 -07:00
|
|
|
lookup,
|
|
|
|
range_check,
|
|
|
|
);
|
2021-06-06 04:26:37 -07:00
|
|
|
let merkle_config_2 = MerkleChip::configure(meta, sinsemilla_config_2.clone());
|
|
|
|
|
|
|
|
(sinsemilla_config_2, merkle_config_2)
|
|
|
|
};
|
|
|
|
|
2021-06-06 10:00:24 -07:00
|
|
|
// Configuration to handle decomposition and canonicity checking
|
|
|
|
// for CommitIvk.
|
|
|
|
let commit_ivk_config =
|
|
|
|
CommitIvkConfig::configure(meta, advices, sinsemilla_config_1.clone());
|
|
|
|
|
2021-06-07 03:24:58 -07:00
|
|
|
// Configuration to handle decomposition and canonicity checking
|
|
|
|
// for NoteCommit_old.
|
|
|
|
let old_note_commit_config =
|
|
|
|
NoteCommitConfig::configure(meta, advices, sinsemilla_config_1.clone());
|
|
|
|
|
2021-06-07 03:45:03 -07:00
|
|
|
// Configuration to handle decomposition and canonicity checking
|
|
|
|
// for NoteCommit_new.
|
|
|
|
let new_note_commit_config =
|
|
|
|
NoteCommitConfig::configure(meta, advices, sinsemilla_config_2.clone());
|
|
|
|
|
2021-06-06 04:26:37 -07:00
|
|
|
Config {
|
|
|
|
primary,
|
2021-07-19 09:42:22 -07:00
|
|
|
q_orchard,
|
2021-07-23 02:58:37 -07:00
|
|
|
q_add,
|
2021-06-06 04:26:37 -07:00
|
|
|
advices,
|
|
|
|
ecc_config,
|
|
|
|
poseidon_config,
|
|
|
|
merkle_config_1,
|
|
|
|
merkle_config_2,
|
|
|
|
sinsemilla_config_1,
|
|
|
|
sinsemilla_config_2,
|
2021-06-06 10:00:24 -07:00
|
|
|
commit_ivk_config,
|
2021-06-07 03:24:58 -07:00
|
|
|
old_note_commit_config,
|
2021-06-07 03:45:03 -07:00
|
|
|
new_note_commit_config,
|
2021-06-06 04:26:37 -07:00
|
|
|
}
|
2021-04-14 21:35:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn synthesize(
|
|
|
|
&self,
|
2021-06-06 04:36:01 -07:00
|
|
|
config: Self::Config,
|
|
|
|
mut layouter: impl Layouter<pallas::Base>,
|
2021-04-14 21:35:48 -07:00
|
|
|
) -> Result<(), plonk::Error> {
|
2021-06-06 04:36:01 -07:00
|
|
|
// Load the Sinsemilla generator lookup table used by the whole circuit.
|
2021-06-06 21:26:02 -07:00
|
|
|
SinsemillaChip::load(config.sinsemilla_config_1.clone(), &mut layouter)?;
|
|
|
|
|
|
|
|
// Construct the ECC chip.
|
|
|
|
let ecc_chip = config.ecc_chip();
|
|
|
|
|
|
|
|
// Witness private inputs that are used across multiple checks.
|
|
|
|
let (rho_old, psi_old, cm_old, g_d_old, ak, nk, v_old, v_new) = {
|
|
|
|
// Witness psi_old
|
|
|
|
let psi_old = self.load_private(
|
|
|
|
layouter.namespace(|| "witness psi_old"),
|
|
|
|
config.advices[0],
|
|
|
|
self.psi_old,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Witness rho_old
|
|
|
|
let rho_old = self.load_private(
|
|
|
|
layouter.namespace(|| "witness rho_old"),
|
|
|
|
config.advices[0],
|
|
|
|
self.rho_old.map(|rho| rho.0),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Witness cm_old
|
|
|
|
let cm_old = Point::new(
|
|
|
|
config.ecc_chip(),
|
|
|
|
layouter.namespace(|| "cm_old"),
|
2021-07-22 07:14:34 -07:00
|
|
|
self.cm_old.as_ref().map(|cm| cm.inner().to_affine()),
|
2021-06-06 21:26:02 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// Witness g_d_old
|
|
|
|
let g_d_old = Point::new(
|
|
|
|
config.ecc_chip(),
|
|
|
|
layouter.namespace(|| "gd_old"),
|
|
|
|
self.g_d_old.as_ref().map(|gd| gd.to_affine()),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Witness ak.
|
|
|
|
let ak: Option<pallas::Point> = self.ak.as_ref().map(|ak| ak.into());
|
|
|
|
let ak = Point::new(
|
|
|
|
ecc_chip.clone(),
|
|
|
|
layouter.namespace(|| "ak"),
|
|
|
|
ak.map(|ak| ak.to_affine()),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Witness nk.
|
|
|
|
let nk = self.load_private(
|
|
|
|
layouter.namespace(|| "witness nk"),
|
|
|
|
config.advices[0],
|
2021-07-22 07:14:34 -07:00
|
|
|
self.nk.map(|nk| nk.inner()),
|
2021-06-06 21:26:02 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// Witness v_old.
|
|
|
|
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())),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// 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())),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
(rho_old, psi_old, cm_old, g_d_old, ak, nk, v_old, v_new)
|
|
|
|
};
|
2021-06-06 04:36:01 -07:00
|
|
|
|
2021-06-06 04:32:59 -07:00
|
|
|
// Merkle path validity check.
|
2021-07-19 09:42:22 -07:00
|
|
|
let anchor = {
|
2021-06-06 04:32:59 -07:00
|
|
|
let merkle_inputs = MerklePath {
|
|
|
|
chip_1: config.merkle_chip_1(),
|
|
|
|
chip_2: config.merkle_chip_2(),
|
|
|
|
domain: SinsemillaHashDomains::MerkleCrh,
|
|
|
|
leaf_pos: self.pos,
|
|
|
|
path: self.path,
|
|
|
|
};
|
|
|
|
let leaf = *cm_old.extract_p().inner();
|
|
|
|
merkle_inputs.calculate_root(layouter.namespace(|| "MerkleCRH"), leaf)?
|
|
|
|
};
|
|
|
|
|
2021-06-06 04:44:24 -07:00
|
|
|
// Value commitment integrity.
|
2021-07-19 09:42:22 -07:00
|
|
|
let v_net = {
|
2021-06-06 04:44:24 -07:00
|
|
|
// v_net = v_old - v_new
|
|
|
|
let v_net = {
|
2021-07-20 01:22:08 -07:00
|
|
|
// 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 magnitude_sign = v_old.zip(v_new).map(|(v_old, v_new)| {
|
|
|
|
let is_negative = v_old < v_new;
|
|
|
|
let magnitude = if is_negative {
|
|
|
|
v_new - v_old
|
|
|
|
} else {
|
|
|
|
v_old - v_new
|
|
|
|
};
|
|
|
|
let sign = if is_negative {
|
|
|
|
-pallas::Base::one()
|
|
|
|
} else {
|
|
|
|
pallas::Base::one()
|
|
|
|
};
|
|
|
|
(magnitude, sign)
|
2021-06-06 04:44:24 -07:00
|
|
|
});
|
2021-07-20 01:22:08 -07:00
|
|
|
|
2021-06-06 04:44:24 -07:00
|
|
|
let magnitude = self.load_private(
|
|
|
|
layouter.namespace(|| "v_net magnitude"),
|
|
|
|
config.advices[9],
|
|
|
|
magnitude_sign.map(|m_s| m_s.0),
|
|
|
|
)?;
|
|
|
|
let sign = self.load_private(
|
|
|
|
layouter.namespace(|| "v_net sign"),
|
|
|
|
config.advices[9],
|
|
|
|
magnitude_sign.map(|m_s| m_s.1),
|
|
|
|
)?;
|
|
|
|
(magnitude, sign)
|
|
|
|
};
|
|
|
|
|
|
|
|
// commitment = [v_net] ValueCommitV
|
|
|
|
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)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// blind = [rcv] ValueCommitR
|
2021-07-20 01:22:08 -07:00
|
|
|
let (blind, _rcv) = {
|
2021-07-22 07:14:34 -07:00
|
|
|
let rcv = self.rcv.as_ref().map(|rcv| rcv.inner());
|
2021-06-06 04:44:24 -07:00
|
|
|
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
|
|
|
|
let value_commit_r = FixedPoint::from_inner(ecc_chip.clone(), value_commit_r);
|
|
|
|
|
|
|
|
// [rcv] ValueCommitR
|
|
|
|
value_commit_r.mul(layouter.namespace(|| "[rcv] ValueCommitR"), rcv)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// [v_net] ValueCommitV + [rcv] ValueCommitR
|
2021-07-19 09:42:22 -07:00
|
|
|
let cv_net = commitment.add(layouter.namespace(|| "cv_net"), &blind)?;
|
|
|
|
|
|
|
|
// Constrain cv_net to equal public input
|
|
|
|
layouter.constrain_instance(cv_net.inner().x().cell(), config.primary, CV_NET_X)?;
|
|
|
|
layouter.constrain_instance(cv_net.inner().y().cell(), config.primary, CV_NET_Y)?;
|
|
|
|
|
|
|
|
v_net
|
2021-06-06 04:44:24 -07:00
|
|
|
};
|
|
|
|
|
2021-06-06 05:05:51 -07:00
|
|
|
// Nullifier integrity
|
2021-06-07 03:45:03 -07:00
|
|
|
let nf_old = {
|
2021-06-06 05:05:51 -07:00
|
|
|
// nk_rho_old = poseidon_hash(nk, rho_old)
|
|
|
|
let nk_rho_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())?;
|
2021-07-22 07:14:34 -07:00
|
|
|
Ok(Word::<_, _, poseidon::OrchardNullifier, 3, 2>::from_inner(
|
|
|
|
StateWord::new(var, value),
|
|
|
|
))
|
2021-06-06 05:05:51 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
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(
|
|
|
|
layouter.namespace(|| "Poseidon hash (nk, rho_old)"),
|
|
|
|
poseidon_message,
|
|
|
|
)?;
|
2021-07-22 07:14:34 -07:00
|
|
|
let poseidon_output: CellValue<pallas::Base> = poseidon_output.inner().into();
|
2021-06-06 05:05:51 -07:00
|
|
|
poseidon_output
|
|
|
|
};
|
|
|
|
|
2021-07-23 02:58:37 -07:00
|
|
|
// Add hash output to psi.
|
2021-06-06 05:05:51 -07:00
|
|
|
// `scalar` = poseidon_hash(nk, rho_old) + psi_old.
|
|
|
|
//
|
2021-07-23 02:58:37 -07:00
|
|
|
let scalar = layouter.assign_region(
|
|
|
|
|| " `scalar` = poseidon_hash(nk, rho_old) + psi_old",
|
|
|
|
|mut region| {
|
|
|
|
config.q_add.enable(&mut region, 0)?;
|
|
|
|
|
|
|
|
copy(
|
|
|
|
&mut region,
|
|
|
|
|| "copy nk_rho_old",
|
|
|
|
config.advices[1],
|
|
|
|
0,
|
|
|
|
&nk_rho_old,
|
|
|
|
)?;
|
|
|
|
copy(
|
|
|
|
&mut region,
|
|
|
|
|| "copy psi_old",
|
|
|
|
config.advices[2],
|
|
|
|
0,
|
|
|
|
&psi_old,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let scalar_val = nk_rho_old
|
|
|
|
.value()
|
|
|
|
.zip(psi_old.value())
|
|
|
|
.map(|(nk_rho_old, psi_old)| nk_rho_old + psi_old);
|
|
|
|
let cell = region.assign_advice(
|
|
|
|
|| "poseidon_hash(nk, rho_old) + psi_old",
|
|
|
|
config.advices[0],
|
|
|
|
0,
|
|
|
|
|| scalar_val.ok_or(plonk::Error::SynthesisError),
|
|
|
|
)?;
|
|
|
|
Ok(CellValue::new(cell, scalar_val))
|
|
|
|
},
|
|
|
|
)?;
|
2021-06-06 05:05:51 -07:00
|
|
|
|
|
|
|
// Multiply scalar by NullifierK
|
|
|
|
// `product` = [poseidon_hash(nk, rho_old) + psi_old] NullifierK.
|
|
|
|
//
|
|
|
|
let product = {
|
|
|
|
let nullifier_k = FixedPointBaseField::from_inner(ecc_chip.clone(), NullifierK);
|
|
|
|
nullifier_k.mul(
|
|
|
|
layouter.namespace(|| "[poseidon_output + psi_old] NullifierK"),
|
|
|
|
scalar,
|
|
|
|
)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add cm_old to multiplied fixed base to get nf_old
|
|
|
|
// cm_old + [poseidon_output + psi_old] NullifierK
|
2021-07-19 09:42:22 -07:00
|
|
|
let nf_old = cm_old
|
2021-06-06 05:05:51 -07:00
|
|
|
.add(layouter.namespace(|| "nf_old"), &product)?
|
2021-07-19 09:42:22 -07:00
|
|
|
.extract_p();
|
|
|
|
|
|
|
|
// Constrain nf_old to equal public input
|
|
|
|
layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?;
|
|
|
|
|
|
|
|
nf_old
|
2021-06-06 05:05:51 -07:00
|
|
|
};
|
|
|
|
|
2021-06-06 05:07:18 -07:00
|
|
|
// Spend authority
|
2021-07-19 09:42:22 -07:00
|
|
|
{
|
2021-06-06 05:07:18 -07:00
|
|
|
// alpha_commitment = [alpha] SpendAuthG
|
|
|
|
let (alpha_commitment, _) = {
|
|
|
|
let spend_auth_g = OrchardFixedBasesFull::SpendAuthG;
|
|
|
|
let spend_auth_g = FixedPoint::from_inner(ecc_chip.clone(), spend_auth_g);
|
|
|
|
spend_auth_g.mul(layouter.namespace(|| "[alpha] SpendAuthG"), self.alpha)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// [alpha] SpendAuthG + ak
|
2021-07-19 09:42:22 -07:00
|
|
|
let rk = alpha_commitment.add(layouter.namespace(|| "rk"), &ak)?;
|
|
|
|
|
|
|
|
// Constrain rk to equal public input
|
|
|
|
layouter.constrain_instance(rk.inner().x().cell(), config.primary, RK_X)?;
|
|
|
|
layouter.constrain_instance(rk.inner().y().cell(), config.primary, RK_Y)?;
|
|
|
|
}
|
2021-06-06 05:07:18 -07:00
|
|
|
|
2021-06-06 10:00:24 -07:00
|
|
|
// Diversified address integrity.
|
2021-07-19 09:42:22 -07:00
|
|
|
let pk_d_old = {
|
2021-06-06 10:00:24 -07:00
|
|
|
let commit_ivk_config = config.commit_ivk_config.clone();
|
|
|
|
|
|
|
|
let ivk = {
|
2021-07-22 07:14:34 -07:00
|
|
|
let rivk = self.rivk.map(|rivk| rivk.inner());
|
2021-06-06 10:00:24 -07:00
|
|
|
|
|
|
|
commit_ivk_config.assign_region(
|
|
|
|
config.sinsemilla_chip_1(),
|
|
|
|
ecc_chip.clone(),
|
|
|
|
layouter.namespace(|| "CommitIvk"),
|
|
|
|
*ak.extract_p().inner(),
|
|
|
|
nk,
|
|
|
|
rivk,
|
|
|
|
)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// [ivk] g_d_old
|
2021-07-20 01:22:08 -07:00
|
|
|
// The scalar value is passed through and discarded.
|
|
|
|
let (derived_pk_d_old, _ivk) =
|
2021-07-19 09:42:22 -07:00
|
|
|
g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk.inner())?;
|
|
|
|
|
|
|
|
// Constrain derived pk_d_old to equal witnessed pk_d_old
|
|
|
|
// This addresses the case where ivk = ⊥ , since ⊥ maps to 0 and variable-base
|
|
|
|
// scalar multiplication maps [0]B to (0, 0).
|
|
|
|
let pk_d_old = Point::new(
|
|
|
|
ecc_chip.clone(),
|
|
|
|
layouter.namespace(|| "witness pk_d_old"),
|
2021-07-22 07:14:34 -07:00
|
|
|
self.pk_d_old.map(|pk_d_old| pk_d_old.inner().to_affine()),
|
2021-07-19 09:42:22 -07:00
|
|
|
)?;
|
|
|
|
derived_pk_d_old
|
|
|
|
.constrain_equal(layouter.namespace(|| "pk_d_old equality"), &pk_d_old)?;
|
|
|
|
|
|
|
|
pk_d_old
|
2021-06-06 10:00:24 -07:00
|
|
|
};
|
|
|
|
|
2021-06-07 03:24:58 -07:00
|
|
|
// Old note commitment integrity.
|
2021-07-19 09:42:22 -07:00
|
|
|
{
|
2021-06-07 03:24:58 -07:00
|
|
|
let old_note_commit_config = config.old_note_commit_config.clone();
|
|
|
|
|
2021-07-22 07:14:34 -07:00
|
|
|
let rcm_old = self.rcm_old.as_ref().map(|rcm_old| rcm_old.inner());
|
2021-06-07 03:24:58 -07:00
|
|
|
|
|
|
|
// g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
|
2021-07-19 09:42:22 -07:00
|
|
|
let derived_cm_old = old_note_commit_config.assign_region(
|
2021-06-07 03:24:58 -07:00
|
|
|
layouter.namespace(|| {
|
|
|
|
"g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
|
|
|
|
}),
|
|
|
|
config.sinsemilla_chip_1(),
|
|
|
|
config.ecc_chip(),
|
|
|
|
g_d_old.inner(),
|
|
|
|
pk_d_old.inner(),
|
|
|
|
v_old,
|
|
|
|
rho_old,
|
|
|
|
psi_old,
|
|
|
|
rcm_old,
|
2021-07-19 09:42:22 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// Constrain derived cm_old to equal witnessed cm_old
|
|
|
|
derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?;
|
|
|
|
}
|
2021-06-07 03:24:58 -07:00
|
|
|
|
2021-07-19 09:42:22 -07:00
|
|
|
// New note commitment integrity.
|
|
|
|
{
|
2021-06-07 03:45:03 -07:00
|
|
|
let new_note_commit_config = config.new_note_commit_config.clone();
|
|
|
|
|
|
|
|
// Witness g_d_new_star
|
|
|
|
let g_d_new = {
|
|
|
|
let g_d_new = self
|
|
|
|
.g_d_new_star
|
|
|
|
.map(|bytes| pallas::Affine::from_bytes(&bytes).unwrap());
|
|
|
|
Point::new(
|
|
|
|
ecc_chip.clone(),
|
|
|
|
layouter.namespace(|| "witness g_d_new_star"),
|
|
|
|
g_d_new,
|
|
|
|
)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// Witness pk_d_new_star
|
|
|
|
let pk_d_new = {
|
|
|
|
let pk_d_new = self
|
|
|
|
.pk_d_new_star
|
|
|
|
.map(|bytes| pallas::Affine::from_bytes(&bytes).unwrap());
|
|
|
|
Point::new(
|
|
|
|
ecc_chip,
|
|
|
|
layouter.namespace(|| "witness pk_d_new"),
|
|
|
|
pk_d_new,
|
|
|
|
)?
|
|
|
|
};
|
|
|
|
|
|
|
|
// Witness psi_new
|
|
|
|
let psi_new = self.load_private(
|
|
|
|
layouter.namespace(|| "witness psi_new"),
|
|
|
|
config.advices[0],
|
|
|
|
self.psi_new,
|
|
|
|
)?;
|
|
|
|
|
2021-07-22 07:14:34 -07:00
|
|
|
let rcm_new = self.rcm_new.as_ref().map(|rcm_new| rcm_new.inner());
|
2021-06-07 03:45:03 -07:00
|
|
|
|
|
|
|
// g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)
|
|
|
|
let cm_new = new_note_commit_config.assign_region(
|
|
|
|
layouter.namespace(|| {
|
|
|
|
"g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)"
|
|
|
|
}),
|
|
|
|
config.sinsemilla_chip_2(),
|
|
|
|
config.ecc_chip(),
|
|
|
|
g_d_new.inner(),
|
|
|
|
pk_d_new.inner(),
|
|
|
|
v_new,
|
|
|
|
*nf_old.inner(),
|
|
|
|
psi_new,
|
|
|
|
rcm_new,
|
|
|
|
)?;
|
|
|
|
|
2021-07-19 09:42:22 -07:00
|
|
|
let cmx = cm_new.extract_p();
|
|
|
|
|
|
|
|
// Constrain cmx to equal public input
|
|
|
|
layouter.constrain_instance(cmx.inner().cell(), config.primary, CMX)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constrain v_old - v_new = magnitude * sign
|
|
|
|
// Either v_old = 0, or anchor equals public input
|
|
|
|
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)?;
|
|
|
|
|
|
|
|
copy(&mut region, || "anchor", config.advices[4], 0, &anchor)?;
|
|
|
|
region.assign_advice_from_instance(
|
|
|
|
|| "pub input anchor",
|
|
|
|
config.primary,
|
|
|
|
ANCHOR,
|
|
|
|
config.advices[5],
|
|
|
|
0,
|
|
|
|
)?;
|
|
|
|
|
2021-07-19 10:01:39 -07:00
|
|
|
region.assign_advice_from_instance(
|
|
|
|
|| "enable spends",
|
|
|
|
config.primary,
|
|
|
|
ENABLE_SPEND,
|
|
|
|
config.advices[6],
|
|
|
|
0,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
region.assign_advice_from_instance(
|
|
|
|
|| "enable outputs",
|
|
|
|
config.primary,
|
|
|
|
ENABLE_OUTPUT,
|
|
|
|
config.advices[7],
|
|
|
|
0,
|
|
|
|
)?;
|
|
|
|
|
2021-07-19 09:42:22 -07:00
|
|
|
config.q_orchard.enable(&mut region, 0)
|
|
|
|
},
|
|
|
|
)?;
|
2021-06-07 03:45:03 -07:00
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The verifying key for the Orchard Action circuit.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct VerifyingKey {
|
|
|
|
params: halo2::poly::commitment::Params<vesta::Affine>,
|
|
|
|
vk: plonk::VerifyingKey<vesta::Affine>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VerifyingKey {
|
|
|
|
/// Builds the verifying key.
|
|
|
|
pub fn build() -> Self {
|
|
|
|
let params = halo2::poly::commitment::Params::new(K);
|
2021-07-20 01:22:08 -07:00
|
|
|
let circuit: Circuit = Default::default();
|
2021-04-14 21:35:48 -07:00
|
|
|
|
|
|
|
let vk = plonk::keygen_vk(¶ms, &circuit).unwrap();
|
|
|
|
|
|
|
|
VerifyingKey { params, vk }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The proving key for the Orchard Action circuit.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ProvingKey {
|
|
|
|
params: halo2::poly::commitment::Params<vesta::Affine>,
|
|
|
|
pk: plonk::ProvingKey<vesta::Affine>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ProvingKey {
|
|
|
|
/// Builds the proving key.
|
|
|
|
pub fn build() -> Self {
|
|
|
|
let params = halo2::poly::commitment::Params::new(K);
|
2021-07-20 01:22:08 -07:00
|
|
|
let circuit: Circuit = Default::default();
|
2021-04-14 21:35:48 -07:00
|
|
|
|
|
|
|
let vk = plonk::keygen_vk(¶ms, &circuit).unwrap();
|
|
|
|
let pk = plonk::keygen_pk(¶ms, vk, &circuit).unwrap();
|
|
|
|
|
|
|
|
ProvingKey { params, pk }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Public inputs to the Orchard Action circuit.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Instance {
|
|
|
|
pub(crate) anchor: Anchor,
|
|
|
|
pub(crate) cv_net: ValueCommitment,
|
|
|
|
pub(crate) nf_old: Nullifier,
|
|
|
|
pub(crate) rk: VerificationKey<SpendAuth>,
|
|
|
|
pub(crate) cmx: ExtractedNoteCommitment,
|
|
|
|
pub(crate) enable_spend: bool,
|
|
|
|
pub(crate) enable_output: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Instance {
|
2021-07-20 01:22:08 -07:00
|
|
|
fn to_halo2_instance(&self) -> [[vesta::Scalar; 9]; 1] {
|
|
|
|
let mut instance = [vesta::Scalar::zero(); 9];
|
2021-07-19 09:42:22 -07:00
|
|
|
|
2021-07-22 07:14:34 -07:00
|
|
|
instance[ANCHOR] = self.anchor.inner();
|
2021-07-19 09:42:22 -07:00
|
|
|
instance[CV_NET_X] = self.cv_net.x();
|
|
|
|
instance[CV_NET_Y] = self.cv_net.y();
|
2021-07-22 07:14:34 -07:00
|
|
|
instance[NF_OLD] = self.nf_old.0;
|
2021-07-19 09:42:22 -07:00
|
|
|
|
|
|
|
let rk = pallas::Point::from_bytes(&self.rk.clone().into())
|
|
|
|
.unwrap()
|
|
|
|
.to_affine()
|
|
|
|
.coordinates()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
instance[RK_X] = *rk.x();
|
|
|
|
instance[RK_Y] = *rk.y();
|
2021-07-22 07:14:34 -07:00
|
|
|
instance[CMX] = self.cmx.inner();
|
2021-07-19 09:42:22 -07:00
|
|
|
instance[ENABLE_SPEND] = vesta::Scalar::from_u64(self.enable_spend.into());
|
|
|
|
instance[ENABLE_OUTPUT] = vesta::Scalar::from_u64(self.enable_output.into());
|
|
|
|
|
|
|
|
[instance]
|
2021-04-14 21:35:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A proof of the validity of an Orchard [`Bundle`].
|
|
|
|
///
|
|
|
|
/// [`Bundle`]: crate::bundle::Bundle
|
2021-05-13 14:35:44 -07:00
|
|
|
#[derive(Debug, Clone)]
|
2021-05-05 10:46:24 -07:00
|
|
|
pub struct Proof(Vec<u8>);
|
2021-04-14 21:35:48 -07:00
|
|
|
|
2021-05-05 10:37:56 -07:00
|
|
|
impl AsRef<[u8]> for Proof {
|
|
|
|
fn as_ref(&self) -> &[u8] {
|
|
|
|
&self.0
|
2021-04-21 13:32:54 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
impl Proof {
|
2021-06-22 08:06:49 -07:00
|
|
|
/// Returns the amount of heap-allocated memory used by this proof.
|
|
|
|
pub(crate) fn dynamic_usage(&self) -> usize {
|
|
|
|
self.0.capacity() * mem::size_of::<u8>()
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
/// Creates a proof for the given circuits and instances.
|
|
|
|
pub fn create(
|
|
|
|
pk: &ProvingKey,
|
|
|
|
circuits: &[Circuit],
|
|
|
|
instances: &[Instance],
|
|
|
|
) -> Result<Self, plonk::Error> {
|
2021-07-15 04:52:15 -07:00
|
|
|
let instances: Vec<_> = instances.iter().map(|i| i.to_halo2_instance()).collect();
|
|
|
|
let instances: Vec<Vec<_>> = instances
|
2021-04-14 21:35:48 -07:00
|
|
|
.iter()
|
2021-07-15 04:52:15 -07:00
|
|
|
.map(|i| i.iter().map(|c| &c[..]).collect())
|
2021-04-14 21:35:48 -07:00
|
|
|
.collect();
|
|
|
|
let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect();
|
|
|
|
|
2021-06-01 09:37:44 -07:00
|
|
|
let mut transcript = Blake2bWrite::<_, vesta::Affine, _>::init(vec![]);
|
2021-04-14 21:35:48 -07:00
|
|
|
plonk::create_proof(&pk.params, &pk.pk, circuits, &instances, &mut transcript)?;
|
|
|
|
Ok(Proof(transcript.finalize()))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verifies this proof with the given instances.
|
|
|
|
pub fn verify(&self, vk: &VerifyingKey, instances: &[Instance]) -> Result<(), plonk::Error> {
|
2021-07-15 04:52:15 -07:00
|
|
|
let instances: Vec<_> = instances.iter().map(|i| i.to_halo2_instance()).collect();
|
|
|
|
let instances: Vec<Vec<_>> = instances
|
2021-04-14 21:35:48 -07:00
|
|
|
.iter()
|
2021-07-15 04:52:15 -07:00
|
|
|
.map(|i| i.iter().map(|c| &c[..]).collect())
|
2021-04-14 21:35:48 -07:00
|
|
|
.collect();
|
|
|
|
let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect();
|
|
|
|
|
|
|
|
let msm = vk.params.empty_msm();
|
|
|
|
let mut transcript = Blake2bRead::init(&self.0[..]);
|
|
|
|
let guard = plonk::verify_proof(&vk.params, &vk.vk, msm, &instances, &mut transcript)?;
|
|
|
|
let msm = guard.clone().use_challenges();
|
|
|
|
if msm.eval() {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(plonk::Error::ConstraintSystemFailure)
|
|
|
|
}
|
|
|
|
}
|
2021-04-21 08:57:48 -07:00
|
|
|
|
2021-05-05 10:37:56 -07:00
|
|
|
/// Constructs a new Proof value.
|
2021-04-21 08:57:48 -07:00
|
|
|
pub fn new(bytes: Vec<u8>) -> Self {
|
|
|
|
Proof(bytes)
|
|
|
|
}
|
2021-04-14 21:35:48 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use ff::Field;
|
2021-06-06 04:21:27 -07:00
|
|
|
use group::GroupEncoding;
|
2021-04-14 21:35:48 -07:00
|
|
|
use halo2::dev::MockProver;
|
|
|
|
use pasta_curves::pallas;
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
use std::iter;
|
|
|
|
|
|
|
|
use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
|
|
|
|
use crate::{
|
|
|
|
keys::SpendValidatingKey,
|
|
|
|
note::Note,
|
2021-06-06 04:21:27 -07:00
|
|
|
tree::MerklePath,
|
2021-04-14 21:35:48 -07:00
|
|
|
value::{ValueCommitTrapdoor, ValueCommitment},
|
|
|
|
};
|
|
|
|
|
2021-04-21 08:57:48 -07:00
|
|
|
// TODO: recast as a proptest
|
2021-04-14 21:35:48 -07:00
|
|
|
#[test]
|
|
|
|
fn round_trip() {
|
|
|
|
let mut rng = OsRng;
|
|
|
|
|
|
|
|
let (circuits, instances): (Vec<_>, Vec<_>) = iter::once(())
|
|
|
|
.map(|()| {
|
2021-04-26 17:25:03 -07:00
|
|
|
let (_, fvk, spent_note) = Note::dummy(&mut rng, None);
|
2021-07-19 09:42:22 -07:00
|
|
|
|
2021-06-06 04:21:27 -07:00
|
|
|
let sender_address = fvk.default_address();
|
|
|
|
let nk = *fvk.nk();
|
|
|
|
let rivk = *fvk.rivk();
|
2021-04-14 21:35:48 -07:00
|
|
|
let nf_old = spent_note.nullifier(&fvk);
|
|
|
|
let ak: SpendValidatingKey = fvk.into();
|
|
|
|
let alpha = pallas::Scalar::random(&mut rng);
|
|
|
|
let rk = ak.randomize(&alpha);
|
|
|
|
|
2021-05-28 04:06:50 -07:00
|
|
|
let (_, _, output_note) = Note::dummy(&mut rng, Some(nf_old));
|
2021-04-14 21:35:48 -07:00
|
|
|
let cmx = output_note.commitment().into();
|
|
|
|
|
|
|
|
let value = spent_note.value() - output_note.value();
|
|
|
|
let cv_net = ValueCommitment::derive(value.unwrap(), ValueCommitTrapdoor::zero());
|
|
|
|
|
2021-06-06 04:21:27 -07:00
|
|
|
let path = MerklePath::dummy(&mut rng);
|
|
|
|
let anchor = path.root(spent_note.commitment().into()).unwrap();
|
|
|
|
|
2021-04-14 21:35:48 -07:00
|
|
|
(
|
2021-06-06 04:21:27 -07:00
|
|
|
Circuit {
|
|
|
|
path: Some(path.auth_path()),
|
|
|
|
pos: Some(path.position()),
|
|
|
|
g_d_old: Some(sender_address.g_d()),
|
|
|
|
pk_d_old: Some(*sender_address.pk_d()),
|
|
|
|
v_old: Some(spent_note.value()),
|
|
|
|
rho_old: Some(spent_note.rho()),
|
|
|
|
psi_old: Some(spent_note.rseed().psi(&spent_note.rho())),
|
|
|
|
rcm_old: Some(spent_note.rseed().rcm(&spent_note.rho())),
|
|
|
|
cm_old: Some(spent_note.commitment()),
|
|
|
|
alpha: Some(alpha),
|
|
|
|
ak: Some(ak),
|
|
|
|
nk: Some(nk),
|
|
|
|
rivk: Some(rivk),
|
|
|
|
g_d_new_star: Some((*output_note.recipient().g_d()).to_bytes()),
|
|
|
|
pk_d_new_star: Some(output_note.recipient().pk_d().to_bytes()),
|
|
|
|
v_new: Some(output_note.value()),
|
|
|
|
psi_new: Some(output_note.rseed().psi(&output_note.rho())),
|
|
|
|
rcm_new: Some(output_note.rseed().rcm(&output_note.rho())),
|
|
|
|
rcv: Some(ValueCommitTrapdoor::zero()),
|
|
|
|
},
|
2021-04-14 21:35:48 -07:00
|
|
|
Instance {
|
2021-06-06 04:21:27 -07:00
|
|
|
anchor,
|
2021-04-14 21:35:48 -07:00
|
|
|
cv_net,
|
|
|
|
nf_old,
|
|
|
|
rk,
|
|
|
|
cmx,
|
|
|
|
enable_spend: true,
|
|
|
|
enable_output: true,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.unzip();
|
|
|
|
|
|
|
|
let vk = VerifyingKey::build();
|
|
|
|
for (circuit, instance) in circuits.iter().zip(instances.iter()) {
|
|
|
|
assert_eq!(
|
|
|
|
MockProver::run(
|
|
|
|
K,
|
|
|
|
circuit,
|
|
|
|
instance
|
2021-07-15 04:52:15 -07:00
|
|
|
.to_halo2_instance()
|
2021-04-14 21:35:48 -07:00
|
|
|
.iter()
|
2021-07-15 04:52:15 -07:00
|
|
|
.map(|p| p.to_vec())
|
2021-04-14 21:35:48 -07:00
|
|
|
.collect()
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
.verify(),
|
|
|
|
Ok(())
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let pk = ProvingKey::build();
|
|
|
|
let proof = Proof::create(&pk, &circuits, &instances).unwrap();
|
|
|
|
assert!(proof.verify(&vk, &instances).is_ok());
|
|
|
|
}
|
2021-07-20 01:22:08 -07:00
|
|
|
|
|
|
|
#[cfg(feature = "dev-graph")]
|
|
|
|
#[test]
|
|
|
|
fn print_action_circuit() {
|
|
|
|
use plotters::prelude::*;
|
|
|
|
|
|
|
|
let root = BitMapBackend::new("action-circuit-layout.png", (1024, 768)).into_drawing_area();
|
|
|
|
root.fill(&WHITE).unwrap();
|
|
|
|
let root = root
|
|
|
|
.titled("Orchard Action Circuit", ("sans-serif", 60))
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let circuit = Circuit {
|
|
|
|
path: None,
|
|
|
|
pos: None,
|
|
|
|
g_d_old: None,
|
|
|
|
pk_d_old: None,
|
|
|
|
v_old: None,
|
|
|
|
rho_old: None,
|
|
|
|
psi_old: None,
|
|
|
|
rcm_old: None,
|
|
|
|
cm_old: None,
|
|
|
|
alpha: None,
|
|
|
|
ak: None,
|
|
|
|
nk: None,
|
|
|
|
rivk: None,
|
|
|
|
g_d_new_star: None,
|
|
|
|
pk_d_new_star: None,
|
|
|
|
v_new: None,
|
|
|
|
psi_new: None,
|
|
|
|
rcm_new: None,
|
|
|
|
rcv: None,
|
|
|
|
};
|
|
|
|
halo2::dev::CircuitLayout::default()
|
|
|
|
.show_labels(false)
|
|
|
|
.view_height(0..(1 << 11))
|
|
|
|
.render(&circuit, &root)
|
|
|
|
.unwrap();
|
|
|
|
}
|
2021-04-14 21:35:48 -07:00
|
|
|
}
|