zcash-hsmbuilder 0.3.0

Library to build transactions for HSM apps
Documentation
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::*;
use ff::PrimeField;
use group::GroupEncoding;
use zcash_primitives::transaction::TransactionData;

const ZCASH_PREVOUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashPrevoutHash";
const ZCASH_SEQUENCE_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSequencHash";
const ZCASH_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashOutputsHash";
const ZCASH_JOINSPLITS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashJSplitsHash";
const ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSSpendsHash";
const ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION: &[u8; 16] = b"ZcashSOutputHash";

const SIGHASH_NONE: u32 = 2;
const SIGHASH_SINGLE: u32 = 3;
const SIGHASH_MASK: u32 = 0x1f;
const SIGHASH_ANYONECANPAY: u32 = 0x80;

const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C4_8270;
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F_2085;
const SAPLING_TX_VERSION: u32 = 4;

#[derive(Default, Clone)]
pub struct TransactionDataSighash {
    pub header: [u8; 4],
    pub version_id: [u8; 4],
    pub prevoutshash: [u8; 32],
    pub sequencehash: [u8; 32],
    pub outputshash: [u8; 32],
    pub joinsplitshash: [u8; 32],
    pub shieldedspendhash: [u8; 32],
    pub shieldedoutputhash: [u8; 32],
    pub lock_time: [u8; 4],
    pub expiry_height: [u8; 4],
    pub value_balance: [u8; 8],
    pub hash_type: [u8; 4],
}

impl TransactionDataSighash {
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut data = Vec::with_capacity(220);
        data.extend_from_slice(&self.header);
        data.extend_from_slice(&self.version_id);
        data.extend_from_slice(&self.prevoutshash);
        data.extend_from_slice(&self.sequencehash);
        data.extend_from_slice(&self.outputshash);
        data.extend_from_slice(&self.joinsplitshash);
        data.extend_from_slice(&self.shieldedspendhash);
        data.extend_from_slice(&self.shieldedoutputhash);
        data.extend_from_slice(&self.lock_time);
        data.extend_from_slice(&self.expiry_height);
        data.extend_from_slice(&self.value_balance);
        data.extend_from_slice(&self.hash_type);
        data
    }
}

macro_rules! write_u32 {
    ($h:expr, $value:expr, $tmp:expr) => {
        //LittleEndian::write_u32(&mut $tmp[..4],$value);
        (&mut $tmp[..4]).write_u32::<LittleEndian>($value).unwrap();
        $h.copy_from_slice(&$tmp[..4]);
    };
}

macro_rules! update_data {
    ($h:expr, $cond:expr, $value:expr) => {
        if $cond {
            $h.copy_from_slice(&$value.as_ref());
        } else {
            $h.copy_from_slice(&[0; 32]);
        }
    };
}

#[derive(PartialEq)]
enum SigHashVersion {
    Sprout,
    Overwinter,
    Sapling,
}

impl SigHashVersion {
    fn from_tx(tx: &TransactionData) -> Self {
        use zcash_primitives::transaction::TxVersion;

        match tx.version {
            TxVersion::Sprout(_) => SigHashVersion::Sprout,
            TxVersion::Overwinter => SigHashVersion::Overwinter,
            TxVersion::Sapling => SigHashVersion::Sapling,
        }
    }
}

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() * 34);
    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 joinsplits_hash(tx: &TransactionData) -> Blake2bHash {
    let mut data = Vec::with_capacity(
        tx.joinsplits.len()
            * if tx.version.uses_groth_proofs() {
                1698 // JSDescription with Groth16 proof
            } else {
                1802 // JSDescription with PHGR13 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 {
        data.extend_from_slice(&s_spend.cv.to_bytes());
        data.extend_from_slice(s_spend.anchor.to_repr().as_ref());
        data.extend_from_slice(&s_spend.nullifier.0[..]);
        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_input_data(tx: &TransactionData, hash_type: u32) -> TransactionDataSighash {
    let mut txdata_sighash = TransactionDataSighash::default();
    let mut tmp = [0; 8];
    let sigversion = SigHashVersion::from_tx(tx);
    match sigversion {
        SigHashVersion::Overwinter | SigHashVersion::Sapling => {
            let header = tx.version.header();
            let version_group_id = tx.version.version_group_id();

            write_u32!(txdata_sighash.header, header, tmp);
            write_u32!(txdata_sighash.version_id, version_group_id, tmp);
            update_data!(
                txdata_sighash.prevoutshash,
                hash_type & SIGHASH_ANYONECANPAY == 0,
                prevout_hash(tx)
            ); //true for sighash_all

            update_data!(
                txdata_sighash.sequencehash,
                hash_type & SIGHASH_ANYONECANPAY == 0
                    && (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
                    && (hash_type & SIGHASH_MASK) != SIGHASH_NONE,
                sequence_hash(tx)
            ); //true for sighash_all

            if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
                && (hash_type & SIGHASH_MASK) != SIGHASH_NONE
            {
                txdata_sighash
                    .outputshash
                    .copy_from_slice(outputs_hash(tx).as_ref()); //true for sighash all
            } else {
                txdata_sighash.outputshash.copy_from_slice(&[0; 32]);
            };
            update_data!(
                txdata_sighash.joinsplitshash,
                !tx.joinsplits.is_empty(),
                joinsplits_hash(tx)
            );
            if sigversion == SigHashVersion::Sapling {
                update_data!(
                    txdata_sighash.shieldedspendhash,
                    !tx.shielded_spends.is_empty(),
                    shielded_spends_hash(tx)
                );
                update_data!(
                    txdata_sighash.shieldedoutputhash,
                    !tx.shielded_outputs.is_empty(),
                    shielded_outputs_hash(tx)
                );
            }
            write_u32!(txdata_sighash.lock_time, tx.lock_time, tmp);
            let expiry_height = tx.expiry_height.into();
            write_u32!(txdata_sighash.expiry_height, expiry_height, tmp);
            if sigversion == SigHashVersion::Sapling {
                txdata_sighash
                    .value_balance
                    .copy_from_slice(&tx.value_balance.to_i64_le_bytes());
            }
            write_u32!(txdata_sighash.hash_type, hash_type, tmp);
        }
        SigHashVersion::Sprout => unimplemented!(),
    }
    txdata_sighash
}