Merge pull request #39 from str4d/zcash-transaction-primitives
Transaction primitives
This commit is contained in:
commit
8be50c9531
|
@ -704,6 +704,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)",
|
||||
"byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pairing 0.14.2",
|
||||
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sapling-crypto 0.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
|
|
|
@ -6,3 +6,12 @@ authors = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1"
|
||||
lazy_static = "1"
|
||||
pairing = { path = "../pairing" }
|
||||
rand = "0.4"
|
||||
sapling-crypto = { path = "../sapling-crypto" }
|
||||
|
||||
[dependencies.blake2-rfc]
|
||||
git = "https://github.com/gtank/blake2-rfc"
|
||||
rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9"
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate blake2_rfc;
|
||||
extern crate byteorder;
|
||||
extern crate pairing;
|
||||
extern crate rand;
|
||||
extern crate sapling_crypto;
|
||||
|
||||
use sapling_crypto::jubjub::JubjubBls12;
|
||||
|
||||
mod serialize;
|
||||
pub mod transaction;
|
||||
|
||||
lazy_static! {
|
||||
static ref JUBJUB: JubjubBls12 = { JubjubBls12::new() };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
const MAX_SIZE: usize = 0x02000000;
|
||||
|
||||
struct CompactSize;
|
||||
|
||||
impl CompactSize {
|
||||
fn read<R: Read>(mut reader: R) -> io::Result<usize> {
|
||||
let flag = reader.read_u8()?;
|
||||
match if flag < 253 {
|
||||
Ok(flag as usize)
|
||||
} else if flag == 253 {
|
||||
match reader.read_u16::<LittleEndian>()? {
|
||||
n if n < 253 => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"non-canonical CompactSize",
|
||||
)),
|
||||
n => Ok(n as usize),
|
||||
}
|
||||
} else if flag == 254 {
|
||||
match reader.read_u32::<LittleEndian>()? {
|
||||
n if n < 0x10000 => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"non-canonical CompactSize",
|
||||
)),
|
||||
n => Ok(n as usize),
|
||||
}
|
||||
} else {
|
||||
match reader.read_u64::<LittleEndian>()? {
|
||||
n if n < 0x100000000 => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"non-canonical CompactSize",
|
||||
)),
|
||||
n => Ok(n as usize),
|
||||
}
|
||||
}? {
|
||||
s if s > MAX_SIZE => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"CompactSize too large",
|
||||
)),
|
||||
s => Ok(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn write<W: Write>(mut writer: W, size: usize) -> io::Result<()> {
|
||||
match size {
|
||||
s if s < 253 => writer.write_u8(s as u8),
|
||||
s if s <= 0xFFFF => {
|
||||
writer.write_u8(253)?;
|
||||
writer.write_u16::<LittleEndian>(s as u16)
|
||||
}
|
||||
s if s <= 0xFFFFFFFF => {
|
||||
writer.write_u8(254)?;
|
||||
writer.write_u32::<LittleEndian>(s as u32)
|
||||
}
|
||||
s => {
|
||||
writer.write_u8(255)?;
|
||||
writer.write_u64::<LittleEndian>(s as u64)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Vector;
|
||||
|
||||
impl Vector {
|
||||
pub fn read<R: Read, E, F>(mut reader: R, func: F) -> io::Result<Vec<E>>
|
||||
where
|
||||
F: Fn(&mut R) -> io::Result<E>,
|
||||
{
|
||||
let count = CompactSize::read(&mut reader)?;
|
||||
(0..count).into_iter().map(|_| func(&mut reader)).collect()
|
||||
}
|
||||
|
||||
pub fn write<W: Write, E, F>(mut writer: W, vec: &[E], func: F) -> io::Result<()>
|
||||
where
|
||||
F: Fn(&mut W, &E) -> io::Result<()>,
|
||||
{
|
||||
CompactSize::write(&mut writer, vec.len())?;
|
||||
vec.iter().map(|e| func(&mut writer, e)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn compact_size() {
|
||||
macro_rules! eval {
|
||||
($value:expr, $expected:expr) => {
|
||||
let mut data = vec![];
|
||||
CompactSize::write(&mut data, $value).unwrap();
|
||||
assert_eq!(&data[..], &$expected[..]);
|
||||
match CompactSize::read(&data[..]) {
|
||||
Ok(n) => assert_eq!(n, $value),
|
||||
Err(e) => panic!("Unexpected error: {:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
eval!(0, [0]);
|
||||
eval!(1, [1]);
|
||||
eval!(252, [252]);
|
||||
eval!(253, [253, 253, 0]);
|
||||
eval!(254, [253, 254, 0]);
|
||||
eval!(255, [253, 255, 0]);
|
||||
eval!(256, [253, 0, 1]);
|
||||
eval!(256, [253, 0, 1]);
|
||||
eval!(65535, [253, 255, 255]);
|
||||
eval!(65536, [254, 0, 0, 1, 0]);
|
||||
eval!(65537, [254, 1, 0, 1, 0]);
|
||||
|
||||
eval!(33554432, [254, 0, 0, 0, 2]);
|
||||
|
||||
{
|
||||
let value = 33554433;
|
||||
let encoded = &[254, 1, 0, 0, 2][..];
|
||||
let mut data = vec![];
|
||||
CompactSize::write(&mut data, value).unwrap();
|
||||
assert_eq!(&data[..], encoded);
|
||||
assert!(CompactSize::read(encoded).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vector() {
|
||||
macro_rules! eval {
|
||||
($value:expr, $expected:expr) => {
|
||||
let mut data = vec![];
|
||||
Vector::write(&mut data, &$value, |w, e| w.write_u8(*e)).unwrap();
|
||||
assert_eq!(&data[..], &$expected[..]);
|
||||
match Vector::read(&data[..], |r| r.read_u8()) {
|
||||
Ok(v) => assert_eq!(v, $value),
|
||||
Err(e) => panic!("Unexpected error: {:?}", e),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
eval!(vec![], [0]);
|
||||
eval!(vec![0], [1, 0]);
|
||||
eval!(vec![1], [1, 1]);
|
||||
eval!(vec![5; 8], [8, 5, 5, 5, 5, 5, 5, 5, 5]);
|
||||
|
||||
{
|
||||
// expected = [253, 4, 1, 7, 7, 7, ...]
|
||||
let mut expected = vec![7; 263];
|
||||
expected[0] = 253;
|
||||
expected[1] = 4;
|
||||
expected[2] = 1;
|
||||
|
||||
eval!(vec![7; 260], expected);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,432 @@
|
|||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use pairing::{
|
||||
bls12_381::{Bls12, Fr, FrRepr},
|
||||
PrimeField, PrimeFieldRepr,
|
||||
};
|
||||
use sapling_crypto::{
|
||||
jubjub::{edwards, Unknown},
|
||||
redjubjub::{PublicKey, Signature},
|
||||
};
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use serialize::Vector;
|
||||
use JUBJUB;
|
||||
|
||||
// π_A + π_B + π_C
|
||||
const GROTH_PROOF_SIZE: usize = (48 + 96 + 48);
|
||||
// π_A + π_A' + π_B + π_B' + π_C + π_C' + π_K + π_H
|
||||
const PHGR_PROOF_SIZE: usize = (33 + 33 + 65 + 33 + 33 + 33 + 33 + 33);
|
||||
|
||||
const ZC_NUM_JS_INPUTS: usize = 2;
|
||||
const ZC_NUM_JS_OUTPUTS: usize = 2;
|
||||
|
||||
const COIN: i64 = 1_0000_0000;
|
||||
const MAX_MONEY: i64 = 21_000_000 * COIN;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Amount(pub i64);
|
||||
|
||||
impl Amount {
|
||||
// Read an Amount from a signed 64-bit little-endian integer.
|
||||
pub fn read_i64<R: Read>(mut reader: R, allow_negative: bool) -> io::Result<Self> {
|
||||
let amount = reader.read_i64::<LittleEndian>()?;
|
||||
if 0 <= amount && amount <= MAX_MONEY {
|
||||
Ok(Amount(amount))
|
||||
} else if allow_negative && -MAX_MONEY <= amount && amount < 0 {
|
||||
Ok(Amount(amount))
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
if allow_negative {
|
||||
"Amount not in {-MAX_MONEY..MAX_MONEY}"
|
||||
} else {
|
||||
"Amount not in {0..MAX_MONEY}"
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Read an Amount from an unsigned 64-bit little-endian integer.
|
||||
pub fn read_u64<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let amount = reader.read_u64::<LittleEndian>()?;
|
||||
if amount <= MAX_MONEY as u64 {
|
||||
Ok(Amount(amount as i64))
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Amount not in {0..MAX_MONEY}",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Script(pub Vec<u8>);
|
||||
|
||||
impl Script {
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let script = Vector::read(&mut reader, |r| r.read_u8())?;
|
||||
Ok(Script(script))
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
Vector::write(&mut writer, &self.0, |w, e| w.write_u8(*e))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutPoint {
|
||||
hash: [u8; 32],
|
||||
n: u32,
|
||||
}
|
||||
|
||||
impl OutPoint {
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let mut hash = [0; 32];
|
||||
reader.read_exact(&mut hash)?;
|
||||
let n = reader.read_u32::<LittleEndian>()?;
|
||||
Ok(OutPoint { hash, n })
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_all(&self.hash)?;
|
||||
writer.write_u32::<LittleEndian>(self.n)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxIn {
|
||||
pub prevout: OutPoint,
|
||||
script_sig: Script,
|
||||
pub sequence: u32,
|
||||
}
|
||||
|
||||
impl TxIn {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let prevout = OutPoint::read(&mut reader)?;
|
||||
let script_sig = Script::read(&mut reader)?;
|
||||
let sequence = reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
Ok(TxIn {
|
||||
prevout,
|
||||
script_sig,
|
||||
sequence,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.prevout.write(&mut writer)?;
|
||||
self.script_sig.write(&mut writer)?;
|
||||
writer.write_u32::<LittleEndian>(self.sequence)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TxOut {
|
||||
value: Amount,
|
||||
script_pubkey: Script,
|
||||
}
|
||||
|
||||
impl TxOut {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
let value = Amount::read_i64(&mut reader, false)?;
|
||||
let script_pubkey = Script::read(&mut reader)?;
|
||||
|
||||
Ok(TxOut {
|
||||
value,
|
||||
script_pubkey,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_i64::<LittleEndian>(self.value.0)?;
|
||||
self.script_pubkey.write(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpendDescription {
|
||||
pub cv: edwards::Point<Bls12, Unknown>,
|
||||
pub anchor: Fr,
|
||||
pub nullifier: [u8; 32],
|
||||
pub rk: PublicKey<Bls12>,
|
||||
pub zkproof: [u8; GROTH_PROOF_SIZE],
|
||||
pub spend_auth_sig: Signature,
|
||||
}
|
||||
|
||||
impl SpendDescription {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
|
||||
// (located in zcash_proofs::sapling::verifier).
|
||||
let cv = edwards::Point::<Bls12, Unknown>::read(&mut reader, &JUBJUB)?;
|
||||
|
||||
// Consensus rule (§7.3): Canonical encoding is enforced here
|
||||
let anchor = {
|
||||
let mut f = FrRepr::default();
|
||||
f.read_le(&mut reader)?;
|
||||
Fr::from_repr(f).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
|
||||
};
|
||||
|
||||
let mut nullifier = [0; 32];
|
||||
reader.read_exact(&mut nullifier)?;
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_spend()
|
||||
let rk = PublicKey::<Bls12>::read(&mut reader, &JUBJUB)?;
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_spend()
|
||||
// due to the need to parse this into a bellman::groth16::Proof.
|
||||
// - Proof validity is enforced in SaplingVerificationContext::check_spend()
|
||||
let mut zkproof = [0; GROTH_PROOF_SIZE];
|
||||
reader.read_exact(&mut zkproof)?;
|
||||
|
||||
// Consensus rules (§4.4):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - Signature validity is enforced in SaplingVerificationContext::check_spend()
|
||||
let spend_auth_sig = Signature::read(&mut reader)?;
|
||||
|
||||
Ok(SpendDescription {
|
||||
cv,
|
||||
anchor,
|
||||
nullifier,
|
||||
rk,
|
||||
zkproof,
|
||||
spend_auth_sig,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.cv.write(&mut writer)?;
|
||||
self.anchor.into_repr().write_le(&mut writer)?;
|
||||
writer.write_all(&self.nullifier)?;
|
||||
self.rk.write(&mut writer)?;
|
||||
writer.write_all(&self.zkproof)?;
|
||||
self.spend_auth_sig.write(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputDescription {
|
||||
pub cv: edwards::Point<Bls12, Unknown>,
|
||||
pub cmu: Fr,
|
||||
pub ephemeral_key: edwards::Point<Bls12, Unknown>,
|
||||
pub enc_ciphertext: [u8; 580],
|
||||
pub out_ciphertext: [u8; 80],
|
||||
pub zkproof: [u8; GROTH_PROOF_SIZE],
|
||||
}
|
||||
|
||||
impl OutputDescription {
|
||||
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
|
||||
// Consensus rules (§4.5):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
|
||||
// (located in zcash_proofs::sapling::verifier).
|
||||
let cv = edwards::Point::<Bls12, Unknown>::read(&mut reader, &JUBJUB)?;
|
||||
|
||||
// Consensus rule (§7.4): Canonical encoding is enforced here
|
||||
let cmu = {
|
||||
let mut f = FrRepr::default();
|
||||
f.read_le(&mut reader)?;
|
||||
Fr::from_repr(f).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?
|
||||
};
|
||||
|
||||
// Consensus rules (§4.5):
|
||||
// - Canonical encoding is enforced here.
|
||||
// - "Not small order" is enforced in SaplingVerificationContext::check_output()
|
||||
let ephemeral_key = edwards::Point::<Bls12, Unknown>::read(&mut reader, &JUBJUB)?;
|
||||
|
||||
let mut enc_ciphertext = [0; 580];
|
||||
let mut out_ciphertext = [0; 80];
|
||||
reader.read_exact(&mut enc_ciphertext)?;
|
||||
reader.read_exact(&mut out_ciphertext)?;
|
||||
|
||||
// Consensus rules (§4.5):
|
||||
// - Canonical encoding is enforced by the API of SaplingVerificationContext::check_output()
|
||||
// due to the need to parse this into a bellman::groth16::Proof.
|
||||
// - Proof validity is enforced in SaplingVerificationContext::check_output()
|
||||
let mut zkproof = [0; GROTH_PROOF_SIZE];
|
||||
reader.read_exact(&mut zkproof)?;
|
||||
|
||||
Ok(OutputDescription {
|
||||
cv,
|
||||
cmu,
|
||||
ephemeral_key,
|
||||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
zkproof,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
self.cv.write(&mut writer)?;
|
||||
self.cmu.into_repr().write_le(&mut writer)?;
|
||||
self.ephemeral_key.write(&mut writer)?;
|
||||
writer.write_all(&self.enc_ciphertext)?;
|
||||
writer.write_all(&self.out_ciphertext)?;
|
||||
writer.write_all(&self.zkproof)
|
||||
}
|
||||
}
|
||||
|
||||
enum SproutProof {
|
||||
Groth([u8; GROTH_PROOF_SIZE]),
|
||||
PHGR([u8; PHGR_PROOF_SIZE]),
|
||||
}
|
||||
|
||||
pub struct JSDescription {
|
||||
vpub_old: Amount,
|
||||
vpub_new: Amount,
|
||||
anchor: [u8; 32],
|
||||
nullifiers: [[u8; 32]; ZC_NUM_JS_INPUTS],
|
||||
commitments: [[u8; 32]; ZC_NUM_JS_OUTPUTS],
|
||||
ephemeral_key: [u8; 32],
|
||||
random_seed: [u8; 32],
|
||||
macs: [[u8; 32]; ZC_NUM_JS_INPUTS],
|
||||
proof: SproutProof,
|
||||
ciphertexts: [[u8; 601]; ZC_NUM_JS_OUTPUTS],
|
||||
}
|
||||
|
||||
impl JSDescription {
|
||||
pub fn read<R: Read>(mut reader: R, use_groth: bool) -> io::Result<Self> {
|
||||
// Consensus rule (§4.3): Canonical encoding is enforced here
|
||||
let vpub_old = Amount::read_u64(&mut reader)?;
|
||||
|
||||
// Consensus rule (§4.3): Canonical encoding is enforced here
|
||||
let vpub_new = Amount::read_u64(&mut reader)?;
|
||||
|
||||
// Consensus rule (§4.3): One of vpub_old and vpub_new being zero is
|
||||
// enforced by CheckTransactionWithoutProofVerification() in zcashd.
|
||||
|
||||
let mut anchor = [0; 32];
|
||||
reader.read_exact(&mut anchor)?;
|
||||
|
||||
let mut nullifiers = [[0; 32]; ZC_NUM_JS_INPUTS];
|
||||
nullifiers
|
||||
.iter_mut()
|
||||
.map(|nf| reader.read_exact(nf))
|
||||
.collect::<io::Result<()>>()?;
|
||||
|
||||
let mut commitments = [[0; 32]; ZC_NUM_JS_OUTPUTS];
|
||||
commitments
|
||||
.iter_mut()
|
||||
.map(|cm| reader.read_exact(cm))
|
||||
.collect::<io::Result<()>>()?;
|
||||
|
||||
// Consensus rule (§4.3): Canonical encoding is enforced by
|
||||
// ZCNoteDecryption::decrypt() in zcashd
|
||||
let mut ephemeral_key = [0; 32];
|
||||
reader.read_exact(&mut ephemeral_key)?;
|
||||
|
||||
let mut random_seed = [0; 32];
|
||||
reader.read_exact(&mut random_seed)?;
|
||||
|
||||
let mut macs = [[0; 32]; ZC_NUM_JS_INPUTS];
|
||||
macs.iter_mut()
|
||||
.map(|mac| reader.read_exact(mac))
|
||||
.collect::<io::Result<()>>()?;
|
||||
|
||||
let proof = match use_groth {
|
||||
true => {
|
||||
// Consensus rules (§4.3):
|
||||
// - Canonical encoding is enforced in librustzcash_sprout_verify()
|
||||
// - Proof validity is enforced in librustzcash_sprout_verify()
|
||||
let mut proof = [0; GROTH_PROOF_SIZE];
|
||||
reader.read_exact(&mut proof)?;
|
||||
SproutProof::Groth(proof)
|
||||
}
|
||||
false => {
|
||||
// Consensus rules (§4.3):
|
||||
// - Canonical encoding is enforced by PHGRProof in zcashd
|
||||
// - Proof validity is enforced by JSDescription::Verify() in zcashd
|
||||
let mut proof = [0; PHGR_PROOF_SIZE];
|
||||
reader.read_exact(&mut proof)?;
|
||||
SproutProof::PHGR(proof)
|
||||
}
|
||||
};
|
||||
|
||||
let mut ciphertexts = [[0; 601]; ZC_NUM_JS_OUTPUTS];
|
||||
ciphertexts
|
||||
.iter_mut()
|
||||
.map(|ct| reader.read_exact(ct))
|
||||
.collect::<io::Result<()>>()?;
|
||||
|
||||
Ok(JSDescription {
|
||||
vpub_old,
|
||||
vpub_new,
|
||||
anchor,
|
||||
nullifiers,
|
||||
commitments,
|
||||
ephemeral_key,
|
||||
random_seed,
|
||||
macs,
|
||||
proof,
|
||||
ciphertexts,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_i64::<LittleEndian>(self.vpub_old.0)?;
|
||||
writer.write_i64::<LittleEndian>(self.vpub_new.0)?;
|
||||
writer.write_all(&self.anchor)?;
|
||||
writer.write_all(&self.nullifiers[0])?;
|
||||
writer.write_all(&self.nullifiers[1])?;
|
||||
writer.write_all(&self.commitments[0])?;
|
||||
writer.write_all(&self.commitments[1])?;
|
||||
writer.write_all(&self.ephemeral_key)?;
|
||||
writer.write_all(&self.random_seed)?;
|
||||
writer.write_all(&self.macs[0])?;
|
||||
writer.write_all(&self.macs[1])?;
|
||||
|
||||
match &self.proof {
|
||||
SproutProof::Groth(p) => writer.write_all(p)?,
|
||||
SproutProof::PHGR(p) => writer.write_all(p)?,
|
||||
}
|
||||
|
||||
writer.write_all(&self.ciphertexts[0])?;
|
||||
writer.write_all(&self.ciphertexts[1])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Amount, MAX_MONEY};
|
||||
|
||||
#[test]
|
||||
fn amount_in_range() {
|
||||
let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
assert_eq!(Amount::read_u64(&zero[..]).unwrap(), Amount(0));
|
||||
assert_eq!(Amount::read_i64(&zero[..], false).unwrap(), Amount(0));
|
||||
assert_eq!(Amount::read_i64(&zero[..], true).unwrap(), Amount(0));
|
||||
|
||||
let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
assert!(Amount::read_u64(&neg_one[..]).is_err());
|
||||
assert!(Amount::read_i64(&neg_one[..], false).is_err());
|
||||
assert_eq!(Amount::read_i64(&neg_one[..], true).unwrap(), Amount(-1));
|
||||
|
||||
let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
|
||||
assert_eq!(Amount::read_u64(&max_money[..]).unwrap(), Amount(MAX_MONEY));
|
||||
assert_eq!(
|
||||
Amount::read_i64(&max_money[..], false).unwrap(),
|
||||
Amount(MAX_MONEY)
|
||||
);
|
||||
assert_eq!(
|
||||
Amount::read_i64(&max_money[..], true).unwrap(),
|
||||
Amount(MAX_MONEY)
|
||||
);
|
||||
|
||||
let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
|
||||
assert!(Amount::read_u64(&max_money_p1[..]).is_err());
|
||||
assert!(Amount::read_i64(&max_money_p1[..], false).is_err());
|
||||
assert!(Amount::read_i64(&max_money_p1[..], true).is_err());
|
||||
|
||||
let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
|
||||
assert!(Amount::read_u64(&neg_max_money[..]).is_err());
|
||||
assert!(Amount::read_i64(&neg_max_money[..], false).is_err());
|
||||
assert_eq!(
|
||||
Amount::read_i64(&neg_max_money[..], true).unwrap(),
|
||||
Amount(-MAX_MONEY)
|
||||
);
|
||||
|
||||
let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
|
||||
assert!(Amount::read_u64(&neg_max_money_m1[..]).is_err());
|
||||
assert!(Amount::read_i64(&neg_max_money_m1[..], false).is_err());
|
||||
assert!(Amount::read_i64(&neg_max_money_m1[..], true).is_err());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use sapling_crypto::redjubjub::Signature;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::Deref;
|
||||
|
||||
use serialize::Vector;
|
||||
|
||||
pub mod components;
|
||||
mod sighash;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::sighash::{signature_hash, signature_hash_data, SIGHASH_ALL};
|
||||
|
||||
use self::components::{Amount, JSDescription, OutputDescription, SpendDescription, TxIn, TxOut};
|
||||
|
||||
const OVERWINTER_VERSION_GROUP_ID: u32 = 0x03C48270;
|
||||
const OVERWINTER_TX_VERSION: u32 = 3;
|
||||
const SAPLING_VERSION_GROUP_ID: u32 = 0x892F2085;
|
||||
const SAPLING_TX_VERSION: u32 = 4;
|
||||
|
||||
/// A Zcash transaction.
|
||||
pub struct Transaction(TransactionData);
|
||||
|
||||
impl Deref for Transaction {
|
||||
type Target = TransactionData;
|
||||
|
||||
fn deref(&self) -> &TransactionData {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TransactionData {
|
||||
pub overwintered: bool,
|
||||
pub version: u32,
|
||||
pub version_group_id: u32,
|
||||
pub vin: Vec<TxIn>,
|
||||
pub vout: Vec<TxOut>,
|
||||
pub lock_time: u32,
|
||||
pub expiry_height: u32,
|
||||
pub value_balance: Amount,
|
||||
pub shielded_spends: Vec<SpendDescription>,
|
||||
pub shielded_outputs: Vec<OutputDescription>,
|
||||
pub joinsplits: Vec<JSDescription>,
|
||||
pub joinsplit_pubkey: Option<[u8; 32]>,
|
||||
pub joinsplit_sig: Option<[u8; 64]>,
|
||||
pub binding_sig: Option<Signature>,
|
||||
}
|
||||
|
||||
impl TransactionData {
|
||||
pub fn new() -> Self {
|
||||
TransactionData {
|
||||
overwintered: true,
|
||||
version: SAPLING_TX_VERSION,
|
||||
version_group_id: SAPLING_VERSION_GROUP_ID,
|
||||
vin: vec![],
|
||||
vout: vec![],
|
||||
lock_time: 0,
|
||||
expiry_height: 0,
|
||||
value_balance: Amount(0),
|
||||
shielded_spends: vec![],
|
||||
shielded_outputs: vec![],
|
||||
joinsplits: vec![],
|
||||
joinsplit_pubkey: None,
|
||||
joinsplit_sig: None,
|
||||
binding_sig: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn header(&self) -> u32 {
|
||||
let mut header = self.version;
|
||||
if self.overwintered {
|
||||
header |= 1 << 31;
|
||||
}
|
||||
header
|
||||
}
|
||||
|
||||
pub fn freeze(self) -> Transaction {
|
||||
Transaction(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
|
||||
let header = reader.read_u32::<LittleEndian>()?;
|
||||
let overwintered = (header >> 31) == 1;
|
||||
let version = header & 0x7FFFFFFF;
|
||||
|
||||
let version_group_id = match overwintered {
|
||||
true => reader.read_u32::<LittleEndian>()?,
|
||||
false => 0,
|
||||
};
|
||||
|
||||
let is_overwinter_v3 = overwintered
|
||||
&& version_group_id == OVERWINTER_VERSION_GROUP_ID
|
||||
&& version == OVERWINTER_TX_VERSION;
|
||||
let is_sapling_v4 = overwintered
|
||||
&& version_group_id == SAPLING_VERSION_GROUP_ID
|
||||
&& version == SAPLING_TX_VERSION;
|
||||
if overwintered && !(is_overwinter_v3 || is_sapling_v4) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unknown transaction format",
|
||||
));
|
||||
}
|
||||
|
||||
let vin = Vector::read(&mut reader, TxIn::read)?;
|
||||
let vout = Vector::read(&mut reader, TxOut::read)?;
|
||||
let lock_time = reader.read_u32::<LittleEndian>()?;
|
||||
let expiry_height = match is_overwinter_v3 || is_sapling_v4 {
|
||||
true => reader.read_u32::<LittleEndian>()?,
|
||||
false => 0,
|
||||
};
|
||||
|
||||
let (value_balance, shielded_spends, shielded_outputs) = if is_sapling_v4 {
|
||||
let vb = Amount::read_i64(&mut reader, true)?;
|
||||
let ss = Vector::read(&mut reader, SpendDescription::read)?;
|
||||
let so = Vector::read(&mut reader, OutputDescription::read)?;
|
||||
(vb, ss, so)
|
||||
} else {
|
||||
(Amount(0), vec![], vec![])
|
||||
};
|
||||
|
||||
let (joinsplits, joinsplit_pubkey, joinsplit_sig) = if version >= 2 {
|
||||
let jss = Vector::read(&mut reader, |r| {
|
||||
JSDescription::read(r, overwintered && version >= SAPLING_TX_VERSION)
|
||||
})?;
|
||||
let (pubkey, sig) = if !jss.is_empty() {
|
||||
let mut joinsplit_pubkey = [0; 32];
|
||||
let mut joinsplit_sig = [0; 64];
|
||||
reader.read_exact(&mut joinsplit_pubkey)?;
|
||||
reader.read_exact(&mut joinsplit_sig)?;
|
||||
(Some(joinsplit_pubkey), Some(joinsplit_sig))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
(jss, pubkey, sig)
|
||||
} else {
|
||||
(vec![], None, None)
|
||||
};
|
||||
|
||||
let binding_sig =
|
||||
match is_sapling_v4 && !(shielded_spends.is_empty() && shielded_outputs.is_empty()) {
|
||||
true => Some(Signature::read(&mut reader)?),
|
||||
false => None,
|
||||
};
|
||||
|
||||
Ok(Transaction(TransactionData {
|
||||
overwintered,
|
||||
version,
|
||||
version_group_id,
|
||||
vin,
|
||||
vout,
|
||||
lock_time,
|
||||
expiry_height,
|
||||
value_balance,
|
||||
shielded_spends,
|
||||
shielded_outputs,
|
||||
joinsplits,
|
||||
joinsplit_pubkey,
|
||||
joinsplit_sig,
|
||||
binding_sig,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
writer.write_u32::<LittleEndian>(self.header())?;
|
||||
if self.overwintered {
|
||||
writer.write_u32::<LittleEndian>(self.version_group_id)?;
|
||||
}
|
||||
|
||||
let is_overwinter_v3 = self.overwintered
|
||||
&& self.version_group_id == OVERWINTER_VERSION_GROUP_ID
|
||||
&& self.version == OVERWINTER_TX_VERSION;
|
||||
let is_sapling_v4 = self.overwintered
|
||||
&& self.version_group_id == SAPLING_VERSION_GROUP_ID
|
||||
&& self.version == SAPLING_TX_VERSION;
|
||||
if self.overwintered && !(is_overwinter_v3 || is_sapling_v4) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unknown transaction format",
|
||||
));
|
||||
}
|
||||
|
||||
Vector::write(&mut writer, &self.vin, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.vout, |w, e| e.write(w))?;
|
||||
writer.write_u32::<LittleEndian>(self.lock_time)?;
|
||||
if is_overwinter_v3 || is_sapling_v4 {
|
||||
writer.write_u32::<LittleEndian>(self.expiry_height)?;
|
||||
}
|
||||
|
||||
if is_sapling_v4 {
|
||||
writer.write_i64::<LittleEndian>(self.value_balance.0)?;
|
||||
Vector::write(&mut writer, &self.shielded_spends, |w, e| e.write(w))?;
|
||||
Vector::write(&mut writer, &self.shielded_outputs, |w, e| e.write(w))?;
|
||||
}
|
||||
|
||||
if self.version >= 2 {
|
||||
Vector::write(&mut writer, &self.joinsplits, |w, e| e.write(w))?;
|
||||
if !self.joinsplits.is_empty() {
|
||||
match self.joinsplit_pubkey {
|
||||
Some(pubkey) => writer.write_all(&pubkey)?,
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing JoinSplit pubkey",
|
||||
))
|
||||
}
|
||||
}
|
||||
match self.joinsplit_sig {
|
||||
Some(sig) => writer.write_all(&sig)?,
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing JoinSplit signature",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.version < 2 || self.joinsplits.is_empty() {
|
||||
if self.joinsplit_pubkey.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"JoinSplit pubkey should not be present",
|
||||
));
|
||||
}
|
||||
if self.joinsplit_sig.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"JoinSplit signature should not be present",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if is_sapling_v4 && !(self.shielded_spends.is_empty() && self.shielded_outputs.is_empty()) {
|
||||
match self.binding_sig {
|
||||
Some(sig) => sig.write(&mut writer)?,
|
||||
None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Missing binding signature",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else if self.binding_sig.is_some() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Binding signature should not be present",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
use blake2_rfc::blake2b::Blake2b;
|
||||
use byteorder::{LittleEndian, WriteBytesExt};
|
||||
use pairing::{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);
|
||||
} 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) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(tx.vin.len() * 36);
|
||||
for t_in in &tx.vin {
|
||||
t_in.prevout.write(&mut data).unwrap();
|
||||
}
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_PREVOUTS_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn sequence_hash(tx: &TransactionData) -> Vec<u8> {
|
||||
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();
|
||||
}
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_SEQUENCE_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn outputs_hash(tx: &TransactionData) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(tx.vout.len() * (4 + 1));
|
||||
for t_out in &tx.vout {
|
||||
t_out.write(&mut data).unwrap();
|
||||
}
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_OUTPUTS_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn single_output_hash(tx_out: &TxOut) -> Vec<u8> {
|
||||
let mut data = vec![];
|
||||
tx_out.write(&mut data).unwrap();
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_OUTPUTS_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn joinsplits_hash(tx: &TransactionData) -> Vec<u8> {
|
||||
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());
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_JOINSPLITS_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn shielded_spends_hash(tx: &TransactionData) -> Vec<u8> {
|
||||
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);
|
||||
}
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_SHIELDED_SPENDS_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn shielded_outputs_hash(tx: &TransactionData) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(tx.shielded_outputs.len() * 948);
|
||||
for s_out in &tx.shielded_outputs {
|
||||
s_out.write(&mut data).unwrap();
|
||||
}
|
||||
let mut h = Blake2b::with_params(32, &[], &[], ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION);
|
||||
h.update(&data);
|
||||
h.finalize().as_ref().to_vec()
|
||||
}
|
||||
|
||||
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 hash_outputs = if (hash_type & SIGHASH_MASK) != SIGHASH_SINGLE
|
||||
&& (hash_type & SIGHASH_MASK) != SIGHASH_NONE
|
||||
{
|
||||
outputs_hash(tx)
|
||||
} else if (hash_type & SIGHASH_MASK) == SIGHASH_SINGLE
|
||||
&& transparent_input.is_some()
|
||||
&& transparent_input.as_ref().unwrap().0 < tx.vout.len()
|
||||
{
|
||||
single_output_hash(&tx.vout[transparent_input.as_ref().unwrap().0])
|
||||
} else {
|
||||
vec![0; 32]
|
||||
};
|
||||
|
||||
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 = Blake2b::with_params(32, &[], &[], &personal);
|
||||
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)
|
||||
);
|
||||
h.update(&hash_outputs);
|
||||
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)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue