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#[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 #[ld(id)]
39 pub id: IriBuf,
40
41 #[ld("sec:controller")]
43 pub controller: UriBuf,
44
45 #[serde(flatten)]
47 #[ld(flatten)]
48 pub public_key: PublicKey,
49}
50
51impl VerificationMethod for EcdsaSecp256k1RecoveryMethod2020 {
52 fn id(&self) -> &Iri {
54 self.id.as_iri()
55 }
56
57 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 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 let algorithm = digest_function.into_crypto_algorithm();
144 let key = ssi_jws::recover(algorithm, signing_bytes, signature)
145 .map_err(|_| ProofValidationError::InvalidSignature)?;
146
147 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 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}