1use std::{fmt, io::Write, path::Path, str::FromStr};
4
5use anyhow::Context;
6use bip39::Mnemonic;
7use lexe_common::{
8 ExposeSecret,
9 api::user::{NodePk as UnstableNodePk, UserPk as UnstableUserPk},
10 root_seed::RootSeed as UnstableRootSeed,
11};
12use lexe_crypto::rng::SysRng;
13use lexe_enclave::enclave::Measurement as UnstableMeasurement;
14use lexe_node_client::credentials::{
15 ClientCredentials as UnstableClientCredentials,
16 CredentialsRef as UnstableCredentialsRef,
17};
18use serde::{Deserialize, Serialize};
19
20use crate::{
21 config::WalletEnv,
22 util::{ByteArray, hex},
23};
24
25#[derive(Debug)]
29pub enum Credentials {
30 RootSeed(RootSeed),
32 ClientCredentials(ClientCredentials),
34}
35
36impl Credentials {
37 pub fn as_ref(&self) -> CredentialsRef<'_> {
39 match self {
40 Self::RootSeed(root_seed) => CredentialsRef::RootSeed(root_seed),
41 Self::ClientCredentials(cc) =>
42 CredentialsRef::ClientCredentials(cc),
43 }
44 }
45}
46
47impl From<RootSeed> for Credentials {
48 fn from(root_seed: RootSeed) -> Self {
49 Self::RootSeed(root_seed)
50 }
51}
52
53impl From<ClientCredentials> for Credentials {
54 fn from(cc: ClientCredentials) -> Self {
55 Self::ClientCredentials(cc)
56 }
57}
58
59#[derive(Copy, Clone, Debug)]
63pub enum CredentialsRef<'a> {
64 RootSeed(&'a RootSeed),
66 ClientCredentials(&'a ClientCredentials),
68}
69
70impl<'a> CredentialsRef<'a> {
71 pub(crate) fn user_pk(self) -> Option<UserPk> {
75 self.to_unstable().user_pk().map(UserPk::from_unstable)
76 }
77
78 pub(crate) fn to_unstable(self) -> UnstableCredentialsRef<'a> {
81 match self {
82 Self::RootSeed(root_seed) =>
83 UnstableCredentialsRef::RootSeed(root_seed.unstable()),
84 Self::ClientCredentials(cc) =>
85 UnstableCredentialsRef::ClientCredentials(cc.unstable()),
86 }
87 }
88}
89
90impl<'a> From<&'a RootSeed> for CredentialsRef<'a> {
91 fn from(root_seed: &'a RootSeed) -> Self {
92 Self::RootSeed(root_seed)
93 }
94}
95
96impl<'a> From<&'a ClientCredentials> for CredentialsRef<'a> {
97 fn from(cc: &'a ClientCredentials) -> Self {
98 Self::ClientCredentials(cc)
99 }
100}
101
102#[derive(Serialize, Deserialize)]
106pub struct RootSeed(UnstableRootSeed);
107
108impl RootSeed {
109 pub fn generate() -> Self {
113 Self(UnstableRootSeed::from_rng(&mut SysRng::new()))
114 }
115
116 pub fn read(wallet_env: &WalletEnv) -> anyhow::Result<Option<Self>> {
121 let lexe_data_dir = lexe_common::default_lexe_data_dir()
122 .context("Could not get default lexe data dir")?;
123 let path = wallet_env.seedphrase_path(&lexe_data_dir);
124 Self::read_from_path(&path)
125 }
126
127 pub fn write(&self, wallet_env: &WalletEnv) -> anyhow::Result<()> {
132 let lexe_data_dir = lexe_common::default_lexe_data_dir()
133 .context("Could not get default lexe data dir")?;
134 let path = wallet_env.seedphrase_path(&lexe_data_dir);
135 self.write_to_path(&path)
136 }
137
138 pub fn read_from_path(path: &Path) -> anyhow::Result<Option<Self>> {
142 match std::fs::read_to_string(path) {
143 Ok(contents) => {
144 let mnemonic = bip39::Mnemonic::from_str(contents.trim())
145 .map_err(|e| anyhow::anyhow!("Invalid mnemonic: {e}"))?;
146 Ok(Some(Self::try_from(mnemonic)?))
147 }
148 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
149 Err(e) => Err(e).context("Failed to read seedphrase file"),
150 }
151 }
152
153 pub fn write_to_path(&self, path: &Path) -> anyhow::Result<()> {
159 #[cfg(unix)]
160 use std::os::unix::fs::OpenOptionsExt;
161
162 if let Some(parent) = path.parent() {
164 std::fs::create_dir_all(parent)
165 .context("Failed to create data directory")?;
166 }
167
168 let mut opts = std::fs::OpenOptions::new();
170 opts.write(true).create_new(true);
171
172 #[cfg(unix)]
174 opts.mode(0o600);
175
176 let mut file = opts.open(path).with_context(|| {
177 format!("Seedphrase file already exists: {}", path.display())
178 })?;
179
180 let mnemonic = self.to_mnemonic();
181 writeln!(file, "{mnemonic}")
182 .context("Failed to write seedphrase file")?;
183
184 tracing::info!("Persisted seedphrase to {}", path.display());
185
186 Ok(())
187 }
188
189 pub fn from_mnemonic(mnemonic: Mnemonic) -> anyhow::Result<Self> {
191 Self::try_from(mnemonic)
192 }
193
194 pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
196 Self::try_from(bytes)
197 }
198
199 pub fn from_hex(hex: &str) -> anyhow::Result<Self> {
201 Self::from_str(hex).map_err(anyhow::Error::from)
202 }
203
204 pub fn to_mnemonic(&self) -> Mnemonic {
208 self.unstable().to_mnemonic()
209 }
210
211 pub fn as_bytes(&self) -> &[u8] {
213 self.unstable().expose_secret()
214 }
215
216 pub fn to_hex(&self) -> String {
218 hex::encode(self.as_bytes())
219 }
220
221 pub fn derive_user_pk(&self) -> UserPk {
224 UserPk::from_unstable(self.unstable().derive_user_pk())
225 }
226
227 pub fn derive_node_pk(&self) -> NodePk {
229 NodePk::from_unstable(self.unstable().derive_node_pk())
230 }
231
232 pub fn password_encrypt(&self, password: &str) -> anyhow::Result<Vec<u8>> {
236 self.unstable()
237 .password_encrypt(&mut SysRng::new(), password)
238 }
239
240 pub fn password_decrypt(
242 password: &str,
243 encrypted: Vec<u8>,
244 ) -> anyhow::Result<Self> {
245 UnstableRootSeed::password_decrypt(password, encrypted).map(Self)
246 }
247
248 cfg_if::cfg_if! {
251 if #[cfg(feature = "unstable")] {
252 pub fn unstable(&self) -> &UnstableRootSeed {
256 &self.0
257 }
258 } else {
259 pub(crate) fn unstable(&self) -> &UnstableRootSeed {
260 &self.0
261 }
262 }
263 }
264
265 cfg_if::cfg_if! {
266 if #[cfg(feature = "unstable")] {
267 pub fn into_unstable(self) -> UnstableRootSeed {
271 self.0
272 }
273 } else {
274 pub(crate) fn into_unstable(self) -> UnstableRootSeed {
275 self.0
276 }
277 }
278 }
279}
280
281impl fmt::Debug for RootSeed {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 fmt::Debug::fmt(self.unstable(), f)
284 }
285}
286
287impl FromStr for RootSeed {
288 type Err = <UnstableRootSeed as FromStr>::Err;
289
290 fn from_str(s: &str) -> Result<Self, Self::Err> {
291 UnstableRootSeed::from_str(s).map(Self)
292 }
293}
294
295impl TryFrom<&[u8]> for RootSeed {
296 type Error = anyhow::Error;
297
298 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
299 UnstableRootSeed::try_from(bytes).map(Self)
300 }
301}
302
303impl TryFrom<Mnemonic> for RootSeed {
304 type Error = anyhow::Error;
305
306 fn try_from(mnemonic: Mnemonic) -> Result<Self, Self::Error> {
307 UnstableRootSeed::try_from(mnemonic).map(Self)
308 }
309}
310
311#[derive(Clone)]
318pub struct ClientCredentials(UnstableClientCredentials);
319
320impl ClientCredentials {
321 pub fn from_string(s: &str) -> anyhow::Result<Self> {
323 Self::from_str(s)
324 }
325
326 pub fn export_string(&self) -> String {
331 self.unstable().to_base64_blob()
332 }
333
334 pub(crate) fn unstable(&self) -> &UnstableClientCredentials {
336 &self.0
337 }
338}
339
340impl FromStr for ClientCredentials {
341 type Err = anyhow::Error;
342
343 fn from_str(s: &str) -> Result<Self, Self::Err> {
344 UnstableClientCredentials::try_from_base64_blob(s).map(Self)
345 }
346}
347
348impl fmt::Debug for ClientCredentials {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 fmt::Debug::fmt(&self.0, f)
351 }
352}
353
354#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
367#[serde(transparent)]
368pub struct Measurement(UnstableMeasurement);
369
370impl ByteArray<32> for Measurement {
371 fn from_array(array: [u8; 32]) -> Self {
372 Self(UnstableMeasurement::from_array(array))
373 }
374 fn to_array(&self) -> [u8; 32] {
375 self.0.to_array()
376 }
377 fn as_array(&self) -> &[u8; 32] {
378 self.0.as_array()
379 }
380}
381
382impl AsRef<[u8]> for Measurement {
383 fn as_ref(&self) -> &[u8] {
384 self.0.as_array().as_slice()
385 }
386}
387
388impl AsRef<[u8; 32]> for Measurement {
389 fn as_ref(&self) -> &[u8; 32] {
390 self.0.as_array()
391 }
392}
393
394impl fmt::Debug for Measurement {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 fmt::Debug::fmt(&self.0, f)
397 }
398}
399
400impl fmt::Display for Measurement {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 fmt::Display::fmt(&self.0, f)
403 }
404}
405
406impl FromStr for Measurement {
407 type Err = <UnstableMeasurement as FromStr>::Err;
408
409 fn from_str(s: &str) -> Result<Self, Self::Err> {
410 UnstableMeasurement::from_str(s).map(Self)
411 }
412}
413
414impl Measurement {
415 #[cfg(feature = "unstable")]
417 pub fn unstable(self) -> UnstableMeasurement {
418 self.0
419 }
420
421 cfg_if::cfg_if! {
422 if #[cfg(feature = "unstable")] {
423 pub fn from_unstable(inner: UnstableMeasurement) -> Self {
425 Self(inner)
426 }
427 } else {
428 pub(crate) fn from_unstable(inner: UnstableMeasurement) -> Self {
429 Self(inner)
430 }
431 }
432 }
433}
434
435#[cfg(feature = "unstable")]
436impl From<UnstableMeasurement> for Measurement {
437 fn from(inner: UnstableMeasurement) -> Self {
438 Self(inner)
439 }
440}
441
442#[cfg(feature = "unstable")]
443impl From<Measurement> for UnstableMeasurement {
444 fn from(outer: Measurement) -> Self {
445 outer.0
446 }
447}
448
449#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
457#[serde(transparent)]
458pub struct UserPk(UnstableUserPk);
459
460impl ByteArray<32> for UserPk {
461 fn from_array(array: [u8; 32]) -> Self {
462 Self(UnstableUserPk::from_array(array))
463 }
464 fn to_array(&self) -> [u8; 32] {
465 self.0.to_array()
466 }
467 fn as_array(&self) -> &[u8; 32] {
468 self.0.as_array()
469 }
470}
471
472impl AsRef<[u8]> for UserPk {
473 fn as_ref(&self) -> &[u8] {
474 self.0.as_array().as_slice()
475 }
476}
477
478impl AsRef<[u8; 32]> for UserPk {
479 fn as_ref(&self) -> &[u8; 32] {
480 self.0.as_array()
481 }
482}
483
484impl fmt::Debug for UserPk {
485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486 fmt::Debug::fmt(&self.0, f)
487 }
488}
489
490impl fmt::Display for UserPk {
491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492 fmt::Display::fmt(&self.0, f)
493 }
494}
495
496impl FromStr for UserPk {
497 type Err = <UnstableUserPk as FromStr>::Err;
498
499 fn from_str(s: &str) -> Result<Self, Self::Err> {
500 UnstableUserPk::from_str(s).map(Self)
501 }
502}
503
504impl UserPk {
505 cfg_if::cfg_if! {
506 if #[cfg(feature = "unstable")] {
507 pub fn unstable(self) -> UnstableUserPk {
509 self.0
510 }
511 } else {
512 pub(crate) fn unstable(self) -> UnstableUserPk {
513 self.0
514 }
515 }
516 }
517
518 cfg_if::cfg_if! {
519 if #[cfg(feature = "unstable")] {
520 pub fn from_unstable(inner: UnstableUserPk) -> Self {
522 Self(inner)
523 }
524 } else {
525 pub(crate) fn from_unstable(inner: UnstableUserPk) -> Self {
526 Self(inner)
527 }
528 }
529 }
530}
531
532#[cfg(feature = "unstable")]
533impl From<UnstableUserPk> for UserPk {
534 fn from(inner: UnstableUserPk) -> Self {
535 Self(inner)
536 }
537}
538
539#[cfg(feature = "unstable")]
540impl From<UserPk> for UnstableUserPk {
541 fn from(outer: UserPk) -> Self {
542 outer.0
543 }
544}
545
546#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
552#[serde(transparent)]
553pub struct NodePk(UnstableNodePk);
554
555impl NodePk {
556 pub fn from_hex(hex_str: &str) -> anyhow::Result<Self> {
558 Self::from_str(hex_str).map_err(anyhow::Error::from)
559 }
560
561 pub fn to_hex(&self) -> String {
563 self.to_string()
564 }
565}
566
567impl fmt::Debug for NodePk {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 fmt::Debug::fmt(&self.0, f)
570 }
571}
572
573impl fmt::Display for NodePk {
574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575 fmt::Display::fmt(&self.0, f)
576 }
577}
578
579impl FromStr for NodePk {
580 type Err = <UnstableNodePk as FromStr>::Err;
581
582 fn from_str(s: &str) -> Result<Self, Self::Err> {
583 UnstableNodePk::from_str(s).map(Self)
584 }
585}
586
587impl NodePk {
588 #[cfg(feature = "unstable")]
590 pub fn unstable(self) -> UnstableNodePk {
591 self.0
592 }
593
594 cfg_if::cfg_if! {
595 if #[cfg(feature = "unstable")] {
596 pub fn from_unstable(inner: UnstableNodePk) -> Self {
598 Self(inner)
599 }
600 } else {
601 pub(crate) fn from_unstable(inner: UnstableNodePk) -> Self {
602 Self(inner)
603 }
604 }
605 }
606}
607
608#[cfg(feature = "unstable")]
609impl From<UnstableNodePk> for NodePk {
610 fn from(inner: UnstableNodePk) -> Self {
611 Self(inner)
612 }
613}
614
615#[cfg(feature = "unstable")]
616impl From<NodePk> for UnstableNodePk {
617 fn from(outer: NodePk) -> Self {
618 outer.0
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625
626 #[test]
627 fn seedphrase_file_roundtrip() {
628 let root_seed1 = RootSeed::generate();
629
630 let tempdir = tempfile::tempdir().unwrap();
631 let path = tempdir.path().join("seedphrase.txt");
632
633 root_seed1.write_to_path(&path).unwrap();
635
636 let root_seed2 = RootSeed::read_from_path(&path).unwrap().unwrap();
638 assert_eq!(root_seed1.as_bytes(), root_seed2.as_bytes());
639
640 let err = root_seed1.write_to_path(&path).unwrap_err();
642 assert!(err.to_string().contains("already exists"));
643
644 let missing = tempdir.path().join("missing.txt");
646 assert!(RootSeed::read_from_path(&missing).unwrap().is_none());
647 }
648}