nautilus_dydx/common/
credential.rs1#![allow(unused_assignments)] use std::fmt::Debug;
24
25use anyhow::Context;
26use cosmrs::{AccountId, crypto::secp256k1::SigningKey, tx::SignDoc};
27
28use crate::common::consts::DYDX_BECH32_PREFIX;
29
30pub struct DydxCredential {
39 signing_key: SigningKey,
41 pub address: String,
43 pub authenticator_ids: Vec<u64>,
45}
46
47impl Debug for DydxCredential {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 f.debug_struct(stringify!(DydxCredential))
50 .field("address", &self.address)
51 .field("authenticator_ids", &self.authenticator_ids)
52 .field("signing_key", &"<redacted>")
53 .finish()
54 }
55}
56
57impl DydxCredential {
58 pub fn from_mnemonic(
64 mnemonic_phrase: &str,
65 account_index: u32,
66 authenticator_ids: Vec<u64>,
67 ) -> anyhow::Result<Self> {
68 use std::str::FromStr;
69
70 use bip32::{DerivationPath, Language, Mnemonic};
71
72 let mnemonic =
75 Mnemonic::new(mnemonic_phrase, Language::English).context("Invalid mnemonic phrase")?;
76 let seed = mnemonic.to_seed("");
77
78 let derivation_path = format!("m/44'/118'/0'/0/{account_index}");
81 let path = DerivationPath::from_str(&derivation_path).context("Invalid derivation path")?;
82
83 let signing_key =
84 SigningKey::derive_from_path(&seed, &path).context("Failed to derive signing key")?;
85
86 let public_key = signing_key.public_key();
88 let account_id = public_key
89 .account_id(DYDX_BECH32_PREFIX)
90 .map_err(|e| anyhow::anyhow!("Failed to derive account ID: {e}"))?;
91 let address = account_id.to_string();
92
93 Ok(Self {
94 signing_key,
95 address,
96 authenticator_ids,
97 })
98 }
99
100 pub fn from_private_key(
106 private_key_hex: &str,
107 authenticator_ids: Vec<u64>,
108 ) -> anyhow::Result<Self> {
109 let key_bytes = hex::decode(private_key_hex.trim_start_matches("0x"))
111 .context("Invalid hex private key")?;
112
113 let signing_key = SigningKey::from_slice(&key_bytes)
114 .map_err(|e| anyhow::anyhow!("Invalid secp256k1 private key: {e}"))?;
115
116 let public_key = signing_key.public_key();
118 let account_id = public_key
119 .account_id(DYDX_BECH32_PREFIX)
120 .map_err(|e| anyhow::anyhow!("Failed to derive account ID: {e}"))?;
121 let address = account_id.to_string();
122
123 Ok(Self {
124 signing_key,
125 address,
126 authenticator_ids,
127 })
128 }
129
130 pub fn account_id(&self) -> anyhow::Result<AccountId> {
136 self.address
137 .parse()
138 .map_err(|e| anyhow::anyhow!("Failed to parse account ID: {e}"))
139 }
140
141 pub fn sign(&self, sign_doc: &SignDoc) -> anyhow::Result<Vec<u8>> {
149 let sign_bytes = sign_doc
150 .clone()
151 .into_bytes()
152 .map_err(|e| anyhow::anyhow!("Failed to serialize SignDoc: {e}"))?;
153
154 let signature = self
155 .signing_key
156 .sign(&sign_bytes)
157 .map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
158 Ok(signature.to_bytes().to_vec())
159 }
160
161 pub fn sign_bytes(&self, message: &[u8]) -> anyhow::Result<Vec<u8>> {
169 let signature = self
170 .signing_key
171 .sign(message)
172 .map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
173 Ok(signature.to_bytes().to_vec())
174 }
175
176 pub fn public_key(&self) -> cosmrs::crypto::PublicKey {
178 self.signing_key.public_key()
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use rstest::rstest;
185
186 use super::*;
187
188 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";
190
191 #[rstest]
192 fn test_from_mnemonic() {
193 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
194 .expect("Failed to create credential");
195
196 assert!(credential.address.starts_with("dydx"));
197 assert!(credential.authenticator_ids.is_empty());
198 }
199
200 #[rstest]
201 fn test_from_mnemonic_with_authenticators() {
202 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![1, 2, 3])
203 .expect("Failed to create credential");
204
205 assert_eq!(credential.authenticator_ids, vec![1, 2, 3]);
206 }
207
208 #[rstest]
209 fn test_from_private_key() {
210 let test_key = format!("{:0>64}", "1");
213
214 let credential = DydxCredential::from_private_key(&test_key, vec![])
215 .expect("Failed to create credential from private key");
216
217 assert!(credential.address.starts_with("dydx"));
218 assert!(credential.authenticator_ids.is_empty());
219 }
220
221 #[rstest]
222 fn test_account_id() {
223 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
224 .expect("Failed to create credential");
225
226 let account_id = credential.account_id().expect("Failed to get account ID");
227 assert_eq!(account_id.to_string(), credential.address);
228 }
229
230 #[rstest]
231 fn test_sign_bytes() {
232 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
233 .expect("Failed to create credential");
234
235 let message = b"test message";
236 let signature = credential
237 .sign_bytes(message)
238 .expect("Failed to sign bytes");
239
240 assert_eq!(signature.len(), 64);
242 }
243
244 #[rstest]
245 fn test_debug_redacts_key() {
246 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
247 .expect("Failed to create credential");
248
249 let debug_str = format!("{credential:?}");
250 assert!(debug_str.contains("<redacted>"));
252 assert!(debug_str.contains("DydxCredential"));
254 assert!(debug_str.contains(&credential.address));
256 }
257}