use crate::{
OwnedVid, Vid,
vid::{
VidError,
did::web::{DidDocument, resolve_document},
vid_to_did_document,
},
};
use base64ct::{Base64UrlUnpadded, Encoding};
use didwebvh_rs::{
DIDWebVHState,
affinidi_secrets_resolver::secrets::Secret,
log_entry::{LogEntry, LogEntryMethods, MetaData},
parameters::Parameters,
url::WebVHURL,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use url::Url;
pub(crate) const SCHEME: &str = "webvh";
#[derive(Debug, Serialize, Deserialize)]
pub struct WebvhMetadata {
pub webvh_meta_data: MetaData,
pub update_keys: Option<Vec<String>>,
}
pub async fn resolve(id: &str) -> Result<(Vid, serde_json::Value), VidError> {
let mut webvh = DIDWebVHState::default();
let (log_entry, meta_data) = webvh.resolve(id, None).await?;
let did_doc: DidDocument = serde_json::from_value(log_entry.get_state().to_owned())?;
let update_keys = log_entry
.get_parameters()
.update_keys
.map(|update_keys| (*update_keys).clone());
let metadata = WebvhMetadata {
webvh_meta_data: meta_data,
update_keys,
};
Ok((
resolve_document(did_doc, id)?,
serde_json::to_value(&metadata)?,
))
}
pub async fn create_webvh(
did_path: &str,
transport: Url,
) -> Result<(OwnedVid, serde_json::Value, String, Vec<u8>), VidError> {
let path_url = Url::parse(&["http://", did_path].concat())?;
let webvh_url = WebVHURL::parse_url(&path_url)?;
let mut vid = OwnedVid::bind(webvh_url.to_string(), transport);
let did_doc = vid_to_did_document(vid.vid());
let signing_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
let sigkey_private = signing_key.to_bytes().to_vec();
let sigkey_public = signing_key.verifying_key().to_bytes();
let mut webvh_signing_key = Secret::from_str(
"webvh-signing-key",
&json!({
"crv": "Ed25519",
"kty": "OKP",
"x": Base64UrlUnpadded::encode_string(&sigkey_public),
"d":Base64UrlUnpadded::encode_string(&sigkey_private),
}),
)
.map_err(|e| VidError::InternalError(format!("Couldn't create WebVH UpdateKey: {}", e)))?;
let webvh_signing_key_public = webvh_signing_key.get_public_keymultibase().map_err(|e| {
VidError::InternalError(format!(
"WebVH signing key couldn't get multibase key: {}",
e
))
})?;
webvh_signing_key.id = [
"did:key:",
&webvh_signing_key_public,
"#",
&webvh_signing_key_public,
]
.concat();
let params = Parameters::new()
.with_update_keys(vec![webvh_signing_key_public.clone()])
.build();
let mut webvh = DIDWebVHState::default();
let log_entry = webvh.create_log_entry(None, &did_doc, ¶ms, &webvh_signing_key)?;
vid.vid.id = log_entry
.get_state()
.get("id")
.and_then(|v| v.as_str())
.ok_or(VidError::InternalError(
"Couldn't get DID ID from WebVH Log Entry".to_string(),
))?
.to_string();
let genesis_log_entry = serde_json::to_value(&log_entry.log_entry)?;
Ok((
vid,
genesis_log_entry,
webvh_signing_key_public,
webvh_signing_key.get_private_bytes().to_vec(),
))
}
pub async fn update(
updated_document: serde_json::Value,
update_key: &[u8; 32],
) -> Result<LogEntry, VidError> {
let signing_key = ed25519_dalek::SigningKey::from_bytes(update_key);
let sigkey_private = signing_key.to_bytes().to_vec();
let sigkey_public = signing_key.verifying_key().to_bytes();
let mut webvh_signing_key = Secret::from_str(
"webvh-signing-key",
&json!({
"crv": "Ed25519",
"kty": "OKP",
"x": Base64UrlUnpadded::encode_string(&sigkey_public),
"d":Base64UrlUnpadded::encode_string(&sigkey_private),
}),
)
.map_err(|e| VidError::InternalError(format!("Couldn't create WebVH UpdateKey: {}", e)))?;
let webvh_signing_key_public = webvh_signing_key.get_public_keymultibase().map_err(|e| {
VidError::InternalError(format!(
"WebVH signing key couldn't get multibase key: {}",
e
))
})?;
webvh_signing_key.id = [
"did:key:",
&webvh_signing_key_public,
"#",
&webvh_signing_key_public,
]
.concat();
let did_id =
updated_document
.get("id")
.and_then(|v| v.as_str())
.ok_or(VidError::InternalError(
"Couldn't get DID ID from updated DID Document".to_string(),
))?;
let mut webvh = DIDWebVHState::default();
webvh.resolve(did_id, None).await?;
let log_entry = webvh.create_log_entry(
None,
&updated_document,
&Parameters::default(),
&webvh_signing_key,
)?;
let mut proofs = Vec::new();
for proof in log_entry.log_entry.get_proofs() {
proofs.push(json!(proof));
}
Ok(log_entry.log_entry.clone())
}
#[cfg(feature = "async")]
#[cfg(test)]
mod tests {
use super::*;
use url::Url;
#[tokio::test]
async fn test_create_webvh_success() {
let did_path = "example/endpoint/alice";
let transport_url = Url::parse("tcp://example.com:1234").unwrap();
let result = create_webvh(did_path, transport_url).await;
assert!(result.is_ok());
let (_vid, genesis_log_entry, webvh_update_key_public, private_key_bytes) = result.unwrap();
assert!(genesis_log_entry.is_object());
let version_id = genesis_log_entry["versionId"].as_str().unwrap();
assert!(version_id.starts_with("1-"));
let did_id_from_state = genesis_log_entry["state"]["id"].as_str().unwrap();
let scid = genesis_log_entry["parameters"]["scid"].as_str().unwrap();
let expected_did_id = format!("did:webvh:{scid}:{}", did_path.replace("/", ":"));
assert_eq!(
did_id_from_state, expected_did_id,
"DID ID in state is incorrect"
);
let update_keys = genesis_log_entry["parameters"]["updateKeys"]
.as_array()
.unwrap();
assert_eq!(
update_keys.len(),
1,
"There should be exactly one update key"
);
assert_eq!(
update_keys[0].as_str().unwrap(),
webvh_update_key_public,
"The update key in parameters does not match the returned public key"
);
let verification_method = genesis_log_entry["proof"][0]["verificationMethod"]
.as_str()
.unwrap();
let expected_vm = format!(
"did:key:{}#{}",
webvh_update_key_public, webvh_update_key_public
);
assert_eq!(
verification_method, expected_vm,
"Verification method in proof is incorrect"
);
assert!(
webvh_update_key_public.starts_with('z'),
"Public key should be in multibase format starting with 'z'"
);
let key_array: [u8; 32] = private_key_bytes
.clone()
.try_into()
.expect("private key must be 32 bytes");
let signing_key = ed25519_dalek::SigningKey::from_bytes(&key_array);
let verifying_key = signing_key.verifying_key();
let rederived_secret = Secret::from_str(
"test-key",
&json!({
"crv": "Ed25519",
"kty": "OKP",
"x": Base64UrlUnpadded::encode_string(verifying_key.as_bytes()),
"d": Base64UrlUnpadded::encode_string(&private_key_bytes),
}),
)
.expect("Failed to create test secret");
let rederived_public_key_multibase = rederived_secret
.get_public_keymultibase()
.expect("Failed to get multibase key");
assert_eq!(
webvh_update_key_public, rederived_public_key_multibase,
"Returned public key does not match the one re-derived from the private key"
);
}
}