Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle-cli: use Did across CLI
Fintan Halpenny committed 3 years ago
commit 5477a3615f30a3d945fb90fbdcece5be00852471
parent b57a4606f656a24d7682b5826016b264dd2df2cd
15 files changed +93 -102
modified radicle-cli/examples/rad-auth.md
@@ -7,7 +7,7 @@ $ rad auth
Initializing your 🌱 profile and identity

ok Creating your 🌱 Ed25519 keypair...
-
ok Profile z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created.
+
ok Profile did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi created.

Your radicle Node ID is z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi. This identifies your device.

@@ -18,7 +18,8 @@ You can get the above information at all times using the `self` command:

```
$ rad self
-
ID             z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
ID             did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
Node ID        z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
Key (hash)     SHA256:UIedaL6Cxm6OUErh9GQUzzglSk7VpQlVTI1TAFB/HWA
Key (full)     AAAAC3NzaC1lZDI1NTE5AAAAIHahWSBEpuT1ESZbynOmBNkLBSnR32Ar4woZqSV2YNH1
Storage (git)  [..]/storage
modified radicle-cli/examples/rad-delegate.md
@@ -14,7 +14,7 @@ We want to add a new maintainer to the project to help out with the
work.

```
-
$ rad delegate add z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
$ rad delegate add did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
Added delegate 'did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG'
ok Update successful!
```
@@ -33,7 +33,7 @@ And finally, we no longer want to be part of the project so we pass on
the torch and remove ourselves from the delegate set.

```
-
$ rad delegate remove z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
$ rad delegate remove did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --to rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
Removed delegate 'did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
ok Update successful!
```
modified radicle-cli/examples/rad-id-rebase.md
@@ -5,7 +5,7 @@ First off, we will create two proposals -- we can imagine two
delegates creating proposals concurrently.

```
-
$ rad id edit --title "Add Alice" --description "Add Alice as a delegate" --delegates z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
+
$ rad id edit --title "Add Alice" --description "Add Alice as a delegate" --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
ok Identity proposal '57332790a2eabc0b2fd8c7ff48c3579d5812d405' created 🌱
title: Add Alice
description: Add Alice as a delegate
@@ -47,7 +47,7 @@ Quorum Reached
```

```
-
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --no-confirm
+
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG --no-confirm
ok Identity proposal 'c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e' created 🌱
title: Add Bob
description: Add Bob as a delegate
@@ -294,7 +294,7 @@ Quorum Reached
We can now update the proposal to have both keys in the delegates set:

```
-
$ rad id update c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e --rev z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4 --delegates z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
+
$ rad id update c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e --rev z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/4 --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
ok Identity proposal 'c3698d4e85f9d4c0ee536b34d6122fc7c81f7e2e' updated 🌱
ok Revision 'z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/6'
title: Add Bob
modified radicle-cli/examples/rad-id.md
@@ -9,11 +9,11 @@ delegate` command. For cases where `threshold > 1`, it is necessary to
gather a quorum of signatures to update the Radicle identity. To do
this, we use the `rad id` command.

-
Let's add Bob as a delegate using their key
-
`z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn`.
+
Let's add Bob as a delegate using their DID
+
`did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn`.

```
-
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
+
$ rad id edit --title "Add Bob" --description "Add Bob as a delegate" --delegates did:key:z6MkedTZGJGqgQ2py2b8kGecfxdt2yRdHWF6JpaZC47fovFn --no-confirm
ok Identity proposal '06d9efa2a9aad06bfdf25a25690e1ec7db2c3c39' created 🌱
title: Add Bob
description: Add Bob as a delegate
modified radicle-cli/examples/rad-issue.md
@@ -22,20 +22,20 @@ others to work on. This is to ensure work is not duplicated.
Let's assign ourselves to this one.

```
-
$ rad assign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
$ rad assign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
```

It will now show in the list of issues assigned to us.

```
$ rad issue list --assigned
-
2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered" z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
2b4650e3c66d568132034de0d02871a2fbf9c5b5 "flux capacitor underpowered" did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
```

Note: this can always be undone with the `unassign` subcommand.

```
-
$ rad unassign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
$ rad unassign 2b4650e3c66d568132034de0d02871a2fbf9c5b5 did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
```

Great, now we have communicated to the world about our car's defect.
modified radicle-cli/src/commands/assign.rs
@@ -2,6 +2,7 @@ use std::ffi::OsString;
use std::str::FromStr;

use anyhow::anyhow;
+
use radicle::prelude::Did;

use crate::terminal as term;
use crate::terminal::args;
@@ -16,7 +17,7 @@ pub const HELP: args::Help = args::Help {
    usage: r#"
Usage

-
    rad assign <issue> <peer>
+
    rad assign <issue> <did>

Options

@@ -27,7 +28,7 @@ Options
#[derive(Debug)]
pub struct Options {
    pub id: issue::IssueId,
-
    pub peer: cob::ActorId,
+
    pub peer: Did,
}

impl args::Args for Options {
@@ -36,7 +37,7 @@ impl args::Args for Options {

        let mut parser = lexopt::Parser::from_args(args);
        let mut id: Option<issue::IssueId> = None;
-
        let mut peer: Option<cob::ActorId> = None;
+
        let mut peer: Option<Did> = None;

        while let Some(arg) = parser.next()? {
            match arg {
@@ -52,12 +53,7 @@ impl args::Args for Options {

                        id = Some(val);
                    } else if peer.is_none() {
-
                        let val = val.to_string_lossy();
-
                        let Ok(val) = cob::ActorId::from_str(&val) else {
-
                            return Err(anyhow!("invalid peer ID '{}'", val));
-
                        };
-

-
                        peer = Some(val);
+
                        peer = Some(term::args::did(val)?);
                    } else {
                        return Err(anyhow!(arg.unexpected()));
                    }
@@ -90,7 +86,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        cob::store::Error::NotFound(_, _) => anyhow!("issue not found '{}'", options.id),
        _ => err.into(),
    })?;
-
    issue.assign(vec![options.peer], &signer)?;
+
    issue.assign(vec![*options.peer], &signer)?;

    Ok(())
}
modified radicle-cli/src/commands/auth.rs
@@ -87,7 +87,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {

    term::success!(
        "Profile {} created.",
-
        term::format::highlight(profile.id().to_string())
+
        term::format::highlight(profile.did().to_string())
    );

    term::blank();
modified radicle-cli/src/commands/delegate.rs
@@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _};

use radicle::identity::Id;
-
use radicle_crypto::PublicKey;
+
use radicle::prelude::Did;

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
@@ -23,7 +23,7 @@ pub const HELP: Help = Help {
    usage: r#"
Usage

-
    rad delegate (add|remove) <public key> [--to <id>]
+
    rad delegate (add|remove) <did> [--to <id>]
    rad delegate list [<id>]

    The `add` and `remove` commands are limited to managing delegates
@@ -47,8 +47,8 @@ pub enum OperationName {

#[derive(Debug, Eq, PartialEq)]
pub enum Operation {
-
    Add { id: Option<Id>, key: PublicKey },
-
    Remove { id: Option<Id>, key: PublicKey },
+
    Add { id: Option<Id>, did: Did },
+
    Remove { id: Option<Id>, did: Did },
    List { id: Option<Id> },
}

@@ -64,7 +64,7 @@ impl Args for Options {
        let mut parser = lexopt::Parser::from_args(args);
        let mut id: Option<Id> = None;
        let mut op: Option<OperationName> = None;
-
        let mut key: Option<PublicKey> = None;
+
        let mut did: Option<Did> = None;

        while let Some(arg) = parser.next()? {
            match arg {
@@ -81,27 +81,21 @@ impl Args for Options {

                    unknown => anyhow::bail!("unknown operation '{}'", unknown),
                },
-
                Value(val) if op.is_some() => {
-
                    let val = val.to_string_lossy();
-

-
                    match op {
-
                        Some(OperationName::Add) | Some(OperationName::Remove) => {
-
                            if let Ok(val) = PublicKey::from_str(&val) {
-
                                key = Some(val);
-
                            } else {
-
                                return Err(anyhow!("invalid Public Key '{}'", val));
-
                            }
-
                        }
-
                        Some(OperationName::List) => {
-
                            if let Ok(val) = Id::from_str(&val) {
-
                                id = Some(val);
-
                            } else {
-
                                return Err(anyhow!("invalid Project ID '{}'", val));
-
                            }
+
                Value(val) if op.is_some() => match op {
+
                    Some(OperationName::Add) | Some(OperationName::Remove) => {
+
                        did = Some(term::args::did(&val)?);
+
                    }
+
                    Some(OperationName::List) => {
+
                        // TODO: create args::project_id function
+
                        let val = val.to_string_lossy();
+
                        if let Ok(val) = Id::from_str(&val) {
+
                            id = Some(val);
+
                        } else {
+
                            return Err(anyhow!("invalid Project ID '{}'", val));
                        }
-
                        None => continue,
                    }
-
                }
+
                    None => continue,
+
                },
                _ => return Err(anyhow!(arg.unexpected())),
            }
        }
@@ -110,11 +104,11 @@ impl Args for Options {
            OperationName::List => Operation::List { id },
            OperationName::Add => Operation::Add {
                id,
-
                key: key.ok_or_else(|| anyhow!("a delegate key must be provided"))?,
+
                did: did.ok_or_else(|| anyhow!("a delegate DID must be provided"))?,
            },
            OperationName::Remove => Operation::Remove {
                id,
-
                key: key.ok_or_else(|| anyhow!("a delegate key must be provided"))?,
+
                did: did.ok_or_else(|| anyhow!("a delegate DID must be provided"))?,
            },
        };

@@ -127,8 +121,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
    let storage = &profile.storage;

    match options.op {
-
        Operation::Add { id, key } => add::run(&profile, storage, get_id(id)?, key)?,
-
        Operation::Remove { id, key } => remove::run(&profile, storage, get_id(id)?, &key)?,
+
        Operation::Add { id, did } => add::run(&profile, storage, get_id(id)?, *did)?,
+
        Operation::Remove { id, did } => remove::run(&profile, storage, get_id(id)?, &did)?,
        Operation::List { id } => list::run(&profile, storage, get_id(id)?)?,
    }

modified radicle-cli/src/commands/id.rs
@@ -4,9 +4,9 @@ use anyhow::{anyhow, Context as _};
use radicle::cob::identity::{self, Proposal, ProposalId, Proposals, Revision, RevisionId};
use radicle::git::Oid;
use radicle::identity::Identity;
-
use radicle::prelude::Doc;
+
use radicle::prelude::{Did, Doc};
use radicle::storage::ReadStorage as _;
-
use radicle_crypto::{PublicKey, Verified};
+
use radicle_crypto::Verified;

use crate::terminal::args::{Args, Error, Help};
use crate::terminal::{self as term, Interactive};
@@ -19,7 +19,7 @@ pub const HELP: Help = Help {
Usage

    rad id (update|edit) [--title|-t] [--description|-d]
-
                         [--delegates <key>] [--threshold <num>]
+
                         [--delegates <did>] [--threshold <num>]
                         [--no-confirm]
    rad id list
    rad id (show|rebase) <id> [--rev <revision id>]
@@ -61,7 +61,7 @@ pub enum Operation {
    Edit {
        title: Option<String>,
        description: Option<String>,
-
        delegates: Vec<PublicKey>,
+
        delegates: Vec<Did>,
        threshold: Option<usize>,
    },
    Update {
@@ -69,7 +69,7 @@ pub enum Operation {
        rev: Option<RevisionId>,
        title: Option<String>,
        description: Option<String>,
-
        delegates: Vec<PublicKey>,
+
        delegates: Vec<Did>,
        threshold: Option<usize>,
    },
    Rebase {
@@ -121,7 +121,7 @@ impl Args for Options {
        let mut rev: Option<RevisionId> = None;
        let mut title: Option<String> = None;
        let mut description: Option<String> = None;
-
        let mut delegates: Vec<PublicKey> = Vec::new();
+
        let mut delegates: Vec<Did> = Vec::new();
        let mut threshold: Option<usize> = None;
        let mut interactive = Interactive::Yes;
        let mut show_revisions = false;
@@ -161,11 +161,8 @@ impl Args for Options {
                    );
                }
                Long("delegates") => {
-
                    let val = String::from(parser.value()?.to_string_lossy());
-
                    delegates.push(
-
                        PublicKey::from_str(&val)
-
                            .map_err(|_| anyhow!("invalid Public Key '{}'", val))?,
-
                    )
+
                    let did = term::args::did(&parser.value()?)?;
+
                    delegates.push(did);
                }
                Long("threshold") => {
                    threshold = Some(parser.value()?.to_string_lossy().parse()?);
@@ -277,9 +274,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            let proposed = {
                let mut proposed = previous.doc.clone();
                proposed.threshold = threshold.unwrap_or(proposed.threshold);
-
                proposed
-
                    .delegates
-
                    .extend(delegates.into_iter().map(|k| k.into()));
+
                proposed.delegates.extend(delegates);
                proposed
            };

@@ -320,9 +315,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            let proposed = {
                let mut proposed = revision.proposed.clone();
                proposed.threshold = threshold.unwrap_or(revision.proposed.threshold);
-
                proposed
-
                    .delegates
-
                    .extend(delegates.into_iter().map(|k| k.into()));
+
                proposed.delegates.extend(delegates);
                proposed
            };

modified radicle-cli/src/commands/issue.rs
@@ -3,6 +3,7 @@ use std::ffi::OsString;
use std::str::FromStr;

use anyhow::{anyhow, Context as _};
+
use radicle::prelude::Did;

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
@@ -22,7 +23,7 @@ Usage

    rad issue
    rad issue delete <id>
-
    rad issue list [--assigned <key>]
+
    rad issue list [--assigned <did>]
    rad issue open [--title <title>] [--description <text>]
    rad issue react <id> [--emoji <char>]
    rad issue show <id>
@@ -38,7 +39,7 @@ Options
pub struct Metadata {
    title: String,
    labels: Vec<Tag>,
-
    assignees: Vec<cob::ActorId>,
+
    assignees: Vec<Did>,
}

#[derive(Default, Debug, PartialEq, Eq)]
@@ -57,7 +58,7 @@ pub enum OperationName {
pub enum Assigned {
    #[default]
    Me,
-
    Peer(cob::ActorId),
+
    Peer(Did),
}

#[derive(Debug, PartialEq, Eq)]
@@ -135,10 +136,7 @@ impl Args for Options {
                }
                Long("assigned") | Short('a') if assigned.is_none() => {
                    if let Ok(val) = parser.value() {
-
                        let val = val.to_string_lossy();
-
                        let Ok(peer) = cob::ActorId::from_str(&val) else {
-
                            return Err(anyhow!("invalid peer ID '{}'", val));
-
                        };
+
                        let peer = term::args::did(&val)?;
                        assigned = Some(Assigned::Peer(peer));
                    } else {
                        assigned = Some(Assigned::Me);
@@ -265,7 +263,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                    &meta.title,
                    description.trim(),
                    meta.labels.as_slice(),
-
                    meta.assignees.as_slice(),
+
                    meta.assignees
+
                        .into_iter()
+
                        .map(cob::ActorId::from)
+
                        .collect::<Vec<_>>()
+
                        .as_slice(),
                    &signer,
                )?;
            }
@@ -273,7 +275,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        Operation::List { assigned } => {
            let assignee = match assigned {
                Some(Assigned::Me) => Some(*profile.id()),
-
                Some(Assigned::Peer(id)) => Some(id),
+
                Some(Assigned::Peer(id)) => Some(id.into()),
                None => None,
            };

@@ -282,7 +284,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                let (id, issue, _) = result?;
                let assigned: Vec<_> = issue.assigned().collect();

-
                if Some(true) == assignee.map(|a| !assigned.contains(&&a)) {
+
                if Some(true) == assignee.map(|a| !assigned.contains(&Did::from(a))) {
                    continue;
                }

modified radicle-cli/src/commands/self.rs
@@ -77,8 +77,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
fn all(profile: &Profile) -> anyhow::Result<()> {
    let mut table = term::Table::default();

+
    let did = profile.did();
+
    table.push(["ID", &term::format::tertiary(did)]);
+

    let node_id = profile.id();
-
    table.push(["ID", &term::format::tertiary(node_id)]);
+
    table.push(["Node ID", &term::format::tertiary(node_id)]);

    let ssh_short = ssh::fmt::fingerprint(node_id);
    table.push(["Key (hash)", &term::format::tertiary(ssh_short)]);
modified radicle-cli/src/commands/unassign.rs
@@ -2,6 +2,7 @@ use std::ffi::OsString;
use std::str::FromStr;

use anyhow::anyhow;
+
use radicle::prelude::Did;

use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
@@ -27,7 +28,7 @@ Options
#[derive(Debug)]
pub struct Options {
    pub id: issue::IssueId,
-
    pub peer: cob::ActorId,
+
    pub peer: Did,
}

impl Args for Options {
@@ -36,7 +37,7 @@ impl Args for Options {

        let mut parser = lexopt::Parser::from_args(args);
        let mut id: Option<issue::IssueId> = None;
-
        let mut peer: Option<cob::ActorId> = None;
+
        let mut peer: Option<Did> = None;

        while let Some(arg) = parser.next()? {
            match arg {
@@ -52,12 +53,7 @@ impl Args for Options {

                        id = Some(val);
                    } else if peer.is_none() {
-
                        let val = val.to_string_lossy();
-
                        let Ok(val) = cob::ActorId::from_str(&val) else {
-
                            return Err(anyhow!("invalid peer ID '{}'", val));
-
                        };
-

-
                        peer = Some(val);
+
                        peer = Some(term::args::did(val)?);
                    } else {
                        return Err(anyhow!(arg.unexpected()));
                    }
@@ -90,7 +86,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        cob::store::Error::NotFound(_, _) => anyhow!("issue '{}' not found", options.id),
        _ => err.into(),
    })?;
-
    issue.unassign(vec![options.peer], &signer)?;
+
    issue.unassign(vec![*options.peer], &signer)?;

    Ok(())
}
modified radicle-cli/src/terminal/args.rs
@@ -74,7 +74,7 @@ pub fn finish(unparsed: Vec<OsString>) -> anyhow::Result<()> {
    Ok(())
}

-
pub fn did(val: OsString) -> anyhow::Result<Did> {
+
pub fn did(val: &OsString) -> anyhow::Result<Did> {
    let val = val.to_string_lossy();
    let Ok(peer) = Did::from_str(&val) else {
        if crypto::PublicKey::from_str(&val).is_ok() {
modified radicle/src/cob/issue.rs
@@ -16,6 +16,7 @@ use crate::cob::thread;
use crate::cob::thread::{CommentId, Thread};
use crate::cob::{store, ActorId, ObjectId, OpId, TypeName};
use crate::crypto::{PublicKey, Signer};
+
use crate::prelude::Did;
use crate::storage::git as storage;

/// Issue operation.
@@ -152,8 +153,8 @@ impl store::FromHistory for Issue {
}

impl Issue {
-
    pub fn assigned(&self) -> impl Iterator<Item = &ActorId> {
-
        self.assignees.iter()
+
    pub fn assigned(&self) -> impl Iterator<Item = Did> + '_ {
+
        self.assignees.iter().map(Did::from)
    }

    pub fn title(&self) -> &str {
@@ -516,21 +517,21 @@ mod test {

        let id = issue.id;
        let issue = issues.get(&id).unwrap().unwrap();
-
        let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
+
        let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();

        assert_eq!(1, assignees.len());
-
        assert!(assignees.contains(&assignee));
+
        assert!(assignees.contains(&Did::from(assignee)));

        let mut issue = issues.get_mut(&id).unwrap();
        issue.assign([assignee_two], &signer).unwrap();

        let id = issue.id;
        let issue = issues.get(&id).unwrap().unwrap();
-
        let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
+
        let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();

        assert_eq!(2, assignees.len());
-
        assert!(assignees.contains(&assignee));
-
        assert!(assignees.contains(&assignee_two));
+
        assert!(assignees.contains(&Did::from(assignee)));
+
        assert!(assignees.contains(&Did::from(assignee_two)));
    }

    #[test]
@@ -556,11 +557,11 @@ mod test {

        let id = issue.id;
        let issue = issues.get(&id).unwrap().unwrap();
-
        let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
+
        let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();

        assert_eq!(2, assignees.len());
-
        assert!(assignees.contains(&assignee));
-
        assert!(assignees.contains(&assignee_two));
+
        assert!(assignees.contains(&Did::from(assignee)));
+
        assert!(assignees.contains(&Did::from(assignee_two)));
    }

    #[test]
@@ -641,10 +642,10 @@ mod test {

        let id = issue.id;
        let issue = issues.get(&id).unwrap().unwrap();
-
        let assignees: Vec<_> = issue.assigned().cloned().collect::<Vec<_>>();
+
        let assignees: Vec<_> = issue.assigned().collect::<Vec<_>>();

        assert_eq!(1, assignees.len());
-
        assert!(assignees.contains(&assignee_two));
+
        assert!(assignees.contains(&Did::from(assignee_two)));
    }

    #[test]
modified radicle/src/profile.rs
@@ -19,6 +19,7 @@ use crate::crypto::ssh::agent::Agent;
use crate::crypto::ssh::{keystore, Keystore, Passphrase};
use crate::crypto::{PublicKey, Signer};
use crate::node;
+
use crate::prelude::Did;
use crate::storage::git::transport;
use crate::storage::git::Storage;

@@ -103,6 +104,10 @@ impl Profile {
        &self.public_key
    }

+
    pub fn did(&self) -> Did {
+
        Did::from(self.public_key)
+
    }
+

    pub fn signer(&self) -> Result<Box<dyn Signer>, Error> {
        if let Some(passphrase) = env::read_passphrase() {
            let signer = keystore::MemorySigner::load(&self.keystore, passphrase)?;