Validate JoinSplit proofs (#3128)
* Validate JoinSplit proofs * Apply suggestions from code review Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> * Move primary input encoding to zebra_consensus * Improve typing of h_sig; add RandomSeed * Apply suggestions from code review Co-authored-by: Deirdre Connolly <deirdre@zfnd.org> Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>
This commit is contained in:
parent
62c78ad939
commit
ee073c0876
|
@ -105,28 +105,6 @@ impl Output {
|
||||||
|
|
||||||
(prefix, self.zkproof)
|
(prefix, self.zkproof)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes the primary inputs for the proof statement as 5 Bls12_381 base
|
|
||||||
/// field elements, to match bellman::groth16::verify_proof.
|
|
||||||
///
|
|
||||||
/// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
|
|
||||||
///
|
|
||||||
/// https://zips.z.cash/protocol/protocol.pdf#cctsaplingoutput
|
|
||||||
pub fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
|
||||||
let mut inputs = vec![];
|
|
||||||
|
|
||||||
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
|
|
||||||
inputs.push(cv_affine.get_u());
|
|
||||||
inputs.push(cv_affine.get_v());
|
|
||||||
|
|
||||||
let epk_affine = jubjub::AffinePoint::from_bytes(self.ephemeral_key.into()).unwrap();
|
|
||||||
inputs.push(epk_affine.get_u());
|
|
||||||
inputs.push(epk_affine.get_v());
|
|
||||||
|
|
||||||
inputs.push(self.cm_u);
|
|
||||||
|
|
||||||
inputs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputInTransactionV4 {
|
impl OutputInTransactionV4 {
|
||||||
|
|
|
@ -110,36 +110,6 @@ impl From<(Spend<PerSpendAnchor>, FieldNotPresent)> for Spend<PerSpendAnchor> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spend<PerSpendAnchor> {
|
|
||||||
/// Encodes the primary inputs for the proof statement as 7 Bls12_381 base
|
|
||||||
/// field elements, to match bellman::groth16::verify_proof.
|
|
||||||
///
|
|
||||||
/// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
|
|
||||||
///
|
|
||||||
/// https://zips.z.cash/protocol/protocol.pdf#cctsaplingspend
|
|
||||||
pub fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
|
||||||
let mut inputs = vec![];
|
|
||||||
|
|
||||||
let rk_affine = jubjub::AffinePoint::from_bytes(self.rk.into()).unwrap();
|
|
||||||
inputs.push(rk_affine.get_u());
|
|
||||||
inputs.push(rk_affine.get_v());
|
|
||||||
|
|
||||||
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
|
|
||||||
inputs.push(cv_affine.get_u());
|
|
||||||
inputs.push(cv_affine.get_v());
|
|
||||||
|
|
||||||
// TODO: V4 only
|
|
||||||
inputs.push(jubjub::Fq::from_bytes(&self.per_spend_anchor.into()).unwrap());
|
|
||||||
|
|
||||||
let nullifier_limbs: [jubjub::Fq; 2] = self.nullifier.into();
|
|
||||||
|
|
||||||
inputs.push(nullifier_limbs[0]);
|
|
||||||
inputs.push(nullifier_limbs[1]);
|
|
||||||
|
|
||||||
inputs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Spend<SharedAnchor> {
|
impl Spend<SharedAnchor> {
|
||||||
/// Combine the prefix and non-prefix fields from V5 transaction
|
/// Combine the prefix and non-prefix fields from V5 transaction
|
||||||
/// deserialization.
|
/// deserialization.
|
||||||
|
|
|
@ -15,4 +15,5 @@ pub mod note;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
|
||||||
pub use joinsplit::JoinSplit;
|
pub use joinsplit::JoinSplit;
|
||||||
|
pub use joinsplit::RandomSeed;
|
||||||
pub use note::{EncryptedNote, Note, Nullifier};
|
pub use note::{EncryptedNote, Note, Nullifier};
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
primitives::ZkSnarkProof,
|
primitives::ZkSnarkProof,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{commitment, note, tree, JoinSplit};
|
use super::{commitment, joinsplit, note, tree, JoinSplit};
|
||||||
|
|
||||||
impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplit<P> {
|
impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplit<P> {
|
||||||
type Parameters = ();
|
type Parameters = ();
|
||||||
|
@ -18,7 +18,7 @@ impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplit<P> {
|
||||||
array::uniform2(any::<note::Nullifier>()),
|
array::uniform2(any::<note::Nullifier>()),
|
||||||
array::uniform2(any::<commitment::NoteCommitment>()),
|
array::uniform2(any::<commitment::NoteCommitment>()),
|
||||||
array::uniform32(any::<u8>()),
|
array::uniform32(any::<u8>()),
|
||||||
array::uniform32(any::<u8>()),
|
any::<joinsplit::RandomSeed>(),
|
||||||
array::uniform2(any::<note::Mac>()),
|
array::uniform2(any::<note::Mac>()),
|
||||||
any::<P>(),
|
any::<P>(),
|
||||||
array::uniform2(any::<note::EncryptedNote>()),
|
array::uniform2(any::<note::EncryptedNote>()),
|
||||||
|
|
|
@ -57,3 +57,9 @@ impl From<NoteCommitment> for [u8; 32] {
|
||||||
cm.0
|
cm.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&NoteCommitment> for [u8; 32] {
|
||||||
|
fn from(cm: &NoteCommitment) -> [u8; 32] {
|
||||||
|
cm.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,35 @@ use crate::{
|
||||||
|
|
||||||
use super::{commitment, note, tree};
|
use super::{commitment, note, tree};
|
||||||
|
|
||||||
|
/// A 256-bit seed that must be chosen independently at
|
||||||
|
/// random for each [JoinSplit description].
|
||||||
|
///
|
||||||
|
/// [JoinSplit description]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencodingandconsensus
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
any(test, feature = "proptest-impl"),
|
||||||
|
derive(proptest_derive::Arbitrary)
|
||||||
|
)]
|
||||||
|
pub struct RandomSeed([u8; 32]);
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for RandomSeed {
|
||||||
|
fn from(bytes: [u8; 32]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RandomSeed> for [u8; 32] {
|
||||||
|
fn from(rt: RandomSeed) -> [u8; 32] {
|
||||||
|
rt.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&RandomSeed> for [u8; 32] {
|
||||||
|
fn from(random_seed: &RandomSeed) -> Self {
|
||||||
|
random_seed.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A _JoinSplit Description_, as described in [protocol specification §7.2][ps].
|
/// A _JoinSplit Description_, as described in [protocol specification §7.2][ps].
|
||||||
///
|
///
|
||||||
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencoding
|
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#joinsplitencoding
|
||||||
|
@ -37,7 +66,7 @@ pub struct JoinSplit<P: ZkSnarkProof> {
|
||||||
pub ephemeral_key: x25519::PublicKey,
|
pub ephemeral_key: x25519::PublicKey,
|
||||||
/// A 256-bit seed that must be chosen independently at random for each
|
/// A 256-bit seed that must be chosen independently at random for each
|
||||||
/// JoinSplit description.
|
/// JoinSplit description.
|
||||||
pub random_seed: [u8; 32],
|
pub random_seed: RandomSeed,
|
||||||
/// A message authentication tag.
|
/// A message authentication tag.
|
||||||
pub vmacs: [note::Mac; 2],
|
pub vmacs: [note::Mac; 2],
|
||||||
/// A ZK JoinSplit proof, either a
|
/// A ZK JoinSplit proof, either a
|
||||||
|
@ -59,7 +88,7 @@ impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
|
||||||
writer.write_32_bytes(&self.commitments[0].into())?;
|
writer.write_32_bytes(&self.commitments[0].into())?;
|
||||||
writer.write_32_bytes(&self.commitments[1].into())?;
|
writer.write_32_bytes(&self.commitments[1].into())?;
|
||||||
writer.write_all(&self.ephemeral_key.as_bytes()[..])?;
|
writer.write_all(&self.ephemeral_key.as_bytes()[..])?;
|
||||||
writer.write_all(&self.random_seed[..])?;
|
writer.write_32_bytes(&(&self.random_seed).into())?;
|
||||||
self.vmacs[0].zcash_serialize(&mut writer)?;
|
self.vmacs[0].zcash_serialize(&mut writer)?;
|
||||||
self.vmacs[1].zcash_serialize(&mut writer)?;
|
self.vmacs[1].zcash_serialize(&mut writer)?;
|
||||||
self.zkproof.zcash_serialize(&mut writer)?;
|
self.zkproof.zcash_serialize(&mut writer)?;
|
||||||
|
@ -105,7 +134,7 @@ impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
|
||||||
commitment::NoteCommitment::from(reader.read_32_bytes()?),
|
commitment::NoteCommitment::from(reader.read_32_bytes()?),
|
||||||
],
|
],
|
||||||
ephemeral_key: x25519_dalek::PublicKey::from(reader.read_32_bytes()?),
|
ephemeral_key: x25519_dalek::PublicKey::from(reader.read_32_bytes()?),
|
||||||
random_seed: reader.read_32_bytes()?,
|
random_seed: RandomSeed::from(reader.read_32_bytes()?),
|
||||||
vmacs: [
|
vmacs: [
|
||||||
note::Mac::zcash_deserialize(&mut reader)?,
|
note::Mac::zcash_deserialize(&mut reader)?,
|
||||||
note::Mac::zcash_deserialize(&mut reader)?,
|
note::Mac::zcash_deserialize(&mut reader)?,
|
||||||
|
|
|
@ -12,6 +12,24 @@ use std::io::{self, Read};
|
||||||
)]
|
)]
|
||||||
pub struct Mac([u8; 32]);
|
pub struct Mac([u8; 32]);
|
||||||
|
|
||||||
|
impl From<[u8; 32]> for Mac {
|
||||||
|
fn from(bytes: [u8; 32]) -> Self {
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mac> for [u8; 32] {
|
||||||
|
fn from(rt: Mac) -> [u8; 32] {
|
||||||
|
rt.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Mac> for [u8; 32] {
|
||||||
|
fn from(mac: &Mac) -> Self {
|
||||||
|
mac.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ZcashDeserialize for Mac {
|
impl ZcashDeserialize for Mac {
|
||||||
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
|
fn zcash_deserialize<R: Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
let bytes = reader.read_32_bytes()?;
|
let bytes = reader.read_32_bytes()?;
|
||||||
|
|
|
@ -86,3 +86,9 @@ impl From<Nullifier> for [u8; 32] {
|
||||||
n.0
|
n.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&Nullifier> for [u8; 32] {
|
||||||
|
fn from(n: &Nullifier) -> Self {
|
||||||
|
n.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use crate::{
|
||||||
amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
|
amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
|
||||||
block, orchard,
|
block, orchard,
|
||||||
parameters::NetworkUpgrade,
|
parameters::NetworkUpgrade,
|
||||||
primitives::{Bctv14Proof, Groth16Proof},
|
primitives::{ed25519, Bctv14Proof, Groth16Proof},
|
||||||
sapling, sprout,
|
sapling, sprout,
|
||||||
transparent::{
|
transparent::{
|
||||||
self, outputs_from_utxos,
|
self, outputs_from_utxos,
|
||||||
|
@ -612,6 +612,42 @@ impl Transaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the JoinSplit public validating key in this transaction,
|
||||||
|
/// regardless of version, if any.
|
||||||
|
pub fn sprout_joinsplit_pub_key(&self) -> Option<ed25519::VerificationKeyBytes> {
|
||||||
|
match self {
|
||||||
|
// JoinSplits with Bctv14 Proofs
|
||||||
|
Transaction::V2 {
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V3 {
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
..
|
||||||
|
} => Some(joinsplit_data.pub_key),
|
||||||
|
// JoinSplits with Groth Proofs
|
||||||
|
Transaction::V4 {
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
..
|
||||||
|
} => Some(joinsplit_data.pub_key),
|
||||||
|
// No JoinSplits
|
||||||
|
Transaction::V1 { .. }
|
||||||
|
| Transaction::V2 {
|
||||||
|
joinsplit_data: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V3 {
|
||||||
|
joinsplit_data: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V4 {
|
||||||
|
joinsplit_data: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Transaction::V5 { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return if the transaction has any Sprout JoinSplit data.
|
/// Return if the transaction has any Sprout JoinSplit data.
|
||||||
pub fn has_sprout_joinsplit_data(&self) -> bool {
|
pub fn has_sprout_joinsplit_data(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Async Groth16 batch verifier service
|
//! Async Groth16 batch verifier service
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::TryInto,
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
mem,
|
mem,
|
||||||
|
@ -9,6 +10,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use bellman::{
|
use bellman::{
|
||||||
|
gadgets::multipack,
|
||||||
groth16::{batch, VerifyingKey},
|
groth16::{batch, VerifyingKey},
|
||||||
VerificationError,
|
VerificationError,
|
||||||
};
|
};
|
||||||
|
@ -22,11 +24,20 @@ use tower::{util::ServiceFn, Service};
|
||||||
use tower_batch::{Batch, BatchControl};
|
use tower_batch::{Batch, BatchControl};
|
||||||
use tower_fallback::Fallback;
|
use tower_fallback::Fallback;
|
||||||
|
|
||||||
use zebra_chain::sapling::{Output, PerSpendAnchor, Spend};
|
use zebra_chain::{
|
||||||
|
primitives::{
|
||||||
|
ed25519::{self, VerificationKeyBytes},
|
||||||
|
Groth16Proof,
|
||||||
|
},
|
||||||
|
sapling::{Output, PerSpendAnchor, Spend},
|
||||||
|
sprout::{JoinSplit, Nullifier, RandomSeed},
|
||||||
|
};
|
||||||
|
|
||||||
mod params;
|
mod params;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod vectors;
|
||||||
|
|
||||||
pub use params::{Groth16Parameters, GROTH16_PARAMETERS};
|
pub use params::{Groth16Parameters, GROTH16_PARAMETERS};
|
||||||
|
|
||||||
|
@ -96,26 +107,204 @@ pub static OUTPUT_VERIFIER: Lazy<
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Global batch verification context for Groth16 proofs of JoinSplit statements.
|
||||||
|
///
|
||||||
|
/// This service does not yet batch verifications, see
|
||||||
|
/// https://github.com/ZcashFoundation/zebra/issues/3127
|
||||||
|
///
|
||||||
|
/// Note that making a `Service` call requires mutable access to the service, so
|
||||||
|
/// you should call `.clone()` on the global handle to create a local, mutable
|
||||||
|
/// handle.
|
||||||
|
pub static JOINSPLIT_VERIFIER: Lazy<ServiceFn<fn(Item) -> Ready<Result<(), VerificationError>>>> =
|
||||||
|
Lazy::new(|| {
|
||||||
|
// We need a Service to use. The obvious way to do this would
|
||||||
|
// be to write a closure that returns an async block. But because we
|
||||||
|
// have to specify the type of a static, we need to be able to write the
|
||||||
|
// type of the closure and its return value, and both closures and async
|
||||||
|
// blocks have eldritch types whose names cannot be written. So instead,
|
||||||
|
// we use a Ready to avoid an async block and cast the closure to a
|
||||||
|
// function (which is possible because it doesn't capture any state).
|
||||||
|
tower::service_fn(
|
||||||
|
(|item: Item| {
|
||||||
|
ready(
|
||||||
|
item.verify_single(&GROTH16_PARAMETERS.sprout.joinsplit_prepared_verifying_key),
|
||||||
|
)
|
||||||
|
}) as fn(_) -> _,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
/// A Groth16 verification item, used as the request type of the service.
|
/// A Groth16 verification item, used as the request type of the service.
|
||||||
pub type Item = batch::Item<Bls12>;
|
pub type Item = batch::Item<Bls12>;
|
||||||
|
|
||||||
/// A wrapper to workaround the missing `ServiceExt::map_err` method.
|
/// A wrapper to workaround the missing `ServiceExt::map_err` method.
|
||||||
pub struct ItemWrapper(Item);
|
pub struct ItemWrapper(Item);
|
||||||
|
|
||||||
impl From<&Spend<PerSpendAnchor>> for ItemWrapper {
|
/// A Groth16 Description (JoinSplit, Spend, or Output) with a Groth16 proof
|
||||||
fn from(spend: &Spend<PerSpendAnchor>) -> Self {
|
/// and its inputs encoded as scalars.
|
||||||
Self(Item::from((
|
pub trait Description {
|
||||||
bellman::groth16::Proof::read(&spend.zkproof.0[..]).unwrap(),
|
/// The Groth16 proof of this description.
|
||||||
spend.primary_inputs(),
|
fn proof(&self) -> &Groth16Proof;
|
||||||
)))
|
/// The primary inputs for this proof, encoded as [`jubjub::Fq`] scalars.
|
||||||
|
fn primary_inputs(&self) -> Vec<jubjub::Fq>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Description for Spend<PerSpendAnchor> {
|
||||||
|
/// Encodes the primary input for the Sapling Spend proof statement as 7 Bls12_381 base
|
||||||
|
/// field elements, to match [`bellman::groth16::verify_proof`] (the starting fixed element
|
||||||
|
/// `1` is filled in by [`bellman`].
|
||||||
|
///
|
||||||
|
/// NB: jubjub::Fq is a type alias for bls12_381::Scalar.
|
||||||
|
///
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#cctsaplingspend>
|
||||||
|
fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
||||||
|
let mut inputs = vec![];
|
||||||
|
|
||||||
|
let rk_affine = jubjub::AffinePoint::from_bytes(self.rk.into()).unwrap();
|
||||||
|
inputs.push(rk_affine.get_u());
|
||||||
|
inputs.push(rk_affine.get_v());
|
||||||
|
|
||||||
|
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
|
||||||
|
inputs.push(cv_affine.get_u());
|
||||||
|
inputs.push(cv_affine.get_v());
|
||||||
|
|
||||||
|
// TODO: V4 only
|
||||||
|
inputs.push(jubjub::Fq::from_bytes(&self.per_spend_anchor.into()).unwrap());
|
||||||
|
|
||||||
|
let nullifier_limbs: [jubjub::Fq; 2] = self.nullifier.into();
|
||||||
|
|
||||||
|
inputs.push(nullifier_limbs[0]);
|
||||||
|
inputs.push(nullifier_limbs[1]);
|
||||||
|
|
||||||
|
inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proof(&self) -> &Groth16Proof {
|
||||||
|
&self.zkproof
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Output> for ItemWrapper {
|
impl Description for Output {
|
||||||
fn from(output: &Output) -> Self {
|
/// Encodes the primary input for the Sapling Output proof statement as 5 Bls12_381 base
|
||||||
|
/// field elements, to match [`bellman::groth16::verify_proof`] (the starting fixed element
|
||||||
|
/// `1` is filled in by [`bellman`].
|
||||||
|
///
|
||||||
|
/// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
|
||||||
|
///
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#cctsaplingoutput>
|
||||||
|
fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
||||||
|
let mut inputs = vec![];
|
||||||
|
|
||||||
|
let cv_affine = jubjub::AffinePoint::from_bytes(self.cv.into()).unwrap();
|
||||||
|
inputs.push(cv_affine.get_u());
|
||||||
|
inputs.push(cv_affine.get_v());
|
||||||
|
|
||||||
|
let epk_affine = jubjub::AffinePoint::from_bytes(self.ephemeral_key.into()).unwrap();
|
||||||
|
inputs.push(epk_affine.get_u());
|
||||||
|
inputs.push(epk_affine.get_v());
|
||||||
|
|
||||||
|
inputs.push(self.cm_u);
|
||||||
|
|
||||||
|
inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proof(&self) -> &Groth16Proof {
|
||||||
|
&self.zkproof
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the [h_{Sig} hash function][1] which is used in JoinSplit descriptions.
|
||||||
|
///
|
||||||
|
/// `random_seed`: the random seed from the JoinSplit description.
|
||||||
|
/// `nf1`: the first nullifier from the JoinSplit description.
|
||||||
|
/// `nf2`: the second nullifier from the JoinSplit description.
|
||||||
|
/// `joinsplit_pub_key`: the JoinSplit public validation key from the transaction.
|
||||||
|
///
|
||||||
|
/// [1]: https://zips.z.cash/protocol/protocol.pdf#hsigcrh
|
||||||
|
pub(super) fn h_sig(
|
||||||
|
random_seed: &RandomSeed,
|
||||||
|
nf1: &Nullifier,
|
||||||
|
nf2: &Nullifier,
|
||||||
|
joinsplit_pub_key: &VerificationKeyBytes,
|
||||||
|
) -> [u8; 32] {
|
||||||
|
let h_sig: [u8; 32] = blake2b_simd::Params::new()
|
||||||
|
.hash_length(32)
|
||||||
|
.personal(b"ZcashComputehSig")
|
||||||
|
.to_state()
|
||||||
|
.update(&(<[u8; 32]>::from(random_seed))[..])
|
||||||
|
.update(&(<[u8; 32]>::from(nf1))[..])
|
||||||
|
.update(&(<[u8; 32]>::from(nf2))[..])
|
||||||
|
.update(joinsplit_pub_key.as_ref())
|
||||||
|
.finalize()
|
||||||
|
.as_bytes()
|
||||||
|
.try_into()
|
||||||
|
.expect("32 byte array");
|
||||||
|
h_sig
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Description for (&JoinSplit<Groth16Proof>, &ed25519::VerificationKeyBytes) {
|
||||||
|
/// Encodes the primary input for the JoinSplit proof statement as Bls12_381 base
|
||||||
|
/// field elements, to match [`bellman::groth16::verify_proof()`].
|
||||||
|
///
|
||||||
|
/// NB: [`jubjub::Fq`] is a type alias for [`bls12_381::Scalar`].
|
||||||
|
///
|
||||||
|
/// `joinsplit_pub_key`: the JoinSplit public validation key for this JoinSplit, from
|
||||||
|
/// the transaction. (All JoinSplits in a transaction share the same validation key.)
|
||||||
|
///
|
||||||
|
/// This is not yet officially documented; see the reference implementation:
|
||||||
|
/// https://github.com/zcash/librustzcash/blob/0ec7f97c976d55e1a194a37b27f247e8887fca1d/zcash_proofs/src/sprout.rs#L152-L166
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitdesc>
|
||||||
|
fn primary_inputs(&self) -> Vec<jubjub::Fq> {
|
||||||
|
let (joinsplit, joinsplit_pub_key) = self;
|
||||||
|
|
||||||
|
let rt: [u8; 32] = joinsplit.anchor.into();
|
||||||
|
let mac1: [u8; 32] = (&joinsplit.vmacs[0]).into();
|
||||||
|
let mac2: [u8; 32] = (&joinsplit.vmacs[1]).into();
|
||||||
|
let nf1: [u8; 32] = (&joinsplit.nullifiers[0]).into();
|
||||||
|
let nf2: [u8; 32] = (&joinsplit.nullifiers[1]).into();
|
||||||
|
let cm1: [u8; 32] = (&joinsplit.commitments[0]).into();
|
||||||
|
let cm2: [u8; 32] = (&joinsplit.commitments[1]).into();
|
||||||
|
let vpub_old = joinsplit.vpub_old.to_bytes();
|
||||||
|
let vpub_new = joinsplit.vpub_new.to_bytes();
|
||||||
|
|
||||||
|
let h_sig = h_sig(
|
||||||
|
&joinsplit.random_seed,
|
||||||
|
&joinsplit.nullifiers[0],
|
||||||
|
&joinsplit.nullifiers[1],
|
||||||
|
joinsplit_pub_key,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Prepare the public input for the verifier
|
||||||
|
let mut public_input = Vec::with_capacity((32 * 8) + (8 * 2));
|
||||||
|
public_input.extend(rt);
|
||||||
|
public_input.extend(h_sig);
|
||||||
|
public_input.extend(nf1);
|
||||||
|
public_input.extend(mac1);
|
||||||
|
public_input.extend(nf2);
|
||||||
|
public_input.extend(mac2);
|
||||||
|
public_input.extend(cm1);
|
||||||
|
public_input.extend(cm2);
|
||||||
|
public_input.extend(vpub_old);
|
||||||
|
public_input.extend(vpub_new);
|
||||||
|
|
||||||
|
let public_input = multipack::bytes_to_bits(&public_input);
|
||||||
|
|
||||||
|
multipack::compute_multipacking(&public_input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proof(&self) -> &Groth16Proof {
|
||||||
|
&self.0.zkproof
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<&T> for ItemWrapper
|
||||||
|
where
|
||||||
|
T: Description,
|
||||||
|
{
|
||||||
|
/// Convert a [`Description`] into an [`ItemWrapper`].
|
||||||
|
fn from(input: &T) -> Self {
|
||||||
Self(Item::from((
|
Self(Item::from((
|
||||||
bellman::groth16::Proof::read(&output.zkproof.0[..]).unwrap(),
|
bellman::groth16::Proof::read(&input.proof().0[..]).unwrap(),
|
||||||
output.primary_inputs(),
|
input.primary_inputs(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub struct SaplingParameters {
|
||||||
///
|
///
|
||||||
/// New Sprout outputs were disabled by the Canopy network upgrade.
|
/// New Sprout outputs were disabled by the Canopy network upgrade.
|
||||||
pub struct SproutParameters {
|
pub struct SproutParameters {
|
||||||
pub spend_prepared_verifying_key: groth16::PreparedVerifyingKey<Bls12>,
|
pub joinsplit_prepared_verifying_key: groth16::PreparedVerifyingKey<Bls12>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Groth16Parameters {
|
impl Groth16Parameters {
|
||||||
|
@ -110,7 +110,7 @@ impl Groth16Parameters {
|
||||||
};
|
};
|
||||||
|
|
||||||
let sprout = SproutParameters {
|
let sprout = SproutParameters {
|
||||||
spend_prepared_verifying_key: parameters
|
joinsplit_prepared_verifying_key: parameters
|
||||||
.sprout_vk
|
.sprout_vk
|
||||||
.expect("unreachable code: sprout loader panics on failure"),
|
.expect("unreachable code: sprout loader panics on failure"),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
//! Tests for transaction verification
|
//! Tests for transaction verification
|
||||||
|
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use futures::stream::{FuturesUnordered, StreamExt};
|
use futures::stream::{FuturesUnordered, StreamExt};
|
||||||
|
use hex::FromHex;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
|
|
||||||
use zebra_chain::{block::Block, serialization::ZcashDeserializeInto, transaction::Transaction};
|
use zebra_chain::{
|
||||||
|
block::Block, serialization::ZcashDeserializeInto, sprout::EncryptedNote,
|
||||||
|
transaction::Transaction,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::primitives::groth16::{self, *};
|
use crate::primitives::groth16::{self, *};
|
||||||
|
|
||||||
|
@ -172,3 +178,251 @@ async fn correctly_err_on_invalid_output_proof() {
|
||||||
.await
|
.await
|
||||||
.expect_err("unexpected success checking invalid groth16 inputs");
|
.expect_err("unexpected success checking invalid groth16 inputs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn verify_groth16_joinsplits<V>(
|
||||||
|
verifier: &mut V,
|
||||||
|
transactions: Vec<std::sync::Arc<Transaction>>,
|
||||||
|
) -> Result<(), V::Error>
|
||||||
|
where
|
||||||
|
V: tower::Service<Item, Response = ()>,
|
||||||
|
<V as tower::Service<bellman::groth16::batch::Item<bls12_381::Bls12>>>::Error:
|
||||||
|
std::fmt::Debug
|
||||||
|
+ std::convert::From<
|
||||||
|
std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let mut async_checks = FuturesUnordered::new();
|
||||||
|
|
||||||
|
for tx in transactions {
|
||||||
|
let joinsplits = tx.sprout_groth16_joinsplits();
|
||||||
|
|
||||||
|
for joinsplit in joinsplits {
|
||||||
|
tracing::trace!(?joinsplit);
|
||||||
|
|
||||||
|
let pub_key = tx
|
||||||
|
.sprout_joinsplit_pub_key()
|
||||||
|
.expect("pub key must exist since there are joinsplits");
|
||||||
|
let joinsplit_rsp = verifier
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(groth16::ItemWrapper::from(&(joinsplit, &pub_key)).into());
|
||||||
|
|
||||||
|
async_checks.push(joinsplit_rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(result) = async_checks.next().await {
|
||||||
|
tracing::trace!(?result);
|
||||||
|
result?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn verify_sprout_groth16() {
|
||||||
|
let mut verifier = tower::service_fn(
|
||||||
|
(|item: Item| {
|
||||||
|
ready(
|
||||||
|
item.verify_single(&GROTH16_PARAMETERS.sprout.joinsplit_prepared_verifying_key)
|
||||||
|
.map_err(tower_fallback::BoxedError::from),
|
||||||
|
)
|
||||||
|
}) as fn(_) -> _,
|
||||||
|
);
|
||||||
|
|
||||||
|
let transactions = zebra_test::vectors::MAINNET_BLOCKS
|
||||||
|
.clone()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(_, bytes)| {
|
||||||
|
let block = bytes
|
||||||
|
.zcash_deserialize_into::<Block>()
|
||||||
|
.expect("a valid block");
|
||||||
|
block.transactions
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// This should fail if any of the proofs fail to validate.
|
||||||
|
verify_groth16_joinsplits(&mut verifier, transactions)
|
||||||
|
.await
|
||||||
|
.expect("verification should pass");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_groth16_joinsplit_vector<V>(
|
||||||
|
verifier: &mut V,
|
||||||
|
joinsplit: &JoinSplit<Groth16Proof>,
|
||||||
|
pub_key: &ed25519::VerificationKeyBytes,
|
||||||
|
) -> Result<(), V::Error>
|
||||||
|
where
|
||||||
|
V: tower::Service<Item, Response = ()>,
|
||||||
|
<V as tower::Service<bellman::groth16::batch::Item<bls12_381::Bls12>>>::Error:
|
||||||
|
std::fmt::Debug
|
||||||
|
+ std::convert::From<
|
||||||
|
std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let mut async_checks = FuturesUnordered::new();
|
||||||
|
|
||||||
|
tracing::trace!(?joinsplit);
|
||||||
|
|
||||||
|
let joinsplit_rsp = verifier
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(groth16::ItemWrapper::from(&(joinsplit, pub_key)).into());
|
||||||
|
|
||||||
|
async_checks.push(joinsplit_rsp);
|
||||||
|
|
||||||
|
while let Some(result) = async_checks.next().await {
|
||||||
|
tracing::trace!(?result);
|
||||||
|
result?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn verify_sprout_groth16_vector() {
|
||||||
|
let mut verifier = tower::service_fn(
|
||||||
|
(|item: Item| {
|
||||||
|
ready(
|
||||||
|
item.verify_single(&GROTH16_PARAMETERS.sprout.joinsplit_prepared_verifying_key)
|
||||||
|
.map_err(tower_fallback::BoxedError::from),
|
||||||
|
)
|
||||||
|
}) as fn(_) -> _,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test vector extracted manually by printing a JoinSplit generated by
|
||||||
|
// the test_joinsplit test of the zcashd repository.
|
||||||
|
// https://github.com/zcash/zcash/blob/7aaefab2d7d671951f153e47cdd83ae55d78144f/src/gtest/test_joinsplit.cpp#L48
|
||||||
|
let joinsplit = JoinSplit::<Groth16Proof> {
|
||||||
|
vpub_old: 0x0Ai32.try_into().unwrap(),
|
||||||
|
vpub_new: 0.try_into().unwrap(),
|
||||||
|
anchor: <[u8; 32]>::from_hex(
|
||||||
|
"D7C612C817793191A1E68652121876D6B3BDE40F4FA52BC314145CE6E5CDD259",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
nullifiers: [
|
||||||
|
<[u8; 32]>::from_hex(
|
||||||
|
"F9AD4EED10C97FF8FDE3C63512242D3937C0E2836389A95B972C50FB942F775B",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
<[u8; 32]>::from_hex(
|
||||||
|
"DF6EB39839A549F0DF24CDEBBB23CA7107E84D2E6BD0294A8B1BFBD0FAE7800C",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
commitments: [
|
||||||
|
<[u8; 32]>::from_hex(
|
||||||
|
"0D595308A445D07EB62C7C13CB9F2630DFD39E6A060E98A9788C92BDDBAEA538",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
<[u8; 32]>::from_hex(
|
||||||
|
"EE7D9622C410878A218ED8A8A6A10B11DDBDA83CB2A627508354BFA490E0F33E",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
// The ephemeral key is not validated in the proof, use a dummy value.
|
||||||
|
ephemeral_key: [0u8; 32].into(),
|
||||||
|
random_seed: <[u8; 32]>::from_hex(
|
||||||
|
"6A14E910A94EF500043A42417D8D2B4124AB35DC1E14DDF830EBCF972E850807",
|
||||||
|
)
|
||||||
|
.unwrap().into(),
|
||||||
|
vmacs: [
|
||||||
|
<[u8; 32]>::from_hex(
|
||||||
|
"630D39F963960E9092E518CEF4C84853C13EF9FC759CBECDD2ED61D1070C82E6",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
<[u8; 32]>::from_hex(
|
||||||
|
"1C8DCEC25F816D0177AC29958D0B8594EC669AED4A32D9FBEEC3C57B4503F19A",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
zkproof: <[u8; 192]>::from_hex(
|
||||||
|
"802BD3D746BA4831E10027C92E0E610618F619E3CE7EE087622BFF86F19B5BC3292DACFD27506C8BFF4C808035EB9C7685010235D47F1D77C5DCC212323E69726F04A46E0BBDCE17C64EEEA36F443E25F21DF2C39FE8A996BAE899AB8F8CCF52054DC6A5553D0F86283E056AED8E6EABE11D85EDF7948005AD9B982759F20E5DE54A59A1B80CD31AD4CC96419492886C91C4D7C521C327B47F4F5688067BE2B19EB8BC0B7BD357BF931CCF8BCC62A7E48A81CD287F00854767B41748F05EDD5B",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
// The ciphertexts are not validated in the proof, use a dummy value.
|
||||||
|
enc_ciphertexts: [EncryptedNote([0u8; 601]),EncryptedNote([0u8; 601])],
|
||||||
|
};
|
||||||
|
|
||||||
|
let pub_key =
|
||||||
|
<[u8; 32]>::from_hex("63A144ABC0524C9EADE1DB9DE17AEC4A39626A0FDB597B9EC6DDA327EE9FE845")
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
|
||||||
|
verify_groth16_joinsplit_vector(&mut verifier, &joinsplit, &pub_key)
|
||||||
|
.await
|
||||||
|
.expect("verification should pass");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_invalid_groth16_joinsplit_description<V>(
|
||||||
|
verifier: &mut V,
|
||||||
|
transactions: Vec<std::sync::Arc<Transaction>>,
|
||||||
|
) -> Result<(), V::Error>
|
||||||
|
where
|
||||||
|
V: tower::Service<Item, Response = ()>,
|
||||||
|
<V as tower::Service<bellman::groth16::batch::Item<bls12_381::Bls12>>>::Error:
|
||||||
|
std::convert::From<
|
||||||
|
std::boxed::Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
zebra_test::init();
|
||||||
|
|
||||||
|
let mut async_checks = FuturesUnordered::new();
|
||||||
|
|
||||||
|
for tx in transactions {
|
||||||
|
let joinsplits = tx.sprout_groth16_joinsplits();
|
||||||
|
|
||||||
|
for joinsplit in joinsplits {
|
||||||
|
// Use an arbitrary public key which is not the correct one,
|
||||||
|
// which will make the verification fail.
|
||||||
|
let modified_pub_key = [0x42; 32].into();
|
||||||
|
let joinsplit_rsp = verifier
|
||||||
|
.ready()
|
||||||
|
.await?
|
||||||
|
.call(groth16::ItemWrapper::from(&(joinsplit, &modified_pub_key)).into());
|
||||||
|
|
||||||
|
async_checks.push(joinsplit_rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(result) = async_checks.next().await {
|
||||||
|
result?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn correctly_err_on_invalid_joinsplit_proof() {
|
||||||
|
// Use separate verifiers so shared batch tasks aren't killed when the test ends (#2390).
|
||||||
|
// Also, since we expect these to fail, we don't want to slow down the communal verifiers.
|
||||||
|
let mut verifier = tower::service_fn(
|
||||||
|
(|item: Item| {
|
||||||
|
ready(
|
||||||
|
item.verify_single(&GROTH16_PARAMETERS.sprout.joinsplit_prepared_verifying_key)
|
||||||
|
.map_err(tower_fallback::BoxedError::from),
|
||||||
|
)
|
||||||
|
}) as fn(_) -> _,
|
||||||
|
);
|
||||||
|
|
||||||
|
let block = zebra_test::vectors::BLOCK_MAINNET_419201_BYTES
|
||||||
|
.clone()
|
||||||
|
.zcash_deserialize_into::<Block>()
|
||||||
|
.expect("a valid block");
|
||||||
|
|
||||||
|
verify_invalid_groth16_joinsplit_description(&mut verifier, block.transactions)
|
||||||
|
.await
|
||||||
|
.expect_err("unexpected success checking invalid groth16 inputs");
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use crate::groth16::h_sig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn h_sig_works() {
|
||||||
|
// Test vector from zcash:
|
||||||
|
// https://github.com/zcash/zcash/blob/2c17d1e2740115c9c88046db4a3bb0aa069dae4f/src/gtest/test_joinsplit.cpp#L252
|
||||||
|
let tests: [[&str; 5]; 4] = [
|
||||||
|
[
|
||||||
|
"6161616161616161616161616161616161616161616161616161616161616161",
|
||||||
|
"6262626262626262626262626262626262626262626262626262626262626262",
|
||||||
|
"6363636363636363636363636363636363636363636363636363636363636363",
|
||||||
|
"6464646464646464646464646464646464646464646464646464646464646464",
|
||||||
|
"a8cba69f1fa329c055756b4af900f8a00b61e44f4cb8a1824ceb58b90a5b8113",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"697322276b5dd93b12fb1fcbd2144b2960f24c73aac6c6a0811447be1e7f1e19",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"4961048919f0ca79d49c9378c36a91a8767060001f4212fe6f7d426f3ccf9f32",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||||
|
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||||
|
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||||
|
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100",
|
||||||
|
"b61110ec162693bc3d9ca7fb0eec3afd2e278e2f41394b3ff11d7cb761ad4b27",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
for t in tests {
|
||||||
|
// Test vectors are byte-reversed (because they are loaded in zcash
|
||||||
|
// with a function that loads in reverse order), so we need to re-reverse them.
|
||||||
|
let mut random_seed = hex::decode(t[0]).unwrap();
|
||||||
|
random_seed.reverse();
|
||||||
|
let mut nf1 = hex::decode(t[1]).unwrap();
|
||||||
|
nf1.reverse();
|
||||||
|
let mut nf2 = hex::decode(t[2]).unwrap();
|
||||||
|
nf2.reverse();
|
||||||
|
let mut pubkey = hex::decode(t[3]).unwrap();
|
||||||
|
pubkey.reverse();
|
||||||
|
let mut r = h_sig(
|
||||||
|
&<[u8; 32]>::try_from(random_seed).unwrap().into(),
|
||||||
|
&<[u8; 32]>::try_from(nf1).unwrap().into(),
|
||||||
|
&<[u8; 32]>::try_from(nf2).unwrap().into(),
|
||||||
|
&<[u8; 32]>::try_from(pubkey).unwrap().into(),
|
||||||
|
);
|
||||||
|
r.reverse();
|
||||||
|
|
||||||
|
assert_eq!(hex::encode(r), t[4]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1441,7 +1441,7 @@ fn mock_sprout_join_split_data() -> (JoinSplitData<Groth16Proof>, ed25519::Signi
|
||||||
let commitment = sprout::commitment::NoteCommitment::from([0u8; 32]);
|
let commitment = sprout::commitment::NoteCommitment::from([0u8; 32]);
|
||||||
let ephemeral_key =
|
let ephemeral_key =
|
||||||
x25519::PublicKey::from(&x25519::EphemeralSecret::new(rand07::thread_rng()));
|
x25519::PublicKey::from(&x25519::EphemeralSecret::new(rand07::thread_rng()));
|
||||||
let random_seed = [0u8; 32];
|
let random_seed = sprout::RandomSeed::from([0u8; 32]);
|
||||||
let mac = sprout::note::Mac::zcash_deserialize(&[0u8; 32][..])
|
let mac = sprout::note::Mac::zcash_deserialize(&[0u8; 32][..])
|
||||||
.expect("Failure to deserialize dummy MAC");
|
.expect("Failure to deserialize dummy MAC");
|
||||||
let zkproof = Groth16Proof([0u8; 192]);
|
let zkproof = Groth16Proof([0u8; 192]);
|
||||||
|
|
|
@ -92,7 +92,7 @@ fn prepare_sprout_block(mut block_to_prepare: Block, reference_block: Block) ->
|
||||||
nullifiers: old_joinsplit.nullifiers,
|
nullifiers: old_joinsplit.nullifiers,
|
||||||
commitments: old_joinsplit.commitments,
|
commitments: old_joinsplit.commitments,
|
||||||
ephemeral_key: old_joinsplit.ephemeral_key,
|
ephemeral_key: old_joinsplit.ephemeral_key,
|
||||||
random_seed: old_joinsplit.random_seed,
|
random_seed: old_joinsplit.random_seed.clone(),
|
||||||
vmacs: old_joinsplit.vmacs.clone(),
|
vmacs: old_joinsplit.vmacs.clone(),
|
||||||
zkproof: Groth16Proof::from([0; 192]),
|
zkproof: Groth16Proof::from([0; 192]),
|
||||||
enc_ciphertexts: old_joinsplit.enc_ciphertexts,
|
enc_ciphertexts: old_joinsplit.enc_ciphertexts,
|
||||||
|
|
Loading…
Reference in New Issue