Remove delegate names from identity doc

We are working on other mechanisms to help identify keys, and this
is just redundant.

Also update some of the CLI examples to create a canonical example
for documentation.

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-12-07 12:16:00 +01:00
parent c33aa87589
commit 207d2133f9
No known key found for this signature in database
10 changed files with 68 additions and 89 deletions

View File

@ -3,19 +3,19 @@ To create your first radicle project, navigate to a git repository, and run
the `init` command:
```
$ rad init --name acme --description "Acme's repository" --no-confirm
$ rad init --name heartwood --description "Radicle Heartwood Protocol & Stack" --no-confirm
Initializing local 🌱 project in .
ok Project acme created
ok Project heartwood created
{
"name": "acme",
"description": "Acme's repository",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"default-branch": "master"
}
Your project id is rad:z4EXAgDJk11uFThfVir5FivKhiAeK. You can show it any time by running:
Your project id is rad:zb2rNRYmmz7SLZ7xMjM7dddswC3K. You can show it any time by running:
rad .
To publish your project to the network, run:
@ -27,5 +27,5 @@ Projects can be listed with the `ls` command:
```
$ rad ls
acme rad:z4EXAgDJk11uFThfVir5FivKhiAeK cdf76ce Acme's repository
heartwood rad:zb2rNRYmmz7SLZ7xMjM7dddswC3K cdf76ce Radicle Heartwood Protocol & Stack
```

View File

@ -110,8 +110,8 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
spinner.finish();
let remotes = delegates
.iter()
.map(|d| *d.id)
.into_iter()
.map(|did| *did)
.filter(|id| id != profile.id())
.collect::<Vec<_>>();

View File

@ -101,7 +101,7 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
let delegates = doc
.delegates
.iter()
.map(|d| *d.id)
.map(|d| **d)
.filter(|id| id != profile.id())
.collect::<Vec<_>>();
let default_branch = doc.payload.default_branch.clone();

View File

@ -8,12 +8,16 @@ pub struct MockSigner {
impl MockSigner {
pub fn new(rng: &mut fastrand::Rng) -> Self {
let mut bytes: [u8; 32] = [0; 32];
let mut seed: [u8; 32] = [0; 32];
for byte in &mut bytes {
for byte in &mut seed {
*byte = rng.u8(..);
}
let seed = Seed::new(bytes);
Self::from_seed(seed)
}
pub fn from_seed(seed: [u8; 32]) -> Self {
let seed = Seed::new(seed);
let keypair = KeyPair::from_seed(seed);
Self::from(SecretKey::from(keypair.sk))

View File

@ -40,7 +40,7 @@ async fn delegates_projects_handler(
let Ok((_, head)) = repo.head() else { return None };
let Ok(Doc { payload, delegates, .. }) = repo.project_of(ctx.profile.id()) else { return None };
if !delegates.iter().any(|d| d.id == delegate) {
if !delegates.iter().any(|d| *d == delegate) {
return None;
}

View File

@ -9,7 +9,7 @@ use thiserror::Error;
use crate::crypto;
pub use crypto::PublicKey;
pub use project::{Delegate, Doc, Id, IdError};
pub use project::{Doc, Id, IdError};
#[derive(Error, Debug)]
pub enum DidError {

View File

@ -68,24 +68,6 @@ impl DocError {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Delegate {
pub name: String,
pub id: Did,
}
impl Delegate {
fn matches(&self, key: &PublicKey) -> bool {
&self.id.0 == key
}
}
impl From<Delegate> for PublicKey {
fn from(delegate: Delegate) -> Self {
delegate.id.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Payload {
@ -105,7 +87,7 @@ pub struct Doc<V> {
pub payload: Payload,
#[serde(flatten)]
pub extensions: BTreeMap<Namespace, serde_json::Value>,
pub delegates: NonEmpty<Delegate>,
pub delegates: NonEmpty<Did>,
pub threshold: usize,
#[serde(skip)]
@ -125,13 +107,9 @@ impl Doc<Verified> {
}
/// Attempt to add a new delegate to the document. Returns `true` if it wasn't there before.
pub fn delegate(&mut self, name: String, key: crypto::PublicKey) -> bool {
let delegate = Delegate {
name,
id: Did::from(key),
};
if self.delegates.iter().all(|d| d.id != delegate.id) {
pub fn delegate(&mut self, key: crypto::PublicKey) -> bool {
let delegate = Did::from(key);
if self.delegates.iter().all(|id| id != &delegate) {
self.delegates.push(delegate);
return true;
}
@ -233,7 +211,7 @@ impl Doc<Unverified> {
name: String,
description: String,
default_branch: BranchName,
delegate: Delegate,
delegate: Did,
) -> Self {
Self {
payload: Payload {
@ -252,7 +230,7 @@ impl Doc<Unverified> {
name: String,
description: String,
default_branch: BranchName,
delegates: NonEmpty<Delegate>,
delegates: NonEmpty<Did>,
threshold: usize,
) -> Self {
Self {
@ -289,15 +267,6 @@ impl Doc<Unverified> {
"number of delegates cannot exceed 255",
));
}
if self
.delegates
.iter()
.any(|d| d.name.is_empty() || d.name.len() > MAX_STRING_LENGTH)
{
return Err(VerificationError::Delegates(
"delegate name must not be empty and must not exceed 255 bytes",
));
}
if self.delegates.is_empty() {
return Err(VerificationError::Delegates(
"delegate list cannot be empty",
@ -471,7 +440,7 @@ impl Identity<Untrusted> {
// Check that enough delegates signed this next version.
let quorum = signatures
.iter()
.filter(|(key, _)| trusted.delegates.iter().any(|d| d.matches(key)))
.filter(|(key, _)| trusted.delegates.iter().any(|d| &**d == key))
.count();
if quorum < trusted.threshold {
return Err(IdentityError::QuorumNotReached(quorum, trusted.threshold));
@ -498,6 +467,7 @@ mod test {
use radicle_crypto::Signer as _;
use crate::rad;
use crate::storage::git::transport;
use crate::storage::git::Storage;
use crate::storage::{ReadStorage, WriteStorage};
use crate::test::arbitrary;
@ -506,6 +476,35 @@ mod test {
use super::*;
use qcheck_macros::quickcheck;
#[test]
fn test_canonical_example() {
let tempdir = tempfile::tempdir().unwrap();
let storage = Storage::open(tempdir.path().join("storage")).unwrap();
transport::local::register(storage.clone());
let delegate = MockSigner::from_seed([0xff; 32]);
let (repo, _) = fixtures::repository(tempdir.path().join("working"));
let (id, _, _) = rad::init(
&repo,
"heartwood",
"Radicle Heartwood Protocol & Stack",
git::refname!("master"),
&delegate,
&storage,
)
.unwrap();
assert_eq!(
delegate.public_key().to_human(),
String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi")
);
assert_eq!(
id.to_human(),
String::from("rad:zb2rNRYmmz7SLZ7xMjM7dddswC3K")
);
}
#[test]
fn test_not_found() {
let tempdir = tempfile::tempdir().unwrap();
@ -559,7 +558,7 @@ mod test {
.unwrap();
// Add Bob as a delegate, and sign it.
proj.delegate("bob".to_owned(), *bob.public_key());
proj.delegate(*bob.public_key());
proj.threshold = 2;
proj.sign(&alice)
.and_then(|(_, sig)| {
@ -573,7 +572,7 @@ mod test {
.unwrap();
// Add Eve as a delegate, and sign it.
proj.delegate("eve".to_owned(), *eve.public_key());
proj.delegate(*eve.public_key());
proj.sign(&alice)
.and_then(|(_, alice_sig)| {
proj.sign(&bob).and_then(|(_, bob_sig)| {

View File

@ -59,11 +59,7 @@ pub fn init<G: Signer>(
) -> Result<(Id, identity::Doc<Verified>, SignedRefs<Verified>), InitError> {
// TODO: Better error when project id already exists in storage, but remote doesn't.
let pk = signer.public_key();
let delegate = identity::Delegate {
// TODO: Use actual user name.
name: String::from("anonymous"),
id: identity::Did::from(*pk),
};
let delegate = identity::Did::from(*pk);
let doc = identity::Doc::initial(
name.to_owned(),
description.to_owned(),
@ -348,7 +344,7 @@ mod tests {
use radicle_crypto::test::signer::MockSigner;
use crate::git::{name::component, qualified, Qualified};
use crate::identity::{Delegate, Did};
use crate::identity::Did;
use crate::storage::git::transport;
use crate::storage::git::Storage;
use crate::storage::{ReadStorage, WriteStorage};
@ -403,13 +399,7 @@ mod tests {
assert_eq!(project.name, "acme");
assert_eq!(project.description, "Acme's repo");
assert_eq!(project.default_branch, git::refname!("master"));
assert_eq!(
project.delegates.first(),
&Delegate {
name: String::from("anonymous"),
id: Did::from(public_key),
}
);
assert_eq!(project.delegates.first(), &Did::from(public_key));
}
#[test]

View File

@ -500,7 +500,7 @@ impl ReadRepository for Repository {
let mut heads = Vec::new();
for delegate in project.delegates.iter() {
let r = self.reference_oid(&delegate.id, &branch_ref)?.into();
let r = self.reference_oid(delegate, &branch_ref)?.into();
heads.push(r);
}

View File

@ -10,7 +10,7 @@ use qcheck::Arbitrary;
use crate::collections::HashMap;
use crate::git;
use crate::identity::{project::Delegate, project::Doc, Did, Id};
use crate::identity::{project::Doc, Did, Id};
use crate::storage;
use crate::storage::refs::{Refs, SignedRefs};
use crate::test::storage::MockStorage;
@ -57,21 +57,12 @@ impl Arbitrary for Did {
}
}
impl Arbitrary for Delegate {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
Self {
name: String::arbitrary(g),
id: Did::arbitrary(g),
}
}
}
impl Arbitrary for Doc<Unverified> {
fn arbitrary(g: &mut qcheck::Gen) -> Self {
let name = String::arbitrary(g);
let description = String::arbitrary(g);
let default_branch = git::RefString::try_from(String::arbitrary(g)).unwrap();
let delegate = Delegate::arbitrary(g);
let delegate = Did::arbitrary(g);
Self::initial(name, description, default_branch, delegate)
}
@ -91,16 +82,11 @@ impl Arbitrary for Doc<Verified> {
.collect::<String>()
.try_into()
.unwrap();
let delegates: NonEmpty<_> = iter::repeat_with(|| Delegate {
name: iter::repeat_with(|| rng.alphanumeric())
.take(rng.usize(1..16))
.collect(),
id: Did::arbitrary(g),
})
.take(rng.usize(1..6))
.collect::<Vec<_>>()
.try_into()
.unwrap();
let delegates: NonEmpty<_> = iter::repeat_with(|| Did::arbitrary(g))
.take(rng.usize(1..6))
.collect::<Vec<_>>()
.try_into()
.unwrap();
let threshold = delegates.len() / 2 + 1;
let doc: Doc<Unverified> =
Doc::new(name, description, default_branch, delegates, threshold);