From 705a319645e734a79df7dd6a27f223af80e569d9 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 22 Mar 2021 09:41:39 -0400 Subject: [PATCH] Add did:pkh:doge for Dogecoin addresses Check address prefixes --- did-pkh/src/lib.rs | 73 ++++++++++++++++++++++++++++++++++- did-pkh/tests/did-doge.jsonld | 18 +++++++++ src/caip10.rs | 13 ++++++- src/ripemd.rs | 6 +-- 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 did-pkh/tests/did-doge.jsonld diff --git a/did-pkh/src/lib.rs b/did-pkh/src/lib.rs index 012f9aafb..1ecd48e17 100644 --- a/did-pkh/src/lib.rs +++ b/did-pkh/src/lib.rs @@ -12,7 +12,9 @@ use ssi::did_resolve::{ }; use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; +// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-4.md const CHAIN_ID_BITCOIN_MAINNET: &str = "bip122:000000000019d6689c085ae165831e93"; +const CHAIN_ID_DOGECOIN_MAINNET: &str = "bip122:1a91e3dace36e2be3bf030a65679fe82"; /// did:pkh DID Method pub struct DIDPKH; @@ -202,6 +204,9 @@ async fn resolve_sol(did: &str, account_address: String) -> ResolutionResult { } async fn resolve_btc(did: &str, account_address: String) -> ResolutionResult { + if !account_address.starts_with("1") { + return resolution_error(&ERROR_INVALID_DID); + }; let blockchain_account_id = BlockchainAccountId { account_address, chain_id: CHAIN_ID_BITCOIN_MAINNET.to_string(), @@ -229,6 +234,37 @@ async fn resolve_btc(did: &str, account_address: String) -> ResolutionResult { resolution_result(doc) } +async fn resolve_doge(did: &str, account_address: String) -> ResolutionResult { + if !account_address.starts_with("D") { + return resolution_error(&ERROR_INVALID_DID); + } + let blockchain_account_id = BlockchainAccountId { + account_address, + chain_id: CHAIN_ID_DOGECOIN_MAINNET.to_string(), + }; + let vm_url = DIDURL { + did: did.to_string(), + fragment: Some("blockchainAccountId".to_string()), + ..Default::default() + }; + let vm = VerificationMethod::Map(VerificationMethodMap { + id: String::from(vm_url.clone()), + type_: "EcdsaSecp256k1RecoveryMethod2020".to_string(), + controller: did.to_string(), + blockchain_account_id: Some(blockchain_account_id.to_string()), + ..Default::default() + }); + let doc = Document { + context: Contexts::One(Context::URI(DEFAULT_CONTEXT.to_string())), + id: did.to_string(), + verification_method: Some(vec![vm]), + authentication: Some(vec![VerificationMethod::DIDURL(vm_url.clone())]), + assertion_method: Some(vec![VerificationMethod::DIDURL(vm_url)]), + ..Default::default() + }; + resolution_result(doc) +} + #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl DIDResolver for DIDPKH { @@ -247,6 +283,7 @@ impl DIDResolver for DIDPKH { "eth" => resolve_eth(did, data).await, "sol" => resolve_sol(did, data).await, "btc" => resolve_btc(did, data).await, + "doge" => resolve_doge(did, data).await, _ => resolution_error(&ERROR_INVALID_DID), } } @@ -265,6 +302,24 @@ fn generate_sol(jwk: &JWK) -> Option { } } +fn generate_btc(key: &JWK) -> Result { + let addr = ssi::ripemd::hash_public_key(key, 0x00)?; + #[cfg(test)] + if !addr.starts_with("1") { + return Err("Expected Bitcoin address".to_string()); + } + Ok(addr) +} + +fn generate_doge(key: &JWK) -> Result { + let addr = ssi::ripemd::hash_public_key(key, 0x1e)?; + #[cfg(test)] + if !addr.starts_with("D") { + return Err("Expected Dogecoin address".to_string()); + } + Ok(addr) +} + impl DIDMethod for DIDPKH { fn name(&self) -> &'static str { return "pkh"; @@ -279,7 +334,8 @@ impl DIDMethod for DIDPKH { "tz" => ssi::blakesig::hash_public_key(key).ok(), "eth" => ssi::keccak_hash::hash_public_key(key).ok(), "sol" => generate_sol(key), - "btc" => ssi::ripemd::hash_public_key(key).ok(), + "btc" => generate_btc(key).ok(), + "doge" => generate_doge(key).ok(), _ => None, } { Some(addr) => addr, @@ -406,6 +462,11 @@ mod tests { include_str!("../tests/did-btc.jsonld"), ) .await; + test_resolve( + "did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L", + include_str!("../tests/did-doge.jsonld"), + ) + .await; test_resolve_error("did:pkh:tz:foo", ERROR_INVALID_DID).await; test_resolve_error("did:pkh:eth:bar", ERROR_INVALID_DID).await; } @@ -640,5 +701,15 @@ mod tests { &ssi::ldp::EcdsaSecp256k1RecoverySignature2020, ) .await; + + println!("did:pkh:doge"); + credential_prove_verify_did_pkh( + key_secp256k1_recovery.clone(), + other_key_secp256k1.clone(), + "doge", + "#blockchainAccountId", + &ssi::ldp::EcdsaSecp256k1RecoverySignature2020, + ) + .await; } } diff --git a/did-pkh/tests/did-doge.jsonld b/did-pkh/tests/did-doge.jsonld new file mode 100644 index 000000000..cd81fa7eb --- /dev/null +++ b/did-pkh/tests/did-doge.jsonld @@ -0,0 +1,18 @@ +{ + "@context": "https://www.w3.org/ns/did/v1", + "id": "did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L", + "verificationMethod": [ + { + "id": "did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L#blockchainAccountId", + "type": "EcdsaSecp256k1RecoveryMethod2020", + "controller": "did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L", + "blockchainAccountId": "DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L@bip122:1a91e3dace36e2be3bf030a65679fe82" + } + ], + "authentication": [ + "did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L#blockchainAccountId" + ], + "assertionMethod": [ + "did:pkh:doge:DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L#blockchainAccountId" + ] +} diff --git a/src/caip10.rs b/src/caip10.rs index 0d6d30d36..b9e0c0e10 100644 --- a/src/caip10.rs +++ b/src/caip10.rs @@ -44,9 +44,18 @@ impl BlockchainAccountId { .map_err(|e| BlockchainAccountIdVerifyError::HashError(e.to_string())), ["solana"] => encode_ed25519(&jwk) .map_err(|e| BlockchainAccountIdVerifyError::HashError(e.to_string())), + // Bitcoin #[cfg(feature = "ripemd160")] - ["bip122", "000000000019d6689c085ae165831e93"] => crate::ripemd::hash_public_key(&jwk) - .map_err(|e| BlockchainAccountIdVerifyError::HashError(e.to_string())), + ["bip122", "000000000019d6689c085ae165831e93"] => { + crate::ripemd::hash_public_key(&jwk, 0x00) + .map_err(|e| BlockchainAccountIdVerifyError::HashError(e.to_string())) + } + // Dogecoin + #[cfg(feature = "ripemd160")] + ["bip122", "1a91e3dace36e2be3bf030a65679fe82"] => { + crate::ripemd::hash_public_key(&jwk, 0x1e) + .map_err(|e| BlockchainAccountIdVerifyError::HashError(e.to_string())) + } _ => Err(BlockchainAccountIdVerifyError::UnknownChainId( self.chain_id.clone(), )), diff --git a/src/ripemd.rs b/src/ripemd.rs index aeae0c168..f73b1d62b 100644 --- a/src/ripemd.rs +++ b/src/ripemd.rs @@ -6,9 +6,7 @@ use crate::jwk::{Params, JWK}; use ripemd160::{Digest, Ripemd160}; -const VERSION_BYTE_BITCOIN_MAINNET: u8 = 0; - -pub fn hash_public_key(jwk: &JWK) -> Result { +pub fn hash_public_key(jwk: &JWK, version: u8) -> Result { let ec_params = match jwk.params { Params::EC(ref params) => params, _ => return Err(Error::UnsupportedKeyType), @@ -21,7 +19,7 @@ pub fn hash_public_key(jwk: &JWK) -> Result { let pk_sha256 = sha256(&pk_bytes)?; let pk_ripemd160 = Ripemd160::digest(&pk_sha256); let mut extended_ripemd160 = Vec::with_capacity(21); - extended_ripemd160.extend_from_slice(&[VERSION_BYTE_BITCOIN_MAINNET]); + extended_ripemd160.extend_from_slice(&[version]); extended_ripemd160.extend_from_slice(&pk_ripemd160); let addr = bs58::encode(&extended_ripemd160).with_check().into_string(); Ok(addr)