remove Header struct and implement stateless merkle verification

Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
Uncle Stinky 2026-10-09 14:56:43 +03:00
parent 84b6dfad9f
commit f3e7122d43
Signed by: st1nky
GPG Key ID: 016064BD97603B40
4 changed files with 109 additions and 92 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-helpers"
version = "0.0.12"
version = "0.0.13"
description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components."
license.workspace = true
authors.workspace = true

View File

@ -3,6 +3,8 @@ use sp_std::vec::Vec;
const MAX_INPUTS: usize = 3;
const MAX_OUTPUTS: usize = 4;
const MAX_FLAGS_LEN: usize = 8;
const MAX_HASHES_LEN: usize = 16;
const MAX_SCRIPT_LEN: usize = 512;
pub const MAX_TX_LEN: usize = 4096;
@ -27,20 +29,17 @@ pub struct Transaction<H: GhostHasher> {
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>,
}
pub struct Header<H: GhostHasher>(sp_std::marker::PhantomData<H>);
impl<H: GhostHasher> Header<H> {
fn calc_tree_width(&self, h: u32) -> u32 {
(self.tx_count + (1 << h) - 1) >> h
fn calc_tree_width(tx_count: u32, h: u32) -> u32 {
(tx_count + (1 << h) - 1) >> h
}
fn traverse(
&self,
tx_count: u32,
flags: &[u8],
tree_hashes: &[H::Hash],
height: u32,
pos: u32,
bits_used: &mut u32,
@ -51,19 +50,19 @@ impl<H: GhostHasher> Header<H> {
let byte_idx = (*bits_used / 8) as usize;
let bit_idx = (*bits_used % 8) as u8;
if byte_idx >= self.flags.len() {
if byte_idx >= flags.len() {
return Err(ParseError::InvalidVarint(VarintError::BufferTooShort));
}
let parent_of_match = ((self.flags[byte_idx] >> bit_idx) & 1) == 1;
let parent_of_match = ((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() {
if *hash_used as usize >= tree_hashes.len() {
return Err(ParseError::InvalidLength);
}
let curr_hash = self.tree_hashes[*hash_used as usize];
let curr_hash = tree_hashes[*hash_used as usize];
*hash_used += 1;
if height == 0 && parent_of_match && curr_hash == target_txid {
@ -73,7 +72,10 @@ impl<H: GhostHasher> Header<H> {
return Ok(curr_hash);
}
let left = self.traverse(
let left = Self::traverse(
tx_count,
flags,
tree_hashes,
height - 1,
pos * 2,
bits_used,
@ -82,8 +84,11 @@ impl<H: GhostHasher> Header<H> {
target_txid,
)?;
let right = if pos * 2 + 1 < self.calc_tree_width(height - 1) {
let right = self.traverse(
let right = if pos * 2 + 1 < Self::calc_tree_width(tx_count, height - 1) {
let right = Self::traverse(
tx_count,
flags,
tree_hashes,
height - 1,
pos * 2 + 1,
bits_used,
@ -109,17 +114,83 @@ impl<H: GhostHasher> Header<H> {
Ok(current_node_hash)
}
pub fn extract_proof(&self, target_txid: H::Hash) -> Result<H::Hash, ParseError> {
pub fn verify_utxo_merkle_proof(
txproof_bytes: &[u8],
target_txid: H::Hash,
) -> Result<(), ParseError> {
if txproof_bytes.len() < 85 {
return Err(ParseError::InvalidLength);
}
let header_bytes = &txproof_bytes[..80];
let mut bytes = &txproof_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 hash_count > MAX_HASHES_LEN {
return Err(ParseError::InvalidLength);
}
let hash_count_bytes = hash_count
.checked_mul(32)
.ok_or(ParseError::TypeConversionFailed)?;
if bytes.len() < hash_count_bytes {
return Err(ParseError::InvalidLength);
}
let tree_hashes_bytes = &bytes[..hash_count_bytes];
bytes = &bytes[hash_count_bytes..];
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 flags_count > MAX_FLAGS_LEN {
return Err(ParseError::InvalidLength);
}
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);
}
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 {
while (1 << max_height) < tx_count {
max_height += 1;
}
let computed_root = self.traverse(
let computed_root = Self::traverse(
tx_count,
&flags,
&tree_hashes,
max_height,
0,
&mut bits_used,
@ -128,19 +199,23 @@ impl<H: GhostHasher> Header<H> {
target_txid,
)?;
if hash_used as usize != self.tree_hashes.len() {
if hash_used as usize != tree_hashes.len() {
return Err(ParseError::InvalidLength);
}
if (bits_used + 7) / 8 != self.flags.len() as u32 {
if (bits_used + 7) / 8 != flags.len() as u32 {
return Err(ParseError::InvalidLength);
}
if matched_index.is_some() {
Ok(computed_root)
} else {
Err(ParseError::TransactionNotFoundInProof)
if matched_index.is_none() {
return Err(ParseError::TransactionNotFoundInProof);
}
if computed_root != H::from_slice(&merkle_root) {
return Err(ParseError::InvalidMerkleRoot);
}
Ok(())
}
}
@ -401,6 +476,7 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
pub enum ParseError {
InvalidLength,
ExceededMaxLimits,
InvalidMerkleRoot,
TypeConversionFailed,
TransactionNotFoundInProof,
InvalidVarint(VarintError),
@ -534,60 +610,3 @@ impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Transaction<H> {
})
}
}
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,
})
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-weaver"
version = "0.0.15"
version = "0.0.16"
description = "Weaving a secure cryptographic tapestry across different external chains."
license.workspace = true
authors.workspace = true

View File

@ -483,7 +483,7 @@ impl UtxoThreadProof {
pub fn verify_thread_proof<Balance>(
&self,
root_hash: H256,
block_hash: H256,
gatekeeper_ref: &[u8],
) -> Option<(AccountId32, Balance)>
where
@ -498,7 +498,7 @@ impl UtxoThreadProof {
let is_chain_valid = verify_hash_ancestry::<UtxoSha2Hasher, _>(
header_bytes,
self.ancestry_headers.iter(),
root_hash,
block_hash,
);
if !is_chain_valid { return None; }
@ -507,13 +507,11 @@ impl UtxoThreadProof {
self.getrawtransaction_bytes.as_ref()
).ok()?;
let header = Header::<UtxoSha2Hasher>::try_from(
self.gettxproof_bytes.as_ref(),
).ok()?;
let txid_preimage = transaction.compute_txid()?;
let root_hash = header.extract_proof(txid_preimage).ok()?;
if root_hash != header.merkle_root { return None; }
Header::<UtxoSha2Hasher>::verify_utxo_merkle_proof(
self.gettxproof_bytes.as_ref(),
txid_preimage,
).ok()?;
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
let destination_script = Transaction::<UtxoSha2Hasher>::convert_into_p2tr(gatekeeper_ref)?;