Skip to content

Commit

Permalink
Fix Sidetree Client
Browse files Browse the repository at this point in the history
  • Loading branch information
tzskp1 committed Jul 25, 2024
1 parent bb65964 commit f4cf607
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 76 deletions.
11 changes: 5 additions & 6 deletions src/did/did_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use http::StatusCode;
use super::sidetree::{
client::SidetreeHttpClient,
payload::{
did_create_payload, DidDocument, DidReplacePayload, DidResolutionResponse, ToPublicKey,
did_create_payload, DidDocument, DidPatchDocument, DidResolutionResponse, ToPublicKey,
},
};
use crate::keyring::{
Expand Down Expand Up @@ -120,11 +120,10 @@ impl<C: SidetreeHttpClient + Send + Sync> DidRepository for DidRepositoryImpl<C>
vec!["auth".to_string(), "general".to_string()],
)
.unwrap();
let update: Jwk = keyring.update.get_public_key().try_into()?;
let recovery: Jwk = keyring.recovery.get_public_key().try_into()?;
let document =
DidReplacePayload { public_keys: vec![sign, enc], service_endpoints: vec![] };
let payload = did_create_payload(document, &update, &recovery)?;
let update = keyring.update.get_public_key();
let recovery = keyring.recovery.get_public_key();
let document = DidPatchDocument { public_keys: vec![sign, enc], service_endpoints: vec![] };
let payload = did_create_payload(document, update, recovery)?;

let response = self
.client
Expand Down
2 changes: 1 addition & 1 deletion src/did/sidetree/multihash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn hash(message: &[u8]) -> Vec<u8> {
}

pub fn double_hash_encode(message: &[u8]) -> String {
let mes = hash(message);
let mes = Sha256::digest(message).to_vec();
let mes = hash(&mes);
BASE64URL_NOPAD.encode(&mes)
}
Expand Down
190 changes: 125 additions & 65 deletions src/did/sidetree/payload.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use core::convert::TryInto;

use data_encoding::BASE64URL_NOPAD;
use k256::ecdsa::{signature::Signer, Signature, SigningKey};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{did::sidetree::multihash, keyring::jwk::Jwk};
use crate::{
did::sidetree::multihash, keyring::jwk::Jwk, verifiable_credentials::jws::JwsEncodeError,
};

// TODO: Migrate Sidetree Version

#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceEndpoint {
Expand Down Expand Up @@ -94,46 +99,39 @@ where
}
}

// ACTION: replace
#[derive(Debug, Serialize, Deserialize)]
pub struct DidReplacePayload {
pub struct DidPatchDocument {
#[serde(rename = "public_keys")]
pub public_keys: Vec<PublicKeyPayload>,

#[serde(rename = "service_endpoints")]
pub service_endpoints: Vec<String>,
pub service_endpoints: Vec<ServiceEndpoint>,
}

#[derive(Debug, Serialize, Deserialize)]
struct DidReplaceAction {
action: String, // 'replace',
document: DidReplacePayload,
#[serde(tag = "action")]
pub enum DidAction {
#[serde(rename = "replace")]
Replace { document: DidPatchDocument },
#[serde(rename = "add-public-keys")]
AddPublicKeys {
#[serde(rename = "public_keys")]
public_keys: Vec<PublicKeyPayload>,
},
}

#[derive(Serialize, Deserialize, Debug)]
struct DidReplaceDeltaObject {
patches: Vec<DidReplaceAction>,
struct DidDeltaObject {
patches: Vec<DidAction>,
update_commitment: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct DidReplaceSuffixObject {
struct DidSuffixObject {
delta_hash: String,
recovery_commitment: String,
}

// ACTION: ietf-json-patch
#[allow(dead_code)]
struct DidIetfJsonPatchAction {
action: String, /* 'replace',
* patches: Vec<> */
}

#[allow(dead_code)]
struct DidResolutionRequest {
did: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct MethodMetadata {
#[serde(rename = "published")]
Expand All @@ -158,32 +156,21 @@ pub struct DidResolutionResponse {
pub method_metadata: MethodMetadata,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct CommitmentKeys {
#[serde(rename = "recovery")]
pub recovery: Jwk,

#[serde(rename = "update")]
pub update: Jwk,
}

#[derive(Clone, Serialize, Deserialize)]
pub struct DidCreateRequest {
#[serde(rename = "publicKeys")]
pub public_keys: Vec<PublicKeyPayload>,

#[serde(rename = "commitmentKeys")]
pub commitment_keys: CommitmentKeys,

#[serde(rename = "serviceEndpoints")]
pub service_endpoints: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
struct DidCreatePayload {
r#type: String, // 'create',
delta: String,
suffix_data: String,
#[serde(tag = "type")]
enum DidPayload {
#[serde(rename = "create")]
Create { delta: String, suffix_data: String },
#[serde(rename = "update")]
Update {
delta: String,
// #[serde(rename = "revealValue")]
// reveal_value: String,
#[serde(rename = "did_suffix")]
did_suffix: String,
#[serde(rename = "signed_data")]
signed_data: String,
},
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -202,6 +189,8 @@ pub struct DidCreateResponse {
pub enum DidCreatePayloadError {
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
#[error("Failed to convert to JWK: {0}")]
Jwk(#[from] crate::keyring::jwk::K256ToJwkError),
}

#[inline]
Expand All @@ -212,31 +201,102 @@ where
Ok(serde_jcs::to_string(value)?.into_bytes())
}

#[inline]
fn commitment_scheme(value: &Jwk) -> Result<String, serde_json::Error> {
Ok(multihash::double_hash_encode(&canon(value)?))
}

pub fn did_create_payload(
replace_payload: DidReplacePayload,
update_key: &Jwk,
recovery_key: &Jwk,
replace_payload: DidPatchDocument,
update_key: k256::PublicKey,
recovery_key: k256::PublicKey,
) -> Result<String, DidCreatePayloadError> {
let update = canon(update_key)?;
let update_commitment = multihash::double_hash_encode(&update);
let recovery = canon(recovery_key)?;
let recovery_commitment = multihash::double_hash_encode(&recovery);
let patch = DidReplaceAction { action: "replace".to_string(), document: replace_payload };
let delta = DidReplaceDeltaObject { patches: vec![patch], update_commitment };
let update_commitment = commitment_scheme(&update_key.try_into()?)?;
let recovery_commitment = commitment_scheme(&recovery_key.try_into()?)?;
let patch = DidAction::Replace { document: replace_payload };
let delta = DidDeltaObject { patches: vec![patch], update_commitment };
let delta = canon(&delta)?;
let delta_hash = multihash::hash_encode(&delta);

let suffix = DidReplaceSuffixObject { delta_hash, recovery_commitment };
let suffix = DidSuffixObject { delta_hash, recovery_commitment };
let suffix = canon(&suffix)?;
let encoded_delta = BASE64URL_NOPAD.encode(&delta);
let encoded_suffix = BASE64URL_NOPAD.encode(&suffix);

let payload = DidCreatePayload {
r#type: "create".to_string(),
let payload = DidPayload::Create { delta: encoded_delta, suffix_data: encoded_suffix };

Ok(serde_jcs::to_string(&payload)?)
}

pub fn parse_did(did: &str) -> Option<(String, String)> {
let ret: Vec<&str> = did.splitn(3, ':').collect();
if ret.len() == 3 { Some((ret[1].to_string(), ret[2].to_string())) } else { None }
}

pub fn get_did_suffix(method_specific_id: &str) -> Option<String> {
let ret: Vec<&str> = method_specific_id.splitn(2, ':').collect();
if ret.len() == 2 || ret.len() == 1 { Some(ret[0].to_string()) } else { None }
}

fn sign(
delta_hash: String,
old_public_key: Jwk,
old_secret_key: &k256::SecretKey,
) -> Result<String, JwsEncodeError> {
// NOTE: header
let header = serde_json::json!({ "alg": "ES256K".to_string() });
let header = serde_jcs::to_string(&header)?;
let header = BASE64URL_NOPAD.encode(header.as_bytes());
// NOTE: payload
let object = serde_json::json!({"delta_hash": delta_hash, "update_key": old_public_key});
let payload = BASE64URL_NOPAD.encode(object.to_string().as_bytes());
// NOTE: message
let message = [header.clone(), payload.clone()].join(".");
let message: &[u8] = message.as_bytes();

// NOTE: signature
let signing_key: SigningKey = old_secret_key.into();
let signature: Signature = signing_key.try_sign(message)?;
let signature = BASE64URL_NOPAD.encode(&signature.to_vec());

Ok([header, payload, signature].join("."))
}

#[derive(Debug, Error)]
pub enum DidUpdatePayloadError {
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
#[error("Failed to convert to JWK: {0}")]
Jwk(#[from] crate::keyring::jwk::K256ToJwkError),
#[error("Failed to parse did")]
DidParse,
#[error("Failed to sign: {0}")]
Sign(#[from] JwsEncodeError),
}

// TODO: Not yet tested because sidetree is broken.
pub fn did_update_payload(
update_payload: Vec<DidAction>,
my_did: &str,
old_update: k256::PublicKey,
old_update_secret: &k256::SecretKey,
new_update: k256::PublicKey,
) -> Result<String, DidUpdatePayloadError> {
let old_update: Jwk = old_update.try_into()?;
let new_update = commitment_scheme(&new_update.try_into()?)?;
let delta = DidDeltaObject { patches: update_payload, update_commitment: new_update };
let delta = canon(&delta)?;
let delta_hash = multihash::hash_encode(&delta);
let encoded_delta = BASE64URL_NOPAD.encode(&delta);
let (_, suff) = parse_did(my_did).ok_or(DidUpdatePayloadError::DidParse)?;
let suff = get_did_suffix(&suff).ok_or(DidUpdatePayloadError::DidParse)?;

let payload = DidPayload::Update {
delta: encoded_delta,
suffix_data: encoded_suffix,
did_suffix: suff,
// reveal_value: multihash::hash_encode(&canon(&old_update)?),
signed_data: sign(delta_hash, old_update, old_update_secret)?,
};

Ok(serde_jcs::to_string(&payload)?)
}

Expand All @@ -255,11 +315,11 @@ pub mod tests {
.get_public_key()
.to_public_key("".to_string(), "key_id".to_string(), vec!["".to_string()])
.unwrap();
let update: Jwk = keyring.recovery.get_public_key().try_into().unwrap();
let recovery: Jwk = keyring.update.get_public_key().try_into().unwrap();
let update = keyring.recovery.get_public_key();
let recovery = keyring.update.get_public_key();

let document = DidReplacePayload { public_keys: vec![public], service_endpoints: vec![] };
let document = DidPatchDocument { public_keys: vec![public], service_endpoints: vec![] };

let _result = did_create_payload(document, &update, &recovery).unwrap();
let _result = did_create_payload(document, update, recovery).unwrap();
}
}
6 changes: 3 additions & 3 deletions src/verifiable_credentials/jws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use thiserror::Error;
// TODO: Design the interface to have an implementation with accelerators.

#[derive(Debug, Serialize, Deserialize)]
struct JWSHeader {
struct JwsHeader {
alg: String,
b64: bool,
crit: Vec<String>,
Expand Down Expand Up @@ -52,7 +52,7 @@ pub enum JwsDecodeError {

pub fn sign(object: &Value, secret_key: &k256::SecretKey) -> Result<String, JwsEncodeError> {
// NOTE: header
let header = JWSHeader { alg: "ES256K".to_string(), b64: false, crit: vec!["b64".to_string()] };
let header = JwsHeader { alg: "ES256K".to_string(), b64: false, crit: vec!["b64".to_string()] };
let header = serde_jcs::to_string(&header)?;
let header = BASE64URL_NOPAD.encode(header.as_bytes());
// NOTE: payload
Expand Down Expand Up @@ -87,7 +87,7 @@ pub fn verify(
// NOTE: header
let decoded = BASE64URL_NOPAD.decode(_header.as_bytes())?;
let decoded = String::from_utf8(decoded)?;
let header = serde_json::from_str::<JWSHeader>(&decoded)?;
let header = serde_json::from_str::<JwsHeader>(&decoded)?;

if header.alg != *"ES256K" {
return Err(JwsDecodeError::InvalidAlgorithm(header.alg));
Expand Down
2 changes: 1 addition & 1 deletion src/verifiable_credentials/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod credential_signer;
pub mod did_vc;
mod jws;
pub mod jws;
pub mod types;

0 comments on commit f4cf607

Please sign in to comment.