From c02ad9ebd0cb31e8d75d903302a9106e8ea7d4ef Mon Sep 17 00:00:00 2001 From: driemworks Date: Wed, 23 Oct 2024 14:55:57 -0500 Subject: [PATCH 01/19] feat: add update to murmur-core --- core/Cargo.toml | 12 + core/src/murmur.rs | 692 +++++++++++++++++++++++-------------- core/src/otp.rs | 12 +- lib/Cargo.toml | 1 + lib/src/bin/murmur/main.rs | 11 +- lib/src/lib.rs | 61 ++-- 6 files changed, 494 insertions(+), 295 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 727bb22..7c86e50 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -24,6 +24,13 @@ ark-serialize = { version = "0.4.0", default-features = false } w3f-bls = { version = "0.1.3", default-features = false } zeroize = { version = "1.8.1", default-features = false } rand_chacha = { version = "0.3.1" } +hkdf = "0.12.4" +ark-ec = { version = "0.4", default-features = false } +ark-ff = { version = "0.4", default-features = false } +# ark-transcript = { git = "https://github.com/w3f/ark-transcript.git", default-features = false} +ark-ed25519 = { version = "0.4", default-features = false } +dleq_vrf = { git = "https://github.com/w3f/ring-vrf.git", default-features = false, features = ["getrandom"]} +ark-transcript = { git = "https://github.com/w3f/ring-vrf.git", default-features = false} [dev-dependencies] rand_core = { version = "0.6.4", features = ["getrandom"], default-features = false } @@ -34,6 +41,11 @@ std = [ "ark-std/std", "ark-serialize/std", "ark-bls12-377/std", + "ark-ed25519/std", + "ark-ec/std", + "ark-ff/std", + "ark-transcript/std", + "dleq_vrf/std", "w3f-bls/std", "serde/std", "codec/std", diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 4ce4018..c3170e8 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -21,7 +21,6 @@ use alloc::{collections::BTreeMap, vec, vec::Vec}; #[cfg(feature = "client")] use crate::otp::BOTPGenerator; -use rand_chacha::ChaCha20Rng; use ark_std::rand::SeedableRng; use ark_std::rand::{CryptoRng, Rng}; @@ -32,162 +31,231 @@ use zeroize::Zeroize; use ark_serialize::CanonicalSerialize; use crate::types::*; +use ark_ec::CurveGroup; +use ark_transcript::{digest::Update, Transcript}; use ckb_merkle_mountain_range::{ - helper::leaf_index_to_pos, - util::{MemMMR, MemStore}, - MerkleProof, + helper::leaf_index_to_pos, + util::{MemMMR, MemStore}, + MerkleProof, +}; +use dleq_vrf::{EcVrfVerifier, PublicKey, SecretKey}; +use etf_crypto_primitives::{ + encryption::tlock::*, + ibe::fullident::Identity }; -use etf_crypto_primitives::{encryption::tlock::*, ibe::fullident::Identity}; use sha3::Digest; use w3f_bls::{DoublePublicKey, EngineBLS}; +/// The Murmur protocol label for domain separation in transcripts +pub const MURMUR_PROTO: &[u8] = b"Murmur://"; +/// The size of a 32-bit buffer +pub const ALLOCATED_BUFFER_BYTES: usize = 32; + /// Error types for murmur wallet usage #[derive(Debug, PartialEq)] pub enum Error { - ExecuteError, - MMRError, - InconsistentStore, - /// No leaf could be identified in the MMR at the specified position - NoLeafFound, - /// No ciphertext could be identified for the block within the current murmur store - NoCiphertextFound, - /// There was an error when executing timelock encryption (is the ciphertext too large?) - TlockFailed, - /// The buffer does not have enough space allocated - InvalidBufferSize, - /// The seed was invalid - InvalidSeed, - /// The public key was invalid (could not be decoded) - InvalidPubkey, + ExecuteError, + MMRError, + InconsistentStore, + /// No leaf could be identified in the MMR at the specified position + NoLeafFound, + /// No ciphertext could be identified for the block within the current murmur store + NoCiphertextFound, + /// There was an error when executing timelock encryption (is the ciphertext too large?) + TlockFailed, + /// The buffer does not have enough space allocated + InvalidBufferSize, + /// The seed was invalid + InvalidSeed, + /// The public key was invalid (could not be decoded) + InvalidPubkey, } /// The murmur store contains minimal data required to use a murmur wallet #[cfg(feature = "client")] #[derive(Clone, serde::Serialize, serde::Deserialize)] pub struct MurmurStore { - /// A map of block numbers to leaf positions in the mmr - pub metadata: BTreeMap, - /// The root of the mmr - pub root: Leaf, + /// The nonce of this murmur store + pub nonce: u64, + /// A map of block numbers to leaf positions in the mmr + pub metadata: BTreeMap, + /// The root of the mmr + pub root: Leaf, + /// A serialized VRF proof + pub proof: Vec, + /// A serialized public key associated with the VRF proof + pub public_key: Vec, } #[cfg(feature = "client")] impl MurmurStore { - /// Create a new Murmur store - /// - /// * `seed`: An any-length seed (i.e. password) - /// * `block_schedule`: The blocks for which OTP codes will be generated - /// * `ephemeral_msk`: Any 32 bytes - /// * `round_public_key`: The IDN beacon's public key - /// - pub fn new>( - mut seed: Vec, - block_schedule: Vec, - mut ephemeral_msk: [u8; 32], - round_public_key: DoublePublicKey, - ) -> Result { - let totp = build_generator(seed.clone())?; - seed.zeroize(); - let mut metadata = BTreeMap::new(); - - let store = MemStore::default(); - let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); - - for i in &block_schedule { - let otp_code = totp.generate(*i); - let identity = I::build_identity(*i); - - // we need to seed a new rng here - let mut hasher = sha3::Sha3_256::default(); - hasher.update(ephemeral_msk.to_vec().clone()); - hasher.update(otp_code.as_bytes().to_vec().clone()); - let hash = hasher.finalize(); - - let ephem_rng = ChaCha20Rng::from_seed(hash.into()); - let ct_bytes = timelock_encrypt::( - identity, - round_public_key.1, - ephemeral_msk, - otp_code.as_bytes(), - ephem_rng, - )?; - let leaf = Leaf(ct_bytes.clone()); - // Q: How can I test this? - // https://github.com/nervosnetwork/merkle-mountain-range/blob/9e77d3ef81ddfdd9b7dd9583762582e859849dde/src/mmr.rs#L60 - let _pos = mmr.push(leaf).map_err(|_| Error::InconsistentStore)?; - metadata.insert(*i, ct_bytes); - } - - ephemeral_msk.zeroize(); - let root = mmr.get_root().map_err(|_| Error::InconsistentStore)?; - - Ok(MurmurStore { metadata, root }) - } - - /// Build data required (proof and commitment) to execute a valid call from a murmur wallet - /// - /// * `seed`: The seed used to create the mmr - /// * `when`: The block number when the wallet is being used (or will be) - /// * `call_data`: The call to be executed with the wallet (at `when`) - /// - pub fn execute( - &self, - mut seed: Vec, - when: BlockNumber, - call_data: Vec, - ) -> Result<(MerkleProof, Vec, Ciphertext, u64), Error> { - if let Some(ciphertext) = self.metadata.get(&when) { - let commitment = MurmurStore::commit(seed.clone(), when, &call_data.clone())?; - seed.zeroize(); - let idx = get_key_index(&self.metadata, &when) - .expect("The key must exist within the metadata."); - let pos = leaf_index_to_pos(idx as u64); - let mmr = self.to_mmr()?; - let proof = mmr - .gen_proof(vec![pos]) - .map_err(|_| Error::InconsistentStore)?; - return Ok((proof, commitment, ciphertext.clone(), pos)); - } - - Err(Error::NoCiphertextFound) - } - - /// Generate a commitment (hash) to commit to executing a call at a specific block number - /// - /// * `seed`: The seed used to generated the MMR - /// * `when`: The block number when the commitment is verifiable - /// * `data`: The data to commit to - /// - fn commit(mut seed: Vec, when: BlockNumber, data: &[u8]) -> Result, Error> { - let botp = build_generator(seed.clone())?; - seed.zeroize(); - let otp_code = botp.generate(when); - - let mut hasher = sha3::Sha3_256::default(); - hasher.update(otp_code.as_bytes()); - hasher.update(data); - Ok(hasher.finalize().to_vec()) - } - /// Builds an mmr from the mmr store - /// - /// * `mmr`: a MemMMR instance (to be populated) - /// - fn to_mmr(&self) -> Result, Error> { - let store = MemStore::default(); - let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); - for (_block_number, ciphertext) in self.metadata.clone() { - mmr.push(Leaf(ciphertext)) - .map_err(|_| Error::InconsistentStore)?; - } + /// Create a new Murmur store + /// + /// * `seed`: An any-length seed (i.e. password) + /// * `block_schedule`: The blocks for which OTP codes will be generated + /// * `ephemeral_msk`: Any 32 bytes + /// * `round_public_key`: The IDN beacon's public key + /// + pub fn new, R>( + mut seed: Vec, + block_schedule: Vec, + nonce: u64, + round_public_key: DoublePublicKey, + rng: &mut R, + ) -> Result + where + R: Rng + CryptoRng + SeedableRng + Sized, + { + let mut witness = generate_witness(seed.clone(), rng); + let mut secret_key = SecretKey::<::Affine>::from_seed(&witness); + let pubkey = secret_key.as_publickey(); + let mut pubkey_bytes = Vec::new(); + pubkey.serialize_compressed(&mut pubkey_bytes).unwrap(); + + // generate a transcript s.t. the proof verification requires the nonce + let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + transcript.write_bytes(&nonce.to_be_bytes()); + let signature = secret_key.sign_thin_vrf_detached(transcript.clone(), &[]); + + let mut sig_bytes = Vec::new(); + signature.serialize_compressed(&mut sig_bytes).unwrap(); + + let totp = BOTPGenerator::new(witness.to_vec()).map_err(|_| Error::InvalidSeed)?; + + seed.zeroize(); + witness.zeroize(); + secret_key.zeroize(); + + let mut metadata = BTreeMap::new(); + let store = MemStore::default(); + let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); + + for &i in &block_schedule { + let otp_code = totp.generate(i); + let identity = I::build_identity(i); + + let mut ephemeral_msk: [u8; 32] = transcript + .clone() + .fork(b"otp-leaf-gen") + .chain(&i.to_be_bytes()) + .chain(&otp_code.as_bytes()) + .challenge(b"ephemeral_msk") + .read_byte_array(); + let ephem_rng = R::from_seed(ephemeral_msk); + + let ct_bytes = timelock_encrypt::( + identity, + round_public_key.1, + ephemeral_msk, + otp_code.as_bytes(), + ephem_rng, + )?; + ephemeral_msk.zeroize(); + let leaf = Leaf(ct_bytes.clone()); + // Q: How can I test this line? + // https://github.com/nervosnetwork/merkle-mountain-range/blob/9e77d3ef81ddfdd9b7dd9583762582e859849dde/src/mmr.rs#L60 + let _pos = mmr.push(leaf).map_err(|_| Error::InconsistentStore)?; + metadata.insert(i, ct_bytes); + } + + let root = mmr.get_root().map_err(|_| Error::InconsistentStore)?; + + Ok(MurmurStore { nonce, metadata, root, proof: sig_bytes, public_key: pubkey_bytes }) + } + + /// Build data required (proof and commitment) to execute a valid call from a murmur wallet + /// + /// * `seed`: The seed used to create the mmr + /// * `when`: The block number when the wallet is being used (or will be) + /// * `call_data`: The call to be executed with the wallet (at `when`) + /// + pub fn execute( + &self, + mut seed: Vec, + when: BlockNumber, + call_data: Vec, + mut rng: R, + ) -> Result<(MerkleProof, Vec, Ciphertext, u64), Error> { + if let Some(ciphertext) = self.metadata.get(&when) { + let commitment = MurmurStore::commit(seed.clone(), when, &call_data.clone(), &mut rng)?; + seed.zeroize(); + // let idx = get_key_index(&self.metadata, &when) + // .expect("The key must exist within the metadata."); + let idx = self.metadata.keys().position(|k| k == &when).expect("The leaf should exist"); + let pos = leaf_index_to_pos(idx as u64); + let mmr = self.to_mmr()?; + let proof = mmr.gen_proof(vec![pos]).map_err(|_| Error::InconsistentStore)?; + return Ok((proof, commitment, ciphertext.clone(), pos)); + } + + Err(Error::NoCiphertextFound) + } + + /// Generate a commitment (hash) to commit to executing a call at a specific block number + /// + /// * `seed`: The seed used to generated the MMR + /// * `when`: The block number when the commitment is verifiable + /// * `data`: The data to commit to + /// + fn commit( + mut seed: Vec, + when: BlockNumber, + data: &[u8], + mut rng: R, + ) -> Result, Error> { + let mut witness = generate_witness(seed.clone(), &mut rng); + let botp = BOTPGenerator::new(witness.to_vec()) + .map_err(|_| Error::InvalidSeed)?; + seed.zeroize(); + witness.zeroize(); + + let otp_code = botp.generate(when); + + let mut hasher = sha3::Sha3_256::default(); + Digest::update(&mut hasher, otp_code.as_bytes()); + Digest::update(&mut hasher, data); + Ok(hasher.finalize().to_vec()) + } + + /// Builds an mmr from the mmr store + /// + /// * `mmr`: a MemMMR instance (to be populated) + /// + fn to_mmr(&self) -> Result, Error> { + let store = MemStore::default(); + let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); + for (_block_number, ciphertext) in self.metadata.clone() { + mmr.push(Leaf(ciphertext)).map_err(|_| Error::InconsistentStore)?; + } + + Ok(mmr) + } +} - Ok(mmr) - } +/// construct a 32-byte witness by seeding a transcript with the given 'seed' +/// using a CSPRNG to generate the witness +/// +/// * `seed`: The value written to the transcript +/// * `rng`: A CSPRNG +/// +fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { + let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + transcript.write_bytes(&seed); + seed.zeroize(); + let witness: [u8; 32] = transcript.clone().witness(&mut rng).read_byte_array(); + witness } -#[cfg(feature = "client")] -/// Timelock encryption helper function -pub fn timelock_encrypt( +/// A helper function to perform timelock encryption +/// +/// * `identity`: The identity to encrypt for +/// * `pk`: The public key of the randomness beacon +/// * `ephemeral_msk`: A randomly sampled 32-byte secret key +/// * `message`: The message to be timelock encrypted +/// * `rng`: A CSPRNG +/// +fn timelock_encrypt( identity: Identity, pk: E::PublicKeyGroup, ephemeral_msk: [u8; 32], @@ -203,46 +271,83 @@ pub fn timelock_encrypt( Ok(ct_bytes) } -/// Build a block-otp generator from the seed -#[cfg(feature = "client")] -fn build_generator(mut seed: Vec) -> Result { - let mut hasher = sha3::Sha3_256::default(); - hasher.update(&seed); - seed.zeroize(); - let hash = hasher.finalize(); - BOTPGenerator::new(hash.to_vec()) - .map_err(|_| Error::InvalidSeed) -} - -// verify the correctness of execution parameters -pub fn verify( - root: Leaf, - proof: MerkleProof, - hash: Vec, - ciphertext: Vec, - otp: Vec, - aux_data: Vec, - pos: u64, -) -> bool { - let mut validity = proof - .verify(root, vec![(pos, Leaf(ciphertext))]) - .unwrap_or(false); - - if validity { - let mut hasher = sha3::Sha3_256::default(); - hasher.update(otp); - hasher.update(aux_data); - let expected_hash = hasher.finalize(); - - validity = validity && expected_hash.to_vec() == hash; - } - - validity -} - -/// get the index of a key in a BTreeMap -pub fn get_key_index(b: &BTreeMap, key: &K) -> Option { - b.keys().position(|k| k == key) +/// Functions for verifying execution and update requests +/// These functions would typically be called by an untrusted verifier (e.g. a blockchain runtime) +pub mod verifier { + use super::*; + use ark_serialize::CanonicalDeserialize; + use dleq_vrf::{ThinVrfProof}; + + #[derive(Debug, PartialEq)] + pub enum VerificationError { + UnserializableProof, + UnserializablePubkey, + } + + /// Verify the correctness of execution parameters by checking that the Merkle proof and hash are valid + /// The function outputs true if both conditions are true: + /// proof.verify(root, [(pos, Leaf(ciphertext))]) + /// hash = Sha256(otp || aux_data) + //// + /// It outputs false otherwise. + /// + /// * `root`: The root of the MMR + /// * `proof`: The Merkle proof to verify + /// * `hash`: A (potential) commitment to the OTP and aux_data + /// * `ciphertext`: A timelocked ciphertext + /// * `otp`: The OTP + /// * `aux_data`: The expected aux data used to generate the commitment + /// * `pos`: The position of the Ciphertext within the MMR + /// + pub fn verify_execute( + root: Leaf, + proof: MerkleProof, + hash: Vec, + ciphertext: Vec, + otp: &[u8], + aux_data: &[u8], + pos: u64, + ) -> bool { + let mut validity = proof.verify(root, vec![(pos, Leaf(ciphertext))]) + .unwrap_or(false); + + if validity { + let mut hasher = sha3::Sha3_256::default(); + Digest::update(&mut hasher, otp); + Digest::update(&mut hasher, aux_data); + let expected_hash = hasher.finalize().to_vec(); + + validity = validity && expected_hash == hash; + } + + validity + } + + /// Verifies a Schnorr proof + /// This is used to ensure that subsequent calls to the 'new' function are called with the same seed + /// + /// * `serialized_proof`: The serialized proof + /// * `serialized_pubkey`: The serialized public key + /// * `nonce`: A nonce value + /// + pub fn verify_update( + serialized_proof: Vec, + serialized_pubkey: Vec, + nonce: u64, + ) -> Result { + // build transcript + let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + transcript.write_bytes(&nonce.to_be_bytes()); + // deserialize proof and pubkey + let proof = ThinVrfProof::<::Affine>:: + deserialize_compressed(&mut &serialized_proof[..]) + .map_err(|_| VerificationError::UnserializableProof)?; + let pk = PublicKey::<::Affine>:: + deserialize_compressed(&mut &serialized_pubkey[..]) + .map_err(|_| VerificationError::UnserializablePubkey)?; + + Ok(pk.vrf_verify_detached(transcript, &[], &proof).is_ok()) + } } #[cfg(test)] @@ -251,8 +356,15 @@ mod tests { use super::*; use w3f_bls::{DoublePublicKeyScheme, TinyBLS377}; use rand_chacha::ChaCha20Rng; - use rand_core::OsRng; - use ark_std::rand::SeedableRng; + use ark_std::rand::SeedableRng; + + /// + pub const BLOCK_SCHEDULE: &[BlockNumber] = &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + ]; + + pub const WHEN: BlockNumber = 10; + pub const OTP: &[u8] = b"823185"; pub struct DummyIdBuilder; impl IdentityBuilder for DummyIdBuilder { @@ -271,52 +383,51 @@ mod tests { keypair.public.0, ); - let ephem_msk = [1; 32]; let seed = vec![1, 2, 3]; - let schedule = vec![1, 2, 3]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::new::( seed.clone(), - schedule.clone(), - ephem_msk, + BLOCK_SCHEDULE.to_vec(), + 0, double_public, - ) - .unwrap(); + &mut rng, + ).unwrap(); - assert!(murmur_store.metadata.keys().len() == 3); + assert!(murmur_store.metadata.keys().len() == BLOCK_SCHEDULE.len()); + assert!(murmur_store.root.0.len() == 32); + assert!(murmur_store.proof.len() == 80); + assert!(murmur_store.public_key.len() == 48); } #[cfg(feature = "client")] #[test] pub fn it_can_generate_valid_output_and_verify_it() { - let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); let double_public: DoublePublicKey = DoublePublicKey( keypair.into_public_key_in_signature_group().0, keypair.public.0, ); - let ephem_msk = [1; 32]; let seed = vec![1, 2, 3]; - let schedule = vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - ]; - let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::new::( seed.clone(), - schedule.clone(), - ephem_msk, + BLOCK_SCHEDULE.to_vec(), + 0, double_public, - ) - .unwrap(); - - // the block number when this would execute - let when = 1; + &mut rng, + ).unwrap(); let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = murmur_store - .execute(seed.clone(), when, aux_data.clone()) + .execute( + seed.clone(), + WHEN, + aux_data.clone(), + &mut rng, + ) .unwrap(); // sanity check @@ -324,18 +435,13 @@ mod tests { .verify(root.clone(), vec![(pos, Leaf(ciphertext.clone()))]) .unwrap()); - // in practice, the otp code would be timelock decrypted - // but for testing purposes, we will just calculate the expected one now - let botp = build_generator(seed.clone()).unwrap(); - let otp_code = botp.generate(when); - - assert!(verify( + assert!(verifier::verify_execute( root, proof, commitment, ciphertext, - otp_code.as_bytes().to_vec(), - aux_data, + OTP, + &aux_data, pos, )); } @@ -343,30 +449,28 @@ mod tests { #[cfg(feature = "client")] #[test] pub fn it_fails_to_generate_execute_output_when_ciphertext_dne() { - let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); let double_public: DoublePublicKey = DoublePublicKey( keypair.into_public_key_in_signature_group().0, keypair.public.0, ); - let ephem_msk = [1; 32]; let seed = vec![1, 2, 3]; - let schedule = vec![1, 2, 3, 4, 5]; - - let aux_data = vec![2, 3, 4, 5]; + let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::new::( seed.clone(), - schedule.clone(), - ephem_msk, + BLOCK_SCHEDULE.to_vec(), + 0, double_public, - ) - .unwrap(); + &mut rng, + ).unwrap(); // the block number when this would execute let when = 1000; - match murmur_store.execute(seed.clone(), when, aux_data.clone()) { + match murmur_store.execute(seed.clone(), when, aux_data.clone(), &mut rng) { Ok(_) => panic!("There should be an error"), Err(e) => assert_eq!(e, Error::NoCiphertextFound), } @@ -375,105 +479,159 @@ mod tests { #[cfg(feature = "client")] #[test] pub fn it_fails_on_verify_bad_aux_data() { - let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); let double_public: DoublePublicKey = DoublePublicKey( keypair.into_public_key_in_signature_group().0, keypair.public.0, ); - let ephem_msk = [1; 32]; let seed = vec![1, 2, 3]; - let schedule = vec![1, 2, 3]; - let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::new::( seed.clone(), - schedule.clone(), - ephem_msk, + BLOCK_SCHEDULE.to_vec(), + 0, double_public, - ) - .unwrap(); + &mut rng, + ).unwrap(); - // the block number when this would execute - let when = 1; let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = murmur_store - .execute(seed.clone(), when, aux_data.clone()) + .execute( + seed.clone(), + WHEN, + aux_data.clone(), + &mut rng, + ) .unwrap(); - // in practice, the otp code would be timelock decrypted - // but for testing purposes, we will just calculate the expected one now - let botp = build_generator(seed.clone()).unwrap(); - let otp_code = botp.generate(when); - let bad_aux = vec![2, 3, 13, 3]; - assert!(!verify( + assert!(!verifier::verify_execute( root, proof, commitment, ciphertext, - otp_code.as_bytes().to_vec(), - bad_aux, + OTP, + &bad_aux, pos, )); } #[test] pub fn it_fails_on_verify_bad_proof() { - let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); let double_public: DoublePublicKey = DoublePublicKey( keypair.into_public_key_in_signature_group().0, keypair.public.0, ); + let other_keypair = w3f_bls::KeypairVT::::generate(&mut rng); let other_double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, + other_keypair.into_public_key_in_signature_group().0, + other_keypair.public.0, ); - let ephem_msk = [1; 32]; let seed = vec![1, 2, 3]; - let schedule = vec![1, 2, 3]; - let other_schedule = vec![1, 2, 3, 4, 5]; + let other_seed = vec![2,3,4]; - let aux_data = vec![2, 3, 4, 5]; - - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::new::( seed.clone(), - schedule.clone(), - ephem_msk, + BLOCK_SCHEDULE.to_vec(), + 0, double_public, - ) - .unwrap(); + &mut rng, + ).unwrap(); - let other_murmur_store = MurmurStore::new::( - seed.clone(), - other_schedule.clone(), - ephem_msk, + let other_murmur_store = MurmurStore::new::( + other_seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, other_double_public, - ) - .unwrap(); + &mut rng, + ).unwrap(); + + let aux_data = vec![2, 3, 4, 5]; // the block number when this would execute - let when = 1; let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = other_murmur_store - .execute(seed.clone(), when, aux_data.clone()) + .execute( + other_seed.clone(), + WHEN, + aux_data.clone(), + &mut rng, + ) .unwrap(); - // in practice, the otp code would be timelock decrypted - // but for testing purposes, we will just calculate the expected one now - let botp = build_generator(seed.clone()).unwrap(); - let otp_code = botp.generate(when); - assert!(!verify( + assert!(!verifier::verify_execute( root, proof, commitment, ciphertext, - otp_code.as_bytes().to_vec(), - aux_data, + OTP, + &aux_data, pos, )); } + + #[test] + fn it_can_generate_and_verify_schnorr_proofs() { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = DoublePublicKey( + keypair.into_public_key_in_signature_group().0, + keypair.public.0, + ); + + let seed = vec![1, 2, 3]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ).unwrap(); + + let proof = murmur_store.proof; + let pk = murmur_store.public_key; + // now verify the proof for nonce = 0 + assert!(verifier::verify_update::( + proof, + pk, + 0, + ).is_ok()); + } + + #[test] + fn it_cannot_verify_schnorr_proof_with_bad_nonce() { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = DoublePublicKey( + keypair.into_public_key_in_signature_group().0, + keypair.public.0, + ); + + let seed = vec![1, 2, 3]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ).unwrap(); + + let proof = murmur_store.proof; + let pk = murmur_store.public_key; + // now verify the proof for nonce = 1 + assert!(!verifier::verify_update::( + proof, + pk, + 1, + ).unwrap()); + } } diff --git a/core/src/otp.rs b/core/src/otp.rs index 1e86787..3979d0d 100644 --- a/core/src/otp.rs +++ b/core/src/otp.rs @@ -14,11 +14,12 @@ * limitations under the License. */ -use totp_rs::{Secret, TOTP, Algorithm}; use alloc::{ vec::Vec, string::String, }; +use totp_rs::{Secret, TOTP, Algorithm}; +use zeroize::Zeroize; #[derive(Debug)] pub enum OTPError { @@ -37,17 +38,18 @@ impl BOTPGenerator { /// /// * `seed`: The seed used to generate OTP codes /// - pub fn new(seed: Vec) -> Result { - let secret = Secret::Raw(seed.to_vec()).to_bytes() + pub fn new(mut seed: Vec) -> Result { + let mut secret = Secret::Raw(seed.clone()).to_bytes() .map_err(|_| OTPError::InvalidSecret)?; + seed.zeroize(); let totp = TOTP::new( Algorithm::SHA256, // algorithm 6, // num digits 1, // skew 1, // step - secret // secret + secret.clone() // secret ).map_err(|_| OTPError::InvalidSecret)?; - + secret.zeroize(); Ok(BOTPGenerator { totp }) } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 86873e9..1699d4b 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -20,6 +20,7 @@ subxt-signer = "0.35.2" tokio = { version = "1.35", features = ["macros", "time", "rt-multi-thread"] } clap = { version = "4.1.1", features = ["derive"] } rand_chacha = "0.3.1" +rand_core = "0.6.4" ckb-merkle-mountain-range = "0.5.2" sha3 = "0.10.8" thiserror = "1.0" diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index a945986..5793b0c 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -24,6 +24,9 @@ use std::fs::File; use std::time::Instant; use subxt_signer::sr25519::dev; use thiserror::Error; +use rand_core::OsRng; +use rand_chacha::ChaCha20Rng; +use ark_std::rand::SeedableRng; /// Command line #[derive(Parser)] @@ -88,7 +91,9 @@ pub const MMR_STORE_FILEPATH: &str = "mmr_store"; async fn main() -> Result<(), Box> { let cli = Cli::parse(); let before = Instant::now(); - let ephem_msk = [1; 32]; + + // The OsRng is the canonical source of randomness + let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); let (client, current_block_number, round_pubkey_bytes) = idn_connect().await?; @@ -106,9 +111,10 @@ async fn main() -> Result<(), Box> { let (call, mmr_store) = create( args.name.as_bytes().to_vec(), args.seed.as_bytes().to_vec(), - ephem_msk, + 0, schedule, round_pubkey_bytes, + &mut rng, ) .map_err(|_| CLIError::MurmurCreationFailed)?; // 3. add to storage @@ -146,6 +152,7 @@ async fn main() -> Result<(), Box> { target_block_number, store, balance_transfer_call, + &mut rng, ) .map_err(|_| CLIError::MurmurExecutionFailed)?; // submit the tx using alice to sign it diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fbed771..81073e6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -27,6 +27,7 @@ pub use etf::{ murmur::calls::types::{Create, Proxy}, runtime_types::node_template_runtime::RuntimeCall, }; +use rand_chacha::ChaCha20Rng; pub use murmur_core::{ murmur::{Error, MurmurStore}, types::BlockNumber, @@ -63,19 +64,22 @@ impl IdentityBuilder for BasicIdBuilder { pub fn create( name: Vec, mut seed: Vec, - mut ephem_msk: [u8; 32], + nonce: u64, block_schedule: Vec, round_pubkey_bytes: Vec, + rng: &mut ChaCha20Rng, ) -> Result<(TxPayload, MurmurStore), Error> { + let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes) .map_err(|_| Error::InvalidPubkey)?; - let mmr_store = MurmurStore::new::( + + let mmr_store = MurmurStore::new::( seed.clone(), block_schedule.clone(), - ephem_msk, + nonce, round_pubkey, + rng, )?; - ephem_msk.zeroize(); seed.zeroize(); let root = mmr_store.root.clone(); @@ -107,8 +111,14 @@ pub fn prepare_execute( when: BlockNumber, store: MurmurStore, call: RuntimeCall, + rng: &mut ChaCha20Rng, ) -> Result, Error> { - let (proof, commitment, ciphertext, pos) = store.execute(seed.clone(), when, call.encode())?; + let (proof, commitment, ciphertext, pos) = store.execute( + seed.clone(), + when, + call.encode(), + rng, + )?; seed.zeroize(); let size = proof.mmr_size(); let proof_items: Vec> = proof @@ -170,22 +180,23 @@ mod tests { use super::*; use subxt::tx::TxPayload; + use rand_core::{OsRng, SeedableRng}; #[test] pub fn it_can_create_an_mmr_store_and_call_data() { let name = b"name".to_vec(); let seed = b"seed".to_vec(); - let ephem_msk = [1; 32]; let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); + let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); let (call, mmr_store) = create( name.clone(), seed, - ephem_msk, + 0, block_schedule, double_public_bytes, - ) - .unwrap(); + &mut rng, + ).unwrap(); let expected_call = etf::tx().murmur().create( mmr_store.root.0, @@ -196,28 +207,28 @@ mod tests { let actual_details = call.validation_details().unwrap(); let expected_details = expected_call.validation_details().unwrap(); - assert_eq!(actual_details.pallet_name, expected_details.pallet_name,); + assert_eq!(actual_details.pallet_name, expected_details.pallet_name); - assert_eq!(actual_details.call_name, expected_details.call_name,); + assert_eq!(actual_details.call_name, expected_details.call_name); - assert_eq!(actual_details.hash, expected_details.hash,); + assert_eq!(actual_details.hash, expected_details.hash); } #[test] pub fn it_can_prepare_valid_execution_call_data() { let name = b"name".to_vec(); let seed = b"seed".to_vec(); - let ephem_msk = [1; 32]; let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); - let (_call, mmr_store) = create( + let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); + let (call, mmr_store) = create( name.clone(), seed.clone(), - ephem_msk, + 0, block_schedule, double_public_bytes, - ) - .unwrap(); + &mut rng, + ).unwrap(); let bob = subxt_signer::sr25519::dev::bob().public_key(); let bob2 = subxt_signer::sr25519::dev::bob().public_key(); @@ -237,18 +248,25 @@ mod tests { }, ); + let when = 1; + let actual_call = prepare_execute( name.clone(), seed.clone(), - 1, + when, mmr_store.clone(), balance_transfer_call, + &mut rng, ) .unwrap(); let (proof, commitment, ciphertext, _pos) = mmr_store - .execute(seed.clone(), 1, balance_transfer_call_2.encode()) - .unwrap(); + .execute( + seed.clone(), + 1, + balance_transfer_call_2.encode(), + &mut rng, + ).unwrap(); let size = proof.mmr_size(); let proof_items: Vec> = proof @@ -256,9 +274,10 @@ mod tests { .iter() .map(|leaf| leaf.0.clone()) .collect::>(); + let expected_call = etf::tx().murmur().proxy( BoundedVec(name), - 0, + when.into(), commitment, ciphertext, proof_items, From 7fa47d953c908598eb8fce4b91df8370bcc79993 Mon Sep 17 00:00:00 2001 From: driemworks Date: Wed, 23 Oct 2024 14:57:05 -0500 Subject: [PATCH 02/19] chore: add Cargo.lock --- Cargo.lock | 268 ++++++++++++++++++++--------------------------------- 1 file changed, 101 insertions(+), 167 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdf98a4..35c7d29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,6 +219,17 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-curve25519" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab653b3eff27100f7dcb06b94785f2fbe0d1230408df55d543ee0ef48cd8760" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ec" version = "0.4.2" @@ -287,6 +298,18 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-ed25519" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed5162ef7c978cfefffcd08cd8393d174836ae1c9fc8fa2dfcd5c2f03a4a9" +dependencies = [ + "ark-curve25519", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -385,6 +408,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-secret-scalar" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf.git#0fef8266d851932ad25d6b41bc4b34d834d1e11d" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "ark-transcript 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", + "digest 0.10.7", + "getrandom_or_panic", + "zeroize", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -446,6 +484,19 @@ dependencies = [ "sha3", ] +[[package]] +name = "ark-transcript" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf.git#0fef8266d851932ad25d6b41bc4b34d834d1e11d" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "digest 0.10.7", + "rand_core", + "sha3", +] + [[package]] name = "array-bytes" version = "6.2.3" @@ -648,7 +699,7 @@ dependencies = [ "ark-ff", "ark-serialize", "ark-std", - "dleq_vrf", + "dleq_vrf 0.0.2 (git+https://github.com/w3f/ring-vrf?rev=e9782f9)", "fflonk", "merlin", "rand_chacha", @@ -1284,17 +1335,6 @@ dependencies = [ "syn 2.0.79", ] -[[package]] -name = "derive-where" -version = "1.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.79", -] - [[package]] name = "derive_more" version = "0.99.18" @@ -1337,7 +1377,7 @@ dependencies = [ "ark-ec", "ark-ff", "ark-scale", - "ark-secret-scalar", + "ark-secret-scalar 0.0.2 (git+https://github.com/w3f/ring-vrf?rev=e9782f9)", "ark-serialize", "ark-std", "ark-transcript 0.0.2 (git+https://github.com/w3f/ring-vrf?rev=e9782f9)", @@ -1345,6 +1385,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dleq_vrf" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf.git#0fef8266d851932ad25d6b41bc4b34d834d1e11d" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-secret-scalar 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", + "ark-serialize", + "ark-std", + "ark-transcript 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", + "arrayvec 0.7.6", + "zeroize", +] + [[package]] name = "docify" version = "0.2.8" @@ -2008,6 +2063,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.8.1" @@ -2516,10 +2580,16 @@ name = "murmur-core" version = "0.1.0" dependencies = [ "ark-bls12-377", + "ark-ec", + "ark-ed25519", + "ark-ff", "ark-serialize", "ark-std", + "ark-transcript 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", "ckb-merkle-mountain-range", + "dleq_vrf 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", "etf-crypto-primitives 0.2.4 (git+https://github.com/ideal-lab5/etf-sdk/?branch=dev)", + "hkdf", "parity-scale-codec", "rand_chacha", "rand_core", @@ -2544,14 +2614,13 @@ dependencies = [ "murmur-test-utils", "parity-scale-codec", "rand_chacha", + "rand_core", "serde", "serde_cbor", "sha3", "sp-consensus-beefy-etf", "sp-core", "subxt", - "subxt-core", - "subxt-metadata 0.37.0", "subxt-signer", "thiserror", "tokio", @@ -3398,19 +3467,7 @@ checksum = "662d10dcd57b1c2a3c41c9cf68f71fb09747ada1ea932ad961aca7e2ca28315f" dependencies = [ "parity-scale-codec", "scale-info", - "scale-type-resolver 0.1.1", - "serde", -] - -[[package]] -name = "scale-bits" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" -dependencies = [ - "parity-scale-codec", - "scale-info", - "scale-type-resolver 0.2.0", + "scale-type-resolver", "serde", ] @@ -3423,24 +3480,9 @@ dependencies = [ "derive_more", "parity-scale-codec", "primitive-types", - "scale-bits 0.5.0", - "scale-decode-derive 0.11.1", - "scale-type-resolver 0.1.1", - "smallvec", -] - -[[package]] -name = "scale-decode" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" -dependencies = [ - "derive_more", - "parity-scale-codec", - "primitive-types", - "scale-bits 0.6.0", - "scale-decode-derive 0.13.1", - "scale-type-resolver 0.2.0", + "scale-bits", + "scale-decode-derive", + "scale-type-resolver", "smallvec", ] @@ -3456,18 +3498,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "scale-decode-derive" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb22f574168103cdd3133b19281639ca65ad985e24612728f727339dcaf4021" -dependencies = [ - "darling 0.14.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "scale-encode" version = "0.6.0" @@ -3477,24 +3507,9 @@ dependencies = [ "derive_more", "parity-scale-codec", "primitive-types", - "scale-bits 0.5.0", - "scale-encode-derive 0.6.0", - "scale-type-resolver 0.1.1", - "smallvec", -] - -[[package]] -name = "scale-encode" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" -dependencies = [ - "derive_more", - "parity-scale-codec", - "primitive-types", - "scale-bits 0.6.0", - "scale-encode-derive 0.7.1", - "scale-type-resolver 0.2.0", + "scale-bits", + "scale-encode-derive", + "scale-type-resolver", "smallvec", ] @@ -3511,19 +3526,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "scale-encode-derive" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ab7e60e2d9c8d47105f44527b26f04418e5e624ffc034f6b4a86c0ba19c5bf" -dependencies = [ - "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "scale-info" version = "2.11.3" @@ -3560,16 +3562,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "scale-type-resolver" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" -dependencies = [ - "scale-info", - "smallvec", -] - [[package]] name = "scale-typegen" version = "0.2.1" @@ -3595,33 +3587,15 @@ dependencies = [ "either", "frame-metadata 15.1.0", "parity-scale-codec", - "scale-bits 0.5.0", - "scale-decode 0.11.1", - "scale-encode 0.6.0", + "scale-bits", + "scale-decode", + "scale-encode", "scale-info", - "scale-type-resolver 0.1.1", + "scale-type-resolver", "serde", "yap", ] -[[package]] -name = "scale-value" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6ab090d823e75cfdb258aad5fe92e13f2af7d04b43a55d607d25fcc38c811" -dependencies = [ - "derive_more", - "either", - "frame-metadata 15.1.0", - "parity-scale-codec", - "scale-bits 0.6.0", - "scale-decode 0.13.1", - "scale-encode 0.7.1", - "scale-info", - "scale-type-resolver 0.2.0", - "serde", -] - [[package]] name = "schannel" version = "0.1.24" @@ -4758,17 +4732,17 @@ dependencies = [ "jsonrpsee", "parity-scale-codec", "primitive-types", - "scale-bits 0.5.0", - "scale-decode 0.11.1", - "scale-encode 0.6.0", + "scale-bits", + "scale-decode", + "scale-encode", "scale-info", - "scale-value 0.14.1", + "scale-value", "serde", "serde_json", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "subxt-lightclient", "subxt-macro", - "subxt-metadata 0.35.3", + "subxt-metadata", "thiserror", "tokio-util", "tracing", @@ -4790,39 +4764,12 @@ dependencies = [ "quote", "scale-info", "scale-typegen", - "subxt-metadata 0.35.3", + "subxt-metadata", "syn 2.0.79", "thiserror", "tokio", ] -[[package]] -name = "subxt-core" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f41eb2e2eea6ed45649508cc735f92c27f1fcfb15229e75f8270ea73177345" -dependencies = [ - "base58", - "blake2", - "derive-where", - "frame-metadata 16.0.0", - "hashbrown 0.14.5", - "hex", - "impl-serde", - "parity-scale-codec", - "primitive-types", - "scale-bits 0.6.0", - "scale-decode 0.13.1", - "scale-encode 0.7.1", - "scale-info", - "scale-value 0.16.3", - "serde", - "serde_json", - "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "subxt-metadata 0.37.0", - "tracing", -] - [[package]] name = "subxt-lightclient" version = "0.35.3" @@ -4869,19 +4816,6 @@ dependencies = [ "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "subxt-metadata" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738be5890fdeff899bbffff4d9c0f244fe2a952fb861301b937e3aa40ebb55da" -dependencies = [ - "frame-metadata 16.0.0", - "hashbrown 0.14.5", - "parity-scale-codec", - "scale-info", - "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "subxt-signer" version = "0.35.3" From fe641cfb52a61d79cfb5e82a77154966f98f0749 Mon Sep 17 00:00:00 2001 From: driemworks Date: Thu, 24 Oct 2024 09:06:56 -0500 Subject: [PATCH 03/19] feat: move idn_connect, fix feature gating --- Cargo.lock | 1 - core/src/murmur.rs | 8 ++++-- lib/Cargo.toml | 1 - lib/src/bin/murmur/main.rs | 46 ++++++++++++++++++++++++----- lib/src/lib.rs | 59 +++++++++++--------------------------- 5 files changed, 61 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 140e30f..35c7d29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2610,7 +2610,6 @@ dependencies = [ "ark-std", "ckb-merkle-mountain-range", "clap", - "hkdf", "murmur-core", "murmur-test-utils", "parity-scale-codec", diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 55fa094..e37dd7c 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -243,6 +243,7 @@ impl MurmurStore { /// * `seed`: The value written to the transcript /// * `rng`: A CSPRNG /// +#[cfg(feature = "client")] fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { let mut transcript = Transcript::new_labeled(MURMUR_PROTO); transcript.write_bytes(&seed); @@ -259,6 +260,7 @@ fn generate_witness(mut seed: Vec, mut rng: R) - /// * `message`: The message to be timelock encrypted /// * `rng`: A CSPRNG /// +#[cfg(feature = "client")] fn timelock_encrypt( identity: Identity, pk: E::PublicKeyGroup, @@ -335,17 +337,19 @@ pub mod verifier { /// * `nonce`: A nonce value /// pub fn verify_update( - serialized_proof: Vec, + serialized_proof: Vec, serialized_pubkey: Vec, nonce: u64, ) -> Result { // build transcript let mut transcript = Transcript::new_labeled(MURMUR_PROTO); transcript.write_bytes(&nonce.to_be_bytes()); - // deserialize proof and pubkey + + // deserialize proof and pubkey let proof = ThinVrfProof::<::Affine>:: deserialize_compressed(&mut &serialized_proof[..]) .map_err(|_| VerificationError::UnserializableProof)?; + let pk = PublicKey::<::Affine>:: deserialize_compressed(&mut &serialized_pubkey[..]) .map_err(|_| VerificationError::UnserializablePubkey)?; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index ee2b399..80ffe1f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -45,7 +45,6 @@ ark-serialize = "0.4.0" w3f-bls = "0.1.3" murmur-core = { path = "../core/", features = ["client"] } zeroize = "1.8.1" -hkdf = "0.12.4" [dev-dependencies] diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index b163d99..f6d9ad4 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -16,12 +16,16 @@ use clap::{Parser, Subcommand}; use murmur_lib::{ - create, etf, idn_connect, prepare_execute, BlockNumber, BoundedVec, MurmurStore, RuntimeCall, + create, etf, prepare_execute, BlockNumber, BoundedVec, MurmurStore, RuntimeCall, }; + use rand_core::{OsRng, SeedableRng}; use rand_chacha::ChaCha20Rng; use sp_core::crypto::Ss58Codec; use std::{fs::File, time::Instant}; +use subxt::{ + backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig, +}; use subxt_signer::sr25519::dev; use thiserror::Error; @@ -105,22 +109,21 @@ async fn main() -> Result<(), Box> { } // 2. create mmr - let create_data = create( + let mmr_store = create( args.seed.as_bytes().to_vec(), 0, schedule, round_pubkey_bytes, &mut rng, - ) - .map_err(|_| CLIError::MurmurCreationFailed)?; + ).map_err(|_| CLIError::MurmurCreationFailed)?; // 3. add to storage - write_mmr_store(create_data.mmr_store.clone(), MMR_STORE_FILEPATH); + write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); // 4. build the call let call = etf::tx().murmur().create( - create_data.root, - create_data.size, + mmr_store.root.0, + mmr_store.metadata.keys().len() as u64, BoundedVec(args.name.as_bytes().to_vec()), ); @@ -175,6 +178,35 @@ async fn main() -> Result<(), Box> { Ok(()) } +/// 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 ws_url = std::env::var("WS_URL").unwrap_or_else(|_| { + let fallback_url = "ws://localhost:9944".to_string(); + println!("⚠️ WS_URL environment variable not set. Using fallback URL: {}", fallback_url); + fallback_url + }); + + let rpc_client = RpcClient::from_url(&ws_url).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 = client.storage().at_latest().await?.fetch(&round_key_query).await?; + let round_pubkey_bytes = result.unwrap().as_type::>()?; + + println!("🔑 Successfully retrieved the round public key."); + + 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((client, current_block_number, round_pubkey_bytes)) +} + /// read an MMR from a file fn load_mmr_store(path: &str) -> Result { let mmr_store_file = File::open(path).expect("Unable to open file"); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index a66e986..53f2d9d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -15,12 +15,8 @@ */ use beefy::{known_payloads, Commitment, Payload}; -use hkdf::Hkdf; use murmur_core::types::{Identity, IdentityBuilder}; use serde::Serialize; -use subxt::{ - backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig, ext::codec::Encode, -}; use w3f_bls::{DoublePublicKey, SerializableToBytes, TinyBLS377}; use zeroize::Zeroize; @@ -32,6 +28,7 @@ pub use murmur_core::{ types::BlockNumber, }; use rand_chacha::ChaCha20Rng; +use subxt::ext::codec::Encode; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "artifacts/metadata.scale")] @@ -60,7 +57,12 @@ pub struct CreateData { pub root: Vec, /// The size of the MMR pub size: u64, + /// The murmur store (map of block nubmer to ciphertext) pub mmr_store: MurmurStore, + /// The serialized VRF public key + pub public_key_bytes: Vec, + /// The serialized Schnorr signature + pub proof_bytes: Vec, } #[derive(Serialize)] @@ -69,8 +71,11 @@ pub struct ProxyData { pub position: u64, /// The hash of the commitment pub hash: Vec, + /// The timelocked ciphertext pub ciphertext: Vec, + /// The Merkle proof items pub proof_items: Vec>, + /// The size of the Merkle proof pub size: u64, } @@ -85,7 +90,7 @@ pub fn create( block_schedule: Vec, round_pubkey_bytes: Vec, rng: &mut ChaCha20Rng, -) -> Result { +) -> Result { let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes) .map_err(|_| Error::InvalidPubkey)?; @@ -97,10 +102,7 @@ pub fn create( rng, )?; seed.zeroize(); - - let root = mmr_store.root.clone(); - - Ok(CreateData { root: root.0, size: mmr_store.metadata.len() as u64, mmr_store }) + Ok(mmr_store) } /// Return the data needed for the immediate execution of the proxied call. @@ -133,35 +135,6 @@ pub fn prepare_execute( Ok(ProxyData { position: pos, hash: commitment, ciphertext, proof_items, size }) } -/// Async connection to the Ideal Network -/// if successful then fetch data -/// else error if unreachable -pub async fn idn_connect( -) -> Result<(OnlineClient, BlockNumber, Vec), Box> { - println!("🎲 Connecting to Ideal network (local node)"); - let ws_url = std::env::var("WS_URL").unwrap_or_else(|_| { - let fallback_url = "ws://localhost:9944".to_string(); - println!("⚠️ WS_URL environment variable not set. Using fallback URL: {}", fallback_url); - fallback_url - }); - - let rpc_client = RpcClient::from_url(&ws_url).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 = client.storage().at_latest().await?.fetch(&round_key_query).await?; - let round_pubkey_bytes = result.unwrap().as_type::>()?; - - println!("🔑 Successfully retrieved the round public key."); - - 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((client, current_block_number, round_pubkey_bytes)) -} - #[cfg(test)] mod tests { use super::*; @@ -176,7 +149,7 @@ mod tests { let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); - let create_data = + let mmr_store = create(seed.clone(), 0, block_schedule.clone(), double_public_bytes.clone(), &mut rng) .unwrap(); @@ -188,8 +161,8 @@ mod tests { // &mut rng, // ).unwrap(); - assert_eq!(create_data.mmr_store.root.0.len(), 32); - assert_eq!(create_data.size, 7); + assert_eq!(mmr_store.root.0.len(), 32); + assert_eq!(mmr_store.size, 7); } #[test] @@ -199,7 +172,7 @@ mod tests { let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); - let create_data = create( + let mmr_store = create( seed.clone(), 0, block_schedule, @@ -234,7 +207,7 @@ mod tests { let proxy_data = prepare_execute( seed.clone(), when, - create_data.mmr_store.clone(), + mmr_store.clone(), &balance_transfer_call, &mut rng, ) From da6590e7795116434e4c5ef678598c19e3e9cf59 Mon Sep 17 00:00:00 2001 From: driemworks Date: Thu, 24 Oct 2024 21:39:33 -0500 Subject: [PATCH 04/19] feat: enhance tests --- core/src/murmur.rs | 33 +++++++++++++++++++++++++++------ lib/src/lib.rs | 23 ++++++++++------------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index e37dd7c..d3541c1 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -182,10 +182,9 @@ impl MurmurStore { mut rng: R, ) -> Result<(MerkleProof, Vec, Ciphertext, u64), Error> { if let Some(ciphertext) = self.metadata.get(&when) { + let commitment = MurmurStore::commit(seed.clone(), when, &call_data.clone(), &mut rng)?; seed.zeroize(); - // let idx = get_key_index(&self.metadata, &when) - // .expect("The key must exist within the metadata."); let idx = self.metadata.keys().position(|k| k == &when).expect("The leaf should exist"); let pos = leaf_index_to_pos(idx as u64); let mmr = self.to_mmr()?; @@ -244,7 +243,7 @@ impl MurmurStore { /// * `rng`: A CSPRNG /// #[cfg(feature = "client")] -fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { +pub fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { let mut transcript = Transcript::new_labeled(MURMUR_PROTO); transcript.write_bytes(&seed); seed.zeroize(); @@ -348,11 +347,11 @@ pub mod verifier { // deserialize proof and pubkey let proof = ThinVrfProof::<::Affine>:: deserialize_compressed(&mut &serialized_proof[..]) - .map_err(|_| VerificationError::UnserializableProof)?; + .map_err(|_| VerificationError::UnserializableProof)?; let pk = PublicKey::<::Affine>:: deserialize_compressed(&mut &serialized_pubkey[..]) - .map_err(|_| VerificationError::UnserializablePubkey)?; + .map_err(|_| VerificationError::UnserializablePubkey)?; Ok(pk.vrf_verify_detached(transcript, &[], &proof).is_ok()) } @@ -365,6 +364,7 @@ mod tests { use w3f_bls::{DoublePublicKeyScheme, TinyBLS377}; use rand_chacha::ChaCha20Rng; use ark_std::rand::SeedableRng; + use ark_serialize::CanonicalDeserialize; /// pub const BLOCK_SCHEDULE: &[BlockNumber] = &[ @@ -585,6 +585,10 @@ mod tests { keypair.public.0, ); + let mut bytes = Vec::new(); + double_public.serialize_compressed(&mut bytes).unwrap(); + let same_double_public = DoublePublicKey::::deserialize_compressed(&mut &bytes[..]).unwrap(); + let seed = vec![1, 2, 3]; let murmur_store = MurmurStore::new::( @@ -600,9 +604,26 @@ mod tests { // now verify the proof for nonce = 0 assert!(verifier::verify_update::( proof, - pk, + pk.clone(), 0, ).is_ok()); + + let another_murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 1, + same_double_public, + &mut rng, + ).unwrap(); + + let another_proof = another_murmur_store.proof; + // let another_pk = another_murmur_store.public_key; + // now verify the proof for nonce = 0 + assert!(verifier::verify_update::( + another_proof, + pk, + 1, + ).is_ok()); } #[test] diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 53f2d9d..6867cb5 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -137,14 +137,12 @@ pub fn prepare_execute( #[cfg(test)] mod tests { - use super::*; - + // use super::*; use super::*; use rand_core::{OsRng, SeedableRng}; #[test] pub fn it_can_create_an_mmr_store_and_call_data() { - let name = b"name".to_vec(); let seed = b"seed".to_vec(); let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); @@ -162,12 +160,11 @@ mod tests { // ).unwrap(); assert_eq!(mmr_store.root.0.len(), 32); - assert_eq!(mmr_store.size, 7); + assert_eq!(mmr_store.metadata.keys().len(), 7); } #[test] pub fn it_can_prepare_valid_execution_call_data() { - let name = b"name".to_vec(); let seed = b"seed".to_vec(); let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); @@ -193,14 +190,14 @@ mod tests { }, ); - let bob2 = subxt_signer::sr25519::dev::bob().public_key(); - let balance_transfer_call_2 = - etf::runtime_types::node_template_runtime::RuntimeCall::Balances( - etf::balances::Call::transfer_allow_death { - dest: subxt::utils::MultiAddress::<_, u32>::from(bob2), - value: 1, - }, - ); + // let bob2 = subxt_signer::sr25519::dev::bob().public_key(); + // let balance_transfer_call_2 = + // etf::runtime_types::node_template_runtime::RuntimeCall::Balances( + // etf::balances::Call::transfer_allow_death { + // dest: subxt::utils::MultiAddress::<_, u32>::from(bob2), + // value: 1, + // }, + // ); let when = 1; From fe88f8b92b1e792233c6c2563e65ab13d59cb577 Mon Sep 17 00:00:00 2001 From: driemworks Date: Fri, 25 Oct 2024 11:14:41 -0500 Subject: [PATCH 05/19] feat: testing wip --- core/src/murmur.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index d3541c1..9d9781c 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -606,24 +606,25 @@ mod tests { proof, pk.clone(), 0, - ).is_ok()); + ).unwrap()); + let mut another_rng = ChaCha20Rng::seed_from_u64(1); let another_murmur_store = MurmurStore::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 1, same_double_public, - &mut rng, + &mut another_rng, ).unwrap(); let another_proof = another_murmur_store.proof; // let another_pk = another_murmur_store.public_key; - // now verify the proof for nonce = 0 + // now verify the proof for nonce = 1 assert!(verifier::verify_update::( another_proof, pk, 1, - ).is_ok()); + ).unwrap()); } #[test] @@ -654,4 +655,18 @@ mod tests { 1, ).unwrap()); } + + #[test] + fn test_generate_witness_determinism() { + // Define the same seed input and random number generator state for reproducibility + let seed = vec![1, 2, 3, 4, 5, 6, 7, 8]; + let rng = rand::rngs::StdRng::seed_from_u64(42); + + let witness1 = generate_witness(seed.clone(), rng.clone()); + let witness2 = generate_witness(seed, rng); + + // Witnesses should match if the function is deterministic + assert_eq!(witness1, witness2, "Witnesses should be identical for the same seed"); + } + } From f1c0e95d18e51d61bacb8e8299ea40e847b2eddb Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 28 Oct 2024 13:00:47 -0500 Subject: [PATCH 06/19] feat: update tests --- core/src/murmur.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 9d9781c..51d474c 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -243,12 +243,17 @@ impl MurmurStore { /// * `rng`: A CSPRNG /// #[cfg(feature = "client")] -pub fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { +pub fn generate_witness( + mut seed: Vec, + mut rng: R +) -> [u8; 32] { let mut transcript = Transcript::new_labeled(MURMUR_PROTO); transcript.write_bytes(&seed); seed.zeroize(); - let witness: [u8; 32] = transcript.clone().witness(&mut rng).read_byte_array(); - witness + + transcript.clone() + .witness(&mut rng) + .read_byte_array() } /// A helper function to perform timelock encryption @@ -575,7 +580,7 @@ mod tests { pos, )); } - + #[test] fn it_can_generate_and_verify_schnorr_proofs() { let mut rng = ChaCha20Rng::seed_from_u64(0); @@ -608,21 +613,20 @@ mod tests { 0, ).unwrap()); - let mut another_rng = ChaCha20Rng::seed_from_u64(1); let another_murmur_store = MurmurStore::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 1, same_double_public, - &mut another_rng, + &mut rng, ).unwrap(); let another_proof = another_murmur_store.proof; - // let another_pk = another_murmur_store.public_key; + let another_pk = another_murmur_store.public_key; // now verify the proof for nonce = 1 assert!(verifier::verify_update::( another_proof, - pk, + another_pk, 1, ).unwrap()); } @@ -658,14 +662,11 @@ mod tests { #[test] fn test_generate_witness_determinism() { - // Define the same seed input and random number generator state for reproducibility let seed = vec![1, 2, 3, 4, 5, 6, 7, 8]; - let rng = rand::rngs::StdRng::seed_from_u64(42); - + let rng = ChaCha20Rng::seed_from_u64(0); let witness1 = generate_witness(seed.clone(), rng.clone()); let witness2 = generate_witness(seed, rng); - - // Witnesses should match if the function is deterministic + assert_eq!(witness1, witness2, "Witnesses should be identical for the same seed"); } From f411ca7cc4a89af9618e908c0b336efe66dd7140 Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 28 Oct 2024 13:42:26 -0500 Subject: [PATCH 07/19] feat: update tests --- core/src/murmur.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 51d474c..dd2f4db 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -370,6 +370,7 @@ mod tests { use rand_chacha::ChaCha20Rng; use ark_std::rand::SeedableRng; use ark_serialize::CanonicalDeserialize; + use rand_core::OsRng; /// pub const BLOCK_SCHEDULE: &[BlockNumber] = &[ @@ -580,11 +581,11 @@ mod tests { pos, )); } - + #[test] fn it_can_generate_and_verify_schnorr_proofs() { let mut rng = ChaCha20Rng::seed_from_u64(0); - let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); let double_public: DoublePublicKey = DoublePublicKey( keypair.into_public_key_in_signature_group().0, keypair.public.0, @@ -613,20 +614,23 @@ mod tests { 0, ).unwrap()); + let mut same_rng = ChaCha20Rng::seed_from_u64(0); let another_murmur_store = MurmurStore::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 1, same_double_public, - &mut rng, + &mut same_rng, ).unwrap(); let another_proof = another_murmur_store.proof; let another_pk = another_murmur_store.public_key; + assert!(pk == another_pk); + // now verify the proof for nonce = 1 assert!(verifier::verify_update::( another_proof, - another_pk, + pk, 1, ).unwrap()); } @@ -668,6 +672,18 @@ mod tests { let witness2 = generate_witness(seed, rng); assert_eq!(witness1, witness2, "Witnesses should be identical for the same seed"); + + let secret_key_1 = + SecretKey::<<::SignatureGroup as CurveGroup>::Affine>::from_seed(&witness1); + let pubkey_1 = secret_key_1.as_publickey(); + + + let secret_key_2 = + SecretKey::<<::SignatureGroup as CurveGroup>::Affine>::from_seed(&witness2); + let pubkey_2 = secret_key_2.as_publickey(); + + assert!(pubkey_1 == pubkey_2); + } } From 27e570a1f250bf299b21cd425b90fc4a41445fd3 Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 28 Oct 2024 14:09:16 -0500 Subject: [PATCH 08/19] chore: reexport witness generation for testing --- test-utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 0e8e54d..ac84c20 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -26,8 +26,8 @@ use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, TinyBLS377}; extern crate alloc; pub use murmur_core::otp::BOTPGenerator; - pub use murmur_core::murmur::MurmurStore; +pub use murmur_core::murmur::generate_witness; pub fn get_dummy_beacon_pubkey() -> Vec { let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); From 93a66cadc16d0d1a67a854ea9c625e898d2bf48c Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 28 Oct 2024 15:09:00 -0500 Subject: [PATCH 09/19] feat: add test func for otp gen --- Cargo.lock | 3 +++ test-utils/Cargo.toml | 6 ++++++ test-utils/src/lib.rs | 22 +++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 35c7d29..af65d94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2632,7 +2632,10 @@ dependencies = [ name = "murmur-test-utils" version = "0.1.0" dependencies = [ + "ark-ec", "ark-serialize", + "ark-std", + "dleq_vrf 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", "murmur-core", "rand_core", "w3f-bls", diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index ad4742e..d14bccd 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -18,15 +18,21 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rand_core = { version = "0.6.4", default-features = false } +ark-ec = { version = "0.4", default-features = false } ark-serialize = { version = "0.4.0", default-features = false } +ark-std = { version = "0.4", default-features = false } w3f-bls = { version = "0.1.3", default-features = false } murmur-core = { package = "murmur-core", path = "../core/", features = ["client"] } +dleq_vrf = { git = "https://github.com/w3f/ring-vrf.git", default-features = false, features = ["getrandom"]} [features] default = ["std"] std = [ + "ark-ec/std", "ark-serialize/std", + "ark-std/std", "w3f-bls/std", "murmur-core/std", + "dleq_vrf/std", ] no_std = [] \ No newline at end of file diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index ac84c20..17520cd 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -20,15 +20,35 @@ use alloc::vec::Vec; use ark_serialize::CanonicalSerialize; +use ark_std::rand::{CryptoRng, Rng}; use rand_core::OsRng; -use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, TinyBLS377}; +use ark_ec::CurveGroup; +use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, EngineBLS, TinyBLS377}; +use dleq_vrf::SecretKey; extern crate alloc; pub use murmur_core::otp::BOTPGenerator; + pub use murmur_core::murmur::MurmurStore; + pub use murmur_core::murmur::generate_witness; +pub fn otp( + seed: Vec, + when: u64, + rng: &mut R +) -> Vec { + let witness = generate_witness(seed.clone(), rng); + let secret_key = + SecretKey::<::Affine>::from_seed(&witness); + let pubkey = secret_key.as_publickey(); + let mut pubkey_bytes = Vec::new(); + pubkey.serialize_compressed(&mut pubkey_bytes).unwrap(); + let totp = BOTPGenerator::new(witness.to_vec()).unwrap(); + totp.generate(when).as_bytes().to_vec() +} + pub fn get_dummy_beacon_pubkey() -> Vec { let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); let double_public: DoublePublicKey = From f14c4f1a96c915f3f1ddd53c6e52982949c2b292 Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 28 Oct 2024 20:45:58 -0500 Subject: [PATCH 10/19] feat: zeroize otp codes --- core/src/murmur.rs | 12 ++++++++---- test-utils/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index dd2f4db..1b7c6ed 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -135,7 +135,7 @@ impl MurmurStore { let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); for &i in &block_schedule { - let otp_code = totp.generate(i as u64); + let mut otp_code = totp.generate(i as u64); let identity = I::build_identity(i); let mut ephemeral_msk: [u8; 32] = transcript @@ -156,6 +156,8 @@ impl MurmurStore { ephem_rng, )?; ephemeral_msk.zeroize(); + otp_code.zeroize(); + let leaf = Leaf(ct_bytes.clone()); // Q: How can I test this line? // https://github.com/nervosnetwork/merkle-mountain-range/blob/9e77d3ef81ddfdd9b7dd9583762582e859849dde/src/mmr.rs#L60 @@ -182,9 +184,9 @@ impl MurmurStore { mut rng: R, ) -> Result<(MerkleProof, Vec, Ciphertext, u64), Error> { if let Some(ciphertext) = self.metadata.get(&when) { - let commitment = MurmurStore::commit(seed.clone(), when, &call_data.clone(), &mut rng)?; seed.zeroize(); + let idx = self.metadata.keys().position(|k| k == &when).expect("The leaf should exist"); let pos = leaf_index_to_pos(idx as u64); let mmr = self.to_mmr()?; @@ -210,14 +212,17 @@ impl MurmurStore { let mut witness = generate_witness(seed.clone(), &mut rng); let botp = BOTPGenerator::new(witness.to_vec()) .map_err(|_| Error::InvalidSeed)?; + seed.zeroize(); witness.zeroize(); - let otp_code = botp.generate(when as u64); + let mut otp_code = botp.generate(when as u64); let mut hasher = sha3::Sha3_256::default(); Digest::update(&mut hasher, otp_code.as_bytes()); Digest::update(&mut hasher, data); + + otp_code.zeroize(); Ok(hasher.finalize().to_vec()) } @@ -326,7 +331,6 @@ pub mod verifier { Digest::update(&mut hasher, otp); Digest::update(&mut hasher, aux_data); let expected_hash = hasher.finalize().to_vec(); - validity = validity && expected_hash == hash; } diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index d14bccd..875d1ba 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -33,6 +33,6 @@ std = [ "ark-std/std", "w3f-bls/std", "murmur-core/std", - "dleq_vrf/std", +"dleq_vrf/std", ] no_std = [] \ No newline at end of file From 8a7e6ef9084036d424067b0b99b8b3d4cae2c8ad Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 28 Oct 2024 20:59:40 -0500 Subject: [PATCH 11/19] chore: fmt --- core/src/murmur.rs | 537 ++++++++++++++++--------------------- core/src/otp.rs | 56 ++-- lib/src/bin/murmur/main.rs | 24 +- lib/src/lib.rs | 55 ++-- test-utils/Cargo.toml | 2 +- test-utils/src/lib.rs | 13 +- 6 files changed, 302 insertions(+), 385 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 1b7c6ed..1740892 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -20,8 +20,7 @@ use alloc::{collections::BTreeMap, vec, vec::Vec}; #[cfg(feature = "client")] use crate::otp::BOTPGenerator; -use ark_std::rand::SeedableRng; -use ark_std::rand::{CryptoRng, Rng}; +use ark_std::rand::{CryptoRng, Rng, SeedableRng}; #[cfg(feature = "client")] use zeroize::Zeroize; @@ -39,10 +38,7 @@ use ckb_merkle_mountain_range::{ }; use codec::{Decode, Encode}; use dleq_vrf::{EcVrfVerifier, PublicKey, SecretKey}; -use etf_crypto_primitives::{ - encryption::tlock::*, - ibe::fullident::Identity -}; +use etf_crypto_primitives::{encryption::tlock::*, ibe::fullident::Identity}; use sha3::Digest; use w3f_bls::{DoublePublicKey, EngineBLS}; @@ -91,14 +87,12 @@ pub struct MurmurStore { #[cfg(feature = "client")] impl MurmurStore { - /// Create a new Murmur store /// /// * `seed`: An any-length seed (i.e. password) /// * `block_schedule`: The blocks for which OTP codes will be generated /// * `ephemeral_msk`: Any 32 bytes /// * `round_public_key`: The IDN beacon's public key - /// pub fn new, R>( mut seed: Vec, block_schedule: Vec, @@ -110,7 +104,7 @@ impl MurmurStore { R: Rng + CryptoRng + SeedableRng + Sized, { let mut witness = generate_witness(seed.clone(), rng); - let mut secret_key = + let mut secret_key = SecretKey::<::Affine>::from_seed(&witness); let pubkey = secret_key.as_publickey(); let mut pubkey_bytes = Vec::new(); @@ -175,7 +169,6 @@ impl MurmurStore { /// * `seed`: The seed used to create the mmr /// * `when`: The block number when the wallet is being used (or will be) /// * `call_data`: The call to be executed with the wallet (at `when`) - /// pub fn execute( &self, mut seed: Vec, @@ -202,7 +195,6 @@ impl MurmurStore { /// * `seed`: The seed used to generated the MMR /// * `when`: The block number when the commitment is verifiable /// * `data`: The data to commit to - /// fn commit( mut seed: Vec, when: BlockNumber, @@ -210,8 +202,7 @@ impl MurmurStore { mut rng: R, ) -> Result, Error> { let mut witness = generate_witness(seed.clone(), &mut rng); - let botp = BOTPGenerator::new(witness.to_vec()) - .map_err(|_| Error::InvalidSeed)?; + let botp = BOTPGenerator::new(witness.to_vec()).map_err(|_| Error::InvalidSeed)?; seed.zeroize(); witness.zeroize(); @@ -229,7 +220,6 @@ impl MurmurStore { /// Builds an mmr from the mmr store /// /// * `mmr`: a MemMMR instance (to be populated) - /// fn to_mmr(&self) -> Result, Error> { let store = MemStore::default(); let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); @@ -241,24 +231,18 @@ impl MurmurStore { } } -/// construct a 32-byte witness by seeding a transcript with the given 'seed' +/// construct a 32-byte witness by seeding a transcript with the given 'seed' /// using a CSPRNG to generate the witness /// /// * `seed`: The value written to the transcript /// * `rng`: A CSPRNG -/// #[cfg(feature = "client")] -pub fn generate_witness( - mut seed: Vec, - mut rng: R -) -> [u8; 32] { - let mut transcript = Transcript::new_labeled(MURMUR_PROTO); - transcript.write_bytes(&seed); - seed.zeroize(); - - transcript.clone() - .witness(&mut rng) - .read_byte_array() +pub fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { + let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + transcript.write_bytes(&seed); + seed.zeroize(); + + transcript.clone().witness(&mut rng).read_byte_array() } /// A helper function to perform timelock encryption @@ -268,14 +252,13 @@ pub fn generate_witness( /// * `ephemeral_msk`: A randomly sampled 32-byte secret key /// * `message`: The message to be timelock encrypted /// * `rng`: A CSPRNG -/// #[cfg(feature = "client")] fn timelock_encrypt( - identity: Identity, - pk: E::PublicKeyGroup, - ephemeral_msk: [u8; 32], - message: &[u8], - rng: R, + identity: Identity, + pk: E::PublicKeyGroup, + ephemeral_msk: [u8; 32], + message: &[u8], + rng: R, ) -> Result, Error> { let ciphertext = tle::(pk, ephemeral_msk, message, identity, rng).map_err(|_| Error::TlockFailed)?; @@ -290,8 +273,8 @@ fn timelock_encrypt( /// These functions would typically be called by an untrusted verifier (e.g. a blockchain runtime) pub mod verifier { use super::*; - use ark_serialize::CanonicalDeserialize; - use dleq_vrf::{ThinVrfProof}; + use ark_serialize::CanonicalDeserialize; + use dleq_vrf::ThinVrfProof; #[derive(Debug, PartialEq)] pub enum VerificationError { @@ -299,21 +282,20 @@ pub mod verifier { UnserializablePubkey, } - /// Verify the correctness of execution parameters by checking that the Merkle proof and hash are valid - /// The function outputs true if both conditions are true: + /// Verify the correctness of execution parameters by checking that the Merkle proof and hash + /// are valid The function outputs true if both conditions are true: /// proof.verify(root, [(pos, Leaf(ciphertext))]) - /// hash = Sha256(otp || aux_data) + /// hash = Sha256(otp || aux_data) //// /// It outputs false otherwise. /// /// * `root`: The root of the MMR /// * `proof`: The Merkle proof to verify /// * `hash`: A (potential) commitment to the OTP and aux_data - /// * `ciphertext`: A timelocked ciphertext + /// * `ciphertext`: A timelocked ciphertext /// * `otp`: The OTP /// * `aux_data`: The expected aux data used to generate the commitment /// * `pos`: The position of the Ciphertext within the MMR - /// pub fn verify_execute( root: Leaf, proof: MerkleProof, @@ -323,8 +305,7 @@ pub mod verifier { aux_data: &[u8], pos: u64, ) -> bool { - let mut validity = proof.verify(root, vec![(pos, Leaf(ciphertext))]) - .unwrap_or(false); + let mut validity = proof.verify(root, vec![(pos, Leaf(ciphertext))]).unwrap_or(false); if validity { let mut hasher = sha3::Sha3_256::default(); @@ -338,30 +319,33 @@ pub mod verifier { } /// Verifies a Schnorr proof - /// This is used to ensure that subsequent calls to the 'new' function are called with the same seed + /// This is used to ensure that subsequent calls to the 'new' function are called with the same + /// seed /// /// * `serialized_proof`: The serialized proof /// * `serialized_pubkey`: The serialized public key /// * `nonce`: A nonce value - /// pub fn verify_update( - serialized_proof: Vec, - serialized_pubkey: Vec, - nonce: u64, - ) -> Result { - // build transcript - let mut transcript = Transcript::new_labeled(MURMUR_PROTO); - transcript.write_bytes(&nonce.to_be_bytes()); - + serialized_proof: Vec, + serialized_pubkey: Vec, + nonce: u64, + ) -> Result { + // build transcript + let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + transcript.write_bytes(&nonce.to_be_bytes()); + // deserialize proof and pubkey - let proof = ThinVrfProof::<::Affine>:: - deserialize_compressed(&mut &serialized_proof[..]) - .map_err(|_| VerificationError::UnserializableProof)?; - - let pk = PublicKey::<::Affine>:: - deserialize_compressed(&mut &serialized_pubkey[..]) - .map_err(|_| VerificationError::UnserializablePubkey)?; - + let proof = + ThinVrfProof::<::Affine>::deserialize_compressed( + &mut &serialized_proof[..], + ) + .map_err(|_| VerificationError::UnserializableProof)?; + + let pk = PublicKey::<::Affine>::deserialize_compressed( + &mut &serialized_pubkey[..], + ) + .map_err(|_| VerificationError::UnserializablePubkey)?; + Ok(pk.vrf_verify_detached(transcript, &[], &proof).is_ok()) } } @@ -369,17 +353,16 @@ pub mod verifier { #[cfg(test)] mod tests { - use super::*; - use w3f_bls::{DoublePublicKeyScheme, TinyBLS377}; - use rand_chacha::ChaCha20Rng; - use ark_std::rand::SeedableRng; - use ark_serialize::CanonicalDeserialize; + use super::*; + use ark_serialize::CanonicalDeserialize; + use ark_std::rand::SeedableRng; + use rand_chacha::ChaCha20Rng; use rand_core::OsRng; + use w3f_bls::{DoublePublicKeyScheme, TinyBLS377}; - /// - pub const BLOCK_SCHEDULE: &[BlockNumber] = &[ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - ]; + /// + pub const BLOCK_SCHEDULE: &[BlockNumber] = + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; pub const WHEN: BlockNumber = 10; pub const OTP: &[u8] = b"823185"; @@ -399,273 +382,224 @@ mod tests { let double_public: DoublePublicKey = DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); - let seed = vec![1, 2, 3]; - - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); - - assert!(murmur_store.metadata.keys().len() == BLOCK_SCHEDULE.len()); - assert!(murmur_store.root.0.len() == 32); - assert!(murmur_store.proof.len() == 80); - assert!(murmur_store.public_key.len() == 48); - } - - #[cfg(feature = "client")] - #[test] - pub fn it_can_generate_valid_output_and_verify_it() { - let mut rng = ChaCha20Rng::seed_from_u64(0); - let keypair = w3f_bls::KeypairVT::::generate(&mut rng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); - - let seed = vec![1, 2, 3]; - let aux_data = vec![2, 3, 4, 5]; - - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); - - let root = murmur_store.root.clone(); - let (proof, commitment, ciphertext, pos) = murmur_store - .execute( - seed.clone(), - WHEN, - aux_data.clone(), - &mut rng, - ) - .unwrap(); - - assert!(verifier::verify_execute( - root, - proof, - commitment, - ciphertext, - OTP, - &aux_data, - pos, - )); - } - - #[cfg(feature = "client")] - #[test] - pub fn it_fails_to_generate_execute_output_when_ciphertext_dne() { + let seed = vec![1, 2, 3]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); + + assert!(murmur_store.metadata.keys().len() == BLOCK_SCHEDULE.len()); + assert!(murmur_store.root.0.len() == 32); + assert!(murmur_store.proof.len() == 80); + assert!(murmur_store.public_key.len() == 48); + } + + #[cfg(feature = "client")] + #[test] + pub fn it_can_generate_valid_output_and_verify_it() { let mut rng = ChaCha20Rng::seed_from_u64(0); - let keypair = w3f_bls::KeypairVT::::generate(&mut rng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); - - let seed = vec![1, 2, 3]; - - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); + let seed = vec![1, 2, 3]; let aux_data = vec![2, 3, 4, 5]; - match murmur_store.execute(seed.clone(), 10000, aux_data.clone(), &mut rng) { - Ok(_) => panic!("There should be an error"), - Err(e) => assert_eq!(e, Error::NoCiphertextFound), - } - } - - #[cfg(feature = "client")] - #[test] - pub fn it_fails_on_verify_bad_aux_data() { - let mut rng = ChaCha20Rng::seed_from_u64(0); - let keypair = w3f_bls::KeypairVT::::generate(&mut rng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); - - let seed = vec![1, 2, 3]; - let aux_data = vec![2, 3, 4, 5]; - - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); - - let root = murmur_store.root.clone(); - let (proof, commitment, ciphertext, pos) = murmur_store - .execute( - seed.clone(), - WHEN, - aux_data.clone(), - &mut rng, - ) - .unwrap(); - - let bad_aux = vec![2, 3, 13, 3]; - assert!(!verifier::verify_execute( - root, - proof, - commitment, - ciphertext, - OTP, - &bad_aux, - pos, - )); - } - - #[test] - pub fn it_fails_on_verify_bad_proof() { + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); + + let root = murmur_store.root.clone(); + let (proof, commitment, ciphertext, pos) = + murmur_store.execute(seed.clone(), WHEN, aux_data.clone(), &mut rng).unwrap(); + + assert!( + verifier::verify_execute(root, proof, commitment, ciphertext, OTP, &aux_data, pos,) + ); + } + + #[cfg(feature = "client")] + #[test] + pub fn it_fails_to_generate_execute_output_when_ciphertext_dne() { let mut rng = ChaCha20Rng::seed_from_u64(0); - let keypair = w3f_bls::KeypairVT::::generate(&mut rng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); + + let seed = vec![1, 2, 3]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); + + let aux_data = vec![2, 3, 4, 5]; + + match murmur_store.execute(seed.clone(), 10000, aux_data.clone(), &mut rng) { + Ok(_) => panic!("There should be an error"), + Err(e) => assert_eq!(e, Error::NoCiphertextFound), + } + } + + #[cfg(feature = "client")] + #[test] + pub fn it_fails_on_verify_bad_aux_data() { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); + + let seed = vec![1, 2, 3]; + let aux_data = vec![2, 3, 4, 5]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); + + let root = murmur_store.root.clone(); + let (proof, commitment, ciphertext, pos) = + murmur_store.execute(seed.clone(), WHEN, aux_data.clone(), &mut rng).unwrap(); + + let bad_aux = vec![2, 3, 13, 3]; + assert!( + !verifier::verify_execute(root, proof, commitment, ciphertext, OTP, &bad_aux, pos,) + ); + } + + #[test] + pub fn it_fails_on_verify_bad_proof() { + let mut rng = ChaCha20Rng::seed_from_u64(0); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); let other_keypair = w3f_bls::KeypairVT::::generate(&mut rng); - let other_double_public: DoublePublicKey = DoublePublicKey( - other_keypair.into_public_key_in_signature_group().0, - other_keypair.public.0, - ); - - let seed = vec![1, 2, 3]; - let other_seed = vec![2,3,4]; - - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); - - let other_murmur_store = MurmurStore::new::( - other_seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - other_double_public, - &mut rng, - ).unwrap(); + let other_double_public: DoublePublicKey = DoublePublicKey( + other_keypair.into_public_key_in_signature_group().0, + other_keypair.public.0, + ); + + let seed = vec![1, 2, 3]; + let other_seed = vec![2, 3, 4]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); + + let other_murmur_store = MurmurStore::new::( + other_seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + other_double_public, + &mut rng, + ) + .unwrap(); let aux_data = vec![2, 3, 13, 3]; - // the block number when this would execute - let root = murmur_store.root.clone(); - let (proof, commitment, ciphertext, pos) = other_murmur_store - .execute( - other_seed.clone(), - WHEN, - aux_data.clone(), - &mut rng, - ) - .unwrap(); - - assert!(!verifier::verify_execute( - root, - proof, - commitment, - ciphertext, - OTP, - &aux_data, - pos, - )); - } + // the block number when this would execute + let root = murmur_store.root.clone(); + let (proof, commitment, ciphertext, pos) = other_murmur_store + .execute(other_seed.clone(), WHEN, aux_data.clone(), &mut rng) + .unwrap(); + + assert!(!verifier::verify_execute( + root, proof, commitment, ciphertext, OTP, &aux_data, pos, + )); + } #[test] fn it_can_generate_and_verify_schnorr_proofs() { let mut rng = ChaCha20Rng::seed_from_u64(0); let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); + let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); let mut bytes = Vec::new(); double_public.serialize_compressed(&mut bytes).unwrap(); - let same_double_public = DoublePublicKey::::deserialize_compressed(&mut &bytes[..]).unwrap(); + let same_double_public = + DoublePublicKey::::deserialize_compressed(&mut &bytes[..]).unwrap(); - let seed = vec![1, 2, 3]; + let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); let proof = murmur_store.proof; let pk = murmur_store.public_key; // now verify the proof for nonce = 0 - assert!(verifier::verify_update::( - proof, - pk.clone(), - 0, - ).unwrap()); + assert!(verifier::verify_update::(proof, pk.clone(), 0,).unwrap()); let mut same_rng = ChaCha20Rng::seed_from_u64(0); let another_murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 1, - same_double_public, - &mut same_rng, - ).unwrap(); + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 1, + same_double_public, + &mut same_rng, + ) + .unwrap(); let another_proof = another_murmur_store.proof; let another_pk = another_murmur_store.public_key; assert!(pk == another_pk); // now verify the proof for nonce = 1 - assert!(verifier::verify_update::( - another_proof, - pk, - 1, - ).unwrap()); + assert!(verifier::verify_update::(another_proof, pk, 1,).unwrap()); } #[test] fn it_cannot_verify_schnorr_proof_with_bad_nonce() { let mut rng = ChaCha20Rng::seed_from_u64(0); - let keypair = w3f_bls::KeypairVT::::generate(&mut rng); - let double_public: DoublePublicKey = DoublePublicKey( - keypair.into_public_key_in_signature_group().0, - keypair.public.0, - ); - - let seed = vec![1, 2, 3]; - - let murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ).unwrap(); + let keypair = w3f_bls::KeypairVT::::generate(&mut rng); + let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); + + let seed = vec![1, 2, 3]; + + let murmur_store = MurmurStore::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + double_public, + &mut rng, + ) + .unwrap(); let proof = murmur_store.proof; let pk = murmur_store.public_key; // now verify the proof for nonce = 1 - assert!(!verifier::verify_update::( - proof, - pk, - 1, - ).unwrap()); + assert!(!verifier::verify_update::(proof, pk, 1,).unwrap()); } #[test] @@ -674,20 +608,19 @@ mod tests { let rng = ChaCha20Rng::seed_from_u64(0); let witness1 = generate_witness(seed.clone(), rng.clone()); let witness2 = generate_witness(seed, rng); - + assert_eq!(witness1, witness2, "Witnesses should be identical for the same seed"); - let secret_key_1 = - SecretKey::<<::SignatureGroup as CurveGroup>::Affine>::from_seed(&witness1); + let secret_key_1 = SecretKey::< + <::SignatureGroup as CurveGroup>::Affine, + >::from_seed(&witness1); let pubkey_1 = secret_key_1.as_publickey(); - - let secret_key_2 = - SecretKey::<<::SignatureGroup as CurveGroup>::Affine>::from_seed(&witness2); + let secret_key_2 = SecretKey::< + <::SignatureGroup as CurveGroup>::Affine, + >::from_seed(&witness2); let pubkey_2 = secret_key_2.as_publickey(); assert!(pubkey_1 == pubkey_2); - } - } diff --git a/core/src/otp.rs b/core/src/otp.rs index b6a314a..eb79f3f 100644 --- a/core/src/otp.rs +++ b/core/src/otp.rs @@ -14,11 +14,8 @@ * limitations under the License. */ -use alloc::{ - vec::Vec, - string::String, -}; -use totp_rs::{Secret, TOTP, Algorithm}; +use alloc::{string::String, vec::Vec}; +use totp_rs::{Algorithm, Secret, TOTP}; use zeroize::Zeroize; #[derive(Debug)] @@ -34,32 +31,31 @@ pub struct BOTPGenerator { } impl BOTPGenerator { - /// Create a new BOTP generator with the given seed - /// - /// * `seed`: The seed used to generate OTP codes - /// - pub fn new(mut seed: Vec) -> Result { - let mut secret = Secret::Raw(seed.clone()).to_bytes() - .map_err(|_| OTPError::InvalidSecret)?; - seed.zeroize(); - let totp = TOTP::new( - Algorithm::SHA256, // algorithm - 6, // num digits - 1, // skew - 1, // step - secret.clone() // secret - ).map_err(|_| OTPError::InvalidSecret)?; - secret.zeroize(); - Ok(BOTPGenerator { totp }) - } + /// Create a new BOTP generator with the given seed + /// + /// * `seed`: The seed used to generate OTP codes + pub fn new(mut seed: Vec) -> Result { + let mut secret = + Secret::Raw(seed.clone()).to_bytes().map_err(|_| OTPError::InvalidSecret)?; + seed.zeroize(); + let totp = TOTP::new( + Algorithm::SHA256, // algorithm + 6, // num digits + 1, // skew + 1, // step + secret.clone(), // secret + ) + .map_err(|_| OTPError::InvalidSecret)?; + secret.zeroize(); + Ok(BOTPGenerator { totp }) + } - /// Generate an otp code - /// - /// * `block_height`: The block for which the code is valid - /// - pub fn generate(&self, block_height: u64) -> String { - self.totp.generate(block_height) - } + /// Generate an otp code + /// + /// * `block_height`: The block for which the code is valid + pub fn generate(&self, block_height: u64) -> String { + self.totp.generate(block_height) + } } #[cfg(test)] diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index f6d9ad4..8b43439 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -15,17 +15,13 @@ */ use clap::{Parser, Subcommand}; -use murmur_lib::{ - create, etf, prepare_execute, BlockNumber, BoundedVec, MurmurStore, RuntimeCall, -}; +use murmur_lib::{create, etf, prepare_execute, BlockNumber, BoundedVec, MurmurStore, RuntimeCall}; -use rand_core::{OsRng, SeedableRng}; use rand_chacha::ChaCha20Rng; +use rand_core::{OsRng, SeedableRng}; use sp_core::crypto::Ss58Codec; use std::{fs::File, time::Instant}; -use subxt::{ - backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig, -}; +use subxt::{backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig}; use subxt_signer::sr25519::dev; use thiserror::Error; @@ -94,7 +90,7 @@ async fn main() -> Result<(), Box> { let before = Instant::now(); let (client, current_block_number, round_pubkey_bytes) = idn_connect().await?; - let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); + let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); match &cli.commands { Commands::New(args) => { @@ -109,13 +105,9 @@ async fn main() -> Result<(), Box> { } // 2. create mmr - let mmr_store = create( - args.seed.as_bytes().to_vec(), - 0, - schedule, - round_pubkey_bytes, - &mut rng, - ).map_err(|_| CLIError::MurmurCreationFailed)?; + let mmr_store = + create(args.seed.as_bytes().to_vec(), 0, schedule, round_pubkey_bytes, &mut rng) + .map_err(|_| CLIError::MurmurCreationFailed)?; // 3. add to storage write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); @@ -156,7 +148,7 @@ async fn main() -> Result<(), Box> { current_block_number + 1, store, &balance_transfer_call, - &mut rng, + &mut rng, ) .map_err(|_| CLIError::MurmurExecutionFailed)?; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6867cb5..bbb70e1 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -57,12 +57,12 @@ pub struct CreateData { pub root: Vec, /// The size of the MMR pub size: u64, - /// The murmur store (map of block nubmer to ciphertext) + /// The murmur store (map of block nubmer to ciphertext) pub mmr_store: MurmurStore, - /// The serialized VRF public key - pub public_key_bytes: Vec, - /// The serialized Schnorr signature - pub proof_bytes: Vec, + /// The serialized VRF public key + pub public_key_bytes: Vec, + /// The serialized Schnorr signature + pub proof_bytes: Vec, } #[derive(Serialize)] @@ -71,11 +71,11 @@ pub struct ProxyData { pub position: u64, /// The hash of the commitment pub hash: Vec, - /// The timelocked ciphertext + /// The timelocked ciphertext pub ciphertext: Vec, - /// The Merkle proof items + /// The Merkle proof items pub proof_items: Vec>, - /// The size of the Merkle proof + /// The size of the Merkle proof pub size: u64, } @@ -149,14 +149,14 @@ mod tests { let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); let mmr_store = create(seed.clone(), 0, block_schedule.clone(), double_public_bytes.clone(), &mut rng) - .unwrap(); + .unwrap(); // let mmr_store = MurmurStore::new::( // seed, // block_schedule, // 0, // DoublePublicKey::::from_bytes(&double_public_bytes).unwrap(), - // &mut rng, + // &mut rng, // ).unwrap(); assert_eq!(mmr_store.root.0.len(), 32); @@ -169,19 +169,14 @@ mod tests { let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); - let mmr_store = create( - seed.clone(), - 0, - block_schedule, - double_public_bytes, - &mut rng - ).unwrap(); + let mmr_store = + create(seed.clone(), 0, block_schedule, double_public_bytes, &mut rng).unwrap(); // let size = proof.mmr_size(); // let proof_items: Vec> = // proof.proof_items().iter().map(|leaf| leaf.0.clone()).collect::>(); - let bob = subxt_signer::sr25519::dev::bob().public_key(); + let bob = subxt_signer::sr25519::dev::bob().public_key(); let balance_transfer_call = etf::runtime_types::node_template_runtime::RuntimeCall::Balances( etf::balances::Call::transfer_allow_death { @@ -190,14 +185,14 @@ mod tests { }, ); - // let bob2 = subxt_signer::sr25519::dev::bob().public_key(); - // let balance_transfer_call_2 = - // etf::runtime_types::node_template_runtime::RuntimeCall::Balances( - // etf::balances::Call::transfer_allow_death { - // dest: subxt::utils::MultiAddress::<_, u32>::from(bob2), - // value: 1, - // }, - // ); + // let bob2 = subxt_signer::sr25519::dev::bob().public_key(); + // let balance_transfer_call_2 = + // etf::runtime_types::node_template_runtime::RuntimeCall::Balances( + // etf::balances::Call::transfer_allow_death { + // dest: subxt::utils::MultiAddress::<_, u32>::from(bob2), + // value: 1, + // }, + // ); let when = 1; @@ -213,9 +208,11 @@ mod tests { // let (proof, commitment, ciphertext, _pos) = create_data.mmr_store // .execute(seed.clone(), when, balance_transfer_call_2.encode(), &mut rng) // .unwrap(); - // let expected_commitment = [71, 71, 72, 200, 197, 44, 120, 151, 127, 6, 162, 244, 138, 122, 196, 183, 30, 47, 111, 239, 225, 32, 57, 141, 186, 229, 164, 113, 113, 44, 131, 168]; - // let expected_ciphertext = [76, 42, 82, 184, 114, 58, 31, 205, 146, 16, 41, 191, 126, 213, 18, 65, 42, 149, 78, 140, 243, 164, 39, 54, 13, 96, 159, 93, 200, 83, 227, 179]; - // let size = proof.mmr_size(); + // let expected_commitment = [71, 71, 72, 200, 197, 44, 120, 151, 127, 6, 162, 244, 138, + // 122, 196, 183, 30, 47, 111, 239, 225, 32, 57, 141, 186, 229, 164, 113, 113, 44, 131, + // 168]; let expected_ciphertext = [76, 42, 82, 184, 114, 58, 31, 205, 146, 16, 41, 191, + // 126, 213, 18, 65, 42, 149, 78, 140, 243, 164, 39, 54, 13, 96, 159, 93, 200, 83, 227, + // 179]; let size = proof.mmr_size(); // let proof_items: Vec> = // proof.proof_items().iter().map(|leaf| leaf.0.clone()).collect::>(); assert_eq!(proxy_data.position, 0); diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 875d1ba..d14bccd 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -33,6 +33,6 @@ std = [ "ark-std/std", "w3f-bls/std", "murmur-core/std", -"dleq_vrf/std", + "dleq_vrf/std", ] no_std = [] \ No newline at end of file diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 17520cd..37ac82c 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -19,12 +19,12 @@ //! various utilities helpful for testing use alloc::vec::Vec; +use ark_ec::CurveGroup; use ark_serialize::CanonicalSerialize; use ark_std::rand::{CryptoRng, Rng}; +use dleq_vrf::SecretKey; use rand_core::OsRng; -use ark_ec::CurveGroup; use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, EngineBLS, TinyBLS377}; -use dleq_vrf::SecretKey; extern crate alloc; @@ -35,13 +35,12 @@ pub use murmur_core::murmur::MurmurStore; pub use murmur_core::murmur::generate_witness; pub fn otp( - seed: Vec, - when: u64, - rng: &mut R + seed: Vec, + when: u64, + rng: &mut R, ) -> Vec { let witness = generate_witness(seed.clone(), rng); - let secret_key = - SecretKey::<::Affine>::from_seed(&witness); + let secret_key = SecretKey::<::Affine>::from_seed(&witness); let pubkey = secret_key.as_publickey(); let mut pubkey_bytes = Vec::new(); pubkey.serialize_compressed(&mut pubkey_bytes).unwrap(); From 43784abb9e3e77b1c3e137e858a050067908b3a4 Mon Sep 17 00:00:00 2001 From: driemworks Date: Mon, 4 Nov 2024 21:38:41 -0600 Subject: [PATCH 12/19] chore: move getrandom behind client feature --- core/Cargo.toml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index b76685e..2c5d78b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,7 +2,7 @@ name = "murmur-core" version = "0.1.0" edition = "2021" -description = "Murmur core" +description = "The core implementation of the Murmur protocol" documentation = "https://docs.rs/murmur-core" readme = "README.md" keywords = ["crypto", "wallet", "keyless"] @@ -21,7 +21,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] totp-rs = { version = "5.5.1", default-features = false, optional = true } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"], default-features = false } -etf-crypto-primitives = { git = "https://github.com/ideal-lab5/etf-sdk/", branch = "dev", default-features = false} +etf-crypto-primitives = { git = "https://github.com/ideal-lab5/etf-sdk/", branch = "dev", default-features = false } ckb-merkle-mountain-range = { version = "0.5.2", default-features = false } sha3 = { version = "0.10.8", default-features = false } serde = { version = "1.0.188", features = ["alloc", "derive"], default-features = false} @@ -34,10 +34,8 @@ rand_chacha = { version = "0.3.1" } hkdf = "0.12.4" ark-ec = { version = "0.4", default-features = false } ark-ff = { version = "0.4", default-features = false } -# ark-transcript = { git = "https://github.com/w3f/ark-transcript.git", default-features = false} -ark-ed25519 = { version = "0.4", default-features = false } -dleq_vrf = { git = "https://github.com/w3f/ring-vrf.git", default-features = false, features = ["getrandom"]} -ark-transcript = { git = "https://github.com/w3f/ring-vrf.git", default-features = false} +dleq_vrf = { git = "https://github.com/w3f/ring-vrf.git", default-features = false } +ark-transcript = { git = "https://github.com/w3f/ring-vrf.git", default-features = false } [dev-dependencies] rand_core = { version = "0.6.4", features = ["getrandom"], default-features = false } @@ -49,7 +47,6 @@ std = [ "ark-std/std", "ark-serialize/std", "ark-bls12-377/std", - "ark-ed25519/std", "ark-ec/std", "ark-ff/std", "ark-transcript/std", @@ -65,4 +62,5 @@ std = [ no_std = [] client = [ "totp-rs", + "dleq_vrf/getrandom" ] From f6b0dc621a5d45c8d2d9bebacb628f8192a1fcc0 Mon Sep 17 00:00:00 2001 From: driemworks Date: Tue, 5 Nov 2024 10:05:31 -0600 Subject: [PATCH 13/19] feat: add update wallet to CLI WIP --- Cargo.lock | 34 ------ core/Cargo.toml | 10 +- lib/src/bin/murmur/main.rs | 214 +++++++++++++++++++++++++------------ lib/src/lib.rs | 15 --- 4 files changed, 147 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af65d94..d9439d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,17 +219,6 @@ dependencies = [ "ark-std", ] -[[package]] -name = "ark-curve25519" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab653b3eff27100f7dcb06b94785f2fbe0d1230408df55d543ee0ef48cd8760" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", -] - [[package]] name = "ark-ec" version = "0.4.2" @@ -298,18 +287,6 @@ dependencies = [ "ark-std", ] -[[package]] -name = "ark-ed25519" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed5162ef7c978cfefffcd08cd8393d174836ae1c9fc8fa2dfcd5c2f03a4a9" -dependencies = [ - "ark-curve25519", - "ark-ec", - "ark-ff", - "ark-std", -] - [[package]] name = "ark-ff" version = "0.4.2" @@ -2063,15 +2040,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - [[package]] name = "hmac" version = "0.8.1" @@ -2581,7 +2549,6 @@ version = "0.1.0" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ed25519", "ark-ff", "ark-serialize", "ark-std", @@ -2589,7 +2556,6 @@ dependencies = [ "ckb-merkle-mountain-range", "dleq_vrf 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", "etf-crypto-primitives 0.2.4 (git+https://github.com/ideal-lab5/etf-sdk/?branch=dev)", - "hkdf", "parity-scale-codec", "rand_chacha", "rand_core", diff --git a/core/Cargo.toml b/core/Cargo.toml index 2c5d78b..f1a2f87 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,28 +18,26 @@ workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] -[dependencies] +[dependencies] totp-rs = { version = "5.5.1", default-features = false, optional = true } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"], default-features = false } etf-crypto-primitives = { git = "https://github.com/ideal-lab5/etf-sdk/", branch = "dev", default-features = false } ckb-merkle-mountain-range = { version = "0.5.2", default-features = false } sha3 = { version = "0.10.8", default-features = false } -serde = { version = "1.0.188", features = ["alloc", "derive"], default-features = false} +serde = { version = "1.0.188", features = ["alloc", "derive"], default-features = false } ark-bls12-377 = { version = "0.4.0", default-features = false } ark-std = { version = "0.4.0", default-features = false } ark-serialize = { version = "0.4.0", default-features = false } w3f-bls = { version = "0.1.3", default-features = false } zeroize = { version = "1.8.1", default-features = false } -rand_chacha = { version = "0.3.1" } -hkdf = "0.12.4" ark-ec = { version = "0.4", default-features = false } ark-ff = { version = "0.4", default-features = false } dleq_vrf = { git = "https://github.com/w3f/ring-vrf.git", default-features = false } ark-transcript = { git = "https://github.com/w3f/ring-vrf.git", default-features = false } [dev-dependencies] -rand_core = { version = "0.6.4", features = ["getrandom"], default-features = false } -hkdf = "0.12.4" +rand_chacha = { version = "0.3.1" } +rand_core = { version = "0.6.4" } [features] default = ["client"] diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index 8b43439..66140fc 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -34,32 +34,44 @@ struct Cli { commands: Commands, } +/// Commands available to user #[derive(Subcommand)] enum Commands { - /// create a new murmur wallet + /// create a new Murmur wallet New(WalletCreationDetails), - /// dispatch (proxy) a call to a murmur wallet + /// Update an existing Murmur wallet + Update(WalletCreationDetails), + /// dispatch (proxy) a call to a Murmur wallet Execute(WalletExecuteDetails), } +/// Arguments for creation and updating a wallet #[derive(Parser)] struct WalletCreationDetails { + /// The name of the wallet #[arg(long, short)] name: String, + /// The seed of the wallet (i.e. password) #[arg(long, short)] seed: String, + /// The lifetime (from now) of the wallet in blocks #[clap(long, short)] validity: u32, } +/// Arguments for executing a balance transfer from the wallet #[derive(Parser)] struct WalletExecuteDetails { + /// The name of the wallet #[arg(long, short)] name: String, + /// The seed of the wallet (i.e. password) #[arg(long, short)] seed: String, + /// The recipient (ss58 encoded) #[arg(long, short)] to: String, + /// The amount to send TODO formatting #[arg(short, long, value_parser = clap::value_parser!(u128))] amount: u128, } @@ -94,82 +106,142 @@ async fn main() -> Result<(), Box> { match &cli.commands { Commands::New(args) => { - 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_number: BlockNumber = current_block_number + i; - schedule.push(next_block_number); - } - - // 2. create mmr - let mmr_store = - create(args.seed.as_bytes().to_vec(), 0, schedule, round_pubkey_bytes, &mut rng) - .map_err(|_| CLIError::MurmurCreationFailed)?; - - // 3. add to storage - write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); - - // 4. build the call - let call = etf::tx().murmur().create( - mmr_store.root.0, - mmr_store.metadata.keys().len() as u64, - BoundedVec(args.name.as_bytes().to_vec()), - ); - - // 5. sign and send the call - client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; - - println!("✅ MMR proxy account creation successful!"); + handle_create(args, client, current_block_number, round_pubkey_bytes, rng).await? }, - Commands::Execute(args) => { - // 1. build proxied call - let from_ss58 = sp_core::crypto::AccountId32::from_ss58check(&args.to) - .map_err(|_| CLIError::InvalidRecipient)?; - let bytes: &[u8] = from_ss58.as_ref(); - let from_ss58_sized: [u8; 32] = - bytes.try_into().map_err(|_| CLIError::InvalidRecipient)?; - let to = subxt::utils::AccountId32::from(from_ss58_sized); - let balance_transfer_call = - RuntimeCall::Balances(etf::balances::Call::transfer_allow_death { - dest: subxt::utils::MultiAddress::<_, u32>::from(to), - value: args.amount, - }); - - // 2. load the MMR store - let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH)?; - println!("💾 Recovered Murmur store from local file"); - - // 3. get the proxy data - let proxy_data = prepare_execute( - args.seed.as_bytes().to_vec(), - current_block_number + 1, - store, - &balance_transfer_call, - &mut rng, - ) - .map_err(|_| CLIError::MurmurExecutionFailed)?; - - // 4. build the call - let call = etf::tx().murmur().proxy( - BoundedVec(args.name.as_bytes().to_vec()), - proxy_data.position, - proxy_data.hash, - proxy_data.ciphertext, - proxy_data.proof_items, - proxy_data.size, - balance_transfer_call, - ); - // 5. sign and send the call - client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; + Commands::Update(args) => { + // handle_update(args, client, current_block_number, round_pubkey_bytes, rng).await? }, + Commands::Execute(args) => handle_execute(args, client, current_block_number, rng).await?, } + println!("Elapsed time: {:.2?}", before.elapsed()); Ok(()) } +/// This function creates a new Murmur wallet +async fn handle_create( + args: &WalletCreationDetails, + client: OnlineClient, + current_block_number: BlockNumber, + round_pubkey_bytes: Vec, + mut rng: ChaCha20Rng, +) -> Result<(), Box> { + let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, rng)?; + let call = etf::tx().murmur().create( + BoundedVec(args.name.as_bytes().to_vec()), + mmr_store.root.0, + mmr_store.metadata.keys().len() as u64, + mmr_store.proof, + mmr_store.public_key, + ); + + client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; + + println!("✅ Murmur Proxy Creation: Successful!"); + + Ok(()) +} + +// /// This function updates an existing Murmur wallet +// async fn handle_update( +// args: &WalletCreationDetails, +// client: OnlineClient, +// current_block_number: BlockNumber, +// round_pubkey_bytes: Vec, +// mut rng: ChaCha20Rng, +// ) -> Result<(), Box> { +// let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, &mut rng)?; + +// let call = etf::tx().murmur().update( +// BoundedVec(args.name.as_bytes().to_vec()), +// mmr_store.root.0, +// mmr_store.metadata.keys().len() as u64, +// mmr_store.proof, +// ); + +// client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; + +// println!("✅ Murmur Proxy Update: Successful!"); + +// Ok(()) +// } + +/// Build a new Murmur store for each block from `current_block_number + 2` to `current_block_number + args.validity` +fn build_mmr_store( + args: &WalletCreationDetails, + current_block_number: BlockNumber, + round_pubkey_bytes: Vec, + mut rng: ChaCha20Rng, +) -> Result> { + 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_number: BlockNumber = current_block_number + i; + schedule.push(next_block_number); + } + + // 2. create mmr + let mmr_store = + create(args.seed.as_bytes().to_vec(), 0, schedule, round_pubkey_bytes, &mut rng) + .map_err(|_| CLIError::MurmurCreationFailed)?; + + // 3. add to storage + write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); + + Ok(mmr_store) +} + +/// This function executes a call from the Murmur proxy at block after the provided `current_block_height` +/// note that this does not guarantee execution. If the transaction is not included in a block in the upcoming block +/// then it will never be executed +async fn handle_execute( + args: &WalletExecuteDetails, + client: OnlineClient, + current_block_number: BlockNumber, + mut rng: ChaCha20Rng, +) -> Result<(), Box> { + // 1. build proxied call + let from_ss58 = sp_core::crypto::AccountId32::from_ss58check(&args.to) + .map_err(|_| CLIError::InvalidRecipient)?; + let bytes: &[u8] = from_ss58.as_ref(); + let from_ss58_sized: [u8; 32] = bytes.try_into().map_err(|_| CLIError::InvalidRecipient)?; + let to = subxt::utils::AccountId32::from(from_ss58_sized); + let balance_transfer_call = RuntimeCall::Balances(etf::balances::Call::transfer_allow_death { + dest: subxt::utils::MultiAddress::<_, u32>::from(to), + value: args.amount, + }); + + // 2. load the MMR store + let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH)?; + println!("💾 Recovered Murmur store from local file"); + + // 3. get the proxy data + let proxy_data = prepare_execute( + args.seed.as_bytes().to_vec(), + current_block_number + 1, + store, + &balance_transfer_call, + &mut rng, + ) + .map_err(|_| CLIError::MurmurExecutionFailed)?; + + // 4. build the call + let call = etf::tx().murmur().proxy( + BoundedVec(args.name.as_bytes().to_vec()), + proxy_data.position, + proxy_data.hash, + proxy_data.ciphertext, + proxy_data.proof_items, + proxy_data.size, + balance_transfer_call, + ); + // 5. sign and send the call + client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; + Ok(()) +} + /// Async connection to the Ideal Network /// if successful then fetch data /// else error if unreachable diff --git a/lib/src/lib.rs b/lib/src/lib.rs index bbb70e1..91856a9 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -50,21 +50,6 @@ impl IdentityBuilder for BasicIdBuilder { } } -#[derive(Serialize)] -/// Data needed to build a valid call for creating a murmur wallet. -pub struct CreateData { - /// The root of the MMR - pub root: Vec, - /// The size of the MMR - pub size: u64, - /// The murmur store (map of block nubmer to ciphertext) - pub mmr_store: MurmurStore, - /// The serialized VRF public key - pub public_key_bytes: Vec, - /// The serialized Schnorr signature - pub proof_bytes: Vec, -} - #[derive(Serialize)] /// Data needed to build a valid call for a proxied execution. pub struct ProxyData { From 4d331ea43f5106b227a1af56dd7070aaf8b77b21 Mon Sep 17 00:00:00 2001 From: driemworks Date: Sun, 10 Nov 2024 17:59:51 -0600 Subject: [PATCH 14/19] feat: add more error types, implement update wallet functionality in CLI --- core/Cargo.toml | 2 +- core/src/murmur.rs | 267 +++++++++++++++++++++-------------- lib/artifacts/metadata.scale | Bin 619440 -> 620513 bytes lib/src/bin/murmur/main.rs | 56 ++++---- lib/src/lib.rs | 4 +- test-utils/src/lib.rs | 19 +-- 6 files changed, 195 insertions(+), 153 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index f1a2f87..c0cfa29 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,7 +18,7 @@ workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] -[dependencies] +[dependencies] totp-rs = { version = "5.5.1", default-features = false, optional = true } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"], default-features = false } etf-crypto-primitives = { git = "https://github.com/ideal-lab5/etf-sdk/", branch = "dev", default-features = false } diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 1740892..382158b 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -26,7 +26,7 @@ use ark_std::rand::{CryptoRng, Rng, SeedableRng}; use zeroize::Zeroize; #[cfg(feature = "client")] -use ark_serialize::CanonicalSerialize; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use crate::types::*; use ark_ec::CurveGroup; @@ -37,6 +37,7 @@ use ckb_merkle_mountain_range::{ MerkleProof, }; use codec::{Decode, Encode}; +use core::marker::PhantomData; use dleq_vrf::{EcVrfVerifier, PublicKey, SecretKey}; use etf_crypto_primitives::{encryption::tlock::*, ibe::fullident::Identity}; use sha3::Digest; @@ -44,6 +45,8 @@ use w3f_bls::{DoublePublicKey, EngineBLS}; /// The Murmur protocol label for domain separation in transcripts pub const MURMUR_PROTO: &[u8] = b"Murmur://"; +pub const MURMUR_PROTO_OTP: &[u8] = b"MurmurOTP://"; +pub const MURMUR_PROTO_VRF: &[u8] = b"MurmurVRF://"; /// The size of a 32-bit buffer pub const ALLOCATED_BUFFER_BYTES: usize = 32; @@ -61,6 +64,8 @@ pub enum Error { NoCiphertextFound, /// There was an error when executing timelock encryption (is the ciphertext too large?) TlockFailed, + /// There was an error when executing AES GCM decryption + AesDecryptFailed, /// The buffer does not have enough space allocated InvalidBufferSize, /// The seed was invalid @@ -69,10 +74,32 @@ pub enum Error { InvalidPubkey, } +pub trait ProtocolEngine { + type Engine: EngineBLS; + fn protocol_id() -> ProtocolId; +} + +/// The supported protocols +#[cfg(feature = "client")] +#[derive(Clone, serde::Serialize, serde::Deserialize, Encode, Decode)] +pub enum ProtocolId { + /// small signatures, SignatureGroup = G1 (48 bytes), PublicKeyGroup = G2 (96 bytes) + TinyBLS377, +} + +/// Use the TinyBLS377 engine +pub struct EngineTinyBLS377; +impl ProtocolEngine for EngineTinyBLS377 { + type Engine = w3f_bls::TinyBLS377; + fn protocol_id() -> ProtocolId { + ProtocolId::TinyBLS377 + } +} + /// The murmur store contains minimal data required to use a murmur wallet #[cfg(feature = "client")] #[derive(Clone, serde::Serialize, serde::Deserialize, Encode, Decode)] -pub struct MurmurStore { +pub struct MurmurStore { /// The nonce of this murmur store pub nonce: u64, /// A map of block numbers to leaf positions in the mmr @@ -83,66 +110,87 @@ pub struct MurmurStore { pub proof: Vec, /// A serialized public key associated with the VRF proof pub public_key: Vec, + /// The id of the protocol used to generate the murmur store + pub protocol_id: ProtocolId, + /// The protocol engine used + _phantom: PhantomData

, } #[cfg(feature = "client")] -impl MurmurStore { +impl MurmurStore

{ /// Create a new Murmur store /// /// * `seed`: An any-length seed (i.e. password) /// * `block_schedule`: The blocks for which OTP codes will be generated /// * `ephemeral_msk`: Any 32 bytes /// * `round_public_key`: The IDN beacon's public key - pub fn new, R>( + pub fn new, R>( mut seed: Vec, block_schedule: Vec, nonce: u64, - round_public_key: DoublePublicKey, + round_public_key: DoublePublicKey, rng: &mut R, ) -> Result where R: Rng + CryptoRng + SeedableRng + Sized, { - let mut witness = generate_witness(seed.clone(), rng); - let mut secret_key = - SecretKey::<::Affine>::from_seed(&witness); + let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + transcript.write_bytes(&seed); + + let mut challenge: [u8; 32] = transcript.challenge(b"challenge").read_byte_array(); + + let mut secret_key = SecretKey::< + <::SignatureGroup as CurveGroup>::Affine, + >::from_seed(&challenge); + let pubkey = secret_key.as_publickey(); let mut pubkey_bytes = Vec::new(); pubkey.serialize_compressed(&mut pubkey_bytes).unwrap(); - // generate a transcript s.t. the proof verification requires the nonce - let mut transcript = Transcript::new_labeled(MURMUR_PROTO); - transcript.write_bytes(&nonce.to_be_bytes()); - let signature = secret_key.sign_thin_vrf_detached(transcript.clone(), &[]); + let mut vrf_transcript = Transcript::new_labeled(MURMUR_PROTO_VRF); + // generate a transcript such that the proof verification requires the nonce + vrf_transcript.write_bytes(&nonce.to_be_bytes()); + let signature = secret_key.sign_thin_vrf_detached(vrf_transcript.clone(), &[]); let mut sig_bytes = Vec::new(); signature.serialize_compressed(&mut sig_bytes).unwrap(); + let mut witness: [u8; 32] = transcript.clone().witness(rng).read_byte_array(); let totp = BOTPGenerator::new(witness.to_vec()).map_err(|_| Error::InvalidSeed)?; - seed.zeroize(); witness.zeroize(); + challenge.zeroize(); secret_key.zeroize(); let mut metadata = BTreeMap::new(); let store = MemStore::default(); let mut mmr = MemMMR::<_, MergeLeaves>::new(0, store); + let mut transcript_otp = Transcript::new_labeled(MURMUR_PROTO_OTP); + transcript_otp.write_bytes(&seed); + transcript_otp.write_bytes(&nonce.to_be_bytes()); + + seed.zeroize(); + + // let t: [u8;32] = transcript_otp.challenge(b"ok").read_byte_array(); + // panic!("{:?}", t); for &i in &block_schedule { let mut otp_code = totp.generate(i as u64); let identity = I::build_identity(i); - - let mut ephemeral_msk: [u8; 32] = transcript + let mut ephemeral_msk: [u8; 32] = transcript_otp .clone() .fork(b"otp-leaf-gen") .chain(&i.to_be_bytes()) - .chain(&otp_code.as_bytes()) .challenge(b"ephemeral_msk") .read_byte_array(); + // if i == 10 { + // panic!("{:?}", ephemeral_msk); + // } + let ephem_rng = R::from_seed(ephemeral_msk); - let ct_bytes = timelock_encrypt::( + let ct_bytes = timelock_encrypt::( identity, round_public_key.1, ephemeral_msk, @@ -161,7 +209,15 @@ impl MurmurStore { let root = mmr.get_root().map_err(|_| Error::InconsistentStore)?; - Ok(MurmurStore { nonce, metadata, root, proof: sig_bytes, public_key: pubkey_bytes }) + Ok(MurmurStore { + nonce, + metadata, + root, + proof: sig_bytes, + public_key: pubkey_bytes, + protocol_id: P::protocol_id(), + _phantom: PhantomData, + }) } /// Build data required (proof and commitment) to execute a valid call from a murmur wallet @@ -169,18 +225,24 @@ impl MurmurStore { /// * `seed`: The seed used to create the mmr /// * `when`: The block number when the wallet is being used (or will be) /// * `call_data`: The call to be executed with the wallet (at `when`) - pub fn execute( + pub fn execute( &self, mut seed: Vec, when: BlockNumber, call_data: Vec, - mut rng: R, ) -> Result<(MerkleProof, Vec, Ciphertext, u64), Error> { if let Some(ciphertext) = self.metadata.get(&when) { - let commitment = MurmurStore::commit(seed.clone(), when, &call_data.clone(), &mut rng)?; + + let commitment = self.commit( + seed.clone(), + ciphertext.clone(), + &call_data.clone(), + when, + )?; + seed.zeroize(); - let idx = self.metadata.keys().position(|k| k == &when).expect("The leaf should exist"); + let idx = self.metadata.keys().position(|k| k == &when).expect("The leaf exists"); let pos = leaf_index_to_pos(idx as u64); let mmr = self.to_mmr()?; let proof = mmr.gen_proof(vec![pos]).map_err(|_| Error::InconsistentStore)?; @@ -195,22 +257,30 @@ impl MurmurStore { /// * `seed`: The seed used to generated the MMR /// * `when`: The block number when the commitment is verifiable /// * `data`: The data to commit to - fn commit( + fn commit( + &self, mut seed: Vec, - when: BlockNumber, + ciphertext: Vec, data: &[u8], - mut rng: R, + when: BlockNumber, ) -> Result, Error> { - let mut witness = generate_witness(seed.clone(), &mut rng); - let botp = BOTPGenerator::new(witness.to_vec()).map_err(|_| Error::InvalidSeed)?; + let mut transcript = Transcript::new_labeled(MURMUR_PROTO_OTP); + transcript.write_bytes(&seed); + transcript.write_bytes(&self.nonce.to_be_bytes()); - seed.zeroize(); - witness.zeroize(); + let mut ephemeral_msk: [u8; 32] = transcript + .clone() + .fork(b"otp-leaf-gen") + .chain(&when.to_be_bytes()) + .challenge(b"ephemeral_msk") + .read_byte_array(); - let mut otp_code = botp.generate(when as u64); + seed.zeroize(); + let mut otp_code = aes_decrypt::(ciphertext, ephemeral_msk.as_slice().to_vec())?; + ephemeral_msk.zeroize(); let mut hasher = sha3::Sha3_256::default(); - Digest::update(&mut hasher, otp_code.as_bytes()); + Digest::update(&mut hasher, &otp_code); Digest::update(&mut hasher, data); otp_code.zeroize(); @@ -231,20 +301,6 @@ impl MurmurStore { } } -/// construct a 32-byte witness by seeding a transcript with the given 'seed' -/// using a CSPRNG to generate the witness -/// -/// * `seed`: The value written to the transcript -/// * `rng`: A CSPRNG -#[cfg(feature = "client")] -pub fn generate_witness(mut seed: Vec, mut rng: R) -> [u8; 32] { - let mut transcript = Transcript::new_labeled(MURMUR_PROTO); - transcript.write_bytes(&seed); - seed.zeroize(); - - transcript.clone().witness(&mut rng).read_byte_array() -} - /// A helper function to perform timelock encryption /// /// * `identity`: The identity to encrypt for @@ -262,13 +318,31 @@ fn timelock_encrypt( ) -> Result, Error> { let ciphertext = tle::(pk, ephemeral_msk, message, identity, rng).map_err(|_| Error::TlockFailed)?; + let mut ct_bytes = Vec::new(); ciphertext .serialize_compressed(&mut ct_bytes) .map_err(|_| Error::InvalidBufferSize)?; + Ok(ct_bytes) } +/// Use a secret key to decrypt the ciphertext before the beacon outputs +/// This uses AES_GCM decryption. It returns the plaintext if decryption is successful, else an error +/// +/// * `ciphertext_bytes`: The ciphertext to decrypt +/// * `secret`: A decryption key (32 bytes) +/// +#[cfg(feature = "client")] +fn aes_decrypt(ciphertext_bytes: Vec, secret: Vec) -> Result, Error> { + let ciphertext: TLECiphertext = + TLECiphertext::deserialize_compressed(&mut &ciphertext_bytes[..]).unwrap(); + + let plaintext = ciphertext.aes_decrypt(secret).map_err(|_| Error::AesDecryptFailed)?; + + Ok(plaintext.message) +} + /// Functions for verifying execution and update requests /// These functions would typically be called by an untrusted verifier (e.g. a blockchain runtime) pub mod verifier { @@ -278,14 +352,17 @@ pub mod verifier { #[derive(Debug, PartialEq)] pub enum VerificationError { + /// The Schnorr proof could not be deserialized UnserializableProof, + /// The public key could not be deserialized UnserializablePubkey, } - /// Verify the correctness of execution parameters by checking that the Merkle proof and hash - /// are valid The function outputs true if both conditions are true: - /// proof.verify(root, [(pos, Leaf(ciphertext))]) - /// hash = Sha256(otp || aux_data) + /// Verify the correctness of execution parameters by checking that the Merkle proof, `Proof`, and hash `H` + /// are valid. The function outputs true if both conditions are true: + /// + /// 1. Proof.Verify(root, [(pos, Leaf(ciphertext))]) + /// 2. H == Sha256(otp || aux_data) //// /// It outputs false otherwise. /// @@ -296,6 +373,7 @@ pub mod verifier { /// * `otp`: The OTP /// * `aux_data`: The expected aux data used to generate the commitment /// * `pos`: The position of the Ciphertext within the MMR + /// pub fn verify_execute( root: Leaf, proof: MerkleProof, @@ -310,6 +388,7 @@ pub mod verifier { if validity { let mut hasher = sha3::Sha3_256::default(); Digest::update(&mut hasher, otp); + Digest::update(&mut hasher, aux_data); let expected_hash = hasher.finalize().to_vec(); validity = validity && expected_hash == hash; @@ -320,18 +399,19 @@ pub mod verifier { /// Verifies a Schnorr proof /// This is used to ensure that subsequent calls to the 'new' function are called with the same - /// seed + /// seed where the output is non-deterministic /// /// * `serialized_proof`: The serialized proof /// * `serialized_pubkey`: The serialized public key /// * `nonce`: A nonce value + /// pub fn verify_update( serialized_proof: Vec, serialized_pubkey: Vec, nonce: u64, ) -> Result { // build transcript - let mut transcript = Transcript::new_labeled(MURMUR_PROTO); + let mut transcript = Transcript::new_labeled(MURMUR_PROTO_VRF); transcript.write_bytes(&nonce.to_be_bytes()); // deserialize proof and pubkey @@ -360,12 +440,11 @@ mod tests { use rand_core::OsRng; use w3f_bls::{DoublePublicKeyScheme, TinyBLS377}; - /// pub const BLOCK_SCHEDULE: &[BlockNumber] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; pub const WHEN: BlockNumber = 10; - pub const OTP: &[u8] = b"823185"; + pub const OTP: &[u8] = &[52, 54, 49, 53, 51, 54]; pub struct DummyIdBuilder; impl IdentityBuilder for DummyIdBuilder { @@ -384,7 +463,7 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -410,7 +489,7 @@ mod tests { let seed = vec![1, 2, 3]; let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -421,10 +500,10 @@ mod tests { let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = - murmur_store.execute(seed.clone(), WHEN, aux_data.clone(), &mut rng).unwrap(); + murmur_store.execute(seed.clone(), WHEN, aux_data.clone()).unwrap(); assert!( - verifier::verify_execute(root, proof, commitment, ciphertext, OTP, &aux_data, pos,) + verifier::verify_execute(root, proof, commitment, ciphertext, OTP, &aux_data, pos) ); } @@ -438,7 +517,7 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -449,7 +528,7 @@ mod tests { let aux_data = vec![2, 3, 4, 5]; - match murmur_store.execute(seed.clone(), 10000, aux_data.clone(), &mut rng) { + match murmur_store.execute(seed.clone(), 10000, aux_data.clone()) { Ok(_) => panic!("There should be an error"), Err(e) => assert_eq!(e, Error::NoCiphertextFound), } @@ -466,7 +545,7 @@ mod tests { let seed = vec![1, 2, 3]; let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -477,7 +556,7 @@ mod tests { let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = - murmur_store.execute(seed.clone(), WHEN, aux_data.clone(), &mut rng).unwrap(); + murmur_store.execute(seed.clone(), 1, aux_data.clone()).unwrap(); let bad_aux = vec![2, 3, 13, 3]; assert!( @@ -501,7 +580,7 @@ mod tests { let seed = vec![1, 2, 3]; let other_seed = vec![2, 3, 4]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -510,21 +589,22 @@ mod tests { ) .unwrap(); - let other_murmur_store = MurmurStore::new::( - other_seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - other_double_public, - &mut rng, - ) - .unwrap(); + let other_murmur_store = + MurmurStore::::new::( + other_seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 0, + other_double_public, + &mut rng, + ) + .unwrap(); let aux_data = vec![2, 3, 13, 3]; // the block number when this would execute let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = other_murmur_store - .execute(other_seed.clone(), WHEN, aux_data.clone(), &mut rng) + .execute(other_seed.clone(), WHEN, aux_data.clone()) .unwrap(); assert!(!verifier::verify_execute( @@ -546,7 +626,7 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -560,15 +640,16 @@ mod tests { // now verify the proof for nonce = 0 assert!(verifier::verify_update::(proof, pk.clone(), 0,).unwrap()); - let mut same_rng = ChaCha20Rng::seed_from_u64(0); - let another_murmur_store = MurmurStore::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 1, - same_double_public, - &mut same_rng, - ) - .unwrap(); + let mut not_same_rng = ChaCha20Rng::seed_from_u64(1); + let another_murmur_store = + MurmurStore::::new::( + seed.clone(), + BLOCK_SCHEDULE.to_vec(), + 1, + same_double_public, + &mut not_same_rng, + ) + .unwrap(); let another_proof = another_murmur_store.proof; let another_pk = another_murmur_store.public_key; @@ -587,7 +668,7 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::new::( + let murmur_store = MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -601,26 +682,4 @@ mod tests { // now verify the proof for nonce = 1 assert!(!verifier::verify_update::(proof, pk, 1,).unwrap()); } - - #[test] - fn test_generate_witness_determinism() { - let seed = vec![1, 2, 3, 4, 5, 6, 7, 8]; - let rng = ChaCha20Rng::seed_from_u64(0); - let witness1 = generate_witness(seed.clone(), rng.clone()); - let witness2 = generate_witness(seed, rng); - - assert_eq!(witness1, witness2, "Witnesses should be identical for the same seed"); - - let secret_key_1 = SecretKey::< - <::SignatureGroup as CurveGroup>::Affine, - >::from_seed(&witness1); - let pubkey_1 = secret_key_1.as_publickey(); - - let secret_key_2 = SecretKey::< - <::SignatureGroup as CurveGroup>::Affine, - >::from_seed(&witness2); - let pubkey_2 = secret_key_2.as_publickey(); - - assert!(pubkey_1 == pubkey_2); - } } diff --git a/lib/artifacts/metadata.scale b/lib/artifacts/metadata.scale index 578d0969aa605efb5b2a2720564786a2aa96e9c2..610fd5218ebac59fb8e5dd86f666c031d7f6177a 100644 GIT binary patch delta 15731 zcmb_@e_T{m+W0+p=ALsd^MgTt3&?7IvF{!L9 zZK5YiQ)=C^&6HgCYGq31?)rUo({0^dcf<5G)~F?;a?Of*pL1tS$Ufii`^T5hFz4KJ zpXWK}Jm>j!&Uxm*)uhYUl4!Zp>j+M0rvEmO@Pl6&YJ*6DZ>2Sj6#8DWjwSPbm#m}7 zLSIbqNO>jm9iZc({Xu3U)xKW^k0dp|4}-_ZwG7;wS-5YAZ33zHx$%3e?|^L#Y4ZKs zmTBC>$Tnz6V`09rA<@QtjQkcB9Z}+Zt3u-BW(Ljm_-1cN3_0XG88VC<^?ehPC7)+t ze~3l+@_I#+3%+H&Qb~vJ*6YnoBzY zzvO)F(bIA&C#mq(U*xktA9O!>?VOzR@Y(Y+{u{Kj=Z&R1HkhM|tDFw6GmO|O9Ocf1 zgxF?aDG^(h+wB#Pv#`WRk87o_GOYBGmc2_Au@Ku$P2EVCNrsQuM zWM8ZkM^SC7B{)&r z&nIfHaIP*wvfU=DH~n8HZ2xVcPEB8@yu4YEHh_l?$*Zm^udWhwt}0wSyT`Sx0_W|naxHUJNVlh*^1qt4B-p;DCKigSlmRf?qwJC8Ozm5bGL^vU zYGqWfXOS(%u1XXiuXBwT*)j))Rx9Bo7n-Y;IN9Na_p6lw*>;ClANDfWDrdz&yS>uw zad}Yt&rYDJmT&;5;qVEy0aD6p+k3gE@F#%wu@LDN~|MAV<6 zRf<#2VesVVN&@tb#ii|BrR0++n7CS*O%}p4tCf4rqRT3`*In!`%YkoJE3-nz+U=`w zrI)&j9bzEKxiEi?(ofD~kljgpL*p7{rd+^a$2A%S-@b!X%{5AmT*%XeyR8<+Ja;y^xn|$XYqTuOuN?+oFrcNacPOno!uzqo!l0?eESgQ<)LPSpbArrkt zu8O5@CnL68EG6I8m?(p^l0nF~G}hPdGauY{&uXJwjkfTlF-q&RNx7REYM2>9j7k)| zU}CEF&?8E%8SU)BaW`Kuk~+U_s|(n+x^A{DP}gl+psw3Ce?4H^K6y!rg}>}prjdHh z-lXgzWD6XB5#^yy`|FEJFY(y^k}_T1%AhuhCHn5aqQa72;W};Ce)lWoZW+t(eyuzs z%BS`!+X-pV`u#@PPe>y?_qy_gwq&0&GVt(cvK`9zE5|L{k#k1AlkTKa1M^k9*&F5@ zP{P6*v02{9B>d>FqogJVwiFhwZ9kyADjW778;+;3aF~5id4}xOK0T;hFdFtU8<1or z9R7J&*+BNe{YMlBY1WP&QOtxKg5B>Zz2W0Gl@G}gt>rD{qD+p6wk&cKF21AOOIl#$ zF?0$oP=8EGCMRM4G37CG4#pf;h9#dwNtjtFgw2SOa0-RMHp5-vL8)LCQI=8p0)z1rSd>=uM`af! zt?=WYln?yHI4$I?@~N3z*1rExxynhK_HmnXnwVR4rgYIRDqV)RuPOt=+OS1#XA*vp zUDs&?kvibURV9OTLdqw~edH!Q@`B3GLqBaU)BQBm<}!<4Lt{FWcod2;9m>CC3y0WgEDFYauBaQ#Q~XvkU)VCg zS17?*mS)@0bpB*L?W-LgyWcaFOz(z}v&LCm?RAz&nO(|5PIW9pBk>a5AnsVQO1Wrmkl|@zPj?sUO9zDxnneDA| zRV6l%iPYl^&XFFIObmtw4C;fkOz$}} z($7_1Sym)Mg9P~TOXYr&1NVQWWDU&WM)M^1YN0*39F+jSB>hXy;}U*QZflJ;sX#ma zmGY{A6vB!dN`*Y1`|3Uqhc9j@{RS@N#@RxB9GpbWbrz4EgF{}ZBi6{N*oyqeN2kC@ zon-XGy1K!-1O(b@-d&Sd@;fwnCBmUNuY~>I;DoE;$~VdYQVXV=%3x9lnKzYKY{Yl5 zu=1v|AhZUB-FsAo60;y(ShcCs}v%hUjO~KisKg$>S6SEibLMQ zA?;zBmK2!h_HJX}s?RgkCT$m!z^4Z7tM8QG5_6+?VV0uo(oXK%7#ppf?o!4Rx#>3F z!uJH|?dwKw51jcCmEZHDGTpkDlh|2KhsRyvk)*xi#ps}YqMJQjYSy_KekefVNH-FP zbP^2}bV1M&0ey%|M|J4PmH>2HH|Q1~CQNfIj3hKP^dyg+=qPhk6gx>2Iz#R%ISC61 z?Jb|=Fx0KY!?T1&lT+{pp($ufFA+Kb4KbJLaN~K5=Y4hNKCn=xiR1!olxeoHm6IL5 zgs)@Zl1zJ(%kZ5{`yzV0fi6Py^#+=5ImgWdD;;HJPH$0#3(^cU9R6aUHu*e<-S5*R zFd1ore1U_l*w`1IHBu`S8EL58%6;))_Y-fe@M|LtlP_~<5`Lu7NG)*1NP7jfiP^Vs zsog*Oj==11cAI@ikJ)$LG5b#c>^t?@N5WeQO*UTVT=EGNEG->2~)o7nM2RWSQsN}|JL^94LHZ(E|5l#P+ z9jb&Mq=_V)Sdm?-3gdaiZltHT3Yg^PzX$utF>0M;2_N1K_6M4Hn@!<^tel~XFXM?~o+tOr7Y7_+V zd8baMNyYfc&oHqzWDgYerP1))L^^@&h3kpbg#|Vp3g4%a%sHq9HDd$%hm70jho0Dv zP9@E-wI40QSgE5QogufV(D@bZBcD_u&{Q7M`|f`vFy^D(#(YE{ zpzV@$RE!v(TKv5|(;vrnQuG!D*ZR|m5vO#(H2Fj6j31>*mCmW~)?pH@EgnFN75Th> zhTq@B8D0Q2g&vSwRSkpXw`BS9?RRoppiylXU3RD#3e^p#H5l+<5et#&bg|*OYKC1_ zxwr39zN+m^r+tj_&29|r0y2ueE|VZ=9z`2u+(1kjjfKzmVqQ| zt&`ARq4P()NvIGxIrKS_py9i{#e1I``$vhGKFqKMe^q2s29TFT$+X(r%!WHSDg?!6>YW) z#!sc=aO1OnD!rSmgcDQg5Y&<{@g1z3Mh6lJo|{JRQlfBpE_h)Y9iXIRwOp^txeVf` z(@3~DjYc7s{u&h<{cGRps4=zL%;^+&S9MUhkd~8rKZ8W509^|ykJu7)+Z1gPbI%~s z)*#>hvKSb#h;GDS^QUEuJ7e&huZC_o4FczkL23kR5%t9H)Y(=@GNmRz18veCy|Opx zND%tX-9>aE?m2!aqHZjN1Po{n@>4z3jcT)wV{0=?hp;B&;}Q7%VmiQdRDTiG5`-!u zut8VllX^YtRDjaCZj`XC+m_@^&>exy8Kj8Xc}5H^3Qjoa3U*HPc`67lFQH+OyM*?& zTnG{t-&4JGscX#zs98djP`R-GNu*V~vV`te(4G29X#r`2wo;m$+}5Ml_B(rR_xIWk zBVBYf>3}L1s!S)m;-YaOok3(^zN5xn?e$;;CrO?9?D}61%LhX{f|&d~RZX{7mzco2oaV@i3Eo$(LS$l+EKt0H&I|y=!v`y9 z1ZwN2D`+AtDMR2sWpqqXI0B<}mZWGXE~BI6IFtXFA_4xpjJnN9;Oz zW~i~xdUcKc&`YyWaeG(Ocsb3a`&NDVGGIfs4sfEHW?IusBzAI@%UMx^k=7_pT}2lg zF9dY+=bAFMUX>`pui+KO}-5e#b{|8=mLUT z{lrFk1GW00I{G+j^`K^&0O^}(KdjH+gaT9z8#d95-Zj|s;KB-VoaOZ39t`Eo>ndL1 z^wz?kHlbOl1IvSSs-ecDhlWY8;X#^_UMslFr5ck|*JB#BcTPiq;xy|0%Or0x`F?z? zw>Eh*{YW-%)yZr(MM>LrMuj&77}eN~Q4O%_VcHnhC??rpl6LAiO^DOv$JrCW+1m|g z50pMa*QxH|Bl?>e z&rzx!dzRWs@C6eYobB;Ay|cZz=kNe|9^E~Aj;7+l!7bSPV=kLWdba;a*bj&2u6U}7 zfqomj_Z%IMnymx(ejW{Xo4>x~dHTGmT};L-Ol^n1wa*SZ7d$&@2-NSO8OG}-DGY`l zrg4hc=UsD!qT!Tu`AeCYp=mG849BCGpLTBwYLk54AgS8t zd+8A(Tkue)$q)rCzdW;pkWAwt`Bh;m}i74OXwM!tFec6jr5^a;5j@UU2;TjavP z1363|jGwO~*mf)=(n6i~8T8u@y%zPFOpMaf4Iv4o$GunEPc{;`!++N0|rrq#NI%iAl9)a=TF?u_aFZOt;2N zV(<1>g_qT7eE1GsAlI59`yM*RETZEbewNii-7y-PT4(MVap(}J2K6|0{Y&0rmhdBV zdWW<%Fv!0hqowG7CmcsN-T-CCX|&v62J3N}CQ5ydXO7eI-i-p$X4`RroFURqoyvLA zWR{u&K+9U_a(RzFxPEY>h4#5?ubB+iqoUbPuO6lBy){5=5Qh&)P;%lohP^nyHn~|R zWsg3Dlao?OdcM9#oQzi-`dnk^qO`-)CQXs1um-X_%MUGLL)pzqUXQIWAe zj$lB0@htr_?mU~%QAeLMX0l*XnZs2sw9k1f;vQ_zEx}Oj45a>sosX;z(_RZ*s0?S`%ne~D`V^y}uQmfhQo7oYmz3^AM)F5Bh#W%7oAinM0#JA0? zZ~bDR^dI!8pmwAnx0$65v%ay_2KjS{gy+(#JvV|l3+L$aqoqPYge!hH!hBH%wCQNW=1iA!{dK@GH>zeHPQ zi^XTZVFx^tP_@>}xHCtkwnejL7;P@TqVMRoUco>t4laL;v7lZIfJGl;wAZugquwm~ zXHB^=?B`I=wIO1-=pY(Sx6yomFLucK7k$zgZ6Rfl{V|5SpZyE>4}reM#jzla#?!9S zWQ4x|D!RdBSbr5wbF`n%J6GvF{o^d%{V7uV`Ppx@1sxIm;S)L-LPYPCpV0Z~$vv=A z?!-zFSdt_PG;nxym}U_MZs0H3s|*B^M_DBNAaDc8vPfC}5mvP0XcM5JogV9(Z6RTk zs;gGvZQS5;S4EMhy0X$;<-OgEn`E6m+dif5j?b|W`()P|XGyNOqO-3laxYzqF6C#< zHNVu1JJ1~1{uxc~m1`kp7vf8~I5M>7c^2Qen=x8^A9m0{3gG!`^bJ#?K4=LKrRT$h zPHKlWU(gvCVf^6>nuDgw(uwC13&oqoxP55qq*E}o_~I)XkEYG`HSH%mbiNF5TKtyB zWx4G^oH}V+5|vz7lku@!5Q>2pzoe59U++%+icSn6tQWnWXSS=mZ3G{}^lqKN3M=53ZVDNE( zvNX9RK>K^jCZS!Mz?dEFnuD=l;}hpB37>{=HUJDkECwn$drUqTK#5RUhkQQp&?ksR z#$B+G^umfI?uwFpx4TT(Wny3L@|G6iF?K7SNU_QCg#i4)AT~*E^-p594gJl9z`9)i zg}Gd|^z=S{cr@f~$Ta;+ZnsGI5gK;EB6V2&M`s$|o`o{fslCyQZB`7Ofo5^3#^By4 zrlDEX4ON}rP~C+8--Zh0J}e9_#;~~<2BR_jUz@4A?q*8v^2^TWag1A|yB#uoiZ z6)U{C8;>kMPhj?N)#|q(l4J?gY*Gk>Oh>MVTOpxI=?f{}Qj7L%B6~tcGq^5^^@i*u zHk!m~SbvJ4vB@67A|NS+^&?3zH3j7(8JqcR`6 zl-sGaPzMQhSp6W*+aPdmG+s|VHJW7xIR&u8D!Hr>Qi``HpN?j4!R#y+sl7dhO(gIx zRv|o#je~@7SX-XOCPvj*$%IL6yeDyrGnGJWF4gE1!r?*|n*z-OICC6xo9pytvZ3{? z4*>je9Gfn03G_F3JUYUykTaeoCT+End&ND3vt*LH3QsgWD<-iY zpwTp*?ZErlaTC}p9PekZPGJuicUlA2avEOCWf0uM9GuG1NEozDW$AryJrFaEC2ojKpXex=^ix^^irdp7J>YhtfgQ6{Fg-=2I$(-Qm9yB5 zm-)_;^t-DoG5-TYC4WMRag9K8Ra91c?P8x{cX{lNGTgwF)Yz*r9b{l`1;(_ll9_Yz z@m^Fjh#3H<-)eaT~gCxCH?d_6Y&9k zjew7Vh&(hDEifUE4HtPJ8_pVg!;|9Sq*Y{r^wsW~fiYRsDIxXtGuCs~^VSR2R%@HJ z-P&QrsM(&6_GsBmmP*d~>u=0tcR_qUlKy5U8-Ryb2r(A-!8+jTeAeG|*-9`?!y!qR zt-iBznD%x)+hM~EuMXjHpbcq<-3~U|&|&4i?SuM0vxMCzcj~ZExr8O6Gw@*%annjN zh3Mz35x2M=S22e1*Zp80Enzcz;h2q5COV#*f??OK7)?(oW>aMn438JHfdxu%cUwn- z@33_wShsa5!rjg~*@#Ie7KGK4PBtR-K+cIJSc1n&&;buFVf`_%o?XHcW5R>Ul){P? z74Fp)bE+H_9*2HvD-37|Y%5{^!1x%8zVL*T%|PSc>O?;t2Zp6=5Eet0vZ$y89D^jj zPx6hKTEa#ncnc6gDeDS}`kv@K;LQqy`2JR`XK$TI}p z+d~&a%J8F>FJlLAf4#euCBXPnmX?~;gG{y{lXHs!+1<$GU>(V13)Fdd&#Y63cCjq9 z{_|WYW_eKOVoAvbJ@}q`C*O1ZgbJZ)1v=#`E>?trXz&U)8MZ9PuxGx%{hj43-`}2x zhs_IDu$bh9Jt#Zwr0noh#;|q;3Z)Y+u0Vt80$UkdfDvhV8OsWG1ry^eJehG~a+TLp z14qhO+z?j}7L*GXlm(_Dxs^~<&c?%=O@3fJApq|vE{>>dte^A6Z4QC zQxrxX6@|9~4lf&EI2z2MrO6Npdpzu}et38`L0F*dYHukf1$t}5Q34rU>@F{NdCP_T zC}#NDgWB8z@m`ia_hgT5Tkh<(#oz6z9_&j;?wz^CJ#iru(AzV9;4|<$FME5#xgI#F z-EmUyz&YOo_<~s2pL)1}WPjF$XfpEF;I?3?6|3^)VCix&zZ)}Z%FC;YDyv-OF6gXA z6>o!>Rcr(%HqBbavWK+y=w);FPB!1sM+da5V!5H6XwqgoJsyn2@=GzzRNP9$t!C~K z*Lx7VDTYjJ0VTZIZOC{ED9n&(lDmQ-bq(5(zpiHY%ftr5*RZ)86kB(@uOf|~<&LR- z3ZjrLHVHpC`OGb<6oQzyJ%N}HX59EaLAUvcWFj0uc#m!bpud~wpK{a!fYG8Ktj9q@ z4I75L^f@)?0}G+9hKb|2eWKV27i!p0(go^TjABz@`dW53se$clQCGLZ@wK}D?Zg7_ zU@`kU0**h(l6}`Og`@5EGaP+@g`oF*{{faGcd5Q6+%^ne$1?F0$+-@xCqctHw1>&? zhjnZ?o=twY4pprVl524|TVZZ3zH5a?Y7sIS4vS(Gd?Jcmh+2;Wa6+yqT(EXMHg17e z*CYBq_`4{M0NsG^PQl0xNbDSxY(S;B2~Tdo$fZjZnbu+Bq=8b{M5l9UjZsp;z7gRQ zpkN~cX2UvBIN{}u$crQJ!A6!Tx0rn!%n2~04iBTw!IV0dF1O<8ojC>?>sS)rbRDZ> zW92Th&us~Zrysz?_eM(!%-O^y8Z8z~Zn*pr4Tt@kSUmcr_c!5)3*g61Y=m5I@m;pY z!JG$KEKXwOgKVU{-Exbkqi{msilz>WuQfOpW^QJEWMUPG^@X@$^Htj7VAKPc4S8)d z?sMX-zGho2tlGq^zJJsh=p$BNeuBHtdKs%LS?!vpm=VN8IpM~7Je z@ekos1LQx1643|`KE!fxKM+W#hbs@E8{7lNhf(7W!H|d9aN|j9Ag^AleVC;vcuW7A z#~B_F#%V!MvQmP9hG!ePjwIN>jZMb&`_DFZ0ylkcZbxUI1uLFHADgZH*Hdi1jGZSn zu=yx}8yeU&GGF_z2KE~Rma7}t^8^ExBreiEd#nhD-{>Xwb zbw%5Ino$E8r5$*mon$0STlF{g5g|F++zZT23^{79_Q!v)T{5U|7yp~ROh}Rsvqt; z@TNfABuMAsZk4Ym7qo1S!zIN#=_f9%jx<t48u+xJG+N^5Iytj^xu(cn4*2FB;)pnfy_d-9e-HU&tQq$5FhIpzzjB z=ArP~7=8qg;+Br(pOW)hK^EUhh(o(JjxSU2>VDqc{0yO&#rP9oXb_Kp36uE*(xD-) zn~0mgdysM$tee7TBCk$Q;lmB#rf86J`3&3&6y)+eXDR0;uNw4 zS9}YME8=~SaSMz1dnnb3i+Q^d&7|4M`x}}~hqMgL0TsF3r=-*ub#=K!A{fw4X&IcON>Mvc1 zJ)ehTtM~xqM=R`K$!8eaO&!lv@tcNDQ@#-6FwN%S4;xT^pIycK|39Gtp`*3pHN3)* z*k!uqk%-?hg$=$H>bab#b9jmR)dRc?FH<$gIzC20Y2R6gf?}|k4~VV@!-NNU_U)$w z5Axs95HoV%HQ2Tb_kB-p=Dl&^uWsg}32H!FJ>Q7Sx%44aP4s;qKFniilKIraW}`+P z;boC<_F-n!%6IWtG=A{tZhns;!@OJcG!VYu&Bx+Wk7_~@PtbNZ@lFK|`%^FT5_F$8 zUgmaO?b_Gzt|Av+e}zAWJEzfmxerz4PkT|s3Lx%PJ_>FA*k<0JnPpj?Z(az?n|V44 z@sU^gOTrW`e4WRmRNSUg1CQ_Lk$zGS!Jq>?LN}3<4&XXg3&Nw|vjaH8t%74=8rvw977F!@Ho%GsNvLc?5Y#KJkF0J?Sn0R9l5EczRO)!5lig1 zglh-R@^`7MSit)?KAD`<-ujy`92N-2bXDB=@4JB3B~9zRz`sPp1Rr1In+N4tMoJht zmOH9eNF_4KgSQ_qAPa1R7EezgM(YO~9wU;%-Dy&QcCG|MZaHuBgP{Vc> zt-Vatd^FVBH?rCv%>~38)hlX3kiD{IsKZkXi;wVZjC){)qV_{g@F?nsGEO+3tFNMM z{En+>hBT{JC`}wpSJlm^CFfN2F0|)es#+=&m$uZb7GheB_O?ZxAtD1)u(}(0{#vja zkHzU=^#e3-yKQO_X?`X|eH?#(xHUwjXzt3&gc1#v1z zwKpTx=?3H^MlGiaD&1dV)g`#*BjeOk^isRy)S;+fXXDhFxB<8KQI{B2TC1VHkNPNj zEn7T}rVfhY)m9uumR-Fc7r)W2uE0fB64ZT`3(R(VVc=MTdIOoGC90c9qc*y)n#~QJ ztP9rPrN-iVJ%5)PBYpsK_%8K1MdcDYw|RrrMBaoDe5edJF+%KokYHeV=3zRU=-qNyh+l3&qpK->4xAmvSHj1T&6?tlHh~^Hy7`-g1w#!s&5&E?Cq35P5j7iigkMnpSK2z*_{1oo&|5gY_qWqGY>u zGae>}M|4;>CzAy>aVl?)#j|BWTFQpusp?o%{mN7|6J7D%RP~@ZD_oSOPBJRNwqL>G zG&MqdD@}cx7_M6}b9=b@z9BmJCiF{Jmj`uPZTN!^M|5y9Je#g|grx*?@0#n@flJEV z#Vd+%CtI4HrZr}$ljPu0!Ts=9+3OTy(ZaH{&qk{F8>-ykJnhK^>I~Tsf|9XvsybO4 znx$Sdp?t*WsvGdwYgev10gFqy>Nc`aLx@y^!Qsb>k>S`(bq+euuzZwAbaqVz>Z7uY z!R|3~L~yfVpnh;)B!-yjgjut0zl>-$*u)uqU9diZOdH0N-pqOknyo&DqV>#d^+6P% z-gDITgkQ%i31@|^33iKL*aV*TXf1^*UQRVb{=KTOEhFZuYf+t==c|Rn@3RGJy`jZF z&U{(8`@r1h5U@^Hw*Vin{svxmprGbK+7fjHn(tCkoP%9U)a7WngNoJPn_7eYKU-}J zZrA=Sw`Z9GMrasY9wcdz7{e8Sl fZ6YDskaG1J?u@iPZuLF`?wD$-)O2Eqvq}F0tX)i0 delta 14863 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 Result<(), Box> { handle_create(args, client, current_block_number, round_pubkey_bytes, rng).await? }, Commands::Update(args) => { - // handle_update(args, client, current_block_number, round_pubkey_bytes, rng).await? + handle_update(args, client, current_block_number, round_pubkey_bytes, rng).await? }, Commands::Execute(args) => handle_execute(args, client, current_block_number, rng).await?, } @@ -124,15 +124,15 @@ async fn handle_create( client: OnlineClient, current_block_number: BlockNumber, round_pubkey_bytes: Vec, - mut rng: ChaCha20Rng, + rng: ChaCha20Rng, ) -> Result<(), Box> { let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, rng)?; let call = etf::tx().murmur().create( BoundedVec(args.name.as_bytes().to_vec()), - mmr_store.root.0, + BoundedVec(mmr_store.root.0), mmr_store.metadata.keys().len() as u64, - mmr_store.proof, - mmr_store.public_key, + BoundedVec(mmr_store.proof), + BoundedVec(mmr_store.public_key), ); client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; @@ -142,29 +142,29 @@ async fn handle_create( Ok(()) } -// /// This function updates an existing Murmur wallet -// async fn handle_update( -// args: &WalletCreationDetails, -// client: OnlineClient, -// current_block_number: BlockNumber, -// round_pubkey_bytes: Vec, -// mut rng: ChaCha20Rng, -// ) -> Result<(), Box> { -// let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, &mut rng)?; - -// let call = etf::tx().murmur().update( -// BoundedVec(args.name.as_bytes().to_vec()), -// mmr_store.root.0, -// mmr_store.metadata.keys().len() as u64, -// mmr_store.proof, -// ); - -// client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; - -// println!("✅ Murmur Proxy Update: Successful!"); - -// Ok(()) -// } +/// This function updates an existing Murmur wallet +async fn handle_update( + args: &WalletCreationDetails, + client: OnlineClient, + current_block_number: BlockNumber, + round_pubkey_bytes: Vec, + rng: ChaCha20Rng, +) -> Result<(), Box> { + let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, rng)?; + + let call = etf::tx().murmur().update( + BoundedVec(args.name.as_bytes().to_vec()), + BoundedVec(mmr_store.root.0), + mmr_store.metadata.keys().len() as u64, + BoundedVec(mmr_store.proof), + ); + + client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; + + println!("✅ Murmur Proxy Update: Successful!"); + + Ok(()) +} /// Build a new Murmur store for each block from `current_block_number + 2` to `current_block_number + args.validity` fn build_mmr_store( diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 91856a9..000e5c3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -24,7 +24,7 @@ pub use etf::runtime_types::{ bounded_collections::bounded_vec::BoundedVec, node_template_runtime::RuntimeCall, }; pub use murmur_core::{ - murmur::{Error, MurmurStore}, + murmur::{Error, MurmurStore, EngineTinyBLS377}, types::BlockNumber, }; use rand_chacha::ChaCha20Rng; @@ -79,7 +79,7 @@ pub fn create( let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes) .map_err(|_| Error::InvalidPubkey)?; - let mmr_store = MurmurStore::new::( + let mmr_store = MurmurStore::::new::( seed.clone(), block_schedule.clone(), nonce, diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 37ac82c..76b7cc5 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -21,10 +21,9 @@ use alloc::vec::Vec; use ark_ec::CurveGroup; use ark_serialize::CanonicalSerialize; -use ark_std::rand::{CryptoRng, Rng}; use dleq_vrf::SecretKey; use rand_core::OsRng; -use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, EngineBLS, TinyBLS377}; +use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, TinyBLS377}; extern crate alloc; @@ -32,22 +31,6 @@ pub use murmur_core::otp::BOTPGenerator; pub use murmur_core::murmur::MurmurStore; -pub use murmur_core::murmur::generate_witness; - -pub fn otp( - seed: Vec, - when: u64, - rng: &mut R, -) -> Vec { - let witness = generate_witness(seed.clone(), rng); - let secret_key = SecretKey::<::Affine>::from_seed(&witness); - let pubkey = secret_key.as_publickey(); - let mut pubkey_bytes = Vec::new(); - pubkey.serialize_compressed(&mut pubkey_bytes).unwrap(); - let totp = BOTPGenerator::new(witness.to_vec()).unwrap(); - totp.generate(when).as_bytes().to_vec() -} - pub fn get_dummy_beacon_pubkey() -> Vec { let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); let double_public: DoublePublicKey = From 556741dbabed455399e03f5482728f848a7ad92d Mon Sep 17 00:00:00 2001 From: driemworks Date: Thu, 14 Nov 2024 21:10:28 -0600 Subject: [PATCH 15/19] feat: add protocol engine, allow specific rng, update to tle lib --- Cargo.lock | 61 +++++------ core/Cargo.toml | 6 +- core/src/murmur.rs | 206 ++++++++++++++++++++----------------- core/src/types.rs | 2 +- lib/src/bin/murmur/main.rs | 83 ++++++++------- lib/src/lib.rs | 28 ++--- test-utils/src/lib.rs | 6 +- 7 files changed, 206 insertions(+), 186 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9439d9..c9ceef2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1539,33 +1539,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "etf-crypto-primitives" -version = "0.2.4" -source = "git+https://github.com/ideal-lab5/etf-sdk/?branch=dev#e2b3615e0936c2cdd1ab8673e15208bef880b040" -dependencies = [ - "aes-gcm", - "ark-bls12-377", - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "array-bytes", - "chacha20poly1305", - "generic-array", - "parity-scale-codec", - "rand_chacha", - "scale-info", - "serde", - "serde_cbor", - "serde_json", - "sha2 0.10.8", - "sha3", - "w3f-bls", -] - [[package]] name = "etf-crypto-primitives" version = "0.2.4" @@ -2555,12 +2528,12 @@ dependencies = [ "ark-transcript 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", "ckb-merkle-mountain-range", "dleq_vrf 0.0.2 (git+https://github.com/w3f/ring-vrf.git)", - "etf-crypto-primitives 0.2.4 (git+https://github.com/ideal-lab5/etf-sdk/?branch=dev)", "parity-scale-codec", "rand_chacha", "rand_core", "serde", "sha3", + "tle", "totp-rs", "w3f-bls", "zeroize", @@ -4921,6 +4894,34 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tle" +version = "0.1.0" +source = "git+https://github.com/ideal-lab5/tle.git?branch=feat/tle-monorepo#1a50d569cdda608df56d4951b7141e43cc067cef" +dependencies = [ + "aes-gcm", + "ark-bls12-377", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "array-bytes", + "chacha20poly1305", + "generic-array", + "parity-scale-codec", + "rand_chacha", + "rand_core", + "scale-info", + "serde", + "serde_cbor", + "serde_json", + "sha2 0.10.8", + "sha3", + "w3f-bls", +] + [[package]] name = "tokio" version = "1.40.0" @@ -5273,9 +5274,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "w3f-bls" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" +checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" dependencies = [ "ark-bls12-377", "ark-bls12-381", diff --git a/core/Cargo.toml b/core/Cargo.toml index c0cfa29..8e43997 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,14 +21,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] totp-rs = { version = "5.5.1", default-features = false, optional = true } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"], default-features = false } -etf-crypto-primitives = { git = "https://github.com/ideal-lab5/etf-sdk/", branch = "dev", default-features = false } +tle = { package = "tle", default-features = false, git = "https://github.com/ideal-lab5/tle.git", branch = "feat/tle-monorepo"} ckb-merkle-mountain-range = { version = "0.5.2", default-features = false } sha3 = { version = "0.10.8", default-features = false } serde = { version = "1.0.188", features = ["alloc", "derive"], default-features = false } ark-bls12-377 = { version = "0.4.0", default-features = false } ark-std = { version = "0.4.0", default-features = false } ark-serialize = { version = "0.4.0", default-features = false } -w3f-bls = { version = "0.1.3", default-features = false } +w3f-bls = { version = "=0.1.3", default-features = false } zeroize = { version = "1.8.1", default-features = false } ark-ec = { version = "0.4", default-features = false } ark-ff = { version = "0.4", default-features = false } @@ -52,7 +52,7 @@ std = [ "w3f-bls/std", "serde/std", "codec/std", - "etf-crypto-primitives/std", + "tle/std", "sha3/std", "ckb-merkle-mountain-range/std", "zeroize/std", diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 382158b..d34c4e4 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -39,13 +39,19 @@ use ckb_merkle_mountain_range::{ use codec::{Decode, Encode}; use core::marker::PhantomData; use dleq_vrf::{EcVrfVerifier, PublicKey, SecretKey}; -use etf_crypto_primitives::{encryption::tlock::*, ibe::fullident::Identity}; use sha3::Digest; +use tle::{ + ibe::fullident::Identity, + stream_ciphers::{AESGCMStreamCipherProvider, AESOutput, StreamCipherProvider}, + tlock::*, +}; use w3f_bls::{DoublePublicKey, EngineBLS}; -/// The Murmur protocol label for domain separation in transcripts +/// The base Murmur protocol label pub const MURMUR_PROTO: &[u8] = b"Murmur://"; +/// The Murmur protocol label used for transcripts used while generation/encryption OTP codes pub const MURMUR_PROTO_OTP: &[u8] = b"MurmurOTP://"; +/// The Murmur protocol label used for generating DLEQ proofs pub const MURMUR_PROTO_VRF: &[u8] = b"MurmurVRF://"; /// The size of a 32-bit buffer pub const ALLOCATED_BUFFER_BYTES: usize = 32; @@ -72,6 +78,8 @@ pub enum Error { InvalidSeed, /// The public key was invalid (could not be decoded) InvalidPubkey, + /// The ciphertext could not be deserialized to a TLECiphertext + CiphertextDeserializationFailed, } pub trait ProtocolEngine { @@ -80,7 +88,6 @@ pub trait ProtocolEngine { } /// The supported protocols -#[cfg(feature = "client")] #[derive(Clone, serde::Serialize, serde::Deserialize, Encode, Decode)] pub enum ProtocolId { /// small signatures, SignatureGroup = G1 (48 bytes), PublicKeyGroup = G2 (96 bytes) @@ -124,7 +131,7 @@ impl MurmurStore

{ /// * `block_schedule`: The blocks for which OTP codes will be generated /// * `ephemeral_msk`: Any 32 bytes /// * `round_public_key`: The IDN beacon's public key - pub fn new, R>( + pub fn new, R, S>( mut seed: Vec, block_schedule: Vec, nonce: u64, @@ -132,7 +139,8 @@ impl MurmurStore

{ rng: &mut R, ) -> Result where - R: Rng + CryptoRng + SeedableRng + Sized, + R: Rng + CryptoRng + Sized, + S: Rng + CryptoRng + SeedableRng + Sized, { let mut transcript = Transcript::new_labeled(MURMUR_PROTO); transcript.write_bytes(&seed); @@ -172,8 +180,6 @@ impl MurmurStore

{ seed.zeroize(); - // let t: [u8;32] = transcript_otp.challenge(b"ok").read_byte_array(); - // panic!("{:?}", t); for &i in &block_schedule { let mut otp_code = totp.generate(i as u64); let identity = I::build_identity(i); @@ -184,13 +190,9 @@ impl MurmurStore

{ .challenge(b"ephemeral_msk") .read_byte_array(); - // if i == 10 { - // panic!("{:?}", ephemeral_msk); - // } + let ephem_rng = S::from_seed(ephemeral_msk); - let ephem_rng = R::from_seed(ephemeral_msk); - - let ct_bytes = timelock_encrypt::( + let ct_bytes = timelock_encrypt::( identity, round_public_key.1, ephemeral_msk, @@ -232,13 +234,8 @@ impl MurmurStore

{ call_data: Vec, ) -> Result<(MerkleProof, Vec, Ciphertext, u64), Error> { if let Some(ciphertext) = self.metadata.get(&when) { - - let commitment = self.commit( - seed.clone(), - ciphertext.clone(), - &call_data.clone(), - when, - )?; + let commitment = + self.commit(seed.clone(), ciphertext.clone(), &call_data.clone(), when)?; seed.zeroize(); @@ -276,7 +273,7 @@ impl MurmurStore

{ .read_byte_array(); seed.zeroize(); - let mut otp_code = aes_decrypt::(ciphertext, ephemeral_msk.as_slice().to_vec())?; + let mut otp_code = aes_decrypt::(ciphertext, ephemeral_msk)?; ephemeral_msk.zeroize(); let mut hasher = sha3::Sha3_256::default(); @@ -301,7 +298,41 @@ impl MurmurStore

{ } } -/// A helper function to perform timelock encryption +#[cfg(feature = "client")] +impl MurmurStore

{ + // Serialize MurmurStore without the PhantomData + pub fn encode(&self) -> Vec { + (self.nonce, &self.metadata, &self.root, &self.proof, &self.public_key, &self.protocol_id) + .encode() + } + + pub fn decode(data: Vec) -> Result, Error> { + // Decode fields from `data` without `_phantom` + let (nonce, metadata, root, proof, public_key, protocol_id): ( + u64, + BTreeMap, + Leaf, + Vec, + Vec, + ProtocolId, + ) = Decode::decode(&mut &data[..]).unwrap(); + + match protocol_id { + ProtocolId::TinyBLS377 => Ok(MurmurStore { + nonce, + metadata, + root, + proof, + public_key, + protocol_id, + _phantom: PhantomData, + }), + } + } +} + +/// A helper function to perform timelock encryption. +/// NOTE: this function is opionated to use AES_GCM /// /// * `identity`: The identity to encrypt for /// * `pk`: The public key of the randomness beacon @@ -317,7 +348,8 @@ fn timelock_encrypt( rng: R, ) -> Result, Error> { let ciphertext = - tle::(pk, ephemeral_msk, message, identity, rng).map_err(|_| Error::TlockFailed)?; + tle::(pk, ephemeral_msk, message, identity, rng) + .map_err(|_| Error::TlockFailed)?; let mut ct_bytes = Vec::new(); ciphertext @@ -328,19 +360,28 @@ fn timelock_encrypt( } /// Use a secret key to decrypt the ciphertext before the beacon outputs -/// This uses AES_GCM decryption. It returns the plaintext if decryption is successful, else an error +/// This uses AES_GCM decryption. It returns the plaintext if decryption is successful, else an +/// error /// /// * `ciphertext_bytes`: The ciphertext to decrypt /// * `secret`: A decryption key (32 bytes) -/// #[cfg(feature = "client")] -fn aes_decrypt(ciphertext_bytes: Vec, secret: Vec) -> Result, Error> { +fn aes_decrypt( + ciphertext_bytes: Vec, + secret: [u8; 32], +) -> Result, Error> { + // TODO handle errors let ciphertext: TLECiphertext = - TLECiphertext::deserialize_compressed(&mut &ciphertext_bytes[..]).unwrap(); + TLECiphertext::deserialize_compressed(&mut &ciphertext_bytes[..]) + .map_err(|_| Error::CiphertextDeserializationFailed)?; + + let aes_ct = + AESOutput::deserialize_compressed(&mut &ciphertext.message_ciphertext[..]).unwrap(); - let plaintext = ciphertext.aes_decrypt(secret).map_err(|_| Error::AesDecryptFailed)?; + let plaintext = + AESGCMStreamCipherProvider::decrypt(aes_ct, secret).map_err(|_| Error::AesDecryptFailed)?; - Ok(plaintext.message) + Ok(plaintext) } /// Functions for verifying execution and update requests @@ -358,11 +399,11 @@ pub mod verifier { UnserializablePubkey, } - /// Verify the correctness of execution parameters by checking that the Merkle proof, `Proof`, and hash `H` - /// are valid. The function outputs true if both conditions are true: + /// Verify the correctness of execution parameters by checking that the Merkle proof, `Proof`, + /// and hash `H` are valid. The function outputs true if both conditions are true: /// - /// 1. Proof.Verify(root, [(pos, Leaf(ciphertext))]) - /// 2. H == Sha256(otp || aux_data) + /// 1. Proof.Verify(root, [(pos, Leaf(ciphertext))]) + /// 2. H == Sha256(otp || aux_data) //// /// It outputs false otherwise. /// @@ -373,7 +414,6 @@ pub mod verifier { /// * `otp`: The OTP /// * `aux_data`: The expected aux data used to generate the commitment /// * `pos`: The position of the Ciphertext within the MMR - /// pub fn verify_execute( root: Leaf, proof: MerkleProof, @@ -404,7 +444,6 @@ pub mod verifier { /// * `serialized_proof`: The serialized proof /// * `serialized_pubkey`: The serialized public key /// * `nonce`: A nonce value - /// pub fn verify_update( serialized_proof: Vec, serialized_pubkey: Vec, @@ -463,13 +502,11 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); assert!(murmur_store.metadata.keys().len() == BLOCK_SCHEDULE.len()); @@ -489,22 +526,18 @@ mod tests { let seed = vec![1, 2, 3]; let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); let root = murmur_store.root.clone(); let (proof, commitment, ciphertext, pos) = murmur_store.execute(seed.clone(), WHEN, aux_data.clone()).unwrap(); - assert!( - verifier::verify_execute(root, proof, commitment, ciphertext, OTP, &aux_data, pos) - ); + assert!(verifier::verify_execute(root, proof, commitment, ciphertext, OTP, &aux_data, pos)); } #[cfg(feature = "client")] @@ -517,13 +550,11 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); let aux_data = vec![2, 3, 4, 5]; @@ -545,13 +576,11 @@ mod tests { let seed = vec![1, 2, 3]; let aux_data = vec![2, 3, 4, 5]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); let root = murmur_store.root.clone(); @@ -580,17 +609,15 @@ mod tests { let seed = vec![1, 2, 3]; let other_seed = vec![2, 3, 4]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); let other_murmur_store = - MurmurStore::::new::( + MurmurStore::::new::( other_seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, @@ -603,9 +630,8 @@ mod tests { // the block number when this would execute let root = murmur_store.root.clone(); - let (proof, commitment, ciphertext, pos) = other_murmur_store - .execute(other_seed.clone(), WHEN, aux_data.clone()) - .unwrap(); + let (proof, commitment, ciphertext, pos) = + other_murmur_store.execute(other_seed.clone(), WHEN, aux_data.clone()).unwrap(); assert!(!verifier::verify_execute( root, proof, commitment, ciphertext, OTP, &aux_data, pos, @@ -626,13 +652,11 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); let proof = murmur_store.proof; @@ -642,7 +666,7 @@ mod tests { let mut not_same_rng = ChaCha20Rng::seed_from_u64(1); let another_murmur_store = - MurmurStore::::new::( + MurmurStore::::new::( seed.clone(), BLOCK_SCHEDULE.to_vec(), 1, @@ -668,13 +692,11 @@ mod tests { let seed = vec![1, 2, 3]; - let murmur_store = MurmurStore::::new::( - seed.clone(), - BLOCK_SCHEDULE.to_vec(), - 0, - double_public, - &mut rng, - ) + let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + ChaCha20Rng, + ChaCha20Rng, + >(seed.clone(), BLOCK_SCHEDULE.to_vec(), 0, double_public, &mut rng) .unwrap(); let proof = murmur_store.proof; diff --git a/core/src/types.rs b/core/src/types.rs index 4145723..abdb059 100644 --- a/core/src/types.rs +++ b/core/src/types.rs @@ -19,7 +19,7 @@ use ckb_merkle_mountain_range::{Merge, Result as MMRResult}; use codec::{Decode, Encode}; use sha3::Digest; -pub use etf_crypto_primitives::ibe::fullident::Identity; +pub use tle::ibe::fullident::Identity; /// The type to represent a block number pub type BlockNumber = u32; diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index 3e6d5fa..877ac58 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -15,10 +15,11 @@ */ use clap::{Parser, Subcommand}; -use murmur_lib::{create, etf, prepare_execute, BlockNumber, BoundedVec, MurmurStore, RuntimeCall}; +use murmur_lib::{ + create, etf, prepare_execute, BlockNumber, BoundedVec, EngineTinyBLS377, MurmurStore, + RuntimeCall, +}; -use rand_chacha::ChaCha20Rng; -use rand_core::{OsRng, SeedableRng}; use sp_core::crypto::Ss58Codec; use std::{fs::File, time::Instant}; use subxt::{backend::rpc::RpcClient, client::OnlineClient, config::SubstrateConfig}; @@ -102,16 +103,13 @@ async fn main() -> Result<(), Box> { let before = Instant::now(); let (client, current_block_number, round_pubkey_bytes) = idn_connect().await?; - let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); match &cli.commands { - Commands::New(args) => { - handle_create(args, client, current_block_number, round_pubkey_bytes, rng).await? - }, - Commands::Update(args) => { - handle_update(args, client, current_block_number, round_pubkey_bytes, rng).await? - }, - Commands::Execute(args) => handle_execute(args, client, current_block_number, rng).await?, + Commands::New(args) => + handle_create(args, client, current_block_number, round_pubkey_bytes).await?, + Commands::Update(args) => + handle_update(args, client, current_block_number, round_pubkey_bytes).await?, + Commands::Execute(args) => handle_execute(args, client, current_block_number).await?, } println!("Elapsed time: {:.2?}", before.elapsed()); @@ -124,9 +122,19 @@ async fn handle_create( client: OnlineClient, current_block_number: BlockNumber, round_pubkey_bytes: Vec, - rng: ChaCha20Rng, ) -> Result<(), Box> { - let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, rng)?; + let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, 0)?; + + // sanity check + let b: bool = murmur_core::murmur::verifier::verify_update::( + mmr_store.proof.clone(), + mmr_store.public_key.clone(), + 0, + ).unwrap(); + assert!(b == true); + + println!("the proof looks good! ok ... proof bytes {:?}, public kye bytes {:?}", mmr_store.proof.clone(), mmr_store.public_key.clone()); + let call = etf::tx().murmur().create( BoundedVec(args.name.as_bytes().to_vec()), BoundedVec(mmr_store.root.0), @@ -138,7 +146,6 @@ async fn handle_create( client.tx().sign_and_submit_then_watch_default(&call, &dev::alice()).await?; println!("✅ Murmur Proxy Creation: Successful!"); - Ok(()) } @@ -148,9 +155,13 @@ async fn handle_update( client: OnlineClient, current_block_number: BlockNumber, round_pubkey_bytes: Vec, - rng: ChaCha20Rng, ) -> Result<(), Box> { - let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, rng)?; + // existing mmr_store, we only need this to get the nonce + // note that there are many other ways to get this as well (e.g. query the runtime) + let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH)?; + + let mmr_store = + build_mmr_store(args, current_block_number, round_pubkey_bytes, store.nonce + 1)?; let call = etf::tx().murmur().update( BoundedVec(args.name.as_bytes().to_vec()), @@ -166,41 +177,38 @@ async fn handle_update( Ok(()) } -/// Build a new Murmur store for each block from `current_block_number + 2` to `current_block_number + args.validity` +/// Build a new Murmur store for each block from `current_block_number + 2` to `current_block_number +/// + args.validity` fn build_mmr_store( args: &WalletCreationDetails, current_block_number: BlockNumber, round_pubkey_bytes: Vec, - mut rng: ChaCha20Rng, -) -> Result> { + nonce: u64, +) -> Result, Box> { 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 + // the wallet is 'active' 2 blocks from the current block let next_block_number: BlockNumber = current_block_number + i; schedule.push(next_block_number); } - // 2. create mmr - let mmr_store = - create(args.seed.as_bytes().to_vec(), 0, schedule, round_pubkey_bytes, &mut rng) + let mmr_store: MurmurStore = + create(args.seed.as_bytes().to_vec(), nonce, schedule, round_pubkey_bytes) .map_err(|_| CLIError::MurmurCreationFailed)?; - // 3. add to storage - write_mmr_store(mmr_store.clone(), MMR_STORE_FILEPATH); + write_mmr_store(mmr_store.encode(), MMR_STORE_FILEPATH); Ok(mmr_store) } -/// This function executes a call from the Murmur proxy at block after the provided `current_block_height` -/// note that this does not guarantee execution. If the transaction is not included in a block in the upcoming block -/// then it will never be executed +/// This function executes a call from the Murmur proxy at block after the provided +/// `current_block_height` note that this does not guarantee execution. If the transaction is not +/// included in a block in the upcoming block then it will never be executed async fn handle_execute( args: &WalletExecuteDetails, client: OnlineClient, current_block_number: BlockNumber, - mut rng: ChaCha20Rng, ) -> Result<(), Box> { // 1. build proxied call let from_ss58 = sp_core::crypto::AccountId32::from_ss58check(&args.to) @@ -214,7 +222,7 @@ async fn handle_execute( }); // 2. load the MMR store - let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH)?; + let store: MurmurStore = load_mmr_store(MMR_STORE_FILEPATH)?; println!("💾 Recovered Murmur store from local file"); // 3. get the proxy data @@ -223,7 +231,6 @@ async fn handle_execute( current_block_number + 1, store, &balance_transfer_call, - &mut rng, ) .map_err(|_| CLIError::MurmurExecutionFailed)?; @@ -272,15 +279,17 @@ async fn idn_connect( } /// read an MMR from a file -fn load_mmr_store(path: &str) -> Result { +fn load_mmr_store(path: &str) -> Result, CLIError> { let mmr_store_file = File::open(path).expect("Unable to open file"); - let data: MurmurStore = + let data: Vec = serde_cbor::from_reader(mmr_store_file).map_err(|_| CLIError::CorruptedMurmurStore)?; - Ok(data) + // TODO: create new error type + let mmr_store = MurmurStore::::decode(data).unwrap(); + Ok(mmr_store) } /// Write the MMR data to a file -fn write_mmr_store(mmr_store: MurmurStore, path: &str) { +fn write_mmr_store(mmr_store_data: Vec, 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(); + serde_cbor::to_writer(mmr_store_file, &mmr_store_data).unwrap(); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 000e5c3..55df634 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -16,6 +16,7 @@ use beefy::{known_payloads, Commitment, Payload}; use murmur_core::types::{Identity, IdentityBuilder}; +use rand_core::OsRng; use serde::Serialize; use w3f_bls::{DoublePublicKey, SerializableToBytes, TinyBLS377}; use zeroize::Zeroize; @@ -24,7 +25,7 @@ pub use etf::runtime_types::{ bounded_collections::bounded_vec::BoundedVec, node_template_runtime::RuntimeCall, }; pub use murmur_core::{ - murmur::{Error, MurmurStore, EngineTinyBLS377}, + murmur::{EngineTinyBLS377, Error, MurmurStore}, types::BlockNumber, }; use rand_chacha::ChaCha20Rng; @@ -46,7 +47,7 @@ impl IdentityBuilder for BasicIdBuilder { 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()) + Identity::new(b"", vec![commitment.encode()]) } } @@ -74,17 +75,16 @@ pub fn create( nonce: u64, block_schedule: Vec, round_pubkey_bytes: Vec, - rng: &mut ChaCha20Rng, -) -> Result { +) -> Result, Error> { let round_pubkey = DoublePublicKey::::from_bytes(&round_pubkey_bytes) .map_err(|_| Error::InvalidPubkey)?; - let mmr_store = MurmurStore::::new::( + let mmr_store = MurmurStore::::new::( seed.clone(), block_schedule.clone(), nonce, round_pubkey, - rng, + &mut OsRng, )?; seed.zeroize(); Ok(mmr_store) @@ -106,12 +106,10 @@ pub fn create( pub fn prepare_execute( mut seed: Vec, when: BlockNumber, - store: MurmurStore, + store: MurmurStore, call: &RuntimeCall, - rng: &mut ChaCha20Rng, ) -> Result { - let (proof, commitment, ciphertext, pos) = - store.execute(seed.clone(), when, call.encode(), rng)?; + let (proof, commitment, ciphertext, pos) = store.execute(seed.clone(), when, call.encode())?; seed.zeroize(); let size = proof.mmr_size(); let proof_items: Vec> = @@ -181,14 +179,8 @@ mod tests { let when = 1; - let proxy_data = prepare_execute( - seed.clone(), - when, - mmr_store.clone(), - &balance_transfer_call, - &mut rng, - ) - .unwrap(); + let proxy_data = + prepare_execute(seed.clone(), when, mmr_store.clone(), &balance_transfer_call).unwrap(); // let (proof, commitment, ciphertext, _pos) = create_data.mmr_store // .execute(seed.clone(), when, balance_transfer_call_2.encode(), &mut rng) diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 76b7cc5..089d60a 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -19,17 +19,13 @@ //! various utilities helpful for testing use alloc::vec::Vec; -use ark_ec::CurveGroup; use ark_serialize::CanonicalSerialize; -use dleq_vrf::SecretKey; use rand_core::OsRng; use w3f_bls::{DoublePublicKey, DoublePublicKeyScheme, TinyBLS377}; extern crate alloc; -pub use murmur_core::otp::BOTPGenerator; - -pub use murmur_core::murmur::MurmurStore; +pub use murmur_core::{murmur::MurmurStore, otp::BOTPGenerator}; pub fn get_dummy_beacon_pubkey() -> Vec { let keypair = w3f_bls::KeypairVT::::generate(&mut OsRng); From 0f57a85bfe9c6dd7035e5bdc323db52931e70cff Mon Sep 17 00:00:00 2001 From: driemworks Date: Sun, 17 Nov 2024 20:14:38 -0600 Subject: [PATCH 16/19] chore: run fmt, address review feedback, remove commented out code --- core/src/murmur.rs | 7 +++--- lib/src/bin/murmur/main.rs | 36 ++++++++++++---------------- lib/src/lib.rs | 49 ++++---------------------------------- 3 files changed, 24 insertions(+), 68 deletions(-) diff --git a/core/src/murmur.rs b/core/src/murmur.rs index d34c4e4..97cecc0 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -80,6 +80,8 @@ pub enum Error { InvalidPubkey, /// The ciphertext could not be deserialized to a TLECiphertext CiphertextDeserializationFailed, + /// The input could not be deserialized, is it right sized? + DeserializationFailure, } pub trait ProtocolEngine { @@ -370,13 +372,12 @@ fn aes_decrypt( ciphertext_bytes: Vec, secret: [u8; 32], ) -> Result, Error> { - // TODO handle errors let ciphertext: TLECiphertext = TLECiphertext::deserialize_compressed(&mut &ciphertext_bytes[..]) .map_err(|_| Error::CiphertextDeserializationFailed)?; - let aes_ct = - AESOutput::deserialize_compressed(&mut &ciphertext.message_ciphertext[..]).unwrap(); + let aes_ct = AESOutput::deserialize_compressed(&mut &ciphertext.body[..]) + .map_err(|_| Error::DeserializationFailure)?; let plaintext = AESGCMStreamCipherProvider::decrypt(aes_ct, secret).map_err(|_| Error::AesDecryptFailed)?; diff --git a/lib/src/bin/murmur/main.rs b/lib/src/bin/murmur/main.rs index 877ac58..0f0d4fd 100644 --- a/lib/src/bin/murmur/main.rs +++ b/lib/src/bin/murmur/main.rs @@ -35,7 +35,7 @@ struct Cli { commands: Commands, } -/// Commands available to user +/// Commands available to the user #[derive(Subcommand)] enum Commands { /// create a new Murmur wallet @@ -72,11 +72,12 @@ struct WalletExecuteDetails { /// The recipient (ss58 encoded) #[arg(long, short)] to: String, - /// The amount to send TODO formatting + /// The balance to send #[arg(short, long, value_parser = clap::value_parser!(u128))] amount: u128, } +/// Errors that can be thrown by this crate #[derive(Error, Debug)] pub enum CLIError { #[error("invalid public key")] @@ -93,8 +94,7 @@ pub enum CLIError { CorruptedMurmurStore, } -/// the mmr_store file location -/// in future, make configurable +/// The default mmr_store file location pub const MMR_STORE_FILEPATH: &str = "mmr_store"; #[tokio::main] @@ -112,7 +112,8 @@ async fn main() -> Result<(), Box> { Commands::Execute(args) => handle_execute(args, client, current_block_number).await?, } - println!("Elapsed time: {:.2?}", before.elapsed()); + println!("Done! Time elapsed: {:.2?}", before.elapsed()); + Ok(()) } @@ -125,16 +126,6 @@ async fn handle_create( ) -> Result<(), Box> { let mmr_store = build_mmr_store(args, current_block_number, round_pubkey_bytes, 0)?; - // sanity check - let b: bool = murmur_core::murmur::verifier::verify_update::( - mmr_store.proof.clone(), - mmr_store.public_key.clone(), - 0, - ).unwrap(); - assert!(b == true); - - println!("the proof looks good! ok ... proof bytes {:?}, public kye bytes {:?}", mmr_store.proof.clone(), mmr_store.public_key.clone()); - let call = etf::tx().murmur().create( BoundedVec(args.name.as_bytes().to_vec()), BoundedVec(mmr_store.root.0), @@ -177,8 +168,8 @@ async fn handle_update( Ok(()) } -/// Build a new Murmur store for each block from `current_block_number + 2` to `current_block_number -/// + args.validity` +/// Build a new Murmur store for each block from `current_block_number + 2` to +/// `current_block_number + args.validity` fn build_mmr_store( args: &WalletCreationDetails, current_block_number: BlockNumber, @@ -254,7 +245,7 @@ async fn handle_execute( /// else error if unreachable async fn idn_connect( ) -> Result<(OnlineClient, BlockNumber, Vec), Box> { - println!("🎲 Connecting to Ideal network (local node)"); + println!("🎲 Connecting to Ideal Network"); let ws_url = std::env::var("WS_URL").unwrap_or_else(|_| { let fallback_url = "ws://localhost:9944".to_string(); println!("⚠️ WS_URL environment variable not set. Using fallback URL: {}", fallback_url); @@ -279,17 +270,20 @@ async fn idn_connect( } /// read an MMR from a file +/// * `path`: The mmr_store path fn load_mmr_store(path: &str) -> Result, CLIError> { let mmr_store_file = File::open(path).expect("Unable to open file"); let data: Vec = serde_cbor::from_reader(mmr_store_file).map_err(|_| CLIError::CorruptedMurmurStore)?; - // TODO: create new error type - let mmr_store = MurmurStore::::decode(data).unwrap(); + let mmr_store = MurmurStore::::decode(data).expect("The data is corrupted."); Ok(mmr_store) } /// Write the MMR data to a file +/// * `mmr_store_data`: The serialized mmr store +/// * `path`: The file path to write to fn write_mmr_store(mmr_store_data: Vec, path: &str) { let mmr_store_file = File::create(path).expect("It should create the file"); - serde_cbor::to_writer(mmr_store_file, &mmr_store_data).unwrap(); + serde_cbor::to_writer(mmr_store_file, &mmr_store_data) + .expect("It failed to write to the local filesystem."); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 55df634..2d15679 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -44,8 +44,11 @@ impl IdentityBuilder for BasicIdBuilder { 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. */ + // Note: Currently the validator set id is always set to 0 by the IDN runtime. + // We have a backlog item to properly update this, which will require + // that we properly estimate future validator set ids here + // see: https://github.com/ideal-lab5/pallets/issues/29 + validator_set_id: 0, }; Identity::new(b"", vec![commitment.encode()]) } @@ -95,14 +98,6 @@ pub fn create( /// * `when`: The block number when OTP codeds should be generated /// * `store`: A murmur store /// * `call`: Proxied call. Any valid runtime call -// 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 fn prepare_execute( mut seed: Vec, when: BlockNumber, @@ -120,7 +115,6 @@ pub fn prepare_execute( #[cfg(test)] mod tests { - // use super::*; use super::*; use rand_core::{OsRng, SeedableRng}; @@ -134,14 +128,6 @@ mod tests { create(seed.clone(), 0, block_schedule.clone(), double_public_bytes.clone(), &mut rng) .unwrap(); - // let mmr_store = MurmurStore::new::( - // seed, - // block_schedule, - // 0, - // DoublePublicKey::::from_bytes(&double_public_bytes).unwrap(), - // &mut rng, - // ).unwrap(); - assert_eq!(mmr_store.root.0.len(), 32); assert_eq!(mmr_store.metadata.keys().len(), 7); } @@ -155,10 +141,6 @@ mod tests { let mmr_store = create(seed.clone(), 0, block_schedule, double_public_bytes, &mut rng).unwrap(); - // let size = proof.mmr_size(); - // let proof_items: Vec> = - // proof.proof_items().iter().map(|leaf| leaf.0.clone()).collect::>(); - let bob = subxt_signer::sr25519::dev::bob().public_key(); let balance_transfer_call = etf::runtime_types::node_template_runtime::RuntimeCall::Balances( @@ -168,34 +150,13 @@ mod tests { }, ); - // let bob2 = subxt_signer::sr25519::dev::bob().public_key(); - // let balance_transfer_call_2 = - // etf::runtime_types::node_template_runtime::RuntimeCall::Balances( - // etf::balances::Call::transfer_allow_death { - // dest: subxt::utils::MultiAddress::<_, u32>::from(bob2), - // value: 1, - // }, - // ); - let when = 1; let proxy_data = prepare_execute(seed.clone(), when, mmr_store.clone(), &balance_transfer_call).unwrap(); - // let (proof, commitment, ciphertext, _pos) = create_data.mmr_store - // .execute(seed.clone(), when, balance_transfer_call_2.encode(), &mut rng) - // .unwrap(); - // let expected_commitment = [71, 71, 72, 200, 197, 44, 120, 151, 127, 6, 162, 244, 138, - // 122, 196, 183, 30, 47, 111, 239, 225, 32, 57, 141, 186, 229, 164, 113, 113, 44, 131, - // 168]; let expected_ciphertext = [76, 42, 82, 184, 114, 58, 31, 205, 146, 16, 41, 191, - // 126, 213, 18, 65, 42, 149, 78, 140, 243, 164, 39, 54, 13, 96, 159, 93, 200, 83, 227, - // 179]; let size = proof.mmr_size(); - // let proof_items: Vec> = - // proof.proof_items().iter().map(|leaf| leaf.0.clone()).collect::>(); assert_eq!(proxy_data.position, 0); assert_eq!(proxy_data.hash.len(), 32); assert_eq!(proxy_data.ciphertext.len(), 266); - // assert_eq!(proxy_data.proof_items, proof_items); - // assert_eq!(proxy_data.size, size); } } From 48e18ba4db398802b522ade90c3adde2fc832cfd Mon Sep 17 00:00:00 2001 From: driemworks Date: Thu, 21 Nov 2024 10:11:32 -0600 Subject: [PATCH 17/19] feat: upgrade to latest timelock library, add more docs, cleanup --- Cargo.lock | 42 +++++++++++++------------- core/Cargo.toml | 8 ++--- core/README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++-- core/src/murmur.rs | 31 +++++++++++++------ core/src/types.rs | 2 +- lib/README.md | 2 +- lib/src/lib.rs | 14 +++------ 7 files changed, 125 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9ceef2..cf9d5a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2533,7 +2533,7 @@ dependencies = [ "rand_core", "serde", "sha3", - "tle", + "timelock", "totp-rs", "w3f-bls", "zeroize", @@ -4880,24 +4880,9 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tle" -version = "0.1.0" -source = "git+https://github.com/ideal-lab5/tle.git?branch=feat/tle-monorepo#1a50d569cdda608df56d4951b7141e43cc067cef" +name = "timelock" +version = "0.0.1" +source = "git+https://github.com/ideal-lab5/tle.git#6420b2231f0fb22d05fb0783d74d8394330d0ae1" dependencies = [ "aes-gcm", "ark-bls12-377", @@ -4922,6 +4907,21 @@ dependencies = [ "w3f-bls", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.40.0" @@ -5274,9 +5274,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "w3f-bls" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" +checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" dependencies = [ "ark-bls12-377", "ark-bls12-381", diff --git a/core/Cargo.toml b/core/Cargo.toml index 8e43997..e57271c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,14 +21,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] totp-rs = { version = "5.5.1", default-features = false, optional = true } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"], default-features = false } -tle = { package = "tle", default-features = false, git = "https://github.com/ideal-lab5/tle.git", branch = "feat/tle-monorepo"} +timelock = { git = "https://github.com/ideal-lab5/tle.git", default-features = false } ckb-merkle-mountain-range = { version = "0.5.2", default-features = false } sha3 = { version = "0.10.8", default-features = false } serde = { version = "1.0.188", features = ["alloc", "derive"], default-features = false } ark-bls12-377 = { version = "0.4.0", default-features = false } ark-std = { version = "0.4.0", default-features = false } ark-serialize = { version = "0.4.0", default-features = false } -w3f-bls = { version = "=0.1.3", default-features = false } +w3f-bls = { version = "0.1.4", default-features = false } zeroize = { version = "1.8.1", default-features = false } ark-ec = { version = "0.4", default-features = false } ark-ff = { version = "0.4", default-features = false } @@ -51,10 +51,10 @@ std = [ "dleq_vrf/std", "w3f-bls/std", "serde/std", + "ckb-merkle-mountain-range/std", "codec/std", - "tle/std", + "timelock/std", "sha3/std", - "ckb-merkle-mountain-range/std", "zeroize/std", ] no_std = [] diff --git a/core/README.md b/core/README.md index b058f88..3a2373e 100644 --- a/core/README.md +++ b/core/README.md @@ -1,6 +1,77 @@ # 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. +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. This crate can support the randomness beacon produced by the [Ideal Network](https://idealabs.network) as well as [Drand](https://drand.love)'s Quicknet. In general, this library is intended to work with a blockchain whose runtime includes the corresponding [Murmur Pallet](https://github.com/ideal-lab5/idn-sdk/tree/main/pallets/murmur). More specifcially, it is intended to run against the [Ideal Network](https://idealabs.network). For examples of usage against a real network, refer to the [CLI](../lib/src/bin/murmur/main.rs). + +## Usage + +### Creation and Execution + +#### Create a Murmur Store + +``` rust +use ark_serialize::CanonicalDeserialize; +use ark_std::rand::SeedableRng; +use rand_chacha::ChaCha20Rng; +use rand_core::OsRng; +use w3f_bls::{DoublePublicKeyScheme, KeypairVT, TinyBLS377}; + +// This simulates the production of a randomness beacon public key +// In practice, this would be fetched from the beacon (e.g. as a hex string) and must be deseraialized +let keypair = KeypairVT::::generate(&mut rng); +let double_public: DoublePublicKey = + DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); + +// The 'lifetime' of the Murmur wallet for the given session +let block_schedule: &[BlockNumber] = + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + +// This is your 'secret' seed, a short password used while constructing OTP codes +let seed = vec![1, 2, 3]; + +// The nonce functions similarly to nonce's for standard accounts, except instead of updating on a +// "per transaction" basis, it only updates on a "per session" basis +// +let nonce = 0; + +let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + OsRng, + ChaCha20Rng, +>(seed.clone(), block_schedule.to_vec(), nonce, double_public, &mut rng) +.unwrap(); +``` +#### Update a Murmur Store + +Updating a Murmur store is done by calling the same new function as above and using the 'next' nonce in the + +``` rust +// update the nonce +let nonce = 1; + +let murmur_store = MurmurStore::::new::< + DummyIdBuilder, + OsRng, + ChaCha20Rng, +>(seed.clone(), block_schedule.to_vec(), nonce, double_public, &mut rng) +.unwrap(); +``` + +#### Prepare Execution Parameters + +``` rust +``` + +### Verification + +#### Verify Updates + +``` rust +``` + +#### Verify Execution Parameters + +``` rust +``` ## Build @@ -33,8 +104,6 @@ cargo test --features "client" ## Future Work/Notes - **OTPAuth Feature**: 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. [TOTP Library Reference](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. - ## Contributing Contributions are welcome! Please open an issue or submit a pull request. diff --git a/core/src/murmur.rs b/core/src/murmur.rs index 97cecc0..4069111 100644 --- a/core/src/murmur.rs +++ b/core/src/murmur.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -//! The murmur protocol implementation +//! The Murmur protocol implementation use alloc::{collections::BTreeMap, vec, vec::Vec}; #[cfg(feature = "client")] @@ -40,12 +40,12 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use dleq_vrf::{EcVrfVerifier, PublicKey, SecretKey}; use sha3::Digest; -use tle::{ +use timelock::{ ibe::fullident::Identity, stream_ciphers::{AESGCMStreamCipherProvider, AESOutput, StreamCipherProvider}, tlock::*, }; -use w3f_bls::{DoublePublicKey, EngineBLS}; +use w3f_bls::DoublePublicKey; /// The base Murmur protocol label pub const MURMUR_PROTO: &[u8] = b"Murmur://"; @@ -63,6 +63,7 @@ pub enum Error { ExecuteError, /// An error occurred when creating a murmur wallet MMRError, + /// Some data in the murmur store is corrupted InconsistentStore, /// No leaf could be identified in the MMR at the specified position NoLeafFound, @@ -92,7 +93,8 @@ pub trait ProtocolEngine { /// The supported protocols #[derive(Clone, serde::Serialize, serde::Deserialize, Encode, Decode)] pub enum ProtocolId { - /// small signatures, SignatureGroup = G1 (48 bytes), PublicKeyGroup = G2 (96 bytes) + /// A curve config with small signatures and large pubkyes + /// SignatureGroup = G1 (48 bytes), PublicKeyGroup = G2 (96 bytes) TinyBLS377, } @@ -127,12 +129,22 @@ pub struct MurmurStore { #[cfg(feature = "client")] impl MurmurStore

{ - /// Create a new Murmur store + /// Create a new Murmur store. + /// + /// This function allows for two separate RNGs to be specified. In general, the first RNG type R + /// should be created externally from this function and passed as an argument. + /// In practice, this should probably be the OsRng or something similar. + /// The second type of RNG, S, must be seedable from [u8;32]. This RNG is instantiated within + /// the function, where we seed a new RNG each time we encrypt a new OTP code using timelock + /// encryption. /// /// * `seed`: An any-length seed (i.e. password) /// * `block_schedule`: The blocks for which OTP codes will be generated - /// * `ephemeral_msk`: Any 32 bytes + /// * `nonce`: A value representing the 'number of times' the Murmur wallet has been created or updated. + /// Should be monotonically increasing with each subsequent call. /// * `round_public_key`: The IDN beacon's public key + /// * `rng`: An instance of an CPRNG of type `R` + pub fn new, R, S>( mut seed: Vec, block_schedule: Vec, @@ -168,6 +180,7 @@ impl MurmurStore

{ let mut witness: [u8; 32] = transcript.clone().witness(rng).read_byte_array(); let totp = BOTPGenerator::new(witness.to_vec()).map_err(|_| Error::InvalidSeed)?; + // drop secret data witness.zeroize(); challenge.zeroize(); secret_key.zeroize(); @@ -403,8 +416,8 @@ pub mod verifier { /// Verify the correctness of execution parameters by checking that the Merkle proof, `Proof`, /// and hash `H` are valid. The function outputs true if both conditions are true: /// - /// 1. Proof.Verify(root, [(pos, Leaf(ciphertext))]) - /// 2. H == Sha256(otp || aux_data) + /// 1. Proof.Verify(root, [(pos, Leaf(ciphertext))]) + /// 2. H == Sha256(otp || aux_data) //// /// It outputs false otherwise. /// @@ -489,7 +502,7 @@ mod tests { pub struct DummyIdBuilder; impl IdentityBuilder for DummyIdBuilder { fn build_identity(at: BlockNumber) -> Identity { - Identity::new(&[at as u8]) + Identity::new(b"", vec![vec![at as u8]]) } } diff --git a/core/src/types.rs b/core/src/types.rs index abdb059..ff76d62 100644 --- a/core/src/types.rs +++ b/core/src/types.rs @@ -19,7 +19,7 @@ use ckb_merkle_mountain_range::{Merge, Result as MMRResult}; use codec::{Decode, Encode}; use sha3::Digest; -pub use tle::ibe::fullident::Identity; +pub use timelock::ibe::fullident::Identity; /// The type to represent a block number pub type BlockNumber = u32; diff --git a/lib/README.md b/lib/README.md index d7312b4..7c53341 100644 --- a/lib/README.md +++ b/lib/README.md @@ -32,7 +32,7 @@ To generate a wallet valid for the next 1000 blocks, use: To send a balance transfer, use: ```shell -./target/debug/murmur execute --name test --seed my_secret_key --to CuqfkE3QieYPAWPpwiygDufmyrKecDcVCF7PN1psaLEn8yr --amount 100_000_000 +./target/debug/murmur execute --name test --seed my_secret_key --to CuqfkE3QieYPAWPpwiygDufmyrKecDcVCF7PN1psaLEn8yr --amount 100000000 ``` ## Test diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 2d15679..87fedf2 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -116,17 +116,14 @@ pub fn prepare_execute( #[cfg(test)] mod tests { use super::*; - use rand_core::{OsRng, SeedableRng}; #[test] pub fn it_can_create_an_mmr_store_and_call_data() { let seed = b"seed".to_vec(); let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); - let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); let mmr_store = - create(seed.clone(), 0, block_schedule.clone(), double_public_bytes.clone(), &mut rng) - .unwrap(); + create(seed.clone(), 0, block_schedule.clone(), double_public_bytes.clone()).unwrap(); assert_eq!(mmr_store.root.0.len(), 32); assert_eq!(mmr_store.metadata.keys().len(), 7); @@ -137,9 +134,7 @@ mod tests { let seed = b"seed".to_vec(); let block_schedule = vec![1, 2, 3, 4, 5, 6, 7]; let double_public_bytes = murmur_test_utils::get_dummy_beacon_pubkey(); - let mut rng = ChaCha20Rng::from_rng(&mut OsRng).unwrap(); - let mmr_store = - create(seed.clone(), 0, block_schedule, double_public_bytes, &mut rng).unwrap(); + let mmr_store = create(seed.clone(), 0, block_schedule, double_public_bytes).unwrap(); let bob = subxt_signer::sr25519::dev::bob().public_key(); let balance_transfer_call = @@ -152,11 +147,10 @@ mod tests { let when = 1; - let proxy_data = - prepare_execute(seed.clone(), when, mmr_store.clone(), &balance_transfer_call).unwrap(); + let proxy_data = prepare_execute(seed, when, mmr_store, &balance_transfer_call).unwrap(); assert_eq!(proxy_data.position, 0); assert_eq!(proxy_data.hash.len(), 32); - assert_eq!(proxy_data.ciphertext.len(), 266); + assert_eq!(proxy_data.ciphertext.len(), 250); } } From 527ae4207ad713cb113c65100bf4ef9edea57c5a Mon Sep 17 00:00:00 2001 From: driemworks Date: Thu, 21 Nov 2024 14:09:43 -0600 Subject: [PATCH 18/19] chore: update readme --- core/README.md | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/core/README.md b/core/README.md index 3a2373e..4153421 100644 --- a/core/README.md +++ b/core/README.md @@ -1,10 +1,12 @@ # 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. This crate can support the randomness beacon produced by the [Ideal Network](https://idealabs.network) as well as [Drand](https://drand.love)'s Quicknet. In general, this library is intended to work with a blockchain whose runtime includes the corresponding [Murmur Pallet](https://github.com/ideal-lab5/idn-sdk/tree/main/pallets/murmur). More specifcially, it is intended to run against the [Ideal Network](https://idealabs.network). For examples of usage against a real network, refer to the [CLI](../lib/src/bin/murmur/main.rs). +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. This crate can support the randomness beacon produced by the [Ideal Network](https://idealabs.network) as well as [Drand](https://drand.love)'s Quicknet. In general, this library is intended to work with a blockchain whose runtime includes the corresponding [Murmur Pallet](https://github.com/ideal-lab5/idn-sdk/tree/main/pallets/murmur). More specifcially, it is intended to run against the [Ideal Network](https://idealabs.network). For examples of usage against the Ideal Network, refer to the [CLI](../lib/src/bin/murmur/main.rs). ## Usage -### Creation and Execution +### Create, Update, Execute + +To create and update murmur wallets, you must define an `IdentityBuilder`. The [BasicIdentityBuilder](../lib/src/lib.rs) struct implements allows for the construction of valid identities on the Ideal Network. In the future, we will add support for Drand's Quicknet as well. #### Create a Murmur Store @@ -13,6 +15,9 @@ use ark_serialize::CanonicalDeserialize; use ark_std::rand::SeedableRng; use rand_chacha::ChaCha20Rng; use rand_core::OsRng; +use murmur_core::{ + murmur::{EngineTinyBLS377, Error, MurmurStore}, +}; use w3f_bls::{DoublePublicKeyScheme, KeypairVT, TinyBLS377}; // This simulates the production of a randomness beacon public key @@ -22,8 +27,9 @@ let double_public: DoublePublicKey = DoublePublicKey(keypair.into_public_key_in_signature_group().0, keypair.public.0); // The 'lifetime' of the Murmur wallet for the given session -let block_schedule: &[BlockNumber] = - &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; +// This corresponds to future rounds of the randomness beacon +// for which timelocked commitments can be made +let schedule = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; // This is your 'secret' seed, a short password used while constructing OTP codes let seed = vec![1, 2, 3]; @@ -34,22 +40,22 @@ let seed = vec![1, 2, 3]; let nonce = 0; let murmur_store = MurmurStore::::new::< - DummyIdBuilder, + BasicIdentityBuilder, OsRng, ChaCha20Rng, ->(seed.clone(), block_schedule.to_vec(), nonce, double_public, &mut rng) +>(seed.clone(), schedule.to_vec(), nonce, double_public, &mut rng) .unwrap(); ``` #### Update a Murmur Store -Updating a Murmur store is done by calling the same new function as above and using the 'next' nonce in the +Updating a Murmur store is done by calling the same new function as above and incrementing the previous nonce by 1. The data any single MMR can contain is finite, so when a Murmur wallet is created it can only be functional for a finite number of blocks (the block schedule). In this sense, Murmur is a "session-based" wallet. To ensure wallet lifetimes can be extended, Murmur wallets can be updated by generated a new Murmur store and submitting the result to a system that implements a verifier (see below). More specifically, when a Murmur store is created a DLEQ proof is generated (Discrete Log Equivalence Proof - a type of zkp) and attached to the store. This proof allows the Murmur store creator to convince a verifier that it knows the secret input (seed) without exposing it. ``` rust -// update the nonce +// Compute the next nonce let nonce = 1; - +// Construct a new murmur store let murmur_store = MurmurStore::::new::< - DummyIdBuilder, + BasicIdentityBuilder, OsRng, ChaCha20Rng, >(seed.clone(), block_schedule.to_vec(), nonce, double_public, &mut rng) @@ -58,19 +64,40 @@ let murmur_store = MurmurStore::::new::< #### Prepare Execution Parameters +This library allows for arbitary payloads to be strictly associated with the reveal of a future OTP code. That is, given an OTP created for a future round r, it allows for the creation of a commitment to that data that cannot be verified until the future round `r` happens and the beacon outputs a signature allowing for decryption of the OTP code. We can consider this a form of `timelocked` commitments. + ``` rust +// The round when the commitment will be verifiable +let when = 156921; +// Generates a Merkle proof, the hash Sha256(OTP || aux_data), +// the timelocked OTP code ciphertext and its position in the MMR. +// This data is used later on by the verifier to verify the commitment. +let (proof, commitment, ciphertext, pos) = + murmur_store.execute(seed.clone(), when, aux_data.clone()).unwrap(); ``` ### Verification +The verifier module provides functionality to verify Murmur store data and timelocked commitments. In general, this would be executed by whichever actor in the system implementing Murmur has agency to directly manipulate the system or otherwise proxy user input to meaningful actions. This is intended to be run in trustless systems, specifically in the context of a blockchain runtime. + #### Verify Updates +When a Murmur store is updated, a DLEQ proof is generated and attached to the store. This is intended to allow the creator of the Murmur store to prove that they know the secret inputs without revealing them, allowing them to update the Murmur store whenever they need to. The `verify_update` function verifies the DLEQ proof. If it is true, then the prover (Murmur store creator) has convinced the verifier (e.g. Blockchain Runtime) that it was generated with the same seed. + ``` rust +verifier::verify_update::(proof, public_key, nonce).unwrap() ``` #### Verify Execution Parameters +This function allows for "tmelocked" commitments to be verified. The OTP input should be the timelock decrypted ciphertext. More specifically, it: +1) Verified a Merkle proof to prove that the ciphertext is indeed at the given position in the MMR defined by the given root. +2) reconstructs the commitment and compares it against the given one + ``` rust +verifier::verify_execute( + root, proof, commitment, ciphertext, OTP, &aux_data, pos, +); ``` ## Build From 32c12f8c2d2b1982a418d7da5a06d39fa159ff32 Mon Sep 17 00:00:00 2001 From: driemworks Date: Fri, 22 Nov 2024 14:13:21 -0600 Subject: [PATCH 19/19] chore: address review feedback --- core/Cargo.toml | 2 +- core/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index e57271c..6eae973 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -21,7 +21,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] totp-rs = { version = "5.5.1", default-features = false, optional = true } codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"], default-features = false } -timelock = { git = "https://github.com/ideal-lab5/tle.git", default-features = false } +timelock = { git = "https://github.com/ideal-lab5/timelock.git", default-features = false } ckb-merkle-mountain-range = { version = "0.5.2", default-features = false } sha3 = { version = "0.10.8", default-features = false } serde = { version = "1.0.188", features = ["alloc", "derive"], default-features = false } diff --git a/core/README.md b/core/README.md index 4153421..f8e7cad 100644 --- a/core/README.md +++ b/core/README.md @@ -48,7 +48,7 @@ let murmur_store = MurmurStore::::new::< ``` #### Update a Murmur Store -Updating a Murmur store is done by calling the same new function as above and incrementing the previous nonce by 1. The data any single MMR can contain is finite, so when a Murmur wallet is created it can only be functional for a finite number of blocks (the block schedule). In this sense, Murmur is a "session-based" wallet. To ensure wallet lifetimes can be extended, Murmur wallets can be updated by generated a new Murmur store and submitting the result to a system that implements a verifier (see below). More specifically, when a Murmur store is created a DLEQ proof is generated (Discrete Log Equivalence Proof - a type of zkp) and attached to the store. This proof allows the Murmur store creator to convince a verifier that it knows the secret input (seed) without exposing it. +Updating a Murmur store is done by calling the same `new` function as above and incrementing the previous nonce by 1. The data any single MMR can contain is finite, so when a Murmur wallet is created it can only be functional for a finite number of blocks (the block schedule). In this sense, Murmur is a "session-based" wallet. To ensure wallet lifetimes can be extended, Murmur wallets can be updated by generating a new Murmur store and submitting the result to a system that implements a verifier (see below). More specifically, when a Murmur store is created a DLEQ proof is generated (Discrete Log Equivalence Proof - a type of zkp) and attached to the store. This proof allows the Murmur store creator to convince a verifier that it knows the secret input (seed) without exposing it. ``` rust // Compute the next nonce @@ -90,8 +90,8 @@ verifier::verify_update::(proof, public_key, nonce).unwrap() #### Verify Execution Parameters -This function allows for "tmelocked" commitments to be verified. The OTP input should be the timelock decrypted ciphertext. More specifically, it: -1) Verified a Merkle proof to prove that the ciphertext is indeed at the given position in the MMR defined by the given root. +This function allows for "timelocked" commitments to be verified. The OTP input should be the timelock decrypted ciphertext. More specifically, it: +1) verifies a Merkle proof to prove that the ciphertext is indeed at the given position in the MMR defined by the given root. 2) reconstructs the commitment and compares it against the given one ``` rust