From c7f5f1eac84a1274d8927a0ba1e5d83c2fb9b9b9 Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Sat, 28 Jan 2023 19:54:44 +0200 Subject: [PATCH 1/7] Add tests for all requests --- src/api/keys.rs | 0 src/api/keys/requests.rs | 0 src/api/keys/responses.rs | 0 tests/api/ethereum.rs | 0 tests/api/keys.rs | 0 tests/api/mod.rs | 0 tests/test.rs | 0 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/api/keys.rs create mode 100644 src/api/keys/requests.rs create mode 100644 src/api/keys/responses.rs create mode 100644 tests/api/ethereum.rs create mode 100644 tests/api/keys.rs create mode 100644 tests/api/mod.rs create mode 100644 tests/test.rs diff --git a/src/api/keys.rs b/src/api/keys.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/api/keys/requests.rs b/src/api/keys/requests.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/api/keys/responses.rs b/src/api/keys/responses.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/ethereum.rs b/tests/api/ethereum.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/keys.rs b/tests/api/keys.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/mod.rs b/tests/api/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..e69de29 From 19f1df35c221875419993c4d2451dc548a6e3d94 Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Sat, 28 Jan 2023 19:57:31 +0200 Subject: [PATCH 2/7] Add support of Keys API set --- Cargo.toml | 5 + examples/simple/src/main.rs | 4 +- src/api.rs | 199 +++++++++++++++- src/api/ethereum/requests.rs | 21 ++ src/api/keys.rs | 2 + src/api/keys/requests.rs | 154 +++++++++++++ src/api/keys/responses.rs | 28 +++ tests/api/ethereum.rs | 274 ++++++++++++++++++++++ tests/api/keys.rs | 430 +++++++++++++++++++++++++++++++++++ tests/api/mod.rs | 2 + tests/test.rs | 1 + 11 files changed, 1113 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca65780..c6dcea4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,8 @@ serde_json = "1.0.91" rustify = "0.5.3" rustify_derive = "0.5.2" eth_checksum = "0.1.2" +base64 = "0.21.0" + +[dev-dependencies] +tokio = { version = "1.20.1", features = ["full"] } +wiremock = "0.5.17" diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index 54921e5..a45dc51 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -9,7 +9,7 @@ async fn main() { let client = VaultClient::new( VaultClientSettingsBuilder::default() .address("http://127.0.0.1:8200") - .token("s.abx7eCduabnQNgMJZs5TECdi") + .token("hvs.E4wFaEETAP1TwJjG9S1qNA7J") .build() .unwrap() ).unwrap(); @@ -20,7 +20,7 @@ async fn main() { let addresses = quorum_vault_client::api::list_accouns(&client, "quorum").await.unwrap(); println!("addresses: {:?}", addresses); - let address = Address::from_str("0x8d3113e29CB92F44F1762E52D2a0276509b36b82").unwrap(); + let address = addresses.keys.first().copied().unwrap(); let result = quorum_vault_client::api::read_account(&client, "quorum", address).await.unwrap(); println!("result: {:?}", result); diff --git a/src/api.rs b/src/api.rs index 289aca6..e0cfa83 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,15 +1,24 @@ -use crate::api::ethereum::requests::{ - CreateEthereumAccountRequest, ListEthereumAccountsRequest, ReadEthereumAccountRequest, - SignEthereumTransactionRequest, -}; +use crate::api::ethereum::requests::{CreateEthereumAccountRequest, ImportPrivateKeyRequest, ListEthereumAccountsRequest, ReadEthereumAccountRequest, SignEthereumTransactionRequest}; use crate::api::ethereum::responses::{ EthereumAccountResponse, EthereumAccountsResponse, EthereumSignTransactionResponse, }; +use crate::api::keys::requests::{CreateKeyRequest, DestroyKeyRequest, ImportKeyRequest, ListKeysRequest, ReadKeyRequest, SignRequest, UpdateKeyTagsRequest}; +use crate::api::keys::responses::{KeyResponse, KeysResponse, SignResponse}; use crate::error::ClientError; +use std::collections::HashMap; +use base64::Engine; use vaultrs::client::Client; +use web3::signing::keccak256; use web3::types::{Address, TransactionRequest}; pub mod ethereum; +pub mod keys; + +/// Key crypto algorithm. +pub enum KeyCryptoAlgorithm { + Secp256k1, + Babyjubjub, +} /// Create a new Ethereum account. /// @@ -30,7 +39,7 @@ pub async fn create_account( /// List Ethereum accounts. /// /// See [ListEthereumAccountsRequest] -pub async fn list_accouns( +pub async fn list_accounts( client: &impl Client, mount: &str, ) -> Result { @@ -90,3 +99,183 @@ pub async fn sign_transaction( .await .map_err(Into::into) } + +/// Import a Private Key +/// See [ImportPrivateKeyRequest] +pub async fn import_private_key( + client: &impl Client, + mount: &str, + private_key: &str, +) -> Result { + let request = ImportPrivateKeyRequest::builder() + .mount(mount) + .private_key(private_key) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// Create a new Key +/// See [CreateKeyRequest] +pub async fn create_key( + client: &impl Client, + mount: &str, + id: &str, + algorithm: KeyCryptoAlgorithm, + tags: HashMap, +) -> Result { + let request = CreateKeyRequest::builder() + .mount(mount) + .id(id) + .signing_algorithm(algorithm.signing_algorithm().to_string()) + .curve(algorithm.curve().to_string()) + .tags(tags) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// Read a Key +/// See [ReadKeyRequest] +pub async fn read_key( + client: &impl Client, + mount: &str, + id: &str, +) -> Result { + let request = ReadKeyRequest::builder() + .mount(mount) + .id(id) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// List Keys +/// See [ListKeysRequest] +pub async fn list_keys(client: &impl Client, mount: &str) -> Result { + let request = ListKeysRequest::builder().mount(mount).build().unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// Update a Key tags +/// See [UpdateKeyTagsRequest] +pub async fn update_key_tags( + client: &impl Client, + mount: &str, + id: &str, + tags: HashMap, +) -> Result { + let request = UpdateKeyTagsRequest::builder() + .mount(mount) + .id(id) + .tags(tags) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// Destroy a Key +/// See [DestroyKeyRequest] +pub async fn destroy_key(client: &impl Client, mount: &str, id: &str) -> Result<(), ClientError> { + let request = DestroyKeyRequest::builder() + .mount(mount) + .id(id) + .build() + .unwrap(); + vaultrs::api::exec_with_empty_result(client, request) + .await + .map_err(Into::into) +} + +/// Import a Key +/// See [ImportKeyRequest] +pub async fn import_key( + client: &impl Client, + mount: &str, + id: &str, + algorithm: KeyCryptoAlgorithm, + tags: HashMap, + private_key: &str, +) -> Result { + let request = ImportKeyRequest::builder() + .mount(mount) + .id(id) + .signing_algorithm(algorithm.signing_algorithm().to_string()) + .curve(algorithm.curve().to_string()) + .tags(tags) + .private_key(private_key) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// Sign a message +/// See [SignRequest] +pub async fn sign( + client: &impl Client, + mount: &str, + id: &str, + data: &[u8], +) -> Result { + let hash = keccak256(data); + let encoded = base64::prelude::BASE64_URL_SAFE.encode(hash); + let request = SignRequest::builder() + .mount(mount) + .id(id) + .data(encoded) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +/// Sign a message +/// See [SignRequest] +pub async fn sign_raw( + client: &impl Client, + mount: &str, + id: &str, + data: [u8; 32], +) -> Result { + let encoded = base64::prelude::BASE64_URL_SAFE.encode(&data); + let request = SignRequest::builder() + .mount(mount) + .id(id) + .data(encoded) + .build() + .unwrap(); + vaultrs::api::exec_with_result(client, request) + .await + .map_err(Into::into) +} + +impl KeyCryptoAlgorithm { + /// Returns the curve name for the algorithm + pub fn curve(&self) -> &'static str { + match self { + KeyCryptoAlgorithm::Secp256k1 => "secp256k1", + KeyCryptoAlgorithm::Babyjubjub => "babyjubjub", + } + } + + /// Returns the signing algorithm name for the algorithm + pub fn signing_algorithm(&self) -> &'static str { + match self { + KeyCryptoAlgorithm::Secp256k1 => "ecdsa", + KeyCryptoAlgorithm::Babyjubjub => "eddsa", + } + } +} diff --git a/src/api/ethereum/requests.rs b/src/api/ethereum/requests.rs index 6c24953..be75940 100644 --- a/src/api/ethereum/requests.rs +++ b/src/api/ethereum/requests.rs @@ -98,3 +98,24 @@ pub struct SignEthereumTransactionRequest { #[endpoint(body)] pub to: String, } + +/// ## Import Private Key +/// This endpoint imports a private key. +/// +/// * Path: {self.mount}/ethereum/accounts/import +/// * Method: POST +/// * Response: [EthereumAccountResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/ethereum/accounts/import", + method = "POST", + response = "EthereumAccountResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct ImportPrivateKeyRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(body)] + pub private_key: String, +} diff --git a/src/api/keys.rs b/src/api/keys.rs index e69de29..116da0f 100644 --- a/src/api/keys.rs +++ b/src/api/keys.rs @@ -0,0 +1,2 @@ +pub mod requests; +pub mod responses; diff --git a/src/api/keys/requests.rs b/src/api/keys/requests.rs index e69de29..9cb4cb4 100644 --- a/src/api/keys/requests.rs +++ b/src/api/keys/requests.rs @@ -0,0 +1,154 @@ +use std::collections::HashMap; +use rustify_derive::Endpoint; +use crate::api::keys::responses::{KeyResponse, KeysResponse, SignResponse}; + +/// ## Create Key +/// This endpoint creates a new key with given crypto algorithm and curve. +/// +/// * Path: {self.mount}/keys +/// * Method: POST +/// * Response: [KeyResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys", + method = "POST", + response = "KeyResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct CreateKeyRequest { + #[endpoint(skip)] + pub mount: String, + pub signing_algorithm: String, + pub curve: String, + pub tags: HashMap, + pub id: String, +} + +/// ## Read Key +/// This endpoint gets a key by ID. +/// +/// * Path: {self.mount}/keys/{self.id} +/// * Method: GET +/// * Response: [KeyResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys/{self.id}", + method = "GET", + response = "KeyResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct ReadKeyRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(skip)] + pub id: String, +} + +/// ## List Keys +/// This endpoint gets all keys. +/// +/// * Path: {self.mount}/keys +/// * Method: GET +/// * Response: [KeysResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys", + method = "GET", + response = "KeysResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct ListKeysRequest { + #[endpoint(skip)] + pub mount: String, +} + +/// ## Destroy Key +/// This endpoint destroys a key by ID. +/// +/// * Path: {self.mount}/keys/{self.id}/destroy +/// * Method: DELETE +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys/{self.id}/destroy", + method = "DELETE", + builder = "true" +)] +#[builder(setter(into))] +pub struct DestroyKeyRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(skip)] + pub id: String, +} + +/// ## Import Key +/// This endpoint imports a key with given crypto algorithm and curve. +/// +/// * Path: {self.mount}/keys/import +/// * Method: POST +/// * Response: [KeyResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys/import", + method = "POST", + response = "KeyResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct ImportKeyRequest { + #[endpoint(skip)] + pub mount: String, + pub signing_algorithm: String, + pub curve: String, + pub tags: HashMap, + pub private_key: String, + pub id: String, +} + +/// ## Update tags of Key +/// This endpoint updates the tags of a key by ID. +/// +/// * Path: {self.mount}/keys/{self.id} +/// * Method: POST +/// * Response: [KeyResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys/{self.id}", + method = "POST", + response = "KeyResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct UpdateKeyTagsRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(skip)] + pub id: String, + pub tags: HashMap, +} + +/// ## Sign Data +/// This endpoint signs data with a key by ID. +/// The data is base64 encoded before signing. +/// +/// * Path: {self.mount}/keys/{self.id}/sign +/// * Method: POST +/// * Response: [SignResponse] +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/keys/{self.id}/sign", + method = "POST", + response = "SignResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct SignRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(skip)] + pub id: String, + pub data: String, +} diff --git a/src/api/keys/responses.rs b/src/api/keys/responses.rs index e69de29..86cbcc2 100644 --- a/src/api/keys/responses.rs +++ b/src/api/keys/responses.rs @@ -0,0 +1,28 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + +/// Response from executing [CreateKeyRequest][crate::api::keys::requests::CreateKeyRequest] +#[derive(Deserialize, Debug, Serialize)] +pub struct KeyResponse { + pub created_at: String, + pub curve: String, + pub id: String, + pub namespace: String, + pub public_key: String, + pub signing_algorithm: String, + pub tags: HashMap, + pub updated_at: String, + pub version: u64, +} + +/// Response from executing [ListKeysRequest][crate::api::keys::requests::ListKeysRequest] +#[derive(Deserialize, Debug, Serialize)] +pub struct KeysResponse { + pub keys: Vec, +} + +/// Response from executing [SignRequest][crate::api::keys::requests::SignRequest] +#[derive(Deserialize, Debug, Serialize)] +pub struct SignResponse { + pub signature: String, +} diff --git a/tests/api/ethereum.rs b/tests/api/ethereum.rs index e69de29..7692c17 100644 --- a/tests/api/ethereum.rs +++ b/tests/api/ethereum.rs @@ -0,0 +1,274 @@ +use quorum_vault_client::api; +use std::str::FromStr; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; +use web3::types::{Address, TransactionRequest, U256}; +use wiremock::matchers::{body_json, method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +#[tokio::test] +async fn test_create_wallet() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let response = serde_json::json!({ + "request_id": "f69eaa6c-1fe8-faa4-cf0b-6901d69955a8", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "address": "0xe4acE2Af74f04689bF0BB86F349747761F31270e", + "compressed_public_key": "0x031bb8452df3cd3a997a74f0ebbaed4e85136af34532b915eb4cefb0f4feda0a56", + "namespace": "", + "public_key": "0x041bb8452df3cd3a997a74f0ebbaed4e85136af34532b915eb4cefb0f4feda0a5697f47f6093b0a1e16de93c5a478771e6e30ac71de08fcb3bc89706630d24aac5" + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path("/v1/quorum/ethereum/accounts")) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let wallet = api::create_account(&vault_client, "quorum").await.unwrap(); + + assert_eq!( + format!("{:?}", wallet.address), + "0xe4acE2Af74f04689bF0BB86F349747761F31270e".to_lowercase() + ); + assert_eq!( + wallet.compressed_public_key, + "0x031bb8452df3cd3a997a74f0ebbaed4e85136af34532b915eb4cefb0f4feda0a56" + ); + assert_eq!(wallet.public_key, "0x041bb8452df3cd3a997a74f0ebbaed4e85136af34532b915eb4cefb0f4feda0a5697f47f6093b0a1e16de93c5a478771e6e30ac71de08fcb3bc89706630d24aac5"); + assert_eq!(wallet.namespace, ""); +} + +#[tokio::test] +async fn test_get_list_accounts() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let response = serde_json::json!({ + "request_id": "c189630e-9c1b-23e8-4e91-7e00b4e4135f", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "keys": [ + "0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5", + "0xe4acE2Af74f04689bF0BB86F349747761F31270e" + ] + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("GET")) + .and(path("/v1/quorum/ethereum/accounts")) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let accounts = api::list_accounts(&vault_client, "quorum").await.unwrap(); + + assert_eq!(accounts.keys.len(), 2); + assert_eq!( + format!("{:?}", accounts.keys[0]), + "0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5".to_lowercase() + ); + assert_eq!( + format!("{:?}", accounts.keys[1]), + "0xe4acE2Af74f04689bF0BB86F349747761F31270e".to_lowercase() + ); +} + +#[tokio::test] +async fn test_read_account() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let address = Address::from_str("0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5").unwrap(); + + let response = serde_json::json!({ + "request_id": "09096b87-393b-00a0-a8ec-540d6023850d", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "address": "0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5", + "compressed_public_key": "0x03e4e5a8a5a1c909b70e8a9b9ce8cb02dc7e2783580d6e5babb989cb595e407704", + "namespace": "", + "public_key": "0x04e4e5a8a5a1c909b70e8a9b9ce8cb02dc7e2783580d6e5babb989cb595e4077040c1f6b6c38ac7ec3b74880aa578c6151e835e6265db6ce4d1e45a5c31855a689" + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("GET")) + .and(path( + "/v1/quorum/ethereum/accounts/0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5", + )) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let account = api::read_account(&vault_client, "quorum", address) + .await + .unwrap(); + + assert_eq!( + format!("{:?}", account.address), + "0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5".to_lowercase() + ); + assert_eq!( + account.compressed_public_key, + "0x03e4e5a8a5a1c909b70e8a9b9ce8cb02dc7e2783580d6e5babb989cb595e407704" + ); + assert_eq!(account.public_key, "0x04e4e5a8a5a1c909b70e8a9b9ce8cb02dc7e2783580d6e5babb989cb595e4077040c1f6b6c38ac7ec3b74880aa578c6151e835e6265db6ce4d1e45a5c31855a689"); + assert_eq!(account.namespace, ""); +} + +#[tokio::test] +async fn test_sign_transaction() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let expected_request = serde_json::json!({ + "chain_id": "1", + "amount": "1000000000000000000", + "data": "0x", + "gas_limit": 21000, + "gas_price": "1", + "nonce": 0, + "to": "0x1dabe0acaaa4d1f81b9b43eaf51c8439378231a0", + }); + let response = serde_json::json!({ + "request_id": "5cede0bc-f7ce-d7a7-cba5-2427cee48bd5", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "signature": "0x3f3c03151a9451832d7b3abacec63cee23e4f697690db03e980ce5b02594a6e6657e236309cbcaa0de351162d69422aa3ebaf2349a8c5e612bd971d9de18be8501" + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path("/v1/quorum/ethereum/accounts/0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5/sign-transaction")) + .and(body_json(expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let address = Address::from_str("0xAd38E61dB0D3f8fEF9B4c5DD0C1A9F691cdCcfF5").unwrap(); + let to = Address::from_str("0x1daBe0aCaAA4D1F81b9b43Eaf51C8439378231a0").unwrap(); + + let mut tx: TransactionRequest = TransactionRequest::builder() + .from(address) + .to(to) + .value(U256::from_dec_str("1000000000000000000").unwrap()) + .gas(U256::from(21000)) + .nonce(U256::from(0)) + .build(); + + tx.gas_price = Some(U256::from(1)); + + let signature = api::sign_transaction(&vault_client, "quorum", 1, tx) + .await + .unwrap(); + + assert_eq!(signature.signature, "0x3f3c03151a9451832d7b3abacec63cee23e4f697690db03e980ce5b02594a6e6657e236309cbcaa0de351162d69422aa3ebaf2349a8c5e612bd971d9de18be8501"); +} + +#[tokio::test] +async fn test_import_private_key() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let expected_request = serde_json::json!({ + "private_key": "0a1232595b77534d99364bfde13383accbcb40775967a7eacd15d355c96288a5" + }); + let response = serde_json::json!({ + "request_id": "a7e5cd28-2867-86d5-7f82-73650ec68950", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "address": "0xeCB96104c306DF32Aed607EF0B8a44cC94BE782F", + "compressed_public_key": "0x026b5ae5ec570abb9c4c50746d08fb63c911641170581b07f5f531a993b8b6cbec", + "namespace": "", + "public_key": "0x046b5ae5ec570abb9c4c50746d08fb63c911641170581b07f5f531a993b8b6cbeced5f8e3de3f4c7416a7661ed2c7eef8fea416c62df47ec43896af26086b87594" + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path("/v1/quorum/ethereum/accounts/import")) + .and(body_json(expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let wallet = api::import_private_key( + &vault_client, + "quorum", + "0a1232595b77534d99364bfde13383accbcb40775967a7eacd15d355c96288a5", + ) + .await + .unwrap(); + + assert_eq!( + format!("{:?}", wallet.address), + "0xeCB96104c306DF32Aed607EF0B8a44cC94BE782F".to_lowercase() + ); + assert_eq!( + wallet.compressed_public_key, + "0x026b5ae5ec570abb9c4c50746d08fb63c911641170581b07f5f531a993b8b6cbec" + ); + assert_eq!(wallet.public_key, "0x046b5ae5ec570abb9c4c50746d08fb63c911641170581b07f5f531a993b8b6cbeced5f8e3de3f4c7416a7661ed2c7eef8fea416c62df47ec43896af26086b87594"); + assert_eq!(wallet.namespace, ""); +} diff --git a/tests/api/keys.rs b/tests/api/keys.rs index e69de29..5a6382d 100644 --- a/tests/api/keys.rs +++ b/tests/api/keys.rs @@ -0,0 +1,430 @@ +use quorum_vault_client::api; +use quorum_vault_client::api::KeyCryptoAlgorithm; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; +use wiremock::matchers::{body_json, method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +#[tokio::test] +async fn test_create_key() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let expected_request = serde_json::json!({ + "signing_algorithm": "ecdsa", + "tags": { + "env": "dev", + "kind": "wallet" + }, + "id": "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + "curve": "secp256k1" + }); + + let response = serde_json::json!({ + "request_id": "b5efba4e-9ed6-5e53-66ab-c072a078e6f5", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "created_at": "2023-01-28T13:33:28.583408531Z", + "curve": "secp256k1", + "id": "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + "namespace": "", + "public_key": "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU=", + "signing_algorithm": "ecdsa", + "tags": { + "env": "dev", + "kind": "wallet" + }, + "updated_at": "2023-01-28T13:33:28.583408531Z", + "version": 1 + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path("/v1/quorum/keys")) + .and(body_json(&expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let key = api::create_key( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + KeyCryptoAlgorithm::Secp256k1, + vec![ + ("env".to_string(), "dev".to_string()), + ("kind".to_string(), "wallet".to_string()), + ] + .into_iter() + .collect(), + ) + .await + .unwrap(); + + assert_eq!(key.id, "dd4b594d-4b89-480d-a8a8-01ed7e1f0140"); + assert_eq!(key.curve, "secp256k1"); + assert_eq!(key.signing_algorithm, "ecdsa"); + assert_eq!( + key.tags, + vec![ + ("env".to_string(), "dev".to_string()), + ("kind".to_string(), "wallet".to_string()) + ] + .into_iter() + .collect() + ); + assert_eq!(key.version, 1); +} + +#[tokio::test] +async fn test_read_key() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let response = serde_json::json!({ + "request_id": "9e4325b4-9d13-5e1c-c8f8-4c568b1728f0", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "created_at": "2023-01-28T13:33:28.583408531Z", + "curve": "secp256k1", + "id": "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + "namespace": "", + "public_key": "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU=", + "signing_algorithm": "ecdsa", + "tags": { + "env": "dev", + "kind": "wallet" + }, + "updated_at": "2023-01-28T13:33:28.583408531Z", + "version": 1 + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("GET")) + .and(path("/v1/quorum/keys/dd4b594d-4b89-480d-a8a8-01ed7e1f0140")) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let key = api::read_key( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + ) + .await + .unwrap(); + + assert_eq!(key.id, "dd4b594d-4b89-480d-a8a8-01ed7e1f0140"); + assert_eq!(key.curve, "secp256k1"); + assert_eq!(key.signing_algorithm, "ecdsa"); + assert_eq!( + key.tags, + vec![ + ("env".to_string(), "dev".to_string()), + ("kind".to_string(), "wallet".to_string()) + ] + .into_iter() + .collect() + ); + assert_eq!(key.version, 1); +} + +#[tokio::test] +async fn test_get_list_keys() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let response = serde_json::json!({ + "request_id": "e3d064d4-8a4d-e7d3-475b-8aa00f43e75a", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "keys": [ + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140" + ] + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("GET")) + .and(path("/v1/quorum/keys")) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let keys = api::list_keys(&vault_client, "quorum").await.unwrap(); + + assert_eq!(keys.keys, vec!["dd4b594d-4b89-480d-a8a8-01ed7e1f0140"]); +} + +#[tokio::test] +async fn test_sign_data() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let data = "Hello, world!"; + + let expected_request: serde_json::Value = serde_json::json!({ + "data": "tuFtJ6xatCen9okArFVZzictxsN8grPgUiRsgiRMUOQ=" + }); + let response = serde_json::json!({ + "request_id": "73baca4c-5690-a384-2105-751e4e33f0da", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "signature": "HgBoSOuweiQUHuXpTjqaSv8yoGUDh37GnMJg9ZyOeTRrjrE9xetYSq-Onjej_kdswHj8FnNRxRhqpYt8jrX71w==" + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path( + "/v1/quorum/keys/dd4b594d-4b89-480d-a8a8-01ed7e1f0140/sign", + )) + .and(body_json(&expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let signature = api::sign( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + data.as_bytes(), + ) + .await + .unwrap(); + + assert_eq!( + signature.signature, + "HgBoSOuweiQUHuXpTjqaSv8yoGUDh37GnMJg9ZyOeTRrjrE9xetYSq-Onjej_kdswHj8FnNRxRhqpYt8jrX71w==" + ); +} + +#[tokio::test] +async fn test_update_key_tags() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let expected_request: serde_json::Value = serde_json::json!({ + "tags": { + "env": "dev", + "kind": "wallet" + } + }); + let response = serde_json::json!({ + "request_id": "39fdefc5-1f30-b5fe-08ba-02dd75f96daa", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "created_at": "2023-01-28T13:33:28.583408531Z", + "curve": "secp256k1", + "id": "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + "namespace": "", + "public_key": "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU=", + "signing_algorithm": "ecdsa", + "tags": { + "env": "dev", + "kind": "wallet" + }, + "updated_at": "2023-01-28T13:33:28.583408531Z", + "version": 1 + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path("/v1/quorum/keys/dd4b594d-4b89-480d-a8a8-01ed7e1f0140")) + .and(body_json(&expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let key = api::update_key_tags( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + [ + ("env".to_string(), "dev".to_string()), + ("kind".to_string(), "wallet".to_string()), + ] + .into_iter() + .collect(), + ) + .await + .unwrap(); + + assert_eq!(key.tags.get("env").unwrap(), "dev"); + assert_eq!(key.tags.get("kind").unwrap(), "wallet"); +} + +#[tokio::test] +async fn test_destroy_key() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let response = serde_json::json!({ + "request_id": "39fdefc5-1f30-b5fe-08ba-02dd75f96daa", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": null, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("DELETE")) + .and(path( + "/v1/quorum/keys/dd4b594d-4b89-480d-a8a8-01ed7e1f0140/destroy", + )) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + api::destroy_key( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn test_import_key() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let expected_request = serde_json::json!({ + "signing_algorithm": "ecdsa", + "tags": { + "env": "dev", + "kind": "wallet" + }, + "private_key": "1s1iv38t1CBEGJOHo2ah7yrrhFoUZKiiU4aUq_59B3A=", + "id": "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + "curve": "secp256k1" + }); + let response = serde_json::json!({ + "request_id": "39fdefc5-1f30-b5fe-08ba-02dd75f96daa", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "created_at": "2023-01-28T13:33:28.583408531Z", + "curve": "secp256k1", + "id": "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + "namespace": "", + "public_key": "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU=", + "signing_algorithm": "ecdsa", + "tags": { + "env": "dev", + "kind": "wallet" + }, + "updated_at": "2023-01-28T13:33:28.583408531Z", + "version": 1 + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path("/v1/quorum/keys/import")) + .and(body_json(&expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let key = api::import_key( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + KeyCryptoAlgorithm::Secp256k1, + [ + ("env".to_string(), "dev".to_string()), + ("kind".to_string(), "wallet".to_string()), + ] + .into_iter() + .collect(), + "1s1iv38t1CBEGJOHo2ah7yrrhFoUZKiiU4aUq_59B3A=", + ) + .await + .unwrap(); + + assert_eq!(key.tags.get("env").unwrap(), "dev"); + assert_eq!(key.tags.get("kind").unwrap(), "wallet"); + assert_eq!(key.id, "dd4b594d-4b89-480d-a8a8-01ed7e1f0140"); + assert_eq!(key.curve, "secp256k1"); + assert_eq!(key.signing_algorithm, "ecdsa"); + assert_eq!(key.version, 1); + assert_eq!(key.public_key, "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU="); +} diff --git a/tests/api/mod.rs b/tests/api/mod.rs index e69de29..6024f57 100644 --- a/tests/api/mod.rs +++ b/tests/api/mod.rs @@ -0,0 +1,2 @@ +mod ethereum; +mod keys; diff --git a/tests/test.rs b/tests/test.rs index e69de29..b32f9e2 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -0,0 +1 @@ +mod api; From b4ef1339a25209647c8cab5149490de01b920ac9 Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Sat, 28 Jan 2023 20:12:22 +0200 Subject: [PATCH 3/7] Add support of Keys API set --- src/api.rs | 16 +++++++++++----- src/api/keys/requests.rs | 4 ++-- src/api/keys/responses.rs | 2 +- src/lib.rs | 18 ++++++++---------- tests/api/keys.rs | 5 ++++- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/api.rs b/src/api.rs index e0cfa83..498015a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,12 +1,18 @@ -use crate::api::ethereum::requests::{CreateEthereumAccountRequest, ImportPrivateKeyRequest, ListEthereumAccountsRequest, ReadEthereumAccountRequest, SignEthereumTransactionRequest}; +use crate::api::ethereum::requests::{ + CreateEthereumAccountRequest, ImportPrivateKeyRequest, ListEthereumAccountsRequest, + ReadEthereumAccountRequest, SignEthereumTransactionRequest, +}; use crate::api::ethereum::responses::{ EthereumAccountResponse, EthereumAccountsResponse, EthereumSignTransactionResponse, }; -use crate::api::keys::requests::{CreateKeyRequest, DestroyKeyRequest, ImportKeyRequest, ListKeysRequest, ReadKeyRequest, SignRequest, UpdateKeyTagsRequest}; +use crate::api::keys::requests::{ + CreateKeyRequest, DestroyKeyRequest, ImportKeyRequest, ListKeysRequest, ReadKeyRequest, + SignRequest, UpdateKeyTagsRequest, +}; use crate::api::keys::responses::{KeyResponse, KeysResponse, SignResponse}; use crate::error::ClientError; -use std::collections::HashMap; use base64::Engine; +use std::collections::HashMap; use vaultrs::client::Client; use web3::signing::keccak256; use web3::types::{Address, TransactionRequest}; @@ -60,7 +66,7 @@ pub async fn read_account( mount: &str, address: Address, ) -> Result { - let address = format!("{:?}", address); + let address = format!("{address:?}"); let checksummed = eth_checksum::checksum(&address); let request = ReadEthereumAccountRequest::builder() .mount(mount) @@ -250,7 +256,7 @@ pub async fn sign_raw( id: &str, data: [u8; 32], ) -> Result { - let encoded = base64::prelude::BASE64_URL_SAFE.encode(&data); + let encoded = base64::prelude::BASE64_URL_SAFE.encode(data); let request = SignRequest::builder() .mount(mount) .id(id) diff --git a/src/api/keys/requests.rs b/src/api/keys/requests.rs index 9cb4cb4..0a77219 100644 --- a/src/api/keys/requests.rs +++ b/src/api/keys/requests.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; -use rustify_derive::Endpoint; use crate::api::keys::responses::{KeyResponse, KeysResponse, SignResponse}; +use rustify_derive::Endpoint; +use std::collections::HashMap; /// ## Create Key /// This endpoint creates a new key with given crypto algorithm and curve. diff --git a/src/api/keys/responses.rs b/src/api/keys/responses.rs index 86cbcc2..5a8ab00 100644 --- a/src/api/keys/responses.rs +++ b/src/api/keys/responses.rs @@ -1,5 +1,5 @@ -use std::collections::HashMap; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; /// Response from executing [CreateKeyRequest][crate::api::keys::requests::CreateKeyRequest] #[derive(Deserialize, Debug, Serialize)] diff --git a/src/lib.rs b/src/lib.rs index 51e7027..19721cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,10 +29,9 @@ //! asynchronous client from [Reqwest](https://docs.rs/reqwest/) for //! communicating to Vault. //! -//! ```rust -//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +//! ```no_run +//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; //! -//! fn main() { //! // Create a client //! let client = VaultClient::new( //! VaultClientSettingsBuilder::default() @@ -41,7 +40,6 @@ //! .build() //! .unwrap() //! ).unwrap(); -//! } //! ``` //! //! ### Ethereum @@ -50,7 +48,7 @@ //! //! The following example creates a new Ethereum Wallet in the Vault. //! -//! ```rust +//! ```no_run //! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; //! //! #[tokio::main] @@ -80,7 +78,7 @@ //! //! The following example gets list of all Ethereum Wallets in the Vault. //! -//! ```rust +//! ```no_run //! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; //! //! #[tokio::main] @@ -110,7 +108,7 @@ //! //! The following example gets the Ethereum Wallet by address. //! -//! ```rust +//! ```no_run //! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder, Address}; //! use std::str::FromStr; //! @@ -126,7 +124,7 @@ //! ).unwrap(); //! //! let address = Address::from_str("0x8d3113e29CB92F44F1762E52D2a0276509b36b82").unwrap(); -//! let read_account = quorum_vault_client::api::read_account(&client, "quorum", account).await.unwrap(); +//! let read_account = quorum_vault_client::api::read_account(&client, "quorum", address).await.unwrap(); //! println!("result: {:?}", read_account); //! } //! @@ -142,7 +140,7 @@ //! //! The following example signs the Ethereum Transaction. //! -//! ```rust +//! ```no_run //! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder, TransactionRequest, Address, U256}; //! use std::str::FromStr; //! @@ -168,7 +166,7 @@ //! //! tx.gas_price = Some(U256::from(1)); //! -//! let sign_transaction = quorum_vault_client::api::sign_transaction(&client, "quorum", 1, &tx).await.unwrap(); +//! let sign_transaction = quorum_vault_client::api::sign_transaction(&client, "quorum", 1, tx).await.unwrap(); //! println!("result: {:?}", sign_transaction); //! } //! diff --git a/tests/api/keys.rs b/tests/api/keys.rs index 5a6382d..61a27c8 100644 --- a/tests/api/keys.rs +++ b/tests/api/keys.rs @@ -426,5 +426,8 @@ async fn test_import_key() { assert_eq!(key.curve, "secp256k1"); assert_eq!(key.signing_algorithm, "ecdsa"); assert_eq!(key.version, 1); - assert_eq!(key.public_key, "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU="); + assert_eq!( + key.public_key, + "BMDV2nRbw6iFuqs0RRF9cdhgvvHDoxmA4MPN0jomXTGhQIhPlCLMsnWFHaoeYihRUNnQ01CAftQUkAMol8G5SuU=" + ); } From 863780ec8c1c85dabb5a3d583cb231d1127a315e Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Sun, 29 Jan 2023 11:02:06 +0200 Subject: [PATCH 4/7] Rename sign_raw to sign_hash. Add test for sign_hash --- src/api.rs | 3 ++- tests/api/keys.rs | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/api.rs b/src/api.rs index 498015a..2fe327b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -249,8 +249,9 @@ pub async fn sign( } /// Sign a message +/// Data must be a 32 byte hash /// See [SignRequest] -pub async fn sign_raw( +pub async fn sign_hash( client: &impl Client, mount: &str, id: &str, diff --git a/tests/api/keys.rs b/tests/api/keys.rs index 61a27c8..191a1bb 100644 --- a/tests/api/keys.rs +++ b/tests/api/keys.rs @@ -1,6 +1,7 @@ use quorum_vault_client::api; use quorum_vault_client::api::KeyCryptoAlgorithm; use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; +use web3::signing::keccak256; use wiremock::matchers::{body_json, method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -244,6 +245,61 @@ async fn test_sign_data() { ); } +#[tokio::test] +async fn test_sign_hash() { + let mock = MockServer::start().await; + let vault_client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(mock.uri()) + .token("s.1234567890abcdef") + .build() + .unwrap(), + ) + .unwrap(); + + let data = "Hello, world!"; + let expected_request: serde_json::Value = serde_json::json!({ + "data": "tuFtJ6xatCen9okArFVZzictxsN8grPgUiRsgiRMUOQ=" + }); + let response = serde_json::json!({ + "request_id": "73baca4c-5690-a384-2105-751e4e33f0da", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": { + "signature": "HgBoSOuweiQUHuXpTjqaSv8yoGUDh37GnMJg9ZyOeTRrjrE9xetYSq-Onjej_kdswHj8FnNRxRhqpYt8jrX71w==" + }, + "wrap_info": null, + "warnings": null, + "auth": null + }); + + Mock::given(method("POST")) + .and(path( + "/v1/quorum/keys/dd4b594d-4b89-480d-a8a8-01ed7e1f0140/sign", + )) + .and(body_json(&expected_request)) + .respond_with(ResponseTemplate::new(200).set_body_json(&response)) + .mount(&mock) + .await; + + let hash = keccak256(data.as_bytes()); + + let signature = api::sign_hash( + &vault_client, + "quorum", + "dd4b594d-4b89-480d-a8a8-01ed7e1f0140", + hash, + ) + .await + .unwrap(); + + assert_eq!( + signature.signature, + "HgBoSOuweiQUHuXpTjqaSv8yoGUDh37GnMJg9ZyOeTRrjrE9xetYSq-Onjej_kdswHj8FnNRxRhqpYt8jrX71w==" + ); +} + #[tokio::test] async fn test_update_key_tags() { let mock = MockServer::start().await; From 93fec3e751d4df95fb7d8e77d955b1876d6c0e80 Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Sun, 29 Jan 2023 11:20:02 +0200 Subject: [PATCH 5/7] Increment minor and patch version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c6dcea4..ae49f7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quorum-vault-client" -version = "0.1.0" +version = "0.2.1" edition = "2021" repository = "https://github.com/MaximFischuk/quorum-vault-client" documentation = "https://docs.rs/quorum-vault-client" From 762ec16cbe187ee282ae778a984fe91e7448c38e Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Mon, 30 Jan 2023 11:09:25 +0200 Subject: [PATCH 6/7] Update examples --- .../src/main.rs => ethereum_wallet.rs} | 43 +++++-------- examples/keys.rs | 63 +++++++++++++++++++ examples/simple/Cargo.toml | 15 ----- 3 files changed, 80 insertions(+), 41 deletions(-) rename examples/{simple/src/main.rs => ethereum_wallet.rs} (54%) create mode 100644 examples/keys.rs delete mode 100644 examples/simple/Cargo.toml diff --git a/examples/simple/src/main.rs b/examples/ethereum_wallet.rs similarity index 54% rename from examples/simple/src/main.rs rename to examples/ethereum_wallet.rs index a45dc51..c73af55 100644 --- a/examples/simple/src/main.rs +++ b/examples/ethereum_wallet.rs @@ -1,28 +1,32 @@ use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; -use web3::types::{Address, TransactionRequest, U256}; -use std::str::FromStr; -use std::io::Write; +use web3::types::{TransactionRequest, U256}; #[tokio::main] async fn main() { - init_logger(); let client = VaultClient::new( VaultClientSettingsBuilder::default() .address("http://127.0.0.1:8200") - .token("hvs.E4wFaEETAP1TwJjG9S1qNA7J") + .token("s.NgpQWnkfYEAxVPC83Bxfa7cy") .build() - .unwrap() - ).unwrap(); + .unwrap(), + ) + .unwrap(); - let created_account = quorum_vault_client::api::create_account(&client, "quorum").await.unwrap(); + let created_account = quorum_vault_client::api::create_account(&client, "quorum") + .await + .unwrap(); println!("result: {:?}", created_account); - let addresses = quorum_vault_client::api::list_accouns(&client, "quorum").await.unwrap(); + let addresses = quorum_vault_client::api::list_accounts(&client, "quorum") + .await + .unwrap(); println!("addresses: {:?}", addresses); let address = addresses.keys.first().copied().unwrap(); - let result = quorum_vault_client::api::read_account(&client, "quorum", address).await.unwrap(); + let result = quorum_vault_client::api::read_account(&client, "quorum", address) + .await + .unwrap(); println!("result: {:?}", result); let mut tx: TransactionRequest = TransactionRequest::builder() @@ -35,21 +39,8 @@ async fn main() { tx.gas_price = Some(U256::from(1)); - let signature = quorum_vault_client::api::sign_transaction(&client, "quorum", 1, tx).await.unwrap(); + let signature = quorum_vault_client::api::sign_transaction(&client, "quorum", 1, tx) + .await + .unwrap(); println!("signature: {:?}", signature); } - -fn init_logger() { - let mut builder = env_logger::Builder::from_default_env(); - builder.format(|buf, record| { - writeln!( - buf, - "{} [{}] - {}", - chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), - record.level(), - record.args() - ) - }); - builder.filter_level(log::LevelFilter::Info); - builder.init(); -} diff --git a/examples/keys.rs b/examples/keys.rs new file mode 100644 index 0000000..fab7b39 --- /dev/null +++ b/examples/keys.rs @@ -0,0 +1,63 @@ +use quorum_vault_client::api::KeyCryptoAlgorithm; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; + +#[tokio::main] +async fn main() { + // Create a client + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address("http://127.0.0.1:8200") + .token("s.NgpQWnkfYEAxVPC83Bxfa7cy") + .build() + .unwrap(), + ) + .unwrap(); + + let key_id = "some-id"; + + let new_key = quorum_vault_client::api::create_key( + &client, + "quorum", + key_id, + KeyCryptoAlgorithm::Secp256k1, + [("tag".to_string(), "value0".to_string())] + .into_iter() + .collect(), + ) + .await + .unwrap(); + println!("key: {new_key:?}"); + + let keys = quorum_vault_client::api::list_keys(&client, "quorum") + .await + .unwrap(); + println!("keys: {keys:?}"); + + let read_key = quorum_vault_client::api::read_key(&client, "quorum", key_id) + .await + .unwrap(); + println!("read_key: {read_key:?}"); + + let signature = + quorum_vault_client::api::sign(&client, "quorum", key_id, "some-data".as_bytes()) + .await + .unwrap(); + println!("signature: {signature:?}"); + + let updated_tags = quorum_vault_client::api::update_key_tags( + &client, + "quorum", + key_id, + [("tag".to_string(), "value1".to_string())] + .into_iter() + .collect(), + ) + .await + .unwrap(); + println!("updated_tags: {updated_tags:?}"); + + quorum_vault_client::api::destroy_key(&client, "quorum", "some-id") + .await + .unwrap(); + println!("destroyed key: {key_id}"); +} diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml deleted file mode 100644 index ab97d7b..0000000 --- a/examples/simple/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "simple" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -quorum-vault-client = { path = "../../" } -tokio = { version = "1.0", features = ["full"] } -vaultrs = "0.6.2" -web3 = "0.18.0" -log = "0.4.17" -env_logger = "0.10.0" -chrono = "0.4.23" From e77cd38b7bc92176e3a5db374058effcd26716be Mon Sep 17 00:00:00 2001 From: MaximFischuk Date: Mon, 30 Jan 2023 12:44:25 +0200 Subject: [PATCH 7/7] Update documentation --- README.md | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 305 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4dfa61c..0100a81 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,20 @@ The following backends are supported: * List Ethereum Accounts * Read Ethereum Account by Address * Sign Ethereum Transaction (only Legacy) + * Import Private Key +* Keys + * Create Key + * List Keys + * Read Key + * Delete Key + * Sign Data + * Import Private Key ## Installation Add the following to your `Cargo.toml`: ```toml [dependencies] -quorum-vault-client = "0.1.0" +quorum-vault-client = "0.2.1" ``` ## Usage @@ -167,7 +175,7 @@ async fn main() { tx.gas_price = Some(U256::from(1)); - let sign_transaction = quorum_vault_client::api::sign_transaction(&client, "quorum", 1, &tx).await.unwrap(); + let sign_transaction = quorum_vault_client::api::sign_transaction(&client, "quorum", 1, tx).await.unwrap(); println!("result: {:?}", sign_transaction); } @@ -177,4 +185,146 @@ Result of the execution is the following: ```bash > signature: EthereumSignTransactionResponse { signature: "0xf29001752503d05ae83874193a8d866d49fc897c1a2fcb6229a0c61e4b5663f7097817a26f4c6014bbfd24c484bad9587c9c627c6f70d020f8638a4067bb78e801" } -``` \ No newline at end of file +``` + +### Keys + +**Create Key** + +The following example creates a new key in the Vault. + +```rust +use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +use quorum_vault_client::api::KeyCryptoAlgorithm; + +#[tokio::main] +async fn main() { + // Create a client + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address("https://127.0.0.1:8200") + .token("TOKEN") + .build() + .unwrap() + ).unwrap(); + + let created_key = quorum_vault_client::api::create_key(&client, "quorum", "some-id", KeyCryptoAlgorithm::Secp256k1, [("tag".to_string(), "value".to_string())].into_iter().collect()).await.unwrap(); + + println!("result: {:?}", created_key); +} +``` + +Result of the execution is the following: + +```bash +> result: KeyResponse { created_at: "2023-01-30T09:08:22.217224856Z", curve: "secp256k1", id: "some-id", namespace: "", public_key: "BIwm5UiSGTiXVRlB_rS7qYSzQ6XZbaWfUOJKVicU85q-N7zuAak2JQfAHUs2Sm2WAA7YyWdN7_4UFJFggEa6AKw=", signing_algorithm: "ecdsa", tags: {"tag": "value0"}, updated_at: "2023-01-30T09:08:22.217224856Z", version: 1 } +``` + +**Read Key** + +The following example reads the key by id. + +```rust +use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; + +#[tokio::main] +async fn main() { + // Create a client + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address("https://127.0.0.1:8200") + .token("TOKEN") + .build() + .unwrap() + ).unwrap(); + + let key = quorum_vault_client::api::read_key(&client, "quorum", "some-id").await.unwrap(); + println!("result: {:?}", key); +} +``` + +Result of the execution is the following: + +```bash +> result: KeyResponse { created_at: "2023-01-30T09:08:22.217224856Z", curve: "secp256k1", id: "some-id", namespace: "", public_key: "BIwm5UiSGTiXVRlB_rS7qYSzQ6XZbaWfUOJKVicU85q-N7zuAak2JQfAHUs2Sm2WAA7YyWdN7_4UFJFggEa6AKw=", signing_algorithm: "ecdsa", tags: {"tag": "value0"}, updated_at: "2023-01-30T09:08:22.217224856Z", version: 1 } +``` + +**List Keys** + +The following example lists all keys in the Vault. + +```rust +use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; + +#[tokio::main] +async fn main() { + // Create a client + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address("https://127.0.0.1:8200") + .token("TOKEN") + .build() + .unwrap() + ).unwrap(); + + let keys = quorum_vault_client::api::list_keys(&client, "quorum").await.unwrap(); + println!("result: {:?}", keys); +} +``` + +Result of the execution is the following: + +```bash +> result: KeysResponse { keys: ["some-id"] } +``` + +**Delete Key** + +The following example deletes the key by id. + +```rust +use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; + +#[tokio::main] +async fn main() { + // Create a client + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address("https://127.0.0.1:8200") + .token("TOKEN") + .build() + .unwrap() + ).unwrap(); + + quorum_vault_client::api::destroy_key(&client, "quorum", "some-id").await.unwrap(); +} +``` + +**Sign data** + +The following example signs the data by key id. + +```rust +use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; + +#[tokio::main] +async fn main() { + // Create a client + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address("https://127.0.0.1:8200") + .token("TOKEN") + .build() + .unwrap() + ).unwrap(); + + let signature = quorum_vault_client::api::sign(&client, "quorum", "some-id", "some-data".as_bytes().await.unwrap(); + println!("signature: {:?}", signature); +} +``` + +Result of the execution is the following: + +```bash +> signature: SignResponse { signature: "Z1ibkBIGjMLh5pSR5mFZ5NbesrM57g-FGkFr0sbIyIlI_M0BYVN_LD-Nt7x1wUo6AoLQyL0I-z7PD8MsdgmkhQ==" } +``` diff --git a/src/lib.rs b/src/lib.rs index 19721cc..4bc519b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,13 @@ //! * List Ethereum Accounts //! * Read Ethereum Account by Address //! * Sign Ethereum Transaction (only Legacy) +//! * Keys +//! * Create Key +//! * List Keys +//! * Read Key +//! * Delete Key +//! * Sign Data +//! * Import Private Key //! //! ## Installation //! Add the following to your `Cargo.toml`: @@ -44,7 +51,7 @@ //! //! ### Ethereum //! -//! * Create new Ethereum Wallet * +//! **Create new Ethereum Wallet** //! //! The following example creates a new Ethereum Wallet in the Vault. //! @@ -74,7 +81,7 @@ //! > result: EthereumAccountResponse { address: 0x1a669bad7bda1f553087df8568b8782bcb0023ac, compressed_public_key: "0x020e44fde7435da96f8260788a89d4c37f2b3d96fd936dd978b886de6872d73062", public_key: "0x040e44fde7435da96f8260788a89d4c37f2b3d96fd936dd978b886de6872d730629c94a4803d3073b0bbe9a3d46f201eef5beec04d0e6f464e07704c159edd2c64", namespace: "" } //! ``` //! -//! * List all Ethereum Wallets * +//! **List all Ethereum Wallets** //! //! The following example gets list of all Ethereum Wallets in the Vault. //! @@ -104,7 +111,7 @@ //! > result: EthereumAccountsResponse { keys: [0x1a669bad7bda1f553087df8568b8782bcb0023ac, 0x8d3113e29cb92f44f1762e52d2a0276509b36b82] } //! ``` //! -//! * Read Ethereum Wallet * +//! **Read Ethereum Wallet** //! //! The following example gets the Ethereum Wallet by address. //! @@ -136,7 +143,7 @@ //! > result: EthereumAccountResponse { address: 0x8d3113e29cb92f44f1762e52d2a0276509b36b82, compressed_public_key: "0x03b1c069a45b14697567661e6426ab0639f73762d7526765b2bd6891a73d84ebb5", public_key: "0x04b1c069a45b14697567661e6426ab0639f73762d7526765b2bd6891a73d84ebb57e6abbec4c9738a025d1a611e431ecf006227dbf6ca400f85518df70e5d101cb", namespace: "" } //! ``` //! -//! * Sign Ethereum Transaction * +//! **Sign Ethereum Transaction** //! //! The following example signs the Ethereum Transaction. //! @@ -178,6 +185,147 @@ //! > signature: EthereumSignTransactionResponse { signature: "0xf29001752503d05ae83874193a8d866d49fc897c1a2fcb6229a0c61e4b5663f7097817a26f4c6014bbfd24c484bad9587c9c627c6f70d020f8638a4067bb78e801" } //! ``` //! +//! ### Keys +//! +//! **Create Key** +//! +//! The following example creates a new key in the Vault. +//! +//! ```no_run +//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +//! use quorum_vault_client::api::KeyCryptoAlgorithm; +//! +//! #[tokio::main] +//! async fn main() { +//! // Create a client +//! let client = VaultClient::new( +//! VaultClientSettingsBuilder::default() +//! .address("https://127.0.0.1:8200") +//! .token("TOKEN") +//! .build() +//! .unwrap() +//! ).unwrap(); +//! +//! let created_key = quorum_vault_client::api::create_key(&client, "quorum", "some-id", KeyCryptoAlgorithm::Secp256k1, [("tag".to_string(), "value".to_string())].into_iter().collect()).await.unwrap(); +//! +//! println!("result: {:?}", created_key); +//! } +//! ``` +//! +//! Result of the execution is the following: +//! +//! ```bash +//! > result: KeyResponse { created_at: "2023-01-30T09:08:22.217224856Z", curve: "secp256k1", id: "some-id", namespace: "", public_key: "BIwm5UiSGTiXVRlB_rS7qYSzQ6XZbaWfUOJKVicU85q-N7zuAak2JQfAHUs2Sm2WAA7YyWdN7_4UFJFggEa6AKw=", signing_algorithm: "ecdsa", tags: {"tag": "value0"}, updated_at: "2023-01-30T09:08:22.217224856Z", version: 1 } +//! ``` +//! +//! **Read Key** +//! +//! The following example reads the key by id. +//! +//! ```no_run +//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +//! +//! #[tokio::main] +//! async fn main() { +//! // Create a client +//! let client = VaultClient::new( +//! VaultClientSettingsBuilder::default() +//! .address("https://127.0.0.1:8200") +//! .token("TOKEN") +//! .build() +//! .unwrap() +//! ).unwrap(); +//! +//! let key = quorum_vault_client::api::read_key(&client, "quorum", "some-id").await.unwrap(); +//! println!("result: {:?}", key); +//! } +//! ``` +//! +//! Result of the execution is the following: +//! +//! ```bash +//! > result: KeyResponse { created_at: "2023-01-30T09:08:22.217224856Z", curve: "secp256k1", id: "some-id", namespace: "", public_key: "BIwm5UiSGTiXVRlB_rS7qYSzQ6XZbaWfUOJKVicU85q-N7zuAak2JQfAHUs2Sm2WAA7YyWdN7_4UFJFggEa6AKw=", signing_algorithm: "ecdsa", tags: {"tag": "value0"}, updated_at: "2023-01-30T09:08:22.217224856Z", version: 1 } +//! ``` +//! +//! **List Keys** +//! +//! The following example lists all keys in the Vault. +//! +//! ```no_run +//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +//! +//! #[tokio::main] +//! async fn main() { +//! // Create a client +//! let client = VaultClient::new( +//! VaultClientSettingsBuilder::default() +//! .address("https://127.0.0.1:8200") +//! .token("TOKEN") +//! .build() +//! .unwrap() +//! ).unwrap(); +//! +//! let keys = quorum_vault_client::api::list_keys(&client, "quorum").await.unwrap(); +//! println!("result: {:?}", keys); +//! } +//! ``` +//! +//! Result of the execution is the following: +//! +//! ```bash +//! > result: KeysResponse { keys: ["some-id"] } +//! ``` +//! +//! **Delete Key** +//! +//! The following example deletes the key by id. +//! +//! ```no_run +//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +//! +//! #[tokio::main] +//! async fn main() { +//! // Create a client +//! let client = VaultClient::new( +//! VaultClientSettingsBuilder::default() +//! .address("https://127.0.0.1:8200") +//! .token("TOKEN") +//! .build() +//! .unwrap() +//! ).unwrap(); +//! +//! quorum_vault_client::api::destroy_key(&client, "quorum", "some-id").await.unwrap(); +//! } +//! ``` +//! +//! **Sign data** +//! +//! The following example signs the data by key id. +//! +//! ```no_run +//! use quorum_vault_client::{Client, VaultClient, VaultClientSettingsBuilder}; +//! +//! #[tokio::main] +//! async fn main() { +//! // Create a client +//! let client = VaultClient::new( +//! VaultClientSettingsBuilder::default() +//! .address("https://127.0.0.1:8200") +//! .token("TOKEN") +//! .build() +//! .unwrap() +//! ).unwrap(); +//! +//! let signature = quorum_vault_client::api::sign(&client, "quorum", "some-id", "some-data".as_bytes()).await.unwrap(); +//! println!("signature: {:?}", signature); +//! } +//! ``` +//! +//! Result of the execution is the following: +//! +//! ```bash +//! > signature: SignResponse { signature: "Z1ibkBIGjMLh5pSR5mFZ5NbesrM57g-FGkFr0sbIyIlI_M0BYVN_LD-Nt7x1wUo6AoLQyL0I-z7PD8MsdgmkhQ==" } +//! ``` pub mod api; pub mod error;