1pub mod did;
8
9use 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
36pub const COMMITMENT_SIGNATURE_DOMAIN_STRING: &[u8] = b"WEB3ID:COMMITMENTS";
38
39pub const REVOKE_DOMAIN_STRING: &[u8] = b"WEB3ID:REVOKE";
42
43pub const LINKING_DOMAIN_STRING: &[u8] = b"WEB3ID:LINKING";
46
47#[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 Account {
58 network: Network,
59 cred_id: CredentialRegistrationID,
60 statement: Vec<AtomicStatement<C, AttributeTag, AttributeType>>,
61 },
62 Web3Id {
65 ty: BTreeSet<String>,
68 network: Network,
69 contract: ContractAddress,
72 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 ¶meter.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
159pub type StatementWithProof<C, TagType, AttributeType> = (
161 AtomicStatement<C, TagType, AttributeType>,
162 AtomicProof<C, AttributeType>,
163);
164
165pub enum CredentialMetadata {
167 Account {
170 issuer: IpIdentity,
171 cred_id: CredentialRegistrationID,
172 },
173 Web3Id {
175 contract: ContractAddress,
176 holder: CredentialHolderId,
177 },
178}
179
180pub struct ProofMetadata {
182 pub created: chrono::DateTime<chrono::Utc>,
184 pub network: Network,
185 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 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")]
259pub enum CredentialProof<C: Curve, AttributeType: Attribute<C::Scalar>> {
264 Account {
265 created: chrono::DateTime<chrono::Utc>,
267 network: Network,
268 cred_id: CredentialRegistrationID,
270 issuer: IpIdentity,
273 proofs: Vec<StatementWithProof<C, AttributeTag, AttributeType>>,
274 },
275 Web3Id {
276 created: chrono::DateTime<chrono::Utc>,
278 holder: CredentialHolderId,
280 network: Network,
281 contract: ContractAddress,
283 ty: BTreeSet<String>,
286 commitments: SignedCommitments<C>,
289 proofs: Vec<StatementWithProof<C, String, AttributeType>>,
291 },
292}
293
294#[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 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 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 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
434fn 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
446fn 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 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)]
611pub enum Web3IdChallengeMarker {}
613
614pub 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))]
624pub 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)]
633pub 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
731impl<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
791pub 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")]
797pub struct Presentation<C: Curve, AttributeType: Attribute<C::Scalar>> {
801 pub presentation_context: Challenge,
802 pub verifiable_credential: Vec<CredentialProof<C, AttributeType>>,
803 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 pub fn metadata(&self) -> impl ExactSizeIterator<Item = ProofMetadata> + '_ {
827 self.verifiable_credential.iter().map(|cp| cp.metadata())
828 }
829
830 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", ¶ms);
847
848 let mut request = Request {
849 challenge: self.presentation_context,
850 credential_statements: Vec::new(),
851 };
852
853 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 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)]
936struct WeakLinkingProof {
943 signature: ed25519_dalek::Signature,
944}
945
946#[derive(Debug, serde::Deserialize)]
947#[serde(try_from = "serde_json::Value")]
948pub 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
1007pub 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
1046pub enum CommitmentInputs<'a, C: Curve, AttributeType, Web3IdSigner> {
1049 Account {
1051 issuer: IpIdentity,
1052 values: &'a BTreeMap<AttributeTag, AttributeType>,
1054 randomness: &'a BTreeMap<AttributeTag, pedersen_commitment::Randomness<C>>,
1056 },
1057 Web3Issuer {
1059 signature: ed25519_dalek::Signature,
1060 signer: &'a Web3IdSigner,
1062 values: &'a BTreeMap<String, AttributeType>,
1064 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")]
1077pub struct Web3IdCredential<C: Curve, AttributeType> {
1079 pub holder_id: CredentialHolderId,
1081 pub network: Network,
1083 pub registry: ContractAddress,
1085 pub credential_type: BTreeSet<String>,
1087 pub credential_schema: String,
1089 pub issuer_key: IssuerKey,
1091 pub valid_from: chrono::DateTime<chrono::Utc>,
1093 pub valid_until: Option<chrono::DateTime<chrono::Utc>>,
1096 pub values: BTreeMap<String, AttributeType>,
1098 pub randomness: BTreeMap<String, pedersen_commitment::Randomness<C>>,
1102 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 {
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 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")]
1319pub 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 #[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)]
1380pub 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
1398fn 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, }
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 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 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 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 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", ¶ms);
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 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#[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 commitments: BTreeMap<AttributeTag, pedersen_commitment::Commitment<C>>,
1660 },
1661 Web3 {
1662 issuer_pk: IssuerKey,
1664 },
1665}
1666
1667#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, serde::Deserialize, Debug)]
1668#[serde(try_from = "serde_json::Value")]
1669pub enum Web3IdAttribute {
1674 String(AttributeKind),
1676 Numeric(u64),
1678 Timestamp(Timestamp),
1682}
1683
1684impl Web3IdAttribute {
1685 const TIMESTAMP_DATE_OFFSET: i64 = 366;
1689 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 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 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 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 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 ¶ms,
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 ¶ms,
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(¶ms, 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(¶ms, 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 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(¶ms, 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 ¶ms,
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(¶ms, 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(¶ms, 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 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}