Merge pull request #285 from zcash/bundle-builder-test

Functional test for building and verifying bundles
This commit is contained in:
str4d 2022-02-15 22:45:56 +00:00 committed by GitHub
commit 4dc1ae059a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 18 deletions

View File

@ -2,6 +2,7 @@
pub mod commitments;
use std::convert::TryInto;
use std::io;
use blake2b_simd::Hash as Blake2bHash;
@ -298,15 +299,6 @@ impl<T: Authorization, V> Bundle<T, V> {
&self.authorization
}
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
/// a transaction ID.
pub fn commitment<'a>(&'a self) -> BundleCommitment
where
i64: From<&'a V>,
{
BundleCommitment(hash_bundle_txid_data(self))
}
/// Construct a new bundle by applying a transformation that might fail
/// to the value balance.
pub fn try_map_value_balance<V0, E, F: FnOnce(V) -> Result<V0, E>>(
@ -405,7 +397,13 @@ impl<T: Authorization, V> Bundle<T, V> {
}
}
impl<T: Authorization, V: Copy + Into<ValueSum>> Bundle<T, V> {
impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
/// a transaction ID.
pub fn commitment(&self) -> BundleCommitment {
BundleCommitment(hash_bundle_txid_data(self))
}
/// Returns the transaction binding validating key for this bundle.
///
/// This can be used to validate the [`Authorized::binding_signature`] returned from
@ -416,7 +414,10 @@ impl<T: Authorization, V: Copy + Into<ValueSum>> Bundle<T, V> {
.iter()
.map(|a| a.cv_net())
.sum::<ValueCommitment>()
- ValueCommitment::derive(self.value_balance.into(), ValueCommitTrapdoor::zero()))
- ValueCommitment::derive(
ValueSum::from_raw(self.value_balance.into()),
ValueCommitTrapdoor::zero(),
))
.into_bvk()
}
}
@ -500,6 +501,13 @@ impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
#[derive(Debug)]
pub struct BundleCommitment(pub Blake2bHash);
impl From<BundleCommitment> for [u8; 32] {
fn from(commitment: BundleCommitment) -> Self {
// The commitment uses BLAKE2b-256.
commitment.0.as_bytes().try_into().unwrap()
}
}
/// A commitment to the authorizing data within a bundle of actions.
#[derive(Debug)]
pub struct BundleAuthorizingCommitment(pub Blake2bHash);

View File

@ -28,10 +28,9 @@ fn hasher(personal: &[u8; 16]) -> State {
/// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION
///
/// [zip244]: https://zips.z.cash/zip-0244
pub fn hash_bundle_txid_data<'a, A: Authorization, V>(bundle: &'a Bundle<A, V>) -> Blake2bHash
where
i64: From<&'a V>,
{
pub fn hash_bundle_txid_data<A: Authorization, V: Copy + Into<i64>>(
bundle: &Bundle<A, V>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION);
let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION);
@ -59,7 +58,7 @@ where
h.write_all(&mh.finalize().as_bytes()).unwrap();
h.write_all(&nh.finalize().as_bytes()).unwrap();
h.write_all(&[bundle.flags().to_byte()]).unwrap();
h.write_all(&<i64>::from(bundle.value_balance()).to_le_bytes())
h.write_all(&(*bundle.value_balance()).into().to_le_bytes())
.unwrap();
h.write_all(&bundle.anchor().to_bytes()).unwrap();
h.finalize()

View File

@ -147,6 +147,13 @@ impl VerificationKey<Binding> {
}
}
impl<T: SigType> VerificationKey<T> {
/// Verifies a purported `signature` over `msg` made by this verification key.
pub fn verify(&self, msg: &[u8], signature: &Signature<T>) -> Result<(), reddsa::Error> {
self.0.verify(msg, &signature.0)
}
}
/// A RedPallas signature.
#[derive(Debug, Clone)]
pub struct Signature<T: SigType>(reddsa::Signature<T>);

View File

@ -107,11 +107,19 @@ impl MerklePath {
/// Instantiates a new Merkle path given a leaf position and authentication path.
pub(crate) fn new(position: u32, auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD]) -> Self {
Self {
Self::from_parts(
position,
auth_path: gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| {
gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| {
MerkleHashOrchard(auth_path[i])
}),
)
}
/// Instantiates a new Merkle path given a leaf position and authentication path.
pub fn from_parts(position: u32, auth_path: [MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]) -> Self {
Self {
position,
auth_path,
}
}

102
tests/builder.rs Normal file
View File

@ -0,0 +1,102 @@
use std::convert::TryInto;
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Hashable, Tree};
use orchard::{
builder::Builder,
bundle::{Authorized, Flags},
circuit::{ProvingKey, VerifyingKey},
keys::{FullViewingKey, IncomingViewingKey, SpendAuthorizingKey, SpendingKey},
note::ExtractedNoteCommitment,
note_encryption::OrchardDomain,
tree::{MerkleHashOrchard, MerklePath},
value::NoteValue,
Bundle,
};
use rand::rngs::OsRng;
use zcash_note_encryption::try_note_decryption;
fn verify_bundle(bundle: &Bundle<Authorized, i64>, vk: &VerifyingKey) {
assert!(matches!(bundle.verify_proof(&vk), Ok(())));
let sighash: [u8; 32] = bundle.commitment().into();
let bvk = bundle.binding_validating_key();
for action in bundle.actions() {
assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(()));
}
assert_eq!(
bvk.verify(&sighash, bundle.authorization().binding_signature()),
Ok(())
);
}
#[test]
fn bundle_chain() {
let mut rng = OsRng;
let pk = ProvingKey::build();
let vk = VerifyingKey::build();
let sk = SpendingKey::from_bytes([0; 32]).unwrap();
let fvk = FullViewingKey::from(&sk);
let recipient = fvk.address_at(0u32);
// Create a shielding bundle.
let shielding_bundle: Bundle<_, i64> = {
// Use the empty tree.
let anchor = MerkleHashOrchard::empty_root(32.into()).into();
let mut builder = Builder::new(Flags::from_parts(false, true), anchor);
assert_eq!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None),
Ok(())
);
let unauthorized = builder.build(&mut rng).unwrap();
let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk).unwrap();
proven.apply_signatures(&mut rng, sighash, &[]).unwrap()
};
// Verify the shielding bundle.
verify_bundle(&shielding_bundle, &vk);
// Create a shielded bundle spending the previous output.
let shielded_bundle: Bundle<_, i64> = {
let ivk = IncomingViewingKey::from(&fvk);
let (note, _, _) = shielding_bundle
.actions()
.iter()
.find_map(|action| {
let domain = OrchardDomain::for_action(action);
try_note_decryption(&domain, &ivk, action)
})
.unwrap();
// Use the tree with a single leaf.
let cmx: ExtractedNoteCommitment = note.commitment().into();
let leaf = MerkleHashOrchard::from_cmx(&cmx);
let mut tree = BridgeTree::<MerkleHashOrchard, 32>::new(0);
tree.append(&leaf);
tree.witness();
let (position, auth_path) = tree.authentication_path(&leaf).unwrap();
let merkle_path = MerklePath::from_parts(
u64::from(position).try_into().unwrap(),
auth_path[..].try_into().unwrap(),
);
let anchor = tree.root().into();
assert_eq!(anchor, merkle_path.root(cmx));
let mut builder = Builder::new(Flags::from_parts(true, true), anchor);
assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(()));
assert_eq!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None),
Ok(())
);
let unauthorized = builder.build(&mut rng).unwrap();
let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk).unwrap();
proven
.apply_signatures(&mut rng, sighash, &[SpendAuthorizingKey::from(&sk)])
.unwrap()
};
// Verify the shielded bundle.
verify_bundle(&shielded_bundle, &vk);
}