From 2dc99ccbfbd5140a65425aff627a09e21542a997 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 19:35:44 +0000 Subject: [PATCH 01/21] Support client or Lair signing --- .github/workflows/test.yml | 5 ++ Cargo.lock | 125 ++++++++++++++++++++++++++++++++-- Cargo.toml | 20 +++++- src/admin_websocket.rs | 39 ++++++++++- src/app_agent_websocket.rs | 59 ++++------------ src/lib.rs | 8 ++- src/signing.rs | 50 ++++++++++++++ src/signing/client_signing.rs | 37 ++++++++++ src/signing/lair_signing.rs | 33 +++++++++ 9 files changed, 317 insertions(+), 59 deletions(-) create mode 100644 src/signing.rs create mode 100644 src/signing/client_signing.rs create mode 100644 src/signing/lair_signing.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e29d844..952ada5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,6 +61,11 @@ jobs: - name: Run tests run: cargo test + + - name: Verify feature independence + run: | + cargo build --no-default-features --features "client_signing" + cargo build --no-default-features --features "lair_signing" - name: Setup tmate session if build and test run failed if: ${{ failure() }} diff --git a/Cargo.lock b/Cargo.lock index 6c26944..59dcba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -978,6 +978,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1234,6 +1240,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2 1.0.78", + "quote 1.0.35", + "syn 2.0.52", +] + [[package]] name = "darling" version = "0.10.2" @@ -1408,6 +1442,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1576,7 +1620,17 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature 2.2.0", ] [[package]] @@ -1585,14 +1639,29 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek", - "ed25519", + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", "rand 0.7.3", "serde", "sha2 0.9.9", "zeroize", ] +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek 4.1.2", + "ed25519 2.2.3", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.10.0" @@ -1800,6 +1869,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + [[package]] name = "filetime" version = "0.2.23" @@ -2601,7 +2676,8 @@ dependencies = [ "again", "anyhow", "arbitrary", - "ed25519-dalek", + "async-trait", + "ed25519-dalek 2.1.1", "event-emitter-rs", "futures", "holo_hash", @@ -2614,7 +2690,7 @@ dependencies = [ "holochain_zome_types", "kitsune_p2p_types", "lair_keystore_api", - "rand 0.7.3", + "rand 0.8.5", "serde", "tokio", "url 2.5.0", @@ -3754,7 +3830,7 @@ version = "0.3.0-beta-dev.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c5528ad3f1710db65bf8e4033a8c9d322b746ce4a1fd1a521f45f927c3338b" dependencies = [ - "ed25519-dalek", + "ed25519-dalek 1.0.1", "kitsune_p2p_bin_data", "kitsune_p2p_bootstrap", "kitsune_p2p_types", @@ -5039,12 +5115,28 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + [[package]] name = "polling" version = "2.8.0" @@ -6476,6 +6568,15 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "simba" version = "0.6.0" @@ -6581,6 +6682,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlformat" version = "0.1.8" @@ -7787,7 +7898,7 @@ name = "utilities" version = "0.1.0" dependencies = [ "arbitrary", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "holochain_client", "holochain_nonce", "holochain_zome_types", diff --git a/Cargo.toml b/Cargo.toml index 01e237c..673481c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,13 @@ holochain_zome_types = "0.3.0-beta-dev.25" [dependencies] again = "0.1" anyhow = "1.0" -ed25519-dalek = "1" +ed25519-dalek = "2.1" serde = "1.0.193" url = "2.2" event-emitter-rs = "0.1" futures = "0.3" +rand = { version = "0.8", optional = true } +async-trait = "0.1" holo_hash = { version = "0.3.0-beta-dev.20", features = ["encoding"] } holochain_conductor_api = "0.3.0-beta-dev.34" @@ -34,12 +36,24 @@ holochain_types = "0.3.0-beta-dev.31" holochain_nonce = "0.3.0-beta-dev.22" holochain_zome_types = { workspace = true } -lair_keystore_api = "0.4.0" +lair_keystore_api = { version = "0.4.0", optional = true } [dev-dependencies] arbitrary = "1.2" holochain = { version = "0.3.0-beta-dev.32", features = ["test_utils"] } -rand = "0.7" +rand = "0.8" tokio = { version = "1.3", features = ["full"] } utilities = { path = "utilities" } kitsune_p2p_types = "0.3.0-beta-dev.14" + +[features] +default = ["lair_signing", "client_signing"] + +lair_signing = [ + "dep:lair_keystore_api", +] + +client_signing = [ + "dep:rand", + "ed25519-dalek/rand_core", +] diff --git a/src/admin_websocket.rs b/src/admin_websocket.rs index 5594a67..e48484c 100644 --- a/src/admin_websocket.rs +++ b/src/admin_websocket.rs @@ -8,7 +8,7 @@ use holochain_types::{ prelude::{CellId, DeleteCloneCellPayload, InstallAppPayload, UpdateCoordinatorsPayload}, }; use holochain_websocket::{connect, WebsocketConfig, WebsocketReceiver, WebsocketSender}; -use holochain_zome_types::prelude::{DnaDef, GrantZomeCallCapabilityPayload, Record}; +use holochain_zome_types::{capability::GrantedFunctions, prelude::{DnaDef, GrantZomeCallCapabilityPayload, Record}}; use serde::{Deserialize, Serialize}; use url::Url; @@ -25,6 +25,12 @@ pub struct EnableAppResponse { pub errors: Vec<(CellId, String)>, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthorizeSigningCredentialsRequest { + pub cell_id: CellId, + pub functions: Option, +} + impl AdminWebsocket { pub async fn connect(admin_url: String) -> Result { let url = Url::parse(&admin_url).context("invalid ws:// URL")?; @@ -208,6 +214,37 @@ impl AdminWebsocket { } } + #[cfg(feature = "client_signing")] + pub async fn authorize_signing_credentials(&mut self, request: AuthorizeSigningCredentialsRequest) -> Result { + use std::collections::BTreeSet; + use holochain_zome_types::capability::{ZomeCallCapGrant, CAP_SECRET_BYTES}; + use rand::{rngs::OsRng, RngCore}; + + let mut csprng = OsRng; + let keypair = ed25519_dalek::SigningKey::generate(&mut csprng); + let public_key = keypair.verifying_key(); + let agent_pub_key_bytes = [132, 32, 36].iter().chain(public_key.as_bytes().into_iter()).chain(&[0, 0, 0, 0]).map(|x| *x).collect::>(); + let signing_agent_key = AgentPubKey::from_raw_39(agent_pub_key_bytes)?; + + let mut cap_secret = [0; CAP_SECRET_BYTES]; + csprng.fill_bytes(&mut cap_secret); + + self.grant_zome_call_capability(GrantZomeCallCapabilityPayload { + cell_id: request.cell_id, + cap_grant: ZomeCallCapGrant { + tag: "zome-call-signing-key".to_string(), + access: holochain_zome_types::capability::CapAccess::Assigned { secret: cap_secret.into(), assignees: BTreeSet::from([signing_agent_key.clone()]) }, + functions: request.functions.unwrap_or(GrantedFunctions::All), + } + }).await.map_err(|e| anyhow::anyhow!("Conductor API error: {:?}", e))?; + + Ok(crate::signing::client_signing::SigningCredentials { + signing_agent_key, + keypair, + cap_secret, + }) + } + async fn send(&mut self, msg: AdminRequest) -> ConductorApiResult { let response: AdminResponse = self .tx diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index 7419b22..e57c728 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -1,28 +1,29 @@ +use std::sync::Arc; + use anyhow::{anyhow, Result}; use holo_hash::AgentPubKey; -use holochain_conductor_api::{AppInfo, CellInfo, ProvisionedCell, ZomeCall}; +use holochain_conductor_api::{AppInfo, CellInfo, ProvisionedCell}; use holochain_nonce::fresh_nonce; use holochain_types::prelude::Signal; use holochain_zome_types::{ clone::ClonedCell, prelude::{ - CellId, ExternIO, FunctionName, RoleName, Signature, Timestamp, ZomeCallUnsigned, ZomeName, + CellId, ExternIO, FunctionName, RoleName, Timestamp, ZomeCallUnsigned, ZomeName, }, }; -use lair_keystore_api::LairClient; -use crate::{AppWebsocket, ConductorApiError, ConductorApiResult}; +use crate::{signing::{sign_zome_call, AgentSigner}, AppWebsocket, ConductorApiError, ConductorApiResult}; #[derive(Clone)] pub struct AppAgentWebsocket { pub my_pub_key: AgentPubKey, app_ws: AppWebsocket, app_info: AppInfo, - lair_client: LairClient, + signer: Arc>, } impl AppAgentWebsocket { - pub async fn connect(url: String, app_id: String, lair_client: LairClient) -> Result { + pub async fn connect(url: String, app_id: String, signer: Arc>) -> Result { let mut app_ws = AppWebsocket::connect(url).await?; let app_info = app_ws @@ -35,7 +36,7 @@ impl AppAgentWebsocket { my_pub_key: app_info.agent_pub_key.clone(), app_ws, app_info, - lair_client, + signer, }) } @@ -99,10 +100,10 @@ impl AppAgentWebsocket { nonce, }; - let signed_zome_call = sign_zome_call_with_client(zome_call_unsigned, &self.lair_client) - .await - .map_err(|err| crate::ConductorApiError::SignZomeCallError(err))?; - + let signed_zome_call = sign_zome_call(zome_call_unsigned, self.signer.clone()).await.map_err(|e| { + ConductorApiError::SignZomeCallError(e.to_string()) + })?; + let result = self.app_ws.call_zome(signed_zome_call).await?; Ok(result) @@ -163,39 +164,3 @@ fn get_base_role_name_from_clone_id(role_name: &RoleName) -> RoleName { .unwrap(), ) } - -/// Signs an unsigned zome call with the given LairClient -pub async fn sign_zome_call_with_client( - zome_call_unsigned: ZomeCallUnsigned, - client: &LairClient, -) -> Result { - // sign the zome call - let pub_key = zome_call_unsigned.provenance.clone(); - let mut pub_key_2 = [0; 32]; - pub_key_2.copy_from_slice(pub_key.get_raw_32()); - - let data_to_sign = zome_call_unsigned - .data_to_sign() - .map_err(|e| format!("Failed to get data to sign from unsigned zome call: {}", e))?; - - let sig = client - .sign_by_pub_key(pub_key_2.into(), None, data_to_sign) - .await - .map_err(|e| format!("Failed to sign zome call by pubkey: {}", e.str_kind()))?; - - let signature = Signature(*sig.0); - - let signed_zome_call = ZomeCall { - cell_id: zome_call_unsigned.cell_id, - zome_name: zome_call_unsigned.zome_name, - fn_name: zome_call_unsigned.fn_name, - payload: zome_call_unsigned.payload, - cap_secret: zome_call_unsigned.cap_secret, - provenance: zome_call_unsigned.provenance, - nonce: zome_call_unsigned.nonce, - expires_at: zome_call_unsigned.expires_at, - signature, - }; - - return Ok(signed_zome_call); -} diff --git a/src/lib.rs b/src/lib.rs index e020f01..f797ce8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,10 @@ mod admin_websocket; mod app_agent_websocket; mod app_websocket; mod error; +mod signing; pub use admin_websocket::{AdminWebsocket, EnableAppResponse}; -pub use app_agent_websocket::{sign_zome_call_with_client, AppAgentWebsocket}; +pub use app_agent_websocket::AppAgentWebsocket; pub use app_websocket::AppWebsocket; pub use error::{ConductorApiError, ConductorApiResult}; pub use holochain_conductor_api::{ @@ -14,3 +15,8 @@ pub use holochain_types::{ app::{InstallAppPayload, InstalledAppId}, dna::AgentPubKey, }; +pub use signing::AgentSigner; +#[cfg(feature = "client_signing")] +pub use signing::client_signing::{ClientAgentSigner, SigningCredentials}; +#[cfg(feature = "lair_signing")] +pub use signing::lair_signing::LairAgentSigner; diff --git a/src/signing.rs b/src/signing.rs new file mode 100644 index 0000000..95234f7 --- /dev/null +++ b/src/signing.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use anyhow::Result; +use holo_hash::AgentPubKey; +use holochain_conductor_api::ZomeCall; +use holochain_zome_types::{cell::CellId, dependencies::holochain_integrity_types::Signature, zome_io::ZomeCallUnsigned}; +use async_trait::async_trait; + +#[cfg(feature = "client_signing")] +pub(crate) mod client_signing; + +#[cfg(feature = "lair_signing")] +pub(crate) mod lair_signing; + +#[async_trait] +pub trait AgentSigner { + /// Sign the given data with the public key found in the agent id of the provenance. + async fn sign(&self, cell_id: &CellId, provenance: AgentPubKey, data_to_sign: Arc<[u8]>) -> Result; +} + +/// Signs an unsigned zome call using the provided signing implementation +pub(crate) async fn sign_zome_call( + zome_call_unsigned: ZomeCallUnsigned, + signer: Arc>, +) -> Result { + let pub_key = zome_call_unsigned.provenance.clone(); + let mut pub_key_2 = [0; 32]; + pub_key_2.copy_from_slice(pub_key.get_raw_32()); + + let data_to_sign = zome_call_unsigned + .data_to_sign() + .map_err(|e| anyhow::anyhow!("Failed to get data to sign from unsigned zome call: {}", e))?; + + let signature = signer.sign(&zome_call_unsigned.cell_id, pub_key, data_to_sign).await?; + + // let signature = Signature(*sig.0); + + Ok(ZomeCall { + cell_id: zome_call_unsigned.cell_id, + zome_name: zome_call_unsigned.zome_name, + fn_name: zome_call_unsigned.fn_name, + payload: zome_call_unsigned.payload, + cap_secret: zome_call_unsigned.cap_secret, + provenance: zome_call_unsigned.provenance, + nonce: zome_call_unsigned.nonce, + expires_at: zome_call_unsigned.expires_at, + signature, + }) +} + diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs new file mode 100644 index 0000000..5815154 --- /dev/null +++ b/src/signing/client_signing.rs @@ -0,0 +1,37 @@ +use std::{collections::HashMap, sync::Arc}; +use async_trait::async_trait; +use ed25519_dalek::Signer; +use holo_hash::AgentPubKey; +use holochain_zome_types::{cell::CellId, dependencies::holochain_integrity_types::Signature}; +use super::AgentSigner; + +pub struct SigningCredentials { + pub signing_agent_key: holo_hash::AgentPubKey, + pub keypair: ed25519_dalek::SigningKey, + pub cap_secret: [u8; holochain_zome_types::capability::CAP_SECRET_BYTES], +} + +pub struct ClientAgentSigner { + credentials: HashMap, +} + +impl ClientAgentSigner { + pub fn new() -> Self { + Self { + credentials: HashMap::new(), + } + } + + pub fn add_credentials(&mut self, cell_id: CellId, credentials: SigningCredentials) { + self.credentials.insert(cell_id, credentials); + } +} + +#[async_trait] +impl AgentSigner for ClientAgentSigner { + async fn sign(&self, cell_id: &CellId, _provenance: AgentPubKey, data_to_sign: Arc<[u8]>) -> Result { + let credentials = self.credentials.get(cell_id).ok_or_else(|| anyhow::anyhow!("No credentials found for cell: {:?}", cell_id))?; + let signature = credentials.keypair.try_sign(&data_to_sign)?; + Ok(Signature(signature.to_bytes())) + } +} diff --git a/src/signing/lair_signing.rs b/src/signing/lair_signing.rs new file mode 100644 index 0000000..eb6be29 --- /dev/null +++ b/src/signing/lair_signing.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; +use anyhow::Result; +use holo_hash::AgentPubKey; +use holochain_zome_types::{cell::CellId, dependencies::holochain_integrity_types::Signature}; +use lair_keystore_api::LairClient; +use async_trait::async_trait; +use crate::AgentSigner; + + +pub struct LairAgentSigner { + lair_client: Arc, +} + +impl LairAgentSigner { + pub fn new(lair_client: Arc) -> Self { + Self { lair_client } + } +} + +#[async_trait] +impl AgentSigner for LairAgentSigner { + async fn sign(&self, _cell_id: &CellId, provenance: AgentPubKey, data_to_sign: Arc<[u8]>) -> Result { + let public_key: [u8; 32] = provenance.get_raw_32().try_into()?; + + let signature = self.lair_client.sign_by_pub_key( + public_key.into(), + None, + data_to_sign, + ).await?; + + Ok(Signature(*signature.0)) + } +} From 3191c13cc85c21075ebc328b8287794503f4f35e Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 20:42:29 +0000 Subject: [PATCH 02/21] Flake update --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 5160e9f..1a7cfcb 100644 --- a/flake.lock +++ b/flake.lock @@ -190,11 +190,11 @@ ] }, "locked": { - "lastModified": 1709216689, - "narHash": "sha256-l7MXlWkjTHCj078ixGMCHP8UJ42wTxgDoFwHtCGxu8o=", + "lastModified": 1709294263, + "narHash": "sha256-jg/o3NjiXwKHOOTyWMeZIOOlP05IVKZYAuwLKxJDuAc=", "owner": "holochain", "repo": "holochain", - "rev": "7019aa187a0c0fd6dae44394b0ddad4a52e8062c", + "rev": "4540acace814504dacc41a6433e17f157749004f", "type": "github" }, "original": { @@ -332,11 +332,11 @@ ] }, "locked": { - "lastModified": 1709172595, - "narHash": "sha256-0oYeE5VkhnPA7YBl+0Utq2cYoHcfsEhSGwraCa27Vs8=", + "lastModified": 1709259239, + "narHash": "sha256-MbrpgqpvUND7+UnOSLazrAMj0+zle16RRiOKTtjBefw=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "72fa0217f76020ad3aeb2dd9dd72490905b23b6f", + "rev": "0e031ddb3f5a339dc6eda93d271ae43618b14eec", "type": "github" }, "original": { @@ -386,11 +386,11 @@ }, "locked": { "dir": "versions/weekly", - "lastModified": 1709216689, - "narHash": "sha256-l7MXlWkjTHCj078ixGMCHP8UJ42wTxgDoFwHtCGxu8o=", + "lastModified": 1709294263, + "narHash": "sha256-jg/o3NjiXwKHOOTyWMeZIOOlP05IVKZYAuwLKxJDuAc=", "owner": "holochain", "repo": "holochain", - "rev": "7019aa187a0c0fd6dae44394b0ddad4a52e8062c", + "rev": "4540acace814504dacc41a6433e17f157749004f", "type": "github" }, "original": { From c5eb309ba4b4fe9ca47fbcc421d671a247764f14 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 22:56:17 +0000 Subject: [PATCH 03/21] Signing --- .github/workflows/test.yml | 6 ++ Cargo.lock | 13 ----- Cargo.toml | 7 +-- src/admin_websocket.rs | 32 +++++++---- src/app_agent_websocket.rs | 102 +++++++++++++++++++++++++++------- src/lib.rs | 4 +- src/signing.rs | 31 +++++++---- src/signing/client_signing.rs | 47 ++++++++++++++-- src/signing/lair_signing.rs | 45 +++++++++++---- tests/admin.rs | 39 +++++++++---- tests/app.rs | 39 +++++++++---- tests/clone_cell.rs | 32 ++++++----- utilities/Cargo.toml | 13 ----- utilities/src/lib.rs | 85 ---------------------------- 14 files changed, 281 insertions(+), 214 deletions(-) delete mode 100644 utilities/Cargo.toml delete mode 100644 utilities/src/lib.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 952ada5..73fc4ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,12 @@ jobs: - name: Build client run: cargo build -p holochain_client + - name: Lint + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Check formatting + run: cargo fmt --all --check + - name: Run tests run: cargo test diff --git a/Cargo.lock b/Cargo.lock index 59dcba2..824d468 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2694,7 +2694,6 @@ dependencies = [ "serde", "tokio", "url 2.5.0", - "utilities", ] [[package]] @@ -7893,18 +7892,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "utilities" -version = "0.1.0" -dependencies = [ - "arbitrary", - "ed25519-dalek 1.0.1", - "holochain_client", - "holochain_nonce", - "holochain_zome_types", - "rand 0.7.3", -] - [[package]] name = "uuid" version = "0.7.4" diff --git a/Cargo.toml b/Cargo.toml index 673481c..616a93e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,10 @@ resolver = "2" version = "0.5.0-dev.28" [workspace] -members = ["fixture/zomes/foo", "utilities"] +members = ["fixture/zomes/foo"] [workspace.dependencies] -holochain_zome_types = "0.3.0-beta-dev.25" +holochain_zome_types = "0.3.0-beta-dev.28" [dependencies] again = "0.1" @@ -40,10 +40,9 @@ lair_keystore_api = { version = "0.4.0", optional = true } [dev-dependencies] arbitrary = "1.2" -holochain = { version = "0.3.0-beta-dev.32", features = ["test_utils"] } +holochain = { version = "0.3.0-beta-dev.38", features = ["test_utils"] } rand = "0.8" tokio = { version = "1.3", features = ["full"] } -utilities = { path = "utilities" } kitsune_p2p_types = "0.3.0-beta-dev.14" [features] diff --git a/src/admin_websocket.rs b/src/admin_websocket.rs index e48484c..1f27f52 100644 --- a/src/admin_websocket.rs +++ b/src/admin_websocket.rs @@ -8,7 +8,10 @@ use holochain_types::{ prelude::{CellId, DeleteCloneCellPayload, InstallAppPayload, UpdateCoordinatorsPayload}, }; use holochain_websocket::{connect, WebsocketConfig, WebsocketReceiver, WebsocketSender}; -use holochain_zome_types::{capability::GrantedFunctions, prelude::{DnaDef, GrantZomeCallCapabilityPayload, Record}}; +use holochain_zome_types::{ + capability::GrantedFunctions, + prelude::{DnaDef, GrantZomeCallCapabilityPayload, Record}, +}; use serde::{Deserialize, Serialize}; use url::Url; @@ -26,7 +29,7 @@ pub struct EnableAppResponse { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct AuthorizeSigningCredentialsRequest { +pub struct AuthorizeSigningCredentialsPayload { pub cell_id: CellId, pub functions: Option, } @@ -215,33 +218,42 @@ impl AdminWebsocket { } #[cfg(feature = "client_signing")] - pub async fn authorize_signing_credentials(&mut self, request: AuthorizeSigningCredentialsRequest) -> Result { - use std::collections::BTreeSet; + pub async fn authorize_signing_credentials( + &mut self, + request: AuthorizeSigningCredentialsPayload, + ) -> Result { use holochain_zome_types::capability::{ZomeCallCapGrant, CAP_SECRET_BYTES}; use rand::{rngs::OsRng, RngCore}; + use std::collections::BTreeSet; let mut csprng = OsRng; let keypair = ed25519_dalek::SigningKey::generate(&mut csprng); let public_key = keypair.verifying_key(); - let agent_pub_key_bytes = [132, 32, 36].iter().chain(public_key.as_bytes().into_iter()).chain(&[0, 0, 0, 0]).map(|x| *x).collect::>(); - let signing_agent_key = AgentPubKey::from_raw_39(agent_pub_key_bytes)?; + println!("Using public key {:?}", public_key.as_bytes().to_vec()); + let signing_agent_key = AgentPubKey::from_raw_32(public_key.as_bytes().to_vec()); let mut cap_secret = [0; CAP_SECRET_BYTES]; csprng.fill_bytes(&mut cap_secret); + println!("Cap secret {:?}", cap_secret.to_vec()); self.grant_zome_call_capability(GrantZomeCallCapabilityPayload { cell_id: request.cell_id, cap_grant: ZomeCallCapGrant { tag: "zome-call-signing-key".to_string(), - access: holochain_zome_types::capability::CapAccess::Assigned { secret: cap_secret.into(), assignees: BTreeSet::from([signing_agent_key.clone()]) }, + access: holochain_zome_types::capability::CapAccess::Assigned { + secret: cap_secret.into(), + assignees: BTreeSet::from([signing_agent_key.clone()]), + }, functions: request.functions.unwrap_or(GrantedFunctions::All), - } - }).await.map_err(|e| anyhow::anyhow!("Conductor API error: {:?}", e))?; + }, + }) + .await + .map_err(|e| anyhow::anyhow!("Conductor API error: {:?}", e))?; Ok(crate::signing::client_signing::SigningCredentials { signing_agent_key, keypair, - cap_secret, + cap_secret: cap_secret.into(), }) } diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index e57c728..9d03b0e 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -1,18 +1,20 @@ -use std::sync::Arc; +use std::{ops::DerefMut, sync::Arc}; use anyhow::{anyhow, Result}; use holo_hash::AgentPubKey; use holochain_conductor_api::{AppInfo, CellInfo, ProvisionedCell}; use holochain_nonce::fresh_nonce; -use holochain_types::prelude::Signal; +use holochain_types::{app::InstalledAppId, prelude::Signal}; use holochain_zome_types::{ - clone::ClonedCell, - prelude::{ - CellId, ExternIO, FunctionName, RoleName, Timestamp, ZomeCallUnsigned, ZomeName, - }, + clone::{ClonedCell, CloneCellId}, + prelude::{CellId, ExternIO, FunctionName, RoleName, Timestamp, ZomeCallUnsigned, ZomeName}, }; +use std::ops::Deref; -use crate::{signing::{sign_zome_call, AgentSigner}, AppWebsocket, ConductorApiError, ConductorApiResult}; +use crate::{ + signing::{sign_zome_call, AgentSigner}, + AppWebsocket, ConductorApiError, ConductorApiResult, +}; #[derive(Clone)] pub struct AppAgentWebsocket { @@ -23,9 +25,20 @@ pub struct AppAgentWebsocket { } impl AppAgentWebsocket { - pub async fn connect(url: String, app_id: String, signer: Arc>) -> Result { - let mut app_ws = AppWebsocket::connect(url).await?; + pub async fn connect( + url: String, + app_id: InstalledAppId, + signer: Arc>, + ) -> Result { + let app_ws = AppWebsocket::connect(url).await?; + AppAgentWebsocket::from_existing(app_ws, app_id, signer).await + } + pub async fn from_existing( + mut app_ws: AppWebsocket, + app_id: InstalledAppId, + signer: Arc>, + ) -> Result { let app_info = app_ws .app_info(app_id.clone()) .await @@ -77,33 +90,40 @@ impl AppAgentWebsocket { pub async fn call_zome( &mut self, - role_name: RoleName, + target: ZomeCallTarget, zome_name: ZomeName, fn_name: FunctionName, payload: ExternIO, ) -> ConductorApiResult { - let cell_id = self.get_cell_id_from_role_name(&role_name)?; - - let agent_pub_key = self.app_info.agent_pub_key.clone(); + let cell_id = match target { + ZomeCallTarget::CellId(cell_id) => cell_id, + ZomeCallTarget::RoleName(role_name) => self.get_cell_id_from_role_name(&role_name)?, + ZomeCallTarget::CloneId(clone_id) => { + match clone_id { + CloneCellId::CellId(cell_id) => cell_id, + CloneCellId::CloneId(clone_id) => self.get_cell_id_from_role_name(&clone_id.0)?, + } + } + }; let (nonce, expires_at) = fresh_nonce(Timestamp::now()) .map_err(|err| crate::ConductorApiError::FreshNonceError(err))?; let zome_call_unsigned = ZomeCallUnsigned { - provenance: agent_pub_key, - cell_id, + provenance: self.signer.get_provenance(&cell_id).ok_or(ConductorApiError::SignZomeCallError("Provenance not found".to_string()))?, + cap_secret: self.signer.get_cap_secret(&cell_id), + cell_id: cell_id.clone(), zome_name, fn_name, payload, - cap_secret: None, expires_at, nonce, }; - let signed_zome_call = sign_zome_call(zome_call_unsigned, self.signer.clone()).await.map_err(|e| { - ConductorApiError::SignZomeCallError(e.to_string()) - })?; - + let signed_zome_call = sign_zome_call(zome_call_unsigned, self.signer.clone()) + .await + .map_err(|e| ConductorApiError::SignZomeCallError(e.to_string()))?; + let result = self.app_ws.call_zome(signed_zome_call).await?; Ok(result) @@ -148,6 +168,33 @@ impl AppAgentWebsocket { } } +pub enum ZomeCallTarget { + CellId(CellId), + /// You can call by role name for provisioned cells but any clone cells you create after + /// creating the [AppAgentWebsocket] will need to be called by [ZomeCallTarget::CellId]. + RoleName(RoleName), + /// Any clone cells you create after creating the [AppAgentWebsocket] will need to be called by [ZomeCallTarget::CellId]. + CloneId(CloneCellId), +} + +impl From for ZomeCallTarget { + fn from(cell_id: CellId) -> Self { + ZomeCallTarget::CellId(cell_id) + } +} + +impl From for ZomeCallTarget { + fn from(role_name: RoleName) -> Self { + ZomeCallTarget::RoleName(role_name) + } +} + +impl From for ZomeCallTarget { + fn from(clone_id: CloneCellId) -> Self { + ZomeCallTarget::CloneId(clone_id) + } +} + fn is_clone_id(role_name: &RoleName) -> bool { role_name.as_str().contains(".") } @@ -164,3 +211,18 @@ fn get_base_role_name_from_clone_id(role_name: &RoleName) -> RoleName { .unwrap(), ) } + +/// Make the [AppWebsocket] functionality available through the [AppAgentWebsocket] +impl Deref for AppAgentWebsocket { + type Target = AppWebsocket; + + fn deref(&self) -> &Self::Target { + &self.app_ws + } +} + +impl DerefMut for AppAgentWebsocket { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.app_ws + } +} diff --git a/src/lib.rs b/src/lib.rs index f797ce8..26fdde5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ mod app_websocket; mod error; mod signing; -pub use admin_websocket::{AdminWebsocket, EnableAppResponse}; +pub use admin_websocket::{AdminWebsocket, AuthorizeSigningCredentialsPayload, EnableAppResponse}; pub use app_agent_websocket::AppAgentWebsocket; pub use app_websocket::AppWebsocket; pub use error::{ConductorApiError, ConductorApiResult}; @@ -15,8 +15,8 @@ pub use holochain_types::{ app::{InstallAppPayload, InstalledAppId}, dna::AgentPubKey, }; -pub use signing::AgentSigner; #[cfg(feature = "client_signing")] pub use signing::client_signing::{ClientAgentSigner, SigningCredentials}; #[cfg(feature = "lair_signing")] pub use signing::lair_signing::LairAgentSigner; +pub use signing::AgentSigner; diff --git a/src/signing.rs b/src/signing.rs index 95234f7..8c72d09 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -1,10 +1,12 @@ use std::sync::Arc; use anyhow::Result; +use async_trait::async_trait; use holo_hash::AgentPubKey; use holochain_conductor_api::ZomeCall; -use holochain_zome_types::{cell::CellId, dependencies::holochain_integrity_types::Signature, zome_io::ZomeCallUnsigned}; -use async_trait::async_trait; +use holochain_zome_types::{ + capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, zome_io::ZomeCallUnsigned +}; #[cfg(feature = "client_signing")] pub(crate) mod client_signing; @@ -15,7 +17,17 @@ pub(crate) mod lair_signing; #[async_trait] pub trait AgentSigner { /// Sign the given data with the public key found in the agent id of the provenance. - async fn sign(&self, cell_id: &CellId, provenance: AgentPubKey, data_to_sign: Arc<[u8]>) -> Result; + async fn sign( + &self, + cell_id: &CellId, + provenance: AgentPubKey, + data_to_sign: Arc<[u8]>, + ) -> Result; + + fn get_provenance(&self, cell_id: &CellId) -> Option; + + /// Get the capability secret for the given `cell_id` if it exists. + fn get_cap_secret(&self, cell_id: &CellId) -> Option; } /// Signs an unsigned zome call using the provided signing implementation @@ -27,13 +39,13 @@ pub(crate) async fn sign_zome_call( let mut pub_key_2 = [0; 32]; pub_key_2.copy_from_slice(pub_key.get_raw_32()); - let data_to_sign = zome_call_unsigned - .data_to_sign() - .map_err(|e| anyhow::anyhow!("Failed to get data to sign from unsigned zome call: {}", e))?; - - let signature = signer.sign(&zome_call_unsigned.cell_id, pub_key, data_to_sign).await?; + let data_to_sign = zome_call_unsigned.data_to_sign().map_err(|e| { + anyhow::anyhow!("Failed to get data to sign from unsigned zome call: {}", e) + })?; - // let signature = Signature(*sig.0); + let signature = signer + .sign(&zome_call_unsigned.cell_id, pub_key, data_to_sign) + .await?; Ok(ZomeCall { cell_id: zome_call_unsigned.cell_id, @@ -47,4 +59,3 @@ pub(crate) async fn sign_zome_call( signature, }) } - diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 5815154..8fc037b 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -1,16 +1,26 @@ -use std::{collections::HashMap, sync::Arc}; +use super::AgentSigner; use async_trait::async_trait; use ed25519_dalek::Signer; use holo_hash::AgentPubKey; -use holochain_zome_types::{cell::CellId, dependencies::holochain_integrity_types::Signature}; -use super::AgentSigner; +use holochain_zome_types::{capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature}; +use std::{collections::HashMap, sync::Arc}; pub struct SigningCredentials { pub signing_agent_key: holo_hash::AgentPubKey, pub keypair: ed25519_dalek::SigningKey, - pub cap_secret: [u8; holochain_zome_types::capability::CAP_SECRET_BYTES], + pub cap_secret: CapSecret, +} + +/// Custom debug implementation which won't attempt to print the `cap_secret` or `keypair` +impl std::fmt::Debug for SigningCredentials { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SigningCredentials") + .field("signing_agent_key", &self.signing_agent_key) + .finish() + } } +#[derive(Debug, Default)] pub struct ClientAgentSigner { credentials: HashMap, } @@ -29,9 +39,34 @@ impl ClientAgentSigner { #[async_trait] impl AgentSigner for ClientAgentSigner { - async fn sign(&self, cell_id: &CellId, _provenance: AgentPubKey, data_to_sign: Arc<[u8]>) -> Result { - let credentials = self.credentials.get(cell_id).ok_or_else(|| anyhow::anyhow!("No credentials found for cell: {:?}", cell_id))?; + async fn sign( + &self, + cell_id: &CellId, + _provenance: AgentPubKey, + data_to_sign: Arc<[u8]>, + ) -> Result { + let credentials = self + .credentials + .get(cell_id) + .ok_or_else(|| anyhow::anyhow!("No credentials found for cell: {:?}", cell_id))?; + println!("Using credentials: {:?}", credentials); let signature = credentials.keypair.try_sign(&data_to_sign)?; + println!("Signature: {:?}", signature.to_bytes()); Ok(Signature(signature.to_bytes())) } + + fn get_provenance(&self, cell_id: &CellId) -> Option { + self.credentials.get(cell_id).map(|c| c.signing_agent_key.clone()) + } + + fn get_cap_secret(&self, cell_id: &CellId) -> Option { + self.credentials.get(cell_id).map(|c| c.cap_secret).clone() + } +} + +/// Convert the ClientAgentSigner into an Arc> +impl From for Arc> { + fn from(cas: ClientAgentSigner) -> Self { + Arc::new(Box::new(cas)) + } } diff --git a/src/signing/lair_signing.rs b/src/signing/lair_signing.rs index eb6be29..7fffbc6 100644 --- a/src/signing/lair_signing.rs +++ b/src/signing/lair_signing.rs @@ -1,33 +1,54 @@ -use std::sync::Arc; +use crate::AgentSigner; use anyhow::Result; +use async_trait::async_trait; use holo_hash::AgentPubKey; -use holochain_zome_types::{cell::CellId, dependencies::holochain_integrity_types::Signature}; +use holochain_zome_types::{capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature}; use lair_keystore_api::LairClient; -use async_trait::async_trait; -use crate::AgentSigner; - +use std::sync::Arc; +use std::collections::HashMap; pub struct LairAgentSigner { lair_client: Arc, + credentials: HashMap, } impl LairAgentSigner { pub fn new(lair_client: Arc) -> Self { - Self { lair_client } + Self { lair_client, credentials: HashMap::new() } + } + + /// Add credentials for a cell to the signer. + /// The provenance should be the `agent_pub_key` that the cell is running as. + pub fn add_credentials(&mut self, cell_id: CellId, provenance: AgentPubKey) { + self.credentials.insert(cell_id, provenance); } } #[async_trait] impl AgentSigner for LairAgentSigner { - async fn sign(&self, _cell_id: &CellId, provenance: AgentPubKey, data_to_sign: Arc<[u8]>) -> Result { + async fn sign( + &self, + _cell_id: &CellId, + provenance: AgentPubKey, + data_to_sign: Arc<[u8]>, + ) -> Result { let public_key: [u8; 32] = provenance.get_raw_32().try_into()?; - let signature = self.lair_client.sign_by_pub_key( - public_key.into(), - None, - data_to_sign, - ).await?; + let signature = self + .lair_client + .sign_by_pub_key(public_key.into(), None, data_to_sign) + .await?; Ok(Signature(*signature.0)) } + + fn get_provenance(&self, cell_id: &CellId) -> Option { + self.credentials.get(cell_id).cloned() + } + + /// Not used with Lair signing. If you have access to Lair then you don't need to prove you + // are supposed to have access to a specific key pair. + fn get_cap_secret(&self, _cell_id: &CellId) -> Option { + None + } } diff --git a/tests/admin.rs b/tests/admin.rs index 57d949d..93d4b9d 100644 --- a/tests/admin.rs +++ b/tests/admin.rs @@ -1,10 +1,12 @@ use holochain::test_utils::itertools::Itertools; use holochain::{prelude::AppBundleSource, sweettest::SweetConductor}; -use holochain_client::{AdminWebsocket, AppWebsocket, InstallAppPayload, InstalledAppId}; +use holochain_client::{ + AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, + ClientAgentSigner, InstallAppPayload, InstalledAppId, +}; use holochain_conductor_api::{CellInfo, StorageBlob}; use holochain_zome_types::prelude::ExternIO; use std::{collections::HashMap, path::PathBuf}; -use utilities::{authorize_signing_credentials, sign_zome_call}; #[tokio::test(flavor = "multi_thread")] async fn app_interfaces() { @@ -43,7 +45,7 @@ async fn signed_zome_call() { let mut app_ws = AppWebsocket::connect(format!("ws://localhost:{}", app_ws_port)) .await .unwrap(); - let installed_app = app_ws.app_info(app_id).await.unwrap().unwrap(); + let installed_app = app_ws.app_info(app_id.clone()).await.unwrap().unwrap(); let cells = installed_app.cell_info.into_values().next().unwrap(); let cell_id = match cells[0].clone() { @@ -56,16 +58,29 @@ async fn signed_zome_call() { const TEST_ZOME_NAME: &str = "foo"; const TEST_FN_NAME: &str = "foo"; - let signing_credentials = authorize_signing_credentials(&mut admin_ws, &cell_id).await; - let signed_zome_call = sign_zome_call( - &cell_id, - &TEST_ZOME_NAME, - &TEST_FN_NAME, - &signing_credentials, - ) - .await; + let mut signer = ClientAgentSigner::default(); + let credentials = admin_ws + .authorize_signing_credentials(AuthorizeSigningCredentialsPayload { + cell_id: cell_id.clone(), + functions: None, + }) + .await + .unwrap(); + signer.add_credentials(cell_id.clone(), credentials); + + let mut app_ws = AppAgentWebsocket::from_existing(app_ws, app_id, signer.into()) + .await + .unwrap(); - let response = app_ws.call_zome(signed_zome_call).await.unwrap(); + let response = app_ws + .call_zome( + cell_id.into(), + TEST_ZOME_NAME.into(), + TEST_FN_NAME.into(), + ExternIO::encode(()).unwrap(), + ) + .await + .unwrap(); assert_eq!( ExternIO::decode::(&response).unwrap(), TEST_FN_NAME.to_string() diff --git a/tests/app.rs b/tests/app.rs index 617b443..6eb17f8 100644 --- a/tests/app.rs +++ b/tests/app.rs @@ -2,7 +2,10 @@ use holochain::{ prelude::{AppBundleSource, NetworkInfoRequestPayload, Signal}, sweettest::SweetConductor, }; -use holochain_client::{AdminWebsocket, AppWebsocket, InstallAppPayload, InstalledAppId}; +use holochain_client::{ + AdminWebsocket, AppAgentWebsocket, AppWebsocket, InstallAppPayload, InstalledAppId, + ClientAgentSigner, AuthorizeSigningCredentialsPayload +}; use holochain_conductor_api::{CellInfo, NetworkInfo}; use kitsune_p2p_types::fetch_pool::FetchPoolInfo; use serde::{Deserialize, Serialize}; @@ -11,7 +14,7 @@ use std::{ path::PathBuf, sync::{Arc, Barrier}, }; -use utilities::{authorize_signing_credentials, sign_zome_call}; +use holochain_zome_types::zome_io::ExternIO; #[tokio::test(flavor = "multi_thread")] async fn network_info() { @@ -99,7 +102,7 @@ async fn handle_signal() { .await .unwrap(); - let installed_app = app_ws.app_info(app_id).await.unwrap().unwrap(); + let installed_app = app_ws.app_info(app_id.clone()).await.unwrap().unwrap(); let cells = installed_app.cell_info.into_values().next().unwrap(); let cell_id = match cells[0].clone() { @@ -112,14 +115,18 @@ async fn handle_signal() { const TEST_ZOME_NAME: &str = "foo"; const TEST_FN_NAME: &str = "emitter"; - let signing_credentials = authorize_signing_credentials(&mut admin_ws, &cell_id).await; - let signed_zome_call = sign_zome_call( - &cell_id, - &TEST_ZOME_NAME, - &TEST_FN_NAME, - &signing_credentials, - ) - .await; + let mut signer = ClientAgentSigner::default(); + let credentials = admin_ws + .authorize_signing_credentials(AuthorizeSigningCredentialsPayload { + cell_id: cell_id.clone(), + functions: None, + }) + .await + .unwrap(); + signer.add_credentials(cell_id.clone(), credentials); + + let mut app_ws = + AppAgentWebsocket::from_existing(app_ws, app_id.clone(), signer.into()).await.unwrap(); let barrier = Arc::new(Barrier::new(2)); let barrier_clone = barrier.clone(); @@ -136,7 +143,15 @@ async fn handle_signal() { .await .unwrap(); - let _response = app_ws.call_zome(signed_zome_call).await.unwrap(); + app_ws + .call_zome( + cell_id.into(), + TEST_ZOME_NAME.into(), + TEST_FN_NAME.into(), + ExternIO::encode(()).unwrap(), + ) + .await + .unwrap(); barrier.wait(); } diff --git a/tests/clone_cell.rs b/tests/clone_cell.rs index 85a92d8..81d57d1 100644 --- a/tests/clone_cell.rs +++ b/tests/clone_cell.rs @@ -4,12 +4,11 @@ use holochain::{ prelude::{DeleteCloneCellPayload, DisableCloneCellPayload, EnableCloneCellPayload}, sweettest::SweetConductor, }; -use holochain_client::{AdminWebsocket, AppWebsocket, InstallAppPayload}; +use holochain_client::{AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, ClientAgentSigner, InstallAppPayload}; use holochain_types::prelude::{ AppBundleSource, CloneCellId, CloneId, CreateCloneCellPayload, DnaModifiersOpt, InstalledAppId, }; -use holochain_zome_types::prelude::RoleName; -use utilities::{authorize_signing_credentials, sign_zome_call}; +use holochain_zome_types::{dependencies::holochain_integrity_types::ExternIO, prelude::RoleName}; #[tokio::test(flavor = "multi_thread")] async fn clone_cell_management() { @@ -53,14 +52,23 @@ async fn clone_cell_management() { }; let cell_id = clone_cell.cell_id.clone(); - let signing_credentials = authorize_signing_credentials(&mut admin_ws, &cell_id).await; + let mut signer = ClientAgentSigner::default(); + let credentials = admin_ws + .authorize_signing_credentials(AuthorizeSigningCredentialsPayload { + cell_id: cell_id.clone(), + functions: None, + }) + .await + .unwrap(); + signer.add_credentials(cell_id.clone(), credentials); + let mut app_ws = AppAgentWebsocket::from_existing(app_ws, app_id.clone(), signer.into()).await.unwrap(); + const TEST_ZOME_NAME: &str = "foo"; const TEST_FN_NAME: &str = "foo"; - let signed_zome_call = - sign_zome_call(&cell_id, TEST_ZOME_NAME, TEST_FN_NAME, &signing_credentials).await; + // call clone cell should succeed - let response = app_ws.call_zome(signed_zome_call).await.unwrap(); + let response = app_ws.call_zome(cell_id.clone().into(), TEST_ZOME_NAME.into(), TEST_FN_NAME.into(), ExternIO::encode(()).unwrap()).await.unwrap(); assert_eq!(response.decode::().unwrap(), "foo"); // disable clone cell @@ -72,11 +80,8 @@ async fn clone_cell_management() { .await .unwrap(); - let signed_zome_call = - sign_zome_call(&cell_id, TEST_ZOME_NAME, TEST_FN_NAME, &signing_credentials).await; - // call disabled clone cell should fail - let response = app_ws.call_zome(signed_zome_call).await; + let response = app_ws.call_zome(cell_id.clone().into(), TEST_ZOME_NAME.into(), TEST_FN_NAME.into(), ExternIO::encode(()).unwrap()).await; assert!(response.is_err()); // enable clone cell @@ -89,11 +94,8 @@ async fn clone_cell_management() { .unwrap(); assert_eq!(enabled_cell, clone_cell); - let signed_zome_call = - sign_zome_call(&cell_id, TEST_ZOME_NAME, TEST_FN_NAME, &signing_credentials).await; - // call enabled clone cell should succeed - let response = app_ws.call_zome(signed_zome_call).await.unwrap(); + let response = app_ws.call_zome(cell_id.clone().into(), TEST_ZOME_NAME.into(), TEST_FN_NAME.into(), ExternIO::encode(()).unwrap()).await.unwrap(); assert_eq!(response.decode::().unwrap(), "foo"); // disable clone cell again diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml deleted file mode 100644 index f28a637..0000000 --- a/utilities/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -authors = ["Holochain Foundation"] -edition = "2021" -name = "utilities" -version = "0.1.0" - -[dependencies] -arbitrary = "1.2" -ed25519-dalek = "1" -holochain_client = { path = "../" } -holochain_nonce = "0.3.0-beta-dev.22" -holochain_zome_types = { workspace = true } -rand = "0.7" diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs deleted file mode 100644 index 4d034af..0000000 --- a/utilities/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -use arbitrary::Arbitrary; -use ed25519_dalek::{Keypair, Signer}; -pub use holochain_client::{AdminWebsocket, AgentPubKey, ZomeCall}; -use holochain_nonce::fresh_nonce; -use holochain_zome_types::prelude::{ - CapAccess, CapSecret, CellId, ExternIO, FunctionName, GrantZomeCallCapabilityPayload, - GrantedFunctions, Signature, Timestamp, ZomeCallCapGrant, ZomeCallUnsigned, ZomeName, -}; -use std::collections::BTreeSet; - -pub struct SigningCredentials { - cap_secret: CapSecret, - keypair: Keypair, - signing_key: AgentPubKey, -} - -pub async fn authorize_signing_credentials( - admin_ws: &mut AdminWebsocket, - cell_id: &CellId, -) -> SigningCredentials { - let mut rng = rand::thread_rng(); - let keypair: Keypair = Keypair::generate(&mut rng); - let signing_key = AgentPubKey::from_raw_32(keypair.public.as_bytes().to_vec()); - - let mut buf = arbitrary::Unstructured::new(&[]); - let cap_secret = CapSecret::arbitrary(&mut buf).unwrap(); - - let mut assignees = BTreeSet::new(); - assignees.insert(signing_key.clone()); - - admin_ws - .grant_zome_call_capability(GrantZomeCallCapabilityPayload { - cell_id: cell_id.clone(), - cap_grant: ZomeCallCapGrant { - tag: "zome-call-signing-key".into(), - functions: GrantedFunctions::All, - access: CapAccess::Assigned { - secret: cap_secret.clone(), - assignees: assignees.clone(), - }, - }, - }) - .await - .unwrap(); - - SigningCredentials { - cap_secret, - keypair, - signing_key, - } -} - -pub async fn sign_zome_call( - cell_id: &CellId, - zome_name: &str, - fn_name: &str, - signing_credentials: &SigningCredentials, -) -> ZomeCall { - let (nonce, expires_at) = fresh_nonce(Timestamp::now()).unwrap(); - let unsigned_zome_call_payload = ZomeCallUnsigned { - cap_secret: Some(signing_credentials.cap_secret), - cell_id: cell_id.clone(), - zome_name: ZomeName::from(zome_name), - fn_name: FunctionName::from(fn_name), - provenance: signing_credentials.signing_key.clone(), - payload: ExternIO::encode(()).unwrap(), - nonce, - expires_at, - }; - let hashed_zome_call = unsigned_zome_call_payload.data_to_sign().unwrap(); - - let signature = signing_credentials.keypair.sign(&hashed_zome_call); - - ZomeCall { - cap_secret: unsigned_zome_call_payload.cap_secret, - cell_id: unsigned_zome_call_payload.cell_id, - zome_name: unsigned_zome_call_payload.zome_name, - fn_name: unsigned_zome_call_payload.fn_name, - provenance: unsigned_zome_call_payload.provenance, - payload: unsigned_zome_call_payload.payload, - nonce: unsigned_zome_call_payload.nonce, - expires_at: unsigned_zome_call_payload.expires_at, - signature: Signature::from(signature.to_bytes()), - } -} From 50f97f75150325a146205e260cf3349cd6ebe315 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 23:04:34 +0000 Subject: [PATCH 04/21] Clippy fixes 1 --- src/admin_websocket.rs | 9 +++---- src/app_agent_websocket.rs | 47 ++++++++++++++++------------------- src/app_websocket.rs | 2 +- src/signing.rs | 3 ++- src/signing/client_signing.rs | 8 ++++-- src/signing/lair_signing.rs | 11 +++++--- tests/app.rs | 11 ++++---- tests/clone_cell.rs | 42 +++++++++++++++++++++++++------ 8 files changed, 84 insertions(+), 49 deletions(-) diff --git a/src/admin_websocket.rs b/src/admin_websocket.rs index 1f27f52..c0e4e54 100644 --- a/src/admin_websocket.rs +++ b/src/admin_websocket.rs @@ -47,10 +47,9 @@ impl AdminWebsocket { Ok(Self { tx, rx }) } - pub fn close(&mut self) -> () { - match self.rx.take_handle() { - Some(h) => h.close(), - None => (), + pub fn close(&mut self) { + if let Some(h) = self.rx.take_handle() { + h.close() } } @@ -262,7 +261,7 @@ impl AdminWebsocket { .tx .request(msg) .await - .map_err(|err| ConductorApiError::WebsocketError(err))?; + .map_err(ConductorApiError::WebsocketError)?; match response { AdminResponse::Error(error) => Err(ConductorApiError::ExternalApiWireError(error)), _ => Ok(response), diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index 9d03b0e..e2bd0e4 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -6,7 +6,7 @@ use holochain_conductor_api::{AppInfo, CellInfo, ProvisionedCell}; use holochain_nonce::fresh_nonce; use holochain_types::{app::InstalledAppId, prelude::Signal}; use holochain_zome_types::{ - clone::{ClonedCell, CloneCellId}, + clone::{CloneCellId, ClonedCell}, prelude::{CellId, ExternIO, FunctionName, RoleName, Timestamp, ZomeCallUnsigned, ZomeName}, }; use std::ops::Deref; @@ -53,37 +53,35 @@ impl AppAgentWebsocket { }) } - pub async fn on_signal () + 'static + Sync + Send>( + pub async fn on_signal( &mut self, handler: F, ) -> Result { let app_info = self.app_info.clone(); self.app_ws - .on_signal(move |signal| match signal.clone() { - Signal::App { + .on_signal(move |signal| { + if let Signal::App { cell_id, zome_name: _, signal: _, - } => { + } = signal.clone() + { if app_info .cell_info .values() - .find(|cells| { + .any(|cells| { cells .iter() - .find(|cell_info| match cell_info { + .any(|cell_info| match cell_info { CellInfo::Provisioned(cell) => cell.cell_id.eq(&cell_id), CellInfo::Cloned(cell) => cell.cell_id.eq(&cell_id), _ => false, }) - .is_some() }) - .is_some() { handler(signal); } } - _ => {} }) .await } @@ -98,19 +96,19 @@ impl AppAgentWebsocket { let cell_id = match target { ZomeCallTarget::CellId(cell_id) => cell_id, ZomeCallTarget::RoleName(role_name) => self.get_cell_id_from_role_name(&role_name)?, - ZomeCallTarget::CloneId(clone_id) => { - match clone_id { - CloneCellId::CellId(cell_id) => cell_id, - CloneCellId::CloneId(clone_id) => self.get_cell_id_from_role_name(&clone_id.0)?, - } - } + ZomeCallTarget::CloneId(clone_id) => match clone_id { + CloneCellId::CellId(cell_id) => cell_id, + CloneCellId::CloneId(clone_id) => self.get_cell_id_from_role_name(&clone_id.0)?, + }, }; let (nonce, expires_at) = fresh_nonce(Timestamp::now()) - .map_err(|err| crate::ConductorApiError::FreshNonceError(err))?; + .map_err(ConductorApiError::FreshNonceError)?; let zome_call_unsigned = ZomeCallUnsigned { - provenance: self.signer.get_provenance(&cell_id).ok_or(ConductorApiError::SignZomeCallError("Provenance not found".to_string()))?, + provenance: self.signer.get_provenance(&cell_id).ok_or( + ConductorApiError::SignZomeCallError("Provenance not found".to_string()), + )?, cap_secret: self.signer.get_cap_secret(&cell_id), cell_id: cell_id.clone(), zome_name, @@ -138,7 +136,7 @@ impl AppAgentWebsocket { }; let maybe_clone_cell: Option = - role_cells.into_iter().find_map(|cell| match cell { + role_cells.iter().find_map(|cell| match cell { CellInfo::Cloned(cloned_cell) => { if cloned_cell.clone_id.0.eq(role_name) { Some(cloned_cell.clone()) @@ -150,20 +148,20 @@ impl AppAgentWebsocket { }); let clone_cell = maybe_clone_cell.ok_or(ConductorApiError::CellNotFound)?; - return Ok(clone_cell.cell_id); + Ok(clone_cell.cell_id) } else { let Some(role_cells) = self.app_info.cell_info.get(role_name) else { return Err(ConductorApiError::CellNotFound); }; let maybe_provisioned: Option = - role_cells.into_iter().find_map(|cell| match cell { + role_cells.iter().find_map(|cell| match cell { CellInfo::Provisioned(provisioned_cell) => Some(provisioned_cell.clone()), _ => None, }); let provisioned_cell = maybe_provisioned.ok_or(ConductorApiError::CellNotFound)?; - return Ok(provisioned_cell.cell_id); + Ok(provisioned_cell.cell_id) } } } @@ -196,15 +194,14 @@ impl From for ZomeCallTarget { } fn is_clone_id(role_name: &RoleName) -> bool { - role_name.as_str().contains(".") + role_name.as_str().contains('.') } fn get_base_role_name_from_clone_id(role_name: &RoleName) -> RoleName { RoleName::from( role_name .as_str() - .split(".") - .into_iter() + .split('.') .map(|s| s.to_string()) .collect::>() .first() diff --git a/src/app_websocket.rs b/src/app_websocket.rs index e76cb04..ad8a983 100644 --- a/src/app_websocket.rs +++ b/src/app_websocket.rs @@ -62,7 +62,7 @@ impl AppWebsocket { }) } - pub async fn on_signal () + 'static + Sync + Send>( + pub async fn on_signal( &mut self, handler: F, ) -> Result { diff --git a/src/signing.rs b/src/signing.rs index 8c72d09..ed9a161 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -5,7 +5,8 @@ use async_trait::async_trait; use holo_hash::AgentPubKey; use holochain_conductor_api::ZomeCall; use holochain_zome_types::{ - capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, zome_io::ZomeCallUnsigned + capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, + zome_io::ZomeCallUnsigned, }; #[cfg(feature = "client_signing")] diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 8fc037b..28dc6af 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -2,7 +2,9 @@ use super::AgentSigner; use async_trait::async_trait; use ed25519_dalek::Signer; use holo_hash::AgentPubKey; -use holochain_zome_types::{capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature}; +use holochain_zome_types::{ + capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, +}; use std::{collections::HashMap, sync::Arc}; pub struct SigningCredentials { @@ -56,7 +58,9 @@ impl AgentSigner for ClientAgentSigner { } fn get_provenance(&self, cell_id: &CellId) -> Option { - self.credentials.get(cell_id).map(|c| c.signing_agent_key.clone()) + self.credentials + .get(cell_id) + .map(|c| c.signing_agent_key.clone()) } fn get_cap_secret(&self, cell_id: &CellId) -> Option { diff --git a/src/signing/lair_signing.rs b/src/signing/lair_signing.rs index 7fffbc6..39c4bd7 100644 --- a/src/signing/lair_signing.rs +++ b/src/signing/lair_signing.rs @@ -2,10 +2,12 @@ use crate::AgentSigner; use anyhow::Result; use async_trait::async_trait; use holo_hash::AgentPubKey; -use holochain_zome_types::{capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature}; +use holochain_zome_types::{ + capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, +}; use lair_keystore_api::LairClient; -use std::sync::Arc; use std::collections::HashMap; +use std::sync::Arc; pub struct LairAgentSigner { lair_client: Arc, @@ -14,7 +16,10 @@ pub struct LairAgentSigner { impl LairAgentSigner { pub fn new(lair_client: Arc) -> Self { - Self { lair_client, credentials: HashMap::new() } + Self { + lair_client, + credentials: HashMap::new(), + } } /// Add credentials for a cell to the signer. diff --git a/tests/app.rs b/tests/app.rs index 6eb17f8..f22a08a 100644 --- a/tests/app.rs +++ b/tests/app.rs @@ -3,10 +3,11 @@ use holochain::{ sweettest::SweetConductor, }; use holochain_client::{ - AdminWebsocket, AppAgentWebsocket, AppWebsocket, InstallAppPayload, InstalledAppId, - ClientAgentSigner, AuthorizeSigningCredentialsPayload + AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, + ClientAgentSigner, InstallAppPayload, InstalledAppId, }; use holochain_conductor_api::{CellInfo, NetworkInfo}; +use holochain_zome_types::zome_io::ExternIO; use kitsune_p2p_types::fetch_pool::FetchPoolInfo; use serde::{Deserialize, Serialize}; use std::{ @@ -14,7 +15,6 @@ use std::{ path::PathBuf, sync::{Arc, Barrier}, }; -use holochain_zome_types::zome_io::ExternIO; #[tokio::test(flavor = "multi_thread")] async fn network_info() { @@ -125,8 +125,9 @@ async fn handle_signal() { .unwrap(); signer.add_credentials(cell_id.clone(), credentials); - let mut app_ws = - AppAgentWebsocket::from_existing(app_ws, app_id.clone(), signer.into()).await.unwrap(); + let mut app_ws = AppAgentWebsocket::from_existing(app_ws, app_id.clone(), signer.into()) + .await + .unwrap(); let barrier = Arc::new(Barrier::new(2)); let barrier_clone = barrier.clone(); diff --git a/tests/clone_cell.rs b/tests/clone_cell.rs index 81d57d1..1363d26 100644 --- a/tests/clone_cell.rs +++ b/tests/clone_cell.rs @@ -4,7 +4,10 @@ use holochain::{ prelude::{DeleteCloneCellPayload, DisableCloneCellPayload, EnableCloneCellPayload}, sweettest::SweetConductor, }; -use holochain_client::{AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, ClientAgentSigner, InstallAppPayload}; +use holochain_client::{ + AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, + ClientAgentSigner, InstallAppPayload, +}; use holochain_types::prelude::{ AppBundleSource, CloneCellId, CloneId, CreateCloneCellPayload, DnaModifiersOpt, InstalledAppId, }; @@ -62,13 +65,23 @@ async fn clone_cell_management() { .unwrap(); signer.add_credentials(cell_id.clone(), credentials); - let mut app_ws = AppAgentWebsocket::from_existing(app_ws, app_id.clone(), signer.into()).await.unwrap(); - + let mut app_ws = AppAgentWebsocket::from_existing(app_ws, app_id.clone(), signer.into()) + .await + .unwrap(); + const TEST_ZOME_NAME: &str = "foo"; const TEST_FN_NAME: &str = "foo"; - + // call clone cell should succeed - let response = app_ws.call_zome(cell_id.clone().into(), TEST_ZOME_NAME.into(), TEST_FN_NAME.into(), ExternIO::encode(()).unwrap()).await.unwrap(); + let response = app_ws + .call_zome( + cell_id.clone().into(), + TEST_ZOME_NAME.into(), + TEST_FN_NAME.into(), + ExternIO::encode(()).unwrap(), + ) + .await + .unwrap(); assert_eq!(response.decode::().unwrap(), "foo"); // disable clone cell @@ -81,7 +94,14 @@ async fn clone_cell_management() { .unwrap(); // call disabled clone cell should fail - let response = app_ws.call_zome(cell_id.clone().into(), TEST_ZOME_NAME.into(), TEST_FN_NAME.into(), ExternIO::encode(()).unwrap()).await; + let response = app_ws + .call_zome( + cell_id.clone().into(), + TEST_ZOME_NAME.into(), + TEST_FN_NAME.into(), + ExternIO::encode(()).unwrap(), + ) + .await; assert!(response.is_err()); // enable clone cell @@ -95,7 +115,15 @@ async fn clone_cell_management() { assert_eq!(enabled_cell, clone_cell); // call enabled clone cell should succeed - let response = app_ws.call_zome(cell_id.clone().into(), TEST_ZOME_NAME.into(), TEST_FN_NAME.into(), ExternIO::encode(()).unwrap()).await.unwrap(); + let response = app_ws + .call_zome( + cell_id.clone().into(), + TEST_ZOME_NAME.into(), + TEST_FN_NAME.into(), + ExternIO::encode(()).unwrap(), + ) + .await + .unwrap(); assert_eq!(response.decode::().unwrap(), "foo"); // disable clone cell again From 32bb07528bd86f6d6f14951649bd4b6b567e4e28 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 23:04:45 +0000 Subject: [PATCH 05/21] More clippy fixes --- src/app_websocket.rs | 2 +- src/signing/client_signing.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app_websocket.rs b/src/app_websocket.rs index ad8a983..7c724c0 100644 --- a/src/app_websocket.rs +++ b/src/app_websocket.rs @@ -148,7 +148,7 @@ impl AppWebsocket { .tx .request(msg) .await - .map_err(|err| ConductorApiError::WebsocketError(err))?; + .map_err(ConductorApiError::WebsocketError)?; match response { AppResponse::Error(error) => Err(ConductorApiError::ExternalApiWireError(error)), diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 28dc6af..cdf4418 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -64,7 +64,7 @@ impl AgentSigner for ClientAgentSigner { } fn get_cap_secret(&self, cell_id: &CellId) -> Option { - self.credentials.get(cell_id).map(|c| c.cap_secret).clone() + self.credentials.get(cell_id).map(|c| c.cap_secret) } } From 64ab8f8d4091a09eb63e7951180a1fcace0c9264 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 23:06:27 +0000 Subject: [PATCH 06/21] Last clippy lints --- src/app_agent_websocket.rs | 22 ++++++++-------------- src/app_websocket.rs | 11 ++++------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index e2bd0e4..0f7b0c4 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -66,19 +66,13 @@ impl AppAgentWebsocket { signal: _, } = signal.clone() { - if app_info - .cell_info - .values() - .any(|cells| { - cells - .iter() - .any(|cell_info| match cell_info { - CellInfo::Provisioned(cell) => cell.cell_id.eq(&cell_id), - CellInfo::Cloned(cell) => cell.cell_id.eq(&cell_id), - _ => false, - }) + if app_info.cell_info.values().any(|cells| { + cells.iter().any(|cell_info| match cell_info { + CellInfo::Provisioned(cell) => cell.cell_id.eq(&cell_id), + CellInfo::Cloned(cell) => cell.cell_id.eq(&cell_id), + _ => false, }) - { + }) { handler(signal); } } @@ -102,8 +96,8 @@ impl AppAgentWebsocket { }, }; - let (nonce, expires_at) = fresh_nonce(Timestamp::now()) - .map_err(ConductorApiError::FreshNonceError)?; + let (nonce, expires_at) = + fresh_nonce(Timestamp::now()).map_err(ConductorApiError::FreshNonceError)?; let zome_call_unsigned = ZomeCallUnsigned { provenance: self.signer.get_provenance(&cell_id).ok_or( diff --git a/src/app_websocket.rs b/src/app_websocket.rs index 7c724c0..71d2594 100644 --- a/src/app_websocket.rs +++ b/src/app_websocket.rs @@ -44,13 +44,10 @@ impl AppWebsocket { std::thread::spawn(move || { futures::executor::block_on(async { while let Some((msg, resp)) = rx.next().await { - match resp { - Respond::Signal => { - let mut ee = m.lock().await; - let signal = Signal::try_from(msg).expect("Malformed signal"); - ee.emit("signal", signal); - } - _ => {} + if let Respond::Signal = resp { + let mut ee = m.lock().await; + let signal = Signal::try_from(msg).expect("Malformed signal"); + ee.emit("signal", signal); } } }); From 92def0afd57049f8c834402af32c8d2811ff2819 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 23:09:35 +0000 Subject: [PATCH 07/21] Remove stray printlns --- src/admin_websocket.rs | 2 -- src/signing/client_signing.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/admin_websocket.rs b/src/admin_websocket.rs index c0e4e54..d42986a 100644 --- a/src/admin_websocket.rs +++ b/src/admin_websocket.rs @@ -228,12 +228,10 @@ impl AdminWebsocket { let mut csprng = OsRng; let keypair = ed25519_dalek::SigningKey::generate(&mut csprng); let public_key = keypair.verifying_key(); - println!("Using public key {:?}", public_key.as_bytes().to_vec()); let signing_agent_key = AgentPubKey::from_raw_32(public_key.as_bytes().to_vec()); let mut cap_secret = [0; CAP_SECRET_BYTES]; csprng.fill_bytes(&mut cap_secret); - println!("Cap secret {:?}", cap_secret.to_vec()); self.grant_zome_call_capability(GrantZomeCallCapabilityPayload { cell_id: request.cell_id, diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index cdf4418..1361882 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -51,9 +51,7 @@ impl AgentSigner for ClientAgentSigner { .credentials .get(cell_id) .ok_or_else(|| anyhow::anyhow!("No credentials found for cell: {:?}", cell_id))?; - println!("Using credentials: {:?}", credentials); let signature = credentials.keypair.try_sign(&data_to_sign)?; - println!("Signature: {:?}", signature.to_bytes()); Ok(Signature(signature.to_bytes())) } From 25ec334c1615c90e88949d30e263adeb39b52b05 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 23:13:11 +0000 Subject: [PATCH 08/21] Remove unused code --- src/signing.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/signing.rs b/src/signing.rs index ed9a161..dddb907 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -37,8 +37,6 @@ pub(crate) async fn sign_zome_call( signer: Arc>, ) -> Result { let pub_key = zome_call_unsigned.provenance.clone(); - let mut pub_key_2 = [0; 32]; - pub_key_2.copy_from_slice(pub_key.get_raw_32()); let data_to_sign = zome_call_unsigned.data_to_sign().map_err(|e| { anyhow::anyhow!("Failed to get data to sign from unsigned zome call: {}", e) From c623fc6c2017d28316642ae469d2273a8816172e Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Fri, 1 Mar 2024 23:39:12 +0000 Subject: [PATCH 09/21] Update changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e4f58b..cfcb3ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## \[Unreleased\] +### Removed +- The utilities crate, it is now replaced by signing built into the client. Please see the updated tests for examples of how to use this. +- `sign_zome_call_with_client` which was used internally but also exposed in the public interface. You probably don't need to call this but if you wish to for some reason then use one of the two new `*Signer` types, and convert them to a `Arc>`, then use the `sign` method to compute a signature. The logic to prepare the data to be signed is no longer public so you would have to set this up yourself following the `sign_zome_call` function in the `signer` module. + +### Added +- Capability to create zome call signing credentials with the AdminWebsocket using `authorize_signing_credentials`. +- `ClientAgentSigner` type which can store (in memory) signing credentials created with `authorize_signing_credentials`. +- `LairAgentSigner` which is analagous to the `ClientAgentSigner` but is a wrapper around a Lair client instead so that private keys are stored in Lair. +- `from_existing` method to the `AppAgentWebsocket` which allows it to wrap an existing `AppAgentWebsocket` instead of having to open a new connection. This is useful if you already have an `AppWebsocket` but otherwise you should just use the `connect` method of the `AppAgentWebsocket` rather than two steps. + +### Changed +- `AppAgentWebsocket::connect` now takes an `Arc>` instead of a `LairClient`. The `Arc>` can be created from a `.into()` on either a `ClientAgentSigner` or a `LairAgentSigner`. Use the latter to restore the previous behaviour. +- `AppAgentWebsocket::call_zome` used to take a `RoleName` as its first parameter. This is now a `ZomeCallTarget`. There is a `.into()` which restores the previous behaviour. Now you can also pass a `CloneCellId` or a `CellId`, also using a `.into()`. Using `CellId` is stronly recommended for now. Please see the doc comments on `ZomeCallTarget` if you intend to use the other options. + ### Added ### Changed ### Fixed From a693972390fa9ba0934ae586d74e9001677eb196 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Sat, 2 Mar 2024 00:59:33 +0000 Subject: [PATCH 10/21] Don't use all targets --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 73fc4ca..95874cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,7 +60,7 @@ jobs: run: cargo build -p holochain_client - name: Lint - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --all-features -- -D warnings - name: Check formatting run: cargo fmt --all --check From 3343a5b8b95d49415969ab8d9a1c95bbafdb8bd0 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 12:01:47 +0000 Subject: [PATCH 11/21] Update CHANGELOG.md Co-authored-by: Jost Schulte --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfcb3ae..748ce58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Capability to create zome call signing credentials with the AdminWebsocket using `authorize_signing_credentials`. - `ClientAgentSigner` type which can store (in memory) signing credentials created with `authorize_signing_credentials`. - `LairAgentSigner` which is analagous to the `ClientAgentSigner` but is a wrapper around a Lair client instead so that private keys are stored in Lair. -- `from_existing` method to the `AppAgentWebsocket` which allows it to wrap an existing `AppAgentWebsocket` instead of having to open a new connection. This is useful if you already have an `AppWebsocket` but otherwise you should just use the `connect` method of the `AppAgentWebsocket` rather than two steps. +- `from_existing` method to the `AppAgentWebsocket` which allows it to wrap an existing `AppWebsocket` instead of having to open a new connection. This is useful if you already have an `AppWebsocket` but otherwise you should just use the `connect` method of the `AppAgentWebsocket` rather than two steps. ### Changed - `AppAgentWebsocket::connect` now takes an `Arc>` instead of a `LairClient`. The `Arc>` can be created from a `.into()` on either a `ClientAgentSigner` or a `LairAgentSigner`. Use the latter to restore the previous behaviour. From 4f15c075f7e1e12b5e4376de853952fcad02c5f2 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 14:23:19 +0000 Subject: [PATCH 12/21] Export ZomeCallTarget --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 26fdde5..75608d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ mod error; mod signing; pub use admin_websocket::{AdminWebsocket, AuthorizeSigningCredentialsPayload, EnableAppResponse}; -pub use app_agent_websocket::AppAgentWebsocket; +pub use app_agent_websocket::{AppAgentWebsocket, ZomeCallTarget}; pub use app_websocket::AppWebsocket; pub use error::{ConductorApiError, ConductorApiResult}; pub use holochain_conductor_api::{ From c7172f51b1c82fef493fc143865c45803d899fa3 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 16:32:39 +0000 Subject: [PATCH 13/21] send+sync and review comments --- src/admin_websocket.rs | 1 - src/app_agent_websocket.rs | 30 +++++++++++++++++++++++------- src/app_websocket.rs | 23 ++++++++++++----------- src/signing.rs | 2 +- src/signing/client_signing.rs | 2 +- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/admin_websocket.rs b/src/admin_websocket.rs index d42986a..65ae5e9 100644 --- a/src/admin_websocket.rs +++ b/src/admin_websocket.rs @@ -216,7 +216,6 @@ impl AdminWebsocket { } } - #[cfg(feature = "client_signing")] pub async fn authorize_signing_credentials( &mut self, request: AuthorizeSigningCredentialsPayload, diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index 0f7b0c4..d0d145d 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -1,4 +1,4 @@ -use std::{ops::DerefMut, sync::Arc}; +use std::{fmt::Result, ops::DerefMut, sync::Arc}; use anyhow::{anyhow, Result}; use holo_hash::AgentPubKey; @@ -21,14 +21,14 @@ pub struct AppAgentWebsocket { pub my_pub_key: AgentPubKey, app_ws: AppWebsocket, app_info: AppInfo, - signer: Arc>, + signer: Arc>, } impl AppAgentWebsocket { pub async fn connect( url: String, app_id: InstalledAppId, - signer: Arc>, + signer: Arc>, ) -> Result { let app_ws = AppWebsocket::connect(url).await?; AppAgentWebsocket::from_existing(app_ws, app_id, signer).await @@ -37,7 +37,7 @@ impl AppAgentWebsocket { pub async fn from_existing( mut app_ws: AppWebsocket, app_id: InstalledAppId, - signer: Arc>, + signer: Arc>, ) -> Result { let app_info = app_ws .app_info(app_id.clone()) @@ -121,6 +121,17 @@ impl AppAgentWebsocket { Ok(result) } + pub fn refresh_app_info(&mut self) -> Result<()> { + self.app_info = self + .app_ws + .app_info(self.app_info.installed_app_id.clone()) + .await + .map_err(|err| anyhow!("Error fetching app_info {err:?}"))? + .ok_or(anyhow!("App doesn't exist"))?; + + Ok(()) + } + fn get_cell_id_from_role_name(&self, role_name: &RoleName) -> ConductorApiResult { if is_clone_id(role_name) { let base_role_name = get_base_role_name_from_clone_id(role_name); @@ -162,10 +173,15 @@ impl AppAgentWebsocket { pub enum ZomeCallTarget { CellId(CellId), - /// You can call by role name for provisioned cells but any clone cells you create after - /// creating the [AppAgentWebsocket] will need to be called by [ZomeCallTarget::CellId]. + /// Call a cell by its role name. + /// + /// Note that when using clone cells, if you create them after creating the [AppAgentWebsocket], you will need to call [AppAgentWebsocket::refresh_app_info] + /// for the right CellId to be found to make the call. RoleName(RoleName), - /// Any clone cells you create after creating the [AppAgentWebsocket] will need to be called by [ZomeCallTarget::CellId]. + /// Call a cell by its clone cell id. + /// + /// Note that when using clone cells, if you create them after creating the [AppAgentWebsocket], you will need to call [AppAgentWebsocket::refresh_app_info] + /// for the right CellId to be found to make the call. CloneId(CloneCellId), } diff --git a/src/app_websocket.rs b/src/app_websocket.rs index 71d2594..34f7ef3 100644 --- a/src/app_websocket.rs +++ b/src/app_websocket.rs @@ -39,18 +39,19 @@ impl AppWebsocket { let event_emitter = EventEmitter::new(); let mutex = Arc::new(Mutex::new(event_emitter)); - let m = mutex.clone(); - - std::thread::spawn(move || { - futures::executor::block_on(async { - while let Some((msg, resp)) = rx.next().await { - if let Respond::Signal = resp { - let mut ee = m.lock().await; - let signal = Signal::try_from(msg).expect("Malformed signal"); - ee.emit("signal", signal); + std::thread::spawn({ + let mutex = mutex.clone(); + move || { + futures::executor::block_on(async { + while let Some((msg, resp)) = rx.next().await { + if let Respond::Signal = resp { + let mut event_emitter = mutex.lock().await; + let signal = Signal::try_from(msg).expect("Malformed signal"); + event_emitter.emit("signal", signal); + } } - } - }); + }); + } }); Ok(Self { diff --git a/src/signing.rs b/src/signing.rs index dddb907..8ab51fc 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -34,7 +34,7 @@ pub trait AgentSigner { /// Signs an unsigned zome call using the provided signing implementation pub(crate) async fn sign_zome_call( zome_call_unsigned: ZomeCallUnsigned, - signer: Arc>, + signer: Arc>, ) -> Result { let pub_key = zome_call_unsigned.provenance.clone(); diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 1361882..8ba5a91 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -67,7 +67,7 @@ impl AgentSigner for ClientAgentSigner { } /// Convert the ClientAgentSigner into an Arc> -impl From for Arc> { +impl From for Arc> { fn from(cas: ClientAgentSigner) -> Self { Arc::new(Box::new(cas)) } From b31cd34c63f22e7f5ee75e620db3ecc88d9005e2 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 16:39:04 +0000 Subject: [PATCH 14/21] Tidy up --- src/app_agent_websocket.rs | 7 +++++-- src/signing/client_signing.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index d0d145d..1d6bb4b 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -1,4 +1,4 @@ -use std::{fmt::Result, ops::DerefMut, sync::Arc}; +use std::{ops::DerefMut, sync::Arc}; use anyhow::{anyhow, Result}; use holo_hash::AgentPubKey; @@ -121,7 +121,10 @@ impl AppAgentWebsocket { Ok(result) } - pub fn refresh_app_info(&mut self) -> Result<()> { + /// Gets a new copy of the [AppInfo] for the app this agent is connected to. + /// + /// This is useful if you have made changes to the app, such as creating new clone cells, and need to refresh the app info. + pub async fn refresh_app_info(&mut self) -> Result<()> { self.app_info = self .app_ws .app_info(self.app_info.installed_app_id.clone()) diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 8ba5a91..05f507f 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -66,7 +66,7 @@ impl AgentSigner for ClientAgentSigner { } } -/// Convert the ClientAgentSigner into an Arc> +/// Convert the ClientAgentSigner into an `Arc>` impl From for Arc> { fn from(cas: ClientAgentSigner) -> Self { Arc::new(Box::new(cas)) From ab35a13bff1d498712dd9b11f983df131c4da281 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 16:44:05 +0000 Subject: [PATCH 15/21] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 748ce58..30b9d6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `sign_zome_call_with_client` which was used internally but also exposed in the public interface. You probably don't need to call this but if you wish to for some reason then use one of the two new `*Signer` types, and convert them to a `Arc>`, then use the `sign` method to compute a signature. The logic to prepare the data to be signed is no longer public so you would have to set this up yourself following the `sign_zome_call` function in the `signer` module. ### Added -- Capability to create zome call signing credentials with the AdminWebsocket using `authorize_signing_credentials`. +- Capability to create zome call signing credentials with the `AdminWebsocket` using `authorize_signing_credentials`. - `ClientAgentSigner` type which can store (in memory) signing credentials created with `authorize_signing_credentials`. - `LairAgentSigner` which is analagous to the `ClientAgentSigner` but is a wrapper around a Lair client instead so that private keys are stored in Lair. - `from_existing` method to the `AppAgentWebsocket` which allows it to wrap an existing `AppWebsocket` instead of having to open a new connection. This is useful if you already have an `AppWebsocket` but otherwise you should just use the `connect` method of the `AppAgentWebsocket` rather than two steps. From 8c05fc68ceff38fd2f1fb3e22c921bb81ee51008 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 21:19:40 +0000 Subject: [PATCH 16/21] Add a test for app info refresh --- Cargo.lock | 1 + Cargo.toml | 1 + src/app_agent_websocket.rs | 17 ++++----- src/signing/client_signing.rs | 18 ++++++---- tests/clone_cell.rs | 66 +++++++++++++++++++++++++++++++++-- 5 files changed, 84 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 824d468..4e99855 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2690,6 +2690,7 @@ dependencies = [ "holochain_zome_types", "kitsune_p2p_types", "lair_keystore_api", + "parking_lot 0.12.1", "rand 0.8.5", "serde", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 616a93e..2b2183e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ event-emitter-rs = "0.1" futures = "0.3" rand = { version = "0.8", optional = true } async-trait = "0.1" +parking_lot = "0.12.1" holo_hash = { version = "0.3.0-beta-dev.20", features = ["encoding"] } holochain_conductor_api = "0.3.0-beta-dev.34" diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index 1d6bb4b..55e0183 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -4,9 +4,9 @@ use anyhow::{anyhow, Result}; use holo_hash::AgentPubKey; use holochain_conductor_api::{AppInfo, CellInfo, ProvisionedCell}; use holochain_nonce::fresh_nonce; -use holochain_types::{app::InstalledAppId, prelude::Signal}; +use holochain_types::{app::InstalledAppId, prelude::{Signal, CloneId}}; use holochain_zome_types::{ - clone::{CloneCellId, ClonedCell}, + clone::ClonedCell, prelude::{CellId, ExternIO, FunctionName, RoleName, Timestamp, ZomeCallUnsigned, ZomeName}, }; use std::ops::Deref; @@ -90,10 +90,7 @@ impl AppAgentWebsocket { let cell_id = match target { ZomeCallTarget::CellId(cell_id) => cell_id, ZomeCallTarget::RoleName(role_name) => self.get_cell_id_from_role_name(&role_name)?, - ZomeCallTarget::CloneId(clone_id) => match clone_id { - CloneCellId::CellId(cell_id) => cell_id, - CloneCellId::CloneId(clone_id) => self.get_cell_id_from_role_name(&clone_id.0)?, - }, + ZomeCallTarget::CloneId(clone_id) => self.get_cell_id_from_role_name(&clone_id.0)?, }; let (nonce, expires_at) = @@ -181,11 +178,11 @@ pub enum ZomeCallTarget { /// Note that when using clone cells, if you create them after creating the [AppAgentWebsocket], you will need to call [AppAgentWebsocket::refresh_app_info] /// for the right CellId to be found to make the call. RoleName(RoleName), - /// Call a cell by its clone cell id. + /// Call a cell by its clone id. /// /// Note that when using clone cells, if you create them after creating the [AppAgentWebsocket], you will need to call [AppAgentWebsocket::refresh_app_info] /// for the right CellId to be found to make the call. - CloneId(CloneCellId), + CloneId(CloneId), } impl From for ZomeCallTarget { @@ -200,8 +197,8 @@ impl From for ZomeCallTarget { } } -impl From for ZomeCallTarget { - fn from(clone_id: CloneCellId) -> Self { +impl From for ZomeCallTarget { + fn from(clone_id: CloneId) -> Self { ZomeCallTarget::CloneId(clone_id) } } diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 05f507f..24beb4d 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -6,6 +6,7 @@ use holochain_zome_types::{ capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, }; use std::{collections::HashMap, sync::Arc}; +use parking_lot::RwLock; pub struct SigningCredentials { pub signing_agent_key: holo_hash::AgentPubKey, @@ -22,20 +23,20 @@ impl std::fmt::Debug for SigningCredentials { } } -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub struct ClientAgentSigner { - credentials: HashMap, + credentials: Arc>>, } impl ClientAgentSigner { pub fn new() -> Self { Self { - credentials: HashMap::new(), + credentials: Arc::new(RwLock::new(HashMap::new())), } } pub fn add_credentials(&mut self, cell_id: CellId, credentials: SigningCredentials) { - self.credentials.insert(cell_id, credentials); + self.credentials.write().insert(cell_id, credentials); } } @@ -47,8 +48,10 @@ impl AgentSigner for ClientAgentSigner { _provenance: AgentPubKey, data_to_sign: Arc<[u8]>, ) -> Result { - let credentials = self - .credentials + let credentials_lock = self + .credentials + .read(); + let credentials = credentials_lock .get(cell_id) .ok_or_else(|| anyhow::anyhow!("No credentials found for cell: {:?}", cell_id))?; let signature = credentials.keypair.try_sign(&data_to_sign)?; @@ -57,12 +60,13 @@ impl AgentSigner for ClientAgentSigner { fn get_provenance(&self, cell_id: &CellId) -> Option { self.credentials + .read() .get(cell_id) .map(|c| c.signing_agent_key.clone()) } fn get_cap_secret(&self, cell_id: &CellId) -> Option { - self.credentials.get(cell_id).map(|c| c.cap_secret) + self.credentials.read().get(cell_id).map(|c| c.cap_secret) } } diff --git a/tests/clone_cell.rs b/tests/clone_cell.rs index 1363d26..81a62ca 100644 --- a/tests/clone_cell.rs +++ b/tests/clone_cell.rs @@ -5,8 +5,7 @@ use holochain::{ sweettest::SweetConductor, }; use holochain_client::{ - AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, - ClientAgentSigner, InstallAppPayload, + AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, ClientAgentSigner, ConductorApiError, InstallAppPayload }; use holochain_types::prelude::{ AppBundleSource, CloneCellId, CloneId, CreateCloneCellPayload, DnaModifiersOpt, InstalledAppId, @@ -152,3 +151,66 @@ async fn clone_cell_management() { .await; assert!(enable_clone_cell_response.is_err()); } + +// Check that app info can be refreshed to allow zome calls to a clone cell identified by its clone cell id +#[tokio::test(flavor = "multi_thread")] +pub async fn app_info_refresh() { + let conductor = SweetConductor::from_standard_config().await; + let admin_port = conductor.get_arbitrary_admin_websocket_port().unwrap(); + let mut admin_ws = AdminWebsocket::connect(format!("ws://localhost:{}", admin_port)) + .await + .unwrap(); + let app_id: InstalledAppId = "test-app".into(); + let role_name: RoleName = "foo".into(); + + // Create our agent key + let agent_key = admin_ws.generate_agent_pub_key().await.unwrap(); + + // Install and enable an app + admin_ws + .install_app(InstallAppPayload { + agent_key: agent_key.clone(), + installed_app_id: Some(app_id.clone()), + membrane_proofs: HashMap::new(), + network_seed: None, + source: AppBundleSource::Path(PathBuf::from("./fixture/test.happ")), + }) + .await + .unwrap(); + admin_ws.enable_app(app_id.clone()).await.unwrap(); + + let mut signer = ClientAgentSigner::default(); + + // Create an app interface and connect an app agent to it + let app_api_port = admin_ws.attach_app_interface(30000).await.unwrap(); + let mut app_agent_ws = AppAgentWebsocket::connect(format!("ws://localhost:{}", app_api_port), app_id.clone(), signer.clone().into()).await.unwrap(); + + // Create a clone cell, AFTER the app agent has been created + let cloned_cell = app_agent_ws.create_clone_cell(CreateCloneCellPayload { + app_id: app_id.clone(), + role_name: role_name.clone(), + modifiers: DnaModifiersOpt::none().with_network_seed("test seed".into()), + membrane_proof: None, + name: None, + }).await.unwrap(); + + // Authorise signing credentials for the cloned cell + let credentials = admin_ws.authorize_signing_credentials(AuthorizeSigningCredentialsPayload { + cell_id: cloned_cell.cell_id.clone(), + functions: None, + }).await.unwrap(); + signer.add_credentials(cloned_cell.cell_id.clone(), credentials); + + // Call the zome function on the clone cell, expecting a failure + let err = app_agent_ws.call_zome(cloned_cell.clone_id.clone().into(), "foo".into(), "foo".into(), ExternIO::encode(()).unwrap()).await.expect_err("Should fail because the client doesn't know the clone cell exists"); + match err { + ConductorApiError::CellNotFound => (), + _ => panic!("Unexpected error: {:?}", err), + } + + // Refresh the app info, which means the app agent will now know about the clone cell + app_agent_ws.refresh_app_info().await.unwrap(); + + // Call the zome function on the clone cell again, expecting success + app_agent_ws.call_zome(cloned_cell.clone_id.clone().into(), "foo".into(), "foo".into(), ExternIO::encode(()).unwrap()).await.unwrap(); +} From 5480b351e90846df386029542b16b11a16fa2c3d Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 21:22:59 +0000 Subject: [PATCH 17/21] Fix feature logic --- Cargo.toml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b2183e..67b4128 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ holochain_zome_types = "0.3.0-beta-dev.28" [dependencies] again = "0.1" anyhow = "1.0" -ed25519-dalek = "2.1" +ed25519-dalek = { version = "2.1", features = ["rand_core"] } serde = "1.0.193" url = "2.2" event-emitter-rs = "0.1" futures = "0.3" -rand = { version = "0.8", optional = true } +rand = { version = "0.8" } async-trait = "0.1" parking_lot = "0.12.1" @@ -47,13 +47,10 @@ tokio = { version = "1.3", features = ["full"] } kitsune_p2p_types = "0.3.0-beta-dev.14" [features] -default = ["lair_signing", "client_signing"] +default = ["client_signing", "lair_signing"] + +client_signing = [] lair_signing = [ "dep:lair_keystore_api", ] - -client_signing = [ - "dep:rand", - "ed25519-dalek/rand_core", -] From 7b72e757035b993fab8ce70282abcd9e261f0601 Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Mon, 4 Mar 2024 21:27:41 +0000 Subject: [PATCH 18/21] Remove client_signing feature --- Cargo.toml | 4 +--- src/lib.rs | 1 - src/signing.rs | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67b4128..9d51416 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,9 +47,7 @@ tokio = { version = "1.3", features = ["full"] } kitsune_p2p_types = "0.3.0-beta-dev.14" [features] -default = ["client_signing", "lair_signing"] - -client_signing = [] +default = ["lair_signing"] lair_signing = [ "dep:lair_keystore_api", diff --git a/src/lib.rs b/src/lib.rs index 75608d8..ccdc64f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ pub use holochain_types::{ app::{InstallAppPayload, InstalledAppId}, dna::AgentPubKey, }; -#[cfg(feature = "client_signing")] pub use signing::client_signing::{ClientAgentSigner, SigningCredentials}; #[cfg(feature = "lair_signing")] pub use signing::lair_signing::LairAgentSigner; diff --git a/src/signing.rs b/src/signing.rs index 8ab51fc..8e32b8c 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -9,7 +9,6 @@ use holochain_zome_types::{ zome_io::ZomeCallUnsigned, }; -#[cfg(feature = "client_signing")] pub(crate) mod client_signing; #[cfg(feature = "lair_signing")] From 01674f91bf3067762683045810b584d16115461d Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Tue, 5 Mar 2024 11:25:08 +0000 Subject: [PATCH 19/21] Format --- src/app_agent_websocket.rs | 5 ++- src/signing/client_signing.rs | 6 ++-- tests/clone_cell.rs | 61 ++++++++++++++++++++++++++--------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/app_agent_websocket.rs b/src/app_agent_websocket.rs index 55e0183..7791778 100644 --- a/src/app_agent_websocket.rs +++ b/src/app_agent_websocket.rs @@ -4,7 +4,10 @@ use anyhow::{anyhow, Result}; use holo_hash::AgentPubKey; use holochain_conductor_api::{AppInfo, CellInfo, ProvisionedCell}; use holochain_nonce::fresh_nonce; -use holochain_types::{app::InstalledAppId, prelude::{Signal, CloneId}}; +use holochain_types::{ + app::InstalledAppId, + prelude::{CloneId, Signal}, +}; use holochain_zome_types::{ clone::ClonedCell, prelude::{CellId, ExternIO, FunctionName, RoleName, Timestamp, ZomeCallUnsigned, ZomeName}, diff --git a/src/signing/client_signing.rs b/src/signing/client_signing.rs index 24beb4d..2f8570b 100644 --- a/src/signing/client_signing.rs +++ b/src/signing/client_signing.rs @@ -5,8 +5,8 @@ use holo_hash::AgentPubKey; use holochain_zome_types::{ capability::CapSecret, cell::CellId, dependencies::holochain_integrity_types::Signature, }; -use std::{collections::HashMap, sync::Arc}; use parking_lot::RwLock; +use std::{collections::HashMap, sync::Arc}; pub struct SigningCredentials { pub signing_agent_key: holo_hash::AgentPubKey, @@ -48,9 +48,7 @@ impl AgentSigner for ClientAgentSigner { _provenance: AgentPubKey, data_to_sign: Arc<[u8]>, ) -> Result { - let credentials_lock = self - .credentials - .read(); + let credentials_lock = self.credentials.read(); let credentials = credentials_lock .get(cell_id) .ok_or_else(|| anyhow::anyhow!("No credentials found for cell: {:?}", cell_id))?; diff --git a/tests/clone_cell.rs b/tests/clone_cell.rs index 81a62ca..505edc7 100644 --- a/tests/clone_cell.rs +++ b/tests/clone_cell.rs @@ -5,7 +5,8 @@ use holochain::{ sweettest::SweetConductor, }; use holochain_client::{ - AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, ClientAgentSigner, ConductorApiError, InstallAppPayload + AdminWebsocket, AppAgentWebsocket, AppWebsocket, AuthorizeSigningCredentialsPayload, + ClientAgentSigner, ConductorApiError, InstallAppPayload, }; use holochain_types::prelude::{ AppBundleSource, CloneCellId, CloneId, CreateCloneCellPayload, DnaModifiersOpt, InstalledAppId, @@ -180,29 +181,49 @@ pub async fn app_info_refresh() { admin_ws.enable_app(app_id.clone()).await.unwrap(); let mut signer = ClientAgentSigner::default(); - + // Create an app interface and connect an app agent to it let app_api_port = admin_ws.attach_app_interface(30000).await.unwrap(); - let mut app_agent_ws = AppAgentWebsocket::connect(format!("ws://localhost:{}", app_api_port), app_id.clone(), signer.clone().into()).await.unwrap(); + let mut app_agent_ws = AppAgentWebsocket::connect( + format!("ws://localhost:{}", app_api_port), + app_id.clone(), + signer.clone().into(), + ) + .await + .unwrap(); // Create a clone cell, AFTER the app agent has been created - let cloned_cell = app_agent_ws.create_clone_cell(CreateCloneCellPayload { - app_id: app_id.clone(), - role_name: role_name.clone(), - modifiers: DnaModifiersOpt::none().with_network_seed("test seed".into()), - membrane_proof: None, - name: None, - }).await.unwrap(); + let cloned_cell = app_agent_ws + .create_clone_cell(CreateCloneCellPayload { + app_id: app_id.clone(), + role_name: role_name.clone(), + modifiers: DnaModifiersOpt::none().with_network_seed("test seed".into()), + membrane_proof: None, + name: None, + }) + .await + .unwrap(); // Authorise signing credentials for the cloned cell - let credentials = admin_ws.authorize_signing_credentials(AuthorizeSigningCredentialsPayload { - cell_id: cloned_cell.cell_id.clone(), - functions: None, - }).await.unwrap(); + let credentials = admin_ws + .authorize_signing_credentials(AuthorizeSigningCredentialsPayload { + cell_id: cloned_cell.cell_id.clone(), + functions: None, + }) + .await + .unwrap(); signer.add_credentials(cloned_cell.cell_id.clone(), credentials); // Call the zome function on the clone cell, expecting a failure - let err = app_agent_ws.call_zome(cloned_cell.clone_id.clone().into(), "foo".into(), "foo".into(), ExternIO::encode(()).unwrap()).await.expect_err("Should fail because the client doesn't know the clone cell exists"); + let err = app_agent_ws + .call_zome( + cloned_cell.clone_id.clone().into(), + "foo".into(), + "foo".into(), + ExternIO::encode(()).unwrap(), + ) + .await + .expect_err("Should fail because the client doesn't know the clone cell exists"); match err { ConductorApiError::CellNotFound => (), _ => panic!("Unexpected error: {:?}", err), @@ -212,5 +233,13 @@ pub async fn app_info_refresh() { app_agent_ws.refresh_app_info().await.unwrap(); // Call the zome function on the clone cell again, expecting success - app_agent_ws.call_zome(cloned_cell.clone_id.clone().into(), "foo".into(), "foo".into(), ExternIO::encode(()).unwrap()).await.unwrap(); + app_agent_ws + .call_zome( + cloned_cell.clone_id.clone().into(), + "foo".into(), + "foo".into(), + ExternIO::encode(()).unwrap(), + ) + .await + .unwrap(); } From 1d0a3d40d027b421f478bcb5032d45d9a9ffbdeb Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Tue, 5 Mar 2024 11:39:59 +0000 Subject: [PATCH 20/21] Update CI check for features --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95874cf..6b91010 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,7 @@ jobs: - name: Verify feature independence run: | - cargo build --no-default-features --features "client_signing" + cargo build --no-default-features cargo build --no-default-features --features "lair_signing" - name: Setup tmate session if build and test run failed From 78b89b8bce67b15bc26f517f583e163360357d1c Mon Sep 17 00:00:00 2001 From: ThetaSinner Date: Tue, 5 Mar 2024 12:27:05 +0000 Subject: [PATCH 21/21] Avoid port clashes in tests --- tests/clone_cell.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/clone_cell.rs b/tests/clone_cell.rs index 505edc7..9d0f458 100644 --- a/tests/clone_cell.rs +++ b/tests/clone_cell.rs @@ -34,7 +34,7 @@ async fn clone_cell_management() { .await .unwrap(); admin_ws.enable_app(app_id.clone()).await.unwrap(); - let app_api_port = admin_ws.attach_app_interface(30000).await.unwrap(); + let app_api_port = admin_ws.attach_app_interface(0).await.unwrap(); let mut app_ws = AppWebsocket::connect(format!("ws://localhost:{}", app_api_port)) .await .unwrap(); @@ -183,7 +183,7 @@ pub async fn app_info_refresh() { let mut signer = ClientAgentSigner::default(); // Create an app interface and connect an app agent to it - let app_api_port = admin_ws.attach_app_interface(30000).await.unwrap(); + let app_api_port = admin_ws.attach_app_interface(0).await.unwrap(); let mut app_agent_ws = AppAgentWebsocket::connect( format!("ws://localhost:{}", app_api_port), app_id.clone(),