diff --git a/Cargo.lock b/Cargo.lock index 981c3d3b..ab61e6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2284,6 +2284,7 @@ dependencies = [ "radicle-git-ext", "serde", "serde_json", + "signature 2.2.0", "tempfile", "thiserror 1.0.69", ] @@ -2316,6 +2317,7 @@ dependencies = [ "radicle-git-ext", "radicle-ssh", "serde", + "signature 2.2.0", "sqlite", "ssh-key", "tempfile", diff --git a/radicle-cli/src/commands/cob.rs b/radicle-cli/src/commands/cob.rs index 1e116ae8..5249fea2 100644 --- a/radicle-cli/src/commands/cob.rs +++ b/radicle-cli/src/commands/cob.rs @@ -471,7 +471,7 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<( let actions: Vec = read_jsonl(reader)?; let mut patches = profile.patches_mut(&repo)?; let mut patch = patches.get_mut(oid)?; - patch.transaction(&message, &profile.signer()?, |tx| { + patch.transaction(&message, &*profile.signer()?, |tx| { tx.extend(actions)?; tx.embed(embeds)?; Ok(()) @@ -481,7 +481,7 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<( let actions: Vec = read_jsonl(reader)?; let mut issues = profile.issues_mut(&repo)?; let mut issue = issues.get_mut(oid)?; - issue.transaction(&message, &profile.signer()?, |tx| { + issue.transaction(&message, &*profile.signer()?, |tx| { tx.extend(actions)?; tx.embed(embeds)?; Ok(()) diff --git a/radicle-cli/src/commands/id.rs b/radicle-cli/src/commands/id.rs index f49eb74a..2b70f293 100644 --- a/radicle-cli/src/commands/id.rs +++ b/radicle-cli/src/commands/id.rs @@ -6,10 +6,11 @@ use anyhow::{anyhow, Context}; use radicle::cob::identity::{self, IdentityMut, Revision, RevisionId}; use radicle::identity::{doc, Doc, Identity, PayloadError, RawDoc, Visibility}; -use radicle::prelude::{Did, RepoId, Signer}; +use radicle::node::device::Device; +use radicle::prelude::{Did, RepoId}; use radicle::storage::refs; use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository}; -use radicle::{cob, Profile}; +use radicle::{cob, crypto, Profile}; use radicle_surf::diff::Diff; use radicle_term::Element; use serde_json as json; @@ -722,13 +723,17 @@ and description. Ok(result) } -fn update( +fn update( title: Option, description: Option, doc: Doc, current: &mut IdentityMut, - signer: &G, -) -> anyhow::Result { + signer: &Device, +) -> anyhow::Result +where + R: WriteRepository + cob::Store, + G: crypto::signature::Signer, +{ if let Some((title, description)) = edit_title_description(title, description)? { let id = current.update(title, description, &doc, signer)?; let revision = current diff --git a/radicle-cli/src/commands/issue.rs b/radicle-cli/src/commands/issue.rs index ed27284b..9332ace7 100644 --- a/radicle-cli/src/commands/issue.rs +++ b/radicle-cli/src/commands/issue.rs @@ -10,8 +10,9 @@ use anyhow::{anyhow, Context as _}; use radicle::cob::common::{Label, Reaction}; use radicle::cob::issue::{CloseReason, State}; use radicle::cob::{issue, thread}; -use radicle::crypto::Signer; +use radicle::crypto; use radicle::issue::cache::Issues as _; +use radicle::node::device::Device; use radicle::prelude::{Did, RepoId}; use radicle::profile; use radicle::storage; @@ -810,12 +811,12 @@ fn open( assignees: Vec, options: &Options, cache: &mut issue::Cache, cob::cache::StoreWriter>, - signer: &G, + signer: &Device, profile: &Profile, ) -> anyhow::Result<()> where R: ReadRepository + WriteRepository + cob::Store, - G: Signer, + G: crypto::signature::Signer, { let (title, description) = if let (Some(t), Some(d)) = (title.as_ref(), description.as_ref()) { (t.to_owned(), d.to_owned()) @@ -845,11 +846,11 @@ fn edit<'a, 'g, R, G>( id: Rev, title: Option, description: Option, - signer: &G, + signer: &Device, ) -> anyhow::Result> where R: WriteRepository + ReadRepository + cob::Store, - G: radicle::crypto::Signer, + G: crypto::signature::Signer, { let id = id.resolve(&repo.backend)?; let mut issue = issues.get_mut(&id)?; diff --git a/radicle-cli/src/commands/job.rs b/radicle-cli/src/commands/job.rs index 365a19e2..ce6f1f7a 100644 --- a/radicle-cli/src/commands/job.rs +++ b/radicle-cli/src/commands/job.rs @@ -4,7 +4,8 @@ use std::ffi::OsString; use anyhow::{anyhow, Context as _}; use radicle::cob::job::{JobStore, Reason, State}; -use radicle::crypto::Signer; +use radicle::crypto; +use radicle::node::device::Device; use radicle::node::Handle; use radicle::storage::{WriteRepository, WriteStorage}; use radicle::{cob, Node}; @@ -260,13 +261,17 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { Ok(()) } -fn trigger( +fn trigger( commit: &Rev, store: &mut JobStore, repo: &radicle::storage::git::Repository, - signer: &G, + signer: &Device, quiet: bool, -) -> anyhow::Result<()> { +) -> anyhow::Result<()> +where + R: WriteRepository + cob::Store, + G: crypto::signature::Signer, +{ let commit = commit.resolve(&repo.backend)?; let job = store.create(commit, signer)?; if !quiet { @@ -275,14 +280,18 @@ fn trigger( Ok(()) } -fn start( +fn start( job_id: &Rev, run_id: &str, info_url: Option, store: &mut JobStore, repo: &radicle::storage::git::Repository, - signer: &G, -) -> anyhow::Result<()> { + signer: &Device, +) -> anyhow::Result<()> +where + R: WriteRepository + cob::Store, + G: crypto::signature::Signer, +{ let job_id = job_id.resolve(&repo.backend)?; let mut job = store.get_mut(&job_id)?; @@ -349,13 +358,17 @@ fn show( Ok(()) } -fn finish( +fn finish( job_id: &Rev, reason: Reason, store: &mut JobStore, repo: &radicle::storage::git::Repository, - signer: &G, -) -> anyhow::Result<()> { + signer: &Device, +) -> anyhow::Result<()> +where + R: WriteRepository + cob::Store, + G: crypto::signature::Signer, +{ let job_id = job_id.resolve(&repo.backend)?; let mut job = store.get_mut(&job_id)?; diff --git a/radicle-cli/src/commands/patch/edit.rs b/radicle-cli/src/commands/patch/edit.rs index 8050b811..70ec695f 100644 --- a/radicle-cli/src/commands/patch/edit.rs +++ b/radicle-cli/src/commands/patch/edit.rs @@ -3,6 +3,7 @@ use super::*; use radicle::cob; use radicle::cob::patch; use radicle::crypto; +use radicle::node::device::Device; use radicle::prelude::*; use radicle::storage::git::Repository; @@ -32,10 +33,10 @@ fn edit_root( mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>, title: String, description: String, - signer: &G, + signer: &Device, ) -> anyhow::Result<()> where - G: crypto::Signer, + G: crypto::signature::Signer, { let title = if title != patch.title() { Some(title) @@ -75,10 +76,10 @@ fn edit_revision( revision: patch::RevisionId, mut title: String, description: String, - signer: &G, + signer: &Device, ) -> anyhow::Result<()> where - G: crypto::Signer, + G: crypto::signature::Signer, { let embeds = patch.embeds().to_owned(); let description = if description.is_empty() { diff --git a/radicle-cli/src/commands/patch/review.rs b/radicle-cli/src/commands/patch/review.rs index 08dcbd22..bf8436f1 100644 --- a/radicle-cli/src/commands/patch/review.rs +++ b/radicle-cli/src/commands/patch/review.rs @@ -89,10 +89,10 @@ pub fn run( .minimal(true) .context_lines(unified as u32); - builder::ReviewBuilder::new(patch_id, signer, repository) + builder::ReviewBuilder::new(patch_id, repository) .hunk(hunk) .verdict(verdict) - .run(revision, &mut opts)?; + .run(revision, &mut opts, &signer)?; } Operation::Review { verdict, .. } => { let message = options.message.get(REVIEW_HELP_MSG)?; diff --git a/radicle-cli/src/commands/patch/review/builder.rs b/radicle-cli/src/commands/patch/review/builder.rs index 8a5f91ba..fe7f2448 100644 --- a/radicle-cli/src/commands/patch/review/builder.rs +++ b/radicle-cli/src/commands/patch/review/builder.rs @@ -21,7 +21,9 @@ use std::{fmt, io}; use radicle::cob; use radicle::cob::patch::{PatchId, Revision, Verdict}; use radicle::cob::{CodeLocation, CodeRange}; +use radicle::crypto; use radicle::git; +use radicle::node::device::Device; use radicle::prelude::*; use radicle::storage::git::{cob::DraftStore, Repository}; use radicle_git_ext::Oid; @@ -569,11 +571,9 @@ impl<'a> Brain<'a> { } /// Builds a patch review interactively, across multiple files. -pub struct ReviewBuilder<'a, G> { +pub struct ReviewBuilder<'a> { /// Patch being reviewed. patch_id: PatchId, - /// Signer. - signer: G, /// Stored copy of repository. repo: &'a Repository, /// Single hunk review. @@ -582,12 +582,11 @@ pub struct ReviewBuilder<'a, G> { verdict: Option, } -impl<'a, G: Signer> ReviewBuilder<'a, G> { +impl<'a> ReviewBuilder<'a> { /// Create a new review builder. - pub fn new(patch_id: PatchId, signer: G, repo: &'a Repository) -> Self { + pub fn new(patch_id: PatchId, repo: &'a Repository) -> Self { Self { patch_id, - signer, repo, hunk: None, verdict: None, @@ -607,9 +606,16 @@ impl<'a, G: Signer> ReviewBuilder<'a, G> { } /// Run the review builder for the given revision. - pub fn run(self, revision: &Revision, opts: &mut git::raw::DiffOptions) -> anyhow::Result<()> { + pub fn run( + self, + revision: &Revision, + opts: &mut git::raw::DiffOptions, + signer: &Device, + ) -> anyhow::Result<()> + where + G: crypto::signature::Signer, + { let repo = self.repo.raw(); - let signer = &self.signer; let base = repo.find_commit((*revision.base()).into())?; let patch_id = self.patch_id; let tree = { diff --git a/radicle-cli/src/terminal/io.rs b/radicle-cli/src/terminal/io.rs index 9b1f6dd6..79bcc37e 100644 --- a/radicle-cli/src/terminal/io.rs +++ b/radicle-cli/src/terminal/io.rs @@ -3,7 +3,8 @@ use radicle::cob::issue::Issue; use radicle::cob::thread::{Comment, CommentId}; use radicle::cob::Reaction; use radicle::crypto::ssh::keystore::MemorySigner; -use radicle::crypto::{ssh::Keystore, Signer}; +use radicle::crypto::ssh::Keystore; +use radicle::node::device::{BoxedDevice, Device}; use radicle::profile::env::RAD_PASSPHRASE; use radicle::profile::Profile; @@ -43,7 +44,7 @@ impl inquire::validator::StringValidator for PassphraseValidator { /// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user, /// if we're connected to a TTY. -pub fn signer(profile: &Profile) -> anyhow::Result> { +pub fn signer(profile: &Profile) -> anyhow::Result { if let Ok(signer) = profile.signer() { return Ok(signer); } @@ -62,7 +63,7 @@ pub fn signer(profile: &Profile) -> anyhow::Result> { spinner.finish(); - Ok(signer.boxed()) + Ok(Device::from(signer).boxed()) } pub fn comment_select(issue: &Issue) -> anyhow::Result<(&CommentId, &Comment)> { diff --git a/radicle-cob/Cargo.toml b/radicle-cob/Cargo.toml index 601c0f2d..97847c72 100644 --- a/radicle-cob/Cargo.toml +++ b/radicle-cob/Cargo.toml @@ -25,6 +25,7 @@ nonempty = { version = "0.9.0", features = ["serialize"] } once_cell = { version = "1.13" } radicle-git-ext = { version = "0.8.0", features = ["serde"] } serde_json = { version = "1.0" } +signature = { version = "2.2" } thiserror = { version = "1.0" } [dependencies.git2] diff --git a/radicle-cob/src/backend/git/change.rs b/radicle-cob/src/backend/git/change.rs index 0d9695cf..c36a7a15 100644 --- a/radicle-cob/src/backend/git/change.rs +++ b/radicle-cob/src/backend/git/change.rs @@ -103,7 +103,7 @@ impl change::Storage for git2::Repository { spec: store::Template, ) -> Result where - Signer: crypto::Signer, + Signer: signature::Signer, { let change::Template { type_name, @@ -115,11 +115,7 @@ impl change::Storage for git2::Repository { let manifest = store::Manifest::new(type_name, Version::default()); let revision = write_manifest(self, &manifest, embeds, &contents)?; let tree = self.find_tree(revision)?; - let signature = { - let sig = signer.sign(revision.as_bytes()); - let key = signer.public_key(); - ExtendedSignature::new(*key, sig) - }; + let signature = signer.sign(revision.as_bytes()); // Make sure there are no duplicates in the related list. related.sort(); diff --git a/radicle-cob/src/change/store.rs b/radicle-cob/src/change/store.rs index 3a5c24c6..f5f9100c 100644 --- a/radicle-cob/src/change/store.rs +++ b/radicle-cob/src/change/store.rs @@ -27,7 +27,7 @@ pub trait Storage { template: Template, ) -> Result, Self::StoreError> where - G: crypto::Signer; + G: signature::Signer; /// Load a change entry. #[allow(clippy::type_complexity)] diff --git a/radicle-cob/src/object/collaboration/create.rs b/radicle-cob/src/object/collaboration/create.rs index 68181754..ce16d6d6 100644 --- a/radicle-cob/src/object/collaboration/create.rs +++ b/radicle-cob/src/object/collaboration/create.rs @@ -64,7 +64,7 @@ pub fn create( where T: Evaluate, S: Store, - G: crypto::Signer, + G: signature::Signer, { let type_name = args.type_name.clone(); let version = args.version; diff --git a/radicle-cob/src/object/collaboration/update.rs b/radicle-cob/src/object/collaboration/update.rs index 8a632cc1..706197d5 100644 --- a/radicle-cob/src/object/collaboration/update.rs +++ b/radicle-cob/src/object/collaboration/update.rs @@ -7,7 +7,7 @@ use radicle_crypto::PublicKey; use crate::{ change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate, - ObjectId, Store, TypeName, + ExtendedSignature, ObjectId, Store, TypeName, }; use super::error; @@ -67,7 +67,7 @@ pub fn update( where T: Evaluate, S: Store, - G: crypto::Signer, + G: signature::Signer, { let Update { type_name: ref typename, diff --git a/radicle-cob/src/test/storage.rs b/radicle-cob/src/test/storage.rs index a5f56fb3..10762309 100644 --- a/radicle-cob/src/test/storage.rs +++ b/radicle-cob/src/test/storage.rs @@ -6,7 +6,7 @@ use tempfile::TempDir; use crate::{ change, object::{self, Reference}, - ObjectId, Store, + signatures, ObjectId, Store, }; pub mod error { @@ -76,7 +76,7 @@ impl change::Storage for Storage { Self::StoreError, > where - Signer: crypto::Signer, + Signer: signature::Signer, { self.as_raw().store(authority, parents, signer, spec) } diff --git a/radicle-crypto/Cargo.toml b/radicle-crypto/Cargo.toml index ab902741..8eac2298 100644 --- a/radicle-crypto/Cargo.toml +++ b/radicle-crypto/Cargo.toml @@ -23,6 +23,7 @@ fastrand = { version = "2.0.0", default-features = false, optional = true } multibase = { version = "0.9.1" } ec25519 = { version = "0.1.0", features = [] } serde = { version = "1", features = ["derive"] } +signature = { version = "2.2" } sqlite = { version = "0.32.0", optional = true, features = ["bundled"] } thiserror = { version = "1" } zeroize = { version = "1.5.7" } diff --git a/radicle-crypto/src/lib.rs b/radicle-crypto/src/lib.rs index 5417e342..5a60a4b1 100644 --- a/radicle-crypto/src/lib.rs +++ b/radicle-crypto/src/lib.rs @@ -8,6 +8,8 @@ use thiserror::Error; pub use ed25519::{edwards25519, Error, KeyPair, Seed}; +pub extern crate signature; + #[cfg(feature = "ssh")] pub mod ssh; #[cfg(any(test, feature = "test"))] diff --git a/radicle-crypto/src/ssh.rs b/radicle-crypto/src/ssh.rs index 1f7e7e54..474ac92b 100644 --- a/radicle-crypto/src/ssh.rs +++ b/radicle-crypto/src/ssh.rs @@ -32,6 +32,12 @@ pub struct ExtendedSignature { pub sig: crypto::Signature, } +impl From for crypto::Signature { + fn from(ExtendedSignature { sig, .. }: ExtendedSignature) -> Self { + sig + } +} + impl ExtendedSignature { /// Create a new extended signature. pub fn new(public_key: crypto::PublicKey, signature: crypto::Signature) -> Self { diff --git a/radicle-crypto/src/ssh/agent.rs b/radicle-crypto/src/ssh/agent.rs index a4967a67..80419140 100644 --- a/radicle-crypto/src/ssh/agent.rs +++ b/radicle-crypto/src/ssh/agent.rs @@ -11,6 +11,8 @@ pub use std::net::TcpStream as Stream; #[cfg(unix)] pub use std::os::unix::net::UnixStream as Stream; +use super::ExtendedSignature; + pub struct Agent { client: AgentClient, } @@ -58,6 +60,21 @@ pub struct AgentSigner { public: PublicKey, } +impl signature::Signer for AgentSigner { + fn try_sign(&self, msg: &[u8]) -> Result { + Signer::try_sign(self, msg).map_err(signature::Error::from_source) + } +} + +impl signature::Signer for AgentSigner { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(ExtendedSignature { + key: self.public, + sig: Signer::try_sign(self, msg).map_err(signature::Error::from_source)?, + }) + } +} + impl AgentSigner { pub fn new(agent: Agent, public: PublicKey) -> Self { let agent = RefCell::new(agent); diff --git a/radicle-crypto/src/ssh/keystore.rs b/radicle-crypto/src/ssh/keystore.rs index d7dfdd86..ab5f2d1a 100644 --- a/radicle-crypto/src/ssh/keystore.rs +++ b/radicle-crypto/src/ssh/keystore.rs @@ -10,6 +10,8 @@ use zeroize::Zeroizing; use crate::{KeyPair, PublicKey, SecretKey, Signature, Signer, SignerError}; +use super::ExtendedSignature; + /// A secret key passphrase. pub type Passphrase = Zeroizing; @@ -186,6 +188,21 @@ pub struct MemorySigner { secret: Zeroizing, } +impl signature::Signer for MemorySigner { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(Signer::sign(self, msg)) + } +} + +impl signature::Signer for MemorySigner { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(ExtendedSignature { + key: self.public, + sig: Signer::sign(self, msg), + }) + } +} + impl Signer for MemorySigner { fn public_key(&self) -> &PublicKey { &self.public diff --git a/radicle-crypto/src/test/signer.rs b/radicle-crypto/src/test/signer.rs index 997beb21..76c1fb2b 100644 --- a/radicle-crypto/src/test/signer.rs +++ b/radicle-crypto/src/test/signer.rs @@ -1,4 +1,6 @@ -use crate::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer, SignerError}; +use crate::{ + ssh::ExtendedSignature, KeyPair, PublicKey, SecretKey, Seed, Signature, Signer, SignerError, +}; #[derive(Debug, Clone)] pub struct MockSigner { @@ -6,6 +8,21 @@ pub struct MockSigner { sk: SecretKey, } +impl signature::Signer for MockSigner { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(ExtendedSignature { + key: self.pk, + sig: signature::Signer::::try_sign(self, msg)?, + }) + } +} + +impl signature::Signer for MockSigner { + fn try_sign(&self, msg: &[u8]) -> Result { + Ok(Signature(self.sk.sign(msg, None))) + } +} + impl MockSigner { pub fn new(rng: &mut fastrand::Rng) -> Self { let mut seed: [u8; 32] = [0; 32]; diff --git a/radicle-node/src/lib.rs b/radicle-node/src/lib.rs index 768b3b9a..269a63e7 100644 --- a/radicle-node/src/lib.rs +++ b/radicle-node/src/lib.rs @@ -34,7 +34,7 @@ pub const VERSION: Version = Version { pub mod prelude { pub use crate::bounded::BoundedVec; - pub use crate::crypto::{PublicKey, Signature, Signer}; + pub use crate::crypto::{PublicKey, Signature}; pub use crate::deserializer::Deserializer; pub use crate::identity::{Did, RepoId}; pub use crate::node::Address; diff --git a/radicle-node/src/main.rs b/radicle-node/src/main.rs index f62a9988..08e19e01 100644 --- a/radicle-node/src/main.rs +++ b/radicle-node/src/main.rs @@ -5,7 +5,7 @@ use anyhow::Context; use crossbeam_channel as chan; use radicle::logger; -use radicle::prelude::Signer; +use radicle::node::device::Device; use radicle::profile; use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner}; use radicle_node::{Runtime, VERSION}; @@ -99,7 +99,9 @@ fn execute() -> anyhow::Result<()> { let passphrase = profile::env::passphrase(); let keystore = Keystore::new(&home.keys()); - let signer = MemorySigner::load(&keystore, passphrase).context("couldn't load secret key")?; + let signer = Device::from( + MemorySigner::load(&keystore, passphrase).context("couldn't load secret key")?, + ); log::info!(target: "node", "Node ID is {}", signer.public_key()); diff --git a/radicle-node/src/runtime.rs b/radicle-node/src/runtime.rs index 0abfcc13..ad2d5cc1 100644 --- a/radicle-node/src/runtime.rs +++ b/radicle-node/src/runtime.rs @@ -9,6 +9,8 @@ use crossbeam_channel as chan; use cyphernet::Ecdh; use netservices::resource::NetAccept; use radicle::cob::migrate; +use radicle::crypto; +use radicle::node::device::Device; use radicle_fetch::FetchLimit; use radicle_signals::Signal; use reactor::poller::popol; @@ -25,7 +27,6 @@ use radicle::profile::Home; use radicle::{cob, git, storage, Storage}; use crate::control; -use crate::crypto::Signer; use crate::node::{routing, NodeId}; use crate::service::message::NodeAnnouncement; use crate::service::{gossip, policy, Event, INITIAL_SUBSCRIBE_BACKLOG_DELTA}; @@ -119,10 +120,10 @@ impl Runtime { config: service::Config, listen: Vec, signals: chan::Receiver, - signer: G, + signer: Device, ) -> Result where - G: Signer + Ecdh + Clone + 'static, + G: crypto::signature::Signer + Ecdh + Clone + 'static, { let id = *signer.public_key(); let alias = config.alias.clone(); diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs index db08f00d..613dc195 100644 --- a/radicle-node/src/service.rs +++ b/radicle-node/src/service.rs @@ -28,6 +28,7 @@ use radicle::node::address; use radicle::node::address::Store as _; use radicle::node::address::{AddressBook, AddressType, KnownAddress}; use radicle::node::config::PeerConfig; +use radicle::node::device::Device; use radicle::node::refs::Store as _; use radicle::node::routing::Store as _; use radicle::node::seed; @@ -37,7 +38,6 @@ use radicle::storage::refs::SIGREFS_BRANCH; use radicle::storage::RepositoryError; use radicle_fetch::policy::SeedingPolicy; -use crate::crypto::Signer; use crate::identity::RepoId; use crate::node::routing; use crate::node::routing::InsertResult; @@ -390,7 +390,7 @@ pub struct Service { /// Service configuration. config: Config, /// Our cryptographic signer and key. - signer: G, + signer: Device, /// Project storage. storage: S, /// Node database. @@ -444,10 +444,7 @@ pub struct Service { metrics: Metrics, } -impl Service -where - G: crypto::Signer, -{ +impl Service { /// Get the local node id. pub fn node_id(&self) -> NodeId { *self.signer.public_key() @@ -467,14 +464,14 @@ impl Service where D: Store, S: ReadStorage + 'static, - G: Signer, + G: crypto::signature::Signer, { pub fn new( config: Config, db: Stores, storage: S, policies: policy::Config, - signer: G, + signer: Device, rng: Rng, node: NodeAnnouncement, emitter: Emitter, @@ -596,7 +593,7 @@ where } /// Get the local signer. - pub fn signer(&self) -> &G { + pub fn signer(&self) -> &Device { &self.signer } @@ -2658,7 +2655,7 @@ pub trait ServiceState { impl ServiceState for Service where D: routing::Store, - G: Signer, + G: crypto::signature::Signer, S: ReadStorage, { fn nid(&self) -> &NodeId { diff --git a/radicle-node/src/service/gossip/store.rs b/radicle-node/src/service/gossip/store.rs index 9ae6dad8..49d9ba4e 100644 --- a/radicle-node/src/service/gossip/store.rs +++ b/radicle-node/src/service/gossip/store.rs @@ -399,7 +399,7 @@ mod test { use crate::test::arbitrary; use localtime::LocalTime; use radicle::assert_matches; - use radicle_crypto::test::signer::MockSigner; + use radicle::node::device::Device; #[test] fn test_announced() { @@ -407,7 +407,7 @@ mod test { let nid = arbitrary::gen::(1); let rid = arbitrary::gen::(1); let timestamp = LocalTime::now().into(); - let signer = MockSigner::default(); + let signer = Device::mock(); let refs = AnnouncementMessage::Refs(RefsAnnouncement { rid, refs: BoundedVec::new(), diff --git a/radicle-node/src/service/message.rs b/radicle-node/src/service/message.rs index 45201b3b..2b92e62d 100644 --- a/radicle-node/src/service/message.rs +++ b/radicle-node/src/service/message.rs @@ -2,6 +2,7 @@ use std::{fmt, io, mem}; use nonempty::NonEmpty; use radicle::git; +use radicle::node::device::Device; use radicle::storage::refs::RefsAt; use crate::crypto; @@ -260,7 +261,12 @@ pub enum AnnouncementMessage { impl AnnouncementMessage { /// Sign this announcement message. - pub fn signed(self, signer: &G) -> Announcement { + pub fn signed(self, signer: &Device) -> Announcement + where + G: crypto::signature::Signer, + { + use crypto::signature::Signer as _; + let msg = wire::serialize(&self); let signature = signer.sign(&msg); @@ -442,11 +448,17 @@ impl Message { .into() } - pub fn node(message: NodeAnnouncement, signer: &G) -> Self { + pub fn node>( + message: NodeAnnouncement, + signer: &Device, + ) -> Self { AnnouncementMessage::from(message).signed(signer).into() } - pub fn inventory(message: InventoryAnnouncement, signer: &G) -> Self { + pub fn inventory>( + message: InventoryAnnouncement, + signer: &Device, + ) -> Self { AnnouncementMessage::from(message).signed(signer).into() } @@ -587,7 +599,6 @@ mod tests { use radicle::git::raw; use super::*; - use crate::crypto::test::signer::MockSigner; use crate::prelude::*; use crate::test::arbitrary; use crate::wire::Encode; @@ -595,7 +606,7 @@ mod tests { #[test] fn test_ref_remote_limit() { let mut refs = BoundedVec::<_, REF_REMOTE_LIMIT>::new(); - let signer = MockSigner::default(); + let signer = Device::mock(); let at = raw::Oid::zero().into(); assert_eq!(refs.capacity(), REF_REMOTE_LIMIT); @@ -613,7 +624,7 @@ mod tests { refs, timestamp: LocalTime::now().into(), }) - .signed(&MockSigner::default()) + .signed(&Device::mock()) .into(); let mut buf: Vec = Vec::new(); @@ -633,7 +644,7 @@ mod tests { .expect("size within bounds limit"), timestamp: LocalTime::now().into(), }, - &MockSigner::default(), + &Device::mock(), ); let mut buf: Vec = Vec::new(); assert!( @@ -655,7 +666,7 @@ mod tests { #[quickcheck] fn prop_refs_announcement_signing(rid: RepoId) { - let signer = MockSigner::new(&mut fastrand::Rng::new()); + let signer = Device::mock_rng(&mut fastrand::Rng::new()); let timestamp = Timestamp::EPOCH; let at = raw::Oid::zero().into(); let refs = BoundedVec::collect_from( diff --git a/radicle-node/src/test/environment.rs b/radicle-node/src/test/environment.rs index 4fc6fb52..a68d11ce 100644 --- a/radicle-node/src/test/environment.rs +++ b/radicle-node/src/test/environment.rs @@ -11,12 +11,14 @@ use crossbeam_channel as chan; use localtime::LocalTime; use radicle::cob::{issue, migrate}; +use radicle::crypto; use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; use radicle::crypto::test::signer::MockSigner; -use radicle::crypto::{KeyPair, Seed, Signer}; +use radicle::crypto::{KeyPair, Seed}; use radicle::git::refname; use radicle::identity::{RepoId, Visibility}; use radicle::node::config::ConnectAddress; +use radicle::node::device::Device; use radicle::node::policy::store as policy; use radicle::node::seed::Store as _; use radicle::node::{Alias, Database, UserAgent, POLICIES_DB_FILE}; @@ -160,7 +162,7 @@ impl Environment { pub struct Node { pub id: NodeId, pub home: Home, - pub signer: G, + pub signer: Device, pub storage: Storage, pub config: Config, pub db: service::Stores, @@ -169,7 +171,7 @@ pub struct Node { impl Node { pub fn new(profile: Profile) -> Self { - let signer = MemorySigner::load(&profile.keystore, None).unwrap(); + let signer = Device::from(MemorySigner::load(&profile.keystore, None).unwrap()); let id = *profile.id(); let policies_db = profile.home.node().join(POLICIES_DB_FILE); let policies = policy::Store::open(policies_db).unwrap(); @@ -189,17 +191,19 @@ impl Node { } /// Handle to a running node. -pub struct NodeHandle { +pub struct NodeHandle + cyphernet::Ecdh + 'static> { pub id: NodeId, pub storage: Storage, - pub signer: G, + pub signer: Device, pub home: Home, pub addr: net::SocketAddr, pub thread: ManuallyDrop>>, pub handle: ManuallyDrop, } -impl Drop for NodeHandle { +impl + cyphernet::Ecdh + 'static> Drop + for NodeHandle +{ fn drop(&mut self) { log::debug!(target: "test", "Node {} shutting down..", self.id); @@ -213,7 +217,7 @@ impl Drop for NodeHandle { } } -impl NodeHandle { +impl + cyphernet::Ecdh> NodeHandle { /// Connect this node to another node, and wait for the connection to be established both ways. pub fn connect(&mut self, remote: &NodeHandle) -> &mut Self { let local_events = self.handle.events(); @@ -481,7 +485,7 @@ impl Node { .collect::(), ); let home = Home::new(home).unwrap(); - let signer = MockSigner::default(); + let signer = Device::mock(); let storage = Storage::open( home.storage(), git::UserInfo { @@ -507,7 +511,10 @@ impl Node { } } -impl + Signer + Clone> Node { +impl Node +where + G: cyphernet::Ecdh + crypto::signature::Signer + Clone, +{ /// Spawn a node in its own thread. pub fn spawn(self) -> NodeHandle { let listen = vec![([0, 0, 0, 0], 0).into()]; @@ -606,7 +613,7 @@ impl + Signer + Clone> Node { /// Checks whether the nodes have converged in their routing tables. #[track_caller] -pub fn converge<'a, G: Signer + cyphernet::Ecdh + 'static>( +pub fn converge<'a, G: crypto::signature::Signer + cyphernet::Ecdh + 'static>( nodes: impl IntoIterator>, ) -> BTreeSet<(RepoId, NodeId)> { let nodes = nodes.into_iter().collect::>(); diff --git a/radicle-node/src/test/gossip.rs b/radicle-node/src/test/gossip.rs index 0184e088..7c530170 100644 --- a/radicle-node/src/test/gossip.rs +++ b/radicle-node/src/test/gossip.rs @@ -1,7 +1,7 @@ use std::str::FromStr; -use radicle::crypto::test::signer::MockSigner; use radicle::node; +use radicle::node::device::Device; use radicle::node::UserAgent; use radicle::test::fixtures::gen; @@ -17,7 +17,7 @@ pub fn messages(count: usize, now: LocalTime, delta: LocalDuration) -> Vec { impl simulator::Peer for Peer where S: WriteStorage + 'static, - G: Signer + 'static, + G: crypto::signature::Signer + 'static, { fn init(&mut self) {} @@ -98,11 +99,11 @@ where } } -pub struct Config { +pub struct Config + 'static> { pub config: service::Config, pub local_time: LocalTime, pub policy: SeedingPolicy, - pub signer: G, + pub signer: Device, pub rng: fastrand::Rng, pub tmp: tempfile::TempDir, } @@ -110,7 +111,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { let mut rng = fastrand::Rng::new(); - let signer = MockSigner::new(&mut rng); + let signer = Device::mock_rng(&mut rng); let tmp = tempfile::TempDir::new().unwrap(); let config = service::Config::test(Alias::from_str("mocky").unwrap()); @@ -125,7 +126,7 @@ impl Default for Config { } } -impl Peer { +impl> Peer { pub fn project(&mut self, name: &str, description: &str) -> RepoId { radicle::storage::git::transport::local::register(self.storage().clone()); @@ -148,7 +149,7 @@ impl Peer { impl Peer where S: WriteStorage + 'static, - G: Signer + 'static, + G: crypto::signature::Signer + 'static, { pub fn config( name: &'static str, @@ -387,7 +388,10 @@ where .expect("`inventory-announcement` must be sent"); } - pub fn connect_to( + pub fn connect_to< + T: WriteStorage + 'static, + H: crypto::signature::Signer + 'static, + >( &mut self, peer: &Peer, ) { diff --git a/radicle-node/src/test/simulator.rs b/radicle-node/src/test/simulator.rs index 5ce87a0d..2f1648f9 100644 --- a/radicle-node/src/test/simulator.rs +++ b/radicle-node/src/test/simulator.rs @@ -14,7 +14,7 @@ use std::{fmt, io, net}; use localtime::{LocalDuration, LocalTime}; use log::*; -use crate::crypto::Signer; +use crate::crypto; use crate::prelude::{Address, RepoId}; use crate::service::io::Io; use crate::service::{DisconnectReason, Event, Message, Metrics, NodeId}; @@ -202,7 +202,11 @@ pub struct Simulation { signer: PhantomData, } -impl Simulation { +impl Simulation +where + S: WriteStorage + 'static, + G: crypto::signature::Signer, +{ /// Create a new simulation. pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self { Self { diff --git a/radicle-node/src/tests.rs b/radicle-node/src/tests.rs index 5af18379..3d8ff039 100644 --- a/radicle-node/src/tests.rs +++ b/radicle-node/src/tests.rs @@ -12,6 +12,7 @@ use netservices::Direction as Link; use once_cell::sync::Lazy; use radicle::identity::Visibility; use radicle::node::address::Store as _; +use radicle::node::device::Device; use radicle::node::refs::Store as _; use radicle::node::routing::Store as _; use radicle::node::{ConnectOptions, DEFAULT_TIMEOUT}; @@ -21,7 +22,6 @@ use radicle::test::arbitrary::gen; use radicle::test::storage::MockRepository; use crate::collections::{RandomMap, RandomSet}; -use crate::crypto::test::signer::MockSigner; use crate::identity::RepoId; use crate::node; use crate::node::config::*; @@ -270,7 +270,7 @@ fn test_inventory_sync() { [7, 7, 7, 7], Storage::open(tmp.path().join("alice"), fixtures::user()).unwrap(), ); - let bob_signer = MockSigner::default(); + let bob_signer = Device::mock(); let bob_storage = fixtures::storage(tmp.path().join("bob"), &bob_signer).unwrap(); let bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_storage); let now = LocalTime::now().into(); @@ -682,7 +682,7 @@ fn test_refs_announcement_relay_public() { let bob = { let mut rng = fastrand::Rng::new(); - let signer = MockSigner::new(&mut rng); + let signer = Device::mock_rng(&mut rng); let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap(); Peer::config( @@ -766,7 +766,7 @@ fn test_refs_announcement_relay_private() { let bob = { let mut rng = fastrand::Rng::new(); - let signer = MockSigner::new(&mut rng); + let signer = Device::mock_rng(&mut rng); let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap(); Peer::config( @@ -855,7 +855,7 @@ fn test_refs_announcement_fetch_trusted_no_inventory() { ); let bob = { let mut rng = fastrand::Rng::new(); - let signer = MockSigner::new(&mut rng); + let signer = Device::mock_rng(&mut rng); let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap(); Peer::config( @@ -967,7 +967,7 @@ fn test_refs_announcement_no_subscribe() { fn test_refs_announcement_offline() { let tmp = tempfile::tempdir().unwrap(); let mut alice = { - let signer = MockSigner::default(); + let signer = Device::mock(); let storage = fixtures::storage(tmp.path().join("alice"), &signer).unwrap(); Peer::config( diff --git a/radicle-node/src/tests/e2e.rs b/radicle-node/src/tests/e2e.rs index ffa83c2a..12b457a9 100644 --- a/radicle-node/src/tests/e2e.rs +++ b/radicle-node/src/tests/e2e.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, thread, time}; -use radicle::crypto::{test::signer::MockSigner, Signer}; +use radicle::node::device::Device; use radicle::node::{Alias, ConnectResult, FetchResult, Handle as _, DEFAULT_TIMEOUT}; use radicle::storage::{ ReadRepository, ReadStorage, RefUpdate, RemoteRepository, SignRepository, ValidateRepository, @@ -281,7 +281,7 @@ fn test_replication_invalid() { let tmp = tempfile::tempdir().unwrap(); let alice = Node::init(tmp.path(), config::relay("alice")); let mut bob = Node::init(tmp.path(), config::relay("bob")); - let carol = MockSigner::default(); + let carol = Device::mock(); let acme = bob.project("acme", ""); let repo = bob.storage.repository_mut(acme).unwrap(); let (_, head) = repo.head().unwrap(); @@ -418,7 +418,7 @@ fn test_fetch_followed_remotes() { let mut signers = Vec::with_capacity(5); { for _ in 0..5 { - let signer = MockSigner::default(); + let signer = Device::mock(); rad::fork_remote(acme, &alice.id, &signer, &alice.storage).unwrap(); signers.push(signer); } @@ -473,7 +473,7 @@ fn test_missing_remote() { let mut alice = alice.spawn(); let mut bob = bob.spawn(); - let carol = MockSigner::default(); + let carol = Device::mock(); alice.connect(&bob); converge([&alice, &bob]); diff --git a/radicle-node/src/wire/message.rs b/radicle-node/src/wire/message.rs index 69e54fd9..4e8062af 100644 --- a/radicle-node/src/wire/message.rs +++ b/radicle-node/src/wire/message.rs @@ -458,9 +458,9 @@ impl wire::Decode for ZeroBytes { mod tests { use super::*; use qcheck_macros::quickcheck; + use radicle::node::device::Device; use radicle::node::UserAgent; use radicle::storage::refs::RefsAt; - use radicle_crypto::test::signer::MockSigner; use crate::deserializer::Deserializer; use crate::test::arbitrary; @@ -468,7 +468,7 @@ mod tests { #[test] fn test_refs_ann_max_size() { - let signer = MockSigner::default(); + let signer = Device::mock(); let refs: [RefsAt; REF_REMOTE_LIMIT] = arbitrary::gen(1); let ann = AnnouncementMessage::Refs(RefsAnnouncement { rid: arbitrary::gen(1), @@ -484,7 +484,7 @@ mod tests { #[test] fn test_inv_ann_max_size() { - let signer = MockSigner::default(); + let signer = Device::mock(); let inv: [RepoId; INVENTORY_LIMIT] = arbitrary::gen(1); let ann = AnnouncementMessage::Inventory(InventoryAnnouncement { inventory: BoundedVec::collect_from(&mut inv.into_iter()), @@ -499,7 +499,7 @@ mod tests { #[test] fn test_node_ann_max_size() { - let signer = MockSigner::default(); + let signer = Device::mock(); let addrs: [Address; ADDRESS_LIMIT] = arbitrary::gen(1); let alias = ['@'; radicle::node::MAX_ALIAS_LENGTH]; let ann = AnnouncementMessage::Node(NodeAnnouncement { diff --git a/radicle-node/src/wire/protocol.rs b/radicle-node/src/wire/protocol.rs index a34a1ba2..3b0f9374 100644 --- a/radicle-node/src/wire/protocol.rs +++ b/radicle-node/src/wire/protocol.rs @@ -18,14 +18,15 @@ use localtime::LocalTime; use netservices::resource::{ListenerEvent, NetAccept, NetTransport, SessionEvent}; use netservices::session::{NoiseSession, ProtocolArtifact, Socks5Session}; use netservices::{NetConnection, NetReader, NetWriter}; +use radicle::node::device::Device; use reactor::{ResourceId, ResourceType, Timestamp}; use radicle::collections::RandomMap; +use radicle::crypto; use radicle::node::config::AddressConfig; use radicle::node::NodeId; use radicle::storage::WriteStorage; -use crate::crypto::Signer; use crate::prelude::Deserializer; use crate::service; use crate::service::io::Io; @@ -306,13 +307,13 @@ impl Peers { } /// Wire protocol implementation for a set of peers. -pub struct Wire { +pub struct Wire + Ecdh> { /// Backing service instance. service: Service, /// Worker pool interface. worker: chan::Sender, /// Used for authentication. - signer: G, + signer: Device, /// Node metrics. metrics: service::Metrics, /// Internal queue of actions to send to the reactor. @@ -331,9 +332,9 @@ impl Wire where D: service::Store, S: WriteStorage + 'static, - G: Signer + Ecdh, + G: crypto::signature::Signer + Ecdh, { - pub fn new(service: Service, worker: chan::Sender, signer: G) -> Self { + pub fn new(service: Service, worker: chan::Sender, signer: Device) -> Self { assert!(service.started().is_some(), "Service must be initialized"); Self { @@ -500,7 +501,7 @@ impl reactor::Handler for Wire where D: service::Store + Send, S: WriteStorage + Send + 'static, - G: Signer + Ecdh + Clone + Send, + G: crypto::signature::Signer + Ecdh + Clone + Send, { type Listener = NetAccept>; type Transport = NetTransport>; @@ -561,14 +562,17 @@ where return; } - let session = - match accept::(remote.clone().into(), connection, self.signer.clone()) { - Ok(s) => s, - Err(e) => { - log::error!(target: "wire", "Error creating session for {ip}: {e}"); - return; - } - }; + let session = match accept::( + remote.clone().into(), + connection, + self.signer.clone().into_inner(), + ) { + Ok(s) => s, + Err(e) => { + log::error!(target: "wire", "Error creating session for {ip}: {e}"); + return; + } + }; let transport = match NetTransport::with_session(session, Link::Inbound) { Ok(transport) => transport, Err(err) => { @@ -963,7 +967,7 @@ impl Iterator for Wire where D: service::Store, S: WriteStorage + 'static, - G: Signer + Ecdh, + G: crypto::signature::Signer + Ecdh + Clone, { type Item = Action; @@ -1016,7 +1020,7 @@ where match dial::( addr.to_inner(), node_id, - self.signer.clone(), + self.signer.clone().into_inner(), self.service.config(), ) .and_then(|session| { @@ -1126,7 +1130,7 @@ where } /// Establish a new outgoing connection. -pub fn dial>( +pub fn dial>( remote_addr: NetAddr, remote_id: ::Pk, signer: G, @@ -1185,7 +1189,7 @@ pub fn dial>( } /// Accept a new connection. -pub fn accept>( +pub fn accept>( remote_addr: NetAddr, connection: net::TcpStream, signer: G, @@ -1194,7 +1198,7 @@ pub fn accept>( } /// Create a new [`WireSession`]. -fn session>( +fn session>( remote_addr: NetAddr, remote_id: Option, connection: net::TcpStream, diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs index 2ccdd850..7459f6e6 100644 --- a/radicle-remote-helper/src/push.rs +++ b/radicle-remote-helper/src/push.rs @@ -5,13 +5,14 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{assert_eq, io}; +use radicle::node::device::Device; use thiserror::Error; use radicle::cob; use radicle::cob::object::ParseObjectId; use radicle::cob::patch; use radicle::cob::patch::cache::Patches as _; -use radicle::crypto::Signer; +use radicle::crypto; use radicle::explorer::ExplorerResource; use radicle::git::canonical; use radicle::git::canonical::Canonical; @@ -389,7 +390,7 @@ pub fn run( } /// Open a new patch. -fn patch_open( +fn patch_open( src: &git::RefStr, upstream: &git::RefString, nid: &NodeId, @@ -399,10 +400,13 @@ fn patch_open( patch::Patches<'_, storage::git::Repository>, cob::cache::StoreWriter, >, - signer: &G, + signer: &Device, profile: &Profile, opts: Options, -) -> Result, Error> { +) -> Result, Error> +where + G: crypto::signature::Signer, +{ let reference = working.find_reference(src.as_str())?; let commit = reference.peel_to_commit()?; let dst = git::refs::storage::staging::patch(nid, commit.id()); @@ -509,7 +513,7 @@ fn patch_open( /// Update an existing patch. #[allow(clippy::too_many_arguments)] -fn patch_update( +fn patch_update( src: &git::RefStr, dst: &git::Qualified, force: bool, @@ -521,9 +525,12 @@ fn patch_update( patch::Patches<'_, storage::git::Repository>, cob::cache::StoreWriter, >, - signer: &G, + signer: &Device, opts: Options, -) -> Result, Error> { +) -> Result, Error> +where + G: crypto::signature::Signer, +{ let reference = working.find_reference(src.as_str())?; let commit = reference.peel_to_commit()?; let patch_id = radicle::cob::ObjectId::from(oid); @@ -592,7 +599,7 @@ fn patch_update( Ok(Some(ExplorerResource::Patch { id: patch_id })) } -fn push( +fn push( src: &git::RefStr, dst: &git::Qualified, force: bool, @@ -603,8 +610,11 @@ fn push( patch::Patches<'_, storage::git::Repository>, cob::cache::StoreWriter, >, - signer: &G, -) -> Result, Error> { + signer: &Device, +) -> Result, Error> +where + G: crypto::signature::Signer, +{ let head = match working.find_reference(src.as_str()) { Ok(obj) => obj.peel_to_commit()?, Err(e) => { @@ -648,7 +658,7 @@ fn push( } /// Revert all patches that are no longer included in the base branch. -fn patch_revert_all( +fn patch_revert_all( old: git::Oid, new: git::Oid, stored: &git::raw::Repository, @@ -656,8 +666,11 @@ fn patch_revert_all( patch::Patches<'_, storage::git::Repository>, cob::cache::StoreWriter, >, - _signer: &G, -) -> Result<(), Error> { + _signer: &Device, +) -> Result<(), Error> +where + G: crypto::signature::Signer, +{ // Find all commits reachable from the old OID but not from the new OID. let mut revwalk = stored.revwalk()?; revwalk.push(*old)?; @@ -710,7 +723,7 @@ fn patch_revert_all( } /// Merge all patches that have been included in the base branch. -fn patch_merge_all( +fn patch_merge_all( old: git::Oid, new: git::Oid, working: &git::raw::Repository, @@ -718,8 +731,11 @@ fn patch_merge_all( patch::Patches<'_, storage::git::Repository>, cob::cache::StoreWriter, >, - signer: &G, -) -> Result<(), Error> { + signer: &Device, +) -> Result<(), Error> +where + G: crypto::signature::Signer, +{ let mut revwalk = working.revwalk()?; revwalk.push_range(&format!("{old}..{new}"))?; @@ -760,13 +776,17 @@ fn patch_merge_all( Ok(()) } -fn patch_merge, G: Signer>( +fn patch_merge( mut patch: patch::PatchMut, revision: patch::RevisionId, commit: git::Oid, working: &git::raw::Repository, - signer: &G, -) -> Result<(), Error> { + signer: &Device, +) -> Result<(), Error> +where + C: cob::cache::Update, + G: crypto::signature::Signer, +{ let (latest, _) = patch.latest(); let merged = patch.merge(revision, commit, signer)?; diff --git a/radicle/src/cob/identity.rs b/radicle/src/cob/identity.rs index 73dc74e3..49f6090f 100644 --- a/radicle/src/cob/identity.rs +++ b/radicle/src/cob/identity.rs @@ -4,13 +4,13 @@ use std::{fmt, ops::Deref, str::FromStr}; use crypto::{PublicKey, Signature}; use once_cell::sync::Lazy; use radicle_cob::{Embed, ObjectId, TypeName}; -use radicle_crypto::Signer; use radicle_git_ext as git_ext; use radicle_git_ext::Oid; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::identity::doc::Doc; +use crate::node::device::Device; use crate::{ cob, cob::{ @@ -192,11 +192,14 @@ impl Identity { } } - pub fn initialize<'a, R: WriteRepository + cob::Store, G: Signer>( + pub fn initialize<'a, R: WriteRepository + cob::Store, G>( doc: &Doc, store: &'a R, - signer: &G, - ) -> Result, cob::store::Error> { + signer: &Device, + ) -> Result, cob::store::Error> + where + G: crypto::signature::Signer, + { let mut store = cob::store::Store::open(store)?; let (id, identity) = Transaction::::initial( "Initialize identity", @@ -732,7 +735,10 @@ impl Revision { }) } - pub fn sign(&self, signer: &G) -> Result { + pub fn sign>( + &self, + signer: &G, + ) -> Result { self.doc.signature_of(signer) } } @@ -839,14 +845,14 @@ impl store::Transaction { } impl store::Transaction { - pub fn revision( + pub fn revision>( &mut self, title: impl ToString, description: impl ToString, doc: &Doc, parent: Option, repo: &R, - signer: &G, + signer: &Device, ) -> Result<(), store::Error> { let (blob, bytes, signature) = doc.sign(signer).map_err(store::Error::Identity)?; // Store document blob in repository. @@ -901,11 +907,11 @@ where pub fn transaction( &mut self, message: &str, - signer: &G, + signer: &Device, operations: F, ) -> Result where - G: Signer, + G: crypto::signature::Signer, F: FnOnce(&mut Transaction, &R) -> Result<(), store::Error>, { let mut tx = Transaction::default(); @@ -919,13 +925,16 @@ where /// Update the identity by proposing a new revision. /// If the signer is the only delegate, the revision is accepted automatically. - pub fn update( + pub fn update( &mut self, title: impl ToString, description: impl ToString, doc: &Doc, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { let parent = self.current; let id = self.transaction("Propose revision", signer, |tx, repo| { tx.revision(title, description, doc, Some(parent), repo, signer) @@ -935,11 +944,10 @@ where } /// Accept an active revision. - pub fn accept( - &mut self, - revision: &RevisionId, - signer: &G, - ) -> Result { + pub fn accept(&mut self, revision: &RevisionId, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { let id = *revision; let revision = self.revision(revision).ok_or(Error::NotFound(id))?; let signature = revision.sign(signer)?; @@ -948,31 +956,32 @@ where } /// Reject an active revision. - pub fn reject( - &mut self, - revision: RevisionId, - signer: &G, - ) -> Result { + pub fn reject(&mut self, revision: RevisionId, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Reject revision", signer, |tx, _| tx.reject(revision)) } /// Redact a revision. - pub fn redact( - &mut self, - revision: RevisionId, - signer: &G, - ) -> Result { + pub fn redact(&mut self, revision: RevisionId, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Redact revision", signer, |tx, _| tx.redact(revision)) } /// Edit an active revision's title or description. - pub fn edit( + pub fn edit( &mut self, revision: RevisionId, title: String, description: String, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Edit revision", signer, |tx, _| { tx.edit(revision, title, description) }) @@ -1021,8 +1030,6 @@ mod lookup { #[allow(clippy::unwrap_used)] mod test { use qcheck_macros::quickcheck; - use radicle_crypto::test::signer::MockSigner; - use radicle_crypto::Signer as _; use crate::cob; use crate::crypto::PublicKey; @@ -1052,7 +1059,7 @@ mod test { #[test] fn test_identity_updates() { let NodeWithRepo { node, repo } = NodeWithRepo::default(); - let bob = MockSigner::default(); + let bob = Device::mock(); let signer = &node.signer; let mut identity = Identity::load_mut(&*repo).unwrap(); let mut doc = identity.doc().clone().edit(); @@ -1112,8 +1119,8 @@ mod test { #[test] fn test_identity_update_rejected() { let NodeWithRepo { node, repo } = NodeWithRepo::default(); - let bob = MockSigner::default(); - let eve = MockSigner::default(); + let bob = Device::mock(); + let eve = Device::mock(); let signer = &node.signer; let mut identity = Identity::load_mut(&*repo).unwrap(); let mut doc = identity.doc().clone().edit(); @@ -1535,9 +1542,9 @@ mod test { let tempdir = tempfile::tempdir().unwrap(); let mut rng = fastrand::Rng::new(); - let alice = MockSigner::new(&mut rng); - let bob = MockSigner::new(&mut rng); - let eve = MockSigner::new(&mut rng); + let alice = Device::mock_rng(&mut rng); + let bob = Device::mock_rng(&mut rng); + let eve = Device::mock_rng(&mut rng); let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap(); let (id, _, _, _) = diff --git a/radicle/src/cob/issue.rs b/radicle/src/cob/issue.rs index a53d20c6..9dc209f4 100644 --- a/radicle/src/cob/issue.rs +++ b/radicle/src/cob/issue.rs @@ -15,8 +15,8 @@ use crate::cob::store::{Cob, CobAction}; use crate::cob::thread; use crate::cob::thread::{Comment, CommentId, Thread}; use crate::cob::{op, store, ActorId, Embed, EntryId, ObjectId, TypeName}; -use crate::crypto::Signer; use crate::identity::doc::DocError; +use crate::node::device::Device; use crate::prelude::{Did, Doc, ReadRepository, RepoId}; use crate::storage::{HasRepoId, RepositoryError, WriteRepository}; @@ -608,26 +608,35 @@ where } /// Assign one or more actors to an issue. - pub fn assign( + pub fn assign( &mut self, assignees: impl IntoIterator, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Assign", signer, |tx| tx.assign(assignees)) } /// Set the issue title. - pub fn edit(&mut self, title: impl ToString, signer: &G) -> Result { + pub fn edit(&mut self, title: impl ToString, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Edit", signer, |tx| tx.edit(title)) } /// Set the issue description. - pub fn edit_description( + pub fn edit_description( &mut self, description: impl ToString, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { let (id, _) = self.root(); let id = *id; self.transaction("Edit description", signer, |tx| { @@ -636,73 +645,89 @@ where } /// Lifecycle an issue. - pub fn lifecycle(&mut self, state: State, signer: &G) -> Result { + pub fn lifecycle(&mut self, state: State, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state)) } /// Comment on an issue. - pub fn comment( + pub fn comment( &mut self, body: S, reply_to: CommentId, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Comment", signer, |tx| { tx.comment(body, reply_to, embeds.into_iter().collect()) }) } /// Edit a comment. - pub fn edit_comment( + pub fn edit_comment( &mut self, id: CommentId, body: S, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Edit comment", signer, |tx| { tx.edit_comment(id, body, embeds.into_iter().collect()) }) } /// Redact a comment. - pub fn redact_comment( - &mut self, - id: CommentId, - signer: &G, - ) -> Result { + pub fn redact_comment(&mut self, id: CommentId, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Redact comment", signer, |tx| tx.redact_comment(id)) } /// Label an issue. - pub fn label( + pub fn label( &mut self, labels: impl IntoIterator, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Label", signer, |tx| tx.label(labels)) } /// React to an issue comment. - pub fn react( + pub fn react( &mut self, to: CommentId, reaction: Reaction, active: bool, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("React", signer, |tx| tx.react(to, reaction, active)) } pub fn transaction( &mut self, message: &str, - signer: &G, + signer: &Device, operations: F, ) -> Result where - G: Signer, + G: crypto::signature::Signer, F: FnOnce(&mut Transaction) -> Result<(), store::Error>, { let mut tx = Transaction::default(); @@ -791,10 +816,10 @@ where assignees: &[Did], embeds: impl IntoIterator>, cache: &'g mut C, - signer: &G, + signer: &Device, ) -> Result, Error> where - G: Signer, + G: crypto::signature::Signer, C: cob::cache::Update, { let (id, issue) = Transaction::initial("Create issue", &mut self.raw, signer, |tx, _| { @@ -822,9 +847,10 @@ where } /// Remove an issue. - pub fn remove(&self, id: &ObjectId, signer: &G) -> Result<(), store::Error> + pub fn remove(&self, id: &ObjectId, signer: &Device) -> Result<(), store::Error> where C: cob::cache::Remove, + G: crypto::signature::Signer, { self.raw.remove(id, signer) } @@ -1717,13 +1743,12 @@ mod test { #[test] fn test_invalid_cob() { - use crate::crypto::test::signer::MockSigner; use cob::change::Storage as _; use cob::object::Storage as _; use nonempty::NonEmpty; let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default(); - let eve = MockSigner::default(); + let eve = Device::mock(); let identity = repo.identity().unwrap().head(); let missing = arbitrary::oid(); let type_name = Issue::type_name().clone(); diff --git a/radicle/src/cob/issue/cache.rs b/radicle/src/cob/issue/cache.rs index 915d42a1..e13f0b91 100644 --- a/radicle/src/cob/issue/cache.rs +++ b/radicle/src/cob/issue/cache.rs @@ -9,7 +9,7 @@ use crate::cob::cache; use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update}; use crate::cob::store; use crate::cob::{Embed, Label, ObjectId, TypeName, Uri}; -use crate::crypto::Signer; +use crate::node::device::Device; use crate::prelude::{Did, RepoId}; use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository}; @@ -104,11 +104,11 @@ impl<'a, R, C> Cache, C> { labels: &[Label], assignees: &[Did], embeds: impl IntoIterator>, - signer: &G, + signer: &Device, ) -> Result, super::Error> where R: ReadRepository + WriteRepository + cob::Store, - G: Signer, + G: crypto::signature::Signer, C: Update, { self.store.create( @@ -124,9 +124,9 @@ impl<'a, R, C> Cache, C> { /// Remove the given `id` from the [`super::Issues`] storage, and /// removing the entry from the `cache`. - pub fn remove(&mut self, id: &IssueId, signer: &G) -> Result<(), super::Error> + pub fn remove(&mut self, id: &IssueId, signer: &Device) -> Result<(), super::Error> where - G: Signer, + G: crypto::signature::Signer, R: ReadRepository + SignRepository + cob::Store, C: Remove, { diff --git a/radicle/src/cob/job.rs b/radicle/src/cob/job.rs index 84d849a7..d5a43975 100644 --- a/radicle/src/cob/job.rs +++ b/radicle/src/cob/job.rs @@ -19,8 +19,8 @@ use crate::cob::store; use crate::cob::store::{Cob, CobAction, Store, Transaction}; use crate::cob::{EntryId, ObjectId, TypeName}; use crate::crypto::ssh::ExtendedSignature; -use crate::crypto::Signer; use crate::git; +use crate::node::device::Device; use crate::prelude::ReadRepository; use crate::storage::{Oid, WriteRepository}; @@ -332,12 +332,15 @@ where /// Transition the `Job` into a running state, storing the provided /// metadata. - pub fn start( + pub fn start( &mut self, run_id: String, info_url: Option, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Start", signer, |tx| { tx.start(run_id, info_url)?; Ok(()) @@ -345,18 +348,21 @@ where } /// Transition the `Job` into a finished state, with the provided `reason`. - pub fn finish(&mut self, reason: Reason, signer: &G) -> Result { + pub fn finish(&mut self, reason: Reason, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Finish", signer, |tx| tx.finish(reason)) } pub fn transaction( &mut self, message: &str, - signer: &G, + signer: &Device, operations: F, ) -> Result where - G: Signer, + G: crypto::signature::Signer, F: FnOnce(&mut Transaction) -> Result<(), store::Error>, { let mut tx = Transaction::default(); @@ -421,11 +427,14 @@ where } /// Create a fresh `Job` with the provided `commit_id`. - pub fn create<'g, G: Signer>( + pub fn create<'g, G>( &'g mut self, commit_id: git::Oid, - signer: &G, - ) -> Result, Error> { + signer: &Device, + ) -> Result, Error> + where + G: crypto::signature::Signer, + { let (id, job) = Transaction::initial("Create job", &mut self.raw, signer, |tx, _| { tx.trigger(commit_id)?; Ok(()) @@ -439,7 +448,10 @@ where } /// Delete the `Job` identified by `id`. - pub fn remove(&self, id: &JobId, signer: &G) -> Result<(), store::Error> { + pub fn remove(&self, id: &JobId, signer: &Device) -> Result<(), store::Error> + where + G: crypto::signature::Signer, + { self.raw.remove(id, signer) } } diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs index 1666c1d3..5cc0fb9b 100644 --- a/radicle/src/cob/patch.rs +++ b/radicle/src/cob/patch.rs @@ -21,10 +21,11 @@ use crate::cob::thread; use crate::cob::thread::Thread; use crate::cob::thread::{Comment, CommentId, Edit, Reactions}; use crate::cob::{op, store, ActorId, Embed, EntryId, ObjectId, TypeName, Uri}; -use crate::crypto::{PublicKey, Signer}; +use crate::crypto::PublicKey; use crate::git; use crate::identity::doc::{DocAt, DocError}; use crate::identity::PayloadError; +use crate::node::device::Device; use crate::prelude::*; use crate::storage; @@ -347,11 +348,14 @@ impl Merged<'_, R> { /// /// This removes Git refs relating to the patch, both in the working copy, /// and the stored copy; and updates `rad/sigrefs`. - pub fn cleanup( + pub fn cleanup( self, working: &git::raw::Repository, - signer: &G, - ) -> Result<(), storage::RepositoryError> { + signer: &Device, + ) -> Result<(), storage::RepositoryError> + where + G: crypto::signature::Signer, + { let nid = signer.public_key(); let stored_ref = git::refs::patch(&self.patch).with_namespace(nid.into()); let working_ref = git::refs::workdir::patch_upstream(&self.patch); @@ -2000,11 +2004,11 @@ where pub fn transaction( &mut self, message: &str, - signer: &G, + signer: &Device, operations: F, ) -> Result where - G: Signer, + G: crypto::signature::Signer, F: FnOnce(&mut Transaction) -> Result<(), store::Error>, { let mut tx = Transaction::default(); @@ -2023,57 +2027,72 @@ where } /// Edit patch metadata. - pub fn edit( + pub fn edit( &mut self, title: String, target: MergeTarget, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Edit", signer, |tx| tx.edit(title, target)) } /// Edit revision metadata. - pub fn edit_revision( + pub fn edit_revision( &mut self, revision: RevisionId, - description: String, + description: S, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Edit revision", signer, |tx| { tx.edit_revision(revision, description, embeds.into_iter().collect()) }) } /// Redact a revision. - pub fn redact( - &mut self, - revision: RevisionId, - signer: &G, - ) -> Result { + pub fn redact(&mut self, revision: RevisionId, signer: &Device) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Redact revision", signer, |tx| tx.redact(revision)) } /// Create a thread on a patch revision. - pub fn thread( + pub fn thread( &mut self, revision: RevisionId, body: S, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Create thread", signer, |tx| tx.thread(revision, body)) } /// Comment on a patch revision. - pub fn comment( + pub fn comment( &mut self, revision: RevisionId, body: S, reply_to: Option, location: Option, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Comment", signer, |tx| { tx.comment( revision, @@ -2086,69 +2105,86 @@ where } /// React on a patch revision. - pub fn react( + pub fn react( &mut self, revision: RevisionId, reaction: Reaction, location: Option, active: bool, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("React", signer, |tx| { tx.react(revision, reaction, location, active) }) } /// Edit a comment on a patch revision. - pub fn comment_edit( + pub fn comment_edit( &mut self, revision: RevisionId, comment: CommentId, body: S, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Edit comment", signer, |tx| { tx.comment_edit(revision, comment, body, embeds.into_iter().collect()) }) } /// React to a comment on a patch revision. - pub fn comment_react( + pub fn comment_react( &mut self, revision: RevisionId, comment: CommentId, reaction: Reaction, active: bool, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("React comment", signer, |tx| { tx.comment_react(revision, comment, reaction, active) }) } /// Redact a comment on a patch revision. - pub fn comment_redact( + pub fn comment_redact( &mut self, revision: RevisionId, comment: CommentId, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Redact comment", signer, |tx| { tx.comment_redact(revision, comment) }) } /// Comment on a line of code as part of a review. - pub fn review_comment( + pub fn review_comment( &mut self, review: ReviewId, body: S, location: Option, reply_to: Option, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Review comment", signer, |tx| { tx.review_comment( review, @@ -2161,54 +2197,67 @@ where } /// Edit review comment. - pub fn edit_review_comment( + pub fn edit_review_comment( &mut self, review: ReviewId, comment: EntryId, body: S, embeds: impl IntoIterator>, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + S: ToString, + { self.transaction("Edit review comment", signer, |tx| { tx.edit_review_comment(review, comment, body, embeds.into_iter().collect()) }) } /// React to a review comment. - pub fn react_review_comment( + pub fn react_review_comment( &mut self, review: ReviewId, comment: EntryId, reaction: Reaction, active: bool, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("React to review comment", signer, |tx| { tx.react_review_comment(review, comment, reaction, active) }) } /// React to a review comment. - pub fn redact_review_comment( + pub fn redact_review_comment( &mut self, review: ReviewId, comment: EntryId, - signer: &G, - ) -> Result { + signer: &Device, + ) -> Result + where + G: crypto::signature::Signer, + { self.transaction("Redact review comment", signer, |tx| { tx.redact_review_comment(review, comment) }) } /// Review a patch revision. - pub fn review( + pub fn review( &mut self, revision: RevisionId, verdict: Option, summary: Option, labels: Vec