radicle: move to `signature` crate

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<ExtendedSignature>`, and everywhere in `radicle`,
`radicle-node`, `radicle-cli`, and `radicle-remote-helper` is expected the
signer is expected to implement `signature::Signer<Signature>`.

A `Device` implements both of these but only requires
`signature::Signer<Signature>` to do so – since an `ExtendedSignature` is
essentially `(PublicKey, Signature)`, and the `NodeId` of the `Device` can be
used.
This commit is contained in:
Fintan Halpenny 2024-12-02 12:29:31 +00:00
parent 41f9048d96
commit 1f4fcc5e6a
60 changed files with 930 additions and 429 deletions

2
Cargo.lock generated
View File

@ -2284,6 +2284,7 @@ dependencies = [
"radicle-git-ext", "radicle-git-ext",
"serde", "serde",
"serde_json", "serde_json",
"signature 2.2.0",
"tempfile", "tempfile",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@ -2316,6 +2317,7 @@ dependencies = [
"radicle-git-ext", "radicle-git-ext",
"radicle-ssh", "radicle-ssh",
"serde", "serde",
"signature 2.2.0",
"sqlite", "sqlite",
"ssh-key", "ssh-key",
"tempfile", "tempfile",

View File

@ -471,7 +471,7 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<(
let actions: Vec<cob::patch::Action> = read_jsonl(reader)?; let actions: Vec<cob::patch::Action> = read_jsonl(reader)?;
let mut patches = profile.patches_mut(&repo)?; let mut patches = profile.patches_mut(&repo)?;
let mut patch = patches.get_mut(oid)?; let mut patch = patches.get_mut(oid)?;
patch.transaction(&message, &profile.signer()?, |tx| { patch.transaction(&message, &*profile.signer()?, |tx| {
tx.extend(actions)?; tx.extend(actions)?;
tx.embed(embeds)?; tx.embed(embeds)?;
Ok(()) Ok(())
@ -481,7 +481,7 @@ pub fn run(Options { op }: Options, ctx: impl term::Context) -> anyhow::Result<(
let actions: Vec<cob::issue::Action> = read_jsonl(reader)?; let actions: Vec<cob::issue::Action> = read_jsonl(reader)?;
let mut issues = profile.issues_mut(&repo)?; let mut issues = profile.issues_mut(&repo)?;
let mut issue = issues.get_mut(oid)?; let mut issue = issues.get_mut(oid)?;
issue.transaction(&message, &profile.signer()?, |tx| { issue.transaction(&message, &*profile.signer()?, |tx| {
tx.extend(actions)?; tx.extend(actions)?;
tx.embed(embeds)?; tx.embed(embeds)?;
Ok(()) Ok(())

View File

@ -6,10 +6,11 @@ use anyhow::{anyhow, Context};
use radicle::cob::identity::{self, IdentityMut, Revision, RevisionId}; use radicle::cob::identity::{self, IdentityMut, Revision, RevisionId};
use radicle::identity::{doc, Doc, Identity, PayloadError, RawDoc, Visibility}; 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::refs;
use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository}; use radicle::storage::{ReadRepository, ReadStorage as _, WriteRepository};
use radicle::{cob, Profile}; use radicle::{cob, crypto, Profile};
use radicle_surf::diff::Diff; use radicle_surf::diff::Diff;
use radicle_term::Element; use radicle_term::Element;
use serde_json as json; use serde_json as json;
@ -722,13 +723,17 @@ and description.
Ok(result) Ok(result)
} }
fn update<R: WriteRepository + cob::Store, G: Signer>( fn update<R, G>(
title: Option<String>, title: Option<String>,
description: Option<String>, description: Option<String>,
doc: Doc, doc: Doc,
current: &mut IdentityMut<R>, current: &mut IdentityMut<R>,
signer: &G, signer: &Device<G>,
) -> anyhow::Result<Revision> { ) -> anyhow::Result<Revision>
where
R: WriteRepository + cob::Store,
G: crypto::signature::Signer<crypto::Signature>,
{
if let Some((title, description)) = edit_title_description(title, description)? { if let Some((title, description)) = edit_title_description(title, description)? {
let id = current.update(title, description, &doc, signer)?; let id = current.update(title, description, &doc, signer)?;
let revision = current let revision = current

View File

@ -10,8 +10,9 @@ use anyhow::{anyhow, Context as _};
use radicle::cob::common::{Label, Reaction}; use radicle::cob::common::{Label, Reaction};
use radicle::cob::issue::{CloseReason, State}; use radicle::cob::issue::{CloseReason, State};
use radicle::cob::{issue, thread}; use radicle::cob::{issue, thread};
use radicle::crypto::Signer; use radicle::crypto;
use radicle::issue::cache::Issues as _; use radicle::issue::cache::Issues as _;
use radicle::node::device::Device;
use radicle::prelude::{Did, RepoId}; use radicle::prelude::{Did, RepoId};
use radicle::profile; use radicle::profile;
use radicle::storage; use radicle::storage;
@ -810,12 +811,12 @@ fn open<R, G>(
assignees: Vec<Did>, assignees: Vec<Did>,
options: &Options, options: &Options,
cache: &mut issue::Cache<issue::Issues<'_, R>, cob::cache::StoreWriter>, cache: &mut issue::Cache<issue::Issues<'_, R>, cob::cache::StoreWriter>,
signer: &G, signer: &Device<G>,
profile: &Profile, profile: &Profile,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
R: ReadRepository + WriteRepository + cob::Store, R: ReadRepository + WriteRepository + cob::Store,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
let (title, description) = if let (Some(t), Some(d)) = (title.as_ref(), description.as_ref()) { let (title, description) = if let (Some(t), Some(d)) = (title.as_ref(), description.as_ref()) {
(t.to_owned(), d.to_owned()) (t.to_owned(), d.to_owned())
@ -845,11 +846,11 @@ fn edit<'a, 'g, R, G>(
id: Rev, id: Rev,
title: Option<String>, title: Option<String>,
description: Option<String>, description: Option<String>,
signer: &G, signer: &Device<G>,
) -> anyhow::Result<issue::IssueMut<'a, 'g, R, cob::cache::StoreWriter>> ) -> anyhow::Result<issue::IssueMut<'a, 'g, R, cob::cache::StoreWriter>>
where where
R: WriteRepository + ReadRepository + cob::Store, R: WriteRepository + ReadRepository + cob::Store,
G: radicle::crypto::Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
let id = id.resolve(&repo.backend)?; let id = id.resolve(&repo.backend)?;
let mut issue = issues.get_mut(&id)?; let mut issue = issues.get_mut(&id)?;

View File

@ -4,7 +4,8 @@ use std::ffi::OsString;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::cob::job::{JobStore, Reason, State}; 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::node::Handle;
use radicle::storage::{WriteRepository, WriteStorage}; use radicle::storage::{WriteRepository, WriteStorage};
use radicle::{cob, Node}; use radicle::{cob, Node};
@ -260,13 +261,17 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn trigger<R: WriteRepository + cob::Store, G: Signer>( fn trigger<R, G>(
commit: &Rev, commit: &Rev,
store: &mut JobStore<R>, store: &mut JobStore<R>,
repo: &radicle::storage::git::Repository, repo: &radicle::storage::git::Repository,
signer: &G, signer: &Device<G>,
quiet: bool, quiet: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()>
where
R: WriteRepository + cob::Store,
G: crypto::signature::Signer<crypto::Signature>,
{
let commit = commit.resolve(&repo.backend)?; let commit = commit.resolve(&repo.backend)?;
let job = store.create(commit, signer)?; let job = store.create(commit, signer)?;
if !quiet { if !quiet {
@ -275,14 +280,18 @@ fn trigger<R: WriteRepository + cob::Store, G: Signer>(
Ok(()) Ok(())
} }
fn start<R: WriteRepository + cob::Store, G: Signer>( fn start<R, G>(
job_id: &Rev, job_id: &Rev,
run_id: &str, run_id: &str,
info_url: Option<String>, info_url: Option<String>,
store: &mut JobStore<R>, store: &mut JobStore<R>,
repo: &radicle::storage::git::Repository, repo: &radicle::storage::git::Repository,
signer: &G, signer: &Device<G>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()>
where
R: WriteRepository + cob::Store,
G: crypto::signature::Signer<crypto::Signature>,
{
let job_id = job_id.resolve(&repo.backend)?; let job_id = job_id.resolve(&repo.backend)?;
let mut job = store.get_mut(&job_id)?; let mut job = store.get_mut(&job_id)?;
@ -349,13 +358,17 @@ fn show<R: WriteRepository + cob::Store>(
Ok(()) Ok(())
} }
fn finish<R: WriteRepository + cob::Store, G: Signer>( fn finish<R, G>(
job_id: &Rev, job_id: &Rev,
reason: Reason, reason: Reason,
store: &mut JobStore<R>, store: &mut JobStore<R>,
repo: &radicle::storage::git::Repository, repo: &radicle::storage::git::Repository,
signer: &G, signer: &Device<G>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()>
where
R: WriteRepository + cob::Store,
G: crypto::signature::Signer<crypto::Signature>,
{
let job_id = job_id.resolve(&repo.backend)?; let job_id = job_id.resolve(&repo.backend)?;
let mut job = store.get_mut(&job_id)?; let mut job = store.get_mut(&job_id)?;

View File

@ -3,6 +3,7 @@ use super::*;
use radicle::cob; use radicle::cob;
use radicle::cob::patch; use radicle::cob::patch;
use radicle::crypto; use radicle::crypto;
use radicle::node::device::Device;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::storage::git::Repository; use radicle::storage::git::Repository;
@ -32,10 +33,10 @@ fn edit_root<G>(
mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>, mut patch: patch::PatchMut<'_, '_, Repository, cob::cache::StoreWriter>,
title: String, title: String,
description: String, description: String,
signer: &G, signer: &Device<G>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
G: crypto::Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
let title = if title != patch.title() { let title = if title != patch.title() {
Some(title) Some(title)
@ -75,10 +76,10 @@ fn edit_revision<G>(
revision: patch::RevisionId, revision: patch::RevisionId,
mut title: String, mut title: String,
description: String, description: String,
signer: &G, signer: &Device<G>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
G: crypto::Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
let embeds = patch.embeds().to_owned(); let embeds = patch.embeds().to_owned();
let description = if description.is_empty() { let description = if description.is_empty() {

View File

@ -89,10 +89,10 @@ pub fn run(
.minimal(true) .minimal(true)
.context_lines(unified as u32); .context_lines(unified as u32);
builder::ReviewBuilder::new(patch_id, signer, repository) builder::ReviewBuilder::new(patch_id, repository)
.hunk(hunk) .hunk(hunk)
.verdict(verdict) .verdict(verdict)
.run(revision, &mut opts)?; .run(revision, &mut opts, &signer)?;
} }
Operation::Review { verdict, .. } => { Operation::Review { verdict, .. } => {
let message = options.message.get(REVIEW_HELP_MSG)?; let message = options.message.get(REVIEW_HELP_MSG)?;

View File

@ -21,7 +21,9 @@ use std::{fmt, io};
use radicle::cob; use radicle::cob;
use radicle::cob::patch::{PatchId, Revision, Verdict}; use radicle::cob::patch::{PatchId, Revision, Verdict};
use radicle::cob::{CodeLocation, CodeRange}; use radicle::cob::{CodeLocation, CodeRange};
use radicle::crypto;
use radicle::git; use radicle::git;
use radicle::node::device::Device;
use radicle::prelude::*; use radicle::prelude::*;
use radicle::storage::git::{cob::DraftStore, Repository}; use radicle::storage::git::{cob::DraftStore, Repository};
use radicle_git_ext::Oid; use radicle_git_ext::Oid;
@ -569,11 +571,9 @@ impl<'a> Brain<'a> {
} }
/// Builds a patch review interactively, across multiple files. /// Builds a patch review interactively, across multiple files.
pub struct ReviewBuilder<'a, G> { pub struct ReviewBuilder<'a> {
/// Patch being reviewed. /// Patch being reviewed.
patch_id: PatchId, patch_id: PatchId,
/// Signer.
signer: G,
/// Stored copy of repository. /// Stored copy of repository.
repo: &'a Repository, repo: &'a Repository,
/// Single hunk review. /// Single hunk review.
@ -582,12 +582,11 @@ pub struct ReviewBuilder<'a, G> {
verdict: Option<Verdict>, verdict: Option<Verdict>,
} }
impl<'a, G: Signer> ReviewBuilder<'a, G> { impl<'a> ReviewBuilder<'a> {
/// Create a new review builder. /// 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 { Self {
patch_id, patch_id,
signer,
repo, repo,
hunk: None, hunk: None,
verdict: None, verdict: None,
@ -607,9 +606,16 @@ impl<'a, G: Signer> ReviewBuilder<'a, G> {
} }
/// Run the review builder for the given revision. /// Run the review builder for the given revision.
pub fn run(self, revision: &Revision, opts: &mut git::raw::DiffOptions) -> anyhow::Result<()> { pub fn run<G>(
self,
revision: &Revision,
opts: &mut git::raw::DiffOptions,
signer: &Device<G>,
) -> anyhow::Result<()>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let repo = self.repo.raw(); let repo = self.repo.raw();
let signer = &self.signer;
let base = repo.find_commit((*revision.base()).into())?; let base = repo.find_commit((*revision.base()).into())?;
let patch_id = self.patch_id; let patch_id = self.patch_id;
let tree = { let tree = {

View File

@ -3,7 +3,8 @@ use radicle::cob::issue::Issue;
use radicle::cob::thread::{Comment, CommentId}; use radicle::cob::thread::{Comment, CommentId};
use radicle::cob::Reaction; use radicle::cob::Reaction;
use radicle::crypto::ssh::keystore::MemorySigner; 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::env::RAD_PASSPHRASE;
use radicle::profile::Profile; 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, /// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user,
/// if we're connected to a TTY. /// if we're connected to a TTY.
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> { pub fn signer(profile: &Profile) -> anyhow::Result<BoxedDevice> {
if let Ok(signer) = profile.signer() { if let Ok(signer) = profile.signer() {
return Ok(signer); return Ok(signer);
} }
@ -62,7 +63,7 @@ pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
spinner.finish(); spinner.finish();
Ok(signer.boxed()) Ok(Device::from(signer).boxed())
} }
pub fn comment_select(issue: &Issue) -> anyhow::Result<(&CommentId, &Comment)> { pub fn comment_select(issue: &Issue) -> anyhow::Result<(&CommentId, &Comment)> {

View File

@ -25,6 +25,7 @@ nonempty = { version = "0.9.0", features = ["serialize"] }
once_cell = { version = "1.13" } once_cell = { version = "1.13" }
radicle-git-ext = { version = "0.8.0", features = ["serde"] } radicle-git-ext = { version = "0.8.0", features = ["serde"] }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
signature = { version = "2.2" }
thiserror = { version = "1.0" } thiserror = { version = "1.0" }
[dependencies.git2] [dependencies.git2]

View File

@ -103,7 +103,7 @@ impl change::Storage for git2::Repository {
spec: store::Template<Self::ObjectId>, spec: store::Template<Self::ObjectId>,
) -> Result<Entry, Self::StoreError> ) -> Result<Entry, Self::StoreError>
where where
Signer: crypto::Signer, Signer: signature::Signer<Self::Signatures>,
{ {
let change::Template { let change::Template {
type_name, type_name,
@ -115,11 +115,7 @@ impl change::Storage for git2::Repository {
let manifest = store::Manifest::new(type_name, Version::default()); let manifest = store::Manifest::new(type_name, Version::default());
let revision = write_manifest(self, &manifest, embeds, &contents)?; let revision = write_manifest(self, &manifest, embeds, &contents)?;
let tree = self.find_tree(revision)?; let tree = self.find_tree(revision)?;
let signature = { let signature = signer.sign(revision.as_bytes());
let sig = signer.sign(revision.as_bytes());
let key = signer.public_key();
ExtendedSignature::new(*key, sig)
};
// Make sure there are no duplicates in the related list. // Make sure there are no duplicates in the related list.
related.sort(); related.sort();

View File

@ -27,7 +27,7 @@ pub trait Storage {
template: Template<Self::ObjectId>, template: Template<Self::ObjectId>,
) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError> ) -> Result<Entry<Self::Parent, Self::ObjectId, Self::Signatures>, Self::StoreError>
where where
G: crypto::Signer; G: signature::Signer<Self::Signatures>;
/// Load a change entry. /// Load a change entry.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]

View File

@ -64,7 +64,7 @@ pub fn create<T, S, G>(
where where
T: Evaluate<S>, T: Evaluate<S>,
S: Store, S: Store,
G: crypto::Signer, G: signature::Signer<crate::ExtendedSignature>,
{ {
let type_name = args.type_name.clone(); let type_name = args.type_name.clone();
let version = args.version; let version = args.version;

View File

@ -7,7 +7,7 @@ use radicle_crypto::PublicKey;
use crate::{ use crate::{
change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate, change, change_graph::ChangeGraph, history::EntryId, CollaborativeObject, Embed, Evaluate,
ObjectId, Store, TypeName, ExtendedSignature, ObjectId, Store, TypeName,
}; };
use super::error; use super::error;
@ -67,7 +67,7 @@ pub fn update<T, S, G>(
where where
T: Evaluate<S>, T: Evaluate<S>,
S: Store, S: Store,
G: crypto::Signer, G: signature::Signer<ExtendedSignature>,
{ {
let Update { let Update {
type_name: ref typename, type_name: ref typename,

View File

@ -6,7 +6,7 @@ use tempfile::TempDir;
use crate::{ use crate::{
change, change,
object::{self, Reference}, object::{self, Reference},
ObjectId, Store, signatures, ObjectId, Store,
}; };
pub mod error { pub mod error {
@ -76,7 +76,7 @@ impl change::Storage for Storage {
Self::StoreError, Self::StoreError,
> >
where where
Signer: crypto::Signer, Signer: signature::Signer<signatures::ExtendedSignature>,
{ {
self.as_raw().store(authority, parents, signer, spec) self.as_raw().store(authority, parents, signer, spec)
} }

View File

@ -23,6 +23,7 @@ fastrand = { version = "2.0.0", default-features = false, optional = true }
multibase = { version = "0.9.1" } multibase = { version = "0.9.1" }
ec25519 = { version = "0.1.0", features = [] } ec25519 = { version = "0.1.0", features = [] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
signature = { version = "2.2" }
sqlite = { version = "0.32.0", optional = true, features = ["bundled"] } sqlite = { version = "0.32.0", optional = true, features = ["bundled"] }
thiserror = { version = "1" } thiserror = { version = "1" }
zeroize = { version = "1.5.7" } zeroize = { version = "1.5.7" }

View File

@ -8,6 +8,8 @@ use thiserror::Error;
pub use ed25519::{edwards25519, Error, KeyPair, Seed}; pub use ed25519::{edwards25519, Error, KeyPair, Seed};
pub extern crate signature;
#[cfg(feature = "ssh")] #[cfg(feature = "ssh")]
pub mod ssh; pub mod ssh;
#[cfg(any(test, feature = "test"))] #[cfg(any(test, feature = "test"))]

View File

@ -32,6 +32,12 @@ pub struct ExtendedSignature {
pub sig: crypto::Signature, pub sig: crypto::Signature,
} }
impl From<ExtendedSignature> for crypto::Signature {
fn from(ExtendedSignature { sig, .. }: ExtendedSignature) -> Self {
sig
}
}
impl ExtendedSignature { impl ExtendedSignature {
/// Create a new extended signature. /// Create a new extended signature.
pub fn new(public_key: crypto::PublicKey, signature: crypto::Signature) -> Self { pub fn new(public_key: crypto::PublicKey, signature: crypto::Signature) -> Self {

View File

@ -11,6 +11,8 @@ pub use std::net::TcpStream as Stream;
#[cfg(unix)] #[cfg(unix)]
pub use std::os::unix::net::UnixStream as Stream; pub use std::os::unix::net::UnixStream as Stream;
use super::ExtendedSignature;
pub struct Agent { pub struct Agent {
client: AgentClient<Stream>, client: AgentClient<Stream>,
} }
@ -58,6 +60,21 @@ pub struct AgentSigner {
public: PublicKey, public: PublicKey,
} }
impl signature::Signer<Signature> for AgentSigner {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
Signer::try_sign(self, msg).map_err(signature::Error::from_source)
}
}
impl signature::Signer<ExtendedSignature> for AgentSigner {
fn try_sign(&self, msg: &[u8]) -> Result<ExtendedSignature, signature::Error> {
Ok(ExtendedSignature {
key: self.public,
sig: Signer::try_sign(self, msg).map_err(signature::Error::from_source)?,
})
}
}
impl AgentSigner { impl AgentSigner {
pub fn new(agent: Agent, public: PublicKey) -> Self { pub fn new(agent: Agent, public: PublicKey) -> Self {
let agent = RefCell::new(agent); let agent = RefCell::new(agent);

View File

@ -10,6 +10,8 @@ use zeroize::Zeroizing;
use crate::{KeyPair, PublicKey, SecretKey, Signature, Signer, SignerError}; use crate::{KeyPair, PublicKey, SecretKey, Signature, Signer, SignerError};
use super::ExtendedSignature;
/// A secret key passphrase. /// A secret key passphrase.
pub type Passphrase = Zeroizing<String>; pub type Passphrase = Zeroizing<String>;
@ -186,6 +188,21 @@ pub struct MemorySigner {
secret: Zeroizing<SecretKey>, secret: Zeroizing<SecretKey>,
} }
impl signature::Signer<Signature> for MemorySigner {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
Ok(Signer::sign(self, msg))
}
}
impl signature::Signer<ExtendedSignature> for MemorySigner {
fn try_sign(&self, msg: &[u8]) -> Result<ExtendedSignature, signature::Error> {
Ok(ExtendedSignature {
key: self.public,
sig: Signer::sign(self, msg),
})
}
}
impl Signer for MemorySigner { impl Signer for MemorySigner {
fn public_key(&self) -> &PublicKey { fn public_key(&self) -> &PublicKey {
&self.public &self.public

View File

@ -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)] #[derive(Debug, Clone)]
pub struct MockSigner { pub struct MockSigner {
@ -6,6 +8,21 @@ pub struct MockSigner {
sk: SecretKey, sk: SecretKey,
} }
impl signature::Signer<ExtendedSignature> for MockSigner {
fn try_sign(&self, msg: &[u8]) -> Result<ExtendedSignature, signature::Error> {
Ok(ExtendedSignature {
key: self.pk,
sig: signature::Signer::<Signature>::try_sign(self, msg)?,
})
}
}
impl signature::Signer<Signature> for MockSigner {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
Ok(Signature(self.sk.sign(msg, None)))
}
}
impl MockSigner { impl MockSigner {
pub fn new(rng: &mut fastrand::Rng) -> Self { pub fn new(rng: &mut fastrand::Rng) -> Self {
let mut seed: [u8; 32] = [0; 32]; let mut seed: [u8; 32] = [0; 32];

View File

@ -34,7 +34,7 @@ pub const VERSION: Version = Version {
pub mod prelude { pub mod prelude {
pub use crate::bounded::BoundedVec; 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::deserializer::Deserializer;
pub use crate::identity::{Did, RepoId}; pub use crate::identity::{Did, RepoId};
pub use crate::node::Address; pub use crate::node::Address;

View File

@ -5,7 +5,7 @@ use anyhow::Context;
use crossbeam_channel as chan; use crossbeam_channel as chan;
use radicle::logger; use radicle::logger;
use radicle::prelude::Signer; use radicle::node::device::Device;
use radicle::profile; use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner}; use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle_node::{Runtime, VERSION}; use radicle_node::{Runtime, VERSION};
@ -99,7 +99,9 @@ fn execute() -> anyhow::Result<()> {
let passphrase = profile::env::passphrase(); let passphrase = profile::env::passphrase();
let keystore = Keystore::new(&home.keys()); 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()); log::info!(target: "node", "Node ID is {}", signer.public_key());

View File

@ -9,6 +9,8 @@ use crossbeam_channel as chan;
use cyphernet::Ecdh; use cyphernet::Ecdh;
use netservices::resource::NetAccept; use netservices::resource::NetAccept;
use radicle::cob::migrate; use radicle::cob::migrate;
use radicle::crypto;
use radicle::node::device::Device;
use radicle_fetch::FetchLimit; use radicle_fetch::FetchLimit;
use radicle_signals::Signal; use radicle_signals::Signal;
use reactor::poller::popol; use reactor::poller::popol;
@ -25,7 +27,6 @@ use radicle::profile::Home;
use radicle::{cob, git, storage, Storage}; use radicle::{cob, git, storage, Storage};
use crate::control; use crate::control;
use crate::crypto::Signer;
use crate::node::{routing, NodeId}; use crate::node::{routing, NodeId};
use crate::service::message::NodeAnnouncement; use crate::service::message::NodeAnnouncement;
use crate::service::{gossip, policy, Event, INITIAL_SUBSCRIBE_BACKLOG_DELTA}; use crate::service::{gossip, policy, Event, INITIAL_SUBSCRIBE_BACKLOG_DELTA};
@ -119,10 +120,10 @@ impl Runtime {
config: service::Config, config: service::Config,
listen: Vec<net::SocketAddr>, listen: Vec<net::SocketAddr>,
signals: chan::Receiver<Signal>, signals: chan::Receiver<Signal>,
signer: G, signer: Device<G>,
) -> Result<Runtime, Error> ) -> Result<Runtime, Error>
where where
G: Signer + Ecdh<Pk = NodeId> + Clone + 'static, G: crypto::signature::Signer<crypto::Signature> + Ecdh<Pk = NodeId> + Clone + 'static,
{ {
let id = *signer.public_key(); let id = *signer.public_key();
let alias = config.alias.clone(); let alias = config.alias.clone();

View File

@ -28,6 +28,7 @@ use radicle::node::address;
use radicle::node::address::Store as _; use radicle::node::address::Store as _;
use radicle::node::address::{AddressBook, AddressType, KnownAddress}; use radicle::node::address::{AddressBook, AddressType, KnownAddress};
use radicle::node::config::PeerConfig; use radicle::node::config::PeerConfig;
use radicle::node::device::Device;
use radicle::node::refs::Store as _; use radicle::node::refs::Store as _;
use radicle::node::routing::Store as _; use radicle::node::routing::Store as _;
use radicle::node::seed; use radicle::node::seed;
@ -37,7 +38,6 @@ use radicle::storage::refs::SIGREFS_BRANCH;
use radicle::storage::RepositoryError; use radicle::storage::RepositoryError;
use radicle_fetch::policy::SeedingPolicy; use radicle_fetch::policy::SeedingPolicy;
use crate::crypto::Signer;
use crate::identity::RepoId; use crate::identity::RepoId;
use crate::node::routing; use crate::node::routing;
use crate::node::routing::InsertResult; use crate::node::routing::InsertResult;
@ -390,7 +390,7 @@ pub struct Service<D, S, G> {
/// Service configuration. /// Service configuration.
config: Config, config: Config,
/// Our cryptographic signer and key. /// Our cryptographic signer and key.
signer: G, signer: Device<G>,
/// Project storage. /// Project storage.
storage: S, storage: S,
/// Node database. /// Node database.
@ -444,10 +444,7 @@ pub struct Service<D, S, G> {
metrics: Metrics, metrics: Metrics,
} }
impl<D, S, G> Service<D, S, G> impl<D, S, G> Service<D, S, G> {
where
G: crypto::Signer,
{
/// Get the local node id. /// Get the local node id.
pub fn node_id(&self) -> NodeId { pub fn node_id(&self) -> NodeId {
*self.signer.public_key() *self.signer.public_key()
@ -467,14 +464,14 @@ impl<D, S, G> Service<D, S, G>
where where
D: Store, D: Store,
S: ReadStorage + 'static, S: ReadStorage + 'static,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
pub fn new( pub fn new(
config: Config, config: Config,
db: Stores<D>, db: Stores<D>,
storage: S, storage: S,
policies: policy::Config<Write>, policies: policy::Config<Write>,
signer: G, signer: Device<G>,
rng: Rng, rng: Rng,
node: NodeAnnouncement, node: NodeAnnouncement,
emitter: Emitter<Event>, emitter: Emitter<Event>,
@ -596,7 +593,7 @@ where
} }
/// Get the local signer. /// Get the local signer.
pub fn signer(&self) -> &G { pub fn signer(&self) -> &Device<G> {
&self.signer &self.signer
} }
@ -2658,7 +2655,7 @@ pub trait ServiceState {
impl<D, S, G> ServiceState for Service<D, S, G> impl<D, S, G> ServiceState for Service<D, S, G>
where where
D: routing::Store, D: routing::Store,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
S: ReadStorage, S: ReadStorage,
{ {
fn nid(&self) -> &NodeId { fn nid(&self) -> &NodeId {

View File

@ -399,7 +399,7 @@ mod test {
use crate::test::arbitrary; use crate::test::arbitrary;
use localtime::LocalTime; use localtime::LocalTime;
use radicle::assert_matches; use radicle::assert_matches;
use radicle_crypto::test::signer::MockSigner; use radicle::node::device::Device;
#[test] #[test]
fn test_announced() { fn test_announced() {
@ -407,7 +407,7 @@ mod test {
let nid = arbitrary::gen::<NodeId>(1); let nid = arbitrary::gen::<NodeId>(1);
let rid = arbitrary::gen::<RepoId>(1); let rid = arbitrary::gen::<RepoId>(1);
let timestamp = LocalTime::now().into(); let timestamp = LocalTime::now().into();
let signer = MockSigner::default(); let signer = Device::mock();
let refs = AnnouncementMessage::Refs(RefsAnnouncement { let refs = AnnouncementMessage::Refs(RefsAnnouncement {
rid, rid,
refs: BoundedVec::new(), refs: BoundedVec::new(),

View File

@ -2,6 +2,7 @@ use std::{fmt, io, mem};
use nonempty::NonEmpty; use nonempty::NonEmpty;
use radicle::git; use radicle::git;
use radicle::node::device::Device;
use radicle::storage::refs::RefsAt; use radicle::storage::refs::RefsAt;
use crate::crypto; use crate::crypto;
@ -260,7 +261,12 @@ pub enum AnnouncementMessage {
impl AnnouncementMessage { impl AnnouncementMessage {
/// Sign this announcement message. /// Sign this announcement message.
pub fn signed<G: crypto::Signer>(self, signer: &G) -> Announcement { pub fn signed<G>(self, signer: &Device<G>) -> Announcement
where
G: crypto::signature::Signer<crypto::Signature>,
{
use crypto::signature::Signer as _;
let msg = wire::serialize(&self); let msg = wire::serialize(&self);
let signature = signer.sign(&msg); let signature = signer.sign(&msg);
@ -442,11 +448,17 @@ impl Message {
.into() .into()
} }
pub fn node<G: crypto::Signer>(message: NodeAnnouncement, signer: &G) -> Self { pub fn node<G: crypto::signature::Signer<crypto::Signature>>(
message: NodeAnnouncement,
signer: &Device<G>,
) -> Self {
AnnouncementMessage::from(message).signed(signer).into() AnnouncementMessage::from(message).signed(signer).into()
} }
pub fn inventory<G: crypto::Signer>(message: InventoryAnnouncement, signer: &G) -> Self { pub fn inventory<G: crypto::signature::Signer<crypto::Signature>>(
message: InventoryAnnouncement,
signer: &Device<G>,
) -> Self {
AnnouncementMessage::from(message).signed(signer).into() AnnouncementMessage::from(message).signed(signer).into()
} }
@ -587,7 +599,6 @@ mod tests {
use radicle::git::raw; use radicle::git::raw;
use super::*; use super::*;
use crate::crypto::test::signer::MockSigner;
use crate::prelude::*; use crate::prelude::*;
use crate::test::arbitrary; use crate::test::arbitrary;
use crate::wire::Encode; use crate::wire::Encode;
@ -595,7 +606,7 @@ mod tests {
#[test] #[test]
fn test_ref_remote_limit() { fn test_ref_remote_limit() {
let mut refs = BoundedVec::<_, REF_REMOTE_LIMIT>::new(); let mut refs = BoundedVec::<_, REF_REMOTE_LIMIT>::new();
let signer = MockSigner::default(); let signer = Device::mock();
let at = raw::Oid::zero().into(); let at = raw::Oid::zero().into();
assert_eq!(refs.capacity(), REF_REMOTE_LIMIT); assert_eq!(refs.capacity(), REF_REMOTE_LIMIT);
@ -613,7 +624,7 @@ mod tests {
refs, refs,
timestamp: LocalTime::now().into(), timestamp: LocalTime::now().into(),
}) })
.signed(&MockSigner::default()) .signed(&Device::mock())
.into(); .into();
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
@ -633,7 +644,7 @@ mod tests {
.expect("size within bounds limit"), .expect("size within bounds limit"),
timestamp: LocalTime::now().into(), timestamp: LocalTime::now().into(),
}, },
&MockSigner::default(), &Device::mock(),
); );
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
assert!( assert!(
@ -655,7 +666,7 @@ mod tests {
#[quickcheck] #[quickcheck]
fn prop_refs_announcement_signing(rid: RepoId) { 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 timestamp = Timestamp::EPOCH;
let at = raw::Oid::zero().into(); let at = raw::Oid::zero().into();
let refs = BoundedVec::collect_from( let refs = BoundedVec::collect_from(

View File

@ -11,12 +11,14 @@ use crossbeam_channel as chan;
use localtime::LocalTime; use localtime::LocalTime;
use radicle::cob::{issue, migrate}; use radicle::cob::{issue, migrate};
use radicle::crypto;
use radicle::crypto::ssh::{keystore::MemorySigner, Keystore}; use radicle::crypto::ssh::{keystore::MemorySigner, Keystore};
use radicle::crypto::test::signer::MockSigner; use radicle::crypto::test::signer::MockSigner;
use radicle::crypto::{KeyPair, Seed, Signer}; use radicle::crypto::{KeyPair, Seed};
use radicle::git::refname; use radicle::git::refname;
use radicle::identity::{RepoId, Visibility}; use radicle::identity::{RepoId, Visibility};
use radicle::node::config::ConnectAddress; use radicle::node::config::ConnectAddress;
use radicle::node::device::Device;
use radicle::node::policy::store as policy; use radicle::node::policy::store as policy;
use radicle::node::seed::Store as _; use radicle::node::seed::Store as _;
use radicle::node::{Alias, Database, UserAgent, POLICIES_DB_FILE}; use radicle::node::{Alias, Database, UserAgent, POLICIES_DB_FILE};
@ -160,7 +162,7 @@ impl Environment {
pub struct Node<G> { pub struct Node<G> {
pub id: NodeId, pub id: NodeId,
pub home: Home, pub home: Home,
pub signer: G, pub signer: Device<G>,
pub storage: Storage, pub storage: Storage,
pub config: Config, pub config: Config,
pub db: service::Stores<Database>, pub db: service::Stores<Database>,
@ -169,7 +171,7 @@ pub struct Node<G> {
impl Node<MemorySigner> { impl Node<MemorySigner> {
pub fn new(profile: Profile) -> Self { 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 id = *profile.id();
let policies_db = profile.home.node().join(POLICIES_DB_FILE); let policies_db = profile.home.node().join(POLICIES_DB_FILE);
let policies = policy::Store::open(policies_db).unwrap(); let policies = policy::Store::open(policies_db).unwrap();
@ -189,17 +191,19 @@ impl Node<MemorySigner> {
} }
/// Handle to a running node. /// Handle to a running node.
pub struct NodeHandle<G: Signer + cyphernet::Ecdh + 'static> { pub struct NodeHandle<G: crypto::signature::Signer<crypto::Signature> + cyphernet::Ecdh + 'static> {
pub id: NodeId, pub id: NodeId,
pub storage: Storage, pub storage: Storage,
pub signer: G, pub signer: Device<G>,
pub home: Home, pub home: Home,
pub addr: net::SocketAddr, pub addr: net::SocketAddr,
pub thread: ManuallyDrop<thread::JoinHandle<Result<(), runtime::Error>>>, pub thread: ManuallyDrop<thread::JoinHandle<Result<(), runtime::Error>>>,
pub handle: ManuallyDrop<Handle>, pub handle: ManuallyDrop<Handle>,
} }
impl<G: Signer + cyphernet::Ecdh + 'static> Drop for NodeHandle<G> { impl<G: crypto::signature::Signer<crypto::Signature> + cyphernet::Ecdh + 'static> Drop
for NodeHandle<G>
{
fn drop(&mut self) { fn drop(&mut self) {
log::debug!(target: "test", "Node {} shutting down..", self.id); log::debug!(target: "test", "Node {} shutting down..", self.id);
@ -213,7 +217,7 @@ impl<G: Signer + cyphernet::Ecdh + 'static> Drop for NodeHandle<G> {
} }
} }
impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> { impl<G: crypto::signature::Signer<crypto::Signature> + cyphernet::Ecdh> NodeHandle<G> {
/// Connect this node to another node, and wait for the connection to be established both ways. /// Connect this node to another node, and wait for the connection to be established both ways.
pub fn connect(&mut self, remote: &NodeHandle<G>) -> &mut Self { pub fn connect(&mut self, remote: &NodeHandle<G>) -> &mut Self {
let local_events = self.handle.events(); let local_events = self.handle.events();
@ -481,7 +485,7 @@ impl Node<MockSigner> {
.collect::<String>(), .collect::<String>(),
); );
let home = Home::new(home).unwrap(); let home = Home::new(home).unwrap();
let signer = MockSigner::default(); let signer = Device::mock();
let storage = Storage::open( let storage = Storage::open(
home.storage(), home.storage(),
git::UserInfo { git::UserInfo {
@ -507,7 +511,10 @@ impl Node<MockSigner> {
} }
} }
impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> { impl<G> Node<G>
where
G: cyphernet::Ecdh<Pk = NodeId> + crypto::signature::Signer<crypto::Signature> + Clone,
{
/// Spawn a node in its own thread. /// Spawn a node in its own thread.
pub fn spawn(self) -> NodeHandle<G> { pub fn spawn(self) -> NodeHandle<G> {
let listen = vec![([0, 0, 0, 0], 0).into()]; let listen = vec![([0, 0, 0, 0], 0).into()];
@ -606,7 +613,7 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
/// Checks whether the nodes have converged in their routing tables. /// Checks whether the nodes have converged in their routing tables.
#[track_caller] #[track_caller]
pub fn converge<'a, G: Signer + cyphernet::Ecdh + 'static>( pub fn converge<'a, G: crypto::signature::Signer<crypto::Signature> + cyphernet::Ecdh + 'static>(
nodes: impl IntoIterator<Item = &'a NodeHandle<G>>, nodes: impl IntoIterator<Item = &'a NodeHandle<G>>,
) -> BTreeSet<(RepoId, NodeId)> { ) -> BTreeSet<(RepoId, NodeId)> {
let nodes = nodes.into_iter().collect::<Vec<_>>(); let nodes = nodes.into_iter().collect::<Vec<_>>();

View File

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use radicle::crypto::test::signer::MockSigner;
use radicle::node; use radicle::node;
use radicle::node::device::Device;
use radicle::node::UserAgent; use radicle::node::UserAgent;
use radicle::test::fixtures::gen; use radicle::test::fixtures::gen;
@ -17,7 +17,7 @@ pub fn messages(count: usize, now: LocalTime, delta: LocalDuration) -> Vec<Messa
let mut msgs = Vec::new(); let mut msgs = Vec::new();
for _ in 0..count { for _ in 0..count {
let signer = MockSigner::new(&mut rng); let signer = Device::mock_rng(&mut rng);
let time = if delta == LocalDuration::from_secs(0) { let time = if delta == LocalDuration::from_secs(0) {
now now
} else { } else {

View File

@ -7,8 +7,10 @@ use std::str::FromStr;
use log::*; use log::*;
use radicle::crypto;
use radicle::identity::Visibility; use radicle::identity::Visibility;
use radicle::node::address::Store as _; use radicle::node::address::Store as _;
use radicle::node::device::Device;
use radicle::node::Database; use radicle::node::Database;
use radicle::node::UserAgent; use radicle::node::UserAgent;
use radicle::node::{address, Alias, ConnectOptions}; use radicle::node::{address, Alias, ConnectOptions};
@ -18,7 +20,6 @@ use radicle::storage::{ReadRepository, RemoteRepository};
use radicle::Storage; use radicle::Storage;
use crate::crypto::test::signer::MockSigner; use crate::crypto::test::signer::MockSigner;
use crate::crypto::Signer;
use crate::identity::RepoId; use crate::identity::RepoId;
use crate::node; use crate::node;
use crate::node::routing::Store as _; use crate::node::routing::Store as _;
@ -56,7 +57,7 @@ pub struct Peer<S, G> {
impl<S, G> simulator::Peer<S, G> for Peer<S, G> impl<S, G> simulator::Peer<S, G> for Peer<S, G>
where where
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + 'static, G: crypto::signature::Signer<crypto::Signature> + 'static,
{ {
fn init(&mut self) {} fn init(&mut self) {}
@ -98,11 +99,11 @@ where
} }
} }
pub struct Config<G: Signer + 'static> { pub struct Config<G: crypto::signature::Signer<crypto::Signature> + 'static> {
pub config: service::Config, pub config: service::Config,
pub local_time: LocalTime, pub local_time: LocalTime,
pub policy: SeedingPolicy, pub policy: SeedingPolicy,
pub signer: G, pub signer: Device<G>,
pub rng: fastrand::Rng, pub rng: fastrand::Rng,
pub tmp: tempfile::TempDir, pub tmp: tempfile::TempDir,
} }
@ -110,7 +111,7 @@ pub struct Config<G: Signer + 'static> {
impl Default for Config<MockSigner> { impl Default for Config<MockSigner> {
fn default() -> Self { fn default() -> Self {
let mut rng = fastrand::Rng::new(); 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 tmp = tempfile::TempDir::new().unwrap();
let config = service::Config::test(Alias::from_str("mocky").unwrap()); let config = service::Config::test(Alias::from_str("mocky").unwrap());
@ -125,7 +126,7 @@ impl Default for Config<MockSigner> {
} }
} }
impl<G: Signer> Peer<Storage, G> { impl<G: crypto::signature::Signer<crypto::Signature>> Peer<Storage, G> {
pub fn project(&mut self, name: &str, description: &str) -> RepoId { pub fn project(&mut self, name: &str, description: &str) -> RepoId {
radicle::storage::git::transport::local::register(self.storage().clone()); radicle::storage::git::transport::local::register(self.storage().clone());
@ -148,7 +149,7 @@ impl<G: Signer> Peer<Storage, G> {
impl<S, G> Peer<S, G> impl<S, G> Peer<S, G>
where where
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + 'static, G: crypto::signature::Signer<crypto::Signature> + 'static,
{ {
pub fn config( pub fn config(
name: &'static str, name: &'static str,
@ -387,7 +388,10 @@ where
.expect("`inventory-announcement` must be sent"); .expect("`inventory-announcement` must be sent");
} }
pub fn connect_to<T: WriteStorage + 'static, H: Signer + 'static>( pub fn connect_to<
T: WriteStorage + 'static,
H: crypto::signature::Signer<crypto::Signature> + 'static,
>(
&mut self, &mut self,
peer: &Peer<T, H>, peer: &Peer<T, H>,
) { ) {

View File

@ -14,7 +14,7 @@ use std::{fmt, io, net};
use localtime::{LocalDuration, LocalTime}; use localtime::{LocalDuration, LocalTime};
use log::*; use log::*;
use crate::crypto::Signer; use crate::crypto;
use crate::prelude::{Address, RepoId}; use crate::prelude::{Address, RepoId};
use crate::service::io::Io; use crate::service::io::Io;
use crate::service::{DisconnectReason, Event, Message, Metrics, NodeId}; use crate::service::{DisconnectReason, Event, Message, Metrics, NodeId};
@ -202,7 +202,11 @@ pub struct Simulation<S, G> {
signer: PhantomData<G>, signer: PhantomData<G>,
} }
impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> { impl<S, G> Simulation<S, G>
where
S: WriteStorage + 'static,
G: crypto::signature::Signer<crypto::Signature>,
{
/// Create a new simulation. /// Create a new simulation.
pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self { pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self {
Self { Self {

View File

@ -12,6 +12,7 @@ use netservices::Direction as Link;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use radicle::identity::Visibility; use radicle::identity::Visibility;
use radicle::node::address::Store as _; use radicle::node::address::Store as _;
use radicle::node::device::Device;
use radicle::node::refs::Store as _; use radicle::node::refs::Store as _;
use radicle::node::routing::Store as _; use radicle::node::routing::Store as _;
use radicle::node::{ConnectOptions, DEFAULT_TIMEOUT}; use radicle::node::{ConnectOptions, DEFAULT_TIMEOUT};
@ -21,7 +22,6 @@ use radicle::test::arbitrary::gen;
use radicle::test::storage::MockRepository; use radicle::test::storage::MockRepository;
use crate::collections::{RandomMap, RandomSet}; use crate::collections::{RandomMap, RandomSet};
use crate::crypto::test::signer::MockSigner;
use crate::identity::RepoId; use crate::identity::RepoId;
use crate::node; use crate::node;
use crate::node::config::*; use crate::node::config::*;
@ -270,7 +270,7 @@ fn test_inventory_sync() {
[7, 7, 7, 7], [7, 7, 7, 7],
Storage::open(tmp.path().join("alice"), fixtures::user()).unwrap(), 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_storage = fixtures::storage(tmp.path().join("bob"), &bob_signer).unwrap();
let bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_storage); let bob = Peer::with_storage("bob", [8, 8, 8, 8], bob_storage);
let now = LocalTime::now().into(); let now = LocalTime::now().into();
@ -682,7 +682,7 @@ fn test_refs_announcement_relay_public() {
let bob = { let bob = {
let mut rng = fastrand::Rng::new(); 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(); let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap();
Peer::config( Peer::config(
@ -766,7 +766,7 @@ fn test_refs_announcement_relay_private() {
let bob = { let bob = {
let mut rng = fastrand::Rng::new(); 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(); let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap();
Peer::config( Peer::config(
@ -855,7 +855,7 @@ fn test_refs_announcement_fetch_trusted_no_inventory() {
); );
let bob = { let bob = {
let mut rng = fastrand::Rng::new(); 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(); let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap();
Peer::config( Peer::config(
@ -967,7 +967,7 @@ fn test_refs_announcement_no_subscribe() {
fn test_refs_announcement_offline() { fn test_refs_announcement_offline() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let mut alice = { let mut alice = {
let signer = MockSigner::default(); let signer = Device::mock();
let storage = fixtures::storage(tmp.path().join("alice"), &signer).unwrap(); let storage = fixtures::storage(tmp.path().join("alice"), &signer).unwrap();
Peer::config( Peer::config(

View File

@ -1,6 +1,6 @@
use std::{collections::HashSet, thread, time}; 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::node::{Alias, ConnectResult, FetchResult, Handle as _, DEFAULT_TIMEOUT};
use radicle::storage::{ use radicle::storage::{
ReadRepository, ReadStorage, RefUpdate, RemoteRepository, SignRepository, ValidateRepository, ReadRepository, ReadStorage, RefUpdate, RemoteRepository, SignRepository, ValidateRepository,
@ -281,7 +281,7 @@ fn test_replication_invalid() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let alice = Node::init(tmp.path(), config::relay("alice")); let alice = Node::init(tmp.path(), config::relay("alice"));
let mut bob = Node::init(tmp.path(), config::relay("bob")); let mut bob = Node::init(tmp.path(), config::relay("bob"));
let carol = MockSigner::default(); let carol = Device::mock();
let acme = bob.project("acme", ""); let acme = bob.project("acme", "");
let repo = bob.storage.repository_mut(acme).unwrap(); let repo = bob.storage.repository_mut(acme).unwrap();
let (_, head) = repo.head().unwrap(); let (_, head) = repo.head().unwrap();
@ -418,7 +418,7 @@ fn test_fetch_followed_remotes() {
let mut signers = Vec::with_capacity(5); let mut signers = Vec::with_capacity(5);
{ {
for _ in 0..5 { for _ in 0..5 {
let signer = MockSigner::default(); let signer = Device::mock();
rad::fork_remote(acme, &alice.id, &signer, &alice.storage).unwrap(); rad::fork_remote(acme, &alice.id, &signer, &alice.storage).unwrap();
signers.push(signer); signers.push(signer);
} }
@ -473,7 +473,7 @@ fn test_missing_remote() {
let mut alice = alice.spawn(); let mut alice = alice.spawn();
let mut bob = bob.spawn(); let mut bob = bob.spawn();
let carol = MockSigner::default(); let carol = Device::mock();
alice.connect(&bob); alice.connect(&bob);
converge([&alice, &bob]); converge([&alice, &bob]);

View File

@ -458,9 +458,9 @@ impl wire::Decode for ZeroBytes {
mod tests { mod tests {
use super::*; use super::*;
use qcheck_macros::quickcheck; use qcheck_macros::quickcheck;
use radicle::node::device::Device;
use radicle::node::UserAgent; use radicle::node::UserAgent;
use radicle::storage::refs::RefsAt; use radicle::storage::refs::RefsAt;
use radicle_crypto::test::signer::MockSigner;
use crate::deserializer::Deserializer; use crate::deserializer::Deserializer;
use crate::test::arbitrary; use crate::test::arbitrary;
@ -468,7 +468,7 @@ mod tests {
#[test] #[test]
fn test_refs_ann_max_size() { fn test_refs_ann_max_size() {
let signer = MockSigner::default(); let signer = Device::mock();
let refs: [RefsAt; REF_REMOTE_LIMIT] = arbitrary::gen(1); let refs: [RefsAt; REF_REMOTE_LIMIT] = arbitrary::gen(1);
let ann = AnnouncementMessage::Refs(RefsAnnouncement { let ann = AnnouncementMessage::Refs(RefsAnnouncement {
rid: arbitrary::gen(1), rid: arbitrary::gen(1),
@ -484,7 +484,7 @@ mod tests {
#[test] #[test]
fn test_inv_ann_max_size() { fn test_inv_ann_max_size() {
let signer = MockSigner::default(); let signer = Device::mock();
let inv: [RepoId; INVENTORY_LIMIT] = arbitrary::gen(1); let inv: [RepoId; INVENTORY_LIMIT] = arbitrary::gen(1);
let ann = AnnouncementMessage::Inventory(InventoryAnnouncement { let ann = AnnouncementMessage::Inventory(InventoryAnnouncement {
inventory: BoundedVec::collect_from(&mut inv.into_iter()), inventory: BoundedVec::collect_from(&mut inv.into_iter()),
@ -499,7 +499,7 @@ mod tests {
#[test] #[test]
fn test_node_ann_max_size() { fn test_node_ann_max_size() {
let signer = MockSigner::default(); let signer = Device::mock();
let addrs: [Address; ADDRESS_LIMIT] = arbitrary::gen(1); let addrs: [Address; ADDRESS_LIMIT] = arbitrary::gen(1);
let alias = ['@'; radicle::node::MAX_ALIAS_LENGTH]; let alias = ['@'; radicle::node::MAX_ALIAS_LENGTH];
let ann = AnnouncementMessage::Node(NodeAnnouncement { let ann = AnnouncementMessage::Node(NodeAnnouncement {

View File

@ -18,14 +18,15 @@ use localtime::LocalTime;
use netservices::resource::{ListenerEvent, NetAccept, NetTransport, SessionEvent}; use netservices::resource::{ListenerEvent, NetAccept, NetTransport, SessionEvent};
use netservices::session::{NoiseSession, ProtocolArtifact, Socks5Session}; use netservices::session::{NoiseSession, ProtocolArtifact, Socks5Session};
use netservices::{NetConnection, NetReader, NetWriter}; use netservices::{NetConnection, NetReader, NetWriter};
use radicle::node::device::Device;
use reactor::{ResourceId, ResourceType, Timestamp}; use reactor::{ResourceId, ResourceType, Timestamp};
use radicle::collections::RandomMap; use radicle::collections::RandomMap;
use radicle::crypto;
use radicle::node::config::AddressConfig; use radicle::node::config::AddressConfig;
use radicle::node::NodeId; use radicle::node::NodeId;
use radicle::storage::WriteStorage; use radicle::storage::WriteStorage;
use crate::crypto::Signer;
use crate::prelude::Deserializer; use crate::prelude::Deserializer;
use crate::service; use crate::service;
use crate::service::io::Io; use crate::service::io::Io;
@ -306,13 +307,13 @@ impl Peers {
} }
/// Wire protocol implementation for a set of peers. /// Wire protocol implementation for a set of peers.
pub struct Wire<D, S, G: Signer + Ecdh> { pub struct Wire<D, S, G: crypto::signature::Signer<crypto::Signature> + Ecdh> {
/// Backing service instance. /// Backing service instance.
service: Service<D, S, G>, service: Service<D, S, G>,
/// Worker pool interface. /// Worker pool interface.
worker: chan::Sender<Task>, worker: chan::Sender<Task>,
/// Used for authentication. /// Used for authentication.
signer: G, signer: Device<G>,
/// Node metrics. /// Node metrics.
metrics: service::Metrics, metrics: service::Metrics,
/// Internal queue of actions to send to the reactor. /// Internal queue of actions to send to the reactor.
@ -331,9 +332,9 @@ impl<D, S, G> Wire<D, S, G>
where where
D: service::Store, D: service::Store,
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + Ecdh<Pk = NodeId>, G: crypto::signature::Signer<crypto::Signature> + Ecdh<Pk = NodeId>,
{ {
pub fn new(service: Service<D, S, G>, worker: chan::Sender<Task>, signer: G) -> Self { pub fn new(service: Service<D, S, G>, worker: chan::Sender<Task>, signer: Device<G>) -> Self {
assert!(service.started().is_some(), "Service must be initialized"); assert!(service.started().is_some(), "Service must be initialized");
Self { Self {
@ -500,7 +501,7 @@ impl<D, S, G> reactor::Handler for Wire<D, S, G>
where where
D: service::Store + Send, D: service::Store + Send,
S: WriteStorage + Send + 'static, S: WriteStorage + Send + 'static,
G: Signer + Ecdh<Pk = NodeId> + Clone + Send, G: crypto::signature::Signer<crypto::Signature> + Ecdh<Pk = NodeId> + Clone + Send,
{ {
type Listener = NetAccept<WireSession<G>>; type Listener = NetAccept<WireSession<G>>;
type Transport = NetTransport<WireSession<G>>; type Transport = NetTransport<WireSession<G>>;
@ -561,14 +562,17 @@ where
return; return;
} }
let session = let session = match accept::<G>(
match accept::<G>(remote.clone().into(), connection, self.signer.clone()) { remote.clone().into(),
Ok(s) => s, connection,
Err(e) => { self.signer.clone().into_inner(),
log::error!(target: "wire", "Error creating session for {ip}: {e}"); ) {
return; 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) { let transport = match NetTransport::with_session(session, Link::Inbound) {
Ok(transport) => transport, Ok(transport) => transport,
Err(err) => { Err(err) => {
@ -963,7 +967,7 @@ impl<D, S, G> Iterator for Wire<D, S, G>
where where
D: service::Store, D: service::Store,
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + Ecdh<Pk = NodeId>, G: crypto::signature::Signer<crypto::Signature> + Ecdh<Pk = NodeId> + Clone,
{ {
type Item = Action<G>; type Item = Action<G>;
@ -1016,7 +1020,7 @@ where
match dial::<G>( match dial::<G>(
addr.to_inner(), addr.to_inner(),
node_id, node_id,
self.signer.clone(), self.signer.clone().into_inner(),
self.service.config(), self.service.config(),
) )
.and_then(|session| { .and_then(|session| {
@ -1126,7 +1130,7 @@ where
} }
/// Establish a new outgoing connection. /// Establish a new outgoing connection.
pub fn dial<G: Signer + Ecdh<Pk = NodeId>>( pub fn dial<G: Ecdh<Pk = NodeId>>(
remote_addr: NetAddr<HostName>, remote_addr: NetAddr<HostName>,
remote_id: <G as EcSk>::Pk, remote_id: <G as EcSk>::Pk,
signer: G, signer: G,
@ -1185,7 +1189,7 @@ pub fn dial<G: Signer + Ecdh<Pk = NodeId>>(
} }
/// Accept a new connection. /// Accept a new connection.
pub fn accept<G: Signer + Ecdh<Pk = NodeId>>( pub fn accept<G: Ecdh<Pk = NodeId>>(
remote_addr: NetAddr<HostName>, remote_addr: NetAddr<HostName>,
connection: net::TcpStream, connection: net::TcpStream,
signer: G, signer: G,
@ -1194,7 +1198,7 @@ pub fn accept<G: Signer + Ecdh<Pk = NodeId>>(
} }
/// Create a new [`WireSession`]. /// Create a new [`WireSession`].
fn session<G: Signer + Ecdh<Pk = NodeId>>( fn session<G: Ecdh<Pk = NodeId>>(
remote_addr: NetAddr<HostName>, remote_addr: NetAddr<HostName>,
remote_id: Option<NodeId>, remote_id: Option<NodeId>,
connection: net::TcpStream, connection: net::TcpStream,

View File

@ -5,13 +5,14 @@ use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::{assert_eq, io}; use std::{assert_eq, io};
use radicle::node::device::Device;
use thiserror::Error; use thiserror::Error;
use radicle::cob; use radicle::cob;
use radicle::cob::object::ParseObjectId; use radicle::cob::object::ParseObjectId;
use radicle::cob::patch; use radicle::cob::patch;
use radicle::cob::patch::cache::Patches as _; use radicle::cob::patch::cache::Patches as _;
use radicle::crypto::Signer; use radicle::crypto;
use radicle::explorer::ExplorerResource; use radicle::explorer::ExplorerResource;
use radicle::git::canonical; use radicle::git::canonical;
use radicle::git::canonical::Canonical; use radicle::git::canonical::Canonical;
@ -389,7 +390,7 @@ pub fn run(
} }
/// Open a new patch. /// Open a new patch.
fn patch_open<G: Signer>( fn patch_open<G>(
src: &git::RefStr, src: &git::RefStr,
upstream: &git::RefString, upstream: &git::RefString,
nid: &NodeId, nid: &NodeId,
@ -399,10 +400,13 @@ fn patch_open<G: Signer>(
patch::Patches<'_, storage::git::Repository>, patch::Patches<'_, storage::git::Repository>,
cob::cache::StoreWriter, cob::cache::StoreWriter,
>, >,
signer: &G, signer: &Device<G>,
profile: &Profile, profile: &Profile,
opts: Options, opts: Options,
) -> Result<Option<ExplorerResource>, Error> { ) -> Result<Option<ExplorerResource>, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let reference = working.find_reference(src.as_str())?; let reference = working.find_reference(src.as_str())?;
let commit = reference.peel_to_commit()?; let commit = reference.peel_to_commit()?;
let dst = git::refs::storage::staging::patch(nid, commit.id()); let dst = git::refs::storage::staging::patch(nid, commit.id());
@ -509,7 +513,7 @@ fn patch_open<G: Signer>(
/// Update an existing patch. /// Update an existing patch.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn patch_update<G: Signer>( fn patch_update<G>(
src: &git::RefStr, src: &git::RefStr,
dst: &git::Qualified, dst: &git::Qualified,
force: bool, force: bool,
@ -521,9 +525,12 @@ fn patch_update<G: Signer>(
patch::Patches<'_, storage::git::Repository>, patch::Patches<'_, storage::git::Repository>,
cob::cache::StoreWriter, cob::cache::StoreWriter,
>, >,
signer: &G, signer: &Device<G>,
opts: Options, opts: Options,
) -> Result<Option<ExplorerResource>, Error> { ) -> Result<Option<ExplorerResource>, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let reference = working.find_reference(src.as_str())?; let reference = working.find_reference(src.as_str())?;
let commit = reference.peel_to_commit()?; let commit = reference.peel_to_commit()?;
let patch_id = radicle::cob::ObjectId::from(oid); let patch_id = radicle::cob::ObjectId::from(oid);
@ -592,7 +599,7 @@ fn patch_update<G: Signer>(
Ok(Some(ExplorerResource::Patch { id: patch_id })) Ok(Some(ExplorerResource::Patch { id: patch_id }))
} }
fn push<G: Signer>( fn push<G>(
src: &git::RefStr, src: &git::RefStr,
dst: &git::Qualified, dst: &git::Qualified,
force: bool, force: bool,
@ -603,8 +610,11 @@ fn push<G: Signer>(
patch::Patches<'_, storage::git::Repository>, patch::Patches<'_, storage::git::Repository>,
cob::cache::StoreWriter, cob::cache::StoreWriter,
>, >,
signer: &G, signer: &Device<G>,
) -> Result<Option<ExplorerResource>, Error> { ) -> Result<Option<ExplorerResource>, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let head = match working.find_reference(src.as_str()) { let head = match working.find_reference(src.as_str()) {
Ok(obj) => obj.peel_to_commit()?, Ok(obj) => obj.peel_to_commit()?,
Err(e) => { Err(e) => {
@ -648,7 +658,7 @@ fn push<G: Signer>(
} }
/// Revert all patches that are no longer included in the base branch. /// Revert all patches that are no longer included in the base branch.
fn patch_revert_all<G: Signer>( fn patch_revert_all<G>(
old: git::Oid, old: git::Oid,
new: git::Oid, new: git::Oid,
stored: &git::raw::Repository, stored: &git::raw::Repository,
@ -656,8 +666,11 @@ fn patch_revert_all<G: Signer>(
patch::Patches<'_, storage::git::Repository>, patch::Patches<'_, storage::git::Repository>,
cob::cache::StoreWriter, cob::cache::StoreWriter,
>, >,
_signer: &G, _signer: &Device<G>,
) -> Result<(), Error> { ) -> Result<(), Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
// Find all commits reachable from the old OID but not from the new OID. // Find all commits reachable from the old OID but not from the new OID.
let mut revwalk = stored.revwalk()?; let mut revwalk = stored.revwalk()?;
revwalk.push(*old)?; revwalk.push(*old)?;
@ -710,7 +723,7 @@ fn patch_revert_all<G: Signer>(
} }
/// Merge all patches that have been included in the base branch. /// Merge all patches that have been included in the base branch.
fn patch_merge_all<G: Signer>( fn patch_merge_all<G>(
old: git::Oid, old: git::Oid,
new: git::Oid, new: git::Oid,
working: &git::raw::Repository, working: &git::raw::Repository,
@ -718,8 +731,11 @@ fn patch_merge_all<G: Signer>(
patch::Patches<'_, storage::git::Repository>, patch::Patches<'_, storage::git::Repository>,
cob::cache::StoreWriter, cob::cache::StoreWriter,
>, >,
signer: &G, signer: &Device<G>,
) -> Result<(), Error> { ) -> Result<(), Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let mut revwalk = working.revwalk()?; let mut revwalk = working.revwalk()?;
revwalk.push_range(&format!("{old}..{new}"))?; revwalk.push_range(&format!("{old}..{new}"))?;
@ -760,13 +776,17 @@ fn patch_merge_all<G: Signer>(
Ok(()) Ok(())
} }
fn patch_merge<C: cob::cache::Update<patch::Patch>, G: Signer>( fn patch_merge<C, G>(
mut patch: patch::PatchMut<storage::git::Repository, C>, mut patch: patch::PatchMut<storage::git::Repository, C>,
revision: patch::RevisionId, revision: patch::RevisionId,
commit: git::Oid, commit: git::Oid,
working: &git::raw::Repository, working: &git::raw::Repository,
signer: &G, signer: &Device<G>,
) -> Result<(), Error> { ) -> Result<(), Error>
where
C: cob::cache::Update<patch::Patch>,
G: crypto::signature::Signer<crypto::Signature>,
{
let (latest, _) = patch.latest(); let (latest, _) = patch.latest();
let merged = patch.merge(revision, commit, signer)?; let merged = patch.merge(revision, commit, signer)?;

View File

@ -4,13 +4,13 @@ use std::{fmt, ops::Deref, str::FromStr};
use crypto::{PublicKey, Signature}; use crypto::{PublicKey, Signature};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use radicle_cob::{Embed, ObjectId, TypeName}; use radicle_cob::{Embed, ObjectId, TypeName};
use radicle_crypto::Signer;
use radicle_git_ext as git_ext; use radicle_git_ext as git_ext;
use radicle_git_ext::Oid; use radicle_git_ext::Oid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::identity::doc::Doc; use crate::identity::doc::Doc;
use crate::node::device::Device;
use crate::{ use crate::{
cob, cob,
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, doc: &Doc,
store: &'a R, store: &'a R,
signer: &G, signer: &Device<G>,
) -> Result<IdentityMut<'a, R>, cob::store::Error> { ) -> Result<IdentityMut<'a, R>, cob::store::Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let mut store = cob::store::Store::open(store)?; let mut store = cob::store::Store::open(store)?;
let (id, identity) = Transaction::<Identity, _>::initial( let (id, identity) = Transaction::<Identity, _>::initial(
"Initialize identity", "Initialize identity",
@ -732,7 +735,10 @@ impl Revision {
}) })
} }
pub fn sign<G: Signer>(&self, signer: &G) -> Result<Signature, DocError> { pub fn sign<G: crypto::signature::Signer<crypto::Signature>>(
&self,
signer: &G,
) -> Result<Signature, DocError> {
self.doc.signature_of(signer) self.doc.signature_of(signer)
} }
} }
@ -839,14 +845,14 @@ impl<R: ReadRepository> store::Transaction<Identity, R> {
} }
impl<R: WriteRepository> store::Transaction<Identity, R> { impl<R: WriteRepository> store::Transaction<Identity, R> {
pub fn revision<G: Signer>( pub fn revision<G: crypto::signature::Signer<crypto::Signature>>(
&mut self, &mut self,
title: impl ToString, title: impl ToString,
description: impl ToString, description: impl ToString,
doc: &Doc, doc: &Doc,
parent: Option<RevisionId>, parent: Option<RevisionId>,
repo: &R, repo: &R,
signer: &G, signer: &Device<G>,
) -> Result<(), store::Error> { ) -> Result<(), store::Error> {
let (blob, bytes, signature) = doc.sign(signer).map_err(store::Error::Identity)?; let (blob, bytes, signature) = doc.sign(signer).map_err(store::Error::Identity)?;
// Store document blob in repository. // Store document blob in repository.
@ -901,11 +907,11 @@ where
pub fn transaction<G, F>( pub fn transaction<G, F>(
&mut self, &mut self,
message: &str, message: &str,
signer: &G, signer: &Device<G>,
operations: F, operations: F,
) -> Result<EntryId, Error> ) -> Result<EntryId, Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
F: FnOnce(&mut Transaction<Identity, R>, &R) -> Result<(), store::Error>, F: FnOnce(&mut Transaction<Identity, R>, &R) -> Result<(), store::Error>,
{ {
let mut tx = Transaction::default(); let mut tx = Transaction::default();
@ -919,13 +925,16 @@ where
/// Update the identity by proposing a new revision. /// Update the identity by proposing a new revision.
/// If the signer is the only delegate, the revision is accepted automatically. /// If the signer is the only delegate, the revision is accepted automatically.
pub fn update<G: Signer>( pub fn update<G>(
&mut self, &mut self,
title: impl ToString, title: impl ToString,
description: impl ToString, description: impl ToString,
doc: &Doc, doc: &Doc,
signer: &G, signer: &Device<G>,
) -> Result<RevisionId, Error> { ) -> Result<RevisionId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let parent = self.current; let parent = self.current;
let id = self.transaction("Propose revision", signer, |tx, repo| { let id = self.transaction("Propose revision", signer, |tx, repo| {
tx.revision(title, description, doc, Some(parent), repo, signer) tx.revision(title, description, doc, Some(parent), repo, signer)
@ -935,11 +944,10 @@ where
} }
/// Accept an active revision. /// Accept an active revision.
pub fn accept<G: Signer>( pub fn accept<G>(&mut self, revision: &RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
&mut self, where
revision: &RevisionId, G: crypto::signature::Signer<crypto::Signature>,
signer: &G, {
) -> Result<EntryId, Error> {
let id = *revision; let id = *revision;
let revision = self.revision(revision).ok_or(Error::NotFound(id))?; let revision = self.revision(revision).ok_or(Error::NotFound(id))?;
let signature = revision.sign(signer)?; let signature = revision.sign(signer)?;
@ -948,31 +956,32 @@ where
} }
/// Reject an active revision. /// Reject an active revision.
pub fn reject<G: Signer>( pub fn reject<G>(&mut self, revision: RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
&mut self, where
revision: RevisionId, G: crypto::signature::Signer<crypto::Signature>,
signer: &G, {
) -> Result<EntryId, Error> {
self.transaction("Reject revision", signer, |tx, _| tx.reject(revision)) self.transaction("Reject revision", signer, |tx, _| tx.reject(revision))
} }
/// Redact a revision. /// Redact a revision.
pub fn redact<G: Signer>( pub fn redact<G>(&mut self, revision: RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
&mut self, where
revision: RevisionId, G: crypto::signature::Signer<crypto::Signature>,
signer: &G, {
) -> Result<EntryId, Error> {
self.transaction("Redact revision", signer, |tx, _| tx.redact(revision)) self.transaction("Redact revision", signer, |tx, _| tx.redact(revision))
} }
/// Edit an active revision's title or description. /// Edit an active revision's title or description.
pub fn edit<G: Signer>( pub fn edit<G>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
title: String, title: String,
description: String, description: String,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Edit revision", signer, |tx, _| { self.transaction("Edit revision", signer, |tx, _| {
tx.edit(revision, title, description) tx.edit(revision, title, description)
}) })
@ -1021,8 +1030,6 @@ mod lookup {
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod test { mod test {
use qcheck_macros::quickcheck; use qcheck_macros::quickcheck;
use radicle_crypto::test::signer::MockSigner;
use radicle_crypto::Signer as _;
use crate::cob; use crate::cob;
use crate::crypto::PublicKey; use crate::crypto::PublicKey;
@ -1052,7 +1059,7 @@ mod test {
#[test] #[test]
fn test_identity_updates() { fn test_identity_updates() {
let NodeWithRepo { node, repo } = NodeWithRepo::default(); let NodeWithRepo { node, repo } = NodeWithRepo::default();
let bob = MockSigner::default(); let bob = Device::mock();
let signer = &node.signer; let signer = &node.signer;
let mut identity = Identity::load_mut(&*repo).unwrap(); let mut identity = Identity::load_mut(&*repo).unwrap();
let mut doc = identity.doc().clone().edit(); let mut doc = identity.doc().clone().edit();
@ -1112,8 +1119,8 @@ mod test {
#[test] #[test]
fn test_identity_update_rejected() { fn test_identity_update_rejected() {
let NodeWithRepo { node, repo } = NodeWithRepo::default(); let NodeWithRepo { node, repo } = NodeWithRepo::default();
let bob = MockSigner::default(); let bob = Device::mock();
let eve = MockSigner::default(); let eve = Device::mock();
let signer = &node.signer; let signer = &node.signer;
let mut identity = Identity::load_mut(&*repo).unwrap(); let mut identity = Identity::load_mut(&*repo).unwrap();
let mut doc = identity.doc().clone().edit(); let mut doc = identity.doc().clone().edit();
@ -1535,9 +1542,9 @@ mod test {
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let mut rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
let alice = MockSigner::new(&mut rng); let alice = Device::mock_rng(&mut rng);
let bob = MockSigner::new(&mut rng); let bob = Device::mock_rng(&mut rng);
let eve = MockSigner::new(&mut rng); let eve = Device::mock_rng(&mut rng);
let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap(); let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap();
let (id, _, _, _) = let (id, _, _, _) =

View File

@ -15,8 +15,8 @@ use crate::cob::store::{Cob, CobAction};
use crate::cob::thread; use crate::cob::thread;
use crate::cob::thread::{Comment, CommentId, Thread}; use crate::cob::thread::{Comment, CommentId, Thread};
use crate::cob::{op, store, ActorId, Embed, EntryId, ObjectId, TypeName}; use crate::cob::{op, store, ActorId, Embed, EntryId, ObjectId, TypeName};
use crate::crypto::Signer;
use crate::identity::doc::DocError; use crate::identity::doc::DocError;
use crate::node::device::Device;
use crate::prelude::{Did, Doc, ReadRepository, RepoId}; use crate::prelude::{Did, Doc, ReadRepository, RepoId};
use crate::storage::{HasRepoId, RepositoryError, WriteRepository}; use crate::storage::{HasRepoId, RepositoryError, WriteRepository};
@ -608,26 +608,35 @@ where
} }
/// Assign one or more actors to an issue. /// Assign one or more actors to an issue.
pub fn assign<G: Signer>( pub fn assign<G>(
&mut self, &mut self,
assignees: impl IntoIterator<Item = Did>, assignees: impl IntoIterator<Item = Did>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Assign", signer, |tx| tx.assign(assignees)) self.transaction("Assign", signer, |tx| tx.assign(assignees))
} }
/// Set the issue title. /// Set the issue title.
pub fn edit<G: Signer>(&mut self, title: impl ToString, signer: &G) -> Result<EntryId, Error> { pub fn edit<G>(&mut self, title: impl ToString, signer: &Device<G>) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Edit", signer, |tx| tx.edit(title)) self.transaction("Edit", signer, |tx| tx.edit(title))
} }
/// Set the issue description. /// Set the issue description.
pub fn edit_description<G: Signer>( pub fn edit_description<G>(
&mut self, &mut self,
description: impl ToString, description: impl ToString,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let (id, _) = self.root(); let (id, _) = self.root();
let id = *id; let id = *id;
self.transaction("Edit description", signer, |tx| { self.transaction("Edit description", signer, |tx| {
@ -636,73 +645,89 @@ where
} }
/// Lifecycle an issue. /// Lifecycle an issue.
pub fn lifecycle<G: Signer>(&mut self, state: State, signer: &G) -> Result<EntryId, Error> { pub fn lifecycle<G>(&mut self, state: State, signer: &Device<G>) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state)) self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state))
} }
/// Comment on an issue. /// Comment on an issue.
pub fn comment<G: Signer, S: ToString>( pub fn comment<G, S>(
&mut self, &mut self,
body: S, body: S,
reply_to: CommentId, reply_to: CommentId,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Comment", signer, |tx| { self.transaction("Comment", signer, |tx| {
tx.comment(body, reply_to, embeds.into_iter().collect()) tx.comment(body, reply_to, embeds.into_iter().collect())
}) })
} }
/// Edit a comment. /// Edit a comment.
pub fn edit_comment<G: Signer, S: ToString>( pub fn edit_comment<G, S>(
&mut self, &mut self,
id: CommentId, id: CommentId,
body: S, body: S,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Edit comment", signer, |tx| { self.transaction("Edit comment", signer, |tx| {
tx.edit_comment(id, body, embeds.into_iter().collect()) tx.edit_comment(id, body, embeds.into_iter().collect())
}) })
} }
/// Redact a comment. /// Redact a comment.
pub fn redact_comment<G: Signer>( pub fn redact_comment<G>(&mut self, id: CommentId, signer: &Device<G>) -> Result<EntryId, Error>
&mut self, where
id: CommentId, G: crypto::signature::Signer<crypto::Signature>,
signer: &G, {
) -> Result<EntryId, Error> {
self.transaction("Redact comment", signer, |tx| tx.redact_comment(id)) self.transaction("Redact comment", signer, |tx| tx.redact_comment(id))
} }
/// Label an issue. /// Label an issue.
pub fn label<G: Signer>( pub fn label<G>(
&mut self, &mut self,
labels: impl IntoIterator<Item = Label>, labels: impl IntoIterator<Item = Label>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Label", signer, |tx| tx.label(labels)) self.transaction("Label", signer, |tx| tx.label(labels))
} }
/// React to an issue comment. /// React to an issue comment.
pub fn react<G: Signer>( pub fn react<G>(
&mut self, &mut self,
to: CommentId, to: CommentId,
reaction: Reaction, reaction: Reaction,
active: bool, active: bool,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("React", signer, |tx| tx.react(to, reaction, active)) self.transaction("React", signer, |tx| tx.react(to, reaction, active))
} }
pub fn transaction<G, F>( pub fn transaction<G, F>(
&mut self, &mut self,
message: &str, message: &str,
signer: &G, signer: &Device<G>,
operations: F, operations: F,
) -> Result<EntryId, Error> ) -> Result<EntryId, Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
F: FnOnce(&mut Transaction<Issue, R>) -> Result<(), store::Error>, F: FnOnce(&mut Transaction<Issue, R>) -> Result<(), store::Error>,
{ {
let mut tx = Transaction::default(); let mut tx = Transaction::default();
@ -791,10 +816,10 @@ where
assignees: &[Did], assignees: &[Did],
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
cache: &'g mut C, cache: &'g mut C,
signer: &G, signer: &Device<G>,
) -> Result<IssueMut<'a, 'g, R, C>, Error> ) -> Result<IssueMut<'a, 'g, R, C>, Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
C: cob::cache::Update<Issue>, C: cob::cache::Update<Issue>,
{ {
let (id, issue) = Transaction::initial("Create issue", &mut self.raw, signer, |tx, _| { let (id, issue) = Transaction::initial("Create issue", &mut self.raw, signer, |tx, _| {
@ -822,9 +847,10 @@ where
} }
/// Remove an issue. /// Remove an issue.
pub fn remove<C, G: Signer>(&self, id: &ObjectId, signer: &G) -> Result<(), store::Error> pub fn remove<C, G>(&self, id: &ObjectId, signer: &Device<G>) -> Result<(), store::Error>
where where
C: cob::cache::Remove<Issue>, C: cob::cache::Remove<Issue>,
G: crypto::signature::Signer<crypto::Signature>,
{ {
self.raw.remove(id, signer) self.raw.remove(id, signer)
} }
@ -1717,13 +1743,12 @@ mod test {
#[test] #[test]
fn test_invalid_cob() { fn test_invalid_cob() {
use crate::crypto::test::signer::MockSigner;
use cob::change::Storage as _; use cob::change::Storage as _;
use cob::object::Storage as _; use cob::object::Storage as _;
use nonempty::NonEmpty; use nonempty::NonEmpty;
let test::setup::NodeWithRepo { node, repo, .. } = test::setup::NodeWithRepo::default(); 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 identity = repo.identity().unwrap().head();
let missing = arbitrary::oid(); let missing = arbitrary::oid();
let type_name = Issue::type_name().clone(); let type_name = Issue::type_name().clone();

View File

@ -9,7 +9,7 @@ use crate::cob::cache;
use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update}; use crate::cob::cache::{Remove, StoreReader, StoreWriter, Update};
use crate::cob::store; use crate::cob::store;
use crate::cob::{Embed, Label, ObjectId, TypeName, Uri}; use crate::cob::{Embed, Label, ObjectId, TypeName, Uri};
use crate::crypto::Signer; use crate::node::device::Device;
use crate::prelude::{Did, RepoId}; use crate::prelude::{Did, RepoId};
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository}; use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
@ -104,11 +104,11 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
labels: &[Label], labels: &[Label],
assignees: &[Did], assignees: &[Did],
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<IssueMut<'a, 'g, R, C>, super::Error> ) -> Result<IssueMut<'a, 'g, R, C>, super::Error>
where where
R: ReadRepository + WriteRepository + cob::Store, R: ReadRepository + WriteRepository + cob::Store,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
C: Update<Issue>, C: Update<Issue>,
{ {
self.store.create( self.store.create(
@ -124,9 +124,9 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
/// Remove the given `id` from the [`super::Issues`] storage, and /// Remove the given `id` from the [`super::Issues`] storage, and
/// removing the entry from the `cache`. /// removing the entry from the `cache`.
pub fn remove<G>(&mut self, id: &IssueId, signer: &G) -> Result<(), super::Error> pub fn remove<G>(&mut self, id: &IssueId, signer: &Device<G>) -> Result<(), super::Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
R: ReadRepository + SignRepository + cob::Store, R: ReadRepository + SignRepository + cob::Store,
C: Remove<Issue>, C: Remove<Issue>,
{ {

View File

@ -19,8 +19,8 @@ use crate::cob::store;
use crate::cob::store::{Cob, CobAction, Store, Transaction}; use crate::cob::store::{Cob, CobAction, Store, Transaction};
use crate::cob::{EntryId, ObjectId, TypeName}; use crate::cob::{EntryId, ObjectId, TypeName};
use crate::crypto::ssh::ExtendedSignature; use crate::crypto::ssh::ExtendedSignature;
use crate::crypto::Signer;
use crate::git; use crate::git;
use crate::node::device::Device;
use crate::prelude::ReadRepository; use crate::prelude::ReadRepository;
use crate::storage::{Oid, WriteRepository}; use crate::storage::{Oid, WriteRepository};
@ -332,12 +332,15 @@ where
/// Transition the `Job` into a running state, storing the provided /// Transition the `Job` into a running state, storing the provided
/// metadata. /// metadata.
pub fn start<G: Signer>( pub fn start<G>(
&mut self, &mut self,
run_id: String, run_id: String,
info_url: Option<String>, info_url: Option<String>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Start", signer, |tx| { self.transaction("Start", signer, |tx| {
tx.start(run_id, info_url)?; tx.start(run_id, info_url)?;
Ok(()) Ok(())
@ -345,18 +348,21 @@ where
} }
/// Transition the `Job` into a finished state, with the provided `reason`. /// Transition the `Job` into a finished state, with the provided `reason`.
pub fn finish<G: Signer>(&mut self, reason: Reason, signer: &G) -> Result<EntryId, Error> { pub fn finish<G>(&mut self, reason: Reason, signer: &Device<G>) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Finish", signer, |tx| tx.finish(reason)) self.transaction("Finish", signer, |tx| tx.finish(reason))
} }
pub fn transaction<G, F>( pub fn transaction<G, F>(
&mut self, &mut self,
message: &str, message: &str,
signer: &G, signer: &Device<G>,
operations: F, operations: F,
) -> Result<EntryId, Error> ) -> Result<EntryId, Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
F: FnOnce(&mut Transaction<Job, R>) -> Result<(), store::Error>, F: FnOnce(&mut Transaction<Job, R>) -> Result<(), store::Error>,
{ {
let mut tx = Transaction::default(); let mut tx = Transaction::default();
@ -421,11 +427,14 @@ where
} }
/// Create a fresh `Job` with the provided `commit_id`. /// Create a fresh `Job` with the provided `commit_id`.
pub fn create<'g, G: Signer>( pub fn create<'g, G>(
&'g mut self, &'g mut self,
commit_id: git::Oid, commit_id: git::Oid,
signer: &G, signer: &Device<G>,
) -> Result<JobMut<'a, 'g, R>, Error> { ) -> Result<JobMut<'a, 'g, R>, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let (id, job) = Transaction::initial("Create job", &mut self.raw, signer, |tx, _| { let (id, job) = Transaction::initial("Create job", &mut self.raw, signer, |tx, _| {
tx.trigger(commit_id)?; tx.trigger(commit_id)?;
Ok(()) Ok(())
@ -439,7 +448,10 @@ where
} }
/// Delete the `Job` identified by `id`. /// Delete the `Job` identified by `id`.
pub fn remove<G: Signer>(&self, id: &JobId, signer: &G) -> Result<(), store::Error> { pub fn remove<G>(&self, id: &JobId, signer: &Device<G>) -> Result<(), store::Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.raw.remove(id, signer) self.raw.remove(id, signer)
} }
} }

View File

@ -21,10 +21,11 @@ use crate::cob::thread;
use crate::cob::thread::Thread; use crate::cob::thread::Thread;
use crate::cob::thread::{Comment, CommentId, Edit, Reactions}; use crate::cob::thread::{Comment, CommentId, Edit, Reactions};
use crate::cob::{op, store, ActorId, Embed, EntryId, ObjectId, TypeName, Uri}; 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::git;
use crate::identity::doc::{DocAt, DocError}; use crate::identity::doc::{DocAt, DocError};
use crate::identity::PayloadError; use crate::identity::PayloadError;
use crate::node::device::Device;
use crate::prelude::*; use crate::prelude::*;
use crate::storage; use crate::storage;
@ -347,11 +348,14 @@ impl<R: WriteRepository> Merged<'_, R> {
/// ///
/// This removes Git refs relating to the patch, both in the working copy, /// This removes Git refs relating to the patch, both in the working copy,
/// and the stored copy; and updates `rad/sigrefs`. /// and the stored copy; and updates `rad/sigrefs`.
pub fn cleanup<G: Signer>( pub fn cleanup<G>(
self, self,
working: &git::raw::Repository, working: &git::raw::Repository,
signer: &G, signer: &Device<G>,
) -> Result<(), storage::RepositoryError> { ) -> Result<(), storage::RepositoryError>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let nid = signer.public_key(); let nid = signer.public_key();
let stored_ref = git::refs::patch(&self.patch).with_namespace(nid.into()); let stored_ref = git::refs::patch(&self.patch).with_namespace(nid.into());
let working_ref = git::refs::workdir::patch_upstream(&self.patch); let working_ref = git::refs::workdir::patch_upstream(&self.patch);
@ -2000,11 +2004,11 @@ where
pub fn transaction<G, F>( pub fn transaction<G, F>(
&mut self, &mut self,
message: &str, message: &str,
signer: &G, signer: &Device<G>,
operations: F, operations: F,
) -> Result<EntryId, Error> ) -> Result<EntryId, Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
F: FnOnce(&mut Transaction<Patch, R>) -> Result<(), store::Error>, F: FnOnce(&mut Transaction<Patch, R>) -> Result<(), store::Error>,
{ {
let mut tx = Transaction::default(); let mut tx = Transaction::default();
@ -2023,57 +2027,72 @@ where
} }
/// Edit patch metadata. /// Edit patch metadata.
pub fn edit<G: Signer>( pub fn edit<G, S>(
&mut self, &mut self,
title: String, title: String,
target: MergeTarget, target: MergeTarget,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Edit", signer, |tx| tx.edit(title, target)) self.transaction("Edit", signer, |tx| tx.edit(title, target))
} }
/// Edit revision metadata. /// Edit revision metadata.
pub fn edit_revision<G: Signer>( pub fn edit_revision<G, S>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
description: String, description: S,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Edit revision", signer, |tx| { self.transaction("Edit revision", signer, |tx| {
tx.edit_revision(revision, description, embeds.into_iter().collect()) tx.edit_revision(revision, description, embeds.into_iter().collect())
}) })
} }
/// Redact a revision. /// Redact a revision.
pub fn redact<G: Signer>( pub fn redact<G>(&mut self, revision: RevisionId, signer: &Device<G>) -> Result<EntryId, Error>
&mut self, where
revision: RevisionId, G: crypto::signature::Signer<crypto::Signature>,
signer: &G, {
) -> Result<EntryId, Error> {
self.transaction("Redact revision", signer, |tx| tx.redact(revision)) self.transaction("Redact revision", signer, |tx| tx.redact(revision))
} }
/// Create a thread on a patch revision. /// Create a thread on a patch revision.
pub fn thread<G: Signer, S: ToString>( pub fn thread<G, S>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
body: S, body: S,
signer: &G, signer: &Device<G>,
) -> Result<CommentId, Error> { ) -> Result<CommentId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Create thread", signer, |tx| tx.thread(revision, body)) self.transaction("Create thread", signer, |tx| tx.thread(revision, body))
} }
/// Comment on a patch revision. /// Comment on a patch revision.
pub fn comment<G: Signer, S: ToString>( pub fn comment<G, S>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
body: S, body: S,
reply_to: Option<CommentId>, reply_to: Option<CommentId>,
location: Option<CodeLocation>, location: Option<CodeLocation>,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Comment", signer, |tx| { self.transaction("Comment", signer, |tx| {
tx.comment( tx.comment(
revision, revision,
@ -2086,69 +2105,86 @@ where
} }
/// React on a patch revision. /// React on a patch revision.
pub fn react<G: Signer>( pub fn react<G>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
reaction: Reaction, reaction: Reaction,
location: Option<CodeLocation>, location: Option<CodeLocation>,
active: bool, active: bool,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("React", signer, |tx| { self.transaction("React", signer, |tx| {
tx.react(revision, reaction, location, active) tx.react(revision, reaction, location, active)
}) })
} }
/// Edit a comment on a patch revision. /// Edit a comment on a patch revision.
pub fn comment_edit<G: Signer, S: ToString>( pub fn comment_edit<G, S>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
comment: CommentId, comment: CommentId,
body: S, body: S,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Edit comment", signer, |tx| { self.transaction("Edit comment", signer, |tx| {
tx.comment_edit(revision, comment, body, embeds.into_iter().collect()) tx.comment_edit(revision, comment, body, embeds.into_iter().collect())
}) })
} }
/// React to a comment on a patch revision. /// React to a comment on a patch revision.
pub fn comment_react<G: Signer>( pub fn comment_react<G>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
comment: CommentId, comment: CommentId,
reaction: Reaction, reaction: Reaction,
active: bool, active: bool,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("React comment", signer, |tx| { self.transaction("React comment", signer, |tx| {
tx.comment_react(revision, comment, reaction, active) tx.comment_react(revision, comment, reaction, active)
}) })
} }
/// Redact a comment on a patch revision. /// Redact a comment on a patch revision.
pub fn comment_redact<G: Signer>( pub fn comment_redact<G>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
comment: CommentId, comment: CommentId,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Redact comment", signer, |tx| { self.transaction("Redact comment", signer, |tx| {
tx.comment_redact(revision, comment) tx.comment_redact(revision, comment)
}) })
} }
/// Comment on a line of code as part of a review. /// Comment on a line of code as part of a review.
pub fn review_comment<G: Signer, S: ToString>( pub fn review_comment<G, S>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
body: S, body: S,
location: Option<CodeLocation>, location: Option<CodeLocation>,
reply_to: Option<CommentId>, reply_to: Option<CommentId>,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Review comment", signer, |tx| { self.transaction("Review comment", signer, |tx| {
tx.review_comment( tx.review_comment(
review, review,
@ -2161,54 +2197,67 @@ where
} }
/// Edit review comment. /// Edit review comment.
pub fn edit_review_comment<G: Signer, S: ToString>( pub fn edit_review_comment<G, S>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
comment: EntryId, comment: EntryId,
body: S, body: S,
embeds: impl IntoIterator<Item = Embed<Uri>>, embeds: impl IntoIterator<Item = Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
S: ToString,
{
self.transaction("Edit review comment", signer, |tx| { self.transaction("Edit review comment", signer, |tx| {
tx.edit_review_comment(review, comment, body, embeds.into_iter().collect()) tx.edit_review_comment(review, comment, body, embeds.into_iter().collect())
}) })
} }
/// React to a review comment. /// React to a review comment.
pub fn react_review_comment<G: Signer>( pub fn react_review_comment<G>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
comment: EntryId, comment: EntryId,
reaction: Reaction, reaction: Reaction,
active: bool, active: bool,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("React to review comment", signer, |tx| { self.transaction("React to review comment", signer, |tx| {
tx.react_review_comment(review, comment, reaction, active) tx.react_review_comment(review, comment, reaction, active)
}) })
} }
/// React to a review comment. /// React to a review comment.
pub fn redact_review_comment<G: Signer>( pub fn redact_review_comment<G>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
comment: EntryId, comment: EntryId,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Redact review comment", signer, |tx| { self.transaction("Redact review comment", signer, |tx| {
tx.redact_review_comment(review, comment) tx.redact_review_comment(review, comment)
}) })
} }
/// Review a patch revision. /// Review a patch revision.
pub fn review<G: Signer>( pub fn review<G>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
verdict: Option<Verdict>, verdict: Option<Verdict>,
summary: Option<String>, summary: Option<String>,
labels: Vec<Label>, labels: Vec<Label>,
signer: &G, signer: &Device<G>,
) -> Result<ReviewId, Error> { ) -> Result<ReviewId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
if verdict.is_none() && summary.is_none() { if verdict.is_none() && summary.is_none() {
return Err(Error::EmptyReview); return Err(Error::EmptyReview);
} }
@ -2219,59 +2268,74 @@ where
} }
/// Edit a review. /// Edit a review.
pub fn review_edit<G: Signer>( pub fn review_edit<G>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
verdict: Option<Verdict>, verdict: Option<Verdict>,
summary: Option<String>, summary: Option<String>,
labels: Vec<Label>, labels: Vec<Label>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Edit review", signer, |tx| { self.transaction("Edit review", signer, |tx| {
tx.review_edit(review, verdict, summary, labels) tx.review_edit(review, verdict, summary, labels)
}) })
} }
/// Redact a patch review. /// Redact a patch review.
pub fn redact_review<G: Signer>( pub fn redact_review<G>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Redact review", signer, |tx| tx.redact_review(review)) self.transaction("Redact review", signer, |tx| tx.redact_review(review))
} }
/// Resolve a patch review comment. /// Resolve a patch review comment.
pub fn resolve_review_comment<G: Signer>( pub fn resolve_review_comment<G>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
comment: CommentId, comment: CommentId,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Resolve review comment", signer, |tx| { self.transaction("Resolve review comment", signer, |tx| {
tx.review_comment_resolve(review, comment) tx.review_comment_resolve(review, comment)
}) })
} }
/// Unresolve a patch review comment. /// Unresolve a patch review comment.
pub fn unresolve_review_comment<G: Signer>( pub fn unresolve_review_comment<G>(
&mut self, &mut self,
review: ReviewId, review: ReviewId,
comment: CommentId, comment: CommentId,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Unresolve review comment", signer, |tx| { self.transaction("Unresolve review comment", signer, |tx| {
tx.review_comment_unresolve(review, comment) tx.review_comment_unresolve(review, comment)
}) })
} }
/// Merge a patch revision. /// Merge a patch revision.
pub fn merge<G: Signer>( pub fn merge<G>(
&mut self, &mut self,
revision: RevisionId, revision: RevisionId,
commit: git::Oid, commit: git::Oid,
signer: &G, signer: &Device<G>,
) -> Result<Merged<R>, Error> { ) -> Result<Merged<R>, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
// TODO: Don't allow merging the same revision twice? // TODO: Don't allow merging the same revision twice?
let entry = self.transaction("Merge revision", signer, |tx| tx.merge(revision, commit))?; let entry = self.transaction("Merge revision", signer, |tx| tx.merge(revision, commit))?;
@ -2283,13 +2347,16 @@ where
} }
/// Update a patch with a new revision. /// Update a patch with a new revision.
pub fn update<G: Signer>( pub fn update<G>(
&mut self, &mut self,
description: impl ToString, description: impl ToString,
base: impl Into<git::Oid>, base: impl Into<git::Oid>,
oid: impl Into<git::Oid>, oid: impl Into<git::Oid>,
signer: &G, signer: &Device<G>,
) -> Result<RevisionId, Error> { ) -> Result<RevisionId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Add revision", signer, |tx| { self.transaction("Add revision", signer, |tx| {
tx.revision(description, base, oid) tx.revision(description, base, oid)
}) })
@ -2297,21 +2364,30 @@ where
} }
/// Lifecycle a patch. /// Lifecycle a patch.
pub fn lifecycle<G: Signer>(&mut self, state: Lifecycle, signer: &G) -> Result<EntryId, Error> { pub fn lifecycle<G>(&mut self, state: Lifecycle, signer: &Device<G>) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state)) self.transaction("Lifecycle", signer, |tx| tx.lifecycle(state))
} }
/// Assign a patch. /// Assign a patch.
pub fn assign<G: Signer>( pub fn assign<G>(
&mut self, &mut self,
assignees: BTreeSet<Did>, assignees: BTreeSet<Did>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Assign", signer, |tx| tx.assign(assignees)) self.transaction("Assign", signer, |tx| tx.assign(assignees))
} }
/// Archive a patch. /// Archive a patch.
pub fn archive<G: Signer>(&mut self, signer: &G) -> Result<bool, Error> { pub fn archive<G>(&mut self, signer: &Device<G>) -> Result<bool, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.lifecycle(Lifecycle::Archived, signer)?; self.lifecycle(Lifecycle::Archived, signer)?;
Ok(true) Ok(true)
@ -2319,7 +2395,10 @@ where
/// Mark an archived patch as ready to be reviewed again. /// Mark an archived patch as ready to be reviewed again.
/// Returns `false` if the patch was not archived. /// Returns `false` if the patch was not archived.
pub fn unarchive<G: Signer>(&mut self, signer: &G) -> Result<bool, Error> { pub fn unarchive<G>(&mut self, signer: &Device<G>) -> Result<bool, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
if !self.is_archived() { if !self.is_archived() {
return Ok(false); return Ok(false);
} }
@ -2330,7 +2409,10 @@ where
/// Mark a patch as ready to be reviewed. /// Mark a patch as ready to be reviewed.
/// Returns `false` if the patch was not a draft. /// Returns `false` if the patch was not a draft.
pub fn ready<G: Signer>(&mut self, signer: &G) -> Result<bool, Error> { pub fn ready<G>(&mut self, signer: &Device<G>) -> Result<bool, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
if !self.is_draft() { if !self.is_draft() {
return Ok(false); return Ok(false);
} }
@ -2341,7 +2423,10 @@ where
/// Mark an open patch as a draft. /// Mark an open patch as a draft.
/// Returns `false` if the patch was not open and free of merges. /// Returns `false` if the patch was not open and free of merges.
pub fn unready<G: Signer>(&mut self, signer: &G) -> Result<bool, Error> { pub fn unready<G>(&mut self, signer: &Device<G>) -> Result<bool, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
if !matches!(self.state(), State::Open { conflicts } if conflicts.is_empty()) { if !matches!(self.state(), State::Open { conflicts } if conflicts.is_empty()) {
return Ok(false); return Ok(false);
} }
@ -2351,11 +2436,14 @@ where
} }
/// Label a patch. /// Label a patch.
pub fn label<G: Signer>( pub fn label<G>(
&mut self, &mut self,
labels: impl IntoIterator<Item = Label>, labels: impl IntoIterator<Item = Label>,
signer: &G, signer: &Device<G>,
) -> Result<EntryId, Error> { ) -> Result<EntryId, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
self.transaction("Label", signer, |tx| tx.label(labels)) self.transaction("Label", signer, |tx| tx.label(labels))
} }
} }
@ -2514,11 +2602,11 @@ where
oid: impl Into<git::Oid>, oid: impl Into<git::Oid>,
labels: &[Label], labels: &[Label],
cache: &'g mut C, cache: &'g mut C,
signer: &G, signer: &Device<G>,
) -> Result<PatchMut<'a, 'g, R, C>, Error> ) -> Result<PatchMut<'a, 'g, R, C>, Error>
where where
C: cob::cache::Update<Patch>, C: cob::cache::Update<Patch>,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
self._create( self._create(
title, title,
@ -2534,7 +2622,7 @@ where
} }
/// Draft a patch. This patch will be created in a [`State::Draft`] state. /// Draft a patch. This patch will be created in a [`State::Draft`] state.
pub fn draft<'g, C, G: Signer>( pub fn draft<'g, C, G>(
&'g mut self, &'g mut self,
title: impl ToString, title: impl ToString,
description: impl ToString, description: impl ToString,
@ -2543,10 +2631,11 @@ where
oid: impl Into<git::Oid>, oid: impl Into<git::Oid>,
labels: &[Label], labels: &[Label],
cache: &'g mut C, cache: &'g mut C,
signer: &G, signer: &Device<G>,
) -> Result<PatchMut<'a, 'g, R, C>, Error> ) -> Result<PatchMut<'a, 'g, R, C>, Error>
where where
C: cob::cache::Update<Patch>, C: cob::cache::Update<Patch>,
G: crypto::signature::Signer<crypto::Signature>,
{ {
self._create( self._create(
title, title,
@ -2581,7 +2670,7 @@ where
} }
/// Create a patch. This is an internal function used by `create` and `draft`. /// Create a patch. This is an internal function used by `create` and `draft`.
fn _create<'g, C, G: Signer>( fn _create<'g, C, G>(
&'g mut self, &'g mut self,
title: impl ToString, title: impl ToString,
description: impl ToString, description: impl ToString,
@ -2591,10 +2680,11 @@ where
labels: &[Label], labels: &[Label],
state: Lifecycle, state: Lifecycle,
cache: &'g mut C, cache: &'g mut C,
signer: &G, signer: &Device<G>,
) -> Result<PatchMut<'a, 'g, R, C>, Error> ) -> Result<PatchMut<'a, 'g, R, C>, Error>
where where
C: cob::cache::Update<Patch>, C: cob::cache::Update<Patch>,
G: crypto::signature::Signer<crypto::Signature>,
{ {
let (id, patch) = Transaction::initial("Create patch", &mut self.raw, signer, |tx, _| { let (id, patch) = Transaction::initial("Create patch", &mut self.raw, signer, |tx, _| {
tx.revision(description, base, oid)?; tx.revision(description, base, oid)?;
@ -3070,7 +3160,7 @@ mod test {
fn test_revision_review_merge_redacted() { fn test_revision_review_merge_redacted() {
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap(); let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap(); let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let mut alice = Actor::new(MockSigner::default()); let mut alice = Actor::<MockSigner>::default();
let rid = gen::<RepoId>(1); let rid = gen::<RepoId>(1);
let doc = RawDoc::new( let doc = RawDoc::new(
gen::<Project>(1), gen::<Project>(1),
@ -3189,7 +3279,7 @@ mod test {
fn test_revision_reaction() { fn test_revision_reaction() {
let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap(); let base = git::Oid::from_str("cb18e95ada2bb38aadd8e6cef0963ce37a87add3").unwrap();
let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap(); let oid = git::Oid::from_str("518d5069f94c03427f694bb494ac1cd7d1339380").unwrap();
let mut alice = Actor::new(MockSigner::default()); let mut alice = Actor::<MockSigner>::default();
let repo = gen::<MockRepository>(1); let repo = gen::<MockRepository>(1);
let reaction = Reaction::new('👍').expect("failed to create a reaction"); let reaction = Reaction::new('👍').expect("failed to create a reaction");

View File

@ -9,8 +9,8 @@ use crate::cob::cache::{self, StoreReader};
use crate::cob::cache::{Remove, StoreWriter, Update}; use crate::cob::cache::{Remove, StoreWriter, Update};
use crate::cob::store; use crate::cob::store;
use crate::cob::{Label, ObjectId, TypeName}; use crate::cob::{Label, ObjectId, TypeName};
use crate::crypto::Signer;
use crate::git; use crate::git;
use crate::node::device::Device;
use crate::prelude::RepoId; use crate::prelude::RepoId;
use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository}; use crate::storage::{HasRepoId, ReadRepository, RepositoryError, SignRepository, WriteRepository};
@ -116,11 +116,11 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
base: impl Into<git::Oid>, base: impl Into<git::Oid>,
oid: impl Into<git::Oid>, oid: impl Into<git::Oid>,
labels: &[Label], labels: &[Label],
signer: &G, signer: &Device<G>,
) -> Result<PatchMut<'a, 'g, R, C>, super::Error> ) -> Result<PatchMut<'a, 'g, R, C>, super::Error>
where where
R: WriteRepository + cob::Store, R: WriteRepository + cob::Store,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
C: Update<Patch>, C: Update<Patch>,
{ {
self.store.create( self.store.create(
@ -146,11 +146,11 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
base: impl Into<git::Oid>, base: impl Into<git::Oid>,
oid: impl Into<git::Oid>, oid: impl Into<git::Oid>,
labels: &[Label], labels: &[Label],
signer: &G, signer: &Device<G>,
) -> Result<PatchMut<'a, 'g, R, C>, super::Error> ) -> Result<PatchMut<'a, 'g, R, C>, super::Error>
where where
R: WriteRepository + cob::Store, R: WriteRepository + cob::Store,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
C: Update<Patch>, C: Update<Patch>,
{ {
self.store.draft( self.store.draft(
@ -167,9 +167,9 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
/// Remove the given `id` from the [`super::Patches`] storage, and /// Remove the given `id` from the [`super::Patches`] storage, and
/// removing the entry from the `cache`. /// removing the entry from the `cache`.
pub fn remove<G>(&mut self, id: &PatchId, signer: &G) -> Result<(), super::Error> pub fn remove<G>(&mut self, id: &PatchId, signer: &Device<G>) -> Result<(), super::Error>
where where
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
R: ReadRepository + SignRepository + cob::Store, R: ReadRepository + SignRepository + cob::Store,
C: Remove<Patch>, C: Remove<Patch>,
{ {

View File

@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
use crate::cob::op::Op; use crate::cob::op::Op;
use crate::cob::{Create, Embed, EntryId, ObjectId, TypeName, Update, Updated, Uri, Version}; use crate::cob::{Create, Embed, EntryId, ObjectId, TypeName, Update, Updated, Uri, Version};
use crate::git; use crate::git;
use crate::node::device::Device;
use crate::prelude::*; use crate::prelude::*;
use crate::storage::git as storage; use crate::storage::git as storage;
use crate::storage::SignRepository; use crate::storage::SignRepository;
@ -208,15 +209,18 @@ where
T::Action: Serialize, T::Action: Serialize,
{ {
/// Update an object. /// Update an object.
pub fn update<G: Signer>( pub fn update<G>(
&self, &self,
type_name: &TypeName, type_name: &TypeName,
object_id: ObjectId, object_id: ObjectId,
message: &str, message: &str,
actions: impl Into<NonEmpty<T::Action>>, actions: impl Into<NonEmpty<T::Action>>,
embeds: Vec<Embed<Uri>>, embeds: Vec<Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<Updated<T>, Error> { ) -> Result<Updated<T>, Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let actions = actions.into(); let actions = actions.into();
let related = actions.iter().flat_map(T::Action::parents).collect(); let related = actions.iter().flat_map(T::Action::parents).collect();
let changes = actions.try_map(encoding::encode)?; let changes = actions.try_map(encoding::encode)?;
@ -251,13 +255,16 @@ where
} }
/// Create an object. /// Create an object.
pub fn create<G: Signer>( pub fn create<G>(
&self, &self,
message: &str, message: &str,
actions: impl Into<NonEmpty<T::Action>>, actions: impl Into<NonEmpty<T::Action>>,
embeds: Vec<Embed<Uri>>, embeds: Vec<Embed<Uri>>,
signer: &G, signer: &Device<G>,
) -> Result<(ObjectId, T), Error> { ) -> Result<(ObjectId, T), Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let actions = actions.into(); let actions = actions.into();
let parents = actions.iter().flat_map(T::Action::parents).collect(); let parents = actions.iter().flat_map(T::Action::parents).collect();
let contents = actions.try_map(encoding::encode)?; let contents = actions.try_map(encoding::encode)?;
@ -270,7 +277,7 @@ where
}) })
}) })
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let cob = cob::create::<T, _, G>( let cob = cob::create::<T, _, _>(
self.repo, self.repo,
signer, signer,
self.identity, self.identity,
@ -296,7 +303,10 @@ where
} }
/// Remove an object. /// Remove an object.
pub fn remove<G: Signer>(&self, id: &ObjectId, signer: &G) -> Result<(), Error> { pub fn remove<G>(&self, id: &ObjectId, signer: &Device<G>) -> Result<(), Error>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let name = git::refs::storage::cob(signer.public_key(), self.type_name, id); let name = git::refs::storage::cob(signer.public_key(), self.type_name, id);
match self match self
.repo .repo
@ -405,13 +415,13 @@ where
pub fn initial<G, F, Tx>( pub fn initial<G, F, Tx>(
message: &str, message: &str,
store: &mut Store<T, R>, store: &mut Store<T, R>,
signer: &G, signer: &Device<G>,
operations: F, operations: F,
) -> Result<(ObjectId, T), Error> ) -> Result<(ObjectId, T), Error>
where where
Tx: From<Self>, Tx: From<Self>,
Self: From<Tx>, Self: From<Tx>,
G: Signer, G: crypto::signature::Signer<crypto::Signature>,
F: FnOnce(&mut Tx, &R) -> Result<(), Error>, F: FnOnce(&mut Tx, &R) -> Result<(), Error>,
R: ReadRepository + SignRepository + cob::Store, R: ReadRepository + SignRepository + cob::Store,
T::Action: Serialize + Clone, T::Action: Serialize + Clone,
@ -469,16 +479,17 @@ where
/// Commit transaction. /// Commit transaction.
/// ///
/// Returns an operation that can be applied onto an in-memory state. /// Returns an operation that can be applied onto an in-memory state.
pub fn commit<G: Signer>( pub fn commit<G>(
self, self,
msg: &str, msg: &str,
id: ObjectId, id: ObjectId,
store: &mut Store<T, R>, store: &mut Store<T, R>,
signer: &G, signer: &Device<G>,
) -> Result<(T, EntryId), Error> ) -> Result<(T, EntryId), Error>
where where
R: ReadRepository + SignRepository + cob::Store, R: ReadRepository + SignRepository + cob::Store,
T::Action: Serialize + Clone, T::Action: Serialize + Clone,
G: crypto::signature::Signer<crypto::Signature>,
{ {
let actions = NonEmpty::from_vec(self.actions) let actions = NonEmpty::from_vec(self.actions)
.expect("Transaction::commit: transaction must not be empty"); .expect("Transaction::commit: transaction must not be empty");

View File

@ -17,6 +17,7 @@ use crate::git::ext::author::Author;
use crate::git::ext::commit::headers::Headers; use crate::git::ext::commit::headers::Headers;
use crate::git::ext::commit::{trailers::OwnedTrailer, Commit}; use crate::git::ext::commit::{trailers::OwnedTrailer, Commit};
use crate::git::Oid; use crate::git::Oid;
use crate::node::device::Device;
use crate::prelude::Did; use crate::prelude::Did;
use crate::profile::env; use crate::profile::env;
use crate::storage::ReadRepository; use crate::storage::ReadRepository;
@ -148,17 +149,17 @@ where
/// An object that can be used to create and sign operations. /// An object that can be used to create and sign operations.
pub struct Actor<G> { pub struct Actor<G> {
pub signer: G, pub signer: Device<G>,
} }
impl<G: Default> Default for Actor<G> { impl<G: Default + Signer> Default for Actor<G> {
fn default() -> Self { fn default() -> Self {
Self::new(G::default()) Self::new(Device::default())
} }
} }
impl<G> Actor<G> { impl<G> Actor<G> {
pub fn new(signer: G) -> Self { pub fn new(signer: Device<G>) -> Self {
Self { signer } Self { signer }
} }
} }

View File

@ -638,6 +638,7 @@ mod tests {
use crate::cob::test; use crate::cob::test;
use crate::crypto::test::signer::MockSigner; use crate::crypto::test::signer::MockSigner;
use crate::crypto::Signer; use crate::crypto::Signer;
use crate::node::device::Device;
use crate::profile::env; use crate::profile::env;
use crate::test::arbitrary; use crate::test::arbitrary;
use crate::test::arbitrary::gen; use crate::test::arbitrary::gen;
@ -651,13 +652,13 @@ mod tests {
impl<G: Default + Signer> Default for Actor<G> { impl<G: Default + Signer> Default for Actor<G> {
fn default() -> Self { fn default() -> Self {
Self { Self {
inner: cob::test::Actor::new(G::default()), inner: cob::test::Actor::<G>::default(),
} }
} }
} }
impl<G: Signer> Actor<G> { impl<G: Signer> Actor<G> {
pub fn new(signer: G) -> Self { pub fn new(signer: Device<G>) -> Self {
Self { Self {
inner: cob::test::Actor::new(signer), inner: cob::test::Actor::new(signer),
} }

View File

@ -249,12 +249,11 @@ impl Canonical {
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod tests { mod tests {
use crypto::test::signer::MockSigner;
use radicle_crypto::Signer;
use super::*; use super::*;
use crate::assert_matches; use crate::assert_matches;
use crate::git; use crate::git;
use crate::node::device::Device;
use crate::test::fixtures; use crate::test::fixtures;
/// Test helper to construct a Canonical and get the quorum /// Test helper to construct a Canonical and get the quorum
@ -267,7 +266,7 @@ mod tests {
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, head)| { .map(|(i, head)| {
let signer = MockSigner::from_seed([(i + 1) as u8; 32]); let signer = Device::mock_from_seed([(i + 1) as u8; 32]);
let did = Did::from(signer.public_key()); let did = Did::from(signer.public_key());
(did, (*head).into()) (did, (*head).into())
}) })

View File

@ -20,6 +20,7 @@ use crate::crypto;
use crate::crypto::Signature; use crate::crypto::Signature;
use crate::git; use crate::git;
use crate::identity::{project::Project, Did}; use crate::identity::{project::Project, Did};
use crate::node::device::Device;
use crate::storage; use crate::storage;
use crate::storage::{ReadRepository, RepositoryError}; use crate::storage::{ReadRepository, RepositoryError};
@ -803,10 +804,10 @@ impl Doc {
/// [`Doc::encode`] and sign the [`Doc`], returning the set of bytes, its /// [`Doc::encode`] and sign the [`Doc`], returning the set of bytes, its
/// corresponding Git [`Oid`] and the [`Signature`] over the [`Oid`]. /// corresponding Git [`Oid`] and the [`Signature`] over the [`Oid`].
pub fn sign<G: crypto::Signer>( pub fn sign<G>(&self, signer: &G) -> Result<(git::Oid, Vec<u8>, Signature), DocError>
&self, where
signer: &G, G: crypto::signature::Signer<crypto::Signature>,
) -> Result<(git::Oid, Vec<u8>, Signature), DocError> { {
let (oid, bytes) = self.encode()?; let (oid, bytes) = self.encode()?;
let sig = signer.sign(oid.as_bytes()); let sig = signer.sign(oid.as_bytes());
@ -814,7 +815,10 @@ impl Doc {
} }
/// Similar to [`Doc::sign`], but only returning the [`Signature`]. /// Similar to [`Doc::sign`], but only returning the [`Signature`].
pub fn signature_of<G: crypto::Signer>(&self, signer: &G) -> Result<Signature, DocError> { pub fn signature_of<G>(&self, signer: &G) -> Result<Signature, DocError>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let (_, _, sig) = self.sign(signer)?; let (_, _, sig) = self.sign(signer)?;
Ok(sig) Ok(sig)
@ -835,11 +839,14 @@ impl Doc {
/// Initialize an [`identity::Identity`] with this [`Doc`] as the associated /// Initialize an [`identity::Identity`] with this [`Doc`] as the associated
/// document. /// document.
pub fn init<G: crypto::Signer>( pub fn init<G>(
&self, &self,
repo: &storage::git::Repository, repo: &storage::git::Repository,
signer: &G, signer: &Device<G>,
) -> Result<git::Oid, RepositoryError> { ) -> Result<git::Oid, RepositoryError>
where
G: crypto::signature::Signer<crypto::Signature>,
{
let cob = identity::Identity::initialize(self, repo, signer)?; let cob = identity::Identity::initialize(self, repo, signer)?;
let id_ref = git::refs::storage::id(signer.public_key()); let id_ref = git::refs::storage::id(signer.public_key());
let cob_ref = git::refs::storage::cob( let cob_ref = git::refs::storage::cob(
@ -862,8 +869,6 @@ impl Doc {
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod test { mod test {
use radicle_crypto::test::signer::MockSigner;
use radicle_crypto::Signer as _;
use serde_json::json; use serde_json::json;
use crate::assert_matches; use crate::assert_matches;
@ -880,7 +885,7 @@ mod test {
#[test] #[test]
fn test_duplicate_dids() { fn test_duplicate_dids() {
let delegate = MockSigner::from_seed([0xff; 32]); let delegate = Device::mock_from_seed([0xff; 32]);
let did = Did::from(delegate.public_key()); let did = Did::from(delegate.public_key());
let mut doc = RawDoc::new(gen::<Project>(1), vec![did], 1, Visibility::Public); let mut doc = RawDoc::new(gen::<Project>(1), vec![did], 1, Visibility::Public);
doc.delegate(did); doc.delegate(did);
@ -1015,7 +1020,7 @@ mod test {
transport::local::register(storage.clone()); transport::local::register(storage.clone());
let delegate = MockSigner::from_seed([0xff; 32]); let delegate = Device::mock_from_seed([0xff; 32]);
let (repo, _) = fixtures::repository(tempdir.path().join("working")); let (repo, _) = fixtures::repository(tempdir.path().join("working"));
let (id, _, _) = rad::init( let (id, _, _) = rad::init(
&repo, &repo,
@ -1063,7 +1068,7 @@ mod test {
let (working, _) = fixtures::repository(tempdir.path().join("working")); let (working, _) = fixtures::repository(tempdir.path().join("working"));
let delegate = MockSigner::from_seed([0xff; 32]); let delegate = Device::mock_from_seed([0xff; 32]);
let (rid, doc, _) = rad::init( let (rid, doc, _) = rad::init(
&working, &working,
"heartwood".try_into().unwrap(), "heartwood".try_into().unwrap(),

View File

@ -39,7 +39,7 @@ pub use storage::git::Storage;
pub mod prelude { pub mod prelude {
use super::*; use super::*;
pub use crypto::{PublicKey, Signer, Verified}; pub use crypto::{PublicKey, Verified};
pub use identity::{project::Project, Did, Doc, RawDoc, RepoId}; pub use identity::{project::Project, Did, Doc, RawDoc, RepoId};
pub use node::{Alias, NodeId, Timestamp}; pub use node::{Alias, NodeId, Timestamp};
pub use profile::Profile; pub use profile::Profile;

View File

@ -5,6 +5,7 @@ mod features;
pub mod address; pub mod address;
pub mod config; pub mod config;
pub mod db; pub mod db;
pub mod device;
pub mod events; pub mod events;
pub mod notifications; pub mod notifications;
pub mod policy; pub mod policy;

174
radicle/src/node/device.rs Normal file
View File

@ -0,0 +1,174 @@
use std::fmt;
use std::ops::Deref;
use crypto::{
signature::{Signer, Verifier},
ssh::ExtendedSignature,
Signature,
};
use crate::crypto;
use super::NodeId;
/// A `Device` identifies the local node through its [`NodeId`], and carries its
/// signing mechanism.
///
/// The signing mechanism is for node specific cryptography, e.g. signing
/// `rad/sigrefs`, COB commits, node messages, etc.
///
/// Note that a `Device` can create [`Signature`]s and [`ExtendedSignature`]s.
/// It can achieve this as long as `S` implements `Signer<Signature>`.
#[derive(Clone)]
pub struct Device<S> {
node: NodeId,
signer: S,
}
impl<S> fmt::Debug for Device<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Device")
.field("node", &self.node.to_human())
.finish()
}
}
impl<S: crypto::Signer + Default> Default for Device<S> {
fn default() -> Self {
Self::from(S::default())
}
}
impl<S> Device<S> {
/// Construct a new `Device`.
pub fn new(node: NodeId, signer: S) -> Self {
Self { node, signer }
}
/// Return the [`NodeId`] of the `Device.`
pub fn node_id(&self) -> &NodeId {
&self.node
}
/// Return the [`crypto::PublicKey`] of the `Device.`
pub fn public_key(&self) -> &crypto::PublicKey {
&self.node
}
/// Convert the `Device` into its signer.
///
/// This consumes the `Device`.
pub fn into_inner(self) -> S {
self.signer
}
}
#[cfg(any(test, feature = "test"))]
impl Device<crypto::test::signer::MockSigner> {
/// Construct a `Device` using a default `MockSigner` as the device signer.
pub fn mock() -> Self {
Device::from(crypto::test::signer::MockSigner::default())
}
/// Construct a `Device`, constructing an RNG'd `MockSigner` for the signer.
pub fn mock_rng(rng: &mut fastrand::Rng) -> Self {
Device::from(crypto::test::signer::MockSigner::new(rng))
}
/// Construct a `Device`, constructing a seeded `MockSigner` for the signer.
pub fn mock_from_seed(seed: [u8; 32]) -> Self {
Device::from(crypto::test::signer::MockSigner::from_seed(seed))
}
}
impl<S: Signer<Signature> + 'static> Device<S> {
/// Construct a [`BoxedDevice`] from a given `Device`.
pub fn boxed(self) -> BoxedDevice {
BoxedDevice(Device {
node: self.node,
signer: BoxedSigner(Box::new(self.signer)),
})
}
}
impl<S> Verifier<Signature> for Device<S> {
fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), crypto::signature::Error> {
self.node
.verify(msg, signature)
.map_err(crypto::signature::Error::from_source)
}
}
impl<S: crypto::Signer> From<S> for Device<S> {
fn from(signer: S) -> Self {
Self {
node: *signer.public_key(),
signer,
}
}
}
impl<S: crypto::Signer + Clone> From<&S> for Device<S> {
fn from(signer: &S) -> Self {
Self::from(signer.clone())
}
}
impl<S: Signer<Signature>> Signer<Signature> for Device<S> {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, crypto::signature::Error> {
self.signer.try_sign(msg)
}
}
impl<S: Signer<Signature>> Signer<ExtendedSignature> for Device<S> {
fn try_sign(&self, msg: &[u8]) -> Result<ExtendedSignature, crypto::signature::Error> {
Ok(ExtendedSignature {
key: *self.public_key(),
sig: self.signer.try_sign(msg)?,
})
}
}
/// A `Signer<Signature>` that is packed in a [`Box`] for dynamic dispatch.
pub struct BoxedSigner(Box<dyn Signer<Signature> + 'static>);
impl Signer<Signature> for BoxedSigner {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, crypto::signature::Error> {
self.0.try_sign(msg)
}
}
/// A `Device` where the signer is a dynamic `Signer<Signature>`, in the form of
/// a [`BoxedSigner`].
///
/// This can be constructed via [`Device::boxed`].
pub struct BoxedDevice(Device<BoxedSigner>);
impl AsRef<Device<BoxedSigner>> for BoxedDevice {
fn as_ref(&self) -> &Device<BoxedSigner> {
&self.0
}
}
impl Deref for BoxedDevice {
type Target = Device<BoxedSigner>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Signer<Signature> for BoxedDevice {
fn try_sign(&self, msg: &[u8]) -> Result<Signature, crypto::signature::Error> {
self.0.signer.try_sign(msg)
}
}
impl Signer<ExtendedSignature> for BoxedDevice {
fn try_sign(&self, msg: &[u8]) -> Result<ExtendedSignature, crypto::signature::Error> {
Ok(ExtendedSignature {
key: *self.0.public_key(),
sig: self.0.signer.try_sign(msg)?,
})
}
}

View File

@ -24,7 +24,8 @@ use thiserror::Error;
use crate::cob::migrate; use crate::cob::migrate;
use crate::crypto::ssh::agent::Agent; use crate::crypto::ssh::agent::Agent;
use crate::crypto::ssh::{keystore, Keystore, Passphrase}; use crate::crypto::ssh::{keystore, Keystore, Passphrase};
use crate::crypto::{PublicKey, Signer}; use crate::crypto::PublicKey;
use crate::node::device::{BoxedDevice, Device};
use crate::node::policy::config::store::Read; use crate::node::policy::config::store::Read;
use crate::node::{ use crate::node::{
notifications, policy, policy::Scope, Alias, AliasStore, Handle as _, Node, UserAgent, notifications, policy, policy::Scope, Alias, AliasStore, Handle as _, Node, UserAgent,
@ -293,22 +294,22 @@ impl Profile {
Did::from(self.public_key) Did::from(self.public_key)
} }
pub fn signer(&self) -> Result<Box<dyn Signer>, Error> { pub fn signer(&self) -> Result<BoxedDevice, Error> {
if !self.keystore.is_encrypted()? { if !self.keystore.is_encrypted()? {
let signer = keystore::MemorySigner::load(&self.keystore, None)?; let signer = keystore::MemorySigner::load(&self.keystore, None)?;
return Ok(signer.boxed()); return Ok(Device::from(signer).boxed());
} }
if let Some(passphrase) = env::passphrase() { if let Some(passphrase) = env::passphrase() {
let signer = keystore::MemorySigner::load(&self.keystore, Some(passphrase))?; let signer = keystore::MemorySigner::load(&self.keystore, Some(passphrase))?;
return Ok(signer.boxed()); return Ok(Device::from(signer).boxed());
} }
match Agent::connect() { match Agent::connect() {
Ok(agent) => { Ok(agent) => {
let signer = agent.signer(self.public_key); let signer = agent.signer(self.public_key);
if signer.is_ready()? { if signer.is_ready()? {
Ok(signer.boxed()) Ok(Device::from(signer).boxed())
} else { } else {
Err(Error::KeyNotRegistered(self.public_key)) Err(Error::KeyNotRegistered(self.public_key))
} }

View File

@ -7,11 +7,12 @@ use once_cell::sync::Lazy;
use thiserror::Error; use thiserror::Error;
use crate::cob::ObjectId; use crate::cob::ObjectId;
use crate::crypto::{Signer, Verified}; use crate::crypto::Verified;
use crate::git; use crate::git;
use crate::identity::doc; use crate::identity::doc;
use crate::identity::doc::{DocError, RepoId, Visibility}; use crate::identity::doc::{DocError, RepoId, Visibility};
use crate::identity::project::{Project, ProjectName}; use crate::identity::project::{Project, ProjectName};
use crate::node::device::Device;
use crate::storage::git::transport; use crate::storage::git::transport;
use crate::storage::git::Repository; use crate::storage::git::Repository;
use crate::storage::refs::SignedRefs; use crate::storage::refs::SignedRefs;
@ -48,15 +49,19 @@ pub enum InitError {
} }
/// Initialize a new radicle project from a git repository. /// Initialize a new radicle project from a git repository.
pub fn init<G: Signer, S: WriteStorage>( pub fn init<G, S>(
repo: &git2::Repository, repo: &git2::Repository,
name: ProjectName, name: ProjectName,
description: &str, description: &str,
default_branch: BranchName, default_branch: BranchName,
visibility: Visibility, visibility: Visibility,
signer: &G, signer: &Device<G>,
storage: S, storage: S,
) -> Result<(RepoId, identity::Doc, SignedRefs<Verified>), InitError> { ) -> Result<(RepoId, identity::Doc, SignedRefs<Verified>), InitError>
where
G: crypto::signature::Signer<crypto::Signature>,
S: WriteStorage,
{
// TODO: Better error when project id already exists in storage, but remote doesn't. // TODO: Better error when project id already exists in storage, but remote doesn't.
let delegate: identity::Did = signer.public_key().into(); let delegate: identity::Did = signer.public_key().into();
let proj = Project::new( let proj = Project::new(
@ -98,10 +103,10 @@ fn init_configure<G>(
default_branch: &BranchName, default_branch: &BranchName,
url: &git::Url, url: &git::Url,
identity: git::Oid, identity: git::Oid,
signer: &G, signer: &Device<G>,
) -> Result<SignedRefs<Verified>, InitError> ) -> Result<SignedRefs<Verified>, InitError>
where where
G: crypto::Signer, G: crypto::signature::Signer<crypto::Signature>,
{ {
let pk = signer.public_key(); let pk = signer.public_key();
@ -162,12 +167,16 @@ pub enum ForkError {
} }
/// Create a local tree for an existing project, from an existing remote. /// Create a local tree for an existing project, from an existing remote.
pub fn fork_remote<G: Signer, S: storage::WriteStorage>( pub fn fork_remote<G, S>(
proj: RepoId, proj: RepoId,
remote: &RemoteId, remote: &RemoteId,
signer: &G, signer: &Device<G>,
storage: S, storage: S,
) -> Result<(), ForkError> { ) -> Result<(), ForkError>
where
G: crypto::signature::Signer<crypto::Signature>,
S: storage::WriteStorage,
{
// TODO: Copy tags over? // TODO: Copy tags over?
// Creates or copies the following references: // Creates or copies the following references:
@ -197,11 +206,11 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
Ok(()) Ok(())
} }
pub fn fork<G: Signer, S: storage::WriteStorage>( pub fn fork<G, S>(rid: RepoId, signer: &Device<G>, storage: &S) -> Result<(), ForkError>
rid: RepoId, where
signer: &G, G: crypto::signature::Signer<crypto::Signature>,
storage: &S, S: storage::WriteStorage,
) -> Result<(), ForkError> { {
let me = signer.public_key(); let me = signer.public_key();
let repository = storage.repository_mut(rid)?; let repository = storage.repository_mut(rid)?;
let (canonical_branch, canonical_head) = repository.head()?; let (canonical_branch, canonical_head) = repository.head()?;
@ -451,7 +460,6 @@ mod tests {
use std::collections::HashMap; use std::collections::HashMap;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use radicle_crypto::test::signer::MockSigner;
use crate::git::{name::component, qualified}; use crate::git::{name::component, qualified};
use crate::identity::Did; use crate::identity::Did;
@ -465,7 +473,7 @@ mod tests {
#[test] #[test]
fn test_init() { fn test_init() {
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let signer = MockSigner::default(); let signer = Device::mock();
let public_key = *signer.public_key(); let public_key = *signer.public_key();
let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap(); let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap();
@ -518,8 +526,8 @@ mod tests {
fn test_fork() { fn test_fork() {
let mut rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let alice = MockSigner::new(&mut rng); let alice = Device::mock_rng(&mut rng);
let bob = MockSigner::new(&mut rng); let bob = Device::mock_rng(&mut rng);
let bob_id = bob.public_key(); let bob_id = bob.public_key();
let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap(); let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap();
@ -556,7 +564,7 @@ mod tests {
#[test] #[test]
fn test_checkout() { fn test_checkout() {
let tempdir = tempfile::tempdir().unwrap(); let tempdir = tempfile::tempdir().unwrap();
let signer = MockSigner::default(); let signer = Device::mock();
let remote_id = signer.public_key(); let remote_id = signer.public_key();
let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap(); let storage = Storage::open(tempdir.path().join("storage"), fixtures::user()).unwrap();

View File

@ -10,7 +10,7 @@ use nonempty::NonEmpty;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crypto::{PublicKey, Signer, Unverified, Verified}; use crypto::{PublicKey, Unverified, Verified};
pub use git::{Validation, Validations}; pub use git::{Validation, Validations};
pub use radicle_git_ext::Oid; pub use radicle_git_ext::Oid;
@ -21,6 +21,7 @@ use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefStr, R
use crate::identity::{Did, PayloadError}; use crate::identity::{Did, PayloadError};
use crate::identity::{Doc, DocAt, DocError}; use crate::identity::{Doc, DocAt, DocError};
use crate::identity::{Identity, RepoId}; use crate::identity::{Identity, RepoId};
use crate::node::device::Device;
use crate::node::SyncedAt; use crate::node::SyncedAt;
use crate::storage::git::NAMESPACES_GLOB; use crate::storage::git::NAMESPACES_GLOB;
use crate::storage::refs::Refs; use crate::storage::refs::Refs;
@ -657,7 +658,9 @@ pub trait WriteRepository: ReadRepository + SignRepository {
/// Allows signing refs. /// Allows signing refs.
pub trait SignRepository { pub trait SignRepository {
/// Sign the repository's refs under the `refs/rad/sigrefs` branch. /// Sign the repository's refs under the `refs/rad/sigrefs` branch.
fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, RepositoryError>; fn sign_refs<G>(&self, signer: &Device<G>) -> Result<SignedRefs<Verified>, RepositoryError>
where
G: crypto::signature::Signer<crypto::Signature>;
} }
impl<T, S> ReadStorage for T impl<T, S> ReadStorage for T

View File

@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fs, io}; use std::{fs, io};
use crypto::{Signer, Verified}; use crypto::Verified;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use tempfile::TempDir; use tempfile::TempDir;
@ -15,6 +15,7 @@ use crate::git::canonical::Canonical;
use crate::identity::doc::DocError; use crate::identity::doc::DocError;
use crate::identity::{Doc, DocAt, RepoId}; use crate::identity::{Doc, DocAt, RepoId};
use crate::identity::{Identity, Project}; use crate::identity::{Identity, Project};
use crate::node::device::Device;
use crate::node::SyncedAt; use crate::node::SyncedAt;
use crate::storage::refs; use crate::storage::refs;
use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt}; use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt};
@ -433,11 +434,15 @@ impl Repository {
} }
/// Create the repository's identity branch. /// Create the repository's identity branch.
pub fn init<G: Signer, S: WriteStorage>( pub fn init<G, S>(
doc: &Doc, doc: &Doc,
storage: &S, storage: &S,
signer: &G, signer: &Device<G>,
) -> Result<(Self, git::Oid), RepositoryError> { ) -> Result<(Self, git::Oid), RepositoryError>
where
G: crypto::signature::Signer<crypto::Signature>,
S: WriteStorage,
{
let (doc_oid, doc_bytes) = doc.encode()?; let (doc_oid, doc_bytes) = doc.encode()?;
let id = RepoId::from(doc_oid); let id = RepoId::from(doc_oid);
let repo = Self::create(paths::repository(storage, &id), id, storage.info())?; let repo = Self::create(paths::repository(storage, &id), id, storage.info())?;
@ -885,7 +890,10 @@ impl WriteRepository for Repository {
} }
impl SignRepository for Repository { impl SignRepository for Repository {
fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, RepositoryError> { fn sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
&self,
signer: &Device<G>,
) -> Result<SignedRefs<Verified>, RepositoryError> {
let remote = signer.public_key(); let remote = signer.public_key();
// Ensure the root reference is set, which is checked during sigref verification. // Ensure the root reference is set, which is checked during sigref verification.
if self.identity_root_of(remote).is_err() { if self.identity_root_of(remote).is_err() {
@ -959,7 +967,6 @@ pub mod paths {
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod tests { mod tests {
use crypto::test::signer::MockSigner;
use super::*; use super::*;
use crate::git; use crate::git;
@ -970,7 +977,7 @@ mod tests {
#[test] #[test]
fn test_remote_refs() { fn test_remote_refs() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let signer = MockSigner::default(); let signer = Device::mock();
let storage = fixtures::storage(dir.path(), &signer).unwrap(); let storage = fixtures::storage(dir.path(), &signer).unwrap();
let inv = storage.repositories().unwrap(); let inv = storage.repositories().unwrap();
let proj = inv.first().unwrap(); let proj = inv.first().unwrap();
@ -996,7 +1003,7 @@ mod tests {
#[test] #[test]
fn test_references_of() { fn test_references_of() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let signer = MockSigner::default(); let signer = Device::mock();
let storage = Storage::open(tmp.path().join("storage"), fixtures::user()).unwrap(); let storage = Storage::open(tmp.path().join("storage"), fixtures::user()).unwrap();
transport::local::register(storage.clone()); transport::local::register(storage.clone());
@ -1031,7 +1038,7 @@ mod tests {
fn test_sign_refs() { fn test_sign_refs() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let mut rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
let signer = MockSigner::new(&mut rng); let signer = Device::mock_rng(&mut rng);
let storage = Storage::open(tmp.path(), fixtures::user()).unwrap(); let storage = Storage::open(tmp.path(), fixtures::user()).unwrap();
let alice = *signer.public_key(); let alice = *signer.public_key();
let (rid, _, working, _) = let (rid, _, working, _) =

View File

@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use std::path::Path; use std::path::Path;
use cob::object::Objects; use cob::object::Objects;
use cob::signatures::ExtendedSignature;
use radicle_cob as cob; use radicle_cob as cob;
use radicle_cob::change; use radicle_cob::change;
use storage::RemoteRepository; use storage::RemoteRepository;
@ -11,6 +12,9 @@ use storage::SignRepository;
use storage::ValidateRepository; use storage::ValidateRepository;
use crate::git::*; use crate::git::*;
use crate::identity;
use crate::identity::doc::DocError;
use crate::node::device::Device;
use crate::storage; use crate::storage;
use crate::storage::Error; use crate::storage::Error;
use crate::storage::{ use crate::storage::{
@ -68,7 +72,7 @@ impl change::Storage for Repository {
spec: change::Template<Self::ObjectId>, spec: change::Template<Self::ObjectId>,
) -> Result<cob::Entry, Self::StoreError> ) -> Result<cob::Entry, Self::StoreError>
where where
Signer: crypto::Signer, Signer: crypto::signature::Signer<ExtendedSignature>,
{ {
self.backend.store(authority, parents, signer, spec) self.backend.store(authority, parents, signer, spec)
} }
@ -208,7 +212,7 @@ impl<R: storage::WriteRepository> change::Storage for DraftStore<'_, R> {
spec: change::Template<Self::ObjectId>, spec: change::Template<Self::ObjectId>,
) -> Result<cob::Entry, Self::StoreError> ) -> Result<cob::Entry, Self::StoreError>
where where
Signer: crypto::Signer, Signer: crypto::signature::Signer<ExtendedSignature>,
{ {
self.repo.raw().store(authority, parents, signer, spec) self.repo.raw().store(authority, parents, signer, spec)
} }
@ -222,10 +226,13 @@ impl<R: storage::WriteRepository> change::Storage for DraftStore<'_, R> {
} }
} }
impl<R: storage::ReadRepository> SignRepository for DraftStore<'_, R> { impl<R> SignRepository for DraftStore<'_, R>
fn sign_refs<G: crypto::Signer>( where
R: storage::ReadRepository,
{
fn sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
&self, &self,
signer: &G, signer: &Device<G>,
) -> Result<storage::refs::SignedRefs<Verified>, RepositoryError> { ) -> Result<storage::refs::SignedRefs<Verified>, RepositoryError> {
// Since this is a draft store, we do not actually want to sign the refs. // Since this is a draft store, we do not actually want to sign the refs.
// Instead, we just return the existing signed refs. // Instead, we just return the existing signed refs.

View File

@ -7,13 +7,15 @@ use std::ops::{Deref, DerefMut};
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use crypto::{PublicKey, Signature, Signer, SignerError, Unverified, Verified}; use crypto::signature::Signer;
use crypto::{PublicKey, Signature, Unverified, Verified};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::git; use crate::git;
use crate::git::ext as git_ext; use crate::git::ext as git_ext;
use crate::git::Oid; use crate::git::Oid;
use crate::node::device::Device;
use crate::profile::env; use crate::profile::env;
use crate::storage; use crate::storage;
use crate::storage::{ReadRepository, RemoteId, RepoId, WriteRepository}; use crate::storage::{ReadRepository, RemoteId, RepoId, WriteRepository};
@ -39,7 +41,7 @@ pub enum Error {
#[error("invalid signature: {0}")] #[error("invalid signature: {0}")]
InvalidSignature(#[from] crypto::Error), InvalidSignature(#[from] crypto::Error),
#[error("signer error: {0}")] #[error("signer error: {0}")]
Signer(#[from] SignerError), Signer(#[from] crypto::signature::Error),
#[error("canonical refs: {0}")] #[error("canonical refs: {0}")]
Canonical(#[from] canonical::Error), Canonical(#[from] canonical::Error),
#[error("invalid reference")] #[error("invalid reference")]
@ -88,15 +90,15 @@ impl Refs {
} }
/// Sign these refs with the given signer and return [`SignedRefs`]. /// Sign these refs with the given signer and return [`SignedRefs`].
pub fn signed<G>(self, signer: &G) -> Result<SignedRefs<Unverified>, Error> pub fn signed<G>(self, device: &Device<G>) -> Result<SignedRefs<Unverified>, Error>
where where
G: Signer, G: Signer<crypto::Signature>,
{ {
let refs = self; let refs = self;
let msg = refs.canonical(); let msg = refs.canonical();
let signature = signer.try_sign(&msg)?; let signature = device.try_sign(&msg)?;
Ok(SignedRefs::new(refs, *signer.public_key(), signature)) Ok(SignedRefs::new(refs, *device.public_key(), signature))
} }
/// Get a particular ref. /// Get a particular ref.
@ -464,7 +466,6 @@ pub mod canonical {
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
mod tests { mod tests {
use crypto::test::signer::MockSigner;
use qcheck_macros::quickcheck; use qcheck_macros::quickcheck;
use storage::{git::transport, RemoteRepository, SignRepository, WriteStorage}; use storage::{git::transport, RemoteRepository, SignRepository, WriteStorage};
@ -490,8 +491,8 @@ mod tests {
// `paris` repo. We also don't expected the signed refs to validate without error. // `paris` repo. We also don't expected the signed refs to validate without error.
fn test_rid_verification() { fn test_rid_verification() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let alice = MockSigner::default(); let alice = Device::mock();
let bob = MockSigner::default(); let bob = Device::mock();
let storage = &Storage::open(tmp.path().join("storage"), fixtures::user()).unwrap(); let storage = &Storage::open(tmp.path().join("storage"), fixtures::user()).unwrap();
transport::local::register(storage.clone()); transport::local::register(storage.clone());

View File

@ -74,6 +74,7 @@ pub mod setup {
use super::storage::{Namespaces, RefUpdate}; use super::storage::{Namespaces, RefUpdate};
use crate::crypto::test::signer::MockSigner; use crate::crypto::test::signer::MockSigner;
use crate::node::device::Device;
use crate::storage::git::transport::remote; use crate::storage::git::transport::remote;
use crate::{ use crate::{
git, git,
@ -91,7 +92,7 @@ pub mod setup {
pub tmp: TempDir, pub tmp: TempDir,
pub root: PathBuf, pub root: PathBuf,
pub storage: Storage, pub storage: Storage,
pub signer: MockSigner, pub signer: Device<MockSigner>,
} }
impl Default for Node { impl Default for Node {
@ -104,6 +105,7 @@ pub mod setup {
impl Node { impl Node {
pub fn new(tmp: TempDir, signer: MockSigner, alias: &str) -> Self { pub fn new(tmp: TempDir, signer: MockSigner, alias: &str) -> Self {
let signer = Device::from(signer);
let root = tmp.path().to_path_buf(); let root = tmp.path().to_path_buf();
let home = root.join("home"); let home = root.join("home");
let paths = Home::new(home.as_path()).unwrap(); let paths = Home::new(home.as_path()).unwrap();

View File

@ -245,7 +245,7 @@ impl Arbitrary for storage::Remote<crypto::Unverified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self { fn arbitrary(g: &mut qcheck::Gen) -> Self {
let refs = Refs::arbitrary(g); let refs = Refs::arbitrary(g);
let signer = MockSigner::arbitrary(g); let signer = MockSigner::arbitrary(g);
let signed = refs.signed(&signer).unwrap(); let signed = refs.signed(&signer.into()).unwrap();
storage::Remote::<crypto::Unverified>::new(signed) storage::Remote::<crypto::Unverified>::new(signed)
} }

View File

@ -1,10 +1,11 @@
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use crate::crypto::{PublicKey, Signer, Verified}; use crate::crypto::{PublicKey, Verified};
use crate::git; use crate::git;
use crate::identity::doc::Visibility; use crate::identity::doc::Visibility;
use crate::identity::RepoId; use crate::identity::RepoId;
use crate::node::device::Device;
use crate::node::Alias; use crate::node::Alias;
use crate::rad; use crate::rad;
use crate::storage::git::transport; use crate::storage::git::transport;
@ -23,7 +24,11 @@ pub fn user() -> git::UserInfo {
} }
/// Create a new storage with a project. /// Create a new storage with a project.
pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: &G) -> Result<Storage, rad::InitError> { pub fn storage<P, G>(path: P, signer: &Device<G>) -> Result<Storage, rad::InitError>
where
P: AsRef<Path>,
G: crypto::signature::Signer<crypto::Signature>,
{
let path = path.as_ref(); let path = path.as_ref();
let storage = Storage::open( let storage = Storage::open(
path.join("storage"), path.join("storage"),
@ -57,11 +62,15 @@ pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: &G) -> Result<Storage
} }
/// Create a new repository at the given path, and initialize it into a project. /// Create a new repository at the given path, and initialize it into a project.
pub fn project<P: AsRef<Path>, G: Signer>( pub fn project<P, G>(
path: P, path: P,
storage: &Storage, storage: &Storage,
signer: &G, signer: &Device<G>,
) -> Result<(RepoId, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> { ) -> Result<(RepoId, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError>
where
P: AsRef<Path>,
G: crypto::signature::Signer<crypto::Signature>,
{
transport::local::register(storage.clone()); transport::local::register(storage.clone());
let (working, head) = repository(path); let (working, head) = repository(path);

View File

@ -6,8 +6,9 @@ use std::str::FromStr;
use git_ext::ref_format as fmt; use git_ext::ref_format as fmt;
use crate::crypto::{Signer, Verified}; use crate::crypto::Verified;
use crate::identity::doc::{Doc, DocAt, DocError, RawDoc, RepoId}; use crate::identity::doc::{Doc, DocAt, DocError, RawDoc, RepoId};
use crate::node::device::Device;
use crate::node::NodeId; use crate::node::NodeId;
pub use crate::storage::*; pub use crate::storage::*;
@ -347,9 +348,9 @@ impl WriteRepository for MockRepository {
} }
impl SignRepository for MockRepository { impl SignRepository for MockRepository {
fn sign_refs<G: Signer>( fn sign_refs<G: crypto::signature::Signer<crypto::Signature>>(
&self, &self,
_signer: &G, _signer: &Device<G>,
) -> Result<crate::storage::refs::SignedRefs<Verified>, RepositoryError> { ) -> Result<crate::storage::refs::SignedRefs<Verified>, RepositoryError> {
todo!() todo!()
} }
@ -419,7 +420,7 @@ impl radicle_cob::change::Storage for MockRepository {
Self::StoreError, Self::StoreError,
> >
where where
G: radicle_crypto::Signer, G: radicle_crypto::signature::Signer<Self::Signatures>,
{ {
todo!() todo!()
} }