From 5a1e92de328e2ca5f00051e2a8004665d0b24437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bruus=20Zeppelin?= Date: Tue, 24 Sep 2024 15:30:32 +0200 Subject: [PATCH] Add function to create `VerifiablePresentation` from statement + context --- Cargo.lock | 5 +- Cargo.toml | 1 + src/id_proofs.rs | 22 ++-- src/types.rs | 2 +- src/web3id.rs | 262 +++++++++++++++++++++++++---------------------- 5 files changed, 158 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 959b078..a21b593 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,9 +454,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.32" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -546,6 +546,7 @@ dependencies = [ name = "concordium-wallet-crypto-uniffi" version = "4.1.0" dependencies = [ + "chrono", "concordium_base", "derive_more", "hex", diff --git a/Cargo.toml b/Cargo.toml index 845c65b..2b7a384 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ serde_json = "1.0" rand = "0.8.5" hex = { version = "0.4.3", features = ["serde"] } derive_more = "0.99.18" +chrono = "0.4.24" [dependencies.concordium_base] path = "./concordium-base/rust-src/concordium_base" diff --git a/src/id_proofs.rs b/src/id_proofs.rs index f5f7bb3..c0e62b2 100644 --- a/src/id_proofs.rs +++ b/src/id_proofs.rs @@ -21,8 +21,8 @@ use crate::{ /// attribute and prove that it is indeed the value inside the on-chain /// commitment. Since the verifier does not know the attribute value before /// seing the proof, the value is not present here. -#[derive(Serialize)] -pub struct RevealAttributeStatement { +#[derive(Serialize, Deserialize)] +pub struct RevealAttributeStatement { /// The attribute that the verifier wants the user to reveal. #[serde(rename = "attributeTag")] pub attribute_tag: Tag, @@ -34,8 +34,8 @@ pub type RevealAttributeStatementV1 = RevealAttributeStatement; /// For the case where the verifier wants the user to prove that an attribute is /// in a range. The statement is that the attribute value lies in `[lower, /// upper)` in the scalar field. -#[derive(Serialize)] -pub struct AttributeInRangeStatement { +#[derive(Serialize, Deserialize)] +pub struct AttributeInRangeStatement { /// The attribute that the verifier wants the user to prove is in a range. #[serde(rename = "attributeTag")] pub attribute_tag: Tag, @@ -54,8 +54,8 @@ pub type AttributeInRangeStatementV1 = AttributeInRangeStatement /// in a set of attributes. /// /// Serves as a uniFFI compatible bridge to [`concordium_base::id::id_proof_types::AttributeInSetStatement`] -#[derive(Serialize)] -pub struct AttributeInSetStatement { +#[derive(Serialize, Deserialize)] +pub struct AttributeInSetStatement { /// The attribute that the verifier wants the user prove lies in a set. #[serde(rename = "attributeTag")] pub attribute_tag: Tag, @@ -71,8 +71,8 @@ pub type AttributeInSetStatementV1 = AttributeInSetStatement; /// not in a set of attributes. /// /// Serves as a uniFFI compatible bridge to [`concordium_base::id::id_proof_types::AttributeNotInSetStatement`] -#[derive(Serialize)] -pub struct AttributeNotInSetStatement { +#[derive(Serialize, Deserialize)] +pub struct AttributeNotInSetStatement { /// The attribute that the verifier wants the user to prove does not lie in /// a set. #[serde(rename = "attributeTag")] @@ -85,9 +85,9 @@ pub struct AttributeNotInSetStatement { /// Serves as a uniFFI compatible bridge to [`concordium_base::id::id_proof_types::AttributeNotInSetStatement`] pub type AttributeNotInSetStatementV1 = AttributeNotInSetStatement; -#[derive(Serialize)] +#[derive(Serialize, Deserialize)] #[serde(tag = "type")] -pub enum AtomicStatement { +pub enum AtomicStatement { /// The atomic statement stating that an attribute should be revealed. RevealAttribute { #[serde(flatten)] @@ -115,7 +115,7 @@ pub type AtomicStatementV1 = AtomicStatement; #[derive(Serialize)] #[serde(transparent)] -pub struct Statement { +pub struct Statement { pub statements: Vec>, } diff --git a/src/types.rs b/src/types.rs index 5429fa4..476c6a1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -42,7 +42,7 @@ impl ConvertError for hex::FromHexError {} /// This should generally be used instead of hex string representation as it takes up half the space when compared to storing strings #[repr(transparent)] #[derive(Debug, Serialize, Deserialize, derive_more::From, Clone, PartialEq)] -pub struct Bytes(#[serde(with = "hex")] Vec); +pub struct Bytes(#[serde(with = "hex")] pub Vec); impl std::fmt::Display for Bytes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/web3id.rs b/src/web3id.rs index 012e8a9..5d9ac02 100644 --- a/src/web3id.rs +++ b/src/web3id.rs @@ -1,18 +1,27 @@ -use std::collections::HashMap; +use std::{ + collections::{BTreeSet, HashMap}, + time::{SystemTime, UNIX_EPOCH}, +}; +use chrono::{DateTime, Utc}; use concordium_base::{ base::ContractAddress, contracts_common::Timestamp, id::constants::{ArCurve, AttributeKind}, - web3id, + web3id::{self, Presentation}, }; +use uniffi::deps::anyhow::Context; -use crate::{AtomicProof, AtomicStatement, AtomicStatementV1, Bytes}; +use crate::{ + AtomicProof, AtomicStatement, AtomicStatementV1, Bytes, ConcordiumWalletCryptoError, + ConvertError, GlobalContext, +}; +/// Serves as a uniFFI compatible bridge to [`web3id::Web3IdAttribute`] pub enum Web3IdAttribute { String(String), Numeric(u64), - Timestamp { millis: u64 }, + Timestamp(SystemTime), } impl From<&Web3IdAttribute> for web3id::Web3IdAttribute { @@ -22,13 +31,28 @@ impl From<&Web3IdAttribute> for web3id::Web3IdAttribute { web3id::Web3IdAttribute::String(AttributeKind(value.to_string())) } Web3IdAttribute::Numeric(value) => web3id::Web3IdAttribute::Numeric(*value), - Web3IdAttribute::Timestamp { millis } => { - web3id::Web3IdAttribute::Timestamp(Timestamp { millis: *millis }) + Web3IdAttribute::Timestamp(value) => { + let v = DateTime::::from(*value); + web3id::Web3IdAttribute::Timestamp(Timestamp { + millis: v.timestamp_millis() as u64, + }) } } } } +impl From for Web3IdAttribute { + fn from(value: web3id::Web3IdAttribute) -> Self { + match value { + web3id::Web3IdAttribute::String(value) => Web3IdAttribute::String(value.0), + web3id::Web3IdAttribute::Numeric(value) => Web3IdAttribute::Numeric(value), + web3id::Web3IdAttribute::Timestamp(value) => Web3IdAttribute::Timestamp( + UNIX_EPOCH + std::time::Duration::from_millis(value.millis), + ), + } + } +} + impl serde::Serialize for Web3IdAttribute { fn serialize(&self, serializer: S) -> Result where @@ -73,39 +97,37 @@ pub enum VerifiableCredentialStatement { }, } -// NOTE: copied from the implementation from `concordium_base::web3id` -impl serde::Serialize for VerifiableCredentialStatement { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { +impl TryFrom + for web3id::CredentialStatement +{ + type Error = serde_json::Error; + + fn try_from(value: VerifiableCredentialStatement) -> Result { + let cred_statement = match value { VerifiableCredentialStatement::Account { network, cred_id, statement, - } => { - let json = serde_json::json!({ - "id": format!("did:ccd:{network}:cred:{cred_id}"), - "statement": statement, - }); - json.serialize(serializer) - } + } => Self::Account { + network: serde_json::to_value(network).and_then(serde_json::from_value)?, + cred_id: serde_json::to_value(cred_id).and_then(serde_json::from_value)?, + statement: serde_json::to_value(statement).and_then(serde_json::from_value)?, + }, VerifiableCredentialStatement::Web3Id { network, contract, holder_id, statement, cred_type, - } => { - let json = serde_json::json!({ - "type": cred_type, - "id": format!("did:ccd:{network}:sci:{}:{}/credentialEntry/{}", contract.index, contract.subindex, holder_id), - "statement": statement, - }); - json.serialize(serializer) - } - } + } => Self::Web3Id { + ty: BTreeSet::from_iter(cred_type), + network: serde_json::to_value(network).and_then(serde_json::from_value)?, + contract, + credential: serde_json::to_value(holder_id).and_then(serde_json::from_value)?, + statement: serde_json::to_value(statement).and_then(serde_json::from_value)?, + }, + }; + Ok(cred_statement) } } @@ -113,7 +135,6 @@ impl serde::Serialize for VerifiableCredentialStatement { /// comes separately. /// /// Serves as a uniFFI compatible bridge to [`web3id::Request`] -#[derive(serde::Serialize)] pub struct VerifiablePresentationRequest { pub challenge: Bytes, pub statements: Vec, @@ -123,7 +144,15 @@ impl TryFrom for web3id::Request Result { - serde_json::to_value(value).and_then(serde_json::from_value) + let converted = Self { + challenge: serde_json::to_value(value.challenge).and_then(serde_json::from_value)?, + credential_statements: value + .statements + .into_iter() + .map(web3id::CredentialStatement::try_from) + .collect::, _>>()?, + }; + Ok(converted) } } @@ -156,18 +185,59 @@ pub enum VerifiableCredentialCommitmentInputs { }, } +impl TryFrom + for web3id::OwnedCommitmentInputs< + ArCurve, + web3id::Web3IdAttribute, + concordium_base::ed25519::SecretKey, + > +{ + type Error = serde_json::Error; + + fn try_from(value: VerifiableCredentialCommitmentInputs) -> Result { + serde_json::to_value(value).and_then(serde_json::from_value) + } +} + +pub fn create_verifiable_presentation( + request: VerifiablePresentationRequest, + global: GlobalContext, + commitment_inputs: Vec, +) -> Result { + let fn_name = "create_verifiable_presentation"; + let request = + web3id::Request::try_from(request).map_err(|e| e.to_call_failed(fn_name.to_string()))?; + let global = concordium_base::id::types::GlobalContext::::try_from(global) + .map_err(|e| e.to_call_failed(fn_name.to_string()))?; + let commitment_inputs: Vec<_> = commitment_inputs + .into_iter() + .map(web3id::OwnedCommitmentInputs::try_from) + .collect::, _>>() + .map_err(|e| e.to_call_failed(fn_name.to_string()))?; + + let presentation = request + .prove(&global, commitment_inputs.iter().map(Into::into)) + .context("Failed to create verifiable presentation") + .map_err(|e| e.to_call_failed(fn_name.to_string()))?; + VerifiablePresentation::try_from(presentation) + .map_err(|e| e.to_call_failed(fn_name.to_string())) +} + +/// Serves as a uniFFI compatible bridge to [`id::id_proof_types::AtomicProof`] +pub type AtomicProofV2 = AtomicProof; + /// A pair of a statement and a proof. /// /// Serves as a uniFFI compatible bridge to [`web3id::StatementWithProof`] -pub struct StatementWithProof { - statement: AtomicStatement, - proof: AtomicProof, +pub struct StatementWithProof { + pub statement: AtomicStatement, + pub proof: AtomicProof, } /// Commitments signed by the issuer. /// /// Serves as a uniFFI compatible bridge to [`web3id::SignedCommitments`] -#[derive(serde::Serialize)] +#[derive(serde::Deserialize)] pub struct SignedCommitments { pub signature: Bytes, pub commitments: HashMap, @@ -181,9 +251,9 @@ pub struct SignedCommitments { /// Serves as a uniFFI compatible bridge to [`web3id::CredentialProof`] pub enum VerifiableCredentialProof { Account { - /// Creation timestamp of the proof. UNIX timestamp + /// Creation timestamp of the proof. /// RFC 3339 formatted datetime - created: String, + created: SystemTime, /// [`web3id::did::Network`] network: String, /// Reference to the credential to which this statement applies. @@ -196,7 +266,7 @@ pub enum VerifiableCredentialProof { Web3Id { /// Creation timestamp of the proof. /// RFC 3339 formatted datetime - created: String, + created: SystemTime, /// Owner of the credential, a public key. /// [`web3id::CredentialHolderId`]. holder_id: Bytes, @@ -216,85 +286,32 @@ pub enum VerifiableCredentialProof { }, } -// NOTE: copied from the implementation from `concordium_base::web3id` -impl serde::Serialize for VerifiableCredentialProof { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - VerifiableCredentialProof::Account { - created, - network, - cred_id, - issuer, - proofs, - } => { - let json = serde_json::json!({ - "type": ["VerifiableCredential", "ConcordiumVerifiableCredential"], - "issuer": format!("did:ccd:{network}:idp:{issuer}"), - "credentialSubject": { - "id": format!("did:ccd:{network}:cred:{cred_id}"), - "statement": proofs.iter().map(|x| &x.statement).collect::>(), - "proof": { - "type": "ConcordiumZKProofV3", - "created": created, - "proofValue": proofs.iter().map(|x| &x.proof).collect::>(), - } - } - }); - json.serialize(serializer) - } - VerifiableCredentialProof::Web3Id { - created, - network, - contract, - cred_type, - commitments, - proofs, - holder_id, - } => { - let json = serde_json::json!({ - "type": cred_type, - "issuer": format!("did:ccd:{network}:sci:{}:{}/issuer", contract.index, contract.subindex), - "credentialSubject": { - "id": format!("did:ccd:{network}:pkc:{}", holder_id), - "statement": proofs.iter().map(|x| &x.statement).collect::>(), - "proof": { - "type": "ConcordiumZKProofV3", - "created": created, - "commitments": commitments, - "proofValue": proofs.iter().map(|x| &x.proof).collect::>(), - } - } - }); - json.serialize(serializer) - } - } +impl TryFrom> + for VerifiableCredentialProof +{ + type Error = serde_json::Error; + + fn try_from( + value: web3id::CredentialProof, + ) -> Result { + todo!() } } -/// + /// A proof that establishes that the owner of the credential has indeed created /// the presentation. At present this is a list of signatures. /// /// Serves as a uniFFI compatible bridge to [`web3id::LinkingProof`] pub struct LinkingProof { - /// RFC 3339 formatted datetime - pub created: String, + pub created: SystemTime, pub proof_value: Vec, } -impl serde::Serialize for LinkingProof { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let json = serde_json::json!({ - "type": "ConcordiumWeakLinkingProofV1", - "created": self.created, - "proofValue": self.proof_value, - }); - json.serialize(serializer) +impl TryFrom for LinkingProof { + type Error = serde_json::Error; + + fn try_from(value: web3id::LinkingProof) -> Result { + todo!() } } @@ -311,18 +328,23 @@ pub struct VerifiablePresentation { pub linking_proof: LinkingProof, } -// NOTE: copied from the implementation from `concordium_base::web3id` -impl serde::Serialize for VerifiablePresentation { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let json = serde_json::json!({ - "type": "VerifiablePresentation", - "presentationContext": self.presentation_context, - "verifiableCredential": &self.verifiable_credential, - "proof": &self.linking_proof - }); - json.serialize(serializer) +impl TryFrom> for VerifiablePresentation { + type Error = serde_json::Error; + + fn try_from( + value: Presentation, + ) -> Result { + let verifiable_credential: Result, _> = value + .verifiable_credential + .into_iter() + .map(VerifiableCredentialProof::try_from) + .collect(); + let converted = Self { + presentation_context: serde_json::to_value(value.presentation_context) + .and_then(serde_json::from_value)?, + linking_proof: value.linking_proof.try_into()?, + verifiable_credential: verifiable_credential?, + }; + Ok(converted) } }