ghost-node/pallets/helpers/src/utxo.rs
Uncle Stretch d83ee59508
let the EXODUS begin...
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-08-29 14:48:50 +03:00

746 lines
22 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 = 34;
const MAX_WITNESS_STACK_DEPTH: usize = 3;
const MAX_WITNESS_ITEMS_LEN: usize = 73;
pub const MAX_TX_LEN: usize = 512;
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 Witness {
pub stack: Vec<Vec<u8>>,
}
pub enum Transaction<H: GhostHasher> {
Legacy {
version: i32,
inputs: Vec<TxIn<H>>,
outputs: Vec<TxOut>,
lock_time: u32,
},
SegWit {
version: i32,
inputs: Vec<TxIn<H>>,
outputs: Vec<TxOut>,
witnesses: Vec<Witness>,
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, inputs, outputs, lock_time) = match self {
Self::Legacy {
version,
inputs,
outputs,
lock_time,
} => (version, inputs, outputs, lock_time),
Self::SegWit {
version,
inputs,
outputs,
lock_time,
..
} => (version, inputs, outputs, lock_time),
};
let version_bytes = version.to_le_bytes();
let lock_time_bytes = 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, inputs, 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 validate<I, A>(
&self,
o_index_i: I,
w_index_i: I,
destination: &[u8],
expected_amount_a: A,
) -> Option<[u8; 32]>
where
I: sp_runtime::traits::UniqueSaturatedInto<usize>,
A: sp_runtime::traits::UniqueSaturatedInto<u128>,
{
let o_index: usize = o_index_i.unique_saturated_into();
let w_index: usize = w_index_i.unique_saturated_into();
let expected_amount_u128: u128 = expected_amount_a.unique_saturated_into();
let expected_amount: u64 = expected_amount_u128.try_into().ok()?;
match self {
Self::Legacy { outputs, .. } => {
let output = outputs.get(o_index)?;
if expected_amount != output.value {
return None;
}
if output.script_pubkey.len() != 57 {
return None;
}
let slice = &output.script_pubkey;
let receiver_address = self.parse_receiver(slice)?;
if receiver_address != destination {
return None;
}
let mut extra_data = [0u8; 32];
extra_data.copy_from_slice(&slice[25..57]);
Some(extra_data)
}
Self::SegWit {
witnesses, outputs, ..
} => {
let output = outputs.get(o_index)?;
if expected_amount != output.value {
return None;
}
let receiver_address = self.parse_receiver(&output.script_pubkey)?;
if receiver_address != destination {
return None;
}
let witness = witnesses.get(w_index)?;
let target_account: [u8; 32];
// NOTE: change it right after the real bridging tx
// will take place, or find something similar
#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
{
let _ = witness.stack;
target_account = [105u8; 32]; // 0x69 in decimal
}
#[cfg(not(any(feature = "runtime-benchmarks", feature = "std")))]
{
if witness.stack.len() < 3 || witness.stack[2].len() != 32 {
return None;
}
target_account = witness.stack[2][..32].try_into().ok()?;
}
Some(target_account)
}
}
}
fn parse_receiver(&self, script: &[u8]) -> Option<[u8; 32]> {
let mut receiver = [0u8; 32];
// 1. Parse Pay-to-Taproot (P2TR / SegWit v1)
// Markers: OP_1 (0x51) + OP_PUSHBYTES_32 (0x20) + 32 bytes key
if script.len() == 34 && script[0] == 0x51 && script[1] == 0x20 {
receiver.copy_from_slice(&script[2..34]);
return Some(receiver);
}
// 2. Parse Pay-to-Witness-Script-Hash (P2WSH / SegWit v0)
// Markers: OP_0 (0x00) + OP_PUSHBYTES_32 (0x20) + 32 bytes hash script
if script.len() == 34 && script[0] == 0x00 && script[1] == 0x20 {
receiver.copy_from_slice(&script[2..34]);
return Some(receiver);
}
// 3a. Parse Native SegWit 20-bytes key (P2WPKH) zero-padding
// Markers: 22 bytes len, starts with OP_0 (0x00), next OP_PUSHBYTES_20 (0x14)
if script.len() == 22 && script[0] == 0x00 && script[1] == 0x14 {
receiver[..20].copy_from_slice(&script[2..22]);
return Some(receiver);
}
// 3b. Parse Pay-to-Script-Hash (P2SH)
// Script: OP_HASH160 (0xa9) OP_PUSHBYTES_20 (0x14) + 20 bytes hash + OP_EQUAL (0x87)
if script.len() == 23 && script[0] == 0xa9 && script[1] == 0x14 && script[22] == 0x87 {
receiver[..20].copy_from_slice(&script[2..22]);
return Some(receiver);
}
// 4. Parse Legacy (P2PKH) zero-padding
// Script: OP_DUP(0x76) OP_HASH160(0xa9) OP_PUSHBYTES_20(0x14) +
// 20 bytes hash + OP_EQUALVERIFY(0x88) OP_CHECKSIG(0xac)
if script.len() == 25
&& script[0] == 0x76
&& script[1] == 0xa9
&& script[2] == 0x14
&& script[23] == 0x88
&& script[24] == 0xac
{
receiver[..20].copy_from_slice(&script[3..23]);
return Some(receiver);
}
// 5. Custom one: Legacy (P2PKH) + 32 bytes
// Script: [0..25] — basic P2PKH, [25..57] — 32 bytes of extra data
if script.len() == 57
&& script[0] == 0x76 // OP_DUP
&& script[1] == 0xa9 // OP_HASH160
&& script[2] == 0x14 // OP_PUSHBYTES_20
&& script[23] == 0x88 // OP_EQUALVERIFY
&& script[24] == 0xac
// OP_CHECKSIG
{
receiver[..20].copy_from_slice(&script[3..23]);
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 is_segwit = bytes.len() >= 2 && bytes[0] == 0x00 && bytes[1] == 0x01;
if is_segwit {
bytes = &bytes[2..];
}
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 is_segwit {
let mut witnesses_buffer: [Option<Witness>; MAX_INPUTS] = [const { None }; MAX_INPUTS];
for idx in 0..input_count {
let item_count = read_varint(&mut bytes)?;
if item_count > MAX_WITNESS_STACK_DEPTH {
return Err(ParseError::ExceededMaxLimits);
}
let mut stack_buffer: [Option<Vec<u8>>; MAX_WITNESS_STACK_DEPTH] =
[const { None }; MAX_WITNESS_STACK_DEPTH];
for s_idx in 0..item_count {
let item_len = read_varint(&mut bytes)?;
if item_len > MAX_WITNESS_ITEMS_LEN {
return Err(ParseError::ExceededMaxLimits);
}
if bytes.len() < item_len {
return Err(ParseError::InvalidLength);
}
stack_buffer[s_idx] = Some(bytes[..item_len].to_vec());
bytes = &bytes[item_len..];
}
let stack: Vec<Vec<u8>> = stack_buffer[..item_count]
.iter_mut()
.filter_map(|opt| opt.take())
.collect();
witnesses_buffer[idx] = Some(Witness { stack });
}
let witnesses: Vec<Witness> = witnesses_buffer[..input_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::SegWit {
version,
inputs,
outputs,
witnesses,
lock_time,
})
} else {
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::Legacy {
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,
})
}
}