ghost-node/pallets/helpers/src/utxo.rs
2026-09-10 21:21:22 +03:00

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,
})
}
}