Skip to content

Commit e0dd894

Browse files
committed
feat(wasm-utxo): parse and encode zcash v5 transaction format
Ticket: CSHLD-1059
1 parent 70e4e07 commit e0dd894

4 files changed

Lines changed: 245 additions & 49 deletions

File tree

packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,7 @@ impl BitGoPsbt {
15701570
),
15711571
expiry_height: Some(zcash_psbt.expiry_height.unwrap_or(0)),
15721572
sapling_fields: zcash_psbt.sapling_fields.clone(),
1573+
consensus_branch_id: None,
15731574
};
15741575
crate::zcash::transaction::encode_zcash_transaction_parts(&parts)
15751576
}

packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/zcash_psbt.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use miniscript::bitcoin::{Transaction, VarInt};
99
use std::io::Read;
1010

1111
pub use crate::zcash::transaction::{
12-
decode_zcash_transaction_meta, ZcashTransactionMeta, ZCASH_SAPLING_VERSION_GROUP_ID,
12+
decode_zcash_transaction_meta, ZcashTransactionMeta, ZCASH_NU5_VERSION_GROUP_ID,
13+
ZCASH_SAPLING_VERSION_GROUP_ID,
1314
};
1415

1516
/// A Zcash-compatible PSBT that can handle overwintered transactions
@@ -135,15 +136,27 @@ impl ZcashBitGoPsbt {
135136
&self,
136137
tx: &Transaction,
137138
) -> Result<Vec<u8>, super::DeserializeError> {
139+
let vgid = self
140+
.version_group_id
141+
.unwrap_or(ZCASH_SAPLING_VERSION_GROUP_ID);
142+
let consensus_branch_id = if vgid == ZCASH_NU5_VERSION_GROUP_ID {
143+
Some(
144+
super::propkv::get_zec_consensus_branch_id(&self.psbt).ok_or_else(|| {
145+
super::DeserializeError::Network(
146+
"v5 transaction requires consensus_branch_id".to_string(),
147+
)
148+
})?,
149+
)
150+
} else {
151+
None
152+
};
138153
let parts = crate::zcash::transaction::ZcashTransactionParts {
139154
transaction: tx.clone(),
140155
is_overwintered: true,
141-
version_group_id: Some(
142-
self.version_group_id
143-
.unwrap_or(ZCASH_SAPLING_VERSION_GROUP_ID),
144-
),
156+
version_group_id: Some(vgid),
145157
expiry_height: Some(self.expiry_height.unwrap_or(0)),
146158
sapling_fields: self.sapling_fields.clone(),
159+
consensus_branch_id,
147160
};
148161
crate::zcash::transaction::encode_zcash_transaction_parts(&parts)
149162
.map_err(super::DeserializeError::Network)
@@ -180,6 +193,7 @@ impl ZcashBitGoPsbt {
180193
.unwrap_or(ZCASH_SAPLING_VERSION_GROUP_ID);
181194
let expiry_height = self.expiry_height.unwrap_or(0);
182195
let sapling_fields = self.sapling_fields;
196+
let stored_branch_id = super::propkv::get_zec_consensus_branch_id(&self.psbt);
183197

184198
let map_extract_err = |e: ExtractTxError| match e {
185199
ExtractTxError::AbsurdFeeRate { .. } => {
@@ -203,21 +217,38 @@ impl ZcashBitGoPsbt {
203217
.map_err(map_extract_err)?,
204218
};
205219

220+
let consensus_branch_id = if version_group_id == ZCASH_NU5_VERSION_GROUP_ID {
221+
Some(stored_branch_id.ok_or_else(|| {
222+
super::DeserializeError::Network(
223+
"v5 transaction requires consensus_branch_id".to_string(),
224+
)
225+
})?)
226+
} else {
227+
None
228+
};
206229
let parts = crate::zcash::transaction::ZcashTransactionParts {
207230
transaction: tx,
208231
is_overwintered: true,
209232
version_group_id: Some(version_group_id),
210233
expiry_height: Some(expiry_height),
211234
sapling_fields,
235+
consensus_branch_id,
212236
};
213237
crate::zcash::transaction::encode_zcash_transaction_parts(&parts)
214238
.map_err(super::DeserializeError::Network)
215239
}
216240

217-
/// Compute the transaction ID for the unsigned Zcash transaction
241+
/// Compute the transaction ID for the unsigned Zcash transaction.
218242
///
219-
/// The txid is the double SHA256 of the full Zcash transaction bytes.
243+
/// v4 (Sapling): double SHA-256 of the serialized transaction bytes.
244+
/// v5 (NU5/ZIP-225): ZIP-244 recursive BLAKE2b hash tree — not yet implemented.
220245
pub fn compute_txid(&self) -> Result<[u8; 32], super::DeserializeError> {
246+
if self.version_group_id == Some(ZCASH_NU5_VERSION_GROUP_ID) {
247+
return Err(super::DeserializeError::Network(
248+
"compute_txid for v5 transactions requires ZIP-244, which is not yet implemented"
249+
.to_string(),
250+
));
251+
}
221252
use miniscript::bitcoin::hashes::{sha256d, Hash};
222253
let tx_bytes = self.extract_unsigned_zcash_transaction()?;
223254
let hash = sha256d::Hash::hash(&tx_bytes);

packages/wasm-utxo/src/inspect/psbt.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,13 @@ pub fn zcash_psbt_to_node(zcash_psbt: &ZcashBitGoPsbt, network: Network) -> Node
570570
version_group_id: zcash_psbt.version_group_id,
571571
expiry_height: zcash_psbt.expiry_height,
572572
sapling_fields: zcash_psbt.sapling_fields.clone(),
573+
consensus_branch_id: if zcash_psbt.version_group_id
574+
== Some(crate::zcash::transaction::ZCASH_NU5_VERSION_GROUP_ID)
575+
{
576+
crate::fixed_script_wallet::bitgo_psbt::propkv::get_zec_consensus_branch_id(psbt)
577+
} else {
578+
None
579+
},
573580
};
574581
psbt_node.add_child(zcash_tx_to_node(&parts, network));
575582

0 commit comments

Comments
 (0)