nautilus_dydx/common/
credential.rs1#![allow(unused_assignments)] use std::fmt::{Debug, Formatter};
24
25use anyhow::Context;
26use cosmrs::{AccountId, crypto::secp256k1::SigningKey, tx::SignDoc};
27
28use crate::common::consts::DYDX_BECH32_PREFIX;
29
30pub struct DydxCredential {
34 signing_key: SigningKey,
36 pub address: String,
38 pub authenticator_ids: Vec<u64>,
40}
41
42impl Drop for DydxCredential {
43 fn drop(&mut self) {
44 }
47}
48
49impl Debug for DydxCredential {
50 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct(stringify!(DydxCredential))
52 .field("address", &self.address)
53 .field("authenticator_ids", &self.authenticator_ids)
54 .field("signing_key", &"<redacted>")
55 .finish()
56 }
57}
58
59impl DydxCredential {
60 pub fn from_mnemonic(
66 mnemonic_phrase: &str,
67 account_index: u32,
68 authenticator_ids: Vec<u64>,
69 ) -> anyhow::Result<Self> {
70 use std::str::FromStr;
71
72 use bip32::{DerivationPath, Language, Mnemonic};
73
74 let mnemonic =
76 Mnemonic::new(mnemonic_phrase, Language::English).context("Invalid mnemonic phrase")?;
77 let seed = mnemonic.to_seed("");
78
79 let derivation_path = format!("m/44'/118'/0'/0/{account_index}");
82 let path = DerivationPath::from_str(&derivation_path).context("Invalid derivation path")?;
83
84 let signing_key =
86 SigningKey::derive_from_path(&seed, &path).context("Failed to derive signing key")?;
87
88 let public_key = signing_key.public_key();
90 let account_id = public_key
91 .account_id(DYDX_BECH32_PREFIX)
92 .map_err(|e| anyhow::anyhow!("Failed to derive account ID: {}", e))?;
93 let address = account_id.to_string();
94
95 Ok(Self {
96 signing_key,
97 address,
98 authenticator_ids,
99 })
100 }
101
102 pub fn from_private_key(
108 private_key_hex: &str,
109 authenticator_ids: Vec<u64>,
110 ) -> anyhow::Result<Self> {
111 let key_bytes = hex::decode(private_key_hex.trim_start_matches("0x"))
113 .context("Invalid hex private key")?;
114
115 let signing_key = SigningKey::from_slice(&key_bytes)
116 .map_err(|e| anyhow::anyhow!("Invalid secp256k1 private key: {}", e))?;
117
118 let public_key = signing_key.public_key();
120 let account_id = public_key
121 .account_id(DYDX_BECH32_PREFIX)
122 .map_err(|e| anyhow::anyhow!("Failed to derive account ID: {}", e))?;
123 let address = account_id.to_string();
124
125 Ok(Self {
126 signing_key,
127 address,
128 authenticator_ids,
129 })
130 }
131
132 pub fn account_id(&self) -> anyhow::Result<AccountId> {
138 self.address
139 .parse()
140 .map_err(|e| anyhow::anyhow!("Failed to parse account ID: {}", e))
141 }
142
143 pub fn sign(&self, sign_doc: &SignDoc) -> anyhow::Result<Vec<u8>> {
151 let sign_bytes = sign_doc
152 .clone()
153 .into_bytes()
154 .map_err(|e| anyhow::anyhow!("Failed to serialize SignDoc: {}", e))?;
155
156 let signature = self
157 .signing_key
158 .sign(&sign_bytes)
159 .map_err(|e| anyhow::anyhow!("Failed to sign: {}", e))?;
160 Ok(signature.to_bytes().to_vec())
161 }
162
163 pub fn sign_bytes(&self, message: &[u8]) -> anyhow::Result<Vec<u8>> {
171 let signature = self
172 .signing_key
173 .sign(message)
174 .map_err(|e| anyhow::anyhow!("Failed to sign: {}", e))?;
175 Ok(signature.to_bytes().to_vec())
176 }
177
178 pub fn public_key(&self) -> cosmrs::crypto::PublicKey {
180 self.signing_key.public_key()
181 }
182}
183
184#[cfg(test)]
189mod tests {
190 use rstest::rstest;
191
192 use super::*;
193
194 const TEST_MNEMONIC: &str = "mirror actor skill push coach wait confirm orchard lunch mobile athlete gossip awake miracle matter bus reopen team ladder lazy list timber render wait";
196
197 #[rstest]
198 fn test_from_mnemonic() {
199 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
200 .expect("Failed to create credential");
201
202 assert!(credential.address.starts_with("dydx"));
203 assert!(credential.authenticator_ids.is_empty());
204 }
205
206 #[rstest]
207 fn test_from_mnemonic_with_authenticators() {
208 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![1, 2, 3])
209 .expect("Failed to create credential");
210
211 assert_eq!(credential.authenticator_ids, vec![1, 2, 3]);
212 }
213
214 #[rstest]
215 fn test_from_private_key() {
216 let test_key = format!("{:0>64}", "1");
219
220 let credential = DydxCredential::from_private_key(&test_key, vec![])
221 .expect("Failed to create credential from private key");
222
223 assert!(credential.address.starts_with("dydx"));
224 assert!(credential.authenticator_ids.is_empty());
225 }
226
227 #[rstest]
228 fn test_account_id() {
229 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
230 .expect("Failed to create credential");
231
232 let account_id = credential.account_id().expect("Failed to get account ID");
233 assert_eq!(account_id.to_string(), credential.address);
234 }
235
236 #[rstest]
237 fn test_sign_bytes() {
238 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
239 .expect("Failed to create credential");
240
241 let message = b"test message";
242 let signature = credential
243 .sign_bytes(message)
244 .expect("Failed to sign bytes");
245
246 assert_eq!(signature.len(), 64);
248 }
249
250 #[rstest]
251 fn test_debug_redacts_key() {
252 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
253 .expect("Failed to create credential");
254
255 let debug_str = format!("{:?}", credential);
256 assert!(debug_str.contains("<redacted>"));
258 assert!(debug_str.contains("DydxCredential"));
260 assert!(debug_str.contains(&credential.address));
262 }
263}