Skip to content
This repository was archived by the owner on Feb 28, 2025. It is now read-only.

Commit 02a0e91

Browse files
authored
Fetch Key Packages Companion (#41)
* didUrl updates for query * query map * query pairs * remove indexmap * test coverage * re-run coverage * small tests * fix assert_eq! -> assert!
1 parent 64b6ca7 commit 02a0e91

File tree

7 files changed

+211
-29
lines changed

7 files changed

+211
-29
lines changed

Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/src/error.rs

+26
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,29 @@ pub enum RegistrySignerError<M: Middleware> {
5757
#[error(transparent)]
5858
Wallet(#[from] WalletError),
5959
}
60+
61+
/// General type error
62+
#[derive(Error, Debug)]
63+
pub enum TypeError {
64+
#[error(transparent)]
65+
HexConversion(#[from] hex::FromHexError),
66+
#[error(transparent)]
67+
Base58Conversion(#[from] bs58::decode::Error),
68+
#[error(transparent)]
69+
Base64Conversion(#[from] base64::DecodeError),
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use ethers::providers::{MockProvider, Provider};
75+
76+
use super::*;
77+
78+
#[test]
79+
fn test_resolver_error() {
80+
let err = ResolverError::<Provider<MockProvider>>::Middleware("test".to_string());
81+
let err_obj: ErrorObjectOwned = err.into();
82+
assert_eq!(err_obj.code(), -31000);
83+
assert_eq!(err_obj.message(), "test");
84+
}
85+
}

lib/src/resolver.rs

+29-8
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@ use crate::{
1919

2020
/// A resolver for did:ethr that follows the steps outlined in the [spec](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md#read-resolve) in order to resolve a did:ethr identifier.
2121
pub struct Resolver<M> {
22-
signer: Arc<M>,
2322
registry: DIDRegistry<M>,
2423
}
2524

25+
impl<M> From<DIDRegistry<M>> for Resolver<M> {
26+
fn from(registry: DIDRegistry<M>) -> Self {
27+
Self { registry }
28+
}
29+
}
30+
2631
impl<M: Middleware + 'static> Resolver<M> {
2732
/// Instantiate a new did:ethr resolver
2833
pub async fn new(middleware: M, registry: Address) -> Result<Self, ResolverError<M>> {
29-
let signer = Arc::new(middleware);
30-
let registry = DIDRegistry::new(registry, signer.clone());
34+
let registry = DIDRegistry::new(registry, middleware.into());
3135
log::debug!("Using deployed registry at {}", registry.address());
32-
Ok(Self { signer, registry })
36+
Ok(Self { registry })
3337
}
3438

3539
/// Resolve a did:ethr identifier
@@ -131,12 +135,12 @@ impl<M: Middleware + 'static> Resolver<M> {
131135
base_document.account_address(&public_key)?;
132136

133137
let current_block = self
134-
.signer
138+
.signer()
135139
.get_block_number()
136140
.await
137141
.map_err(|e| ResolverError::Middleware(e.to_string()))?;
138142
let current_block = self
139-
.signer
143+
.signer()
140144
.get_block(current_block)
141145
.await
142146
.map_err(|e| ResolverError::Middleware(e.to_string()))?;
@@ -182,7 +186,7 @@ impl<M: Middleware + 'static> Resolver<M> {
182186

183187
// get the timestamp for the current_verison_id
184188
let current_version_timestamp = self
185-
.signer
189+
.signer()
186190
.get_block(current_version_id)
187191
.await
188192
.map_err(|e| ResolverError::Middleware(e.to_string()))?
@@ -196,7 +200,7 @@ impl<M: Middleware + 'static> Resolver<M> {
196200
next_version_id: last_updated_did_version_id.map(|ver| ver.as_u64()),
197201
next_update: match last_updated_did_version_id {
198202
Some(ver) => self
199-
.signer
203+
.signer()
200204
.get_block(ver)
201205
.await
202206
.map_err(|e| ResolverError::Middleware(e.to_string()))?
@@ -211,4 +215,21 @@ impl<M: Middleware + 'static> Resolver<M> {
211215
};
212216
Ok(resolution_result)
213217
}
218+
219+
fn signer(&self) -> Arc<M> {
220+
self.registry.client()
221+
}
222+
}
223+
224+
#[cfg(test)]
225+
mod tests {
226+
use super::*;
227+
228+
#[test]
229+
fn test_from_registry() {
230+
let (provider, _) = ethers::providers::Provider::mocked();
231+
let registry = DIDRegistry::new(Address::zero(), Arc::new(provider));
232+
let resolver = Resolver::from(registry);
233+
assert_eq!(resolver.registry.address(), Address::zero());
234+
}
214235
}

lib/src/rpc/methods.rs

+16
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,19 @@ impl From<RpcError> for ErrorObjectOwned {
6666
}
6767
}
6868
}
69+
70+
#[cfg(test)]
71+
mod tests {
72+
use super::*;
73+
74+
#[test]
75+
fn test_rpc_error_conversion() {
76+
let gen_error = H160::from_str("0xerror").unwrap_err();
77+
let rpc_error = RpcError::from(gen_error);
78+
79+
let error_object = ErrorObjectOwned::from(rpc_error);
80+
81+
assert_eq!(error_object.code(), -31999);
82+
assert_eq!(error_object.message(), "Invalid public key format");
83+
}
84+
}

lib/src/types.rs

+89-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ mod did_url;
66
mod ethr;
77
mod xmtp;
88

9+
use crate::error::TypeError;
10+
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
911
use serde::{Deserialize, Serialize};
1012
use std::fmt;
1113
use url::Url;
@@ -120,23 +122,34 @@ pub enum VerificationMethodProperties {
120122
#[serde(rename = "publicKeyBase58")]
121123
public_key_base58: String,
122124
},
123-
/// Public key as a Json-Web-Key
124-
PublicKeyJwk {
125-
#[serde(rename = "publicKeyJwk")]
126-
public_key_jwk: String,
127-
},
128-
/// Public key in Multibase format
129-
PublicKeyMultibase {
130-
#[serde(rename = "publicKeyMultibase")]
131-
public_key_multibase: String,
132-
},
133125
/// Blockcahin account identitfier, case insensitive (does not support EIP-55)
134126
BlockchainAccountId {
135127
#[serde(rename = "blockchainAccountId")]
136128
blockchain_account_id: String,
137129
},
138130
}
139131

132+
impl TryFrom<VerificationMethodProperties> for Vec<u8> {
133+
type Error = TypeError;
134+
135+
fn try_from(prop: VerificationMethodProperties) -> Result<Vec<u8>, Self::Error> {
136+
match prop {
137+
VerificationMethodProperties::PublicKeyHex { public_key_hex } => {
138+
Ok(hex::decode(public_key_hex)?)
139+
}
140+
VerificationMethodProperties::PublicKeyBase64 { public_key_base64 } => {
141+
Ok(BASE64.decode(public_key_base64)?)
142+
}
143+
VerificationMethodProperties::PublicKeyBase58 { public_key_base58 } => {
144+
Ok(bs58::decode(public_key_base58).into_vec()?)
145+
}
146+
VerificationMethodProperties::BlockchainAccountId {
147+
blockchain_account_id,
148+
} => Ok(hex::decode(blockchain_account_id)?),
149+
}
150+
}
151+
}
152+
140153
/// Represents different types of services associated with a DID.
141154
/// Currently, only [`ServiceType::Messaging`] is directly supported
142155
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
@@ -253,14 +266,20 @@ impl From<Attribute> for String {
253266
// will be cutoff.
254267
impl From<Attribute> for [u8; 32] {
255268
fn from(attribute: Attribute) -> [u8; 32] {
256-
let mut attr_bytes: [u8; 32] = [b' '; 32];
257-
let attr_string = attribute.to_string();
258-
let length = std::cmp::min(attr_string.as_bytes().len(), 32);
259-
attr_bytes[0..length].copy_from_slice(&attr_string.as_bytes()[0..length]);
260-
attr_bytes
269+
string_to_bytes32(attribute.to_string())
261270
}
262271
}
263272

273+
// internal function to fill a [u8; 32] with bytes.
274+
// anything over 32 bytes will be cutoff.
275+
fn string_to_bytes32<S: AsRef<str>>(s: S) -> [u8; 32] {
276+
let s = s.as_ref();
277+
let mut attr_bytes: [u8; 32] = [b' '; 32];
278+
let length = std::cmp::min(s.as_bytes().len(), 32);
279+
attr_bytes[0..length].copy_from_slice(&s.as_bytes()[0..length]);
280+
attr_bytes
281+
}
282+
264283
/// Indicates the encoding of a key in a did:ethr attribute
265284
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
266285
pub enum KeyEncoding {
@@ -369,7 +388,7 @@ mod test {
369388
{
370389
"controller": "did:ethr:mainnet:0x6ceb0bf1f28ca4165d5c0a04f61dc733987ed6ad",
371390
"id": "did:ethr:mainnet:0x6ceb0bf1f28ca4165d5c0a04f61dc733987ed6ad",
372-
"publicKeyMultibase": "0x6ceb0bf1f28ca4165d5c0a04f61dc733987ed6ad",
391+
"blockchainAccountId": "0x6ceb0bf1f28ca4165d5c0a04f61dc733987ed6ad",
373392
"type": "Ed25519VerificationKey2020"
374393
}
375394
]
@@ -405,8 +424,8 @@ mod test {
405424
.unwrap(),
406425
verification_type: KeyType::Ed25519VerificationKey2020,
407426
verification_properties: Some(
408-
VerificationMethodProperties::PublicKeyMultibase {
409-
public_key_multibase: "0x6ceb0bf1f28ca4165d5c0a04f61dc733987ed6ad"
427+
VerificationMethodProperties::BlockchainAccountId {
428+
blockchain_account_id: "0x6ceb0bf1f28ca4165d5c0a04f61dc733987ed6ad"
410429
.to_string(),
411430
}
412431
),
@@ -545,4 +564,56 @@ mod test {
545564
let encoding = KeyEncoding::Base58;
546565
assert_eq!(String::from(encoding), "base58".to_string());
547566
}
567+
568+
#[test]
569+
fn test_string_to_bytes32() {
570+
let s = "xmtp/installation/base58";
571+
let bytes: [u8; 32] = string_to_bytes32(s);
572+
assert_eq!(
573+
String::from_utf8_lossy(&bytes),
574+
"xmtp/installation/base58 "
575+
);
576+
577+
let s = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
578+
let bytes: [u8; 32] = string_to_bytes32(s);
579+
assert_eq!(
580+
String::from_utf8_lossy(&bytes),
581+
s.chars().take(32).collect::<String>()
582+
);
583+
}
584+
585+
#[test]
586+
fn test_verification_method_properties_converts_to_bytes() {
587+
let prop = VerificationMethodProperties::PublicKeyHex {
588+
public_key_hex: "0000000000000000000000000000000000000000".to_string(),
589+
};
590+
let bytes: Vec<u8> = prop.try_into().unwrap();
591+
assert_eq!(
592+
bytes,
593+
hex::decode("0000000000000000000000000000000000000000").unwrap()
594+
);
595+
596+
let b64 = BASE64.encode("base64");
597+
let prop = VerificationMethodProperties::PublicKeyBase64 {
598+
public_key_base64: b64.clone(),
599+
};
600+
let bytes: Vec<u8> = prop.try_into().unwrap();
601+
assert_eq!(bytes, BASE64.decode(b64).unwrap());
602+
603+
let b58 = bs58::encode("base58").into_string();
604+
let prop = VerificationMethodProperties::PublicKeyBase58 {
605+
public_key_base58: b58.clone(),
606+
};
607+
let bytes: Vec<u8> = prop.try_into().unwrap();
608+
assert_eq!(bytes, bs58::decode(b58.clone()).into_vec().unwrap());
609+
610+
let prop = VerificationMethodProperties::BlockchainAccountId {
611+
blockchain_account_id: "0000000000000000000000000000000000000000".to_string(),
612+
};
613+
let bytes: Vec<u8> = prop.try_into().unwrap();
614+
assert_eq!(
615+
bytes,
616+
hex::decode("0000000000000000000000000000000000000000").unwrap()
617+
);
618+
}
548619
}

lib/src/types/did_url.rs

+28
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,13 @@ impl DidUrl {
282282
self.path.as_deref()
283283
}
284284

285+
pub fn contains_query(&self, key: String, value: String) -> bool {
286+
self.query
287+
.as_ref()
288+
.map(|q| q.contains(&(key, value)))
289+
.unwrap_or(false)
290+
}
291+
285292
pub fn query(&self) -> Option<&[(String, String)]> {
286293
self.query.as_deref()
287294
}
@@ -717,4 +724,25 @@ mod tests {
717724
assert_eq!(Network::from("sepolia"), Network::Sepolia);
718725
assert_eq!(Network::from("1a1"), Network::Other(0x1a1));
719726
}
727+
728+
#[test]
729+
fn test_contains_query() {
730+
let did_url = DidUrl::parse("did:ethr:mainnet:0x0000000000000000000000000000000000000000?meta=hi&username=&password=hunter2").unwrap();
731+
732+
assert!(did_url.contains_query("meta".into(), "hi".into()));
733+
assert!(did_url.contains_query("username".into(), "".into()));
734+
assert!(did_url.contains_query("password".into(), "hunter2".into()));
735+
assert!(!did_url.contains_query("does".into(), "not exist".into()));
736+
737+
let did_url =
738+
DidUrl::parse("did:ethr:mainnet:0x0000000000000000000000000000000000000000").unwrap();
739+
assert!(!did_url.contains_query("no".into(), "queries".into()));
740+
}
741+
742+
#[test]
743+
fn test_network_to_string() {
744+
assert_eq!(Network::Mainnet.to_string(), "mainnet");
745+
assert_eq!(Network::Sepolia.to_string(), "sepolia");
746+
assert_eq!(Network::Other(0x1a1).to_string(), "417");
747+
}
720748
}

lib/src/types/xmtp.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//! *_NOTE:_* This format may be updated and changed according to the needs of the XMTP protocol. (currently
1515
//! in development)
1616
17-
use super::{Attribute, EthrBuilder, KeyEncoding, KeyType, VerificationMethod};
17+
use super::{string_to_bytes32, Attribute, EthrBuilder, KeyEncoding, KeyType, VerificationMethod};
1818
use crate::error::EthrBuilderError;
1919

2020
use std::fmt;
@@ -30,6 +30,12 @@ pub struct XmtpAttribute {
3030
pub encoding: KeyEncoding,
3131
}
3232

33+
impl From<XmtpAttribute> for [u8; 32] {
34+
fn from(attr: XmtpAttribute) -> Self {
35+
string_to_bytes32(attr.to_string())
36+
}
37+
}
38+
3339
impl fmt::Display for XmtpAttribute {
3440
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3541
write!(f, "xmtp/{}/{}", self.purpose, self.encoding)
@@ -89,6 +95,7 @@ impl EthrBuilder {
8995
verification_type: KeyType::Ed25519VerificationKey2020,
9096
verification_properties: Self::encode_attribute_value(value, key.encoding)?,
9197
};
98+
9299
match key.purpose {
93100
XmtpKeyPurpose::Installation => {
94101
self.authentication.push(method.id.clone());
@@ -191,4 +198,17 @@ mod test {
191198
assert_eq!(purpose.to_string(), "installation");
192199
assert_eq!(String::from(purpose), "installation");
193200
}
201+
202+
#[test]
203+
fn test_xmtp_attribute_bytes() {
204+
let attr = XmtpAttribute {
205+
purpose: XmtpKeyPurpose::Installation,
206+
encoding: KeyEncoding::Hex,
207+
};
208+
209+
assert_eq!(
210+
b"xmtp/installation/hex ".as_slice(),
211+
<[u8; 32]>::from(attr)
212+
);
213+
}
194214
}

0 commit comments

Comments
 (0)