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 {
36 signing_key: SigningKey,
38 pub address: String,
40 pub authenticator_ids: Vec<u64>,
42}
43
44impl Debug for DydxCredential {
45 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct(stringify!(DydxCredential))
47 .field("address", &self.address)
48 .field("authenticator_ids", &self.authenticator_ids)
49 .field("signing_key", &"<redacted>")
50 .finish()
51 }
52}
53
54impl DydxCredential {
55 pub fn from_mnemonic(
61 mnemonic_phrase: &str,
62 account_index: u32,
63 authenticator_ids: Vec<u64>,
64 ) -> anyhow::Result<Self> {
65 use std::str::FromStr;
66
67 use bip32::{DerivationPath, Language, Mnemonic};
68
69 let mnemonic =
71 Mnemonic::new(mnemonic_phrase, Language::English).context("Invalid mnemonic phrase")?;
72 let seed = mnemonic.to_seed("");
73
74 let derivation_path = format!("m/44'/118'/0'/0/{account_index}");
77 let path = DerivationPath::from_str(&derivation_path).context("Invalid derivation path")?;
78
79 let signing_key =
81 SigningKey::derive_from_path(&seed, &path).context("Failed to derive signing key")?;
82
83 let public_key = signing_key.public_key();
85 let account_id = public_key
86 .account_id(DYDX_BECH32_PREFIX)
87 .map_err(|e| anyhow::anyhow!("Failed to derive account ID: {e}"))?;
88 let address = account_id.to_string();
89
90 Ok(Self {
91 signing_key,
92 address,
93 authenticator_ids,
94 })
95 }
96
97 pub fn from_private_key(
103 private_key_hex: &str,
104 authenticator_ids: Vec<u64>,
105 ) -> anyhow::Result<Self> {
106 let key_bytes = hex::decode(private_key_hex.trim_start_matches("0x"))
108 .context("Invalid hex private key")?;
109
110 let signing_key = SigningKey::from_slice(&key_bytes)
111 .map_err(|e| anyhow::anyhow!("Invalid secp256k1 private key: {e}"))?;
112
113 let public_key = signing_key.public_key();
115 let account_id = public_key
116 .account_id(DYDX_BECH32_PREFIX)
117 .map_err(|e| anyhow::anyhow!("Failed to derive account ID: {e}"))?;
118 let address = account_id.to_string();
119
120 Ok(Self {
121 signing_key,
122 address,
123 authenticator_ids,
124 })
125 }
126
127 pub fn account_id(&self) -> anyhow::Result<AccountId> {
133 self.address
134 .parse()
135 .map_err(|e| anyhow::anyhow!("Failed to parse account ID: {e}"))
136 }
137
138 pub fn sign(&self, sign_doc: &SignDoc) -> anyhow::Result<Vec<u8>> {
146 let sign_bytes = sign_doc
147 .clone()
148 .into_bytes()
149 .map_err(|e| anyhow::anyhow!("Failed to serialize SignDoc: {e}"))?;
150
151 let signature = self
152 .signing_key
153 .sign(&sign_bytes)
154 .map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
155 Ok(signature.to_bytes().to_vec())
156 }
157
158 pub fn sign_bytes(&self, message: &[u8]) -> anyhow::Result<Vec<u8>> {
166 let signature = self
167 .signing_key
168 .sign(message)
169 .map_err(|e| anyhow::anyhow!("Failed to sign: {e}"))?;
170 Ok(signature.to_bytes().to_vec())
171 }
172
173 pub fn public_key(&self) -> cosmrs::crypto::PublicKey {
175 self.signing_key.public_key()
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use rstest::rstest;
182
183 use super::*;
184
185 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";
187
188 #[rstest]
189 fn test_from_mnemonic() {
190 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
191 .expect("Failed to create credential");
192
193 assert!(credential.address.starts_with("dydx"));
194 assert!(credential.authenticator_ids.is_empty());
195 }
196
197 #[rstest]
198 fn test_from_mnemonic_with_authenticators() {
199 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![1, 2, 3])
200 .expect("Failed to create credential");
201
202 assert_eq!(credential.authenticator_ids, vec![1, 2, 3]);
203 }
204
205 #[rstest]
206 fn test_from_private_key() {
207 let test_key = format!("{:0>64}", "1");
210
211 let credential = DydxCredential::from_private_key(&test_key, vec![])
212 .expect("Failed to create credential from private key");
213
214 assert!(credential.address.starts_with("dydx"));
215 assert!(credential.authenticator_ids.is_empty());
216 }
217
218 #[rstest]
219 fn test_account_id() {
220 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
221 .expect("Failed to create credential");
222
223 let account_id = credential.account_id().expect("Failed to get account ID");
224 assert_eq!(account_id.to_string(), credential.address);
225 }
226
227 #[rstest]
228 fn test_sign_bytes() {
229 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
230 .expect("Failed to create credential");
231
232 let message = b"test message";
233 let signature = credential
234 .sign_bytes(message)
235 .expect("Failed to sign bytes");
236
237 assert_eq!(signature.len(), 64);
239 }
240
241 #[rstest]
242 fn test_debug_redacts_key() {
243 let credential = DydxCredential::from_mnemonic(TEST_MNEMONIC, 0, vec![])
244 .expect("Failed to create credential");
245
246 let debug_str = format!("{credential:?}");
247 assert!(debug_str.contains("<redacted>"));
249 assert!(debug_str.contains("DydxCredential"));
251 assert!(debug_str.contains(&credential.address));
253 }
254}