Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ zcash_encoding = { path = "components/zcash_encoding" }
zcash_note_encryption = { path = "components/zcash_note_encryption" }
schemer = { git = "https://github.com/aschampion/schemer.git", rev = "6726b60f43f72c6e24a18d31be0ec7d42829e5e1" }
schemer-rusqlite = { git = "https://github.com/aschampion/schemer.git", rev = "6726b60f43f72c6e24a18d31be0ec7d42829e5e1" }
orchard = { version = "0.3", git = "https://github.com/QED-it/orchard", rev = "35da288c7867c5b0fb0ae84e444915689e49084c" }
orchard = { version = "0.3", git = "https://github.com/QED-it/orchard", rev = "65c9f1817c55e05319dc8e0e118f647f29ffd178" }
4 changes: 4 additions & 0 deletions zcash_history/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ assert_matches = "1.3.0"
quickcheck = "0.9"

[dependencies]
# TODO: Pined until we move to rust v1.64.0 or above to support winnow > 0.4.1
winnow = "=0.4.1"
# TODO: Pined until we move to rust v1.64.0 or above to support toml_datetime > 0.6.1
toml_datetime = "=0.6.1"
primitive-types = "0.11"
byteorder = "1"
blake2 = { package = "blake2b_simd", version = "1" }
Expand Down
9 changes: 8 additions & 1 deletion zcash_primitives/src/transaction/components/orchard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ use zcash_encoding::{Array, CompactSize, Vector};
use super::Amount;
use crate::transaction::Transaction;

mod burn_serialization;
mod burn_validation;

use burn_serialization::{read_bundle_burn, write_bundle_burn};

pub const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
pub const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
pub const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
Expand Down Expand Up @@ -44,6 +49,7 @@ pub fn read_v5_bundle<R: Read>(
} else {
let flags = read_flags(&mut reader)?;
let value_balance = Transaction::read_amount(&mut reader)?;
let burn = read_bundle_burn(&mut reader)?;
let anchor = read_anchor(&mut reader)?;
let proof_bytes = Vector::read(&mut reader, |r| r.read_u8())?;
let actions = NonEmpty::from_vec(
Expand All @@ -64,7 +70,7 @@ pub fn read_v5_bundle<R: Read>(
actions,
flags,
value_balance,
vec![], // TODO implement "burn" reading and writing
burn,
anchor,
authorization,
)))
Expand Down Expand Up @@ -194,6 +200,7 @@ pub fn write_v5_bundle<W: Write>(

writer.write_all(&[bundle.flags().to_byte()])?;
writer.write_all(&bundle.value_balance().to_i64_le_bytes())?;
write_bundle_burn(&mut writer, bundle.burn())?;
writer.write_all(&bundle.anchor().to_bytes())?;
Vector::write(
&mut writer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::io::{self, Read, Write};

use orchard::note::AssetBase;

use zcash_encoding::Vector;

use crate::transaction::Transaction;

use super::{burn_validation::validate_bundle_burn, Amount};

fn read_asset_base<R: Read>(mut reader: R) -> io::Result<AssetBase> {
let mut bytes = [0u8; 32];

reader.read_exact(&mut bytes)?;

Option::from(AssetBase::from_bytes(&bytes))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid AssetBase!"))
}

fn read_asset_burn<R: Read>(mut reader: R) -> io::Result<(AssetBase, Amount)> {
let asset_base = read_asset_base(&mut reader)?;
let amount = Transaction::read_amount(&mut reader)?;

Ok((asset_base, amount))
}

pub fn read_bundle_burn<R: Read>(mut reader: R) -> io::Result<Vec<(AssetBase, Amount)>> {
let burn = Vector::read(&mut reader, |r| read_asset_burn(r))?;
validate_bundle_burn(&burn)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string()))?;
Ok(burn)
}

fn write_amount<W: Write>(mut writer: W, amount: &Amount) -> io::Result<()> {
writer.write_all(&amount.to_i64_le_bytes())
}

fn write_asset_base<W: Write>(mut writer: W, asset_base: &AssetBase) -> io::Result<()> {
writer.write_all(&asset_base.to_bytes())
}

fn write_asset_burn<W: Write>(
mut writer: W,
(asset_base, amount): &(AssetBase, Amount),
) -> io::Result<()> {
write_asset_base(&mut writer, asset_base)?;
write_amount(&mut writer, amount)?;

Ok(())
}

pub fn write_bundle_burn<W: Write>(
mut writer: W,
bundle_burn: &[(AssetBase, Amount)],
) -> io::Result<()> {
Vector::write(&mut writer, bundle_burn, |w, b| write_asset_burn(w, b))
}

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

use std::io::Cursor;

use crate::transaction::tests::create_test_asset;

use super::super::burn_validation::BurnError;

#[test]
fn test_read_write_bundle_burn_success() {
let bundle_burn = (1..10)
.map(|i| {
(
create_test_asset(&format!("Asset {i}")),
Amount::from_u64(i * 10).unwrap(),
)
})
.collect::<Vec<_>>();

let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
write_bundle_burn(&mut cursor, &bundle_burn).unwrap();

cursor.set_position(0);
let result = read_bundle_burn(&mut cursor).unwrap();

assert_eq!(result, bundle_burn);
}

// This test implementation covers only one failure case intentionally,
// as the other cases are already covered in the validate_bundle_burn tests.
#[test]
fn test_read_bundle_burn_duplicate_asset() {
let bundle_burn = vec![
(create_test_asset("Asset 1"), Amount::from_u64(10).unwrap()),
(create_test_asset("Asset 1"), Amount::from_u64(20).unwrap()),
(create_test_asset("Asset 3"), Amount::from_u64(10).unwrap()),
];

let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);

write_bundle_burn(&mut cursor, &bundle_burn).unwrap();

cursor.set_position(0);

let result = read_bundle_burn(&mut cursor);

assert!(
matches!(result, Err(ref err) if err.kind() == io::ErrorKind::InvalidData &&
err.to_string() == BurnError::DuplicateAsset.to_string())
);
}
}
118 changes: 118 additions & 0 deletions zcash_primitives/src/transaction/components/orchard/burn_validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::fmt;

use orchard::note::AssetBase;

use super::Amount;

// FIXME: Consider making tuple (AssetBase, Amount) a new type.

#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub enum BurnError {
DuplicateAsset,
NativeAsset,
ZeroAmount,
}

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::ZeroAmount => write!(f, "Cannot burn an asset with a zero amount."),
}
}
}

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

for (asset, amount) in bundle_burn {
if !asset_set.insert(*asset) {
return Err(BurnError::DuplicateAsset);
}
if asset.is_native().into() {
return Err(BurnError::NativeAsset);
}
// FIXME: check for negative amounts?
if i64::from(amount) == 0 {
return Err(BurnError::ZeroAmount);
}
}

Ok(())
}

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

use crate::transaction::tests::create_test_asset;

#[test]
fn test_validate_bundle_burn_success() {
let bundle_burn = vec![
(create_test_asset("Asset 1"), Amount::from_u64(10).unwrap()),
(create_test_asset("Asset 2"), Amount::from_u64(20).unwrap()),
(create_test_asset("Asset 3"), Amount::from_u64(10).unwrap()),
];

let result = validate_bundle_burn(&bundle_burn);

assert!(result.is_ok());
}

#[test]
fn test_validate_bundle_burn_duplicate_asset() {
let bundle_burn = vec![
(create_test_asset("Asset 1"), Amount::from_u64(10).unwrap()),
(create_test_asset("Asset 1"), Amount::from_u64(20).unwrap()),
(create_test_asset("Asset 3"), Amount::from_u64(10).unwrap()),
];

let result = validate_bundle_burn(&bundle_burn);

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

#[test]
fn test_validate_bundle_burn_native_asset() {
let bundle_burn = vec![
(create_test_asset("Asset 1"), Amount::from_u64(10).unwrap()),
(AssetBase::native(), Amount::from_u64(20).unwrap()),
(create_test_asset("Asset 3"), Amount::from_u64(10).unwrap()),
];

let result = validate_bundle_burn(&bundle_burn);

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

#[test]
fn test_validate_bundle_burn_zero_amount() {
let bundle_burn = vec![
(create_test_asset("Asset 1"), Amount::from_u64(10).unwrap()),
(create_test_asset("Asset 2"), Amount::from_u64(0).unwrap()),
(create_test_asset("Asset 3"), Amount::from_u64(10).unwrap()),
];

let result = validate_bundle_burn(&bundle_burn);

assert_eq!(result, Err(BurnError::ZeroAmount));
}
}
12 changes: 12 additions & 0 deletions zcash_primitives/src/transaction/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ use super::{
#[cfg(feature = "zfuture")]
use super::components::tze;

pub fn create_test_asset(asset_desc: &str) -> orchard::note::AssetBase {
use orchard::{
keys::{IssuanceAuthorizingKey, IssuanceValidatingKey, SpendingKey},
note::AssetBase,
};

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

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

#[test]
fn tx_read_write() {
let data = &self::data::tx_read_write::TX_READ_WRITE;
Expand Down
Loading