Files
pirate-librustzcash/zcash_primitives/src/transaction/sighash.rs
Jack Grigg 999dcbfcab Migrate to blake2b_simd and blake2s_simd crates
The primary reason for migrating is that these crates provide APIs for
setting the personalisation string. This enables us to depend solely on
published crates, and thus publish our own crates.

The SIMD implementations are ported from libsodium.

Closes #67.
2019-07-10 13:12:14 -04:00

245 lines
8.1 KiB
Rust

use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::{PrimeField, PrimeFieldRepr};
use super::{
components::{Amount, Script, TxOut},
Transaction, TransactionData, OVERWINTER_VERSION_GROUP_ID, SAPLING_TX_VERSION,
SAPLING_VERSION_GROUP_ID,
};
const ZCASH_SIGHASH_PERSONALIZATION_PREFIX: &'static [u8; 12] = b"ZcashSigHash";
const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &'static [u8; 16] = b"ZcashSOutputHash";
pub const SIGHASH_ALL: u32 = 1;
const SIGHASH_NONE: u32 = 2;
const SIGHASH_SINGLE: u32 = 3;
const SIGHASH_MASK: u32 = 0x1f;
const SIGHASH_ANYONECANPAY: u32 = 0x80;
macro_rules! update_u32 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
$h.update(&$tmp[..4]);
};
}
macro_rules! update_i64 {
($h:expr, $value:expr, $tmp:expr) => {
(&mut $tmp[..8]).write_i64::<LittleEndian>($value).unwrap();
$h.update(&$tmp[..8]);
};
}
macro_rules! update_hash {
($h:expr, $cond:expr, $value:expr) => {
if $cond {
$h.update(&$value.as_ref());
} else {
$h.update(&[0; 32]);
}
};
}
#[derive(PartialEq)]
enum SigHashVersion {
Sprout,
Overwinter,
Sapling,
}
impl SigHashVersion {
fn from_tx(tx: &TransactionData) -> Self {
if tx.overwintered {
match tx.version_group_id {
OVERWINTER_VERSION_GROUP_ID => SigHashVersion::Overwinter,
SAPLING_VERSION_GROUP_ID => SigHashVersion::Sapling,
_ => unimplemented!(),
}
} else {
SigHashVersion::Sprout
}
}
}
fn prevout_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = Vec::with_capacity(tx.vin.len() * 36);
for t_in in &tx.vin {
t_in.prevout.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_PREVOUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn sequence_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = Vec::with_capacity(tx.vin.len() * 4);
for t_in in &tx.vin {
(&mut data)
.write_u32::<LittleEndian>(t_in.sequence)
.unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SEQUENCE_HASH_PERSONALIZATION)
.hash(&data)
}
fn outputs_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = Vec::with_capacity(tx.vout.len() * (4 + 1));
for t_out in &tx.vout {
t_out.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn single_output_hash(tx_out: &TxOut) -> Blake2bHash {
let mut data = vec![];
tx_out.write(&mut data).unwrap();
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
fn joinsplits_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = Vec::with_capacity(
tx.joinsplits.len()
* if tx.version < SAPLING_TX_VERSION {
1802 // JSDescription with PHGR13 proof
} else {
1698 // JSDescription with Groth16 proof
},
);
for js in &tx.joinsplits {
js.write(&mut data).unwrap();
}
data.extend_from_slice(&tx.joinsplit_pubkey.unwrap());
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_JOINSPLITS_HASH_PERSONALIZATION)
.hash(&data)
}
fn shielded_spends_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = Vec::with_capacity(tx.shielded_spends.len() * 384);
for s_spend in &tx.shielded_spends {
s_spend.cv.write(&mut data).unwrap();
s_spend.anchor.into_repr().write_le(&mut data).unwrap();
data.extend_from_slice(&s_spend.nullifier);
s_spend.rk.write(&mut data).unwrap();
data.extend_from_slice(&s_spend.zkproof);
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION)
.hash(&data)
}
fn shielded_outputs_hash(tx: &TransactionData) -> Blake2bHash {
let mut data = Vec::with_capacity(tx.shielded_outputs.len() * 948);
for s_out in &tx.shielded_outputs {
s_out.write(&mut data).unwrap();
}
Blake2bParams::new()
.hash_length(32)
.personal(ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION)
.hash(&data)
}
pub fn signature_hash_data(
tx: &TransactionData,
consensus_branch_id: u32,
hash_type: u32,
transparent_input: Option<(usize, Script, Amount)>,
) -> Vec<u8> {
let sigversion = SigHashVersion::from_tx(tx);
match sigversion {
SigHashVersion::Overwinter | SigHashVersion::Sapling => {
let mut personal = [0; 16];
(&mut personal[..12]).copy_from_slice(ZCASH_SIGHASH_PERSONALIZATION_PREFIX);
(&mut personal[12..])
.write_u32::<LittleEndian>(consensus_branch_id)
.unwrap();
let mut h = Blake2bParams::new()
.hash_length(32)
.personal(&personal)
.to_state();
let mut tmp = [0; 8];
update_u32!(h, tx.header(), tmp);
update_u32!(h, tx.version_group_id, tmp);
update_hash!(h, hash_type & SIGHASH_ANYONECANPAY == 0, prevout_hash(tx));
update_hash!(
h,
hash_type & SIGHASH_ANYONECANPAY == 0
&& (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
sequence_hash(tx)
);
if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
{
h.update(outputs_hash(tx).as_ref());
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE
&& transparent_input.is_some()
&& transparent_input.as_ref().unwrap().0 < tx.vout.len()
{
h.update(
single_output_hash(&tx.vout[transparent_input.as_ref().unwrap().0]).as_ref(),
);
} else {
h.update(&[0; 32]);
};
update_hash!(h, !tx.joinsplits.is_empty(), joinsplits_hash(tx));
if sigversion == SigHashVersion::Sapling {
update_hash!(h, !tx.shielded_spends.is_empty(), shielded_spends_hash(tx));
update_hash!(
h,
!tx.shielded_outputs.is_empty(),
shielded_outputs_hash(tx)
);
}
update_u32!(h, tx.lock_time, tmp);
update_u32!(h, tx.expiry_height, tmp);
if sigversion == SigHashVersion::Sapling {
update_i64!(h, tx.value_balance.0, tmp);
}
update_u32!(h, hash_type, tmp);
if let Some((n, script_code, amount)) = transparent_input {
let mut data = vec![];
tx.vin[n].prevout.write(&mut data).unwrap();
script_code.write(&mut data).unwrap();
(&mut data).write_i64::<LittleEndian>(amount.0).unwrap();
(&mut data)
.write_u32::<LittleEndian>(tx.vin[n].sequence)
.unwrap();
h.update(&data);
}
h.finalize().as_ref().to_vec()
}
SigHashVersion::Sprout => unimplemented!(),
}
}
pub fn signature_hash(
tx: &Transaction,
consensus_branch_id: u32,
hash_type: u32,
transparent_input: Option<(usize, Script, Amount)>,
) -> Vec<u8> {
signature_hash_data(tx, consensus_branch_id, hash_type, transparent_input)
}