Skip to content

Commit

Permalink
Credentials crate & PresentationDefinitionV2 struct definition (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
amika-sq authored Nov 29, 2023
1 parent d6fe02a commit 694db64
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 0 deletions.
13 changes: 13 additions & 0 deletions crates/credentials/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "credentials"
version = "0.1.0"
edition = "2021"
homepage.workspace = true
repository.workspace = true
license-file.workspace = true

[dependencies]
jsonschema = "0.17.1"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
serde_with = "3.4.0"
1 change: 1 addition & 0 deletions crates/credentials/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod pex;
1 change: 1 addition & 0 deletions crates/credentials/src/pex/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod v2;
2 changes: 2 additions & 0 deletions crates/credentials/src/pex/v2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod presentation_definition;
pub use presentation_definition::*;
230 changes: 230 additions & 0 deletions crates/credentials/src/pex/v2/presentation_definition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use jsonschema::{Draft, JSONSchema};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use serde_with::skip_serializing_none;
use std::collections::HashMap;

/// Presentation Exchange
///
/// Presentation Exchange specification codifies a Presentation Definition data format Verifiers
/// can use to articulate proof requirements, and a Presentation Submission data format Holders can
/// use to describe proofs submitted in accordance with them.
///
/// See [Presentation Definition](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition)
/// for more information.
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct PresentationDefinition {
pub id: String,
pub name: Option<String>,
pub purpose: Option<String>,
pub format: Option<Format>,
pub submission_requirements: Option<Vec<SubmissionRequirement>>,
pub input_descriptors: Vec<InputDescriptor>,
pub frame: Option<HashMap<String, JsonValue>>,
}

/// Represents an input descriptor in a presentation definition.
///
/// See [Input Descriptor](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object)
/// for more information.
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct InputDescriptor {
pub id: String,
pub name: Option<String>,
pub purpose: Option<String>,
pub format: Option<Format>,
pub constraints: Constraints,
}

/// Represents constraints for an input descriptor.
///
/// See 'constraints object' defined in
/// [Input Descriptor](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object)
/// for more information.
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Constraints {
pub fields: Option<Vec<Field>>,
pub limit_disclosure: Option<ConformantConsumerDisclosure>,
}

/// Represents a field in a presentation input descriptor.
///
/// See 'fields object' as defined in
/// [Input Descriptor](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object)
/// for more information.
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Field {
pub id: Option<String>,
pub path: Vec<String>,
pub purpose: Option<String>,
pub filter: Option<JsonValue>,
pub predicate: Option<Optionality>,
pub name: Option<String>,
pub optional: Option<bool>,
}

impl Field {
pub fn filter_schema(&self) -> Option<JSONSchema> {
self.filter
.as_ref()
.map(|json| {
JSONSchema::options()
.with_draft(Draft::Draft7)
.compile(json)
.ok()
})
.flatten()
}
}

/// Enumeration representing consumer disclosure options.
///
/// Represents the possible values of `limit_disclosure' property as defined in
// [Input Descriptor](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object)
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConformantConsumerDisclosure {
Required,
Preferred,
}

/// Represents the format of a presentation definition
///
/// See `format` as defined in
/// [Input Descriptor](https://identity.foundation/presentation-exchange/spec/v2.0.0/#input-descriptor-object)
/// and [Registry](https://identity.foundation/claim-format-registry/#registry)
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Format {
pub jwt: Option<JwtObject>,
pub jwt_vc: Option<JwtObject>,
pub jwt_vp: Option<JwtObject>,
}

/// Represents a JWT object.
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct JwtObject {
pub alg: Vec<String>,
}

/// Represents submission requirements for a presentation definition.
#[skip_serializing_none]
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct SubmissionRequirement {
pub name: Option<String>,
pub purpose: Option<String>,
pub rule: Rule,
pub count: Option<u32>,
pub min: Option<u32>,
pub max: Option<u32>,
pub from: Option<String>,
pub from_nested: Option<Vec<SubmissionRequirement>>,
}

/// Enumeration representing presentation rule options.
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Rule {
#[default]
All,
Pick,
}

/// Enumeration representing optionality.
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Optionality {
Required,
Preferred,
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::fs;
use std::path::Path;

#[test]
fn can_serialize() {
let pd = PresentationDefinition {
id: "tests-pd-id".to_string(),
name: "simple PD".to_string().into(),
purpose: "pd for testing".to_string().into(),
input_descriptors: vec![InputDescriptor {
id: "whatever".to_string(),
purpose: "purpose".to_string().into(),
constraints: Constraints {
fields: vec![Field {
id: "field-id".to_string().into(),
path: vec!["$.issuer".to_string()],
purpose: "purpose".to_string().into(),
filter: json!({"type": "string", "const": "123"}).into(),
..Default::default()
}]
.into(),
limit_disclosure: Some(ConformantConsumerDisclosure::Required),
},
..Default::default()
}],
..Default::default()
};

let serialized_pd = serde_json::to_string(&pd).unwrap();

assert!(serialized_pd.contains("input_descriptors"));
assert!(serialized_pd.contains("123"));
}

#[test]
fn can_deserialize() {
let expected_id = "ec11a434-fe24-479b-aae0-511428b37e4f";

let expected_format = Format {
jwt_vc: Some(JwtObject {
alg: vec!["ES256K".to_string(), "EdDSA".to_string()],
}),
..Default::default()
};

let expected_input_descriptors = vec![InputDescriptor {
id: "7b928839-f0b1-4237-893d-b27124b57952".to_string(),
constraints: Constraints {
fields: Some(vec![
Field {
path: vec!["$.iss".to_string(), "$.vc.issuer".to_string()],
filter: Some(json!({"type": "string", "pattern": "^did:[^:]+:.+"})),
..Default::default()
},
Field {
path: vec!["$.vc.type[*]".to_string(), "$.type[*]".to_string()],
filter: Some(json!({"type": "string", "const": "SanctionsCredential"})),
..Default::default()
},
]),
..Default::default()
},
..Default::default()
}];

let pd_string = load_json("tests/resources/pd_sanctions.json");
let deserialized_pd: PresentationDefinition = serde_json::from_str(&pd_string).unwrap();

assert_eq!(deserialized_pd.id, expected_id);
assert_eq!(deserialized_pd.format, Some(expected_format));
assert_eq!(
deserialized_pd.input_descriptors,
expected_input_descriptors
);
}

fn load_json(path: &str) -> String {
let path = Path::new(path);
let json = fs::read_to_string(path).expect("Unable to load json file");
json
}
}
40 changes: 40 additions & 0 deletions crates/credentials/tests/resources/pd_sanctions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"id": "ec11a434-fe24-479b-aae0-511428b37e4f",
"format": {
"jwt_vc": {
"alg": [
"ES256K",
"EdDSA"
]
}
},
"input_descriptors": [
{
"id": "7b928839-f0b1-4237-893d-b27124b57952",
"constraints": {
"fields": [
{
"path": [
"$.iss",
"$.vc.issuer"
],
"filter": {
"type": "string",
"pattern": "^did:[^:]+:.+"
}
},
{
"path": [
"$.vc.type[*]",
"$.type[*]"
],
"filter": {
"type": "string",
"const": "SanctionsCredential"
}
}
]
}
}
]
}

0 comments on commit 694db64

Please sign in to comment.