diff --git a/Cargo.toml b/Cargo.toml index 31f901c..b5c47c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ bs-traits = { path = "crates/bs-traits" } bs-wallets = { path = "crates/bs-wallets" } comrade = { path = "crates/comrade" } comrade-reference = { path = "crates/comrade-reference" } +futures = "0.3.30" multibase = { path = "crates/multibase" } multicid = { path = "crates/multicid" } multicodec = { path = "crates/multicodec" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5122d9f..41eb8f6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,7 +19,7 @@ default = ["serde"] anyhow = "1.0" async-trait = "0.1" best-practices.workspace = true -bs.workspace = true +bs = { workspace = true, features = ["sync"] } bs-traits.workspace = true clap = { version = "4.5.36", features = ["cargo"] } colored = "3.0.0" diff --git a/cli/src/subcmds/plog.rs b/cli/src/subcmds/plog.rs index 95744bf..5fc66ab 100644 --- a/cli/src/subcmds/plog.rs +++ b/cli/src/subcmds/plog.rs @@ -81,7 +81,10 @@ impl SyncPrepareEphemeralSigning for KeyManager { ) -> Result< ( ::PubKey, - Box Result<::Signature, ::Error>>, + Box< + dyn FnOnce(&[u8]) -> Result<::Signature, ::Error> + + Send, + >, ), ::Error, > { @@ -101,7 +104,10 @@ impl SyncPrepareEphemeralSigning for KeyManager { let public_key = secret_key.conv_view()?.to_public_key()?; // Create the signing closure that owns the secret key - let sign_once = Box::new( + let sign_once: Box< + dyn FnOnce(&[u8]) -> Result<::Signature, ::Error> + + Send, + > = Box::new( move |data: &[u8]| -> Result<::Signature, ::Error> { debug!("Signing data with ephemeral key"); let signature = secret_key.sign_view()?.sign(data, false, None)?; @@ -196,7 +202,7 @@ pub async fn go(cmd: Command, _config: &Config) -> Result<(), Error> { let key_manager = KeyManager::default(); // open the p.log - let plog = open::open_plog(&cfg, &key_manager, &key_manager)?; + let plog = open::open_plog_sync(&cfg, &key_manager, &key_manager)?; println!("Created p.log {}", writer_name(&output)?.to_string_lossy()); print_plog(&plog)?; @@ -257,7 +263,7 @@ pub async fn go(cmd: Command, _config: &Config) -> Result<(), Error> { }; let cfg = update::Config::builder() - .add_entry_lock_scripts(vec![lock_script.clone()]) + .with_entry_lock_scripts(vec![lock_script.clone()]) .unlock(unlock_script) .entry_signing_key(entry_signing_key) .additional_ops(entry_ops) @@ -266,7 +272,7 @@ pub async fn go(cmd: Command, _config: &Config) -> Result<(), Error> { let key_manager = KeyManager::default(); // update the p.log - update::update_plog::(&mut plog, &cfg, &key_manager, &key_manager)?; + update::update_plog_sync::(&mut plog, &cfg, &key_manager, &key_manager)?; println!("Writing p.log {}", writer_name(&output)?.to_string_lossy()); print_plog(&plog)?; diff --git a/crates/bs-peer/.gitignore b/crates/bs-peer/.gitignore new file mode 100644 index 0000000..01d0a08 --- /dev/null +++ b/crates/bs-peer/.gitignore @@ -0,0 +1 @@ +pkg/ diff --git a/crates/bs-peer/src/peer.rs b/crates/bs-peer/src/peer.rs index c43f18c..b4461fc 100644 --- a/crates/bs-peer/src/peer.rs +++ b/crates/bs-peer/src/peer.rs @@ -1,13 +1,15 @@ //! BetterSign Peer: BetterSign core + libp2p networking + Blockstore -use std::sync::{Arc, Mutex}; - use crate::{platform, Error}; use ::cid::Cid; use blockstore::Blockstore as BlockstoreTrait; pub use bs::resolver_ext::ResolverExt; pub use bs::update::Config as UpdateConfig; +use bs::BetterSign; use bs::{ - config::sync::{KeyManager, MultiSigner}, + config::{ + asynchronous::{KeyManager as AsyncKeyManager, MultiSigner as AsyncMultiSigner}, + sync::{KeyManager, MultiSigner}, + }, params::{ anykey::PubkeyParams, vlad::{FirstEntryKeyParams, VladParams}, @@ -25,6 +27,8 @@ use multihash::mh; use provenance_log::key::key_paths::ValidatedKeyParams; pub use provenance_log::resolver::{ResolvedPlog, Resolver}; pub use provenance_log::{self as p, Key, Script}; +use std::sync::Arc; +use tokio::sync::Mutex; /// A peer that is generic over the blockstore type. /// @@ -35,8 +39,8 @@ where KP: KeyManager + MultiSigner, BS: BlockstoreTrait + CondSync, { - /// The Provenance Log of the peer, which contains the history of operations - plog: Arc>>, + /// The BetterSign instance containing the plog and crypto operations + better_sign: Arc>>>, /// Key provider for the peer, used for signing and key management key_provider: KP, /// [Blockstore] to save data @@ -56,15 +60,15 @@ where { fn eq(&self, other: &Self) -> bool { // Compare peer IDs and blockstore references - // Equal is the plogs match - self.peer_id == other.peer_id && Arc::ptr_eq(&self.plog, &other.plog) + // Equal if the better_sign Arc pointers match + self.peer_id == other.peer_id && Arc::ptr_eq(&self.better_sign, &other.better_sign) } } /// Impl Clone for BsPeer - You get everything except the events because you can't clone a /// Receiver. /// -/// Plog is wraped in Arc to allow shared access of a single [provenance_log::Log] across threads, +/// BetterSign is wrapped in Arc to allow shared access across threads. impl Clone for BsPeer where KP: KeyManager + MultiSigner + CondSync + Clone, @@ -72,7 +76,7 @@ where { fn clone(&self) -> Self { Self { - plog: self.plog.clone(), + better_sign: self.better_sign.clone(), key_provider: self.key_provider.clone(), blockstore: self.blockstore.clone(), network_client: self.network_client.clone(), @@ -118,7 +122,7 @@ where Ok(Self { network_client: Some(network_client), - plog: Default::default(), + better_sign: Default::default(), key_provider, blockstore, events: Some(rx_evts), @@ -129,25 +133,31 @@ where impl BsPeer where - KP: KeyManager + MultiSigner + CondSync, + KP: KeyManager + + MultiSigner + + CondSync + + AsyncKeyManager + + AsyncMultiSigner + + Clone, BS: BlockstoreTrait + CondSync, { - /// Returns a clone of the p[p::Log] of the peer, if it exists. - pub fn plog(&self) -> Option { - self.plog.lock().unwrap().as_ref().cloned() - // { - // Ok(plog) => plog.clone(), - // Err(_) => { - // tracing::error!("Failed to acquire lock on Plog"); - // None - // } - // } + /// Returns a clone of the provenance log of the peer, if it exists. + pub async fn plog(&self) -> Option { + self.better_sign + .lock() + .await + .as_ref() + .map(|bs| bs.plog().clone()) } - /// use lock to replace current plog with given plog - fn set_plog(&mut self, plog: p::Log) -> Result<(), Error> { - let mut plog_lock = self.plog.lock().map_err(|_| Error::LockPosioned)?; - *plog_lock = Some(plog); + /// Set the BetterSign instance with the given plog and key provider + async fn set_better_sign(&mut self, plog: p::Log, key_provider: KP) -> Result<(), Error> { + let mut bs_lock = self.better_sign.lock().await; + *bs_lock = Some(BetterSign::from_parts( + plog, + key_provider.clone(), + key_provider, + )); Ok(()) } @@ -155,7 +165,7 @@ where pub fn with_blockstore(key_provider: KP, blockstore: BS) -> Self { Self { key_provider, - plog: Default::default(), + better_sign: Default::default(), blockstore, network_client: Default::default(), events: None, @@ -214,15 +224,16 @@ where /// Store all the plog [provenance_log::Entry]s in the [blockstore::Blockstore] async fn store_entries(&self) -> Result<(), Error> { let (first_lock_cid, first_lock_bytes, entries) = { - let plog = self.plog.lock().map_err(|_| Error::LockPosioned)?; + let bs = self.better_sign.lock().await; - plog.as_ref() - .map(|p| { - let first_lock_cid_bytes: Vec = p.vlad.cid().clone().into(); + bs.as_ref() + .map(|bs| { + let plog = bs.plog(); + let first_lock_cid_bytes: Vec = plog.vlad.cid().clone().into(); let first_lock_cid = Cid::try_from(first_lock_cid_bytes).unwrap(); - let first_lock_bytes: Vec = p.first_lock.clone().into(); + let first_lock_bytes: Vec = plog.first_lock.clone().into(); - (first_lock_cid, first_lock_bytes, p.entries.clone()) + (first_lock_cid, first_lock_bytes, plog.entries.clone()) }) .ok_or(Error::PlogNotInitialized)? }; @@ -259,29 +270,32 @@ where } /// Generate a new Plog with the given configuration. - pub async fn generate_with_config(&mut self, config: bs::open::Config) -> Result<(), Error> { + pub async fn generate_with_config(&mut self, config: bs::open::Config) -> Result<(), Error> + where + KP: AsyncKeyManager + AsyncMultiSigner + Clone, + { { - match self.plog.lock() { - Ok(plog) => { - if plog.is_some() { - tracing::error!("[generate_with_config]: Plog already exists, cannot generate a new one"); - return Err(Error::PlogAlreadyExists); - } else { - tracing::debug!("[generate_with_config]: Acquired lock on Plog"); - } - } - Err(_) => { - tracing::error!("[generate_with_config]: Failed to acquire lock on Plog"); - return Err(Error::LockPosioned); - } + let bs = self.better_sign.lock().await; + if bs.is_some() { + tracing::error!( + "[generate_with_config]: BetterSign already exists, cannot generate a new one" + ); + return Err(Error::PlogAlreadyExists); } + tracing::debug!("[generate_with_config]: Acquired lock on BetterSign"); } - // Pass the key_provider directly as both key_manager and signer - let plog = bs::ops::open_plog(&config, &self.key_provider, &self.key_provider)?; - { - let verify_iter = &mut plog.verify(); + // Create BetterSign instance (no lock held) + let bs = BetterSign::new( + config.clone(), + self.key_provider.clone(), + self.key_provider.clone(), + ) + .await?; + // Verify the plog + { + let verify_iter = &mut bs.plog().verify(); for result in verify_iter { if let Err(e) = result { tracing::error!("Plog verification failed: {}", e); @@ -290,8 +304,12 @@ where } } + // Store ops and set the better_sign self.store_ops(config.into()).await?; - self.set_plog(plog)?; + { + let mut bs_lock = self.better_sign.lock().await; + *bs_lock = Some(bs); + } self.store_entries().await?; self.record_plog_to_dht().await?; self.publish_to_pubsub().await?; @@ -303,11 +321,14 @@ where &mut self, lock: impl AsRef, unlock: impl AsRef, - ) -> Result<(), Error> { + ) -> Result<(), Error> + where + KP: AsyncKeyManager + AsyncMultiSigner + Clone, + { { - let plog = self.plog.lock().map_err(|_| Error::LockPosioned)?; - if plog.is_some() { - tracing::error!("[generate]: Plog already exists, cannot generate a new one"); + let bs = self.better_sign.lock().await; + if bs.is_some() { + tracing::error!("[generate]: BetterSign already exists, cannot generate a new one"); return Err(Error::PlogAlreadyExists); } } @@ -336,25 +357,26 @@ where /// Update the BsPeer's Plog with new data. pub async fn update(&mut self, config: UpdateConfig) -> Result<(), Error> { + // Update with lock held briefly { - let mut plog = self.plog.lock().map_err(|_| Error::LockPosioned)?; - let Some(ref mut plog) = *plog else { - return Err(Error::PlogNotInitialized); - }; - // Apply the update to the plog - bs::ops::update_plog(plog, &config, &self.key_provider, &self.key_provider)?; + let mut bs_guard = self.better_sign.lock().await; + let bs = bs_guard.as_mut().ok_or(Error::PlogNotInitialized)?; + + // Update happens - this is async but the lock is held + // This is acceptable because BetterSign encapsulates both the plog and crypto ops + bs.update(config.clone()).await?; // Verify the updated plog - let verify_iter = &mut plog.verify(); + let verify_iter = &mut bs.plog().verify(); for result in verify_iter { if let Err(e) = result { - tracing::error!("Plog verification failed after update: {}", e); + tracing::error!("Plog verification failed after update: {e}"); return Err(Error::PlogVerificationFailed(e)); } } } - // After successful update, store CIDs and publish DHT record + // Now do async operations without holding any lock self.store_ops(config.into()).await?; self.store_entries().await?; self.record_plog_to_dht().await?; @@ -362,11 +384,11 @@ where Ok(()) } - /// Load a Plog into ths BsPeer. + /// Load a Plog into this BsPeer. pub async fn load(&mut self, plog: p::Log) -> Result<(), Error> { { - let plog = self.plog.lock().map_err(|_| Error::LockPosioned)?; - if plog.is_some() { + let bs = self.better_sign.lock().await; + if bs.is_some() { return Err(Error::PlogAlreadyExists); } } @@ -382,8 +404,9 @@ where } } - // Store the plog, entries, and record to DHT - self.set_plog(plog)?; + // Store the plog and create BetterSign instance + self.set_better_sign(plog, self.key_provider.clone()) + .await?; self.store_entries().await?; self.record_plog_to_dht().await?; @@ -394,11 +417,12 @@ where pub async fn publish_to_pubsub(&self) -> Result<(), Error> { // publish Vlad as topic with head cid bytes to pubsub let (vlad_bytes, head) = { - let plog = self.plog.lock().map_err(|_| Error::LockPosioned)?; - let Some(ref plog) = *plog else { + let bs = self.better_sign.lock().await; + let Some(ref bs) = *bs else { return Err(Error::PlogNotInitialized); }; + let plog = bs.plog(); let vlad_bytes: Vec = plog.vlad.clone().into(); (vlad_bytes, plog.head.clone()) @@ -429,11 +453,12 @@ where /// - There is an error putting the record into the DHT. pub async fn record_plog_to_dht(&mut self) -> Result<(), Error> { let (vlad_bytes, head_bytes) = { - let plog = self.plog.lock().map_err(|_| Error::LockPosioned)?; - let Some(ref plog) = *plog else { + let bs = self.better_sign.lock().await; + let Some(ref bs) = *bs else { return Err(Error::PlogNotInitialized); }; + let plog = bs.plog(); let vlad_bytes: Vec = plog.vlad.clone().into(); let head_bytes: Vec = plog.head.clone().into(); (vlad_bytes, head_bytes) diff --git a/crates/bs-peer/src/utils.rs b/crates/bs-peer/src/utils.rs index 9d26c8f..46df08b 100644 --- a/crates/bs-peer/src/utils.rs +++ b/crates/bs-peer/src/utils.rs @@ -188,12 +188,12 @@ pub async fn run_basic_test() { // Check if the plog is initialized assert!( - fixture.peer.plog().is_some(), + fixture.peer.plog().await.is_some(), "Expected plog to be initialized" ); // Check if the plog can be verified - let plog = fixture.peer.plog().unwrap(); + let plog = fixture.peer.plog().await.unwrap(); let verify_iter = &mut plog.verify(); for result in verify_iter { if let Err(e) = result { @@ -237,12 +237,12 @@ pub async fn run_basic_network_test() { // Check if the plog is initialized assert!( - fixture.peer.plog().is_some(), + fixture.peer.plog().await.is_some(), "Expected plog to be initialized in network peer" ); // Check if the plog can be verified - let plog = fixture.peer.plog().unwrap(); + let plog = fixture.peer.plog().await.unwrap(); let verify_iter = &mut plog.verify(); for result in verify_iter { if let Err(e) = result { @@ -306,7 +306,7 @@ pub async fn run_in_memory_blockstore_test() { assert!(res.is_ok(), "Expected successful creation of peer"); // verify the plog - let plog = fixture.peer.plog(); + let plog = fixture.peer.plog().await; // Verify the CID was stored let cid = { @@ -448,7 +448,7 @@ pub async fn run_store_entries_test() { // Let's verify the stored entries // Get the first lock CID from the plog for verification - let plog = fixture.peer.plog(); + let plog = fixture.peer.plog().await; let cid = { let binding = plog.as_ref().unwrap(); let first_lock_cid = binding.vlad.cid(); @@ -497,7 +497,7 @@ pub async fn run_network_store_entries_test() { .await .expect("Should create initialized network peer"); - let plog = fixture.peer.plog(); + let plog = fixture.peer.plog().await; // Get the first lock CID from the plog for verification let cid = { @@ -586,7 +586,7 @@ pub async fn run_update_test() { assert!(stored, "Updated CID should be stored in blockstore"); // Verify plog is still valid after update - let plog = fixture.peer.plog(); + let plog = fixture.peer.plog().await; let binding = plog.as_ref().unwrap(); let verify_iter = &mut binding.verify(); for result in verify_iter { @@ -643,7 +643,7 @@ pub async fn run_network_update_test() { ); // Verify plog is still valid - let plog = fixture.peer.plog(); + let plog = fixture.peer.plog().await; let binding = plog.as_ref().unwrap(); let verify_iter = &mut binding.verify(); for result in verify_iter { @@ -660,14 +660,14 @@ pub async fn run_load_test() { let fixture = setup_initialized_peer().await; // Get the plog from the initialized peer - let original_plog = fixture.peer.plog().as_ref().unwrap().clone(); + let original_plog = fixture.peer.plog().await.as_ref().unwrap().clone(); // Create a new peer with empty state let mut new_fixture = setup_test_peer().await; // Ensure the new peer has no plog yet assert!( - new_fixture.peer.plog().is_none(), + new_fixture.peer.plog().await.is_none(), "New peer should have no plog initially" ); @@ -677,12 +677,12 @@ pub async fn run_load_test() { // Verify the plog was loaded assert!( - new_fixture.peer.plog().is_some(), + new_fixture.peer.plog().await.is_some(), "Plog should now be loaded" ); // Verify the loaded plog has the correct data - let loaded_plog = new_fixture.peer.plog().unwrap(); + let loaded_plog = new_fixture.peer.plog().await.unwrap(); assert_eq!( loaded_plog.vlad.cid(), original_plog.vlad.cid(), @@ -730,7 +730,7 @@ pub async fn run_network_load_test() { let fixture = setup_initialized_peer().await; // Get the plog from the initialized peer - let original_plog = fixture.peer.plog().clone().unwrap().clone(); + let original_plog = fixture.peer.plog().await.clone().unwrap().clone(); // Create a new network peer with empty state let mut new_fixture = setup_network_test_peer() @@ -739,7 +739,7 @@ pub async fn run_network_load_test() { // Ensure the new network peer has no plog yet assert!( - new_fixture.peer.plog().is_none(), + new_fixture.peer.plog().await.is_none(), "New network peer should have no plog initially" ); @@ -752,12 +752,12 @@ pub async fn run_network_load_test() { // Verify the plog was loaded assert!( - new_fixture.peer.plog().is_some(), + new_fixture.peer.plog().await.is_some(), "Plog should now be loaded in network peer" ); // Verify the loaded plog has the correct data - let loaded_plog = new_fixture.peer.plog().as_ref().unwrap().clone(); + let loaded_plog = new_fixture.peer.plog().await.as_ref().unwrap().clone(); assert_eq!( loaded_plog.vlad.cid(), original_plog.vlad.cid(), diff --git a/crates/bs-traits/Cargo.toml b/crates/bs-traits/Cargo.toml index 69a254e..4617913 100644 --- a/crates/bs-traits/Cargo.toml +++ b/crates/bs-traits/Cargo.toml @@ -9,6 +9,7 @@ license = "Functional Source License 1.1" [dependencies] thiserror.workspace = true +multicid.workspace = true [dev-dependencies] futures = "0.3" diff --git a/crates/bs-traits/README.md b/crates/bs-traits/README.md index 9241901..d00ea68 100644 --- a/crates/bs-traits/README.md +++ b/crates/bs-traits/README.md @@ -3,4 +3,108 @@ # BS-Traits -The traits used by Better Sign \ No newline at end of file +**Generic trait definitions for BetterSign - the foundation for maximum flexibility.** + +## Purpose + +This crate provides pure, generic trait definitions that enable custom implementations with **any concrete types you choose**. It is intentionally free of opinions about specific cryptographic implementations. + +## When to Use + +**Use this crate directly when:** +- You need custom key types (e.g., HSM integration, hardware wallets) +- You want custom signature formats (e.g., Ethereum, Bitcoin-specific formats) +- You're integrating with existing cryptographic infrastructure +- You need maximum flexibility and control + +**Use `bs::config` instead when:** +- You're building a standard wallet with Multikey/Multisig +- You want rapid development with minimal boilerplate +- You want to use the reference `open_plog` and `update_plog` functions + +## Architecture + +BS-Traits is **Layer 1** in BetterSign's three-layer architecture: + +``` +Layer 3: Application Code (your implementations) + ↓ +Layer 2: Configuration Layer (bs::config - optional convenience) + ↓ +Layer 1: Generic Traits (bs-traits - maximum flexibility) ← YOU ARE HERE +``` + +See [ARCHITECTURE.md](../../docs/ARCHITECTURE.md) for complete documentation. + +## Trait Categories + +### Core Traits +- `Signer` / `Verifier` - Cryptographic signing and verification +- `Encryptor` / `Decryptor` - Encryption operations +- `GetKey` / `KeyDetails` - Key management +- `SecretSplitter` / `SecretCombiner` - Secret sharing schemes + +### Async Traits (`bs_traits::asyncro`) +- `AsyncSigner`, `AsyncVerifier` +- `AsyncEncryptor`, `AsyncDecryptor` +- `AsyncKeyManager`, `AsyncMultiSigner` +- All async operations return `BoxFuture` for dyn compatibility + +### Sync Traits (`bs_traits::sync`) +- `SyncSigner`, `SyncVerifier` +- `SyncEncryptor`, `SyncDecryptor` +- `SyncGetKey`, `SyncPrepareEphemeralSigning` + +## Example: Custom Implementation + +```rust +use bs_traits::{GetKey, Signer}; +use bs_traits::sync::{SyncGetKey, SyncSigner}; + +struct MyHsmWallet { + // Your custom HSM integration +} + +// Use YOUR types +impl GetKey for MyHsmWallet { + type Key = YourPublicKeyType; + type KeyPath = YourPathType; + type Codec = YourCodecType; + type Error = YourError; +} + +impl Signer for MyHsmWallet { + type KeyPath = YourPathType; + type Signature = YourSignatureType; // Not limited to Multisig! + type Error = YourError; +} + +impl SyncSigner for MyHsmWallet { + fn try_sign(&self, key: &Self::KeyPath, data: &[u8]) + -> Result + { + // Your custom signing logic + } +} +``` + +## Comparison with bs::config + +| Aspect | bs-traits (this crate) | bs::config | +|--------|----------------------|------------| +| Flexibility | Maximum - any types | Fixed types | +| Boilerplate | More verbose | Minimal | +| Types | Your choice | Multikey, Multisig, etc. | +| Use with open_plog | Need custom implementation | Direct use | +| Best for | Custom integrations | Standard wallets | + +## Features + +- `dyn-compatible` (default): Ensures traits can be used as `dyn Trait` objects + +## License + +Functional Source License 1.1 + +[CRYPTID]: https://cryptid.tech +[PROVENANCE]: https://github.com/cryptidtech/provenance-specifications \ No newline at end of file diff --git a/crates/bs-traits/src/asyncro.rs b/crates/bs-traits/src/asyncro.rs index e28dbae..a9b5391 100644 --- a/crates/bs-traits/src/asyncro.rs +++ b/crates/bs-traits/src/asyncro.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: FSL-1.1 //! This module provides traits for asynchronous operations use crate::cond_send::CondSend; +use crate::sync::EphemeralSigningTuple; use crate::*; use std::future::Future; use std::num::NonZeroUsize; @@ -158,7 +159,41 @@ pub trait AsyncGetKey: GetKey { &'a self, key_path: &'a Self::KeyPath, codec: &'a Self::Codec, - threshold: usize, - limit: usize, + threshold: NonZeroUsize, + limit: NonZeroUsize, ) -> Result, Self::Error>; } + +/// An async version of KeyManager +pub trait AsyncKeyManager: GetKey + CondSync { + /// Get the key asynchronously + fn get_key<'a>( + &'a self, + key_path: &'a Self::KeyPath, + codec: &'a Self::Codec, + threshold: NonZeroUsize, + limit: NonZeroUsize, + ) -> BoxFuture<'a, Result>; + + /// Emables you to pre-process the Vlad during the creation process + /// For example, you can provide a function that will be called shortly after the Vlad is created + #[allow(unused_variables)] + fn preprocess_vlad<'a>(&'a mut self, vlad: &'a multicid::Vlad) -> BoxFuture<'a, Result<(), E>> { + Box::pin(async move { Ok(()) }) + } +} + +/// An async version of MultiSigner, including ephemeral signing +pub trait AsyncMultiSigner: + AsyncSigner + EphemeralKey + GetKey +where + S: Send, + E: Send, +{ + fn prepare_ephemeral_signing<'a>( + &'a self, + codec: &'a Self::Codec, + threshold: NonZeroUsize, + limit: NonZeroUsize, + ) -> BoxFuture<'a, EphemeralSigningTuple>; +} diff --git a/crates/bs-traits/src/sync.rs b/crates/bs-traits/src/sync.rs index dde4572..e816255 100644 --- a/crates/bs-traits/src/sync.rs +++ b/crates/bs-traits/src/sync.rs @@ -18,7 +18,7 @@ pub trait SyncSigner: Signer { } } -pub type OneTimeSignFn = Box Result>; +pub type OneTimeSignFn = Box Result + Send>; pub type EphemeralSigningTuple = Result<(PK, OneTimeSignFn), E>; diff --git a/crates/bs-wallets/Cargo.toml b/crates/bs-wallets/Cargo.toml index ea6b653..8616bf7 100644 --- a/crates/bs-wallets/Cargo.toml +++ b/crates/bs-wallets/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true [dependencies] bs-traits.workspace = true +futures = { workspace = true, optional = true } multibase.workspace = true multicodec.workspace = true multikey.workspace = true @@ -28,5 +29,8 @@ bs.workspace = true bs-peer.workspace = true tracing-subscriber.workspace = true +[features] +sync = ["dep:futures"] + [lints] workspace = true diff --git a/crates/bs-wallets/src/async_memory.rs b/crates/bs-wallets/src/async_memory.rs new file mode 100644 index 0000000..c51e27c --- /dev/null +++ b/crates/bs-wallets/src/async_memory.rs @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: FSL-1.1 +//! Async implementation for the in-memory wallet. + +use bs_traits::asyncro::{AsyncKeyManager, AsyncMultiSigner, AsyncSigner, BoxFuture, SignerFuture}; +use bs_traits::sync::{EphemeralSigningTuple, SyncGetKey, SyncPrepareEphemeralSigning, SyncSigner}; +use bs_traits::{CondSync, EphemeralKey, GetKey, Signer}; +use multicodec::Codec; +use multikey::Multikey; +use multisig::Multisig; +use provenance_log::Key; +use std::num::NonZeroUsize; + +// Reuse the existing struct definition from memory.rs +pub use crate::memory::InMemoryKeyManager; + +impl AsyncSigner for InMemoryKeyManager +where + E: From + + From + + From + + From + + std::fmt::Debug + + Send + + Sync + + 'static, + Self: SyncSigner, + ::KeyPath: CondSync, +{ + fn try_sign<'a>( + &'a self, + key_path: &'a Self::KeyPath, + data: &'a [u8], + ) -> SignerFuture<'a, Self::Signature, Self::Error> { + Box::pin(async move { SyncSigner::try_sign(self, key_path, data) }) + } +} + +impl AsyncKeyManager for InMemoryKeyManager +where + E: From + From + std::fmt::Debug + Send + Sync + 'static, + Self: SyncGetKey, + ::KeyPath: CondSync, + ::Codec: CondSync, +{ + fn get_key<'a>( + &'a self, + key_path: &'a Self::KeyPath, + codec: &'a Self::Codec, + threshold: NonZeroUsize, + limit: NonZeroUsize, + ) -> BoxFuture<'a, Result> { + Box::pin(async move { SyncGetKey::get_key(self, key_path, codec, threshold, limit) }) + } +} + +impl AsyncMultiSigner for InMemoryKeyManager +where + E: From + + From + + From + + From + + std::fmt::Debug + + Send + + Sync + + 'static, + Self: SyncPrepareEphemeralSigning< + Codec = Codec, + PubKey = Multikey, + Signature = Multisig, + Error = E, + > + EphemeralKey + + GetKey + + Signer, + ::Codec: CondSync, + ::KeyPath: CondSync, +{ + fn prepare_ephemeral_signing<'a>( + &'a self, + codec: &'a ::Codec, + threshold: NonZeroUsize, + limit: NonZeroUsize, + ) -> BoxFuture<'a, EphemeralSigningTuple> { + Box::pin(async move { + SyncPrepareEphemeralSigning::prepare_ephemeral_signing(self, codec, threshold, limit) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bs_traits::asyncro::{AsyncKeyManager, AsyncMultiSigner, AsyncSigner}; + use multicodec::Codec; + use multikey::Views; + use std::num::NonZero; + use tracing_subscriber::fmt; + + fn init_logger() { + let subscriber = fmt().with_env_filter("trace").finish(); + if let Err(e) = tracing::subscriber::set_global_default(subscriber) { + tracing::warn!("failed to set subscriber: {}", e); + } + } + + // Test fixture that ensures async traits are properly implemented + async fn test_async_traits(_km: &KM, _ms: &MS) + where + KM: AsyncKeyManager, + MS: AsyncMultiSigner, + { + } + + #[tokio::test] + async fn test_async_default_key_manager() { + // Create key manager with default error type + let key_manager = InMemoryKeyManager::default(); + test_async_traits(&key_manager, &key_manager).await; + } + + #[tokio::test] + async fn test_async_key_manager() { + // Create key manager + let key_manager = InMemoryKeyManager::::new(); + + // Test a regular non-ephemeral key + let key_path = Key::try_from("/async/test/key/path").unwrap(); + let test_mk = + multikey::Builder::new_from_random_bytes(Codec::Ed25519Priv, &mut rand_core_6::OsRng) + .unwrap() + .try_build() + .unwrap(); + + // Add to wallet + key_manager + .store_secret_key(key_path.clone(), test_mk.clone()) + .unwrap(); + + // Can sign with stored key using async API + let data = b"test async data"; + let signature = AsyncSigner::try_sign(&key_manager, &key_path, data) + .await + .unwrap(); + let verify_result = test_mk + .verify_view() + .unwrap() + .verify(&signature, Some(data)); + assert!(verify_result.is_ok()); + } + + #[tokio::test] + async fn test_async_dynamic_key_generation() { + // Create key manager + let key_manager = InMemoryKeyManager::::new(); + + // Request a key with a custom path using async API + let custom_path = Key::try_from("/async/custom/key/path").unwrap(); + + // First call to get_key generates a key pair and stores the secret key, + // but returns the public key + let public_key = AsyncKeyManager::get_key( + &key_manager, + &custom_path, + &Codec::Ed25519Priv, + NonZero::new(1).unwrap(), + NonZero::new(1).unwrap(), + ) + .await + .unwrap(); + + // Verify we got a public key + assert!(public_key.attr_view().unwrap().is_public_key()); + + // Get the key again - should be the same public key + let public_key2 = AsyncKeyManager::get_key( + &key_manager, + &custom_path, + &Codec::Ed25519Priv, + NonZero::new(1).unwrap(), + NonZero::new(1).unwrap(), + ) + .await + .unwrap(); + + assert!( + public_key.eq(&public_key2), + "The two retrieved public keys should be equal" + ); + + // Try signing with the key at the custom path using async API + // This works because the secret key is stored internally in the key manager + let data = b"test async custom key"; + let signature = AsyncSigner::try_sign(&key_manager, &custom_path, data) + .await + .unwrap(); + + // Verify signature with the public key we have + let verify_result = public_key + .verify_view() + .unwrap() + .verify(&signature, Some(data)); + assert!( + verify_result.is_ok(), + "Signature verification should succeed" + ); + } + + #[tokio::test] + async fn test_async_prepare_ephemeral_signing() { + init_logger(); + + tracing::info!("Starting test_async_prepare_ephemeral_signing"); + + let key_manager = InMemoryKeyManager::::new(); + let data = b"test async ephemeral signing"; + + // Use async API to get an ephemeral public key and a one-time signing function + let (public_key, sign_once) = AsyncMultiSigner::prepare_ephemeral_signing( + &key_manager, + &Codec::Ed25519Priv, + NonZero::new(1).unwrap(), + NonZero::new(1).unwrap(), + ) + .await + .expect("Failed to prepare ephemeral signing"); + + // Verify that we got a public key + assert!(public_key.attr_view().unwrap().is_public_key()); + + // Sign the data with the one-time function + let signature = sign_once(data).expect("Failed to sign with ephemeral key"); + + // Create a new multikey for verification since we only have the public key + let verify_key = public_key.clone(); + + // Verify the signature + let verify_result = verify_key + .verify_view() + .unwrap() + .verify(&signature, Some(data)); + + assert!( + verify_result.is_ok(), + "Signature verification should succeed" + ); + + tracing::info!("Async ephemeral signing test completed successfully"); + } + + #[tokio::test] + async fn test_async_concurrent_key_generation() { + // Test that multiple concurrent async operations work correctly + let key_manager = InMemoryKeyManager::::new(); + + // Create multiple tasks that generate keys concurrently + let mut handles = vec![]; + for i in 0..5 { + let km = key_manager.clone(); + let handle = tokio::spawn(async move { + let path = Key::try_from(format!("/concurrent/key/{}", i).as_str()).unwrap(); + AsyncKeyManager::get_key( + &km, + &path, + &Codec::Ed25519Priv, + NonZero::new(1).unwrap(), + NonZero::new(1).unwrap(), + ) + .await + .unwrap() + }); + handles.push(handle); + } + + // Wait for all tasks to complete + let mut public_keys = vec![]; + for handle in handles { + let pk = handle.await.unwrap(); + assert!(pk.attr_view().unwrap().is_public_key()); + public_keys.push(pk); + } + + // All keys should be unique + for i in 0..public_keys.len() { + for j in (i + 1)..public_keys.len() { + assert!( + !public_keys[i].eq(&public_keys[j]), + "Keys {} and {} should be different", + i, + j + ); + } + } + } + + #[tokio::test] + async fn test_async_concurrent_signing() { + // Test that multiple concurrent async signing operations work correctly + let key_manager = InMemoryKeyManager::::new(); + + // Generate a shared key + let key_path = Key::try_from("/shared/signing/key").unwrap(); + let _ = AsyncKeyManager::get_key( + &key_manager, + &key_path, + &Codec::Ed25519Priv, + NonZero::new(1).unwrap(), + NonZero::new(1).unwrap(), + ) + .await + .unwrap(); + + // Create multiple tasks that sign data concurrently + let mut handles = vec![]; + for i in 0..5 { + let km = key_manager.clone(); + let kp = key_path.clone(); + let handle = tokio::spawn(async move { + let data = format!("concurrent data {}", i); + AsyncSigner::try_sign(&km, &kp, data.as_bytes()) + .await + .unwrap() + }); + handles.push(handle); + } + + // Wait for all tasks to complete + let mut signatures = vec![]; + for handle in handles { + let sig = handle.await.unwrap(); + signatures.push(sig); + } + + // All signatures should be valid (we don't check uniqueness as they sign different data) + assert_eq!(signatures.len(), 5); + } + + #[tokio::test] + async fn test_async_multiple_ephemeral_keys() { + // Test creating multiple ephemeral keys concurrently + let key_manager = InMemoryKeyManager::::new(); + + let mut handles = vec![]; + for i in 0..3 { + let km = key_manager.clone(); + let handle = tokio::spawn(async move { + let (public_key, sign_once) = AsyncMultiSigner::prepare_ephemeral_signing( + &km, + &Codec::Ed25519Priv, + NonZero::new(1).unwrap(), + NonZero::new(1).unwrap(), + ) + .await + .unwrap(); + + let data = format!("ephemeral data {}", i); + let signature = sign_once(data.as_bytes()).unwrap(); + + // Verify the signature + public_key + .verify_view() + .unwrap() + .verify(&signature, Some(data.as_bytes())) + .unwrap(); + + public_key + }); + handles.push(handle); + } + + // Wait for all tasks to complete + let mut public_keys = vec![]; + for handle in handles { + let pk = handle.await.unwrap(); + public_keys.push(pk); + } + + // All ephemeral keys should be unique + for i in 0..public_keys.len() { + for j in (i + 1)..public_keys.len() { + assert!( + !public_keys[i].eq(&public_keys[j]), + "Ephemeral keys {} and {} should be different", + i, + j + ); + } + } + } +} diff --git a/crates/bs-wallets/src/lib.rs b/crates/bs-wallets/src/lib.rs index 347577c..4761420 100644 --- a/crates/bs-wallets/src/lib.rs +++ b/crates/bs-wallets/src/lib.rs @@ -3,3 +3,6 @@ pub use error::Error; /// In memory Key manager and signer pub mod memory; + +/// In memory async Key manager and signer +pub mod async_memory; diff --git a/crates/bs-wallets/src/memory.rs b/crates/bs-wallets/src/memory.rs index 76f4cca..e35c930 100644 --- a/crates/bs-wallets/src/memory.rs +++ b/crates/bs-wallets/src/memory.rs @@ -200,11 +200,7 @@ where // Generate a new key since we don't have it yet let secret_key = Self::generate_key(codec)?; let fingerprint = secret_key.fingerprint_view()?.fingerprint(Codec::Sha2256)?; - tracing::debug!( - "Generated new key for path {}: {:?}", - key_path, - fingerprint - ); + tracing::debug!("Generated new key for path {key_path}: {fingerprint:?}"); let public_key = secret_key.conv_view()?.to_public_key()?; // Store the secret key for future use diff --git a/crates/bs/Cargo.toml b/crates/bs/Cargo.toml index cb0cdf9..1750c6f 100644 --- a/crates/bs/Cargo.toml +++ b/crates/bs/Cargo.toml @@ -9,9 +9,10 @@ license = "Functional Source License 1.1" [dependencies] bs-traits.workspace = true -bs-wallets.workspace = true +bs-wallets = { workspace = true, features = ["sync"] } best-practices.workspace = true comrade.workspace = true +futures = { workspace = true, optional = true } multicid.workspace = true multicodec.workspace = true multihash.workspace = true @@ -27,11 +28,14 @@ bon = "3.6.3" [dev-dependencies] tracing-subscriber.workspace = true -bs-wallets.workspace = true +bs-wallets = { workspace = true, features = ["sync"] } multibase.workspace = true +tokio = { workspace = true, features = ["macros", "rt"] } [lints] workspace = true [features] +default = ["sync"] serde = ["dep:serde"] +sync = ["dep:futures", "bs-wallets/sync"] diff --git a/crates/bs/src/better_sign.rs b/crates/bs/src/better_sign.rs new file mode 100644 index 0000000..2358c04 --- /dev/null +++ b/crates/bs/src/better_sign.rs @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: FSL-1.1 +//! The [`BetterSign`] struct provides an encapsulated interface to provenance log operations. +//! +//! This module offers a clean, preferred API compared to directly using the functional +//! `open_plog` and `update_plog` functions. +use crate::{ + config::asynchronous::{KeyManager, MultiSigner}, + error::{BsCompatibleError, Error}, + ops::{open, update}, +}; +use provenance_log::{entry::Entry, Log}; + +/// A BetterSign instance that encapsulates a provenance log with its key manager and signer. +/// +/// This struct provides an ergonomic API for working with provenance logs by keeping +/// the log, key manager, and signer together as a single unit. +#[derive(Debug)] +pub struct BetterSign { + plog: Log, + key_manager: KM, + signer: S, + _phantom: std::marker::PhantomData, +} + +impl BetterSign { + /// Create a BetterSign instance from existing parts. + /// + /// This is useful when you already have a plog and want to wrap it with key management. + pub fn from_parts(plog: Log, key_manager: KM, signer: S) -> Self { + Self { + plog, + key_manager, + signer, + _phantom: std::marker::PhantomData, + } + } + + /// Get a reference to the provenance [Log]. + pub fn plog(&self) -> &Log { + &self.plog + } + + /// Get a mutable reference to the provenance [Log]. + pub fn plog_mut(&mut self) -> &mut Log { + &mut self.plog + } + + /// Get a reference to the key manager. + pub fn key_manager(&self) -> &KM { + &self.key_manager + } + + /// Get a reference to the signer. + pub fn signer(&self) -> &S { + &self.signer + } + + /// Consume self and return the provenance log. + pub fn into_plog(self) -> Log { + self.plog + } + + /// Consume self and return all components. + pub fn into_parts(self) -> (Log, KM, S) { + (self.plog, self.key_manager, self.signer) + } +} + +impl BetterSign +where + E: BsCompatibleError + Send, + KM: KeyManager, + S: MultiSigner, +{ + /// Create a new BetterSign instance with the given configuration. + pub async fn new(config: open::Config, mut key_manager: KM, signer: S) -> Result { + let plog = open::open_plog_core(&config, &mut key_manager, &signer).await?; + Ok(Self { + plog, + key_manager, + signer, + _phantom: std::marker::PhantomData, + }) + } + + /// Update the provenance log with new operations. + pub async fn update(&mut self, config: update::Config) -> Result { + update::update_plog_core(&mut self.plog, &config, &self.key_manager, &self.signer).await?; + // Return a clone of the last entry (the one we just added) + Ok(self + .plog + .entries + .get(&self.plog.head) + .expect("Head entry should exist") + .clone()) + } +} + +#[cfg(feature = "sync")] +impl BetterSign +where + E: BsCompatibleError + Send, + KM: KeyManager, + S: MultiSigner, +{ + /// Synchronously create a new BetterSign instance with the given configuration. + /// + /// This blocks on the async `new` method using `futures::executor::block_on`. + /// + /// # Errors + /// + /// Returns an error if the provenance log creation fails. + pub fn new_sync(config: open::Config, key_manager: KM, signer: S) -> Result { + futures::executor::block_on(Self::new(config, key_manager, signer)) + } + + /// Synchronously update the provenance log with new operations. + /// + /// This blocks on the async `update` method using `futures::executor::block_on`. + /// + /// # Errors + /// + /// Returns an error if the update operation fails. + pub fn update_sync(&mut self, config: update::Config) -> Result { + futures::executor::block_on(self.update(config)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::params::vlad::FirstEntryKeyParams; + use crate::params::{anykey::PubkeyParams, vlad::VladParams}; + use bs_wallets::memory::InMemoryKeyManager; + use multicodec::Codec; + use provenance_log::key::key_paths::ValidatedKeyParams; + use provenance_log::{Key, Script}; + + #[tokio::test] + async fn test_better_sign_new() { + let config = open::Config::builder() + .vlad(VladParams::default()) + .pubkey( + PubkeyParams::builder() + .codec(Codec::Ed25519Priv) + .build() + .into(), + ) + .entrykey( + FirstEntryKeyParams::builder() + .codec(Codec::Ed25519Priv) + .build() + .into(), + ) + .lock(Script::Code( + Key::default(), + "check_signature(\"/pubkey\", \"/entry/\")".to_string(), + )) + .unlock(Script::Code( + Key::default(), + "push(\"/entry/\"); push(\"/entry/proof\")".to_string(), + )) + .build(); + + let key_manager = InMemoryKeyManager::::default(); + let signer = key_manager.clone(); + + let bs = BetterSign::new(config, key_manager, signer) + .await + .expect("Failed to create BetterSign"); + + // Verify the plog was created + assert!(!bs.plog().entries.is_empty()); + assert!(bs.plog().verify().count() > 0); + } + + #[tokio::test] + async fn test_better_sign_update() { + let open_config = open::Config::builder() + .vlad(VladParams::default()) + .pubkey( + PubkeyParams::builder() + .codec(Codec::Ed25519Priv) + .build() + .into(), + ) + .entrykey( + FirstEntryKeyParams::builder() + .codec(Codec::Ed25519Priv) + .build() + .into(), + ) + .lock(Script::Code( + Key::default(), + "check_signature(\"/pubkey\", \"/entry/\")".to_string(), + )) + .unlock(Script::Code( + Key::default(), + "push(\"/entry/\"); push(\"/entry/proof\")".to_string(), + )) + .build(); + + let key_manager = InMemoryKeyManager::::default(); + let signer = key_manager.clone(); + + let mut bs = BetterSign::new(open_config, key_manager, signer) + .await + .expect("Failed to create BetterSign"); + + let initial_entry_count = bs.plog().entries.len(); + + // Update the plog + let update_config = update::Config::builder() + .unlock(Script::Code( + Key::default(), + "push(\"/entry/\"); push(\"/entry/proof\")".to_string(), + )) + .entry_signing_key(PubkeyParams::KEY_PATH.into()) + .build(); + + let entry = bs + .update(update_config) + .await + .expect("Failed to update plog"); + + // Verify a new entry was added + assert_eq!(bs.plog().entries.len(), initial_entry_count + 1); + assert_eq!(bs.plog().head, entry.cid()); + } + + #[cfg(feature = "sync")] + #[test] + fn test_better_sign_sync() { + let config = open::Config::builder() + .vlad(VladParams::default()) + .pubkey( + PubkeyParams::builder() + .codec(Codec::Ed25519Priv) + .build() + .into(), + ) + .entrykey( + FirstEntryKeyParams::builder() + .codec(Codec::Ed25519Priv) + .build() + .into(), + ) + .lock(Script::Code( + Key::default(), + "check_signature(\"/pubkey\", \"/entry/\")".to_string(), + )) + .unlock(Script::Code( + Key::default(), + "push(\"/entry/\"); push(\"/entry/proof\")".to_string(), + )) + .build(); + + let key_manager = InMemoryKeyManager::::default(); + let signer = key_manager.clone(); + + let bs = + BetterSign::new_sync(config, key_manager, signer).expect("Failed to create BetterSign"); + + // Verify the plog was created + assert!(!bs.plog().entries.is_empty()); + } +} diff --git a/crates/bs/src/config.rs b/crates/bs/src/config.rs index 9eedbb2..101ba18 100644 --- a/crates/bs/src/config.rs +++ b/crates/bs/src/config.rs @@ -1,14 +1,90 @@ -//! Holds opinionated configuration about what concrete types should be used for the traits. +//! Opinionated configuration layer for BetterSign trait bounds with concrete types. //! -//! Users can pick any concrete types that implement the traits, but this module provides -//! default implementations that can be used directly. +//! # Architecture Overview +//! +//! This module provides a convenience layer on top of the generic traits from `bs-traits`, +//! eliminating repetitive type parameter boilerplate throughout the BetterSign codebase. +//! +//! ## Layered Design +//! +//! ```text +//! ┌─────────────────────────────────────────────────────┐ +//! │ Application Layer (bs-peer, bs-wallets, etc.) │ +//! │ Uses: config::sync::KeyManager │ +//! │ config::asynchronous::MultiSigner │ +//! └─────────────────────────────────────────────────────┘ +//! ↓ +//! ┌─────────────────────────────────────────────────────┐ +//! │ Configuration Layer (this module) │ +//! │ Provides: Opinionated supertraits with concrete │ +//! │ types (Key, Codec, Multikey, Multisig) │ +//! └─────────────────────────────────────────────────────┘ +//! ↓ +//! ┌─────────────────────────────────────────────────────┐ +//! │ Traits Layer (bs-traits) │ +//! │ Provides: Generic sync/async traits │ +//! │ (SyncSigner, AsyncSigner, etc.) │ +//! └─────────────────────────────────────────────────────┘ +//! ``` +//! +//! ## Modules +//! +//! - **`sync`**: Synchronous supertraits combining `bs_traits::sync` traits with concrete types +//! - **`asynchronous`**: Asynchronous supertraits combining `bs_traits::asyncro` traits with concrete types +//! - **`adapters`**: Bridges that convert sync trait implementations to async interfaces +//! +//! ## Concrete Types +//! +//! This configuration layer standardizes on these concrete types: +//! +//! - **Key paths**: [`provenance_log::Key`] +//! - **Codec**: [`multicodec::Codec`] +//! - **Public keys**: [`multikey::Multikey`] +//! - **Signatures**: [`multisig::Multisig`] (re-exported as [`crate::Signature`]) +//! +//! ## Benefits +//! +//! 1. **Reduced boilerplate**: Write `KeyManager` instead of repeating all associated types +//! 2. **Type safety**: Ensures consistent concrete types across the application +//! 3. **Flexibility**: Generic over error type `E` for different error handling strategies +//! 4. **Maintainability**: Change concrete types in one place if needed +//! +//! ## When to Use +//! +//! - **Use this module** when writing BetterSign-specific code that uses the standard concrete types +//! - **Use `bs-traits` directly** when writing generic code that should work with any types +//! +//! ## Example +//! +//! ```ignore +//! // Without config module (verbose) +//! use bs_traits::asyncro::AsyncKeyManager; +//! use multicodec::Codec; +//! use multikey::Multikey; +//! use provenance_log::Key; +//! +//! async fn verbose(km: &KM) -> Result +//! where +//! KM: AsyncKeyManager, +//! { +//! // ... +//! } +//! +//! // With config module (clean) +//! use bs::config::asynchronous::KeyManager; +//! +//! async fn clean(km: &(dyn KeyManager + Send + Sync)) -> Result { +//! // ... +//! } +//! ``` +/// Sync to async adapters +pub mod adapters; /// Opinionated configuration for the async traits types pub mod asynchronous; /// Opinionated configuration for the sync traits types pub mod sync; -use crate::Error; use bs_traits::{GetKey, Signer}; /// Re-export the types used in the traits @@ -16,3 +92,19 @@ pub use multicodec::Codec; pub use multikey::Multikey; pub use multisig::Multisig; pub use provenance_log::Key; + +/// The concrete signature type used throughout BetterSign. +/// +/// This is an alias for [`multisig::Multisig`] and is the standard signature type +/// used in both [`sync::MultiSigner`] and [`asynchronous::MultiSigner`] traits. +/// +/// # Example +/// +/// ```ignore +/// use bs::config::Signature; +/// +/// fn verify_signature(sig: &Signature) { +/// // Work with the concrete signature type +/// } +/// ``` +pub type Signature = Multisig; diff --git a/crates/bs/src/config/adapters.rs b/crates/bs/src/config/adapters.rs new file mode 100644 index 0000000..b7ccfa8 --- /dev/null +++ b/crates/bs/src/config/adapters.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: FSL-1.1 +//! Adapters to bridge sync traits to async traits. + +use crate::config::sync::{KeyManager, MultiSigner}; +use crate::Error; +use bs_traits::asyncro::{AsyncKeyManager, AsyncMultiSigner}; +use bs_traits::asyncro::{AsyncSigner, BoxFuture, SignerFuture}; +use bs_traits::sync::EphemeralSigningTuple; +use bs_traits::{self, EphemeralKey, GetKey, Signer}; +use multicodec::Codec; +use multikey::Multikey; +use multisig::Multisig; +use provenance_log::Key; +use std::marker::PhantomData; +use std::num::NonZeroUsize; + +/// An adapter that wraps a sync `KeyManager` to expose an `AsyncKeyManager` interface. +pub struct SyncToAsyncManager<'a, E: 'a> { + sync_manager: &'a (dyn KeyManager + Send + Sync), + _phantom: PhantomData, +} + +impl<'a, E> SyncToAsyncManager<'a, E> { + /// Create a new [`SyncToAsyncManager`] wrapping the given sync [`KeyManager`]. + pub fn new(sync_manager: &'a (dyn KeyManager + Send + Sync)) -> Self { + Self { + sync_manager, + _phantom: PhantomData, + } + } +} + +impl<'a, E> GetKey for SyncToAsyncManager<'a, E> +where + E: From + From + std::fmt::Debug + Send + Sync + 'static, +{ + type Key = Multikey; + type KeyPath = Key; + type Codec = Codec; + type Error = E; +} + +impl<'a, E> AsyncKeyManager for SyncToAsyncManager<'a, E> +where + E: From + From + std::fmt::Debug + Send + Sync + 'static, +{ + fn get_key( + &self, + key_path: &::KeyPath, + codec: &::Codec, + threshold: NonZeroUsize, + limit: NonZeroUsize, + ) -> BoxFuture<'_, Result<::Key, E>> { + let res = self.sync_manager.get_key(key_path, codec, threshold, limit); + Box::pin(async { res }) + } +} + +/// An adapter that wraps a sync `MultiSigner` to expose an `AsyncMultiSigner` interface. +pub struct SyncToAsyncSigner<'a, E: 'a> { + sync_signer: &'a (dyn MultiSigner + Send + Sync), + _phantom: PhantomData, +} + +impl<'a, E> SyncToAsyncSigner<'a, E> { + /// Create a new [`SyncToAsyncSigner`] wrapping the given sync [`MultiSigner`]. + pub fn new(sync_signer: &'a (dyn MultiSigner + Send + Sync)) -> Self { + Self { + sync_signer, + _phantom: PhantomData, + } + } +} + +impl<'a, E> Signer for SyncToAsyncSigner<'a, E> +where + E: From + + From + + From + + From + + std::fmt::Debug + + Send + + Sync + + 'static, +{ + type KeyPath = Key; + type Signature = Multisig; + type Error = E; +} + +impl<'a, E> EphemeralKey for SyncToAsyncSigner<'a, E> +where + E: From + + From + + From + + From + + std::fmt::Debug + + Send + + Sync + + 'static, +{ + type PubKey = Multikey; +} + +impl<'a, E> AsyncSigner for SyncToAsyncSigner<'a, E> +where + E: From + + From + + From + + From + + std::fmt::Debug + + Send + + Sync + + 'static, +{ + fn try_sign<'b>( + &'b self, + key_path: &'b ::KeyPath, + data: &'b [u8], + ) -> SignerFuture<'b, ::Signature, ::Error> { + let res = self.sync_signer.try_sign(key_path, data); + Box::pin(async { res }) + } +} + +impl<'a, E> AsyncMultiSigner for SyncToAsyncSigner<'a, E> +where + E: From + + From + + From + + From + + std::fmt::Debug + + Send + + Sync + + 'static, +{ + fn prepare_ephemeral_signing<'b>( + &'b self, + codec: &'b ::Codec, + threshold: NonZeroUsize, + limit: NonZeroUsize, + ) -> BoxFuture<'b, EphemeralSigningTuple<::PubKey, Multisig, E>> { + let res = self + .sync_signer + .prepare_ephemeral_signing(codec, threshold, limit); + Box::pin(async { res }) + } +} + +impl<'a, E> GetKey for SyncToAsyncSigner<'a, E> +where + E: 'a, +{ + type Key = Multikey; + type KeyPath = Key; + type Codec = Codec; + type Error = E; +} diff --git a/crates/bs/src/config/asynchronous.rs b/crates/bs/src/config/asynchronous.rs index f2f074e..7021263 100644 --- a/crates/bs/src/config/asynchronous.rs +++ b/crates/bs/src/config/asynchronous.rs @@ -1,36 +1,118 @@ +//! Asynchronous trait supertraits with opinionated concrete types for BetterSign. +//! +//! This module provides convenience supertraits that combine the generic asynchronous traits +//! from `bs_traits::asyncro` with concrete types specific to the BetterSign application. +//! +//! # Purpose +//! +//! Rather than repeating verbose trait bounds throughout the codebase: +//! ```ignore +//! async fn example( +//! key_manager: &KM, +//! signer: &S, +//! ) -> Result<(), E> +//! where +//! KM: bs_traits::asyncro::AsyncKeyManager< +//! E, +//! KeyPath = provenance_log::Key, +//! Codec = multicodec::Codec, +//! Key = multikey::Multikey, +//! >, +//! S: bs_traits::asyncro::AsyncMultiSigner< +//! multisig::Multisig, +//! E, +//! PubKey = multikey::Multikey, +//! Codec = multicodec::Codec, +//! >, +//! ``` +//! +//! You can use the opinionated supertraits from this module: +//! ```ignore +//! use bs::config::asynchronous::{KeyManager, MultiSigner}; +//! +//! async fn example( +//! key_manager: &(dyn KeyManager + Send + Sync), +//! signer: &(dyn MultiSigner + Send + Sync), +//! ) -> Result<(), E> +//! ``` +//! +//! # Relationship to Other Modules +//! +//! - **`bs_traits::asyncro`**: Provides generic asynchronous traits that work with any types +//! - **`bs::config::asynchronous`** (this module): Provides opinionated supertraits with concrete types for BetterSign +//! - **`bs::config::sync`**: Parallel module providing synchronous supertraits +//! - **`bs::config::adapters`**: Bridges between sync and async implementations +//! +//! # Concrete Types Used +//! +//! - **KeyPath**: `provenance_log::Key` +//! - **Codec**: `multicodec::Codec` +//! - **Key**: `multikey::Multikey` +//! - **Signature**: `multisig::Multisig` (aliased as `bs::Signature`) +//! +//! # Example +//! +//! ```ignore +//! use bs::config::asynchronous::{KeyManager, MultiSigner}; +//! +//! async fn process_async( +//! km: &(dyn KeyManager + Send + Sync), +//! signer: &(dyn MultiSigner + Send + Sync), +//! ) -> Result<(), E> { +//! // All concrete types are already specified in the trait bounds +//! // ... +//! } +//! ``` use super::*; -use bs_traits::asyncro::{AsyncGetKey, AsyncSigner}; +pub use bs_traits::asyncro::{ + AsyncKeyManager as AsyncGetKeyTrait, AsyncMultiSigner as AsyncMultiSignerTrait, AsyncSigner, +}; +use bs_traits::EphemeralKey; /// Supertrait for key management operations -pub trait KeyManager: - GetKey - + AsyncGetKey +pub trait KeyManager: + GetKey + + AsyncGetKeyTrait + Send + Sync + 'static { } -/// Supertrait for signing operations -pub trait MultiSigner: - Signer + AsyncSigner + Send + Sync + 'static -{ -} - -impl KeyManager for T where - T: GetKey - + AsyncGetKey +impl KeyManager for T where + T: GetKey + + AsyncGetKeyTrait + Send + Sync + 'static { } -impl MultiSigner for T where - T: Signer +/// Supertrait for signing operations +pub trait MultiSigner: + Signer + + AsyncSigner + + EphemeralKey + + GetKey + + AsyncMultiSignerTrait + + Send + + Sync + + 'static +where + E: Send, +{ +} + +impl MultiSigner for T +where + T: Signer + AsyncSigner + + EphemeralKey + + GetKey + + AsyncMultiSignerTrait + Send + Sync - + 'static + + 'static, + E: Send, { } diff --git a/crates/bs/src/config/sync.rs b/crates/bs/src/config/sync.rs index b68ef03..e8875d0 100644 --- a/crates/bs/src/config/sync.rs +++ b/crates/bs/src/config/sync.rs @@ -1,5 +1,58 @@ -//! Sync alterntives to the asynchronous traits. -use bs_traits::sync::{SyncGetKey, SyncPrepareEphemeralSigning, SyncSigner}; +//! Synchronous trait supertraits with opinionated concrete types for BetterSign. +//! +//! This module provides convenience supertraits that combine the generic synchronous traits +//! from `bs_traits::sync` with concrete types specific to the BetterSign application. +//! +//! # Purpose +//! +//! Rather than repeating verbose trait bounds throughout the codebase: +//! ```ignore +//! fn example( +//! key_manager: &KM +//! ) where +//! KM: bs_traits::sync::SyncGetKey< +//! KeyPath = provenance_log::Key, +//! Codec = multicodec::Codec, +//! Key = multikey::Multikey, +//! Error = E +//! > +//! ``` +//! +//! You can use the opinionated supertraits from this module: +//! ```ignore +//! use bs::config::sync::KeyManager; +//! +//! fn example(key_manager: &(dyn KeyManager + Send + Sync)) +//! ``` +//! +//! # Relationship to Other Modules +//! +//! - **`bs_traits::sync`**: Provides generic synchronous traits that work with any types +//! - **`bs::config::sync`** (this module): Provides opinionated supertraits with concrete types for BetterSign +//! - **`bs::config::asynchronous`**: Parallel module providing async supertraits +//! - **`bs::config::adapters`**: Bridges between sync and async implementations +//! +//! # Concrete Types Used +//! +//! - **KeyPath**: `provenance_log::Key` +//! - **Codec**: `multicodec::Codec` +//! - **Key**: `multikey::Multikey` +//! - **Signature**: `multisig::Multisig` (aliased as `bs::Signature`) +//! +//! # Example +//! +//! ```ignore +//! use bs::config::sync::{KeyManager, MultiSigner}; +//! +//! fn process_sync( +//! km: &(dyn KeyManager + Send + Sync), +//! signer: &(dyn MultiSigner + Send + Sync), +//! ) -> Result<(), E> { +//! // All concrete types are already specified in the trait bounds +//! // ... +//! } +//! ``` +pub use bs_traits::sync::{SyncGetKey, SyncPrepareEphemeralSigning, SyncSigner}; use bs_traits::EphemeralKey; use super::*; diff --git a/crates/bs/src/lib.rs b/crates/bs/src/lib.rs index 818d3d2..50b4e06 100644 --- a/crates/bs/src/lib.rs +++ b/crates/bs/src/lib.rs @@ -14,6 +14,10 @@ pub mod error; pub use error::Error; +/// BetterSign module for managing provenance logs +pub mod better_sign; +pub use better_sign::BetterSign; + /// bettersign operations pub mod ops; pub use ops::prelude::*; @@ -21,10 +25,14 @@ pub use ops::prelude::*; /// convenient export pub mod prelude { pub use super::*; + pub use multihash; + pub use multikey; } /// Opinionated configuation for the BetterSign library pub mod config; +// Re-export the concrete Signature type from config for convenience +pub use config::Signature; /// Resolver extension for bettersign pub mod resolver_ext; diff --git a/crates/bs/src/ops/open.rs b/crates/bs/src/ops/open.rs index ac6e904..89438fb 100644 --- a/crates/bs/src/ops/open.rs +++ b/crates/bs/src/ops/open.rs @@ -5,9 +5,11 @@ pub mod config; use std::num::NonZeroUsize; use crate::{ + config::asynchronous::{KeyManager, MultiSigner}, error::{BsCompatibleError, OpenError}, params::vlad::{FirstEntryKeyParams, VladParams}, update::{op, OpParams}, + Signature, }; pub use config::Config; use multicid::{cid, Cid, Vlad}; @@ -17,41 +19,81 @@ use multikey::{Multikey, Views}; use provenance_log::{entry, error::EntryError, Error as PlogError, Key, Log, OpId}; use tracing::debug; -/// Open a new provenance log based on the [Config] provided. -// -// To Open a Plog, the critical steps are: -// - First get the public key of the ephemeral first entry key -// - Add the public key of the ephemeral first entry key operation to `op_params` -// - Add ALL operations to the entry builder -// - Sign that operated entry using the ephemeral first entry key's one-time signing function -// - Finalize the Entry with the signature -// -// When the script runtime checks the first entry data (the Entry without the proof), against the -// first lock script, it will use the first entry key's public key to verify the signature. -pub fn open_plog( +/// Open a new provenance log based on the [Config] provided. (async) +pub async fn open_plog( config: &Config, - key_manager: &dyn crate::config::sync::KeyManager, - signer: &dyn crate::config::sync::MultiSigner, -) -> Result { + key_manager: &mut (dyn KeyManager + Send + Sync), + signer: &(dyn MultiSigner + Send + Sync), +) -> Result +where + E: BsCompatibleError + Send, +{ + open_plog_core(config, key_manager, signer).await +} + +/// Synchronous version of [open_plog] +#[cfg(feature = "sync")] +pub fn open_plog_sync( + config: &Config, + key_manager: &(dyn crate::config::sync::KeyManager + Send + Sync), + signer: &(dyn crate::config::sync::MultiSigner + Send + Sync), +) -> Result +where + E: BsCompatibleError + Send + Sync + 'static, +{ + use crate::config::adapters::{SyncToAsyncManager, SyncToAsyncSigner}; + + let mut key_manager_adapter = SyncToAsyncManager::new(key_manager); + let signer_adapter = SyncToAsyncSigner::new(signer); + + futures::executor::block_on(open_plog_impl( + config, + &mut key_manager_adapter, + &signer_adapter, + )) +} + +/// Core function to open a provenance log based on the [Config] provided. (async) +pub(crate) async fn open_plog_core( + config: &Config, + key_manager: &mut (dyn KeyManager + Send + Sync), + signer: &(dyn MultiSigner + Send + Sync), +) -> Result +where + E: BsCompatibleError + Send, +{ + open_plog_impl(config, key_manager, signer).await +} + +/// Internal implementation that works with base async traits (for adapters) +async fn open_plog_impl( + config: &Config, + key_manager: &mut KM, + signer: &S, +) -> Result +where + E: BsCompatibleError + Send, + KM: bs_traits::asyncro::AsyncKeyManager + + ?Sized, + S: bs_traits::asyncro::AsyncMultiSigner + + bs_traits::asyncro::AsyncSigner + + ?Sized, +{ // 0. Set up the list of ops let mut op_params = Vec::default(); // Process initial operations - config - .additional_ops() - .iter() - .try_for_each(|params| -> Result<(), E> { - match params { - p @ OpParams::KeyGen { .. } => { - let _ = load_key::(&mut op_params, p, key_manager)?; - } - p @ OpParams::CidGen { .. } => { - let _ = load_cid::(&mut op_params, p)?; - } - p => op_params.push(p.clone()), + for params in config.additional_ops().iter() { + match params { + p @ OpParams::KeyGen { .. } => { + let _ = load_key(&mut op_params, p, key_manager).await?; } - Ok(()) - })?; + p @ OpParams::CidGen { .. } => { + let _ = load_cid::(&mut op_params, p)?; + } + p => op_params.push(p.clone()), + } + } // 1. Extract VLAD parameters and prepare signing let (vlad_key_params, vlad_cid_params): (OpParams, OpParams) = @@ -59,7 +101,9 @@ pub fn open_plog( let (codec, threshold, limit) = extract_key_params::(&vlad_key_params)?; // get ephemeral public key and one time signing function - let (vlad_pubkey, sign_vlad) = signer.prepare_ephemeral_signing(&codec, threshold, limit)?; + let (vlad_pubkey, sign_vlad) = signer + .prepare_ephemeral_signing(&codec, threshold, limit) + .await?; let cid = load_cid::(&mut op_params, &vlad_cid_params)?; @@ -81,12 +125,17 @@ pub fn open_plog( Ok(multisig.into()) })?; + // Pass vlad to the caller for any pre-processing + key_manager.preprocess_vlad(&vlad).await?; + // 2. Extract entry key parameters and prepare signing let entrykey_params = &config.entrykey(); let (codec, threshold, limit) = extract_key_params::(entrykey_params)?; // Get the public key and signing function - let (entry_pubkey, sign_entry) = signer.prepare_ephemeral_signing(&codec, threshold, limit)?; + let (entry_pubkey, sign_entry) = signer + .prepare_ephemeral_signing(&codec, threshold, limit) + .await?; // 3. Add the entry public key operation to op_params if let OpParams::KeyGen { key, .. } = entrykey_params { @@ -97,7 +146,7 @@ pub fn open_plog( } // 4. Continue with other preparations - let _ = load_key::(&mut op_params, config.pubkey(), key_manager)?; + let _ = load_key(&mut op_params, config.pubkey(), key_manager).await?; let lock_script = config.lock_script().clone(); let unlock_script = config.unlock().clone(); @@ -182,13 +231,15 @@ fn extract_key_params( } } -fn load_key( +async fn load_key( ops: &mut Vec, params: &OpParams, - key_manager: &dyn crate::config::sync::KeyManager, + key_manager: &KM, ) -> Result where E: From + From + From, + KM: bs_traits::asyncro::AsyncKeyManager + + ?Sized, { debug!("load_key: {:?}", params); match params { @@ -200,7 +251,7 @@ where revoke, } => { // call back to generate the key - let mk = key_manager.get_key(key, codec, *threshold, *limit)?; + let mk = key_manager.get_key(key, codec, *threshold, *limit).await?; // get the public key let pk = if mk.attr_view()?.is_secret_key() { @@ -386,7 +437,8 @@ mod tests { let key_manager = InMemoryKeyManager::::default(); - let plog = open_plog(&config, &key_manager, &key_manager).expect("Failed to open plog"); + let plog = + open_plog_sync(&config, &key_manager, &key_manager).expect("Failed to open plog"); // log.first_lock should match assert_eq!(&plog.first_lock, config.first_lock()); diff --git a/crates/bs/src/ops/open/config.rs b/crates/bs/src/ops/open/config.rs index 31ac78b..07e6b45 100644 --- a/crates/bs/src/ops/open/config.rs +++ b/crates/bs/src/ops/open/config.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: FSL-1.1 - -use provenance_log::{key::key_paths::ValidatedKeyParams, Script}; +pub use provenance_log::{ + entry::Field, format_with_fields, key::key_paths::ValidatedKeyParams, Script, +}; use crate::{ params::vlad::{FirstEntryKeyParams, VladParams}, diff --git a/crates/bs/src/ops/update.rs b/crates/bs/src/ops/update.rs index 8281108..0f12f88 100644 --- a/crates/bs/src/ops/update.rs +++ b/crates/bs/src/ops/update.rs @@ -13,27 +13,57 @@ pub use config::Config; pub mod op_params; pub use op_params::OpParams; -use crate::error::UpdateError; +use crate::{ + config::asynchronous::{KeyManager, MultiSigner}, + error::{BsCompatibleError, UpdateError}, + Signature, +}; use multicid::{cid, Cid}; +use multicodec::Codec; use multihash::mh; use multikey::{Multikey, Views}; use provenance_log::{ entry::{self, Entry}, error::EntryError, - Error as PlogError, Lipmaa as _, Log, OpId, + Error as PlogError, Key, Lipmaa as _, Log, OpId, }; use std::{fs::read, path::Path}; use tracing::debug; -/// Updates a provenance log given the update config -pub fn update_plog( +/// Updates a provenance log given the update config (async) +pub async fn update_plog( + plog: &mut Log, + config: &Config, + key_manager: &(dyn KeyManager + Send + Sync), + signer: &(dyn MultiSigner + Send + Sync), +) -> Result +where + E: BsCompatibleError + + From + + From + + From + + From + + From + + From + + From + + ToString + + std::fmt::Debug + + Send, +{ + update_plog_core(plog, config, key_manager, signer).await +} + +/// Updates a provenance log given the update config (sync) +#[cfg(feature = "sync")] +pub fn update_plog_sync( plog: &mut Log, config: &Config, - key_manager: &dyn crate::config::sync::KeyManager, - signer: &dyn crate::config::sync::MultiSigner, + key_manager: &(dyn crate::config::sync::KeyManager + Send + Sync), + signer: &(dyn crate::config::sync::MultiSigner + Send + Sync), ) -> Result where - E: From + E: BsCompatibleError + + From + From + From + From @@ -41,30 +71,89 @@ where + From + From + ToString - + std::fmt::Debug, + + std::fmt::Debug + + Send + + Sync + + 'static, +{ + use crate::config::adapters::{SyncToAsyncManager, SyncToAsyncSigner}; + + let key_manager_adapter = SyncToAsyncManager::new(key_manager); + let signer_adapter = SyncToAsyncSigner::new(signer); + + futures::executor::block_on(update_plog_impl( + plog, + config, + &key_manager_adapter, + &signer_adapter, + )) +} + +pub(crate) async fn update_plog_core( + plog: &mut Log, + config: &Config, + key_manager: &(dyn KeyManager + Send + Sync), + signer: &(dyn MultiSigner + Send + Sync), +) -> Result +where + E: BsCompatibleError + + From + + From + + From + + From + + From + + From + + From + + ToString + + std::fmt::Debug + + Send, +{ + update_plog_impl(plog, config, key_manager, signer).await +} + +/// Internal implementation that works with base async traits (for adapters) +async fn update_plog_impl( + plog: &mut Log, + config: &Config, + key_manager: &KM, + signer: &S, +) -> Result +where + E: BsCompatibleError + + From + + From + + From + + From + + From + + From + + From + + ToString + + std::fmt::Debug + + Send, + KM: bs_traits::asyncro::AsyncKeyManager + + ?Sized, + S: bs_traits::asyncro::AsyncMultiSigner + + bs_traits::asyncro::AsyncSigner + + ?Sized, { // 0. Set up the list of ops we're going to add let mut op_params = Vec::default(); // go through the additional ops and generate CIDs and keys and adding the resulting op params // to the vec of op params - config - .additional_ops() - .iter() - .try_for_each(|params| -> Result<(), E> { - match params { - p @ OpParams::KeyGen { .. } => { - let _ = load_key::(&mut op_params, p, key_manager)?; - } - p @ OpParams::CidGen { .. } => { - let _ = load_cid(&mut op_params, p, |path| -> Result, E> { - read(path).map_err(E::from) - })?; - } - p => op_params.push(p.clone()), + for params in config.additional_ops().iter() { + match params { + p @ OpParams::KeyGen { .. } => { + let _ = load_key(&mut op_params, p, key_manager).await?; + } + p @ OpParams::CidGen { .. } => { + let _ = load_cid(&mut op_params, p, |path| -> Result, E> { + read(path).map_err(E::from) + })?; } - Ok(()) - })?; + p => op_params.push(p.clone()), + } + } // 1. validate the p.log and get the last entry and state let (_, last_entry, _kvp) = plog.verify().last().ok_or(UpdateError::NoLastEntry)??; @@ -78,7 +167,7 @@ where let entry_builder = entry::EntryBuilder::from(&last_entry); let mut mutable_entry = entry_builder.unlock(config.unlock().clone()).build(); - for lock in config.add_entry_lock_scripts() { + for lock in config.entry_lock_scripts() { mutable_entry.add_lock(lock); } @@ -136,6 +225,7 @@ where // Sign the entry let signature = signer .try_sign(entry_key_path, &entry_bytes) + .await .map_err(|e| PlogError::from(EntryError::SignFailed(e.to_string())))?; // Finalize the entry with the signature as proof @@ -147,13 +237,15 @@ where Ok(entry) } -fn load_key( +async fn load_key( ops: &mut Vec, params: &OpParams, - key_manager: &dyn crate::config::sync::KeyManager, + key_manager: &KM, ) -> Result where E: From + From + From, + KM: bs_traits::asyncro::AsyncKeyManager + + ?Sized, { debug!("load_key: {:?}", params); match params { @@ -165,7 +257,7 @@ where revoke, } => { // call back to generate the key - let mk = key_manager.get_key(key, codec, *threshold, *limit)?; + let mk = key_manager.get_key(key, codec, *threshold, *limit).await?; // get the public key let pk = if mk.attr_view()?.is_secret_key() { @@ -243,11 +335,11 @@ where #[cfg(test)] mod tests { use super::*; + use crate::open::{self, open_plog_sync}; use crate::params::{ anykey::PubkeyParams, vlad::{FirstEntryKeyParams, VladParams}, }; - use crate::{open, open_plog}; use bs_traits::sync::SyncGetKey; use bs_wallets::memory::InMemoryKeyManager; @@ -319,17 +411,17 @@ mod tests { let key_manager = InMemoryKeyManager::::default(); let mut plog = - open_plog(&open_config, &key_manager, &key_manager).expect("Failed to open plog"); + open_plog_sync(&open_config, &key_manager, &key_manager).expect("Failed to open plog"); // We need to generate PubkeyParams key in our wallet: - let old_pk = key_manager - .get_key( - &PubkeyParams::KEY_PATH.into(), - &pubkey_params.codec(), - pubkey_params.threshold(), - pubkey_params.limit(), - ) - .expect("Failed to create and store pubkey"); + let old_pk = SyncGetKey::get_key( + &key_manager, + &PubkeyParams::KEY_PATH.into(), + &pubkey_params.codec(), + pubkey_params.threshold(), + pubkey_params.limit(), + ) + .expect("Failed to create and store pubkey"); // 2. Update the p.log with a new entry // - add a lock Script @@ -352,7 +444,7 @@ mod tests { key: VladParams::::FIRST_ENTRY_KEY_PATH.into(), }]) // Entry lock scripts define conditions which must be met by the next entry in the plog for it to be valid. - .add_entry_lock_scripts(vec![Script::Code( + .with_entry_lock_scripts(vec![Script::Code( Key::try_from("/delegated/").unwrap(), delegated_lock, )]) @@ -361,7 +453,7 @@ mod tests { let prev = plog.head.clone(); // take config and use update method with TestKeyManager to update the log - update_plog(&mut plog, &update_cfg, &key_manager, &key_manager) + update_plog_sync(&mut plog, &update_cfg, &key_manager, &key_manager) .expect("Failed to update plog"); // plog head prev should match prev @@ -413,7 +505,7 @@ mod tests { ]) .build(); - update_plog(&mut plog, &key_rotation_config, &key_manager, &key_manager) + update_plog_sync(&mut plog, &key_rotation_config, &key_manager, &key_manager) .expect("Failed to rotate key"); // Update the key manager's path mapping diff --git a/crates/bs/src/ops/update/config.rs b/crates/bs/src/ops/update/config.rs index 6138e52..1a0cfea 100644 --- a/crates/bs/src/ops/update/config.rs +++ b/crates/bs/src/ops/update/config.rs @@ -32,7 +32,7 @@ pub struct Config { /// ); /// ``` #[builder(default = Vec::new())] - add_entry_lock_scripts: Vec