Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a single cryptosuite identifier and tag proofValue with subtype #317

Merged
merged 2 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading