Bring in refactor of the crate

This refactor was performed over several years. The crate was previously
focused on the Sapling circuit; it now encompasses the Sapling protocol,
and has types better suited to use in non-circuit contexts. Many of the
circuit helper types were moved into the `bellman` crate, and the Sprout
circuit was left in `zcash_primitives`.

Source: https://github.com/zcash/librustzcash
Rev: zcash/librustzcash@6acc64e61c
This commit is contained in:
Jack Grigg 2023-12-11 17:17:00 +00:00
parent 49017b4e05
commit df6681c104
55 changed files with 13509 additions and 11059 deletions

View File

@ -1,31 +1,72 @@
[package]
authors = ["Sean Bowe <sean@z.cash>"]
description = "Cryptographic library for Zcash Sapling"
documentation = "https://github.com/zcash-hackworks/sapling"
homepage = "https://github.com/zcash-hackworks/sapling"
license = "MIT/Apache-2.0"
name = "sapling-crypto"
repository = "https://github.com/zcash-hackworks/sapling"
version = "0.0.1"
[dependencies.pairing]
version = "0.14.2"
features = ["expose-arith"]
authors = [
"Sean Bowe <sean@electriccoin.co>",
"Jack Grigg <jack@electriccoin.co>",
"Kris Nuttycombe <kris@electriccoin.co>",
]
edition = "2021"
rust-version = "1.65"
description = "Cryptographic library for Zcash Sapling"
homepage = "https://github.com/zcash/sapling-crypto"
repository = "https://github.com/zcash/sapling-crypto"
license = "MIT OR Apache-2.0"
[dependencies]
rand = "0.4"
digest = "0.7"
bellman = "0.1"
byteorder = "1"
ff = "0.13"
group = "0.13"
[dependencies.blake2-rfc]
git = "https://github.com/gtank/blake2-rfc"
rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9"
bls12_381 = "0.8"
jubjub = "0.10"
redjubjub = "0.7"
zcash_spec = "0.1"
# Circuits
bellman = { version = "0.14", default-features = false, features = ["groth16"] }
# CSPRNG
rand = "0.8"
rand_core = "0.6"
# Digests
blake2b_simd = "1"
blake2s_simd = "1"
# Encodings
byteorder = "1"
hex = "0.4"
# Logging and metrics
memuse = "0.2.1"
tracing = "0.1"
# Note Commitment Trees
bitvec = "1"
incrementalmerkletree = "0.5"
# Note encryption
zcash_note_encryption = "0.4"
# Secret management
subtle = "2.2.3"
# Static constants
lazy_static = "1"
# Test dependencies
proptest = { version = "1", optional = true }
# ZIP 32
aes = "0.8"
fpe = "0.6"
zip32 = "0.1"
[dev-dependencies]
hex-literal = "0.1"
rust-crypto = "0.2"
chacha20poly1305 = "0.10"
proptest = "1"
rand_xorshift = "0.3"
[features]
default = ["u128-support"]
u128-support = ["pairing/u128-support"]
multicore = ["bellman/multicore"]
test-dependencies = ["proptest"]

118
src/address.rs Normal file
View File

@ -0,0 +1,118 @@
use super::{
keys::{DiversifiedTransmissionKey, Diversifier},
note::{Note, Rseed},
value::NoteValue,
};
/// A Sapling payment address.
///
/// # Invariants
///
/// - `diversifier` is guaranteed to be valid for Sapling (only 50% of diversifiers are).
/// - `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub,
/// and not the identity).
#[derive(Clone, Copy, Debug)]
pub struct PaymentAddress {
pk_d: DiversifiedTransmissionKey,
diversifier: Diversifier,
}
impl PartialEq for PaymentAddress {
fn eq(&self, other: &Self) -> bool {
self.pk_d == other.pk_d && self.diversifier == other.diversifier
}
}
impl Eq for PaymentAddress {}
impl PaymentAddress {
/// Constructs a PaymentAddress from a diversifier and a Jubjub point.
///
/// Returns None if `diversifier` is not valid for Sapling, or `pk_d` is the identity.
/// Note that we cannot verify in this constructor that `pk_d` is derived from
/// `diversifier`, so addresses for which these values have no known relationship
/// (and therefore no-one can receive funds at them) can still be constructed.
pub fn from_parts(diversifier: Diversifier, pk_d: DiversifiedTransmissionKey) -> Option<Self> {
// Check that the diversifier is valid
diversifier.g_d()?;
Self::from_parts_unchecked(diversifier, pk_d)
}
/// Constructs a PaymentAddress from a diversifier and a Jubjub point.
///
/// Returns None if `pk_d` is the identity. The caller must check that `diversifier`
/// is valid for Sapling.
pub(crate) fn from_parts_unchecked(
diversifier: Diversifier,
pk_d: DiversifiedTransmissionKey,
) -> Option<Self> {
if pk_d.is_identity() {
None
} else {
Some(PaymentAddress { pk_d, diversifier })
}
}
/// Parses a PaymentAddress from bytes.
pub fn from_bytes(bytes: &[u8; 43]) -> Option<Self> {
let diversifier = {
let mut tmp = [0; 11];
tmp.copy_from_slice(&bytes[0..11]);
Diversifier(tmp)
};
let pk_d = DiversifiedTransmissionKey::from_bytes(bytes[11..43].try_into().unwrap());
if pk_d.is_some().into() {
// The remaining invariants are checked here.
PaymentAddress::from_parts(diversifier, pk_d.unwrap())
} else {
None
}
}
/// Returns the byte encoding of this `PaymentAddress`.
pub fn to_bytes(&self) -> [u8; 43] {
let mut bytes = [0; 43];
bytes[0..11].copy_from_slice(&self.diversifier.0);
bytes[11..].copy_from_slice(&self.pk_d.to_bytes());
bytes
}
/// Returns the [`Diversifier`] for this `PaymentAddress`.
pub fn diversifier(&self) -> &Diversifier {
&self.diversifier
}
/// Returns `pk_d` for this `PaymentAddress`.
pub fn pk_d(&self) -> &DiversifiedTransmissionKey {
&self.pk_d
}
pub(crate) fn g_d(&self) -> jubjub::SubgroupPoint {
self.diversifier.g_d().expect("checked at construction")
}
pub fn create_note(&self, value: NoteValue, rseed: Rseed) -> Note {
Note::from_parts(*self, value, rseed)
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub(super) mod testing {
use proptest::prelude::*;
use super::{
super::keys::{testing::arb_incoming_viewing_key, Diversifier, SaplingIvk},
PaymentAddress,
};
pub fn arb_payment_address() -> impl Strategy<Value = PaymentAddress> {
arb_incoming_viewing_key().prop_flat_map(|ivk: SaplingIvk| {
any::<[u8; 11]>().prop_filter_map(
"Sampled diversifier must generate a valid Sapling payment address.",
move |d| ivk.to_payment_address(Diversifier(d)),
)
})
}
}

929
src/builder.rs Normal file
View File

@ -0,0 +1,929 @@
//! Types and functions for building Sapling transaction components.
use core::fmt;
use std::marker::PhantomData;
use group::ff::Field;
use rand::{seq::SliceRandom, RngCore};
use rand_core::CryptoRng;
use redjubjub::{Binding, SpendAuth};
use crate::{
bundle::{
Authorization, Authorized, Bundle, GrothProofBytes, MapAuth, OutputDescription,
SpendDescription,
},
circuit,
keys::{OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey},
note_encryption::{sapling_note_encryption, Zip212Enforcement},
prover::{OutputProver, SpendProver},
util::generate_random_rseed_internal,
value::{
CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum,
},
zip32::ExtendedSpendingKey,
Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey, SaplingIvk,
};
/// If there are any shielded inputs, always have at least two shielded outputs, padding
/// with dummy outputs if necessary. See <https://github.com/zcash/zcash/issues/3615>.
const MIN_SHIELDED_OUTPUTS: usize = 2;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
AnchorMismatch,
BindingSig,
/// A signature is valid for more than one input. This should never happen if `alpha`
/// is sampled correctly, and indicates a critical failure in randomness generation.
DuplicateSignature,
InvalidAddress,
InvalidAmount,
/// External signature is not valid.
InvalidExternalSignature,
/// A bundle could not be built because required signatures were missing.
MissingSignatures,
SpendProof,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::AnchorMismatch => {
write!(f, "Anchor mismatch (anchors for all spends must be equal)")
}
Error::BindingSig => write!(f, "Failed to create bindingSig"),
Error::DuplicateSignature => write!(f, "Signature valid for more than one input"),
Error::InvalidAddress => write!(f, "Invalid address"),
Error::InvalidAmount => write!(f, "Invalid amount"),
Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
}
}
}
#[derive(Debug, Clone)]
pub struct SpendDescriptionInfo {
proof_generation_key: ProofGenerationKey,
note: Note,
alpha: jubjub::Fr,
merkle_path: MerklePath,
rcv: ValueCommitTrapdoor,
}
impl SpendDescriptionInfo {
fn new_internal<R: RngCore>(
mut rng: &mut R,
extsk: &ExtendedSpendingKey,
note: Note,
merkle_path: MerklePath,
) -> Self {
SpendDescriptionInfo {
proof_generation_key: extsk.expsk.proof_generation_key(),
note,
alpha: jubjub::Fr::random(&mut rng),
merkle_path,
rcv: ValueCommitTrapdoor::random(rng),
}
}
pub fn value(&self) -> NoteValue {
self.note.value()
}
fn build<Pr: SpendProver>(
self,
anchor: Option<bls12_381::Scalar>,
) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
let anchor = anchor.expect("Sapling anchor must be set if Sapling spends are present.");
// Construct the value commitment.
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
let ak = self.proof_generation_key.ak.clone();
// This is the result of the re-randomization, we compute it for the caller
let rk = ak.randomize(&self.alpha);
let nullifier = self.note.nf(
&self.proof_generation_key.to_viewing_key().nk,
u64::try_from(self.merkle_path.position())
.expect("Sapling note commitment tree position must fit into a u64"),
);
let zkproof = Pr::prepare_circuit(
self.proof_generation_key,
*self.note.recipient().diversifier(),
*self.note.rseed(),
self.note.value(),
self.alpha,
self.rcv,
anchor,
self.merkle_path.clone(),
)
.ok_or(Error::SpendProof)?;
Ok(SpendDescription::from_parts(
cv,
anchor,
nullifier,
rk,
zkproof,
SigningParts {
ak,
alpha: self.alpha,
},
))
}
}
/// A struct containing the information required in order to construct a
/// Sapling output to a transaction.
#[derive(Clone)]
pub struct SaplingOutputInfo {
/// `None` represents the `ovk = ⊥` case.
ovk: Option<OutgoingViewingKey>,
note: Note,
memo: Option<[u8; 512]>,
rcv: ValueCommitTrapdoor,
}
impl SaplingOutputInfo {
fn dummy<R: RngCore>(mut rng: &mut R, zip212_enforcement: Zip212Enforcement) -> Self {
// This is a dummy output
let dummy_to = {
let mut diversifier = Diversifier([0; 11]);
loop {
rng.fill_bytes(&mut diversifier.0);
let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
break addr;
}
}
};
Self::new_internal(
rng,
None,
dummy_to,
NoteValue::from_raw(0),
None,
zip212_enforcement,
)
}
fn new_internal<R: RngCore>(
rng: &mut R,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: NoteValue,
memo: Option<[u8; 512]>,
zip212_enforcement: Zip212Enforcement,
) -> Self {
let rseed = generate_random_rseed_internal(zip212_enforcement, rng);
let note = Note::from_parts(to, value, rseed);
SaplingOutputInfo {
ovk,
note,
memo,
rcv: ValueCommitTrapdoor::random(rng),
}
}
fn build<Pr: OutputProver, R: RngCore>(
self,
rng: &mut R,
) -> OutputDescription<circuit::Output> {
let encryptor = sapling_note_encryption::<R>(
self.ovk,
self.note.clone(),
self.memo.unwrap_or_else(|| {
let mut memo = [0; 512];
memo[0] = 0xf6;
memo
}),
rng,
);
// Construct the value commitment.
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
// Prepare the circuit that will be used to construct the proof.
let zkproof = Pr::prepare_circuit(
encryptor.esk().0,
self.note.recipient(),
self.note.rcm(),
self.note.value(),
self.rcv,
);
let cmu = self.note.cmu();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
let epk = encryptor.epk();
OutputDescription::from_parts(
cv,
cmu,
epk.to_bytes(),
enc_ciphertext,
out_ciphertext,
zkproof,
)
}
pub fn recipient(&self) -> PaymentAddress {
self.note.recipient()
}
pub fn value(&self) -> NoteValue {
self.note.value()
}
}
/// Metadata about a transaction created by a [`SaplingBuilder`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaplingMetadata {
spend_indices: Vec<usize>,
output_indices: Vec<usize>,
}
impl SaplingMetadata {
pub fn empty() -> Self {
SaplingMetadata {
spend_indices: vec![],
output_indices: vec![],
}
}
/// Returns the index within the transaction of the [`SpendDescription`] corresponding
/// to the `n`-th call to [`SaplingBuilder::add_spend`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first spend
/// they added (via the first call to [`SaplingBuilder::add_spend`]) is the first
/// [`SpendDescription`] in the transaction.
pub fn spend_index(&self, n: usize) -> Option<usize> {
self.spend_indices.get(n).copied()
}
/// Returns the index within the transaction of the [`OutputDescription`] corresponding
/// to the `n`-th call to [`SaplingBuilder::add_output`].
///
/// Note positions are randomized when building transactions for indistinguishability.
/// This means that the transaction consumer cannot assume that e.g. the first output
/// they added (via the first call to [`SaplingBuilder::add_output`]) is the first
/// [`OutputDescription`] in the transaction.
pub fn output_index(&self, n: usize) -> Option<usize> {
self.output_indices.get(n).copied()
}
}
pub struct SaplingBuilder {
anchor: Option<bls12_381::Scalar>,
value_balance: ValueSum,
spends: Vec<SpendDescriptionInfo>,
outputs: Vec<SaplingOutputInfo>,
zip212_enforcement: Zip212Enforcement,
}
impl SaplingBuilder {
pub fn new(zip212_enforcement: Zip212Enforcement) -> Self {
SaplingBuilder {
anchor: None,
value_balance: ValueSum::zero(),
spends: vec![],
outputs: vec![],
zip212_enforcement,
}
}
/// Returns the list of Sapling inputs that will be consumed by the transaction being
/// constructed.
pub fn inputs(&self) -> &[SpendDescriptionInfo] {
&self.spends
}
/// Returns the Sapling outputs that will be produced by the transaction being constructed
pub fn outputs(&self) -> &[SaplingOutputInfo] {
&self.outputs
}
/// Returns the number of outputs that will be present in the Sapling bundle built by
/// this builder.
///
/// This may be larger than the number of outputs that have been added to the builder,
/// depending on whether padding is going to be applied.
pub fn bundle_output_count(&self) -> usize {
// This matches the padding behaviour in `Self::build`.
match self.spends.len() {
0 => self.outputs.len(),
_ => std::cmp::max(MIN_SHIELDED_OUTPUTS, self.outputs.len()),
}
}
/// Returns the net value represented by the spends and outputs added to this builder,
/// or an error if the values added to this builder overflow the range of a Zcash
/// monetary amount.
fn try_value_balance<V: TryFrom<i64>>(&self) -> Result<V, Error> {
self.value_balance
.try_into()
.map_err(|_| ())
.and_then(|vb| V::try_from(vb).map_err(|_| ()))
.map_err(|()| Error::InvalidAmount)
}
/// Returns the net value represented by the spends and outputs added to this builder.
pub fn value_balance<V: TryFrom<i64>>(&self) -> V {
self.try_value_balance()
.expect("we check this when mutating self.value_balance")
}
/// Adds a Sapling note to be spent in this transaction.
///
/// Returns an error if the given Merkle path does not have the same anchor as the
/// paths for previous Sapling notes.
pub fn add_spend<R: RngCore>(
&mut self,
mut rng: R,
extsk: &ExtendedSpendingKey,
note: Note,
merkle_path: MerklePath,
) -> Result<(), Error> {
// Consistency check: all anchors must equal the first one
let node = Node::from_cmu(&note.cmu());
if let Some(anchor) = self.anchor {
let path_root: bls12_381::Scalar = merkle_path.root(node).into();
if path_root != anchor {
return Err(Error::AnchorMismatch);
}
} else {
self.anchor = Some(merkle_path.root(node).into())
}
self.value_balance = (self.value_balance + note.value()).ok_or(Error::InvalidAmount)?;
self.try_value_balance::<i64>()?;
let spend = SpendDescriptionInfo::new_internal(&mut rng, extsk, note, merkle_path);
self.spends.push(spend);
Ok(())
}
/// Adds a Sapling address to send funds to.
#[allow(clippy::too_many_arguments)]
pub fn add_output<R: RngCore>(
&mut self,
mut rng: R,
ovk: Option<OutgoingViewingKey>,
to: PaymentAddress,
value: NoteValue,
memo: Option<[u8; 512]>,
) -> Result<(), Error> {
let output = SaplingOutputInfo::new_internal(
&mut rng,
ovk,
to,
value,
memo,
self.zip212_enforcement,
);
self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
self.try_value_balance::<i64>()?;
self.outputs.push(output);
Ok(())
}
pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
self,
mut rng: R,
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
let value_balance = self.try_value_balance()?;
// Record initial positions of spends and outputs
let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect();
let mut indexed_outputs: Vec<_> = self
.outputs
.into_iter()
.enumerate()
.map(|(i, o)| Some((i, o)))
.collect();
// Set up the transaction metadata that will be used to record how
// inputs and outputs are shuffled.
let mut tx_metadata = SaplingMetadata::empty();
tx_metadata.spend_indices.resize(indexed_spends.len(), 0);
tx_metadata.output_indices.resize(indexed_outputs.len(), 0);
// Pad Sapling outputs
if !indexed_spends.is_empty() {
while indexed_outputs.len() < MIN_SHIELDED_OUTPUTS {
indexed_outputs.push(None);
}
}
// Randomize order of inputs and outputs
indexed_spends.shuffle(&mut rng);
indexed_outputs.shuffle(&mut rng);
// Record the transaction metadata and create dummy outputs.
let spend_infos = indexed_spends
.into_iter()
.enumerate()
.map(|(i, (pos, spend))| {
// Record the post-randomized spend location
tx_metadata.spend_indices[pos] = i;
spend
})
.collect::<Vec<_>>();
let output_infos = indexed_outputs
.into_iter()
.enumerate()
.map(|(i, output)| {
if let Some((pos, output)) = output {
// Record the post-randomized output location
tx_metadata.output_indices[pos] = i;
output
} else {
// This is a dummy output
SaplingOutputInfo::dummy(&mut rng, self.zip212_enforcement)
}
})
.collect::<Vec<_>>();
// Compute the transaction binding signing key.
let bsk = {
let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
(spends - outputs).into_bsk()
};
// Create the unauthorized Spend and Output descriptions.
let shielded_spends = spend_infos
.into_iter()
.map(|a| a.build::<SP>(self.anchor))
.collect::<Result<Vec<_>, _>>()?;
let shielded_outputs = output_infos
.into_iter()
.map(|a| a.build::<OP, _>(&mut rng))
.collect::<Vec<_>>();
// Verify that bsk and bvk are consistent.
let bvk = {
let spends = shielded_spends
.iter()
.map(|spend| spend.cv())
.sum::<CommitmentSum>();
let outputs = shielded_outputs
.iter()
.map(|output| output.cv())
.sum::<CommitmentSum>();
(spends - outputs)
.into_bvk(i64::try_from(self.value_balance).map_err(|_| Error::InvalidAmount)?)
};
assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
Ok(Bundle::from_parts(
shielded_spends,
shielded_outputs,
value_balance,
InProgress {
sigs: Unsigned { bsk },
_proof_state: PhantomData::default(),
},
)
.map(|b| (b, tx_metadata)))
}
}
/// Type alias for an in-progress bundle that has no proofs or signatures.
///
/// This is returned by [`SaplingBuilder::build`].
pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
/// Marker trait representing bundle proofs in the process of being created.
pub trait InProgressProofs: fmt::Debug {
/// The proof type of a Sapling spend in the process of being proven.
type SpendProof: Clone + fmt::Debug;
/// The proof type of a Sapling output in the process of being proven.
type OutputProof: Clone + fmt::Debug;
}
/// Marker trait representing bundle signatures in the process of being created.
pub trait InProgressSignatures: fmt::Debug {
/// The authorization type of a Sapling spend or output in the process of being
/// authorized.
type AuthSig: Clone + fmt::Debug;
}
/// Marker for a bundle in the process of being built.
#[derive(Clone, Debug)]
pub struct InProgress<P: InProgressProofs, S: InProgressSignatures> {
sigs: S,
_proof_state: PhantomData<P>,
}
impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<P, S> {
type SpendProof = P::SpendProof;
type OutputProof = P::OutputProof;
type AuthSig = S::AuthSig;
}
/// Marker for a [`Bundle`] without proofs.
///
/// The [`SpendDescription`]s and [`OutputDescription`]s within the bundle contain the
/// private data needed to create proofs.
#[derive(Clone, Copy, Debug)]
pub struct Unproven;
impl InProgressProofs for Unproven {
type SpendProof = circuit::Spend;
type OutputProof = circuit::Output;
}
/// Marker for a [`Bundle`] with proofs.
#[derive(Clone, Copy, Debug)]
pub struct Proven;
impl InProgressProofs for Proven {
type SpendProof = GrothProofBytes;
type OutputProof = GrothProofBytes;
}
/// Reports on the progress made towards creating proofs for a bundle.
pub trait ProverProgress {
/// Updates the progress instance with the number of steps completed and the total
/// number of steps.
fn update(&mut self, cur: u32, end: u32);
}
impl ProverProgress for () {
fn update(&mut self, _: u32, _: u32) {}
}
impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
fn update(&mut self, cur: u32, end: u32) {
// If the send fails, we should ignore the error, not crash.
self.send(U::from((cur, end))).unwrap_or(());
}
}
impl<U: ProverProgress> ProverProgress for &mut U {
fn update(&mut self, cur: u32, end: u32) {
(*self).update(cur, end);
}
}
struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> {
spend_prover: &'a SP,
output_prover: &'a OP,
rng: R,
progress_notifier: U,
total_progress: u32,
progress: u32,
}
impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
CreateProofs<'a, SP, OP, R, U>
{
fn new(
spend_prover: &'a SP,
output_prover: &'a OP,
rng: R,
progress_notifier: U,
total_progress: u32,
) -> Self {
// Keep track of the total number of steps computed
Self {
spend_prover,
output_prover,
rng,
progress_notifier,
total_progress,
progress: 0u32,
}
}
fn update_progress(&mut self) {
// Update progress and send a notification on the channel
self.progress += 1;
self.progress_notifier
.update(self.progress, self.total_progress);
}
}
impl<
'a,
S: InProgressSignatures,
SP: SpendProver,
OP: OutputProver,
R: RngCore,
U: ProverProgress,
> MapAuth<InProgress<Unproven, S>, InProgress<Proven, S>> for CreateProofs<'a, SP, OP, R, U>
{
fn map_spend_proof(&mut self, spend: circuit::Spend) -> GrothProofBytes {
let proof = self.spend_prover.create_proof(spend, &mut self.rng);
self.update_progress();
SP::encode_proof(proof)
}
fn map_output_proof(&mut self, output: circuit::Output) -> GrothProofBytes {
let proof = self.output_prover.create_proof(output, &mut self.rng);
self.update_progress();
OP::encode_proof(proof)
}
fn map_auth_sig(&mut self, s: S::AuthSig) -> S::AuthSig {
s
}
fn map_authorization(&mut self, a: InProgress<Unproven, S>) -> InProgress<Proven, S> {
InProgress {
sigs: a.sigs,
_proof_state: PhantomData::default(),
}
}
}
impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
/// Creates the proofs for this bundle.
pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
self,
spend_prover: &SP,
output_prover: &OP,
rng: impl RngCore,
progress_notifier: impl ProverProgress,
) -> Bundle<InProgress<Proven, S>, V> {
let total_progress =
self.shielded_spends().len() as u32 + self.shielded_outputs().len() as u32;
self.map_authorization(CreateProofs::new(
spend_prover,
output_prover,
rng,
progress_notifier,
total_progress,
))
}
}
/// Marker for an unauthorized bundle with no signatures.
#[derive(Clone)]
pub struct Unsigned {
bsk: redjubjub::SigningKey<Binding>,
}
impl fmt::Debug for Unsigned {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Unsigned").finish_non_exhaustive()
}
}
impl InProgressSignatures for Unsigned {
type AuthSig = SigningParts;
}
/// The parts needed to sign a [`SpendDescription`].
#[derive(Clone, Debug)]
pub struct SigningParts {
/// The spend validating key for this spend description. Used to match spend
/// authorizing keys to spend descriptions they can create signatures for.
ak: SpendValidatingKey,
/// The randomization needed to derive the actual signing key for this note.
alpha: jubjub::Scalar,
}
/// Marker for a partially-authorized bundle, in the process of being signed.
#[derive(Clone, Debug)]
pub struct PartiallyAuthorized {
binding_signature: redjubjub::Signature<Binding>,
sighash: [u8; 32],
}
impl InProgressSignatures for PartiallyAuthorized {
type AuthSig = MaybeSigned;
}
/// A heisen[`Signature`] for a particular [`SpendDescription`].
///
/// [`Signature`]: redjubjub::Signature
#[derive(Clone, Debug)]
pub enum MaybeSigned {
/// The information needed to sign this [`SpendDescription`].
SigningMetadata(SigningParts),
/// The signature for this [`SpendDescription`].
Signature(redjubjub::Signature<SpendAuth>),
}
impl MaybeSigned {
fn finalize(self) -> Result<redjubjub::Signature<SpendAuth>, Error> {
match self {
Self::Signature(sig) => Ok(sig),
_ => Err(Error::MissingSignatures),
}
}
}
impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
/// Loads the sighash into this bundle, preparing it for signing.
///
/// This API ensures that all signatures are created over the same sighash.
pub fn prepare<R: RngCore + CryptoRng>(
self,
mut rng: R,
sighash: [u8; 32],
) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
self.map_authorization((
|proof| proof,
|proof| proof,
MaybeSigned::SigningMetadata,
|auth: InProgress<P, Unsigned>| InProgress {
sigs: PartiallyAuthorized {
binding_signature: auth.sigs.bsk.sign(&mut rng, &sighash),
sighash,
},
_proof_state: PhantomData::default(),
},
))
}
}
impl<V> Bundle<InProgress<Proven, Unsigned>, V> {
/// Applies signatures to this bundle, in order to authorize it.
///
/// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and
/// [`Bundle::finalize`].
pub fn apply_signatures<R: RngCore + CryptoRng>(
self,
mut rng: R,
sighash: [u8; 32],
signing_keys: &[SpendAuthorizingKey],
) -> Result<Bundle<Authorized, V>, Error> {
signing_keys
.iter()
.fold(self.prepare(&mut rng, sighash), |partial, ask| {
partial.sign(&mut rng, ask)
})
.finalize()
}
}
impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
/// Signs this bundle with the given [`redjubjub::SigningKey`].
///
/// This will apply signatures for all notes controlled by this spending key.
pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
let expected_ak = ask.into();
let sighash = self.authorization().sigs.sighash;
self.map_authorization((
|proof| proof,
|proof| proof,
|maybe| match maybe {
MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => {
MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(&mut rng, &sighash))
}
s => s,
},
|partial| partial,
))
}
/// Appends externally computed [`redjubjub::Signature`]s.
///
/// Each signature will be applied to the one input for which it is valid. An error
/// will be returned if the signature is not valid for any inputs, or if it is valid
/// for more than one input.
pub fn append_signatures(
self,
signatures: &[redjubjub::Signature<SpendAuth>],
) -> Result<Self, Error> {
signatures.iter().try_fold(self, Self::append_signature)
}
fn append_signature(self, signature: &redjubjub::Signature<SpendAuth>) -> Result<Self, Error> {
let sighash = self.authorization().sigs.sighash;
let mut signature_valid_for = 0usize;
let bundle = self.map_authorization((
|proof| proof,
|proof| proof,
|maybe| match maybe {
MaybeSigned::SigningMetadata(parts) => {
let rk = parts.ak.randomize(&parts.alpha);
if rk.verify(&sighash, signature).is_ok() {
signature_valid_for += 1;
MaybeSigned::Signature(*signature)
} else {
// Signature isn't for this input.
MaybeSigned::SigningMetadata(parts)
}
}
s => s,
},
|partial| partial,
));
match signature_valid_for {
0 => Err(Error::InvalidExternalSignature),
1 => Ok(bundle),
_ => Err(Error::DuplicateSignature),
}
}
}
impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
/// Finalizes this bundle, enabling it to be included in a transaction.
///
/// Returns an error if any signatures are missing.
pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
self.try_map_authorization((
Ok,
Ok,
|maybe: MaybeSigned| maybe.finalize(),
|partial: InProgress<Proven, PartiallyAuthorized>| {
Ok(Authorized {
binding_sig: partial.sigs.binding_signature,
})
},
))
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use std::fmt;
use proptest::collection::vec;
use proptest::prelude::*;
use rand::{rngs::StdRng, SeedableRng};
use crate::{
bundle::{Authorized, Bundle},
note_encryption::Zip212Enforcement,
prover::mock::{MockOutputProver, MockSpendProver},
testing::{arb_node, arb_note},
value::testing::arb_positive_note_value,
zip32::testing::arb_extended_spending_key,
};
use incrementalmerkletree::{
frontier::testing::arb_commitment_tree, witness::IncrementalWitness,
};
use super::SaplingBuilder;
#[allow(dead_code)]
fn arb_bundle<V: fmt::Debug + From<i64>>(
max_money: u64,
zip212_enforcement: Zip212Enforcement,
) -> impl Strategy<Value = Bundle<Authorized, V>> {
(1..30usize)
.prop_flat_map(move |n_notes| {
(
arb_extended_spending_key(),
vec(
arb_positive_note_value(max_money / 10000).prop_flat_map(arb_note),
n_notes,
),
vec(
arb_commitment_tree::<_, _, 32>(n_notes, arb_node())
.prop_map(|t| IncrementalWitness::from_tree(t).path().unwrap()),
n_notes,
),
prop::array::uniform32(any::<u8>()),
prop::array::uniform32(any::<u8>()),
)
})
.prop_map(
move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| {
let mut builder = SaplingBuilder::new(zip212_enforcement);
let mut rng = StdRng::from_seed(rng_seed);
for (note, path) in spendable_notes
.into_iter()
.zip(commitment_trees.into_iter())
{
builder.add_spend(&mut rng, &extsk, note, path).unwrap();
}
let (bundle, _) = builder
.build::<MockSpendProver, MockOutputProver, _, _>(&mut rng)
.unwrap()
.unwrap();
let bundle =
bundle.create_proofs(&MockSpendProver, &MockOutputProver, &mut rng, ());
bundle
.apply_signatures(&mut rng, fake_sighash_bytes, &[extsk.expsk.ask])
.unwrap()
},
)
}
}

706
src/bundle.rs Normal file
View File

@ -0,0 +1,706 @@
use core::fmt::Debug;
use memuse::DynamicUsage;
use redjubjub::{Binding, SpendAuth};
use zcash_note_encryption::{
EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
};
use crate::{
circuit::GROTH_PROOF_SIZE,
note::ExtractedNoteCommitment,
note_encryption::{CompactOutputDescription, SaplingDomain},
value::ValueCommitment,
Nullifier,
};
pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE];
/// Defines the authorization type of a Sapling bundle.
pub trait Authorization: Debug {
type SpendProof: Clone + Debug;
type OutputProof: Clone + Debug;
type AuthSig: Clone + Debug;
}
/// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to
/// the ledger.
#[derive(Debug, Copy, Clone)]
pub struct Authorized {
// TODO: Make this private.
pub binding_sig: redjubjub::Signature<Binding>,
}
impl Authorization for Authorized {
type SpendProof = GrothProofBytes;
type OutputProof = GrothProofBytes;
type AuthSig = redjubjub::Signature<SpendAuth>;
}
/// A map from one bundle authorization to another.
///
/// For use with [`Bundle::map_authorization`].
pub trait MapAuth<A: Authorization, B: Authorization> {
fn map_spend_proof(&mut self, p: A::SpendProof) -> B::SpendProof;
fn map_output_proof(&mut self, p: A::OutputProof) -> B::OutputProof;
fn map_auth_sig(&mut self, s: A::AuthSig) -> B::AuthSig;
fn map_authorization(&mut self, a: A) -> B;
}
/// The identity map.
///
/// This can be used with [`TransactionData::map_authorization`] when you want to map the
/// authorization of a subset of the transaction's bundles.
///
/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
impl MapAuth<Authorized, Authorized> for () {
fn map_spend_proof(
&mut self,
p: <Authorized as Authorization>::SpendProof,
) -> <Authorized as Authorization>::SpendProof {
p
}
fn map_output_proof(
&mut self,
p: <Authorized as Authorization>::OutputProof,
) -> <Authorized as Authorization>::OutputProof {
p
}
fn map_auth_sig(
&mut self,
s: <Authorized as Authorization>::AuthSig,
) -> <Authorized as Authorization>::AuthSig {
s
}
fn map_authorization(&mut self, a: Authorized) -> Authorized {
a
}
}
/// A helper for implementing `MapAuth` with a set of closures.
impl<A, B, F, G, H, I> MapAuth<A, B> for (F, G, H, I)
where
A: Authorization,
B: Authorization,
F: FnMut(A::SpendProof) -> B::SpendProof,
G: FnMut(A::OutputProof) -> B::OutputProof,
H: FnMut(A::AuthSig) -> B::AuthSig,
I: FnMut(A) -> B,
{
fn map_spend_proof(&mut self, p: A::SpendProof) -> B::SpendProof {
self.0(p)
}
fn map_output_proof(&mut self, p: A::OutputProof) -> B::OutputProof {
self.1(p)
}
fn map_auth_sig(&mut self, s: A::AuthSig) -> B::AuthSig {
self.2(s)
}
fn map_authorization(&mut self, a: A) -> B {
self.3(a)
}
}
/// A fallible map from one bundle authorization to another.
///
/// For use with [`Bundle::try_map_authorization`].
pub trait TryMapAuth<A: Authorization, B: Authorization> {
type Error;
fn try_map_spend_proof(&mut self, p: A::SpendProof) -> Result<B::SpendProof, Self::Error>;
fn try_map_output_proof(&mut self, p: A::OutputProof) -> Result<B::OutputProof, Self::Error>;
fn try_map_auth_sig(&mut self, s: A::AuthSig) -> Result<B::AuthSig, Self::Error>;
fn try_map_authorization(&mut self, a: A) -> Result<B, Self::Error>;
}
/// A helper for implementing `TryMapAuth` with a set of closures.
impl<A, B, E, F, G, H, I> TryMapAuth<A, B> for (F, G, H, I)
where
A: Authorization,
B: Authorization,
F: FnMut(A::SpendProof) -> Result<B::SpendProof, E>,
G: FnMut(A::OutputProof) -> Result<B::OutputProof, E>,
H: FnMut(A::AuthSig) -> Result<B::AuthSig, E>,
I: FnMut(A) -> Result<B, E>,
{
type Error = E;
fn try_map_spend_proof(&mut self, p: A::SpendProof) -> Result<B::SpendProof, Self::Error> {
self.0(p)
}
fn try_map_output_proof(&mut self, p: A::OutputProof) -> Result<B::OutputProof, Self::Error> {
self.1(p)
}
fn try_map_auth_sig(&mut self, s: A::AuthSig) -> Result<B::AuthSig, Self::Error> {
self.2(s)
}
fn try_map_authorization(&mut self, a: A) -> Result<B, Self::Error> {
self.3(a)
}
}
#[derive(Debug, Clone)]
pub struct Bundle<A: Authorization, V> {
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: V,
authorization: A,
}
impl<A: Authorization, V> Bundle<A, V> {
/// Constructs a `Bundle` from its constituent parts.
pub fn from_parts(
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: V,
authorization: A,
) -> Option<Self> {
if shielded_spends.is_empty() && shielded_outputs.is_empty() {
None
} else {
Some(Bundle {
shielded_spends,
shielded_outputs,
value_balance,
authorization,
})
}
}
/// Returns the list of spends in this bundle.
pub fn shielded_spends(&self) -> &[SpendDescription<A>] {
&self.shielded_spends
}
/// Returns the list of outputs in this bundle.
pub fn shielded_outputs(&self) -> &[OutputDescription<A::OutputProof>] {
&self.shielded_outputs
}
/// Returns the net value moved into or out of the Sapling shielded pool.
///
/// This is the sum of Sapling spends minus the sum of Sapling outputs.
pub fn value_balance(&self) -> &V {
&self.value_balance
}
/// Returns the authorization for this bundle.
///
/// In the case of a `Bundle<Authorized>`, this is the binding signature.
pub fn authorization(&self) -> &A {
&self.authorization
}
/// Transitions this bundle from one authorization state to another.
pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, mut f: F) -> Bundle<B, V> {
Bundle {
shielded_spends: self
.shielded_spends
.into_iter()
.map(|d| SpendDescription {
cv: d.cv,
anchor: d.anchor,
nullifier: d.nullifier,
rk: d.rk,
zkproof: f.map_spend_proof(d.zkproof),
spend_auth_sig: f.map_auth_sig(d.spend_auth_sig),
})
.collect(),
shielded_outputs: self
.shielded_outputs
.into_iter()
.map(|o| OutputDescription {
cv: o.cv,
cmu: o.cmu,
ephemeral_key: o.ephemeral_key,
enc_ciphertext: o.enc_ciphertext,
out_ciphertext: o.out_ciphertext,
zkproof: f.map_output_proof(o.zkproof),
})
.collect(),
value_balance: self.value_balance,
authorization: f.map_authorization(self.authorization),
}
}
/// Transitions this bundle from one authorization state to another.
pub fn try_map_authorization<B: Authorization, F: TryMapAuth<A, B>>(
self,
mut f: F,
) -> Result<Bundle<B, V>, F::Error> {
Ok(Bundle {
shielded_spends: self
.shielded_spends
.into_iter()
.map(|d| {
Ok(SpendDescription {
cv: d.cv,
anchor: d.anchor,
nullifier: d.nullifier,
rk: d.rk,
zkproof: f.try_map_spend_proof(d.zkproof)?,
spend_auth_sig: f.try_map_auth_sig(d.spend_auth_sig)?,
})
})
.collect::<Result<_, _>>()?,
shielded_outputs: self
.shielded_outputs
.into_iter()
.map(|o| {
Ok(OutputDescription {
cv: o.cv,
cmu: o.cmu,
ephemeral_key: o.ephemeral_key,
enc_ciphertext: o.enc_ciphertext,
out_ciphertext: o.out_ciphertext,
zkproof: f.try_map_output_proof(o.zkproof)?,
})
})
.collect::<Result<_, _>>()?,
value_balance: self.value_balance,
authorization: f.try_map_authorization(self.authorization)?,
})
}
}
impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
fn dynamic_usage(&self) -> usize {
self.shielded_spends.dynamic_usage()
+ self.shielded_outputs.dynamic_usage()
+ self.value_balance.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
let bounds = (
self.shielded_spends.dynamic_usage_bounds(),
self.shielded_outputs.dynamic_usage_bounds(),
self.value_balance.dynamic_usage_bounds(),
);
(
bounds.0 .0 + bounds.1 .0 + bounds.2 .0,
bounds
.0
.1
.zip(bounds.1 .1)
.zip(bounds.2 .1)
.map(|((a, b), c)| a + b + c),
)
}
}
#[derive(Clone)]
pub struct SpendDescription<A: Authorization> {
cv: ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: Nullifier,
rk: redjubjub::VerificationKey<SpendAuth>,
zkproof: A::SpendProof,
spend_auth_sig: A::AuthSig,
}
impl<A: Authorization> std::fmt::Debug for SpendDescription<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})",
self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig
)
}
}
impl<A: Authorization> SpendDescription<A> {
/// Constructs a v4 `SpendDescription` from its constituent parts.
pub fn from_parts(
cv: ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: Nullifier,
rk: redjubjub::VerificationKey<SpendAuth>,
zkproof: A::SpendProof,
spend_auth_sig: A::AuthSig,
) -> Self {
Self {
cv,
anchor,
nullifier,
rk,
zkproof,
spend_auth_sig,
}
}
/// Returns the commitment to the value consumed by this spend.
pub fn cv(&self) -> &ValueCommitment {
&self.cv
}
/// Returns the root of the Sapling commitment tree that this spend commits to.
pub fn anchor(&self) -> &bls12_381::Scalar {
&self.anchor
}
/// Returns the nullifier of the note being spent.
pub fn nullifier(&self) -> &Nullifier {
&self.nullifier
}
/// Returns the randomized verification key for the note being spent.
pub fn rk(&self) -> &redjubjub::VerificationKey<SpendAuth> {
&self.rk
}
/// Returns the proof for this spend.
pub fn zkproof(&self) -> &A::SpendProof {
&self.zkproof
}
/// Returns the authorization signature for this spend.
pub fn spend_auth_sig(&self) -> &A::AuthSig {
&self.spend_auth_sig
}
}
impl DynamicUsage for SpendDescription<Authorized> {
fn dynamic_usage(&self) -> usize {
self.zkproof.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.zkproof.dynamic_usage_bounds()
}
}
#[derive(Clone)]
pub struct SpendDescriptionV5 {
cv: ValueCommitment,
nullifier: Nullifier,
rk: redjubjub::VerificationKey<SpendAuth>,
}
impl SpendDescriptionV5 {
/// Constructs a v5 `SpendDescription` from its constituent parts.
pub fn from_parts(
cv: ValueCommitment,
nullifier: Nullifier,
rk: redjubjub::VerificationKey<SpendAuth>,
) -> Self {
Self { cv, nullifier, rk }
}
pub fn into_spend_description(
self,
anchor: bls12_381::Scalar,
zkproof: GrothProofBytes,
spend_auth_sig: redjubjub::Signature<SpendAuth>,
) -> SpendDescription<Authorized> {
SpendDescription {
cv: self.cv,
anchor,
nullifier: self.nullifier,
rk: self.rk,
zkproof,
spend_auth_sig,
}
}
}
#[derive(Clone)]
pub struct OutputDescription<Proof> {
cv: ValueCommitment,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
zkproof: Proof,
}
impl<Proof> OutputDescription<Proof> {
/// Returns the commitment to the value consumed by this output.
pub fn cv(&self) -> &ValueCommitment {
&self.cv
}
/// Returns the commitment to the new note being created.
pub fn cmu(&self) -> &ExtractedNoteCommitment {
&self.cmu
}
pub fn ephemeral_key(&self) -> &EphemeralKeyBytes {
&self.ephemeral_key
}
/// Returns the encrypted note ciphertext.
pub fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
&self.enc_ciphertext
}
/// Returns the output recovery ciphertext.
pub fn out_ciphertext(&self) -> &[u8; OUT_CIPHERTEXT_SIZE] {
&self.out_ciphertext
}
/// Returns the proof for this output.
pub fn zkproof(&self) -> &Proof {
&self.zkproof
}
/// Constructs a v4 `OutputDescription` from its constituent parts.
pub fn from_parts(
cv: ValueCommitment,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
zkproof: Proof,
) -> Self {
OutputDescription {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
}
}
}
#[cfg(test)]
impl<Proof> OutputDescription<Proof> {
pub(crate) fn cv_mut(&mut self) -> &mut ValueCommitment {
&mut self.cv
}
pub(crate) fn cmu_mut(&mut self) -> &mut ExtractedNoteCommitment {
&mut self.cmu
}
pub(crate) fn ephemeral_key_mut(&mut self) -> &mut EphemeralKeyBytes {
&mut self.ephemeral_key
}
pub(crate) fn enc_ciphertext_mut(&mut self) -> &mut [u8; ENC_CIPHERTEXT_SIZE] {
&mut self.enc_ciphertext
}
pub(crate) fn out_ciphertext_mut(&mut self) -> &mut [u8; OUT_CIPHERTEXT_SIZE] {
&mut self.out_ciphertext
}
}
impl<Proof: DynamicUsage> DynamicUsage for OutputDescription<Proof> {
fn dynamic_usage(&self) -> usize {
self.zkproof.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.zkproof.dynamic_usage_bounds()
}
}
impl<A> ShieldedOutput<SaplingDomain, ENC_CIPHERTEXT_SIZE> for OutputDescription<A> {
fn ephemeral_key(&self) -> EphemeralKeyBytes {
self.ephemeral_key.clone()
}
fn cmstar_bytes(&self) -> [u8; 32] {
self.cmu.to_bytes()
}
fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
&self.enc_ciphertext
}
}
impl<A> std::fmt::Debug for OutputDescription<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})",
self.cv, self.cmu, self.ephemeral_key
)
}
}
#[derive(Clone)]
pub struct OutputDescriptionV5 {
cv: ValueCommitment,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
}
memuse::impl_no_dynamic_usage!(OutputDescriptionV5);
impl OutputDescriptionV5 {
/// Constructs a v5 `OutputDescription` from its constituent parts.
pub fn from_parts(
cv: ValueCommitment,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
) -> Self {
Self {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
}
}
pub fn into_output_description(
self,
zkproof: GrothProofBytes,
) -> OutputDescription<GrothProofBytes> {
OutputDescription {
cv: self.cv,
cmu: self.cmu,
ephemeral_key: self.ephemeral_key,
enc_ciphertext: self.enc_ciphertext,
out_ciphertext: self.out_ciphertext,
zkproof,
}
}
}
impl<A> From<OutputDescription<A>> for CompactOutputDescription {
fn from(out: OutputDescription<A>) -> CompactOutputDescription {
CompactOutputDescription {
ephemeral_key: out.ephemeral_key,
cmu: out.cmu,
enc_ciphertext: out.enc_ciphertext[..COMPACT_NOTE_SIZE].try_into().unwrap(),
}
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use std::fmt;
use ff::Field;
use group::{Group, GroupEncoding};
use proptest::collection::vec;
use proptest::prelude::*;
use rand::{rngs::StdRng, SeedableRng};
use crate::{
circuit::GROTH_PROOF_SIZE,
note::testing::arb_cmu,
value::{
testing::{arb_note_value_bounded, arb_trapdoor},
ValueCommitment, MAX_NOTE_VALUE,
},
Nullifier,
};
use super::{
Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription,
ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
};
prop_compose! {
fn arb_extended_point()(rng_seed in prop::array::uniform32(any::<u8>())) -> jubjub::ExtendedPoint {
let mut rng = StdRng::from_seed(rng_seed);
let scalar = jubjub::Scalar::random(&mut rng);
jubjub::ExtendedPoint::generator() * scalar
}
}
prop_compose! {
/// produce a spend description with invalid data (useful only for serialization
/// roundtrip testing).
fn arb_spend_description(n_spends: usize)(
value in arb_note_value_bounded(MAX_NOTE_VALUE.checked_div(n_spends as u64).unwrap_or(0)),
rcv in arb_trapdoor(),
anchor in vec(any::<u8>(), 64)
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
nullifier in prop::array::uniform32(any::<u8>())
.prop_map(|v| Nullifier::from_slice(&v).unwrap()),
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY),
) -> SpendDescription<Authorized> {
let mut rng = StdRng::from_seed(rng_seed);
let sk1 = redjubjub::SigningKey::new(&mut rng);
let rk = redjubjub::VerificationKey::from(&sk1);
let cv = ValueCommitment::derive(value, rcv);
SpendDescription {
cv,
anchor,
nullifier,
rk,
zkproof,
spend_auth_sig: sk1.sign(&mut rng, &fake_sighash_bytes),
}
}
}
prop_compose! {
/// produce an output description with invalid data (useful only for serialization
/// roundtrip testing).
pub fn arb_output_description(n_outputs: usize)(
value in arb_note_value_bounded(MAX_NOTE_VALUE.checked_div(n_outputs as u64).unwrap_or(0)),
rcv in arb_trapdoor(),
cmu in arb_cmu(),
enc_ciphertext in vec(any::<u8>(), ENC_CIPHERTEXT_SIZE)
.prop_map(|v| <[u8; ENC_CIPHERTEXT_SIZE]>::try_from(v.as_slice()).unwrap()),
epk in arb_extended_point(),
out_ciphertext in vec(any::<u8>(), OUT_CIPHERTEXT_SIZE)
.prop_map(|v| <[u8; OUT_CIPHERTEXT_SIZE]>::try_from(v.as_slice()).unwrap()),
zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
.prop_map(|v| <[u8; GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
) -> OutputDescription<GrothProofBytes> {
let cv = ValueCommitment::derive(value, rcv);
OutputDescription {
cv,
cmu,
ephemeral_key: epk.to_bytes().into(),
enc_ciphertext,
out_ciphertext,
zkproof,
}
}
}
pub fn arb_bundle<V: Copy + fmt::Debug + 'static>(
value_balance: V,
) -> impl Strategy<Value = Option<Bundle<Authorized, V>>> {
(0usize..30, 0usize..30)
.prop_flat_map(|(n_spends, n_outputs)| {
(
vec(arb_spend_description(n_spends), n_spends),
vec(arb_output_description(n_outputs), n_outputs),
prop::array::uniform32(prop::num::u8::ANY),
prop::array::uniform32(prop::num::u8::ANY),
)
})
.prop_map(
move |(shielded_spends, shielded_outputs, rng_seed, fake_bvk_bytes)| {
if shielded_spends.is_empty() && shielded_outputs.is_empty() {
None
} else {
let mut rng = StdRng::from_seed(rng_seed);
let bsk = redjubjub::SigningKey::new(&mut rng);
Some(Bundle {
shielded_spends,
shielded_outputs,
value_balance,
authorization: Authorized {
binding_sig: bsk.sign(&mut rng, &fake_bvk_bytes),
},
})
}
},
)
}
}

1073
src/circuit.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,438 +0,0 @@
use pairing::{
Engine,
};
use bellman::{
SynthesisError,
ConstraintSystem
};
use super::boolean::{
Boolean
};
use super::uint32::{
UInt32
};
use super::multieq::MultiEq;
/*
2.1. Parameters
The following table summarizes various parameters and their ranges:
| BLAKE2b | BLAKE2s |
--------------+------------------+------------------+
Bits in word | w = 64 | w = 32 |
Rounds in F | r = 12 | r = 10 |
Block bytes | bb = 128 | bb = 64 |
Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 |
Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 |
Input bytes | 0 <= ll < 2**128 | 0 <= ll < 2**64 |
--------------+------------------+------------------+
G Rotation | (R1, R2, R3, R4) | (R1, R2, R3, R4) |
constants = | (32, 24, 16, 63) | (16, 12, 8, 7) |
--------------+------------------+------------------+
*/
const R1: usize = 16;
const R2: usize = 12;
const R3: usize = 8;
const R4: usize = 7;
/*
Round | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
----------+-------------------------------------------------+
SIGMA[0] | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SIGMA[1] | 14 10 4 8 9 15 13 6 1 12 0 2 11 7 5 3 |
SIGMA[2] | 11 8 12 0 5 2 15 13 10 14 3 6 7 1 9 4 |
SIGMA[3] | 7 9 3 1 13 12 11 14 2 6 5 10 4 0 15 8 |
SIGMA[4] | 9 0 5 7 2 4 10 15 14 1 11 12 6 8 3 13 |
SIGMA[5] | 2 12 6 10 0 11 8 3 4 13 7 5 15 14 1 9 |
SIGMA[6] | 12 5 1 15 14 13 4 10 0 7 6 3 9 2 8 11 |
SIGMA[7] | 13 11 7 14 12 1 3 9 5 0 15 4 8 6 2 10 |
SIGMA[8] | 6 15 14 9 11 3 0 8 12 2 13 7 1 4 10 5 |
SIGMA[9] | 10 2 8 4 7 6 1 5 15 11 9 14 3 12 13 0 |
----------+-------------------------------------------------+
*/
const SIGMA: [[usize; 16]; 10] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]
];
/*
3.1. Mixing Function G
The G primitive function mixes two input words, "x" and "y", into
four words indexed by "a", "b", "c", and "d" in the working vector
v[0..15]. The full modified vector is returned. The rotation
constants (R1, R2, R3, R4) are given in Section 2.1.
FUNCTION G( v[0..15], a, b, c, d, x, y )
|
| v[a] := (v[a] + v[b] + x) mod 2**w
| v[d] := (v[d] ^ v[a]) >>> R1
| v[c] := (v[c] + v[d]) mod 2**w
| v[b] := (v[b] ^ v[c]) >>> R2
| v[a] := (v[a] + v[b] + y) mod 2**w
| v[d] := (v[d] ^ v[a]) >>> R3
| v[c] := (v[c] + v[d]) mod 2**w
| v[b] := (v[b] ^ v[c]) >>> R4
|
| RETURN v[0..15]
|
END FUNCTION.
*/
fn mixing_g<E: Engine, CS: ConstraintSystem<E>, M>(
mut cs: M,
v: &mut [UInt32],
a: usize,
b: usize,
c: usize,
d: usize,
x: &UInt32,
y: &UInt32
) -> Result<(), SynthesisError>
where M: ConstraintSystem<E, Root=MultiEq<E, CS>>
{
v[a] = UInt32::addmany(cs.namespace(|| "mixing step 1"), &[v[a].clone(), v[b].clone(), x.clone()])?;
v[d] = v[d].xor(cs.namespace(|| "mixing step 2"), &v[a])?.rotr(R1);
v[c] = UInt32::addmany(cs.namespace(|| "mixing step 3"), &[v[c].clone(), v[d].clone()])?;
v[b] = v[b].xor(cs.namespace(|| "mixing step 4"), &v[c])?.rotr(R2);
v[a] = UInt32::addmany(cs.namespace(|| "mixing step 5"), &[v[a].clone(), v[b].clone(), y.clone()])?;
v[d] = v[d].xor(cs.namespace(|| "mixing step 6"), &v[a])?.rotr(R3);
v[c] = UInt32::addmany(cs.namespace(|| "mixing step 7"), &[v[c].clone(), v[d].clone()])?;
v[b] = v[b].xor(cs.namespace(|| "mixing step 8"), &v[c])?.rotr(R4);
Ok(())
}
/*
3.2. Compression Function F
Compression function F takes as an argument the state vector "h",
message block vector "m" (last block is padded with zeros to full
block size, if required), 2w-bit offset counter "t", and final block
indicator flag "f". Local vector v[0..15] is used in processing. F
returns a new state vector. The number of rounds, "r", is 12 for
BLAKE2b and 10 for BLAKE2s. Rounds are numbered from 0 to r - 1.
FUNCTION F( h[0..7], m[0..15], t, f )
|
| // Initialize local work vector v[0..15]
| v[0..7] := h[0..7] // First half from state.
| v[8..15] := IV[0..7] // Second half from IV.
|
| v[12] := v[12] ^ (t mod 2**w) // Low word of the offset.
| v[13] := v[13] ^ (t >> w) // High word.
|
| IF f = TRUE THEN // last block flag?
| | v[14] := v[14] ^ 0xFF..FF // Invert all bits.
| END IF.
|
| // Cryptographic mixing
| FOR i = 0 TO r - 1 DO // Ten or twelve rounds.
| |
| | // Message word selection permutation for this round.
| | s[0..15] := SIGMA[i mod 10][0..15]
| |
| | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] )
| | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] )
| | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] )
| | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] )
| |
| | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] )
| | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] )
| | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] )
| | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] )
| |
| END FOR
|
| FOR i = 0 TO 7 DO // XOR the two halves.
| | h[i] := h[i] ^ v[i] ^ v[i + 8]
| END FOR.
|
| RETURN h[0..7] // New state.
|
END FUNCTION.
*/
fn blake2s_compression<E: Engine, CS: ConstraintSystem<E>>(
mut cs: CS,
h: &mut [UInt32],
m: &[UInt32],
t: u64,
f: bool
) -> Result<(), SynthesisError>
{
assert_eq!(h.len(), 8);
assert_eq!(m.len(), 16);
/*
static const uint32_t blake2s_iv[8] =
{
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
};
*/
let mut v = Vec::with_capacity(16);
v.extend_from_slice(h);
v.push(UInt32::constant(0x6A09E667));
v.push(UInt32::constant(0xBB67AE85));
v.push(UInt32::constant(0x3C6EF372));
v.push(UInt32::constant(0xA54FF53A));
v.push(UInt32::constant(0x510E527F));
v.push(UInt32::constant(0x9B05688C));
v.push(UInt32::constant(0x1F83D9AB));
v.push(UInt32::constant(0x5BE0CD19));
assert_eq!(v.len(), 16);
v[12] = v[12].xor(cs.namespace(|| "first xor"), &UInt32::constant(t as u32))?;
v[13] = v[13].xor(cs.namespace(|| "second xor"), &UInt32::constant((t >> 32) as u32))?;
if f {
v[14] = v[14].xor(cs.namespace(|| "third xor"), &UInt32::constant(u32::max_value()))?;
}
{
let mut cs = MultiEq::new(&mut cs);
for i in 0..10 {
let mut cs = cs.namespace(|| format!("round {}", i));
let s = SIGMA[i % 10];
mixing_g(cs.namespace(|| "mixing invocation 1"), &mut v, 0, 4, 8, 12, &m[s[ 0]], &m[s[ 1]])?;
mixing_g(cs.namespace(|| "mixing invocation 2"), &mut v, 1, 5, 9, 13, &m[s[ 2]], &m[s[ 3]])?;
mixing_g(cs.namespace(|| "mixing invocation 3"), &mut v, 2, 6, 10, 14, &m[s[ 4]], &m[s[ 5]])?;
mixing_g(cs.namespace(|| "mixing invocation 4"), &mut v, 3, 7, 11, 15, &m[s[ 6]], &m[s[ 7]])?;
mixing_g(cs.namespace(|| "mixing invocation 5"), &mut v, 0, 5, 10, 15, &m[s[ 8]], &m[s[ 9]])?;
mixing_g(cs.namespace(|| "mixing invocation 6"), &mut v, 1, 6, 11, 12, &m[s[10]], &m[s[11]])?;
mixing_g(cs.namespace(|| "mixing invocation 7"), &mut v, 2, 7, 8, 13, &m[s[12]], &m[s[13]])?;
mixing_g(cs.namespace(|| "mixing invocation 8"), &mut v, 3, 4, 9, 14, &m[s[14]], &m[s[15]])?;
}
}
for i in 0..8 {
let mut cs = cs.namespace(|| format!("h[{i}] ^ v[{i}] ^ v[{i} + 8]", i=i));
h[i] = h[i].xor(cs.namespace(|| "first xor"), &v[i])?;
h[i] = h[i].xor(cs.namespace(|| "second xor"), &v[i + 8])?;
}
Ok(())
}
/*
FUNCTION BLAKE2( d[0..dd-1], ll, kk, nn )
|
| h[0..7] := IV[0..7] // Initialization Vector.
|
| // Parameter block p[0]
| h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn
|
| // Process padded key and data blocks
| IF dd > 1 THEN
| | FOR i = 0 TO dd - 2 DO
| | | h := F( h, d[i], (i + 1) * bb, FALSE )
| | END FOR.
| END IF.
|
| // Final block.
| IF kk = 0 THEN
| | h := F( h, d[dd - 1], ll, TRUE )
| ELSE
| | h := F( h, d[dd - 1], ll + bb, TRUE )
| END IF.
|
| RETURN first "nn" bytes from little-endian word array h[].
|
END FUNCTION.
*/
pub fn blake2s<E: Engine, CS: ConstraintSystem<E>>(
mut cs: CS,
input: &[Boolean],
personalization: &[u8]
) -> Result<Vec<Boolean>, SynthesisError>
{
use byteorder::{ByteOrder, LittleEndian};
assert_eq!(personalization.len(), 8);
assert!(input.len() % 8 == 0);
let mut h = Vec::with_capacity(8);
h.push(UInt32::constant(0x6A09E667 ^ 0x01010000 ^ 32));
h.push(UInt32::constant(0xBB67AE85));
h.push(UInt32::constant(0x3C6EF372));
h.push(UInt32::constant(0xA54FF53A));
h.push(UInt32::constant(0x510E527F));
h.push(UInt32::constant(0x9B05688C));
// Personalization is stored here
h.push(UInt32::constant(0x1F83D9AB ^ LittleEndian::read_u32(&personalization[0..4])));
h.push(UInt32::constant(0x5BE0CD19 ^ LittleEndian::read_u32(&personalization[4..8])));
let mut blocks: Vec<Vec<UInt32>> = vec![];
for block in input.chunks(512) {
let mut this_block = Vec::with_capacity(16);
for word in block.chunks(32) {
let mut tmp = word.to_vec();
while tmp.len() < 32 {
tmp.push(Boolean::constant(false));
}
this_block.push(UInt32::from_bits(&tmp));
}
while this_block.len() < 16 {
this_block.push(UInt32::constant(0));
}
blocks.push(this_block);
}
if blocks.len() == 0 {
blocks.push((0..16).map(|_| UInt32::constant(0)).collect());
}
for (i, block) in blocks[0..blocks.len() - 1].iter().enumerate() {
let cs = cs.namespace(|| format!("block {}", i));
blake2s_compression(cs, &mut h, block, ((i as u64) + 1) * 64, false)?;
}
{
let cs = cs.namespace(|| "final block");
blake2s_compression(cs, &mut h, &blocks[blocks.len() - 1], (input.len() / 8) as u64, true)?;
}
Ok(h.iter().flat_map(|b| b.into_bits()).collect())
}
#[cfg(test)]
mod test {
use rand::{XorShiftRng, SeedableRng, Rng};
use pairing::bls12_381::{Bls12};
use ::circuit::boolean::{Boolean, AllocatedBit};
use ::circuit::test::TestConstraintSystem;
use super::blake2s;
use bellman::{ConstraintSystem};
use blake2_rfc::blake2s::Blake2s;
#[test]
fn test_blank_hash() {
let mut cs = TestConstraintSystem::<Bls12>::new();
let input_bits = vec![];
let out = blake2s(&mut cs, &input_bits, b"12345678").unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 0);
// >>> import blake2s from hashlib
// >>> h = blake2s(digest_size=32, person=b'12345678')
// >>> h.hexdigest()
let expected = hex!("c59f682376d137f3f255e671e207d1f2374ebe504e9314208a52d9f88d69e8c8");
let mut out = out.into_iter();
for b in expected.into_iter() {
for i in 0..8 {
let c = out.next().unwrap().get_value().unwrap();
assert_eq!(c, (b >> i) & 1u8 == 1u8);
}
}
}
#[test]
fn test_blake2s_constraints() {
let mut cs = TestConstraintSystem::<Bls12>::new();
let input_bits: Vec<_> = (0..512).map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()).collect();
blake2s(&mut cs, &input_bits, b"12345678").unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 21518);
}
#[test]
fn test_blake2s_precomp_constraints() {
// Test that 512 fixed leading bits (constants)
// doesn't result in more constraints.
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let input_bits: Vec<_> = (0..512)
.map(|_| Boolean::constant(rng.gen()))
.chain((0..512)
.map(|i| AllocatedBit::alloc(cs.namespace(|| format!("input bit {}", i)), Some(true)).unwrap().into()))
.collect();
blake2s(&mut cs, &input_bits, b"12345678").unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 21518);
}
#[test]
fn test_blake2s_constant_constraints() {
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let input_bits: Vec<_> = (0..512).map(|_| Boolean::constant(rng.gen())).collect();
blake2s(&mut cs, &input_bits, b"12345678").unwrap();
assert_eq!(cs.num_constraints(), 0);
}
#[test]
fn test_blake2s() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0))
{
let mut h = Blake2s::with_params(32, &[], &[], b"12345678");
let data: Vec<u8> = (0..input_len).map(|_| rng.gen()).collect();
h.update(&data);
let hash_result = h.finalize();
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut input_bits = vec![];
for (byte_i, input_byte) in data.into_iter().enumerate() {
for bit_i in 0..8 {
let cs = cs.namespace(|| format!("input bit {} {}", byte_i, bit_i));
input_bits.push(AllocatedBit::alloc(cs, Some((input_byte >> bit_i) & 1u8 == 1u8)).unwrap().into());
}
}
let r = blake2s(&mut cs, &input_bits, b"12345678").unwrap();
assert!(cs.is_satisfied());
let mut s = hash_result.as_ref().iter()
.flat_map(|&byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8));
for b in r {
match b {
Boolean::Is(b) => {
assert!(s.next().unwrap() == b.get_value().unwrap());
},
Boolean::Not(b) => {
assert!(s.next().unwrap() != b.get_value().unwrap());
},
Boolean::Constant(b) => {
assert!(input_len == 0);
assert!(s.next().unwrap() == b);
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

196
src/circuit/constants.rs Normal file
View File

@ -0,0 +1,196 @@
//! Various constants used for the Zcash proofs.
use crate::constants::{PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_GENERATORS};
use bls12_381::Scalar;
use group::{ff::Field, Curve, Group};
use jubjub::ExtendedPoint;
use lazy_static::lazy_static;
/// The `d` constant of the twisted Edwards curve.
pub(crate) const EDWARDS_D: Scalar = Scalar::from_raw([
0x0106_5fd6_d634_3eb1,
0x292d_7f6d_3757_9d26,
0xf5fd_9207_e6bd_7fd4,
0x2a93_18e7_4bfa_2b48,
]);
/// The `A` constant of the birationally equivalent Montgomery curve.
pub(crate) const MONTGOMERY_A: Scalar = Scalar::from_raw([
0x0000_0000_0000_a002,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
0x0000_0000_0000_0000,
]);
/// The scaling factor used for conversion to and from the Montgomery form.
pub(crate) const MONTGOMERY_SCALE: Scalar = Scalar::from_raw([
0x8f45_35f7_cf82_b8d9,
0xce40_6970_3da8_8abd,
0x31de_341e_77d7_64e5,
0x2762_de61_e862_645e,
]);
/// The number of chunks needed to represent a full scalar during fixed-base
/// exponentiation.
const FIXED_BASE_CHUNKS_PER_GENERATOR: usize = 84;
/// Reference to a circuit version of a generator for fixed-base salar multiplication.
pub type FixedGenerator = &'static [Vec<(Scalar, Scalar)>];
/// Circuit version of a generator for fixed-base salar multiplication.
pub type FixedGeneratorOwned = Vec<Vec<(Scalar, Scalar)>>;
lazy_static! {
pub static ref PROOF_GENERATION_KEY_GENERATOR: FixedGeneratorOwned =
generate_circuit_generator(crate::constants::PROOF_GENERATION_KEY_GENERATOR);
pub static ref NOTE_COMMITMENT_RANDOMNESS_GENERATOR: FixedGeneratorOwned =
generate_circuit_generator(crate::constants::NOTE_COMMITMENT_RANDOMNESS_GENERATOR);
pub static ref NULLIFIER_POSITION_GENERATOR: FixedGeneratorOwned =
generate_circuit_generator(crate::constants::NULLIFIER_POSITION_GENERATOR);
pub static ref VALUE_COMMITMENT_VALUE_GENERATOR: FixedGeneratorOwned =
generate_circuit_generator(crate::constants::VALUE_COMMITMENT_VALUE_GENERATOR);
pub static ref VALUE_COMMITMENT_RANDOMNESS_GENERATOR: FixedGeneratorOwned =
generate_circuit_generator(crate::constants::VALUE_COMMITMENT_RANDOMNESS_GENERATOR);
pub static ref SPENDING_KEY_GENERATOR: FixedGeneratorOwned =
generate_circuit_generator(crate::constants::SPENDING_KEY_GENERATOR);
/// The pre-computed window tables `[-4, 3, 2, 1, 1, 2, 3, 4]` of different magnitudes
/// of the Pedersen hash segment generators.
pub(crate) static ref PEDERSEN_CIRCUIT_GENERATORS: Vec<Vec<Vec<(Scalar, Scalar)>>> =
generate_pedersen_circuit_generators();
}
/// Creates the 3-bit window table `[0, 1, ..., 8]` for different magnitudes of a fixed
/// generator.
pub fn generate_circuit_generator(mut gen: jubjub::SubgroupPoint) -> FixedGeneratorOwned {
let mut windows = vec![];
for _ in 0..FIXED_BASE_CHUNKS_PER_GENERATOR {
let mut coeffs = vec![(Scalar::zero(), Scalar::one())];
let mut g = gen;
for _ in 0..7 {
let g_affine = jubjub::ExtendedPoint::from(g).to_affine();
coeffs.push((g_affine.get_u(), g_affine.get_v()));
g += gen;
}
windows.push(coeffs);
// gen = gen * 8
gen = g;
}
windows
}
/// Returns the coordinates of this point's Montgomery curve representation, or `None` if
/// it is the point at infinity.
#[allow(clippy::many_single_char_names)]
pub(crate) fn to_montgomery_coords(g: ExtendedPoint) -> Option<(Scalar, Scalar)> {
let g = g.to_affine();
let (x, y) = (g.get_u(), g.get_v());
if y == Scalar::one() {
// The only solution for y = 1 is x = 0. (0, 1) is the neutral element, so we map
// this to the point at infinity.
None
} else {
// The map from a twisted Edwards curve is defined as
// (x, y) -> (u, v) where
// u = (1 + y) / (1 - y)
// v = u / x
//
// This mapping is not defined for y = 1 and for x = 0.
//
// We have that y != 1 above. If x = 0, the only
// solutions for y are 1 (contradiction) or -1.
if x.is_zero_vartime() {
// (0, -1) is the point of order two which is not
// the neutral element, so we map it to (0, 0) which is
// the only affine point of order 2.
Some((Scalar::zero(), Scalar::zero()))
} else {
// The mapping is defined as above.
//
// (x, y) -> (u, v) where
// u = (1 + y) / (1 - y)
// v = u / x
let u = (Scalar::one() + y) * (Scalar::one() - y).invert().unwrap();
let v = u * x.invert().unwrap();
// Scale it into the correct curve constants
// scaling factor = sqrt(4 / (a - d))
Some((u, v * MONTGOMERY_SCALE))
}
}
}
/// Creates the 2-bit window table lookups for each 4-bit "chunk" in each segment of the
/// Pedersen hash.
fn generate_pedersen_circuit_generators() -> Vec<Vec<Vec<(Scalar, Scalar)>>> {
// Process each segment
PEDERSEN_HASH_GENERATORS
.iter()
.cloned()
.map(|mut gen| {
let mut windows = vec![];
for _ in 0..PEDERSEN_HASH_CHUNKS_PER_GENERATOR {
// Create (x, y) coeffs for this chunk
let mut coeffs = vec![];
let mut g = gen;
// coeffs = g, g*2, g*3, g*4
for _ in 0..4 {
coeffs.push(
to_montgomery_coords(g.into())
.expect("we never encounter the point at infinity"),
);
g += gen;
}
windows.push(coeffs);
// Our chunks are separated by 2 bits to prevent overlap.
for _ in 0..4 {
gen = gen.double();
}
}
windows
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edwards_d() {
// d = -(10240/10241)
assert_eq!(
-Scalar::from(10240) * Scalar::from(10241).invert().unwrap(),
EDWARDS_D
);
}
#[test]
fn montgomery_a() {
assert_eq!(Scalar::from(40962), MONTGOMERY_A);
}
#[test]
fn montgomery_scale() {
// scaling factor = sqrt(4 / (a - d))
assert_eq!(
MONTGOMERY_SCALE.square() * (-Scalar::one() - EDWARDS_D),
Scalar::from(4),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,307 +0,0 @@
use pairing::{Engine, Field};
use super::*;
use super::num::{
AllocatedNum,
Num
};
use super::boolean::Boolean;
use bellman::{
ConstraintSystem
};
// Synthesize the constants for each base pattern.
fn synth<'a, E: Engine, I>(
window_size: usize,
constants: I,
assignment: &mut [E::Fr]
)
where I: IntoIterator<Item=&'a E::Fr>
{
assert_eq!(assignment.len(), 1 << window_size);
for (i, constant) in constants.into_iter().enumerate() {
let mut cur = assignment[i];
cur.negate();
cur.add_assign(constant);
assignment[i] = cur;
for (j, eval) in assignment.iter_mut().enumerate().skip(i + 1) {
if j & i == i {
eval.add_assign(&cur);
}
}
}
}
/// Performs a 3-bit window table lookup. `bits` is in
/// little-endian order.
pub fn lookup3_xy<E: Engine, CS>(
mut cs: CS,
bits: &[Boolean],
coords: &[(E::Fr, E::Fr)]
) -> Result<(AllocatedNum<E>, AllocatedNum<E>), SynthesisError>
where CS: ConstraintSystem<E>
{
assert_eq!(bits.len(), 3);
assert_eq!(coords.len(), 8);
// Calculate the index into `coords`
let i =
match (bits[0].get_value(), bits[1].get_value(), bits[2].get_value()) {
(Some(a_value), Some(b_value), Some(c_value)) => {
let mut tmp = 0;
if a_value {
tmp += 1;
}
if b_value {
tmp += 2;
}
if c_value {
tmp += 4;
}
Some(tmp)
},
_ => None
};
// Allocate the x-coordinate resulting from the lookup
let res_x = AllocatedNum::alloc(
cs.namespace(|| "x"),
|| {
Ok(coords[*i.get()?].0)
}
)?;
// Allocate the y-coordinate resulting from the lookup
let res_y = AllocatedNum::alloc(
cs.namespace(|| "y"),
|| {
Ok(coords[*i.get()?].1)
}
)?;
// Compute the coefficients for the lookup constraints
let mut x_coeffs = [E::Fr::zero(); 8];
let mut y_coeffs = [E::Fr::zero(); 8];
synth::<E, _>(3, coords.iter().map(|c| &c.0), &mut x_coeffs);
synth::<E, _>(3, coords.iter().map(|c| &c.1), &mut y_coeffs);
let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[1], &bits[2])?;
let one = CS::one();
cs.enforce(
|| "x-coordinate lookup",
|lc| lc + (x_coeffs[0b001], one)
+ &bits[1].lc::<E>(one, x_coeffs[0b011])
+ &bits[2].lc::<E>(one, x_coeffs[0b101])
+ &precomp.lc::<E>(one, x_coeffs[0b111]),
|lc| lc + &bits[0].lc::<E>(one, E::Fr::one()),
|lc| lc + res_x.get_variable()
- (x_coeffs[0b000], one)
- &bits[1].lc::<E>(one, x_coeffs[0b010])
- &bits[2].lc::<E>(one, x_coeffs[0b100])
- &precomp.lc::<E>(one, x_coeffs[0b110]),
);
cs.enforce(
|| "y-coordinate lookup",
|lc| lc + (y_coeffs[0b001], one)
+ &bits[1].lc::<E>(one, y_coeffs[0b011])
+ &bits[2].lc::<E>(one, y_coeffs[0b101])
+ &precomp.lc::<E>(one, y_coeffs[0b111]),
|lc| lc + &bits[0].lc::<E>(one, E::Fr::one()),
|lc| lc + res_y.get_variable()
- (y_coeffs[0b000], one)
- &bits[1].lc::<E>(one, y_coeffs[0b010])
- &bits[2].lc::<E>(one, y_coeffs[0b100])
- &precomp.lc::<E>(one, y_coeffs[0b110]),
);
Ok((res_x, res_y))
}
/// Performs a 3-bit window table lookup, where
/// one of the bits is a sign bit.
pub fn lookup3_xy_with_conditional_negation<E: Engine, CS>(
mut cs: CS,
bits: &[Boolean],
coords: &[(E::Fr, E::Fr)]
) -> Result<(Num<E>, Num<E>), SynthesisError>
where CS: ConstraintSystem<E>
{
assert_eq!(bits.len(), 3);
assert_eq!(coords.len(), 4);
// Calculate the index into `coords`
let i =
match (bits[0].get_value(), bits[1].get_value()) {
(Some(a_value), Some(b_value)) => {
let mut tmp = 0;
if a_value {
tmp += 1;
}
if b_value {
tmp += 2;
}
Some(tmp)
},
_ => None
};
// Allocate the y-coordinate resulting from the lookup
// and conditional negation
let y = AllocatedNum::alloc(
cs.namespace(|| "y"),
|| {
let mut tmp = coords[*i.get()?].1;
if *bits[2].get_value().get()? {
tmp.negate();
}
Ok(tmp)
}
)?;
let one = CS::one();
// Compute the coefficients for the lookup constraints
let mut x_coeffs = [E::Fr::zero(); 4];
let mut y_coeffs = [E::Fr::zero(); 4];
synth::<E, _>(2, coords.iter().map(|c| &c.0), &mut x_coeffs);
synth::<E, _>(2, coords.iter().map(|c| &c.1), &mut y_coeffs);
let precomp = Boolean::and(cs.namespace(|| "precomp"), &bits[0], &bits[1])?;
let x = Num::zero()
.add_bool_with_coeff(one, &Boolean::constant(true), x_coeffs[0b00])
.add_bool_with_coeff(one, &bits[0], x_coeffs[0b01])
.add_bool_with_coeff(one, &bits[1], x_coeffs[0b10])
.add_bool_with_coeff(one, &precomp, x_coeffs[0b11]);
let y_lc = precomp.lc::<E>(one, y_coeffs[0b11]) +
&bits[1].lc::<E>(one, y_coeffs[0b10]) +
&bits[0].lc::<E>(one, y_coeffs[0b01]) +
(y_coeffs[0b00], one);
cs.enforce(
|| "y-coordinate lookup",
|lc| lc + &y_lc + &y_lc,
|lc| lc + &bits[2].lc::<E>(one, E::Fr::one()),
|lc| lc + &y_lc - y.get_variable()
);
Ok((x, y.into()))
}
#[cfg(test)]
mod test {
use rand::{SeedableRng, Rand, Rng, XorShiftRng};
use super::*;
use ::circuit::test::*;
use ::circuit::boolean::{Boolean, AllocatedBit};
use pairing::bls12_381::{Bls12, Fr};
#[test]
fn test_lookup3_xy() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0656]);
for _ in 0..100 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a_val = rng.gen();
let a = Boolean::from(
AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap()
);
let b_val = rng.gen();
let b = Boolean::from(
AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap()
);
let c_val = rng.gen();
let c = Boolean::from(
AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap()
);
let bits = vec![a, b, c];
let points: Vec<(Fr, Fr)> = (0..8).map(|_| (rng.gen(), rng.gen())).collect();
let res = lookup3_xy(&mut cs, &bits, &points).unwrap();
assert!(cs.is_satisfied());
let mut index = 0;
if a_val { index += 1 }
if b_val { index += 2 }
if c_val { index += 4 }
assert_eq!(res.0.get_value().unwrap(), points[index].0);
assert_eq!(res.1.get_value().unwrap(), points[index].1);
}
}
#[test]
fn test_lookup3_xy_with_conditional_negation() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..100 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a_val = rng.gen();
let a = Boolean::from(
AllocatedBit::alloc(cs.namespace(|| "a"), Some(a_val)).unwrap()
);
let b_val = rng.gen();
let b = Boolean::from(
AllocatedBit::alloc(cs.namespace(|| "b"), Some(b_val)).unwrap()
);
let c_val = rng.gen();
let c = Boolean::from(
AllocatedBit::alloc(cs.namespace(|| "c"), Some(c_val)).unwrap()
);
let bits = vec![a, b, c];
let points: Vec<(Fr, Fr)> = (0..4).map(|_| (rng.gen(), rng.gen())).collect();
let res = lookup3_xy_with_conditional_negation(&mut cs, &bits, &points).unwrap();
assert!(cs.is_satisfied());
let mut index = 0;
if a_val { index += 1 }
if b_val { index += 2 }
assert_eq!(res.0.get_value().unwrap(), points[index].0);
let mut tmp = points[index].1;
if c_val { tmp.negate() }
assert_eq!(res.1.get_value().unwrap(), tmp);
}
}
#[test]
fn test_synth() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let window_size = 4;
let mut assignment = vec![Fr::zero(); 1 << window_size];
let constants: Vec<_> = (0..(1 << window_size)).map(|_| Fr::rand(&mut rng)).collect();
synth::<Bls12, _>(window_size, &constants, &mut assignment);
for b in 0..(1 << window_size) {
let mut acc = Fr::zero();
for j in 0..(1 << window_size) {
if j & b == j {
acc.add_assign(&assignment[j]);
}
}
assert_eq!(acc, constants[b]);
}
}
}

View File

@ -1,39 +0,0 @@
#[cfg(test)]
pub mod test;
pub mod boolean;
pub mod multieq;
pub mod uint32;
pub mod blake2s;
pub mod num;
pub mod lookup;
pub mod ecc;
pub mod pedersen_hash;
pub mod multipack;
pub mod sha256;
pub mod sapling;
pub mod sprout;
use bellman::{
SynthesisError
};
// TODO: This should probably be removed and we
// should use existing helper methods on `Option`
// for mapping with an error.
/// This basically is just an extension to `Option`
/// which allows for a convenient mapping to an
/// error on `None`.
trait Assignment<T> {
fn get(&self) -> Result<&T, SynthesisError>;
}
impl<T> Assignment<T> for Option<T> {
fn get(&self) -> Result<&T, SynthesisError> {
match *self {
Some(ref v) => Ok(v),
None => Err(SynthesisError::AssignmentMissing)
}
}
}

View File

@ -1,137 +0,0 @@
use pairing::{
Engine,
Field,
PrimeField
};
use bellman::{
SynthesisError,
ConstraintSystem,
LinearCombination,
Variable
};
pub struct MultiEq<E: Engine, CS: ConstraintSystem<E>>{
cs: CS,
ops: usize,
bits_used: usize,
lhs: LinearCombination<E>,
rhs: LinearCombination<E>,
}
impl<E: Engine, CS: ConstraintSystem<E>> MultiEq<E, CS> {
pub fn new(cs: CS) -> Self {
MultiEq {
cs: cs,
ops: 0,
bits_used: 0,
lhs: LinearCombination::zero(),
rhs: LinearCombination::zero()
}
}
fn accumulate(&mut self)
{
let ops = self.ops;
let lhs = self.lhs.clone();
let rhs = self.rhs.clone();
self.cs.enforce(
|| format!("multieq {}", ops),
|_| lhs,
|lc| lc + CS::one(),
|_| rhs
);
self.lhs = LinearCombination::zero();
self.rhs = LinearCombination::zero();
self.bits_used = 0;
self.ops += 1;
}
pub fn enforce_equal(
&mut self,
num_bits: usize,
lhs: &LinearCombination<E>,
rhs: &LinearCombination<E>
)
{
// Check if we will exceed the capacity
if (E::Fr::CAPACITY as usize) <= (self.bits_used + num_bits) {
self.accumulate();
}
assert!((E::Fr::CAPACITY as usize) > (self.bits_used + num_bits));
let coeff = E::Fr::from_str("2").unwrap().pow(&[self.bits_used as u64]);
self.lhs = self.lhs.clone() + (coeff, lhs);
self.rhs = self.rhs.clone() + (coeff, rhs);
self.bits_used += num_bits;
}
}
impl<E: Engine, CS: ConstraintSystem<E>> Drop for MultiEq<E, CS> {
fn drop(&mut self) {
if self.bits_used > 0 {
self.accumulate();
}
}
}
impl<E: Engine, CS: ConstraintSystem<E>> ConstraintSystem<E> for MultiEq<E, CS>
{
type Root = Self;
fn one() -> Variable {
CS::one()
}
fn alloc<F, A, AR>(
&mut self,
annotation: A,
f: F
) -> Result<Variable, SynthesisError>
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
{
self.cs.alloc(annotation, f)
}
fn alloc_input<F, A, AR>(
&mut self,
annotation: A,
f: F
) -> Result<Variable, SynthesisError>
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
{
self.cs.alloc_input(annotation, f)
}
fn enforce<A, AR, LA, LB, LC>(
&mut self,
annotation: A,
a: LA,
b: LB,
c: LC
)
where A: FnOnce() -> AR, AR: Into<String>,
LA: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
LB: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
LC: FnOnce(LinearCombination<E>) -> LinearCombination<E>
{
self.cs.enforce(annotation, a, b, c)
}
fn push_namespace<NR, N>(&mut self, name_fn: N)
where NR: Into<String>, N: FnOnce() -> NR
{
self.cs.get_root().push_namespace(name_fn)
}
fn pop_namespace(&mut self)
{
self.cs.get_root().pop_namespace()
}
fn get_root(&mut self) -> &mut Self::Root
{
self
}
}

View File

@ -1,113 +0,0 @@
use pairing::{Engine, Field, PrimeField};
use bellman::{ConstraintSystem, SynthesisError};
use super::boolean::{Boolean};
use super::num::Num;
use super::Assignment;
/// Takes a sequence of booleans and exposes them as compact
/// public inputs
pub fn pack_into_inputs<E, CS>(
mut cs: CS,
bits: &[Boolean]
) -> Result<(), SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
for (i, bits) in bits.chunks(E::Fr::CAPACITY as usize).enumerate()
{
let mut num = Num::<E>::zero();
let mut coeff = E::Fr::one();
for bit in bits {
num = num.add_bool_with_coeff(CS::one(), bit, coeff);
coeff.double();
}
let input = cs.alloc_input(|| format!("input {}", i), || {
Ok(*num.get_value().get()?)
})?;
// num * 1 = input
cs.enforce(
|| format!("packing constraint {}", i),
|_| num.lc(E::Fr::one()),
|lc| lc + CS::one(),
|lc| lc + input
);
}
Ok(())
}
pub fn bytes_to_bits(bytes: &[u8]) -> Vec<bool>
{
bytes.iter()
.flat_map(|&v| (0..8).rev().map(move |i| (v >> i) & 1 == 1))
.collect()
}
pub fn bytes_to_bits_le(bytes: &[u8]) -> Vec<bool>
{
bytes.iter()
.flat_map(|&v| (0..8).map(move |i| (v >> i) & 1 == 1))
.collect()
}
pub fn compute_multipacking<E: Engine>(
bits: &[bool]
) -> Vec<E::Fr>
{
let mut result = vec![];
for bits in bits.chunks(E::Fr::CAPACITY as usize)
{
let mut cur = E::Fr::zero();
let mut coeff = E::Fr::one();
for bit in bits {
if *bit {
cur.add_assign(&coeff);
}
coeff.double();
}
result.push(cur);
}
result
}
#[test]
fn test_multipacking() {
use rand::{SeedableRng, Rng, XorShiftRng};
use bellman::{ConstraintSystem};
use pairing::bls12_381::{Bls12};
use ::circuit::test::*;
use super::boolean::{AllocatedBit, Boolean};
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for num_bits in 0..1500 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let bits: Vec<bool> = (0..num_bits).map(|_| rng.gen()).collect();
let circuit_bits = bits.iter().enumerate()
.map(|(i, &b)| {
Boolean::from(
AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
Some(b)
).unwrap()
)
})
.collect::<Vec<_>>();
let expected_inputs = compute_multipacking::<Bls12>(&bits);
pack_into_inputs(cs.namespace(|| "pack"), &circuit_bits).unwrap();
assert!(cs.is_satisfied());
assert!(cs.verify(&expected_inputs));
}
}

View File

@ -1,622 +0,0 @@
use pairing::{
Engine,
Field,
PrimeField,
PrimeFieldRepr,
BitIterator
};
use bellman::{
SynthesisError,
ConstraintSystem,
LinearCombination,
Variable
};
use super::{
Assignment
};
use super::boolean::{
self,
Boolean,
AllocatedBit
};
pub struct AllocatedNum<E: Engine> {
value: Option<E::Fr>,
variable: Variable
}
impl<E: Engine> Clone for AllocatedNum<E> {
fn clone(&self) -> Self {
AllocatedNum {
value: self.value,
variable: self.variable
}
}
}
impl<E: Engine> AllocatedNum<E> {
pub fn alloc<CS, F>(
mut cs: CS,
value: F,
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E>,
F: FnOnce() -> Result<E::Fr, SynthesisError>
{
let mut new_value = None;
let var = cs.alloc(|| "num", || {
let tmp = value()?;
new_value = Some(tmp);
Ok(tmp)
})?;
Ok(AllocatedNum {
value: new_value,
variable: var
})
}
pub fn inputize<CS>(
&self,
mut cs: CS
) -> Result<(), SynthesisError>
where CS: ConstraintSystem<E>
{
let input = cs.alloc_input(
|| "input variable",
|| {
Ok(*self.value.get()?)
}
)?;
cs.enforce(
|| "enforce input is correct",
|lc| lc + input,
|lc| lc + CS::one(),
|lc| lc + self.variable
);
Ok(())
}
/// Deconstructs this allocated number into its
/// boolean representation in little-endian bit
/// order, requiring that the representation
/// strictly exists "in the field" (i.e., a
/// congruency is not allowed.)
pub fn into_bits_le_strict<CS>(
&self,
mut cs: CS
) -> Result<Vec<Boolean>, SynthesisError>
where CS: ConstraintSystem<E>
{
pub fn kary_and<E, CS>(
mut cs: CS,
v: &[AllocatedBit]
) -> Result<AllocatedBit, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
assert!(v.len() > 0);
// Let's keep this simple for now and just AND them all
// manually
let mut cur = None;
for (i, v) in v.iter().enumerate() {
if cur.is_none() {
cur = Some(v.clone());
} else {
cur = Some(AllocatedBit::and(
cs.namespace(|| format!("and {}", i)),
cur.as_ref().unwrap(),
v
)?);
}
}
Ok(cur.expect("v.len() > 0"))
}
// We want to ensure that the bit representation of a is
// less than or equal to r - 1.
let mut a = self.value.map(|e| BitIterator::new(e.into_repr()));
let mut b = E::Fr::char();
b.sub_noborrow(&1.into());
let mut result = vec![];
// Runs of ones in r
let mut last_run = None;
let mut current_run = vec![];
let mut found_one = false;
let mut i = 0;
for b in BitIterator::new(b) {
let a_bit = a.as_mut().map(|e| e.next().unwrap());
// Skip over unset bits at the beginning
found_one |= b;
if !found_one {
// a_bit should also be false
a_bit.map(|e| assert!(!e));
continue;
}
if b {
// This is part of a run of ones. Let's just
// allocate the boolean with the expected value.
let a_bit = AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
a_bit
)?;
// ... and add it to the current run of ones.
current_run.push(a_bit.clone());
result.push(a_bit);
} else {
if current_run.len() > 0 {
// This is the start of a run of zeros, but we need
// to k-ary AND against `last_run` first.
if last_run.is_some() {
current_run.push(last_run.clone().unwrap());
}
last_run = Some(kary_and(
cs.namespace(|| format!("run ending at {}", i)),
&current_run
)?);
current_run.truncate(0);
}
// If `last_run` is true, `a` must be false, or it would
// not be in the field.
//
// If `last_run` is false, `a` can be true or false.
let a_bit = AllocatedBit::alloc_conditionally(
cs.namespace(|| format!("bit {}", i)),
a_bit,
&last_run.as_ref().expect("char always starts with a one")
)?;
result.push(a_bit);
}
i += 1;
}
// char is prime, so we'll always end on
// a run of zeros.
assert_eq!(current_run.len(), 0);
// Now, we have `result` in big-endian order.
// However, now we have to unpack self!
let mut lc = LinearCombination::zero();
let mut coeff = E::Fr::one();
for bit in result.iter().rev() {
lc = lc + (coeff, bit.get_variable());
coeff.double();
}
lc = lc - self.variable;
cs.enforce(
|| "unpacking constraint",
|lc| lc,
|lc| lc,
|_| lc
);
// Convert into booleans, and reverse for little-endian bit order
Ok(result.into_iter().map(|b| Boolean::from(b)).rev().collect())
}
/// Convert the allocated number into its little-endian representation.
/// Note that this does not strongly enforce that the commitment is
/// "in the field."
pub fn into_bits_le<CS>(
&self,
mut cs: CS
) -> Result<Vec<Boolean>, SynthesisError>
where CS: ConstraintSystem<E>
{
let bits = boolean::field_into_allocated_bits_le(
&mut cs,
self.value
)?;
let mut lc = LinearCombination::zero();
let mut coeff = E::Fr::one();
for bit in bits.iter() {
lc = lc + (coeff, bit.get_variable());
coeff.double();
}
lc = lc - self.variable;
cs.enforce(
|| "unpacking constraint",
|lc| lc,
|lc| lc,
|_| lc
);
Ok(bits.into_iter().map(|b| Boolean::from(b)).collect())
}
pub fn mul<CS>(
&self,
mut cs: CS,
other: &Self
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E>
{
let mut value = None;
let var = cs.alloc(|| "product num", || {
let mut tmp = *self.value.get()?;
tmp.mul_assign(other.value.get()?);
value = Some(tmp);
Ok(tmp)
})?;
// Constrain: a * b = ab
cs.enforce(
|| "multiplication constraint",
|lc| lc + self.variable,
|lc| lc + other.variable,
|lc| lc + var
);
Ok(AllocatedNum {
value: value,
variable: var
})
}
pub fn square<CS>(
&self,
mut cs: CS
) -> Result<Self, SynthesisError>
where CS: ConstraintSystem<E>
{
let mut value = None;
let var = cs.alloc(|| "squared num", || {
let mut tmp = *self.value.get()?;
tmp.square();
value = Some(tmp);
Ok(tmp)
})?;
// Constrain: a * a = aa
cs.enforce(
|| "squaring constraint",
|lc| lc + self.variable,
|lc| lc + self.variable,
|lc| lc + var
);
Ok(AllocatedNum {
value: value,
variable: var
})
}
pub fn assert_nonzero<CS>(
&self,
mut cs: CS
) -> Result<(), SynthesisError>
where CS: ConstraintSystem<E>
{
let inv = cs.alloc(|| "ephemeral inverse", || {
let tmp = *self.value.get()?;
if tmp.is_zero() {
Err(SynthesisError::DivisionByZero)
} else {
Ok(tmp.inverse().unwrap())
}
})?;
// Constrain a * inv = 1, which is only valid
// iff a has a multiplicative inverse, untrue
// for zero.
cs.enforce(
|| "nonzero assertion constraint",
|lc| lc + self.variable,
|lc| lc + inv,
|lc| lc + CS::one()
);
Ok(())
}
/// Takes two allocated numbers (a, b) and returns
/// (b, a) if the condition is true, and (a, b)
/// otherwise.
pub fn conditionally_reverse<CS>(
mut cs: CS,
a: &Self,
b: &Self,
condition: &Boolean
) -> Result<(Self, Self), SynthesisError>
where CS: ConstraintSystem<E>
{
let c = Self::alloc(
cs.namespace(|| "conditional reversal result 1"),
|| {
if *condition.get_value().get()? {
Ok(*b.value.get()?)
} else {
Ok(*a.value.get()?)
}
}
)?;
cs.enforce(
|| "first conditional reversal",
|lc| lc + a.variable - b.variable,
|_| condition.lc(CS::one(), E::Fr::one()),
|lc| lc + a.variable - c.variable
);
let d = Self::alloc(
cs.namespace(|| "conditional reversal result 2"),
|| {
if *condition.get_value().get()? {
Ok(*a.value.get()?)
} else {
Ok(*b.value.get()?)
}
}
)?;
cs.enforce(
|| "second conditional reversal",
|lc| lc + b.variable - a.variable,
|_| condition.lc(CS::one(), E::Fr::one()),
|lc| lc + b.variable - d.variable
);
Ok((c, d))
}
pub fn get_value(&self) -> Option<E::Fr> {
self.value
}
pub fn get_variable(&self) -> Variable {
self.variable
}
}
pub struct Num<E: Engine> {
value: Option<E::Fr>,
lc: LinearCombination<E>
}
impl<E: Engine> From<AllocatedNum<E>> for Num<E> {
fn from(num: AllocatedNum<E>) -> Num<E> {
Num {
value: num.value,
lc: LinearCombination::<E>::zero() + num.variable
}
}
}
impl<E: Engine> Num<E> {
pub fn zero() -> Self {
Num {
value: Some(E::Fr::zero()),
lc: LinearCombination::zero()
}
}
pub fn get_value(&self) -> Option<E::Fr> {
self.value
}
pub fn lc(&self, coeff: E::Fr) -> LinearCombination<E> {
LinearCombination::zero() + (coeff, &self.lc)
}
pub fn add_bool_with_coeff(
self,
one: Variable,
bit: &Boolean,
coeff: E::Fr
) -> Self
{
let newval = match (self.value, bit.get_value()) {
(Some(mut curval), Some(bval)) => {
if bval {
curval.add_assign(&coeff);
}
Some(curval)
},
_ => None
};
Num {
value: newval,
lc: self.lc + &bit.lc(one, coeff)
}
}
}
#[cfg(test)]
mod test {
use rand::{SeedableRng, Rand, Rng, XorShiftRng};
use bellman::{ConstraintSystem};
use pairing::bls12_381::{Bls12, Fr};
use pairing::{Field, PrimeField, BitIterator};
use ::circuit::test::*;
use super::{AllocatedNum, Boolean};
#[test]
fn test_allocated_num() {
let mut cs = TestConstraintSystem::<Bls12>::new();
AllocatedNum::alloc(&mut cs, || Ok(Fr::one())).unwrap();
assert!(cs.get("num") == Fr::one());
}
#[test]
fn test_num_squaring() {
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap();
let n2 = n.square(&mut cs).unwrap();
assert!(cs.is_satisfied());
assert!(cs.get("squared num") == Fr::from_str("9").unwrap());
assert!(n2.value.unwrap() == Fr::from_str("9").unwrap());
cs.set("squared num", Fr::from_str("10").unwrap());
assert!(!cs.is_satisfied());
}
#[test]
fn test_num_multiplication() {
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::from_str("12").unwrap())).unwrap();
let n2 = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::from_str("10").unwrap())).unwrap();
let n3 = n.mul(&mut cs, &n2).unwrap();
assert!(cs.is_satisfied());
assert!(cs.get("product num") == Fr::from_str("120").unwrap());
assert!(n3.value.unwrap() == Fr::from_str("120").unwrap());
cs.set("product num", Fr::from_str("121").unwrap());
assert!(!cs.is_satisfied());
}
#[test]
fn test_num_conditional_reversal() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(rng.gen())).unwrap();
let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(rng.gen())).unwrap();
let condition = Boolean::constant(false);
let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap();
assert!(cs.is_satisfied());
assert_eq!(a.value.unwrap(), c.value.unwrap());
assert_eq!(b.value.unwrap(), d.value.unwrap());
}
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(rng.gen())).unwrap();
let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(rng.gen())).unwrap();
let condition = Boolean::constant(true);
let (c, d) = AllocatedNum::conditionally_reverse(&mut cs, &a, &b, &condition).unwrap();
assert!(cs.is_satisfied());
assert_eq!(a.value.unwrap(), d.value.unwrap());
assert_eq!(b.value.unwrap(), c.value.unwrap());
}
}
#[test]
fn test_num_nonzero() {
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::from_str("3").unwrap())).unwrap();
n.assert_nonzero(&mut cs).unwrap();
assert!(cs.is_satisfied());
cs.set("ephemeral inverse", Fr::from_str("3").unwrap());
assert!(cs.which_is_unsatisfied() == Some("nonzero assertion constraint"));
}
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(&mut cs, || Ok(Fr::zero())).unwrap();
assert!(n.assert_nonzero(&mut cs).is_err());
}
}
#[test]
fn test_into_bits_strict() {
let mut negone = Fr::one();
negone.negate();
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(&mut cs, || Ok(negone)).unwrap();
n.into_bits_le_strict(&mut cs).unwrap();
assert!(cs.is_satisfied());
// make the bit representation the characteristic
cs.set("bit 254/boolean", Fr::one());
// this makes the conditional boolean constraint fail
assert_eq!(cs.which_is_unsatisfied().unwrap(), "bit 254/boolean constraint");
}
#[test]
fn test_into_bits() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for i in 0..200 {
let r = Fr::rand(&mut rng);
let mut cs = TestConstraintSystem::<Bls12>::new();
let n = AllocatedNum::alloc(&mut cs, || Ok(r)).unwrap();
let bits = if i % 2 == 0 {
n.into_bits_le(&mut cs).unwrap()
} else {
n.into_bits_le_strict(&mut cs).unwrap()
};
assert!(cs.is_satisfied());
for (b, a) in BitIterator::new(r.into_repr()).skip(1).zip(bits.iter().rev()) {
if let &Boolean::Is(ref a) = a {
assert_eq!(b, a.get_value().unwrap());
} else {
unreachable!()
}
}
cs.set("num", Fr::rand(&mut rng));
assert!(!cs.is_satisfied());
cs.set("num", r);
assert!(cs.is_satisfied());
for i in 0..Fr::NUM_BITS {
let name = format!("bit {}/boolean", i);
let cur = cs.get(&name);
let mut tmp = Fr::one();
tmp.sub_assign(&cur);
cs.set(&name, tmp);
assert!(!cs.is_satisfied());
cs.set(&name, cur);
assert!(cs.is_satisfied());
}
}
}
}

View File

@ -1,46 +1,42 @@
use super::*;
use super::ecc::{
MontgomeryPoint,
EdwardsPoint
};
use super::boolean::Boolean;
use ::jubjub::*;
use bellman::{
ConstraintSystem
};
use super::lookup::*;
pub use pedersen_hash::Personalization;
//! Gadget for Zcash's Pedersen hash.
impl Personalization {
fn get_constant_bools(&self) -> Vec<Boolean> {
self.get_bits()
use super::ecc::{EdwardsPoint, MontgomeryPoint};
pub use crate::pedersen_hash::Personalization;
use bellman::gadgets::boolean::Boolean;
use bellman::gadgets::lookup::*;
use bellman::{ConstraintSystem, SynthesisError};
use super::constants::PEDERSEN_CIRCUIT_GENERATORS;
fn get_constant_bools(person: &Personalization) -> Vec<Boolean> {
person
.get_bits()
.into_iter()
.map(|e| Boolean::constant(e))
.map(Boolean::constant)
.collect()
}
}
pub fn pedersen_hash<E: JubjubEngine, CS>(
pub fn pedersen_hash<CS>(
mut cs: CS,
personalization: Personalization,
bits: &[Boolean],
params: &E::Params
) -> Result<EdwardsPoint<E>, SynthesisError>
where CS: ConstraintSystem<E>
) -> Result<EdwardsPoint, SynthesisError>
where
CS: ConstraintSystem<bls12_381::Scalar>,
{
let personalization = personalization.get_constant_bools();
let personalization = get_constant_bools(&personalization);
assert_eq!(personalization.len(), 6);
let mut edwards_result = None;
let mut bits = personalization.iter().chain(bits.iter());
let mut segment_generators = params.pedersen_circuit_generators().iter();
let mut bits = personalization.iter().chain(bits.iter()).peekable();
let mut segment_generators = PEDERSEN_CIRCUIT_GENERATORS.iter();
let boolean_false = Boolean::constant(false);
let mut segment_i = 0;
loop {
while bits.peek().is_some() {
let mut segment_result = None;
let mut segment_windows = &segment_generators.next()
.expect("enough segments")[..];
let mut segment_windows = &segment_generators.next().expect("enough segments")[..];
let mut window_i = 0;
while let Some(a) = bits.next() {
@ -50,7 +46,7 @@ pub fn pedersen_hash<E: JubjubEngine, CS>(
let tmp = lookup3_xy_with_conditional_negation(
cs.namespace(|| format!("segment {}, window {}", segment_i, window_i)),
&[a.clone(), b.clone(), c.clone()],
&segment_windows[0]
&segment_windows[0],
)?;
let tmp = MontgomeryPoint::interpret_unchecked(tmp.0, tmp.1);
@ -58,49 +54,46 @@ pub fn pedersen_hash<E: JubjubEngine, CS>(
match segment_result {
None => {
segment_result = Some(tmp);
},
}
Some(ref mut segment_result) => {
*segment_result = tmp.add(
cs.namespace(|| format!("addition of segment {}, window {}", segment_i, window_i)),
cs.namespace(|| {
format!("addition of segment {}, window {}", segment_i, window_i)
}),
segment_result,
params
)?;
}
}
segment_windows = &segment_windows[1..];
if segment_windows.len() == 0 {
if segment_windows.is_empty() {
break;
}
window_i += 1;
}
match segment_result {
Some(segment_result) => {
// Convert this segment into twisted Edwards form.
let segment_result = segment_result.into_edwards(
cs.namespace(|| format!("conversion of segment {} into edwards", segment_i)),
params
)?;
let segment_result = segment_result.expect(
"bits is not exhausted due to while condition;
thus there must be a segment window;
thus there must be a segment result",
);
match edwards_result {
Some(ref mut edwards_result) => {
*edwards_result = segment_result.add(
cs.namespace(|| format!("addition of segment {} to accumulator", segment_i)),
edwards_result,
params
)?;
},
None => {
edwards_result = Some(segment_result);
}
}
},
// Convert this segment into twisted Edwards form.
let segment_result = segment_result.into_edwards(
cs.namespace(|| format!("conversion of segment {} into edwards", segment_i)),
)?;
match edwards_result {
Some(ref mut edwards_result) => {
*edwards_result = segment_result.add(
cs.namespace(|| format!("addition of segment {} to accumulator", segment_i)),
edwards_result,
)?;
}
None => {
// We didn't process any new bits.
break;
edwards_result = Some(segment_result);
}
}
@ -112,83 +105,200 @@ pub fn pedersen_hash<E: JubjubEngine, CS>(
#[cfg(test)]
mod test {
use rand::{SeedableRng, Rng, XorShiftRng};
use super::*;
use ::circuit::test::*;
use ::circuit::boolean::{Boolean, AllocatedBit};
use pairing::bls12_381::{Bls12, Fr};
use pairing::PrimeField;
use crate::pedersen_hash;
use bellman::gadgets::boolean::{AllocatedBit, Boolean};
use bellman::gadgets::test::*;
use group::{ff::PrimeField, Curve};
use rand_core::{RngCore, SeedableRng};
use rand_xorshift::XorShiftRng;
/// Predict the number of constraints of a Pedersen hash
fn ph_num_constraints(input_bits: usize) -> usize {
// Account for the 6 personalization bits.
let personalized_bits = 6 + input_bits;
// Constant booleans in the personalization and padding don't need lookup "precomp" constraints.
let precomputed_booleans = 2 + (personalized_bits % 3 == 1) as usize;
// Count chunks and segments with ceiling division
let chunks = (personalized_bits + 3 - 1) / 3;
let segments = (chunks + 63 - 1) / 63;
let all_but_last_segments = segments - 1;
let last_chunks = chunks - all_but_last_segments * 63;
// Constraints per operation
let lookup_chunk = 2;
let add_chunks = 3; // Montgomery addition
let convert_segment = 2; // Conversion to Edwards
let add_segments = 6; // Edwards addition
(chunks) * lookup_chunk - precomputed_booleans
+ segments * convert_segment
+ all_but_last_segments * ((63 - 1) * add_chunks + add_segments)
+ (last_chunks - 1) * add_chunks
}
#[test]
fn test_pedersen_hash_constraints() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let params = &JubjubBls12::new();
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut rng = XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);
let input: Vec<bool> = (0..(Fr::NUM_BITS * 2)).map(|_| rng.gen()).collect();
let leaves_len = 2 * 255;
let note_len = 64 + 256 + 256;
let input_bools: Vec<Boolean> = input.iter().enumerate().map(|(i, b)| {
Boolean::from(
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap()
for &n_bits in [
0,
3 * 63 - 6,
3 * 63 - 6 + 1,
3 * 63 - 6 + 2,
leaves_len,
note_len,
]
.iter()
{
let mut cs = TestConstraintSystem::new();
let input: Vec<bool> = (0..n_bits).map(|_| rng.next_u32() % 2 != 0).collect();
let input_bools: Vec<Boolean> = input
.iter()
.enumerate()
.map(|(i, b)| {
Boolean::from(
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b))
.unwrap(),
)
})
.collect();
pedersen_hash(
cs.namespace(|| "pedersen hash"),
Personalization::NoteCommitment,
&input_bools,
)
}).collect();
.unwrap();
pedersen_hash(
cs.namespace(|| "pedersen hash"),
Personalization::NoteCommitment,
&input_bools,
params
).unwrap();
assert!(cs.is_satisfied());
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 1377);
let bitness_constraints = n_bits;
let ph_constraints = ph_num_constraints(n_bits);
assert_eq!(cs.num_constraints(), bitness_constraints + ph_constraints);
// The actual usages
if n_bits == leaves_len {
assert_eq!(cs.num_constraints(), leaves_len + 867)
};
if n_bits == note_len {
assert_eq!(cs.num_constraints(), note_len + 982)
};
}
}
#[test]
fn test_pedersen_hash() {
let mut rng = XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let params = &JubjubBls12::new();
let mut rng = XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);
for length in 0..751 {
for _ in 0..5 {
let mut input: Vec<bool> = (0..length).map(|_| rng.gen()).collect();
let input: Vec<bool> = (0..length).map(|_| rng.next_u32() % 2 != 0).collect();
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut cs = TestConstraintSystem::new();
let input_bools: Vec<Boolean> = input.iter().enumerate().map(|(i, b)| {
Boolean::from(
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b)).unwrap()
)
}).collect();
let input_bools: Vec<Boolean> = input
.iter()
.enumerate()
.map(|(i, b)| {
Boolean::from(
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b))
.unwrap(),
)
})
.collect();
let res = pedersen_hash(
cs.namespace(|| "pedersen hash"),
Personalization::MerkleTree(1),
&input_bools,
params
).unwrap();
)
.unwrap();
assert!(cs.is_satisfied());
let expected = ::pedersen_hash::pedersen_hash::<Bls12, _>(
let expected = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash(
Personalization::MerkleTree(1),
input.clone().into_iter(),
params
).into_xy();
))
.to_affine();
assert_eq!(res.get_x().get_value().unwrap(), expected.0);
assert_eq!(res.get_y().get_value().unwrap(), expected.1);
assert_eq!(res.get_u().get_value().unwrap(), expected.get_u());
assert_eq!(res.get_v().get_value().unwrap(), expected.get_v());
// Test against the output of a different personalization
let unexpected = ::pedersen_hash::pedersen_hash::<Bls12, _>(
let unexpected = jubjub::ExtendedPoint::from(pedersen_hash::pedersen_hash(
Personalization::MerkleTree(0),
input.into_iter(),
params
).into_xy();
))
.to_affine();
assert!(res.get_x().get_value().unwrap() != unexpected.0);
assert!(res.get_y().get_value().unwrap() != unexpected.1);
assert!(res.get_u().get_value().unwrap() != unexpected.get_u());
assert!(res.get_v().get_value().unwrap() != unexpected.get_v());
}
}
}
#[test]
fn test_pedersen_hash_external_test_vectors() {
let mut rng = XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);
let expected_us = [
"28161926966428986673895580777285905189725480206811328272001879986576840909576",
"39669831794597628158501766225645040955899576179071014703006420393381978263045",
];
let expected_vs = [
"26869991781071974894722407757894142583682396277979904369818887810555917099932",
"2112827187110048608327330788910224944044097981650120385961435904443901436107",
];
for length in 300..302 {
let input: Vec<bool> = (0..length).map(|_| rng.next_u32() % 2 != 0).collect();
let mut cs = TestConstraintSystem::new();
let input_bools: Vec<Boolean> = input
.iter()
.enumerate()
.map(|(i, b)| {
Boolean::from(
AllocatedBit::alloc(cs.namespace(|| format!("input {}", i)), Some(*b))
.unwrap(),
)
})
.collect();
let res = pedersen_hash(
cs.namespace(|| "pedersen hash"),
Personalization::MerkleTree(1),
&input_bools,
)
.unwrap();
assert!(cs.is_satisfied());
assert_eq!(
res.get_u().get_value().unwrap(),
bls12_381::Scalar::from_str_vartime(expected_us[length - 300]).unwrap()
);
assert_eq!(
res.get_v().get_value().unwrap(),
bls12_381::Scalar::from_str_vartime(expected_vs[length - 300]).unwrap()
);
}
}
}

View File

@ -1,815 +0,0 @@
use pairing::{
PrimeField,
PrimeFieldRepr,
Field,
};
use bellman::{
SynthesisError,
ConstraintSystem,
Circuit
};
use jubjub::{
JubjubEngine,
FixedGenerators
};
use constants;
use primitives::{
ValueCommitment,
ProofGenerationKey,
PaymentAddress
};
use super::Assignment;
use super::boolean;
use super::ecc;
use super::pedersen_hash;
use super::blake2s;
use super::num;
use super::multipack;
/// This is an instance of the `Spend` circuit.
pub struct Spend<'a, E: JubjubEngine> {
pub params: &'a E::Params,
/// Pedersen commitment to the value being spent
pub value_commitment: Option<ValueCommitment<E>>,
/// Key required to construct proofs for spending notes
/// for a particular spending key
pub proof_generation_key: Option<ProofGenerationKey<E>>,
/// The payment address associated with the note
pub payment_address: Option<PaymentAddress<E>>,
/// The randomness of the note commitment
pub commitment_randomness: Option<E::Fs>,
/// Re-randomization of the public key
pub ar: Option<E::Fs>,
/// The authentication path of the commitment in the tree
pub auth_path: Vec<Option<(E::Fr, bool)>>,
/// The anchor; the root of the tree. If the note being
/// spent is zero-value, this can be anything.
pub anchor: Option<E::Fr>
}
/// This is an output circuit instance.
pub struct Output<'a, E: JubjubEngine> {
pub params: &'a E::Params,
/// Pedersen commitment to the value being spent
pub value_commitment: Option<ValueCommitment<E>>,
/// The payment address of the recipient
pub payment_address: Option<PaymentAddress<E>>,
/// The randomness used to hide the note commitment data
pub commitment_randomness: Option<E::Fs>,
/// The ephemeral secret key for DH with recipient
pub esk: Option<E::Fs>
}
/// Exposes a Pedersen commitment to the value as an
/// input to the circuit
fn expose_value_commitment<E, CS>(
mut cs: CS,
value_commitment: Option<ValueCommitment<E>>,
params: &E::Params
) -> Result<Vec<boolean::Boolean>, SynthesisError>
where E: JubjubEngine,
CS: ConstraintSystem<E>
{
// Booleanize the value into little-endian bit order
let value_bits = boolean::u64_into_boolean_vec_le(
cs.namespace(|| "value"),
value_commitment.as_ref().map(|c| c.value)
)?;
// Compute the note value in the exponent
let value = ecc::fixed_base_multiplication(
cs.namespace(|| "compute the value in the exponent"),
FixedGenerators::ValueCommitmentValue,
&value_bits,
params
)?;
// Booleanize the randomness. This does not ensure
// the bit representation is "in the field" because
// it doesn't matter for security.
let rcv = boolean::field_into_boolean_vec_le(
cs.namespace(|| "rcv"),
value_commitment.as_ref().map(|c| c.randomness)
)?;
// Compute the randomness in the exponent
let rcv = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of rcv"),
FixedGenerators::ValueCommitmentRandomness,
&rcv,
params
)?;
// Compute the Pedersen commitment to the value
let cv = value.add(
cs.namespace(|| "computation of cv"),
&rcv,
params
)?;
// Expose the commitment as an input to the circuit
cv.inputize(cs.namespace(|| "commitment point"))?;
Ok(value_bits)
}
impl<'a, E: JubjubEngine> Circuit<E> for Spend<'a, E> {
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError>
{
// Prover witnesses ak (ensures that it's on the curve)
let ak = ecc::EdwardsPoint::witness(
cs.namespace(|| "ak"),
self.proof_generation_key.as_ref().map(|k| k.ak.clone()),
self.params
)?;
// There are no sensible attacks on small order points
// of ak (that we're aware of!) but it's a cheap check,
// so we do it.
ak.assert_not_small_order(
cs.namespace(|| "ak not small order"),
self.params
)?;
// Rerandomize ak and expose it as an input to the circuit
{
let ar = boolean::field_into_boolean_vec_le(
cs.namespace(|| "ar"),
self.ar
)?;
// Compute the randomness in the exponent
let ar = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of randomization for the signing key"),
FixedGenerators::SpendingKeyGenerator,
&ar,
self.params
)?;
let rk = ak.add(
cs.namespace(|| "computation of rk"),
&ar,
self.params
)?;
rk.inputize(cs.namespace(|| "rk"))?;
}
// Compute nk = [nsk] ProofGenerationKey
let nk;
{
// Witness nsk as bits
let nsk = boolean::field_into_boolean_vec_le(
cs.namespace(|| "nsk"),
self.proof_generation_key.as_ref().map(|k| k.nsk.clone())
)?;
// NB: We don't ensure that the bit representation of nsk
// is "in the field" (Fs) because it's not used except to
// demonstrate the prover knows it. If they know a
// congruency then that's equivalent.
// Compute nk = [nsk] ProvingPublicKey
nk = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of nk"),
FixedGenerators::ProofGenerationKey,
&nsk,
self.params
)?;
}
// This is the "viewing key" preimage for CRH^ivk
let mut ivk_preimage = vec![];
// Place ak in the preimage for CRH^ivk
ivk_preimage.extend(
ak.repr(cs.namespace(|| "representation of ak"))?
);
// This is the nullifier preimage for PRF^nf
let mut nf_preimage = vec![];
// Extend ivk and nf preimages with the representation of
// nk.
{
let repr_nk = nk.repr(
cs.namespace(|| "representation of nk")
)?;
ivk_preimage.extend(repr_nk.iter().cloned());
nf_preimage.extend(repr_nk);
}
assert_eq!(ivk_preimage.len(), 512);
assert_eq!(nf_preimage.len(), 256);
// Compute the incoming viewing key ivk
let mut ivk = blake2s::blake2s(
cs.namespace(|| "computation of ivk"),
&ivk_preimage,
constants::CRH_IVK_PERSONALIZATION
)?;
// drop_5 to ensure it's in the field
ivk.truncate(E::Fs::CAPACITY as usize);
// Witness g_d, checking that it's on the curve.
let g_d = {
// This binding is to avoid a weird edge case in Rust's
// ownership/borrowing rules. self is partially moved
// above, but the closure for and_then will have to
// move self (or a reference to self) to reference
// self.params, so we have to copy self.params here.
let params = self.params;
ecc::EdwardsPoint::witness(
cs.namespace(|| "witness g_d"),
self.payment_address.as_ref().and_then(|a| a.g_d(params)),
self.params
)?
};
// Check that g_d is not small order. Technically, this check
// is already done in the Output circuit, and this proof ensures
// g_d is bound to a product of that check, but for defense in
// depth let's check it anyway. It's cheap.
g_d.assert_not_small_order(
cs.namespace(|| "g_d not small order"),
self.params
)?;
// Compute pk_d = g_d^ivk
let pk_d = g_d.mul(
cs.namespace(|| "compute pk_d"),
&ivk,
self.params
)?;
// Compute note contents:
// value (in big endian) followed by g_d and pk_d
let mut note_contents = vec![];
// Handle the value; we'll need it later for the
// dummy input check.
let mut value_num = num::Num::zero();
{
// Get the value in little-endian bit order
let value_bits = expose_value_commitment(
cs.namespace(|| "value commitment"),
self.value_commitment,
self.params
)?;
// Compute the note's value as a linear combination
// of the bits.
let mut coeff = E::Fr::one();
for bit in &value_bits {
value_num = value_num.add_bool_with_coeff(
CS::one(),
bit,
coeff
);
coeff.double();
}
// Place the value in the note
note_contents.extend(value_bits);
}
// Place g_d in the note
note_contents.extend(
g_d.repr(cs.namespace(|| "representation of g_d"))?
);
// Place pk_d in the note
note_contents.extend(
pk_d.repr(cs.namespace(|| "representation of pk_d"))?
);
assert_eq!(
note_contents.len(),
64 + // value
256 + // g_d
256 // p_d
);
// Compute the hash of the note contents
let mut cm = pedersen_hash::pedersen_hash(
cs.namespace(|| "note content hash"),
pedersen_hash::Personalization::NoteCommitment,
&note_contents,
self.params
)?;
{
// Booleanize the randomness for the note commitment
let rcm = boolean::field_into_boolean_vec_le(
cs.namespace(|| "rcm"),
self.commitment_randomness
)?;
// Compute the note commitment randomness in the exponent
let rcm = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of commitment randomness"),
FixedGenerators::NoteCommitmentRandomness,
&rcm,
self.params
)?;
// Randomize the note commitment. Pedersen hashes are not
// themselves hiding commitments.
cm = cm.add(
cs.namespace(|| "randomization of note commitment"),
&rcm,
self.params
)?;
}
// This will store (least significant bit first)
// the position of the note in the tree, for use
// in nullifier computation.
let mut position_bits = vec![];
// This is an injective encoding, as cur is a
// point in the prime order subgroup.
let mut cur = cm.get_x().clone();
// Ascend the merkle tree authentication path
for (i, e) in self.auth_path.into_iter().enumerate() {
let cs = &mut cs.namespace(|| format!("merkle tree hash {}", i));
// Determines if the current subtree is the "right" leaf at this
// depth of the tree.
let cur_is_right = boolean::Boolean::from(boolean::AllocatedBit::alloc(
cs.namespace(|| "position bit"),
e.map(|e| e.1)
)?);
// Push this boolean for nullifier computation later
position_bits.push(cur_is_right.clone());
// Witness the authentication path element adjacent
// at this depth.
let path_element = num::AllocatedNum::alloc(
cs.namespace(|| "path element"),
|| {
Ok(e.get()?.0)
}
)?;
// Swap the two if the current subtree is on the right
let (xl, xr) = num::AllocatedNum::conditionally_reverse(
cs.namespace(|| "conditional reversal of preimage"),
&cur,
&path_element,
&cur_is_right
)?;
// We don't need to be strict, because the function is
// collision-resistant. If the prover witnesses a congruency,
// they will be unable to find an authentication path in the
// tree with high probability.
let mut preimage = vec![];
preimage.extend(xl.into_bits_le(cs.namespace(|| "xl into bits"))?);
preimage.extend(xr.into_bits_le(cs.namespace(|| "xr into bits"))?);
// Compute the new subtree value
cur = pedersen_hash::pedersen_hash(
cs.namespace(|| "computation of pedersen hash"),
pedersen_hash::Personalization::MerkleTree(i),
&preimage,
self.params
)?.get_x().clone(); // Injective encoding
}
{
let real_anchor_value = self.anchor;
// Allocate the "real" anchor that will be exposed.
let rt = num::AllocatedNum::alloc(
cs.namespace(|| "conditional anchor"),
|| {
Ok(*real_anchor_value.get()?)
}
)?;
// (cur - rt) * value = 0
// if value is zero, cur and rt can be different
// if value is nonzero, they must be equal
cs.enforce(
|| "conditionally enforce correct root",
|lc| lc + cur.get_variable() - rt.get_variable(),
|lc| lc + &value_num.lc(E::Fr::one()),
|lc| lc
);
// Expose the anchor
rt.inputize(cs.namespace(|| "anchor"))?;
}
// Compute the cm + g^position for preventing
// faerie gold attacks
let mut rho = cm;
{
// Compute the position in the exponent
let position = ecc::fixed_base_multiplication(
cs.namespace(|| "g^position"),
FixedGenerators::NullifierPosition,
&position_bits,
self.params
)?;
// Add the position to the commitment
rho = rho.add(
cs.namespace(|| "faerie gold prevention"),
&position,
self.params
)?;
}
// Let's compute nf = BLAKE2s(nk || rho)
nf_preimage.extend(
rho.repr(cs.namespace(|| "representation of rho"))?
);
assert_eq!(nf_preimage.len(), 512);
// Compute nf
let nf = blake2s::blake2s(
cs.namespace(|| "nf computation"),
&nf_preimage,
constants::PRF_NF_PERSONALIZATION
)?;
multipack::pack_into_inputs(cs.namespace(|| "pack nullifier"), &nf)
}
}
impl<'a, E: JubjubEngine> Circuit<E> for Output<'a, E> {
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError>
{
// Let's start to construct our note, which contains
// value (big endian)
let mut note_contents = vec![];
// Expose the value commitment and place the value
// in the note.
note_contents.extend(expose_value_commitment(
cs.namespace(|| "value commitment"),
self.value_commitment,
self.params
)?);
// Let's deal with g_d
{
let params = self.params;
// Prover witnesses g_d, ensuring it's on the
// curve.
let g_d = ecc::EdwardsPoint::witness(
cs.namespace(|| "witness g_d"),
self.payment_address.as_ref().and_then(|a| a.g_d(params)),
self.params
)?;
// g_d is ensured to be large order. The relationship
// between g_d and pk_d ultimately binds ivk to the
// note. If this were a small order point, it would
// not do this correctly, and the prover could
// double-spend by finding random ivk's that satisfy
// the relationship.
//
// Further, if it were small order, epk would be
// small order too!
g_d.assert_not_small_order(
cs.namespace(|| "g_d not small order"),
self.params
)?;
// Extend our note contents with the representation of
// g_d.
note_contents.extend(
g_d.repr(cs.namespace(|| "representation of g_d"))?
);
// Booleanize our ephemeral secret key
let esk = boolean::field_into_boolean_vec_le(
cs.namespace(|| "esk"),
self.esk
)?;
// Create the ephemeral public key from g_d.
let epk = g_d.mul(
cs.namespace(|| "epk computation"),
&esk,
self.params
)?;
// Expose epk publicly.
epk.inputize(cs.namespace(|| "epk"))?;
}
// Now let's deal with pk_d. We don't do any checks and
// essentially allow the prover to witness any 256 bits
// they would like.
{
// Just grab pk_d from the witness
let pk_d = self.payment_address.as_ref().map(|e| e.pk_d.into_xy());
// Witness the y-coordinate, encoded as little
// endian bits (to match the representation)
let y_contents = boolean::field_into_boolean_vec_le(
cs.namespace(|| "pk_d bits of y"),
pk_d.map(|e| e.1)
)?;
// Witness the sign bit
let sign_bit = boolean::Boolean::from(boolean::AllocatedBit::alloc(
cs.namespace(|| "pk_d bit of x"),
pk_d.map(|e| e.0.into_repr().is_odd())
)?);
// Extend the note with pk_d representation
note_contents.extend(y_contents);
note_contents.push(sign_bit);
}
assert_eq!(
note_contents.len(),
64 + // value
256 + // g_d
256 // pk_d
);
// Compute the hash of the note contents
let mut cm = pedersen_hash::pedersen_hash(
cs.namespace(|| "note content hash"),
pedersen_hash::Personalization::NoteCommitment,
&note_contents,
self.params
)?;
{
// Booleanize the randomness
let rcm = boolean::field_into_boolean_vec_le(
cs.namespace(|| "rcm"),
self.commitment_randomness
)?;
// Compute the note commitment randomness in the exponent
let rcm = ecc::fixed_base_multiplication(
cs.namespace(|| "computation of commitment randomness"),
FixedGenerators::NoteCommitmentRandomness,
&rcm,
self.params
)?;
// Randomize our note commitment
cm = cm.add(
cs.namespace(|| "randomization of note commitment"),
&rcm,
self.params
)?;
}
// Only the x-coordinate of the output is revealed,
// since we know it is prime order, and we know that
// the x-coordinate is an injective encoding for
// prime-order elements.
cm.get_x().inputize(cs.namespace(|| "commitment"))?;
Ok(())
}
}
#[test]
fn test_input_circuit_with_bls12_381() {
use pairing::{Field, BitIterator};
use pairing::bls12_381::*;
use rand::{SeedableRng, Rng, XorShiftRng};
use ::circuit::test::*;
use jubjub::{JubjubBls12, fs, edwards};
let params = &JubjubBls12::new();
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let tree_depth = 32;
for _ in 0..10 {
let value_commitment = ValueCommitment {
value: rng.gen(),
randomness: rng.gen()
};
let nsk: fs::Fs = rng.gen();
let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params);
let proof_generation_key = ::primitives::ProofGenerationKey {
ak: ak.clone(),
nsk: nsk.clone()
};
let viewing_key = proof_generation_key.into_viewing_key(params);
let payment_address;
loop {
let diversifier = ::primitives::Diversifier(rng.gen());
if let Some(p) = viewing_key.into_payment_address(
diversifier,
params
)
{
payment_address = p;
break;
}
}
let g_d = payment_address.diversifier.g_d(params).unwrap();
let commitment_randomness: fs::Fs = rng.gen();
let auth_path = vec![Some((rng.gen(), rng.gen())); tree_depth];
let ar: fs::Fs = rng.gen();
{
let rk = viewing_key.rk(ar, params).into_xy();
let expected_value_cm = value_commitment.cm(params).into_xy();
let note = ::primitives::Note {
value: value_commitment.value,
g_d: g_d.clone(),
pk_d: payment_address.pk_d.clone(),
r: commitment_randomness.clone()
};
let mut position = 0u64;
let cm: Fr = note.cm(params);
let mut cur = cm.clone();
for (i, val) in auth_path.clone().into_iter().enumerate()
{
let (uncle, b) = val.unwrap();
let mut lhs = cur;
let mut rhs = uncle;
if b {
::std::mem::swap(&mut lhs, &mut rhs);
}
let mut lhs: Vec<bool> = BitIterator::new(lhs.into_repr()).collect();
let mut rhs: Vec<bool> = BitIterator::new(rhs.into_repr()).collect();
lhs.reverse();
rhs.reverse();
cur = ::pedersen_hash::pedersen_hash::<Bls12, _>(
::pedersen_hash::Personalization::MerkleTree(i),
lhs.into_iter()
.take(Fr::NUM_BITS as usize)
.chain(rhs.into_iter().take(Fr::NUM_BITS as usize)),
params
).into_xy().0;
if b {
position |= 1 << i;
}
}
let expected_nf = note.nf(&viewing_key, position, params);
let expected_nf = multipack::bytes_to_bits_le(&expected_nf);
let expected_nf = multipack::compute_multipacking::<Bls12>(&expected_nf);
assert_eq!(expected_nf.len(), 2);
let mut cs = TestConstraintSystem::<Bls12>::new();
let instance = Spend {
params: params,
value_commitment: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
commitment_randomness: Some(commitment_randomness),
ar: Some(ar),
auth_path: auth_path.clone(),
anchor: Some(cur)
};
instance.synthesize(&mut cs).unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 98777);
assert_eq!(cs.hash(), "d37c738e83df5d9b0bb6495ac96abf21bcb2697477e2c15c2c7916ff7a3b6a89");
assert_eq!(cs.get("randomization of note commitment/x3/num"), cm);
assert_eq!(cs.num_inputs(), 8);
assert_eq!(cs.get_input(0, "ONE"), Fr::one());
assert_eq!(cs.get_input(1, "rk/x/input variable"), rk.0);
assert_eq!(cs.get_input(2, "rk/y/input variable"), rk.1);
assert_eq!(cs.get_input(3, "value commitment/commitment point/x/input variable"), expected_value_cm.0);
assert_eq!(cs.get_input(4, "value commitment/commitment point/y/input variable"), expected_value_cm.1);
assert_eq!(cs.get_input(5, "anchor/input variable"), cur);
assert_eq!(cs.get_input(6, "pack nullifier/input 0"), expected_nf[0]);
assert_eq!(cs.get_input(7, "pack nullifier/input 1"), expected_nf[1]);
}
}
}
#[test]
fn test_output_circuit_with_bls12_381() {
use pairing::{Field};
use pairing::bls12_381::*;
use rand::{SeedableRng, Rng, XorShiftRng};
use ::circuit::test::*;
use jubjub::{JubjubBls12, fs, edwards};
let params = &JubjubBls12::new();
let rng = &mut XorShiftRng::from_seed([0x3dbe6258, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..100 {
let value_commitment = ValueCommitment {
value: rng.gen(),
randomness: rng.gen()
};
let nsk: fs::Fs = rng.gen();
let ak = edwards::Point::rand(rng, params).mul_by_cofactor(params);
let proof_generation_key = ::primitives::ProofGenerationKey {
ak: ak.clone(),
nsk: nsk.clone()
};
let viewing_key = proof_generation_key.into_viewing_key(params);
let payment_address;
loop {
let diversifier = ::primitives::Diversifier(rng.gen());
if let Some(p) = viewing_key.into_payment_address(
diversifier,
params
)
{
payment_address = p;
break;
}
}
let commitment_randomness: fs::Fs = rng.gen();
let esk: fs::Fs = rng.gen();
{
let mut cs = TestConstraintSystem::<Bls12>::new();
let instance = Output {
params: params,
value_commitment: Some(value_commitment.clone()),
payment_address: Some(payment_address.clone()),
commitment_randomness: Some(commitment_randomness),
esk: Some(esk.clone())
};
instance.synthesize(&mut cs).unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 7827);
assert_eq!(cs.hash(), "c26d5cdfe6ccd65c03390902c02e11393ea6bb96aae32a7f2ecb12eb9103faee");
let expected_cm = payment_address.create_note(
value_commitment.value,
commitment_randomness,
params
).expect("should be valid").cm(params);
let expected_value_cm = value_commitment.cm(params).into_xy();
let expected_epk = payment_address.g_d(params).expect("should be valid").mul(esk, params);
let expected_epk_xy = expected_epk.into_xy();
assert_eq!(cs.num_inputs(), 6);
assert_eq!(cs.get_input(0, "ONE"), Fr::one());
assert_eq!(cs.get_input(1, "value commitment/commitment point/x/input variable"), expected_value_cm.0);
assert_eq!(cs.get_input(2, "value commitment/commitment point/y/input variable"), expected_value_cm.1);
assert_eq!(cs.get_input(3, "epk/x/input variable"), expected_epk_xy.0);
assert_eq!(cs.get_input(4, "epk/y/input variable"), expected_epk_xy.1);
assert_eq!(cs.get_input(5, "commitment/input variable"), expected_cm);
}
}
}

View File

@ -1,417 +0,0 @@
use super::uint32::UInt32;
use super::multieq::MultiEq;
use super::boolean::Boolean;
use bellman::{ConstraintSystem, SynthesisError};
use pairing::Engine;
const ROUND_CONSTANTS: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
];
const IV: [u32; 8] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
];
pub fn sha256_block_no_padding<E, CS>(
mut cs: CS,
input: &[Boolean]
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
assert_eq!(input.len(), 512);
Ok(sha256_compression_function(
&mut cs,
&input,
&get_sha256_iv()
)?
.into_iter()
.flat_map(|e| e.into_bits_be())
.collect())
}
pub fn sha256<E, CS>(
mut cs: CS,
input: &[Boolean]
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
assert!(input.len() % 8 == 0);
let mut padded = input.to_vec();
let plen = padded.len() as u64;
// append a single '1' bit
padded.push(Boolean::constant(true));
// append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512
while (padded.len() + 64) % 512 != 0 {
padded.push(Boolean::constant(false));
}
// append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits
for b in (0..64).rev().map(|i| (plen >> i) & 1 == 1) {
padded.push(Boolean::constant(b));
}
assert!(padded.len() % 512 == 0);
let mut cur = get_sha256_iv();
for (i, block) in padded.chunks(512).enumerate() {
cur = sha256_compression_function(
cs.namespace(|| format!("block {}", i)),
block,
&cur
)?;
}
Ok(cur.into_iter()
.flat_map(|e| e.into_bits_be())
.collect())
}
fn get_sha256_iv() -> Vec<UInt32> {
IV.iter().map(|&v| UInt32::constant(v)).collect()
}
fn sha256_compression_function<E, CS>(
cs: CS,
input: &[Boolean],
current_hash_value: &[UInt32]
) -> Result<Vec<UInt32>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
assert_eq!(input.len(), 512);
assert_eq!(current_hash_value.len(), 8);
let mut w = input.chunks(32)
.map(|e| UInt32::from_bits_be(e))
.collect::<Vec<_>>();
// We can save some constraints by combining some of
// the constraints in different u32 additions
let mut cs = MultiEq::new(cs);
for i in 16..64 {
let cs = &mut cs.namespace(|| format!("w extension {}", i));
// s0 := (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3)
let mut s0 = w[i-15].rotr(7);
s0 = s0.xor(
cs.namespace(|| "first xor for s0"),
&w[i-15].rotr(18)
)?;
s0 = s0.xor(
cs.namespace(|| "second xor for s0"),
&w[i-15].shr(3)
)?;
// s1 := (w[i-2] rightrotate 17) xor (w[i-2] rightrotate 19) xor (w[i-2] rightshift 10)
let mut s1 = w[i-2].rotr(17);
s1 = s1.xor(
cs.namespace(|| "first xor for s1"),
&w[i-2].rotr(19)
)?;
s1 = s1.xor(
cs.namespace(|| "second xor for s1"),
&w[i-2].shr(10)
)?;
let tmp = UInt32::addmany(
cs.namespace(|| "computation of w[i]"),
&[w[i-16].clone(), s0, w[i-7].clone(), s1]
)?;
// w[i] := w[i-16] + s0 + w[i-7] + s1
w.push(tmp);
}
assert_eq!(w.len(), 64);
enum Maybe {
Deferred(Vec<UInt32>),
Concrete(UInt32)
}
impl Maybe {
fn compute<E, CS, M>(
self,
cs: M,
others: &[UInt32]
) -> Result<UInt32, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>,
M: ConstraintSystem<E, Root=MultiEq<E, CS>>
{
Ok(match self {
Maybe::Concrete(ref v) => {
return Ok(v.clone())
},
Maybe::Deferred(mut v) => {
v.extend(others.into_iter().cloned());
UInt32::addmany(
cs,
&v
)?
}
})
}
}
let mut a = Maybe::Concrete(current_hash_value[0].clone());
let mut b = current_hash_value[1].clone();
let mut c = current_hash_value[2].clone();
let mut d = current_hash_value[3].clone();
let mut e = Maybe::Concrete(current_hash_value[4].clone());
let mut f = current_hash_value[5].clone();
let mut g = current_hash_value[6].clone();
let mut h = current_hash_value[7].clone();
for i in 0..64 {
let cs = &mut cs.namespace(|| format!("compression round {}", i));
// S1 := (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25)
let new_e = e.compute(cs.namespace(|| "deferred e computation"), &[])?;
let mut s1 = new_e.rotr(6);
s1 = s1.xor(
cs.namespace(|| "first xor for s1"),
&new_e.rotr(11)
)?;
s1 = s1.xor(
cs.namespace(|| "second xor for s1"),
&new_e.rotr(25)
)?;
// ch := (e and f) xor ((not e) and g)
let ch = UInt32::sha256_ch(
cs.namespace(|| "ch"),
&new_e,
&f,
&g
)?;
// temp1 := h + S1 + ch + k[i] + w[i]
let temp1 = vec![
h.clone(),
s1,
ch,
UInt32::constant(ROUND_CONSTANTS[i]),
w[i].clone()
];
// S0 := (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22)
let new_a = a.compute(cs.namespace(|| "deferred a computation"), &[])?;
let mut s0 = new_a.rotr(2);
s0 = s0.xor(
cs.namespace(|| "first xor for s0"),
&new_a.rotr(13)
)?;
s0 = s0.xor(
cs.namespace(|| "second xor for s0"),
&new_a.rotr(22)
)?;
// maj := (a and b) xor (a and c) xor (b and c)
let maj = UInt32::sha256_maj(
cs.namespace(|| "maj"),
&new_a,
&b,
&c
)?;
// temp2 := S0 + maj
let temp2 = vec![s0, maj];
/*
h := g
g := f
f := e
e := d + temp1
d := c
c := b
b := a
a := temp1 + temp2
*/
h = g;
g = f;
f = new_e;
e = Maybe::Deferred(temp1.iter().cloned().chain(Some(d)).collect::<Vec<_>>());
d = c;
c = b;
b = new_a;
a = Maybe::Deferred(temp1.iter().cloned().chain(temp2.iter().cloned()).collect::<Vec<_>>());
}
/*
Add the compressed chunk to the current hash value:
h0 := h0 + a
h1 := h1 + b
h2 := h2 + c
h3 := h3 + d
h4 := h4 + e
h5 := h5 + f
h6 := h6 + g
h7 := h7 + h
*/
let h0 = a.compute(
cs.namespace(|| "deferred h0 computation"),
&[current_hash_value[0].clone()]
)?;
let h1 = UInt32::addmany(
cs.namespace(|| "new h1"),
&[current_hash_value[1].clone(), b]
)?;
let h2 = UInt32::addmany(
cs.namespace(|| "new h2"),
&[current_hash_value[2].clone(), c]
)?;
let h3 = UInt32::addmany(
cs.namespace(|| "new h3"),
&[current_hash_value[3].clone(), d]
)?;
let h4 = e.compute(
cs.namespace(|| "deferred h4 computation"),
&[current_hash_value[4].clone()]
)?;
let h5 = UInt32::addmany(
cs.namespace(|| "new h5"),
&[current_hash_value[5].clone(), f]
)?;
let h6 = UInt32::addmany(
cs.namespace(|| "new h6"),
&[current_hash_value[6].clone(), g]
)?;
let h7 = UInt32::addmany(
cs.namespace(|| "new h7"),
&[current_hash_value[7].clone(), h]
)?;
Ok(vec![h0, h1, h2, h3, h4, h5, h6, h7])
}
#[cfg(test)]
mod test {
use super::*;
use circuit::boolean::AllocatedBit;
use pairing::bls12_381::Bls12;
use circuit::test::TestConstraintSystem;
use rand::{XorShiftRng, SeedableRng, Rng};
#[test]
fn test_blank_hash() {
let iv = get_sha256_iv();
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut input_bits: Vec<_> = (0..512).map(|_| Boolean::Constant(false)).collect();
input_bits[0] = Boolean::Constant(true);
let out = sha256_compression_function(
&mut cs,
&input_bits,
&iv
).unwrap();
let out_bits: Vec<_> = out.into_iter().flat_map(|e| e.into_bits_be()).collect();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 0);
let expected = hex!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
let mut out = out_bits.into_iter();
for b in expected.into_iter() {
for i in (0..8).rev() {
let c = out.next().unwrap().get_value().unwrap();
assert_eq!(c, (b >> i) & 1u8 == 1u8);
}
}
}
#[test]
fn test_full_block() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let iv = get_sha256_iv();
let mut cs = TestConstraintSystem::<Bls12>::new();
let input_bits: Vec<_> = (0..512).map(|i| {
Boolean::from(
AllocatedBit::alloc(
cs.namespace(|| format!("input bit {}", i)),
Some(rng.gen())
).unwrap()
)
}).collect();
sha256_compression_function(
cs.namespace(|| "sha256"),
&input_bits,
&iv
).unwrap();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints() - 512, 25840);
}
#[test]
fn test_against_vectors() {
use crypto::sha2::Sha256;
use crypto::digest::Digest;
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0))
{
let mut h = Sha256::new();
let data: Vec<u8> = (0..input_len).map(|_| rng.gen()).collect();
h.input(&data);
let mut hash_result = [0u8; 32];
h.result(&mut hash_result[..]);
let mut cs = TestConstraintSystem::<Bls12>::new();
let mut input_bits = vec![];
for (byte_i, input_byte) in data.into_iter().enumerate() {
for bit_i in (0..8).rev() {
let cs = cs.namespace(|| format!("input bit {} {}", byte_i, bit_i));
input_bits.push(AllocatedBit::alloc(cs, Some((input_byte >> bit_i) & 1u8 == 1u8)).unwrap().into());
}
}
let r = sha256(&mut cs, &input_bits).unwrap();
assert!(cs.is_satisfied());
let mut s = hash_result.as_ref().iter()
.flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8));
for b in r {
match b {
Boolean::Is(b) => {
assert!(s.next().unwrap() == b.get_value().unwrap());
},
Boolean::Not(b) => {
assert!(s.next().unwrap() != b.get_value().unwrap());
},
Boolean::Constant(b) => {
assert!(input_len == 0);
assert!(s.next().unwrap() == b);
}
}
}
}
}
}

View File

@ -1,42 +0,0 @@
use pairing::{Engine};
use bellman::{ConstraintSystem, SynthesisError};
use circuit::sha256::{
sha256
};
use circuit::boolean::{
Boolean
};
pub fn note_comm<E, CS>(
cs: CS,
a_pk: &[Boolean],
value: &[Boolean],
rho: &[Boolean],
r: &[Boolean]
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
assert_eq!(a_pk.len(), 256);
assert_eq!(value.len(), 64);
assert_eq!(rho.len(), 256);
assert_eq!(r.len(), 256);
let mut image = vec![];
image.push(Boolean::constant(true));
image.push(Boolean::constant(false));
image.push(Boolean::constant(true));
image.push(Boolean::constant(true));
image.push(Boolean::constant(false));
image.push(Boolean::constant(false));
image.push(Boolean::constant(false));
image.push(Boolean::constant(false));
image.extend(a_pk.iter().cloned());
image.extend(value.iter().cloned());
image.extend(rho.iter().cloned());
image.extend(r.iter().cloned());
sha256(
cs,
&image
)
}

View File

@ -1,226 +0,0 @@
use pairing::{Engine};
use bellman::{ConstraintSystem, SynthesisError};
use circuit::sha256::{
sha256_block_no_padding
};
use circuit::boolean::{
AllocatedBit,
Boolean
};
use super::*;
use super::prfs::*;
use super::commitment::note_comm;
pub struct InputNote {
pub nf: Vec<Boolean>,
pub mac: Vec<Boolean>,
}
impl InputNote {
pub fn compute<E, CS>(
mut cs: CS,
a_sk: Option<SpendingKey>,
rho: Option<UniqueRandomness>,
r: Option<CommitmentRandomness>,
value: &NoteValue,
h_sig: &[Boolean],
nonce: bool,
auth_path: [Option<([u8; 32], bool)>; TREE_DEPTH],
rt: &[Boolean]
) -> Result<InputNote, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
let a_sk = witness_u252(
cs.namespace(|| "a_sk"),
a_sk.as_ref().map(|a_sk| &a_sk.0[..])
)?;
let rho = witness_u256(
cs.namespace(|| "rho"),
rho.as_ref().map(|rho| &rho.0[..])
)?;
let r = witness_u256(
cs.namespace(|| "r"),
r.as_ref().map(|r| &r.0[..])
)?;
let a_pk = prf_a_pk(
cs.namespace(|| "a_pk computation"),
&a_sk
)?;
let nf = prf_nf(
cs.namespace(|| "nf computation"),
&a_sk,
&rho
)?;
let mac = prf_pk(
cs.namespace(|| "mac computation"),
&a_sk,
h_sig,
nonce
)?;
let cm = note_comm(
cs.namespace(|| "cm computation"),
&a_pk,
&value.bits_le(),
&rho,
&r
)?;
// Witness into the merkle tree
let mut cur = cm.clone();
for (i, layer) in auth_path.into_iter().enumerate() {
let cs = &mut cs.namespace(|| format!("layer {}", i));
let cur_is_right = AllocatedBit::alloc(
cs.namespace(|| "cur is right"),
layer.as_ref().map(|&(_, p)| p)
)?;
let lhs = cur;
let rhs = witness_u256(
cs.namespace(|| "sibling"),
layer.as_ref().map(|&(ref sibling, _)| &sibling[..])
)?;
// Conditionally swap if cur is right
let preimage = conditionally_swap_u256(
cs.namespace(|| "conditional swap"),
&lhs[..],
&rhs[..],
&cur_is_right
)?;
cur = sha256_block_no_padding(
cs.namespace(|| "hash of this layer"),
&preimage
)?;
}
// enforce must be true if the value is nonzero
let enforce = AllocatedBit::alloc(
cs.namespace(|| "enforce"),
value.get_value().map(|n| n != 0)
)?;
// value * (1 - enforce) = 0
// If `value` is zero, `enforce` _can_ be zero.
// If `value` is nonzero, `enforce` _must_ be one.
cs.enforce(
|| "enforce validity",
|_| value.lc(),
|lc| lc + CS::one() - enforce.get_variable(),
|lc| lc
);
assert_eq!(cur.len(), rt.len());
// Check that the anchor (exposed as a public input)
// is equal to the merkle tree root that we calculated
// for this note
for (i, (cur, rt)) in cur.into_iter().zip(rt.iter()).enumerate() {
// (cur - rt) * enforce = 0
// if enforce is zero, cur and rt can be different
// if enforce is one, they must be equal
cs.enforce(
|| format!("conditionally enforce correct root for bit {}", i),
|_| cur.lc(CS::one(), E::Fr::one()) - &rt.lc(CS::one(), E::Fr::one()),
|lc| lc + enforce.get_variable(),
|lc| lc
);
}
Ok(InputNote {
mac: mac,
nf: nf
})
}
}
/// Swaps two 256-bit blobs conditionally, returning the
/// 512-bit concatenation.
pub fn conditionally_swap_u256<E, CS>(
mut cs: CS,
lhs: &[Boolean],
rhs: &[Boolean],
condition: &AllocatedBit
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>,
{
assert_eq!(lhs.len(), 256);
assert_eq!(rhs.len(), 256);
let mut new_lhs = vec![];
let mut new_rhs = vec![];
for (i, (lhs, rhs)) in lhs.iter().zip(rhs.iter()).enumerate() {
let cs = &mut cs.namespace(|| format!("bit {}", i));
let x = Boolean::from(AllocatedBit::alloc(
cs.namespace(|| "x"),
condition.get_value().and_then(|v| {
if v {
rhs.get_value()
} else {
lhs.get_value()
}
})
)?);
// x = (1-condition)lhs + (condition)rhs
// x = lhs - lhs(condition) + rhs(condition)
// x - lhs = condition (rhs - lhs)
// if condition is zero, we don't swap, so
// x - lhs = 0
// x = lhs
// if condition is one, we do swap, so
// x - lhs = rhs - lhs
// x = rhs
cs.enforce(
|| "conditional swap for x",
|lc| lc + &rhs.lc(CS::one(), E::Fr::one())
- &lhs.lc(CS::one(), E::Fr::one()),
|lc| lc + condition.get_variable(),
|lc| lc + &x.lc(CS::one(), E::Fr::one())
- &lhs.lc(CS::one(), E::Fr::one())
);
let y = Boolean::from(AllocatedBit::alloc(
cs.namespace(|| "y"),
condition.get_value().and_then(|v| {
if v {
lhs.get_value()
} else {
rhs.get_value()
}
})
)?);
// y = (1-condition)rhs + (condition)lhs
// y - rhs = condition (lhs - rhs)
cs.enforce(
|| "conditional swap for y",
|lc| lc + &lhs.lc(CS::one(), E::Fr::one())
- &rhs.lc(CS::one(), E::Fr::one()),
|lc| lc + condition.get_variable(),
|lc| lc + &y.lc(CS::one(), E::Fr::one())
- &rhs.lc(CS::one(), E::Fr::one())
);
new_lhs.push(x);
new_rhs.push(y);
}
let mut f = new_lhs;
f.extend(new_rhs);
assert_eq!(f.len(), 512);
Ok(f)
}

View File

@ -1,488 +0,0 @@
use pairing::{Engine, Field};
use bellman::{ConstraintSystem, SynthesisError, Circuit, LinearCombination};
use circuit::boolean::{
AllocatedBit,
Boolean
};
use circuit::multipack::pack_into_inputs;
mod prfs;
mod commitment;
mod input;
mod output;
use self::input::*;
use self::output::*;
pub const TREE_DEPTH: usize = 29;
pub struct SpendingKey(pub [u8; 32]);
pub struct PayingKey(pub [u8; 32]);
pub struct UniqueRandomness(pub [u8; 32]);
pub struct CommitmentRandomness(pub [u8; 32]);
pub struct JoinSplit {
pub vpub_old: Option<u64>,
pub vpub_new: Option<u64>,
pub h_sig: Option<[u8; 32]>,
pub phi: Option<[u8; 32]>,
pub inputs: Vec<JSInput>,
pub outputs: Vec<JSOutput>,
pub rt: Option<[u8; 32]>,
}
pub struct JSInput {
pub value: Option<u64>,
pub a_sk: Option<SpendingKey>,
pub rho: Option<UniqueRandomness>,
pub r: Option<CommitmentRandomness>,
pub auth_path: [Option<([u8; 32], bool)>; TREE_DEPTH]
}
pub struct JSOutput {
pub value: Option<u64>,
pub a_pk: Option<PayingKey>,
pub r: Option<CommitmentRandomness>
}
impl<E: Engine> Circuit<E> for JoinSplit {
fn synthesize<CS: ConstraintSystem<E>>(
self,
cs: &mut CS
) -> Result<(), SynthesisError>
{
assert_eq!(self.inputs.len(), 2);
assert_eq!(self.outputs.len(), 2);
// vpub_old is the value entering the
// JoinSplit from the "outside" value
// pool
let vpub_old = NoteValue::new(
cs.namespace(|| "vpub_old"),
self.vpub_old
)?;
// vpub_new is the value leaving the
// JoinSplit into the "outside" value
// pool
let vpub_new = NoteValue::new(
cs.namespace(|| "vpub_new"),
self.vpub_new
)?;
// The left hand side of the balance equation
// vpub_old + inputs[0].value + inputs[1].value
let mut lhs = vpub_old.lc();
// The right hand side of the balance equation
// vpub_old + inputs[0].value + inputs[1].value
let mut rhs = vpub_new.lc();
// Witness rt (merkle tree root)
let rt = witness_u256(
cs.namespace(|| "rt"),
self.rt.as_ref().map(|v| &v[..])
).unwrap();
// Witness h_sig
let h_sig = witness_u256(
cs.namespace(|| "h_sig"),
self.h_sig.as_ref().map(|v| &v[..])
).unwrap();
// Witness phi
let phi = witness_u252(
cs.namespace(|| "phi"),
self.phi.as_ref().map(|v| &v[..])
).unwrap();
let mut input_notes = vec![];
let mut lhs_total = self.vpub_old;
// Iterate over the JoinSplit inputs
for (i, input) in self.inputs.into_iter().enumerate() {
let cs = &mut cs.namespace(|| format!("input {}", i));
// Accumulate the value of the left hand side
if let Some(value) = input.value {
lhs_total = lhs_total.map(|v| v.wrapping_add(value));
}
// Allocate the value of the note
let value = NoteValue::new(
cs.namespace(|| "value"),
input.value
)?;
// Compute the nonce (for PRF inputs) which is false
// for the first input, and true for the second input.
let nonce = match i {
0 => false,
1 => true,
_ => unreachable!()
};
// Perform input note computations
input_notes.push(InputNote::compute(
cs.namespace(|| "note"),
input.a_sk,
input.rho,
input.r,
&value,
&h_sig,
nonce,
input.auth_path,
&rt
)?);
// Add the note value to the left hand side of
// the balance equation
lhs = lhs + &value.lc();
}
// Rebind lhs so that it isn't mutable anymore
let lhs = lhs;
// See zcash/zcash/issues/854
{
// Expected sum of the left hand side of the balance
// equation, expressed as a 64-bit unsigned integer
let lhs_total = NoteValue::new(
cs.namespace(|| "total value of left hand side"),
lhs_total
)?;
// Enforce that the left hand side can be expressed as a 64-bit
// integer
cs.enforce(
|| "left hand side can be expressed as a 64-bit unsigned integer",
|_| lhs.clone(),
|lc| lc + CS::one(),
|_| lhs_total.lc()
);
}
let mut output_notes = vec![];
// Iterate over the JoinSplit outputs
for (i, output) in self.outputs.into_iter().enumerate() {
let cs = &mut cs.namespace(|| format!("output {}", i));
let value = NoteValue::new(
cs.namespace(|| "value"),
output.value
)?;
// Compute the nonce (for PRF inputs) which is false
// for the first output, and true for the second output.
let nonce = match i {
0 => false,
1 => true,
_ => unreachable!()
};
// Perform output note computations
output_notes.push(OutputNote::compute(
cs.namespace(|| "note"),
output.a_pk,
&value,
output.r,
&phi,
&h_sig,
nonce
)?);
// Add the note value to the right hand side of
// the balance equation
rhs = rhs + &value.lc();
}
// Enforce that balance is equal
cs.enforce(
|| "balance equation",
|_| lhs.clone(),
|lc| lc + CS::one(),
|_| rhs
);
let mut public_inputs = vec![];
public_inputs.extend(rt);
public_inputs.extend(h_sig);
for note in input_notes {
public_inputs.extend(note.nf);
public_inputs.extend(note.mac);
}
for note in output_notes {
public_inputs.extend(note.cm);
}
public_inputs.extend(vpub_old.bits_le());
public_inputs.extend(vpub_new.bits_le());
pack_into_inputs(cs.namespace(|| "input packing"), &public_inputs)
}
}
pub struct NoteValue {
value: Option<u64>,
// Least significant digit first
bits: Vec<AllocatedBit>
}
impl NoteValue {
fn new<E, CS>(
mut cs: CS,
value: Option<u64>
) -> Result<NoteValue, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>,
{
let mut values;
match value {
Some(mut val) => {
values = vec![];
for _ in 0..64 {
values.push(Some(val & 1 == 1));
val >>= 1;
}
},
None => {
values = vec![None; 64];
}
}
let mut bits = vec![];
for (i, value) in values.into_iter().enumerate() {
bits.push(
AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
value
)?
);
}
Ok(NoteValue {
value: value,
bits: bits
})
}
/// Encodes the bits of the value into little-endian
/// byte order.
fn bits_le(&self) -> Vec<Boolean> {
self.bits.chunks(8)
.flat_map(|v| v.iter().rev())
.cloned()
.map(|e| Boolean::from(e))
.collect()
}
/// Computes this value as a linear combination of
/// its bits.
fn lc<E: Engine>(&self) -> LinearCombination<E> {
let mut tmp = LinearCombination::zero();
let mut coeff = E::Fr::one();
for b in &self.bits {
tmp = tmp + (coeff, b.get_variable());
coeff.double();
}
tmp
}
fn get_value(&self) -> Option<u64> {
self.value
}
}
/// Witnesses some bytes in the constraint system,
/// skipping the first `skip_bits`.
fn witness_bits<E, CS>(
mut cs: CS,
value: Option<&[u8]>,
num_bits: usize,
skip_bits: usize
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>,
{
let bit_values = if let Some(value) = value {
let mut tmp = vec![];
for b in value.iter()
.flat_map(|&m| (0..8).rev().map(move |i| m >> i & 1 == 1))
.skip(skip_bits)
{
tmp.push(Some(b));
}
tmp
} else {
vec![None; num_bits]
};
assert_eq!(bit_values.len(), num_bits);
let mut bits = vec![];
for (i, value) in bit_values.into_iter().enumerate() {
bits.push(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("bit {}", i)),
value
)?));
}
Ok(bits)
}
fn witness_u256<E, CS>(
cs: CS,
value: Option<&[u8]>,
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>,
{
witness_bits(cs, value, 256, 0)
}
fn witness_u252<E, CS>(
cs: CS,
value: Option<&[u8]>,
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>,
{
witness_bits(cs, value, 252, 4)
}
#[test]
fn test_sprout_constraints() {
use pairing::bls12_381::{Bls12};
use ::circuit::test::*;
use byteorder::{WriteBytesExt, ReadBytesExt, LittleEndian};
let test_vector = include_bytes!("test_vectors.dat");
let mut test_vector = &test_vector[..];
fn get_u256<R: ReadBytesExt>(mut reader: R) -> [u8; 32] {
let mut result = [0u8; 32];
for i in 0..32 {
result[i] = reader.read_u8().unwrap();
}
result
}
while test_vector.len() != 0 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let phi = Some(get_u256(&mut test_vector));
let rt = Some(get_u256(&mut test_vector));
let h_sig = Some(get_u256(&mut test_vector));
let mut inputs = vec![];
for _ in 0..2 {
test_vector.read_u8().unwrap();
let mut auth_path = [None; TREE_DEPTH];
for i in (0..TREE_DEPTH).rev() {
test_vector.read_u8().unwrap();
let sibling = get_u256(&mut test_vector);
auth_path[i] = Some((sibling, false));
}
let mut position = test_vector.read_u64::<LittleEndian>().unwrap();
for i in 0..TREE_DEPTH {
auth_path[i].as_mut().map(|p| {
p.1 = (position & 1) == 1
});
position >>= 1;
}
// a_pk
let _ = Some(SpendingKey(get_u256(&mut test_vector)));
let value = Some(test_vector.read_u64::<LittleEndian>().unwrap());
let rho = Some(UniqueRandomness(get_u256(&mut test_vector)));
let r = Some(CommitmentRandomness(get_u256(&mut test_vector)));
let a_sk = Some(SpendingKey(get_u256(&mut test_vector)));
inputs.push(
JSInput {
value: value,
a_sk: a_sk,
rho: rho,
r: r,
auth_path: auth_path
}
);
}
let mut outputs = vec![];
for _ in 0..2 {
let a_pk = Some(PayingKey(get_u256(&mut test_vector)));
let value = Some(test_vector.read_u64::<LittleEndian>().unwrap());
get_u256(&mut test_vector);
let r = Some(CommitmentRandomness(get_u256(&mut test_vector)));
outputs.push(
JSOutput {
value: value,
a_pk: a_pk,
r: r
}
);
}
let vpub_old = Some(test_vector.read_u64::<LittleEndian>().unwrap());
let vpub_new = Some(test_vector.read_u64::<LittleEndian>().unwrap());
let nf1 = get_u256(&mut test_vector);
let nf2 = get_u256(&mut test_vector);
let cm1 = get_u256(&mut test_vector);
let cm2 = get_u256(&mut test_vector);
let mac1 = get_u256(&mut test_vector);
let mac2 = get_u256(&mut test_vector);
let js = JoinSplit {
vpub_old: vpub_old,
vpub_new: vpub_new,
h_sig: h_sig,
phi: phi,
inputs: inputs,
outputs: outputs,
rt: rt
};
js.synthesize(&mut cs).unwrap();
if let Some(s) = cs.which_is_unsatisfied() {
panic!("{:?}", s);
}
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 1989085);
assert_eq!(cs.num_inputs(), 10);
assert_eq!(cs.hash(), "1a228d3c6377130d1778c7885811dc8b8864049cb5af8aff7e6cd46c5bc4b84c");
let mut expected_inputs = vec![];
expected_inputs.extend(rt.unwrap().to_vec());
expected_inputs.extend(h_sig.unwrap().to_vec());
expected_inputs.extend(nf1.to_vec());
expected_inputs.extend(mac1.to_vec());
expected_inputs.extend(nf2.to_vec());
expected_inputs.extend(mac2.to_vec());
expected_inputs.extend(cm1.to_vec());
expected_inputs.extend(cm2.to_vec());
expected_inputs.write_u64::<LittleEndian>(vpub_old.unwrap()).unwrap();
expected_inputs.write_u64::<LittleEndian>(vpub_new.unwrap()).unwrap();
use circuit::multipack;
let expected_inputs = multipack::bytes_to_bits(&expected_inputs);
let expected_inputs = multipack::compute_multipacking::<Bls12>(&expected_inputs);
assert!(cs.verify(&expected_inputs));
}
}

View File

@ -1,54 +0,0 @@
use pairing::{Engine};
use bellman::{ConstraintSystem, SynthesisError};
use circuit::boolean::{Boolean};
use super::*;
use super::prfs::*;
use super::commitment::note_comm;
pub struct OutputNote {
pub cm: Vec<Boolean>
}
impl OutputNote {
pub fn compute<'a, E, CS>(
mut cs: CS,
a_pk: Option<PayingKey>,
value: &NoteValue,
r: Option<CommitmentRandomness>,
phi: &[Boolean],
h_sig: &[Boolean],
nonce: bool
) -> Result<Self, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>,
{
let rho = prf_rho(
cs.namespace(|| "rho"),
phi,
h_sig,
nonce
)?;
let a_pk = witness_u256(
cs.namespace(|| "a_pk"),
a_pk.as_ref().map(|a_pk| &a_pk.0[..])
)?;
let r = witness_u256(
cs.namespace(|| "r"),
r.as_ref().map(|r| &r.0[..])
)?;
let cm = note_comm(
cs.namespace(|| "cm computation"),
&a_pk,
&value.bits_le(),
&rho,
&r
)?;
Ok(OutputNote {
cm: cm
})
}
}

View File

@ -1,79 +0,0 @@
use pairing::{Engine};
use bellman::{ConstraintSystem, SynthesisError};
use circuit::sha256::{
sha256_block_no_padding
};
use circuit::boolean::{
Boolean
};
fn prf<E, CS>(
cs: CS,
a: bool,
b: bool,
c: bool,
d: bool,
x: &[Boolean],
y: &[Boolean]
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
assert_eq!(x.len(), 252);
assert_eq!(y.len(), 256);
let mut image = vec![];
image.push(Boolean::constant(a));
image.push(Boolean::constant(b));
image.push(Boolean::constant(c));
image.push(Boolean::constant(d));
image.extend(x.iter().cloned());
image.extend(y.iter().cloned());
assert_eq!(image.len(), 512);
sha256_block_no_padding(
cs,
&image
)
}
pub fn prf_a_pk<E, CS>(
cs: CS,
a_sk: &[Boolean]
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
prf(cs, true, true, false, false, a_sk, &(0..256).map(|_| Boolean::constant(false)).collect::<Vec<_>>())
}
pub fn prf_nf<E, CS>(
cs: CS,
a_sk: &[Boolean],
rho: &[Boolean]
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
prf(cs, true, true, true, false, a_sk, rho)
}
pub fn prf_pk<E, CS>(
cs: CS,
a_sk: &[Boolean],
h_sig: &[Boolean],
nonce: bool
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
prf(cs, false, nonce, false, false, a_sk, h_sig)
}
pub fn prf_rho<E, CS>(
cs: CS,
phi: &[Boolean],
h_sig: &[Boolean],
nonce: bool
) -> Result<Vec<Boolean>, SynthesisError>
where E: Engine, CS: ConstraintSystem<E>
{
prf(cs, false, nonce, true, false, phi, h_sig)
}

Binary file not shown.

View File

@ -1,492 +0,0 @@
use pairing::{
Engine,
Field,
PrimeField,
PrimeFieldRepr
};
use bellman::{
LinearCombination,
SynthesisError,
ConstraintSystem,
Variable,
Index
};
use std::collections::HashMap;
use std::fmt::Write;
use byteorder::{BigEndian, ByteOrder};
use std::cmp::Ordering;
use std::collections::BTreeMap;
use blake2_rfc::blake2s::Blake2s;
#[derive(Debug)]
enum NamedObject {
Constraint(usize),
Var(Variable),
Namespace
}
/// Constraint system for testing purposes.
pub struct TestConstraintSystem<E: Engine> {
named_objects: HashMap<String, NamedObject>,
current_namespace: Vec<String>,
constraints: Vec<(
LinearCombination<E>,
LinearCombination<E>,
LinearCombination<E>,
String
)>,
inputs: Vec<(E::Fr, String)>,
aux: Vec<(E::Fr, String)>
}
#[derive(Clone, Copy)]
struct OrderedVariable(Variable);
impl Eq for OrderedVariable {}
impl PartialEq for OrderedVariable {
fn eq(&self, other: &OrderedVariable) -> bool {
match (self.0.get_unchecked(), other.0.get_unchecked()) {
(Index::Input(ref a), Index::Input(ref b)) => a == b,
(Index::Aux(ref a), Index::Aux(ref b)) => a == b,
_ => false
}
}
}
impl PartialOrd for OrderedVariable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OrderedVariable {
fn cmp(&self, other: &Self) -> Ordering {
match (self.0.get_unchecked(), other.0.get_unchecked()) {
(Index::Input(ref a), Index::Input(ref b)) => a.cmp(b),
(Index::Aux(ref a), Index::Aux(ref b)) => a.cmp(b),
(Index::Input(_), Index::Aux(_)) => Ordering::Less,
(Index::Aux(_), Index::Input(_)) => Ordering::Greater
}
}
}
fn proc_lc<E: Engine>(
terms: &[(Variable, E::Fr)],
) -> BTreeMap<OrderedVariable, E::Fr>
{
let mut map = BTreeMap::new();
for &(var, coeff) in terms {
map.entry(OrderedVariable(var))
.or_insert(E::Fr::zero())
.add_assign(&coeff);
}
// Remove terms that have a zero coefficient to normalize
let mut to_remove = vec![];
for (var, coeff) in map.iter() {
if coeff.is_zero() {
to_remove.push(var.clone())
}
}
for var in to_remove {
map.remove(&var);
}
map
}
fn hash_lc<E: Engine>(
terms: &[(Variable, E::Fr)],
h: &mut Blake2s
)
{
let map = proc_lc::<E>(terms);
let mut buf = [0u8; 9 + 32];
BigEndian::write_u64(&mut buf[0..8], map.len() as u64);
h.update(&buf[0..8]);
for (var, coeff) in map {
match var.0.get_unchecked() {
Index::Input(i) => {
buf[0] = b'I';
BigEndian::write_u64(&mut buf[1..9], i as u64);
},
Index::Aux(i) => {
buf[0] = b'A';
BigEndian::write_u64(&mut buf[1..9], i as u64);
}
}
coeff.into_repr().write_be(&mut buf[9..]).unwrap();
h.update(&buf);
}
}
fn eval_lc<E: Engine>(
terms: &[(Variable, E::Fr)],
inputs: &[(E::Fr, String)],
aux: &[(E::Fr, String)]
) -> E::Fr
{
let mut acc = E::Fr::zero();
for &(var, ref coeff) in terms {
let mut tmp = match var.get_unchecked() {
Index::Input(index) => inputs[index].0,
Index::Aux(index) => aux[index].0
};
tmp.mul_assign(&coeff);
acc.add_assign(&tmp);
}
acc
}
impl<E: Engine> TestConstraintSystem<E> {
pub fn new() -> TestConstraintSystem<E> {
let mut map = HashMap::new();
map.insert("ONE".into(), NamedObject::Var(TestConstraintSystem::<E>::one()));
TestConstraintSystem {
named_objects: map,
current_namespace: vec![],
constraints: vec![],
inputs: vec![(E::Fr::one(), "ONE".into())],
aux: vec![]
}
}
pub fn pretty_print(&self) -> String {
let mut s = String::new();
let negone = {
let mut tmp = E::Fr::one();
tmp.negate();
tmp
};
let powers_of_two = (0..E::Fr::NUM_BITS).map(|i| {
E::Fr::from_str("2").unwrap().pow(&[i as u64])
}).collect::<Vec<_>>();
let pp = |s: &mut String, lc: &LinearCombination<E>| {
write!(s, "(").unwrap();
let mut is_first = true;
for (var, coeff) in proc_lc::<E>(lc.as_ref()) {
if coeff == negone {
write!(s, " - ").unwrap();
} else if !is_first {
write!(s, " + ").unwrap();
}
is_first = false;
if coeff != E::Fr::one() && coeff != negone {
for (i, x) in powers_of_two.iter().enumerate() {
if x == &coeff {
write!(s, "2^{} . ", i).unwrap();
break;
}
}
write!(s, "{} . ", coeff).unwrap();
}
match var.0.get_unchecked() {
Index::Input(i) => {
write!(s, "`{}`", &self.inputs[i].1).unwrap();
},
Index::Aux(i) => {
write!(s, "`{}`", &self.aux[i].1).unwrap();
}
}
}
if is_first {
// Nothing was visited, print 0.
write!(s, "0").unwrap();
}
write!(s, ")").unwrap();
};
for &(ref a, ref b, ref c, ref name) in &self.constraints {
write!(&mut s, "\n").unwrap();
write!(&mut s, "{}: ", name).unwrap();
pp(&mut s, a);
write!(&mut s, " * ").unwrap();
pp(&mut s, b);
write!(&mut s, " = ").unwrap();
pp(&mut s, c);
}
write!(&mut s, "\n").unwrap();
s
}
pub fn hash(&self) -> String {
let mut h = Blake2s::new(32);
{
let mut buf = [0u8; 24];
BigEndian::write_u64(&mut buf[0..8], self.inputs.len() as u64);
BigEndian::write_u64(&mut buf[8..16], self.aux.len() as u64);
BigEndian::write_u64(&mut buf[16..24], self.constraints.len() as u64);
h.update(&buf);
}
for constraint in &self.constraints {
hash_lc::<E>(constraint.0.as_ref(), &mut h);
hash_lc::<E>(constraint.1.as_ref(), &mut h);
hash_lc::<E>(constraint.2.as_ref(), &mut h);
}
let mut s = String::new();
for b in h.finalize().as_ref() {
s += &format!("{:02x}", b);
}
s
}
pub fn which_is_unsatisfied(&self) -> Option<&str> {
for &(ref a, ref b, ref c, ref path) in &self.constraints {
let mut a = eval_lc::<E>(a.as_ref(), &self.inputs, &self.aux);
let b = eval_lc::<E>(b.as_ref(), &self.inputs, &self.aux);
let c = eval_lc::<E>(c.as_ref(), &self.inputs, &self.aux);
a.mul_assign(&b);
if a != c {
return Some(&*path)
}
}
None
}
pub fn is_satisfied(&self) -> bool
{
self.which_is_unsatisfied().is_none()
}
pub fn num_constraints(&self) -> usize
{
self.constraints.len()
}
pub fn set(&mut self, path: &str, to: E::Fr)
{
match self.named_objects.get(path) {
Some(&NamedObject::Var(ref v)) => {
match v.get_unchecked() {
Index::Input(index) => self.inputs[index].0 = to,
Index::Aux(index) => self.aux[index].0 = to
}
}
Some(e) => panic!("tried to set path `{}` to value, but `{:?}` already exists there.", path, e),
_ => panic!("no variable exists at path: {}", path)
}
}
pub fn verify(&self, expected: &[E::Fr]) -> bool
{
assert_eq!(expected.len() + 1, self.inputs.len());
for (a, b) in self.inputs.iter().skip(1).zip(expected.iter())
{
if &a.0 != b {
return false
}
}
return true;
}
pub fn num_inputs(&self) -> usize {
self.inputs.len()
}
pub fn get_input(&mut self, index: usize, path: &str) -> E::Fr
{
let (assignment, name) = self.inputs[index].clone();
assert_eq!(path, name);
assignment
}
pub fn get(&mut self, path: &str) -> E::Fr
{
match self.named_objects.get(path) {
Some(&NamedObject::Var(ref v)) => {
match v.get_unchecked() {
Index::Input(index) => self.inputs[index].0,
Index::Aux(index) => self.aux[index].0
}
}
Some(e) => panic!("tried to get value of path `{}`, but `{:?}` exists there (not a variable)", path, e),
_ => panic!("no variable exists at path: {}", path)
}
}
fn set_named_obj(&mut self, path: String, to: NamedObject) {
if self.named_objects.contains_key(&path) {
panic!("tried to create object at existing path: {}", path);
}
self.named_objects.insert(path, to);
}
}
fn compute_path(ns: &[String], this: String) -> String {
if this.chars().any(|a| a == '/') {
panic!("'/' is not allowed in names");
}
let mut name = String::new();
let mut needs_separation = false;
for ns in ns.iter().chain(Some(&this).into_iter())
{
if needs_separation {
name += "/";
}
name += ns;
needs_separation = true;
}
name
}
impl<E: Engine> ConstraintSystem<E> for TestConstraintSystem<E> {
type Root = Self;
fn alloc<F, A, AR>(
&mut self,
annotation: A,
f: F
) -> Result<Variable, SynthesisError>
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
{
let index = self.aux.len();
let path = compute_path(&self.current_namespace, annotation().into());
self.aux.push((f()?, path.clone()));
let var = Variable::new_unchecked(Index::Aux(index));
self.set_named_obj(path, NamedObject::Var(var));
Ok(var)
}
fn alloc_input<F, A, AR>(
&mut self,
annotation: A,
f: F
) -> Result<Variable, SynthesisError>
where F: FnOnce() -> Result<E::Fr, SynthesisError>, A: FnOnce() -> AR, AR: Into<String>
{
let index = self.inputs.len();
let path = compute_path(&self.current_namespace, annotation().into());
self.inputs.push((f()?, path.clone()));
let var = Variable::new_unchecked(Index::Input(index));
self.set_named_obj(path, NamedObject::Var(var));
Ok(var)
}
fn enforce<A, AR, LA, LB, LC>(
&mut self,
annotation: A,
a: LA,
b: LB,
c: LC
)
where A: FnOnce() -> AR, AR: Into<String>,
LA: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
LB: FnOnce(LinearCombination<E>) -> LinearCombination<E>,
LC: FnOnce(LinearCombination<E>) -> LinearCombination<E>
{
let path = compute_path(&self.current_namespace, annotation().into());
let index = self.constraints.len();
self.set_named_obj(path.clone(), NamedObject::Constraint(index));
let a = a(LinearCombination::zero());
let b = b(LinearCombination::zero());
let c = c(LinearCombination::zero());
self.constraints.push((a, b, c, path));
}
fn push_namespace<NR, N>(&mut self, name_fn: N)
where NR: Into<String>, N: FnOnce() -> NR
{
let name = name_fn().into();
let path = compute_path(&self.current_namespace, name.clone());
self.set_named_obj(path.clone(), NamedObject::Namespace);
self.current_namespace.push(name);
}
fn pop_namespace(&mut self)
{
assert!(self.current_namespace.pop().is_some());
}
fn get_root(&mut self) -> &mut Self::Root
{
self
}
}
#[test]
fn test_cs() {
use pairing::bls12_381::{Bls12, Fr};
use pairing::PrimeField;
let mut cs = TestConstraintSystem::<Bls12>::new();
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 0);
let a = cs.namespace(|| "a").alloc(|| "var", || Ok(Fr::from_str("10").unwrap())).unwrap();
let b = cs.namespace(|| "b").alloc(|| "var", || Ok(Fr::from_str("4").unwrap())).unwrap();
let c = cs.alloc(|| "product", || Ok(Fr::from_str("40").unwrap())).unwrap();
cs.enforce(
|| "mult",
|lc| lc + a,
|lc| lc + b,
|lc| lc + c
);
assert!(cs.is_satisfied());
assert_eq!(cs.num_constraints(), 1);
cs.set("a/var", Fr::from_str("4").unwrap());
let one = TestConstraintSystem::<Bls12>::one();
cs.enforce(
|| "eq",
|lc| lc + a,
|lc| lc + one,
|lc| lc + b
);
assert!(!cs.is_satisfied());
assert!(cs.which_is_unsatisfied() == Some("mult"));
assert!(cs.get("product") == Fr::from_str("40").unwrap());
cs.set("product", Fr::from_str("16").unwrap());
assert!(cs.is_satisfied());
{
let mut cs = cs.namespace(|| "test1");
let mut cs = cs.namespace(|| "test2");
cs.alloc(|| "hehe", || Ok(Fr::one())).unwrap();
}
assert!(cs.get("test1/test2/hehe") == Fr::one());
}

View File

@ -1,755 +0,0 @@
use pairing::{
Engine,
Field,
PrimeField
};
use bellman::{
SynthesisError,
ConstraintSystem,
LinearCombination
};
use super::boolean::{
Boolean,
AllocatedBit
};
use super::multieq::MultiEq;
/// Represents an interpretation of 32 `Boolean` objects as an
/// unsigned integer.
#[derive(Clone)]
pub struct UInt32 {
// Least significant bit first
bits: Vec<Boolean>,
value: Option<u32>
}
impl UInt32 {
/// Construct a constant `UInt32` from a `u32`
pub fn constant(value: u32) -> Self
{
let mut bits = Vec::with_capacity(32);
let mut tmp = value;
for _ in 0..32 {
if tmp & 1 == 1 {
bits.push(Boolean::constant(true))
} else {
bits.push(Boolean::constant(false))
}
tmp >>= 1;
}
UInt32 {
bits: bits,
value: Some(value)
}
}
/// Allocate a `UInt32` in the constraint system
pub fn alloc<E, CS>(
mut cs: CS,
value: Option<u32>
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
let values = match value {
Some(mut val) => {
let mut v = Vec::with_capacity(32);
for _ in 0..32 {
v.push(Some(val & 1 == 1));
val >>= 1;
}
v
},
None => vec![None; 32]
};
let bits = values.into_iter()
.enumerate()
.map(|(i, v)| {
Ok(Boolean::from(AllocatedBit::alloc(
cs.namespace(|| format!("allocated bit {}", i)),
v
)?))
})
.collect::<Result<Vec<_>, SynthesisError>>()?;
Ok(UInt32 {
bits: bits,
value: value
})
}
pub fn into_bits_be(&self) -> Vec<Boolean> {
self.bits.iter().rev().cloned().collect()
}
pub fn from_bits_be(bits: &[Boolean]) -> Self {
assert_eq!(bits.len(), 32);
let mut value = Some(0u32);
for b in bits {
value.as_mut().map(|v| *v <<= 1);
match b.get_value() {
Some(true) => { value.as_mut().map(|v| *v |= 1); },
Some(false) => {},
None => { value = None; }
}
}
UInt32 {
value: value,
bits: bits.iter().rev().cloned().collect()
}
}
/// Turns this `UInt32` into its little-endian byte order representation.
pub fn into_bits(&self) -> Vec<Boolean> {
self.bits.clone()
}
/// Converts a little-endian byte order representation of bits into a
/// `UInt32`.
pub fn from_bits(bits: &[Boolean]) -> Self
{
assert_eq!(bits.len(), 32);
let new_bits = bits.to_vec();
let mut value = Some(0u32);
for b in new_bits.iter().rev() {
value.as_mut().map(|v| *v <<= 1);
match b {
&Boolean::Constant(b) => {
if b {
value.as_mut().map(|v| *v |= 1);
}
},
&Boolean::Is(ref b) => {
match b.get_value() {
Some(true) => { value.as_mut().map(|v| *v |= 1); },
Some(false) => {},
None => { value = None }
}
},
&Boolean::Not(ref b) => {
match b.get_value() {
Some(false) => { value.as_mut().map(|v| *v |= 1); },
Some(true) => {},
None => { value = None }
}
}
}
}
UInt32 {
value: value,
bits: new_bits
}
}
pub fn rotr(&self, by: usize) -> Self {
let by = by % 32;
let new_bits = self.bits.iter()
.skip(by)
.chain(self.bits.iter())
.take(32)
.cloned()
.collect();
UInt32 {
bits: new_bits,
value: self.value.map(|v| v.rotate_right(by as u32))
}
}
pub fn shr(&self, by: usize) -> Self {
let by = by % 32;
let fill = Boolean::constant(false);
let new_bits = self.bits
.iter() // The bits are least significant first
.skip(by) // Skip the bits that will be lost during the shift
.chain(Some(&fill).into_iter().cycle()) // Rest will be zeros
.take(32) // Only 32 bits needed!
.cloned()
.collect();
UInt32 {
bits: new_bits,
value: self.value.map(|v| v >> by as u32)
}
}
fn triop<E, CS, F, U>(
mut cs: CS,
a: &Self,
b: &Self,
c: &Self,
tri_fn: F,
circuit_fn: U
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>,
F: Fn(u32, u32, u32) -> u32,
U: Fn(&mut CS, usize, &Boolean, &Boolean, &Boolean) -> Result<Boolean, SynthesisError>
{
let new_value = match (a.value, b.value, c.value) {
(Some(a), Some(b), Some(c)) => {
Some(tri_fn(a, b, c))
},
_ => None
};
let bits = a.bits.iter()
.zip(b.bits.iter())
.zip(c.bits.iter())
.enumerate()
.map(|(i, ((a, b), c))| circuit_fn(&mut cs, i, a, b, c))
.collect::<Result<_, _>>()?;
Ok(UInt32 {
bits: bits,
value: new_value
})
}
/// Compute the `maj` value (a and b) xor (a and c) xor (b and c)
/// during SHA256.
pub fn sha256_maj<E, CS>(
cs: CS,
a: &Self,
b: &Self,
c: &Self
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ (a & c) ^ (b & c),
|cs, i, a, b, c| {
Boolean::sha256_maj(
cs.namespace(|| format!("maj {}", i)),
a,
b,
c
)
}
)
}
/// Compute the `ch` value `(a and b) xor ((not a) and c)`
/// during SHA256.
pub fn sha256_ch<E, CS>(
cs: CS,
a: &Self,
b: &Self,
c: &Self
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
Self::triop(cs, a, b, c, |a, b, c| (a & b) ^ ((!a) & c),
|cs, i, a, b, c| {
Boolean::sha256_ch(
cs.namespace(|| format!("ch {}", i)),
a,
b,
c
)
}
)
}
/// XOR this `UInt32` with another `UInt32`
pub fn xor<E, CS>(
&self,
mut cs: CS,
other: &Self
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>
{
let new_value = match (self.value, other.value) {
(Some(a), Some(b)) => {
Some(a ^ b)
},
_ => None
};
let bits = self.bits.iter()
.zip(other.bits.iter())
.enumerate()
.map(|(i, (a, b))| {
Boolean::xor(
cs.namespace(|| format!("xor of bit {}", i)),
a,
b
)
})
.collect::<Result<_, _>>()?;
Ok(UInt32 {
bits: bits,
value: new_value
})
}
/// Perform modular addition of several `UInt32` objects.
pub fn addmany<E, CS, M>(
mut cs: M,
operands: &[Self]
) -> Result<Self, SynthesisError>
where E: Engine,
CS: ConstraintSystem<E>,
M: ConstraintSystem<E, Root=MultiEq<E, CS>>
{
// Make some arbitrary bounds for ourselves to avoid overflows
// in the scalar field
assert!(E::Fr::NUM_BITS >= 64);
assert!(operands.len() >= 2); // Weird trivial cases that should never happen
assert!(operands.len() <= 10);
// Compute the maximum value of the sum so we allocate enough bits for
// the result
let mut max_value = (operands.len() as u64) * (u32::max_value() as u64);
// Keep track of the resulting value
let mut result_value = Some(0u64);
// This is a linear combination that we will enforce to equal the
// output
let mut lc = LinearCombination::zero();
let mut all_constants = true;
// Iterate over the operands
for op in operands {
// Accumulate the value
match op.value {
Some(val) => {
result_value.as_mut().map(|v| *v += val as u64);
},
None => {
// If any of our operands have unknown value, we won't
// know the value of the result
result_value = None;
}
}
// Iterate over each bit of the operand and add the operand to
// the linear combination
let mut coeff = E::Fr::one();
for bit in &op.bits {
lc = lc + &bit.lc(CS::one(), coeff);
all_constants &= bit.is_constant();
coeff.double();
}
}
// The value of the actual result is modulo 2^32
let modular_value = result_value.map(|v| v as u32);
if all_constants && modular_value.is_some() {
// We can just return a constant, rather than
// unpacking the result into allocated bits.
return Ok(UInt32::constant(modular_value.unwrap()));
}
// Storage area for the resulting bits
let mut result_bits = vec![];
// Linear combination representing the output,
// for comparison with the sum of the operands
let mut result_lc = LinearCombination::zero();
// Allocate each bit of the result
let mut coeff = E::Fr::one();
let mut i = 0;
while max_value != 0 {
// Allocate the bit
let b = AllocatedBit::alloc(
cs.namespace(|| format!("result bit {}", i)),
result_value.map(|v| (v >> i) & 1 == 1)
)?;
// Add this bit to the result combination
result_lc = result_lc + (coeff, b.get_variable());
result_bits.push(b.into());
max_value >>= 1;
i += 1;
coeff.double();
}
// Enforce equality between the sum and result
cs.get_root().enforce_equal(i, &lc, &result_lc);
// Discard carry bits that we don't care about
result_bits.truncate(32);
Ok(UInt32 {
bits: result_bits,
value: modular_value
})
}
}
#[cfg(test)]
mod test {
use rand::{XorShiftRng, SeedableRng, Rng};
use ::circuit::boolean::{Boolean};
use super::{UInt32};
use pairing::bls12_381::{Bls12};
use pairing::{Field};
use ::circuit::test::*;
use bellman::{ConstraintSystem};
use circuit::multieq::MultiEq;
#[test]
fn test_uint32_from_bits_be() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
for _ in 0..1000 {
let mut v = (0..32).map(|_| Boolean::constant(rng.gen())).collect::<Vec<_>>();
let b = UInt32::from_bits_be(&v);
for (i, bit) in b.bits.iter().enumerate() {
match bit {
&Boolean::Constant(bit) => {
assert!(bit == ((b.value.unwrap() >> i) & 1 == 1));
},
_ => unreachable!()
}
}
let expected_to_be_same = b.into_bits_be();
for x in v.iter().zip(expected_to_be_same.iter())
{
match x {
(&Boolean::Constant(true), &Boolean::Constant(true)) => {},
(&Boolean::Constant(false), &Boolean::Constant(false)) => {},
_ => unreachable!()
}
}
}
}
#[test]
fn test_uint32_from_bits() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
for _ in 0..1000 {
let mut v = (0..32).map(|_| Boolean::constant(rng.gen())).collect::<Vec<_>>();
let b = UInt32::from_bits(&v);
for (i, bit) in b.bits.iter().enumerate() {
match bit {
&Boolean::Constant(bit) => {
assert!(bit == ((b.value.unwrap() >> i) & 1 == 1));
},
_ => unreachable!()
}
}
let expected_to_be_same = b.into_bits();
for x in v.iter().zip(expected_to_be_same.iter())
{
match x {
(&Boolean::Constant(true), &Boolean::Constant(true)) => {},
(&Boolean::Constant(false), &Boolean::Constant(false)) => {},
_ => unreachable!()
}
}
}
}
#[test]
fn test_uint32_xor() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
for _ in 0..1000 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a: u32 = rng.gen();
let b: u32 = rng.gen();
let c: u32 = rng.gen();
let mut expected = a ^ b ^ c;
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
let b_bit = UInt32::constant(b);
let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap();
let r = a_bit.xor(cs.namespace(|| "first xor"), &b_bit).unwrap();
let r = r.xor(cs.namespace(|| "second xor"), &c_bit).unwrap();
assert!(cs.is_satisfied());
assert!(r.value == Some(expected));
for b in r.bits.iter() {
match b {
&Boolean::Is(ref b) => {
assert!(b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Not(ref b) => {
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Constant(b) => {
assert!(b == (expected & 1 == 1));
}
}
expected >>= 1;
}
}
}
#[test]
fn test_uint32_addmany_constants() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a: u32 = rng.gen();
let b: u32 = rng.gen();
let c: u32 = rng.gen();
let a_bit = UInt32::constant(a);
let b_bit = UInt32::constant(b);
let c_bit = UInt32::constant(c);
let mut expected = a.wrapping_add(b).wrapping_add(c);
let r = {
let mut cs = MultiEq::new(&mut cs);
let r = UInt32::addmany(cs.namespace(|| "addition"), &[a_bit, b_bit, c_bit]).unwrap();
r
};
assert!(r.value == Some(expected));
for b in r.bits.iter() {
match b {
&Boolean::Is(_) => panic!(),
&Boolean::Not(_) => panic!(),
&Boolean::Constant(b) => {
assert!(b == (expected & 1 == 1));
}
}
expected >>= 1;
}
}
}
#[test]
fn test_uint32_addmany() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a: u32 = rng.gen();
let b: u32 = rng.gen();
let c: u32 = rng.gen();
let d: u32 = rng.gen();
let mut expected = (a ^ b).wrapping_add(c).wrapping_add(d);
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
let b_bit = UInt32::constant(b);
let c_bit = UInt32::constant(c);
let d_bit = UInt32::alloc(cs.namespace(|| "d_bit"), Some(d)).unwrap();
let r = a_bit.xor(cs.namespace(|| "xor"), &b_bit).unwrap();
let r = {
let mut cs = MultiEq::new(&mut cs);
let r = UInt32::addmany(cs.namespace(|| "addition"), &[r, c_bit, d_bit]).unwrap();
r
};
assert!(cs.is_satisfied());
assert!(r.value == Some(expected));
for b in r.bits.iter() {
match b {
&Boolean::Is(ref b) => {
assert!(b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Not(ref b) => {
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Constant(_) => {
unreachable!()
}
}
expected >>= 1;
}
// Flip a bit and see if the addition constraint still works
if cs.get("addition/result bit 0/boolean").is_zero() {
cs.set("addition/result bit 0/boolean", Field::one());
} else {
cs.set("addition/result bit 0/boolean", Field::zero());
}
assert!(!cs.is_satisfied());
}
}
#[test]
fn test_uint32_rotr() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let mut num = rng.gen();
let a = UInt32::constant(num);
for i in 0..32 {
let b = a.rotr(i);
assert_eq!(a.bits.len(), b.bits.len());
assert!(b.value.unwrap() == num);
let mut tmp = num;
for b in &b.bits {
match b {
&Boolean::Constant(b) => {
assert_eq!(b, tmp & 1 == 1);
},
_ => unreachable!()
}
tmp >>= 1;
}
num = num.rotate_right(1);
}
}
#[test]
fn test_uint32_shr() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..50 {
for i in 0..60 {
let num = rng.gen();
let a = UInt32::constant(num).shr(i);
let b = UInt32::constant(num >> i);
assert_eq!(a.value.unwrap(), num >> i);
assert_eq!(a.bits.len(), b.bits.len());
for (a, b) in a.bits.iter().zip(b.bits.iter()) {
assert_eq!(a.get_value().unwrap(), b.get_value().unwrap());
}
}
}
}
#[test]
fn test_uint32_sha256_maj() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
for _ in 0..1000 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a: u32 = rng.gen();
let b: u32 = rng.gen();
let c: u32 = rng.gen();
let mut expected = (a & b) ^ (a & c) ^ (b & c);
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
let b_bit = UInt32::constant(b);
let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap();
let r = UInt32::sha256_maj(&mut cs, &a_bit, &b_bit, &c_bit).unwrap();
assert!(cs.is_satisfied());
assert!(r.value == Some(expected));
for b in r.bits.iter() {
match b {
&Boolean::Is(ref b) => {
assert!(b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Not(ref b) => {
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Constant(b) => {
assert!(b == (expected & 1 == 1));
}
}
expected >>= 1;
}
}
}
#[test]
fn test_uint32_sha256_ch() {
let mut rng = XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0653]);
for _ in 0..1000 {
let mut cs = TestConstraintSystem::<Bls12>::new();
let a: u32 = rng.gen();
let b: u32 = rng.gen();
let c: u32 = rng.gen();
let mut expected = (a & b) ^ ((!a) & c);
let a_bit = UInt32::alloc(cs.namespace(|| "a_bit"), Some(a)).unwrap();
let b_bit = UInt32::constant(b);
let c_bit = UInt32::alloc(cs.namespace(|| "c_bit"), Some(c)).unwrap();
let r = UInt32::sha256_ch(&mut cs, &a_bit, &b_bit, &c_bit).unwrap();
assert!(cs.is_satisfied());
assert!(r.value == Some(expected));
for b in r.bits.iter() {
match b {
&Boolean::Is(ref b) => {
assert!(b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Not(ref b) => {
assert!(!b.get_value().unwrap() == (expected & 1 == 1));
},
&Boolean::Constant(b) => {
assert!(b == (expected & 1 == 1));
}
}
expected >>= 1;
}
}
}
}

View File

@ -1,40 +1,436 @@
//! Various constants used by the Sapling protocol.
use ff::PrimeField;
use group::Group;
use jubjub::SubgroupPoint;
use lazy_static::lazy_static;
/// First 64 bytes of the BLAKE2s input during group hash.
/// This is chosen to be some random string that we couldn't have anticipated when we designed
/// the algorithm, for rigidity purposes.
/// We deliberately use an ASCII hex string of 32 bytes here.
pub const GH_FIRST_BLOCK: &'static [u8; 64]
= b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0";
pub const GH_FIRST_BLOCK: &[u8; 64] =
b"096b36a5804bfacef1691e173c366a47ff5ba84a44f26ddd7e8d9f79d5b42df0";
// BLAKE2s invocation personalizations
/// BLAKE2s Personalization for CRH^ivk = BLAKE2s(ak | nk)
pub const CRH_IVK_PERSONALIZATION: &'static [u8; 8]
= b"Zcashivk";
pub const CRH_IVK_PERSONALIZATION: &[u8; 8] = b"Zcashivk";
/// BLAKE2s Personalization for PRF^nf = BLAKE2s(nk | rho)
pub const PRF_NF_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_nf";
pub const PRF_NF_PERSONALIZATION: &[u8; 8] = b"Zcash_nf";
// Group hash personalizations
/// BLAKE2s Personalization for Pedersen hash generators.
pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_PH";
pub const PEDERSEN_HASH_GENERATORS_PERSONALIZATION: &[u8; 8] = b"Zcash_PH";
/// BLAKE2s Personalization for the group hash for key diversification
pub const KEY_DIVERSIFICATION_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_gd";
pub const KEY_DIVERSIFICATION_PERSONALIZATION: &[u8; 8] = b"Zcash_gd";
/// BLAKE2s Personalization for the spending key base point
pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_G_";
pub const SPENDING_KEY_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_G_";
/// BLAKE2s Personalization for the proof generation key base point
pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_H_";
pub const PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_H_";
/// BLAKE2s Personalization for the value commitment generator for the value
pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_cv";
pub const VALUE_COMMITMENT_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_cv";
/// BLAKE2s Personalization for the nullifier position generator (for computing rho)
pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &'static [u8; 8]
= b"Zcash_J_";
pub const NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION: &[u8; 8] = b"Zcash_J_";
/// The prover will demonstrate knowledge of discrete log with respect to this base when
/// they are constructing a proof, in order to authorize proof construction.
pub const PROOF_GENERATION_KEY_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x3af2_dbef_b96e_2571,
0xadf2_d038_f2fb_b820,
0x7043_03f1_e890_6081,
0x1457_a502_31cd_e2df,
]),
bls12_381::Scalar::from_raw([
0x467a_f9f7_e05d_e8e7,
0x50df_51ea_f5a1_49d2,
0xdec9_0184_0f49_48cc,
0x54b6_d107_18df_2a7a,
]),
);
/// The note commitment is randomized over this generator.
pub const NOTE_COMMITMENT_RANDOMNESS_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0xa514_3b34_a8e3_6462,
0xf091_9d06_ffb1_ecda,
0xa140_9aa1_f33b_ec2c,
0x26eb_9f8a_9ec7_2a8c,
]),
bls12_381::Scalar::from_raw([
0xd4fc_6365_796c_77ac,
0x96b7_8bea_fa9c_c44c,
0x949d_7747_6e26_2c95,
0x114b_7501_ad10_4c57,
]),
);
/// The node commitment is randomized again by the position in order to supply the
/// nullifier computation with a unique input w.r.t. the note being spent, to prevent
/// Faerie gold attacks.
pub const NULLIFIER_POSITION_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x2ce3_3921_888d_30db,
0xe81c_ee09_a561_229e,
0xdb56_b6db_8d80_75ed,
0x2400_c2e2_e336_2644,
]),
bls12_381::Scalar::from_raw([
0xa3f7_fa36_c72b_0065,
0xe155_b8e8_ffff_2e42,
0xfc9e_8a15_a096_ba8f,
0x6136_9d54_40bf_84a5,
]),
);
/// The value commitment is used to check balance between inputs and outputs. The value is
/// placed over this generator.
pub const VALUE_COMMITMENT_VALUE_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x3618_3b2c_b4d7_ef51,
0x9472_c89a_c043_042d,
0xd861_8ed1_d15f_ef4e,
0x273f_910d_9ecc_1615,
]),
bls12_381::Scalar::from_raw([
0xa77a_81f5_0667_c8d7,
0xbc33_32d0_fa1c_cd18,
0xd322_94fd_8977_4ad6,
0x466a_7e3a_82f6_7ab1,
]),
);
/// The value commitment is randomized over this generator, for privacy.
pub const VALUE_COMMITMENT_RANDOMNESS_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x3bce_3b77_9366_4337,
0xd1d8_da41_af03_744e,
0x7ff6_826a_d580_04b4,
0x6800_f4fa_0f00_1cfc,
]),
bls12_381::Scalar::from_raw([
0x3cae_fab9_380b_6a8b,
0xad46_f1b0_473b_803b,
0xe6fb_2a6e_1e22_ab50,
0x6d81_d3a9_cb45_dedb,
]),
);
/// The spender proves discrete log with respect to this base at spend time.
pub const SPENDING_KEY_GENERATOR: SubgroupPoint = SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x47bf_4692_0a95_a753,
0xd5b9_a7d3_ef8e_2827,
0xd418_a7ff_2675_3b6a,
0x0926_d4f3_2059_c712,
]),
bls12_381::Scalar::from_raw([
0x3056_32ad_aaf2_b530,
0x6d65_674d_cedb_ddbc,
0x53bb_37d0_c21c_fd05,
0x57a1_019e_6de9_b675,
]),
);
/// The generators (for each segment) used in all Pedersen commitments.
pub const PEDERSEN_HASH_GENERATORS: &[SubgroupPoint] = &[
SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x194e_4292_6f66_1b51,
0x2f0c_718f_6f0f_badd,
0xb5ea_25de_7ec0_e378,
0x73c0_16a4_2ded_9578,
]),
bls12_381::Scalar::from_raw([
0x77bf_abd4_3224_3cca,
0xf947_2e8b_c04e_4632,
0x79c9_166b_837e_dc5e,
0x289e_87a2_d352_1b57,
]),
),
SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0xb981_9dc8_2d90_607e,
0xa361_ee3f_d48f_df77,
0x52a3_5a8c_1908_dd87,
0x15a3_6d1f_0f39_0d88,
]),
bls12_381::Scalar::from_raw([
0x7b0d_c53c_4ebf_1891,
0x1f3a_beeb_98fa_d3e8,
0xf789_1142_c001_d925,
0x015d_8c7f_5b43_fe33,
]),
),
SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x76d6_f7c2_b67f_c475,
0xbae8_e5c4_6641_ae5c,
0xeb69_ae39_f5c8_4210,
0x6643_21a5_8246_e2f6,
]),
bls12_381::Scalar::from_raw([
0x80ed_502c_9793_d457,
0x8bb2_2a7f_1784_b498,
0xe000_a46c_8e8c_e853,
0x362e_1500_d24e_ee9e,
]),
),
SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x4c76_7804_c1c4_a2cc,
0x7d02_d50e_654b_87f2,
0xedc5_f4a9_cff2_9fd5,
0x323a_6548_ce9d_9876,
]),
bls12_381::Scalar::from_raw([
0x8471_4bec_a335_70e9,
0x5103_afa1_a11f_6a85,
0x9107_0acb_d8d9_47b7,
0x2f7e_e40c_4b56_cad8,
]),
),
SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0x4680_9430_657f_82d1,
0xefd5_9313_05f2_f0bf,
0x89b6_4b4e_0336_2796,
0x3bd2_6660_00b5_4796,
]),
bls12_381::Scalar::from_raw([
0x9996_8299_c365_8aef,
0xb3b9_d809_5859_d14c,
0x3978_3238_1406_c9e5,
0x494b_c521_03ab_9d0a,
]),
),
SubgroupPoint::from_raw_unchecked(
bls12_381::Scalar::from_raw([
0xcb3c_0232_58d3_2079,
0x1d9e_5ca2_1135_ff6f,
0xda04_9746_d76d_3ee5,
0x6344_7b2b_a31b_b28a,
]),
bls12_381::Scalar::from_raw([
0x4360_8211_9f8d_629a,
0xa802_00d2_c66b_13a7,
0x64cd_b107_0a13_6a28,
0x64ec_4689_e8bf_b6e5,
]),
),
];
/// The maximum number of chunks per segment of the Pedersen hash.
pub const PEDERSEN_HASH_CHUNKS_PER_GENERATOR: usize = 63;
/// The window size for exponentiation of Pedersen hash generators outside the circuit.
pub const PEDERSEN_HASH_EXP_WINDOW_SIZE: u32 = 8;
lazy_static! {
/// The exp table for [`PEDERSEN_HASH_GENERATORS`].
pub static ref PEDERSEN_HASH_EXP_TABLE: Vec<Vec<Vec<SubgroupPoint>>> =
generate_pedersen_hash_exp_table();
}
/// Creates the exp table for the Pedersen hash generators.
fn generate_pedersen_hash_exp_table() -> Vec<Vec<Vec<SubgroupPoint>>> {
let window = PEDERSEN_HASH_EXP_WINDOW_SIZE;
PEDERSEN_HASH_GENERATORS
.iter()
.cloned()
.map(|mut g| {
let mut tables = vec![];
let mut num_bits = 0;
while num_bits <= jubjub::Fr::NUM_BITS {
let mut table = Vec::with_capacity(1 << window);
let mut base = SubgroupPoint::identity();
for _ in 0..(1 << window) {
table.push(base);
base += g;
}
tables.push(table);
num_bits += window;
for _ in 0..window {
g = g.double();
}
}
tables
})
.collect()
}
#[cfg(test)]
mod tests {
use jubjub::SubgroupPoint;
use super::*;
use crate::group_hash::group_hash;
fn find_group_hash(m: &[u8], personalization: &[u8; 8]) -> SubgroupPoint {
let mut tag = m.to_vec();
let i = tag.len();
tag.push(0u8);
loop {
let gh = group_hash(&tag, personalization);
// We don't want to overflow and start reusing generators
assert!(tag[i] != u8::max_value());
tag[i] += 1;
if let Some(gh) = gh {
break gh;
}
}
}
#[test]
fn proof_generation_key_base_generator() {
assert_eq!(
find_group_hash(&[], PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION),
PROOF_GENERATION_KEY_GENERATOR,
);
}
#[test]
fn note_commitment_randomness_generator() {
assert_eq!(
find_group_hash(b"r", PEDERSEN_HASH_GENERATORS_PERSONALIZATION),
NOTE_COMMITMENT_RANDOMNESS_GENERATOR,
);
}
#[test]
fn nullifier_position_generator() {
assert_eq!(
find_group_hash(&[], NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION),
NULLIFIER_POSITION_GENERATOR,
);
}
#[test]
fn value_commitment_value_generator() {
assert_eq!(
find_group_hash(b"v", VALUE_COMMITMENT_GENERATOR_PERSONALIZATION),
VALUE_COMMITMENT_VALUE_GENERATOR,
);
}
#[test]
fn value_commitment_randomness_generator() {
assert_eq!(
find_group_hash(b"r", VALUE_COMMITMENT_GENERATOR_PERSONALIZATION),
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
);
}
#[test]
fn spending_key_generator() {
assert_eq!(
find_group_hash(&[], SPENDING_KEY_GENERATOR_PERSONALIZATION),
SPENDING_KEY_GENERATOR,
);
}
#[test]
fn pedersen_hash_generators() {
for (m, actual) in PEDERSEN_HASH_GENERATORS.iter().enumerate() {
assert_eq!(
&find_group_hash(
&(m as u32).to_le_bytes(),
PEDERSEN_HASH_GENERATORS_PERSONALIZATION
),
actual
);
}
}
#[test]
fn no_duplicate_fixed_base_generators() {
let fixed_base_generators = [
PROOF_GENERATION_KEY_GENERATOR,
NOTE_COMMITMENT_RANDOMNESS_GENERATOR,
NULLIFIER_POSITION_GENERATOR,
VALUE_COMMITMENT_VALUE_GENERATOR,
VALUE_COMMITMENT_RANDOMNESS_GENERATOR,
SPENDING_KEY_GENERATOR,
];
// Check for duplicates, far worse than spec inconsistencies!
for (i, p1) in fixed_base_generators.iter().enumerate() {
if p1.is_identity().into() {
panic!("Neutral element!");
}
for p2 in fixed_base_generators.iter().skip(i + 1) {
if p1 == p2 {
panic!("Duplicate generator!");
}
}
}
}
/// Check for simple relations between the generators, that make finding collisions easy;
/// far worse than spec inconsistencies!
fn check_consistency_of_pedersen_hash_generators(
pedersen_hash_generators: &[jubjub::SubgroupPoint],
) {
for (i, p1) in pedersen_hash_generators.iter().enumerate() {
if p1.is_identity().into() {
panic!("Neutral element!");
}
for p2 in pedersen_hash_generators.iter().skip(i + 1) {
if p1 == p2 {
panic!("Duplicate generator!");
}
if *p1 == -p2 {
panic!("Inverse generator!");
}
}
// check for a generator being the sum of any other two
for (j, p2) in pedersen_hash_generators.iter().enumerate() {
if j == i {
continue;
}
for (k, p3) in pedersen_hash_generators.iter().enumerate() {
if k == j || k == i {
continue;
}
let sum = p2 + p3;
if sum == *p1 {
panic!("Linear relation between generators!");
}
}
}
}
}
#[test]
fn pedersen_hash_generators_consistency() {
check_consistency_of_pedersen_hash_generators(PEDERSEN_HASH_GENERATORS);
}
#[test]
#[should_panic(expected = "Linear relation between generators!")]
fn test_jubjub_bls12_pedersen_hash_generators_consistency_check_linear_relation() {
let mut pedersen_hash_generators = PEDERSEN_HASH_GENERATORS.to_vec();
// Test for linear relation
pedersen_hash_generators.push(PEDERSEN_HASH_GENERATORS[0] + PEDERSEN_HASH_GENERATORS[1]);
check_consistency_of_pedersen_hash_generators(&pedersen_hash_generators);
}
}

View File

@ -1,46 +1,43 @@
use jubjub::{
JubjubEngine,
PrimeOrder,
edwards
};
//! Implementation of [group hashing into Jubjub][grouphash].
//!
//! [grouphash]: https://zips.z.cash/protocol/protocol.pdf#concretegrouphashjubjub
use pairing::{
PrimeField
};
use ff::PrimeField;
use group::{cofactor::CofactorGroup, Group, GroupEncoding};
use blake2_rfc::blake2s::Blake2s;
use constants;
use super::constants;
use blake2s_simd::Params;
/// Produces a random point in the Jubjub curve.
/// The point is guaranteed to be prime order
/// and not the identity.
pub fn group_hash<E: JubjubEngine>(
tag: &[u8],
personalization: &[u8],
params: &E::Params
) -> Option<edwards::Point<E, PrimeOrder>>
{
#[allow(clippy::assertions_on_constants)]
pub fn group_hash(tag: &[u8], personalization: &[u8]) -> Option<jubjub::SubgroupPoint> {
assert_eq!(personalization.len(), 8);
// Check to see that scalar field is 255 bits
assert!(E::Fr::NUM_BITS == 255);
assert!(bls12_381::Scalar::NUM_BITS == 255);
let mut h = Blake2s::with_params(32, &[], &[], personalization);
h.update(constants::GH_FIRST_BLOCK);
h.update(tag);
let h = h.finalize().as_ref().to_vec();
assert!(h.len() == 32);
let h = Params::new()
.hash_length(32)
.personal(personalization)
.to_state()
.update(constants::GH_FIRST_BLOCK)
.update(tag)
.finalize();
match edwards::Point::<E, _>::read(&h[..], params) {
Ok(p) => {
let p = p.mul_by_cofactor(params);
let p = jubjub::ExtendedPoint::from_bytes(h.as_array());
if p.is_some().into() {
// <ExtendedPoint as CofactorGroup>::clear_cofactor is implemented using
// ExtendedPoint::mul_by_cofactor in the jubjub crate.
let p = CofactorGroup::clear_cofactor(&p.unwrap());
if p != edwards::Point::zero() {
Some(p)
} else {
None
}
},
Err(_) => None
if p.is_identity().into() {
None
} else {
Some(p)
}
} else {
None
}
}

View File

@ -1,523 +0,0 @@
use pairing::{
Field,
SqrtField,
PrimeField,
PrimeFieldRepr,
BitIterator
};
use super::{
JubjubEngine,
JubjubParams,
Unknown,
PrimeOrder,
montgomery
};
use rand::{
Rng
};
use std::marker::PhantomData;
use std::io::{
self,
Write,
Read
};
// Represents the affine point (X/Z, Y/Z) via the extended
// twisted Edwards coordinates.
//
// See "Twisted Edwards Curves Revisited"
// Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson
pub struct Point<E: JubjubEngine, Subgroup> {
x: E::Fr,
y: E::Fr,
t: E::Fr,
z: E::Fr,
_marker: PhantomData<Subgroup>
}
fn convert_subgroup<E: JubjubEngine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2>
{
Point {
x: from.x,
y: from.y,
t: from.t,
z: from.z,
_marker: PhantomData
}
}
impl<E: JubjubEngine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
{
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
{
convert_subgroup(&p)
}
}
impl<E: JubjubEngine, Subgroup> Clone for Point<E, Subgroup>
{
fn clone(&self) -> Self {
convert_subgroup(self)
}
}
impl<E: JubjubEngine, Subgroup> PartialEq for Point<E, Subgroup> {
fn eq(&self, other: &Point<E, Subgroup>) -> bool {
// p1 = (x1/z1, y1/z1)
// p2 = (x2/z2, y2/z2)
// Deciding that these two points are equal is a matter of
// determining that x1/z1 = x2/z2, or equivalently that
// x1*z2 = x2*z1, and similarly for y.
let mut x1 = self.x;
x1.mul_assign(&other.z);
let mut y1 = self.y;
y1.mul_assign(&other.z);
let mut x2 = other.x;
x2.mul_assign(&self.z);
let mut y2 = other.y;
y2.mul_assign(&self.z);
x1 == x2 && y1 == y2
}
}
impl<E: JubjubEngine> Point<E, Unknown> {
pub fn read<R: Read>(
reader: R,
params: &E::Params
) -> io::Result<Self>
{
let mut y_repr = <E::Fr as PrimeField>::Repr::default();
y_repr.read_le(reader)?;
let x_sign = (y_repr.as_ref()[3] >> 63) == 1;
y_repr.as_mut()[3] &= 0x7fffffffffffffff;
match E::Fr::from_repr(y_repr) {
Ok(y) => {
match Self::get_for_y(y, x_sign, params) {
Some(p) => Ok(p),
None => {
Err(io::Error::new(io::ErrorKind::InvalidInput, "not on curve"))
}
}
},
Err(_) => {
Err(io::Error::new(io::ErrorKind::InvalidInput, "y is not in field"))
}
}
}
pub fn get_for_y(y: E::Fr, sign: bool, params: &E::Params) -> Option<Self>
{
// Given a y on the curve, x^2 = (y^2 - 1) / (dy^2 + 1)
// This is defined for all valid y-coordinates,
// as dy^2 + 1 = 0 has no solution in Fr.
// tmp1 = y^2
let mut tmp1 = y;
tmp1.square();
// tmp2 = (y^2 * d) + 1
let mut tmp2 = tmp1;
tmp2.mul_assign(params.edwards_d());
tmp2.add_assign(&E::Fr::one());
// tmp1 = y^2 - 1
tmp1.sub_assign(&E::Fr::one());
match tmp2.inverse() {
Some(tmp2) => {
// tmp1 = (y^2 - 1) / (dy^2 + 1)
tmp1.mul_assign(&tmp2);
match tmp1.sqrt() {
Some(mut x) => {
if x.into_repr().is_odd() != sign {
x.negate();
}
let mut t = x;
t.mul_assign(&y);
Some(Point {
x: x,
y: y,
t: t,
z: E::Fr::one(),
_marker: PhantomData
})
},
None => None
}
},
None => None
}
}
/// This guarantees the point is in the prime order subgroup
#[must_use]
pub fn mul_by_cofactor(&self, params: &E::Params) -> Point<E, PrimeOrder>
{
let tmp = self.double(params)
.double(params)
.double(params);
convert_subgroup(&tmp)
}
pub fn rand<R: Rng>(rng: &mut R, params: &E::Params) -> Self
{
loop {
let y: E::Fr = rng.gen();
if let Some(p) = Self::get_for_y(y, rng.gen(), params) {
return p;
}
}
}
}
impl<E: JubjubEngine, Subgroup> Point<E, Subgroup> {
pub fn write<W: Write>(
&self,
writer: W
) -> io::Result<()>
{
let (x, y) = self.into_xy();
assert_eq!(E::Fr::NUM_BITS, 255);
let x_repr = x.into_repr();
let mut y_repr = y.into_repr();
if x_repr.is_odd() {
y_repr.as_mut()[3] |= 0x8000000000000000u64;
}
y_repr.write_le(writer)
}
/// Convert from a Montgomery point
pub fn from_montgomery(
m: &montgomery::Point<E, Subgroup>,
params: &E::Params
) -> Self
{
match m.into_xy() {
None => {
// Map the point at infinity to the neutral element.
Point::zero()
},
Some((x, y)) => {
// The map from a Montgomery curve is defined as:
// (x, y) -> (u, v) where
// u = x / y
// v = (x - 1) / (x + 1)
//
// This map is not defined for y = 0 and x = -1.
//
// y = 0 is a valid point only for x = 0:
// y^2 = x^3 + A.x^2 + x
// 0 = x^3 + A.x^2 + x
// 0 = x(x^2 + A.x + 1)
// We have: x = 0 OR x^2 + A.x + 1 = 0
// x^2 + A.x + 1 = 0
// (2.x + A)^2 = A^2 - 4 (Complete the square.)
// The left hand side is a square, and so if A^2 - 4
// is nonsquare, there is no solution. Indeed, A^2 - 4
// is nonsquare.
//
// (0, 0) is a point of order 2, and so we map it to
// (0, -1) in the twisted Edwards curve, which is the
// only point of order 2 that is not the neutral element.
if y.is_zero() {
// This must be the point (0, 0) as above.
let mut neg1 = E::Fr::one();
neg1.negate();
Point {
x: E::Fr::zero(),
y: neg1,
t: E::Fr::zero(),
z: E::Fr::one(),
_marker: PhantomData
}
} else {
// Otherwise, as stated above, the mapping is still
// not defined at x = -1. However, x = -1 is not
// on the curve when A - 2 is nonsquare:
// y^2 = x^3 + A.x^2 + x
// y^2 = (-1) + A + (-1)
// y^2 = A - 2
// Indeed, A - 2 is nonsquare.
//
// We need to map into (projective) extended twisted
// Edwards coordinates (X, Y, T, Z) which represents
// the point (X/Z, Y/Z) with Z nonzero and T = XY/Z.
//
// Thus, we compute...
//
// u = x(x + 1)
// v = y(x - 1)
// t = x(x - 1)
// z = y(x + 1) (Cannot be nonzero, as above.)
//
// ... which represents the point ( x / y , (x - 1) / (x + 1) )
// as required by the mapping and preserves the property of
// the auxiliary coordinate t.
//
// We need to scale the coordinate, so u and t will have
// an extra factor s.
// u = xs
let mut u = x;
u.mul_assign(params.scale());
// v = x - 1
let mut v = x;
v.sub_assign(&E::Fr::one());
// t = xs(x - 1)
let mut t = u;
t.mul_assign(&v);
// z = (x + 1)
let mut z = x;
z.add_assign(&E::Fr::one());
// u = xs(x + 1)
u.mul_assign(&z);
// z = y(x + 1)
z.mul_assign(&y);
// v = y(x - 1)
v.mul_assign(&y);
Point {
x: u,
y: v,
t: t,
z: z,
_marker: PhantomData
}
}
}
}
}
/// Attempts to cast this as a prime order element, failing if it's
/// not in the prime order subgroup.
pub fn as_prime_order(&self, params: &E::Params) -> Option<Point<E, PrimeOrder>> {
if self.mul(E::Fs::char(), params) == Point::zero() {
Some(convert_subgroup(self))
} else {
None
}
}
pub fn zero() -> Self {
Point {
x: E::Fr::zero(),
y: E::Fr::one(),
t: E::Fr::zero(),
z: E::Fr::one(),
_marker: PhantomData
}
}
pub fn into_xy(&self) -> (E::Fr, E::Fr)
{
let zinv = self.z.inverse().unwrap();
let mut x = self.x;
x.mul_assign(&zinv);
let mut y = self.y;
y.mul_assign(&zinv);
(x, y)
}
#[must_use]
pub fn negate(&self) -> Self {
let mut p = self.clone();
p.x.negate();
p.t.negate();
p
}
#[must_use]
pub fn double(&self, _: &E::Params) -> Self {
// See "Twisted Edwards Curves Revisited"
// Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson
// Section 3.3
// http://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd
// A = X1^2
let mut a = self.x;
a.square();
// B = Y1^2
let mut b = self.y;
b.square();
// C = 2*Z1^2
let mut c = self.z;
c.square();
c.double();
// D = a*A
// = -A
let mut d = a;
d.negate();
// E = (X1+Y1)^2 - A - B
let mut e = self.x;
e.add_assign(&self.y);
e.square();
e.add_assign(&d); // -A = D
e.sub_assign(&b);
// G = D+B
let mut g = d;
g.add_assign(&b);
// F = G-C
let mut f = g;
f.sub_assign(&c);
// H = D-B
let mut h = d;
h.sub_assign(&b);
// X3 = E*F
let mut x3 = e;
x3.mul_assign(&f);
// Y3 = G*H
let mut y3 = g;
y3.mul_assign(&h);
// T3 = E*H
let mut t3 = e;
t3.mul_assign(&h);
// Z3 = F*G
let mut z3 = f;
z3.mul_assign(&g);
Point {
x: x3,
y: y3,
t: t3,
z: z3,
_marker: PhantomData
}
}
#[must_use]
pub fn add(&self, other: &Self, params: &E::Params) -> Self
{
// See "Twisted Edwards Curves Revisited"
// Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson
// 3.1 Unified Addition in E^e
// A = x1 * x2
let mut a = self.x;
a.mul_assign(&other.x);
// B = y1 * y2
let mut b = self.y;
b.mul_assign(&other.y);
// C = d * t1 * t2
let mut c = params.edwards_d().clone();
c.mul_assign(&self.t);
c.mul_assign(&other.t);
// D = z1 * z2
let mut d = self.z;
d.mul_assign(&other.z);
// H = B - aA
// = B + A
let mut h = b;
h.add_assign(&a);
// E = (x1 + y1) * (x2 + y2) - A - B
// = (x1 + y1) * (x2 + y2) - H
let mut e = self.x;
e.add_assign(&self.y);
{
let mut tmp = other.x;
tmp.add_assign(&other.y);
e.mul_assign(&tmp);
}
e.sub_assign(&h);
// F = D - C
let mut f = d;
f.sub_assign(&c);
// G = D + C
let mut g = d;
g.add_assign(&c);
// x3 = E * F
let mut x3 = e;
x3.mul_assign(&f);
// y3 = G * H
let mut y3 = g;
y3.mul_assign(&h);
// t3 = E * H
let mut t3 = e;
t3.mul_assign(&h);
// z3 = F * G
let mut z3 = f;
z3.mul_assign(&g);
Point {
x: x3,
y: y3,
t: t3,
z: z3,
_marker: PhantomData
}
}
#[must_use]
pub fn mul<S: Into<<E::Fs as PrimeField>::Repr>>(
&self,
scalar: S,
params: &E::Params
) -> Self
{
// Standard double-and-add scalar multiplication
let mut res = Self::zero();
for b in BitIterator::new(scalar.into()) {
res = res.double(params);
if b {
res = res.add(self, params);
}
}
res
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,435 +0,0 @@
//! Jubjub is a twisted Edwards curve defined over the BLS12-381 scalar
//! field, Fr. It takes the form `-x^2 + y^2 = 1 + dx^2y^2` with
//! `d = -(10240/10241)`. It is birationally equivalent to a Montgomery
//! curve of the form `y^2 = x^3 + Ax^2 + x` with `A = 40962`. This
//! value `A` is the smallest integer choice such that:
//!
//! * `(A - 2) / 4` is a small integer (`10240`).
//! * `A^2 - 4` is quadratic nonresidue.
//! * The group order of the curve and its quadratic twist has a large
//! prime factor.
//!
//! Jubjub has `s = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7`
//! as the prime subgroup order, with cofactor 8. (The twist has
//! cofactor 4.)
//!
//! It is a complete twisted Edwards curve, so the equivalence with
//! the Montgomery curve forms a group isomorphism, allowing points
//! to be freely converted between the two forms.
use pairing::{
Engine,
Field,
PrimeField,
SqrtField
};
use group_hash::group_hash;
use constants;
use pairing::bls12_381::{
Bls12,
Fr
};
/// This is an implementation of the twisted Edwards Jubjub curve.
pub mod edwards;
/// This is an implementation of the birationally equivalent
/// Montgomery curve.
pub mod montgomery;
/// This is an implementation of the scalar field for Jubjub.
pub mod fs;
#[cfg(test)]
pub mod tests;
/// Point of unknown order.
pub enum Unknown { }
/// Point of prime order.
pub enum PrimeOrder { }
/// Fixed generators of the Jubjub curve of unknown
/// exponent.
#[derive(Copy, Clone)]
pub enum FixedGenerators {
/// The prover will demonstrate knowledge of discrete log
/// with respect to this base when they are constructing
/// a proof, in order to authorize proof construction.
ProofGenerationKey = 0,
/// The note commitment is randomized over this generator.
NoteCommitmentRandomness = 1,
/// The node commitment is randomized again by the position
/// in order to supply the nullifier computation with a
/// unique input w.r.t. the note being spent, to prevent
/// Faerie gold attacks.
NullifierPosition = 2,
/// The value commitment is used to check balance between
/// inputs and outputs. The value is placed over this
/// generator.
ValueCommitmentValue = 3,
/// The value commitment is randomized over this generator,
/// for privacy.
ValueCommitmentRandomness = 4,
/// The spender proves discrete log with respect to this
/// base at spend time.
SpendingKeyGenerator = 5,
Max = 6
}
pub trait ToUniform {
fn to_uniform(digest: &[u8]) -> Self;
}
/// This is an extension to the pairing Engine trait which
/// offers a scalar field for the embedded curve (Jubjub)
/// and some pre-computed parameters.
pub trait JubjubEngine: Engine {
/// The scalar field of the Jubjub curve
type Fs: PrimeField + SqrtField + ToUniform;
/// The parameters of Jubjub and the Sapling protocol
type Params: JubjubParams<Self>;
}
/// The pre-computed parameters for Jubjub, including curve
/// constants and various limits and window tables.
pub trait JubjubParams<E: JubjubEngine>: Sized {
/// The `d` constant of the twisted Edwards curve.
fn edwards_d(&self) -> &E::Fr;
/// The `A` constant of the birationally equivalent Montgomery curve.
fn montgomery_a(&self) -> &E::Fr;
/// The `A` constant, doubled.
fn montgomery_2a(&self) -> &E::Fr;
/// The scaling factor used for conversion from the Montgomery form.
fn scale(&self) -> &E::Fr;
/// Returns the generators (for each segment) used in all Pedersen commitments.
fn pedersen_hash_generators(&self) -> &[edwards::Point<E, PrimeOrder>];
/// Returns the exp table for Pedersen hashes.
fn pedersen_hash_exp_table(&self) -> &[Vec<Vec<edwards::Point<E, PrimeOrder>>>];
/// Returns the maximum number of chunks per segment of the Pedersen hash.
fn pedersen_hash_chunks_per_generator(&self) -> usize;
/// Returns the pre-computed window tables [-4, 3, 2, 1, 1, 2, 3, 4] of different
/// magnitudes of the Pedersen hash segment generators.
fn pedersen_circuit_generators(&self) -> &[Vec<Vec<(E::Fr, E::Fr)>>];
/// Returns the number of chunks needed to represent a full scalar during fixed-base
/// exponentiation.
fn fixed_base_chunks_per_generator(&self) -> usize;
/// Returns a fixed generator.
fn generator(&self, base: FixedGenerators) -> &edwards::Point<E, PrimeOrder>;
/// Returns a window table [0, 1, ..., 8] for different magnitudes of some
/// fixed generator.
fn circuit_generators(&self, FixedGenerators) -> &[Vec<(E::Fr, E::Fr)>];
/// Returns the window size for exponentiation of Pedersen hash generators
/// outside the circuit
fn pedersen_hash_exp_window_size() -> u32;
}
impl JubjubEngine for Bls12 {
type Fs = self::fs::Fs;
type Params = JubjubBls12;
}
pub struct JubjubBls12 {
edwards_d: Fr,
montgomery_a: Fr,
montgomery_2a: Fr,
scale: Fr,
pedersen_hash_generators: Vec<edwards::Point<Bls12, PrimeOrder>>,
pedersen_hash_exp: Vec<Vec<Vec<edwards::Point<Bls12, PrimeOrder>>>>,
pedersen_circuit_generators: Vec<Vec<Vec<(Fr, Fr)>>>,
fixed_base_generators: Vec<edwards::Point<Bls12, PrimeOrder>>,
fixed_base_circuit_generators: Vec<Vec<Vec<(Fr, Fr)>>>,
}
impl JubjubParams<Bls12> for JubjubBls12 {
fn edwards_d(&self) -> &Fr { &self.edwards_d }
fn montgomery_a(&self) -> &Fr { &self.montgomery_a }
fn montgomery_2a(&self) -> &Fr { &self.montgomery_2a }
fn scale(&self) -> &Fr { &self.scale }
fn pedersen_hash_generators(&self) -> &[edwards::Point<Bls12, PrimeOrder>] {
&self.pedersen_hash_generators
}
fn pedersen_hash_exp_table(&self) -> &[Vec<Vec<edwards::Point<Bls12, PrimeOrder>>>] {
&self.pedersen_hash_exp
}
fn pedersen_hash_chunks_per_generator(&self) -> usize {
63
}
fn fixed_base_chunks_per_generator(&self) -> usize {
84
}
fn pedersen_circuit_generators(&self) -> &[Vec<Vec<(Fr, Fr)>>] {
&self.pedersen_circuit_generators
}
fn generator(&self, base: FixedGenerators) -> &edwards::Point<Bls12, PrimeOrder>
{
&self.fixed_base_generators[base as usize]
}
fn circuit_generators(&self, base: FixedGenerators) -> &[Vec<(Fr, Fr)>]
{
&self.fixed_base_circuit_generators[base as usize][..]
}
fn pedersen_hash_exp_window_size() -> u32 {
8
}
}
impl JubjubBls12 {
pub fn new() -> Self {
let montgomery_a = Fr::from_str("40962").unwrap();
let mut montgomery_2a = montgomery_a;
montgomery_2a.double();
let mut tmp_params = JubjubBls12 {
// d = -(10240/10241)
edwards_d: Fr::from_str("19257038036680949359750312669786877991949435402254120286184196891950884077233").unwrap(),
// A = 40962
montgomery_a: montgomery_a,
// 2A = 2.A
montgomery_2a: montgomery_2a,
// scaling factor = sqrt(4 / (a - d))
scale: Fr::from_str("17814886934372412843466061268024708274627479829237077604635722030778476050649").unwrap(),
// We'll initialize these below
pedersen_hash_generators: vec![],
pedersen_hash_exp: vec![],
pedersen_circuit_generators: vec![],
fixed_base_generators: vec![],
fixed_base_circuit_generators: vec![],
};
fn find_group_hash<E: JubjubEngine>(
m: &[u8],
personalization: &[u8; 8],
params: &E::Params
) -> edwards::Point<E, PrimeOrder>
{
let mut tag = m.to_vec();
let i = tag.len();
tag.push(0u8);
loop {
let gh = group_hash(
&tag,
personalization,
params
);
// We don't want to overflow and start reusing generators
assert!(tag[i] != u8::max_value());
tag[i] += 1;
if let Some(gh) = gh {
break gh;
}
}
}
// Create the bases for the Pedersen hashes
{
let mut pedersen_hash_generators = vec![];
for m in 0..5 {
use byteorder::{WriteBytesExt, LittleEndian};
let mut segment_number = [0u8; 4];
(&mut segment_number[0..4]).write_u32::<LittleEndian>(m).unwrap();
pedersen_hash_generators.push(
find_group_hash(
&segment_number,
constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION,
&tmp_params
)
);
}
// Check for duplicates, far worse than spec inconsistencies!
for (i, p1) in pedersen_hash_generators.iter().enumerate() {
if p1 == &edwards::Point::zero() {
panic!("Neutral element!");
}
for p2 in pedersen_hash_generators.iter().skip(i+1) {
if p1 == p2 {
panic!("Duplicate generator!");
}
}
}
tmp_params.pedersen_hash_generators = pedersen_hash_generators;
}
// Create the exp table for the Pedersen hash generators
{
let mut pedersen_hash_exp = vec![];
for g in &tmp_params.pedersen_hash_generators {
let mut g = g.clone();
let window = JubjubBls12::pedersen_hash_exp_window_size();
let mut tables = vec![];
let mut num_bits = 0;
while num_bits <= fs::Fs::NUM_BITS {
let mut table = Vec::with_capacity(1 << window);
let mut base = edwards::Point::zero();
for _ in 0..(1 << window) {
table.push(base.clone());
base = base.add(&g, &tmp_params);
}
tables.push(table);
num_bits += window;
for _ in 0..window {
g = g.double(&tmp_params);
}
}
pedersen_hash_exp.push(tables);
}
tmp_params.pedersen_hash_exp = pedersen_hash_exp;
}
// Create the bases for other parts of the protocol
{
let mut fixed_base_generators = vec![edwards::Point::zero(); FixedGenerators::Max as usize];
fixed_base_generators[FixedGenerators::ProofGenerationKey as usize] =
find_group_hash(&[], constants::PROOF_GENERATION_KEY_BASE_GENERATOR_PERSONALIZATION, &tmp_params);
fixed_base_generators[FixedGenerators::NoteCommitmentRandomness as usize] =
find_group_hash(b"r", constants::PEDERSEN_HASH_GENERATORS_PERSONALIZATION, &tmp_params);
fixed_base_generators[FixedGenerators::NullifierPosition as usize] =
find_group_hash(&[], constants::NULLIFIER_POSITION_IN_TREE_GENERATOR_PERSONALIZATION, &tmp_params);
fixed_base_generators[FixedGenerators::ValueCommitmentValue as usize] =
find_group_hash(b"v", constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, &tmp_params);
fixed_base_generators[FixedGenerators::ValueCommitmentRandomness as usize] =
find_group_hash(b"r", constants::VALUE_COMMITMENT_GENERATOR_PERSONALIZATION, &tmp_params);
fixed_base_generators[FixedGenerators::SpendingKeyGenerator as usize] =
find_group_hash(&[], constants::SPENDING_KEY_GENERATOR_PERSONALIZATION, &tmp_params);
// Check for duplicates, far worse than spec inconsistencies!
for (i, p1) in fixed_base_generators.iter().enumerate() {
if p1 == &edwards::Point::zero() {
panic!("Neutral element!");
}
for p2 in fixed_base_generators.iter().skip(i+1) {
if p1 == p2 {
panic!("Duplicate generator!");
}
}
}
tmp_params.fixed_base_generators = fixed_base_generators;
}
// Create the 2-bit window table lookups for each 4-bit
// "chunk" in each segment of the Pedersen hash
{
let mut pedersen_circuit_generators = vec![];
// Process each segment
for mut gen in tmp_params.pedersen_hash_generators.iter().cloned() {
let mut gen = montgomery::Point::from_edwards(&gen, &tmp_params);
let mut windows = vec![];
for _ in 0..tmp_params.pedersen_hash_chunks_per_generator() {
// Create (x, y) coeffs for this chunk
let mut coeffs = vec![];
let mut g = gen.clone();
// coeffs = g, g*2, g*3, g*4
for _ in 0..4 {
coeffs.push(g.into_xy().expect("cannot produce O"));
g = g.add(&gen, &tmp_params);
}
windows.push(coeffs);
// Our chunks are separated by 2 bits to prevent overlap.
for _ in 0..4 {
gen = gen.double(&tmp_params);
}
}
pedersen_circuit_generators.push(windows);
}
tmp_params.pedersen_circuit_generators = pedersen_circuit_generators;
}
// Create the 3-bit window table lookups for fixed-base
// exp of each base in the protocol.
{
let mut fixed_base_circuit_generators = vec![];
for mut gen in tmp_params.fixed_base_generators.iter().cloned() {
let mut windows = vec![];
for _ in 0..tmp_params.fixed_base_chunks_per_generator() {
let mut coeffs = vec![(Fr::zero(), Fr::one())];
let mut g = gen.clone();
for _ in 0..7 {
coeffs.push(g.into_xy());
g = g.add(&gen, &tmp_params);
}
windows.push(coeffs);
// gen = gen * 8
gen = g;
}
fixed_base_circuit_generators.push(windows);
}
tmp_params.fixed_base_circuit_generators = fixed_base_circuit_generators;
}
tmp_params
}
}
#[test]
fn test_jubjub_bls12() {
let params = JubjubBls12::new();
tests::test_suite::<Bls12>(&params);
let test_repr = hex!("9d12b88b08dcbef8a11ee0712d94cb236ee2f4ca17317075bfafc82ce3139d31");
let p = edwards::Point::<Bls12, _>::read(&test_repr[..], &params).unwrap();
let q = edwards::Point::<Bls12, _>::get_for_y(
Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(),
false,
&params
).unwrap();
assert!(p == q);
// Same thing, but sign bit set
let test_repr = hex!("9d12b88b08dcbef8a11ee0712d94cb236ee2f4ca17317075bfafc82ce3139db1");
let p = edwards::Point::<Bls12, _>::read(&test_repr[..], &params).unwrap();
let q = edwards::Point::<Bls12, _>::get_for_y(
Fr::from_str("22440861827555040311190986994816762244378363690614952020532787748720529117853").unwrap(),
true,
&params
).unwrap();
assert!(p == q);
}

View File

@ -1,358 +0,0 @@
use pairing::{
Field,
SqrtField,
PrimeField,
PrimeFieldRepr,
BitIterator
};
use super::{
JubjubEngine,
JubjubParams,
Unknown,
PrimeOrder,
edwards
};
use rand::{
Rng
};
use std::marker::PhantomData;
// Represents the affine point (X, Y)
pub struct Point<E: JubjubEngine, Subgroup> {
x: E::Fr,
y: E::Fr,
infinity: bool,
_marker: PhantomData<Subgroup>
}
fn convert_subgroup<E: JubjubEngine, S1, S2>(from: &Point<E, S1>) -> Point<E, S2>
{
Point {
x: from.x,
y: from.y,
infinity: from.infinity,
_marker: PhantomData
}
}
impl<E: JubjubEngine> From<Point<E, PrimeOrder>> for Point<E, Unknown>
{
fn from(p: Point<E, PrimeOrder>) -> Point<E, Unknown>
{
convert_subgroup(&p)
}
}
impl<E: JubjubEngine, Subgroup> Clone for Point<E, Subgroup>
{
fn clone(&self) -> Self {
convert_subgroup(self)
}
}
impl<E: JubjubEngine, Subgroup> PartialEq for Point<E, Subgroup> {
fn eq(&self, other: &Point<E, Subgroup>) -> bool {
match (self.infinity, other.infinity) {
(true, true) => true,
(true, false) | (false, true) => false,
(false, false) => {
self.x == other.x && self.y == other.y
}
}
}
}
impl<E: JubjubEngine> Point<E, Unknown> {
pub fn get_for_x(x: E::Fr, sign: bool, params: &E::Params) -> Option<Self>
{
// Given an x on the curve, y = sqrt(x^3 + A*x^2 + x)
let mut x2 = x;
x2.square();
let mut rhs = x2;
rhs.mul_assign(params.montgomery_a());
rhs.add_assign(&x);
x2.mul_assign(&x);
rhs.add_assign(&x2);
match rhs.sqrt() {
Some(mut y) => {
if y.into_repr().is_odd() != sign {
y.negate();
}
return Some(Point {
x: x,
y: y,
infinity: false,
_marker: PhantomData
})
},
None => None
}
}
/// This guarantees the point is in the prime order subgroup
#[must_use]
pub fn mul_by_cofactor(&self, params: &E::Params) -> Point<E, PrimeOrder>
{
let tmp = self.double(params)
.double(params)
.double(params);
convert_subgroup(&tmp)
}
pub fn rand<R: Rng>(rng: &mut R, params: &E::Params) -> Self
{
loop {
let x: E::Fr = rng.gen();
match Self::get_for_x(x, rng.gen(), params) {
Some(p) => {
return p
},
None => {}
}
}
}
}
impl<E: JubjubEngine, Subgroup> Point<E, Subgroup> {
/// Convert from an Edwards point
pub fn from_edwards(
e: &edwards::Point<E, Subgroup>,
params: &E::Params
) -> Self
{
let (x, y) = e.into_xy();
if y == E::Fr::one() {
// The only solution for y = 1 is x = 0. (0, 1) is
// the neutral element, so we map this to the point
// at infinity.
Point::zero()
} else {
// The map from a twisted Edwards curve is defined as
// (x, y) -> (u, v) where
// u = (1 + y) / (1 - y)
// v = u / x
//
// This mapping is not defined for y = 1 and for x = 0.
//
// We have that y != 1 above. If x = 0, the only
// solutions for y are 1 (contradiction) or -1.
if x.is_zero() {
// (0, -1) is the point of order two which is not
// the neutral element, so we map it to (0, 0) which is
// the only affine point of order 2.
Point {
x: E::Fr::zero(),
y: E::Fr::zero(),
infinity: false,
_marker: PhantomData
}
} else {
// The mapping is defined as above.
//
// (x, y) -> (u, v) where
// u = (1 + y) / (1 - y)
// v = u / x
let mut u = E::Fr::one();
u.add_assign(&y);
{
let mut tmp = E::Fr::one();
tmp.sub_assign(&y);
u.mul_assign(&tmp.inverse().unwrap())
}
let mut v = u;
v.mul_assign(&x.inverse().unwrap());
// Scale it into the correct curve constants
v.mul_assign(params.scale());
Point {
x: u,
y: v,
infinity: false,
_marker: PhantomData
}
}
}
}
/// Attempts to cast this as a prime order element, failing if it's
/// not in the prime order subgroup.
pub fn as_prime_order(&self, params: &E::Params) -> Option<Point<E, PrimeOrder>> {
if self.mul(E::Fs::char(), params) == Point::zero() {
Some(convert_subgroup(self))
} else {
None
}
}
pub fn zero() -> Self {
Point {
x: E::Fr::zero(),
y: E::Fr::zero(),
infinity: true,
_marker: PhantomData
}
}
pub fn into_xy(&self) -> Option<(E::Fr, E::Fr)>
{
if self.infinity {
None
} else {
Some((self.x, self.y))
}
}
#[must_use]
pub fn negate(&self) -> Self {
let mut p = self.clone();
p.y.negate();
p
}
#[must_use]
pub fn double(&self, params: &E::Params) -> Self {
if self.infinity {
return Point::zero();
}
// (0, 0) is the point of order 2. Doubling
// produces the point at infinity.
if self.y == E::Fr::zero() {
return Point::zero();
}
// This is a standard affine point doubling formula
// See 4.3.2 The group law for Weierstrass curves
// Montgomery curves and the Montgomery Ladder
// Daniel J. Bernstein and Tanja Lange
let mut delta = E::Fr::one();
{
let mut tmp = params.montgomery_a().clone();
tmp.mul_assign(&self.x);
tmp.double();
delta.add_assign(&tmp);
}
{
let mut tmp = self.x;
tmp.square();
delta.add_assign(&tmp);
tmp.double();
delta.add_assign(&tmp);
}
{
let mut tmp = self.y;
tmp.double();
delta.mul_assign(&tmp.inverse().expect("y is nonzero so this must be nonzero"));
}
let mut x3 = delta;
x3.square();
x3.sub_assign(params.montgomery_a());
x3.sub_assign(&self.x);
x3.sub_assign(&self.x);
let mut y3 = x3;
y3.sub_assign(&self.x);
y3.mul_assign(&delta);
y3.add_assign(&self.y);
y3.negate();
Point {
x: x3,
y: y3,
infinity: false,
_marker: PhantomData
}
}
#[must_use]
pub fn add(&self, other: &Self, params: &E::Params) -> Self
{
// This is a standard affine point addition formula
// See 4.3.2 The group law for Weierstrass curves
// Montgomery curves and the Montgomery Ladder
// Daniel J. Bernstein and Tanja Lange
match (self.infinity, other.infinity) {
(true, true) => Point::zero(),
(true, false) => other.clone(),
(false, true) => self.clone(),
(false, false) => {
if self.x == other.x {
if self.y == other.y {
self.double(params)
} else {
Point::zero()
}
} else {
let mut delta = other.y;
delta.sub_assign(&self.y);
{
let mut tmp = other.x;
tmp.sub_assign(&self.x);
delta.mul_assign(&tmp.inverse().expect("self.x != other.x, so this must be nonzero"));
}
let mut x3 = delta;
x3.square();
x3.sub_assign(params.montgomery_a());
x3.sub_assign(&self.x);
x3.sub_assign(&other.x);
let mut y3 = x3;
y3.sub_assign(&self.x);
y3.mul_assign(&delta);
y3.add_assign(&self.y);
y3.negate();
Point {
x: x3,
y: y3,
infinity: false,
_marker: PhantomData
}
}
}
}
}
#[must_use]
pub fn mul<S: Into<<E::Fs as PrimeField>::Repr>>(
&self,
scalar: S,
params: &E::Params
) -> Self
{
// Standard double-and-add scalar multiplication
let mut res = Self::zero();
for b in BitIterator::new(scalar.into()) {
res = res.double(params);
if b {
res = res.add(self, params);
}
}
res
}
}

View File

@ -1,416 +0,0 @@
use super::{
JubjubEngine,
JubjubParams,
PrimeOrder,
montgomery,
edwards
};
use pairing::{
Field,
PrimeField,
PrimeFieldRepr,
SqrtField,
LegendreSymbol
};
use rand::{XorShiftRng, SeedableRng, Rand};
pub fn test_suite<E: JubjubEngine>(params: &E::Params) {
test_back_and_forth::<E>(params);
test_jubjub_params::<E>(params);
test_rand::<E>(params);
test_get_for::<E>(params);
test_identities::<E>(params);
test_addition_associativity::<E>(params);
test_order::<E>(params);
test_mul_associativity::<E>(params);
test_loworder::<E>(params);
test_read_write::<E>(params);
}
fn is_on_mont_curve<E: JubjubEngine, P: JubjubParams<E>>(
x: E::Fr,
y: E::Fr,
params: &P
) -> bool
{
let mut lhs = y;
lhs.square();
let mut x2 = x;
x2.square();
let mut x3 = x2;
x3.mul_assign(&x);
let mut rhs = x2;
rhs.mul_assign(params.montgomery_a());
rhs.add_assign(&x);
rhs.add_assign(&x3);
lhs == rhs
}
fn is_on_twisted_edwards_curve<E: JubjubEngine, P: JubjubParams<E>>(
x: E::Fr,
y: E::Fr,
params: &P
) -> bool
{
let mut x2 = x;
x2.square();
let mut y2 = y;
y2.square();
// -x^2 + y^2
let mut lhs = y2;
lhs.sub_assign(&x2);
// 1 + d x^2 y^2
let mut rhs = y2;
rhs.mul_assign(&x2);
rhs.mul_assign(params.edwards_d());
rhs.add_assign(&E::Fr::one());
lhs == rhs
}
fn test_loworder<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
let inf = montgomery::Point::zero();
// try to find a point of order 8
let p = loop {
let r = montgomery::Point::<E, _>::rand(rng, params).mul(E::Fs::char(), params);
let r2 = r.double(params);
let r4 = r2.double(params);
let r8 = r4.double(params);
if r2 != inf && r4 != inf && r8 == inf {
break r;
}
};
let mut loworder_points = vec![];
{
let mut tmp = p.clone();
for _ in 0..8 {
assert!(!loworder_points.contains(&tmp));
loworder_points.push(tmp.clone());
tmp = tmp.add(&p, params);
}
}
assert!(loworder_points[7] == inf);
}
fn test_mul_associativity<E: JubjubEngine>(params: &E::Params) {
use self::edwards::Point;
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..100 {
// Pick a random point and multiply it by the cofactor
let base = Point::<E, _>::rand(rng, params).mul_by_cofactor(params);
let mut a = E::Fs::rand(rng);
let b = E::Fs::rand(rng);
let c = E::Fs::rand(rng);
let res1 = base.mul(a, params).mul(b, params).mul(c, params);
let res2 = base.mul(b, params).mul(c, params).mul(a, params);
let res3 = base.mul(c, params).mul(a, params).mul(b, params);
a.mul_assign(&b);
a.mul_assign(&c);
let res4 = base.mul(a, params);
assert!(res1 == res2);
assert!(res2 == res3);
assert!(res3 == res4);
let (x, y) = res1.into_xy();
assert!(is_on_twisted_edwards_curve(x, y, params));
let (x, y) = res2.into_xy();
assert!(is_on_twisted_edwards_curve(x, y, params));
let (x, y) = res3.into_xy();
assert!(is_on_twisted_edwards_curve(x, y, params));
}
}
fn test_order<E: JubjubEngine>(params: &E::Params) {
use self::edwards::Point;
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
// The neutral element is in the prime order subgroup.
assert!(Point::<E, PrimeOrder>::zero().as_prime_order(params).is_some());
for _ in 0..50 {
// Pick a random point and multiply it by the cofactor
let base = Point::<E, _>::rand(rng, params).mul_by_cofactor(params);
// Any point multiplied by the cofactor will be in the prime
// order subgroup
assert!(base.as_prime_order(params).is_some());
}
// It's very likely that at least one out of 50 random points on the curve
// is not in the prime order subgroup.
let mut at_least_one_not_in_prime_order_subgroup = false;
for _ in 0..50 {
// Pick a random point.
let base = Point::<E, _>::rand(rng, params);
at_least_one_not_in_prime_order_subgroup |= base.as_prime_order(params).is_none();
}
assert!(at_least_one_not_in_prime_order_subgroup);
}
fn test_addition_associativity<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
use self::montgomery::Point;
let a = Point::<E, _>::rand(rng, params);
let b = Point::<E, _>::rand(rng, params);
let c = Point::<E, _>::rand(rng, params);
assert!(a.add(&b, &params).add(&c, &params) == c.add(&a, &params).add(&b, &params));
}
for _ in 0..1000 {
use self::edwards::Point;
let a = Point::<E, _>::rand(rng, params);
let b = Point::<E, _>::rand(rng, params);
let c = Point::<E, _>::rand(rng, params);
assert!(a.add(&b, &params).add(&c, &params) == c.add(&a, &params).add(&b, &params));
}
}
fn test_identities<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
{
use self::edwards::Point;
let z = Point::<E, PrimeOrder>::zero();
assert!(z.double(&params) == z);
assert!(z.negate() == z);
for _ in 0..100 {
let r = Point::<E, _>::rand(rng, params);
assert!(r.add(&Point::zero(), &params) == r);
assert!(r.add(&r.negate(), &params) == Point::zero());
}
}
{
use self::montgomery::Point;
let z = Point::<E, PrimeOrder>::zero();
assert!(z.double(&params) == z);
assert!(z.negate() == z);
for _ in 0..100 {
let r = Point::<E, _>::rand(rng, params);
assert!(r.add(&Point::zero(), &params) == r);
assert!(r.add(&r.negate(), &params) == Point::zero());
}
}
}
fn test_get_for<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let y = E::Fr::rand(rng);
let sign = bool::rand(rng);
if let Some(mut p) = edwards::Point::<E, _>::get_for_y(y, sign, params) {
assert!(p.into_xy().0.into_repr().is_odd() == sign);
p = p.negate();
assert!(
edwards::Point::<E, _>::get_for_y(y, !sign, params).unwrap()
==
p
);
}
}
}
fn test_read_write<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let e = edwards::Point::<E, _>::rand(rng, params);
let mut v = vec![];
e.write(&mut v).unwrap();
let e2 = edwards::Point::read(&v[..], params).unwrap();
assert!(e == e2);
}
}
fn test_rand<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x3dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let p = montgomery::Point::<E, _>::rand(rng, params);
let e = edwards::Point::<E, _>::rand(rng, params);
{
let (x, y) = p.into_xy().unwrap();
assert!(is_on_mont_curve(x, y, params));
}
{
let (x, y) = e.into_xy();
assert!(is_on_twisted_edwards_curve(x, y, params));
}
}
}
fn test_back_and_forth<E: JubjubEngine>(params: &E::Params) {
let rng = &mut XorShiftRng::from_seed([0x5dbe6259, 0x8d313d76, 0x3237db17, 0xe5bc0654]);
for _ in 0..1000 {
let s = E::Fs::rand(rng);
let edwards_p1 = edwards::Point::<E, _>::rand(rng, params);
let mont_p1 = montgomery::Point::from_edwards(&edwards_p1, params);
let mont_p2 = montgomery::Point::<E, _>::rand(rng, params);
let edwards_p2 = edwards::Point::from_montgomery(&mont_p2, params);
let mont = mont_p1.add(&mont_p2, params).mul(s, params);
let edwards = edwards_p1.add(&edwards_p2, params).mul(s, params);
assert!(
montgomery::Point::from_edwards(&edwards, params) == mont
);
assert!(
edwards::Point::from_montgomery(&mont, params) == edwards
);
}
}
fn test_jubjub_params<E: JubjubEngine>(params: &E::Params) {
// a = -1
let mut a = E::Fr::one();
a.negate();
{
// Check that 2A is consistent with A
let mut tmp = *params.montgomery_a();
tmp.double();
assert_eq!(&tmp, params.montgomery_2a());
}
{
// The twisted Edwards addition law is complete when d is nonsquare
// and a is square.
assert!(params.edwards_d().legendre() == LegendreSymbol::QuadraticNonResidue);
assert!(a.legendre() == LegendreSymbol::QuadraticResidue);
}
{
// Other convenient sanity checks regarding d
// tmp = d
let mut tmp = *params.edwards_d();
// 1 / d is nonsquare
assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue);
// tmp = -d
tmp.negate();
// -d is nonsquare
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
// 1 / -d is nonsquare
assert!(tmp.inverse().unwrap().legendre() == LegendreSymbol::QuadraticNonResidue);
}
{
// Check that A^2 - 4 is nonsquare:
let mut tmp = params.montgomery_a().clone();
tmp.square();
tmp.sub_assign(&E::Fr::from_str("4").unwrap());
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
}
{
// Check that A - 2 is nonsquare:
let mut tmp = params.montgomery_a().clone();
tmp.sub_assign(&E::Fr::from_str("2").unwrap());
assert!(tmp.legendre() == LegendreSymbol::QuadraticNonResidue);
}
{
// Check the validity of the scaling factor
let mut tmp = a;
tmp.sub_assign(&params.edwards_d());
tmp = tmp.inverse().unwrap();
tmp.mul_assign(&E::Fr::from_str("4").unwrap());
tmp = tmp.sqrt().unwrap();
assert_eq!(&tmp, params.scale());
}
{
// Check that the number of windows per generator
// in the Pedersen hash does not allow for collisions
let mut cur = E::Fs::one().into_repr();
let mut max = E::Fs::char();
{
max.sub_noborrow(&E::Fs::one().into_repr());
max.div2();
}
let mut pacc = E::Fs::zero().into_repr();
let mut nacc = E::Fs::char();
for _ in 0..params.pedersen_hash_chunks_per_generator()
{
// tmp = cur * 4
let mut tmp = cur;
tmp.mul2();
tmp.mul2();
pacc.add_nocarry(&tmp);
nacc.sub_noborrow(&tmp);
assert!(pacc < max);
assert!(pacc < nacc);
// cur = cur * 16
for _ in 0..4 {
cur.mul2();
}
}
}
{
// Check that the number of windows for fixed-base
// scalar multiplication is sufficient for all scalars.
assert!(params.fixed_base_chunks_per_generator() * 3 >= E::Fs::NUM_BITS as usize);
// ... and that it's *just* efficient enough.
assert!((params.fixed_base_chunks_per_generator() - 1) * 3 < E::Fs::NUM_BITS as usize);
}
}

746
src/keys.rs Normal file
View File

@ -0,0 +1,746 @@
//! Sapling key components.
//!
//! Implements [section 4.2.2] of the Zcash Protocol Specification.
//!
//! [section 4.2.2]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
use std::fmt;
use std::io::{self, Read, Write};
use super::{
address::PaymentAddress,
constants::{self, PROOF_GENERATION_KEY_GENERATOR},
note_encryption::KDF_SAPLING_PERSONALIZATION,
spec::{
crh_ivk, diversify_hash, ka_sapling_agree, ka_sapling_agree_prepared,
ka_sapling_derive_public, ka_sapling_derive_public_subgroup_prepared, PreparedBase,
PreparedBaseSubgroup, PreparedScalar,
},
};
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use ff::{Field, PrimeField};
use group::{Curve, Group, GroupEncoding};
use redjubjub::SpendAuth;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_note_encryption::EphemeralKeyBytes;
use zcash_spec::PrfExpand;
#[cfg(test)]
use rand_core::RngCore;
/// Errors that can occur in the decoding of Sapling spending keys.
pub enum DecodingError {
/// The length of the byte slice provided for decoding was incorrect.
LengthInvalid { expected: usize, actual: usize },
/// Could not decode the `ask` bytes to a jubjub field element.
InvalidAsk,
/// Could not decode the `nsk` bytes to a jubjub field element.
InvalidNsk,
/// An extended spending key had an unsupported child index: either a non-hardened
/// index, or a non-zero index at depth 0.
UnsupportedChildIndex,
}
/// A spend authorizing key, used to create spend authorization signatures.
///
/// $\mathsf{ask}$ as defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Clone, Debug)]
pub struct SpendAuthorizingKey(redjubjub::SigningKey<SpendAuth>);
impl PartialEq for SpendAuthorizingKey {
fn eq(&self, other: &Self) -> bool {
<[u8; 32]>::from(self.0)
.ct_eq(&<[u8; 32]>::from(other.0))
.into()
}
}
impl Eq for SpendAuthorizingKey {}
impl From<&SpendValidatingKey> for jubjub::ExtendedPoint {
fn from(spend_validating_key: &SpendValidatingKey) -> jubjub::ExtendedPoint {
jubjub::ExtendedPoint::from_bytes(&spend_validating_key.to_bytes()).unwrap()
}
}
impl SpendAuthorizingKey {
/// Derives ask from sk. Internal use only, does not enforce all constraints.
fn derive_inner(sk: &[u8]) -> jubjub::Scalar {
jubjub::Scalar::from_bytes_wide(&PrfExpand::SAPLING_ASK.with(sk))
}
/// Constructs a `SpendAuthorizingKey` from a raw scalar.
pub(crate) fn from_scalar(ask: jubjub::Scalar) -> Option<Self> {
if ask.is_zero().into() {
None
} else {
Some(SpendAuthorizingKey(ask.to_bytes().try_into().unwrap()))
}
}
/// Derives a `SpendAuthorizingKey` from a spending key.
fn from_spending_key(sk: &[u8]) -> Option<Self> {
Self::from_scalar(Self::derive_inner(sk))
}
/// Parses a `SpendAuthorizingKey` from its encoded form.
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
<[u8; 32]>::try_from(bytes)
.ok()
.and_then(|b| {
// RedJubjub.Private permits the full set of Jubjub scalars including
// zero. However, a SpendAuthorizingKey is further restricted within the
// Sapling key tree to be a non-zero scalar.
jubjub::Scalar::from_repr(b)
.and_then(|s| {
CtOption::new(
redjubjub::SigningKey::try_from(b)
.expect("RedJubjub permits the set of valid SpendAuthorizingKeys"),
!s.is_zero(),
)
})
.into()
})
.map(SpendAuthorizingKey)
}
/// Converts this spend authorizing key to its serialized form.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
/// Converts this spend authorizing key to a raw scalar.
///
/// Only used for ZIP 32 child derivation.
pub(crate) fn to_scalar(&self) -> jubjub::Scalar {
jubjub::Scalar::from_repr(self.0.into()).unwrap()
}
/// Randomizes this spend authorizing key with the given `randomizer`.
///
/// The resulting key can be used to actually sign a spend.
pub fn randomize(&self, randomizer: &jubjub::Scalar) -> redjubjub::SigningKey<SpendAuth> {
self.0.randomize(randomizer)
}
}
/// A key used to validate spend authorization signatures.
///
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Clone, Debug)]
pub struct SpendValidatingKey(redjubjub::VerificationKey<SpendAuth>);
impl From<&SpendAuthorizingKey> for SpendValidatingKey {
fn from(ask: &SpendAuthorizingKey) -> Self {
SpendValidatingKey((&ask.0).into())
}
}
impl PartialEq for SpendValidatingKey {
fn eq(&self, other: &Self) -> bool {
<[u8; 32]>::from(self.0)
.ct_eq(&<[u8; 32]>::from(other.0))
.into()
}
}
impl Eq for SpendValidatingKey {}
impl SpendValidatingKey {
/// For circuit tests only.
#[cfg(test)]
pub(crate) fn fake_random<R: RngCore>(mut rng: R) -> Self {
loop {
if let Some(k) = Self::from_bytes(&jubjub::SubgroupPoint::random(&mut rng).to_bytes()) {
break k;
}
}
}
/// Only exposed for `zcashd` unit tests.
#[cfg(feature = "temporary-zcashd")]
pub fn temporary_zcash_from_bytes(bytes: &[u8]) -> Option<Self> {
Self::from_bytes(bytes)
}
/// Parses a `SpendValidatingKey` from its encoded form.
pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
<[u8; 32]>::try_from(bytes)
.ok()
.and_then(|b| {
// RedJubjub.Public permits the full set of Jubjub points including the
// identity and cofactors; this is the type used for `rk` in Spend
// descriptions. However, a SpendValidatingKey is further restricted
// within the Sapling key tree to be a non-identity element of the
// prime-order subgroup.
jubjub::SubgroupPoint::from_bytes(&b)
.and_then(|p| {
CtOption::new(
redjubjub::VerificationKey::try_from(b)
.expect("RedJubjub permits the set of valid SpendValidatingKeys"),
!p.is_identity(),
)
})
.into()
})
.map(SpendValidatingKey)
}
/// Converts this spend validating key to its serialized form,
/// `LEBS2OSP_256(repr_J(ak))`.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
/// Randomizes this spend validating key with the given `randomizer`.
pub fn randomize(&self, randomizer: &jubjub::Scalar) -> redjubjub::VerificationKey<SpendAuth> {
self.0.randomize(randomizer)
}
}
/// An outgoing viewing key
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OutgoingViewingKey(pub [u8; 32]);
/// A Sapling expanded spending key
#[derive(Clone)]
pub struct ExpandedSpendingKey {
pub ask: SpendAuthorizingKey,
pub nsk: jubjub::Fr,
pub ovk: OutgoingViewingKey,
}
impl fmt::Debug for ExpandedSpendingKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExpandedSpendingKey")
.finish_non_exhaustive()
}
}
impl ExpandedSpendingKey {
/// Expands a spending key into its components.
///
/// # Panics
///
/// Panics if this spending key expands to `ask = 0`. This has a negligible
/// probability of occurring.
pub fn from_spending_key(sk: &[u8]) -> Self {
let ask =
SpendAuthorizingKey::from_spending_key(sk).expect("negligible chance of ask == 0");
let nsk = jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_NSK.with(sk));
let mut ovk = OutgoingViewingKey([0u8; 32]);
ovk.0
.copy_from_slice(&PrfExpand::SAPLING_OVK.with(sk)[..32]);
ExpandedSpendingKey { ask, nsk, ovk }
}
pub fn proof_generation_key(&self) -> ProofGenerationKey {
ProofGenerationKey {
ak: (&self.ask).into(),
nsk: self.nsk,
}
}
/// Decodes the expanded spending key from its serialized representation
/// as part of the encoding of the extended spending key as defined in
/// [ZIP 32](https://zips.z.cash/zip-0032)
pub fn from_bytes(b: &[u8]) -> Result<Self, DecodingError> {
if b.len() != 96 {
return Err(DecodingError::LengthInvalid {
expected: 96,
actual: b.len(),
});
}
let ask = SpendAuthorizingKey::from_bytes(&b[0..32]).ok_or(DecodingError::InvalidAsk)?;
let nsk = Option::from(jubjub::Fr::from_repr(b[32..64].try_into().unwrap()))
.ok_or(DecodingError::InvalidNsk)?;
let ovk = OutgoingViewingKey(b[64..96].try_into().unwrap());
Ok(ExpandedSpendingKey { ask, nsk, ovk })
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut repr = [0u8; 96];
reader.read_exact(repr.as_mut())?;
Self::from_bytes(&repr).map_err(|e| match e {
DecodingError::InvalidAsk => {
io::Error::new(io::ErrorKind::InvalidData, "ask not in field")
}
DecodingError::InvalidNsk => {
io::Error::new(io::ErrorKind::InvalidData, "nsk not in field")
}
DecodingError::LengthInvalid { .. } | DecodingError::UnsupportedChildIndex => {
unreachable!()
}
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.to_bytes())
}
/// Encodes the expanded spending key to the its seralized representation
/// as part of the encoding of the extended spending key as defined in
/// [ZIP 32](https://zips.z.cash/zip-0032)
pub fn to_bytes(&self) -> [u8; 96] {
let mut result = [0u8; 96];
result[0..32].copy_from_slice(&self.ask.to_bytes());
result[32..64].copy_from_slice(&self.nsk.to_repr());
result[64..96].copy_from_slice(&self.ovk.0);
result
}
}
#[derive(Clone)]
pub struct ProofGenerationKey {
pub ak: SpendValidatingKey,
pub nsk: jubjub::Fr,
}
impl fmt::Debug for ProofGenerationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProofGenerationKey")
.field("ak", &self.ak)
.finish_non_exhaustive()
}
}
impl ProofGenerationKey {
pub fn to_viewing_key(&self) -> ViewingKey {
ViewingKey {
ak: self.ak.clone(),
nk: NullifierDerivingKey(constants::PROOF_GENERATION_KEY_GENERATOR * self.nsk),
}
}
}
/// A key used to derive the nullifier for a Sapling note.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NullifierDerivingKey(pub jubjub::SubgroupPoint);
#[derive(Debug, Clone)]
pub struct ViewingKey {
pub ak: SpendValidatingKey,
pub nk: NullifierDerivingKey,
}
impl ViewingKey {
pub fn rk(&self, ar: jubjub::Fr) -> redjubjub::VerificationKey<SpendAuth> {
self.ak.randomize(&ar)
}
pub fn ivk(&self) -> SaplingIvk {
SaplingIvk(crh_ivk(self.ak.to_bytes(), self.nk.0.to_bytes()))
}
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
self.ivk().to_payment_address(diversifier)
}
}
/// A Sapling key that provides the capability to view incoming and outgoing transactions.
#[derive(Debug)]
pub struct FullViewingKey {
pub vk: ViewingKey,
pub ovk: OutgoingViewingKey,
}
impl Clone for FullViewingKey {
fn clone(&self) -> Self {
FullViewingKey {
vk: ViewingKey {
ak: self.vk.ak.clone(),
nk: self.vk.nk,
},
ovk: self.ovk,
}
}
}
impl FullViewingKey {
pub fn from_expanded_spending_key(expsk: &ExpandedSpendingKey) -> Self {
FullViewingKey {
vk: ViewingKey {
ak: (&expsk.ask).into(),
nk: NullifierDerivingKey(PROOF_GENERATION_KEY_GENERATOR * expsk.nsk),
},
ovk: expsk.ovk,
}
}
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let ak = {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
SpendValidatingKey::from_bytes(&buf)
};
let nk = {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
jubjub::SubgroupPoint::from_bytes(&buf)
};
if ak.is_none() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"ak not of prime order",
));
}
if nk.is_none().into() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"nk not in prime-order subgroup",
));
}
let ak = ak.unwrap();
let nk = NullifierDerivingKey(nk.unwrap());
let mut ovk = [0u8; 32];
reader.read_exact(&mut ovk)?;
Ok(FullViewingKey {
vk: ViewingKey { ak, nk },
ovk: OutgoingViewingKey(ovk),
})
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.vk.ak.to_bytes())?;
writer.write_all(&self.vk.nk.0.to_bytes())?;
writer.write_all(&self.ovk.0)?;
Ok(())
}
pub fn to_bytes(&self) -> [u8; 96] {
let mut result = [0u8; 96];
self.write(&mut result[..])
.expect("should be able to serialize a FullViewingKey");
result
}
}
#[derive(Debug, Clone)]
pub struct SaplingIvk(pub jubjub::Fr);
impl SaplingIvk {
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
let prepared_ivk = PreparedIncomingViewingKey::new(self);
DiversifiedTransmissionKey::derive(&prepared_ivk, &diversifier)
.and_then(|pk_d| PaymentAddress::from_parts(diversifier, pk_d))
}
pub fn to_repr(&self) -> [u8; 32] {
self.0.to_repr()
}
}
/// A Sapling incoming viewing key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedIncomingViewingKey(PreparedScalar);
impl memuse::DynamicUsage for PreparedIncomingViewingKey {
fn dynamic_usage(&self) -> usize {
self.0.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.0.dynamic_usage_bounds()
}
}
impl PreparedIncomingViewingKey {
/// Performs the necessary precomputations to use a `SaplingIvk` for note decryption.
pub fn new(ivk: &SaplingIvk) -> Self {
Self(PreparedScalar::new(&ivk.0))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Diversifier(pub [u8; 11]);
impl Diversifier {
pub fn g_d(&self) -> Option<jubjub::SubgroupPoint> {
diversify_hash(&self.0)
}
}
/// The diversified transmission key for a given payment address.
///
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
///
/// Note that this type is allowed to be the identity in the protocol, but we reject this
/// in [`PaymentAddress::from_parts`].
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DiversifiedTransmissionKey(jubjub::SubgroupPoint);
impl DiversifiedTransmissionKey {
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
///
/// Returns `None` if `d` is an invalid diversifier.
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
pub(crate) fn derive(ivk: &PreparedIncomingViewingKey, d: &Diversifier) -> Option<Self> {
d.g_d()
.map(PreparedBaseSubgroup::new)
.map(|g_d| ka_sapling_derive_public_subgroup_prepared(&ivk.0, &g_d))
.map(DiversifiedTransmissionKey)
}
/// $abst_J(bytes)$
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
jubjub::SubgroupPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
}
/// $repr_J(self)$
pub(crate) fn to_bytes(self) -> [u8; 32] {
self.0.to_bytes()
}
/// Returns true if this is the identity.
pub(crate) fn is_identity(&self) -> bool {
self.0.is_identity().into()
}
/// Exposes the inner Jubjub point.
///
/// This API is exposed for `zcash_proof` usage, and will be removed when this type is
/// refactored into the `sapling-crypto` crate.
pub fn inner(&self) -> jubjub::SubgroupPoint {
self.0
}
}
impl ConditionallySelectable for DiversifiedTransmissionKey {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
DiversifiedTransmissionKey(jubjub::SubgroupPoint::conditional_select(
&a.0, &b.0, choice,
))
}
}
/// An ephemeral secret key used to encrypt an output note on-chain.
///
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
/// practice, `esk` is derived deterministically from the note that it is encrypting.
///
/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{Private} := \mathbb{F}_{r_J}$
///
/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
#[derive(Debug)]
pub struct EphemeralSecretKey(pub(crate) jubjub::Scalar);
impl ConstantTimeEq for EphemeralSecretKey {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.0.ct_eq(&other.0)
}
}
impl EphemeralSecretKey {
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
jubjub::Scalar::from_bytes(bytes).map(EphemeralSecretKey)
}
pub(crate) fn derive_public(&self, g_d: jubjub::ExtendedPoint) -> EphemeralPublicKey {
EphemeralPublicKey(ka_sapling_derive_public(&self.0, &g_d))
}
pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
SharedSecret(ka_sapling_agree(&self.0, &pk_d.0.into()))
}
}
/// An ephemeral public key used to encrypt an output note on-chain.
///
/// `epk` is "ephemeral" in the sense that each public key is only used once. In practice,
/// `epk` is derived deterministically from the note that it is encrypting.
///
/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{Public} := \mathbb{J}$
///
/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
#[derive(Debug)]
pub struct EphemeralPublicKey(jubjub::ExtendedPoint);
impl EphemeralPublicKey {
pub(crate) fn from_affine(epk: jubjub::AffinePoint) -> Self {
EphemeralPublicKey(epk.into())
}
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
jubjub::ExtendedPoint::from_bytes(bytes).map(EphemeralPublicKey)
}
pub(crate) fn to_bytes(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.0.to_bytes())
}
}
/// A Sapling ephemeral public key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedEphemeralPublicKey(PreparedBase);
impl PreparedEphemeralPublicKey {
pub(crate) fn new(epk: EphemeralPublicKey) -> Self {
PreparedEphemeralPublicKey(PreparedBase::new(epk.0))
}
pub(crate) fn agree(&self, ivk: &PreparedIncomingViewingKey) -> SharedSecret {
SharedSecret(ka_sapling_agree_prepared(&ivk.0, &self.0))
}
}
/// $\mathsf{KA}^\mathsf{Sapling}.\mathsf{SharedSecret} := \mathbb{J}^{(r)}$
///
/// Defined in [section 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
#[derive(Debug)]
pub struct SharedSecret(jubjub::SubgroupPoint);
impl SharedSecret {
/// For checking test vectors only.
#[cfg(test)]
pub(crate) fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
/// Only for use in batched note encryption.
pub(crate) fn batch_to_affine(
shared_secrets: Vec<Option<Self>>,
) -> impl Iterator<Item = Option<jubjub::AffinePoint>> {
// Filter out the positions for which ephemeral_key was not a valid encoding.
let secrets: Vec<_> = shared_secrets
.iter()
.filter_map(|s| s.as_ref().map(|s| jubjub::ExtendedPoint::from(s.0)))
.collect();
// Batch-normalize the shared secrets.
let mut secrets_affine = vec![jubjub::AffinePoint::identity(); secrets.len()];
group::Curve::batch_normalize(&secrets, &mut secrets_affine);
// Re-insert the invalid ephemeral_key positions.
let mut secrets_affine = secrets_affine.into_iter();
shared_secrets
.into_iter()
.map(move |s| s.and_then(|_| secrets_affine.next()))
}
/// Defined in [Zcash Protocol Spec § 5.4.5.4: Sapling Key Agreement][concretesaplingkdf].
///
/// [concretesaplingkdf]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkdf
pub(crate) fn kdf_sapling(self, ephemeral_key: &EphemeralKeyBytes) -> Blake2bHash {
Self::kdf_sapling_inner(
jubjub::ExtendedPoint::from(self.0).to_affine(),
ephemeral_key,
)
}
/// Only for direct use in batched note encryption.
pub(crate) fn kdf_sapling_inner(
secret: jubjub::AffinePoint,
ephemeral_key: &EphemeralKeyBytes,
) -> Blake2bHash {
Blake2bParams::new()
.hash_length(32)
.personal(KDF_SAPLING_PERSONALIZATION)
.to_state()
.update(&secret.to_bytes())
.update(ephemeral_key.as_ref())
.finalize()
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use proptest::collection::vec;
use proptest::prelude::*;
use super::{ExpandedSpendingKey, FullViewingKey, SaplingIvk};
prop_compose! {
pub fn arb_expanded_spending_key()(v in vec(any::<u8>(), 32..252)) -> ExpandedSpendingKey {
ExpandedSpendingKey::from_spending_key(&v)
}
}
prop_compose! {
pub fn arb_full_viewing_key()(sk in arb_expanded_spending_key()) -> FullViewingKey {
FullViewingKey::from_expanded_spending_key(&sk)
}
}
prop_compose! {
pub fn arb_incoming_viewing_key()(fvk in arb_full_viewing_key()) -> SaplingIvk {
fvk.vk.ivk()
}
}
}
#[cfg(test)]
mod tests {
use group::{Group, GroupEncoding};
use super::{FullViewingKey, SpendAuthorizingKey, SpendValidatingKey};
use crate::{constants::SPENDING_KEY_GENERATOR, test_vectors};
#[test]
fn ak_must_be_prime_order() {
let mut buf = [0; 96];
let identity = jubjub::SubgroupPoint::identity();
// Set both ak and nk to the identity.
buf[0..32].copy_from_slice(&identity.to_bytes());
buf[32..64].copy_from_slice(&identity.to_bytes());
// ak is not allowed to be the identity.
assert_eq!(
FullViewingKey::read(&buf[..]).unwrap_err().to_string(),
"ak not of prime order"
);
// Set ak to a basepoint.
let basepoint = SPENDING_KEY_GENERATOR;
buf[0..32].copy_from_slice(&basepoint.to_bytes());
// nk is allowed to be the identity.
assert!(FullViewingKey::read(&buf[..]).is_ok());
}
#[test]
fn spend_auth_sig_test_vectors() {
for tv in test_vectors::signatures::make_test_vectors() {
let sk = SpendAuthorizingKey::from_bytes(&tv.sk).unwrap();
let vk = SpendValidatingKey::from_bytes(&tv.vk).unwrap();
let rvk = redjubjub::VerificationKey::try_from(tv.rvk).unwrap();
let sig = redjubjub::Signature::from(tv.sig);
let rsig = redjubjub::Signature::from(tv.rsig);
let alpha = jubjub::Scalar::from_bytes(&tv.alpha).unwrap();
assert_eq!(<[u8; 32]>::from(sk.randomize(&alpha)), tv.rsk);
assert_eq!(vk.randomize(&alpha), rvk);
// assert_eq!(vk.0.verify(&tv.m, &sig), Ok(()));
// assert_eq!(rvk.verify(&tv.m, &rsig), Ok(()));
assert_eq!(
vk.0.verify(&tv.m, &rsig),
Err(redjubjub::Error::InvalidSignature),
);
assert_eq!(
rvk.verify(&tv.m, &sig),
Err(redjubjub::Error::InvalidSignature),
);
}
}
}

View File

@ -1,22 +1,39 @@
extern crate pairing;
extern crate bellman;
extern crate blake2_rfc;
extern crate digest;
extern crate rand;
extern crate byteorder;
//! Structs and constants specific to the Sapling shielded pool.
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
#[cfg(test)]
extern crate crypto;
pub mod jubjub;
pub mod group_hash;
mod address;
pub mod builder;
pub mod bundle;
pub mod circuit;
pub mod pedersen_hash;
pub mod primitives;
pub mod constants;
pub mod redjubjub;
pub mod group_hash;
pub mod keys;
pub mod note;
pub mod note_encryption;
pub mod pedersen_hash;
pub mod prover;
mod spec;
mod tree;
pub mod util;
pub mod value;
mod verifier;
pub mod zip32;
pub use address::PaymentAddress;
pub use bundle::Bundle;
pub use keys::{Diversifier, NullifierDerivingKey, ProofGenerationKey, SaplingIvk, ViewingKey};
pub use note::{nullifier::Nullifier, Note, Rseed};
pub use tree::{
merkle_hash, CommitmentTree, IncrementalWitness, MerklePath, Node, NOTE_COMMITMENT_TREE_DEPTH,
};
pub use verifier::{BatchValidator, SaplingVerificationContext};
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
pub use super::{
address::testing::arb_payment_address, keys::testing::arb_incoming_viewing_key,
note::testing::arb_note, tree::testing::arb_node,
};
}
#[cfg(test)]
mod test_vectors;

184
src/note.rs Normal file
View File

@ -0,0 +1,184 @@
use group::{ff::Field, GroupEncoding};
use rand_core::{CryptoRng, RngCore};
use zcash_spec::PrfExpand;
use super::{
keys::EphemeralSecretKey, value::NoteValue, Nullifier, NullifierDerivingKey, PaymentAddress,
};
mod commitment;
pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
pub(super) mod nullifier;
/// Enum for note randomness before and after [ZIP 212](https://zips.z.cash/zip-0212).
///
/// Before ZIP 212, the note commitment trapdoor `rcm` must be a scalar value.
/// After ZIP 212, the note randomness `rseed` is a 32-byte sequence, used to derive
/// both the note commitment trapdoor `rcm` and the ephemeral private key `esk`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Rseed {
BeforeZip212(jubjub::Fr),
AfterZip212([u8; 32]),
}
impl Rseed {
/// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
///
/// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
pub(crate) fn rcm(&self) -> commitment::NoteCommitTrapdoor {
commitment::NoteCommitTrapdoor(match self {
Rseed::BeforeZip212(rcm) => *rcm,
Rseed::AfterZip212(rseed) => {
jubjub::Fr::from_bytes_wide(&PrfExpand::SAPLING_RCM.with(rseed))
}
})
}
}
/// A discrete amount of funds received by an address.
#[derive(Clone, Debug)]
pub struct Note {
/// The recipient of the funds.
recipient: PaymentAddress,
/// The value of this note.
value: NoteValue,
/// The seed randomness for various note components.
rseed: Rseed,
}
impl PartialEq for Note {
fn eq(&self, other: &Self) -> bool {
// Notes are canonically defined by their commitments.
self.cmu().eq(&other.cmu())
}
}
impl Eq for Note {}
impl Note {
/// Creates a note from its component parts.
///
/// # Caveats
///
/// This low-level constructor enforces that the provided arguments produce an
/// internally valid `Note`. However, it allows notes to be constructed in a way that
/// violates required security checks for note decryption, as specified in
/// [Section 4.19] of the Zcash Protocol Specification. Users of this constructor
/// should only call it with note components that have been fully validated by
/// decrypting a received note according to [Section 4.19].
///
/// [Section 4.19]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband
pub fn from_parts(recipient: PaymentAddress, value: NoteValue, rseed: Rseed) -> Self {
Note {
recipient,
value,
rseed,
}
}
/// Returns the recipient of this note.
pub fn recipient(&self) -> PaymentAddress {
self.recipient
}
/// Returns the value of this note.
pub fn value(&self) -> NoteValue {
self.value
}
/// Returns the rseed value of this note.
pub fn rseed(&self) -> &Rseed {
&self.rseed
}
/// Computes the note commitment, returning the full point.
fn cm_full_point(&self) -> NoteCommitment {
NoteCommitment::derive(
self.recipient.g_d().to_bytes(),
self.recipient.pk_d().to_bytes(),
self.value,
self.rseed.rcm(),
)
}
/// Computes the nullifier given the nullifier deriving key and
/// note position
pub fn nf(&self, nk: &NullifierDerivingKey, position: u64) -> Nullifier {
Nullifier::derive(nk, self.cm_full_point(), position)
}
/// Computes the note commitment
pub fn cmu(&self) -> ExtractedNoteCommitment {
self.cm_full_point().into()
}
/// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
///
/// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
pub fn rcm(&self) -> jubjub::Fr {
self.rseed.rcm().0
}
/// Derives `esk` from the internal `Rseed` value, or generates a random value if this
/// note was created with a v1 (i.e. pre-ZIP 212) note plaintext.
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
) -> EphemeralSecretKey {
self.generate_or_derive_esk_internal(rng)
}
pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(
&self,
rng: &mut R,
) -> EphemeralSecretKey {
match self.derive_esk() {
None => EphemeralSecretKey(jubjub::Fr::random(rng)),
Some(esk) => esk,
}
}
/// Returns the derived `esk` if this note was created after ZIP 212 activated.
pub(crate) fn derive_esk(&self) -> Option<EphemeralSecretKey> {
match self.rseed {
Rseed::BeforeZip212(_) => None,
Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide(
&PrfExpand::SAPLING_ESK.with(&rseed),
))),
}
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub(super) mod testing {
use proptest::{collection::vec, prelude::*};
use super::{
super::{testing::arb_payment_address, value::NoteValue},
ExtractedNoteCommitment, Note, Rseed,
};
prop_compose! {
pub fn arb_note(value: NoteValue)(
recipient in arb_payment_address(),
rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
) -> Note {
Note {
recipient,
value,
rseed
}
}
}
prop_compose! {
pub(crate) fn arb_cmu()(
cmu in vec(any::<u8>(), 64)
.prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
.prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
) -> ExtractedNoteCommitment {
ExtractedNoteCommitment(cmu)
}
}
}

110
src/note/commitment.rs Normal file
View File

@ -0,0 +1,110 @@
use core::iter;
use bitvec::{array::BitArray, order::Lsb0};
use group::ff::PrimeField;
use subtle::{ConstantTimeEq, CtOption};
use crate::{
pedersen_hash::Personalization,
spec::{extract_p, windowed_pedersen_commit},
value::NoteValue,
};
/// The trapdoor for a Sapling note commitment.
#[derive(Clone, Debug)]
pub(crate) struct NoteCommitTrapdoor(pub(super) jubjub::Fr);
/// A commitment to a note.
#[derive(Clone, Debug)]
pub struct NoteCommitment(jubjub::SubgroupPoint);
impl NoteCommitment {
pub(crate) fn inner(&self) -> jubjub::SubgroupPoint {
self.0
}
}
impl NoteCommitment {
/// Derives a Sapling note commitment.
#[cfg(feature = "temporary-zcashd")]
pub fn temporary_zcashd_derive(
g_d: [u8; 32],
pk_d: [u8; 32],
v: NoteValue,
rcm: jubjub::Fr,
) -> Self {
Self::derive(g_d, pk_d, v, NoteCommitTrapdoor(rcm))
}
/// $NoteCommit^Sapling$.
///
/// Defined in [Zcash Protocol Spec § 5.4.8.2: Windowed Pedersen commitments][concretewindowedcommit].
///
/// [concretewindowedcommit]: https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit
pub(super) fn derive(
g_d: [u8; 32],
pk_d: [u8; 32],
v: NoteValue,
rcm: NoteCommitTrapdoor,
) -> Self {
NoteCommitment(windowed_pedersen_commit(
Personalization::NoteCommitment,
iter::empty()
.chain(v.to_le_bits().iter().by_vals())
.chain(BitArray::<_, Lsb0>::new(g_d).iter().by_vals())
.chain(BitArray::<_, Lsb0>::new(pk_d).iter().by_vals()),
rcm.0,
))
}
}
/// The u-coordinate of the commitment to a note.
#[derive(Copy, Clone, Debug)]
pub struct ExtractedNoteCommitment(pub(super) bls12_381::Scalar);
impl ExtractedNoteCommitment {
/// Deserialize the extracted note commitment from a byte array.
///
/// This method enforces the [consensus rule][cmucanon] that the byte representation
/// of cmu MUST be canonical.
///
/// [cmucanon]: https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
bls12_381::Scalar::from_repr(*bytes).map(ExtractedNoteCommitment)
}
/// Serialize the value commitment to its canonical byte representation.
pub fn to_bytes(self) -> [u8; 32] {
self.0.to_repr()
}
pub(crate) fn inner(&self) -> jubjub::Base {
self.0
}
}
impl From<NoteCommitment> for ExtractedNoteCommitment {
fn from(cm: NoteCommitment) -> Self {
ExtractedNoteCommitment(extract_p(&cm.0))
}
}
impl From<&ExtractedNoteCommitment> for [u8; 32] {
fn from(cmu: &ExtractedNoteCommitment) -> Self {
cmu.to_bytes()
}
}
impl ConstantTimeEq for ExtractedNoteCommitment {
fn ct_eq(&self, other: &Self) -> subtle::Choice {
self.0.ct_eq(&other.0)
}
}
impl PartialEq for ExtractedNoteCommitment {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for ExtractedNoteCommitment {}

54
src/note/nullifier.rs Normal file
View File

@ -0,0 +1,54 @@
use std::array::TryFromSliceError;
use std::fmt;
use subtle::{Choice, ConstantTimeEq};
use super::NoteCommitment;
use crate::{
keys::NullifierDerivingKey,
spec::{mixing_pedersen_hash, prf_nf},
};
/// Typesafe wrapper for nullifier values.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Nullifier(pub [u8; 32]);
impl fmt::Debug for Nullifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Nullifier")
.field(&hex::encode(self.0))
.finish()
}
}
impl Nullifier {
pub fn from_slice(bytes: &[u8]) -> Result<Nullifier, TryFromSliceError> {
bytes.try_into().map(Nullifier)
}
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
/// $DeriveNullifier$.
///
/// Defined in [Zcash Protocol Spec § 4.16: Note Commitments and Nullifiers][commitmentsandnullifiers].
///
/// [commitmentsandnullifiers]: https://zips.z.cash/protocol/protocol.pdf#commitmentsandnullifiers
pub(super) fn derive(nk: &NullifierDerivingKey, cm: NoteCommitment, position: u64) -> Self {
let rho = mixing_pedersen_hash(cm.inner(), position);
Nullifier(prf_nf(&nk.0, &rho))
}
}
impl AsRef<[u8]> for Nullifier {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl ConstantTimeEq for Nullifier {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}

1515
src/note_encryption.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,27 @@
use jubjub::*;
use pairing::*;
//! Implementation of the Pedersen hash function used in Sapling.
#[cfg(test)]
pub(crate) mod test_vectors;
use byteorder::{ByteOrder, LittleEndian};
use ff::PrimeField;
use group::Group;
use std::ops::{AddAssign, Neg};
use super::constants::{
PEDERSEN_HASH_CHUNKS_PER_GENERATOR, PEDERSEN_HASH_EXP_TABLE, PEDERSEN_HASH_EXP_WINDOW_SIZE,
};
#[derive(Copy, Clone)]
pub enum Personalization {
NoteCommitment,
MerkleTree(usize)
MerkleTree(usize),
}
impl Personalization {
pub fn get_bits(&self) -> Vec<bool> {
match *self {
Personalization::NoteCommitment =>
vec![true, true, true, true, true, true],
Personalization::NoteCommitment => vec![true, true, true, true, true, true],
Personalization::MerkleTree(num) => {
assert!(num < 63);
@ -21,23 +31,22 @@ impl Personalization {
}
}
pub fn pedersen_hash<E, I>(
personalization: Personalization,
bits: I,
params: &E::Params
) -> edwards::Point<E, PrimeOrder>
where I: IntoIterator<Item=bool>,
E: JubjubEngine
pub fn pedersen_hash<I>(personalization: Personalization, bits: I) -> jubjub::SubgroupPoint
where
I: IntoIterator<Item = bool>,
{
let mut bits = personalization.get_bits().into_iter().chain(bits.into_iter());
let mut bits = personalization
.get_bits()
.into_iter()
.chain(bits.into_iter());
let mut result = edwards::Point::zero();
let mut generators = params.pedersen_hash_exp_table().iter();
let mut result = jubjub::SubgroupPoint::identity();
let mut generators = PEDERSEN_HASH_EXP_TABLE.iter();
loop {
let mut acc = E::Fs::zero();
let mut cur = E::Fs::one();
let mut chunks_remaining = params.pedersen_hash_chunks_per_generator();
let mut acc = jubjub::Fr::zero();
let mut cur = jubjub::Fr::one();
let mut chunks_remaining = PEDERSEN_HASH_CHUNKS_PER_GENERATOR;
let mut encountered_bits = false;
// Grab three bits from the input
@ -52,14 +61,14 @@ pub fn pedersen_hash<E, I>(
if a {
tmp.add_assign(&cur);
}
cur.double(); // 2^1 * cur
cur = cur.double(); // 2^1 * cur
if b {
tmp.add_assign(&cur);
}
// conditionally negate
if c {
tmp.negate();
tmp = tmp.neg();
}
acc.add_assign(&tmp);
@ -69,9 +78,7 @@ pub fn pedersen_hash<E, I>(
if chunks_remaining == 0 {
break;
} else {
cur.double(); // 2^2 * cur
cur.double(); // 2^3 * cur
cur.double(); // 2^4 * cur
cur = cur.double().double().double(); // 2^4 * cur
}
}
@ -79,25 +86,75 @@ pub fn pedersen_hash<E, I>(
break;
}
let mut table: &[Vec<edwards::Point<E, _>>] = &generators.next().expect("we don't have enough generators");
let window = JubjubBls12::pedersen_hash_exp_window_size();
let window_mask = (1 << window) - 1;
let mut table: &[Vec<jubjub::SubgroupPoint>] =
generators.next().expect("we don't have enough generators");
let window = PEDERSEN_HASH_EXP_WINDOW_SIZE as usize;
let window_mask = (1u64 << window) - 1;
let mut acc = acc.into_repr();
let mut tmp = edwards::Point::zero();
let acc = acc.to_repr();
let num_limbs: usize = acc.as_ref().len() / 8;
let mut limbs = vec![0u64; num_limbs + 1];
LittleEndian::read_u64_into(acc.as_ref(), &mut limbs[..num_limbs]);
while !acc.is_zero() {
let i = (acc.as_ref()[0] & window_mask) as usize;
let mut tmp = jubjub::SubgroupPoint::identity();
tmp = tmp.add(&table[0][i], params);
let mut pos = 0;
while pos < jubjub::Fr::NUM_BITS as usize {
let u64_idx = pos / 64;
let bit_idx = pos % 64;
let i = (if bit_idx + window < 64 {
// This window's bits are contained in a single u64.
limbs[u64_idx] >> bit_idx
} else {
// Combine the current u64's bits with the bits from the next u64.
(limbs[u64_idx] >> bit_idx) | (limbs[u64_idx + 1] << (64 - bit_idx))
} & window_mask) as usize;
acc.shr(window);
tmp += table[0][i];
pos += window;
table = &table[1..];
}
result = result.add(&tmp, params);
result += tmp;
}
result
}
#[cfg(test)]
pub mod test {
use group::Curve;
use super::*;
pub struct TestVector<'a> {
pub personalization: Personalization,
pub input_bits: Vec<u8>,
pub hash_u: &'a str,
pub hash_v: &'a str,
}
#[test]
fn test_pedersen_hash_points() {
let test_vectors = test_vectors::get_vectors();
assert!(!test_vectors.is_empty());
for v in test_vectors.iter() {
let input_bools: Vec<bool> = v.input_bits.iter().map(|&i| i == 1).collect();
// The 6 bits prefix is handled separately
assert_eq!(v.personalization.get_bits(), &input_bools[..6]);
let p = jubjub::ExtendedPoint::from(pedersen_hash(
v.personalization,
input_bools.into_iter().skip(6),
))
.to_affine();
assert_eq!(p.get_u().to_string(), v.hash_u);
assert_eq!(p.get_v().to_string(), v.hash_v);
}
}
}

View File

@ -0,0 +1,715 @@
//! Test vectors from https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_pedersen.py
use super::{test::TestVector, Personalization};
pub fn get_vectors<'a>() -> Vec<TestVector<'a>> {
vec![
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![1, 1, 1, 1, 1, 1],
hash_u: "0x06b1187c11ca4fb4383b2e0d0dbbde3ad3617338b5029187ec65a5eaed5e4d0b",
hash_v: "0x3ce70f536652f0dea496393a1e55c4e08b9d55508e16d11e5db40d4810cbc982",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![1, 1, 1, 1, 1, 1, 0],
hash_u: "0x2fc3bc454c337f71d4f04f86304262fcbfc9ecd808716b92fc42cbe6827f7f1a",
hash_v: "0x46d0d25bf1a654eedc6a9b1e5af398925113959feac31b7a2c036ff9b9ec0638",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![1, 1, 1, 1, 1, 1, 1],
hash_u: "0x4f8ce0e0a9e674b3ab9606a7d7aefba386e81583d81918127814cde41d209d97",
hash_v: "0x312b5ab93b14c9b9af334fe1fe3c50fffb53fbd074fa40ca600febde7c97e346",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![1, 1, 1, 1, 1, 1, 1, 0, 0],
hash_u: "0x4f8ce0e0a9e674b3ab9606a7d7aefba386e81583d81918127814cde41d209d97",
hash_v: "0x312b5ab93b14c9b9af334fe1fe3c50fffb53fbd074fa40ca600febde7c97e346",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1,
0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1,
1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1,
0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
],
hash_u: "0x599ab788360ae8c6d5bb7618aec37056d6227408d857fdc394078a3d7afdfe0f",
hash_v: "0x4320c373da670e28d168f4ffd72b43208e8c815f40841682c57a3ee1d005a527",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0,
1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1,
1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0,
1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
],
hash_u: "0x2da510317620f5dfdce1f31db6019f947eedcf02ff2972cff597a5c3ad21f5dd",
hash_v: "0x198789969c0c33e6c359b9da4a51771f4d50863f36beef90436944fe568399f2",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1,
],
hash_u: "0x601247c7e640992d193dfb51df6ed93446687a7f2bcd0e4a598e6feb1ef20c40",
hash_v: "0x371931733b73e7b95c2cad55a6cebd15c83619f697c64283e54e5ef61442a743",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0,
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1,
1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1,
0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1,
1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0,
1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0,
1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1,
1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0,
0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0,
1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1,
0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
],
hash_u: "0x314192ecb1f2d8806a8108704c875a25d9fb7e444f9f373919adedebe8f2ae27",
hash_v: "0x6b12b32f1372ad574799dee9eb591d961b704bf611f55fcc71f7e82cd3330b74",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0,
0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1,
0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0,
0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0,
1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0,
1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0,
1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1,
1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0,
1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1,
1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0,
0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1,
0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1,
1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1,
0,
],
hash_u: "0x0666c2bce7f362a2b807d212e9a577f116891a932affd7addec39fbf372c494e",
hash_v: "0x6758bccfaf2e47c07756b96edea23aa8d10c33b38220bd1c411af612eeec18ab",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0,
0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1,
1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,
0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1,
0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1,
1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0,
1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0,
0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1,
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1,
1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0,
1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0,
1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1,
],
hash_u: "0x130afe02b99375484efb0998f5331d2178e1d00e803049bb0769099420624f5f",
hash_v: "0x5e2fc6970554ffe358652aa7968ac4fcf3de0c830e6ea492e01a38fafb68cd71",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0,
1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1,
0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0,
0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0,
1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0,
1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1,
1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0,
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1,
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1,
1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1,
1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1,
0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0,
1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1,
1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0,
1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0,
1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0,
0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1,
],
hash_u: "0x67914ebd539961b70f468fa23d4cb42133693a8ac57cd35a1e6369fe34fbedf7",
hash_v: "0x44770870c0f0cfe59a10df95d6c21e6f1514a2f464b66377599438c126052d9f",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![0, 0, 0, 0, 0, 0],
hash_u: "0x62454a957289b3930d10f3def0d512cfe0ef3de06421321221af3558de9d481d",
hash_v: "0x0279f0aebfb66e53ff69fba16b6608dbf4319b944432f45c6e69a3dbd1f7b330",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![0, 0, 0, 0, 0, 0, 0],
hash_u: "0x283c7880f35179e201161402d9c4556b255917dbbf0142ae60519787d36d4dea",
hash_v: "0x648224408b4b83297cd0feb4cdc4eeb224237734931145432793bcd414228dc4",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![0, 0, 0, 0, 0, 0, 1],
hash_u: "0x1f1086b287636a20063c9614db2de66bb7d49242e88060956a5e5845057f6f5d",
hash_v: "0x6b1b395421dde74d53341caa9e01f39d7a3138efb9b57fc0381f98f4868df622",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![0, 0, 0, 0, 0, 0, 1, 0, 0],
hash_u: "0x1f1086b287636a20063c9614db2de66bb7d49242e88060956a5e5845057f6f5d",
hash_v: "0x6b1b395421dde74d53341caa9e01f39d7a3138efb9b57fc0381f98f4868df622",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1,
1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1,
0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0,
],
hash_u: "0x20d2b1b0551efe511755d564f8da4f5bf285fd6051331fa5f129ad95b318f6cd",
hash_v: "0x2834d96950de67ae80e85545f8333c6e14b5cf5be7325dac768f401e6edd9544",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1,
1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1,
1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0,
0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0,
0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0,
],
hash_u: "0x01f4850a0f40e07186fee1f0a276f52fb12cffe05c18eb2aa18170330a93c555",
hash_v: "0x19b0807358e7c8cba9168815ec54c4cd76997c34c592607d172151c48d5377cb",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1,
0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1,
0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1,
1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1,
0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0,
],
hash_u: "0x26dd81a3ffa37452c6a932d41eb4f2e0fedd531e9af8c2a7935b91dff653879d",
hash_v: "0x2fc7aebb729ef5cabf0fb3f883bc2eb2603093850b0ec19c1a3c08b653e7f27f",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0,
1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1,
1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1,
1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0,
1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0,
0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1,
0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,
1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1,
0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0,
0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1,
0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1,
0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1,
],
hash_u: "0x1111740552773b00aa6a2334575aa94102cfbd084290a430c90eb56d6db65b85",
hash_v: "0x6560c44b11683c20030626f89456f78a53ae8a89f565956a98ffc554b48fbb1a",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0,
0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0,
0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1,
0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0,
0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1,
1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1,
1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0,
1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1,
0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1,
0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0,
1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1,
1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0,
1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0,
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1,
1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1,
1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0,
],
hash_u: "0x429349ea9b5f8163bcda3014b3e15554df5173353fd73f315a49360c97265f68",
hash_v: "0x188774bb6de41eba669be5d368942783f937acf2f418385fc5c78479b0a405ee",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1,
0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1,
1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1,
0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0,
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0,
1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,
0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1,
1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1,
1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1,
0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1,
0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0,
],
hash_u: "0x00e827f3ed136f3c91c61c97ab9b7cca0ea53c20e47abb5e226ede297bdd5f37",
hash_v: "0x315cc00a54972df6a19f650d3fab5f2ad0fb07397bacb6944568618f2aa76bf6",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0,
0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1,
0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0,
1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1,
1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1,
1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1,
0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1,
1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0,
0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1,
1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0,
],
hash_u: "0x3ee50557c4aa9158c4bb9d5961208e6c62f55c73ad7c7695a0eba0bcb6d83d05",
hash_v: "0x1b1a2be6e47688828aeadf2d37db298eac0c2736c2722b227871fdeeee29de33",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![0, 1, 0, 0, 0, 1],
hash_u: "0x61f8e2cb8e945631677b450d5e5669bc6b5f2ec69b321ac550dbe74525d7ac9a",
hash_v: "0x4e11951ab9c9400ee38a18bd98cdb9453f1f67141ee9d9bf0c1c157d4fb34f9a",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![0, 1, 0, 0, 0, 1, 0],
hash_u: "0x27fa1e296c37dde8448483ce5485c2604d1d830e53812246299773a02ecd519c",
hash_v: "0x08e499113675202cb42b4b681a31430814edebd72c5bb3bc3bfedf91fb0605df",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![0, 1, 0, 0, 0, 1, 1],
hash_u: "0x52112dd7a4293d049bb011683244a0f957e6ba95e1d1cf2fb6654d449a6d3fbc",
hash_v: "0x2ae14ecd81bb5b4489d2d64b5d2eb92a684087b28dd9a4950ecdb78c014e178c",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![0, 1, 0, 0, 0, 1, 1, 0, 0],
hash_u: "0x52112dd7a4293d049bb011683244a0f957e6ba95e1d1cf2fb6654d449a6d3fbc",
hash_v: "0x2ae14ecd81bb5b4489d2d64b5d2eb92a684087b28dd9a4950ecdb78c014e178c",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,
1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0,
1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0,
0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
],
hash_u: "0x544a0b44c35dca64ee806d1af70b7c44134e5d86efed413947657ffd71adf9b2",
hash_v: "0x5ddc5dbf12abbbc5561defd3782a32f450b3c398f52ff4629677e59e86e3ab31",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1,
0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0,
0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
],
hash_u: "0x6cb6490ccb0ca9ccd657146f58a7b800bc4fb2556ee37861227ee8fda724acfb",
hash_v: "0x05c6fe100926f5cc441e54e72f024b6b12c907f2ec5680335057896411984c9f",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1,
0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1,
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0,
],
hash_u: "0x40901e2175cb7f06a00c676d54d90e59fd448f11cbbc5eb517f9fea74b795ce2",
hash_v: "0x42d512891f91087310c9bc630c8d0ecc014596f884fd6df55dada8195ed726de",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0,
0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1,
1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1,
1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1,
0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0,
1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1,
1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0,
1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0,
1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1,
0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
],
hash_u: "0x66a433542419f1a086ed0663b0e8df2ece9a04065f147896976baba1a916b6dc",
hash_v: "0x203bd3672522e1d3c86fa6b9f3b58f20199a4216adfd40982add13a856f6f3de",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1,
0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1,
1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1,
0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0,
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0,
1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1,
1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1,
0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1,
1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1,
1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0,
1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0,
1,
],
hash_u: "0x119db3b38086c1a3c6c6f53c529ee62d9311d69c2d8aeeafa6e172e650d3afda",
hash_v: "0x72287540be7d2b0f58f5c73eaa53c55bea6b79dd79873b4e47cc11787bb9a15d",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1,
1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1,
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0,
1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0,
0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0,
1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0,
1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0,
1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1,
0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0,
1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1,
1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0,
0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0,
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1,
1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1,
1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0,
],
hash_u: "0x446efdcf89b70ba2b03427a0893008181d0fc4e76b84b1a500d7ee523c8e3666",
hash_v: "0x125ee0048efb0372b92c3c15d51a7c5c77a712054cc4fdd0774563da46ec7289",
},
TestVector {
personalization: Personalization::MerkleTree(34),
input_bits: vec![
0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1,
0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0,
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0,
1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0,
0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0,
0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0,
1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1,
1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0,
0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1,
1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1,
1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1,
1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0,
0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0,
1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1,
0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1,
0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0,
0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1,
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1,
],
hash_u: "0x72723bf0573bcb4b72d4184cfeb707d9556b7f705f56a4652707a36f2edf10f7",
hash_v: "0x3a7f0999a6a1393bd49fc82302e7352e01176fbebb0192bf5e6ef39eb8c585ad",
},
TestVector {
personalization: Personalization::MerkleTree(27),
input_bits: vec![
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
],
hash_u: "0x414f6ba05f6b92da1f9051950769e1083d05615def32b016ae424309828a11f4",
hash_v: "0x471d2109656afcb96d0609b371b132b97efcf72c6051064dd19fdc004799bfa9",
},
TestVector {
personalization: Personalization::MerkleTree(36),
input_bits: vec![
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
],
hash_u: "0x62d6fe1e373225a5695f3115aed8265c59e2d6275ceef6bbc53fde3fc6594024",
hash_v: "0x407275be7d5a4c48204c8d83f5b211d09a2f285d4f0f87a928d4de9a6338e1d1",
},
TestVector {
personalization: Personalization::MerkleTree(0),
input_bits: vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
hash_u: "0x1116a934f26b57a2c9daa6f25ac9b1a8f9dacddba30f65433ac021bf39a6bfdd",
hash_v: "0x407275be7d5a4c48204c8d83f5b211d09a2f285d4f0f87a928d4de9a6338e1d1",
},
TestVector {
personalization: Personalization::NoteCommitment,
input_bits: vec![
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
],
hash_u: "0x329e3bb2ca31ea6e13a986730237f6fd16b842a510cbabe851bdbcf57d75ee0d",
hash_v: "0x471d2109656afcb96d0609b371b132b97efcf72c6051064dd19fdc004799bfa9",
},
]
}

View File

@ -1,258 +0,0 @@
use pairing::{
Field,
PrimeField,
PrimeFieldRepr
};
use constants;
use group_hash::group_hash;
use pedersen_hash::{
pedersen_hash,
Personalization
};
use byteorder::{
LittleEndian,
WriteBytesExt
};
use jubjub::{
JubjubEngine,
JubjubParams,
edwards,
PrimeOrder,
FixedGenerators
};
use blake2_rfc::blake2s::Blake2s;
#[derive(Clone)]
pub struct ValueCommitment<E: JubjubEngine> {
pub value: u64,
pub randomness: E::Fs
}
impl<E: JubjubEngine> ValueCommitment<E> {
pub fn cm(
&self,
params: &E::Params
) -> edwards::Point<E, PrimeOrder>
{
params.generator(FixedGenerators::ValueCommitmentValue)
.mul(self.value, params)
.add(
&params.generator(FixedGenerators::ValueCommitmentRandomness)
.mul(self.randomness, params),
params
)
}
}
#[derive(Clone)]
pub struct ProofGenerationKey<E: JubjubEngine> {
pub ak: edwards::Point<E, PrimeOrder>,
pub nsk: E::Fs
}
impl<E: JubjubEngine> ProofGenerationKey<E> {
pub fn into_viewing_key(&self, params: &E::Params) -> ViewingKey<E> {
ViewingKey {
ak: self.ak.clone(),
nk: params.generator(FixedGenerators::ProofGenerationKey)
.mul(self.nsk, params)
}
}
}
pub struct ViewingKey<E: JubjubEngine> {
pub ak: edwards::Point<E, PrimeOrder>,
pub nk: edwards::Point<E, PrimeOrder>
}
impl<E: JubjubEngine> ViewingKey<E> {
pub fn rk(
&self,
ar: E::Fs,
params: &E::Params
) -> edwards::Point<E, PrimeOrder> {
self.ak.add(
&params.generator(FixedGenerators::SpendingKeyGenerator)
.mul(ar, params),
params
)
}
pub fn ivk(&self) -> E::Fs {
let mut preimage = [0; 64];
self.ak.write(&mut preimage[0..32]).unwrap();
self.nk.write(&mut preimage[32..64]).unwrap();
let mut h = Blake2s::with_params(32, &[], &[], constants::CRH_IVK_PERSONALIZATION);
h.update(&preimage);
let mut h = h.finalize().as_ref().to_vec();
// Drop the most significant five bits, so it can be interpreted as a scalar.
h[31] &= 0b0000_0111;
let mut e = <E::Fs as PrimeField>::Repr::default();
e.read_le(&h[..]).unwrap();
E::Fs::from_repr(e).expect("should be a valid scalar")
}
pub fn into_payment_address(
&self,
diversifier: Diversifier,
params: &E::Params
) -> Option<PaymentAddress<E>>
{
diversifier.g_d(params).map(|g_d| {
let pk_d = g_d.mul(self.ivk(), params);
PaymentAddress {
pk_d: pk_d,
diversifier: diversifier
}
})
}
}
#[derive(Copy, Clone)]
pub struct Diversifier(pub [u8; 11]);
impl Diversifier {
pub fn g_d<E: JubjubEngine>(
&self,
params: &E::Params
) -> Option<edwards::Point<E, PrimeOrder>>
{
group_hash::<E>(&self.0, constants::KEY_DIVERSIFICATION_PERSONALIZATION, params)
}
}
#[derive(Clone)]
pub struct PaymentAddress<E: JubjubEngine> {
pub pk_d: edwards::Point<E, PrimeOrder>,
pub diversifier: Diversifier
}
impl<E: JubjubEngine> PaymentAddress<E> {
pub fn g_d(
&self,
params: &E::Params
) -> Option<edwards::Point<E, PrimeOrder>>
{
self.diversifier.g_d(params)
}
pub fn create_note(
&self,
value: u64,
randomness: E::Fs,
params: &E::Params
) -> Option<Note<E>>
{
self.g_d(params).map(|g_d| {
Note {
value: value,
r: randomness,
g_d: g_d,
pk_d: self.pk_d.clone()
}
})
}
}
pub struct Note<E: JubjubEngine> {
/// The value of the note
pub value: u64,
/// The diversified base of the address, GH(d)
pub g_d: edwards::Point<E, PrimeOrder>,
/// The public key of the address, g_d^ivk
pub pk_d: edwards::Point<E, PrimeOrder>,
/// The commitment randomness
pub r: E::Fs
}
impl<E: JubjubEngine> Note<E> {
pub fn uncommitted() -> E::Fr {
// The smallest u-coordinate that is not on the curve
// is one.
// TODO: This should be relocated to JubjubEngine as
// it's specific to the curve we're using, not all
// twisted edwards curves.
E::Fr::one()
}
/// Computes the note commitment, returning the full point.
fn cm_full_point(&self, params: &E::Params) -> edwards::Point<E, PrimeOrder>
{
// Calculate the note contents, as bytes
let mut note_contents = vec![];
// Writing the value in little endian
(&mut note_contents).write_u64::<LittleEndian>(self.value).unwrap();
// Write g_d
self.g_d.write(&mut note_contents).unwrap();
// Write pk_d
self.pk_d.write(&mut note_contents).unwrap();
assert_eq!(note_contents.len(), 32 + 32 + 8);
// Compute the Pedersen hash of the note contents
let hash_of_contents = pedersen_hash(
Personalization::NoteCommitment,
note_contents.into_iter()
.flat_map(|byte| {
(0..8).map(move |i| ((byte >> i) & 1) == 1)
}),
params
);
// Compute final commitment
params.generator(FixedGenerators::NoteCommitmentRandomness)
.mul(self.r, params)
.add(&hash_of_contents, params)
}
/// Computes the nullifier given the viewing key and
/// note position
pub fn nf(
&self,
viewing_key: &ViewingKey<E>,
position: u64,
params: &E::Params
) -> Vec<u8>
{
// Compute rho = cm + position.G
let rho = self
.cm_full_point(params)
.add(
&params.generator(FixedGenerators::NullifierPosition)
.mul(position, params),
params
);
// Compute nf = BLAKE2s(nk | rho)
let mut nf_preimage = [0u8; 64];
viewing_key.nk.write(&mut nf_preimage[0..32]).unwrap();
rho.write(&mut nf_preimage[32..64]).unwrap();
let mut h = Blake2s::with_params(32, &[], &[], constants::PRF_NF_PERSONALIZATION);
h.update(&nf_preimage);
h.finalize().as_ref().to_vec()
}
/// Computes the note commitment
pub fn cm(&self, params: &E::Params) -> E::Fr
{
// The commitment is in the prime order subgroup, so mapping the
// commitment to the x-coordinate is an injective encoding.
self.cm_full_point(params).into_xy().0
}
}

266
src/prover.rs Normal file
View File

@ -0,0 +1,266 @@
//! Abstractions over the proving system and parameters.
use bellman::groth16::{create_random_proof, Proof};
use bls12_381::Bls12;
use rand_core::RngCore;
use crate::{
bundle::GrothProofBytes,
circuit::{self, GROTH_PROOF_SIZE},
value::{NoteValue, ValueCommitTrapdoor},
MerklePath,
};
use super::{
circuit::{Output, OutputParameters, Spend, SpendParameters, ValueCommitmentOpening},
Diversifier, Note, PaymentAddress, ProofGenerationKey, Rseed,
};
/// Interface for creating Sapling Spend proofs.
pub trait SpendProver {
/// The proof type created by this prover.
type Proof;
/// Prepares an instance of the Sapling Spend circuit for the given inputs.
///
/// Returns `None` if `diversifier` is not a valid Sapling diversifier.
#[allow(clippy::too_many_arguments)]
fn prepare_circuit(
proof_generation_key: ProofGenerationKey,
diversifier: Diversifier,
rseed: Rseed,
value: NoteValue,
alpha: jubjub::Fr,
rcv: ValueCommitTrapdoor,
anchor: bls12_381::Scalar,
merkle_path: MerklePath,
) -> Option<circuit::Spend>;
/// Create the proof for a Sapling [`SpendDescription`].
///
/// [`SpendDescription`]: crate::transaction::components::SpendDescription
fn create_proof<R: RngCore>(&self, circuit: circuit::Spend, rng: &mut R) -> Self::Proof;
/// Encodes the given Sapling [`SpendDescription`] proof, erasing its type.
///
/// [`SpendDescription`]: crate::transaction::components::SpendDescription
fn encode_proof(proof: Self::Proof) -> GrothProofBytes;
}
/// Interface for creating Sapling Output proofs.
pub trait OutputProver {
/// The proof type created by this prover.
type Proof;
/// Prepares an instance of the Sapling Output circuit for the given inputs.
///
/// Returns `None` if `diversifier` is not a valid Sapling diversifier.
fn prepare_circuit(
esk: jubjub::Fr,
payment_address: PaymentAddress,
rcm: jubjub::Fr,
value: NoteValue,
rcv: ValueCommitTrapdoor,
) -> circuit::Output;
/// Create the proof for a Sapling [`OutputDescription`].
///
/// [`OutputDescription`]: crate::transaction::components::OutputDescription
fn create_proof<R: RngCore>(&self, circuit: circuit::Output, rng: &mut R) -> Self::Proof;
/// Encodes the given Sapling [`OutputDescription`] proof, erasing its type.
///
/// [`OutputDescription`]: crate::transaction::components::OutputDescription
fn encode_proof(proof: Self::Proof) -> GrothProofBytes;
}
impl SpendProver for SpendParameters {
type Proof = Proof<Bls12>;
fn prepare_circuit(
proof_generation_key: ProofGenerationKey,
diversifier: Diversifier,
rseed: Rseed,
value: NoteValue,
alpha: jubjub::Fr,
rcv: ValueCommitTrapdoor,
anchor: bls12_381::Scalar,
merkle_path: MerklePath,
) -> Option<Spend> {
// Construct the value commitment
let value_commitment_opening = ValueCommitmentOpening {
value,
randomness: rcv.inner(),
};
// Construct the viewing key
let viewing_key = proof_generation_key.to_viewing_key();
// Construct the payment address with the viewing key / diversifier
let payment_address = viewing_key.to_payment_address(diversifier)?;
let note = Note::from_parts(payment_address, value, rseed);
// We now have the full witness for our circuit
let pos: u64 = merkle_path.position().into();
Some(Spend {
value_commitment_opening: Some(value_commitment_opening),
proof_generation_key: Some(proof_generation_key),
payment_address: Some(payment_address),
commitment_randomness: Some(note.rcm()),
ar: Some(alpha),
auth_path: merkle_path
.path_elems()
.iter()
.enumerate()
.map(|(i, node)| Some(((*node).into(), pos >> i & 0x1 == 1)))
.collect(),
anchor: Some(anchor),
})
}
fn create_proof<R: RngCore>(&self, circuit: Spend, rng: &mut R) -> Self::Proof {
create_random_proof(circuit, &self.0, rng).expect("proving should not fail")
}
fn encode_proof(proof: Self::Proof) -> GrothProofBytes {
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
proof
.write(&mut zkproof[..])
.expect("should be able to serialize a proof");
zkproof
}
}
impl OutputProver for OutputParameters {
type Proof = Proof<Bls12>;
fn prepare_circuit(
esk: jubjub::Fr,
payment_address: PaymentAddress,
rcm: jubjub::Fr,
value: NoteValue,
rcv: ValueCommitTrapdoor,
) -> Output {
// Construct the value commitment for the proof instance
let value_commitment_opening = ValueCommitmentOpening {
value,
randomness: rcv.inner(),
};
// We now have a full witness for the output proof.
Output {
value_commitment_opening: Some(value_commitment_opening),
payment_address: Some(payment_address),
commitment_randomness: Some(rcm),
esk: Some(esk),
}
}
fn create_proof<R: RngCore>(&self, circuit: Output, rng: &mut R) -> Self::Proof {
create_random_proof(circuit, &self.0, rng).expect("proving should not fail")
}
fn encode_proof(proof: Self::Proof) -> GrothProofBytes {
let mut zkproof = [0u8; GROTH_PROOF_SIZE];
proof
.write(&mut zkproof[..])
.expect("should be able to serialize a proof");
zkproof
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub mod mock {
use ff::Field;
use super::{OutputProver, SpendProver};
use crate::{
bundle::GrothProofBytes,
circuit::{self, ValueCommitmentOpening, GROTH_PROOF_SIZE},
value::{NoteValue, ValueCommitTrapdoor},
Diversifier, MerklePath, PaymentAddress, ProofGenerationKey, Rseed,
};
pub struct MockSpendProver;
impl SpendProver for MockSpendProver {
type Proof = GrothProofBytes;
fn prepare_circuit(
proof_generation_key: ProofGenerationKey,
diversifier: Diversifier,
_rseed: Rseed,
value: NoteValue,
alpha: jubjub::Fr,
rcv: ValueCommitTrapdoor,
anchor: bls12_381::Scalar,
_merkle_path: MerklePath,
) -> Option<circuit::Spend> {
let payment_address = proof_generation_key
.to_viewing_key()
.ivk()
.to_payment_address(diversifier);
Some(circuit::Spend {
value_commitment_opening: Some(ValueCommitmentOpening {
value,
randomness: rcv.inner(),
}),
proof_generation_key: Some(proof_generation_key),
payment_address,
commitment_randomness: Some(jubjub::Scalar::ZERO),
ar: Some(alpha),
auth_path: vec![],
anchor: Some(anchor),
})
}
fn create_proof<R: rand_core::RngCore>(
&self,
_circuit: circuit::Spend,
_rng: &mut R,
) -> Self::Proof {
[0u8; GROTH_PROOF_SIZE]
}
fn encode_proof(proof: Self::Proof) -> GrothProofBytes {
proof
}
}
pub struct MockOutputProver;
impl OutputProver for MockOutputProver {
type Proof = GrothProofBytes;
fn prepare_circuit(
esk: jubjub::Fr,
payment_address: PaymentAddress,
rcm: jubjub::Fr,
value: NoteValue,
rcv: ValueCommitTrapdoor,
) -> circuit::Output {
circuit::Output {
value_commitment_opening: Some(ValueCommitmentOpening {
value,
randomness: rcv.inner(),
}),
payment_address: Some(payment_address),
commitment_randomness: Some(rcm),
esk: Some(esk),
}
}
fn create_proof<R: rand_core::RngCore>(
&self,
_circuit: circuit::Output,
_rng: &mut R,
) -> Self::Proof {
[0u8; GROTH_PROOF_SIZE]
}
fn encode_proof(proof: Self::Proof) -> GrothProofBytes {
proof
}
}
}

View File

@ -1,343 +0,0 @@
//! Implementation of RedJubjub, a specialization of RedDSA to the Jubjub curve.
//! See section 5.4.6 of the Sapling protocol specification.
use pairing::{Field, PrimeField, PrimeFieldRepr};
use rand::{Rng, Rand};
use std::io::{self, Read, Write};
use jubjub::{FixedGenerators, JubjubEngine, JubjubParams, Unknown, edwards::Point};
use util::{hash_to_scalar};
fn read_scalar<E: JubjubEngine, R: Read>(reader: R) -> io::Result<E::Fs> {
let mut s_repr = <E::Fs as PrimeField>::Repr::default();
s_repr.read_le(reader)?;
match E::Fs::from_repr(s_repr) {
Ok(s) => Ok(s),
Err(_) => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"scalar is not in field",
)),
}
}
fn write_scalar<E: JubjubEngine, W: Write>(s: &E::Fs, writer: W) -> io::Result<()> {
s.into_repr().write_le(writer)
}
fn h_star<E: JubjubEngine>(a: &[u8], b: &[u8]) -> E::Fs {
hash_to_scalar::<E>(b"Zcash_RedJubjubH", a, b)
}
#[derive(Copy, Clone)]
pub struct Signature {
rbar: [u8; 32],
sbar: [u8; 32],
}
pub struct PrivateKey<E: JubjubEngine>(pub E::Fs);
pub struct PublicKey<E: JubjubEngine>(pub Point<E, Unknown>);
impl Signature {
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
let mut rbar = [0u8; 32];
let mut sbar = [0u8; 32];
reader.read_exact(&mut rbar)?;
reader.read_exact(&mut sbar)?;
Ok(Signature { rbar, sbar })
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.rbar)?;
writer.write_all(&self.sbar)
}
}
impl<E: JubjubEngine> PrivateKey<E> {
pub fn randomize(&self, alpha: E::Fs) -> Self {
let mut tmp = self.0;
tmp.add_assign(&alpha);
PrivateKey(tmp)
}
pub fn read<R: Read>(reader: R) -> io::Result<Self> {
let pk = read_scalar::<E, R>(reader)?;
Ok(PrivateKey(pk))
}
pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
write_scalar::<E, W>(&self.0, writer)
}
pub fn sign<R: Rng>(
&self,
msg: &[u8],
rng: &mut R,
p_g: FixedGenerators,
params: &E::Params,
) -> Signature {
// T = (l_H + 128) bits of randomness
// For H*, l_H = 512 bits
let mut t = [0u8; 80];
rng.fill_bytes(&mut t[..]);
// r = H*(T || M)
let r = h_star::<E>(&t[..], msg);
// R = r . P_G
let r_g = params.generator(p_g).mul(r, params);
let mut rbar = [0u8; 32];
r_g.write(&mut rbar[..])
.expect("Jubjub points should serialize to 32 bytes");
// S = r + H*(Rbar || M) . sk
let mut s = h_star::<E>(&rbar[..], msg);
s.mul_assign(&self.0);
s.add_assign(&r);
let mut sbar = [0u8; 32];
write_scalar::<E, &mut [u8]>(&s, &mut sbar[..])
.expect("Jubjub scalars should serialize to 32 bytes");
Signature { rbar, sbar }
}
}
impl<E: JubjubEngine> PublicKey<E> {
pub fn from_private(privkey: &PrivateKey<E>, p_g: FixedGenerators, params: &E::Params) -> Self {
let res = params.generator(p_g).mul(privkey.0, params).into();
PublicKey(res)
}
pub fn randomize(&self, alpha: E::Fs, p_g: FixedGenerators, params: &E::Params) -> Self {
let res: Point<E, Unknown> = params.generator(p_g).mul(alpha, params).into();
let res = res.add(&self.0, params);
PublicKey(res)
}
pub fn read<R: Read>(reader: R, params: &E::Params) -> io::Result<Self> {
let p = Point::read(reader, params)?;
Ok(PublicKey(p))
}
pub fn write<W: Write>(&self, writer: W) -> io::Result<()> {
self.0.write(writer)
}
pub fn verify(
&self,
msg: &[u8],
sig: &Signature,
p_g: FixedGenerators,
params: &E::Params,
) -> bool {
// c = H*(Rbar || M)
let c = h_star::<E>(&sig.rbar[..], msg);
// Signature checks:
// R != invalid
let r = match Point::read(&sig.rbar[..], params) {
Ok(r) => r,
Err(_) => return false,
};
// S < order(G)
// (E::Fs guarantees its representation is in the field)
let s = match read_scalar::<E, &[u8]>(&sig.sbar[..]) {
Ok(s) => s,
Err(_) => return false,
};
// 0 = h_G(-S . P_G + R + c . vk)
self.0.mul(c, params).add(&r, params).add(
&params.generator(p_g).mul(s, params).negate().into(),
params
).mul_by_cofactor(params).eq(&Point::zero())
}
}
pub struct BatchEntry<'a, E: JubjubEngine> {
vk: PublicKey<E>,
msg: &'a [u8],
sig: Signature,
}
// TODO: #82: This is a naive implementation currently,
// and doesn't use multiexp.
pub fn batch_verify<'a, E: JubjubEngine, R: Rng>(
rng: &mut R,
batch: &[BatchEntry<'a, E>],
p_g: FixedGenerators,
params: &E::Params,
) -> bool
{
let mut acc = Point::<E, Unknown>::zero();
for entry in batch {
let mut r = match Point::<E, Unknown>::read(&entry.sig.rbar[..], params) {
Ok(r) => r,
Err(_) => return false,
};
let mut s = match read_scalar::<E, &[u8]>(&entry.sig.sbar[..]) {
Ok(s) => s,
Err(_) => return false,
};
let mut c = h_star::<E>(&entry.sig.rbar[..], entry.msg);
let z = E::Fs::rand(rng);
s.mul_assign(&z);
s.negate();
r = r.mul(z, params);
c.mul_assign(&z);
acc = acc.add(&r, params);
acc = acc.add(&entry.vk.0.mul(c, params), params);
acc = acc.add(&params.generator(p_g).mul(s, params).into(), params);
}
acc = acc.mul_by_cofactor(params).into();
acc.eq(&Point::zero())
}
#[cfg(test)]
mod tests {
use pairing::bls12_381::Bls12;
use rand::thread_rng;
use jubjub::{JubjubBls12, fs::Fs, edwards};
use super::*;
#[test]
fn test_batch_verify() {
let rng = &mut thread_rng();
let params = &JubjubBls12::new();
let p_g = FixedGenerators::SpendingKeyGenerator;
let sk1 = PrivateKey::<Bls12>(rng.gen());
let vk1 = PublicKey::from_private(&sk1, p_g, params);
let msg1 = b"Foo bar";
let sig1 = sk1.sign(msg1, rng, p_g, params);
assert!(vk1.verify(msg1, &sig1, p_g, params));
let sk2 = PrivateKey::<Bls12>(rng.gen());
let vk2 = PublicKey::from_private(&sk2, p_g, params);
let msg2 = b"Foo bar";
let sig2 = sk2.sign(msg2, rng, p_g, params);
assert!(vk2.verify(msg2, &sig2, p_g, params));
let mut batch = vec![
BatchEntry { vk: vk1, msg: msg1, sig: sig1 },
BatchEntry { vk: vk2, msg: msg2, sig: sig2 }
];
assert!(batch_verify(rng, &batch, p_g, params));
batch[0].sig = sig2;
assert!(!batch_verify(rng, &batch, p_g, params));
}
#[test]
fn cofactor_check() {
let rng = &mut thread_rng();
let params = &JubjubBls12::new();
let zero = edwards::Point::zero();
let p_g = FixedGenerators::SpendingKeyGenerator;
// Get a point of order 8
let p8 = loop {
let r = edwards::Point::<Bls12, _>::rand(rng, params).mul(Fs::char(), params);
let r2 = r.double(params);
let r4 = r2.double(params);
let r8 = r4.double(params);
if r2 != zero && r4 != zero && r8 == zero {
break r;
}
};
let sk = PrivateKey::<Bls12>(rng.gen());
let vk = PublicKey::from_private(&sk, p_g, params);
// TODO: This test will need to change when #77 is fixed
let msg = b"Foo bar";
let sig = sk.sign(msg, rng, p_g, params);
assert!(vk.verify(msg, &sig, p_g, params));
let vktorsion = PublicKey(vk.0.add(&p8, params));
assert!(vktorsion.verify(msg, &sig, p_g, params));
}
#[test]
fn round_trip_serialization() {
let rng = &mut thread_rng();
let p_g = FixedGenerators::SpendingKeyGenerator;
let params = &JubjubBls12::new();
for _ in 0..1000 {
let sk = PrivateKey::<Bls12>(rng.gen());
let vk = PublicKey::from_private(&sk, p_g, params);
let msg = b"Foo bar";
let sig = sk.sign(msg, rng, p_g, params);
let mut sk_bytes = [0u8; 32];
let mut vk_bytes = [0u8; 32];
let mut sig_bytes = [0u8; 64];
sk.write(&mut sk_bytes[..]).unwrap();
vk.write(&mut vk_bytes[..]).unwrap();
sig.write(&mut sig_bytes[..]).unwrap();
let sk_2 = PrivateKey::<Bls12>::read(&sk_bytes[..]).unwrap();
let vk_2 = PublicKey::from_private(&sk_2, p_g, params);
let mut vk_2_bytes = [0u8; 32];
vk_2.write(&mut vk_2_bytes[..]).unwrap();
assert!(vk_bytes == vk_2_bytes);
let vk_2 = PublicKey::<Bls12>::read(&vk_bytes[..], params).unwrap();
let sig_2 = Signature::read(&sig_bytes[..]).unwrap();
assert!(vk.verify(msg, &sig_2, p_g, params));
assert!(vk_2.verify(msg, &sig, p_g, params));
assert!(vk_2.verify(msg, &sig_2, p_g, params));
}
}
#[test]
fn random_signatures() {
let rng = &mut thread_rng();
let p_g = FixedGenerators::SpendingKeyGenerator;
let params = &JubjubBls12::new();
for _ in 0..1000 {
let sk = PrivateKey::<Bls12>(rng.gen());
let vk = PublicKey::from_private(&sk, p_g, params);
let msg1 = b"Foo bar";
let msg2 = b"Spam eggs";
let sig1 = sk.sign(msg1, rng, p_g, params);
let sig2 = sk.sign(msg2, rng, p_g, params);
assert!(vk.verify(msg1, &sig1, p_g, params));
assert!(vk.verify(msg2, &sig2, p_g, params));
assert!(!vk.verify(msg1, &sig2, p_g, params));
assert!(!vk.verify(msg2, &sig1, p_g, params));
let alpha = rng.gen();
let rsk = sk.randomize(alpha);
let rvk = vk.randomize(alpha, p_g, params);
let sig1 = rsk.sign(msg1, rng, p_g, params);
let sig2 = rsk.sign(msg2, rng, p_g, params);
assert!(rvk.verify(msg1, &sig1, p_g, params));
assert!(rvk.verify(msg2, &sig2, p_g, params));
assert!(!rvk.verify(msg1, &sig2, p_g, params));
assert!(!rvk.verify(msg2, &sig1, p_g, params));
}
}
}

165
src/spec.rs Normal file
View File

@ -0,0 +1,165 @@
//! Helper functions defined in the Zcash Protocol Specification.
use blake2s_simd::Params as Blake2sParams;
use group::{cofactor::CofactorGroup, ff::PrimeField, Curve, GroupEncoding, WnafBase, WnafScalar};
use super::{
constants::{
CRH_IVK_PERSONALIZATION, KEY_DIVERSIFICATION_PERSONALIZATION,
NOTE_COMMITMENT_RANDOMNESS_GENERATOR, NULLIFIER_POSITION_GENERATOR, PRF_NF_PERSONALIZATION,
},
group_hash::group_hash,
pedersen_hash::{pedersen_hash, Personalization},
};
const PREPARED_WINDOW_SIZE: usize = 4;
pub(crate) type PreparedBase = WnafBase<jubjub::ExtendedPoint, PREPARED_WINDOW_SIZE>;
pub(crate) type PreparedBaseSubgroup = WnafBase<jubjub::SubgroupPoint, PREPARED_WINDOW_SIZE>;
pub(crate) type PreparedScalar = WnafScalar<jubjub::Scalar, PREPARED_WINDOW_SIZE>;
/// $CRH^\mathsf{ivk}(ak, nk)$
///
/// Defined in [Zcash Protocol Spec § 5.4.1.5: CRH^ivk Hash Function][concretecrhivk].
///
/// [concretecrhivk]: https://zips.z.cash/protocol/protocol.pdf#concretecrhivk
pub(crate) fn crh_ivk(ak: [u8; 32], nk: [u8; 32]) -> jubjub::Scalar {
let mut h: [u8; 32] = Blake2sParams::new()
.hash_length(32)
.personal(CRH_IVK_PERSONALIZATION)
.to_state()
.update(&ak)
.update(&nk)
.finalize()
.as_bytes()
.try_into()
.expect("output length is correct");
// Drop the most significant five bits, so it can be interpreted as a scalar.
h[31] &= 0b0000_0111;
jubjub::Fr::from_repr(h).unwrap()
}
/// Defined in [Zcash Protocol Spec § 5.4.1.6: DiversifyHash^Sapling and DiversifyHash^Orchard Hash Functions][concretediversifyhash].
///
/// [concretediversifyhash]: https://zips.z.cash/protocol/protocol.pdf#concretediversifyhash
pub(crate) fn diversify_hash(d: &[u8; 11]) -> Option<jubjub::SubgroupPoint> {
group_hash(d, KEY_DIVERSIFICATION_PERSONALIZATION)
}
/// $MixingPedersenHash$.
///
/// Defined in [Zcash Protocol Spec § 5.4.1.8: Mixing Pedersen Hash Function][concretemixinghash].
///
/// [concretemixinghash]: https://zips.z.cash/protocol/protocol.pdf#concretemixinghash
pub(crate) fn mixing_pedersen_hash(
cm: jubjub::SubgroupPoint,
position: u64,
) -> jubjub::SubgroupPoint {
cm + (NULLIFIER_POSITION_GENERATOR * jubjub::Fr::from(position))
}
/// $PRF^\mathsf{nfSapling}_{nk}(\rho)$
///
/// Defined in [Zcash Protocol Spec § 5.4.2: Pseudo Random Functions][concreteprfs].
///
/// [concreteprfs]: https://zips.z.cash/protocol/protocol.pdf#concreteprfs
pub(crate) fn prf_nf(nk: &jubjub::SubgroupPoint, rho: &jubjub::SubgroupPoint) -> [u8; 32] {
Blake2sParams::new()
.hash_length(32)
.personal(PRF_NF_PERSONALIZATION)
.to_state()
.update(&nk.to_bytes())
.update(&rho.to_bytes())
.finalize()
.as_bytes()
.try_into()
.expect("output length is correct")
}
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
pub(crate) fn ka_sapling_derive_public(
sk: &jubjub::Scalar,
b: &jubjub::ExtendedPoint,
) -> jubjub::ExtendedPoint {
ka_sapling_derive_public_prepared(&PreparedScalar::new(sk), &PreparedBase::new(*b))
}
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
pub(crate) fn ka_sapling_derive_public_prepared(
sk: &PreparedScalar,
b: &PreparedBase,
) -> jubjub::ExtendedPoint {
// [sk] b
b * sk
}
/// This is defined implicitly by [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents]
/// which uses $KA^\mathsf{Sapling}.\mathsf{DerivePublic}$ to produce a diversified
/// transmission key with type $KA^\mathsf{Sapling}.\mathsf{PublicPrimeSubgroup}$.
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
pub(crate) fn ka_sapling_derive_public_subgroup_prepared(
sk: &PreparedScalar,
b: &PreparedBaseSubgroup,
) -> jubjub::SubgroupPoint {
// [sk] b
b * sk
}
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
pub(crate) fn ka_sapling_agree(
sk: &jubjub::Scalar,
b: &jubjub::ExtendedPoint,
) -> jubjub::SubgroupPoint {
ka_sapling_agree_prepared(&PreparedScalar::new(sk), &PreparedBase::new(*b))
}
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement
pub(crate) fn ka_sapling_agree_prepared(
sk: &PreparedScalar,
b: &PreparedBase,
) -> jubjub::SubgroupPoint {
// [8 sk] b
// <ExtendedPoint as CofactorGroup>::clear_cofactor is implemented using
// ExtendedPoint::mul_by_cofactor in the jubjub crate.
(b * sk).clear_cofactor()
}
/// $WindowedPedersenCommit_r(s)$
///
/// Defined in [Zcash Protocol Spec § 5.4.8.2: Windowed Pedersen commitments][concretewindowedcommit].
///
/// [concretewindowedcommit]: https://zips.z.cash/protocol/protocol.pdf#concretewindowedcommit
pub(crate) fn windowed_pedersen_commit<I>(
personalization: Personalization,
s: I,
r: jubjub::Scalar,
) -> jubjub::SubgroupPoint
where
I: IntoIterator<Item = bool>,
{
pedersen_hash(personalization, s) + (NOTE_COMMITMENT_RANDOMNESS_GENERATOR * r)
}
/// Coordinate extractor for Jubjub.
///
/// Defined in [Zcash Protocol Spec § 5.4.9.4: Coordinate Extractor for Jubjub][concreteextractorjubjub].
///
/// [concreteextractorjubjub]: https://zips.z.cash/protocol/protocol.pdf#concreteextractorjubjub
pub(crate) fn extract_p(point: &jubjub::SubgroupPoint) -> bls12_381::Scalar {
// The commitment is in the prime order subgroup, so mapping the
// commitment to the u-coordinate is an injective encoding.
Into::<&jubjub::ExtendedPoint>::into(point)
.to_affine()
.get_u()
}

2
src/test_vectors.rs Normal file
View File

@ -0,0 +1,2 @@
pub(crate) mod note_encryption;
pub(crate) mod signatures;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,476 @@
pub(crate) struct TestVector {
pub(crate) sk: [u8; 32],
pub(crate) vk: [u8; 32],
pub(crate) alpha: [u8; 32],
pub(crate) rsk: [u8; 32],
pub(crate) rvk: [u8; 32],
pub(crate) m: [u8; 32],
pub(crate) sig: [u8; 64],
pub(crate) rsig: [u8; 64],
}
pub(crate) fn make_test_vectors() -> Vec<TestVector> {
// From https://github.com/zcash/zcash-test-vectors/blob/master/zcash_test_vectors/sapling/redjubjub.py
vec![
TestVector {
sk: [
0x18, 0xe2, 0x8d, 0xea, 0x5c, 0x11, 0x81, 0x7a, 0xee, 0xb2, 0x1a, 0x19, 0x98, 0x1d,
0x28, 0x36, 0x8e, 0xc4, 0x38, 0xaf, 0xc2, 0x5a, 0x8d, 0xb9, 0x4e, 0xbe, 0x08, 0xd7,
0xa0, 0x28, 0x8e, 0x09,
],
vk: [
0x9b, 0x01, 0x53, 0xb0, 0x3d, 0x32, 0x0f, 0xe2, 0x3e, 0x28, 0x34, 0xd5, 0xd6, 0x1d,
0xbb, 0x1f, 0x51, 0x9b, 0x3f, 0x41, 0xf8, 0xf9, 0x46, 0x15, 0x2b, 0xf0, 0xc3, 0xf2,
0x47, 0xd1, 0x18, 0x07,
],
alpha: [
0xff, 0xd1, 0xa1, 0x27, 0x32, 0x52, 0xb1, 0x87, 0xf4, 0xed, 0x32, 0x6d, 0xfc, 0x98,
0x85, 0x3e, 0x29, 0x17, 0xc2, 0xb3, 0x63, 0x79, 0xb1, 0x75, 0xda, 0x63, 0xb9, 0xef,
0x6d, 0xda, 0x6c, 0x08,
],
rsk: [
0x60, 0x87, 0x38, 0x3b, 0x30, 0x55, 0x9b, 0x31, 0x60, 0x90, 0x85, 0xb9, 0x00, 0x96,
0x45, 0xce, 0xb6, 0xa0, 0xc6, 0x61, 0x25, 0x99, 0xd7, 0x28, 0x80, 0x72, 0x8e, 0x61,
0x24, 0x4e, 0x7d, 0x03,
],
rvk: [
0xc1, 0xba, 0xbc, 0xb6, 0xea, 0xe2, 0xb9, 0x94, 0xee, 0x6d, 0x65, 0xc1, 0x0b, 0x9d,
0xad, 0x59, 0x40, 0xdc, 0x73, 0x5b, 0x07, 0x50, 0x4d, 0xae, 0xd1, 0xe4, 0x6b, 0x07,
0x09, 0xb4, 0x51, 0x36,
],
m: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
],
sig: [
0xdc, 0xa3, 0xbb, 0x2c, 0xb8, 0xf0, 0x48, 0xcc, 0xab, 0x10, 0xae, 0xd7, 0x75, 0x46,
0xc1, 0xdb, 0xb1, 0x0c, 0xc4, 0xfb, 0x15, 0xab, 0x02, 0xac, 0xae, 0xf9, 0x44, 0xdd,
0xab, 0x8b, 0x67, 0x22, 0x54, 0x5f, 0xda, 0x4c, 0x62, 0x04, 0x6d, 0x69, 0xd9, 0x8f,
0x92, 0x2f, 0x4e, 0x8c, 0x21, 0x0b, 0xc4, 0x7b, 0x4f, 0xdd, 0xe0, 0xa1, 0x94, 0x71,
0x79, 0x80, 0x4c, 0x1a, 0xce, 0x56, 0x90, 0x05,
],
rsig: [
0x70, 0xc2, 0x84, 0x50, 0x4e, 0x90, 0xf0, 0x00, 0x8e, 0x8e, 0xd2, 0x20, 0x8f, 0x49,
0x69, 0x72, 0x7a, 0x41, 0x5e, 0xc3, 0x10, 0x2c, 0x29, 0x9e, 0x39, 0x8b, 0x6c, 0x16,
0x57, 0x2b, 0xd9, 0x64, 0x3e, 0xe1, 0x01, 0x17, 0x66, 0x68, 0x1e, 0x40, 0x6e, 0xe6,
0xbe, 0xe3, 0xd0, 0x3e, 0xe8, 0xf2, 0x71, 0x76, 0xe3, 0x2f, 0xba, 0xbd, 0xde, 0xd2,
0x0b, 0x0d, 0x17, 0x86, 0xa4, 0xee, 0x18, 0x01,
],
},
TestVector {
sk: [
0x05, 0x96, 0x54, 0xf9, 0x61, 0x27, 0x3d, 0xaf, 0xda, 0x3b, 0x26, 0x77, 0xb3, 0x5c,
0x18, 0xaf, 0x6b, 0x11, 0xad, 0xfb, 0x9e, 0xe9, 0x0b, 0x48, 0x93, 0x5e, 0x55, 0x7c,
0x8d, 0x5d, 0x9c, 0x04,
],
vk: [
0xfa, 0xf6, 0xc3, 0xb7, 0x37, 0xe8, 0xe6, 0x11, 0xaa, 0xfe, 0xa5, 0x2f, 0x03, 0xbb,
0x27, 0x86, 0xe1, 0x83, 0x53, 0xeb, 0xe0, 0xd3, 0x13, 0x9e, 0x3c, 0x54, 0x49, 0x87,
0x80, 0xc8, 0xc1, 0x99,
],
alpha: [
0xc3, 0x0b, 0x96, 0x20, 0x8d, 0xa8, 0x00, 0xe1, 0x0a, 0xf0, 0x25, 0x42, 0xce, 0x69,
0x4b, 0x7e, 0xd7, 0x6a, 0x28, 0x29, 0x9f, 0x85, 0x99, 0x8e, 0x5d, 0x61, 0x08, 0x12,
0x68, 0x1b, 0xf0, 0x03,
],
rsk: [
0xc8, 0xa1, 0xea, 0x19, 0xef, 0xcf, 0x3d, 0x90, 0xe5, 0x2b, 0x4c, 0xb9, 0x81, 0xc6,
0x63, 0x2d, 0x43, 0x7c, 0xd5, 0x24, 0x3e, 0x6f, 0xa5, 0xd6, 0xf0, 0xbf, 0x5d, 0x8e,
0xf5, 0x78, 0x8c, 0x08,
],
rvk: [
0xd5, 0x24, 0xdc, 0xe7, 0x73, 0x40, 0x69, 0x75, 0x8a, 0x91, 0xf0, 0x07, 0xa8, 0x69,
0x50, 0x5d, 0xfc, 0x4a, 0xba, 0x17, 0x20, 0x59, 0x4d, 0x4d, 0x74, 0xf0, 0x07, 0x70,
0x0e, 0x62, 0xee, 0x00,
],
m: [
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01,
],
sig: [
0xb5, 0xa1, 0xf3, 0x2d, 0x3d, 0x50, 0xfc, 0x73, 0x8b, 0x5c, 0x3b, 0x4e, 0x99, 0x60,
0x72, 0x9c, 0xe4, 0x31, 0x6b, 0xa7, 0x72, 0x1a, 0x12, 0x68, 0x66, 0x04, 0xfe, 0xba,
0x6b, 0xd7, 0x48, 0x45, 0x00, 0x70, 0xcb, 0x92, 0x24, 0x06, 0xfd, 0xfc, 0x5d, 0x60,
0xde, 0xa9, 0xbe, 0x3a, 0x52, 0x6a, 0x16, 0xcf, 0xeb, 0x87, 0x77, 0x79, 0xfb, 0x78,
0x2d, 0x5d, 0x41, 0x39, 0x5b, 0x45, 0x5f, 0x04,
],
rsig: [
0x5a, 0x5a, 0x20, 0xd2, 0x00, 0xef, 0xdd, 0xd4, 0x98, 0xdf, 0xae, 0x2a, 0x9e, 0xf8,
0xcf, 0x01, 0x28, 0x1a, 0x89, 0x19, 0x01, 0x8a, 0x82, 0x4c, 0xc7, 0xa4, 0x98, 0x3b,
0x9a, 0x0d, 0x4a, 0x06, 0xff, 0x17, 0x20, 0x79, 0xe0, 0x13, 0xd4, 0x2a, 0x2a, 0x3a,
0x88, 0xa6, 0x52, 0x0c, 0x86, 0xfc, 0xe3, 0xb9, 0x8e, 0x1e, 0xfa, 0xa3, 0x25, 0x83,
0x2a, 0x6a, 0x56, 0x58, 0xd8, 0xdd, 0x7c, 0x0a,
],
},
TestVector {
sk: [
0xad, 0xe7, 0xab, 0xb5, 0x51, 0xc7, 0x9d, 0x0f, 0x0e, 0x42, 0xef, 0x7f, 0x12, 0x06,
0xb8, 0x77, 0x12, 0xa8, 0x4a, 0x61, 0xde, 0xa3, 0xf3, 0x7b, 0x42, 0x49, 0x6d, 0x7e,
0xfd, 0x12, 0x52, 0x0c,
],
vk: [
0x36, 0x9e, 0xa7, 0x51, 0x76, 0x2f, 0x83, 0x9d, 0x25, 0x70, 0x1a, 0x5e, 0xeb, 0x55,
0x1e, 0xc4, 0xf0, 0x6c, 0x12, 0x90, 0xb3, 0xb9, 0xc3, 0xa7, 0x24, 0x40, 0x2d, 0xec,
0x02, 0x73, 0x92, 0x21,
],
alpha: [
0x81, 0x92, 0x25, 0x29, 0xa6, 0x3e, 0xe7, 0x43, 0xfc, 0x4f, 0xbb, 0xac, 0x45, 0xc4,
0x98, 0x83, 0x16, 0xbc, 0x9b, 0x6e, 0x42, 0x8b, 0x01, 0xa8, 0xd3, 0x1f, 0xc1, 0xc2,
0xa6, 0xca, 0x62, 0x05,
],
rsk: [
0x77, 0x4d, 0xda, 0x07, 0x99, 0xf7, 0xed, 0x82, 0x87, 0x81, 0xe2, 0x5f, 0xc4, 0xa9,
0xe8, 0x54, 0x28, 0x29, 0xb2, 0xce, 0x1f, 0xf4, 0x8d, 0x1d, 0x6d, 0xb9, 0xfa, 0xdb,
0xb9, 0x28, 0x37, 0x03,
],
rvk: [
0x0d, 0x92, 0xad, 0x6d, 0x46, 0xed, 0xac, 0xd0, 0x23, 0xd4, 0xd2, 0xef, 0x70, 0x3a,
0x6c, 0xa0, 0xa7, 0x92, 0xcf, 0xc4, 0xb7, 0xda, 0x11, 0xc2, 0x35, 0x3b, 0xc8, 0x45,
0xa2, 0x7a, 0x97, 0x4d,
],
m: [
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02,
],
sig: [
0x1f, 0x3e, 0x8a, 0x94, 0x31, 0x0c, 0x20, 0x71, 0xa7, 0x0f, 0x9d, 0xf5, 0xe7, 0x9a,
0xa9, 0xe8, 0x48, 0x5d, 0xec, 0xcb, 0x17, 0x8b, 0xdf, 0xf9, 0x80, 0x5f, 0xcb, 0xe6,
0xf7, 0xd5, 0x51, 0xee, 0xe3, 0xc3, 0x54, 0x2c, 0xa7, 0x5c, 0x9d, 0x8d, 0x4a, 0xdc,
0x54, 0xd7, 0x2c, 0x3d, 0xbe, 0x28, 0x62, 0x6d, 0x20, 0x78, 0x5b, 0xb7, 0xf5, 0x88,
0xc1, 0xa5, 0x82, 0xb8, 0x93, 0xdb, 0xb6, 0x01,
],
rsig: [
0xd1, 0x36, 0x21, 0x4c, 0x5d, 0x52, 0x8e, 0xa3, 0xd4, 0xcb, 0x7b, 0x63, 0x1a, 0x6b,
0xb0, 0x36, 0x06, 0x49, 0x73, 0xa1, 0x08, 0xb7, 0x33, 0xa5, 0xe3, 0xa4, 0x52, 0xab,
0x52, 0xa6, 0x59, 0xe5, 0x67, 0xcb, 0x55, 0xd2, 0x64, 0x4e, 0x74, 0xb6, 0xe8, 0x42,
0x6f, 0x2a, 0x7d, 0xd2, 0xa0, 0x4d, 0x2d, 0xda, 0x49, 0x35, 0xcc, 0x38, 0x20, 0xb7,
0x7a, 0x9c, 0x1a, 0xb6, 0x19, 0x86, 0x3c, 0x05,
],
},
TestVector {
sk: [
0xc9, 0xd2, 0xae, 0x1f, 0x6d, 0x32, 0xa6, 0x75, 0xd0, 0x9e, 0xb0, 0x82, 0x3f, 0x46,
0x7f, 0xa9, 0x21, 0xb3, 0x28, 0x4a, 0xcb, 0x35, 0xfa, 0xbd, 0xfc, 0x99, 0x4d, 0xe5,
0x49, 0xb8, 0x59, 0x0d,
],
vk: [
0x2d, 0x2f, 0x31, 0x6e, 0x5c, 0x36, 0x9a, 0xe4, 0xdd, 0x2c, 0x82, 0x5f, 0x3d, 0x86,
0x46, 0x00, 0x58, 0x40, 0x71, 0x84, 0x60, 0x3b, 0x21, 0x2c, 0xf3, 0x45, 0x9f, 0x36,
0xc8, 0x69, 0x7f, 0xd8,
],
alpha: [
0xeb, 0xbc, 0x89, 0x03, 0x11, 0x07, 0xc4, 0x4f, 0x47, 0x88, 0x9e, 0xd4, 0xd4, 0x37,
0x5a, 0x41, 0x14, 0xcf, 0x8a, 0x75, 0xdd, 0x33, 0xb9, 0x62, 0xf2, 0xd7, 0x59, 0xd3,
0xf4, 0xc6, 0xdf, 0x06,
],
rsk: [
0xfd, 0x62, 0x41, 0x4c, 0x1f, 0x2b, 0xd3, 0xf4, 0x94, 0x16, 0x87, 0x8a, 0x80, 0x5d,
0x71, 0x44, 0x35, 0x47, 0x7f, 0xbe, 0xa7, 0x2e, 0x4c, 0x1a, 0x46, 0xc2, 0x73, 0x53,
0x54, 0xca, 0xbb, 0x05,
],
rvk: [
0xf0, 0x43, 0x0e, 0x95, 0x3b, 0xe6, 0x0b, 0xf4, 0x38, 0xdb, 0xdc, 0xc2, 0x30, 0x3f,
0x0e, 0x32, 0xa6, 0xf7, 0xce, 0x2f, 0xbe, 0xdf, 0xb1, 0x3a, 0xc5, 0x18, 0xf7, 0x5a,
0x3f, 0xd1, 0x0e, 0xb5,
],
m: [
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03,
],
sig: [
0x12, 0xc7, 0x8d, 0xdd, 0x20, 0xd3, 0x0a, 0x61, 0xf8, 0x93, 0x0c, 0x6f, 0xe0, 0x85,
0x0f, 0xd1, 0x12, 0xbb, 0x7b, 0xe8, 0x8b, 0x12, 0x38, 0xea, 0x33, 0xd6, 0xbe, 0xf8,
0x81, 0xc1, 0x02, 0xd1, 0x04, 0xaa, 0x36, 0x54, 0x4a, 0x78, 0x47, 0x1c, 0x9e, 0x28,
0x42, 0xe6, 0xfd, 0x42, 0x55, 0x83, 0x46, 0xcf, 0xf4, 0x31, 0x27, 0x03, 0x26, 0x66,
0xeb, 0x11, 0x6f, 0x44, 0x2a, 0x28, 0x48, 0x0c,
],
rsig: [
0x01, 0xba, 0xaa, 0x26, 0x27, 0x4c, 0x14, 0x9a, 0xcf, 0x12, 0xe1, 0xcc, 0xf5, 0x50,
0x7d, 0x56, 0x79, 0x04, 0x82, 0xf0, 0x67, 0xe5, 0xc9, 0x2b, 0x32, 0x19, 0xad, 0x6b,
0xf9, 0x11, 0x18, 0xcc, 0x3f, 0xce, 0x8d, 0x2a, 0x23, 0x19, 0x8a, 0x3b, 0x29, 0x0a,
0x7b, 0xf6, 0x8c, 0x2a, 0xc0, 0x7b, 0x5d, 0x90, 0x62, 0xb9, 0xf8, 0x68, 0x66, 0x2b,
0xb2, 0x52, 0x49, 0x12, 0xd4, 0x85, 0x6e, 0x0c,
],
},
TestVector {
sk: [
0x33, 0xbc, 0xd2, 0x86, 0x45, 0x41, 0xb8, 0xbb, 0x7f, 0xdc, 0x77, 0xa1, 0x9d, 0x97,
0x0f, 0x92, 0x4e, 0xae, 0xec, 0xf4, 0x10, 0x3c, 0x38, 0xc8, 0xd2, 0xb0, 0x66, 0x81,
0x42, 0xf2, 0x7d, 0x09,
],
vk: [
0x74, 0x17, 0x94, 0xe6, 0x2c, 0xf9, 0x32, 0x0c, 0x58, 0xba, 0xc5, 0x94, 0xa2, 0xb9,
0x0e, 0x34, 0x0a, 0x6d, 0x8a, 0x68, 0x05, 0x6f, 0x6e, 0xd5, 0xc7, 0x86, 0x8c, 0x5f,
0xf3, 0xe4, 0xd6, 0x16,
],
alpha: [
0x7c, 0xe7, 0x25, 0xa5, 0xfe, 0xf6, 0x1b, 0xd4, 0xa1, 0xe9, 0xc7, 0x73, 0x28, 0xe8,
0x21, 0x0e, 0xb7, 0x29, 0x2d, 0x95, 0x4c, 0x64, 0xe9, 0x9e, 0x8b, 0xed, 0xd0, 0x7a,
0xb3, 0xab, 0x0e, 0x0d,
],
rsk: [
0xf8, 0x76, 0x01, 0x55, 0xe5, 0x29, 0x3d, 0xbf, 0x9e, 0xb5, 0x77, 0x48, 0x32, 0x5f,
0xc9, 0xf9, 0x04, 0x9d, 0xe5, 0x88, 0x5c, 0x65, 0xba, 0x60, 0xb5, 0xee, 0x03, 0x97,
0x0b, 0xe9, 0x0e, 0x08,
],
rvk: [
0x66, 0x62, 0xba, 0x09, 0x95, 0x0a, 0xcc, 0xd2, 0xce, 0xa3, 0xc7, 0xa8, 0x12, 0x90,
0xcd, 0x59, 0x78, 0xa6, 0x2b, 0x5a, 0xc5, 0xbb, 0xc4, 0x8d, 0x9f, 0x58, 0x19, 0xcd,
0xc9, 0x64, 0x6f, 0x0a,
],
m: [
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04,
],
sig: [
0x77, 0x4a, 0xc4, 0x67, 0x3f, 0x09, 0xf3, 0xac, 0x57, 0x89, 0xb2, 0x86, 0xb5, 0xee,
0xcb, 0xed, 0xb2, 0x57, 0x23, 0x4e, 0x8c, 0xdf, 0xd9, 0x3f, 0x02, 0x89, 0x09, 0x78,
0xa6, 0xbb, 0xa6, 0x11, 0x69, 0xed, 0x48, 0xf9, 0xe1, 0xc9, 0xfd, 0x13, 0x19, 0xbd,
0x33, 0x0d, 0x2c, 0xf5, 0xb4, 0x91, 0x01, 0x0d, 0x69, 0xb0, 0x43, 0xf4, 0x64, 0x8b,
0xff, 0x55, 0x41, 0x62, 0xc6, 0xa6, 0xdc, 0x09,
],
rsig: [
0x7c, 0x6c, 0x49, 0x8d, 0xe0, 0x01, 0x78, 0x61, 0x09, 0xb3, 0x03, 0xa4, 0xc5, 0xdc,
0xb7, 0xfd, 0x07, 0x57, 0x50, 0xa0, 0xb9, 0xdf, 0x5e, 0x1e, 0x2a, 0x8e, 0x75, 0x47,
0xb7, 0xed, 0x70, 0xcc, 0x0b, 0x56, 0xa5, 0xbf, 0xa9, 0x65, 0x78, 0x43, 0xef, 0xd8,
0x9c, 0x66, 0xa8, 0x4f, 0x41, 0xd2, 0xb1, 0xb5, 0x07, 0x51, 0x19, 0x6b, 0x1e, 0x8c,
0x0c, 0x44, 0x98, 0x60, 0x06, 0x96, 0xa4, 0x04,
],
},
TestVector {
sk: [
0xca, 0x35, 0x06, 0xd6, 0xaf, 0x77, 0x67, 0xb5, 0x79, 0x0e, 0xf0, 0xc5, 0x19, 0x0f,
0xb3, 0xf3, 0x87, 0x7c, 0x4a, 0xab, 0x40, 0xe0, 0xdd, 0x65, 0x1a, 0xbb, 0xda, 0xcb,
0x54, 0x4e, 0xd0, 0x05,
],
vk: [
0xba, 0xb6, 0xcf, 0xb5, 0xc8, 0xea, 0x34, 0x91, 0x25, 0x1b, 0x46, 0xd5, 0x2a, 0xca,
0x25, 0xd9, 0xe9, 0xaf, 0x69, 0xfa, 0xa9, 0xb4, 0xe4, 0x0b, 0x03, 0xad, 0x00, 0x86,
0xde, 0x59, 0xb5, 0x1f,
],
alpha: [
0xbe, 0xa3, 0x87, 0x20, 0x3f, 0x43, 0x76, 0x0a, 0xd3, 0x7d, 0x61, 0xde, 0x0e, 0xb5,
0x9f, 0xca, 0x6c, 0xab, 0x75, 0x60, 0xdf, 0x64, 0xfa, 0xbb, 0x95, 0x11, 0x57, 0x9f,
0x6f, 0x68, 0x26, 0x06,
],
rsk: [
0x88, 0xd9, 0x8d, 0xf6, 0xee, 0xba, 0xdd, 0xbf, 0x4c, 0x8c, 0x51, 0xa4, 0x28, 0xc4,
0x52, 0xbe, 0xf4, 0x27, 0xc0, 0x0b, 0x20, 0x45, 0xd8, 0x21, 0xb0, 0xcc, 0x31, 0x6b,
0xc4, 0xb6, 0xf6, 0x0b,
],
rvk: [
0x11, 0x26, 0x7d, 0x14, 0xd5, 0xe0, 0xb2, 0xbb, 0x3c, 0xe0, 0x99, 0xe8, 0xef, 0x84,
0x49, 0x47, 0x1c, 0xbc, 0xfc, 0x69, 0x39, 0xa4, 0xb3, 0x48, 0xde, 0xa2, 0xc1, 0x73,
0x56, 0xa1, 0xe8, 0xdd,
],
m: [
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05,
],
sig: [
0x9a, 0x25, 0x42, 0x9f, 0x3e, 0xfd, 0x9b, 0x2f, 0x7d, 0xe2, 0x9e, 0x45, 0x12, 0x8d,
0xd7, 0xb7, 0x60, 0xf0, 0x50, 0x8c, 0xd9, 0x58, 0x21, 0x82, 0xab, 0xaf, 0x53, 0xdd,
0x76, 0xc0, 0x34, 0x2c, 0xe4, 0x1b, 0x4a, 0xcf, 0x8e, 0x0a, 0x48, 0x24, 0xe4, 0x11,
0x08, 0xc2, 0x02, 0x65, 0x73, 0x11, 0x4b, 0x60, 0xbe, 0xec, 0xb1, 0x74, 0x01, 0x2a,
0x2b, 0xdb, 0xee, 0xcb, 0xaa, 0x00, 0xb5, 0x06,
],
rsig: [
0xcf, 0xf5, 0x83, 0x57, 0x13, 0xbe, 0x07, 0xfb, 0xe1, 0x25, 0xbb, 0xf2, 0x7a, 0x63,
0x6a, 0xdd, 0x13, 0x1c, 0x90, 0x81, 0x71, 0x6c, 0x52, 0xfd, 0xa8, 0x75, 0x42, 0x6d,
0x03, 0x98, 0x2c, 0xd2, 0x7e, 0xbd, 0x14, 0xb4, 0x22, 0x7b, 0x83, 0x96, 0x15, 0xfd,
0x03, 0x71, 0xbf, 0xdb, 0x8a, 0x30, 0xab, 0xdd, 0xff, 0x74, 0xd7, 0x95, 0xf3, 0xe2,
0x7d, 0x1d, 0x47, 0xc6, 0x29, 0x46, 0x9b, 0x08,
],
},
TestVector {
sk: [
0xbc, 0x27, 0x83, 0x8d, 0xe2, 0xa6, 0x14, 0xcf, 0xba, 0x6c, 0x3e, 0x92, 0x2a, 0x8f,
0x84, 0x24, 0xd9, 0x85, 0x6f, 0x68, 0x16, 0xf3, 0xbc, 0x61, 0x02, 0x31, 0x3b, 0x7f,
0xaf, 0x5c, 0x3a, 0x0c,
],
vk: [
0xd7, 0x9b, 0xe9, 0xff, 0x22, 0x9a, 0x2e, 0x35, 0xf5, 0xbc, 0xa4, 0x48, 0xe5, 0xeb,
0x4a, 0x8a, 0xa9, 0x7f, 0xb4, 0x18, 0x02, 0x91, 0x25, 0xcf, 0xba, 0xa7, 0x8a, 0x91,
0xa3, 0x82, 0xb0, 0x94,
],
alpha: [
0x21, 0xa7, 0x15, 0x0e, 0x19, 0x4f, 0xed, 0xfe, 0xf9, 0x0c, 0x5d, 0x10, 0xe4, 0x20,
0x85, 0x8b, 0xca, 0x40, 0x04, 0x04, 0x0e, 0xb6, 0x81, 0xd1, 0x4e, 0x75, 0xc4, 0x47,
0x13, 0x51, 0xcb, 0x02,
],
rsk: [
0x26, 0xa2, 0xa1, 0xc4, 0x9c, 0xe7, 0x6a, 0xfd, 0x31, 0x69, 0xd3, 0xd5, 0x7a, 0x8f,
0xa1, 0x09, 0xa3, 0x8b, 0x3f, 0x6b, 0x23, 0x6e, 0xd7, 0x2c, 0xa8, 0xf6, 0xcb, 0x61,
0xd8, 0xf8, 0x87, 0x00,
],
rvk: [
0x54, 0xbf, 0x1b, 0xe7, 0x2e, 0x6d, 0x41, 0x20, 0x8b, 0x8a, 0xec, 0x11, 0x61, 0xd3,
0xba, 0x59, 0x51, 0x9f, 0xb9, 0x3d, 0xa0, 0x1a, 0x55, 0xe6, 0x78, 0xe2, 0x75, 0x20,
0x06, 0x60, 0x36, 0xc9,
],
m: [
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06,
],
sig: [
0xbb, 0xe0, 0x23, 0x59, 0x87, 0xc6, 0xe0, 0xec, 0x68, 0x6d, 0xdb, 0x8a, 0x65, 0x72,
0x66, 0xad, 0x60, 0x5f, 0x7b, 0x75, 0x95, 0x5b, 0xb0, 0xe8, 0x02, 0xf8, 0x81, 0x64,
0xa0, 0xff, 0xe1, 0x0c, 0x3b, 0x73, 0x85, 0x04, 0xab, 0xb3, 0xd1, 0x05, 0x62, 0xb9,
0x27, 0xb3, 0xd2, 0x9f, 0xe9, 0xb0, 0xd3, 0x56, 0x28, 0x6a, 0xea, 0xe5, 0xa2, 0xac,
0x9e, 0x43, 0x5f, 0x20, 0x79, 0x1a, 0xf8, 0x00,
],
rsig: [
0x6d, 0xe3, 0x2b, 0x54, 0x15, 0xd7, 0x7a, 0x90, 0x5f, 0x09, 0x03, 0x90, 0x2a, 0x11,
0x7e, 0xda, 0x79, 0x3c, 0x70, 0x8e, 0x23, 0xa5, 0x42, 0x45, 0xba, 0x8a, 0x8d, 0x1f,
0xe0, 0x26, 0x75, 0x23, 0x23, 0x15, 0x65, 0xe0, 0x57, 0x09, 0xae, 0xd9, 0x6c, 0x22,
0x1f, 0xb1, 0xf3, 0xd0, 0x42, 0x04, 0x35, 0x03, 0xff, 0x33, 0x85, 0x85, 0xa9, 0xbb,
0x98, 0x9c, 0x9d, 0xd4, 0x30, 0xd6, 0xd6, 0x0b,
],
},
TestVector {
sk: [
0xb2, 0x08, 0x59, 0xb8, 0x8e, 0xe3, 0x33, 0x8a, 0x64, 0x95, 0x4f, 0x8a, 0x9e, 0x8e,
0x9b, 0xf3, 0xe7, 0x11, 0x5a, 0xcf, 0x7c, 0x6e, 0x7f, 0x01, 0x43, 0x2c, 0x5f, 0x76,
0x96, 0xd2, 0xd0, 0x05,
],
vk: [
0xa8, 0x1f, 0xe6, 0x84, 0x6d, 0xbe, 0x0a, 0x75, 0xc0, 0xf4, 0x9b, 0x21, 0x32, 0x32,
0xbe, 0xad, 0xd1, 0xf9, 0xa5, 0x64, 0x67, 0x3d, 0x25, 0xb9, 0x1e, 0xe0, 0xf1, 0x7c,
0xe9, 0xca, 0xa3, 0x63,
],
alpha: [
0x44, 0xd9, 0x08, 0xe1, 0xc1, 0x5e, 0x6b, 0xd9, 0x38, 0x0a, 0x8b, 0x23, 0x5a, 0xce,
0x02, 0xfa, 0xc1, 0xc0, 0x87, 0x94, 0x45, 0x4b, 0xcd, 0xb4, 0xa6, 0xf4, 0x8c, 0xea,
0x78, 0xa7, 0x4a, 0x04,
],
rsk: [
0xf6, 0xe1, 0x61, 0x99, 0x50, 0x42, 0x9f, 0x63, 0x9d, 0x9f, 0xda, 0xad, 0xf8, 0x5c,
0x9e, 0xed, 0xa9, 0xd2, 0xe1, 0x63, 0xc2, 0xb9, 0x4c, 0xb6, 0xe9, 0x20, 0xec, 0x60,
0x0f, 0x7a, 0x1b, 0x0a,
],
rvk: [
0x0b, 0x68, 0xd5, 0x0f, 0x91, 0x3c, 0xd1, 0xb7, 0x8b, 0x59, 0x92, 0x1e, 0x16, 0x56,
0xd5, 0x76, 0xb0, 0xeb, 0x17, 0x1e, 0xd3, 0x87, 0x0d, 0x39, 0xfe, 0xc6, 0x94, 0x41,
0xb3, 0x4b, 0x25, 0x38,
],
m: [
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07,
],
sig: [
0x44, 0x6d, 0x67, 0x7c, 0x4c, 0xfe, 0xfd, 0x02, 0x4b, 0x0a, 0xeb, 0x37, 0xa5, 0x98,
0xcc, 0x2e, 0xb3, 0xd2, 0x9b, 0x02, 0x94, 0xfe, 0x5b, 0xb6, 0x97, 0x8e, 0x8b, 0x43,
0xd3, 0x2b, 0x2e, 0x4f, 0x09, 0x56, 0xac, 0xd1, 0x3e, 0x7e, 0x3a, 0x63, 0xa1, 0x8f,
0xca, 0x32, 0xd6, 0xab, 0x94, 0xb9, 0x4e, 0xd0, 0x33, 0xe9, 0xa1, 0x0f, 0xc5, 0x69,
0x28, 0xbc, 0x8a, 0x0f, 0x4f, 0x8e, 0x95, 0x00,
],
rsig: [
0x8d, 0xe0, 0x41, 0xe7, 0x09, 0xdb, 0x62, 0x4a, 0xe2, 0xbe, 0x16, 0x48, 0xb6, 0x62,
0x23, 0x9c, 0xde, 0xdf, 0x85, 0xec, 0xd3, 0x82, 0x26, 0x8b, 0x0e, 0x35, 0x54, 0xbf,
0xa0, 0xf2, 0x08, 0x1c, 0xd6, 0x41, 0xbc, 0xa0, 0x40, 0x78, 0xaa, 0x89, 0xf7, 0xdd,
0x25, 0x40, 0x58, 0x7c, 0xed, 0x6b, 0x45, 0x89, 0x16, 0xb1, 0x3e, 0x4b, 0x6a, 0x36,
0x30, 0xda, 0x69, 0x76, 0x46, 0xdb, 0xbf, 0x09,
],
},
TestVector {
sk: [
0x32, 0x16, 0xae, 0x47, 0xe9, 0xf5, 0x3e, 0x8a, 0x52, 0x79, 0x6f, 0x24, 0xb6, 0x24,
0x60, 0x77, 0x6b, 0xd5, 0xf2, 0x05, 0xa7, 0x8e, 0x15, 0x95, 0xbc, 0x8e, 0xfe, 0xdc,
0x51, 0x9d, 0x36, 0x0b,
],
vk: [
0xdf, 0x74, 0xbf, 0x04, 0x79, 0x61, 0xcc, 0x5c, 0xda, 0xc8, 0x28, 0x90, 0xc7, 0x6e,
0xc6, 0x75, 0xbd, 0x4e, 0x89, 0xea, 0xd2, 0x80, 0xc9, 0x52, 0xd7, 0xc3, 0x3e, 0xea,
0xf2, 0xb5, 0xa6, 0x6b,
],
alpha: [
0xc9, 0x61, 0xf2, 0xdd, 0x93, 0x68, 0x2a, 0xdb, 0x93, 0xf5, 0xc0, 0x5a, 0x73, 0xfd,
0xbc, 0x6d, 0x43, 0xc7, 0x0e, 0x1b, 0x15, 0xe8, 0xd5, 0x3e, 0x3f, 0x17, 0xa8, 0x24,
0x94, 0xe3, 0xf2, 0x09,
],
rsk: [
0x44, 0x4b, 0xa9, 0x4e, 0x1e, 0x50, 0xd2, 0x94, 0x63, 0x5e, 0x68, 0xb2, 0x95, 0x01,
0xb5, 0x3e, 0xae, 0x61, 0xcd, 0x1f, 0xbb, 0x3b, 0x84, 0xcd, 0x52, 0xf6, 0x72, 0x9c,
0xfb, 0xcb, 0xab, 0x06,
],
rvk: [
0x0a, 0xfb, 0xe4, 0x06, 0xa8, 0x91, 0xc3, 0xb8, 0xc3, 0x10, 0xc2, 0x15, 0xbc, 0x68,
0xa9, 0x13, 0xde, 0x7c, 0xda, 0x06, 0xaf, 0x29, 0x42, 0x00, 0x56, 0x46, 0x8d, 0x0c,
0x08, 0x85, 0x5b, 0x28,
],
m: [
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08,
],
sig: [
0x99, 0x35, 0x80, 0xef, 0x93, 0x34, 0x9a, 0x1c, 0x9e, 0xe9, 0x60, 0xca, 0x3e, 0x7c,
0xd0, 0x4c, 0x13, 0xb4, 0xa0, 0xec, 0x4f, 0xd1, 0x80, 0x53, 0xa1, 0x9c, 0xff, 0x77,
0x63, 0x62, 0x09, 0x65, 0xfb, 0xee, 0x96, 0xc1, 0x64, 0x72, 0x30, 0xe3, 0x73, 0xcb,
0x82, 0xb8, 0x1d, 0x00, 0x03, 0x92, 0x23, 0xd3, 0x0b, 0x39, 0x3e, 0xd1, 0x72, 0xc9,
0xb3, 0xc5, 0x63, 0xc6, 0x11, 0x79, 0x22, 0x05,
],
rsig: [
0xcc, 0x7a, 0xae, 0x1c, 0xed, 0xad, 0x2d, 0x7f, 0x6c, 0xe0, 0x4c, 0x19, 0xc5, 0xa5,
0xb6, 0xb7, 0xa6, 0xa0, 0x82, 0x78, 0x5c, 0x54, 0x0c, 0x14, 0xf6, 0x30, 0x9b, 0x06,
0x4d, 0x1f, 0xfa, 0x68, 0x17, 0x29, 0x53, 0xfb, 0xa0, 0xc2, 0xfc, 0xfb, 0x87, 0x5c,
0xa7, 0xf7, 0xea, 0x98, 0xef, 0x55, 0xa0, 0x40, 0x2f, 0xd5, 0x29, 0xcf, 0xcd, 0xdf,
0x99, 0x6c, 0xa2, 0xb8, 0xca, 0x89, 0x90, 0x0a,
],
},
TestVector {
sk: [
0x85, 0x83, 0x6f, 0x98, 0x32, 0xb2, 0x8d, 0xe7, 0xc6, 0x36, 0x13, 0xe2, 0xa6, 0xed,
0x36, 0xfb, 0x1a, 0xb4, 0x4f, 0xb0, 0xc1, 0x3f, 0xa8, 0x79, 0x8c, 0xd9, 0xcd, 0x30,
0x30, 0xd4, 0x55, 0x03,
],
vk: [
0xbf, 0xd5, 0xbc, 0x00, 0xc7, 0xc0, 0x22, 0xaa, 0x89, 0x01, 0xae, 0x08, 0x3c, 0x12,
0xd5, 0x4b, 0x82, 0xf0, 0xdd, 0xff, 0x8e, 0xd6, 0xdb, 0x9a, 0x12, 0xd5, 0x9a, 0x5e,
0xf6, 0xa5, 0xa2, 0xe0,
],
alpha: [
0xa2, 0xe8, 0xb9, 0xe1, 0x6d, 0x6f, 0xf3, 0xca, 0x6c, 0x53, 0xd4, 0xe8, 0x8a, 0xbb,
0xb9, 0x9b, 0xe7, 0xaf, 0x7e, 0x36, 0x59, 0x63, 0x1f, 0x1e, 0xae, 0x1e, 0xff, 0x23,
0x87, 0x4d, 0x8e, 0x0c,
],
rsk: [
0x70, 0x3f, 0x32, 0xa3, 0x41, 0x13, 0xea, 0xe1, 0xb0, 0x79, 0x1f, 0xfe, 0x9d, 0x88,
0x88, 0xf0, 0x01, 0x29, 0x9a, 0xe5, 0x19, 0x68, 0x60, 0x91, 0x91, 0x48, 0x99, 0xef,
0xcc, 0x6c, 0x66, 0x01,
],
rvk: [
0xeb, 0x92, 0x97, 0x03, 0x6c, 0xf5, 0x17, 0xe1, 0x5e, 0x9e, 0xfe, 0x39, 0x75, 0x32,
0x8d, 0xb4, 0x8e, 0xe7, 0xc2, 0x69, 0x4e, 0x94, 0x6d, 0xb2, 0x5f, 0x52, 0x87, 0x88,
0xf6, 0xa1, 0xdb, 0x14,
],
m: [
0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
0x09, 0x09, 0x09, 0x09,
],
sig: [
0xce, 0x90, 0xdd, 0xf4, 0xaf, 0x21, 0xaa, 0xc4, 0xd9, 0x41, 0x93, 0xea, 0x16, 0xff,
0x35, 0xcd, 0x93, 0x79, 0x20, 0x4e, 0x7d, 0x8f, 0xf4, 0xc0, 0xf5, 0x41, 0x17, 0xab,
0xb1, 0x6b, 0x7c, 0x85, 0xa0, 0xb1, 0x97, 0xcf, 0x13, 0xab, 0x14, 0xd7, 0xc3, 0xba,
0x68, 0x01, 0x0a, 0xb8, 0x05, 0x12, 0x25, 0x91, 0x3b, 0xdb, 0xc3, 0x9a, 0x51, 0xf6,
0x03, 0x7a, 0xfc, 0x6c, 0xee, 0xcb, 0x0b, 0x06,
],
rsig: [
0xa8, 0x47, 0x74, 0x2e, 0x94, 0x01, 0xcf, 0x22, 0x39, 0x21, 0x3d, 0xc8, 0x81, 0x3e,
0x97, 0x72, 0xe9, 0x7a, 0xf8, 0xd6, 0x7a, 0xdf, 0xfe, 0xab, 0xc8, 0xe6, 0x7f, 0x5d,
0x2d, 0x90, 0xd0, 0xb4, 0x1b, 0xc2, 0x5b, 0x05, 0xf9, 0x4a, 0xce, 0x16, 0x8a, 0xec,
0xc6, 0x58, 0x3e, 0x18, 0xf7, 0x63, 0x74, 0x92, 0xf3, 0x7a, 0x9c, 0xa3, 0x00, 0x20,
0x2b, 0xc0, 0x65, 0xab, 0xd3, 0x80, 0xec, 0x00,
],
},
]
}

142
src/tree.rs Normal file
View File

@ -0,0 +1,142 @@
use bitvec::{order::Lsb0, view::AsBits};
use group::{ff::PrimeField, Curve};
use incrementalmerkletree::{Hashable, Level};
use lazy_static::lazy_static;
use subtle::CtOption;
use std::fmt;
use super::{
note::ExtractedNoteCommitment,
pedersen_hash::{pedersen_hash, Personalization},
};
pub const NOTE_COMMITMENT_TREE_DEPTH: u8 = 32;
pub type CommitmentTree =
incrementalmerkletree::frontier::CommitmentTree<Node, NOTE_COMMITMENT_TREE_DEPTH>;
pub type IncrementalWitness =
incrementalmerkletree::witness::IncrementalWitness<Node, NOTE_COMMITMENT_TREE_DEPTH>;
pub type MerklePath = incrementalmerkletree::MerklePath<Node, NOTE_COMMITMENT_TREE_DEPTH>;
lazy_static! {
static ref UNCOMMITTED_SAPLING: bls12_381::Scalar = bls12_381::Scalar::one();
static ref EMPTY_ROOTS: Vec<Node> = {
let mut v = vec![Node::empty_leaf()];
for d in 0..NOTE_COMMITMENT_TREE_DEPTH {
let next = Node::combine(d.into(), &v[usize::from(d)], &v[usize::from(d)]);
v.push(next);
}
v
};
}
/// Compute a parent node in the Sapling commitment tree given its two children.
pub fn merkle_hash(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> [u8; 32] {
merkle_hash_field(depth, lhs, rhs).to_repr()
}
fn merkle_hash_field(depth: usize, lhs: &[u8; 32], rhs: &[u8; 32]) -> jubjub::Base {
let lhs = {
let mut tmp = [false; 256];
for (a, b) in tmp.iter_mut().zip(lhs.as_bits::<Lsb0>()) {
*a = *b;
}
tmp
};
let rhs = {
let mut tmp = [false; 256];
for (a, b) in tmp.iter_mut().zip(rhs.as_bits::<Lsb0>()) {
*a = *b;
}
tmp
};
jubjub::ExtendedPoint::from(pedersen_hash(
Personalization::MerkleTree(depth),
lhs.iter()
.copied()
.take(bls12_381::Scalar::NUM_BITS as usize)
.chain(
rhs.iter()
.copied()
.take(bls12_381::Scalar::NUM_BITS as usize),
),
))
.to_affine()
.get_u()
}
/// A node within the Sapling commitment tree.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Node(jubjub::Base);
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Node")
.field("repr", &hex::encode(self.0.to_bytes()))
.finish()
}
}
impl Node {
/// Creates a tree leaf from the given Sapling note commitment.
pub fn from_cmu(value: &ExtractedNoteCommitment) -> Self {
Node(value.inner())
}
/// Constructs a new note commitment tree node from a [`bls12_381::Scalar`]
pub fn from_scalar(cmu: bls12_381::Scalar) -> Self {
Self(cmu)
}
/// Parses a tree leaf from the bytes of a Sapling note commitment.
///
/// Returns `None` if the provided bytes represent a non-canonical encoding.
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
jubjub::Base::from_repr(bytes).map(Self)
}
/// Returns the canonical byte representation of this node.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_repr()
}
}
impl Hashable for Node {
fn empty_leaf() -> Self {
Node(*UNCOMMITTED_SAPLING)
}
fn combine(level: Level, lhs: &Self, rhs: &Self) -> Self {
Node(merkle_hash_field(
level.into(),
&lhs.0.to_bytes(),
&rhs.0.to_bytes(),
))
}
fn empty_root(level: Level) -> Self {
EMPTY_ROOTS[<usize>::from(level)]
}
}
impl From<Node> for bls12_381::Scalar {
fn from(node: Node) -> Self {
node.0
}
}
#[cfg(any(test, feature = "test-dependencies"))]
pub(super) mod testing {
use proptest::prelude::*;
use super::Node;
use crate::note::testing::arb_cmu;
prop_compose! {
pub fn arb_node()(cmu in arb_cmu()) -> Node {
Node::from_cmu(&cmu)
}
}
}

View File

@ -1,11 +1,34 @@
use blake2_rfc::blake2b::Blake2b;
use blake2b_simd::Params;
use ff::Field;
use rand_core::{CryptoRng, RngCore};
use jubjub::{JubjubEngine, ToUniform};
use super::{note_encryption::Zip212Enforcement, Rseed};
pub fn hash_to_scalar<E: JubjubEngine>(persona: &[u8], a: &[u8], b: &[u8]) -> E::Fs {
let mut hasher = Blake2b::with_params(64, &[], &[], persona);
pub fn hash_to_scalar(persona: &[u8], a: &[u8], b: &[u8]) -> jubjub::Fr {
let mut hasher = Params::new().hash_length(64).personal(persona).to_state();
hasher.update(a);
hasher.update(b);
let ret = hasher.finalize();
E::Fs::to_uniform(ret.as_ref())
jubjub::Fr::from_bytes_wide(ret.as_array())
}
pub fn generate_random_rseed<R: RngCore + CryptoRng>(
zip212_enforcement: Zip212Enforcement,
rng: &mut R,
) -> Rseed {
generate_random_rseed_internal(zip212_enforcement, rng)
}
pub(crate) fn generate_random_rseed_internal<R: RngCore>(
zip212_enforcement: Zip212Enforcement,
rng: &mut R,
) -> Rseed {
match zip212_enforcement {
Zip212Enforcement::Off => Rseed::BeforeZip212(jubjub::Fr::random(rng)),
Zip212Enforcement::GracePeriod | Zip212Enforcement::On => {
let mut buffer = [0u8; 32];
rng.fill_bytes(&mut buffer);
Rseed::AfterZip212(buffer)
}
}
}

264
src/value.rs Normal file
View File

@ -0,0 +1,264 @@
//! Monetary values within the Sapling shielded pool.
//!
//! Values are represented in three places within the Sapling protocol:
//! - [`NoteValue`], the value of an individual note. It is an unsigned 64-bit integer
//! (with maximum value [`MAX_NOTE_VALUE`]), and is serialized in a note plaintext.
//! - [`ValueSum`], the sum of note values within a Sapling [`Bundle`]. It is represented
//! as an `i128` and places an upper bound on the maximum number of notes within a
//! single [`Bundle`].
//! - `valueBalanceSapling`, which is a signed 63-bit integer. This is represented
//! by a user-defined type parameter on [`Bundle`], returned by
//! [`Bundle::value_balance`] and [`SaplingBuilder::value_balance`].
//!
//! If your specific instantiation of the Sapling protocol requires a smaller bound on
//! valid note values (for example, Zcash's `MAX_MONEY` fits into a 51-bit integer), you
//! should enforce this in two ways:
//!
//! - Define your `valueBalanceSapling` type to enforce your valid value range. This can
//! be checked in its `TryFrom<i64>` implementation.
//! - Define your own "amount" type for note values, and convert it to `NoteValue` prior
//! to calling [`SaplingBuilder::add_output`].
//!
//! Inside the circuit, note values are constrained to be unsigned 64-bit integers.
//!
//! # Caution!
//!
//! An `i64` is _not_ a signed 64-bit integer! The [Rust documentation] calls `i64` the
//! 64-bit signed integer type, which is true in the sense that its encoding in memory
//! takes up 64 bits. Numerically, however, `i64` is a signed 63-bit integer.
//!
//! Fortunately, users of this crate should never need to construct [`ValueSum`] directly;
//! you should only need to interact with [`NoteValue`] (which can be safely constructed
//! from a `u64`) and `valueBalanceSapling` (which can be represented as an `i64`).
//!
//! [`Bundle`]: crate::sapling::Bundle
//! [`Bundle::value_balance`]: crate::sapling::Bundle::value_balance
//! [`SaplingBuilder::value_balance`]: crate::sapling::builder::SaplingBuilder::value_balance
//! [`SaplingBuilder::add_output`]: crate::sapling::builder::SaplingBuilder::add_output
//! [Rust documentation]: https://doc.rust-lang.org/stable/std/primitive.i64.html
use bitvec::{array::BitArray, order::Lsb0};
use ff::{Field, PrimeField};
use group::GroupEncoding;
use rand::RngCore;
use subtle::CtOption;
use super::constants::{VALUE_COMMITMENT_RANDOMNESS_GENERATOR, VALUE_COMMITMENT_VALUE_GENERATOR};
mod sums;
pub use sums::{CommitmentSum, OverflowError, TrapdoorSum, ValueSum};
/// Maximum note value.
pub const MAX_NOTE_VALUE: u64 = u64::MAX;
/// The non-negative value of an individual Sapling note.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NoteValue(u64);
impl NoteValue {
/// Returns the raw underlying value.
pub fn inner(&self) -> u64 {
self.0
}
/// Creates a note value from its raw numeric value.
///
/// This only enforces that the value is an unsigned 64-bit integer. Callers should
/// enforce any additional constraints on the value's valid range themselves.
pub fn from_raw(value: u64) -> Self {
NoteValue(value)
}
pub(crate) fn from_bytes(bytes: [u8; 8]) -> Self {
NoteValue(u64::from_le_bytes(bytes))
}
pub(crate) fn to_le_bits(self) -> BitArray<[u8; 8], Lsb0> {
BitArray::<_, Lsb0>::new(self.0.to_le_bytes())
}
}
/// The blinding factor for a [`ValueCommitment`].
#[derive(Clone, Debug)]
pub struct ValueCommitTrapdoor(jubjub::Scalar);
impl ValueCommitTrapdoor {
/// Generates a new value commitment trapdoor.
///
/// This is public for access by `zcash_proofs`.
pub fn random(rng: impl RngCore) -> Self {
ValueCommitTrapdoor(jubjub::Scalar::random(rng))
}
/// Constructs `ValueCommitTrapdoor` from the byte representation of a scalar.
///
/// Returns a `None` [`CtOption`] if `bytes` is not a canonical representation of a
/// Jubjub scalar.
///
/// This is a low-level API, requiring a detailed understanding of the
/// [use of value commitment trapdoors][saplingbalance] in the Zcash protocol
/// to use correctly and securely. It is intended to be used in combination
/// with [`ValueCommitment::derive`].
///
/// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Self> {
jubjub::Scalar::from_repr(bytes).map(ValueCommitTrapdoor)
}
/// Returns the inner Jubjub scalar representing this trapdoor.
///
/// This is public for access by `zcash_proofs`.
pub fn inner(&self) -> jubjub::Scalar {
self.0
}
}
/// A commitment to a [`ValueSum`].
///
/// # Consensus rules
///
/// The Zcash Protocol Spec requires Sapling Spend Descriptions and Output Descriptions to
/// not contain a small order `ValueCommitment`. However, the `ValueCommitment` type as
/// specified (and implemented here) may contain a small order point. In practice, it will
/// not occur:
/// - [`ValueCommitment::derive`] will only produce a small order point if both the given
/// [`NoteValue`] and [`ValueCommitTrapdoor`] are zero. However, the only constructor
/// available for `ValueCommitTrapdoor` is [`ValueCommitTrapdoor::random`], which will
/// produce zero with negligible probability (assuming a non-broken PRNG).
/// - [`ValueCommitment::from_bytes_not_small_order`] enforces this by definition, and is
/// the only constructor that can be used with data received over the network.
#[derive(Clone, Debug)]
pub struct ValueCommitment(jubjub::ExtendedPoint);
impl ValueCommitment {
/// Derives a `ValueCommitment` by $\mathsf{ValueCommit^{Sapling}}$.
///
/// Defined in [Zcash Protocol Spec § 5.4.8.3: Homomorphic Pedersen commitments (Sapling and Orchard)][concretehomomorphiccommit].
///
/// [concretehomomorphiccommit]: https://zips.z.cash/protocol/protocol.pdf#concretehomomorphiccommit
pub fn derive(value: NoteValue, rcv: ValueCommitTrapdoor) -> Self {
let cv = (VALUE_COMMITMENT_VALUE_GENERATOR * jubjub::Scalar::from(value.0))
+ (VALUE_COMMITMENT_RANDOMNESS_GENERATOR * rcv.0);
ValueCommitment(cv.into())
}
/// Returns the inner Jubjub point representing this value commitment.
///
/// This is public for access by `zcash_proofs`.
pub fn as_inner(&self) -> &jubjub::ExtendedPoint {
&self.0
}
/// Deserializes a value commitment from its byte representation.
///
/// Returns `None` if `bytes` is an invalid representation of a Jubjub point, or the
/// resulting point is of small order.
///
/// This method can be used to enforce the "not small order" consensus rules defined
/// in [Zcash Protocol Spec § 4.4: Spend Descriptions][spenddesc] and
/// [§ 4.5: Output Descriptions][outputdesc].
///
/// [spenddesc]: https://zips.z.cash/protocol/protocol.pdf#spenddesc
/// [outputdesc]: https://zips.z.cash/protocol/protocol.pdf#outputdesc
pub fn from_bytes_not_small_order(bytes: &[u8; 32]) -> CtOption<ValueCommitment> {
jubjub::ExtendedPoint::from_bytes(bytes)
.and_then(|cv| CtOption::new(ValueCommitment(cv), !cv.is_small_order()))
}
/// Serializes this value commitment to its canonical byte representation.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}
}
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
pub mod testing {
use proptest::prelude::*;
use super::{NoteValue, ValueCommitTrapdoor, MAX_NOTE_VALUE};
prop_compose! {
/// Generate an arbitrary value in the range of valid nonnegative amounts.
pub fn arb_note_value()(value in 0u64..MAX_NOTE_VALUE) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
/// Generate an arbitrary value in the range of valid positive amounts less than a
/// specified value.
pub fn arb_note_value_bounded(max: u64)(value in 0u64..max) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
/// Generate an arbitrary value in the range of valid positive amounts less than a
/// specified value.
pub fn arb_positive_note_value(max: u64)(value in 1u64..max) -> NoteValue {
NoteValue(value)
}
}
prop_compose! {
/// Generate an arbitrary Jubjub scalar.
fn arb_scalar()(bytes in prop::array::uniform32(0u8..)) -> jubjub::Scalar {
// Instead of rejecting out-of-range bytes, let's reduce them.
let mut buf = [0; 64];
buf[..32].copy_from_slice(&bytes);
jubjub::Scalar::from_bytes_wide(&buf)
}
}
prop_compose! {
/// Generate an arbitrary ValueCommitTrapdoor
pub fn arb_trapdoor()(rcv in arb_scalar()) -> ValueCommitTrapdoor {
ValueCommitTrapdoor(rcv)
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{
testing::{arb_note_value_bounded, arb_trapdoor},
CommitmentSum, OverflowError, TrapdoorSum, ValueCommitment, ValueSum,
};
proptest! {
#[test]
fn bsk_consistent_with_bvk(
values in (1usize..10).prop_flat_map(|n_values| prop::collection::vec(
(arb_note_value_bounded((i64::MAX as u64) / (n_values as u64)), arb_trapdoor()),
n_values,
))
) {
let value_balance: i64 = values
.iter()
.map(|(value, _)| value)
.sum::<Result<ValueSum, OverflowError>>()
.expect("we generate values that won't overflow")
.try_into()
.unwrap();
let bsk = values
.iter()
.map(|(_, rcv)| rcv)
.sum::<TrapdoorSum>()
.into_bsk();
let bvk = values
.into_iter()
.map(|(value, rcv)| ValueCommitment::derive(value, rcv))
.sum::<CommitmentSum>()
.into_bvk(value_balance);
assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
}
}
}

241
src/value/sums.rs Normal file
View File

@ -0,0 +1,241 @@
use core::fmt::{self, Debug};
use core::iter::Sum;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use group::GroupEncoding;
use redjubjub::Binding;
use super::{NoteValue, ValueCommitTrapdoor, ValueCommitment};
use crate::constants::VALUE_COMMITMENT_VALUE_GENERATOR;
/// A value operation overflowed.
#[derive(Debug)]
pub struct OverflowError;
impl fmt::Display for OverflowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Sapling value operation overflowed")
}
}
impl std::error::Error for OverflowError {}
/// A sum of Sapling note values.
///
/// [Zcash Protocol Spec § 4.13: Balance and Binding Signature (Sapling)][saplingbalance]
/// constrains the range of this type to between `[-(r_J - 1)/2..(r_J - 1)/2]` in the
/// abstract protocol, and `[38913406623490299131842..104805176454780817500623]` in the
/// concrete Zcash protocol. We represent it as an `i128`, which has a range large enough
/// to handle Zcash transactions while small enough to ensure the abstract protocol bounds
/// are not breached.
///
/// [saplingbalance]: https://zips.z.cash/protocol/protocol.pdf#saplingbalance
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ValueSum(i128);
impl ValueSum {
/// Initializes a sum of `NoteValue`s to zero.
pub fn zero() -> Self {
ValueSum(0)
}
}
impl Add<NoteValue> for ValueSum {
type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: NoteValue) -> Self::Output {
self.0.checked_add(rhs.0.into()).map(ValueSum)
}
}
impl Sub<NoteValue> for ValueSum {
type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: NoteValue) -> Self::Output {
self.0.checked_sub(rhs.0.into()).map(ValueSum)
}
}
impl<'a> Sum<&'a NoteValue> for Result<ValueSum, OverflowError> {
fn sum<I: Iterator<Item = &'a NoteValue>>(iter: I) -> Self {
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + *v).ok_or(OverflowError))
}
}
impl Sum<NoteValue> for Result<ValueSum, OverflowError> {
fn sum<I: Iterator<Item = NoteValue>>(iter: I) -> Self {
iter.fold(Ok(ValueSum(0)), |acc, v| (acc? + v).ok_or(OverflowError))
}
}
impl TryFrom<ValueSum> for i64 {
type Error = OverflowError;
fn try_from(v: ValueSum) -> Result<i64, Self::Error> {
i64::try_from(v.0).map_err(|_| OverflowError)
}
}
/// A sum of Sapling value commitment blinding factors.
#[derive(Clone, Copy, Debug)]
pub struct TrapdoorSum(jubjub::Scalar);
impl TrapdoorSum {
/// Initializes a sum of `ValueCommitTrapdoor`s to zero.
pub fn zero() -> Self {
TrapdoorSum(jubjub::Scalar::zero())
}
/// Transform this trapdoor sum into the corresponding RedJubjub private key.
///
/// This is public for access by `zcash_proofs`.
pub fn into_bsk(self) -> redjubjub::SigningKey<Binding> {
redjubjub::SigningKey::try_from(self.0.to_bytes())
.expect("valid scalars are valid signing keys")
}
}
impl Add<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
type Output = TrapdoorSum;
fn add(self, rhs: &Self) -> Self::Output {
TrapdoorSum(self.0 + rhs.0)
}
}
impl Add<&ValueCommitTrapdoor> for TrapdoorSum {
type Output = TrapdoorSum;
fn add(self, rhs: &ValueCommitTrapdoor) -> Self::Output {
TrapdoorSum(self.0 + rhs.0)
}
}
impl AddAssign<&ValueCommitTrapdoor> for TrapdoorSum {
fn add_assign(&mut self, rhs: &ValueCommitTrapdoor) {
self.0 += rhs.0;
}
}
impl Sub<&ValueCommitTrapdoor> for ValueCommitTrapdoor {
type Output = TrapdoorSum;
fn sub(self, rhs: &Self) -> Self::Output {
TrapdoorSum(self.0 - rhs.0)
}
}
impl Sub<TrapdoorSum> for TrapdoorSum {
type Output = TrapdoorSum;
fn sub(self, rhs: Self) -> Self::Output {
TrapdoorSum(self.0 - rhs.0)
}
}
impl SubAssign<&ValueCommitTrapdoor> for TrapdoorSum {
fn sub_assign(&mut self, rhs: &ValueCommitTrapdoor) {
self.0 -= rhs.0;
}
}
impl<'a> Sum<&'a ValueCommitTrapdoor> for TrapdoorSum {
fn sum<I: Iterator<Item = &'a ValueCommitTrapdoor>>(iter: I) -> Self {
iter.fold(TrapdoorSum::zero(), |acc, cv| acc + cv)
}
}
/// A sum of Sapling value commitments.
#[derive(Clone, Copy, Debug)]
pub struct CommitmentSum(jubjub::ExtendedPoint);
impl CommitmentSum {
/// Initializes a sum of `ValueCommitment`s to zero.
pub fn zero() -> Self {
CommitmentSum(jubjub::ExtendedPoint::identity())
}
/// Transform this value commitment sum into the corresponding RedJubjub public key.
///
/// This is public for access by `zcash_proofs`.
pub fn into_bvk<V: Into<i64>>(self, value_balance: V) -> redjubjub::VerificationKey<Binding> {
let value: i64 = value_balance.into();
// Compute the absolute value.
let abs_value = match value.checked_abs() {
Some(v) => u64::try_from(v).expect("v is non-negative"),
None => 1u64 << 63,
};
// Construct the field representation of the signed value.
let value_balance = if value.is_negative() {
-jubjub::Scalar::from(abs_value)
} else {
jubjub::Scalar::from(abs_value)
};
// Subtract `value_balance` from the sum to get the final bvk.
let bvk = self.0 - VALUE_COMMITMENT_VALUE_GENERATOR * value_balance;
redjubjub::VerificationKey::try_from(bvk.to_bytes())
.expect("valid points are valid verification keys")
}
}
impl Add<&ValueCommitment> for ValueCommitment {
type Output = CommitmentSum;
fn add(self, rhs: &Self) -> Self::Output {
CommitmentSum(self.0 + rhs.0)
}
}
impl Add<&ValueCommitment> for CommitmentSum {
type Output = CommitmentSum;
fn add(self, rhs: &ValueCommitment) -> Self::Output {
CommitmentSum(self.0 + rhs.0)
}
}
impl AddAssign<&ValueCommitment> for CommitmentSum {
fn add_assign(&mut self, rhs: &ValueCommitment) {
self.0 += rhs.0;
}
}
impl Sub<&ValueCommitment> for ValueCommitment {
type Output = CommitmentSum;
fn sub(self, rhs: &Self) -> Self::Output {
CommitmentSum(self.0 - rhs.0)
}
}
impl SubAssign<&ValueCommitment> for CommitmentSum {
fn sub_assign(&mut self, rhs: &ValueCommitment) {
self.0 -= rhs.0;
}
}
impl Sub<CommitmentSum> for CommitmentSum {
type Output = CommitmentSum;
fn sub(self, rhs: Self) -> Self::Output {
CommitmentSum(self.0 - rhs.0)
}
}
impl Sum<ValueCommitment> for CommitmentSum {
fn sum<I: Iterator<Item = ValueCommitment>>(iter: I) -> Self {
iter.fold(CommitmentSum::zero(), |acc, cv| acc + &cv)
}
}
impl<'a> Sum<&'a ValueCommitment> for CommitmentSum {
fn sum<I: Iterator<Item = &'a ValueCommitment>>(iter: I) -> Self {
iter.fold(CommitmentSum::zero(), |acc, cv| acc + cv)
}
}

149
src/verifier.rs Normal file
View File

@ -0,0 +1,149 @@
use bellman::{gadgets::multipack, groth16::Proof};
use bls12_381::Bls12;
use group::{ff::PrimeField, Curve};
use redjubjub::{Binding, SpendAuth};
use crate::{
note::ExtractedNoteCommitment,
value::{CommitmentSum, ValueCommitment},
};
mod single;
pub use single::SaplingVerificationContext;
mod batch;
pub use batch::BatchValidator;
/// A context object for verifying the Sapling components of a Zcash transaction.
struct SaplingVerificationContextInner {
// (sum of the Spend value commitments) - (sum of the Output value commitments)
cv_sum: CommitmentSum,
}
impl SaplingVerificationContextInner {
/// Construct a new context to be used with a single transaction.
fn new() -> Self {
SaplingVerificationContextInner {
cv_sum: CommitmentSum::zero(),
}
}
/// Perform consensus checks on a Sapling SpendDescription, while
/// accumulating its value commitment inside the context for later use.
#[allow(clippy::too_many_arguments)]
fn check_spend<C>(
&mut self,
cv: &ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: &[u8; 32],
rk: &redjubjub::VerificationKey<SpendAuth>,
zkproof: Proof<Bls12>,
verifier_ctx: &mut C,
spend_auth_sig_verifier: impl FnOnce(&mut C, &redjubjub::VerificationKey<SpendAuth>) -> bool,
proof_verifier: impl FnOnce(&mut C, Proof<Bls12>, [bls12_381::Scalar; 7]) -> bool,
) -> bool {
// The "cv is not small order" happens when a SpendDescription is deserialized.
// This happens when transactions or blocks are received over the network, or when
// mined blocks are introduced via the `submitblock` RPC method on full nodes.
let rk_affine = jubjub::AffinePoint::from_bytes((*rk).into()).unwrap();
if rk_affine.is_small_order().into() {
return false;
}
// Accumulate the value commitment in the context
self.cv_sum += cv;
// Grab the nullifier as a sequence of bytes
let nullifier = &nullifier[..];
// Verify the spend_auth_sig
if !spend_auth_sig_verifier(verifier_ctx, rk) {
return false;
}
// Construct public input for circuit
let mut public_input = [bls12_381::Scalar::zero(); 7];
{
let affine = rk_affine;
let (u, v) = (affine.get_u(), affine.get_v());
public_input[0] = u;
public_input[1] = v;
}
{
let affine = cv.as_inner().to_affine();
let (u, v) = (affine.get_u(), affine.get_v());
public_input[2] = u;
public_input[3] = v;
}
public_input[4] = anchor;
// Add the nullifier through multiscalar packing
{
let nullifier = multipack::bytes_to_bits_le(nullifier);
let nullifier = multipack::compute_multipacking(&nullifier);
assert_eq!(nullifier.len(), 2);
public_input[5] = nullifier[0];
public_input[6] = nullifier[1];
}
// Verify the proof
proof_verifier(verifier_ctx, zkproof, public_input)
}
/// Perform consensus checks on a Sapling OutputDescription, while
/// accumulating its value commitment inside the context for later use.
fn check_output(
&mut self,
cv: &ValueCommitment,
cmu: ExtractedNoteCommitment,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
proof_verifier: impl FnOnce(Proof<Bls12>, [bls12_381::Scalar; 5]) -> bool,
) -> bool {
// The "cv is not small order" happens when an OutputDescription is deserialized.
// This happens when transactions or blocks are received over the network, or when
// mined blocks are introduced via the `submitblock` RPC method on full nodes.
if epk.is_small_order().into() {
return false;
}
// Accumulate the value commitment in the context
self.cv_sum -= cv;
// Construct public input for circuit
let mut public_input = [bls12_381::Scalar::zero(); 5];
{
let affine = cv.as_inner().to_affine();
let (u, v) = (affine.get_u(), affine.get_v());
public_input[0] = u;
public_input[1] = v;
}
{
let affine = epk.to_affine();
let (u, v) = (affine.get_u(), affine.get_v());
public_input[2] = u;
public_input[3] = v;
}
public_input[4] = bls12_381::Scalar::from_repr(cmu.to_bytes()).unwrap();
// Verify the proof
proof_verifier(zkproof, public_input)
}
/// Perform consensus checks on the valueBalance and bindingSig parts of a
/// Sapling transaction. All SpendDescriptions and OutputDescriptions must
/// have been checked before calling this function.
fn final_check<V: Into<i64>>(
&self,
value_balance: V,
binding_sig_verifier: impl FnOnce(redjubjub::VerificationKey<Binding>) -> bool,
) -> bool {
// Compute the final bvk.
let bvk = self.cv_sum.into_bvk(value_balance);
// Verify the binding_sig
binding_sig_verifier(bvk)
}
}

166
src/verifier/batch.rs Normal file
View File

@ -0,0 +1,166 @@
use bellman::groth16;
use bls12_381::Bls12;
use group::GroupEncoding;
use rand_core::{CryptoRng, RngCore};
use super::SaplingVerificationContextInner;
use crate::{
bundle::{Authorized, Bundle},
circuit::{OutputVerifyingKey, SpendVerifyingKey},
};
/// Batch validation context for Sapling.
///
/// This batch-validates Spend and Output proofs, and RedJubjub signatures.
///
/// Signatures are verified assuming ZIP 216 is active.
pub struct BatchValidator {
bundles_added: bool,
spend_proofs: groth16::batch::Verifier<Bls12>,
output_proofs: groth16::batch::Verifier<Bls12>,
signatures: redjubjub::batch::Verifier,
}
impl Default for BatchValidator {
fn default() -> Self {
Self::new()
}
}
impl BatchValidator {
/// Constructs a new batch validation context.
pub fn new() -> Self {
BatchValidator {
bundles_added: false,
spend_proofs: groth16::batch::Verifier::new(),
output_proofs: groth16::batch::Verifier::new(),
signatures: redjubjub::batch::Verifier::new(),
}
}
/// Checks the bundle against Sapling-specific consensus rules, and adds its proof and
/// signatures to the validator.
///
/// Returns `false` if the bundle doesn't satisfy all of the consensus rules. This
/// `BatchValidator` can continue to be used regardless, but some or all of the proofs
/// and signatures from this bundle may have already been added to the batch even if
/// it fails other consensus rules.
pub fn check_bundle<V: Copy + Into<i64>>(
&mut self,
bundle: Bundle<Authorized, V>,
sighash: [u8; 32],
) -> bool {
self.bundles_added = true;
let mut ctx = SaplingVerificationContextInner::new();
for spend in bundle.shielded_spends() {
// Deserialize the proof
let zkproof = match groth16::Proof::read(&spend.zkproof()[..]) {
Ok(p) => p,
Err(_) => return false,
};
// Check the Spend consensus rules, and batch its proof and spend
// authorization signature.
let consensus_rules_passed = ctx.check_spend(
spend.cv(),
*spend.anchor(),
&spend.nullifier().0,
spend.rk(),
zkproof,
self,
|this, rk| {
this.signatures
.queue(((*rk).into(), *spend.spend_auth_sig(), &sighash));
true
},
|this, proof, public_inputs| {
this.spend_proofs.queue((proof, public_inputs.to_vec()));
true
},
);
if !consensus_rules_passed {
return false;
}
}
for output in bundle.shielded_outputs() {
// Deserialize the ephemeral key
let epk = match jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key().0).into() {
Some(p) => p,
None => return false,
};
// Deserialize the proof
let zkproof = match groth16::Proof::read(&output.zkproof()[..]) {
Ok(p) => p,
Err(_) => return false,
};
// Check the Output consensus rules, and batch its proof.
let consensus_rules_passed = ctx.check_output(
output.cv(),
*output.cmu(),
epk,
zkproof,
|proof, public_inputs| {
self.output_proofs.queue((proof, public_inputs.to_vec()));
true
},
);
if !consensus_rules_passed {
return false;
}
}
// Check the whole-bundle consensus rules, and batch the binding signature.
ctx.final_check(*bundle.value_balance(), |bvk| {
self.signatures
.queue((bvk.into(), bundle.authorization().binding_sig, &sighash));
true
})
}
/// Batch-validates the accumulated bundles.
///
/// Returns `true` if every proof and signature in every bundle added to the batch
/// validator is valid, or `false` if one or more are invalid. No attempt is made to
/// figure out which of the accumulated bundles might be invalid; if that information
/// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles.
pub fn validate<R: RngCore + CryptoRng>(
self,
spend_vk: &SpendVerifyingKey,
output_vk: &OutputVerifyingKey,
mut rng: R,
) -> bool {
if !self.bundles_added {
// An empty batch is always valid, but is not free to run; skip it.
return true;
}
if let Err(e) = self.signatures.verify(&mut rng) {
tracing::debug!("Signature batch validation failed: {}", e);
return false;
}
#[cfg(feature = "multicore")]
let verify_proofs = |batch: groth16::batch::Verifier<Bls12>, vk| batch.verify_multicore(vk);
#[cfg(not(feature = "multicore"))]
let mut verify_proofs =
|batch: groth16::batch::Verifier<Bls12>, vk| batch.verify(&mut rng, vk);
if verify_proofs(self.spend_proofs, &spend_vk.0).is_err() {
tracing::debug!("Spend proof batch validation failed");
return false;
}
if verify_proofs(self.output_proofs, &output_vk.0).is_err() {
tracing::debug!("Output proof batch validation failed");
return false;
}
true
}
}

83
src/verifier/single.rs Normal file
View File

@ -0,0 +1,83 @@
use bellman::groth16::{verify_proof, Proof};
use bls12_381::Bls12;
use redjubjub::{Binding, SpendAuth};
use super::SaplingVerificationContextInner;
use crate::{
circuit::{PreparedOutputVerifyingKey, PreparedSpendVerifyingKey},
note::ExtractedNoteCommitment,
value::ValueCommitment,
};
/// A context object for verifying the Sapling components of a single Zcash transaction.
pub struct SaplingVerificationContext {
inner: SaplingVerificationContextInner,
}
impl SaplingVerificationContext {
/// Construct a new context to be used with a single transaction.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
SaplingVerificationContext {
inner: SaplingVerificationContextInner::new(),
}
}
/// Perform consensus checks on a Sapling SpendDescription, while
/// accumulating its value commitment inside the context for later use.
#[allow(clippy::too_many_arguments)]
pub fn check_spend(
&mut self,
cv: &ValueCommitment,
anchor: bls12_381::Scalar,
nullifier: &[u8; 32],
rk: redjubjub::VerificationKey<SpendAuth>,
sighash_value: &[u8; 32],
spend_auth_sig: redjubjub::Signature<SpendAuth>,
zkproof: Proof<Bls12>,
verifying_key: &PreparedSpendVerifyingKey,
) -> bool {
self.inner.check_spend(
cv,
anchor,
nullifier,
&rk,
zkproof,
&mut (),
|_, rk| rk.verify(sighash_value, &spend_auth_sig).is_ok(),
|_, proof, public_inputs| {
verify_proof(&verifying_key.0, &proof, &public_inputs[..]).is_ok()
},
)
}
/// Perform consensus checks on a Sapling OutputDescription, while
/// accumulating its value commitment inside the context for later use.
pub fn check_output(
&mut self,
cv: &ValueCommitment,
cmu: ExtractedNoteCommitment,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
verifying_key: &PreparedOutputVerifyingKey,
) -> bool {
self.inner
.check_output(cv, cmu, epk, zkproof, |proof, public_inputs| {
verify_proof(&verifying_key.0, &proof, &public_inputs[..]).is_ok()
})
}
/// Perform consensus checks on the valueBalance and bindingSig parts of a
/// Sapling transaction. All SpendDescriptions and OutputDescriptions must
/// have been checked before calling this function.
pub fn final_check<V: Into<i64>>(
&self,
value_balance: V,
sighash_value: &[u8; 32],
binding_sig: redjubjub::Signature<Binding>,
) -> bool {
self.inner.final_check(value_balance, |bvk| {
bvk.verify(sighash_value, &binding_sig).is_ok()
})
}
}

1732
src/zip32.rs Normal file

File diff suppressed because it is too large Load Diff