From 9cf81834a33e4a7cb808924bb10ab9ff7878e330 Mon Sep 17 00:00:00 2001 From: Victor Lopes Date: Thu, 18 Nov 2021 19:59:25 +0100 Subject: [PATCH] Update contract id to use celestia specs BMT (#48) Closes #49 --- .github/workflows/cargo_test.yml | 1 + Cargo.toml | 1 + src/call.rs | 1 - src/contract.rs | 1 - src/crypto.rs | 112 ++++++++---------------------- src/interpreter/executors/main.rs | 2 - src/interpreter/flow.rs | 1 - src/interpreter/internal.rs | 1 - src/interpreter/memory.rs | 1 - src/interpreter/metadata.rs | 2 - tests/crypto.rs | 1 - 11 files changed, 31 insertions(+), 93 deletions(-) diff --git a/.github/workflows/cargo_test.yml b/.github/workflows/cargo_test.yml index 82200a78e3..d24d0cb8d1 100644 --- a/.github/workflows/cargo_test.yml +++ b/.github/workflows/cargo_test.yml @@ -25,6 +25,7 @@ jobs: with: ssh-private-key: | ${{ secrets.PRIVATE_KEY_FUEL_ASM }} + ${{ secrets.PRIVATE_KEY_FUEL_MERKLE }} ${{ secrets.PRIVATE_KEY_FUEL_STORAGE }} ${{ secrets.PRIVATE_KEY_FUEL_TX }} ${{ secrets.PRIVATE_KEY_FUEL_TYPES }} diff --git a/Cargo.toml b/Cargo.toml index baf7a6509d..d29c34d7e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ description = "FuelVM interpreter." [dependencies] fuel-asm = { git = "ssh://git@github.com/FuelLabs/fuel-asm.git" } +fuel-merkle = { git = "ssh://git@github.com/FuelLabs/fuel-merkle.git" } fuel-storage = { git = "ssh://git@github.com/FuelLabs/fuel-storage.git" } fuel-tx = { git = "ssh://git@github.com/FuelLabs/fuel-tx.git" } fuel-types = { git = "ssh://git@github.com/FuelLabs/fuel-types.git" } diff --git a/src/call.rs b/src/call.rs index 68838c7768..1141be2f20 100644 --- a/src/call.rs +++ b/src/call.rs @@ -4,7 +4,6 @@ use crate::contract::Contract; use fuel_types::bytes::{self, SizedBytes}; use fuel_types::{Color, ContractId, Word}; -use std::convert::TryFrom; use std::io::{self, Write}; use std::mem; diff --git a/src/contract.rs b/src/contract.rs index 8ef1e26eea..70268084d9 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -6,7 +6,6 @@ use fuel_tx::{Transaction, ValidationError}; use fuel_types::{Bytes32, ContractId, Salt}; use std::cmp; -use std::convert::TryFrom; #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde-types", derive(serde::Serialize, serde::Deserialize))] diff --git a/src/crypto.rs b/src/crypto.rs index 052ccf623c..b3d8dd37b7 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,12 +1,10 @@ -use fuel_tx::crypto::Hasher; +use fuel_merkle::binary::MerkleTree; +use fuel_merkle::common::StorageMap; use fuel_types::{Bytes32, Bytes64}; use secp256k1::recovery::{RecoverableSignature, RecoveryId}; use secp256k1::Error as Secp256k1Error; use secp256k1::{Message, Secp256k1, SecretKey}; -use std::convert::TryFrom; -use std::mem; - /// Sign a given message and compress the `v` to the signature /// /// The compression scheme is described in @@ -47,60 +45,21 @@ pub fn secp256k1_sign_compact_recover(signature: &[u8], message: &[u8]) -> Resul Ok(pk) } -/// Calculate a binary merkle root -/// -/// The space complexity of this operation is O(n). This means it expects small -/// sets. For bigger sets (e.g. blockchain state), use a storage backed merkle -/// implementation +/// Calculate a binary merkle root with in-memory storage pub fn ephemeral_merkle_root(mut leaves: I) -> Bytes32 where L: AsRef<[u8]>, I: Iterator + ExactSizeIterator, { - let mut hasher = Hasher::default(); - let mut width = leaves.len().next_power_of_two(); - let mut len = leaves.len() as f32; - - if width <= 2 { - return leaves.collect::().digest(); - } - - width /= 2; - len /= 2.0; - - let mut current = vec![Bytes32::default(); width]; - - // Drain the leaves - current.iter_mut().for_each(|l| { - hasher.reset(); - - leaves.next().map(|a| hasher.input(a)); - leaves.next().map(|b| hasher.input(b)); - - *l = hasher.digest(); - }); - - let mut next = current.clone(); - - // Cheap loop with no realloc - while width > 1 { - mem::swap(&mut current, &mut next); - - let mut c = current.iter().take(len.ceil() as usize); - - width /= 2; - len /= 2.0; - next.iter_mut().take(width).for_each(|n| { - hasher.reset(); - - c.next().map(|a| hasher.input(a)); - c.next().map(|b| hasher.input(b)); - - *n = hasher.digest(); - }); - } - - next[0] + let mut storage = StorageMap::new(); + let mut tree = MerkleTree::new(&mut storage); + + // TODO fuel-merkle should have infallible in-memory struct + leaves + .try_for_each(|l| tree.push(l.as_ref())) + .and_then(|_| tree.root()) + .expect("In-memory impl should be infallible") + .into() } #[cfg(all(test, feature = "random"))] @@ -113,8 +72,6 @@ mod tests { use rand::{Rng, RngCore, SeedableRng}; use secp256k1::PublicKey; - use std::convert::TryFrom; - #[test] fn ecrecover() { let secp = Secp256k1::new(); @@ -145,7 +102,12 @@ mod tests { fn ephemeral_merkle_root_works() { let mut rng = StdRng::seed_from_u64(2322u64); + const LEAF_PREFIX: u8 = 0x00; + const NODE_PREFIX: u8 = 0x01; + // Test for 0 leaves + // + // Expected root is `h()` let empty: Vec
= vec![]; let root = ephemeral_merkle_root(empty.iter()); @@ -162,38 +124,22 @@ mod tests { let initial = [a, b, c, d, e]; - let a = [a, b].iter().collect::().digest(); - let b = [c, d].iter().collect::().digest(); - let c = [e].iter().collect::().digest(); + let a = Hasher::default().chain(&[LEAF_PREFIX]).chain(a).digest(); + let b = Hasher::default().chain(&[LEAF_PREFIX]).chain(b).digest(); + let c = Hasher::default().chain(&[LEAF_PREFIX]).chain(c).digest(); + let d = Hasher::default().chain(&[LEAF_PREFIX]).chain(d).digest(); + let e = Hasher::default().chain(&[LEAF_PREFIX]).chain(e).digest(); + + let a = Hasher::default().chain(&[NODE_PREFIX]).extend_chain([a, b]).digest(); + let b = Hasher::default().chain(&[NODE_PREFIX]).extend_chain([c, d]).digest(); + let c = e; - let a = [a, b].iter().collect::().digest(); - let b = [c].iter().collect::().digest(); + let a = Hasher::default().chain(&[NODE_PREFIX]).extend_chain([a, b]).digest(); + let b = c; - let root = [a, b].iter().collect::().digest(); + let root = Hasher::default().chain(&[NODE_PREFIX]).extend_chain([a, b]).digest(); let root_p = ephemeral_merkle_root(initial.iter()); assert_eq!(root, root_p); - - // Test for n leaves - let mut inputs = vec![Address::default(); 64]; - - inputs.iter_mut().for_each(|i| *i = rng.gen()); - - (0..65).into_iter().for_each(|w| { - let initial: Vec<&Address> = inputs.iter().take(w).collect(); - let mut level: Vec = initial - .chunks(2) - .map(|c| c.iter().collect::().digest()) - .collect(); - - while level.len() > 1 { - level = level.chunks(2).map(|c| c.iter().collect::().digest()).collect(); - } - - let root = level.first().copied().unwrap_or(empty); - let root_p = ephemeral_merkle_root(initial.iter()); - - assert_eq!(root, root_p); - }); } } diff --git a/src/interpreter/executors/main.rs b/src/interpreter/executors/main.rs index afa9d22c06..686a6e9ba7 100644 --- a/src/interpreter/executors/main.rs +++ b/src/interpreter/executors/main.rs @@ -10,8 +10,6 @@ use fuel_tx::{Input, Output, Receipt, Transaction}; use fuel_types::bytes::SerializableVec; use fuel_types::Word; -use std::convert::TryFrom; - impl Interpreter where S: InterpreterStorage, diff --git a/src/interpreter/flow.rs b/src/interpreter/flow.rs index b5c283ee3c..5884b1716c 100644 --- a/src/interpreter/flow.rs +++ b/src/interpreter/flow.rs @@ -12,7 +12,6 @@ use fuel_types::bytes::SerializableVec; use fuel_types::{Bytes32, Color, RegisterId, Word}; use std::cmp; -use std::convert::TryFrom; impl Interpreter where diff --git a/src/interpreter/internal.rs b/src/interpreter/internal.rs index 554d51641e..92ffb560a2 100644 --- a/src/interpreter/internal.rs +++ b/src/interpreter/internal.rs @@ -8,7 +8,6 @@ use fuel_tx::consts::*; use fuel_tx::Transaction; use fuel_types::{Bytes32, Color, ContractId, RegisterId, Word}; -use std::convert::TryFrom; use std::mem; const WORD_SIZE: usize = mem::size_of::(); diff --git a/src/interpreter/memory.rs b/src/interpreter/memory.rs index 55431f893f..5477c9cc75 100644 --- a/src/interpreter/memory.rs +++ b/src/interpreter/memory.rs @@ -4,7 +4,6 @@ use crate::error::InterpreterError; use fuel_types::{RegisterId, Word}; -use std::convert::TryFrom; use std::{ops, ptr}; // Memory bounds must be manually checked and cannot follow general PartialEq diff --git a/src/interpreter/metadata.rs b/src/interpreter/metadata.rs index 81b896d0dc..49dd7f0e2d 100644 --- a/src/interpreter/metadata.rs +++ b/src/interpreter/metadata.rs @@ -4,8 +4,6 @@ use crate::error::InterpreterError; use fuel_types::{Immediate18, RegisterId, Word}; -use std::convert::TryFrom; - const IS_CALLER_EXTERNAL: Immediate18 = 0x000001; const GET_CALLER: Immediate18 = 0x000002; diff --git a/tests/crypto.rs b/tests/crypto.rs index 59ecc3c519..b73bde568e 100644 --- a/tests/crypto.rs +++ b/tests/crypto.rs @@ -3,7 +3,6 @@ use fuel_vm::consts::*; use fuel_vm::crypto; use fuel_vm::prelude::*; -use std::convert::TryFrom; use std::str::FromStr; #[test]