Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IotaDocument type #58

Merged
merged 25 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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