diff --git a/Cargo.lock b/Cargo.lock index db9cb77..c547bae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2604,10 +2604,9 @@ dependencies = [ "ckb-merkle-mountain-range", "clap", "crossterm", - "etf-crypto-primitives 0.2.4 (git+https://github.com/ideal-lab5/etf-sdk/?branch=tony/dev)", "getrandom", "hex", - "murmur-core", + "murmur-lib", "parity-scale-codec", "rand_chacha", "rand_core", @@ -2616,7 +2615,7 @@ dependencies = [ "serde_cbor", "serde_json", "sha3", - "sp-consensus-beefy-etf", + "sp-consensus-beefy-etf 13.0.0 (git+https://github.com/ideal-lab5/etf.git?branch=murmur)", "sp-core", "subxt", "subxt-signer", @@ -2652,7 +2651,7 @@ name = "murmur-lib" version = "0.1.0" dependencies = [ "murmur-core", - "sp-consensus-beefy-etf", + "sp-consensus-beefy-etf 13.0.0 (git+https://github.com/ideal-lab5/pallets.git?branch=main)", "subxt", "w3f-bls", ] @@ -4210,6 +4209,26 @@ dependencies = [ "sp-crypto-ec-utils", ] +[[package]] +name = "sp-consensus-beefy-etf" +version = "13.0.0" +source = "git+https://github.com/ideal-lab5/pallets.git?branch=main#d9aa53506d59b350a23647a6c9b04e253955094d" +dependencies = [ + "lazy_static", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-crypto-hashing 0.1.0 (git+https://github.com/ideal-lab5/polkadot-sdk.git?branch=testing)", + "sp-io", + "sp-keystore", + "sp-mmr-primitives", + "sp-runtime", + "strum 0.24.1", +] + [[package]] name = "sp-consensus-beefy-etf" version = "13.0.0" diff --git a/README.md b/README.md index 2d7f7a9..6c6bfe0 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # Murmur -Murmur is an air-gapped keyless crypto wallet protocol that runs on the Ideal Network. It is based on the [Hours of Horus](https://eprint.iacr.org/2021/715) protocol, which leverages timelock encryption and a secure OTP code generator to construct a keyless wallet scheme. Our scheme improves on this construction in several ways. +Murmur is an air-gapped keyless crypto wallet protocol that runs on the [Ideal Network](). This repository contains the core implementation of the murmur protocol and a CLI to allow fully non-custodial usage of murmur wallets. +The murmur protocol enables **keyless account abstraction** capabilities for any chain bridged to the Ideal Network (alternatively, we can do this with drand). wallet is a special pure proxy that can only be executed when presented with proof that the origin knows a correct time-based OTP code. + +It is based on the [Hours of Horus](https://eprint.iacr.org/2021/715) protocol, which leverages timelock encryption and a secure OTP code generator to construct a keyless wallet scheme. Our scheme improves on this construction in several ways. + +- We leverage the Ideal Network to instantiate practical timelock encryption, allowing the HoH scheme to be realized in the first place. - We use a Merkle mountain range in place of a Merkle tree, allowing for arbitrary numbers of OTP codes to be generated - JIT execution: Rather than relying on a commit-reveal scheme in order to use the wallet, our scheme uses a 'just-in-time' approach leveraging the Ideal Network's on-chain randomness, which provides the decryption key (i.e. BLS signature) necessary to verify proofs. -This repository contains the core implementation of the murmur protocol and a CLI to allow fully non-custodial usage of murmur wallets on the Ideal Network. - -## Setup +## Build ``` cargo build diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 06400ae..ebbfdcb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,8 +9,7 @@ license.workspace = true subxt = "0.35.2" subxt-signer = "0.35.2" tokio = { version = "1.35", features = ["macros", "time", "rt-multi-thread"] } -murmur-core = { package = "murmur-core", path = "../core/", features = ["client"] } -etf-crypto-primitives = { git = "https://github.com/ideal-lab5/etf-sdk/", branch = "tony/dev" } +murmur-lib = { package = "murmur-lib", path = "../lib/" } clap = { version = "4.1.1", features = ["derive"] } rand_chacha = "0.3.1" ckb-merkle-mountain-range = "0.5.2" diff --git a/cli/README.md b/cli/README.md index c2e4e3d..8722bfb 100644 --- a/cli/README.md +++ b/cli/README.md @@ -6,6 +6,14 @@ It allows for the creation and exeuction of ephemeral 'murmur' wallets on the Id ## Setup +Install the cli with + +``` shell +cargo install murmur +# verify it works +murmur --help +``` + To setup a dev environment: - run a local [IDN solochain node](https://github.com/ideal-lab5/etf) - [generate metadata](#generating-metadata-for-the-chain) diff --git a/cli/artifacts/metadata.scale b/cli/artifacts/metadata.scale deleted file mode 100644 index 578d096..0000000 Binary files a/cli/artifacts/metadata.scale and /dev/null differ diff --git a/cli/src/main.rs b/cli/src/main.rs index 4fd78bf..19dd7b6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -17,34 +17,15 @@ #![allow(missing_docs)] use subxt::{backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig}; use subxt_signer::sr25519::dev; - -// Generate an interface that we can use from the node's metadata. -#[subxt::subxt(runtime_metadata_path = "artifacts/metadata.scale")] -pub mod etf {} - use std::fs::File; - +use std::time::Instant; use clap::{Parser, Subcommand}; use thiserror::Error; - -use subxt::ext::codec::Encode; - -use beefy::{known_payloads, Commitment, Payload}; -use etf::murmur::calls::types::{Create, Proxy}; -use etf::runtime_types::{ - bounded_collections::bounded_vec::BoundedVec, - node_template_runtime::{RuntimeCall, RuntimeCall::Balances}, -}; - -use murmur_core::{ - murmur::MurmurStore, - types::{BlockNumber, Identity, IdentityBuilder, Leaf}, +use etf::runtime_types::node_template_runtime::RuntimeCall::Balances; +use murmur_lib::{ + etf, create, prepare_execute, + MurmurStore, BlockNumber }; -use rand_core::OsRng; -use ark_serialize::CanonicalSerialize; -use w3f_bls::{KeypairVT, DoublePublicKey, DoublePublicKeyScheme, SerializableToBytes, TinyBLS377}; - -use std::time::Instant; /// Command line #[derive(Parser)] @@ -59,8 +40,6 @@ struct Cli { enum Commands { /// create a new murmur wallet New(WalletCreationDetails), - /// dispatch (proxy) a call to a murmur wallet in the future - ScheduleExecute(WalletExecuteDetails), /// dispatch (proxy) a call to a murmur wallet Execute(WalletExecuteDetails), } @@ -72,9 +51,7 @@ struct WalletCreationDetails { #[arg(long)] seed: String, #[clap(long)] - validity: u32, - #[clap(short, long)] - x: bool, + validity: u32 } #[derive(Parser)] @@ -84,9 +61,7 @@ struct WalletExecuteDetails { #[arg(long)] seed: String, #[arg(short, long)] - amount: String, - #[clap(short, long)] - x: bool, + amount: String } #[derive(Error, Debug)] @@ -95,55 +70,28 @@ pub enum CLIError { InvalidPubkey, } -#[derive(Debug)] -pub struct BasicIdBuilder; -impl IdentityBuilder for BasicIdBuilder { - fn build_identity(when: BlockNumber) -> Identity { - let payload = Payload::from_single_entry(known_payloads::ETF_SIGNATURE, Vec::new()); - let commitment = Commitment { - payload, - block_number: when, - validator_set_id: 0, // TODO: how to ensure correct validator set ID is used? could just always set to 1 for now, else set input param. - }; - Identity::new(&commitment.encode()) - } -} - +/// the mmr_store file location +/// in future, make configurable pub const MMR_STORE_FILEPATH: &str = "mmr_store"; #[tokio::main] async fn main() -> Result<(), Box> { let cli = Cli::parse(); let before = Instant::now(); - - let mut current_block_number = 0; - - - let keypair = KeypairVT::::generate(&mut OsRng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); - let mut round_pubkey_bytes = Vec::new(); - double_public.serialize_compressed(&mut round_pubkey_bytes).unwrap(); // TODO: HKDF? just hash the seed? let ephem_msk = [1; 32]; + let (client, current_block_number, round_pubkey_bytes) = idn_connect().await?; + match &cli.commands { Commands::New(args) => { - // if args.x { - let (etf, block_number, pk_bytes) = idn_connect().await?; - current_block_number = block_number; - round_pubkey_bytes = pk_bytes; - // } - println!("🏭 Murmur: Generating Merkle mountain range"); // 1. prepare block schedule let mut schedule: Vec = Vec::new(); for i in 2..args.validity + 2 { // wallet is 'active' in 2 blocks - let next_block = current_block_number + i; - schedule.push(next_block); + let next_block_number: BlockNumber = current_block_number + i; + schedule.push(next_block_number); } // 2. create mmr let (call, mmr_store) = create( @@ -153,26 +101,18 @@ async fn main() -> Result<(), Box> { schedule, round_pubkey_bytes, ); - // println!("seriously wtf: {:?}", mmr_store.metadata.clone()); // 3. add to storage write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); // TODO: make the origin configurable // sign and send the call - // let from = dev::alice(); - // let _events = etf - // .tx() - // .sign_and_submit_then_watch_default(&call, &from) - // .await?; + let from = dev::alice(); + let _events = client + .tx() + .sign_and_submit_then_watch_default(&call, &from) + .await?; println!("✅ MMR proxy account creation successful!"); } Commands::Execute(args) => { - // if args.x { - let (etf, block_number, pk_bytes) = idn_connect().await?; - current_block_number = block_number; - round_pubkey_bytes = pk_bytes; - // } else { - // current_block_number += 2; - // } // build balance transfer let bob = dev::alice().public_key(); // get the value argument @@ -181,42 +121,45 @@ async fn main() -> Result<(), Box> { .split_whitespace() .map(|r| r.replace('_', "").parse().unwrap()) .collect::>()[0]; - // TODO: cleanup type defs let balance_transfer_call = Balances(etf::balances::Call::transfer_allow_death { dest: subxt::utils::MultiAddress::<_, u32>::from(bob), value: v, }); let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH); + let target_block_number: BlockNumber = current_block_number + 1; + println!("💾 Recovered Murmur store from local file"); let tx = prepare_execute( args.name.clone().as_bytes().to_vec(), args.seed.clone().as_bytes().to_vec(), - current_block_number + 1, + target_block_number, store, balance_transfer_call, ) .await; // submit the tx using alice to sign it - // let _result = etf.tx() - // .sign_and_submit_then_watch_default(&tx, &dev::alice()) - // .await; - } - _ => panic!("Hey, don't do that!"), + let _result = client.tx() + .sign_and_submit_then_watch_default(&tx, &dev::alice()) + .await; + }, } println!("Elapsed time: {:.2?}", before.elapsed()); Ok(()) } -async fn idn_connect( -) -> Result<(OnlineClient, BlockNumber, Vec), Box> { +/// async connection to the Ideal Network +/// if successful then fetch data +/// else error if unreachable +async fn idn_connect() -> + Result<(OnlineClient, BlockNumber, Vec), Box> { println!("🎲 Connecting to Ideal network (local node)"); let rpc_client = RpcClient::from_url("ws://localhost:9944").await?; - let etf = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; + let client = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; println!("🔗 RPC Client: connection established"); // fetch the round public key from etf runtime storage let round_key_query = subxt::dynamic::storage("Etf", "RoundPublic", ()); - let result = etf + let result = client .storage() .at_latest() .await? @@ -226,84 +169,10 @@ async fn idn_connect( println!("🔑 Successfully retrieved the round public key."); - let current_block = etf.blocks().at_latest().await?; - let current_block_number = current_block.header().number; + let current_block = client.blocks().at_latest().await?; + let current_block_number: BlockNumber = current_block.header().number; println!("🧊 Current block number: #{:?}", current_block_number); - Ok((etf, current_block_number, round_pubkey_bytes)) -} -/// create a new MMR and use it to generate a valid call to create a murmur wallet -/// returns the call data and the mmr_store -/// -/// * `name`: The name of the murmur wallet -/// * `seed`: The seed used to generate otp codes -/// * `ephem_msk`: An ephemeral secret key TODO: replace with an hkdf? -/// * `block_schedule`: A list of block numbers when the wallet will be executable -/// * `round_pubkey_bytes`: The Ideal Network randomness beacon public key -/// -pub fn create( - name: String, - seed: String, - ephem_msk: [u8; 32], - block_schedule: Vec, - round_pubkey_bytes: Vec, -) -> (subxt::tx::Payload, MurmurStore) { - let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes).unwrap(); // TODO: error handlking - let mmr_store = MurmurStore::new::( - seed.clone().into(), - block_schedule.clone(), - ephem_msk, - round_pubkey, - ); - let root = mmr_store.root.clone(); - let name = name.as_bytes().to_vec(); - - let call = etf::tx() - .murmur() - .create(root.0, mmr_store.metadata.len() as u64, BoundedVec(name)); - (call, mmr_store) -} - -/// prepare the call for immediate execution -/// Note to self: in the future, we can consider ways to prune the murmurstore as OTP codes are consumed -/// for example, we can take the next values from the map, reducing storage to 0 over time -/// However, to do this we need to think of a way to prove it with a merkle proof -/// my thought is that we would have a subtree, so first we prove that the subtree is indeed in the parent MMR -/// then we prove that the specific leaf is in the subtree. -/// We could potentially use that idea as a way to optimize the execute function in general. Rather than -/// loading the entire MMR into memory, we really only need to load a minimal subtree containing the leaf we want to consume -/// -> add this to the 'future work' section later -pub async fn prepare_execute( - name: Vec, - seed: Vec, - when: BlockNumber, - store: MurmurStore, - call: RuntimeCall, -) -> subxt::tx::Payload { - let (proof, commitment, ciphertext, pos) = store - .execute(seed.clone(), when, call.encode().to_vec()) - .unwrap(); - let test = proof - .verify(store.root, vec![(pos.clone(), Leaf(ciphertext.clone()))]) - .unwrap(); - println!("valid ? {:?}", test); - // println!("MMR ROOT:{:?}", store.root.clone()); - - let size: u64 = proof.mmr_size() as u64; - let proof_items: Vec> = proof - .proof_items() - .iter() - .map(|leaf| leaf.0.clone()) - .collect::>(); - - etf::tx().murmur().proxy( - BoundedVec(name), - pos, - commitment, - ciphertext, - proof_items, - size, - call, - ) + Ok((client, current_block_number, round_pubkey_bytes)) } /// read an MMR from a file @@ -318,133 +187,3 @@ fn write_mmr_store(mmr_store: MurmurStore, path: &str) { let mmr_store_file = File::create(path).expect("It should create the file"); serde_cbor::to_writer(mmr_store_file, &mmr_store).unwrap(); } - -// #[cfg(test)] -// mod tests { - -// use super::*; - - -// #[test] -// pub fn it_can_create_mmr_store_and_verifiable_execution_params() { -// let name = "name".to_string(); -// let seed = "seed".to_string(); -// let ephem_msk = [1;32]; -// let schedule = vec![1,2,3,4,5,6,7,8,9]; - -// let (_call, mmr_store) = create( -// name.clone(), -// seed.clone(), -// ephem_msk, -// schedule, -// pk_bytes, -// ); - -// assert!(write_mmr_store(mmr_store.clone(), "mmr_store").is_ok()); - -// // then try to load the mmr_store -// } -// } - -// Commands::ScheduleExecute(args) => { -// // build balance transfer -// let bob = AccountKeyring::Bob.to_account_id().into(); -// // get the value argument -// let v: u128 = args.amount -// .split_whitespace() -// .map(|r| r.replace('_', "").parse().unwrap()) -// .collect::>()[0]; -// let balance_transfer_call = RuntimeCall::Balances( -// BalancesCall::transfer_allow_death { -// dest: bob, -// value: v, -// }); -// let call = prepare_execution_payload_for_proxy::( -// etf.clone(), -// args.name.clone().as_bytes().to_vec(), -// args.seed.clone().as_bytes().to_vec(), -// args.when.clone(), -// balance_transfer_call, -// ).await; -// // sign and send the tx (with the alice wallet for now) -// dispatch_sealed_tx::( -// etf, -// args.when, -// ephem_msk, -// round_pubkey, -// call, -// ).await; -// } - -// /// prepare the proxy call for a scheduled transaction -// async fn prepare_execution_payload_for_proxy( -// etf: OnlineClient, -// name: Vec, -// seed: Vec, -// when: BlockNumber, -// call: RuntimeCall, -// ) -> RuntimeCall { -// let data: Vec<(BlockNumber, Ciphertext)> = load_mmr_store(); -// let ciphertext = data.iter().filter(|d| d.0 == when).collect().unwrap()[0]; - -// let call_data = call.encode(); -// // prepare the proof required to used the mmr wallet at the specific block height -// let payload = murmur::execute::( -// seed, -// when, -// call_data, -// leaves.clone(), -// ).map_err(|e| println!("Murmur execution failed due to {:?}", e)).unwrap(); - -// // let root: Leaf = payload.root; -// let hash: Vec = payload.hash; -// let proof: MerkleProof = payload.proof; -// let target_leaf: Leaf = payload.target; -// let pos: u64 = payload.pos; - -// let proof_items: Vec> = proof.proof_items().iter() -// .map(|leaf| leaf.0.to_vec().clone()) -// .collect::>(); - -// let bounded = >>::truncate_from(name); - -// RuntimeCall::Murmur(MurmurCall::proxy { -// name: bounded, -// position: pos, -// target_leaf: target_leaf.0, -// proof: proof_items, -// ciphertext, -// call: Box::new(call), -// hash, -// }) -// } - -// /// dispatch a shielded (timelocked) transaction for a future block -// async fn dispatch_sealed_tx>( -// etf: OnlineClient, -// when: BlockNumber, -// ephemeral_msk: [u8;32], -// pk: DoublePublicKey, -// proxy_call: RuntimeCall, -// ) { -// let proxy_call_bytes: &[u8] = &proxy_call.encode(); -// // then construct a scheduled transaction for "when" -// // 1. tlock -// let identity = I::build_identity(when); -// let timelocked_proxy_call = murmur::timelock_encrypt::( -// identity, -// pk.1, -// ephemeral_msk, -// proxy_call_bytes, -// ); -// let bounded_ciphertext = etf::runtime_types::bounded_collections::bounded_vec::BoundedVec(timelocked_proxy_call); -// // 2. build tx -// let sealed_tx = etf::tx() -// .scheduler() -// .schedule_sealed(when, 127, bounded_ciphertext); -// // 3. submit tx -// let events = etf -// .tx() -// .sign_and_submit_then_watch_default(&sealed_tx, &dev::alice()) -// .await; -// } diff --git a/core/README.md b/core/README.md index f480eeb..9efec1d 100644 --- a/core/README.md +++ b/core/README.md @@ -1,5 +1,7 @@ # Murmur Core +This library contains the core implementation of the murmur protocol. This implementation can support both BLS12-377 and BLS12-381, but is left curve-agnostic, only expecting that the beacon is produced by an ETF-PFG instance. + ## Build ``` shell @@ -20,4 +22,8 @@ cargo test The OTP code generator is gated under the "client" feature, so run tests with: ``` shell cargo test --features "client" -``` \ No newline at end of file +``` + +## Future Work/Notes +- There is an 'otpauth' feature that can be enabled on the totp lib. It allows for the inclusion of an issuer and account_name. We can investigate usage of this in the future. https://github.com/constantoine/totp-rs/blob/da78569b0c233adbce126dbe0c35452340fd3929/src/lib.rs#L160 +- Wallet Update logic: Each murmur wallet is ephemeral, since any MMR must be limited in size. We can use a zkp to prove knowledge of the seed in order to allow the wallet owner to update the wallet by providing a new MMR root. \ No newline at end of file diff --git a/core/src/murmur.rs b/core/src/murmur.rs index c2a8bf5..b0245f0 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -43,6 +43,7 @@ use ckb_merkle_mountain_range::{ MemMMR, MemStore, }, + helper::leaf_index_to_pos, }; use sha3::Digest; @@ -74,7 +75,7 @@ impl MurmurStore { /// * `round_public_key`: The IDN beacon's public key /// pub fn new>( - mut seed: Vec, + seed: Vec, block_schedule: Vec, ephemeral_msk: [u8;32], round_public_key: DoublePublicKey, @@ -86,7 +87,7 @@ impl MurmurStore { let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); for i in &block_schedule { - let mut otp_code = totp.generate(*i); + let otp_code = totp.generate(*i); let identity = I::build_identity(*i); let ct_bytes = timelock_encrypt::( identity, @@ -106,10 +107,6 @@ impl MurmurStore { } /// Build data required (proof and commitment) to execute a valid call from a murmur wallet - /// note: this rebuilds the entire mmr - /// we can look into ways to optimize this in the future - /// the main issue is that he MemStore is not serializable - /// a possible fix is to externalize mmr logic /// /// TODO: this should probably be a result, not option /// @@ -118,13 +115,14 @@ impl MurmurStore { /// * `call_data`: The call to be executed with the wallet (at `when`) pub fn execute( &self, - mut seed: Vec, + seed: Vec, when: BlockNumber, call_data: Vec ) -> Option<(MerkleProof::, Vec, Ciphertext, u64)> { if let Some(ciphertext) = self.metadata.get(&when) { let commitment = MurmurStore::commit(seed.clone(), when, &call_data.clone()); - let pos = get_key_index(&self.metadata, &when).unwrap() as u64; + let idx = get_key_index(&self.metadata, &when).unwrap() as u64; + let pos = leaf_index_to_pos(idx); let mmr = self.to_mmr(); let proof = mmr.gen_proof(vec![pos]).expect("todo: handle error"); return Some((proof, commitment, ciphertext.clone(), pos)); @@ -139,13 +137,12 @@ impl MurmurStore { /// * `when`: The block number when the commitment is verifiable /// * `data`: The data to commit to /// - fn commit(mut seed: Vec, when: BlockNumber, data: &[u8]) -> Vec { + fn commit(seed: Vec, when: BlockNumber, data: &[u8]) -> Vec { let botp = build_generator(&seed); - let mut otp_code = botp.generate(when); + let otp_code = botp.generate(when); let mut hasher = sha3::Sha3_256::default(); hasher.update(otp_code.as_bytes()); - hasher.update(data); hasher.finalize().to_vec() } @@ -170,7 +167,7 @@ impl MurmurStore { pub fn timelock_encrypt( identity: Identity, pk: E::PublicKeyGroup, - mut ephemeral_msk: [u8;32], + ephemeral_msk: [u8;32], message: &[u8], ) -> Vec { let ciphertext = tle::( @@ -209,7 +206,6 @@ pub fn verify( .unwrap_or(false); if validity { - // verify the hash let mut hasher = sha3::Sha3_256::default(); hasher.update(otp); hasher.update(aux_data); @@ -296,23 +292,8 @@ mod tests { .execute(seed.clone(), when, aux_data.clone()) .unwrap(); + // sanity check assert!(proof.verify(root.clone(), vec![(pos, Leaf(ciphertext.clone()))]).unwrap()); - // // we also simulate the serialization of the proof items - // let proof_items: Vec> = proof.proof_items().iter() - // .map(|leaf| leaf.0.to_vec().clone()) - // .collect::>(); - - // let leaves: Vec = proof_items.clone().into_iter() - // .map(|p| Leaf(p)).collect::>(); - // let new_merkle_proof = MerkleProof::::new( - // proof.mmr_size() as u64, leaves); - // let root_bytes = root.0.clone(); - // let new_root = Leaf(root_bytes); - - // let test = new_merkle_proof.verify(new_root.clone(), vec![(pos.clone(), Leaf(ciphertext.clone()))]).unwrap(); - // assert!(test == true); - - // in practice, the otp code would be timelock decrypted // but for testing purposes, we will just calculate the expected one now diff --git a/core/src/otp.rs b/core/src/otp.rs index f217beb..480e3a5 100644 --- a/core/src/otp.rs +++ b/core/src/otp.rs @@ -52,4 +52,4 @@ impl BOTPGenerator { self.totp.generate(block_height as u64) } -} \ No newline at end of file +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index d17b015..a405839 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" authors.workspace = true [dependencies] -beefy = { package = "sp-consensus-beefy-etf", git = "https://github.com/ideal-lab5/etf.git", branch = "murmur", features = ["bls-experimental"] } +beefy = { package = "sp-consensus-beefy-etf", git = "https://github.com/ideal-lab5/pallets.git", branch = "main", features = ["bls-experimental"] } murmur-core = { package = "murmur-core", path = "../core/", features = ["client"] } subxt = "0.35.2" w3f-bls = "0.1.3" \ No newline at end of file diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..a3972a9 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,5 @@ +# murmur-lib + +This is middleware to allow easy integration of Murmur as middleware for various services. For example, it is used to build both the [murmur-cli](../cli/README.md) and the [murmur-api](). + +Specifically, this library implements an [IdentityBuilder]() for usage with the Ideal Network's core randomness beacon (produced with the ETF-Post-Finality Gadget). It \ No newline at end of file diff --git a/lib/artifacts/metadata.scale b/lib/artifacts/metadata.scale index d74dc51..578d096 100644 Binary files a/lib/artifacts/metadata.scale and b/lib/artifacts/metadata.scale differ diff --git a/lib/src/lib.rs b/lib/src/lib.rs index dcd9a90..a7829d1 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -16,11 +16,19 @@ use beefy::{known_payloads, Commitment, Payload}; use murmur_core::{ - murmur::MurmurStore, - types::{BlockNumber, Identity, IdentityBuilder}, + types::{Identity, IdentityBuilder}, +}; +pub use murmur_core::types::BlockNumber; +pub use murmur_core::murmur::MurmurStore; +use etf::murmur::calls::types::{Create, Proxy}; +use etf::runtime_types::{ + bounded_collections::bounded_vec::BoundedVec, + node_template_runtime::RuntimeCall, }; use subxt::ext::codec::Encode; -use w3f_bls::{DoublePublicKey, EngineBLS, SerializableToBytes, TinyBLS377}; +use w3f_bls::{DoublePublicKey, SerializableToBytes, TinyBLS377}; + +// pub mod MurmurStore; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "artifacts/metadata.scale")] @@ -39,18 +47,23 @@ impl IdentityBuilder for BasicIdBuilder { Identity::new(&commitment.encode()) } } - -pub async fn create( +/// create a new MMR and use it to generate a valid call to create a murmur wallet +/// returns the call data and the mmr_store +/// +/// * `name`: The name of the murmur proxy +/// * `seed`: The seed used to generate otp codes +/// * `ephem_msk`: An ephemeral secret key TODO: replace with an hkdf? +/// * `block_schedule`: A list of block numbers when the wallet will be executable +/// * `round_pubkey_bytes`: The Ideal Network randomness beacon public key +/// +pub fn create( name: String, seed: String, ephem_msk: [u8; 32], block_schedule: Vec, round_pubkey_bytes: Vec, -) -> ( - subxt::tx::Payload, - MurmurStore, -) { - let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes).unwrap(); +) -> (subxt::tx::Payload, MurmurStore) { + let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes).unwrap(); // TODO: error handlking let mmr_store = MurmurStore::new::( seed.clone().into(), block_schedule.clone(), @@ -59,43 +72,52 @@ pub async fn create( ); let root = mmr_store.root.clone(); let name = name.as_bytes().to_vec(); - let call = etf::tx().murmur().create( - root.0.into(), - mmr_store.metadata.len() as u64, - etf::runtime_types::bounded_collections::bounded_vec::BoundedVec(name), - ); + + let call = etf::tx() + .murmur() + .create(root.0, mmr_store.metadata.len() as u64, BoundedVec(name)); (call, mmr_store) } -/// Prepare the call for immediate execution -// Note: in the future, we can consider ways to prune the murmurstore as OTP codes are consumed -// for example, we can take the next values from the map, reducing storage to 0 over time -// However, to do this we need to think of a way to prove it with a merkle proof -// my though is that we would have a subtree, so first we prove that the subtree is indeed in the parent MMR -// then we prove that the specific leaf is in the subtree. -// We could potentially use that idea as a way to optimize the execute function in general. Rather than -// loading the entire MMR into memory, we really only need to load a minimal subtree containing the leaf we want to consume -// -> add this to the 'future work' section later -pub async fn prepare_execute( +/// prepare the call for immediate execution +/// Note to self: in the future, we can consider ways to prune the murmurstore as OTP codes are consumed +/// for example, we can take the next values from the map, reducing storage to 0 over time +/// However, to do this we need to think of a way to prove it with a merkle proof +/// my thought is that we would have a subtree, so first we prove that the subtree is indeed in the parent MMR +/// then we prove that the specific leaf is in the subtree. +/// We could potentially use that idea as a way to optimize the execute function in general. Rather than +/// loading the entire MMR into memory, we really only need to load a minimal subtree containing the leaf we want to consume +/// -> add this to the 'future work' section later +/// +/// * `name`: The name of the murmur proxy +/// * `seed`: The seed used to generate otp codes +/// * `when`: The block number when OTP codeds should be generated +/// * `store`: A murmur store +/// * `call`: Any valid runtime call +/// +pub async fn prepare_execute( name: Vec, seed: Vec, when: BlockNumber, store: MurmurStore, - call: etf::runtime_types::node_template_runtime::RuntimeCall, -) -> subxt::tx::Payload { - let (proof, commitment, ciphertext, pos) = store - .execute(seed.clone(), when, call.encode().to_vec()) - .unwrap(); - + call: RuntimeCall, +) -> subxt::tx::Payload { + let (proof, commitment, ciphertext, pos) = + store.execute(seed.clone(), when, call.encode()).unwrap(); + let size: u64 = proof.mmr_size(); let proof_items: Vec> = proof .proof_items() .iter() - .map(|leaf| leaf.0.to_vec().clone()) + .map(|leaf| leaf.0.clone()) .collect::>(); - let bounded = etf::runtime_types::bounded_collections::bounded_vec::BoundedVec(name); - - etf::tx() - .murmur() - .proxy(bounded, pos, commitment, ciphertext, proof_items, call) + etf::tx().murmur().proxy( + BoundedVec(name), + pos, + commitment, + ciphertext, + proof_items, + size, + call, + ) } diff --git a/mmr_store b/mmr_store index 0701f32..ea19e9b 100644 Binary files a/mmr_store and b/mmr_store differ