-
Notifications
You must be signed in to change notification settings - Fork 23
direct identity credentials, integration with web3id #760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
ba495ef
e6ef3d6
a33509c
c6f70d7
1b47a1d
5b45a41
162f780
1f54425
808057b
800ed2f
56931c2
5cdd104
8d77f29
c4a5917
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ pub mod did; | |
| // TODO: | ||
| // - Documentation. | ||
| use crate::curve_arithmetic::Pairing; | ||
| use crate::hashes::BlockHash; | ||
| use crate::id::identity_attributes_credentials; | ||
| use crate::id::types::{ | ||
| ArIdentity, ArPublicKey, ChainArData, HasIdentityObjectFields, IdObjectUseData, | ||
|
|
@@ -689,9 +690,144 @@ impl<P: Pairing, C: Curve<Scalar = P::ScalarField>, AttributeType: Attribute<C:: | |
| /// Used as a phantom type to indicate a Web3ID challenge. | ||
| pub enum Web3IdChallengeMarker {} | ||
|
|
||
| /// Challenge string that serves as a distinguishing context when requesting | ||
| /// Sha256 challenge string that serves as a distinguishing context when requesting | ||
| /// proofs. | ||
| pub type Challenge = HashBytes<Web3IdChallengeMarker>; | ||
| pub type Sha256Challenge = HashBytes<Web3IdChallengeMarker>; | ||
|
|
||
| /// V1 structured challenge that serves as a distinguishing context when requesting | ||
| /// proofs. | ||
| /// Also called `ContextInfo` in ADR. | ||
| #[derive( | ||
| Clone, | ||
| Eq, | ||
| PartialEq, | ||
| Ord, | ||
| PartialOrd, | ||
| serde::Deserialize, | ||
| serde::Serialize, | ||
| crate::common::Serial, | ||
| Debug, | ||
| )] | ||
| pub struct V1Challenge { | ||
| /// This part of the challenge is suppose to be provided by the dapp backend (e.g. merchant backend). | ||
| given: GivenContextInfo, | ||
| /// This part of the challenge is suppose to be provided by the wallet or ID app. | ||
| requested: RequestedContextInfo, | ||
| } | ||
|
|
||
| // This data is suppose to be provided by the dapp backend (e.g. merchant backend). | ||
| #[derive( | ||
| Clone, | ||
| Eq, | ||
| PartialEq, | ||
| Ord, | ||
| PartialOrd, | ||
| serde::Deserialize, | ||
| serde::Serialize, | ||
| crate::common::Serial, | ||
| Debug, | ||
| )] | ||
| struct GivenContextInfo { | ||
| // Randomly generated nonce. It is important that the nonce is freshly generated by the backend | ||
| // for each request so that the presentation request anchor on-chain truely looks random. | ||
| nonce: Sha256Challenge, | ||
| // Human readable string giving more context to the request. | ||
| context_string: String, | ||
| // The topic of the wallet connection as defined by `walletConnect`. | ||
| // The wallet or ID app use this value to check that the topic matches the current active connection. | ||
| // TODO: Should we decide on a fix-sized length type `e.g. a type that will represent the walletConntect topic`. | ||
|
Comment on lines
+730
to
+738
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the properties should be dynamically represented and not as hardcoded fields? The |
||
| connection_id: String, | ||
| } | ||
|
|
||
| // This data is suppose to be provided by the wallet or ID app. | ||
| #[derive( | ||
| Clone, | ||
| Eq, | ||
| PartialEq, | ||
| Ord, | ||
| PartialOrd, | ||
| serde::Deserialize, | ||
| serde::Serialize, | ||
| crate::common::Serial, | ||
| Debug, | ||
| )] | ||
| struct RequestedContextInfo { | ||
| // The looked up block hash by the wallet or ID app which contains the presentation request anchor transaction. | ||
| block_hash: BlockHash, | ||
| // The website URL that the wallet is connected to. | ||
| // TODO: Should we decide on a type that represent a URL. | ||
| resource_id: String, | ||
| } | ||
|
|
||
| #[derive(serde::Deserialize, serde::Serialize)] | ||
| // The type is `untagged` to be backward compatible with old proofs and requests. | ||
| #[serde(untagged)] | ||
| #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] | ||
| pub enum Challenge { | ||
| Sha256(Sha256Challenge), | ||
| // Also called `ConcordiumContextInformationV1` in ADR. | ||
| V1(V1Challenge), | ||
| } | ||
|
|
||
| impl crate::common::Serial for Challenge { | ||
| fn serial<B: crate::common::Buffer>(&self, out: &mut B) { | ||
| match self { | ||
| Challenge::Sha256(hash_bytes) => { | ||
| // No tag is added to be backward compatible with old proofs and requests. | ||
| hash_bytes.serial(out) | ||
| } | ||
| Challenge::V1(context_info) => { | ||
| // TODO: We should probably add the tag of the enum to the serialization to represent the versions of challenge for future | ||
| // extendability of the challenge enum. | ||
| context_info.serial(out) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// A trait that represents a challenge in a proof response request protocol. | ||
| /// Types implementing this trait define how they can be incorporated into a cryptographic transcript (random oracle). | ||
| pub trait IsChallenge: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we need a polymorphic behaviour for different types of challenges, I think we should settle on one approach. Either an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ( |
||
| Clone + crate::common::Serial + serde::Serialize + for<'de> serde::Deserialize<'de> | ||
| { | ||
| /// Appends this challenge to the provided transcript (random oracle), | ||
| /// ensuring it contributes uniquely to the proof context. | ||
| /// | ||
| /// Implementations should use deterministic versions, as well as labels and ensure every part of the challenge | ||
| /// is accounted for when appending the transcript. No two types implementing this trait should contribute in | ||
| /// the same way to the transcript. | ||
| fn append_to_transcript(&self, transcript: &mut RandomOracle); | ||
| } | ||
|
|
||
| impl IsChallenge for Challenge { | ||
| fn append_to_transcript(&self, transcript: &mut RandomOracle) { | ||
| match self { | ||
| Challenge::Sha256(hash_bytes) => hash_bytes.append_to_transcript(transcript), | ||
| Challenge::V1(context_info) => context_info.append_to_transcript(transcript), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl IsChallenge for Sha256Challenge { | ||
| fn append_to_transcript(&self, transcript: &mut RandomOracle) { | ||
| // No tag `V0` is added to be backward compatible with old proofs and requests. | ||
| transcript.add_bytes(self); | ||
| } | ||
| } | ||
|
|
||
| impl IsChallenge for V1Challenge { | ||
| fn append_to_transcript(&self, transcript: &mut RandomOracle) { | ||
| // The tag `V1` is added for the challenge version. | ||
| transcript.add_bytes(b"V1"); | ||
| transcript.add_bytes(b"given"); | ||
| transcript.append_message(b"nonce", &self.given.nonce); | ||
| transcript.append_message(b"contextString", &self.given.context_string); | ||
| transcript.append_message(b"connectionID", &self.given.connection_id); | ||
| transcript.add_bytes(b"requested"); | ||
| transcript.append_message(b"blockHash", &self.requested.block_hash); | ||
| transcript.append_message(b"resourceID", &self.requested.resource_id); | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] | ||
| #[serde(rename_all = "camelCase")] | ||
|
|
@@ -701,7 +837,7 @@ pub type Challenge = HashBytes<Web3IdChallengeMarker>; | |
| ))] | ||
| /// A request for a proof. This is the statement and challenge. The secret data | ||
| /// comes separately. | ||
| pub struct Request<C: Curve, AttributeType: Attribute<C::Scalar>> { | ||
| pub struct Request<Challenge: IsChallenge, C: Curve, AttributeType: Attribute<C::Scalar>> { | ||
| pub challenge: Challenge, | ||
| pub credential_statements: Vec<CredentialStatement<C, AttributeType>>, | ||
| } | ||
|
|
@@ -876,6 +1012,7 @@ pub type CredentialHolderId = Ed25519PublicKey<CredentialHolderIdRole>; | |
| /// statements, ownership proof for all Web3 credentials, and a context. The | ||
| /// only missing part to verify the proof are the public commitments. | ||
| pub struct Presentation< | ||
| Challenge: IsChallenge, | ||
| P: Pairing, | ||
| C: Curve<Scalar = P::ScalarField>, | ||
| AttributeType: Attribute<C::Scalar>, | ||
|
|
@@ -902,8 +1039,12 @@ pub enum PresentationVerificationError { | |
| InvalidCredential, | ||
| } | ||
|
|
||
| impl<P: Pairing, C: Curve<Scalar = P::ScalarField>, AttributeType: Attribute<C::Scalar>> | ||
| Presentation<P, C, AttributeType> | ||
| impl< | ||
| Challenge: IsChallenge, | ||
| P: Pairing, | ||
| C: Curve<Scalar = P::ScalarField>, | ||
| AttributeType: Attribute<C::Scalar>, | ||
| > Presentation<Challenge, P, C, AttributeType> | ||
| { | ||
| /// Get an iterator over the metadata for each of the verifiable credentials | ||
| /// in the order they appear in the presentation. | ||
|
|
@@ -924,19 +1065,19 @@ impl<P: Pairing, C: Curve<Scalar = P::ScalarField>, AttributeType: Attribute<C:: | |
| &self, | ||
| params: &GlobalContext<C>, | ||
| public: impl ExactSizeIterator<Item = &'a CredentialsInputs<P, C>>, | ||
| ) -> Result<Request<C, AttributeType>, PresentationVerificationError> { | ||
| ) -> Result<Request<Challenge, C, AttributeType>, PresentationVerificationError> { | ||
| let mut transcript = RandomOracle::domain("ConcordiumWeb3ID"); | ||
| transcript.add_bytes(self.presentation_context); | ||
| transcript.append_challenge(&self.presentation_context); | ||
| transcript.append_message(b"ctx", ¶ms); | ||
|
|
||
| let mut request = Request { | ||
| challenge: self.presentation_context, | ||
| challenge: self.presentation_context.clone(), | ||
| credential_statements: Vec::new(), | ||
| }; | ||
|
|
||
| // Compute the data that the linking proof signed. | ||
| let to_sign = | ||
| linking_proof_message_to_sign(self.presentation_context, &self.verifiable_credential); | ||
| linking_proof_message_to_sign(&self.presentation_context, &self.verifiable_credential); | ||
|
|
||
| let mut linking_proof_iter = self.linking_proof.proof_value.iter(); | ||
|
|
||
|
|
@@ -968,8 +1109,12 @@ impl<P: Pairing, C: Curve<Scalar = P::ScalarField>, AttributeType: Attribute<C:: | |
| } | ||
| } | ||
|
|
||
| impl<P: Pairing, C: Curve<Scalar = P::ScalarField>, AttributeType: Attribute<C::Scalar>> | ||
| crate::common::Serial for Presentation<P, C, AttributeType> | ||
| impl< | ||
| Challenge: IsChallenge, | ||
| P: Pairing, | ||
| C: Curve<Scalar = P::ScalarField>, | ||
| AttributeType: Attribute<C::Scalar>, | ||
| > crate::common::Serial for Presentation<Challenge, P, C, AttributeType> | ||
| { | ||
| fn serial<B: crate::common::Buffer>(&self, out: &mut B) { | ||
| self.presentation_context.serial(out); | ||
|
|
@@ -979,10 +1124,11 @@ impl<P: Pairing, C: Curve<Scalar = P::ScalarField>, AttributeType: Attribute<C:: | |
| } | ||
|
|
||
| impl< | ||
| Challenge: IsChallenge, | ||
| P: Pairing, | ||
| C: Curve<Scalar = P::ScalarField>, | ||
| AttributeType: Attribute<C::Scalar> + DeserializeOwned, | ||
| > TryFrom<serde_json::Value> for Presentation<P, C, AttributeType> | ||
| > TryFrom<serde_json::Value> for Presentation<Challenge, P, C, AttributeType> | ||
| { | ||
| type Error = anyhow::Error; | ||
|
|
||
|
|
@@ -1003,10 +1149,11 @@ impl< | |
| } | ||
|
|
||
| impl< | ||
| Challenge: IsChallenge, | ||
| P: Pairing, | ||
| C: Curve<Scalar = P::ScalarField>, | ||
| AttributeType: Attribute<C::Scalar> + serde::Serialize, | ||
| > serde::Serialize for Presentation<P, C, AttributeType> | ||
| > serde::Serialize for Presentation<Challenge, P, C, AttributeType> | ||
| { | ||
| fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
| where | ||
|
|
@@ -1778,11 +1925,12 @@ impl<C: Curve, AttributeType: Attribute<C::Scalar>> CredentialStatement<C, Attri | |
| } | ||
|
|
||
| fn linking_proof_message_to_sign< | ||
| Challenge: IsChallenge, | ||
| P: Pairing, | ||
| C: Curve<Scalar = P::ScalarField>, | ||
| AttributeType: Attribute<C::Scalar>, | ||
| >( | ||
| challenge: Challenge, | ||
| challenge: &Challenge, | ||
| proofs: &[CredentialProof<P, C, AttributeType>], | ||
| ) -> Vec<u8> { | ||
| use crate::common::Serial; | ||
|
|
@@ -1796,20 +1944,22 @@ fn linking_proof_message_to_sign< | |
| msg | ||
| } | ||
|
|
||
| impl<C: Curve, AttributeType: Attribute<C::Scalar>> Request<C, AttributeType> { | ||
| impl<Challenge: IsChallenge, C: Curve, AttributeType: Attribute<C::Scalar>> | ||
| Request<Challenge, C, AttributeType> | ||
| { | ||
| /// Construct a proof for the [`Request`] using the provided cryptographic | ||
| /// parameters and secrets. | ||
| pub fn prove<'a, P: Pairing<ScalarField = C::Scalar>, Signer: 'a + Web3IdSigner>( | ||
| self, | ||
| params: &GlobalContext<C>, | ||
| attrs: impl ExactSizeIterator<Item = CommitmentInputs<'a, P, C, AttributeType, Signer>>, | ||
| ) -> Result<Presentation<P, C, AttributeType>, ProofError> | ||
| ) -> Result<Presentation<Challenge, P, C, AttributeType>, ProofError> | ||
| where | ||
| AttributeType: 'a, | ||
| { | ||
| let mut proofs = Vec::with_capacity(attrs.len()); | ||
| let mut transcript = RandomOracle::domain("ConcordiumWeb3ID"); | ||
| transcript.add_bytes(self.challenge); | ||
| transcript.append_challenge(&self.challenge); | ||
| transcript.append_message(b"ctx", ¶ms); | ||
| let mut csprng = rand::thread_rng(); | ||
| if self.credential_statements.len() != attrs.len() { | ||
|
|
@@ -1823,7 +1973,7 @@ impl<C: Curve, AttributeType: Attribute<C::Scalar>> Request<C, AttributeType> { | |
| let proof = cred_statement.prove(params, &mut transcript, &mut csprng, attributes)?; | ||
| proofs.push(proof); | ||
| } | ||
| let to_sign = linking_proof_message_to_sign(self.challenge, &proofs); | ||
| let to_sign = linking_proof_message_to_sign(&self.challenge, &proofs); | ||
| // Linking proof | ||
| let mut proof_value = Vec::new(); | ||
| for signer in signers { | ||
|
|
@@ -2080,7 +2230,7 @@ mod tests { | |
| /// JSON serialization of requests and presentations is also tested. | ||
| fn test_web3_only() -> anyhow::Result<()> { | ||
| let mut rng = rand::thread_rng(); | ||
| let challenge = Challenge::new(rng.gen()); | ||
| let challenge = Sha256Challenge::new(rng.gen()); | ||
| let signer_1 = ed25519_dalek::SigningKey::generate(&mut rng); | ||
| let signer_2 = ed25519_dalek::SigningKey::generate(&mut rng); | ||
| let issuer_1 = ed25519_dalek::SigningKey::generate(&mut rng); | ||
|
|
@@ -2178,7 +2328,7 @@ mod tests { | |
| }, | ||
| ]; | ||
|
|
||
| let request = Request::<ArCurve, Web3IdAttribute> { | ||
| let request = Request::<Sha256Challenge, ArCurve, Web3IdAttribute> { | ||
| challenge, | ||
| credential_statements, | ||
| }; | ||
|
|
@@ -2274,14 +2424,16 @@ mod tests { | |
|
|
||
| let data = serde_json::to_string_pretty(&proof)?; | ||
| assert!( | ||
| serde_json::from_str::<Presentation<IpPairing, ArCurve, Web3IdAttribute>>(&data) | ||
| .is_ok(), | ||
| serde_json::from_str::< | ||
| Presentation<Sha256Challenge, IpPairing, ArCurve, Web3IdAttribute>, | ||
| >(&data) | ||
| .is_ok(), | ||
| "Cannot deserialize proof correctly." | ||
| ); | ||
|
|
||
| let data = serde_json::to_string_pretty(&request)?; | ||
| assert_eq!( | ||
| serde_json::from_str::<Request<ArCurve, Web3IdAttribute>>(&data)?, | ||
| serde_json::from_str::<Request<Sha256Challenge, ArCurve, Web3IdAttribute>>(&data)?, | ||
| request, | ||
| "Cannot deserialize request correctly." | ||
| ); | ||
|
|
@@ -2296,7 +2448,7 @@ mod tests { | |
| /// JSON serialization of requests and presentations is also tested. | ||
| fn test_mixed() -> anyhow::Result<()> { | ||
| let mut rng = rand::thread_rng(); | ||
| let challenge = Challenge::new(rng.gen()); | ||
| let challenge = Sha256Challenge::new(rng.gen()); | ||
| let params = GlobalContext::generate("Test".into()); | ||
| let cred_id_exp = ArCurve::generate_scalar(&mut rng); | ||
| let cred_id = CredentialRegistrationID::from_exponent(¶ms, cred_id_exp); | ||
|
|
@@ -2368,7 +2520,7 @@ mod tests { | |
| }, | ||
| ]; | ||
|
|
||
| let request = Request::<ArCurve, Web3IdAttribute> { | ||
| let request = Request::<Sha256Challenge, ArCurve, Web3IdAttribute> { | ||
| challenge, | ||
| credential_statements, | ||
| }; | ||
|
|
@@ -2462,14 +2614,16 @@ mod tests { | |
|
|
||
| let data = serde_json::to_string_pretty(&proof)?; | ||
| assert!( | ||
| serde_json::from_str::<Presentation<IpPairing, ArCurve, Web3IdAttribute>>(&data) | ||
| .is_ok(), | ||
| serde_json::from_str::< | ||
| Presentation<Sha256Challenge, IpPairing, ArCurve, Web3IdAttribute>, | ||
| >(&data) | ||
| .is_ok(), | ||
| "Cannot deserialize proof correctly." | ||
| ); | ||
|
|
||
| let data = serde_json::to_string_pretty(&request)?; | ||
| assert_eq!( | ||
| serde_json::from_str::<Request<ArCurve, Web3IdAttribute>>(&data)?, | ||
| serde_json::from_str::<Request<Sha256Challenge, ArCurve, Web3IdAttribute>>(&data)?, | ||
| request, | ||
| "Cannot deserialize request correctly." | ||
| ); | ||
|
|
||
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs on an item needs three slashes:
///There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Else it is just a comment