Skip to content

Commit

Permalink
Use a single cryptosuite identifier and tag proofValue with subtype (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewwhitehead authored Feb 2, 2024
1 parent 168d13c commit 2c4689d
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 224 deletions.
19 changes: 10 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,34 @@ crate-type = ["staticlib", "rlib", "cdylib"]
[features]
default = ["ffi", "logger", "zeroize", "w3c"]
ffi = ["dep:ffi-support"]
zeroize = ["dep:zeroize"]
logger = ["dep:env_logger"]
vendored = ["anoncreds-clsignatures/openssl_vendored"]
w3c = ["base64", "chrono", "rmp-serde"]
w3c = ["dep:base64", "dep:chrono", "dep:rmp-serde"]
zeroize = ["dep:zeroize"]

[dependencies]
anoncreds-clsignatures = "0.3.1"
bs58 = "0.4.0"
anoncreds-clsignatures = "0.3.2"
base64 = { version = "0.21.5", optional = true }
bitvec = { version = "1.0.1", features = ["serde"] }
bs58 = "0.5.0"
chrono = { version = "0.4.31", optional = true, features = ["serde"] }
env_logger = { version = "0.9.3", optional = true }
ffi-support = { version = "0.4.0", optional = true }
log = "0.4.17"
once_cell = "1.17.1"
rand = "0.8.5"
regex = "1.7.1"
rmp-serde = { version = "1.1.2", optional = true }
serde = { version = "1.0.155", features = ["derive"] }
bitvec = { version = "1.0.1", features = ["serde"] }
serde_json = { version = "1.0.94", features = ["raw_value"] }
sha2 = "0.10.6"
thiserror = "1.0.39"
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
base64 = { version = "0.21.5", optional = true }
chrono = { version = "0.4.31", optional = true, features = ["serde"] }
rmp-serde = { version = "1.1.2", optional = true }

[dev-dependencies]
rstest = "0.18.2"

[profile.release]
lto = true
codegen-units = 1
lto = true
strip = "debuginfo"
2 changes: 1 addition & 1 deletion src/data_types/w3c/constants.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::data_types::w3c::context::{Context, Contexts};
use once_cell::sync::Lazy;
use serde_json::{json, Value};
use std::collections::HashSet;

use crate::data_types::w3c::context::{Context, Contexts};
use crate::data_types::w3c::credential::Types;
use crate::data_types::w3c::uri::URI;

Expand Down
44 changes: 31 additions & 13 deletions src/data_types/w3c/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::Result;

/// AnonCreds W3C Credential definition
/// Note, that this definition is tied to AnonCreds W3C form
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct W3CCredential {
#[serde(rename = "@context")]
Expand Down Expand Up @@ -49,10 +49,10 @@ pub type IssuanceDate = DateTime<Utc>;
pub type NonAnonCredsDataIntegrityProof = serde_json::Value;

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CredentialProof {
DataIntegrityProof(DataIntegrityProof),
AnonCredsDataIntegrityProof(DataIntegrityProof),
NonAnonCredsDataIntegrityProof(NonAnonCredsDataIntegrityProof),
}

Expand All @@ -74,7 +74,7 @@ impl W3CCredential {
issuance_date,
issuer,
credential_subject,
proof: OneOrMany::Many(vec![CredentialProof::DataIntegrityProof(proof)]),
proof: OneOrMany::Many(vec![CredentialProof::AnonCredsDataIntegrityProof(proof)]),
valid_from: None,
id: None,
}
Expand All @@ -93,38 +93,40 @@ impl W3CCredential {
issuance_date: credential.issuance_date,
valid_from: credential.valid_from,
credential_subject,
proof: OneOrMany::One(CredentialProof::DataIntegrityProof(proof)),
proof: OneOrMany::One(CredentialProof::AnonCredsDataIntegrityProof(proof)),
}
}

pub fn version(&self) -> Result<VerifiableCredentialSpecVersion> {
self.context.version()
}

pub fn get_credential_signature_proof(&self) -> Result<CredentialSignatureProofValue> {
pub fn get_credential_signature_proof(&self) -> Result<&CredentialSignatureProofValue> {
self.get_data_integrity_proof()?
.get_credential_signature_proof()
}

pub fn get_credential_presentation_proof(&self) -> Result<CredentialPresentationProofValue> {
pub fn get_credential_presentation_proof(&self) -> Result<&CredentialPresentationProofValue> {
self.get_data_integrity_proof()?
.get_credential_presentation_proof()
}

pub(crate) fn get_data_integrity_proof(&self) -> Result<&DataIntegrityProof> {
self.proof
.get_value(&|proof: &CredentialProof| match proof {
CredentialProof::DataIntegrityProof(proof) => Ok(proof),
_ => Err(err_msg!("Credential does not contain data integrity proof")),
.find_value(&|proof: &CredentialProof| match proof {
CredentialProof::AnonCredsDataIntegrityProof(proof) => Some(proof),
_ => None,
})
.ok_or_else(|| err_msg!("Credential does not contain data integrity proof"))
}

pub(crate) fn get_mut_data_integrity_proof(&mut self) -> Result<&mut DataIntegrityProof> {
self.proof
.get_mut_value(&|proof: &mut CredentialProof| match proof {
CredentialProof::DataIntegrityProof(proof) => Ok(proof),
_ => Err(err_msg!("Credential does not contain data integrity proof")),
.find_mut_value(&|proof: &mut CredentialProof| match proof {
CredentialProof::AnonCredsDataIntegrityProof(proof) => Some(proof),
_ => None,
})
.ok_or_else(|| err_msg!("Credential does not contain data integrity proof"))
}

pub(crate) fn validate(&self) -> Result<()> {
Expand All @@ -145,3 +147,19 @@ impl W3CCredential {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::W3CCredential;

#[test]
fn serde_w3c_credential() {
let cred_json = include_str!("sample_credential.json");
let cred1: W3CCredential =
serde_json::from_str(&cred_json).expect("Error deserializing w3c credential");
let out_json = serde_json::to_string(&cred1).expect("Error serializing w3c credential");
let cred2: W3CCredential =
serde_json::from_str(&out_json).expect("Error deserializing w3c credential");
assert_eq!(cred1, cred2);
}
}
79 changes: 79 additions & 0 deletions src/data_types/w3c/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
pub mod base64_msgpack {
use serde::{de::Visitor, ser::Error, Deserialize, Serialize};
use std::marker::PhantomData;

use crate::utils::{base64, msg_pack};

pub const BASE_HEADER: &str = "u";

pub fn serialize<T, S>(obj: &T, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
T: Serialize,
S: serde::Serializer,
{
let msg_pack_encoded = msg_pack::encode(obj).map_err(S::Error::custom)?;
let base64_encoded = base64::encode(msg_pack_encoded);
serializer.collect_str(&format_args!("{}{}", BASE_HEADER, base64_encoded))
}

pub fn deserialize<'de, T, D>(deserializer: D) -> std::result::Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: for<'a> Deserialize<'a>,
{
struct DeserVisitor<VT>(PhantomData<VT>);

impl<'v, VT> Visitor<'v> for DeserVisitor<VT>
where
VT: for<'a> Deserialize<'a>,
{
type Value = VT;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("expected base64-msgpack encoded value")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let Some(obj) = v.strip_prefix(BASE_HEADER).and_then(|v| {
base64::decode(v).ok()
}).and_then(|v| {
msg_pack::decode(&v).ok()
}) else {
return Err(E::custom(format!("Unexpected multibase base header: {:?}", v)))
};
Ok(obj)
}
}

deserializer.deserialize_str(DeserVisitor(PhantomData))
}
}

#[cfg(test)]
mod tests {

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestObject {
type_: String,
value: i32,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
struct Container(#[serde(with = "super::base64_msgpack")] TestObject);

#[test]
fn base64_msgpack_serde_works() {
let obj = Container(TestObject {
type_: "Test".to_string(),
value: 1,
});
let encoded = serde_json::to_string(&obj).unwrap();
assert_eq!("\"ugqV0eXBlX6RUZXN0pXZhbHVlAQ\"", encoded);
let decoded: Container = serde_json::from_str(&encoded).unwrap();
assert_eq!(obj, decoded)
}
}
2 changes: 2 additions & 0 deletions src/data_types/w3c/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub mod presentation;
pub mod proof;
pub mod uri;

mod format;

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum VerifiableCredentialSpecVersion {
V1_1,
Expand Down
26 changes: 9 additions & 17 deletions src/data_types/w3c/one_or_many.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::error::Result;

/// Helper structure to handle the case when value is either single object or list of objects
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOrMany<T> {
Many(Vec<T>),
Expand All @@ -15,26 +13,20 @@ impl<T> Default for OneOrMany<T> {
}

impl<T> OneOrMany<T> {
pub fn get_value<F>(&self, closure: &dyn Fn(&T) -> Result<&F>) -> Result<&F> {
match &self {
pub fn find_value<'a, F: 'a>(&'a self, closure: &dyn Fn(&'a T) -> Option<F>) -> Option<F> {
match self {
OneOrMany::One(value) => closure(value),
OneOrMany::Many(values) => values
.iter()
.find_map(|value| closure(value).ok())
.ok_or_else(|| err_msg!("Object does not contain required value")),
OneOrMany::Many(values) => values.iter().find_map(closure),
}
}

pub(crate) fn get_mut_value<F>(
&mut self,
closure: &dyn Fn(&mut T) -> Result<&mut F>,
) -> Result<&mut F> {
pub(crate) fn find_mut_value<'a, F: 'a>(
&'a mut self,
closure: &dyn Fn(&'a mut T) -> Option<F>,
) -> Option<F> {
match self {
OneOrMany::One(value) => closure(value),
OneOrMany::Many(values) => values
.iter_mut()
.find_map(|value| closure(value).ok())
.ok_or_else(|| err_msg!("Object does not contain required value")),
OneOrMany::Many(values) => values.iter_mut().find_map(closure),
}
}
}
38 changes: 23 additions & 15 deletions src/data_types/w3c/presentation.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
use serde::{Deserialize, Serialize};

use crate::data_types::w3c::constants::ANONCREDS_PRESENTATION_TYPES;
use crate::data_types::w3c::constants::{ANONCREDS_PRESENTATION_TYPES, W3C_PRESENTATION_TYPE};
use crate::data_types::w3c::context::Contexts;
use crate::data_types::w3c::credential::Types;
use crate::data_types::w3c::proof::PresentationProofValue;
use crate::data_types::w3c::proof::{CryptoSuite, DataIntegrityProof};
use crate::data_types::w3c::{
constants::W3C_PRESENTATION_TYPE, credential::W3CCredential, VerifiableCredentialSpecVersion,
};
use crate::data_types::w3c::credential::{Types, W3CCredential};
use crate::data_types::w3c::proof::{DataIntegrityProof, PresentationProofValue};
use crate::data_types::w3c::VerifiableCredentialSpecVersion;
use crate::Result;

/// AnonCreds W3C Presentation definition
/// Note, that this definition is tied to AnonCreds W3C form
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct W3CPresentation {
#[serde(rename = "@context")]
Expand Down Expand Up @@ -43,13 +40,8 @@ impl W3CPresentation {
self.context.version()
}

pub fn get_presentation_proof(&self) -> Result<PresentationProofValue> {
if self.proof.cryptosuite != CryptoSuite::AnonCredsPresVp2023 {
return Err(err_msg!(
"Credential does not contain anoncredspresvc-2023 proof"
));
}
self.proof.get_proof_value()
pub fn get_presentation_proof(&self) -> Result<&PresentationProofValue> {
self.proof.get_presentation_proof()
}

pub(crate) fn validate(&self) -> Result<()> {
Expand All @@ -62,3 +54,19 @@ impl W3CPresentation {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::W3CPresentation;

#[test]
fn serde_w3c_presentation() {
let pres_json = include_str!("sample_presentation.json");
let pres1: W3CPresentation =
serde_json::from_str(&pres_json).expect("Error deserializing w3c presentation");
let out_json = serde_json::to_string(&pres1).expect("Error serializing w3c presentation");
let pres2: W3CPresentation =
serde_json::from_str(&out_json).expect("Error deserializing w3c presentation");
assert_eq!(pres1, pres2);
}
}
Loading

0 comments on commit 2c4689d

Please sign in to comment.