Skip to content

Commit 2f56b89

Browse files
committed
refactor(owx): remove unused types and optimize memory handling for secret material
Remove unused ChainId CAIP-2 type and related validation logic from chain.rs. Remove log_agent convenience method from audit.rs. Remove SecretKey wrapper type from credential.rs, moving all secret key handling into internal modules. Optimize secret material handling: change Secret::to_bytes to return Zeroizing<Vec<u8>> for automatic memory scrubbing, remove manual zeroize call in create_api_key. Convert Config::default_rpc to
1 parent 3295cb2 commit 2f56b89

10 files changed

Lines changed: 93 additions & 273 deletions

File tree

owx/src/audit.rs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,27 +83,6 @@ impl AuditLog {
8383
});
8484
}
8585

86-
/// Convenience: log an agent operation.
87-
pub fn log_agent(
88-
&self,
89-
operation: &str,
90-
wallet_id: Option<&str>,
91-
api_key_id: &str,
92-
chain_id: Option<&str>,
93-
success: bool,
94-
error: Option<&str>,
95-
) {
96-
self.log(&AuditEntry {
97-
timestamp: chrono::Utc::now().to_rfc3339(),
98-
operation: operation.to_owned(),
99-
wallet_id: wallet_id.map(ToOwned::to_owned),
100-
api_key_id: Some(api_key_id.to_owned()),
101-
chain_id: chain_id.map(ToOwned::to_owned),
102-
success,
103-
error: error.map(ToOwned::to_owned),
104-
});
105-
}
106-
10786
/// Read all audit entries from the log file.
10887
///
10988
/// Returns an empty vec if the file does not exist.

owx/src/broadcast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ pub fn resolve_rpc(
174174
return Ok(v.clone());
175175
}
176176
}
177-
for (k, v) in &defaults {
177+
for (k, v) in defaults {
178178
if k.starts_with(ns) {
179179
return Ok(v.clone());
180180
}

owx/src/chain.rs

Lines changed: 1 addition & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use std::fmt;
88
use std::str::FromStr;
99

10-
use serde::{Deserialize, Serialize, de};
10+
use serde::{Deserialize, Serialize};
1111

1212
/// Master chain table. Every row is:
1313
///
@@ -123,98 +123,6 @@ macro_rules! impl_chain_family_methods {
123123
}
124124
for_each_chain!(impl_chain_family_methods);
125125

126-
/// CAIP-2 chain identifier (`namespace:reference`).
127-
#[derive(Debug, Clone, Eq)]
128-
pub struct ChainId {
129-
/// CAIP-2 namespace (e.g. "eip155", "solana").
130-
pub namespace: String,
131-
/// CAIP-2 reference (e.g. "1", "mainnet").
132-
pub reference: String,
133-
}
134-
135-
impl ChainId {
136-
/// Validate a CAIP-2 namespace: 3–8 chars, `[a-z0-9]` only.
137-
fn validate_namespace(ns: &str) -> Result<(), String> {
138-
if !(3..=8).contains(&ns.len()) {
139-
return Err(format!(
140-
"namespace must be 3–8 chars, got {} ('{ns}')",
141-
ns.len()
142-
));
143-
}
144-
if !ns
145-
.bytes()
146-
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit())
147-
{
148-
return Err(format!("namespace must be [a-z0-9], got '{ns}'"));
149-
}
150-
Ok(())
151-
}
152-
153-
/// Validate a CAIP-2 reference: 1–64 chars, `[a-zA-Z0-9-_]` only.
154-
fn validate_reference(r: &str) -> Result<(), String> {
155-
if r.is_empty() || r.len() > 64 {
156-
return Err(format!(
157-
"reference must be 1–64 chars, got {} ('{r}')",
158-
r.len()
159-
));
160-
}
161-
if !r
162-
.bytes()
163-
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
164-
{
165-
return Err(format!("reference contains invalid chars: '{r}'"));
166-
}
167-
Ok(())
168-
}
169-
}
170-
171-
impl FromStr for ChainId {
172-
type Err = String;
173-
fn from_str(s: &str) -> Result<Self, Self::Err> {
174-
let (ns, reference) = s
175-
.split_once(':')
176-
.ok_or_else(|| format!("expected 'namespace:reference', got '{s}'"))?;
177-
Self::validate_namespace(ns)?;
178-
Self::validate_reference(reference)?;
179-
Ok(Self {
180-
namespace: ns.to_owned(),
181-
reference: reference.to_owned(),
182-
})
183-
}
184-
}
185-
186-
impl fmt::Display for ChainId {
187-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188-
write!(f, "{}:{}", self.namespace, self.reference)
189-
}
190-
}
191-
192-
impl PartialEq for ChainId {
193-
fn eq(&self, other: &Self) -> bool {
194-
self.namespace == other.namespace && self.reference == other.reference
195-
}
196-
}
197-
198-
impl std::hash::Hash for ChainId {
199-
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
200-
self.namespace.hash(state);
201-
self.reference.hash(state);
202-
}
203-
}
204-
205-
impl Serialize for ChainId {
206-
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
207-
serializer.serialize_str(&self.to_string())
208-
}
209-
}
210-
211-
impl<'de> Deserialize<'de> for ChainId {
212-
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
213-
let s = String::deserialize(deserializer)?;
214-
s.parse().map_err(de::Error::custom)
215-
}
216-
}
217-
218126
/// A resolved chain: name + family + CAIP-2 identifier.
219127
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220128
pub struct Chain {
@@ -470,22 +378,6 @@ mod tests {
470378
}
471379
}
472380

473-
#[test]
474-
fn chain_id_parse_valid() {
475-
let id: ChainId = "eip155:1".parse().unwrap();
476-
assert_eq!(id.namespace, "eip155");
477-
assert_eq!(id.reference, "1");
478-
assert_eq!(id.to_string(), "eip155:1");
479-
}
480-
481-
#[test]
482-
fn chain_id_reject_invalid() {
483-
assert!("".parse::<ChainId>().is_err());
484-
assert!("ab:1".parse::<ChainId>().is_err());
485-
assert!("eip1551".parse::<ChainId>().is_err());
486-
assert!("EIP155:1".parse::<ChainId>().is_err());
487-
}
488-
489381
#[test]
490382
fn default_chain_all_families() {
491383
for &fam in &ALL_FAMILIES {

owx/src/config.rs

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::collections::HashMap;
44
use std::path::{Path, PathBuf};
5+
use std::sync::LazyLock;
56

67
use serde::{Deserialize, Serialize};
78

@@ -36,60 +37,63 @@ pub struct Config {
3637
}
3738

3839
impl Config {
39-
/// Built-in default RPC endpoints for well-known chains.
40+
/// Built-in default RPC endpoints for well-known chains (lazily initialized).
4041
#[must_use]
41-
pub fn default_rpc() -> HashMap<String, String> {
42-
HashMap::from([
43-
("eip155:1".into(), "https://eth.llamarpc.com".into()),
44-
("eip155:137".into(), "https://polygon-rpc.com".into()),
45-
("eip155:42161".into(), "https://arb1.arbitrum.io/rpc".into()),
46-
("eip155:10".into(), "https://mainnet.optimism.io".into()),
47-
("eip155:8453".into(), "https://mainnet.base.org".into()),
48-
(
49-
"eip155:56".into(),
50-
"https://bsc-dataseed.binance.org".into(),
51-
),
52-
("eip155:9745".into(), "https://rpc.plasma.to".into()),
53-
(
54-
"eip155:43114".into(),
55-
"https://api.avax.network/ext/bc/C/rpc".into(),
56-
),
57-
(
58-
"eip155:42793".into(),
59-
"https://node.mainnet.etherlink.com".into(),
60-
),
61-
(
62-
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp".into(),
63-
"https://api.mainnet-beta.solana.com".into(),
64-
),
65-
(
66-
"bip122:000000000019d6689c085ae165831e93".into(),
67-
"https://mempool.space/api".into(),
68-
),
69-
(
70-
"cosmos:cosmoshub-4".into(),
71-
"https://cosmos-rest.publicnode.com".into(),
72-
),
73-
("tron:mainnet".into(), "https://api.trongrid.io".into()),
74-
("ton:mainnet".into(), "https://toncenter.com/api/v2".into()),
75-
(
76-
"fil:mainnet".into(),
77-
"https://api.node.glif.io/rpc/v1".into(),
78-
),
79-
(
80-
"sui:mainnet".into(),
81-
"https://fullnode.mainnet.sui.io:443".into(),
82-
),
83-
("xrpl:mainnet".into(), "https://s1.ripple.com:51234".into()),
84-
(
85-
"xrpl:testnet".into(),
86-
"https://s.altnet.rippletest.net:51234".into(),
87-
),
88-
(
89-
"xrpl:devnet".into(),
90-
"https://s.devnet.rippletest.net:51234".into(),
91-
),
92-
])
42+
pub fn default_rpc() -> &'static HashMap<String, String> {
43+
static RPC: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
44+
HashMap::from([
45+
("eip155:1".into(), "https://eth.llamarpc.com".into()),
46+
("eip155:137".into(), "https://polygon-rpc.com".into()),
47+
("eip155:42161".into(), "https://arb1.arbitrum.io/rpc".into()),
48+
("eip155:10".into(), "https://mainnet.optimism.io".into()),
49+
("eip155:8453".into(), "https://mainnet.base.org".into()),
50+
(
51+
"eip155:56".into(),
52+
"https://bsc-dataseed.binance.org".into(),
53+
),
54+
("eip155:9745".into(), "https://rpc.plasma.to".into()),
55+
(
56+
"eip155:43114".into(),
57+
"https://api.avax.network/ext/bc/C/rpc".into(),
58+
),
59+
(
60+
"eip155:42793".into(),
61+
"https://node.mainnet.etherlink.com".into(),
62+
),
63+
(
64+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp".into(),
65+
"https://api.mainnet-beta.solana.com".into(),
66+
),
67+
(
68+
"bip122:000000000019d6689c085ae165831e93".into(),
69+
"https://mempool.space/api".into(),
70+
),
71+
(
72+
"cosmos:cosmoshub-4".into(),
73+
"https://cosmos-rest.publicnode.com".into(),
74+
),
75+
("tron:mainnet".into(), "https://api.trongrid.io".into()),
76+
("ton:mainnet".into(), "https://toncenter.com/api/v2".into()),
77+
(
78+
"fil:mainnet".into(),
79+
"https://api.node.glif.io/rpc/v1".into(),
80+
),
81+
(
82+
"sui:mainnet".into(),
83+
"https://fullnode.mainnet.sui.io:443".into(),
84+
),
85+
("xrpl:mainnet".into(), "https://s1.ripple.com:51234".into()),
86+
(
87+
"xrpl:testnet".into(),
88+
"https://s.altnet.rippletest.net:51234".into(),
89+
),
90+
(
91+
"xrpl:devnet".into(),
92+
"https://s.devnet.rippletest.net:51234".into(),
93+
),
94+
])
95+
});
96+
&RPC
9397
}
9498

9599
/// Look up an RPC URL by CAIP-2 chain ID.
@@ -123,7 +127,7 @@ impl Default for Config {
123127
fn default() -> Self {
124128
Self {
125129
vault_path: default_vault_path(),
126-
rpc: Self::default_rpc(),
130+
rpc: Self::default_rpc().clone(),
127131
plugins: HashMap::new(),
128132
backup: None,
129133
}

owx/src/credential.rs

Lines changed: 1 addition & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
//! Authentication credentials and secret key material.
2-
3-
use crate::error::Error;
1+
//! Authentication credentials.
42
53
/// Authentication credential for wallet operations.
64
///
@@ -37,40 +35,6 @@ impl<'a> Credential<'a> {
3735
}
3836
}
3937

40-
/// A private signing key that is zeroized on drop.
41-
///
42-
/// Wraps raw key bytes (not hex). Never exposed outside the `owx` crate
43-
/// as a public type — callers interact only through `Owx` methods.
44-
pub struct SecretKey(zeroize::Zeroizing<Vec<u8>>);
45-
46-
impl SecretKey {
47-
/// Create from raw bytes.
48-
#[must_use]
49-
pub fn new(bytes: Vec<u8>) -> Self {
50-
Self(zeroize::Zeroizing::new(bytes))
51-
}
52-
53-
/// Create from a hex-encoded string.
54-
pub fn from_hex(hex_str: &str) -> Result<Self, Error> {
55-
let clean = hex_str.strip_prefix("0x").unwrap_or(hex_str);
56-
let bytes =
57-
hex::decode(clean).map_err(|e| Error::InvalidInput(format!("invalid hex key: {e}")))?;
58-
Ok(Self::new(bytes))
59-
}
60-
61-
/// Expose the raw key bytes.
62-
#[must_use]
63-
pub fn as_bytes(&self) -> &[u8] {
64-
&self.0
65-
}
66-
}
67-
68-
impl std::fmt::Debug for SecretKey {
69-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70-
f.write_str("SecretKey([REDACTED])")
71-
}
72-
}
73-
7438
#[cfg(test)]
7539
#[allow(clippy::unwrap_used)]
7640
mod tests {
@@ -95,31 +59,4 @@ mod tests {
9559
fn parse_empty_is_passphrase() {
9660
assert!(matches!(Credential::parse(""), Credential::Passphrase("")));
9761
}
98-
99-
#[test]
100-
fn secret_key_from_hex() {
101-
let key =
102-
SecretKey::from_hex("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318")
103-
.unwrap();
104-
assert_eq!(key.as_bytes().len(), 32);
105-
}
106-
107-
#[test]
108-
fn secret_key_strips_0x_prefix() {
109-
let key = SecretKey::from_hex("0xaabb").unwrap();
110-
assert_eq!(key.as_bytes(), &[0xaa, 0xbb]);
111-
}
112-
113-
#[test]
114-
fn secret_key_invalid_hex_rejected() {
115-
assert!(SecretKey::from_hex("not-hex").is_err());
116-
}
117-
118-
#[test]
119-
fn secret_key_debug_redacted() {
120-
let key = SecretKey::from_hex("aabb").unwrap();
121-
let dbg = format!("{key:?}");
122-
assert_eq!(dbg, "SecretKey([REDACTED])");
123-
assert!(!dbg.contains("aa"));
124-
}
12562
}

0 commit comments

Comments
 (0)