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:
parent
c33aa87589
commit
207d2133f9
|
|
@ -3,19 +3,19 @@ To create your first radicle project, navigate to a git repository, and run
|
||||||
the `init` command:
|
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 .
|
Initializing local 🌱 project in .
|
||||||
|
|
||||||
ok Project acme created
|
ok Project heartwood created
|
||||||
{
|
{
|
||||||
"name": "acme",
|
"name": "heartwood",
|
||||||
"description": "Acme's repository",
|
"description": "Radicle Heartwood Protocol & Stack",
|
||||||
"default-branch": "master"
|
"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 .
|
rad .
|
||||||
|
|
||||||
To publish your project to the network, run:
|
To publish your project to the network, run:
|
||||||
|
|
@ -27,5 +27,5 @@ Projects can be listed with the `ls` command:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad ls
|
$ rad ls
|
||||||
acme rad:z4EXAgDJk11uFThfVir5FivKhiAeK cdf76ce Acme's repository
|
heartwood rad:zb2rNRYmmz7SLZ7xMjM7dddswC3K cdf76ce Radicle Heartwood Protocol & Stack
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,8 @@ pub fn execute(options: Options, profile: &Profile) -> anyhow::Result<PathBuf> {
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
let remotes = delegates
|
let remotes = delegates
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|d| *d.id)
|
.map(|did| *did)
|
||||||
.filter(|id| id != profile.id())
|
.filter(|id| id != profile.id())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ pub fn clone(id: Id, _interactive: Interactive, ctx: impl term::Context) -> anyh
|
||||||
let delegates = doc
|
let delegates = doc
|
||||||
.delegates
|
.delegates
|
||||||
.iter()
|
.iter()
|
||||||
.map(|d| *d.id)
|
.map(|d| **d)
|
||||||
.filter(|id| id != profile.id())
|
.filter(|id| id != profile.id())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let default_branch = doc.payload.default_branch.clone();
|
let default_branch = doc.payload.default_branch.clone();
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,16 @@ pub struct MockSigner {
|
||||||
|
|
||||||
impl MockSigner {
|
impl MockSigner {
|
||||||
pub fn new(rng: &mut fastrand::Rng) -> Self {
|
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(..);
|
*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);
|
let keypair = KeyPair::from_seed(seed);
|
||||||
|
|
||||||
Self::from(SecretKey::from(keypair.sk))
|
Self::from(SecretKey::from(keypair.sk))
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ async fn delegates_projects_handler(
|
||||||
let Ok((_, head)) = repo.head() else { return None };
|
let Ok((_, head)) = repo.head() else { return None };
|
||||||
let Ok(Doc { payload, delegates, .. }) = repo.project_of(ctx.profile.id()) 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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use thiserror::Error;
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
|
|
||||||
pub use crypto::PublicKey;
|
pub use crypto::PublicKey;
|
||||||
pub use project::{Delegate, Doc, Id, IdError};
|
pub use project::{Doc, Id, IdError};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum DidError {
|
pub enum DidError {
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
|
|
@ -105,7 +87,7 @@ pub struct Doc<V> {
|
||||||
pub payload: Payload,
|
pub payload: Payload,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub extensions: BTreeMap<Namespace, serde_json::Value>,
|
pub extensions: BTreeMap<Namespace, serde_json::Value>,
|
||||||
pub delegates: NonEmpty<Delegate>,
|
pub delegates: NonEmpty<Did>,
|
||||||
pub threshold: usize,
|
pub threshold: usize,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[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.
|
/// 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 {
|
pub fn delegate(&mut self, key: crypto::PublicKey) -> bool {
|
||||||
let delegate = Delegate {
|
let delegate = Did::from(key);
|
||||||
name,
|
if self.delegates.iter().all(|id| id != &delegate) {
|
||||||
id: Did::from(key),
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.delegates.iter().all(|d| d.id != delegate.id) {
|
|
||||||
self.delegates.push(delegate);
|
self.delegates.push(delegate);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +211,7 @@ impl Doc<Unverified> {
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
default_branch: BranchName,
|
default_branch: BranchName,
|
||||||
delegate: Delegate,
|
delegate: Did,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
payload: Payload {
|
payload: Payload {
|
||||||
|
|
@ -252,7 +230,7 @@ impl Doc<Unverified> {
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
default_branch: BranchName,
|
default_branch: BranchName,
|
||||||
delegates: NonEmpty<Delegate>,
|
delegates: NonEmpty<Did>,
|
||||||
threshold: usize,
|
threshold: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -289,15 +267,6 @@ impl Doc<Unverified> {
|
||||||
"number of delegates cannot exceed 255",
|
"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() {
|
if self.delegates.is_empty() {
|
||||||
return Err(VerificationError::Delegates(
|
return Err(VerificationError::Delegates(
|
||||||
"delegate list cannot be empty",
|
"delegate list cannot be empty",
|
||||||
|
|
@ -471,7 +440,7 @@ impl Identity<Untrusted> {
|
||||||
// Check that enough delegates signed this next version.
|
// Check that enough delegates signed this next version.
|
||||||
let quorum = signatures
|
let quorum = signatures
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(key, _)| trusted.delegates.iter().any(|d| d.matches(key)))
|
.filter(|(key, _)| trusted.delegates.iter().any(|d| &**d == key))
|
||||||
.count();
|
.count();
|
||||||
if quorum < trusted.threshold {
|
if quorum < trusted.threshold {
|
||||||
return Err(IdentityError::QuorumNotReached(quorum, trusted.threshold));
|
return Err(IdentityError::QuorumNotReached(quorum, trusted.threshold));
|
||||||
|
|
@ -498,6 +467,7 @@ mod test {
|
||||||
use radicle_crypto::Signer as _;
|
use radicle_crypto::Signer as _;
|
||||||
|
|
||||||
use crate::rad;
|
use crate::rad;
|
||||||
|
use crate::storage::git::transport;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::storage::{ReadStorage, WriteStorage};
|
use crate::storage::{ReadStorage, WriteStorage};
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
|
|
@ -506,6 +476,35 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use qcheck_macros::quickcheck;
|
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]
|
#[test]
|
||||||
fn test_not_found() {
|
fn test_not_found() {
|
||||||
let tempdir = tempfile::tempdir().unwrap();
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -559,7 +558,7 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Add Bob as a delegate, and sign it.
|
// 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.threshold = 2;
|
||||||
proj.sign(&alice)
|
proj.sign(&alice)
|
||||||
.and_then(|(_, sig)| {
|
.and_then(|(_, sig)| {
|
||||||
|
|
@ -573,7 +572,7 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Add Eve as a delegate, and sign it.
|
// Add Eve as a delegate, and sign it.
|
||||||
proj.delegate("eve".to_owned(), *eve.public_key());
|
proj.delegate(*eve.public_key());
|
||||||
proj.sign(&alice)
|
proj.sign(&alice)
|
||||||
.and_then(|(_, alice_sig)| {
|
.and_then(|(_, alice_sig)| {
|
||||||
proj.sign(&bob).and_then(|(_, bob_sig)| {
|
proj.sign(&bob).and_then(|(_, bob_sig)| {
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,7 @@ pub fn init<G: Signer>(
|
||||||
) -> Result<(Id, identity::Doc<Verified>, SignedRefs<Verified>), InitError> {
|
) -> Result<(Id, identity::Doc<Verified>, SignedRefs<Verified>), InitError> {
|
||||||
// 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 pk = signer.public_key();
|
let pk = signer.public_key();
|
||||||
let delegate = identity::Delegate {
|
let delegate = identity::Did::from(*pk);
|
||||||
// TODO: Use actual user name.
|
|
||||||
name: String::from("anonymous"),
|
|
||||||
id: identity::Did::from(*pk),
|
|
||||||
};
|
|
||||||
let doc = identity::Doc::initial(
|
let doc = identity::Doc::initial(
|
||||||
name.to_owned(),
|
name.to_owned(),
|
||||||
description.to_owned(),
|
description.to_owned(),
|
||||||
|
|
@ -348,7 +344,7 @@ mod tests {
|
||||||
use radicle_crypto::test::signer::MockSigner;
|
use radicle_crypto::test::signer::MockSigner;
|
||||||
|
|
||||||
use crate::git::{name::component, qualified, Qualified};
|
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::transport;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::storage::{ReadStorage, WriteStorage};
|
use crate::storage::{ReadStorage, WriteStorage};
|
||||||
|
|
@ -403,13 +399,7 @@ mod tests {
|
||||||
assert_eq!(project.name, "acme");
|
assert_eq!(project.name, "acme");
|
||||||
assert_eq!(project.description, "Acme's repo");
|
assert_eq!(project.description, "Acme's repo");
|
||||||
assert_eq!(project.default_branch, git::refname!("master"));
|
assert_eq!(project.default_branch, git::refname!("master"));
|
||||||
assert_eq!(
|
assert_eq!(project.delegates.first(), &Did::from(public_key));
|
||||||
project.delegates.first(),
|
|
||||||
&Delegate {
|
|
||||||
name: String::from("anonymous"),
|
|
||||||
id: Did::from(public_key),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -500,7 +500,7 @@ impl ReadRepository for Repository {
|
||||||
|
|
||||||
let mut heads = Vec::new();
|
let mut heads = Vec::new();
|
||||||
for delegate in project.delegates.iter() {
|
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);
|
heads.push(r);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use qcheck::Arbitrary;
|
||||||
|
|
||||||
use crate::collections::HashMap;
|
use crate::collections::HashMap;
|
||||||
use crate::git;
|
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;
|
||||||
use crate::storage::refs::{Refs, SignedRefs};
|
use crate::storage::refs::{Refs, SignedRefs};
|
||||||
use crate::test::storage::MockStorage;
|
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> {
|
impl Arbitrary for Doc<Unverified> {
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||||
let name = String::arbitrary(g);
|
let name = String::arbitrary(g);
|
||||||
let description = String::arbitrary(g);
|
let description = String::arbitrary(g);
|
||||||
let default_branch = git::RefString::try_from(String::arbitrary(g)).unwrap();
|
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)
|
Self::initial(name, description, default_branch, delegate)
|
||||||
}
|
}
|
||||||
|
|
@ -91,12 +82,7 @@ impl Arbitrary for Doc<Verified> {
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let delegates: NonEmpty<_> = iter::repeat_with(|| Delegate {
|
let delegates: NonEmpty<_> = iter::repeat_with(|| Did::arbitrary(g))
|
||||||
name: iter::repeat_with(|| rng.alphanumeric())
|
|
||||||
.take(rng.usize(1..16))
|
|
||||||
.collect(),
|
|
||||||
id: Did::arbitrary(g),
|
|
||||||
})
|
|
||||||
.take(rng.usize(1..6))
|
.take(rng.usize(1..6))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.try_into()
|
.try_into()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue