concordium_base/web3id/
mod.rs

1//! Functionality related to constructing and verifying Web3ID proofs.
2//!
3//! The main entrypoints in this module are the [`verify`](Presentation::verify)
4//! function for verifying [`Presentation`]s in the context of given public
5//! data, and the [`prove`](Request::prove) function for constructing a proof.
6
7pub mod did;
8
9// TODO:
10// - Documentation.
11use crate::{
12    base::CredentialRegistrationID,
13    cis4_types::IssuerKey,
14    common::{base16_decode_string, base16_encode_string},
15    curve_arithmetic::Curve,
16    id::{
17        constants::{ArCurve, AttributeKind},
18        id_proof_types::{AtomicProof, AtomicStatement, ProofVersion},
19        types::{Attribute, AttributeTag, GlobalContext, IpIdentity},
20    },
21    pedersen_commitment,
22    random_oracle::RandomOracle,
23};
24use concordium_contracts_common::{
25    hashes::HashBytes, ContractAddress, OwnedEntrypointName, OwnedParameter, Timestamp,
26};
27use did::*;
28use ed25519_dalek::Verifier;
29use serde::de::DeserializeOwned;
30use std::{
31    collections::{BTreeMap, BTreeSet},
32    marker::PhantomData,
33    str::FromStr,
34};
35
36/// Domain separation string used when the issuer signs the commitments.
37pub const COMMITMENT_SIGNATURE_DOMAIN_STRING: &[u8] = b"WEB3ID:COMMITMENTS";
38
39/// Domain separation string used when signing the revoke transaction
40/// using the credential secret key.
41pub const REVOKE_DOMAIN_STRING: &[u8] = b"WEB3ID:REVOKE";
42
43/// Domain separation string used when signing the linking proof using
44/// the credential secret key.
45pub const LINKING_DOMAIN_STRING: &[u8] = b"WEB3ID:LINKING";
46
47/// A statement about a single credential, either an identity credential or a
48/// Web3 credential.
49#[derive(Debug, Clone, serde::Deserialize, PartialEq, Eq)]
50#[serde(
51    try_from = "serde_json::Value",
52    bound(deserialize = "C: Curve, AttributeType: Attribute<C::Scalar> + DeserializeOwned")
53)]
54pub enum CredentialStatement<C: Curve, AttributeType: Attribute<C::Scalar>> {
55    /// Statement about a credential derived from an identity issued by an
56    /// identity provider.
57    Account {
58        network: Network,
59        cred_id: CredentialRegistrationID,
60        statement: Vec<AtomicStatement<C, AttributeTag, AttributeType>>,
61    },
62    /// Statement about a credential issued by a Web3 identity provider, a smart
63    /// contract.
64    Web3Id {
65        /// The credential type. This is chosen by the provider to provide
66        /// some information about what the credential is about.
67        ty: BTreeSet<String>,
68        network: Network,
69        /// Reference to a specific smart contract instance that issued the
70        /// credential.
71        contract: ContractAddress,
72        /// Credential identifier inside the contract.
73        credential: CredentialHolderId,
74        statement: Vec<AtomicStatement<C, String, AttributeType>>,
75    },
76}
77
78impl<C: Curve, AttributeType: Attribute<C::Scalar> + DeserializeOwned> TryFrom<serde_json::Value>
79    for CredentialStatement<C, AttributeType>
80{
81    type Error = anyhow::Error;
82
83    fn try_from(mut value: serde_json::Value) -> Result<Self, Self::Error> {
84        let id_value = get_field(&mut value, "id")?;
85        let Some(Ok((_, id))) = id_value.as_str().map(parse_did) else {
86            anyhow::bail!("id field is not a valid DID");
87        };
88        match id.ty {
89            IdentifierType::Credential { cred_id } => {
90                let statement = get_field(&mut value, "statement")?;
91                Ok(Self::Account {
92                    network: id.network,
93                    cred_id,
94                    statement: serde_json::from_value(statement)?,
95                })
96            }
97            IdentifierType::ContractData {
98                address,
99                entrypoint,
100                parameter,
101            } => {
102                let statement = get_field(&mut value, "statement")?;
103                let ty = get_field(&mut value, "type")?;
104                anyhow::ensure!(entrypoint == "credentialEntry", "Invalid entrypoint.");
105                Ok(Self::Web3Id {
106                    ty: serde_json::from_value(ty)?,
107                    network: id.network,
108                    contract: address,
109                    credential: CredentialHolderId::new(ed25519_dalek::VerifyingKey::from_bytes(
110                        &parameter.as_ref().try_into()?,
111                    )?),
112                    statement: serde_json::from_value(statement)?,
113                })
114            }
115            _ => {
116                anyhow::bail!("Only ID credentials and Web3 credentials are supported.")
117            }
118        }
119    }
120}
121
122impl<C: Curve, AttributeType: Attribute<C::Scalar> + serde::Serialize> serde::Serialize
123    for CredentialStatement<C, AttributeType>
124{
125    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126    where
127        S: serde::Serializer,
128    {
129        match self {
130            CredentialStatement::Account {
131                network,
132                cred_id,
133                statement,
134            } => {
135                let json = serde_json::json!({
136                    "id": format!("did:ccd:{network}:cred:{cred_id}"),
137                    "statement": statement,
138                });
139                json.serialize(serializer)
140            }
141            CredentialStatement::Web3Id {
142                network,
143                contract,
144                credential,
145                statement,
146                ty,
147            } => {
148                let json = serde_json::json!({
149                    "type": ty,
150                    "id": format!("did:ccd:{network}:sci:{}:{}/credentialEntry/{}", contract.index, contract.subindex, credential),
151                    "statement": statement,
152                });
153                json.serialize(serializer)
154            }
155        }
156    }
157}
158
159/// A pair of a statement and a proof.
160pub type StatementWithProof<C, TagType, AttributeType> = (
161    AtomicStatement<C, TagType, AttributeType>,
162    AtomicProof<C, AttributeType>,
163);
164
165/// Metadata of a single credential.
166pub enum CredentialMetadata {
167    /// Metadata of an account credential, i.e., a credential derived from an
168    /// identity object.
169    Account {
170        issuer: IpIdentity,
171        cred_id: CredentialRegistrationID,
172    },
173    /// Metadata of a Web3Id credential.
174    Web3Id {
175        contract: ContractAddress,
176        holder: CredentialHolderId,
177    },
178}
179
180/// Metadata about a single [`CredentialProof`].
181pub struct ProofMetadata {
182    /// Timestamp of when the proof was created.
183    pub created: chrono::DateTime<chrono::Utc>,
184    pub network: Network,
185    /// The DID of the credential the proof is about.
186    pub cred_metadata: CredentialMetadata,
187}
188
189impl<C: Curve, AttributeType: Attribute<C::Scalar>> CredentialProof<C, AttributeType> {
190    pub fn metadata(&self) -> ProofMetadata {
191        match self {
192            CredentialProof::Account {
193                created,
194                network,
195                cred_id,
196                issuer,
197                proofs: _,
198            } => ProofMetadata {
199                created: *created,
200                network: *network,
201                cred_metadata: CredentialMetadata::Account {
202                    issuer: *issuer,
203                    cred_id: *cred_id,
204                },
205            },
206            CredentialProof::Web3Id {
207                created,
208                holder,
209                network,
210                contract,
211                ty: _,
212                commitments: _,
213                proofs: _,
214            } => ProofMetadata {
215                created: *created,
216                network: *network,
217                cred_metadata: CredentialMetadata::Web3Id {
218                    contract: *contract,
219                    holder: *holder,
220                },
221            },
222        }
223    }
224
225    /// Extract the statement from the proof.
226    pub fn statement(&self) -> CredentialStatement<C, AttributeType> {
227        match self {
228            CredentialProof::Account {
229                network,
230                cred_id,
231                proofs,
232                ..
233            } => CredentialStatement::Account {
234                network: *network,
235                cred_id: *cred_id,
236                statement: proofs.iter().map(|(x, _)| x.clone()).collect(),
237            },
238            CredentialProof::Web3Id {
239                holder,
240                network,
241                contract,
242                ty,
243                proofs,
244                ..
245            } => CredentialStatement::Web3Id {
246                ty: ty.clone(),
247                network: *network,
248                contract: *contract,
249                credential: *holder,
250                statement: proofs.iter().map(|(x, _)| x.clone()).collect(),
251            },
252        }
253    }
254}
255
256#[derive(Clone, serde::Deserialize)]
257#[serde(bound(deserialize = "C: Curve, AttributeType: Attribute<C::Scalar> + DeserializeOwned"))]
258#[serde(try_from = "serde_json::Value")]
259/// A proof corresponding to one [`CredentialStatement`]. This contains almost
260/// all the information needed to verify it, except the issuer's public key in
261/// case of the `Web3Id` proof, and the public commitments in case of the
262/// `Account` proof.
263pub enum CredentialProof<C: Curve, AttributeType: Attribute<C::Scalar>> {
264    Account {
265        /// Creation timestamp of the proof.
266        created: chrono::DateTime<chrono::Utc>,
267        network: Network,
268        /// Reference to the credential to which this statement applies.
269        cred_id: CredentialRegistrationID,
270        /// Issuer of this credential, the identity provider index on the
271        /// relevant network.
272        issuer: IpIdentity,
273        proofs: Vec<StatementWithProof<C, AttributeTag, AttributeType>>,
274    },
275    Web3Id {
276        /// Creation timestamp of the proof.
277        created: chrono::DateTime<chrono::Utc>,
278        /// Owner of the credential, a public key.
279        holder: CredentialHolderId,
280        network: Network,
281        /// Reference to a specific smart contract instance.
282        contract: ContractAddress,
283        /// The credential type. This is chosen by the provider to provide
284        /// some information about what the credential is about.
285        ty: BTreeSet<String>,
286        /// Commitments that the user has. These are all the commitments that
287        /// are part of the credential, indexed by the attribute tag.
288        commitments: SignedCommitments<C>,
289        /// Individual proofs for statements.
290        proofs: Vec<StatementWithProof<C, String, AttributeType>>,
291    },
292}
293
294/// Commitments signed by the issuer.
295#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, crate::common::Serialize)]
296#[serde(bound = "C: Curve")]
297pub struct SignedCommitments<C: Curve> {
298    #[serde(
299        serialize_with = "crate::common::base16_encode",
300        deserialize_with = "crate::common::base16_decode"
301    )]
302    pub signature: ed25519_dalek::Signature,
303    pub commitments: BTreeMap<String, pedersen_commitment::Commitment<C>>,
304}
305
306impl<C: Curve> SignedCommitments<C> {
307    /// Verify signatures on the commitments in the context of the holder's
308    /// public key, and the issuer contract.
309    pub fn verify_signature(
310        &self,
311        holder: &CredentialHolderId,
312        issuer_pk: &IssuerKey,
313        issuer_contract: ContractAddress,
314    ) -> bool {
315        use crate::common::Serial;
316        let mut data = COMMITMENT_SIGNATURE_DOMAIN_STRING.to_vec();
317        holder.serial(&mut data);
318        issuer_contract.serial(&mut data);
319        self.commitments.serial(&mut data);
320        issuer_pk.public_key.verify(&data, &self.signature).is_ok()
321    }
322
323    /// Sign commitments for the owner.
324    pub fn from_commitments(
325        commitments: BTreeMap<String, pedersen_commitment::Commitment<C>>,
326        holder: &CredentialHolderId,
327        signer: &impl Web3IdSigner,
328        issuer_contract: ContractAddress,
329    ) -> Self {
330        use crate::common::Serial;
331        let mut data = COMMITMENT_SIGNATURE_DOMAIN_STRING.to_vec();
332        holder.serial(&mut data);
333        issuer_contract.serial(&mut data);
334        commitments.serial(&mut data);
335        Self {
336            signature: signer.sign(&data),
337            commitments,
338        }
339    }
340
341    pub fn from_secrets<AttributeType: Attribute<C::Scalar>>(
342        global: &GlobalContext<C>,
343        values: &BTreeMap<String, AttributeType>,
344        randomness: &BTreeMap<String, pedersen_commitment::Randomness<C>>,
345        holder: &CredentialHolderId,
346        signer: &impl Web3IdSigner,
347        issuer_contract: ContractAddress,
348    ) -> Option<Self> {
349        // TODO: This is a bit inefficient. We don't need the intermediate map, we can
350        // just serialize directly.
351
352        let cmm_key = &global.on_chain_commitment_key;
353        let mut commitments = BTreeMap::new();
354        for ((vi, value), (ri, randomness)) in values.iter().zip(randomness.iter()) {
355            if vi != ri {
356                return None;
357            }
358            commitments.insert(
359                ri.clone(),
360                cmm_key.hide(
361                    &pedersen_commitment::Value::<C>::new(value.to_field_element()),
362                    randomness,
363                ),
364            );
365        }
366        Some(Self::from_commitments(
367            commitments,
368            holder,
369            signer,
370            issuer_contract,
371        ))
372    }
373}
374
375impl<C: Curve, AttributeType: Attribute<C::Scalar> + serde::Serialize> serde::Serialize
376    for CredentialProof<C, AttributeType>
377{
378    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
379    where
380        S: serde::Serializer,
381    {
382        match self {
383            CredentialProof::Account {
384                created,
385                network,
386                cred_id,
387                issuer,
388                proofs,
389            } => {
390                let json = serde_json::json!({
391                    "type": ["VerifiableCredential", "ConcordiumVerifiableCredential"],
392                    "issuer": format!("did:ccd:{network}:idp:{issuer}"),
393                    "credentialSubject": {
394                        "id": format!("did:ccd:{network}:cred:{cred_id}"),
395                        "statement": proofs.iter().map(|x| &x.0).collect::<Vec<_>>(),
396                        "proof": {
397                            "type": "ConcordiumZKProofV3",
398                            "created": created,
399                            "proofValue": proofs.iter().map(|x| &x.1).collect::<Vec<_>>(),
400                        }
401                    }
402                });
403                json.serialize(serializer)
404            }
405            CredentialProof::Web3Id {
406                created,
407                network,
408                contract,
409                ty,
410                commitments,
411                proofs,
412                holder,
413            } => {
414                let json = serde_json::json!({
415                    "type": ty,
416                    "issuer": format!("did:ccd:{network}:sci:{}:{}/issuer", contract.index, contract.subindex),
417                    "credentialSubject": {
418                        "id": format!("did:ccd:{network}:pkc:{}", holder),
419                        "statement": proofs.iter().map(|x| &x.0).collect::<Vec<_>>(),
420                        "proof": {
421                            "type": "ConcordiumZKProofV3",
422                            "created": created,
423                            "commitments": commitments,
424                            "proofValue": proofs.iter().map(|x| &x.1).collect::<Vec<_>>(),
425                        }
426                    }
427                });
428                json.serialize(serializer)
429            }
430        }
431    }
432}
433
434/// Extract the value at the given key. This mutates the `value` replacing the
435/// value at the provided key with `Null`.
436fn get_field(
437    value: &mut serde_json::Value,
438    field: &'static str,
439) -> anyhow::Result<serde_json::Value> {
440    match value.get_mut(field) {
441        Some(v) => Ok(v.take()),
442        None => anyhow::bail!("Field {field} is not present."),
443    }
444}
445
446/// Extract an optional value at the given key. This mutates the `value`
447/// replacing the value at the provided key with `Null`.
448fn get_optional_field(
449    value: &mut serde_json::Value,
450    field: &'static str,
451) -> anyhow::Result<serde_json::Value> {
452    match value.get_mut(field) {
453        Some(v) => Ok(v.take()),
454        None => Ok(serde_json::Value::Null),
455    }
456}
457
458impl<C: Curve, AttributeType: Attribute<C::Scalar> + serde::de::DeserializeOwned>
459    TryFrom<serde_json::Value> for CredentialProof<C, AttributeType>
460{
461    type Error = anyhow::Error;
462
463    fn try_from(mut value: serde_json::Value) -> Result<Self, Self::Error> {
464        let issuer: String = serde_json::from_value(get_field(&mut value, "issuer")?)?;
465        let ty: BTreeSet<String> = serde_json::from_value(get_field(&mut value, "type")?)?;
466        anyhow::ensure!(
467            ty.contains("VerifiableCredential") && ty.contains("ConcordiumVerifiableCredential")
468        );
469        let mut credential_subject = get_field(&mut value, "credentialSubject")?;
470        let issuer = parse_did(&issuer)
471            .map_err(|e| anyhow::anyhow!("Unable to parse issuer: {e}"))?
472            .1;
473        match issuer.ty {
474            IdentifierType::Idp { idp_identity } => {
475                let id = get_field(&mut credential_subject, "id")?;
476                let Some(Ok(id)) = id.as_str().map(parse_did) else {
477                    anyhow::bail!("Credential ID invalid.")
478                };
479                let IdentifierType::Credential { cred_id } = id.1.ty else {
480                    anyhow::bail!("Credential identifier must be a public key.")
481                };
482                anyhow::ensure!(issuer.network == id.1.network);
483                let statement: Vec<AtomicStatement<_, _, _>> =
484                    serde_json::from_value(get_field(&mut credential_subject, "statement")?)?;
485
486                let mut proof = get_field(&mut credential_subject, "proof")?;
487
488                anyhow::ensure!(
489                    get_field(&mut proof, "type")?.as_str() == Some("ConcordiumZKProofV3")
490                );
491                let created = serde_json::from_value::<chrono::DateTime<chrono::Utc>>(get_field(
492                    &mut proof, "created",
493                )?)?;
494
495                let proof_value: Vec<_> =
496                    serde_json::from_value(get_field(&mut proof, "proofValue")?)?;
497
498                anyhow::ensure!(proof_value.len() == statement.len());
499                let proofs = statement.into_iter().zip(proof_value).collect();
500                Ok(Self::Account {
501                    created,
502                    network: issuer.network,
503                    cred_id,
504                    issuer: idp_identity,
505                    proofs,
506                })
507            }
508            IdentifierType::ContractData {
509                address,
510                entrypoint,
511                parameter,
512            } => {
513                anyhow::ensure!(entrypoint == "issuer", "Invalid issuer DID.");
514                anyhow::ensure!(
515                    parameter.as_ref().is_empty(),
516                    "Issuer must have an empty parameter."
517                );
518                let id = get_field(&mut credential_subject, "id")?;
519                let Some(Ok(id)) = id.as_str().map(parse_did) else {
520                    anyhow::bail!("Credential ID invalid.")
521                };
522                let IdentifierType::PublicKey { key } = id.1.ty else {
523                    anyhow::bail!("Credential identifier must be a public key.")
524                };
525                anyhow::ensure!(issuer.network == id.1.network);
526                // Make sure that the id's point to the same credential.
527                let statement: Vec<AtomicStatement<_, _, _>> =
528                    serde_json::from_value(get_field(&mut credential_subject, "statement")?)?;
529
530                let mut proof = get_field(&mut credential_subject, "proof")?;
531
532                anyhow::ensure!(
533                    get_field(&mut proof, "type")?.as_str() == Some("ConcordiumZKProofV3")
534                );
535                let created = serde_json::from_value::<chrono::DateTime<chrono::Utc>>(get_field(
536                    &mut proof, "created",
537                )?)?;
538
539                let commitments = serde_json::from_value(get_field(&mut proof, "commitments")?)?;
540
541                let proof_value: Vec<_> =
542                    serde_json::from_value(get_field(&mut proof, "proofValue")?)?;
543
544                anyhow::ensure!(proof_value.len() == statement.len());
545                let proofs = statement.into_iter().zip(proof_value).collect();
546
547                Ok(Self::Web3Id {
548                    created,
549                    holder: CredentialHolderId::new(key),
550                    network: issuer.network,
551                    contract: address,
552                    commitments,
553                    proofs,
554                    ty,
555                })
556            }
557            _ => anyhow::bail!("Only IDPs and smart contracts can be issuers."),
558        }
559    }
560}
561
562impl<C: Curve, AttributeType: Attribute<C::Scalar>> crate::common::Serial
563    for CredentialProof<C, AttributeType>
564{
565    fn serial<B: crate::common::Buffer>(&self, out: &mut B) {
566        match self {
567            CredentialProof::Account {
568                created,
569                network,
570                cred_id,
571                proofs,
572                issuer,
573            } => {
574                0u8.serial(out);
575                created.timestamp_millis().serial(out);
576                network.serial(out);
577                cred_id.serial(out);
578                issuer.serial(out);
579                proofs.serial(out)
580            }
581            CredentialProof::Web3Id {
582                created,
583                network,
584                contract,
585                commitments,
586                proofs,
587                holder,
588                ty,
589            } => {
590                1u8.serial(out);
591                created.timestamp_millis().serial(out);
592                let len = ty.len() as u8;
593                len.serial(out);
594                for s in ty {
595                    (s.len() as u16).serial(out);
596                    out.write_all(s.as_bytes())
597                        .expect("Writing to buffer succeeds.");
598                }
599                network.serial(out);
600                contract.serial(out);
601                holder.serial(out);
602                commitments.serial(out);
603                proofs.serial(out)
604            }
605        }
606    }
607}
608
609#[doc(hidden)]
610#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
611/// Used as a phantom type to indicate a Web3ID challenge.
612pub enum Web3IdChallengeMarker {}
613
614/// Challenge string that serves as a distinguishing context when requesting
615/// proofs.
616pub type Challenge = HashBytes<Web3IdChallengeMarker>;
617
618#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
619#[serde(rename_all = "camelCase")]
620#[serde(bound(
621    serialize = "C: Curve, AttributeType: Attribute<C::Scalar> + serde::Serialize",
622    deserialize = "C: Curve, AttributeType: Attribute<C::Scalar> + DeserializeOwned"
623))]
624/// A request for a proof. This is the statement and challenge. The secret data
625/// comes separately.
626pub struct Request<C: Curve, AttributeType: Attribute<C::Scalar>> {
627    pub challenge: Challenge,
628    pub credential_statements: Vec<CredentialStatement<C, AttributeType>>,
629}
630
631#[repr(transparent)]
632#[doc(hidden)]
633/// An ed25519 public key tagged with a phantom type parameter based on its
634/// role, e.g., an owner of a credential or a revocation key.
635pub struct Ed25519PublicKey<Role> {
636    pub public_key: ed25519_dalek::VerifyingKey,
637    phantom: PhantomData<Role>,
638}
639
640impl<Role> From<ed25519_dalek::VerifyingKey> for Ed25519PublicKey<Role> {
641    fn from(value: ed25519_dalek::VerifyingKey) -> Self {
642        Self::new(value)
643    }
644}
645
646impl<Role> serde::Serialize for Ed25519PublicKey<Role> {
647    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
648    where
649        S: serde::Serializer,
650    {
651        let s = self.to_string();
652        s.serialize(serializer)
653    }
654}
655
656impl<'de, Role> serde::Deserialize<'de> for Ed25519PublicKey<Role> {
657    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
658    where
659        D: serde::Deserializer<'de>,
660    {
661        use serde::de::Error;
662        let s: String = String::deserialize(deserializer)?;
663        s.try_into().map_err(D::Error::custom)
664    }
665}
666
667#[derive(thiserror::Error, Debug)]
668pub enum Ed25519PublicKeyFromStrError {
669    #[error("Not a valid hex string: {0}")]
670    InvalidHex(#[from] hex::FromHexError),
671    #[error("Not a valid representation of a public key: {0}")]
672    InvalidBytes(#[from] ed25519_dalek::SignatureError),
673}
674
675impl<Role> TryFrom<String> for Ed25519PublicKey<Role> {
676    type Error = Ed25519PublicKeyFromStrError;
677
678    fn try_from(value: String) -> Result<Self, Self::Error> {
679        Self::try_from(value.as_str())
680    }
681}
682
683impl<Role> FromStr for Ed25519PublicKey<Role> {
684    type Err = Ed25519PublicKeyFromStrError;
685
686    fn from_str(s: &str) -> Result<Self, Self::Err> {
687        Self::try_from(s)
688    }
689}
690
691impl<Role> TryFrom<&str> for Ed25519PublicKey<Role> {
692    type Error = Ed25519PublicKeyFromStrError;
693
694    fn try_from(value: &str) -> Result<Self, Self::Error> {
695        let bytes: [u8; 32] = hex::decode(value)?.try_into().map_err(|_| {
696            Self::Error::InvalidBytes(ed25519_dalek::SignatureError::from_source(
697                "Incorrect public key length.",
698            ))
699        })?;
700        Ok(Self::new(ed25519_dalek::VerifyingKey::from_bytes(&bytes)?))
701    }
702}
703
704impl<Role> Ed25519PublicKey<Role> {
705    pub fn new(public_key: ed25519_dalek::VerifyingKey) -> Self {
706        Self {
707            public_key,
708            phantom: PhantomData,
709        }
710    }
711}
712
713impl<Role> std::fmt::Debug for Ed25519PublicKey<Role> {
714    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
715        for byte in self.public_key.as_bytes() {
716            write!(f, "{:02x}", byte)?;
717        }
718        Ok(())
719    }
720}
721
722impl<Role> std::fmt::Display for Ed25519PublicKey<Role> {
723    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
724        for byte in self.public_key.as_bytes() {
725            write!(f, "{:02x}", byte)?;
726        }
727        Ok(())
728    }
729}
730
731// Manual trait implementations to avoid bounds on the `Role` parameter.
732impl<Role> Eq for Ed25519PublicKey<Role> {}
733
734impl<Role> PartialEq for Ed25519PublicKey<Role> {
735    fn eq(&self, other: &Self) -> bool {
736        self.public_key.eq(&other.public_key)
737    }
738}
739
740impl<Role> Clone for Ed25519PublicKey<Role> {
741    fn clone(&self) -> Self {
742        *self
743    }
744}
745
746impl<Role> Copy for Ed25519PublicKey<Role> {}
747
748impl<Role> crate::contracts_common::Serial for Ed25519PublicKey<Role> {
749    fn serial<W: crate::contracts_common::Write>(&self, out: &mut W) -> Result<(), W::Err> {
750        out.write_all(self.public_key.as_bytes())
751    }
752}
753
754impl<Role> crate::contracts_common::Deserial for Ed25519PublicKey<Role> {
755    fn deserial<R: crate::contracts_common::Read>(
756        source: &mut R,
757    ) -> crate::contracts_common::ParseResult<Self> {
758        let public_key_bytes = <[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::deserial(source)?;
759        let public_key = ed25519_dalek::VerifyingKey::from_bytes(&public_key_bytes)
760            .map_err(|_| crate::contracts_common::ParseError {})?;
761        Ok(Self {
762            public_key,
763            phantom: PhantomData,
764        })
765    }
766}
767
768impl<Role> crate::common::Serial for Ed25519PublicKey<Role> {
769    fn serial<W: crate::common::Buffer>(&self, out: &mut W) {
770        out.write_all(self.public_key.as_bytes())
771            .expect("Writing to buffer always succeeds.");
772    }
773}
774
775impl<Role> crate::common::Deserial for Ed25519PublicKey<Role> {
776    fn deserial<R: std::io::Read>(source: &mut R) -> crate::common::ParseResult<Self> {
777        use anyhow::Context;
778        let public_key_bytes = <[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::deserial(source)?;
779        let public_key = ed25519_dalek::VerifyingKey::from_bytes(&public_key_bytes)
780            .context("Invalid public key.")?;
781        Ok(Self {
782            public_key,
783            phantom: PhantomData,
784        })
785    }
786}
787
788#[doc(hidden)]
789pub enum CredentialHolderIdRole {}
790
791/// The owner of a Web3Id credential.
792pub type CredentialHolderId = Ed25519PublicKey<CredentialHolderIdRole>;
793
794#[derive(serde::Deserialize)]
795#[serde(bound(deserialize = "C: Curve, AttributeType: Attribute<C::Scalar> + DeserializeOwned"))]
796#[serde(try_from = "serde_json::Value")]
797/// A presentation is the response to a [`Request`]. It contains proofs for
798/// statements, ownership proof for all Web3 credentials, and a context. The
799/// only missing part to verify the proof are the public commitments.
800pub struct Presentation<C: Curve, AttributeType: Attribute<C::Scalar>> {
801    pub presentation_context: Challenge,
802    pub verifiable_credential: Vec<CredentialProof<C, AttributeType>>,
803    /// Signatures from keys of Web3 credentials (not from ID credentials).
804    /// The order is the same as that in the `credential_proofs` field.
805    pub linking_proof: LinkingProof,
806}
807
808#[derive(Debug, thiserror::Error)]
809#[non_exhaustive]
810pub enum PresentationVerificationError {
811    #[error("The linking proof was incomplete.")]
812    MissingLinkingProof,
813    #[error("The linking proof had extra signatures.")]
814    ExcessiveLinkingProof,
815    #[error("The linking proof was not valid.")]
816    InvalidLinkinProof,
817    #[error("The public data did not match the credentials.")]
818    InconsistentPublicData,
819    #[error("The credential was not valid.")]
820    InvalidCredential,
821}
822
823impl<C: Curve, AttributeType: Attribute<C::Scalar>> Presentation<C, AttributeType> {
824    /// Get an iterator over the metadata for each of the verifiable credentials
825    /// in the order they appear in the presentation.
826    pub fn metadata(&self) -> impl ExactSizeIterator<Item = ProofMetadata> + '_ {
827        self.verifiable_credential.iter().map(|cp| cp.metadata())
828    }
829
830    /// Verify a presentation in the context of the provided public data and
831    /// cryptographic parameters.
832    ///
833    /// In case of success returns the [`Request`] for which the presentation
834    /// verifies.
835    ///
836    /// **NB:** This only verifies the cryptographic consistentcy of the data.
837    /// It does not check metadata, such as expiry. This should be checked
838    /// separately by the verifier.
839    pub fn verify<'a>(
840        &self,
841        params: &GlobalContext<C>,
842        public: impl ExactSizeIterator<Item = &'a CredentialsInputs<C>>,
843    ) -> Result<Request<C, AttributeType>, PresentationVerificationError> {
844        let mut transcript = RandomOracle::domain("ConcordiumWeb3ID");
845        transcript.add_bytes(self.presentation_context);
846        transcript.append_message(b"ctx", &params);
847
848        let mut request = Request {
849            challenge: self.presentation_context,
850            credential_statements: Vec::new(),
851        };
852
853        // Compute the data that the linking proof signed.
854        let to_sign =
855            linking_proof_message_to_sign(self.presentation_context, &self.verifiable_credential);
856
857        let mut linking_proof_iter = self.linking_proof.proof_value.iter();
858
859        if public.len() != self.verifiable_credential.len() {
860            return Err(PresentationVerificationError::InconsistentPublicData);
861        }
862
863        for (cred_public, cred_proof) in public.zip(&self.verifiable_credential) {
864            request.credential_statements.push(cred_proof.statement());
865            if let CredentialProof::Web3Id { holder: owner, .. } = &cred_proof {
866                let Some(sig) = linking_proof_iter.next() else {
867                    return Err(PresentationVerificationError::MissingLinkingProof);
868                };
869                if owner.public_key.verify(&to_sign, &sig.signature).is_err() {
870                    return Err(PresentationVerificationError::InvalidLinkinProof);
871                }
872            }
873            if !verify_single_credential(params, &mut transcript, cred_proof, cred_public) {
874                return Err(PresentationVerificationError::InvalidCredential);
875            }
876        }
877
878        // No bogus signatures should be left.
879        if linking_proof_iter.next().is_none() {
880            Ok(request)
881        } else {
882            Err(PresentationVerificationError::ExcessiveLinkingProof)
883        }
884    }
885}
886
887impl<C: Curve, AttributeType: Attribute<C::Scalar>> crate::common::Serial
888    for Presentation<C, AttributeType>
889{
890    fn serial<B: crate::common::Buffer>(&self, out: &mut B) {
891        self.presentation_context.serial(out);
892        self.verifiable_credential.serial(out);
893        self.linking_proof.serial(out);
894    }
895}
896
897impl<C: Curve, AttributeType: Attribute<C::Scalar> + DeserializeOwned> TryFrom<serde_json::Value>
898    for Presentation<C, AttributeType>
899{
900    type Error = anyhow::Error;
901
902    fn try_from(mut value: serde_json::Value) -> Result<Self, Self::Error> {
903        let ty: String = serde_json::from_value(get_field(&mut value, "type")?)?;
904        anyhow::ensure!(ty == "VerifiablePresentation");
905        let presentation_context =
906            serde_json::from_value(get_field(&mut value, "presentationContext")?)?;
907        let verifiable_credential =
908            serde_json::from_value(get_field(&mut value, "verifiableCredential")?)?;
909        let linking_proof = serde_json::from_value(get_field(&mut value, "proof")?)?;
910        Ok(Self {
911            presentation_context,
912            verifiable_credential,
913            linking_proof,
914        })
915    }
916}
917
918impl<C: Curve, AttributeType: Attribute<C::Scalar> + serde::Serialize> serde::Serialize
919    for Presentation<C, AttributeType>
920{
921    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
922    where
923        S: serde::Serializer,
924    {
925        let json = serde_json::json!({
926            "type": "VerifiablePresentation",
927            "presentationContext": self.presentation_context,
928            "verifiableCredential": &self.verifiable_credential,
929            "proof": &self.linking_proof
930        });
931        json.serialize(serializer)
932    }
933}
934
935#[derive(Debug, crate::common::SerdeBase16Serialize, crate::common::Serialize)]
936/// A proof that establishes that the owner of the credential itself produced
937/// the proof. Technically this means that there is a signature on the entire
938/// rest of the presentation using the public key that is associated with the
939/// Web3 credential. The identity credentials do not have linking proofs since
940/// the owner of those credentials retains full control of their secret
941/// material.
942struct WeakLinkingProof {
943    signature: ed25519_dalek::Signature,
944}
945
946#[derive(Debug, serde::Deserialize)]
947#[serde(try_from = "serde_json::Value")]
948/// A proof that establishes that the owner of the credential has indeed created
949/// the presentation. At present this is a list of signatures.
950pub struct LinkingProof {
951    pub created: chrono::DateTime<chrono::Utc>,
952    proof_value: Vec<WeakLinkingProof>,
953}
954
955impl crate::common::Serial for LinkingProof {
956    fn serial<B: crate::common::Buffer>(&self, out: &mut B) {
957        self.created.timestamp_millis().serial(out);
958        self.proof_value.serial(out)
959    }
960}
961
962impl serde::Serialize for LinkingProof {
963    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
964    where
965        S: serde::Serializer,
966    {
967        let json = serde_json::json!({
968            "type": "ConcordiumWeakLinkingProofV1",
969            "created": self.created,
970            "proofValue": self.proof_value,
971        });
972        json.serialize(serializer)
973    }
974}
975
976impl TryFrom<serde_json::Value> for LinkingProof {
977    type Error = anyhow::Error;
978
979    fn try_from(mut value: serde_json::Value) -> Result<Self, Self::Error> {
980        use anyhow::Context;
981        let ty = value
982            .get_mut("type")
983            .context("No type field present.")?
984            .take();
985        if ty.as_str() != Some("ConcordiumWeakLinkingProofV1") {
986            anyhow::bail!("Unrecognized proof type.");
987        }
988        let created = serde_json::from_value(
989            value
990                .get_mut("created")
991                .context("No created field present.")?
992                .take(),
993        )?;
994        let proof_value = serde_json::from_value(
995            value
996                .get_mut("proofValue")
997                .context("No proofValue field present.")?
998                .take(),
999        )?;
1000        Ok(Self {
1001            created,
1002            proof_value,
1003        })
1004    }
1005}
1006
1007/// An auxiliary trait that provides access to the owner of the Web3 verifiable
1008/// credential. The intention is that this is implemented by ed25519 keypairs
1009/// or hardware wallets.
1010pub trait Web3IdSigner {
1011    fn id(&self) -> ed25519_dalek::VerifyingKey;
1012    fn sign(&self, msg: &impl AsRef<[u8]>) -> ed25519_dalek::Signature;
1013}
1014
1015impl Web3IdSigner for ed25519_dalek::SigningKey {
1016    fn id(&self) -> ed25519_dalek::VerifyingKey {
1017        self.verifying_key()
1018    }
1019
1020    fn sign(&self, msg: &impl AsRef<[u8]>) -> ed25519_dalek::Signature {
1021        ed25519_dalek::Signer::sign(self, msg.as_ref())
1022    }
1023}
1024
1025impl Web3IdSigner for crate::common::types::KeyPair {
1026    fn id(&self) -> ed25519_dalek::VerifyingKey {
1027        self.public()
1028    }
1029
1030    fn sign(&self, msg: &impl AsRef<[u8]>) -> ed25519_dalek::Signature {
1031        self.sign(msg.as_ref())
1032    }
1033}
1034
1035impl Web3IdSigner for ed25519_dalek::SecretKey {
1036    fn id(&self) -> ed25519_dalek::VerifyingKey {
1037        ed25519_dalek::SigningKey::from(self).verifying_key()
1038    }
1039
1040    fn sign(&self, msg: &impl AsRef<[u8]>) -> ed25519_dalek::Signature {
1041        let expanded: ed25519_dalek::SigningKey = self.into();
1042        ed25519_dalek::Signer::sign(&expanded, msg.as_ref())
1043    }
1044}
1045
1046/// The additional inputs, additional to the [`Request`] that are needed to
1047/// produce a [`Presentation`].
1048pub enum CommitmentInputs<'a, C: Curve, AttributeType, Web3IdSigner> {
1049    /// Inputs are for an identity credential issued by an identity provider.
1050    Account {
1051        issuer: IpIdentity,
1052        /// The values that are committed to and are required in the proofs.
1053        values: &'a BTreeMap<AttributeTag, AttributeType>,
1054        /// The randomness to go along with commitments in `values`.
1055        randomness: &'a BTreeMap<AttributeTag, pedersen_commitment::Randomness<C>>,
1056    },
1057    /// Inputs are for a credential issued by Web3ID issuer.
1058    Web3Issuer {
1059        signature: ed25519_dalek::Signature,
1060        /// The signer that will sign the presentation.
1061        signer: &'a Web3IdSigner,
1062        /// All the values the user has and are required in the proofs.
1063        values: &'a BTreeMap<String, AttributeType>,
1064        /// The randomness to go along with commitments in `values`. This has to
1065        /// have the same keys as the `values` field, but it is more
1066        /// convenient if it is a separate map itself.
1067        randomness: &'a BTreeMap<String, pedersen_commitment::Randomness<C>>,
1068    },
1069}
1070
1071#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
1072#[serde(bound(
1073    deserialize = "AttributeType: DeserializeOwned, C: Curve",
1074    serialize = "AttributeType: Clone + serde::Serialize, C: Curve"
1075))]
1076#[serde(try_from = "serde_json::Value", into = "serde_json::Value")]
1077/// The full credential, including secrets.
1078pub struct Web3IdCredential<C: Curve, AttributeType> {
1079    /// The credential holder's public key.
1080    pub holder_id: CredentialHolderId,
1081    /// The network to which the credential applies.
1082    pub network: Network,
1083    /// The address of the credential registry where the credential is tracked.
1084    pub registry: ContractAddress,
1085    /// Credential type describing what kind of a credential it is.
1086    pub credential_type: BTreeSet<String>,
1087    /// Link to the credential schema.
1088    pub credential_schema: String,
1089    /// The issuer's public key.
1090    pub issuer_key: IssuerKey,
1091    /// Start of the validity of the credential.
1092    pub valid_from: chrono::DateTime<chrono::Utc>,
1093    /// After this date, the credential becomes expired. `None` corresponds to a
1094    /// credential that cannot expire.
1095    pub valid_until: Option<chrono::DateTime<chrono::Utc>>,
1096    /// The values of different attributes, indexed by attribute tags.
1097    pub values: BTreeMap<String, AttributeType>,
1098    /// The randomness to go along with commitments in `values`. This has to
1099    /// have the same keys as the `values` field, but it is more
1100    /// convenient if it is a separate map itself.
1101    pub randomness: BTreeMap<String, pedersen_commitment::Randomness<C>>,
1102    /// The signature on the holder's public key, the contract address of the
1103    /// issuer, and the commitments from the issuer.
1104    pub signature: ed25519_dalek::Signature,
1105}
1106
1107impl<C: Curve, AttributeType: serde::Serialize> From<Web3IdCredential<C, AttributeType>>
1108    for serde_json::Value
1109{
1110    fn from(value: Web3IdCredential<C, AttributeType>) -> Self {
1111        let id = Method {
1112            network: value.network,
1113            ty: IdentifierType::ContractData {
1114                address: value.registry,
1115                entrypoint: OwnedEntrypointName::new_unchecked("credentialEntry".into()),
1116                parameter: OwnedParameter::from_serial(&value.holder_id).unwrap(),
1117            },
1118        };
1119        let verification_method = Method {
1120            network: value.network,
1121            ty: IdentifierType::PublicKey {
1122                key: value.issuer_key.public_key,
1123            },
1124        };
1125        let cred_id = Method {
1126            network: value.network,
1127            ty: IdentifierType::PublicKey {
1128                key: value.holder_id.public_key,
1129            },
1130        };
1131        let issuer = Method {
1132            network: value.network,
1133            ty: IdentifierType::ContractData {
1134                address: value.registry,
1135                entrypoint: OwnedEntrypointName::new_unchecked("issuer".into()),
1136                parameter: OwnedParameter::empty(),
1137            },
1138        };
1139
1140        let subject = serde_json::json!({
1141            "id": cred_id,
1142            "attributes": value.values,
1143        });
1144        let proof = serde_json::json!({
1145            "type": "Ed25519Signature2020",
1146            "verificationMethod": verification_method,
1147            "proofPurpose": "assertionMethod",
1148            "proofValue": base16_encode_string(&value.signature),
1149        });
1150
1151        serde_json::json!({
1152            "id": id,
1153            "type": value.credential_type,
1154            "issuer": issuer,
1155            "validFrom": value.valid_from,
1156            "validUntil": value.valid_until,
1157            "credentialSubject": subject,
1158            "credentialSchema": {
1159                "type": "JsonSchema2023",
1160                "id": value.credential_schema
1161            },
1162            "randomness": value.randomness,
1163            "proof": proof,
1164        })
1165    }
1166}
1167
1168impl<C: Curve, AttributeType: DeserializeOwned> TryFrom<serde_json::Value>
1169    for Web3IdCredential<C, AttributeType>
1170{
1171    type Error = anyhow::Error;
1172
1173    fn try_from(mut value: serde_json::Value) -> Result<Self, Self::Error> {
1174        use anyhow::Context;
1175
1176        let id_value = get_field(&mut value, "id")?;
1177        let Some(Ok((_, id))) = id_value.as_str().map(parse_did) else {
1178            anyhow::bail!("id field is not a valid DID");
1179        };
1180        let IdentifierType::ContractData {
1181            address,
1182            entrypoint,
1183            parameter,
1184        } = id.ty
1185        else {
1186            anyhow::bail!("Only Web3 credentials are supported.")
1187        };
1188        anyhow::ensure!(entrypoint == "credentialEntry", "Incorrect entrypoint.");
1189        let holder_id = CredentialHolderId::new(ed25519_dalek::VerifyingKey::from_bytes(
1190            parameter.as_ref().try_into()?,
1191        )?);
1192
1193        // Just validate the issuer field.
1194        {
1195            let issuer_value = get_field(&mut value, "issuer")?;
1196            let Some(Ok((_, id))) = issuer_value.as_str().map(parse_did) else {
1197                anyhow::bail!("issuer field is not a valid DID");
1198            };
1199            let IdentifierType::ContractData {
1200                address: issuer_address,
1201                entrypoint: issuer_entrypoint,
1202                parameter: issuer_parameter,
1203            } = id.ty
1204            else {
1205                anyhow::bail!("Only Web3 credentials are supported.")
1206            };
1207            anyhow::ensure!(address == issuer_address, "Inconsistent issuer addresses.");
1208            anyhow::ensure!(issuer_entrypoint == "issuer", "Invalid issuer entrypoint.");
1209            anyhow::ensure!(
1210                issuer_parameter == OwnedParameter::empty(),
1211                "Issuer parameter should be empty."
1212            )
1213        }
1214
1215        let valid_from = get_field(&mut value, "validFrom")?;
1216        let valid_until = get_optional_field(&mut value, "validUntil")?;
1217
1218        let randomness_value = get_field(&mut value, "randomness")?;
1219        let randomness = serde_json::from_value::<
1220            BTreeMap<String, pedersen_commitment::Randomness<C>>,
1221        >(randomness_value)?;
1222
1223        let values = {
1224            let mut subject = get_field(&mut value, "credentialSubject")?;
1225
1226            let cred_id = get_field(&mut subject, "id")?;
1227            let Some(Ok((_, cred_id))) = cred_id.as_str().map(parse_did) else {
1228                anyhow::bail!("credentialSubject/id field is not a valid DID");
1229            };
1230            let IdentifierType::PublicKey { key } = cred_id.ty else {
1231                anyhow::bail!("Credential subject id must be a public key.")
1232            };
1233            anyhow::ensure!(
1234                holder_id.public_key == key,
1235                "Inconsistent data. Holder id and credential id do not match."
1236            );
1237            anyhow::ensure!(cred_id.network == id.network, "Inconsistent networks.");
1238
1239            serde_json::from_value(get_field(&mut subject, "attributes")?)?
1240        };
1241
1242        let (issuer_key, signature) = {
1243            let mut proof = get_field(&mut value, "proof")?;
1244            let ty = get_field(&mut proof, "type")?;
1245            anyhow::ensure!(
1246                ty == "Ed25519Signature2020",
1247                "Only `Ed25519Signature2020` type is supported."
1248            );
1249            let purpose = get_field(&mut proof, "proofPurpose")?;
1250            anyhow::ensure!(
1251                purpose == "assertionMethod",
1252                "Only `assertionMethod` purpose is supported."
1253            );
1254            let method = get_field(&mut proof, "verificationMethod")?;
1255            let Some(Ok((_, method))) = method.as_str().map(parse_did) else {
1256                anyhow::bail!("verificationMethod field is not a valid DID");
1257            };
1258            let IdentifierType::PublicKey { key } = method.ty else {
1259                anyhow::bail!("Verification method must be a public key.")
1260            };
1261            anyhow::ensure!(method.network == id.network, "Inconsistent networks.");
1262            let sig = get_field(&mut proof, "proofValue")?;
1263            let signature =
1264                base16_decode_string(sig.as_str().context("proofValue must be a string.")?)?;
1265            (key.into(), signature)
1266        };
1267
1268        let credential_schema = {
1269            let mut schema = get_field(&mut value, "credentialSchema")?;
1270            let ty = get_field(&mut schema, "type")?;
1271            anyhow::ensure!(
1272                ty == "JsonSchema2023",
1273                "Only `JsonSchema2023` type is supported."
1274            );
1275            let id = get_field(&mut schema, "id")?;
1276            let serde_json::Value::String(id) = id else {
1277                anyhow::bail!("The id should be a string.")
1278            };
1279            id
1280        };
1281
1282        let credential_type = serde_json::from_value(get_field(&mut value, "type")?)?;
1283
1284        Ok(Self {
1285            holder_id,
1286            network: id.network,
1287            registry: address,
1288            credential_type,
1289            issuer_key,
1290            values,
1291            randomness,
1292            signature,
1293            valid_from: serde_json::from_value(valid_from)?,
1294            valid_until: serde_json::from_value(valid_until)?,
1295            credential_schema,
1296        })
1297    }
1298}
1299
1300impl<C: Curve, AttributeType> Web3IdCredential<C, AttributeType> {
1301    /// Convert the credential into inputs for a proof.
1302    pub fn into_inputs<'a, S: Web3IdSigner>(
1303        &'a self,
1304        signer: &'a S,
1305    ) -> CommitmentInputs<'a, C, AttributeType, S> {
1306        CommitmentInputs::Web3Issuer {
1307            signature: self.signature,
1308            signer,
1309            values: &self.values,
1310            randomness: &self.randomness,
1311        }
1312    }
1313}
1314
1315#[serde_with::serde_as]
1316#[derive(serde::Deserialize)]
1317#[serde(bound(deserialize = "AttributeType: DeserializeOwned, Web3IdSigner: DeserializeOwned"))]
1318#[serde(rename_all = "camelCase", tag = "type")]
1319/// An owned version of [`CommitmentInputs`] that can be deserialized.
1320pub enum OwnedCommitmentInputs<C: Curve, AttributeType, Web3IdSigner> {
1321    #[serde(rename_all = "camelCase")]
1322    Account {
1323        issuer: IpIdentity,
1324        #[serde_as(as = "BTreeMap<serde_with::DisplayFromStr, _>")]
1325        values: BTreeMap<AttributeTag, AttributeType>,
1326        #[serde_as(as = "BTreeMap<serde_with::DisplayFromStr, _>")]
1327        randomness: BTreeMap<AttributeTag, pedersen_commitment::Randomness<C>>,
1328    },
1329    #[serde(rename_all = "camelCase")]
1330    Web3Issuer {
1331        signer: Web3IdSigner,
1332        #[serde_as(as = "BTreeMap<serde_with::DisplayFromStr, _>")]
1333        values: BTreeMap<String, AttributeType>,
1334        /// The randomness to go along with commitments in `values`. This has to
1335        /// have the same keys as the `values` field, but it is more
1336        /// convenient if it is a separate map itself.
1337        #[serde_as(as = "BTreeMap<serde_with::DisplayFromStr, _>")]
1338        randomness: BTreeMap<String, pedersen_commitment::Randomness<C>>,
1339        #[serde(
1340            serialize_with = "crate::common::base16_encode",
1341            deserialize_with = "crate::common::base16_decode"
1342        )]
1343        signature: ed25519_dalek::Signature,
1344    },
1345}
1346
1347impl<'a, C: Curve, AttributeType, Web3IdSigner>
1348    From<&'a OwnedCommitmentInputs<C, AttributeType, Web3IdSigner>>
1349    for CommitmentInputs<'a, C, AttributeType, Web3IdSigner>
1350{
1351    fn from(
1352        owned: &'a OwnedCommitmentInputs<C, AttributeType, Web3IdSigner>,
1353    ) -> CommitmentInputs<'a, C, AttributeType, Web3IdSigner> {
1354        match owned {
1355            OwnedCommitmentInputs::Account {
1356                issuer,
1357                values,
1358                randomness,
1359            } => CommitmentInputs::Account {
1360                issuer: *issuer,
1361                values,
1362                randomness,
1363            },
1364            OwnedCommitmentInputs::Web3Issuer {
1365                signer,
1366                values,
1367                randomness,
1368                signature,
1369            } => CommitmentInputs::Web3Issuer {
1370                signer,
1371                values,
1372                randomness,
1373                signature: *signature,
1374            },
1375        }
1376    }
1377}
1378
1379#[derive(thiserror::Error, Debug)]
1380/// An error that can occurr when attempting to produce a proof.
1381pub enum ProofError {
1382    #[error("Too many attributes to produce a proof.")]
1383    TooManyAttributes,
1384    #[error("Missing identity attribute.")]
1385    MissingAttribute,
1386    #[error("No attributes were provided.")]
1387    NoAttributes,
1388    #[error("Inconsistent values and randomness. Cannot construct commitments.")]
1389    InconsistentValuesAndRandomness,
1390    #[error("Cannot construct gluing proof.")]
1391    UnableToProve,
1392    #[error("The number of commitment inputs and statements is inconsistent.")]
1393    CommitmentsStatementsMismatch,
1394    #[error("The ID in the statement and in the provided signer do not match.")]
1395    InconsistentIds,
1396}
1397
1398/// Verify a single credential. This only checks the cryptographic parts and
1399/// ignores the metadata such as issuance date.
1400fn verify_single_credential<C: Curve, AttributeType: Attribute<C::Scalar>>(
1401    global: &GlobalContext<C>,
1402    transcript: &mut RandomOracle,
1403    cred_proof: &CredentialProof<C, AttributeType>,
1404    public: &CredentialsInputs<C>,
1405) -> bool {
1406    match (&cred_proof, public) {
1407        (
1408            CredentialProof::Account {
1409                network: _,
1410                cred_id: _,
1411                proofs,
1412                created: _,
1413                issuer: _,
1414            },
1415            CredentialsInputs::Account { commitments },
1416        ) => {
1417            for (statement, proof) in proofs.iter() {
1418                if !statement.verify(
1419                    ProofVersion::Version2,
1420                    global,
1421                    transcript,
1422                    commitments,
1423                    proof,
1424                ) {
1425                    return false;
1426                }
1427            }
1428        }
1429        (
1430            CredentialProof::Web3Id {
1431                network: _proof_network,
1432                contract: proof_contract,
1433                commitments,
1434                proofs,
1435                created: _,
1436                holder: owner,
1437                ty: _,
1438            },
1439            CredentialsInputs::Web3 { issuer_pk },
1440        ) => {
1441            if !commitments.verify_signature(owner, issuer_pk, *proof_contract) {
1442                return false;
1443            }
1444            for (statement, proof) in proofs.iter() {
1445                if !statement.verify(
1446                    ProofVersion::Version2,
1447                    global,
1448                    transcript,
1449                    &commitments.commitments,
1450                    proof,
1451                ) {
1452                    return false;
1453                }
1454            }
1455        }
1456        _ => return false, // mismatch in data
1457    }
1458    true
1459}
1460
1461impl<C: Curve, AttributeType: Attribute<C::Scalar>> CredentialStatement<C, AttributeType> {
1462    fn prove<Signer: Web3IdSigner>(
1463        self,
1464        global: &GlobalContext<C>,
1465        ro: &mut RandomOracle,
1466        csprng: &mut impl rand::Rng,
1467        input: CommitmentInputs<C, AttributeType, Signer>,
1468    ) -> Result<CredentialProof<C, AttributeType>, ProofError> {
1469        match (self, input) {
1470            (
1471                CredentialStatement::Account {
1472                    network,
1473                    cred_id,
1474                    statement,
1475                },
1476                CommitmentInputs::Account {
1477                    values,
1478                    randomness,
1479                    issuer,
1480                },
1481            ) => {
1482                let mut proofs = Vec::new();
1483                for statement in statement {
1484                    let proof = statement
1485                        .prove(
1486                            ProofVersion::Version2,
1487                            global,
1488                            ro,
1489                            csprng,
1490                            values,
1491                            randomness,
1492                        )
1493                        .ok_or(ProofError::MissingAttribute)?;
1494                    proofs.push((statement, proof));
1495                }
1496                let created = chrono::Utc::now();
1497                Ok(CredentialProof::Account {
1498                    cred_id,
1499                    proofs,
1500                    network,
1501                    created,
1502                    issuer,
1503                })
1504            }
1505            (
1506                CredentialStatement::Web3Id {
1507                    network,
1508                    contract,
1509                    credential,
1510                    statement,
1511                    ty,
1512                },
1513                CommitmentInputs::Web3Issuer {
1514                    signature,
1515                    values,
1516                    randomness,
1517                    signer,
1518                },
1519            ) => {
1520                let mut proofs = Vec::new();
1521                if credential != signer.id().into() {
1522                    return Err(ProofError::InconsistentIds);
1523                }
1524                if values.len() != randomness.len() {
1525                    return Err(ProofError::InconsistentValuesAndRandomness);
1526                }
1527
1528                // We use the same commitment key to commit to values for all the different
1529                // attributes. TODO: This is not ideal, but is probably fine
1530                // since the tags are signed as well, so you cannot switch one
1531                // commitment for another. We could instead use bulletproof generators, that
1532                // would be cleaner.
1533                let cmm_key = &global.on_chain_commitment_key;
1534
1535                let mut commitments = BTreeMap::new();
1536                for ((vi, value), (ri, randomness)) in values.iter().zip(randomness.iter()) {
1537                    if vi != ri {
1538                        return Err(ProofError::InconsistentValuesAndRandomness);
1539                    }
1540                    commitments.insert(
1541                        ri.clone(),
1542                        cmm_key.hide(
1543                            &pedersen_commitment::Value::<C>::new(value.to_field_element()),
1544                            randomness,
1545                        ),
1546                    );
1547                }
1548                // TODO: For better user experience/debugging we could check the signature here.
1549                let commitments = SignedCommitments {
1550                    signature,
1551                    commitments,
1552                };
1553                for statement in statement {
1554                    let proof = statement
1555                        .prove(
1556                            ProofVersion::Version2,
1557                            global,
1558                            ro,
1559                            csprng,
1560                            values,
1561                            randomness,
1562                        )
1563                        .ok_or(ProofError::MissingAttribute)?;
1564                    proofs.push((statement, proof));
1565                }
1566                let created = chrono::Utc::now();
1567                Ok(CredentialProof::Web3Id {
1568                    commitments,
1569                    proofs,
1570                    network,
1571                    contract,
1572                    created,
1573                    holder: signer.id().into(),
1574                    ty,
1575                })
1576            }
1577            _ => Err(ProofError::CommitmentsStatementsMismatch),
1578        }
1579    }
1580}
1581
1582fn linking_proof_message_to_sign<C: Curve, AttributeType: Attribute<C::Scalar>>(
1583    challenge: Challenge,
1584    proofs: &[CredentialProof<C, AttributeType>],
1585) -> Vec<u8> {
1586    use crate::common::Serial;
1587    use sha2::Digest;
1588    // hash the context and proof.
1589    let mut out = sha2::Sha512::new();
1590    challenge.serial(&mut out);
1591    proofs.serial(&mut out);
1592    let mut msg = LINKING_DOMAIN_STRING.to_vec();
1593    msg.extend_from_slice(&out.finalize());
1594    msg
1595}
1596
1597impl<C: Curve, AttributeType: Attribute<C::Scalar>> Request<C, AttributeType> {
1598    /// Construct a proof for the [`Request`] using the provided cryptographic
1599    /// parameters and secrets.
1600    pub fn prove<'a, Signer: 'a + Web3IdSigner>(
1601        self,
1602        params: &GlobalContext<C>,
1603        attrs: impl ExactSizeIterator<Item = CommitmentInputs<'a, C, AttributeType, Signer>>,
1604    ) -> Result<Presentation<C, AttributeType>, ProofError>
1605    where
1606        AttributeType: 'a,
1607    {
1608        let mut proofs = Vec::with_capacity(attrs.len());
1609        let mut transcript = RandomOracle::domain("ConcordiumWeb3ID");
1610        transcript.add_bytes(self.challenge);
1611        transcript.append_message(b"ctx", &params);
1612        let mut csprng = rand::thread_rng();
1613        if self.credential_statements.len() != attrs.len() {
1614            return Err(ProofError::CommitmentsStatementsMismatch);
1615        }
1616        let mut signers = Vec::new();
1617        for (cred_statement, attributes) in self.credential_statements.into_iter().zip(attrs) {
1618            if let CommitmentInputs::Web3Issuer { signer, .. } = attributes {
1619                signers.push(signer);
1620            }
1621            let proof = cred_statement.prove(params, &mut transcript, &mut csprng, attributes)?;
1622            proofs.push(proof);
1623        }
1624        let to_sign = linking_proof_message_to_sign(self.challenge, &proofs);
1625        // Linking proof
1626        let mut proof_value = Vec::new();
1627        for signer in signers {
1628            let signature = signer.sign(&to_sign);
1629            proof_value.push(WeakLinkingProof { signature });
1630        }
1631        let linking_proof = LinkingProof {
1632            created: chrono::Utc::now(),
1633            proof_value,
1634        };
1635        Ok(Presentation {
1636            presentation_context: self.challenge,
1637            linking_proof,
1638            verifiable_credential: proofs,
1639        })
1640    }
1641}
1642
1643/// Public inputs to the verification function. These are the public commitments
1644/// that are contained in the credentials for identity credentials, and the
1645/// issuer's public key for Web3ID credentials which do not store commitments on
1646/// the chain.
1647#[derive(Debug, serde::Deserialize)]
1648#[serde(
1649    bound = "C: Curve",
1650    rename_all = "camelCase",
1651    rename_all_fields = "camelCase",
1652    tag = "type"
1653)]
1654pub enum CredentialsInputs<C: Curve> {
1655    Account {
1656        // All the commitments of the credential.
1657        // In principle we only ever need to borrow this, but it is simpler to
1658        // have the owned map instead of a reference to it.
1659        commitments: BTreeMap<AttributeTag, pedersen_commitment::Commitment<C>>,
1660    },
1661    Web3 {
1662        /// The public key of the issuer.
1663        issuer_pk: IssuerKey,
1664    },
1665}
1666
1667#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, serde::Deserialize, Debug)]
1668#[serde(try_from = "serde_json::Value")]
1669/// A value of an attribute. This is the low-level representation. The
1670/// different variants are present to enable different representations in JSON,
1671/// and different embeddings as field elements when constructing and verifying
1672/// proofs.
1673pub enum Web3IdAttribute {
1674    /// A string value
1675    String(AttributeKind),
1676    /// A number that is embedded as is to the field element.
1677    Numeric(u64),
1678    /// A timestamp in milliseconds that is embedded like the
1679    /// [`Numeric`](Self::Numeric) variant, but has a different JSON
1680    /// representation, in ISO8601 compatible format.
1681    Timestamp(Timestamp),
1682}
1683
1684impl Web3IdAttribute {
1685    /// Used to offset the duration stored in [`Web3IdAttribute::Timestamp`] to
1686    /// align with previous versions of chrono (<0.32), which introduced
1687    /// breaking changes to the value of [chrono::DateTime::MIN_UTC]
1688    const TIMESTAMP_DATE_OFFSET: i64 = 366;
1689    /// The lowest possible value to record in a [`Web3IdAttribute::Timestamp`]
1690    const TIMESTAMP_MIN_DATETIME: chrono::DateTime<chrono::Utc> =
1691        chrono::DateTime::<chrono::Utc>::MIN_UTC;
1692}
1693
1694impl TryFrom<chrono::DateTime<chrono::Utc>> for Web3IdAttribute {
1695    type Error = anyhow::Error;
1696
1697    fn try_from(value: chrono::DateTime<chrono::Utc>) -> Result<Self, Self::Error> {
1698        use anyhow::Context;
1699
1700        // We construct a timestamp in milliseconds from the lowest possible value, and
1701        // add the offset to align with the previously defined lowest possible
1702        // value.
1703        let timestamp = value
1704            .signed_duration_since(Self::TIMESTAMP_MIN_DATETIME)
1705            .checked_add(
1706                &chrono::Duration::try_days(Self::TIMESTAMP_DATE_OFFSET)
1707                    .expect("Can contain offset duration"),
1708            )
1709            .context("Timestamp out of range")?
1710            .num_milliseconds();
1711        let timestamp = Timestamp::from_timestamp_millis(
1712            timestamp
1713                .try_into()
1714                .context("Timestamps before -262144-01-01T00:00:00Z are not supported.")?,
1715        );
1716        Ok(Self::Timestamp(timestamp))
1717    }
1718}
1719
1720impl TryFrom<&Web3IdAttribute> for chrono::DateTime<chrono::Utc> {
1721    type Error = anyhow::Error;
1722
1723    fn try_from(value: &Web3IdAttribute) -> Result<Self, Self::Error> {
1724        use anyhow::Context;
1725
1726        let Web3IdAttribute::Timestamp(timestamp) = value else {
1727            anyhow::bail!("Cannot convert non timestamp web3 attribute values into date-time");
1728        };
1729
1730        let millis: i64 = timestamp.timestamp_millis().try_into()?;
1731        // We construct a date-time by subtracting the timestamp offset from the
1732        // timestamp, and add this to the minimum date, thus acting as a
1733        // reversing the conversion from date-time to tiemstamp within the web3
1734        // id attribute context
1735        let date_time = chrono::Duration::try_milliseconds(millis)
1736            .and_then(|dur| {
1737                let ms = dur.checked_sub(
1738                    &chrono::Duration::try_days(Web3IdAttribute::TIMESTAMP_DATE_OFFSET)
1739                        .expect("Can contain offset duration"),
1740                )?;
1741                Web3IdAttribute::TIMESTAMP_MIN_DATETIME.checked_add_signed(ms)
1742            })
1743            .context("Timestamp out of range")?;
1744        Ok(date_time)
1745    }
1746}
1747
1748impl TryFrom<serde_json::Value> for Web3IdAttribute {
1749    type Error = anyhow::Error;
1750
1751    fn try_from(mut value: serde_json::Value) -> Result<Self, Self::Error> {
1752        use anyhow::Context;
1753
1754        if let Some(v) = value.as_str() {
1755            Ok(Self::String(v.parse()?))
1756        } else if let Some(v) = value.as_u64() {
1757            Ok(Self::Numeric(v))
1758        } else {
1759            let obj = value
1760                .as_object_mut()
1761                .context("Not a string, number or object")?;
1762            if obj.get("type").and_then(|x| x.as_str()) != Some("date-time") {
1763                anyhow::bail!("Unknown or missing attribute `type`.")
1764            }
1765            let dt_value = obj
1766                .get_mut("timestamp")
1767                .context("Missing timestamp value.")?
1768                .take();
1769            let dt: chrono::DateTime<chrono::Utc> = serde_json::from_value(dt_value)?;
1770            dt.try_into()
1771        }
1772    }
1773}
1774
1775impl serde::Serialize for Web3IdAttribute {
1776    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1777    where
1778        S: serde::Serializer,
1779    {
1780        use serde::ser::{Error, SerializeMap};
1781        match self {
1782            Web3IdAttribute::String(ak) => ak.serialize(serializer),
1783            Web3IdAttribute::Numeric(n) => n.serialize(serializer),
1784            Web3IdAttribute::Timestamp(_) => {
1785                let dt =
1786                    chrono::DateTime::<chrono::Utc>::try_from(self).map_err(S::Error::custom)?;
1787                let mut map = serializer.serialize_map(Some(2))?;
1788                map.serialize_entry("type", "date-time")?;
1789                map.serialize_entry("timestamp", &dt)?;
1790                map.end()
1791            }
1792        }
1793    }
1794}
1795
1796impl crate::common::Serial for Web3IdAttribute {
1797    fn serial<B: crate::common::Buffer>(&self, out: &mut B) {
1798        match self {
1799            Web3IdAttribute::String(ak) => {
1800                0u8.serial(out);
1801                ak.serial(out)
1802            }
1803            Web3IdAttribute::Numeric(n) => {
1804                1u8.serial(out);
1805                n.serial(out)
1806            }
1807            Web3IdAttribute::Timestamp(ts) => {
1808                2u8.serial(out);
1809                ts.serial(out)
1810            }
1811        }
1812    }
1813}
1814
1815impl crate::common::Deserial for Web3IdAttribute {
1816    fn deserial<R: byteorder::ReadBytesExt>(source: &mut R) -> crate::common::ParseResult<Self> {
1817        use crate::common::Get;
1818        match source.get()? {
1819            0u8 => source.get().map(Web3IdAttribute::String),
1820            1u8 => source.get().map(Web3IdAttribute::Numeric),
1821            2u8 => source.get().map(Web3IdAttribute::Timestamp),
1822            n => anyhow::bail!("Unrecognized attribute tag: {n}"),
1823        }
1824    }
1825}
1826
1827impl std::fmt::Display for Web3IdAttribute {
1828    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1829        match self {
1830            Web3IdAttribute::String(ak) => ak.fmt(f),
1831            Web3IdAttribute::Numeric(n) => n.fmt(f),
1832            Web3IdAttribute::Timestamp(ts) => {
1833                // If possible render as a RFC3339 string.
1834                // Revert to millisecond timestamp if this is not possible due to overflow.
1835                if let Ok(dt) = chrono::DateTime::<chrono::Utc>::try_from(self) {
1836                    dt.fmt(f)
1837                } else {
1838                    ts.fmt(f)
1839                }
1840            }
1841        }
1842    }
1843}
1844
1845impl Attribute<<ArCurve as Curve>::Scalar> for Web3IdAttribute {
1846    fn to_field_element(&self) -> <ArCurve as Curve>::Scalar {
1847        match self {
1848            Web3IdAttribute::String(ak) => ak.to_field_element(),
1849            Web3IdAttribute::Numeric(n) => ArCurve::scalar_from_u64(*n),
1850            Web3IdAttribute::Timestamp(n) => ArCurve::scalar_from_u64(n.timestamp_millis()),
1851        }
1852    }
1853}
1854
1855#[cfg(test)]
1856mod tests {
1857    use super::*;
1858    use crate::id::id_proof_types::{
1859        AttributeInRangeStatement, AttributeInSetStatement, AttributeNotInSetStatement,
1860    };
1861    use anyhow::Context;
1862    use chrono::TimeZone;
1863    use rand::Rng;
1864    use std::marker::PhantomData;
1865
1866    #[test]
1867    /// Test that constructing proofs for web3 only credentials works in the
1868    /// sense that the proof verifies.
1869    ///
1870    /// JSON serialization of requests and presentations is also tested.
1871    fn test_web3_only() -> anyhow::Result<()> {
1872        let mut rng = rand::thread_rng();
1873        let challenge = Challenge::new(rng.gen());
1874        let signer_1 = ed25519_dalek::SigningKey::generate(&mut rng);
1875        let signer_2 = ed25519_dalek::SigningKey::generate(&mut rng);
1876        let issuer_1 = ed25519_dalek::SigningKey::generate(&mut rng);
1877        let issuer_2 = ed25519_dalek::SigningKey::generate(&mut rng);
1878        let contract_1 = ContractAddress::new(1337, 42);
1879        let contract_2 = ContractAddress::new(1338, 0);
1880        let min_timestamp = chrono::Duration::try_days(Web3IdAttribute::TIMESTAMP_DATE_OFFSET)
1881            .unwrap()
1882            .num_milliseconds()
1883            .try_into()
1884            .unwrap();
1885
1886        let credential_statements = vec![
1887            CredentialStatement::Web3Id {
1888                ty: [
1889                    "VerifiableCredential".into(),
1890                    "ConcordiumVerifiableCredential".into(),
1891                    "TestCredential".into(),
1892                ]
1893                .into_iter()
1894                .collect(),
1895                network: Network::Testnet,
1896                contract: contract_1,
1897                credential: CredentialHolderId::new(signer_1.verifying_key()),
1898                statement: vec![
1899                    AtomicStatement::AttributeInRange {
1900                        statement: AttributeInRangeStatement {
1901                            attribute_tag: "17".into(),
1902                            lower: Web3IdAttribute::Numeric(80),
1903                            upper: Web3IdAttribute::Numeric(1237),
1904                            _phantom: PhantomData,
1905                        },
1906                    },
1907                    AtomicStatement::AttributeInSet {
1908                        statement: AttributeInSetStatement {
1909                            attribute_tag: "23".into(),
1910                            set: [
1911                                Web3IdAttribute::String(
1912                                    AttributeKind::try_new("ff".into()).expect("attribute kind"),
1913                                ),
1914                                Web3IdAttribute::String(
1915                                    AttributeKind::try_new("aa".into()).expect("attribute kind"),
1916                                ),
1917                                Web3IdAttribute::String(
1918                                    AttributeKind::try_new("zz".into()).expect("attribute kind"),
1919                                ),
1920                            ]
1921                            .into_iter()
1922                            .collect(),
1923                            _phantom: PhantomData,
1924                        },
1925                    },
1926                ],
1927            },
1928            CredentialStatement::Web3Id {
1929                ty: [
1930                    "VerifiableCredential".into(),
1931                    "ConcordiumVerifiableCredential".into(),
1932                    "TestCredential".into(),
1933                ]
1934                .into_iter()
1935                .collect(),
1936                network: Network::Testnet,
1937                contract: contract_2,
1938                credential: CredentialHolderId::new(signer_2.verifying_key()),
1939                statement: vec![
1940                    AtomicStatement::AttributeInRange {
1941                        statement: AttributeInRangeStatement {
1942                            attribute_tag: 0.to_string(),
1943                            lower: Web3IdAttribute::Numeric(80),
1944                            upper: Web3IdAttribute::Numeric(1237),
1945                            _phantom: PhantomData,
1946                        },
1947                    },
1948                    AtomicStatement::AttributeNotInSet {
1949                        statement: AttributeNotInSetStatement {
1950                            attribute_tag: 1u8.to_string(),
1951                            set: [
1952                                Web3IdAttribute::String(
1953                                    AttributeKind::try_new("ff".into()).expect("attribute kind"),
1954                                ),
1955                                Web3IdAttribute::String(
1956                                    AttributeKind::try_new("aa".into()).expect("attribute kind"),
1957                                ),
1958                                Web3IdAttribute::String(
1959                                    AttributeKind::try_new("zz".into()).expect("attribute kind"),
1960                                ),
1961                            ]
1962                            .into_iter()
1963                            .collect(),
1964                            _phantom: PhantomData,
1965                        },
1966                    },
1967                    AtomicStatement::AttributeInRange {
1968                        statement: AttributeInRangeStatement {
1969                            attribute_tag: 2.to_string(),
1970                            lower: Web3IdAttribute::Timestamp(Timestamp::from_timestamp_millis(
1971                                min_timestamp,
1972                            )),
1973                            upper: Web3IdAttribute::Timestamp(Timestamp::from_timestamp_millis(
1974                                min_timestamp * 3,
1975                            )),
1976                            _phantom: PhantomData,
1977                        },
1978                    },
1979                ],
1980            },
1981        ];
1982
1983        let request = Request::<ArCurve, Web3IdAttribute> {
1984            challenge,
1985            credential_statements,
1986        };
1987        let params = GlobalContext::generate("Test".into());
1988        let mut values_1 = BTreeMap::new();
1989        values_1.insert(17.to_string(), Web3IdAttribute::Numeric(137));
1990        values_1.insert(
1991            23.to_string(),
1992            Web3IdAttribute::String(AttributeKind::try_new("ff".into()).expect("attribute kind")),
1993        );
1994        let mut randomness_1 = BTreeMap::new();
1995        randomness_1.insert(
1996            17.to_string(),
1997            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
1998        );
1999        randomness_1.insert(
2000            23.to_string(),
2001            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2002        );
2003        let commitments_1 = SignedCommitments::from_secrets(
2004            &params,
2005            &values_1,
2006            &randomness_1,
2007            &CredentialHolderId::new(signer_1.verifying_key()),
2008            &issuer_1,
2009            contract_1,
2010        )
2011        .unwrap();
2012
2013        let secrets_1 = CommitmentInputs::Web3Issuer {
2014            signer: &signer_1,
2015            values: &values_1,
2016            randomness: &randomness_1,
2017            signature: commitments_1.signature,
2018        };
2019
2020        let mut values_2 = BTreeMap::new();
2021        values_2.insert(0.to_string(), Web3IdAttribute::Numeric(137));
2022        values_2.insert(
2023            1.to_string(),
2024            Web3IdAttribute::String(AttributeKind::try_new("xkcd".into()).expect("attribute kind")),
2025        );
2026        values_2.insert(
2027            2.to_string(),
2028            Web3IdAttribute::Timestamp(Timestamp::from_timestamp_millis(min_timestamp * 2)),
2029        );
2030        let mut randomness_2 = BTreeMap::new();
2031        randomness_2.insert(
2032            0.to_string(),
2033            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2034        );
2035        randomness_2.insert(
2036            1.to_string(),
2037            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2038        );
2039        randomness_2.insert(
2040            2.to_string(),
2041            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2042        );
2043        let commitments_2 = SignedCommitments::from_secrets(
2044            &params,
2045            &values_2,
2046            &randomness_2,
2047            &CredentialHolderId::new(signer_2.verifying_key()),
2048            &issuer_2,
2049            contract_2,
2050        )
2051        .unwrap();
2052        let secrets_2 = CommitmentInputs::Web3Issuer {
2053            signer: &signer_2,
2054            values: &values_2,
2055            randomness: &randomness_2,
2056            signature: commitments_2.signature,
2057        };
2058        let attrs = [secrets_1, secrets_2];
2059        let proof = request
2060            .clone()
2061            .prove(&params, attrs.into_iter())
2062            .context("Cannot prove")?;
2063
2064        let public = vec![
2065            CredentialsInputs::Web3 {
2066                issuer_pk: issuer_1.verifying_key().into(),
2067            },
2068            CredentialsInputs::Web3 {
2069                issuer_pk: issuer_2.verifying_key().into(),
2070            },
2071        ];
2072        anyhow::ensure!(
2073            proof.verify(&params, public.iter())? == request,
2074            "Proof verification failed."
2075        );
2076
2077        let data = serde_json::to_string_pretty(&proof)?;
2078        assert!(
2079            serde_json::from_str::<Presentation<ArCurve, Web3IdAttribute>>(&data).is_ok(),
2080            "Cannot deserialize proof correctly."
2081        );
2082
2083        let data = serde_json::to_string_pretty(&request)?;
2084        assert_eq!(
2085            serde_json::from_str::<Request<ArCurve, Web3IdAttribute>>(&data)?,
2086            request,
2087            "Cannot deserialize request correctly."
2088        );
2089
2090        Ok(())
2091    }
2092
2093    #[test]
2094    /// Test that constructing proofs for a mixed (both web3 and id2 credentials
2095    /// involved) request works in the sense that the proof verifies.
2096    ///
2097    /// JSON serialization of requests and presentations is also tested.
2098    fn test_mixed() -> anyhow::Result<()> {
2099        let mut rng = rand::thread_rng();
2100        let challenge = Challenge::new(rng.gen());
2101        let params = GlobalContext::generate("Test".into());
2102        let cred_id_exp = ArCurve::generate_scalar(&mut rng);
2103        let cred_id = CredentialRegistrationID::from_exponent(&params, cred_id_exp);
2104        let signer_1 = ed25519_dalek::SigningKey::generate(&mut rng);
2105        let issuer_1 = ed25519_dalek::SigningKey::generate(&mut rng);
2106        let contract_1 = ContractAddress::new(1337, 42);
2107        let credential_statements = vec![
2108            CredentialStatement::Web3Id {
2109                ty: [
2110                    "VerifiableCredential".into(),
2111                    "ConcordiumVerifiableCredential".into(),
2112                    "TestCredential".into(),
2113                ]
2114                .into_iter()
2115                .collect(),
2116                network: Network::Testnet,
2117                contract: contract_1,
2118                credential: CredentialHolderId::new(signer_1.verifying_key()),
2119                statement: vec![
2120                    AtomicStatement::AttributeInRange {
2121                        statement: AttributeInRangeStatement {
2122                            attribute_tag: 17.to_string(),
2123                            lower: Web3IdAttribute::Numeric(80),
2124                            upper: Web3IdAttribute::Numeric(1237),
2125                            _phantom: PhantomData,
2126                        },
2127                    },
2128                    AtomicStatement::AttributeInSet {
2129                        statement: AttributeInSetStatement {
2130                            attribute_tag: 23u8.to_string(),
2131                            set: [
2132                                Web3IdAttribute::String(
2133                                    AttributeKind::try_new("ff".into()).expect("attribute kind"),
2134                                ),
2135                                Web3IdAttribute::String(
2136                                    AttributeKind::try_new("aa".into()).expect("attribute kind"),
2137                                ),
2138                                Web3IdAttribute::String(
2139                                    AttributeKind::try_new("zz".into()).expect("attribute kind"),
2140                                ),
2141                            ]
2142                            .into_iter()
2143                            .collect(),
2144                            _phantom: PhantomData,
2145                        },
2146                    },
2147                ],
2148            },
2149            CredentialStatement::Account {
2150                network: Network::Testnet,
2151                cred_id,
2152                statement: vec![
2153                    AtomicStatement::AttributeInRange {
2154                        statement: AttributeInRangeStatement {
2155                            attribute_tag: 3.into(),
2156                            lower: Web3IdAttribute::Numeric(80),
2157                            upper: Web3IdAttribute::Numeric(1237),
2158                            _phantom: PhantomData,
2159                        },
2160                    },
2161                    AtomicStatement::AttributeNotInSet {
2162                        statement: AttributeNotInSetStatement {
2163                            attribute_tag: 1u8.into(),
2164                            set: [
2165                                Web3IdAttribute::String(
2166                                    AttributeKind::try_new("ff".into()).expect("attribute kind"),
2167                                ),
2168                                Web3IdAttribute::String(
2169                                    AttributeKind::try_new("aa".into()).expect("attribute kind"),
2170                                ),
2171                                Web3IdAttribute::String(
2172                                    AttributeKind::try_new("zz".into()).expect("attribute kind"),
2173                                ),
2174                            ]
2175                            .into_iter()
2176                            .collect(),
2177                            _phantom: PhantomData,
2178                        },
2179                    },
2180                ],
2181            },
2182        ];
2183
2184        let request = Request::<ArCurve, Web3IdAttribute> {
2185            challenge,
2186            credential_statements,
2187        };
2188        let mut values_1 = BTreeMap::new();
2189        values_1.insert(17.to_string(), Web3IdAttribute::Numeric(137));
2190        values_1.insert(
2191            23.to_string(),
2192            Web3IdAttribute::String(AttributeKind::try_new("ff".into()).expect("attribute kind")),
2193        );
2194        let mut randomness_1 = BTreeMap::new();
2195        randomness_1.insert(
2196            17.to_string(),
2197            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2198        );
2199        randomness_1.insert(
2200            23.to_string(),
2201            pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2202        );
2203        let signed_commitments_1 = SignedCommitments::from_secrets(
2204            &params,
2205            &values_1,
2206            &randomness_1,
2207            &CredentialHolderId::new(signer_1.verifying_key()),
2208            &issuer_1,
2209            contract_1,
2210        )
2211        .unwrap();
2212        let secrets_1 = CommitmentInputs::Web3Issuer {
2213            signer: &signer_1,
2214            values: &values_1,
2215            randomness: &randomness_1,
2216            signature: signed_commitments_1.signature,
2217        };
2218
2219        let mut values_2 = BTreeMap::new();
2220        values_2.insert(3.into(), Web3IdAttribute::Numeric(137));
2221        values_2.insert(
2222            1.into(),
2223            Web3IdAttribute::String(AttributeKind::try_new("xkcd".into()).expect("attribute kind")),
2224        );
2225        let mut randomness_2 = BTreeMap::new();
2226        for tag in values_2.keys() {
2227            randomness_2.insert(
2228                *tag,
2229                pedersen_commitment::Randomness::<ArCurve>::generate(&mut rng),
2230            );
2231        }
2232        let secrets_2 = CommitmentInputs::Account {
2233            values: &values_2,
2234            randomness: &randomness_2,
2235            issuer: IpIdentity::from(17u32),
2236        };
2237        let attrs = [secrets_1, secrets_2];
2238        let proof = request
2239            .clone()
2240            .prove(&params, attrs.into_iter())
2241            .context("Cannot prove")?;
2242
2243        let commitments_2 = {
2244            let key = params.on_chain_commitment_key;
2245            let mut comms = BTreeMap::new();
2246            for (tag, value) in randomness_2.iter() {
2247                let _ = comms.insert(
2248                    AttributeTag::from(*tag),
2249                    key.hide(
2250                        &pedersen_commitment::Value::<ArCurve>::new(
2251                            values_2.get(tag).unwrap().to_field_element(),
2252                        ),
2253                        value,
2254                    ),
2255                );
2256            }
2257            comms
2258        };
2259
2260        let public = vec![
2261            CredentialsInputs::Web3 {
2262                issuer_pk: issuer_1.verifying_key().into(),
2263            },
2264            CredentialsInputs::Account {
2265                commitments: commitments_2,
2266            },
2267        ];
2268        anyhow::ensure!(
2269            proof
2270                .verify(&params, public.iter())
2271                .context("Verification of mixed presentation failed.")?
2272                == request,
2273            "Proof verification failed."
2274        );
2275
2276        let data = serde_json::to_string_pretty(&proof)?;
2277        assert!(
2278            serde_json::from_str::<Presentation<ArCurve, Web3IdAttribute>>(&data).is_ok(),
2279            "Cannot deserialize proof correctly."
2280        );
2281
2282        let data = serde_json::to_string_pretty(&request)?;
2283        assert_eq!(
2284            serde_json::from_str::<Request<ArCurve, Web3IdAttribute>>(&data)?,
2285            request,
2286            "Cannot deserialize request correctly."
2287        );
2288
2289        Ok(())
2290    }
2291
2292    #[test]
2293    /// Basic test that conversion of Web3IdCredential from/to JSON works.
2294    fn test_credential_json() {
2295        let mut rng = rand::thread_rng();
2296        let signer = ed25519_dalek::SigningKey::generate(&mut rng);
2297        let issuer = ed25519_dalek::SigningKey::generate(&mut rng);
2298        let mut randomness = BTreeMap::new();
2299        randomness.insert(
2300            0.to_string(),
2301            pedersen_commitment::Randomness::generate(&mut rng),
2302        );
2303        randomness.insert(
2304            3.to_string(),
2305            pedersen_commitment::Randomness::generate(&mut rng),
2306        );
2307        randomness.insert(
2308            17.to_string(),
2309            pedersen_commitment::Randomness::generate(&mut rng),
2310        );
2311
2312        let mut values = BTreeMap::new();
2313        values.insert("0".into(), Web3IdAttribute::Numeric(1234));
2314        values.insert(
2315            "3".into(),
2316            Web3IdAttribute::String(
2317                AttributeKind::try_new("Hello".into()).expect("attribute kind"),
2318            ),
2319        );
2320        values.insert(
2321            "17".into(),
2322            Web3IdAttribute::String(
2323                AttributeKind::try_new("World".into()).expect("attribute kind"),
2324            ),
2325        );
2326
2327        let cred = Web3IdCredential::<ArCurve, Web3IdAttribute> {
2328            holder_id: signer.verifying_key().into(),
2329            network: Network::Testnet,
2330            registry: ContractAddress::new(3, 17),
2331            credential_type: [
2332                "VerifiableCredential".into(),
2333                "ConcordiumVerifiableCredential".into(),
2334                "UniversityDegreeCredential".into(),
2335            ]
2336            .into_iter()
2337            .collect(),
2338            credential_schema: "http://link/to/schema".into(),
2339            issuer_key: issuer.verifying_key().into(),
2340            valid_from: chrono::Utc.timestamp_millis_opt(17).unwrap(),
2341            valid_until: chrono::Utc.timestamp_millis_opt(12345).earliest(),
2342            values,
2343            randomness,
2344            signature: issuer.sign(b"Something"),
2345        };
2346
2347        let json: serde_json::Value = cred.clone().into();
2348
2349        let value = Web3IdCredential::<ArCurve, Web3IdAttribute>::try_from(json)
2350            .expect("JSON parsing succeeds");
2351
2352        assert_eq!(value, cred, "Credential and parsed credential differ.");
2353    }
2354
2355    #[test]
2356    fn test_web3_id_attribute_timestamp_serde() {
2357        let date_time = chrono::DateTime::parse_from_rfc3339("2023-08-28T00:00:00.000Z")
2358            .expect("Can parse datetime value");
2359        let value = serde_json::json!({"type": "date-time", "timestamp": date_time});
2360        let attr: Web3IdAttribute =
2361            serde_json::from_value(value.clone()).expect("Can deserialize from JSON");
2362
2363        assert_eq!(
2364            attr,
2365            Web3IdAttribute::Timestamp(8336326032000000.into()),
2366            "Unexpected value for deserialized attribute"
2367        );
2368
2369        let ser = serde_json::to_value(attr).expect("Serialize does not fail");
2370        assert_eq!(
2371            ser, value,
2372            "Expected deserialized value to serialize into its origin"
2373        );
2374    }
2375}