From d297d6e862b65dbc1c60a5fb32dd2595ac9985f1 Mon Sep 17 00:00:00 2001 From: John Cairns Date: Wed, 14 Feb 2024 09:28:10 -0600 Subject: [PATCH] 47: add support for handling name bytes if they have nul - fixes documentation - add documentation test to ci --- .github/workflows/github-pages.yml | 8 ++- .vscode/launch.json | 4 +- Dockerfile | 2 + lib/src/resolver/did_registry.rs | 6 +++ lib/src/types/did_parser.rs | 30 ++++++++++- lib/src/types/did_url.rs | 6 +-- lib/src/types/ethr.rs | 87 ++++++++++++++++++++++++++---- resolver/src/main.rs | 7 ++- 8 files changed, 132 insertions(+), 18 deletions(-) diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml index c245849..a2a7b51 100644 --- a/.github/workflows/github-pages.yml +++ b/.github/workflows/github-pages.yml @@ -42,10 +42,14 @@ jobs: - name: Invoke cargo doc run: | rm -rf ./_site - cargo doc --workspace --no-deps + RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps rm -f target/doc/.lock cp -r target/doc _site - echo "" > _site/index.html + if [ ! -d _site/lib_didethresolver ]; then + echo "Error: _site/lib_didethresolver does not exist" + exit 1 + fi + echo "" > _site/index.html echo "Taking care of pedantic permissions requirements required by GitHub Pages" chmod -R +rX _site id: docgen diff --git a/.vscode/launch.json b/.vscode/launch.json index f4719eb..7664e3d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,7 +29,6 @@ "cargo": { "args": [ "build", - "--bin=resolver", "--package=resolver" ], "filter": { @@ -38,6 +37,9 @@ } }, "args": [], + "env": { + "RPC_URL": "wss://eth-sepolia.g.alchemy.com/v2/uoO_-LFYsnyxFuDC1amzK6e_EPPz8Sh8", + }, "cwd": "${workspaceFolder}" }, ] diff --git a/Dockerfile b/Dockerfile index f84123f..be4d6e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,8 @@ RUN cargo check --all-features RUN cargo fmt --check --all RUN cargo clippy --all-features --no-deps -- -D warnings RUN cargo test --workspace --all-features +ENV RUSTDOCFLAGS="-D warnings" +RUN cargo doc --all-features --workspace --no-deps RUN CARGO_TARGET_DIR=/workspaces/${PROJECT}/target cargo install --path resolver --bin=resolver --root ~${USER}/.cargo/ RUN valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ~${USER}/.cargo/bin/resolver --help diff --git a/lib/src/resolver/did_registry.rs b/lib/src/resolver/did_registry.rs index 7680569..68bf745 100644 --- a/lib/src/resolver/did_registry.rs +++ b/lib/src/resolver/did_registry.rs @@ -1,4 +1,5 @@ //! Generated ABI Functions, along with some extra to make it easier to interact with the registry. +use std::ffi::CStr; use crate::error::RegistrySignerError; use ethers::{ @@ -56,6 +57,11 @@ impl DidattributeChangedFilter { /// Get the name of the attribute as a string. non-UTF8 bytes will be replaced with the unicode replacement character, �. pub fn name_string_lossy(&self) -> String { + let is_cstr = self.name.iter().rev().any(|&c| c == 0); + if is_cstr { + let cstr = unsafe { CStr::from_ptr(self.name.as_ptr()) }; + return cstr.to_string_lossy().to_string(); + } String::from_utf8_lossy(self.name.as_ref()).to_string() } diff --git a/lib/src/types/did_parser.rs b/lib/src/types/did_parser.rs index af228da..7c6bb98 100644 --- a/lib/src/types/did_parser.rs +++ b/lib/src/types/did_parser.rs @@ -1,5 +1,4 @@ //! Parsing Expression Grammer (PEG) parsing rules for parts of a Decentralized Identifier - use ethers::types::Address; use crate::types::*; @@ -235,8 +234,11 @@ peg::parser! { #[cfg(test)] mod tests { + use super::*; + use std::ffi::CStr; + #[test] fn test_did_attribute_parser() { let keys = [ @@ -285,6 +287,32 @@ mod tests { } } + #[test] + fn test_parse_hex_c_string_svc() { + let decoded_data = + hex::decode("6469642f7376632f4d6573736167696e67536572766963650000000000000000") + .unwrap(); + let cstr = CStr::from_bytes_until_nul(decoded_data.as_slice()).unwrap(); + let parsed = parse_attribute(&cstr.as_ref().to_str().unwrap()); + assert_eq!(parsed, Ok(Attribute::Service(ServiceType::Messaging))); + } + + #[test] + fn test_parse_hex_c_string_pub() { + let decoded_data = + hex::decode("6469642f7075622f456432353531392f766572694b65792f62617365353800").unwrap(); + let cstr = CStr::from_bytes_until_nul(decoded_data.as_slice()).unwrap(); + let parsed = parse_attribute(&cstr.as_ref().to_str().unwrap()); + assert_eq!( + parsed, + Ok(Attribute::PublicKey(PublicKey { + key_type: KeyType::Ed25519VerificationKey2020, + purpose: KeyPurpose::VerificationKey, + encoding: KeyEncoding::Base58 + })) + ); + } + #[test] fn test_did_xmtp_attribute_parser() { let keys = [ diff --git a/lib/src/types/did_url.rs b/lib/src/types/did_url.rs index 3eca68b..1fb5af3 100644 --- a/lib/src/types/did_url.rs +++ b/lib/src/types/did_url.rs @@ -40,7 +40,7 @@ pub struct DidUrl { pub fragment: Option, } -/// The `did` part of a [did:ethr](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md) URL. returned by [`parse_ethr_did`] +/// The `did` part of a [did:ethr](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md) URL. returned by [`super::parse_ethr_did`] #[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] pub struct Did { pub method: Method, @@ -245,7 +245,7 @@ impl DidUrl { /// Retrieves the chainId for an DID:ETHR URL, as defined in the [did-ethr](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md). /// /// # Returns - /// A enum [`ChainId`] indicating the chain this DID belongs to. + /// A enum [`Network`] indicating the chain this DID belongs to. /// /// # Examples /// ``` @@ -261,7 +261,7 @@ impl DidUrl { /// Retrieves the identity part from the DID URL, as defined in the [did-ethr spec](https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md)). /// /// # Returns - /// A Enum [`AddressOrHexKey`] which identifies the DID. This can be either an 20-byte [`Address`] or a 33-byte [`Vec`]. + /// A Enum [`Account`] which identifies the DID. This can be either an 20-byte [`Address`] or a 33-byte [`Vec`]. /// /// # Examples /// ``` diff --git a/lib/src/types/ethr.rs b/lib/src/types/ethr.rs index 09634fd..7d940c3 100644 --- a/lib/src/types/ethr.rs +++ b/lib/src/types/ethr.rs @@ -182,16 +182,19 @@ impl EthrBuilder { event: DidattributeChangedFilter, ) -> Result<(), EthrBuilderError> { let name = event.name_string_lossy(); - let attribute = types::parse_attribute(&name).unwrap_or(Attribute::Other(name.to_string())); - - log::trace!( - "Attribute Event name={}, value={}, now={}, valid_to={}, attr={:?}", - name, - event.value, - self.now, - event.valid_to, - attribute - ); + + if log::log_enabled!(log::Level::Trace) { + log::trace!( + "Attribute Event name={}, value={}, now={}, valid_to={}", + name.clone(), + event.value_string_lossy(), + self.now, + event.valid_to + ); + } + + let attribute = + types::parse_attribute(&name).unwrap_or_else(|_err| Attribute::Other(name.to_owned())); let key = Key::Attribute { name: event.name, @@ -520,6 +523,40 @@ pub(crate) mod tests { ); } + #[test] + fn test_attribute_changed_ed25519_from_hex_bytes() { + let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); + let name_from_hex_str = + hex::decode("6469642f7075622f456432353531392f766572694b65792f6261736535380000") + .unwrap(); + let name_bytes: [u8; 32] = name_from_hex_str.try_into().unwrap(); + + let event = DidattributeChangedFilter { + name: name_bytes, + value: b"b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + ..base_attr_changed(identity, None) + }; + + let mut builder = EthrBuilder::default(); + builder.account_address(&identity).unwrap(); + builder.now(U256::zero()); + builder.attribute_event(event).unwrap(); + let doc = builder.build().unwrap(); + assert_eq!( + doc.verification_method[1], + VerificationMethod { + id: DidUrl::parse("did:ethr:0x7e575682a8e450e33eb0493f9972821ae333cd7f#delegate-0") + .unwrap(), + verification_type: KeyType::Ed25519VerificationKey2020, + controller: DidUrl::parse("did:ethr:0x7e575682a8e450e33eb0493f9972821ae333cd7f") + .unwrap(), + verification_properties: Some(VerificationMethodProperties::PublicKeyBase58 { + public_key_base58: "DV4G2kpBKjE6zxKor7Cj21iL9x9qyXb6emqjszBXcuhz".into() + }), + } + ); + } + #[test] fn test_attribute_changed_x25519() { let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); @@ -576,6 +613,36 @@ pub(crate) mod tests { ); } + #[test] + fn test_attribute_changed_service_from_hex_bytes() { + let name_data = + hex::decode("6469642f7376632f4d6573736167696e67536572766963650000000000000000") + .unwrap(); + let name_bytes: [u8; 32] = name_data.try_into().unwrap(); + let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); + let event = DidattributeChangedFilter { + name: name_bytes, + value: b"https://xmtp.com/resolver".into(), + ..base_attr_changed(identity, None) + }; + + let mut builder = EthrBuilder::default(); + builder.account_address(&identity).unwrap(); + builder.now(U256::zero()); + builder.attribute_event(event).unwrap(); + let doc = builder.build().unwrap(); + assert_eq!( + doc.service, + vec![Service { + id: DidUrl::parse("did:ethr:0x7e575682a8e450e33eb0493f9972821ae333cd7f#service-0") + .unwrap(), + service_type: ServiceType::Messaging, + service_endpoint: Url::parse("https://xmtp.com/resolver").unwrap(), + recipient_keys: "".into(), + }] + ); + } + #[test] fn test_attribute_increments_correctly() { let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); diff --git a/resolver/src/main.rs b/resolver/src/main.rs index b269de5..1632dc6 100644 --- a/resolver/src/main.rs +++ b/resolver/src/main.rs @@ -7,7 +7,7 @@ //! ### Endpoint //! //! ```text -//! POST /api/v1/resolveDid +//! POST //! ``` //! //! ### Request Format @@ -74,6 +74,11 @@ //! - All requests must be made over HTTPS. //! - Rate limiting is applied to prevent abuse. //! +//! ### Example +//! +//! ```bash +//! curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method":"did_resolveDid", "params": { "publicKey":"x"} }' http://localhost:8080 +//! ``` //! //! ### Support //!