Skip to content

Commit

Permalink
return metadata
Browse files Browse the repository at this point in the history
Signed-off-by: George Mulhearn <[email protected]>
  • Loading branch information
gmulhearn-anonyome committed Dec 4, 2024
1 parent c08b2c1 commit 19d4e21
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 36 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion aries/aries_vcx_ledger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ edition.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
vdr_proxy_ledger = ["dep:indy-vdr-proxy-client"]
cheqd = ["dep:did_cheqd"]
cheqd = ["dep:did_cheqd", "dep:did_resolver"]

[dependencies]
aries_vcx_wallet = { path = "../aries_vcx_wallet" }
Expand All @@ -19,6 +19,7 @@ thiserror = "1.0.40"
indy-vdr.workspace = true
indy-vdr-proxy-client = { workspace = true, optional = true }
did_cheqd = { path = "../../did_core/did_methods/did_cheqd", optional = true }
did_resolver = { path = "../../did_core/did_resolver", optional = true }
serde_json = "1.0.95"
public_key = { path = "../../did_core/public_key" }
async-trait = "0.1.68"
Expand Down
81 changes: 55 additions & 26 deletions aries/aries_vcx_ledger/src/ledger/cheqd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use async_trait::async_trait;
use chrono::DateTime;
use did_cheqd::resolution::resolver::DidCheqdResolver;
use did_parser_nom::{Did, DidUrl};
use did_resolver::shared_types::did_resource::{DidResource, DidResourceMetadata};
use models::{
CheqdAnoncredsCredentialDefinition, CheqdAnoncredsRevocationRegistryDefinition,
CheqdAnoncredsRevocationStatusList, CheqdAnoncredsSchema,
Expand All @@ -28,6 +29,9 @@ use crate::errors::error::{VcxLedgerError, VcxLedgerResult};

mod models;

const SCHEMA_RESOURCE_TYPE: &str = "anonCredsSchema";
const CRED_DEF_RESOURCE_TYPE: &str = "anonCredsCredDef";
const REV_REG_DEF_RESOURCE_TYPE: &str = "anonCredsRevocRegDef";
const STATUS_LIST_RESOURCE_TYPE: &str = "anonCredsStatusList";

pub struct CheqdAnoncredsLedgerRead {
Expand All @@ -38,19 +42,49 @@ impl CheqdAnoncredsLedgerRead {
pub fn new(resolver: Arc<DidCheqdResolver>) -> Self {
Self { resolver }
}
}

// TODO - issue with our anoncreds-types conversions - we are missing `issuerId`, so we make
// issuerId from the resource ID - which assumes it is a legacy sovrin identifier for the resource.
// i.e. split(":")[0]. FIX! we could fix the indyvdr type conversions to include the `issuerId`, and
// make `issuerId` required in our anoncreds-types UPDATE - actually ^, check what credo is doing
fn check_resource_type(&self, resource: &DidResource, expected: &str) -> VcxLedgerResult<()> {
let rtyp = &resource.metadata.resource_type;
if rtyp != expected {
return Err(VcxLedgerError::InvalidLedgerResponse(format!(
"Returned resource is not expected type. Got {rtyp}, expected: {expected}"
)));
}
Ok(())
}

async fn get_rev_reg_def_with_metadata(
&self,
rev_reg_id: &RevocationRegistryDefinitionId,
) -> VcxLedgerResult<(RevocationRegistryDefinition, DidResourceMetadata)> {
let url = DidUrl::parse(rev_reg_id.to_string())?;
let resource = self.resolver.resolve_resource(&url).await?;
self.check_resource_type(&resource, REV_REG_DEF_RESOURCE_TYPE)?;

let data: CheqdAnoncredsRevocationRegistryDefinition =
serde_json::from_slice(&resource.content)?;
Ok((
RevocationRegistryDefinition {
id: rev_reg_id.to_owned(),
revoc_def_type: data.revoc_def_type,
tag: data.tag,
cred_def_id: data.cred_def_id,
value: data.value,
issuer_id: extract_issuer_id(&url)?,
},
resource.metadata,
))
}
}

#[async_trait]
impl AnoncredsLedgerRead for CheqdAnoncredsLedgerRead {
async fn get_schema(&self, schema_id: &SchemaId, _: Option<&Did>) -> VcxLedgerResult<Schema> {
let url = DidUrl::parse(schema_id.to_string())?;
let resource = self.resolver.resolve_resource(&url).await?;
let data: CheqdAnoncredsSchema = serde_json::from_slice(&resource)?;
self.check_resource_type(&resource, SCHEMA_RESOURCE_TYPE)?;

let data: CheqdAnoncredsSchema = serde_json::from_slice(&resource.content)?;
Ok(Schema {
id: schema_id.to_owned(),
seq_no: None,
Expand All @@ -68,7 +102,9 @@ impl AnoncredsLedgerRead for CheqdAnoncredsLedgerRead {
) -> VcxLedgerResult<CredentialDefinition> {
let url = DidUrl::parse(cred_def_id.to_string())?;
let resource = self.resolver.resolve_resource(&url).await?;
let data: CheqdAnoncredsCredentialDefinition = serde_json::from_slice(&resource)?;
self.check_resource_type(&resource, CRED_DEF_RESOURCE_TYPE)?;

let data: CheqdAnoncredsCredentialDefinition = serde_json::from_slice(&resource.content)?;
Ok(CredentialDefinition {
id: cred_def_id.to_owned(),
schema_id: data.schema_id,
Expand All @@ -83,17 +119,9 @@ impl AnoncredsLedgerRead for CheqdAnoncredsLedgerRead {
&self,
rev_reg_id: &RevocationRegistryDefinitionId,
) -> VcxLedgerResult<RevocationRegistryDefinition> {
let url = DidUrl::parse(rev_reg_id.to_string())?;
let resource = self.resolver.resolve_resource(&url).await?;
let data: CheqdAnoncredsRevocationRegistryDefinition = serde_json::from_slice(&resource)?;
Ok(RevocationRegistryDefinition {
id: rev_reg_id.to_owned(),
revoc_def_type: data.revoc_def_type,
tag: data.tag,
cred_def_id: data.cred_def_id,
value: data.value,
issuer_id: extract_issuer_id(&url)?,
})
self.get_rev_reg_def_with_metadata(rev_reg_id)
.await
.map(|v| v.0)
}

async fn get_rev_reg_delta_json(
Expand All @@ -112,16 +140,15 @@ impl AnoncredsLedgerRead for CheqdAnoncredsLedgerRead {
&self,
rev_reg_id: &RevocationRegistryDefinitionId,
timestamp: u64,
pre_fetched_rev_reg_def: Option<&RevocationRegistryDefinition>,
// unused, we need to fetch anyway for resourceName
_pre_fetched_rev_reg_def: Option<&RevocationRegistryDefinition>,
) -> VcxLedgerResult<(RevocationStatusList, u64)> {
let rev_reg_def_url = DidUrl::parse(rev_reg_id.to_string())?;

let rev_reg_def = match pre_fetched_rev_reg_def {
Some(v) => v,
None => &self.get_rev_reg_def_json(rev_reg_id).await?,
};
let (_def, rev_reg_def_metadata) = self.get_rev_reg_def_with_metadata(rev_reg_id).await?;

let name = &rev_reg_def.tag; // TODO - credo-ts uses the metadata.name or fails (https://docs.cheqd.io/product/advanced/anoncreds/revocation-status-list#same-resource-name-different-resource-type)
//credo-ts uses the metadata.name or fails (https://docs.cheqd.io/product/advanced/anoncreds/revocation-status-list#same-resource-name-different-resource-type)
let name = &rev_reg_def_metadata.resource_name;

let did = rev_reg_def_url
.did()
Expand All @@ -141,8 +168,10 @@ impl AnoncredsLedgerRead for CheqdAnoncredsLedgerRead {
let query_url = DidUrl::parse(query)?;

let resource = self.resolver.resolve_resource(&query_url).await?;
let data: CheqdAnoncredsRevocationStatusList = serde_json::from_slice(&resource)?;
let timestamp = 0; // TODO - from metadata
self.check_resource_type(&resource, STATUS_LIST_RESOURCE_TYPE)?;

let data: CheqdAnoncredsRevocationStatusList = serde_json::from_slice(&resource.content)?;
let timestamp = resource.metadata.created.timestamp() as u64;

let status_list = RevocationStatusList {
rev_reg_def_id: Some(rev_reg_id.to_owned()),
Expand Down
28 changes: 23 additions & 5 deletions did_core/did_methods/did_cheqd/src/resolution/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use did_resolver::{
did_doc::schema::did_doc::DidDocument,
did_parser_nom::{Did, DidUrl},
error::GenericError,
shared_types::did_document_metadata::DidDocumentMetadata,
shared_types::{
did_document_metadata::DidDocumentMetadata,
did_resource::{DidResource, DidResourceMetadata},
},
traits::resolvable::{resolution_output::DidResolutionOutput, DidResolvable},
};
use http_body_util::combinators::UnsyncBoxBody;
Expand All @@ -18,6 +21,7 @@ use hyper_util::{
use tokio::sync::Mutex;
use tonic::{transport::Uri, Status};

use super::transformer::CheqdResourceMetadataWithUri;
use crate::{
error::{DidCheqdError, DidCheqdResult},
proto::cheqd::{
Expand Down Expand Up @@ -184,7 +188,7 @@ impl DidCheqdResolver {
}

// TODO - better return structure
pub async fn resolve_resource(&self, url: &DidUrl) -> DidCheqdResult<Vec<u8>> {
pub async fn resolve_resource(&self, url: &DidUrl) -> DidCheqdResult<DidResource> {
let method = url.method();
if method != Some("cheqd") {
return Err(DidCheqdError::MethodNotSupported(format!("{method:?}")));
Expand Down Expand Up @@ -219,7 +223,7 @@ impl DidCheqdResolver {
did_id: &str,
resource_id: &str,
network: &str,
) -> DidCheqdResult<Vec<u8>> {
) -> DidCheqdResult<DidResource> {
let mut client = self.client_for_network(network).await?;

let request = QueryResourceRequest {
Expand All @@ -239,9 +243,23 @@ impl DidCheqdResolver {
.ok_or(DidCheqdError::InvalidResponse(
"Resource query did not return a resource".into(),
))?;
// TODO - metadata
let query_metadata = query_response
.metadata
.ok_or(DidCheqdError::InvalidResponse(
"Resource query did not return metadata".into(),
))?;
let metadata = DidResourceMetadata::try_from(CheqdResourceMetadataWithUri {
uri: format!(
"did:cheqd:{network}:{}/resources/{}",
query_metadata.collection_id, query_metadata.id
),
meta: query_metadata,
})?;

Ok(query_resource.data)
Ok(DidResource {
content: query_resource.data,
metadata,
})
}
}

Expand Down
67 changes: 63 additions & 4 deletions did_core/did_methods/did_cheqd/src/resolution/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ use did_resolver::{
verification_method::{PublicKeyField, VerificationMethod, VerificationMethodType},
},
did_parser_nom::Did,
shared_types::did_document_metadata::DidDocumentMetadata,
shared_types::{
did_document_metadata::DidDocumentMetadata, did_resource::DidResourceMetadata,
},
};
use serde_json::json;

use crate::{
error::{DidCheqdError, DidCheqdResult},
proto::cheqd::did::v2::{
DidDoc as CheqdDidDoc, Metadata as CheqdDidDocMetadata, Service as CheqdService,
VerificationMethod as CheqdVerificationMethod,
proto::cheqd::{
did::v2::{
DidDoc as CheqdDidDoc, Metadata as CheqdDidDocMetadata, Service as CheqdService,
VerificationMethod as CheqdVerificationMethod,
},
resource::v2::Metadata as CheqdResourceMetadata,
},
};

Expand Down Expand Up @@ -204,6 +209,60 @@ impl TryFrom<CheqdDidDocMetadata> for DidDocumentMetadata {
}
}

pub(super) struct CheqdResourceMetadataWithUri {
pub uri: String,
pub meta: CheqdResourceMetadata,
}

impl TryFrom<CheqdResourceMetadataWithUri> for DidResourceMetadata {
type Error = DidCheqdError;

fn try_from(value: CheqdResourceMetadataWithUri) -> Result<Self, Self::Error> {
let uri = value.uri;
let value = value.meta;

let Some(created) = value.created else {
return Err(DidCheqdError::InvalidDidDocument(format!(
"created field missing from resource: {value:?}"
)))?;
};

let version = (!value.version.trim().is_empty()).then_some(value.version);
let previous_version_id =
(!value.previous_version_id.trim().is_empty()).then_some(value.previous_version_id);
let next_version_id =
(!value.next_version_id.trim().is_empty()).then_some(value.next_version_id);

let also_known_as = value
.also_known_as
.into_iter()
.map(|aka| {
json!({
"uri": aka.uri,
"description": aka.description
})
})
.collect();

DidResourceMetadata::builder()
.resource_uri(uri)
.resource_collection_id(value.collection_id)
.resource_id(value.id)
.resource_name(value.name)
.resource_type(value.resource_type)
.resource_version(version)
.also_known_as(Some(also_known_as))
.media_type(value.media_type)
.created(prost_timestamp_to_dt(created)?)
.updated(None)
.checksum(value.checksum)
.previous_version_id(previous_version_id)
.next_version_id(next_version_id)
.build();
todo!()
}
}

fn prost_timestamp_to_dt(mut timestamp: prost_types::Timestamp) -> DidCheqdResult<DateTime<Utc>> {
timestamp.normalize();
DateTime::from_timestamp(timestamp.seconds, timestamp.nanos.try_into()?).ok_or(
Expand Down
1 change: 1 addition & 0 deletions did_core/did_resolver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ async-trait = "0.1.68"
chrono = { version = "0.4.24", default-features = false, features = ["serde"] }
serde = { version = "1.0.160", default-features = false, features = ["derive"] }
serde_json = "1.0.103"
typed-builder = "0.19.1"
61 changes: 61 additions & 0 deletions did_core/did_resolver/src/shared_types/did_resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use typed_builder::TypedBuilder;

/// https://w3c-ccg.github.io/DID-Linked-Resources/
#[derive(Clone, Debug, PartialEq, Default)]
pub struct DidResource {
pub content: Vec<u8>,
pub metadata: DidResourceMetadata,
}

/// https://w3c-ccg.github.io/DID-Linked-Resources/
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, TypedBuilder)]
#[serde(default)]
#[serde(rename_all = "camelCase")]
pub struct DidResourceMetadata {
// FUTURE - could be a map according to spec
/// A string or a map that conforms to the rules of RFC3986 URIs which SHOULD directly lead to
/// a location where the resource can be accessed.
/// For example:
/// did:example:46e2af9a-2ea0-4815-999d-730a6778227c/resources/
/// 0f964a80-5d18-4867-83e3-b47f5a756f02.
pub resource_uri: String,
/// A string that conforms to a method-specific supported unique identifier format.
/// For example, a UUID: 46e2af9a-2ea0-4815-999d-730a6778227c.
pub resource_collection_id: String,
/// A string that uniquely identifies the resource.
/// For example, a UUID: 0f964a80-5d18-4867-83e3-b47f5a756f02.
pub resource_id: String,
/// A string that uniquely names and identifies a resource. This property, along with the
/// resourceType below, can be used to track version changes within a resource.
pub resource_name: String,
/// A string that identifies the type of resource. This property, along with the resourceName
/// above, can be used to track version changes within a resource. Not to be confused with
/// mediaType.
pub resource_type: String,
/// (Optional) A string that identifies the version of the resource.
/// This property is provided by the client and can be any value.
#[serde(skip_serializing_if = "Option::is_none")]
pub resource_version: Option<String>,
/// (Optional) An array that describes alternative URIs for the resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub also_known_as: Option<Vec<Value>>,
/// A string that identifies the IANA-media type of the resource.
pub media_type: String,
// TODO - check datetime serializes into XML-date-time
/// A string that identifies the time the resource was created, as an XML date-time.
pub created: DateTime<Utc>,
/// (Optional) A string that identifies the time the resource was updated, as an XML date-time.
#[serde(skip_serializing_if = "Option::is_none")]
pub updated: Option<DateTime<Utc>>,
/// A string that may be used to prove that the resource has not been tampered with.
pub checksum: String,
/// (Optional) A string that identifies the previous version of the resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_version_id: Option<String>,
/// (Optional) A string that identifies the next version of the resource.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_version_id: Option<String>,
}
1 change: 1 addition & 0 deletions did_core/did_resolver/src/shared_types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod did_document_metadata;
pub mod did_resource;
pub mod media_type;

0 comments on commit 19d4e21

Please sign in to comment.