Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BatchValidator updates for Orchard and Issue bundles #80

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ff2ac96
Circuit: Add constraints (#77)
ConstanceBeguier Jun 29, 2023
9067ca2
Validate burn of the Orchard bundles in BatchValidator
dmidem Jul 24, 2023
081513b
Circuit: Fix balance violation (#78)
ConstanceBeguier Jul 24, 2023
5a16d93
Fix reference to zcash_note_encryption in Cargo.toml
dmidem Aug 21, 2023
2f0f10a
Minor refinements in burn_validation.rs
dmidem Aug 21, 2023
9ef1331
Add call of validate_bundle_burn to Orchard bundle batch validator
dmidem Aug 21, 2023
5bc2dc4
Add BatchValidator for IssueBundle
dmidem Aug 21, 2023
3958281
Pin rustix dependency version to 0.37.20 as 0.38 requires Rust v1.63
dmidem Aug 23, 2023
1342673
Pin tempfile dependency version to 3.6.0 as 3.8.0 requires Rust v1.63
dmidem Aug 23, 2023
1249764
Pin backtrace dependency version to 0.3.67 as newer ones require Rust…
dmidem Aug 23, 2023
8977652
Pin flate2 dependency version to 1.0.26 as newer ones require newer R…
dmidem Aug 23, 2023
e4a76d5
Fix PartialOrd impl for VerificationKey id redpallas, to fix incorrec…
dmidem Aug 23, 2023
6ac8c65
Sync Cargo.toml with PR #79 (except zcash_note_encryption)
dmidem Aug 23, 2023
7a9e2de
Fix Cargo.toml for the previous commit
dmidem Aug 23, 2023
a027850
Fix Cargo.toml for the previous commit 2
dmidem Aug 23, 2023
5cd3fa4
Pin backtrace dependency version to 0.3.67 as newer ones require Rust…
dmidem Aug 23, 2023
c1a41a9
Fix Cargo.toml to use zcash_note_encryption (librustzcash) from zsa-b…
dmidem Aug 23, 2023
139ecca
Circuit: Add enable_zsa flag (#79)
ConstanceBeguier Aug 31, 2023
006d240
Merge branch 'zsa1' into ivk-to-bytes-visibility-downgrade-with-burn-…
PaulLaux Sep 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ hex = "0.4"
lazy_static = "1"
memuse = { version = "0.2.1", features = ["nonempty"] }
pasta_curves = "0.5"
tempfile = "= 3.5.0" # Last version required rust 1.63
proptest = { version = "1.0.0", optional = true }
rand = "0.8"
reddsa = "0.5"
reddsa = "=0.5.0" # Last version required rust 1.65
nonempty = "0.7"
serde = { version = "1.0", features = ["derive"] }
subtle = "2.3"
Expand All @@ -49,10 +50,12 @@ tracing = "0.1"

# Developer tooling dependencies
image = { version = ">= 0.24, < 0.24.5", optional = true } # 0.24.5 has MSRV 1.61
flate2 = ">= 1.0, <1.0.27" # Clippy issues in last version
plotters = { version = "0.3.0", optional = true }

[dev-dependencies]
bridgetree = "0.3"
half = ">= 1.8, < 2.3"
criterion = "0.3"
halo2_gadgets = { git = "https://github.com/QED-it/halo2", branch = "zsa1", features = ["test-dependencies"] }
hex = "0.4"
Expand All @@ -61,8 +64,11 @@ zcash_note_encryption = { version = "0.4", features = ["pre-zip-212"] }
incrementalmerkletree = { version = "0.4", features = ["test-dependencies"] }

[target.'cfg(unix)'.dev-dependencies]
hashbrown = ">= 0.12, <0.13"
dashmap = ">= 5.4, <5.5"
inferno = ">= 0.11, < 0.11.15"
pprof = { version = "0.9", features = ["criterion", "flamegraph"] } # MSRV 1.56
backtrace = "=0.3.67" # Last version required rust 1.65

[lib]
bench = false
Expand Down Expand Up @@ -92,4 +98,4 @@ debug = true
debug = true

[patch.crates-io]
zcash_note_encryption = { version = "0.4", git = "https://github.com/QED-it/librustzcash.git", branch = "zsa-bundle-in-v5-vector-serialization" }
zcash_note_encryption = { version = "0.4", git = "https://github.com/QED-it/librustzcash.git", branch = "zsa-bundle-in-v5-vector-serialization-with-burn-valid" }
2 changes: 1 addition & 1 deletion benches/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn criterion_benchmark(c: &mut Criterion) {

let create_bundle = |num_recipients| {
let mut builder = Builder::new(
Flags::from_parts(true, true),
Flags::from_parts(true, true, false),
Anchor::from_bytes([0; 32]).unwrap(),
);
for _ in 0..num_recipients {
Expand Down
2 changes: 1 addition & 1 deletion benches/note_decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ fn bench_note_decryption(c: &mut Criterion) {

let bundle = {
let mut builder = Builder::new(
Flags::from_parts(true, true),
Flags::from_parts(true, true, false),
Anchor::from_bytes([0; 32]).unwrap(),
);
// The builder pads to two actions, and shuffles their order. Add two recipients
Expand Down
4 changes: 2 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@
.iter()
.map(|recipient| NoteValue::zero() - recipient.value),
)
.fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value)

Check warning on line 455 in src/builder.rs

View workflow job for this annotation

GitHub Actions / Clippy (beta)

usage of `Iterator::fold` on a type that implements `Try`

warning: usage of `Iterator::fold` on a type that implements `Try` --> src/builder.rs:455:14 | 455 | .fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(ValueSum::zero(), |acc, note_value| ...)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold = note: `#[warn(clippy::manual_try_fold)]` on by default

Check warning on line 455 in src/builder.rs

View workflow job for this annotation

GitHub Actions / Clippy (beta)

usage of `Iterator::fold` on a type that implements `Try`

warning: usage of `Iterator::fold` on a type that implements `Try` --> src/builder.rs:455:14 | 455 | .fold(Some(ValueSum::zero()), |acc, note_value| acc? + note_value) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `try_fold` instead: `try_fold(ValueSum::zero(), |acc, note_value| ...)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold = note: `#[warn(clippy::manual_try_fold)]` on by default
.ok_or(OverflowError)?;
i64::try_from(value_balance).and_then(|i| V::try_from(i).map_err(|_| value::OverflowError))
}
Expand Down Expand Up @@ -527,9 +527,9 @@
let native_value_balance: V = pre_actions
.iter()
.filter(|action| action.spend.note.asset().is_native().into())
.fold(Some(ValueSum::zero()), |acc, action| {
acc? + action.value_sum()
})

Check warning on line 532 in src/builder.rs

View workflow job for this annotation

GitHub Actions / Clippy (beta)

usage of `Iterator::fold` on a type that implements `Try`

warning: usage of `Iterator::fold` on a type that implements `Try` --> src/builder.rs:530:14 | 530 | .fold(Some(ValueSum::zero()), |acc, action| { | ______________^ 531 | | acc? + action.value_sum() 532 | | }) | |______________^ help: use `try_fold` instead: `try_fold(ValueSum::zero(), |acc, action| ...)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold

Check warning on line 532 in src/builder.rs

View workflow job for this annotation

GitHub Actions / Clippy (beta)

usage of `Iterator::fold` on a type that implements `Try`

warning: usage of `Iterator::fold` on a type that implements `Try` --> src/builder.rs:530:14 | 530 | .fold(Some(ValueSum::zero()), |acc, action| { | ______________^ 531 | | acc? + action.value_sum() 532 | | }) | |______________^ help: use `try_fold` instead: `try_fold(ValueSum::zero(), |acc, action| ...)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold
.ok_or(OverflowError)?
.into()?;

Expand Down Expand Up @@ -947,7 +947,7 @@
/// Create a bundle from the set of arbitrary bundle inputs.
fn into_bundle<V: TryFrom<i64> + Copy + Into<i64>>(mut self) -> Bundle<Authorized, V> {
let fvk = FullViewingKey::from(&self.sk);
let flags = Flags::from_parts(true, true);
let flags = Flags::from_parts(true, true, true);
let mut builder = Builder::new(flags, self.anchor);

for (note, path) in self.notes.into_iter() {
Expand Down Expand Up @@ -1068,7 +1068,7 @@
let recipient = fvk.address_at(0u32, Scope::External);

let mut builder = Builder::new(
Flags::from_parts(true, true),
Flags::from_parts(true, true, false),
EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
);

Expand Down
29 changes: 25 additions & 4 deletions src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Structs related to bundles of Orchard actions.

mod batch;
pub mod burn_validation;
pub mod commitments;

pub use batch::BatchValidator;
Expand Down Expand Up @@ -38,6 +39,7 @@ impl<T> Action<T> {
cmx: *self.cmx(),
enable_spend: flags.spends_enabled,
enable_output: flags.outputs_enabled,
enable_zsa: flags.zsa_enabled,
}
}
}
Expand All @@ -57,18 +59,25 @@ pub struct Flags {
/// guaranteed to be dummy notes. If `true`, the created notes may be either real or
/// dummy notes.
outputs_enabled: bool,
/// Flag denoting whether ZSA transaction is enabled.
///
/// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are
/// guaranteed to be notes with native asset.
zsa_enabled: bool,
}

const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
const FLAG_ZSA_ENABLED: u8 = 0b0000_0100;
const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED | FLAG_ZSA_ENABLED);

impl Flags {
/// Construct a set of flags from its constituent parts
pub fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self {
pub fn from_parts(spends_enabled: bool, outputs_enabled: bool, zsa_enabled: bool) -> Self {
Flags {
spends_enabled,
outputs_enabled,
zsa_enabled,
}
}

Expand All @@ -90,6 +99,14 @@ impl Flags {
self.outputs_enabled
}

/// Flag denoting whether ZSA transaction is enabled.
///
/// If `false`, all notes within [`Action`]s in the transaction's [`Bundle`] are
/// guaranteed to be notes with native asset.
pub fn zsa_enabled(&self) -> bool {
self.zsa_enabled
}

/// Serialize flags to a byte as defined in [Zcash Protocol Spec § 7.1: Transaction
/// Encoding And Consensus][txencoding].
///
Expand All @@ -102,6 +119,9 @@ impl Flags {
if self.outputs_enabled {
value |= FLAG_OUTPUTS_ENABLED;
}
if self.zsa_enabled {
value |= FLAG_ZSA_ENABLED;
}
value
}

Expand All @@ -116,6 +136,7 @@ impl Flags {
Some(Self::from_parts(
value & FLAG_SPENDS_ENABLED != 0,
value & FLAG_OUTPUTS_ENABLED != 0,
value & FLAG_ZSA_ENABLED != 0,
))
} else {
None
Expand Down Expand Up @@ -607,8 +628,8 @@ pub mod testing {

prop_compose! {
/// Create an arbitrary set of flags.
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags {
Flags::from_parts(spends_enabled, outputs_enabled)
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY, zsa_enabled in prop::bool::ANY) -> Flags {
Flags::from_parts(spends_enabled, outputs_enabled, zsa_enabled)
}
}

Expand Down
18 changes: 16 additions & 2 deletions src/bundle/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ use pasta_curves::vesta;
use rand::{CryptoRng, RngCore};
use tracing::debug;

use super::{Authorized, Bundle};
use super::{burn_validation::validate_bundle_burn, Authorized, Bundle};
use crate::{
circuit::VerifyingKey,
note::AssetBase,
primitives::redpallas::{self, Binding, SpendAuth},
};

Expand All @@ -23,6 +24,7 @@ struct BundleSignature {
pub struct BatchValidator {
proofs: plonk::BatchVerifier<vesta::Affine>,
signatures: Vec<BundleSignature>,
burns: Vec<(AssetBase, i64)>,
}

impl BatchValidator {
Expand All @@ -31,10 +33,11 @@ impl BatchValidator {
BatchValidator {
proofs: plonk::BatchVerifier::new(),
signatures: vec![],
burns: vec![],
}
}

/// Adds the proof and RedPallas signatures from the given bundle to the validator.
/// Adds the proof, RedPallas signatures and burn from the given bundle to the validator.
pub fn add_bundle<V: Copy + Into<i64>>(
&mut self,
bundle: &Bundle<Authorized, V>,
Expand All @@ -58,6 +61,13 @@ impl BatchValidator {
.authorization()
.proof()
.add_to_batch(&mut self.proofs, bundle.to_instances());

self.burns.extend(
bundle
.burn
.iter()
.map(|(asset, amount)| (*asset, (*amount).into())),
);
}

/// Batch-validates the accumulated bundles.
Expand All @@ -67,6 +77,10 @@ impl BatchValidator {
/// 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, vk: &VerifyingKey, rng: R) -> bool {
if validate_bundle_burn(&self.burns).is_err() {
return false;
}

if self.signatures.is_empty() {
// An empty batch is always valid, but is not free to run; skip it.
// Note that a transaction has at least a binding signature, so if
Expand Down
146 changes: 146 additions & 0 deletions src/bundle/burn_validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! Validating burn operations on asset bundles.
//!
//! The module provides a function `validate_bundle_burn` that can be used to validate a burn for a bundle.
//!
use std::fmt;

use crate::note::AssetBase;

/// Possible errors that can occur during bundle burn validation.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub enum BurnError {
/// Encountered a duplicate asset to burn.
DuplicateAsset,
/// Cannot burn a native asset.
NativeAsset,
/// Cannot burn an asset with a nonpositive value.
NonPositiveAmount,
}

impl fmt::Display for BurnError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
BurnError::DuplicateAsset => write!(f, "Encountered a duplicate asset to burn."),
BurnError::NativeAsset => write!(f, "Cannot burn a native asset."),
BurnError::NonPositiveAmount => {
write!(f, "Cannot burn an asset with a nonpositive value.")
}
}
}
}

/// Validates burn for a bundle by ensuring each asset is unique, non-native, and has a positive value.
///
/// Each burn element is represented as a tuple of `AssetBase` and `i64` (value for the burn).
///
/// # Arguments
///
/// * `burn` - A vector of assets, where each asset is represented as a tuple of `AssetBase` and `i64` (value the burn).
///
/// # Errors
///
/// Returns a `BurnError` if:
/// * Any asset in the `burn` vector is not unique (`BurnError::DuplicateAsset`).
/// * Any asset in the `burn` vector is native (`BurnError::NativeAsset`).
/// * Any asset in the `burn` vector has a nonpositive value (`BurnError::NonPositiveAmount`).
pub fn validate_bundle_burn(bundle_burn: &Vec<(AssetBase, i64)>) -> Result<(), BurnError> {
let mut asset_set = std::collections::HashSet::<&AssetBase>::new();

for (asset, value) in bundle_burn {
if !asset_set.insert(asset) {
return Err(BurnError::DuplicateAsset);
}
if asset.is_native().into() {
return Err(BurnError::NativeAsset);
}
if *value <= 0 {
return Err(BurnError::NonPositiveAmount);
}
}

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

/// Creates an item of bundle burn list for a given asset description and value.
///
/// This function is deterministic and guarantees that each call with the same parameters
/// will return the same result. It achieves determinism by using a static `IssuanceKey`.
///
/// # Arguments
///
/// * `asset_desc` - The asset description string.
/// * `value` - The value for the burn.
///
/// # Returns
///
/// A tuple `(AssetBase, Amount)` representing the burn list item.
///
pub fn get_burn_tuple(asset_desc: &str, value: i64) -> (AssetBase, i64) {
use crate::keys::{IssuanceAuthorizingKey, IssuanceKey, IssuanceValidatingKey};

let sk_iss = IssuanceKey::from_bytes([0u8; 32]).unwrap();
let isk: IssuanceAuthorizingKey = (&sk_iss).into();

(
AssetBase::derive(&IssuanceValidatingKey::from(&isk), asset_desc),
value,
)
}

#[test]
fn validate_bundle_burn_success() {
let bundle_burn = vec![
get_burn_tuple("Asset 1", 10),
get_burn_tuple("Asset 2", 20),
get_burn_tuple("Asset 3", 10),
];

let result = validate_bundle_burn(&bundle_burn);

assert!(result.is_ok());
}

#[test]
fn validate_bundle_burn_duplicate_asset() {
let bundle_burn = vec![
get_burn_tuple("Asset 1", 10),
get_burn_tuple("Asset 1", 20),
get_burn_tuple("Asset 3", 10),
];

let result = validate_bundle_burn(&bundle_burn);

assert_eq!(result, Err(BurnError::DuplicateAsset));
}

#[test]
fn validate_bundle_burn_native_asset() {
let bundle_burn = vec![
get_burn_tuple("Asset 1", 10),
(AssetBase::native(), 20),
get_burn_tuple("Asset 3", 10),
];

let result = validate_bundle_burn(&bundle_burn);

assert_eq!(result, Err(BurnError::NativeAsset));
}

#[test]
fn validate_bundle_burn_zero_value() {
let bundle_burn = vec![
get_burn_tuple("Asset 1", 10),
get_burn_tuple("Asset 2", 0),
get_burn_tuple("Asset 3", 10),
];

let result = validate_bundle_burn(&bundle_burn);

assert_eq!(result, Err(BurnError::NonPositiveAmount));
}
}
Loading
Loading