use crate::hashes;
use crate::hashes::BlockHash;
use crate::id::constants::ArCurve;
use crate::id::id_proof_types::RevealAttributeStatement;
use crate::id::types;
use crate::id::types::{AttributeTag, GlobalContext};
use crate::web3id::v1::anchor::{
ContextLabel, IdentityCredentialType, LabeledContextProperty, ParseContextPropertyError,
RequestedStatement, RequestedSubjectClaims, UnfilledContextInformation,
VerifiablePresentationRequestV1, VerifiablePresentationV1, VerificationMaterial,
VerificationRequest, VerificationRequestAnchor, VerificationRequestData,
};
use crate::web3id::v1::{AtomicStatementV1, ContextInformation, SubjectClaims};
use crate::web3id::{did, Web3IdAttribute};
use itertools::Itertools;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct VerificationContext {
pub network: did::Network,
pub validity_time: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VerificationRequestAnchorAndBlockHash {
pub verification_request_anchor: VerificationRequestAnchor,
pub block_hash: BlockHash,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VerificationMaterialWithValidity {
pub verification_material: VerificationMaterial,
pub validity: CredentialValidityType,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum CredentialValidityType {
ValidityPeriod(types::CredentialValidity),
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PresentationVerifyFailure {
CredentialNotValidYet,
CredentialExpired,
Network,
PresentationUnverifiable,
RequestAnchor,
NoVraBlockHash,
VraBlockHash,
ContextInformation,
InvalidContextPropertyValue,
UnknownContextProperty,
SubjectClaims,
CredentialType,
CredentialIssuer,
}
impl PresentationVerifyFailure {
fn as_str(self) -> &'static str {
match self {
PresentationVerifyFailure::CredentialNotValidYet => "credential not valid yet",
PresentationVerifyFailure::CredentialExpired => "credential expired",
PresentationVerifyFailure::Network => "blockchain network does not match",
PresentationVerifyFailure::PresentationUnverifiable => {
"presentation not cryptographically verifiable"
}
PresentationVerifyFailure::RequestAnchor => {
"verification request anchor hash does not match verification request"
}
PresentationVerifyFailure::NoVraBlockHash => {
"verification request anchor block hash not set in context"
}
PresentationVerifyFailure::VraBlockHash => {
"verification request anchor block hash does not match block hash set in context"
}
PresentationVerifyFailure::ContextInformation => {
"context does not match request context"
}
PresentationVerifyFailure::InvalidContextPropertyValue => "invalid value in context",
PresentationVerifyFailure::UnknownContextProperty => "unknown label in context",
PresentationVerifyFailure::SubjectClaims => {
"subject claims/statements does not match requested claims/statements"
}
PresentationVerifyFailure::CredentialType => {
"credential type does not match types allowed by request"
}
PresentationVerifyFailure::CredentialIssuer => {
"credential issuer does not match issuers allowed by request"
}
}
}
}
impl Display for PresentationVerifyFailure {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PresentationVerificationResult {
Verified,
Failed(PresentationVerifyFailure),
}
impl PresentationVerificationResult {
pub fn is_success(&self) -> bool {
matches!(self, PresentationVerificationResult::Verified)
}
}
pub fn verify_presentation_with_request_anchor<'a, B>(
global_context: &GlobalContext<ArCurve>,
verification_context: &VerificationContext,
verification_request: &VerificationRequest,
verifiable_presentation: &VerifiablePresentationV1,
verification_request_anchor: &VerificationRequestAnchorAndBlockHash,
verification_material: B,
) -> PresentationVerificationResult
where
B: IntoIterator<Item = &'a VerificationMaterialWithValidity> + Copy,
B::IntoIter: ExactSizeIterator,
{
if let Err(failure) = (|| {
verify_network(verification_context, verifiable_presentation)?;
verify_credential_validity(verification_context, verification_material)?;
verify_request_anchor(verification_request, verification_request_anchor)?;
verify_anchor_block_hash(verification_request_anchor, verifiable_presentation)?;
let request_from_presentation = verify_presentation(
global_context,
verifiable_presentation,
verification_material.into_iter(),
)?;
verify_request(&request_from_presentation, verification_request)?;
Ok(())
})() {
PresentationVerificationResult::Failed(failure)
} else {
PresentationVerificationResult::Verified
}
}
fn verify_network(
verification_context: &VerificationContext,
presentation: &VerifiablePresentationV1,
) -> Result<(), PresentationVerifyFailure> {
for metadata in presentation.metadata() {
if metadata.network != verification_context.network {
return Err(PresentationVerifyFailure::Network);
}
}
Ok(())
}
fn verify_credential_validity<'a>(
verification_context: &VerificationContext,
verification_material: impl IntoIterator<Item = &'a VerificationMaterialWithValidity>,
) -> Result<(), PresentationVerifyFailure> {
for credential_validity in verification_material {
match &credential_validity.validity {
CredentialValidityType::ValidityPeriod(cred_validity) => {
verify_credential_validity_period(
verification_context.validity_time,
cred_validity,
)?;
}
}
}
Ok(())
}
fn verify_credential_validity_period(
now: chrono::DateTime<chrono::Utc>,
credential_validity: &types::CredentialValidity,
) -> Result<(), PresentationVerifyFailure> {
let valid_from = credential_validity
.created_at
.lower()
.ok_or(PresentationVerifyFailure::CredentialNotValidYet)?;
let valid_to = credential_validity
.valid_to
.upper()
.ok_or(PresentationVerifyFailure::CredentialExpired)?;
if now < valid_from {
Err(PresentationVerifyFailure::CredentialNotValidYet)
} else if now >= valid_to {
Err(PresentationVerifyFailure::CredentialExpired)
} else {
Ok(())
}
}
fn verify_presentation<'a>(
global_context: &GlobalContext<ArCurve>,
presentation: &VerifiablePresentationV1,
verification_material: impl ExactSizeIterator<Item = &'a VerificationMaterialWithValidity>,
) -> Result<VerifiablePresentationRequestV1, PresentationVerifyFailure> {
presentation
.verify(
global_context,
verification_material.map(|vm| &vm.verification_material),
)
.map_err(|_| PresentationVerifyFailure::PresentationUnverifiable)
}
fn verify_anchor_block_hash(
request_anchor: &VerificationRequestAnchorAndBlockHash,
presentation: &VerifiablePresentationV1,
) -> Result<(), PresentationVerifyFailure> {
let Some(context_block_hash_parse_res) = presentation
.presentation_context
.requested
.iter()
.find_map(|prop| {
if prop.label == ContextLabel::BlockHash.as_str() {
Some(hashes::BlockHash::from_str(&prop.context))
} else {
None
}
})
else {
return Err(PresentationVerifyFailure::NoVraBlockHash);
};
let context_block_hash = context_block_hash_parse_res
.map_err(|_| PresentationVerifyFailure::InvalidContextPropertyValue)?;
if request_anchor.block_hash != context_block_hash {
return Err(PresentationVerifyFailure::VraBlockHash);
}
Ok(())
}
fn verify_request_anchor(
verification_request: &VerificationRequest,
request_anchor: &VerificationRequestAnchorAndBlockHash,
) -> Result<(), PresentationVerifyFailure> {
let verification_request_data = VerificationRequestData {
context: verification_request.context.clone(),
subject_claims: verification_request.subject_claims.clone(),
};
if verification_request_data.hash() != request_anchor.verification_request_anchor.hash {
return Err(PresentationVerifyFailure::RequestAnchor);
}
Ok(())
}
fn verify_request(
request_from_presentation: &VerifiablePresentationRequestV1,
verification_request: &VerificationRequest,
) -> Result<(), PresentationVerifyFailure> {
verify_request_context(
&request_from_presentation.context,
&verification_request.context,
)?;
verify_request_subject_claims_list(
&request_from_presentation.subject_claims,
&verification_request.subject_claims,
)?;
Ok(())
}
fn verify_request_subject_claims_list(
presentation_claims: &[SubjectClaims<ArCurve, Web3IdAttribute>],
request_claims: &[RequestedSubjectClaims],
) -> Result<(), PresentationVerifyFailure> {
for pair in presentation_claims.iter().zip_longest(request_claims) {
let (pres_claims, req_claims) = pair
.both()
.ok_or(PresentationVerifyFailure::SubjectClaims)?;
verify_request_subject_claims(pres_claims, req_claims)?;
}
Ok(())
}
fn verify_request_subject_claims(
presentation_claims: &SubjectClaims<ArCurve, Web3IdAttribute>,
request_claims: &RequestedSubjectClaims,
) -> Result<(), PresentationVerifyFailure> {
match request_claims {
RequestedSubjectClaims::Identity(req_id_claims) => {
let (pres_issuer, pres_network, pres_statements) = match presentation_claims {
SubjectClaims::Account(acc_claims) => {
if !req_id_claims
.source
.contains(&IdentityCredentialType::AccountCredential)
{
return Err(PresentationVerifyFailure::CredentialType);
}
let stmts: Vec<_> = acc_claims
.statements
.iter()
.map(statement_to_requested_statement)
.collect();
(acc_claims.issuer, acc_claims.network, stmts)
}
SubjectClaims::Identity(id_claims) => {
if !req_id_claims
.source
.contains(&IdentityCredentialType::IdentityCredential)
{
return Err(PresentationVerifyFailure::CredentialType);
}
let stmts: Vec<_> = id_claims
.statements
.iter()
.map(statement_to_requested_statement)
.collect();
(id_claims.issuer, id_claims.network, stmts)
}
};
if !req_id_claims.issuers.iter().any(|issuer| {
issuer.identity_provider == pres_issuer && issuer.network == pres_network
}) {
return Err(PresentationVerifyFailure::CredentialIssuer);
}
if pres_statements != req_id_claims.statements {
return Err(PresentationVerifyFailure::SubjectClaims);
}
Ok(())
}
}
}
fn statement_to_requested_statement(
statement: &AtomicStatementV1<ArCurve, AttributeTag, Web3IdAttribute>,
) -> RequestedStatement<AttributeTag> {
match statement {
AtomicStatementV1::AttributeValue(stmt) => {
RequestedStatement::RevealAttribute(RevealAttributeStatement {
attribute_tag: stmt.attribute_tag,
})
}
AtomicStatementV1::AttributeInRange(stmt) => {
RequestedStatement::AttributeInRange(stmt.clone())
}
AtomicStatementV1::AttributeInSet(stmt) => RequestedStatement::AttributeInSet(stmt.clone()),
AtomicStatementV1::AttributeNotInSet(stmt) => {
RequestedStatement::AttributeNotInSet(stmt.clone())
}
}
}
fn verify_request_context(
presentation_context: &ContextInformation,
request_context: &UnfilledContextInformation,
) -> Result<(), PresentationVerifyFailure> {
fn map_parse_prop_err<T>(
res: Result<T, ParseContextPropertyError>,
) -> Result<T, PresentationVerifyFailure> {
res.map_err(|err| match err {
ParseContextPropertyError::ParseLabel(_) => {
PresentationVerifyFailure::UnknownContextProperty
}
ParseContextPropertyError::ParseValue(_) => {
PresentationVerifyFailure::InvalidContextPropertyValue
}
})
}
let presentation_given_properties_parse_res: Result<Vec<_>, _> = presentation_context
.given
.iter()
.map(LabeledContextProperty::try_from_context_property)
.collect();
if map_parse_prop_err(presentation_given_properties_parse_res)? != request_context.given {
return Err(PresentationVerifyFailure::ContextInformation);
}
let presentation_requested_property_labels_parse_res: Result<Vec<_>, _> = presentation_context
.requested
.iter()
.map(|prop| {
LabeledContextProperty::try_from_context_property(prop).map(|prop| prop.label())
})
.collect();
if map_parse_prop_err(presentation_requested_property_labels_parse_res)?
!= request_context.requested
{
return Err(PresentationVerifyFailure::ContextInformation);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hashes;
use crate::id::constants::AttributeKind;
use crate::id::id_proof_types::AttributeInSetStatement;
use crate::id::types::YearMonth;
use crate::web3id::did::Network;
use crate::web3id::v1::anchor::{
fixtures, ContextLabel, IdentityProviderDid, LabeledContextProperty,
VerificationRequestDataBuilder,
};
use crate::web3id::v1::{ContextProperty, CredentialV1};
use assert_matches::assert_matches;
fn verification_context() -> VerificationContext {
VerificationContext {
network: Network::Testnet,
validity_time: chrono::DateTime::parse_from_rfc3339("2023-08-28T23:12:15Z")
.unwrap()
.to_utc(),
}
}
fn validity() -> CredentialValidityType {
CredentialValidityType::ValidityPeriod(types::CredentialValidity {
valid_to: YearMonth::new(2030, 01).unwrap(),
created_at: YearMonth::new(2020, 01).unwrap(),
})
}
#[test]
fn test_verify_completeness_identity() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Verified
);
}
#[test]
fn test_verify_completeness_account() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let account_cred =
fixtures::account_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_account(
&account_cred,
fixtures::verification_request_to_verifiable_presentation_request_account(
&account_cred,
&request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: account_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Verified
);
}
#[test]
fn test_verify_completeness_multiple_credentials() {
let global_context = GlobalContext::generate("Test".into());
let context = fixtures::unfilled_context_fixture();
let request_data = VerificationRequestDataBuilder::new(context)
.subject_claim(fixtures::identity_subject_claims_fixture())
.subject_claim(fixtures::identity_subject_claims_fixture())
.build();
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let account_cred =
fixtures::account_credentials_fixture(fixtures::default_attributes(), &global_context);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let verifiable_presentation_request = VerifiablePresentationRequestV1 {
context: fixtures::unfilled_context_information_to_context_information(
&request.context,
),
subject_claims: vec![
fixtures::requested_subject_claims_to_subject_claims_account(
&account_cred,
&request.subject_claims[0],
),
fixtures::requested_subject_claims_to_subject_claims_identity(
&id_cred,
&request.subject_claims[1],
),
],
};
let global_context = GlobalContext::generate("Test".into());
let now = chrono::DateTime::parse_from_rfc3339("2023-08-28T23:12:15Z")
.unwrap()
.with_timezone(&chrono::Utc);
let presentation = verifiable_presentation_request
.prove_with_rng(
&global_context,
[account_cred.private_inputs(), id_cred.private_inputs()].into_iter(),
&mut fixtures::seed0(),
now,
)
.expect("prove");
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = [
VerificationMaterialWithValidity {
verification_material: account_cred.verification_material.clone(),
validity: validity(),
},
VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
},
];
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&material,
),
PresentationVerificationResult::Verified
);
}
#[test]
fn test_verify_soundness_network() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
let mut verification_context = verification_context();
verification_context.network = Network::Mainnet;
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context,
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::Network)
);
}
#[test]
fn test_verify_soundness_credential_validity() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
let mut verification_context = verification_context();
verification_context.validity_time =
chrono::DateTime::parse_from_rfc3339("2035-08-28T23:12:15Z")
.unwrap()
.to_utc();
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context,
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::CredentialExpired)
);
}
#[test]
fn test_verify_soundness_cryptographic_verification() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
let CredentialV1::Identity(cred) = &mut presentation.verifiable_credentials[0] else {
panic!("expected identity credential");
};
cred.proof.proof_value.statement_proofs.clear();
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(
PresentationVerifyFailure::PresentationUnverifiable
)
);
}
#[test]
fn test_verify_soundness_request_anchor() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let mut anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
anchor.verification_request_anchor.hash = hashes::Hash::from([0u8; 32]);
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::RequestAnchor)
);
}
#[test]
fn test_verify_soundness_context_anchor_block_hash() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
for prop in &mut presentation_request.context.requested {
if prop.label == ContextLabel::BlockHash.as_str() {
prop.context =
LabeledContextProperty::BlockHash(hashes::BlockHash::from([0u8; 32]))
.value()
.to_string();
}
}
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::VraBlockHash)
);
}
#[test]
fn test_verify_soundness_context_no_anchor_block_hash() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
presentation_request
.context
.requested
.retain(|prop| prop.label != ContextLabel::BlockHash.as_str());
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::NoVraBlockHash)
);
}
#[test]
fn test_verify_soundness_context_unknown_property() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
presentation_request.context.given.push(ContextProperty {
label: "UnknownPropertyLabel".to_string(),
context: "testvalue".to_string(),
});
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(
PresentationVerifyFailure::UnknownContextProperty
)
);
}
#[test]
fn test_verify_soundness_context_invalid_property_value() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
for prop in &mut presentation_request.context.given {
if prop.label == ContextLabel::Nonce.as_str() {
prop.context = "invalidvalue".to_string();
}
}
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(
PresentationVerifyFailure::InvalidContextPropertyValue
)
);
}
#[test]
fn test_verify_soundness_context_given() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
for prop in &mut presentation_request.context.given {
if prop.label == ContextLabel::Nonce.as_str() {
prop.context = hashes::Hash::from([0u8; 32]).to_string();
}
}
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::ContextInformation)
);
}
#[test]
fn test_verify_soundness_context_requested() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
presentation_request
.context
.requested
.push(presentation_request.context.requested[0].clone());
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::ContextInformation)
);
}
#[test]
fn test_verify_soundness_claims_cred_type_account() {
let global_context = GlobalContext::generate("Test".into());
let mut request_data = fixtures::verification_request_data_fixture();
assert_matches!(&mut request_data.subject_claims[0], RequestedSubjectClaims::Identity(id_claims) => {
id_claims.source.clear();
id_claims.source.push(IdentityCredentialType::IdentityCredential);
});
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let account_cred =
fixtures::account_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_account(
&account_cred,
fixtures::verification_request_to_verifiable_presentation_request_account(
&account_cred,
&request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: account_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::CredentialType)
);
}
#[test]
fn test_verify_soundness_claims_cred_type_identity() {
let global_context = GlobalContext::generate("Test".into());
let mut request_data = fixtures::verification_request_data_fixture();
assert_matches!(&mut request_data.subject_claims[0], RequestedSubjectClaims::Identity(id_claims) => {
id_claims.source.clear();
id_claims.source.push(IdentityCredentialType::AccountCredential);
});
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::CredentialType)
);
}
#[test]
fn test_verify_soundness_claims_issuer_account() {
let global_context = GlobalContext::generate("Test".into());
let mut request_data = fixtures::verification_request_data_fixture();
assert_matches!(&mut request_data.subject_claims[0], RequestedSubjectClaims::Identity(id_claims) => {
id_claims.issuers.clear();
id_claims.issuers.push(IdentityProviderDid::new(1u32, did::Network::Testnet));
});
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let account_cred =
fixtures::account_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_account(
&account_cred,
fixtures::verification_request_to_verifiable_presentation_request_account(
&account_cred,
&request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: account_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::CredentialIssuer)
);
}
#[test]
fn test_verify_soundness_claims_issuer_identity() {
let global_context = GlobalContext::generate("Test".into());
let mut request_data = fixtures::verification_request_data_fixture();
assert_matches!(&mut request_data.subject_claims[0], RequestedSubjectClaims::Identity(id_claims) => {
id_claims.issuers.clear();
id_claims.issuers.push(IdentityProviderDid::new(1u32, did::Network::Testnet));
});
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::CredentialIssuer)
);
}
#[test]
fn test_verify_soundness_claims_issuer_network() {
let global_context = GlobalContext::generate("Test".into());
let mut request_data = fixtures::verification_request_data_fixture();
assert_matches!(&mut request_data.subject_claims[0], RequestedSubjectClaims::Identity(id_claims) => {
id_claims.issuers.clear();
id_claims.issuers.push(IdentityProviderDid::new(0u32, did::Network::Mainnet));
});
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let presentation = fixtures::generate_and_prove_presentation_identity(
&id_cred,
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
),
);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::CredentialIssuer)
);
}
#[test]
fn test_verify_soundness_claims_statement_modified() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
assert_matches!(&mut presentation_request.subject_claims[0], SubjectClaims::Identity(id_claims) => {
assert_matches!(&mut id_claims.statements[1],
AtomicStatementV1::AttributeInSet(AttributeInSetStatement { set, .. }) => {
set.insert(Web3IdAttribute::String(AttributeKind::try_new("bb".into()).unwrap()));
});
});
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::SubjectClaims)
);
}
#[test]
fn test_verify_soundness_claims_statement_removed() {
let global_context = GlobalContext::generate("Test".into());
let (request, vra) = fixtures::verification_request_data_to_request_and_anchor(
fixtures::verification_request_data_fixture(),
);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
assert_matches!(&mut presentation_request.subject_claims[0], SubjectClaims::Identity(id_claims) => {
id_claims.statements.pop();
});
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::SubjectClaims)
);
}
#[test]
fn test_verify_soundness_claims_subject_claim_removed() {
let global_context = GlobalContext::generate("Test".into());
let mut request_data = fixtures::verification_request_data_fixture();
request_data
.subject_claims
.push(request_data.subject_claims[0].clone());
let (request, vra) =
fixtures::verification_request_data_to_request_and_anchor(request_data);
let id_cred =
fixtures::identity_credentials_fixture(fixtures::default_attributes(), &global_context);
let mut presentation_request =
fixtures::verification_request_to_verifiable_presentation_request_identity(
&id_cred, &request,
);
presentation_request.subject_claims.pop();
let presentation =
fixtures::generate_and_prove_presentation_identity(&id_cred, presentation_request);
let anchor = VerificationRequestAnchorAndBlockHash {
verification_request_anchor: vra,
block_hash: *fixtures::VRA_BLOCK_HASH,
};
let material = VerificationMaterialWithValidity {
verification_material: id_cred.verification_material.clone(),
validity: validity(),
};
assert_eq!(
verify_presentation_with_request_anchor(
&global_context,
&verification_context(),
&request,
&presentation,
&anchor,
&[material],
),
PresentationVerificationResult::Failed(PresentationVerifyFailure::SubjectClaims)
);
}
#[test]
fn test_verify_credential_validity_period() {
let validity = types::CredentialValidity {
created_at: YearMonth::new(2018, 05).unwrap(),
valid_to: YearMonth::new(2020, 08).unwrap(),
};
assert_eq!(
verify_credential_validity_period(
chrono::DateTime::parse_from_rfc3339("2019-08-29T23:12:15Z")
.unwrap()
.to_utc(),
&validity,
),
Ok(())
);
assert_eq!(
verify_credential_validity_period(
chrono::DateTime::parse_from_rfc3339("2018-05-01T00:00:00Z")
.unwrap()
.to_utc(),
&validity,
),
Ok(())
);
assert_eq!(
verify_credential_validity_period(
chrono::DateTime::parse_from_rfc3339("2018-04-30T23:59:59Z")
.unwrap()
.to_utc(),
&validity,
),
Err(PresentationVerifyFailure::CredentialNotValidYet)
);
assert_eq!(
verify_credential_validity_period(
chrono::DateTime::parse_from_rfc3339("2020-08-31T23:59:59Z")
.unwrap()
.to_utc(),
&validity,
),
Ok(())
);
assert_eq!(
verify_credential_validity_period(
chrono::DateTime::parse_from_rfc3339("2020-09-01T00:00:00Z")
.unwrap()
.to_utc(),
&validity,
),
Err(PresentationVerifyFailure::CredentialExpired)
);
}
}