Skip to content

Commit

Permalink
Create, Publish, and Resolve did:dht (etro-js#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
Diane Huxley authored Jun 27, 2024
1 parent cfe6f16 commit 8c59510
Show file tree
Hide file tree
Showing 20 changed files with 504 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use web5::apid::{crypto::jwk::Jwk, dids::methods::did_dht::DidDht as InnerDidDht
pub struct DidDht(pub InnerDidDht);

pub fn did_dht_resolve(uri: &str) -> Result<Arc<ResolutionResult>> {
let resolution_result = InnerDidDht::resolve(uri).map_err(|e| Arc::new(e.into()))?;
let resolution_result = InnerDidDht::resolve(uri);
Ok(Arc::new(ResolutionResult(resolution_result)))
}

Expand Down
3 changes: 2 additions & 1 deletion crates/web5/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jsonschema = { version = "0.18.0", default-features = false }
k256 = { version = "0.13.3", features = ["ecdsa", "jwk"] }
rand = { workspace = true }
regex = "1.10.4"
reqwest = { version = "0.12.4", features = ["json"] }
reqwest = { version = "0.12.4", features = ["json", "blocking"] }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
Expand All @@ -33,6 +33,7 @@ ssi-jwk = "0.1.2"
thiserror = { workspace = true }
url = "2.5.0"
uuid = { version = "1.8.0", features = ["v4"] }
zbase32 = "0.1.2"

[dev-dependencies]
chrono = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::{
time::{SystemTime, SystemTimeError, UNIX_EPOCH},
};

use crate::crypto::CryptoError;
use crate::keys::key::{KeyError, PublicKey};
use crate::apid::dsa::{ed25519::Ed25519Verifier, DsaError, Verifier};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

/// Minimum size of a bep44 encoded message
Expand All @@ -21,15 +20,13 @@ pub enum Bep44EncodingError {
#[error(transparent)]
SystemTimeError(#[from] SystemTimeError),
#[error(transparent)]
CryptoError(#[from] CryptoError),
DsaError(#[from] DsaError),
#[error("Failure creating DID: {0}")]
BigEndianError(String),
#[error(
"Message must have size between {MIN_MESSAGE_LEN} and {MAX_MESSAGE_LEN} but got size {0}"
)]
SizeError(usize),
#[error(transparent)]
SignatureError(#[from] KeyError),
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -70,7 +67,7 @@ fn decode_seq(seq_bytes: &[u8]) -> Result<u64, Bep44EncodingError> {
/// Represents a BEP44 message, which is used for storing and retrieving data
/// in the Mainline DHT network.
///
/// A BEP44 message is used primarily in the context of the DID DHT method
/// A BEP44 message is used in the context of the DID DHT method
/// for publishing and resolving DID documents in the DHT network. This type
/// encapsulates the data structure required for such operations in accordance
/// with BEP44.
Expand All @@ -79,7 +76,7 @@ fn decode_seq(seq_bytes: &[u8]) -> Result<u64, Bep44EncodingError> {
impl Bep44Message {
pub fn new<F>(message: &[u8], sign: F) -> Result<Self, Bep44EncodingError>
where
F: Fn(Vec<u8>) -> Result<Vec<u8>, KeyError>,
F: Fn(Vec<u8>) -> Result<Vec<u8>, DsaError>,
{
let message_len = message.len();
if message_len > MAX_V_LEN {
Expand Down Expand Up @@ -126,45 +123,47 @@ impl Bep44Message {
})
}

pub fn verify(&self, public_key: &dyn PublicKey) -> Result<(), Bep44EncodingError> {
pub fn verify(&self, verifier: &Ed25519Verifier) -> Result<(), Bep44EncodingError> {
let signable = signable(self.seq, &self.v);
public_key.verify(&signable, &self.sig)?;
verifier.verify(&signable, &self.sig)?;

Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::crypto::{ed25519::Ed25519, CurveOperations};
use crate::keys::key::PrivateKey;
use crate::apid::dsa::{
ed25519::{Ed25519Generator, Ed25519Signer},
Signer,
};

use super::*;

#[test]
fn test_new_verify() {
let message = "Hello World".as_bytes();

let private_key = Ed25519::generate().expect("Failed to generate Ed25519 key");
let private_jwk = Ed25519Generator::generate();
let signer = Ed25519Signer::new(private_jwk.clone());

let result_bep44_message =
Bep44Message::new(message, |payload| -> Result<Vec<u8>, KeyError> {
private_key.sign(&payload)
Bep44Message::new(message, |payload| -> Result<Vec<u8>, DsaError> {
signer.sign(&payload)
});
assert!(result_bep44_message.is_ok());

let bep44_message = result_bep44_message.unwrap();
let public_key = private_key
.to_public()
.expect("Failed to convert private key to public key");
let verify_result = bep44_message.verify(public_key.as_ref());

let verifier = Ed25519Verifier::new(private_jwk);
let verify_result = bep44_message.verify(&verifier);
assert!(verify_result.is_ok());
}

#[test]
fn test_new_message_too_big() {
let too_big = vec![0; 10_000];
let error = Bep44Message::new(&too_big, |_| -> Result<Vec<u8>, KeyError> { Ok(vec![]) })
let error = Bep44Message::new(&too_big, |_| -> Result<Vec<u8>, DsaError> { Ok(vec![]) })
.expect_err("Should have returned error for malformed signature");

match error {
Expand All @@ -177,13 +176,13 @@ mod tests {
fn test_new_sign_fails() {
let message = "Hello World".as_bytes();

let error = Bep44Message::new(message, |_| -> Result<Vec<u8>, KeyError> {
Err(KeyError::CurveNotFound)
let error = Bep44Message::new(message, |_| -> Result<Vec<u8>, DsaError> {
Err(DsaError::UnsupportedCurve)
})
.expect_err("Should have returned error for malformed signature");

match error {
Bep44EncodingError::SignatureError(_) => {}
Bep44EncodingError::DsaError(_) => {}
_ => panic!(),
}
}
Expand All @@ -192,31 +191,31 @@ mod tests {
fn test_verify_malformed_sig() {
let message = "Hello World".as_bytes();

let private_key = Ed25519::generate().expect("Failed to generate Ed25519 key");
let private_jwk = Ed25519Generator::generate();
let signer = Ed25519Signer::new(private_jwk.clone());

let mut bep44_message =
Bep44Message::new(message, |payload| -> Result<Vec<u8>, KeyError> {
private_key.sign(&payload)
Bep44Message::new(message, |payload| -> Result<Vec<u8>, DsaError> {
signer.sign(&payload)
})
.unwrap();

// Overwrite sig with malformed signature
bep44_message.sig = vec![0, 1, 2, 3];
let public_key = private_key
.to_public()
.expect("Failed to convert private key to public key");
let verify_result = bep44_message.verify(public_key.as_ref());
let verifier = Ed25519Verifier::new(private_jwk);
let verify_result = bep44_message.verify(&verifier);
assert!(verify_result.is_err());
}

#[test]
fn test_encoded_decode() {
let message = "Hello World".as_bytes();

let private_key = Ed25519::generate().expect("Failed to generate Ed25519 key");
let private_jwk = Ed25519Generator::generate();
let signer = Ed25519Signer::new(private_jwk);

let bep44_message = Bep44Message::new(message, |payload| -> Result<Vec<u8>, KeyError> {
private_key.sign(&payload)
let bep44_message = Bep44Message::new(message, |payload| -> Result<Vec<u8>, DsaError> {
signer.sign(&payload)
})
.unwrap();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl AlsoKnownAs {

#[cfg(test)]
mod test {
use crate::dids::methods::dht::document_packet::controller::Controller;
use crate::apid::dids::methods::did_dht::document_packet::controller::Controller;

use super::*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Controller {

#[cfg(test)]
mod tests {
use crate::dids::methods::dht::document_packet::also_known_as::AlsoKnownAs;
use crate::apid::dids::methods::did_dht::document_packet::also_known_as::AlsoKnownAs;

use super::*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::crypto::CryptoError;
use crate::jwk::JwkError;
use crate::apid::crypto::jwk::JwkError;
use crate::apid::dids::data_model::document::Document;
use crate::apid::dids::data_model::{service::Service, verification_method::VerificationMethod};
use crate::apid::dsa::DsaError;
use simple_dns::SimpleDnsError;
use std::collections::HashMap;

use simple_dns::{Packet, ResourceRecord};

use crate::dids::document::{Document, Service, VerificationMethod};

use self::{also_known_as::AlsoKnownAs, controller::Controller, root_record::RootRecord};

mod also_known_as;
Expand Down Expand Up @@ -47,7 +47,7 @@ fn reconstitute_verification_relationship(
#[derive(thiserror::Error, Debug)]
pub enum DocumentPacketError {
#[error(transparent)]
CryptoError(#[from] CryptoError),
DsaError(#[from] DsaError),
#[error("DID Document is malformed for did:dht: {0}")]
DocumentError(String),
#[error(transparent)]
Expand All @@ -64,8 +64,16 @@ pub enum DocumentPacketError {
RDataError(String),
}

impl<'a> TryFrom<Packet<'a>> for Document {
type Error = DocumentPacketError;

fn try_from(packet: Packet) -> Result<Self, Self::Error> {
Document::from_packet(&packet)
}
}

impl Document {
pub fn to_dns_packet(&self) -> Result<Packet, DocumentPacketError> {
pub fn to_packet(&self) -> Result<Packet, DocumentPacketError> {
// 0. Init root_record and empty answers array
let did_uri = &self.id;
let did_id = did_uri
Expand Down Expand Up @@ -104,22 +112,19 @@ impl Document {

// 1.2 Add all other verification methods
let mut idx = 1;
self.verification_method
.iter()
.try_for_each(|vm| -> Result<(), DocumentPacketError> {
// skip identity key because we already added it
if vm.id == identity_key_id {
return Ok(());
}
for vm in self.verification_method.iter() {
// skip identity key because we already added it
if vm.id == identity_key_id {
continue;
}

let vm_record = vm.to_resource_record(did_uri, idx)?.to_owned();
answers.push(vm_record);
root_record.vm.push(idx);
vm_id_to_idx.insert(vm.id.clone(), idx);
let vm_record = vm.to_resource_record(did_uri, idx)?.to_owned();
answers.push(vm_record);
root_record.vm.push(idx);
vm_id_to_idx.insert(vm.id.clone(), idx);

idx += 1;
Ok(())
})?;
idx += 1;
}

// 2. Add verification relationships to root_record
// 2.1 Add assertion methods to root_record
Expand Down Expand Up @@ -201,7 +206,7 @@ impl Document {
Ok(packet)
}

pub fn from_dns_packet(packet: Packet) -> Result<Document, DocumentPacketError> {
pub fn from_packet(packet: &Packet) -> Result<Document, DocumentPacketError> {
let answers = &packet.answers;

// 0. Get root record
Expand Down Expand Up @@ -323,45 +328,27 @@ impl Document {

#[cfg(test)]
mod tests {
use std::sync::Arc;

use crate::crypto::Curve;
use crate::keys::key_manager::local_key_manager::LocalKeyManager;
use crate::keys::key_manager::KeyManager;
use crate::apid::dsa::ed25519::{self, Ed25519Generator};

use super::*;

fn generate_identity_key_vm(did_uri: &str) -> VerificationMethod {
let key_manager = Arc::new(LocalKeyManager::new());
let key_alias = key_manager
.generate_private_key(Curve::Ed25519, Some("0".to_string()))
.unwrap();
let public_key = key_manager.get_public_key(&key_alias).unwrap();
let public_jwk = public_key.jwk().unwrap();

VerificationMethod {
id: format!("{}#0", did_uri),
r#type: "JsonWebKey".to_string(),
controller: did_uri.to_string(),
public_key_jwk: public_jwk.clone(),
public_key_jwk: ed25519::to_public_jwk(&Ed25519Generator::generate()),
}
}

fn generate_additional_vm(did_uri: &str) -> VerificationMethod {
let key_manager = Arc::new(LocalKeyManager::new());
let key_alias = key_manager
.generate_private_key(Curve::Ed25519, Some("0".to_string()))
.unwrap();
let public_key = key_manager.get_public_key(&key_alias).unwrap();
let public_jwk = public_key.jwk().unwrap();

let public_jwk = ed25519::to_public_jwk(&Ed25519Generator::generate());
let thumbprint = public_jwk.compute_thumbprint().unwrap();

VerificationMethod {
id: format!("{}#{}", did_uri, thumbprint),
r#type: "JsonWebKey".to_string(),
controller: did_uri.to_string(),
public_key_jwk: public_jwk.clone(),
public_key_jwk: public_jwk,
}
}

Expand Down Expand Up @@ -407,11 +394,12 @@ mod tests {
};

let packet = document
.to_dns_packet()
.to_packet()
.expect("expected to convert document to packet");

let document2 =
Document::from_dns_packet(packet).expect("expected to convert back from packet");
let document2: Document = packet
.try_into()
.expect("expected to convert back from packet");

assert_eq!(document, document2);
}
Expand All @@ -428,11 +416,12 @@ mod tests {
};

let packet = document
.to_dns_packet()
.to_packet()
.expect("expected to convert document to packet");

let document2 =
Document::from_dns_packet(packet).expect("expected to convert back from packet");
let document2: Document = packet
.try_into()
.expect("expected to convert back from packet");

assert_eq!(document, document2);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::collections::HashMap;

use crate::dids::document::Service;

use simple_dns::{
rdata::{RData, TXT},
Name, ResourceRecord,
};

use url::Url;

use crate::apid::dids::data_model::service::Service;

use super::{
rdata_encoder::{get_rdata_txt_value, record_rdata_to_hash_map},
DocumentPacketError, DEFAULT_TTL,
Expand Down
Loading

0 comments on commit 8c59510

Please sign in to comment.