Skip to content

Commit

Permalink
Add IotaDocument type (#58)
Browse files Browse the repository at this point in the history
* Don't serialize empty signatures

* Resolve key as fragment or full DID

* Enforce inclusion of signature verification_method

* Add LdDocument trait

* Add Generic LdRead/LdWrite helpers

* Add IotaDocument type

* Move diff address helpers to IotaDocument

* Dont hash twice when generating auth chain address

* Test IotaDID, not deprecated mod
  • Loading branch information
l1h3r authored Oct 27, 2020
1 parent 43271d4 commit 2b63414
Show file tree
Hide file tree
Showing 31 changed files with 559 additions and 349 deletions.
4 changes: 4 additions & 0 deletions identity_core/src/common/convert/as_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ pub trait FromJson: for<'de> Deserialize<'de> + Sized {
fn from_json_slice(json: &(impl AsRef<[u8]> + ?Sized)) -> Result<Self> {
serde_json::from_slice(json.as_ref()).map_err(Error::DecodeJSON)
}

fn from_json_value(json: serde_json::Value) -> Result<Self> {
serde_json::from_value(json).map_err(Error::DecodeJSON)
}
}

impl<T> FromJson for T where T: for<'de> Deserialize<'de> + Sized {}
Expand Down
6 changes: 6 additions & 0 deletions identity_core/src/did/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ impl DID {
})
}

pub fn matches_base(&self, base: &Self) -> bool {
self.method_name == base.method_name
&& self.id_segments == base.id_segments
&& self.path_segments == base.path_segments
}

pub fn parse<T>(input: T) -> crate::Result<Self>
where
T: ToString,
Expand Down
5 changes: 4 additions & 1 deletion identity_core/src/did/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,10 @@ impl DIDDocument {
where
T: HasId<Id = DID>,
{
matches!(method.id().fragment.as_deref(), Some(fragment) if key == fragment)
let target: Option<&str> = key.normalize();
let current: Option<&str> = method.id().fragment.as_deref();

matches!((current, target), (Some(current), Some(target)) if target == current)
}

fn key_iter(&self, relation: KeyRelation) -> Iter<Authentication> {
Expand Down
16 changes: 16 additions & 0 deletions identity_core/src/key/key_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ pub enum KeyIndex<'i> {
Ident(&'i str),
}

impl<'i> KeyIndex<'i> {
pub fn normalize(&self) -> Option<&str> {
match self {
Self::Ident(ident) if ident.starts_with("did:") => {
if let Some(index) = ident.rfind('#') {
Some(&ident[index + 1..])
} else {
None
}
}
Self::Ident(ident) => Some(ident.trim_start_matches('#')),
Self::Index(_) => None,
}
}
}

impl<'i> From<&'i str> for KeyIndex<'i> {
fn from(other: &'i str) -> Self {
Self::Ident(other)
Expand Down
18 changes: 9 additions & 9 deletions identity_core/src/resolver/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,16 @@ fn dereference_primary(document: Document, mut did: DID) -> Result<Option<Primar

fn dereference_document(document: Document, fragment: &str) -> Result<Option<SecondaryResource>> {
macro_rules! extract {
($base:expr, $target:expr, $iter:expr) => {
for object in $iter {
let did: DID = DID::join_relative($base, object.id())?;
($base:expr, $target:expr, $iter:expr) => {
for object in $iter {
let did: DID = DID::join_relative($base, object.id())?;

if matches!(did.fragment.as_deref(), Some(fragment) if fragment == $target) {
return Ok(Some(object.into()));
}
}
};
}
if matches!(did.fragment.as_deref(), Some(fragment) if fragment == $target) {
return Ok(Some(object.into()));
}
}
};
}

extract!(&document.id, fragment, document.public_keys);
extract!(&document.id, fragment, document.verification);
Expand Down
3 changes: 2 additions & 1 deletion identity_iota/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ serde_json = { version = "1.0", features = ["preserve_order"] }
thiserror = { version = "1.0", default-features = false }

identity_core = { path = "../identity_core" }
identity_crypto = { git = "https://github.com/iotaledger/identity.rs", branch = "feat/identity-signature-suites" }
identity_crypto = { path = "../identity_crypto" }
identity_proof = { path = "../identity_proof" }

[dev-dependencies]
smol-potat = { version = "0.3.3" }
Expand Down
6 changes: 3 additions & 3 deletions identity_iota/examples/mem_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ use identity_core::{
dereference, resolve, Dereference, DocumentMetadata, InputMetadata, MetaDocument, Resolution, ResolverMethod,
},
};
use identity_crypto::{Ed25519, KeyGen as _, KeyPair};
use identity_iota::error::Result;
use identity_crypto::KeyPair;
use identity_iota::{did::IotaDocument, error::Result};
use multihash::{Blake2b256, MultihashGeneric};

#[smol_potat::main]
async fn main() -> Result<()> {
let mut resolver: MemResolver = MemResolver::new();

let keypair: KeyPair = Ed25519::generate(&Ed25519, Default::default())?;
let keypair: KeyPair = IotaDocument::generate_ed25519_keypair();
let public: String = bs58::encode(keypair.public()).into_string();

let ident: MultihashGeneric<_> = Blake2b256::digest(public.as_bytes());
Expand Down
27 changes: 18 additions & 9 deletions identity_iota/examples/publish.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Publish new did document and read it from the tangle
//! cargo run --example publish
use identity_crypto::{Ed25519, KeyGen};
use identity_core::key::PublicKey;
use identity_crypto::KeyPair;
use identity_iota::{
did::TangleDocument as _,
did::{IotaDID, IotaDocument},
error::Result,
helpers::create_document,
helpers::create_ed25519_key,
io::TangleWriter,
network::{Network, NodeList},
};
Expand All @@ -24,16 +25,24 @@ async fn main() -> Result<()> {
let tangle_writer = TangleWriter::new(&nodelist)?;

// Create keypair
let keypair = Ed25519::generate(&Ed25519, Default::default())?;
let keypair: KeyPair = IotaDocument::generate_ed25519_keypair();

// Create, sign and publish DID document to the Tangle
let mut did_document = create_document(keypair.public().as_ref())?;
// Create DID and authentication method
let did: IotaDID = IotaDID::new(keypair.public().as_ref())?;
let key: PublicKey = create_ed25519_key(&did, keypair.public().as_ref())?;

did_document.sign_unchecked(keypair.secret())?;
// Create a minimal DID document from the DID and authentication method
let mut document: IotaDocument = IotaDocument::new(did, key)?;

println!("DID: {}", did_document.did());
// Sign the document with the authentication method secret
document.sign(keypair.secret())?;

let tail_transaction = tangle_writer.write_json(did_document.did(), &did_document).await?;
// Ensure the document proof is valid
assert!(document.verify().is_ok());

println!("DID: {}", document.did());

let tail_transaction = tangle_writer.write_json(document.did(), &document).await?;

println!(
"DID document published: https://thetangle.org/transaction/{}",
Expand Down
81 changes: 35 additions & 46 deletions identity_iota/examples/publish_read.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//! Publish new did document and read it from the tangle
//! cargo run --example publish_read
use anyhow::Result;
use identity_crypto::{Ed25519, KeyGen, KeyGenerator};
use identity_core::key::PublicKey;
use identity_crypto::KeyPair;
use identity_iota::{
core::{did::DIDDocument, diff::Diff, key::KeyRelation},
did::{DIDDiff, DIDProof, TangleDocument as _},
error::Error,
helpers::create_document,
did::{IotaDID, IotaDocument},
error::Result,
helpers::create_ed25519_key,
io::{TangleReader, TangleWriter},
network::{Network, NodeList},
};
Expand All @@ -26,76 +25,66 @@ async fn main() -> Result<()> {
let tangle_writer = TangleWriter::new(&nodelist)?;

// Create keypair
let keypair = Ed25519::generate(&Ed25519, KeyGenerator::default())?;
let keypair: KeyPair = IotaDocument::generate_ed25519_keypair();

// Create, sign and publish DID document to the Tangle
let mut did_document = create_document(keypair.public().as_ref())?;
// Create DID and authentication method
let did: IotaDID = IotaDID::new(keypair.public().as_ref())?;
let key: PublicKey = create_ed25519_key(&did, keypair.public().as_ref())?;

did_document.sign_unchecked(keypair.secret())?;
// Create a minimal DID document from the DID and authentication method
let mut document: IotaDocument = IotaDocument::new(did, key)?;

println!("DID: {}", did_document.did());
// Sign the document with the authentication method secret
document.sign(keypair.secret())?;

let tail_transaction = tangle_writer.write_json(did_document.did(), &did_document).await?;
// Ensure the document proof is valid
assert!(document.verify().is_ok());

println!("DID: {}", document.did());

let tail_transaction = tangle_writer.write_json(document.did(), &document).await?;

println!(
"DID document published: https://thetangle.org/transaction/{}",
tail_transaction.as_i8_slice().trytes().expect("Couldn't get Trytes")
);

// Create, sign and publish diff to the Tangle
let signed_diff = create_diff(did_document.clone(), &keypair).await?;
let auth_key = did_document
.resolve_key(0, KeyRelation::Authentication)
.ok_or(Error::InvalidAuthenticationKey)?;
let tail_transaction = tangle_writer
.publish_diff_json(&did_document.did(), auth_key.key_data(), &signed_diff)
.await?;
// Update document and publish diff to the Tangle
let mut update = document.clone();

update.set_metadata("new-value", true);

let signed_diff = document.diff(update.into(), keypair.secret())?;

// Ensure the diff proof is valid
assert!(document.verify_diff(&signed_diff).is_ok());

let tail_transaction = tangle_writer.publish_json(&document.did(), &signed_diff).await?;

println!(
"DID document DIDDiff published: https://thetangle.org/transaction/{}",
tail_transaction.as_i8_slice().trytes().expect("Couldn't get Trytes")
);

// Get document and diff from the tangle and validate the signatures
let did = did_document.did();
let did = document.did();
let tangle_reader = TangleReader::new(&nodelist)?;

let received_messages = tangle_reader.fetch(&did).await?;
println!("{:?}", received_messages);

let docs = TangleReader::extract_documents(&did, &received_messages)?;
let mut docs = TangleReader::extract_documents(&did, &received_messages)?;
println!("extracted docs: {:?}", docs);

let diffs = TangleReader::extract_diffs(&did, &received_messages)?;
println!("extracted diffs: {:?}", diffs);

let sig = docs[0].data.verify_unchecked().is_ok();
let doc = IotaDocument::try_from_document(docs.remove(0).data)?;
let sig = doc.verify().is_ok();
println!("Document has valid signature: {}", sig);

let sig = docs[0].data.verify_diff_unchecked(&diffs[0].data).is_ok();
let sig = doc.verify_diff(&diffs[0].data).is_ok();
println!("Diff has valid signature: {}", sig);

Ok(())
}

async fn create_diff(did_document: DIDDocument, keypair: &identity_crypto::KeyPair) -> crate::Result<DIDDiff> {
// updated doc and publish diff
let mut new = did_document.clone();
new.update_time();

// diff the two docs.
let diff = did_document.diff(&new)?;

let key_did = new
.resolve_key(0, KeyRelation::Authentication)
.ok_or(Error::InvalidAuthenticationKey)?;
let mut diddiff = DIDDiff {
id: new.did().clone(),
diff: serde_json::to_string(&diff)?,
proof: DIDProof::new(key_did.id().clone())?,
};

did_document.sign_diff_unchecked(&mut diddiff, keypair.secret())?;

Ok(diddiff)
}
27 changes: 20 additions & 7 deletions identity_iota/src/did/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use identity_core::{
did::DID,
utils::{decode_b58, encode_b58},
};
use identity_crypto::KeyPair;
use identity_proof::signature::jcsed25519signature2020;
use iota::transaction::bundled::Address;
use multihash::Blake2b256;

Expand All @@ -24,6 +26,14 @@ pub struct IotaDID(DID);

impl IotaDID {
pub const METHOD: &'static str = "iota";
pub const NETWORK: &'static str = "main";

pub fn generate_ed25519() -> Result<(Self, KeyPair)> {
let key: KeyPair = jcsed25519signature2020::new_keypair();
let did: Self = Self::new(key.public().as_ref())?;

Ok((did, key))
}

pub fn try_from_did(did: DID) -> Result<Self> {
if did.method_name != Self::METHOD {
Expand Down Expand Up @@ -85,7 +95,7 @@ impl IotaDID {

pub fn network(&self) -> &str {
match &*self.id_segments {
[_] => "main",
[_] => Self::NETWORK,
[network, _] => &*network,
[network, _, _] => &*network,
_ => unreachable!("IotaDID::network called for invalid DID"),
Expand All @@ -112,7 +122,7 @@ impl IotaDID {

pub fn normalize(&mut self) {
match &*self.id_segments {
[_] => self.id_segments.insert(0, "main".into()),
[_] => self.id_segments.insert(0, Self::NETWORK.into()),
[_, _] | [_, _, _] => {}
_ => unreachable!("IotaDID::normalize called for invalid DID"),
}
Expand Down Expand Up @@ -140,6 +150,12 @@ impl IotaDID {
}
}

impl PartialEq<DID> for IotaDID {
fn eq(&self, other: &DID) -> bool {
self.0.eq(other)
}
}

impl Display for IotaDID {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", self.0)
Expand All @@ -160,6 +176,7 @@ impl Deref for IotaDID {
}
}

// TODO: Remove this
impl DerefMut for IotaDID {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
Expand Down Expand Up @@ -189,10 +206,8 @@ impl FromStr for IotaDID {
}

pub mod deprecated {
use bs58::encode;
use identity_core::did::DID;
use iota::transaction::bundled::Address;
use multihash::Blake2b256;

use crate::{
error::{Error, Result},
Expand All @@ -208,9 +223,7 @@ pub mod deprecated {

/// Creates an 81 Trytes IOTA address from the DID
pub fn create_address(did: &DID) -> Result<Address> {
let digest: &[u8] = &Blake2b256::digest(method_id(did)?.as_bytes());
let encoded: String = encode(digest).into_string();
let mut trytes: String = utf8_to_trytes(&encoded);
let mut trytes: String = utf8_to_trytes(method_id(did)?);

trytes.truncate(iota_constants::HASH_TRYTES_SIZE);

Expand Down
21 changes: 15 additions & 6 deletions identity_iota/src/did/diff.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use identity_core::did::DID;
use identity_core::did::{DiffDIDDocument, DID};
use identity_proof::{HasProof, LdSignature};
use serde::{Deserialize, Serialize};

use crate::did::DIDProof;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct DIDDiff {
pub id: DID,
pub diff: String, // TODO: Replace with DiffDIDDocument
pub proof: DIDProof,
pub diff: DiffDIDDocument,
pub proof: LdSignature,
}

impl HasProof for DIDDiff {
fn proof(&self) -> &LdSignature {
&self.proof
}

fn proof_mut(&mut self) -> &mut LdSignature {
&mut self.proof
}
}
Loading

0 comments on commit 2b63414

Please sign in to comment.