diff --git a/Cargo.lock b/Cargo.lock index 28bd8c8..498c87f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1731,9 +1731,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown", @@ -1821,13 +1821,27 @@ name = "jsonrpsee" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9579d0ca9fb30da026bac2f0f7d9576ec93489aeb7cd4971dd5b4617d82c79b2" +dependencies = [ + "jsonrpsee-core 0.21.0", + "jsonrpsee-proc-macros 0.21.0", + "jsonrpsee-server 0.21.0", + "jsonrpsee-types 0.21.0", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a95f7cc23d5fab0cdeeaf6bad8c8f5e7a3aa7f0d211957ea78232b327ab27b0" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core", + "jsonrpsee-core 0.22.0", "jsonrpsee-http-client", - "jsonrpsee-proc-macros", - "jsonrpsee-server", - "jsonrpsee-types", + "jsonrpsee-proc-macros 0.22.0", + "jsonrpsee-server 0.22.0", + "jsonrpsee-types 0.22.0", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", "tokio", @@ -1836,15 +1850,15 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9f9ed46590a8d5681975f126e22531698211b926129a40a2db47cbca429220" +checksum = "6b1736cfa3845fd9f8f43751f2b8e0e83f7b6081e754502f7d63b6587692cc83" dependencies = [ "futures-channel", "futures-util", "gloo-net", "http", - "jsonrpsee-core", + "jsonrpsee-core 0.22.0", "pin-project", "rustls-native-certs 0.7.0", "rustls-pki-types", @@ -1863,6 +1877,28 @@ name = "jsonrpsee-core" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "776d009e2f591b78c038e0d053a796f94575d66ca4e77dd84bfc5e81419e436c" +dependencies = [ + "anyhow", + "async-trait", + "beef", + "futures-util", + "hyper", + "jsonrpsee-types 0.21.0", + "parking_lot", + "rand", + "rustc-hash", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82030d038658974732103e623ba2e0abec03bbbe175b39c0a2fafbada60c5868" dependencies = [ "anyhow", "async-lock", @@ -1871,7 +1907,7 @@ dependencies = [ "futures-timer", "futures-util", "hyper", - "jsonrpsee-types", + "jsonrpsee-types 0.22.0", "parking_lot", "pin-project", "rand", @@ -1887,15 +1923,15 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b7de9f3219d95985eb77fd03194d7c1b56c19bce1abfcc9d07462574b15572" +checksum = "36a06ef0de060005fddf772d54597bb6a8b0413da47dcffd304b0306147b9678" dependencies = [ "async-trait", "hyper", "hyper-rustls", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.22.0", + "jsonrpsee-types 0.22.0", "serde", "serde_json", "thiserror", @@ -1912,7 +1948,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d94b7505034e2737e688e1153bf81e6f93ad296695c43958d6da2e4321f0a990" dependencies = [ "heck", - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fc56131589f82e57805f7338b87023db4aafef813555708b159787e34ad6bc" +dependencies = [ + "heck", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -1927,8 +1976,32 @@ dependencies = [ "futures-util", "http", "hyper", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.21.0", + "jsonrpsee-types 0.21.0", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85be77fe5b2a94589e3164fb780017f7aff7d646b49278c0d0346af16975c8e" +dependencies = [ + "futures-util", + "http", + "hyper", + "jsonrpsee-core 0.22.0", + "jsonrpsee-types 0.22.0", "pin-project", "route-recognizer", "serde", @@ -1955,27 +2028,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "jsonrpsee-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a48fdc1202eafc51c63e00406575e59493284ace8b8b61aa16f3a6db5d64f1a" +dependencies = [ + "anyhow", + "beef", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "jsonrpsee-wasm-client" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30f36d27503d0efc0355c1630b74ecfb367050847bf7241a0ed75fab6dfa96c0" +checksum = "d36eb0e312840c69af0e5c6624fe959864d2e8b80bec194d2e20a39839ceef31" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.22.0", + "jsonrpsee-types 0.22.0", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "073c077471e89c4b511fa88b3df9a0f0abdf4a0a2e6683dd2ab36893af87bb2d" +checksum = "c5ce25d70a8e4d3cc574bbc3cad0137c326ad64b194793d5e7bbdd3fa4504181" dependencies = [ "http", "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.22.0", + "jsonrpsee-types 0.22.0", "url", ] @@ -2053,7 +2139,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lib-didethresolver" version = "0.1.0" -source = "git+https://github.com/xmtp/didethresolver?branch=main#a30b4da8b436cdbef6f93a17538612bc4d512e81" +source = "git+https://github.com/xmtp/didethresolver?branch=main#02a0e915755430cc7c47470f48dd581bca7bda4a" dependencies = [ "async-trait", "base64 0.21.7", @@ -2061,7 +2147,7 @@ dependencies = [ "chrono", "ethers", "hex", - "jsonrpsee", + "jsonrpsee 0.21.0", "log", "peg", "percent-encoding", @@ -2071,7 +2157,6 @@ dependencies = [ "sha3", "smart-default", "thiserror", - "tiny-keccak", "tokio", "tracing", "url", @@ -2249,7 +2334,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.48", @@ -2339,7 +2424,7 @@ version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 2.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -2628,14 +2713,22 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" dependencies = [ - "toml_datetime", "toml_edit 0.20.2", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -3731,6 +3824,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -3777,9 +3871,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -3808,6 +3902,17 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -4389,7 +4494,7 @@ dependencies = [ "futures", "gateway-types", "hex", - "jsonrpsee", + "jsonrpsee 0.22.0", "lib-didethresolver", "log", "rand", diff --git a/Cargo.toml b/Cargo.toml index 48d79e8..3fc3be0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,12 @@ serde = "1.0" serde_json = "1.0" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } async-trait = "0.1" -jsonrpsee = { version = "0.21", features = ["macros", "server", "client-core"] } +jsonrpsee = { version = "0.22", features = ["macros", "server", "client-core"] } anyhow = "1.0" thiserror = "1.0" ethers = { version = "2.0.11", features = ["abigen"] } ctor = "0.2" -lib-didethresolver = { git = "https://github.com/xmtp/didethresolver", package = "lib-didethresolver", branch = "main" } +lib-didethresolver = { git = "https://github.com/xmtp/didethresolver", branch = "main" } gateway-types = { path = "./gateway-types" } rustc-hex = "2.1" hex = "0.4" diff --git a/gateway-types/src/lib.rs b/gateway-types/src/lib.rs index f782625..dac8247 100644 --- a/gateway-types/src/lib.rs +++ b/gateway-types/src/lib.rs @@ -1,5 +1,7 @@ //! Shared types between XPS Gateawy and client (libxmtp) +use std::fmt; + use serde::{Deserialize, Serialize}; /// Address of the did:ethr Registry on Sepolia @@ -21,6 +23,8 @@ pub struct Message { pub s: Vec, } +pub type Bytes = Vec; + /// GrantInstallationResult represents the result of a grant installation operation in the DID registry. /// /// This struct encapsulates the outcome of an attempt to grant an installation, @@ -28,16 +32,52 @@ pub struct Message { /// transaction identifier associated with the blockchain transaction. /// /// # Fields -/// * `status` - A `String` indicating the outcome status of the operation. Typically, this -/// would be values like "Success" or "Failure". +/// * `status` - One of [`Status::Success`] or [`Status::Failed`], indicating the outcome of the +/// operation. /// * `message` - A `String` providing more detailed information about the operation. This /// can be a success message, error description, or any other relevant information. /// * `transaction` - A `String` representing the unique identifier of the transaction on the /// blockchain. This can be used to track the transaction in a blockchain explorer. /// -#[derive(Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct GrantInstallationResult { - pub status: String, + pub status: Status, pub message: String, pub transaction: String, } + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct KeyPackageResult { + /// Status of the operation + pub status: Status, + /// A message relating to the operation + pub message: String, + /// A list of key packages + pub installation: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum Status { + Success, + Failed, +} + +impl fmt::Display for Status { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Status::Success => write!(f, "success"), + Status::Failed => write!(f, "failed"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_status_display() { + assert_eq!(format!("{}", Status::Success), "success"); + assert_eq!(format!("{}", Status::Failed), "failed"); + } +} diff --git a/registry/src/error.rs b/registry/src/error.rs index 5d19b2b..b643878 100644 --- a/registry/src/error.rs +++ b/registry/src/error.rs @@ -18,4 +18,10 @@ pub enum ContactOperationError { ProviderError(#[from] ProviderError), #[error("Error converting from int: {0}")] IntConversion(#[from] TryFromIntError), + #[error("Error Resolving {1}: {0}")] + ResolutionError(lib_didethresolver::error::ResolverError, String), + #[error("The DID has been deactivated, and no longer valid")] + DIDDeactivated, + #[error("Type failed to convert")] + Type(#[from] lib_didethresolver::error::TypeError), } diff --git a/registry/src/lib.rs b/registry/src/lib.rs index 5b58f4c..e54d422 100644 --- a/registry/src/lib.rs +++ b/registry/src/lib.rs @@ -5,14 +5,14 @@ use std::str::FromStr; use error::ContactOperationError; use ethers::types::{H160, U256}; use ethers::{core::types::Signature, providers::Middleware, types::Address}; -use gateway_types::GrantInstallationResult; -use lib_didethresolver::{ - did_registry::DIDRegistry, - types::{Attribute, XmtpAttribute}, -}; +use gateway_types::{GrantInstallationResult, KeyPackageResult, Status}; +use lib_didethresolver::types::VerificationMethodProperties; +use lib_didethresolver::Resolver; +use lib_didethresolver::{did_registry::DIDRegistry, types::XmtpAttribute}; pub struct ContactOperations { registry: DIDRegistry, + resolver: Resolver, } impl ContactOperations @@ -21,9 +21,11 @@ where { /// Creates a new ContactOperations instance pub fn new(registry: DIDRegistry) -> Self { - Self { registry } + let resolver = registry.clone().into(); + Self { registry, resolver } } + /// Internal function to resolve a DID to an ethereum address fn resolve_did_address(&self, did: String) -> Result> { // for now, we will just assume the DID is a valid ethereum wallet address // TODO: Parse or resolve the actual DID @@ -31,6 +33,52 @@ where Ok(address) } + /// Fetches key packages for a given DID using [`Resolver::resolve_did`] + pub async fn fetch_key_packages( + &self, + did: String, + ) -> Result> { + let address = Address::from_str(&did)?; + + let resolution = self + .resolver + .resolve_did(address, None) + .await + .map_err(|e| ContactOperationError::ResolutionError(e, did))?; + + if resolution.metadata.deactivated { + return Err(ContactOperationError::DIDDeactivated); + } + + let document = resolution.document; + + let properties = document + .verification_method + .into_iter() + .filter(|method| { + method + .id + .fragment() + .map(|f| f.starts_with("xmtp-")) + .unwrap_or(false) + && method + .id + .contains_query("meta".into(), "installation".into()) + }) + .filter_map(|method| method.verification_properties) + .collect::>(); + + Ok(KeyPackageResult { + status: Status::Success, + message: "Key packages retrieved".to_string(), + installation: properties + .into_iter() + .map(TryFrom::try_from) + .collect::>()?, + }) + } + + /// Grants an XMTP installation via the did:ethr registry. pub async fn grant_installation( &self, did: String, @@ -40,7 +88,7 @@ where validity: U256, ) -> Result> { let address = self.resolve_did_address(did)?; - let attribute: [u8; 32] = Attribute::from(name).into(); + let attribute: [u8; 32] = name.into(); log::debug!( "setting attribute {:#?}", String::from_utf8_lossy(&attribute) @@ -61,12 +109,13 @@ where .await? .await?; Ok(GrantInstallationResult { - status: "completed".to_string(), + status: Status::Success, message: "Installation request complete.".to_string(), transaction: transaction_receipt.unwrap().transaction_hash.to_string(), }) } + /// Revokes an XMTP installation via the did:ethr registry. pub async fn revoke_installation( &self, did: String, @@ -75,7 +124,7 @@ where signature: Signature, ) -> Result<(), ContactOperationError> { let address = self.resolve_did_address(did)?; - let attribute: [u8; 32] = Attribute::from(name).into(); + let attribute: [u8; 32] = name.into(); log::debug!( "Revoking attribute {:#?}", String::from_utf8_lossy(&attribute) diff --git a/xps-gateway/src/rpc/api.rs b/xps-gateway/src/rpc/api.rs index 4b180ce..85abf69 100644 --- a/xps-gateway/src/rpc/api.rs +++ b/xps-gateway/src/rpc/api.rs @@ -4,8 +4,8 @@ use ethers::core::types::Signature; use ethers::prelude::*; use jsonrpsee::{proc_macros::rpc, types::ErrorObjectOwned}; -use gateway_types::GrantInstallationResult; use gateway_types::Message; +use gateway_types::{GrantInstallationResult, KeyPackageResult}; use lib_didethresolver::types::XmtpAttribute; /// XPS JSON-RPC Interface Methods @@ -222,6 +222,94 @@ pub trait Xps { signature: Signature, ) -> Result<(), ErrorObjectOwned>; + /// ## JSON-RPC Endpoint Documentation + /// + /// #### Request: + /// + /// - **Method:** `POST` + /// - **URL:** `/rpc/v1/fetchKeyPackages` + /// - **Headers:** + /// - `Content-Type: application/json` + /// - **Body:** + /// - **JSON Object:** + /// - `jsonrpc`: `"2.0"` + /// - `method`: `"fetchKeyPackages"` + /// - `params`: Array (optional parameters as required) + /// - `id`: Request identifier (integer or string) + /// + /// ### Endpoint: `fetchKeyPackages` + /// + /// #### Description + /// + /// The `fetchKeyPackages` endpoint is responsible for retrieving the contact bundle for the XMTP device installations. The request must be made to a valid did with an XMTP profile. + /// + /// #### Request + /// + /// The request for this endpoint should contain a valid DID. All returned information is public. + /// + /// ##### Parameters: + /// + /// - `DID` (string): Unique XMTP identifier for the user requesting the installation. + /// + /// ##### Example Request: + /// + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "method": "fetchKeyPackages", + /// "params": { + /// "did": "12345" + /// }, + /// "id": 1 + /// } + /// ``` + /// + /// #### Response + /// + /// The response will provide an optionally empty list of installation bundles. + /// + /// ##### Result Fields: + /// + /// - `status` (string): The status of the request, e.g., 'success'. + /// - `installation` (array): Array of installation bundles. + /// + /// ##### Example Response: + /// + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "result": { + /// "status": "success", + /// "installation": ["bundle1...", "bundle2..."] + /// }, + /// "id": 1 + /// } + /// ``` + /// + /// #### Error Handling + /// + /// In case of an error, the response will include an error object with details. + /// + /// ##### Error Object Fields: + /// + /// - `code` (integer): Numeric code representing the error type. + /// - `message` (string): Description of the error. + /// + /// ##### Example Error Response: + /// + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "error": { + /// "code": 403, + /// "message": "User not authorized for installation." + /// }, + /// "id": 1 + /// } + /// ``` + #[method(name = "fetchKeyPackages")] + async fn fetch_key_packages(&self, did: String) -> Result; + /// # Documentation for JSON RPC Endpoint: `status` /// ## Overview @@ -304,7 +392,7 @@ pub trait Xps { /// $ $ curl -H "Content-Type: application/json" -d '{"id":7000, "jsonrpc":"2.0", "method":"xps_status"}' http:///localhost:34695 /// {"jsonrpc":"2.0","result":"OK","id":7000} /// ``` - + /// /// ### Notes /// - The system should have proper error handling to deal with invalid requests, unauthorized access, and other potential issues. #[method(name = "status")] diff --git a/xps-gateway/src/rpc/methods.rs b/xps-gateway/src/rpc/methods.rs index 0a05697..25f3ce6 100644 --- a/xps-gateway/src/rpc/methods.rs +++ b/xps-gateway/src/rpc/methods.rs @@ -8,7 +8,7 @@ use jsonrpsee::types::error::ErrorCode; use async_trait::async_trait; use ethers::prelude::*; use ethers::{core::types::Signature, providers::Middleware}; -use gateway_types::GrantInstallationResult; +use gateway_types::{GrantInstallationResult, KeyPackageResult}; use jsonrpsee::types::ErrorObjectOwned; use lib_didethresolver::types::XmtpAttribute; use rand::{rngs::StdRng, SeedableRng}; @@ -95,6 +95,16 @@ impl XpsServer for XpsMethods

{ async fn wallet_address(&self) -> Result { Ok(self.wallet.address()) } + + async fn fetch_key_packages(&self, did: String) -> Result { + log::debug!("xps_fetchKeyPackages called"); + let result = self + .contact_operations + .fetch_key_packages(did) + .await + .map_err(RpcError::from)?; + Ok(result) + } } /// Error types for DID Registry JSON-RPC diff --git a/xps-gateway/tests/integration_test.rs b/xps-gateway/tests/integration_test.rs index 7bfde76..9abab96 100644 --- a/xps-gateway/tests/integration_test.rs +++ b/xps-gateway/tests/integration_test.rs @@ -1,17 +1,19 @@ mod integration_util; +use std::str::FromStr; + use anyhow::Error; -use ethers::signers::LocalWallet; +use ethers::{signers::LocalWallet, signers::Signer}; +use jsonrpsee::core::ClientError; use lib_didethresolver::{ did_registry::RegistrySignerExt, - types::{DidUrl, KeyEncoding, XmtpAttribute, XmtpKeyPurpose}, + types::{DidUrl, KeyEncoding, XmtpAttribute, XmtpKeyPurpose, NULL_ADDRESS}, }; -use xps_gateway::rpc::XpsClient; +use xps_gateway::rpc::*; use ethers::types::{Address, U256}; -use gateway_types::Message; -use xps_gateway::rpc::*; +use gateway_types::{Message, Status}; use integration_util::*; @@ -266,25 +268,11 @@ async fn test_grant_installation() -> Result<(), Error> { #[tokio::test] async fn test_revoke_installation() -> Result<(), Error> { with_xps_client(None, |client, context, resolver, anvil| async move { - let wallet: LocalWallet = anvil.keys()[3].clone().into(); - let me = get_user(&anvil, 3).await; + let me: LocalWallet = anvil.keys()[3].clone().into(); let name = *b"xmtp/installation/hex "; let value = b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71"; - let validity = U256::from(604_800); - let signature = wallet - .sign_attribute(&context.registry, name, value.to_vec(), validity) - .await?; - let attr = context.registry.set_attribute_signed( - me.address(), - signature.v.try_into().unwrap(), - signature.r.into(), - signature.s.into(), - name, - value.into(), - validity, - ); - attr.send().await?.await?; + set_attribute(name, value.to_vec(), &me, &context.registry).await?; let doc = resolver .resolve_did(me.address(), None) @@ -300,7 +288,7 @@ async fn test_revoke_installation() -> Result<(), Error> { .unwrap() ); - let signature = wallet + let signature = me .sign_revoke_attribute(&context.registry, name, value.to_vec()) .await?; @@ -340,3 +328,162 @@ async fn test_revoke_installation() -> Result<(), Error> { }) .await } + +#[tokio::test] +async fn test_fetch_key_packages() -> Result<(), Error> { + with_xps_client(None, |client, context, _, anvil| async move { + let me: LocalWallet = anvil.keys()[3].clone().into(); + let name = *b"xmtp/installation/hex "; + let value = b"000000000000000000000000000000000000000000000000000000000000000000"; + set_attribute(name, value.to_vec(), &me, &context.registry).await?; + + let value = b"111111111111111111111111111111111111111111111111111111111111111111"; + set_attribute(name, value.to_vec(), &me, &context.registry).await?; + + let res = client + .fetch_key_packages(format!("0x{}", hex::encode(me.address()))) + .await?; + + assert_eq!(res.status, Status::Success); + assert_eq!(&res.message, "Key packages retrieved"); + assert_eq!( + res.installation, + vec![ + hex::decode(b"000000000000000000000000000000000000000000000000000000000000000000") + .unwrap(), + hex::decode(b"111111111111111111111111111111111111111111111111111111111111111111") + .unwrap() + ] + ); + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_fetch_key_packages_revoke() -> Result<(), Error> { + with_xps_client(None, |client, context, _, anvil| async move { + let me: LocalWallet = anvil.keys()[3].clone().into(); + let name = *b"xmtp/installation/hex "; + let value = b"000000000000000000000000000000000000000000000000000000000000000000"; + set_attribute(name, value.to_vec(), &me, &context.registry).await?; + + let value = b"111111111111111111111111111111111111111111111111111111111111111111"; + set_attribute(name, value.to_vec(), &me, &context.registry).await?; + + client + .revoke_installation( + format!("0x{}", hex::encode(me.address())), + XmtpAttribute { + purpose: XmtpKeyPurpose::Installation, + encoding: KeyEncoding::Hex, + }, + value.to_vec(), + me.sign_revoke_attribute(&context.registry, name, value.to_vec()) + .await?, + ) + .await?; + + let res = client + .fetch_key_packages(format!("0x{}", hex::encode(me.address()))) + .await?; + + assert_eq!(res.status, Status::Success); + assert_eq!(&res.message, "Key packages retrieved"); + assert_eq!( + res.installation, + vec![hex::decode( + b"000000000000000000000000000000000000000000000000000000000000000000" + ) + .unwrap()] + ); + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_fetch_key_packages_client() -> Result<(), Error> { + with_xps_client(None, |client, context, _, anvil| async move { + let me: LocalWallet = anvil.keys()[3].clone().into(); + let attribute = XmtpAttribute { + purpose: XmtpKeyPurpose::Installation, + encoding: KeyEncoding::Hex, + }; + let value = b"000000000000000000000000000000000000000000000000000000000000000000"; + + client + .grant_installation( + format!("0x{}", hex::encode(me.address())), + attribute.clone(), + value.to_vec(), + me.sign_attribute( + &context.registry, + attribute.into(), + value.to_vec(), + U256::from(DEFAULT_ATTRIBUTE_VALIDITY), + ) + .await?, + ) + .await?; + let res = client + .fetch_key_packages(format!("0x{}", hex::encode(me.address()))) + .await?; + + assert_eq!(res.status, Status::Success); + assert_eq!(&res.message, "Key packages retrieved"); + assert_eq!( + res.installation, + vec![hex::decode( + b"000000000000000000000000000000000000000000000000000000000000000000" + ) + .unwrap()] + ); + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_did_deactivation() -> Result<(), Error> { + with_xps_client(None, |client, context, _, anvil| async move { + let me: LocalWallet = anvil.keys()[3].clone().into(); + + let new_owner = Address::from_str(NULL_ADDRESS).unwrap(); + let signature = me.sign_owner(&context.registry, new_owner).await.unwrap(); + let _ = context + .registry + .change_owner_signed( + me.address(), + signature.v.try_into().unwrap(), + signature.r.into(), + signature.s.into(), + new_owner, + ) + .send() + .await? + .await?; + + let res = client + .fetch_key_packages(format!("0x{}", hex::encode(me.address()))) + .await + .unwrap_err(); + + assert!(matches!(res, ClientError::Call(_))); + match res { + ClientError::Call(err) => { + assert_eq!(err.code(), -31999); + assert_eq!( + err.message(), + "The DID has been deactivated, and no longer valid" + ); + } + _ => panic!("Expected a client error. this should never match"), + } + Ok(()) + }) + .await +} diff --git a/xps-gateway/tests/integration_util/mod.rs b/xps-gateway/tests/integration_util/mod.rs index 492be3f..25a6232 100644 --- a/xps-gateway/tests/integration_util/mod.rs +++ b/xps-gateway/tests/integration_util/mod.rs @@ -11,10 +11,14 @@ use ethers::{ middleware::SignerMiddleware, providers::{Provider, Ws}, signers::{LocalWallet, Signer as _}, + types::U256, utils::AnvilInstance, }; use futures::future::FutureExt; -use lib_didethresolver::{did_registry::DIDRegistry, Resolver}; +use lib_didethresolver::{ + did_registry::{DIDRegistry, RegistrySignerExt}, + Resolver, +}; use std::{ future::Future, sync::{Arc, Once}, @@ -145,3 +149,27 @@ fn init_test_logging() { .init() }) } + +pub async fn set_attribute( + name: [u8; 32], + value: Vec, + wallet: &LocalWallet, + registry: &DIDRegistry>>, +) -> Result<(), Error> { + let validity = U256::from(604_800); + let signature = wallet + .sign_attribute(registry, name, value.to_vec(), validity) + .await?; + + let attr = registry.set_attribute_signed( + wallet.address(), + signature.v.try_into().unwrap(), + signature.r.into(), + signature.s.into(), + name, + value.into(), + validity, + ); + attr.send().await?.await?; + Ok(()) +}