Merge branch 'pallet-exodus' into pallet-weaver
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
commit
d7ac7885b6
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ghost-exodus"
|
name = "ghost-exodus"
|
||||||
version = "0.0.17"
|
version = "0.0.19"
|
||||||
description = "Threshold signature generation with DKG included"
|
description = "Threshold signature generation with DKG included"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|||||||
@ -85,8 +85,7 @@ mod benchmarks {
|
|||||||
dkg_state.insert_index(authority_index);
|
dkg_state.insert_index(authority_index);
|
||||||
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
|
||||||
|
|
||||||
let mut rng = Pallet::<T>::get_offchain_rng(authority_index);
|
let mut rng = ChaCha20Rng::seed_from_u64(694201337);
|
||||||
|
|
||||||
let (_, public_package) = network_curve.dkg_part1(
|
let (_, public_package) = network_curve.dkg_part1(
|
||||||
authority_index,
|
authority_index,
|
||||||
max_signers,
|
max_signers,
|
||||||
@ -353,11 +352,8 @@ mod benchmarks {
|
|||||||
dummy_verifying_key,
|
dummy_verifying_key,
|
||||||
).expect("Bounded verifying key should be correct");
|
).expect("Bounded verifying key should be correct");
|
||||||
|
|
||||||
let metadata_hashes = [
|
let metadata_hashes = [dummy_merkle_root.as_ref(), bounded_verifying_key.as_ref()];
|
||||||
SubstrateBlake2Hasher::hash(dummy_merkle_root.as_ref()),
|
let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
|
||||||
SubstrateBlake2Hasher::hash(bounded_verifying_key.as_ref()),
|
|
||||||
];
|
|
||||||
let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
|
|
||||||
|
|
||||||
let verification_key = (network_curve, dkg_index, verifying_hash);
|
let verification_key = (network_curve, dkg_index, verifying_hash);
|
||||||
let participants = Verifications::<T>::get(verification_key);
|
let participants = Verifications::<T>::get(verification_key);
|
||||||
|
|||||||
@ -31,7 +31,7 @@ use sp_io::hashing::blake2_256;
|
|||||||
|
|
||||||
use ghost_helpers::{
|
use ghost_helpers::{
|
||||||
get_byzantium_quorum_threshold, SubstrateBlake2Hasher,
|
get_byzantium_quorum_threshold, SubstrateBlake2Hasher,
|
||||||
hash_chain::sequential_hash,
|
hash_chain::cumulative_hash,
|
||||||
networks::{NetworkData, NetworkCurve, NetworkType},
|
networks::{NetworkData, NetworkCurve, NetworkType},
|
||||||
bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap},
|
bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap},
|
||||||
};
|
};
|
||||||
@ -40,7 +40,6 @@ use ghost_helpers::{
|
|||||||
use ghost_traits::exodus::IdentifierConverter;
|
use ghost_traits::exodus::IdentifierConverter;
|
||||||
|
|
||||||
use ghost_traits::{
|
use ghost_traits::{
|
||||||
hashing::GhostHasher,
|
|
||||||
bounded_bitmap::{
|
bounded_bitmap::{
|
||||||
BoundedBitmapGenerator, BoundedBitmapWriter, BoundedBitmapReader,
|
BoundedBitmapGenerator, BoundedBitmapWriter, BoundedBitmapReader,
|
||||||
BoundedBitmapIterable,
|
BoundedBitmapIterable,
|
||||||
@ -1254,11 +1253,9 @@ pub mod pallet {
|
|||||||
return Err(Error::<T>::Round5PackageInvalidProof.into());
|
return Err(Error::<T>::Round5PackageInvalidProof.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let metadata_hashes = [
|
let metadata_hashes = [round5.merkle_root.as_ref(), round5.verifying_key.as_ref()];
|
||||||
SubstrateBlake2Hasher::hash(round5.merkle_root.as_ref()),
|
let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
|
||||||
SubstrateBlake2Hasher::hash(round5.verifying_key.as_ref()),
|
|
||||||
];
|
|
||||||
let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
|
|
||||||
let state = QualificationDkgState::<T>::get(&network_curve);
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
||||||
ensure!(state.is_fifth_phase(), Error::<T>::DkgWrongRound);
|
ensure!(state.is_fifth_phase(), Error::<T>::DkgWrongRound);
|
||||||
|
|
||||||
@ -3559,12 +3556,10 @@ impl<T: Config> Pallet<T> {
|
|||||||
let authority_index = exodus_storage.authority_index;
|
let authority_index = exodus_storage.authority_index;
|
||||||
let network_curve = exodus_storage.network_curve;
|
let network_curve = exodus_storage.network_curve;
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
let exodus_storage_encoded = exodus_storage.encode();
|
||||||
let mut rng = Self::get_offchain_rng();
|
let rng_context = [exodus_storage_encoded.as_slice(), secret_share];
|
||||||
|
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
|
||||||
let mut rng = Self::get_offchain_rng(authority_index);
|
|
||||||
|
|
||||||
|
let mut rng = Self::get_offchain_rng(&rng_context);
|
||||||
let (nonce, hiding_commitment, binding_commitment) = network_curve
|
let (nonce, hiding_commitment, binding_commitment) = network_curve
|
||||||
.generate_nonce_commitment(secret_share, &mut rng)?;
|
.generate_nonce_commitment(secret_share, &mut rng)?;
|
||||||
|
|
||||||
@ -3776,12 +3771,15 @@ impl<T: Config> Pallet<T> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
let dkg_storage_encoded = dkg_storage.encode();
|
||||||
let mut rng = Self::get_offchain_rng();
|
let qualification_state_encoded = qualification_state.encode();
|
||||||
|
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
let rng_context = [
|
||||||
let mut rng = Self::get_offchain_rng(authority_index);
|
dkg_storage_encoded.as_slice(),
|
||||||
|
qualification_state_encoded.as_slice(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut rng = Self::get_offchain_rng(&rng_context);
|
||||||
let (secret_package, public_package) = network_curve
|
let (secret_package, public_package) = network_curve
|
||||||
.dkg_part1(authority_index, max_signers, min_signers, &mut rng)?;
|
.dkg_part1(authority_index, max_signers, min_signers, &mut rng)?;
|
||||||
|
|
||||||
@ -4736,11 +4734,17 @@ impl<T: Config> Pallet<T> {
|
|||||||
dkg_index: DkgIndex,
|
dkg_index: DkgIndex,
|
||||||
network_curve: NetworkCurve,
|
network_curve: NetworkCurve,
|
||||||
) {
|
) {
|
||||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
let authority_index_bytes = authority_index.to_le_bytes();
|
||||||
let mut rng = Self::get_offchain_rng();
|
let dkg_index_bytes = dkg_index.to_le_bytes();
|
||||||
|
let network_curve_bytes = [network_curve as u8];
|
||||||
|
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
let rng_context = [
|
||||||
let mut rng = Self::get_offchain_rng(authority_index);
|
round1_secret_package,
|
||||||
|
authority_index_bytes.as_slice(),
|
||||||
|
dkg_index_bytes.as_slice(),
|
||||||
|
network_curve_bytes.as_slice(),
|
||||||
|
];
|
||||||
|
let mut rng = Self::get_offchain_rng(&rng_context);
|
||||||
|
|
||||||
for (&receiver_index, round2_package) in round2_packages.iter() {
|
for (&receiver_index, round2_package) in round2_packages.iter() {
|
||||||
if receiver_index == authority_index { continue; }
|
if receiver_index == authority_index { continue; }
|
||||||
@ -4811,26 +4815,13 @@ impl<T: Config> Pallet<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
fn get_offchain_rng(context: &[&[u8]]) -> ChaCha20Rng {
|
||||||
fn get_offchain_rng(authority_index: AuthIndex) -> ChaCha20Rng {
|
|
||||||
let mut seed = [0u8; 32];
|
|
||||||
|
|
||||||
let unique_id = (authority_index as u64).saturating_add(1);
|
|
||||||
let index_bytes = unique_id.to_le_bytes();
|
|
||||||
|
|
||||||
for (i, &byte) in index_bytes.iter().enumerate() {
|
|
||||||
if i < seed.len() {
|
|
||||||
seed[i] ^= byte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rand_chacha::ChaCha20Rng::from_seed(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
|
||||||
fn get_offchain_rng() -> ChaCha20Rng {
|
|
||||||
let seed = sp_io::offchain::random_seed();
|
let seed = sp_io::offchain::random_seed();
|
||||||
ChaCha20Rng::from_seed(seed)
|
let seeded_context = sp_std::iter::once(&seed[..])
|
||||||
|
.chain(context.iter().copied());
|
||||||
|
|
||||||
|
let hashed_seed = cumulative_hash::<SubstrateBlake2Hasher, _>(seeded_context);
|
||||||
|
ChaCha20Rng::from_seed(hashed_seed.to_fixed_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_storage_key(first: &[u8], second: &[u8]) -> Vec<u8> {
|
fn create_storage_key(first: &[u8], second: &[u8]) -> Vec<u8> {
|
||||||
|
|||||||
@ -75,7 +75,7 @@ impl<ExodusInnerType> ExodusStorageWrapper<ExodusInnerType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Encode)]
|
||||||
pub struct ExodusStorage<BlockNumber, NetworkCurve>
|
pub struct ExodusStorage<BlockNumber, NetworkCurve>
|
||||||
where
|
where
|
||||||
BlockNumber: Default,
|
BlockNumber: Default,
|
||||||
@ -365,7 +365,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy)]
|
#[derive(Default, Encode, Clone, Copy)]
|
||||||
pub struct DkgStorage<BlockNumber, NetworkCurve>
|
pub struct DkgStorage<BlockNumber, NetworkCurve>
|
||||||
where
|
where
|
||||||
BlockNumber: Copy + Clone,
|
BlockNumber: Copy + Clone,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ghost-helpers"
|
name = "ghost-helpers"
|
||||||
version = "0.0.11"
|
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,6 +432,9 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| VarintError::TypeConversionFailed)?,
|
.map_err(|_| VarintError::TypeConversionFailed)?,
|
||||||
);
|
);
|
||||||
|
if val < 0xfd {
|
||||||
|
return Err(VarintError::TypeConversionFailed);
|
||||||
|
}
|
||||||
*bytes = &bytes[2..];
|
*bytes = &bytes[2..];
|
||||||
Ok(val as usize)
|
Ok(val as usize)
|
||||||
}
|
}
|
||||||
@ -369,6 +447,9 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| VarintError::TypeConversionFailed)?,
|
.map_err(|_| VarintError::TypeConversionFailed)?,
|
||||||
);
|
);
|
||||||
|
if val < 0x10000 {
|
||||||
|
return Err(VarintError::TypeConversionFailed);
|
||||||
|
}
|
||||||
*bytes = &bytes[4..];
|
*bytes = &bytes[4..];
|
||||||
Ok(val as usize)
|
Ok(val as usize)
|
||||||
}
|
}
|
||||||
@ -381,6 +462,9 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| VarintError::TypeConversionFailed)?,
|
.map_err(|_| VarintError::TypeConversionFailed)?,
|
||||||
);
|
);
|
||||||
|
if val < 0x100000000 {
|
||||||
|
return Err(VarintError::TypeConversionFailed);
|
||||||
|
}
|
||||||
*bytes = &bytes[8..];
|
*bytes = &bytes[8..];
|
||||||
Ok(val as usize)
|
Ok(val as usize)
|
||||||
}
|
}
|
||||||
@ -392,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),
|
||||||
@ -525,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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -801,6 +801,116 @@ fn should_successfully_pull_utxo_thread_and_mint_currency() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_successfully_pull_utxo_thread_and_reject_nominal_varint_encoding() {
|
||||||
|
let pair = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
|
||||||
|
let incoming_share = 0;
|
||||||
|
|
||||||
|
let network_id = 69;
|
||||||
|
let network_data = NetworkDataBuilder::default()
|
||||||
|
.with_network_type(NetworkType::Utxo)
|
||||||
|
.with_incoming_share(incoming_share)
|
||||||
|
.with_gatekeeper(
|
||||||
|
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
|
||||||
|
hex::decode("4635b1794a22bfbe6c5c5eba17b693f4aaf0e348")
|
||||||
|
.expect("Bytes32 from UTXO should be valid"),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let authorities = vec![pair.clone()];
|
||||||
|
let networks = vec![(network_id, network_data, Default::default())];
|
||||||
|
|
||||||
|
// Real world tx at block #481838
|
||||||
|
// Block https://blockstream.info/block/0000000000000000011bdccb39e19e942a60edffdccd9779653c5b46b337904a
|
||||||
|
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
|
||||||
|
|
||||||
|
let payload = (
|
||||||
|
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "gettxoutproof", "params": [["4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e"], "0000000000000000011bdccb39e19e942a60edffdccd9779653c5b46b337904a"]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
|
||||||
|
hex::decode("00000020375a14cfcf5730daa8ff8d7a5bd2cf59fba797e289c2e0000000000000000000529cd017146361b57e6b7353b16d8d38d32f92ee842039dd779d35bfbfa2f3e53f649e59e93c01187900ea9fb40600000cfbd7b718845d123b1ae1a7fd3bce9c97acba097dc912c4ac67a920395db07ba4eef685960869ba70e0ca38890565c97dc11f1d7ae538e7b509069f81e38eff1a2e4a6b66fc4233d7d0580af4d195c7f0b274849f19d405dfc6b930bb9edd3f4a4c1f1e9fa3a336c50e8ea16039e4db4bef7ff5946e3a019937bdf626c2336d0e3926a83a575218f84e6dc55ffb9d0997a41e4b4d5425a85f22160c9f52362a95021a573ff96bb429bd80a8ec9dcb899971553efe91b6a0fe57c4694c3e8da8dc7f1babd514f2746da1ed06f08664baed853c334e0a3917d0c859b19ee14f448f77e624822070db3b606d5092027ca9ecf7e0243e6c151081021ebb3ddcdd28c1b8f9fdcffebd1962590477675f3d29d52602a1e7e941f772a3bbff42cf2eb6e74a1124bf424f767e294ae6253a21602c7c6b0381743dd6dcd57b025983e0e34375b24f67ac8beb00713d2f70594bbf797d5d94c061cb9965e6458f2f017f983d7ce37361fa13fda372f595dc1f22895ab93339457c72c480fed1c4d1164a242303ff3600").expect("Bytes32 from UTXO should be valid"),
|
||||||
|
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getrawtransaction", "params": ["4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
|
||||||
|
hex::decode("010000000299a43fae581f80baf44e021ddf7b11995f184db282d6e48c22248b9fe79c43bb010000008b483045022100e9db01784dcd879c65d66127c2d52a4f9244321d15796893d6dccadaa1146e3d02203d9bb80f8df8b66692d3d9517f7cbb898d7cc40a30b175432322e1956c9b973a0141048097e4f8bb8ee104d5c2ece11d42f7e4719df8ddba459ecadecaba87d233c81a3c14830371392dd8ed030ef970450c0b7af48d9db4ba20c046896481602eb9dbffffffffb3fd05bc6000a5a0384e9a613d452e9307568df0a964907068e00403a0a0e09e020000006a47304402205bb1cbad4b29421648c1e22cac32a56aa06c933fe655559823fffb71ffebbc0b022021648d970560aa5b2031560b52a0fd06305995a58a457663324c18c1a9b0bd0b8121020d6e50b2660af27933c42bc1395fe93df90ffac5e2a989f6a134919fb8cf8075ffffffff030000000000000000296a2769642b5bb666ac1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e7c150000000000001976a914c1c69d91a5719a23ba2094d89a4df3eadfa4aef788acefea2309000000001976a9144635b1794a22bfbe6c5c5eba17b693f4aaf0e34888ac00000000").expect("Bytes32 from UTXO should be valid"),
|
||||||
|
vec![
|
||||||
|
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getblockheader", "params": ["000000000000000000e7da64065cc23d5c5a5aa402f088aaaf37c5fe1c3b6b92", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
|
||||||
|
hex::decode("000000204a9037b3465b3c657997cddcffed602a949ee139cbdc1b010000000000000000669c3e2472384bc95993f154aada38d6b2113d8d32069e32e2724ba40915a1802a669e59e93c011801c8c735").expect("Bytes32 from UTXO should be valid"),
|
||||||
|
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getblockheader", "params": ["00000000000000000063da6d9362c0ac3b87877d1c5ad7afd5bd706571d5b393", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
|
||||||
|
hex::decode("02000020926b3b1cfec537afaa88f002a45a5a5c3dc25c0664dae7000000000000000000af0f270bf3f868d47089f395197809c360f1506636c960d96afed68bbd72d0b4496d9e59e93c01183ac94b67").expect("Bytes32 from UTXO should be valid"),
|
||||||
|
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getblockheader", "params": ["000000000000000000ec6063eb743366264368be7ff5021727682cb518963026", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
|
||||||
|
hex::decode("0000002093b3d5716570bdd5afd75a1c7d87873bacc062936dda630000000000000000009ab437e7c66b10a59b67127f290d24f381aba4e03d5e16c062a356eea4d608dea56d9e59e93c0118264f310e").expect("Bytes32 from UTXO should be valid"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let encodings = vec![
|
||||||
|
vec![0x02], // canonical
|
||||||
|
vec![0xFD, 0x02, 0x00], // non-minimal 3 bytes
|
||||||
|
vec![0xFE, 0x02, 0x00, 0x00, 0x00], // non-minimal 5 bytes
|
||||||
|
vec![0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // non-minimal 9 bytes
|
||||||
|
];
|
||||||
|
|
||||||
|
new_test_ext(authorities, networks).execute_with(|| {
|
||||||
|
let session = 0;
|
||||||
|
let block_hash =
|
||||||
|
hex::decode("000000000000000000ec6063eb743366264368be7ff5021727682cb518963026")
|
||||||
|
.expect("Bitcoin Root hex should be valid");
|
||||||
|
|
||||||
|
let expected_root = H256::from_slice(&block_hash);
|
||||||
|
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_root);
|
||||||
|
|
||||||
|
let expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
|
||||||
|
let mut receiver_raw = [0u8; 32];
|
||||||
|
receiver_raw.copy_from_slice(&expected_receiver_bytes);
|
||||||
|
|
||||||
|
let receiver_account = sp_runtime::AccountId32::from(receiver_raw);
|
||||||
|
let transfer_amount: u64 = 153348847;
|
||||||
|
let mut expected_pure_amount: u64 = 0;
|
||||||
|
|
||||||
|
for (idx, enc) in encodings.iter().enumerate() {
|
||||||
|
let mut variant = Vec::with_capacity(payload.1.len() + enc.len() - 1);
|
||||||
|
variant.extend_from_slice(&payload.1[0..4]);
|
||||||
|
variant.extend_from_slice(&enc);
|
||||||
|
variant.extend_from_slice(&payload.1[5..]);
|
||||||
|
|
||||||
|
let utxo_thread_proof = UtxoThreadProof::new(payload.0.clone(), variant, payload.2.clone());
|
||||||
|
let thread_info = ThreadProof::UtxoThreadProof(utxo_thread_proof);
|
||||||
|
let pulled_thread_key = thread_info.get_unique_key(session, network_id);
|
||||||
|
|
||||||
|
assert_eq!(Balances::free_balance(&receiver_account), expected_pure_amount);
|
||||||
|
assert!(!PulledThreads::<TestRuntime>::contains_key(
|
||||||
|
&pulled_thread_key
|
||||||
|
));
|
||||||
|
|
||||||
|
let pull_result = GhostWeaver::pull_thread(
|
||||||
|
RuntimeOrigin::none(),
|
||||||
|
network_id,
|
||||||
|
session,
|
||||||
|
thread_info
|
||||||
|
);
|
||||||
|
|
||||||
|
if idx == 0 {
|
||||||
|
assert!(pull_result.is_ok());
|
||||||
|
assert!(PulledThreads::<TestRuntime>::contains_key(&pulled_thread_key));
|
||||||
|
|
||||||
|
let curve_share = Perbill::from_parts(incoming_share).mul_ceil(transfer_amount);
|
||||||
|
expected_pure_amount += transfer_amount.saturating_sub(curve_share);
|
||||||
|
|
||||||
|
assert_eq!(Balances::free_balance(&receiver_account), expected_pure_amount);
|
||||||
|
|
||||||
|
System::assert_last_event(RuntimeEvent::GhostWeaver(Event::ThreadPulled {
|
||||||
|
pulled_thread_key,
|
||||||
|
network_id,
|
||||||
|
receiver: receiver_account.clone(),
|
||||||
|
amount: transfer_amount,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
assert!(pull_result.is_err());
|
||||||
|
assert!(!PulledThreads::<TestRuntime>::contains_key(&pulled_thread_key));
|
||||||
|
assert_eq!(Balances::free_balance(&receiver_account), expected_pure_amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(Balances::free_balance(&receiver_account), transfer_amount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn on_initialize_should_finalize_session_when_timeout_is_reached() {
|
fn on_initialize_should_finalize_session_when_timeout_is_reached() {
|
||||||
let pair1 = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
|
let pair1 = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
|
||||||
|
|||||||
@ -479,7 +479,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
|
||||||
@ -494,7 +494,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; }
|
||||||
@ -503,13 +503,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