585 lines
17 KiB
Rust
585 lines
17 KiB
Rust
use ghost_traits::hashing::GhostHasher;
|
|
use sp_std::vec::Vec;
|
|
|
|
const MAX_INPUTS: usize = 3;
|
|
const MAX_OUTPUTS: usize = 4;
|
|
const MAX_SCRIPT_LEN: usize = 512;
|
|
pub const MAX_TX_LEN: usize = 4096;
|
|
|
|
type Prefix = [u8; 7];
|
|
|
|
pub struct TxIn<H: GhostHasher> {
|
|
pub prev_txid: H::Hash,
|
|
pub vout: u32,
|
|
pub script_sig: Vec<u8>,
|
|
pub sequence: u32,
|
|
}
|
|
|
|
pub struct TxOut {
|
|
pub value: u64,
|
|
pub script_pubkey: Vec<u8>,
|
|
}
|
|
|
|
pub struct Transaction<H: GhostHasher> {
|
|
version: i32,
|
|
inputs: Vec<TxIn<H>>,
|
|
outputs: Vec<TxOut>,
|
|
lock_time: u32,
|
|
}
|
|
|
|
pub struct Header<H: GhostHasher> {
|
|
pub merkle_root: H::Hash,
|
|
pub tx_count: u32,
|
|
pub tree_hashes: Vec<H::Hash>,
|
|
pub flags: Vec<u8>,
|
|
}
|
|
|
|
impl<H: GhostHasher> Header<H> {
|
|
fn calc_tree_width(&self, h: u32) -> u32 {
|
|
(self.tx_count + (1 << h) - 1) >> h
|
|
}
|
|
|
|
fn traverse(
|
|
&self,
|
|
height: u32,
|
|
pos: u32,
|
|
bits_used: &mut u32,
|
|
hash_used: &mut u32,
|
|
matched_index: &mut Option<usize>,
|
|
target_txid: H::Hash,
|
|
) -> Result<H::Hash, ParseError> {
|
|
let byte_idx = (*bits_used / 8) as usize;
|
|
let bit_idx = (*bits_used % 8) as u8;
|
|
|
|
if byte_idx >= self.flags.len() {
|
|
return Err(ParseError::InvalidVarint(VarintError::BufferTooShort));
|
|
}
|
|
|
|
let parent_of_match = ((self.flags[byte_idx] >> bit_idx) & 1) == 1;
|
|
*bits_used += 1;
|
|
|
|
if height == 0 || !parent_of_match {
|
|
if *hash_used as usize >= self.tree_hashes.len() {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
let curr_hash = self.tree_hashes[*hash_used as usize];
|
|
*hash_used += 1;
|
|
|
|
if height == 0 && parent_of_match && curr_hash == target_txid {
|
|
*matched_index = Some(pos as usize);
|
|
}
|
|
|
|
return Ok(curr_hash);
|
|
}
|
|
|
|
let left = self.traverse(
|
|
height - 1,
|
|
pos * 2,
|
|
bits_used,
|
|
hash_used,
|
|
matched_index,
|
|
target_txid,
|
|
)?;
|
|
|
|
let right = if pos * 2 + 1 < self.calc_tree_width(height - 1) {
|
|
let right = self.traverse(
|
|
height - 1,
|
|
pos * 2 + 1,
|
|
bits_used,
|
|
hash_used,
|
|
matched_index,
|
|
target_txid,
|
|
)?;
|
|
|
|
if right == left {
|
|
return Err(ParseError::TypeConversionFailed);
|
|
}
|
|
|
|
right
|
|
} else {
|
|
left
|
|
};
|
|
|
|
let mut concat = [0u8; 64];
|
|
concat[..32].copy_from_slice(left.as_ref());
|
|
concat[32..].copy_from_slice(right.as_ref());
|
|
let current_node_hash = H::hash(&concat);
|
|
|
|
Ok(current_node_hash)
|
|
}
|
|
|
|
pub fn extract_proof(&self, target_txid: H::Hash) -> Result<H::Hash, ParseError> {
|
|
let mut bits_used = 0u32;
|
|
let mut hash_used = 0u32;
|
|
|
|
let mut matched_index = None;
|
|
let mut max_height = 0;
|
|
while (1 << max_height) < self.tx_count {
|
|
max_height += 1;
|
|
}
|
|
|
|
let computed_root = self.traverse(
|
|
max_height,
|
|
0,
|
|
&mut bits_used,
|
|
&mut hash_used,
|
|
&mut matched_index,
|
|
target_txid,
|
|
)?;
|
|
|
|
if hash_used as usize != self.tree_hashes.len() {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
if (bits_used + 7) / 8 != self.flags.len() as u32 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
if matched_index.is_some() {
|
|
Ok(computed_root)
|
|
} else {
|
|
Err(ParseError::TransactionNotFoundInProof)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<H> Transaction<H>
|
|
where
|
|
H: GhostHasher<HashBytes = [u8; 32]>,
|
|
{
|
|
pub fn compute_txid(&self) -> Option<H::Hash> {
|
|
let version_bytes = self.version.to_le_bytes();
|
|
let lock_time_bytes = self.lock_time.to_le_bytes();
|
|
|
|
let mut tx_buffer = [0u8; MAX_TX_LEN];
|
|
let mut cursor = 0;
|
|
|
|
tx_buffer[cursor..cursor + 4].copy_from_slice(&version_bytes);
|
|
cursor += 4;
|
|
|
|
cursor = Self::serialize_inputs_and_outputs(
|
|
&mut tx_buffer,
|
|
cursor,
|
|
&self.inputs,
|
|
&self.outputs,
|
|
)?;
|
|
|
|
if cursor + 4 > MAX_TX_LEN { return None; }
|
|
tx_buffer[cursor..cursor + 4].copy_from_slice(&lock_time_bytes);
|
|
cursor += 4;
|
|
|
|
let final_tx_bytes = &tx_buffer[..cursor];
|
|
|
|
Some(H::hash(&final_tx_bytes))
|
|
}
|
|
|
|
fn serialize_inputs_and_outputs(
|
|
buffer: &mut [u8; MAX_TX_LEN],
|
|
cursor: usize,
|
|
inputs: &[TxIn<H>],
|
|
outputs: &[TxOut],
|
|
) -> Option<usize> {
|
|
let mut cursor = Self::write_varint_to_buf(buffer, cursor, inputs.len() as u64)?;
|
|
|
|
for input in inputs {
|
|
if cursor + 32 > MAX_TX_LEN {
|
|
return None;
|
|
}
|
|
buffer[cursor..cursor + 32].copy_from_slice(input.prev_txid.as_ref());
|
|
cursor += 32;
|
|
|
|
if cursor + 4 > MAX_TX_LEN {
|
|
return None;
|
|
}
|
|
buffer[cursor..cursor + 4].copy_from_slice(&input.vout.to_le_bytes());
|
|
cursor += 4;
|
|
|
|
cursor = Self::write_varint_to_buf(buffer, cursor, input.script_sig.len() as u64)?;
|
|
|
|
if cursor + input.script_sig.len() > MAX_TX_LEN {
|
|
return None;
|
|
}
|
|
buffer[cursor..cursor + input.script_sig.len()].copy_from_slice(&input.script_sig);
|
|
cursor += input.script_sig.len();
|
|
|
|
if cursor + 4 > MAX_TX_LEN {
|
|
return None;
|
|
}
|
|
buffer[cursor..cursor + 4].copy_from_slice(&input.sequence.to_le_bytes());
|
|
cursor += 4;
|
|
}
|
|
|
|
cursor = Self::write_varint_to_buf(buffer, cursor, outputs.len() as u64)?;
|
|
|
|
for output in outputs {
|
|
if cursor + 8 > MAX_TX_LEN {
|
|
return None;
|
|
}
|
|
buffer[cursor..cursor + 8].copy_from_slice(&output.value.to_le_bytes());
|
|
cursor += 8;
|
|
|
|
cursor = Self::write_varint_to_buf(buffer, cursor, output.script_pubkey.len() as u64)?;
|
|
|
|
if cursor + output.script_pubkey.len() > MAX_TX_LEN {
|
|
return None;
|
|
}
|
|
buffer[cursor..cursor + output.script_pubkey.len()]
|
|
.copy_from_slice(&output.script_pubkey);
|
|
cursor += output.script_pubkey.len();
|
|
}
|
|
|
|
Some(cursor)
|
|
}
|
|
|
|
fn write_varint_to_buf(
|
|
buffer: &mut [u8; MAX_TX_LEN],
|
|
cursor: usize,
|
|
value: u64,
|
|
) -> Option<usize> {
|
|
let mut cursor = cursor;
|
|
|
|
if value < 253 {
|
|
if cursor + 1 > MAX_TX_LEN { return None; }
|
|
buffer[cursor] = value as u8;
|
|
cursor += 1;
|
|
} else if value <= 0xffff {
|
|
if cursor + 3 > MAX_TX_LEN { return None; }
|
|
buffer[cursor] = 253;
|
|
buffer[cursor + 1..cursor + 3].copy_from_slice(&(value as u16).to_le_bytes());
|
|
cursor += 3;
|
|
} else if value <= 0xffffffff {
|
|
if cursor + 5 > MAX_TX_LEN { return None; }
|
|
buffer[cursor] = 254;
|
|
buffer[cursor + 1..cursor + 5].copy_from_slice(&(value as u32).to_le_bytes());
|
|
cursor += 5;
|
|
} else {
|
|
if cursor + 9 > MAX_TX_LEN { return None; }
|
|
buffer[cursor] = 255;
|
|
buffer[cursor + 1..cursor + 9].copy_from_slice(&value.to_le_bytes());
|
|
cursor += 9;
|
|
}
|
|
|
|
Some(cursor)
|
|
}
|
|
|
|
pub fn parse_receiver_and_amount(
|
|
&self,
|
|
destination: &[u8],
|
|
prefix: &Prefix,
|
|
) -> Option<([u8; 32], u64)> {
|
|
let mut maybe_receiver = None;
|
|
let mut op_return_count = 0;
|
|
let mut total_amount: u64 = 0;
|
|
|
|
for output in self.outputs.iter() {
|
|
let script = &output.script_pubkey;
|
|
|
|
if let Some(receiver) = self.parse_receiver(script, prefix) {
|
|
op_return_count += 1;
|
|
maybe_receiver = Some(receiver);
|
|
}
|
|
|
|
if script == &destination {
|
|
total_amount = total_amount.checked_add(output.value)?;
|
|
}
|
|
}
|
|
|
|
if total_amount > 0 && op_return_count == 1 {
|
|
maybe_receiver.map(|receiver| (receiver, total_amount))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn convert_into_p2pkh(hash: &[u8]) -> Option<[u8; 25]> {
|
|
if hash.len() != 20 { return None; }
|
|
|
|
let mut script = [0u8; 25];
|
|
|
|
script[0] = 0x76; // OP_DUP
|
|
script[1] = 0xa9; // OP_HASH160
|
|
script[2] = 0x14; // OP_PUSHBYTES_20
|
|
script[3..23].copy_from_slice(hash);
|
|
script[23] = 0x88; // OP_EQUALVERIFY
|
|
script[24] = 0xac; // OP_CHECKSIG
|
|
|
|
Some(script)
|
|
}
|
|
|
|
pub fn convert_into_p2tr(pubkey: &[u8]) -> Option<[u8; 34]> {
|
|
if pubkey.len() != 32 { return None; }
|
|
|
|
let mut script = [0u8; 34];
|
|
script[0] = 0x51; // OP_1 (Taproot indicator)
|
|
script[1] = 0x20; // OP_PUSHBYTES_32
|
|
script[2..34].copy_from_slice(pubkey);
|
|
|
|
Some(script)
|
|
}
|
|
|
|
fn parse_receiver(&self, script: &[u8], prefix: &Prefix) -> Option<[u8; 32]> {
|
|
// Standard OP_RETURN with 32 bytes of data:
|
|
// script[0] = 0x6a (OP_RETURN)
|
|
// script[1] = 0x27 (OP_PUSHBYTES_39)
|
|
// script[2..9] = 7 bytes for prefix
|
|
// script[9..41] = 32 bytes of account
|
|
if script.len() == 41 && script[0] == 0x6a && script[1] == 0x27 {
|
|
if &script[2..9] == prefix.as_ref() {
|
|
let mut receiver = [0u8; 32];
|
|
receiver.copy_from_slice(&script[9..41]);
|
|
return Some(receiver)
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
pub enum VarintError {
|
|
EmptyInput,
|
|
BufferTooShort,
|
|
TypeConversionFailed,
|
|
}
|
|
|
|
fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
|
|
let first = *bytes.get(0).ok_or(VarintError::EmptyInput)?;
|
|
*bytes = &bytes[1..];
|
|
|
|
match first {
|
|
0xfd => {
|
|
if bytes.len() < 2 {
|
|
return Err(VarintError::BufferTooShort);
|
|
}
|
|
let val = u16::from_le_bytes(
|
|
bytes[..2]
|
|
.try_into()
|
|
.map_err(|_| VarintError::TypeConversionFailed)?,
|
|
);
|
|
*bytes = &bytes[2..];
|
|
Ok(val as usize)
|
|
}
|
|
0xfe => {
|
|
if bytes.len() < 4 {
|
|
return Err(VarintError::BufferTooShort);
|
|
}
|
|
let val = u32::from_le_bytes(
|
|
bytes[..4]
|
|
.try_into()
|
|
.map_err(|_| VarintError::TypeConversionFailed)?,
|
|
);
|
|
*bytes = &bytes[4..];
|
|
Ok(val as usize)
|
|
}
|
|
0xff => {
|
|
if bytes.len() < 8 {
|
|
return Err(VarintError::BufferTooShort);
|
|
}
|
|
let val = u64::from_le_bytes(
|
|
bytes[..8]
|
|
.try_into()
|
|
.map_err(|_| VarintError::TypeConversionFailed)?,
|
|
);
|
|
*bytes = &bytes[8..];
|
|
Ok(val as usize)
|
|
}
|
|
_ => Ok(first as usize),
|
|
}
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
pub enum ParseError {
|
|
InvalidLength,
|
|
ExceededMaxLimits,
|
|
TypeConversionFailed,
|
|
TransactionNotFoundInProof,
|
|
InvalidVarint(VarintError),
|
|
}
|
|
|
|
impl From<VarintError> for ParseError {
|
|
fn from(err: VarintError) -> Self {
|
|
ParseError::InvalidVarint(err)
|
|
}
|
|
}
|
|
|
|
impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Transaction<H> {
|
|
type Error = ParseError;
|
|
|
|
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
|
|
if bytes.len() < 4 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let version = i32::from_le_bytes(
|
|
bytes[..4]
|
|
.try_into()
|
|
.map_err(|_| ParseError::TypeConversionFailed)?,
|
|
);
|
|
bytes = &bytes[4..];
|
|
|
|
let input_count = read_varint(&mut bytes)?;
|
|
if input_count > MAX_INPUTS {
|
|
return Err(ParseError::ExceededMaxLimits);
|
|
}
|
|
|
|
let mut inputs_buffer: [Option<TxIn<H>>; MAX_INPUTS] = [const { None }; MAX_INPUTS];
|
|
for idx in 0..input_count {
|
|
if bytes.len() < 36 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let mut prev_txid = [0u8; 32];
|
|
prev_txid.copy_from_slice(&bytes[..32]);
|
|
let vout = u32::from_le_bytes(
|
|
bytes[32..36]
|
|
.try_into()
|
|
.map_err(|_| ParseError::TypeConversionFailed)?,
|
|
);
|
|
bytes = &bytes[36..];
|
|
|
|
let script_len = read_varint(&mut bytes)?;
|
|
if script_len > MAX_SCRIPT_LEN {
|
|
return Err(ParseError::ExceededMaxLimits);
|
|
}
|
|
if bytes.len() < script_len {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let script_sig = bytes[..script_len].to_vec();
|
|
bytes = &bytes[script_len..];
|
|
|
|
if bytes.len() < 4 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let sequence = u32::from_le_bytes(
|
|
bytes[..4]
|
|
.try_into()
|
|
.map_err(|_| ParseError::TypeConversionFailed)?,
|
|
);
|
|
bytes = &bytes[4..];
|
|
|
|
inputs_buffer[idx] = Some(TxIn {
|
|
prev_txid: H::from_slice(&prev_txid),
|
|
vout,
|
|
script_sig,
|
|
sequence,
|
|
});
|
|
}
|
|
|
|
let output_count = read_varint(&mut bytes)?;
|
|
if output_count > MAX_OUTPUTS {
|
|
return Err(ParseError::ExceededMaxLimits);
|
|
}
|
|
|
|
let mut outputs_buffer: [Option<TxOut>; MAX_OUTPUTS] = [const { None }; MAX_OUTPUTS];
|
|
for idx in 0..output_count {
|
|
if bytes.len() < 8 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let value = u64::from_le_bytes(
|
|
bytes[..8]
|
|
.try_into()
|
|
.map_err(|_| ParseError::TypeConversionFailed)?,
|
|
);
|
|
bytes = &bytes[8..];
|
|
|
|
let script_len = read_varint(&mut bytes)?;
|
|
if script_len > MAX_SCRIPT_LEN {
|
|
return Err(ParseError::ExceededMaxLimits);
|
|
}
|
|
if bytes.len() < script_len {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let script_pubkey = bytes[..script_len].to_vec();
|
|
bytes = &bytes[script_len..];
|
|
|
|
outputs_buffer[idx] = Some(TxOut {
|
|
value,
|
|
script_pubkey,
|
|
});
|
|
}
|
|
|
|
let inputs: Vec<TxIn<H>> = inputs_buffer[..input_count]
|
|
.iter_mut()
|
|
.filter_map(|opt| opt.take())
|
|
.collect();
|
|
|
|
let outputs: Vec<TxOut> = outputs_buffer[..output_count]
|
|
.iter_mut()
|
|
.filter_map(|opt| opt.take())
|
|
.collect();
|
|
|
|
if bytes.len() < 4 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
let lock_time = u32::from_le_bytes(
|
|
bytes[..4]
|
|
.try_into()
|
|
.map_err(|_| ParseError::TypeConversionFailed)?,
|
|
);
|
|
|
|
Ok(Transaction {
|
|
version,
|
|
inputs,
|
|
outputs,
|
|
lock_time,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Header<H> {
|
|
type Error = ParseError;
|
|
|
|
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
|
|
if bytes.len() < 85 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
let header_bytes = &bytes[..80];
|
|
bytes = &bytes[80..];
|
|
|
|
let mut merkle_root = [0u8; 32];
|
|
merkle_root.copy_from_slice(&header_bytes[36..68]);
|
|
|
|
if bytes.len() < 4 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let tx_count = u32::from_le_bytes(
|
|
bytes[..4]
|
|
.try_into()
|
|
.map_err(|_| ParseError::TypeConversionFailed)?,
|
|
);
|
|
bytes = &bytes[4..];
|
|
|
|
let hash_count = read_varint(&mut bytes)?;
|
|
if bytes.len() < hash_count * 32 {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
let tree_hashes_bytes = &bytes[..hash_count * 32];
|
|
bytes = &bytes[hash_count * 32..];
|
|
|
|
let tree_hashes: Vec<H::Hash> = tree_hashes_bytes
|
|
.chunks_exact(32)
|
|
.map(|chunk| H::from_slice(chunk))
|
|
.collect();
|
|
|
|
let flags_count = read_varint(&mut bytes)?;
|
|
if bytes.len() < flags_count {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
let flags = bytes[..flags_count].to_vec();
|
|
bytes = &bytes[flags_count..];
|
|
|
|
if !bytes.is_empty() {
|
|
return Err(ParseError::InvalidLength);
|
|
}
|
|
|
|
Ok(Self {
|
|
merkle_root: H::from_slice(&merkle_root),
|
|
tx_count,
|
|
tree_hashes,
|
|
flags,
|
|
})
|
|
}
|
|
}
|