Skip to main content

ssi_verification_methods/methods/w3c/
ecdsa_secp_256k1_recovery_method_2020.rs

1use hex::FromHexError;
2use iref::{Iri, IriBuf, UriBuf};
3use rdf_types::{Interpretation, Vocabulary};
4use serde::{Deserialize, Serialize};
5use ssi_claims_core::{InvalidProof, MessageSignatureError, ProofValidationError, ProofValidity};
6use ssi_crypto::algorithm::ES256KR;
7use ssi_jwk::JWK;
8use ssi_verification_methods_core::{VerificationMethodSet, VerifyBytes};
9use static_iref::iri;
10use std::{borrow::Cow, hash::Hash, str::FromStr};
11
12use crate::{
13    ExpectedType, GenericVerificationMethod, InvalidVerificationMethod, SigningMethod,
14    TypedVerificationMethod, VerificationMethod,
15};
16
17pub const ECDSA_SECP_256K1_RECOVERY_METHOD_2020_TYPE: &str = "EcdsaSecp256k1RecoveryMethod2020";
18
19/// EcdsaSecp256k1RecoveryMethod2020 verification method.
20///
21/// See: <https://w3c-ccg.github.io/security-vocab/#EcdsaSecp256k1RecoveryMethod2020>
22#[derive(
23    Debug,
24    Clone,
25    PartialEq,
26    Eq,
27    Hash,
28    Serialize,
29    Deserialize,
30    linked_data::Serialize,
31    linked_data::Deserialize,
32)]
33#[serde(tag = "type", rename = "EcdsaSecp256k1RecoveryMethod2020")]
34#[ld(prefix("sec" = "https://w3id.org/security#"))]
35#[ld(type = "sec:EcdsaSecp256k1RecoveryMethod2020")]
36pub struct EcdsaSecp256k1RecoveryMethod2020 {
37    /// Key identifier.
38    #[ld(id)]
39    pub id: IriBuf,
40
41    /// Key controller.
42    #[ld("sec:controller")]
43    pub controller: UriBuf,
44
45    /// Public key.
46    #[serde(flatten)]
47    #[ld(flatten)]
48    pub public_key: PublicKey,
49}
50
51impl VerificationMethod for EcdsaSecp256k1RecoveryMethod2020 {
52    /// Returns the identifier of the key.
53    fn id(&self) -> &Iri {
54        self.id.as_iri()
55    }
56
57    /// Returns an URI to the key controller.
58    fn controller(&self) -> Option<&Iri> {
59        Some(self.controller.as_iri())
60    }
61}
62
63impl VerificationMethodSet for EcdsaSecp256k1RecoveryMethod2020 {
64    type TypeSet = &'static str;
65
66    fn type_set() -> Self::TypeSet {
67        Self::NAME
68    }
69}
70
71impl TypedVerificationMethod for EcdsaSecp256k1RecoveryMethod2020 {
72    fn expected_type() -> Option<ExpectedType> {
73        Some(
74            ECDSA_SECP_256K1_RECOVERY_METHOD_2020_TYPE
75                .to_string()
76                .into(),
77        )
78    }
79
80    fn type_match(ty: &str) -> bool {
81        ty == ECDSA_SECP_256K1_RECOVERY_METHOD_2020_TYPE
82    }
83
84    /// Returns the type of the key.
85    fn type_(&self) -> &str {
86        ECDSA_SECP_256K1_RECOVERY_METHOD_2020_TYPE
87    }
88}
89
90#[derive(Debug, thiserror::Error)]
91pub enum SignatureError {
92    #[error("invalid secret key")]
93    InvalidSecretKey,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub enum DigestFunction {
98    Sha256,
99    Keccack,
100}
101
102impl DigestFunction {
103    pub fn into_crypto_algorithm(self) -> ssi_jwk::Algorithm {
104        match self {
105            Self::Sha256 => ssi_jwk::Algorithm::ES256KR,
106            Self::Keccack => ssi_jwk::Algorithm::ESKeccakKR,
107        }
108    }
109}
110
111impl EcdsaSecp256k1RecoveryMethod2020 {
112    pub const NAME: &'static str = ECDSA_SECP_256K1_RECOVERY_METHOD_2020_TYPE;
113    pub const IRI: &'static Iri =
114        iri!("https://w3id.org/security#EcdsaSecp256k1RecoveryMethod2020");
115
116    pub fn public_key_jwk(&'_ self) -> Option<Cow<'_, JWK>> {
117        self.public_key.to_jwk()
118    }
119
120    pub fn sign(
121        &self,
122        secret_key: &JWK,
123        data: &[u8],
124        digest_function: DigestFunction,
125    ) -> Result<Vec<u8>, SignatureError> {
126        let algorithm = digest_function.into_crypto_algorithm();
127        let key_algorithm = secret_key.algorithm.unwrap_or(algorithm);
128        if !algorithm.is_compatible_with(key_algorithm) {
129            return Err(SignatureError::InvalidSecretKey);
130        }
131
132        ssi_jws::sign_bytes(algorithm, data, secret_key)
133            .map_err(|_| SignatureError::InvalidSecretKey)
134    }
135
136    pub fn verify_bytes(
137        &self,
138        signing_bytes: &[u8],
139        signature: &[u8],
140        digest_function: DigestFunction,
141    ) -> Result<ProofValidity, ProofValidationError> {
142        // Recover the key used to sign the message.
143        let algorithm = digest_function.into_crypto_algorithm();
144        let key = ssi_jws::recover(algorithm, signing_bytes, signature)
145            .map_err(|_| ProofValidationError::InvalidSignature)?;
146
147        // Check the validity of the signing key.
148        let matching_keys = self
149            .public_key
150            .matches(&key)
151            .map_err(|_| ProofValidationError::InvalidProof)?;
152        if !matching_keys {
153            return Ok(Err(InvalidProof::KeyMismatch));
154        }
155
156        // Verify the signature.
157        Ok(
158            ssi_jws::verify_bytes(algorithm, signing_bytes, &key, signature)
159                .map_err(|_| InvalidProof::Signature),
160        )
161    }
162}
163
164impl VerifyBytes<ES256KR> for EcdsaSecp256k1RecoveryMethod2020 {
165    fn verify_bytes(
166        &self,
167        _: ES256KR,
168        signing_bytes: &[u8],
169        signature: &[u8],
170    ) -> Result<ProofValidity, ProofValidationError> {
171        self.verify_bytes(signing_bytes, signature, DigestFunction::Sha256)
172    }
173}
174
175#[derive(
176    Debug,
177    Clone,
178    PartialEq,
179    Eq,
180    Hash,
181    Serialize,
182    Deserialize,
183    linked_data::Serialize,
184    linked_data::Deserialize,
185)]
186#[ld(prefix("sec" = "https://w3id.org/security#"))]
187pub enum PublicKey {
188    #[serde(rename = "publicKeyJwk")]
189    #[ld("sec:publicKeyJwk")]
190    Jwk(Box<JWK>),
191
192    #[serde(rename = "publicKeyHex")]
193    #[ld("sec:publicKeyHex")]
194    Hex(Box<PublicKeyHex>),
195
196    #[serde(rename = "ethereumAddress")]
197    #[ld("sec:ethereumAddress")]
198    EthereumAddress(ssi_security::EthereumAddressBuf),
199
200    #[serde(rename = "blockchainAccountId")]
201    #[ld("sec:blockchainAccoundId")]
202    BlockchainAccountId(ssi_caips::caip10::BlockchainAccountId),
203}
204
205#[derive(Debug, thiserror::Error)]
206pub enum InvalidPublicKey {
207    #[error("invalid hex encoding: {0}")]
208    Hex(#[from] FromHexError),
209
210    #[error("invalid key bytes: {0}")]
211    K256(#[from] k256::elliptic_curve::Error),
212
213    #[error("invalid key parameters")]
214    InvalidParams,
215
216    #[error("unknown chain id `{0}`")]
217    UnknownChainId(String),
218
219    #[error("unable to hash public key `{0}`")]
220    HashError(String),
221}
222
223impl From<InvalidPublicKey> for ProofValidationError {
224    fn from(_value: InvalidPublicKey) -> Self {
225        Self::InvalidKey
226    }
227}
228
229impl PublicKey {
230    pub fn to_jwk(&'_ self) -> Option<Cow<'_, JWK>> {
231        match self {
232            Self::Jwk(jwk) => Some(Cow::Borrowed(jwk)),
233            Self::Hex(hex) => Some(Cow::Owned(hex.to_jwk())),
234            Self::EthereumAddress(_) => None,
235            Self::BlockchainAccountId(_) => None,
236        }
237    }
238
239    pub fn matches(&self, other: &JWK) -> Result<bool, InvalidPublicKey> {
240        match self {
241            Self::Jwk(jwk) => Ok(jwk.equals_public(other)),
242            Self::Hex(hex) => Ok(hex.to_jwk().equals_public(other)),
243            Self::EthereumAddress(a) => {
244                let ssi_jwk::Params::EC(params) = &other.params else {
245                    return Err(InvalidPublicKey::InvalidParams);
246                };
247
248                let pk: k256::PublicKey = params
249                    .try_into()
250                    .map_err(|_| InvalidPublicKey::InvalidParams)?;
251                let b = ssi_crypto::hashes::keccak::hash_public_key(&pk);
252                Ok(a.as_str() == b.as_str())
253            }
254            Self::BlockchainAccountId(id) => {
255                use ssi_caips::caip10::BlockchainAccountIdVerifyError as VerifyError;
256
257                match id.verify(other) {
258                    Err(VerifyError::UnknownChainId(name)) => {
259                        Err(InvalidPublicKey::UnknownChainId(name))
260                    }
261                    Err(VerifyError::HashError(e)) => Err(InvalidPublicKey::HashError(e)),
262                    Err(VerifyError::KeyMismatch(_, _)) => Ok(false),
263                    Ok(()) => Ok(true),
264                }
265            }
266        }
267    }
268}
269
270#[derive(Debug, Clone)]
271pub struct PublicKeyHex {
272    encoded: String,
273    decoded: k256::PublicKey,
274}
275
276impl PublicKeyHex {
277    pub fn decode(encoded: String) -> Result<Self, InvalidPublicKey> {
278        let bytes = hex::decode(&encoded)?;
279        let decoded = k256::PublicKey::from_sec1_bytes(&bytes)?;
280
281        Ok(Self { encoded, decoded })
282    }
283
284    pub fn to_jwk(&self) -> JWK {
285        self.decoded.into()
286    }
287}
288
289impl PartialEq for PublicKeyHex {
290    fn eq(&self, other: &Self) -> bool {
291        self.decoded.eq(&other.decoded)
292    }
293}
294
295impl Eq for PublicKeyHex {}
296
297impl Hash for PublicKeyHex {
298    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
299        self.encoded.hash(state)
300    }
301}
302
303impl Serialize for PublicKeyHex {
304    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
305    where
306        S: serde::Serializer,
307    {
308        self.encoded.serialize(serializer)
309    }
310}
311
312impl<'a> Deserialize<'a> for PublicKeyHex {
313    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
314    where
315        D: serde::Deserializer<'a>,
316    {
317        use serde::de::Error;
318        let encoded = String::deserialize(deserializer)?;
319        Self::decode(encoded).map_err(D::Error::custom)
320    }
321}
322
323impl FromStr for PublicKeyHex {
324    type Err = InvalidPublicKey;
325
326    fn from_str(s: &str) -> Result<Self, Self::Err> {
327        Self::decode(s.to_owned())
328    }
329}
330
331impl<I: Interpretation, V: Vocabulary> linked_data::LinkedDataResource<I, V> for PublicKeyHex
332where
333    String: linked_data::LinkedDataResource<I, V>,
334{
335    fn interpretation(
336        &'_ self,
337        vocabulary: &mut V,
338        interpretation: &mut I,
339    ) -> linked_data::ResourceInterpretation<'_, I, V> {
340        self.encoded.interpretation(vocabulary, interpretation)
341    }
342}
343
344impl<I: Interpretation, V: Vocabulary> linked_data::LinkedDataSubject<I, V> for PublicKeyHex
345where
346    String: linked_data::LinkedDataSubject<I, V>,
347{
348    fn visit_subject<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
349    where
350        S: linked_data::SubjectVisitor<I, V>,
351    {
352        self.encoded.visit_subject(serializer)
353    }
354}
355
356impl<I: Interpretation, V: Vocabulary> linked_data::LinkedDataPredicateObjects<I, V>
357    for PublicKeyHex
358where
359    String: linked_data::LinkedDataPredicateObjects<I, V>,
360{
361    fn visit_objects<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
362    where
363        S: linked_data::PredicateObjectsVisitor<I, V>,
364    {
365        self.encoded.visit_objects(visitor)
366    }
367}
368
369impl TryFrom<GenericVerificationMethod> for EcdsaSecp256k1RecoveryMethod2020 {
370    type Error = InvalidVerificationMethod;
371
372    fn try_from(mut m: GenericVerificationMethod) -> Result<Self, Self::Error> {
373        let public_key = match (
374            m.properties.remove("publicKeyJwk"),
375            m.properties.get("publicKeyHex"),
376            m.properties.get("ethereumAddress"),
377            m.properties.get("blockchainAccountId"),
378        ) {
379            (Some(k), None, None, None) => {
380                PublicKey::Jwk(Box::new(serde_json::from_value(k).map_err(|_| {
381                    InvalidVerificationMethod::invalid_property("publicKeyJwk")
382                })?))
383            }
384            (None, Some(k), None, None) => PublicKey::Hex(Box::new(
385                k.as_str()
386                    .ok_or_else(|| InvalidVerificationMethod::invalid_property("publicKeyHex"))?
387                    .parse()
388                    .map_err(|_| InvalidVerificationMethod::invalid_property("publicKeyHex"))?,
389            )),
390            (None, None, Some(k), None) => PublicKey::EthereumAddress(
391                k.as_str()
392                    .ok_or_else(|| InvalidVerificationMethod::invalid_property("ethereumAddress"))?
393                    .parse()
394                    .map_err(|_| InvalidVerificationMethod::invalid_property("ethereumAddress"))?,
395            ),
396            (None, None, None, Some(k)) => PublicKey::BlockchainAccountId(
397                k.as_str()
398                    .ok_or_else(|| {
399                        InvalidVerificationMethod::invalid_property("blockchainAccountId")
400                    })?
401                    .parse()
402                    .map_err(|_| {
403                        InvalidVerificationMethod::invalid_property("blockchainAccountId")
404                    })?,
405            ),
406            (None, None, None, None) => {
407                return Err(InvalidVerificationMethod::missing_property("publicKeyJwk"))
408            }
409            _ => return Err(InvalidVerificationMethod::AmbiguousPublicKey),
410        };
411
412        Ok(Self {
413            id: m.id,
414            controller: m.controller,
415            public_key,
416        })
417    }
418}
419
420impl SigningMethod<JWK, ssi_crypto::algorithm::ES256KR> for EcdsaSecp256k1RecoveryMethod2020 {
421    fn sign_bytes(
422        &self,
423        secret: &JWK,
424        _algorithm: ssi_crypto::algorithm::ES256KR,
425        bytes: &[u8],
426    ) -> Result<Vec<u8>, MessageSignatureError> {
427        self.sign(secret, bytes, DigestFunction::Sha256)
428            .map_err(MessageSignatureError::signature_failed)
429    }
430}
431
432impl SigningMethod<JWK, ssi_crypto::algorithm::ESKeccakKR> for EcdsaSecp256k1RecoveryMethod2020 {
433    fn sign_bytes(
434        &self,
435        secret: &JWK,
436        _algorithm: ssi_crypto::algorithm::ESKeccakKR,
437        bytes: &[u8],
438    ) -> Result<Vec<u8>, MessageSignatureError> {
439        self.sign(secret, bytes, DigestFunction::Keccack)
440            .map_err(MessageSignatureError::signature_failed)
441    }
442}