From 82548971dac54e67ea75672f330f6a769ce933e7 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:24:19 -0500 Subject: [PATCH] add MetadataAddress util for encode/decode --- packages/provwasm-std/Cargo.toml | 7 +- packages/provwasm-std/src/lib.rs | 2 +- packages/provwasm-std/src/metadata_address.rs | 291 ++++++++++++++++++ 3 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 packages/provwasm-std/src/metadata_address.rs diff --git a/packages/provwasm-std/Cargo.toml b/packages/provwasm-std/Cargo.toml index 88b5a9fc..f7413989 100644 --- a/packages/provwasm-std/Cargo.toml +++ b/packages/provwasm-std/Cargo.toml @@ -10,9 +10,12 @@ keywords = ["provenance", "blockchain", "smart-contracts", "defi", "finance"] categories = ["api-bindings", "cryptography::cryptocurrencies", "wasm"] [dependencies] +base64 = "0.22.1" +bech32 = { version = "0.11.0", default-features = false } chrono = { version = "0.4.24", default-features = false } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +hex = { version = "0.4.3", default-features = false } prost = { workspace = true, default-features = false, features = ["prost-derive"] } prost-types = { workspace = true, default-features = false } provwasm-common = { workspace = true } @@ -20,5 +23,7 @@ provwasm-proc-macro = { workspace = true } schemars = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } serde-cw-value = "0.7.0" -base64 = "0.22.1" +sha2 = { version = "0.10.8", default-features = false } strum_macros = "0.26.4" +thiserror = { workspace = true } +uuid = { version = "1.10.0", default-features = false } \ No newline at end of file diff --git a/packages/provwasm-std/src/lib.rs b/packages/provwasm-std/src/lib.rs index 0a9794f8..04cbc517 100644 --- a/packages/provwasm-std/src/lib.rs +++ b/packages/provwasm-std/src/lib.rs @@ -1,7 +1,7 @@ /// The version (commit hash) of the Cosmos SDK used when generating this library. pub const PROVENANCE_VERSION: &str = include_str!("types/PROVENANCE_COMMIT"); -// mod serde; +pub mod metadata_address; pub mod shim; #[allow( deprecated, diff --git a/packages/provwasm-std/src/metadata_address.rs b/packages/provwasm-std/src/metadata_address.rs new file mode 100644 index 00000000..5a4fe23c --- /dev/null +++ b/packages/provwasm-std/src/metadata_address.rs @@ -0,0 +1,291 @@ +use crate::metadata_address::KeyType::{ + ContractSpecification, Record, RecordSpecification, Scope, ScopeSpecification, Session, +}; +use bech32::primitives::hrp; +use bech32::{Bech32, Hrp}; +use cosmwasm_std::StdError; +use sha2::{Digest, Sha256}; +use thiserror::Error; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum KeyType { + Scope = 0x00, + Session = 0x01, + Record = 0x02, + ContractSpecification = 0x03, + ScopeSpecification = 0x04, + RecordSpecification = 0x05, +} + +impl KeyType { + pub fn to_str(&self) -> &str { + match self { + Scope => "scope", + Session => "session", + Record => "record", + ContractSpecification => "contractspec", + ScopeSpecification => "scopespec", + RecordSpecification => "recspec", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Error)] +pub enum MetadataAddressError { + #[error("Failed to parse HRP: {0}")] + ParseError(#[from] hrp::Error), + + #[error("Failed to encode Bech32: {0}")] + EncodeError(#[from] bech32::EncodeError), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MetadataAddress { + pub bech32: String, + pub bytes: Vec, + pub key_type: KeyType, +} + +impl MetadataAddress { + pub fn contract_specification( + contract_specification_uuid: Uuid, + ) -> Result { + let key_type_byte = ContractSpecification as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(contract_specification_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(ContractSpecification, &bytes).unwrap(); + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: ContractSpecification, + }) + } + + pub fn scope_specification( + scope_specification_uuid: Uuid, + ) -> Result { + let key_type_byte = ScopeSpecification as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_specification_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(ScopeSpecification, &bytes).unwrap(); + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: ScopeSpecification, + }) + } + + pub fn scope(scope_uuid: Uuid) -> Result { + let key_type_byte = Scope as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(Scope, &bytes).unwrap(); + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: Scope, + }) + } + + pub fn record(scope_uuid: Uuid, record_name: String) -> Result { + let key_type_byte = Record as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_uuid)) + .chain(Self::hash_bytes(record_name)) + .collect::>(); + + let addr = Self::encode_bech32(Record, &bytes).unwrap(); + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: Record, + }) + } + + pub fn record_specification( + contract_specification_uuid: Uuid, + record_specification_name: String, + ) -> Result { + let key_type_byte = RecordSpecification as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(contract_specification_uuid)) + .chain(Self::hash_bytes(record_specification_name)) + .collect::>(); + + let addr = Self::encode_bech32(RecordSpecification, &bytes).unwrap(); + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: RecordSpecification, + }) + } + + pub fn session(scope_uuid: Uuid, session_uuid: Uuid) -> Result { + let key_type_byte = Session as u8; + let bytes = [key_type_byte] + .iter() + .cloned() + .chain(Self::hex_encode_uuid(scope_uuid)) + .chain(Self::hex_encode_uuid(session_uuid)) + .collect::>(); + + let addr = Self::encode_bech32(Session, &bytes).unwrap(); + + Ok(MetadataAddress { + bech32: addr, + bytes, + key_type: Session, + }) + } + + pub fn to_bytes(&self) -> Vec { + self.bech32.as_bytes().to_vec() + } + + fn hex_encode_uuid(uuid: Uuid) -> Vec { + hex::decode(uuid.simple().encode_lower(&mut Uuid::encode_buffer())).unwrap() + } + + fn encode_bech32(key_type: KeyType, bytes: &[u8]) -> Result { + let hrp = Hrp::parse(key_type.to_str()).map_err(MetadataAddressError::ParseError)?; + let encoded = + bech32::encode::(hrp, bytes).map_err(MetadataAddressError::EncodeError)?; + Ok(encoded) + } + + pub fn hash_bytes(data: String) -> Vec { + let hash = Sha256::digest(data.trim().to_lowercase().as_bytes()); + hash[0..16].to_vec() + } +} + +#[cfg(test)] +pub mod test { + use std::str::FromStr; + + use uuid::Uuid; + + use crate::metadata_address::{KeyType, MetadataAddress}; + + #[test] + pub fn new_contract_spec() { + let meta_addr = MetadataAddress::contract_specification( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "contractspec1qw07zlu62ms5zk9g4azsdq9vnesqy4dtgm".to_string(), + bytes: vec![ + 3, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::ContractSpecification, + } + ); + } + + #[test] + pub fn new_scope_spec() { + let meta_addr = MetadataAddress::scope_specification( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "scopespec1qj07zlu62ms5zk9g4azsdq9vnesqxcv7hd".to_string(), + bytes: vec![ + 4, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::ScopeSpecification, + } + ); + } + + #[test] + pub fn new_scope() { + let meta_addr = + MetadataAddress::scope(Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap()) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "scope1qz07zlu62ms5zk9g4azsdq9vnesqg74ssc".to_string(), + bytes: vec![ + 0, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96 + ], + key_type: KeyType::Scope, + } + ); + } + + #[test] + pub fn new_record_spec() { + let meta_addr = MetadataAddress::record_specification( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + "nft_record_spec_name".to_string(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "recspec1qk07zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4juadl3c0" + .to_string(), + bytes: vec![ + 5, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96, + 232, 114, 59, 59, 150, 14, 60, 73, 7, 11, 150, 164, 227, 247, 235, 46 + ], + key_type: KeyType::RecordSpecification, + } + ); + } + + #[test] + pub fn new_record() { + let meta_addr = MetadataAddress::record( + Uuid::from_str("9fe17f9a-56e1-4158-a8af-450680ac9e60").unwrap(), + "nft_record_spec_name".to_string(), + ) + .unwrap(); + + assert_eq!( + meta_addr, + MetadataAddress { + bech32: "record1q207zlu62ms5zk9g4azsdq9vneswsu3m8wtqu0zfqu9edf8r7l4jugz3hcl" + .to_string(), + bytes: vec![ + 2, 159, 225, 127, 154, 86, 225, 65, 88, 168, 175, 69, 6, 128, 172, 158, 96, + 232, 114, 59, 59, 150, 14, 60, 73, 7, 11, 150, 164, 227, 247, 235, 46 + ], + key_type: KeyType::Record, + } + ); + } +}