From 1f4fcc5e6a3dadef99284bc3845ada11c74ccd97 Mon Sep 17 00:00:00 2001 From: Fintan Halpenny Date: Mon, 2 Dec 2024 12:29:31 +0000 Subject: [PATCH] radicle: move to `signature` crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The motivation of this change is to move away from tying signing to be specifically for ed25519. The reason being that the protocol will want move towards two different kinds of signing – the node signing artifacts, and an author signing artifacts. This change captures the former, node signing, by introducing a `Device` type that is the `NodeId` – a PublicKey underneath the hood – and a signing mechanism. This allows the replacement of the `Signer` trait being used – which always assumed a `PublicKey`. Instead, the `Device` is constructed with `NodeId`. In `radicle-cob`, a signer is expected to implement `signature::Signer`, and everywhere in `radicle`, `radicle-node`, `radicle-cli`, and `radicle-remote-helper` is expected the signer is expected to implement `signature::Signer`. A `Device` implements both of these but only requires `signature::Signer` to do so – since an `ExtendedSignature` is essentially `(PublicKey, Signature)`, and the `NodeId` of the `Device` can be used. --- Cargo.lock | 2 + radicle-cli/src/commands/cob.rs | 4 +- radicle-cli/src/commands/id.rs | 15 +- radicle-cli/src/commands/issue.rs | 11 +- radicle-cli/src/commands/job.rs | 33 ++- radicle-cli/src/commands/patch/edit.rs | 9 +- radicle-cli/src/commands/patch/review.rs | 4 +- .../src/commands/patch/review/builder.rs | 22 +- radicle-cli/src/terminal/io.rs | 7 +- radicle-cob/Cargo.toml | 1 + radicle-cob/src/backend/git/change.rs | 8 +- radicle-cob/src/change/store.rs | 2 +- .../src/object/collaboration/create.rs | 2 +- .../src/object/collaboration/update.rs | 4 +- radicle-cob/src/test/storage.rs | 4 +- radicle-crypto/Cargo.toml | 1 + radicle-crypto/src/lib.rs | 2 + radicle-crypto/src/ssh.rs | 6 + radicle-crypto/src/ssh/agent.rs | 17 ++ radicle-crypto/src/ssh/keystore.rs | 17 ++ radicle-crypto/src/test/signer.rs | 19 +- radicle-node/src/lib.rs | 2 +- radicle-node/src/main.rs | 6 +- radicle-node/src/runtime.rs | 7 +- radicle-node/src/service.rs | 17 +- radicle-node/src/service/gossip/store.rs | 4 +- radicle-node/src/service/message.rs | 27 +- radicle-node/src/test/environment.rs | 27 +- radicle-node/src/test/gossip.rs | 4 +- radicle-node/src/test/peer.rs | 20 +- radicle-node/src/test/simulator.rs | 8 +- radicle-node/src/tests.rs | 12 +- radicle-node/src/tests/e2e.rs | 8 +- radicle-node/src/wire/message.rs | 8 +- radicle-node/src/wire/protocol.rs | 42 +-- radicle-remote-helper/src/push.rs | 58 ++-- radicle/src/cob/identity.rs | 83 +++--- radicle/src/cob/issue.rs | 91 +++--- radicle/src/cob/issue/cache.rs | 10 +- radicle/src/cob/job.rs | 34 ++- radicle/src/cob/patch.rs | 266 ++++++++++++------ radicle/src/cob/patch/cache.rs | 14 +- radicle/src/cob/store.rs | 35 ++- radicle/src/cob/test.rs | 9 +- radicle/src/cob/thread.rs | 5 +- radicle/src/git/canonical.rs | 5 +- radicle/src/identity/doc.rs | 31 +- radicle/src/lib.rs | 2 +- radicle/src/node.rs | 1 + radicle/src/node/device.rs | 174 ++++++++++++ radicle/src/profile.rs | 11 +- radicle/src/rad.rs | 46 +-- radicle/src/storage.rs | 7 +- radicle/src/storage/git.rs | 25 +- radicle/src/storage/git/cob.rs | 17 +- radicle/src/storage/refs.rs | 19 +- radicle/src/test.rs | 4 +- radicle/src/test/arbitrary.rs | 2 +- radicle/src/test/fixtures.rs | 19 +- radicle/src/test/storage.rs | 9 +- 60 files changed, 930 insertions(+), 429 deletions(-) create mode 100644 radicle/src/node/device.rs 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