From c3a2b0d910e687424843dc369a6d821426e8535e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Fri, 31 May 2024 17:37:32 +0200 Subject: [PATCH] Add `AnyDidMethod::generate`. --- crates/dids/Cargo.toml | 4 +- crates/dids/core/src/did.rs | 4 ++ crates/dids/methods/key/src/lib.rs | 91 ++++++++++++++++------------- crates/dids/methods/pkh/Cargo.toml | 1 + crates/dids/methods/pkh/src/lib.rs | 92 ++++++++++++++++++------------ crates/dids/src/lib.rs | 41 +++++++++++++ 6 files changed, 157 insertions(+), 76 deletions(-) diff --git a/crates/dids/Cargo.toml b/crates/dids/Cargo.toml index d8736af80..e59d97201 100644 --- a/crates/dids/Cargo.toml +++ b/crates/dids/Cargo.toml @@ -24,6 +24,7 @@ aleo = ["did-pkh/aleo"] solana = ["did-pkh/solana"] [dependencies] +ssi-jwk.workspace = true ssi-dids-core.workspace = true did-ethr.workspace = true did-ion.workspace = true @@ -31,4 +32,5 @@ did-jwk.workspace = true did-method-key.workspace = true did-pkh.workspace = true did-tz.workspace = true -did-web.workspace = true \ No newline at end of file +did-web.workspace = true +thiserror.workspace = true \ No newline at end of file diff --git a/crates/dids/core/src/did.rs b/crates/dids/core/src/did.rs index 6bde0bf48..d306c10c0 100644 --- a/crates/dids/core/src/did.rs +++ b/crates/dids/core/src/did.rs @@ -221,6 +221,10 @@ impl DIDBuf { pub fn into_uri(self) -> UriBuf { unsafe { UriBuf::new_unchecked(self.0) } } + + pub fn into_string(self) -> String { + unsafe { String::from_utf8_unchecked(self.0) } + } } impl TryFrom for DIDBuf { diff --git a/crates/dids/methods/key/src/lib.rs b/crates/dids/methods/key/src/lib.rs index 6ddc18f78..e8b711f90 100644 --- a/crates/dids/methods/key/src/lib.rs +++ b/crates/dids/methods/key/src/lib.rs @@ -36,97 +36,110 @@ pub enum Unsupported { P384, } +#[derive(Debug, thiserror::Error)] +pub enum GenerateError { + #[error("missing curve")] + MissingCurve, + + #[error("unsupported curve `{0}`")] + UnsupportedCurve(String), + + #[error("unsupported key type")] + UnsupportedKeyType, + + #[error("invalid input key")] + InvalidInputKey, +} + pub struct DIDKey; impl DIDKey { - pub fn generate(jwk: &JWK) -> Option { + pub fn generate(jwk: &JWK) -> Result { use ssi_jwk::Params; let id = match jwk.params { - Params::OKP(ref params) => { - match ¶ms.curve[..] { - "Ed25519" => Some(multibase::encode( - multibase::Base::Base58Btc, - [DID_KEY_ED25519_PREFIX.to_vec(), params.public_key.0.clone()].concat(), - )), - "Bls12381G2" => Some(multibase::encode( - multibase::Base::Base58Btc, - [ - DID_KEY_BLS12381_G2_PREFIX.to_vec(), - params.public_key.0.clone(), - ] - .concat(), - )), - //_ => return Some(Err(DIDKeyError::UnsupportedCurve(params.curve.clone()))), - _ => return None, - } - } + Params::OKP(ref params) => match ¶ms.curve[..] { + "Ed25519" => multibase::encode( + multibase::Base::Base58Btc, + [DID_KEY_ED25519_PREFIX.to_vec(), params.public_key.0.clone()].concat(), + ), + "Bls12381G2" => multibase::encode( + multibase::Base::Base58Btc, + [ + DID_KEY_BLS12381_G2_PREFIX.to_vec(), + params.public_key.0.clone(), + ] + .concat(), + ), + _ => return Err(GenerateError::UnsupportedCurve(params.curve.clone())), + }, Params::EC(ref params) => { let curve = match params.curve { Some(ref curve) => curve, - None => return None, + None => return Err(GenerateError::MissingCurve), }; - match &curve[..] { + + match curve.as_str() { #[cfg(feature = "secp256k1")] "secp256k1" => { use k256::elliptic_curve::sec1::ToEncodedPoint; let pk = match k256::PublicKey::try_from(params) { Ok(pk) => pk, - Err(_err) => return None, + Err(_err) => return Err(GenerateError::InvalidInputKey), }; - Some(multibase::encode( + multibase::encode( multibase::Base::Base58Btc, [ DID_KEY_SECP256K1_PREFIX.to_vec(), pk.to_encoded_point(true).as_bytes().to_vec(), ] .concat(), - )) + ) } #[cfg(feature = "secp256r1")] "P-256" => { use p256::elliptic_curve::sec1::ToEncodedPoint; let pk = match p256::PublicKey::try_from(params) { Ok(pk) => pk, - Err(_err) => return None, + Err(_err) => return Err(GenerateError::InvalidInputKey), }; - Some(multibase::encode( + multibase::encode( multibase::Base::Base58Btc, [ DID_KEY_P256_PREFIX.to_vec(), pk.to_encoded_point(true).as_bytes().to_vec(), ] .concat(), - )) + ) } #[cfg(feature = "secp384r1")] "P-384" => { let pk_bytes = match ssi_jwk::serialize_p384(params) { Ok(pk) => pk, - Err(_err) => return None, + Err(_err) => return Err(GenerateError::InvalidInputKey), }; - Some(multibase::encode( + multibase::encode( multibase::Base::Base58Btc, [DID_KEY_P384_PREFIX.to_vec(), pk_bytes].concat(), - )) + ) } - //_ => return Some(Err(DIDKeyError::UnsupportedCurve(params.curve.clone()))), - _ => return None, + _ => return Err(GenerateError::UnsupportedCurve(curve.to_owned())), } } Params::RSA(ref params) => { - let der = simple_asn1::der_encode(¶ms.to_public()).ok()?; - Some(multibase::encode( + let der = simple_asn1::der_encode(¶ms.to_public()) + .map_err(|_| GenerateError::InvalidInputKey)?; + multibase::encode( multibase::Base::Base58Btc, [DID_KEY_RSA_PREFIX.to_vec(), der.to_vec()].concat(), - )) + ) } - _ => return None, // _ => return Some(Err(DIDKeyError::UnsupportedKeyType)), + _ => return Err(GenerateError::UnsupportedKeyType), }; - id.map(|id| DIDBuf::from_string(format!("did:key:{id}")).unwrap()) + Ok(DIDBuf::from_string(format!("did:key:{id}")).unwrap()) } } @@ -314,7 +327,7 @@ fn build_public_key(id: &str, data: &[u8]) -> Result<(PublicKey, VerificationMet Err(_) => Err(Error::InvalidMethodSpecificId(id.to_owned())), } #[cfg(not(feature = "secp256k1"))] - Err(Error::Internal(Box::new(Unsupported::Secp256k1))) + Err(Error::internal(Unsupported::Secp256k1)) } else if data[0] == DID_KEY_P256_PREFIX[0] && data[1] == DID_KEY_P256_PREFIX[1] { #[cfg(feature = "secp256r1")] { @@ -329,7 +342,7 @@ fn build_public_key(id: &str, data: &[u8]) -> Result<(PublicKey, VerificationMet )) } #[cfg(not(feature = "secp256r1"))] - return Err(Error::Internal(Box::new(Unsupported::P256))); + return Err(Error::internal(Unsupported::P256)); } else if data[0] == DID_KEY_P384_PREFIX[0] && data[1] == DID_KEY_P384_PREFIX[1] { #[cfg(feature = "secp384r1")] match ssi_jwk::p384_parse(&data[2..]) { diff --git a/crates/dids/methods/pkh/Cargo.toml b/crates/dids/methods/pkh/Cargo.toml index f02e71a24..b28ab32a9 100644 --- a/crates/dids/methods/pkh/Cargo.toml +++ b/crates/dids/methods/pkh/Cargo.toml @@ -32,6 +32,7 @@ async-trait.workspace = true bs58 = { workspace = true, features = ["check"] } bech32 = "0.8" chrono = { workspace = true, features = ["serde"] } +thiserror.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] chrono = { workspace = true, features = ["serde", "wasmbind"] } diff --git a/crates/dids/methods/pkh/src/lib.rs b/crates/dids/methods/pkh/src/lib.rs index 0718f22cc..8e9318207 100644 --- a/crates/dids/methods/pkh/src/lib.rs +++ b/crates/dids/methods/pkh/src/lib.rs @@ -525,31 +525,31 @@ impl DIDMethodResolver for DIDPKH { } } -fn generate_sol(jwk: &JWK) -> Option { +fn generate_sol(jwk: &JWK) -> Result { match jwk.params { Params::OKP(ref params) if params.curve == "Ed25519" => { - Some(bs58::encode(¶ms.public_key.0).into_string()) + Ok(bs58::encode(¶ms.public_key.0).into_string()) } - _ => None, + _ => Err(GenerateError::UnsupportedKeyType), } } #[cfg(feature = "ripemd-160")] -fn generate_btc(key: &JWK) -> Result { - let addr = ssi_jwk::ripemd160::hash_public_key(key, 0x00).map_err(|e| e.to_string())?; +fn generate_btc(key: &JWK) -> Result { + let addr = ssi_jwk::ripemd160::hash_public_key(key, 0x00).map_err(GenerateError::other)?; #[cfg(test)] if !addr.starts_with('1') { - return Err("Expected Bitcoin address".to_string()); + return Err(GenerateError::other("Expected Bitcoin address")); } Ok(addr) } #[cfg(feature = "ripemd-160")] -fn generate_doge(key: &JWK) -> Result { - let addr = ssi_jwk::ripemd160::hash_public_key(key, 0x1e).map_err(|e| e.to_string())?; +fn generate_doge(key: &JWK) -> Result { + let addr = ssi_jwk::ripemd160::hash_public_key(key, 0x1e).map_err(GenerateError::other)?; #[cfg(test)] if !addr.starts_with('D') { - return Err("Expected Dogecoin address".to_string()); + return Err(GenerateError::other("Expected Dogecoin address")); } Ok(addr) } @@ -558,8 +558,8 @@ fn generate_doge(key: &JWK) -> Result { fn generate_caip10_tezos( key: &JWK, ref_opt: Option, -) -> Result { - let hash = ssi_jwk::blakesig::hash_public_key(key).map_err(|e| e.to_string())?; +) -> Result { + let hash = ssi_jwk::blakesig::hash_public_key(key).map_err(GenerateError::other)?; let reference = ref_opt.unwrap_or_else(|| REFERENCE_TEZOS_MAINNET.to_string()); Ok(BlockchainAccountId { account_address: hash, @@ -574,8 +574,8 @@ fn generate_caip10_tezos( fn generate_caip10_eip155( key: &JWK, ref_opt: Option, -) -> Result { - let hash = ssi_jwk::eip155::hash_public_key_eip55(key).map_err(|e| e.to_string())?; +) -> Result { + let hash = ssi_jwk::eip155::hash_public_key_eip55(key).map_err(GenerateError::other)?; let reference = ref_opt.unwrap_or_else(|| REFERENCE_EIP155_ETHEREUM_MAINNET.to_string()); Ok(BlockchainAccountId { account_address: hash, @@ -590,24 +590,24 @@ fn generate_caip10_eip155( fn generate_caip10_bip122( key: &JWK, ref_opt: Option, -) -> Result { +) -> Result { let reference = ref_opt.unwrap_or_else(|| REFERENCE_BIP122_BITCOIN_MAINNET.to_string()); let addr; match &reference[..] { REFERENCE_BIP122_BITCOIN_MAINNET => { - addr = ssi_jwk::ripemd160::hash_public_key(key, 0x00).map_err(|e| e.to_string())?; + addr = ssi_jwk::ripemd160::hash_public_key(key, 0x00).map_err(GenerateError::other)?; if !addr.starts_with('1') { - return Err("Expected Bitcoin address".to_string()); + return Err(GenerateError::other("Expected Bitcoin address")); } } REFERENCE_BIP122_DOGECOIN_MAINNET => { - addr = ssi_jwk::ripemd160::hash_public_key(key, 0x1e).map_err(|e| e.to_string())?; + addr = ssi_jwk::ripemd160::hash_public_key(key, 0x1e).map_err(GenerateError::other)?; if !addr.starts_with('D') { - return Err("Expected Dogecoin address".to_string()); + return Err(GenerateError::other("Expected Dogecoin address")); } } _ => { - return Err("Expected Bitcoin address type".to_string()); + return Err(GenerateError::other("Expected Bitcoin address type")); } } @@ -666,7 +666,7 @@ fn generate_caip10_aleo(key: &JWK, ref_opt: Option) -> Result Result { +fn generate_caip10_did(key: &JWK, name: &str) -> Result { // Require name to be a either CAIP-2 namespace or a // full CAIP-2 string - namespace and reference (e.g. internal // chain id or genesis hash). @@ -676,7 +676,7 @@ fn generate_caip10_did(key: &JWK, name: &str) -> Result { let (namespace, reference_opt) = match name.splitn(2, ':').collect::>().as_slice() { [namespace] => (namespace.to_string(), None), [namespace, reference] => (namespace.to_string(), Some(reference.to_string())), - _ => return Err("Unable to parse chain id or namespace".to_string()), + _ => return Err(GenerateError::InvalidChainId), }; let account_id: BlockchainAccountId = match &namespace[..] { #[cfg(feature = "tezos")] @@ -689,35 +689,55 @@ fn generate_caip10_did(key: &JWK, name: &str) -> Result { "solana" => generate_caip10_solana(key, reference_opt)?, #[cfg(feature = "aleo")] "aleo" => generate_caip10_aleo(key, reference_opt)?, - _ => return Err("Namespace not supported".to_string()), + _ => return Err(GenerateError::UnsupportedNamespace), }; + Ok(DIDBuf::from_string(format!("did:pkh:{}", account_id)).unwrap()) } +#[derive(Debug, thiserror::Error)] +pub enum GenerateError { + #[error("Unable to parse chain id or namespace")] + InvalidChainId, + + #[error("Namespace not supported")] + UnsupportedNamespace, + + #[error("Unsupported key type")] + UnsupportedKeyType, + + #[error("{0}")] + Other(String), +} + +impl GenerateError { + pub fn other(e: impl ToString) -> Self { + Self::Other(e.to_string()) + } +} + impl DIDPKH { - pub fn generate(key: &JWK, pkh_name: &str) -> Option { - let addr = match match pkh_name { + pub fn generate(key: &JWK, pkh_name: &str) -> Result { + let addr = match pkh_name { // Aliases for did:pkh pre-CAIP-10. Deprecate? #[cfg(feature = "tezos")] - "tz" => ssi_jwk::blakesig::hash_public_key(key).ok(), + "tz" => ssi_jwk::blakesig::hash_public_key(key).map_err(GenerateError::other)?, #[cfg(feature = "eip")] - "eth" => ssi_jwk::eip155::hash_public_key(key).ok(), + "eth" => ssi_jwk::eip155::hash_public_key(key).map_err(GenerateError::other)?, #[cfg(feature = "eip")] - "celo" => ssi_jwk::eip155::hash_public_key(key).ok(), + "celo" => ssi_jwk::eip155::hash_public_key(key).map_err(GenerateError::other)?, #[cfg(feature = "eip")] - "poly" => ssi_jwk::eip155::hash_public_key(key).ok(), - "sol" => generate_sol(key), + "poly" => ssi_jwk::eip155::hash_public_key(key).map_err(GenerateError::other)?, + "sol" => generate_sol(key)?, #[cfg(feature = "ripemd-160")] - "btc" => generate_btc(key).ok(), + "btc" => generate_btc(key)?, #[cfg(feature = "ripemd-160")] - "doge" => generate_doge(key).ok(), + "doge" => generate_doge(key)?, // CAIP-10/CAIP-2 chain id - name => return generate_caip10_did(key, name).ok(), - } { - Some(addr) => addr, - None => return None, + name => return generate_caip10_did(key, name), }; - Some(DIDBuf::from_string(format!("did:pkh:{}:{}", pkh_name, addr)).unwrap()) + + Ok(DIDBuf::from_string(format!("did:pkh:{}:{}", pkh_name, addr)).unwrap()) } } diff --git a/crates/dids/src/lib.rs b/crates/dids/src/lib.rs index 9cc50f6f5..4c25d8713 100644 --- a/crates/dids/src/lib.rs +++ b/crates/dids/src/lib.rs @@ -23,6 +23,24 @@ pub use pkh::DIDPKH; pub use tz::DIDTz; pub use web::DIDWeb; +#[derive(Debug, thiserror::Error)] +pub enum GenerateError { + #[error(transparent)] + Ethr(ssi_jwk::Error), + + #[error(transparent)] + Key(key::GenerateError), + + #[error(transparent)] + Pkh(pkh::GenerateError), + + #[error(transparent)] + Tz(ssi_jwk::Error), + + #[error("unsupported method pattern `{0}`")] + UnsupportedMethodPattern(String), +} + #[derive(Default)] pub struct AnyDidMethod { ion: DIDION, @@ -33,6 +51,29 @@ impl AnyDidMethod { pub fn new(ion: DIDION, tz: DIDTz) -> Self { Self { ion, tz } } + + pub fn generate( + &self, + key: &ssi_jwk::JWK, + method_pattern: &str, + ) -> Result { + match method_pattern + .split_once(':') + .map(|(m, p)| (m, Some(p))) + .unwrap_or((method_pattern, None)) + { + ("ethr", None) => ethr::DIDEthr::generate(key).map_err(GenerateError::Ethr), + ("jwk", None) => Ok(jwk::DIDJWK::generate(key)), + ("key", None) => key::DIDKey::generate(key).map_err(GenerateError::Key), + ("pkh", Some(pkh_name)) => { + pkh::DIDPKH::generate(key, pkh_name).map_err(GenerateError::Pkh) + } + ("tz", None) => self.tz.generate(key).map_err(GenerateError::Tz), + _ => Err(GenerateError::UnsupportedMethodPattern( + method_pattern.to_string(), + )), + } + } } impl DIDResolver for AnyDidMethod {