remove Header struct and implement stateless merkle verification
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
parent
84b6dfad9f
commit
f3e7122d43
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ghost-helpers"
|
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."
|
description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components."
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|||||||
@ -3,6 +3,8 @@ use sp_std::vec::Vec;
|
|||||||
|
|
||||||
const MAX_INPUTS: usize = 3;
|
const MAX_INPUTS: usize = 3;
|
||||||
const MAX_OUTPUTS: usize = 4;
|
const MAX_OUTPUTS: usize = 4;
|
||||||
|
const MAX_FLAGS_LEN: usize = 8;
|
||||||
|
const MAX_HASHES_LEN: usize = 16;
|
||||||
const MAX_SCRIPT_LEN: usize = 512;
|
const MAX_SCRIPT_LEN: usize = 512;
|
||||||
pub const MAX_TX_LEN: usize = 4096;
|
pub const MAX_TX_LEN: usize = 4096;
|
||||||
|
|
||||||
@ -27,20 +29,17 @@ pub struct Transaction<H: GhostHasher> {
|
|||||||
lock_time: u32,
|
lock_time: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Header<H: GhostHasher> {
|
pub struct Header<H: GhostHasher>(sp_std::marker::PhantomData<H>);
|
||||||
pub merkle_root: H::Hash,
|
|
||||||
pub tx_count: u32,
|
|
||||||
pub tree_hashes: Vec<H::Hash>,
|
|
||||||
pub flags: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H: GhostHasher> Header<H> {
|
impl<H: GhostHasher> Header<H> {
|
||||||
fn calc_tree_width(&self, h: u32) -> u32 {
|
fn calc_tree_width(tx_count: u32, h: u32) -> u32 {
|
||||||
(self.tx_count + (1 << h) - 1) >> h
|
(tx_count + (1 << h) - 1) >> h
|
||||||
}
|
}
|
||||||
|
|
||||||
fn traverse(
|
fn traverse(
|
||||||
&self,
|
tx_count: u32,
|
||||||
|
flags: &[u8],
|
||||||
|
tree_hashes: &[H::Hash],
|
||||||
height: u32,
|
height: u32,
|
||||||
pos: u32,
|
pos: u32,
|
||||||
bits_used: &mut u32,
|
bits_used: &mut u32,
|
||||||
@ -51,19 +50,19 @@ impl<H: GhostHasher> Header<H> {
|
|||||||
let byte_idx = (*bits_used / 8) as usize;
|
let byte_idx = (*bits_used / 8) as usize;
|
||||||
let bit_idx = (*bits_used % 8) as u8;
|
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));
|
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;
|
*bits_used += 1;
|
||||||
|
|
||||||
if height == 0 || !parent_of_match {
|
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);
|
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;
|
*hash_used += 1;
|
||||||
|
|
||||||
if height == 0 && parent_of_match && curr_hash == target_txid {
|
if height == 0 && parent_of_match && curr_hash == target_txid {
|
||||||
@ -73,7 +72,10 @@ impl<H: GhostHasher> Header<H> {
|
|||||||
return Ok(curr_hash);
|
return Ok(curr_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
let left = self.traverse(
|
let left = Self::traverse(
|
||||||
|
tx_count,
|
||||||
|
flags,
|
||||||
|
tree_hashes,
|
||||||
height - 1,
|
height - 1,
|
||||||
pos * 2,
|
pos * 2,
|
||||||
bits_used,
|
bits_used,
|
||||||
@ -82,8 +84,11 @@ impl<H: GhostHasher> Header<H> {
|
|||||||
target_txid,
|
target_txid,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let right = if pos * 2 + 1 < self.calc_tree_width(height - 1) {
|
let right = if pos * 2 + 1 < Self::calc_tree_width(tx_count, height - 1) {
|
||||||
let right = self.traverse(
|
let right = Self::traverse(
|
||||||
|
tx_count,
|
||||||
|
flags,
|
||||||
|
tree_hashes,
|
||||||
height - 1,
|
height - 1,
|
||||||
pos * 2 + 1,
|
pos * 2 + 1,
|
||||||
bits_used,
|
bits_used,
|
||||||
@ -109,17 +114,83 @@ impl<H: GhostHasher> Header<H> {
|
|||||||
Ok(current_node_hash)
|
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 bits_used = 0u32;
|
||||||
let mut hash_used = 0u32;
|
let mut hash_used = 0u32;
|
||||||
|
|
||||||
let mut matched_index = None;
|
let mut matched_index = None;
|
||||||
let mut max_height = 0;
|
let mut max_height = 0;
|
||||||
while (1 << max_height) < self.tx_count {
|
|
||||||
|
while (1 << max_height) < tx_count {
|
||||||
max_height += 1;
|
max_height += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let computed_root = self.traverse(
|
let computed_root = Self::traverse(
|
||||||
|
tx_count,
|
||||||
|
&flags,
|
||||||
|
&tree_hashes,
|
||||||
max_height,
|
max_height,
|
||||||
0,
|
0,
|
||||||
&mut bits_used,
|
&mut bits_used,
|
||||||
@ -128,19 +199,23 @@ impl<H: GhostHasher> Header<H> {
|
|||||||
target_txid,
|
target_txid,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if hash_used as usize != self.tree_hashes.len() {
|
if hash_used as usize != tree_hashes.len() {
|
||||||
return Err(ParseError::InvalidLength);
|
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);
|
return Err(ParseError::InvalidLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matched_index.is_some() {
|
if matched_index.is_none() {
|
||||||
Ok(computed_root)
|
return Err(ParseError::TransactionNotFoundInProof);
|
||||||
} else {
|
|
||||||
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 {
|
pub enum ParseError {
|
||||||
InvalidLength,
|
InvalidLength,
|
||||||
ExceededMaxLimits,
|
ExceededMaxLimits,
|
||||||
|
InvalidMerkleRoot,
|
||||||
TypeConversionFailed,
|
TypeConversionFailed,
|
||||||
TransactionNotFoundInProof,
|
TransactionNotFoundInProof,
|
||||||
InvalidVarint(VarintError),
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ghost-weaver"
|
name = "ghost-weaver"
|
||||||
version = "0.0.15"
|
version = "0.0.16"
|
||||||
description = "Weaving a secure cryptographic tapestry across different external chains."
|
description = "Weaving a secure cryptographic tapestry across different external chains."
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|||||||
@ -483,7 +483,7 @@ impl UtxoThreadProof {
|
|||||||
|
|
||||||
pub fn verify_thread_proof<Balance>(
|
pub fn verify_thread_proof<Balance>(
|
||||||
&self,
|
&self,
|
||||||
root_hash: H256,
|
block_hash: H256,
|
||||||
gatekeeper_ref: &[u8],
|
gatekeeper_ref: &[u8],
|
||||||
) -> Option<(AccountId32, Balance)>
|
) -> Option<(AccountId32, Balance)>
|
||||||
where
|
where
|
||||||
@ -498,7 +498,7 @@ impl UtxoThreadProof {
|
|||||||
let is_chain_valid = verify_hash_ancestry::<UtxoSha2Hasher, _>(
|
let is_chain_valid = verify_hash_ancestry::<UtxoSha2Hasher, _>(
|
||||||
header_bytes,
|
header_bytes,
|
||||||
self.ancestry_headers.iter(),
|
self.ancestry_headers.iter(),
|
||||||
root_hash,
|
block_hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
if !is_chain_valid { return None; }
|
if !is_chain_valid { return None; }
|
||||||
@ -507,13 +507,11 @@ impl UtxoThreadProof {
|
|||||||
self.getrawtransaction_bytes.as_ref()
|
self.getrawtransaction_bytes.as_ref()
|
||||||
).ok()?;
|
).ok()?;
|
||||||
|
|
||||||
let header = Header::<UtxoSha2Hasher>::try_from(
|
|
||||||
self.gettxproof_bytes.as_ref(),
|
|
||||||
).ok()?;
|
|
||||||
|
|
||||||
let txid_preimage = transaction.compute_txid()?;
|
let txid_preimage = transaction.compute_txid()?;
|
||||||
let root_hash = header.extract_proof(txid_preimage).ok()?;
|
Header::<UtxoSha2Hasher>::verify_utxo_merkle_proof(
|
||||||
if root_hash != header.merkle_root { return None; }
|
self.gettxproof_bytes.as_ref(),
|
||||||
|
txid_preimage,
|
||||||
|
).ok()?;
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
||||||
let destination_script = Transaction::<UtxoSha2Hasher>::convert_into_p2tr(gatekeeper_ref)?;
|
let destination_script = Transaction::<UtxoSha2Hasher>::convert_into_p2tr(gatekeeper_ref)?;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user