What happened?
Reported by @robustfengbin. Opened as issue because not currently exploitable.
Summary
A crafted V6 transaction passes Transaction::zcash_deserialize() but panics at zebra-chain/src/transaction/hash.rs:77 when computing the tx hash β TxIdBuilder::txid() returns None (because to_librustzcash() fails on V6 due to zcash_primitives not yet supporting the ZIP-233 zip233_amount field), and the code calls .expect(). This happens in the P2P codec during Transaction β UnminedTx conversion, before any consensus checks. Any peer can crash any Zebra node compiled with --cfg zcash_unstable="nu7" + feature="tx_v6" by sending a single 33-byte tx message.
The V5 arm in serialize.rs already calls tx.to_librustzcash(network_upgrade)?; immediately after deserializing V5 transactions, which validates librustzcash compatibility at the deserialize boundary and prevents the panic for V5 inputs. The V6 arm in the same match does not include this check, so V6 inputs reach hash.rs:77 and panic.
Current attack surface is zero β production ZF release binaries do not enable tx_v6 + zcash_unstable="nu7", so the V6 match arm is preprocessed out. Once NU7 testnet/mainnet activation requires enabling those flags in production, every Zebra node becomes vulnerable to a single 33-byte broadcast tx. We are reporting before NU7 activation so the fix can land on a calm release schedule rather than as an emergency response.
Details
zebra-chain/src/transaction/hash.rs:72-79:
impl From<&Transaction> for Hash {
fn from(transaction: &Transaction) -> Self {
let hasher = TxIdBuilder::new(transaction);
hasher
.txid()
.expect("zcash_primitives and Zebra transaction formats must be compatible")
// ^^^^ panics when txid() returns None
}
}
For V6 inputs, txid() delegates to txid_v5(), which calls to_librustzcash(nu).ok()?. Because zcash_primitives 0.26 does not recognize zip233_amount, this returns None, propagates up, and .expect() panics.
crash path
zebra-network/src/protocol/external/codec.rs read_tx()
β Transaction::zcash_deserialize(reader) β V6 deserialize succeeds
β Message::Tx(result?.into()) β Transaction β UnminedTx
β UnminedTx::from(Transaction)
β UnminedTxId::from(&Transaction) β V6 arm Witnessed(transaction.into())
β WtxId::from(&Transaction) β id: transaction.into()
β Hash::from(&Transaction)
β TxIdBuilder::new(tx).txid().expect(...) β panic at hash.rs:77
Suggested fix β 3 lines, mirroring the V5 arm
#[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
(6, true) => {
// ...existing deserialize logic up to building V6 fields...
let tx = Transaction::V6 { ... };
tx.to_librustzcash(network_upgrade)?; // β NEW (mirrors V5 arm)
Ok(tx)
}
PoC
use std::io::Cursor;
use zebra_chain::serialization::ZcashDeserialize;
use zebra_chain::transaction::Transaction;
fn main() {
let bytes: Vec<u8> = vec![
0x06, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF,
0x55, 0x10, 0xE7, 0xC8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
];
let tx = Transaction::zcash_deserialize(Cursor::new(&bytes))
.expect("deserializes successfully");
let _hash = tx.hash(); // panics at hash.rs:77
}
Expected output:
thread '' panicked at zebra-chain/src/transaction/hash.rs:77:14:
zcash_primitives and Zebra transaction formats must be compatible
Impact
Remote Denial of Service, network-wide once V6 transactions are accepted.
- Every Zebra node that has P2P port 8233 open is reachable
- A single attacker-crafted 33-byte V6
tx message panics the codec, before any consensus check
- No authentication, signatures, or special access required
- Same propagation surface as any normal P2P
tx β one broadcast can hit the whole network
Current attack surface (today, pre-NU7): zero. ZF release binaries (zfnd/zebra:latest) do not set tx_v6 Cargo feature nor --cfg zcash_unstable="nu7", so rustc preprocesses the V6 match arm out of the binary. No production deployment is currently affected.
Future attack surface (NU7 activation): high. When NU7 ships and ZF release binaries enable tx_v6 + zcash_unstable="nu7", the V6 match arm becomes part of the shipped binary, the 33-byte PoC above immediately becomes a remote DoS primitive, and one peer-to-peer broadcast crashes all V6-enabled Zebra nodes simultaneously β at the worst possible moment, right at NU7 activation, potentially destabilizing the upgrade itself.
We are submitting this advisory well in advance of NU7 mainnet activation so the fix can be designed, reviewed, merged, released, and propagated through the deployment chain before V6 transactions become live. Pre-activation disclosure means a calm fix on the project's normal release cadence; post-activation disclosure of the same finding would be an emergency security release with downtime risk for the network.
What were you doing when the issue happened?
No response
Zebra logs
No response
Zebra Version
No response
Which operating systems does the issue happen on?
OS details
No response
Additional information
No response
What happened?
Reported by @robustfengbin. Opened as issue because not currently exploitable.
Summary
A crafted V6 transaction passes
Transaction::zcash_deserialize()but panics atzebra-chain/src/transaction/hash.rs:77when computing the tx hash βTxIdBuilder::txid()returnsNone(becauseto_librustzcash()fails on V6 due tozcash_primitivesnot yet supporting the ZIP-233zip233_amountfield), and the code calls.expect(). This happens in the P2P codec duringTransactionβUnminedTxconversion, before any consensus checks. Any peer can crash any Zebra node compiled with--cfg zcash_unstable="nu7"+feature="tx_v6"by sending a single 33-bytetxmessage.The V5 arm in
serialize.rsalready callstx.to_librustzcash(network_upgrade)?;immediately after deserializing V5 transactions, which validates librustzcash compatibility at the deserialize boundary and prevents the panic for V5 inputs. The V6 arm in the samematchdoes not include this check, so V6 inputs reachhash.rs:77and panic.Current attack surface is zero β production ZF release binaries do not enable
tx_v6+zcash_unstable="nu7", so the V6 match arm is preprocessed out. Once NU7 testnet/mainnet activation requires enabling those flags in production, every Zebra node becomes vulnerable to a single 33-byte broadcast tx. We are reporting before NU7 activation so the fix can land on a calm release schedule rather than as an emergency response.Details
zebra-chain/src/transaction/hash.rs:72-79:For V6 inputs,
txid()delegates totxid_v5(), which callsto_librustzcash(nu).ok()?. Becausezcash_primitives 0.26does not recognizezip233_amount, this returnsNone, propagates up, and.expect()panics.crash path
Suggested fix β 3 lines, mirroring the V5 arm
PoC
Expected output:
thread '' panicked at zebra-chain/src/transaction/hash.rs:77:14:
zcash_primitives and Zebra transaction formats must be compatible
Impact
Remote Denial of Service, network-wide once V6 transactions are accepted.
txmessage panics the codec, before any consensus checktxβ one broadcast can hit the whole networkCurrent attack surface (today, pre-NU7): zero. ZF release binaries (
zfnd/zebra:latest) do not settx_v6Cargo feature nor--cfg zcash_unstable="nu7", sorustcpreprocesses the V6 match arm out of the binary. No production deployment is currently affected.Future attack surface (NU7 activation): high. When NU7 ships and ZF release binaries enable
tx_v6+zcash_unstable="nu7", the V6 match arm becomes part of the shipped binary, the 33-byte PoC above immediately becomes a remote DoS primitive, and one peer-to-peer broadcast crashes all V6-enabled Zebra nodes simultaneously β at the worst possible moment, right at NU7 activation, potentially destabilizing the upgrade itself.We are submitting this advisory well in advance of NU7 mainnet activation so the fix can be designed, reviewed, merged, released, and propagated through the deployment chain before V6 transactions become live. Pre-activation disclosure means a calm fix on the project's normal release cadence; post-activation disclosure of the same finding would be an emergency security release with downtime risk for the network.
What were you doing when the issue happened?
No response
Zebra logs
No response
Zebra Version
No response
Which operating systems does the issue happen on?
OS details
No response
Additional information
No response