2019-09-10 13:27:10 -07:00
|
|
|
//! Newtype wrappers for primitive data types with semantic meaning.
|
|
|
|
|
2019-12-20 16:15:40 -08:00
|
|
|
use std::{
|
|
|
|
fmt,
|
|
|
|
io::{self, Read},
|
|
|
|
};
|
2019-12-05 12:56:58 -08:00
|
|
|
|
2019-12-13 14:25:14 -08:00
|
|
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
2019-12-05 12:56:58 -08:00
|
|
|
use chrono::{DateTime, TimeZone, Utc};
|
2020-01-27 23:00:07 -08:00
|
|
|
|
2020-01-14 13:41:33 -08:00
|
|
|
#[cfg(test)]
|
2019-12-31 12:37:31 -08:00
|
|
|
use proptest_derive::Arbitrary;
|
2019-12-05 12:56:58 -08:00
|
|
|
|
2019-12-20 16:15:40 -08:00
|
|
|
use crate::serialization::{
|
|
|
|
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
|
|
|
|
};
|
2019-10-15 15:59:55 -07:00
|
|
|
|
2019-09-24 22:14:48 -07:00
|
|
|
/// A 4-byte checksum using truncated double-SHA256 (two rounds of SHA256).
|
2019-10-15 15:59:55 -07:00
|
|
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
2019-09-12 03:46:39 -07:00
|
|
|
pub struct Sha256dChecksum(pub [u8; 4]);
|
|
|
|
|
|
|
|
impl<'a> From<&'a [u8]> for Sha256dChecksum {
|
|
|
|
fn from(bytes: &'a [u8]) -> Self {
|
2019-09-18 12:57:06 -07:00
|
|
|
use sha2::{Digest, Sha256};
|
2019-09-12 03:46:39 -07:00
|
|
|
let hash1 = Sha256::digest(bytes);
|
|
|
|
let hash2 = Sha256::digest(&hash1);
|
|
|
|
let mut checksum = [0u8; 4];
|
|
|
|
checksum[0..4].copy_from_slice(&hash2[0..4]);
|
|
|
|
Self(checksum)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-15 15:59:55 -07:00
|
|
|
impl fmt::Debug for Sha256dChecksum {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_tuple("Sha256dChecksum")
|
|
|
|
.field(&hex::encode(&self.0))
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-10 13:27:10 -07:00
|
|
|
/// A u32 which represents a block height value.
|
Parse block heights in coinbase transactions.
BIP34, which is included in Zcash, encodes the block height into each
block by adding it into the unused BitcoinScript field of the block's
coinbase transaction. However, this is done just by requiring that the
script pushes the block height onto the stack when it executes, and
there are multiple different ways to push data onto the stack in
BitcoinScript. Also, the genesis block does not include the block
height, by accident.
Because we want to *parse* transactions into an algebraic data type that
encodes their structural properties, rather than allow possibly-invalid
data to float through the internals of our node, we want to extract the
block height upfront and store it separately from the rest of the
coinbase data, which is inert. So the serialization code now contains
just enough logic to parse BitcoinScript-encoded block heights, and
special-case the encoding of the genesis block.
Elsewhere in the source code, the `LockTime` struct requires that we
must use block heights less than 500,000,000 (above which the number is
interpreted as a unix timestamp, not a height). To unify invariants, we
ensure that the parsing logic works with block heights up to
500,000,000, even though these are unlikely to ever be used for Zcash.
2020-02-07 15:56:06 -08:00
|
|
|
///
|
|
|
|
/// # Invariants
|
|
|
|
///
|
|
|
|
/// Users should not construct block heights greater than or equal to `500_000_000`.
|
2020-06-21 15:51:43 -07:00
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
2019-09-10 13:27:10 -07:00
|
|
|
pub struct BlockHeight(pub u32);
|
2019-09-11 19:44:13 -07:00
|
|
|
|
Parse block heights in coinbase transactions.
BIP34, which is included in Zcash, encodes the block height into each
block by adding it into the unused BitcoinScript field of the block's
coinbase transaction. However, this is done just by requiring that the
script pushes the block height onto the stack when it executes, and
there are multiple different ways to push data onto the stack in
BitcoinScript. Also, the genesis block does not include the block
height, by accident.
Because we want to *parse* transactions into an algebraic data type that
encodes their structural properties, rather than allow possibly-invalid
data to float through the internals of our node, we want to extract the
block height upfront and store it separately from the rest of the
coinbase data, which is inert. So the serialization code now contains
just enough logic to parse BitcoinScript-encoded block heights, and
special-case the encoding of the genesis block.
Elsewhere in the source code, the `LockTime` struct requires that we
must use block heights less than 500,000,000 (above which the number is
interpreted as a unix timestamp, not a height). To unify invariants, we
ensure that the parsing logic works with block heights up to
500,000,000, even though these are unlikely to ever be used for Zcash.
2020-02-07 15:56:06 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
impl Arbitrary for BlockHeight {
|
|
|
|
type Parameters = ();
|
|
|
|
|
|
|
|
fn arbitrary_with(_args: ()) -> Self::Strategy {
|
2020-05-26 18:00:58 -07:00
|
|
|
(0u32..500_000_000_u32).prop_map(BlockHeight).boxed()
|
Parse block heights in coinbase transactions.
BIP34, which is included in Zcash, encodes the block height into each
block by adding it into the unused BitcoinScript field of the block's
coinbase transaction. However, this is done just by requiring that the
script pushes the block height onto the stack when it executes, and
there are multiple different ways to push data onto the stack in
BitcoinScript. Also, the genesis block does not include the block
height, by accident.
Because we want to *parse* transactions into an algebraic data type that
encodes their structural properties, rather than allow possibly-invalid
data to float through the internals of our node, we want to extract the
block height upfront and store it separately from the rest of the
coinbase data, which is inert. So the serialization code now contains
just enough logic to parse BitcoinScript-encoded block heights, and
special-case the encoding of the genesis block.
Elsewhere in the source code, the `LockTime` struct requires that we
must use block heights less than 500,000,000 (above which the number is
interpreted as a unix timestamp, not a height). To unify invariants, we
ensure that the parsing logic works with block heights up to
500,000,000, even though these are unlikely to ever be used for Zcash.
2020-02-07 15:56:06 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
}
|
|
|
|
|
2019-12-05 12:56:58 -08:00
|
|
|
/// A Bitcoin-style `locktime`, representing either a block height or an epoch
|
|
|
|
/// time.
|
|
|
|
///
|
|
|
|
/// # Invariants
|
|
|
|
///
|
|
|
|
/// Users should not construct a `LockTime` with `BlockHeight` greater than or
|
|
|
|
/// equal to `500_000_000` or a timestamp before 4 November 1985 (Unix timestamp
|
|
|
|
/// less than `500_000_000`).
|
2020-06-15 15:08:14 -07:00
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
2019-12-05 12:56:58 -08:00
|
|
|
pub enum LockTime {
|
|
|
|
/// Unlock at a particular block height.
|
|
|
|
Height(BlockHeight),
|
|
|
|
/// Unlock at a particular time.
|
|
|
|
Time(DateTime<Utc>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ZcashSerialize for LockTime {
|
2020-02-05 14:32:10 -08:00
|
|
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
2019-12-05 12:56:58 -08:00
|
|
|
// This implementation does not check the invariants on `LockTime` so that the
|
|
|
|
// serialization is fallible only if the underlying writer is. This ensures that
|
|
|
|
// we can always compute a hash of a transaction object.
|
|
|
|
use LockTime::*;
|
|
|
|
match self {
|
|
|
|
Height(BlockHeight(n)) => writer.write_u32::<LittleEndian>(*n)?,
|
|
|
|
Time(t) => writer.write_u32::<LittleEndian>(t.timestamp() as u32)?,
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ZcashDeserialize for LockTime {
|
|
|
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
|
|
|
let n = reader.read_u32::<LittleEndian>()?;
|
|
|
|
if n < 500_000_000 {
|
|
|
|
Ok(LockTime::Height(BlockHeight(n)))
|
|
|
|
} else {
|
|
|
|
Ok(LockTime::Time(Utc.timestamp(n as i64, 0)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-14 13:41:33 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
impl Arbitrary for LockTime {
|
|
|
|
type Parameters = ();
|
|
|
|
|
|
|
|
fn arbitrary_with(_args: ()) -> Self::Strategy {
|
|
|
|
prop_oneof![
|
|
|
|
(0u32..500_000_000_u32).prop_map(|n| LockTime::Height(BlockHeight(n))),
|
2020-01-28 00:39:48 -08:00
|
|
|
// XXX Setting max to i64::MAX doesn't work, this is 2**32.
|
|
|
|
(500_000_000i64..4_294_967_296).prop_map(|n| { LockTime::Time(Utc.timestamp(n, 0)) })
|
2020-01-14 13:41:33 -08:00
|
|
|
]
|
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
|
|
|
|
type Strategy = BoxedStrategy<Self>;
|
|
|
|
}
|
|
|
|
|
2019-12-05 12:56:58 -08:00
|
|
|
/// An encoding of a Bitcoin script.
|
2020-06-15 15:08:14 -07:00
|
|
|
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
2019-12-31 12:37:31 -08:00
|
|
|
#[cfg_attr(test, derive(Arbitrary))]
|
2019-12-05 12:56:58 -08:00
|
|
|
pub struct Script(pub Vec<u8>);
|
|
|
|
|
2020-02-07 12:53:44 -08:00
|
|
|
impl fmt::Debug for Script {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_tuple("Script")
|
|
|
|
.field(&hex::encode(&self.0))
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-20 16:15:40 -08:00
|
|
|
impl ZcashSerialize for Script {
|
2020-02-05 14:32:10 -08:00
|
|
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
2019-12-20 16:15:40 -08:00
|
|
|
writer.write_compactsize(self.0.len() as u64)?;
|
|
|
|
writer.write_all(&self.0[..])?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ZcashDeserialize for Script {
|
|
|
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
|
|
|
// XXX what is the max length of a script?
|
|
|
|
let len = reader.read_compactsize()?;
|
|
|
|
let mut bytes = Vec::new();
|
|
|
|
reader.take(len).read_to_end(&mut bytes)?;
|
|
|
|
Ok(Script(bytes))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-27 23:00:07 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
2019-09-12 03:46:39 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn sha256d_checksum() {
|
|
|
|
// https://en.bitcoin.it/wiki/Protocol_documentation#Hashes
|
|
|
|
let input = b"hello";
|
|
|
|
let checksum = Sha256dChecksum::from(&input[..]);
|
|
|
|
let expected = Sha256dChecksum([0x95, 0x95, 0xc9, 0xdf]);
|
|
|
|
assert_eq!(checksum, expected);
|
|
|
|
}
|
2019-10-15 15:59:55 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn sha256d_checksum_debug() {
|
|
|
|
let input = b"hello";
|
|
|
|
let checksum = Sha256dChecksum::from(&input[..]);
|
|
|
|
|
|
|
|
assert_eq!(format!("{:?}", checksum), "Sha256dChecksum(\"9595c9df\")");
|
|
|
|
}
|
2019-09-18 12:57:06 -07:00
|
|
|
}
|
2019-12-19 12:30:26 -08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2020-01-14 13:41:33 -08:00
|
|
|
mod proptests {
|
2019-12-19 12:30:26 -08:00
|
|
|
|
|
|
|
use std::io::Cursor;
|
|
|
|
|
|
|
|
use proptest::prelude::*;
|
|
|
|
|
2020-01-27 20:26:01 -08:00
|
|
|
use super::{LockTime, Script};
|
2019-12-19 12:30:26 -08:00
|
|
|
use crate::serialization::{ZcashDeserialize, ZcashSerialize};
|
|
|
|
|
|
|
|
proptest! {
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn locktime_roundtrip(locktime in any::<LockTime>()) {
|
|
|
|
let mut bytes = Cursor::new(Vec::new());
|
|
|
|
locktime.zcash_serialize(&mut bytes)?;
|
|
|
|
|
|
|
|
bytes.set_position(0);
|
|
|
|
let other_locktime = LockTime::zcash_deserialize(&mut bytes)?;
|
|
|
|
|
|
|
|
prop_assert_eq![locktime, other_locktime];
|
2019-12-31 12:37:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn script_roundtrip(script in any::<Script>()) {
|
|
|
|
let mut bytes = Cursor::new(Vec::new());
|
|
|
|
script.zcash_serialize(&mut bytes)?;
|
2019-12-19 12:30:26 -08:00
|
|
|
|
2019-12-31 12:37:31 -08:00
|
|
|
bytes.set_position(0);
|
|
|
|
let other_script = Script::zcash_deserialize(&mut bytes)?;
|
2019-12-19 12:30:26 -08:00
|
|
|
|
2019-12-31 12:37:31 -08:00
|
|
|
prop_assert_eq![script, other_script];
|
2019-12-19 12:30:26 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|