Skip to content

Commit

Permalink
ActionGroup-based serialization format for Orchard v6 (#86)
Browse files Browse the repository at this point in the history
ActionGroup-based serialization format is implemented for Orchard v6
  • Loading branch information
alexeykoren authored Jan 25, 2025
1 parent b61b7f2 commit 5ea95ea
Show file tree
Hide file tree
Showing 11 changed files with 2,326 additions and 2,790 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ codegen-units = 1
[patch.crates-io]
zcash_note_encryption = { version = "0.4", git = "https://github.com/QED-it/zcash_note_encryption", branch = "zsa1" }
sapling = { package = "sapling-crypto", version = "0.1.3", git = "https://github.com/QED-it/sapling-crypto", branch = "zsa1" }
orchard = { version = "0.8.0", git = "https://github.com/QED-it/orchard", branch = "zsa1" }
orchard = { version = "0.8.0", git = "https://github.com/QED-it/orchard", rev = "3dbdbc52c6e2ffeca015ae6eb80ad7f1c870384d" }
4 changes: 2 additions & 2 deletions components/zcash_protocol/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ impl TryFrom<u32> for BranchId {
#[cfg(zcash_unstable = "nu6")]
0xc8e7_1055 => Ok(BranchId::Nu6),
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
0x7777_7777 => Ok(BranchId::Nu7),
0x7719_0ad8 => Ok(BranchId::Nu7),
#[cfg(zcash_unstable = "zfuture")]
0xffff_ffff => Ok(BranchId::ZFuture),
_ => Err("Unknown consensus branch ID"),
Expand All @@ -647,7 +647,7 @@ impl From<BranchId> for u32 {
#[cfg(zcash_unstable = "nu6")]
BranchId::Nu6 => 0xc8e7_1055,
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
BranchId::Nu7 => 0x7777_7777,
BranchId::Nu7 => 0x7719_0ad8,
#[cfg(zcash_unstable = "zfuture")]
BranchId::ZFuture => 0xffff_ffff,
}
Expand Down
2 changes: 1 addition & 1 deletion zcash_client_backend/src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use zcash_primitives::{
use crate::{data_api::DecryptedTransaction, keys::UnifiedFullViewingKey};

#[cfg(feature = "orchard")]
use orchard::note_encryption::OrchardDomain;
use orchard::domain::OrchardDomain;

/// An enumeration of the possible relationships a TXO can have to the wallet.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
Expand Down
4 changes: 2 additions & 2 deletions zcash_client_backend/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,12 @@ impl compact_formats::CompactSaplingSpend {

#[cfg(feature = "orchard")]
impl TryFrom<&compact_formats::CompactOrchardAction>
for orchard::note_encryption::CompactAction<orchard::orchard_flavor::OrchardVanilla>
for orchard::domain::CompactAction<orchard::orchard_flavor::OrchardVanilla>
{
type Error = ();

fn try_from(value: &compact_formats::CompactOrchardAction) -> Result<Self, Self::Error> {
Ok(orchard::note_encryption::CompactAction::from_parts(
Ok(orchard::domain::CompactAction::from_parts(
value.nf()?,
value.cmx()?,
value.ephemeral_key()?,
Expand Down
6 changes: 3 additions & 3 deletions zcash_client_backend/src/scanning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{

#[cfg(feature = "orchard")]
use orchard::{
note_encryption::{CompactAction, OrchardDomain},
domain::{CompactAction, OrchardDomain},
orchard_flavor::OrchardVanilla,
tree::MerkleHashOrchard,
};
Expand Down Expand Up @@ -537,14 +537,14 @@ type TaggedSaplingBatchRunner<IvkTag, Tasks> = BatchRunner<
type TaggedOrchardBatch<IvkTag> = Batch<
IvkTag,
OrchardDomain<OrchardVanilla>,
orchard::note_encryption::CompactAction<OrchardVanilla>,
orchard::domain::CompactAction<OrchardVanilla>,
CompactDecryptor,
>;
#[cfg(feature = "orchard")]
type TaggedOrchardBatchRunner<IvkTag, Tasks> = BatchRunner<
IvkTag,
OrchardDomain<OrchardVanilla>,
orchard::note_encryption::CompactAction<OrchardVanilla>,
orchard::domain::CompactAction<OrchardVanilla>,
CompactDecryptor,
Tasks,
>;
Expand Down
2 changes: 1 addition & 1 deletion zcash_client_sqlite/src/wallet/orchard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ pub(crate) mod tests {
use incrementalmerkletree::{Hashable, Level};
use orchard::{
keys::{FullViewingKey, SpendingKey},
note_encryption::OrchardDomain,
domain::OrchardDomain,
tree::MerkleHashOrchard,
};
use shardtree::error::ShardTreeError;
Expand Down
186 changes: 139 additions & 47 deletions zcash_primitives/src/transaction/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, ()> {
ik: IssuanceAuthorizingKey,
asset_desc: Vec<u8>,
issue_info: Option<IssueInfo>,
first_issuance: bool,
) -> Result<(), Error<FE>> {
assert!(self.build_config.orchard_bundle_type()? == BundleType::DEFAULT_ZSA);

Expand All @@ -482,6 +483,7 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, ()> {
IssuanceValidatingKey::from(&ik),
asset_desc,
issue_info,
first_issuance,
OsRng,
)
.map_err(Error::IssuanceBundle)?
Expand All @@ -499,12 +501,13 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, ()> {
asset_desc: &[u8],
recipient: Address,
value: orchard::value::NoteValue,
first_issuance: bool,
) -> Result<(), Error<FE>> {
assert!(self.build_config.orchard_bundle_type()? == BundleType::DEFAULT_ZSA);
self.issuance_builder
.as_mut()
.ok_or(Error::IssuanceBuilderNotAvailable)?
.add_recipient(asset_desc, recipient, value, OsRng)
.add_recipient(asset_desc, recipient, value, first_issuance, OsRng)
.map_err(Error::IssuanceBundle)?;

Ok(())
Expand Down Expand Up @@ -874,19 +877,13 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder<
if bundle_type == BundleType::DEFAULT_ZSA {
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
{
let (bundle, meta) = builder
.build(&mut rng)
.map_err(Error::OrchardBuild)?
.unwrap();
let (bundle, meta) = builder.build(&mut rng).map_err(Error::OrchardBuild)?;

unproven_orchard_zsa_bundle = Some(bundle);
orchard_meta = meta;
}
} else {
let (bundle, meta) = builder
.build(&mut rng)
.map_err(Error::OrchardBuild)?
.unwrap();
let (bundle, meta) = builder.build(&mut rng).map_err(Error::OrchardBuild)?;
unproven_orchard_bundle = Some(bundle);
orchard_meta = meta;
}
Expand Down Expand Up @@ -1157,6 +1154,7 @@ mod tests {
orchard::note::AssetBase,
orchard::value::NoteValue,
orchard::Address,
orchard::ReferenceKeys,
zcash_protocol::consensus::TestNetwork,
zcash_protocol::constants::testnet::COIN_TYPE,
zip32::Scope::External,
Expand Down Expand Up @@ -1458,20 +1456,14 @@ mod tests {
let asset_desc: Vec<u8> = "asset_desc".into();

builder
.init_issuance_bundle::<FeeError>(iak, asset_desc.clone(), None)
.init_issuance_bundle::<FeeError>(iak, asset_desc.clone(), None, false)
.unwrap();

let tx = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = tx.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 1, "There should be only one action");
let actions = bundle.get_actions_by_desc(&asset_desc);
assert_eq!(
actions.len(),
1,
"The one action should correspond to asset_desc"
);
let action = actions[0];
let action = bundle.get_action_by_desc(&asset_desc).unwrap();
assert!(action.is_finalized(), "Action should be finalized");
assert_eq!(action.notes().len(), 0, "Action should have zero notes");
}
Expand All @@ -1491,20 +1483,15 @@ mod tests {
recipient: address,
value: NoteValue::from_raw(42),
}),
false,
)
.unwrap();

let binding = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = binding.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 1, "There should be only one action");
let actions = bundle.get_actions_by_desc(&asset_desc);
assert_eq!(
actions.len(),
1,
"The one action should correspond to asset_desc"
);
let action = actions[0];
let action = bundle.get_action_by_desc(&asset_desc).unwrap();
assert!(!action.is_finalized(), "Action should not be finalized");
assert_eq!(action.notes().len(), 1, "Action should have 1 note");
assert_eq!(
Expand All @@ -1529,23 +1516,18 @@ mod tests {
recipient: address,
value: NoteValue::from_raw(42),
}),
false,
)
.unwrap();
builder
.add_recipient::<FeeError>(&asset_desc, address, NoteValue::from_raw(21))
.add_recipient::<FeeError>(&asset_desc, address, NoteValue::from_raw(21), false)
.unwrap();

let binding = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = binding.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 1, "There should be only one action");
let actions = bundle.get_actions_by_desc(&asset_desc);
assert_eq!(
actions.len(),
1,
"The one action should correspond to asset_desc"
);
let action = actions[0];
let action = bundle.get_action_by_desc(&asset_desc).unwrap();
assert!(!action.is_finalized(), "Action should not be finalized");
assert_eq!(action.notes().len(), 2, "Action should have 2 notes");
assert_eq!(
Expand Down Expand Up @@ -1575,24 +1557,19 @@ mod tests {
recipient: address,
value: NoteValue::from_raw(42),
}),
false,
)
.unwrap();
builder
.add_recipient::<FeeError>(&asset_desc_2, address, NoteValue::from_raw(21))
.add_recipient::<FeeError>(&asset_desc_2, address, NoteValue::from_raw(21), false)
.unwrap();

let binding = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = binding.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 2, "There should be 2 actions");

let actions = bundle.get_actions_by_desc(&asset_desc_1);
assert_eq!(
actions.len(),
1,
"Only one action should correspond to asset_desc_1"
);
let action = actions[0];
let action = bundle.get_action_by_desc(&asset_desc_1).unwrap();
assert!(!action.is_finalized(), "Action should not be finalized");
assert_eq!(action.notes().len(), 1, "Action should have 1 note");
assert_eq!(
Expand All @@ -1605,13 +1582,7 @@ mod tests {
"Incorrect notes sum"
);

let actions_2 = bundle.get_actions_by_desc(&asset_desc_2);
assert_eq!(
actions_2.len(),
1,
"Only one action should correspond to asset_desc_2"
);
let action2 = actions_2[0];
let action2 = bundle.get_action_by_desc(&asset_desc_2).unwrap();
assert!(!action2.is_finalized(), "Action should not be finalized");
assert_eq!(action2.notes().len(), 1, "Action should have 1 note");
assert_eq!(
Expand All @@ -1625,6 +1596,127 @@ mod tests {
);
}

#[test]
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
fn first_issuance_init_issuance_bundle() {
let (mut builder, iak, address) = prepare_zsa_test();

let asset_desc: Vec<u8> = "asset_desc".into();

builder
.init_issuance_bundle::<FeeError>(
iak,
asset_desc.clone(),
Some(IssueInfo {
recipient: address,
value: NoteValue::from_raw(42),
}),
true,
)
.unwrap();

let binding = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = binding.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 1, "There should be only one action");
let action = bundle.get_action_by_desc(&asset_desc).unwrap();
assert_eq!(
action.notes().len(),
2,
"Action should have 2 notes, including the reference note"
);
assert_eq!(
action.notes().first().unwrap().value().inner(),
0,
"Incorrect reference note value"
);
assert_eq!(
action.notes().first().unwrap().recipient(),
ReferenceKeys::recipient(),
"Incorrect recipient in reference note"
);
assert_eq!(
action.notes()[1].value().inner(),
42,
"Incorrect note value"
);
}

#[test]
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
fn first_issuance_add_recipient() {
let (mut builder, iak, address) = prepare_zsa_test();

let asset_desc: Vec<u8> = "asset_desc".into();

builder
.init_issuance_bundle::<FeeError>(iak, asset_desc.clone(), None, false)
.unwrap();

builder
.add_recipient::<FeeError>(&asset_desc, address, NoteValue::from_raw(42), true)
.unwrap();

let binding = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = binding.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 1, "There should be only one action");
let action = bundle.get_action_by_desc(&asset_desc).unwrap();
assert_eq!(
action.notes().len(),
2,
"Action should have 2 notes, including the reference note"
);
assert_eq!(
action.notes().first().unwrap().value().inner(),
0,
"Incorrect reference note value"
);
assert_eq!(
action.notes().first().unwrap().recipient(),
ReferenceKeys::recipient(),
"Incorrect recipient in reference note"
);
assert_eq!(
action.notes()[1].value().inner(),
42,
"Incorrect note value"
);
}

#[test]
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
fn first_issuance_only_reference_note() {
let (mut builder, iak, _) = prepare_zsa_test();

let asset_desc: Vec<u8> = "asset_desc".into();

builder
.init_issuance_bundle::<FeeError>(iak, asset_desc.clone(), None, true)
.unwrap();

let binding = builder.mock_build_no_fee(OsRng).unwrap().into_transaction();
let bundle = binding.issue_bundle().unwrap();

assert_eq!(bundle.actions().len(), 1, "There should be only one action");
let action = bundle.get_action_by_desc(&asset_desc).unwrap();
assert_eq!(
action.notes().len(),
1,
"Action should have 1 note, including the reference note"
);
assert_eq!(
action.notes().first().unwrap().value().inner(),
0,
"Incorrect reference note value"
);
assert_eq!(
action.notes().first().unwrap().recipient(),
ReferenceKeys::recipient(),
"Incorrect recipient in reference note"
);
}

#[test]
#[should_panic]
#[cfg(zcash_unstable = "nu6" /* TODO nu7 */ )]
Expand Down
Loading

0 comments on commit 5ea95ea

Please sign in to comment.