use std::io;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use zcash_script::{opcode::Evaluable, pattern};
use zcash_transparent::coinbase::{MAX_COINBASE_SCRIPT_LEN, MIN_COINBASE_SCRIPT_LEN};
use crate::{
block::Height,
serialization::{
zcash_deserialize_bytes_external_count, CompactSizeMessage, ReadZcashExt,
SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize,
},
transaction,
};
use super::{Input, OutPoint, Output, Script};
pub const GENESIS_COINBASE_SCRIPT_SIG: [u8; 77] = [
4, 255, 255, 7, 31, 1, 4, 69, 90, 99, 97, 115, 104, 48, 98, 57, 99, 52, 101, 101, 102, 56, 98,
55, 99, 99, 52, 49, 55, 101, 101, 53, 48, 48, 49, 101, 51, 53, 48, 48, 57, 56, 52, 98, 54, 102,
101, 97, 51, 53, 54, 56, 51, 97, 55, 99, 97, 99, 49, 52, 49, 97, 48, 52, 51, 99, 52, 50, 48,
54, 52, 56, 51, 53, 100, 51, 52,
];
fn parse_coinbase_height(script_sig: &[u8]) -> Result<(Height, Vec<u8>), SerializationError> {
let parse_err = SerializationError::Parse;
let (h, len): (i64, usize) = match *script_sig
.first()
.ok_or(parse_err("Empty coinbase script"))?
{
op_n @ 0x51..=0x60 => (i64::from(op_n - 0x50), 1),
n @ 1..=5 => {
let bytes = script_sig
.get(1..=usize::from(n))
.ok_or(parse_err("Coinbase height push truncated"))?;
let mut buf = [0u8; 8];
buf[..bytes.len()].copy_from_slice(bytes);
(i64::from_le_bytes(buf), 1 + bytes.len())
}
_ => return Err(parse_err("Invalid coinbase script prefix")),
};
if script_sig
.get(..len)
.ok_or(parse_err("Coinbase script too short"))?
!= pattern::push_num(h).to_bytes().as_slice()
{
return Err(parse_err("Non-canonical coinbase height encoding"));
}
let h = u32::try_from(h).map_err(|_| parse_err("Negative coinbase height"))?;
let height =
Height::try_from(h).map_err(|_| parse_err("Coinbase height exceeds Height::MAX"))?;
Ok((height, script_sig[len..].to_vec()))
}
impl ZcashSerialize for OutPoint {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
writer.write_all(&self.hash.0[..])?;
writer.write_u32::<LittleEndian>(self.index)?;
Ok(())
}
}
impl ZcashDeserialize for OutPoint {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
Ok(OutPoint {
hash: transaction::Hash(reader.read_32_bytes()?),
index: reader.read_u32::<LittleEndian>()?,
})
}
}
impl ZcashSerialize for Input {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
Input::PrevOut {
outpoint,
unlock_script,
sequence,
} => {
outpoint.zcash_serialize(&mut writer)?;
unlock_script.zcash_serialize(&mut writer)?;
writer.write_u32::<LittleEndian>(*sequence)?;
}
Input::Coinbase { sequence, .. } => {
writer.write_all(&[0; 32][..])?;
writer.write_u32::<LittleEndian>(0xffff_ffff)?;
self.coinbase_script()
.ok_or_else(|| io::Error::other("invalid coinbase script sig"))?
.zcash_serialize(&mut writer)?;
writer.write_u32::<LittleEndian>(*sequence)?;
}
}
Ok(())
}
}
impl ZcashDeserialize for Input {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let hash = reader.read_32_bytes()?;
if hash == [0; 32] {
if reader.read_u32::<LittleEndian>()? != 0xffff_ffff {
return Err(SerializationError::Parse("Wrong index in coinbase"));
}
let len: CompactSizeMessage = (&mut reader).zcash_deserialize_into()?;
let len: usize = len.into();
if len < MIN_COINBASE_SCRIPT_LEN {
return Err(SerializationError::Parse("Coinbase script is too short"));
} else if len > MAX_COINBASE_SCRIPT_LEN {
return Err(SerializationError::Parse("Coinbase script is too long"));
}
let script_sig = zcash_deserialize_bytes_external_count(len, &mut reader)?;
let (height, data) = if script_sig.as_slice() == GENESIS_COINBASE_SCRIPT_SIG {
(Height::MIN, GENESIS_COINBASE_SCRIPT_SIG.to_vec())
} else {
parse_coinbase_height(&script_sig)?
};
Ok(Input::Coinbase {
height,
data,
sequence: reader.read_u32::<LittleEndian>()?,
})
} else {
Ok(Input::PrevOut {
outpoint: OutPoint {
hash: transaction::Hash(hash),
index: reader.read_u32::<LittleEndian>()?,
},
unlock_script: Script::zcash_deserialize(&mut reader)?,
sequence: reader.read_u32::<LittleEndian>()?,
})
}
}
}
impl ZcashSerialize for Output {
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
self.value.zcash_serialize(&mut writer)?;
self.lock_script.zcash_serialize(&mut writer)?;
Ok(())
}
}
impl ZcashDeserialize for Output {
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let reader = &mut reader;
Ok(Output {
value: reader.zcash_deserialize_into()?,
lock_script: Script::zcash_deserialize(reader)?,
})
}
}