Add transaction version 5 stubs (#1824)

* add transaction V5 stub
* add v5_strategy
* deduplicate version group ids
* Update comment for V5 transactions
* Add V5 transactions to non_finalized_state

Currently these are all `unimplemented!(...)`

* Fix struct matches
* Apply trivial panic message changes
* add zcash_deserialize for V5
* make all tx versions explicit in sprout and sapling nullifier functions
* match exhaustively in sprout and sapling nullifier functions
* fix matches in zebra-consensus
* fix NU5 strategy
* We're still deciding if v5 transactions support Sprout

Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
Alfredo Garcia 2021-03-03 18:56:41 -03:00 committed by GitHub
parent 65fa1c6bd9
commit 8883543a85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 181 additions and 24 deletions

View File

@ -15,10 +15,12 @@
mod genesis;
mod network;
mod network_upgrade;
mod transaction;
pub use genesis::*;
pub use network::Network;
pub use network_upgrade::*;
pub use transaction::*;
#[cfg(test)]
mod tests;

View File

@ -0,0 +1,13 @@
//! Transaction consensus and utility parameters.
/// The version group ID for Overwinter transactions.
pub const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
/// The version group ID for Sapling transactions.
pub const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085;
/// The version group ID for version 5 transactions.
///
/// Orchard transactions must use transaction version 5 and this version
/// group ID. Sapling transactions can use v4 or v5 transactions.
pub const TX_V5_VERSION_GROUP_ID: u32 = 0x26A7_270A;

View File

@ -42,7 +42,7 @@ use crate::{
/// internally by different enum variants. Because we checkpoint on Sapling
/// activation, we do not validate any pre-Sapling transaction types.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
// XXX consider boxing the Optional fields of V4 txs
// XXX consider boxing the Optional fields of V4 and V5 txs
#[allow(clippy::large_enum_variant)]
pub enum Transaction {
/// A fully transparent transaction (`version = 1`).
@ -99,6 +99,22 @@ pub enum Transaction {
/// The shielded data for this transaction, if any.
shielded_data: Option<ShieldedData>,
},
/// A `version = 5` transaction, which supports `Sapling` and `Orchard`.
// TODO: does this transaction type support `Sprout`?
// Check for ZIP-225 updates after the decision on 2021-03-05.
V5 {
/// The earliest time or block height that this transaction can be added to the
/// chain.
lock_time: LockTime,
/// The latest block height that this transaction can be added to the chain.
expiry_height: block::Height,
/// The transparent inputs to the transaction.
inputs: Vec<transparent::Input>,
/// The transparent outputs from the transaction.
outputs: Vec<transparent::Output>,
/// The rest of the transaction as bytes
rest: Vec<u8>,
},
}
impl Transaction {
@ -114,6 +130,7 @@ impl Transaction {
Transaction::V2 { ref inputs, .. } => inputs,
Transaction::V3 { ref inputs, .. } => inputs,
Transaction::V4 { ref inputs, .. } => inputs,
Transaction::V5 { ref inputs, .. } => inputs,
}
}
@ -124,6 +141,7 @@ impl Transaction {
Transaction::V2 { ref outputs, .. } => outputs,
Transaction::V3 { ref outputs, .. } => outputs,
Transaction::V4 { ref outputs, .. } => outputs,
Transaction::V5 { ref outputs, .. } => outputs,
}
}
@ -134,6 +152,7 @@ impl Transaction {
Transaction::V2 { lock_time, .. } => *lock_time,
Transaction::V3 { lock_time, .. } => *lock_time,
Transaction::V4 { lock_time, .. } => *lock_time,
Transaction::V5 { lock_time, .. } => *lock_time,
}
}
@ -144,6 +163,7 @@ impl Transaction {
Transaction::V2 { .. } => None,
Transaction::V3 { expiry_height, .. } => Some(*expiry_height),
Transaction::V4 { expiry_height, .. } => Some(*expiry_height),
Transaction::V5 { expiry_height, .. } => Some(*expiry_height),
}
}
@ -174,8 +194,26 @@ impl Transaction {
.joinsplits()
.flat_map(|joinsplit| joinsplit.nullifiers.iter()),
),
// Maybe JoinSplits, maybe not, we're still deciding
Transaction::V5 { .. } => {
unimplemented!(
"v5 transaction format as specified in ZIP-225 after decision on 2021-03-12"
)
}
// No JoinSplits
_ => Box::new(std::iter::empty()),
Transaction::V1 { .. }
| Transaction::V2 {
joinsplit_data: None,
..
}
| Transaction::V3 {
joinsplit_data: None,
..
}
| Transaction::V4 {
joinsplit_data: None,
..
} => Box::new(std::iter::empty()),
}
}
@ -189,8 +227,17 @@ impl Transaction {
shielded_data: Some(shielded_data),
..
} => Box::new(shielded_data.nullifiers()),
Transaction::V5 { .. } => {
unimplemented!("v5 transaction format as specified in ZIP-225")
}
// No JoinSplits
_ => Box::new(std::iter::empty()),
Transaction::V1 { .. }
| Transaction::V2 { .. }
| Transaction::V3 { .. }
| Transaction::V4 {
shielded_data: None,
..
} => Box::new(std::iter::empty()),
}
}

View File

@ -104,6 +104,26 @@ impl Transaction {
)
.boxed()
}
/// Generate a proptest strategy for V5 Transactions
pub fn v5_strategy(ledger_state: LedgerState) -> BoxedStrategy<Self> {
(
any::<LockTime>(),
any::<block::Height>(),
transparent::Input::vec_strategy(ledger_state, 10),
vec(any::<transparent::Output>(), 0..10),
any::<Vec<u8>>(),
)
.prop_map(
|(lock_time, expiry_height, inputs, outputs, rest)| Transaction::V5 {
lock_time,
expiry_height,
inputs,
outputs,
rest,
},
)
.boxed()
}
/// Proptest Strategy for creating a Vector of transactions where the first
/// transaction is always the only coinbase transaction
@ -236,9 +256,11 @@ impl Arbitrary for Transaction {
NetworkUpgrade::Blossom | NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
Self::v4_strategy(ledger_state)
}
NetworkUpgrade::NU5 => {
unimplemented!("NU5 upgrade can use v4 or v5 transactions, as specified in ZIP-225")
}
NetworkUpgrade::NU5 => prop_oneof![
Self::v4_strategy(ledger_state),
Self::v5_strategy(ledger_state)
]
.boxed(),
}
}

View File

@ -6,6 +6,7 @@ use std::{io, sync::Arc};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crate::{
parameters::{OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID, TX_V5_VERSION_GROUP_ID},
primitives::ZkSnarkProof,
serialization::{
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashDeserializeInto,
@ -16,9 +17,6 @@ use crate::{
use super::*;
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085;
impl ZcashDeserialize for jubjub::Fq {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let possible_scalar = jubjub::Fq::from_bytes(&reader.read_32_bytes()?);
@ -182,6 +180,23 @@ impl ZcashSerialize for Transaction {
None => {}
}
}
Transaction::V5 {
lock_time,
expiry_height,
inputs,
outputs,
rest,
} => {
// Write version 5 and set the fOverwintered bit.
writer.write_u32::<LittleEndian>(5 | (1 << 31))?;
writer.write_u32::<LittleEndian>(TX_V5_VERSION_GROUP_ID)?;
lock_time.zcash_serialize(&mut writer)?;
writer.write_u32::<LittleEndian>(expiry_height.0)?;
inputs.zcash_serialize(&mut writer)?;
outputs.zcash_serialize(&mut writer)?;
// write the rest
writer.write_all(rest)?;
}
}
Ok(())
}
@ -285,6 +300,26 @@ impl ZcashDeserialize for Transaction {
joinsplit_data,
})
}
(5, false) => {
let id = reader.read_u32::<LittleEndian>()?;
if id != TX_V5_VERSION_GROUP_ID {
return Err(SerializationError::Parse("expected TX_V5_VERSION_GROUP_ID"));
}
let lock_time = LockTime::zcash_deserialize(&mut reader)?;
let expiry_height = block::Height(reader.read_u32::<LittleEndian>()?);
let inputs = Vec::zcash_deserialize(&mut reader)?;
let outputs = Vec::zcash_deserialize(&mut reader)?;
let mut rest = Vec::new();
reader.read_to_end(&mut rest)?;
Ok(Transaction::V5 {
lock_time,
expiry_height,
inputs,
outputs,
rest,
})
}
(_, _) => Err(SerializationError::Parse("bad tx header")),
}
}

View File

@ -1,6 +1,9 @@
use super::Transaction;
use crate::{
parameters::{ConsensusBranchId, NetworkUpgrade},
parameters::{
ConsensusBranchId, NetworkUpgrade, OVERWINTER_VERSION_GROUP_ID, SAPLING_VERSION_GROUP_ID,
TX_V5_VERSION_GROUP_ID,
},
serialization::{WriteZcashExt, ZcashSerialize},
transparent,
};
@ -12,9 +15,6 @@ use std::io;
static ZIP143_EXPLANATION: &str = "Invalid transaction version: after Overwinter activation transaction versions 1 and 2 are rejected";
static ZIP243_EXPLANATION: &str = "Invalid transaction version: after Sapling activation transaction versions 1, 2, and 3 are rejected";
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085;
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &[u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash";
@ -129,6 +129,7 @@ impl<'a> SigHasher<'a> {
Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION),
Transaction::V3 { .. } => 3 | overwintered_flag,
Transaction::V4 { .. } => 4 | overwintered_flag,
Transaction::V5 { .. } => 5 | overwintered_flag,
})
}
@ -137,6 +138,7 @@ impl<'a> SigHasher<'a> {
Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION),
Transaction::V3 { .. } => OVERWINTER_VERSION_GROUP_ID,
Transaction::V4 { .. } => SAPLING_VERSION_GROUP_ID,
Transaction::V5 { .. } => TX_V5_VERSION_GROUP_ID,
})
}
@ -243,6 +245,9 @@ impl<'a> SigHasher<'a> {
Transaction::V1 { .. } | Transaction::V2 { .. } => unreachable!(ZIP143_EXPLANATION),
Transaction::V3 { joinsplit_data, .. } => joinsplit_data.is_some(),
Transaction::V4 { joinsplit_data, .. } => joinsplit_data.is_some(),
Transaction::V5 { .. } => {
unimplemented!("v5 transaction hash as specified in ZIP-225 and ZIP-244")
}
};
if !has_joinsplits {
@ -256,7 +261,7 @@ impl<'a> SigHasher<'a> {
// This code, and the check above for has_joinsplits cannot be combined
// into a single branch because the `joinsplit_data` type of each
// tranaction kind has a different type.
// transaction kind has a different type.
//
// For v3 joinsplit_data is a JoinSplitData<Bctv14Proof>
// For v4 joinsplit_data is a JoinSplitData<Groth16Proof>
@ -264,6 +269,8 @@ impl<'a> SigHasher<'a> {
// The type parameter on these types prevents them from being unified,
// which forces us to duplicate the logic in each branch even though the
// code within each branch is identical.
//
// TODO: use a generic function to remove the duplicate code
match self.trans {
Transaction::V3 {
joinsplit_data: Some(jsd),
@ -283,7 +290,20 @@ impl<'a> SigHasher<'a> {
}
(&mut hash).write_all(&<[u8; 32]>::from(jsd.pub_key)[..])?;
}
_ => unreachable!("already checked transaction kind above"),
Transaction::V5 { .. } => {
unimplemented!("v5 transaction hash as specified in ZIP-225 and ZIP-244")
}
Transaction::V1 { .. }
| Transaction::V2 { .. }
| Transaction::V3 {
joinsplit_data: None,
..
}
| Transaction::V4 {
joinsplit_data: None,
..
} => unreachable!("already checked transaction kind above"),
};
writer.write_all(hash.finalize().as_ref())
@ -398,14 +418,15 @@ impl<'a> SigHasher<'a> {
use Transaction::*;
let shielded_data = match self.trans {
Transaction::V4 {
V4 {
shielded_data: Some(shielded_data),
..
} => shielded_data,
Transaction::V4 {
V4 {
shielded_data: None,
..
} => return writer.write_all(&[0; 32]),
V5 { .. } => unimplemented!("v5 transaction hash as specified in ZIP-225 and ZIP-244"),
V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION),
};
@ -434,14 +455,15 @@ impl<'a> SigHasher<'a> {
use Transaction::*;
let shielded_data = match self.trans {
Transaction::V4 {
V4 {
shielded_data: Some(shielded_data),
..
} => shielded_data,
Transaction::V4 {
V4 {
shielded_data: None,
..
} => return writer.write_all(&[0; 32]),
V5 { .. } => unimplemented!("v5 transaction hash as specified in ZIP-225 and ZIP-244"),
V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION),
};
@ -465,7 +487,8 @@ impl<'a> SigHasher<'a> {
use Transaction::*;
let value_balance = match self.trans {
Transaction::V4 { value_balance, .. } => value_balance,
V4 { value_balance, .. } => value_balance,
V5 { .. } => unimplemented!("v5 transaction hash as specified in ZIP-225 and ZIP-244"),
V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(ZIP243_EXPLANATION),
};

View File

@ -258,6 +258,9 @@ where
Ok(tx.hash())
}
Transaction::V5 { .. } => {
unimplemented!("v5 transaction validation as specified in ZIP-216, ZIP-224, ZIP-225, and ZIP-244")
}
}
}
.instrument(span)

View File

@ -71,6 +71,9 @@ pub fn has_inputs_and_outputs(tx: &Transaction) -> Result<(), TransactionError>
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
unreachable!("tx version is checked first")
}
Transaction::V5 { .. } => {
unimplemented!("v5 transaction format as specified in ZIP-225")
}
}
}
@ -114,6 +117,10 @@ pub fn coinbase_tx_no_joinsplit_or_spend(tx: &Transaction) -> Result<(), Transac
Transaction::V1 { .. } | Transaction::V2 { .. } | Transaction::V3 { .. } => {
unreachable!("tx version is checked first")
}
Transaction::V5 { .. } => {
unimplemented!("v5 coinbase validation as specified in ZIP-225 and the draft spec")
}
}
} else {
Ok(())

View File

@ -165,14 +165,16 @@ impl UpdateWith<PreparedBlock> for Chain {
.zip(transaction_hashes.iter().cloned())
.enumerate()
{
use transaction::Transaction::*;
let (inputs, shielded_data, joinsplit_data) = match transaction.deref() {
transaction::Transaction::V4 {
V4 {
inputs,
shielded_data,
joinsplit_data,
..
} => (inputs, shielded_data, joinsplit_data),
_ => unreachable!(
V5 { .. } => unimplemented!("v5 transaction format as specified in ZIP-225"),
V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(
"older transaction versions only exist in finalized blocks pre sapling",
),
};
@ -223,14 +225,16 @@ impl UpdateWith<PreparedBlock> for Chain {
for (transaction, transaction_hash) in
block.transactions.iter().zip(transaction_hashes.iter())
{
use transaction::Transaction::*;
let (inputs, shielded_data, joinsplit_data) = match transaction.deref() {
transaction::Transaction::V4 {
V4 {
inputs,
shielded_data,
joinsplit_data,
..
} => (inputs, shielded_data, joinsplit_data),
_ => unreachable!(
V5 { .. } => unimplemented!("v5 transaction format as specified in ZIP-225"),
V1 { .. } | V2 { .. } | V3 { .. } => unreachable!(
"older transaction versions only exist in finalized blocks pre sapling",
),
};

View File

@ -53,6 +53,7 @@ impl FakeChainHelper for Arc<Block> {
Transaction::V2 { inputs, .. } => &mut inputs[0],
Transaction::V3 { inputs, .. } => &mut inputs[0],
Transaction::V4 { inputs, .. } => &mut inputs[0],
Transaction::V5 { inputs, .. } => &mut inputs[0],
};
match input {