use std::{fmt, io::Write, path::Path, str::FromStr};
use anyhow::Context;
use bip39::Mnemonic;
use lexe_common::{
ExposeSecret,
api::user::{NodePk as UnstableNodePk, UserPk as UnstableUserPk},
root_seed::RootSeed as UnstableRootSeed,
};
use lexe_crypto::rng::SysRng;
use lexe_enclave::enclave::Measurement as UnstableMeasurement;
use lexe_node_client::credentials::{
ClientCredentials as UnstableClientCredentials,
CredentialsRef as UnstableCredentialsRef,
};
use serde::{Deserialize, Serialize};
use crate::{
config::WalletEnv,
util::{ByteArray, hex},
};
#[derive(Debug)]
pub enum Credentials {
RootSeed(RootSeed),
ClientCredentials(ClientCredentials),
}
impl Credentials {
pub fn as_ref(&self) -> CredentialsRef<'_> {
match self {
Self::RootSeed(root_seed) => CredentialsRef::RootSeed(root_seed),
Self::ClientCredentials(cc) =>
CredentialsRef::ClientCredentials(cc),
}
}
}
impl From<RootSeed> for Credentials {
fn from(root_seed: RootSeed) -> Self {
Self::RootSeed(root_seed)
}
}
impl From<ClientCredentials> for Credentials {
fn from(cc: ClientCredentials) -> Self {
Self::ClientCredentials(cc)
}
}
#[derive(Copy, Clone, Debug)]
pub enum CredentialsRef<'a> {
RootSeed(&'a RootSeed),
ClientCredentials(&'a ClientCredentials),
}
impl<'a> CredentialsRef<'a> {
pub(crate) fn user_pk(self) -> Option<UserPk> {
self.to_unstable().user_pk().map(UserPk::from_unstable)
}
pub(crate) fn to_unstable(self) -> UnstableCredentialsRef<'a> {
match self {
Self::RootSeed(root_seed) =>
UnstableCredentialsRef::RootSeed(root_seed.unstable()),
Self::ClientCredentials(cc) =>
UnstableCredentialsRef::ClientCredentials(cc.unstable()),
}
}
}
impl<'a> From<&'a RootSeed> for CredentialsRef<'a> {
fn from(root_seed: &'a RootSeed) -> Self {
Self::RootSeed(root_seed)
}
}
impl<'a> From<&'a ClientCredentials> for CredentialsRef<'a> {
fn from(cc: &'a ClientCredentials) -> Self {
Self::ClientCredentials(cc)
}
}
#[derive(Serialize, Deserialize)]
pub struct RootSeed(UnstableRootSeed);
impl RootSeed {
pub fn generate() -> Self {
Self(UnstableRootSeed::from_rng(&mut SysRng::new()))
}
pub fn read(wallet_env: &WalletEnv) -> anyhow::Result<Option<Self>> {
let lexe_data_dir = lexe_common::default_lexe_data_dir()
.context("Could not get default lexe data dir")?;
let path = wallet_env.seedphrase_path(&lexe_data_dir);
Self::read_from_path(&path)
}
pub fn write(&self, wallet_env: &WalletEnv) -> anyhow::Result<()> {
let lexe_data_dir = lexe_common::default_lexe_data_dir()
.context("Could not get default lexe data dir")?;
let path = wallet_env.seedphrase_path(&lexe_data_dir);
self.write_to_path(&path)
}
pub fn read_from_path(path: &Path) -> anyhow::Result<Option<Self>> {
match std::fs::read_to_string(path) {
Ok(contents) => {
let mnemonic = bip39::Mnemonic::from_str(contents.trim())
.map_err(|e| anyhow::anyhow!("Invalid mnemonic: {e}"))?;
Ok(Some(Self::try_from(mnemonic)?))
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(e) => Err(e).context("Failed to read seedphrase file"),
}
}
pub fn write_to_path(&self, path: &Path) -> anyhow::Result<()> {
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.context("Failed to create data directory")?;
}
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create_new(true);
#[cfg(unix)]
opts.mode(0o600);
let mut file = opts.open(path).with_context(|| {
format!("Seedphrase file already exists: {}", path.display())
})?;
let mnemonic = self.to_mnemonic();
writeln!(file, "{mnemonic}")
.context("Failed to write seedphrase file")?;
tracing::info!("Persisted seedphrase to {}", path.display());
Ok(())
}
pub fn from_mnemonic(mnemonic: Mnemonic) -> anyhow::Result<Self> {
Self::try_from(mnemonic)
}
pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
Self::try_from(bytes)
}
pub fn from_hex(hex: &str) -> anyhow::Result<Self> {
Self::from_str(hex).map_err(anyhow::Error::from)
}
pub fn to_mnemonic(&self) -> Mnemonic {
self.unstable().to_mnemonic()
}
pub fn as_bytes(&self) -> &[u8] {
self.unstable().expose_secret()
}
pub fn to_hex(&self) -> String {
hex::encode(self.as_bytes())
}
pub fn derive_user_pk(&self) -> UserPk {
UserPk::from_unstable(self.unstable().derive_user_pk())
}
pub fn derive_node_pk(&self) -> NodePk {
NodePk::from_unstable(self.unstable().derive_node_pk())
}
pub fn password_encrypt(&self, password: &str) -> anyhow::Result<Vec<u8>> {
self.unstable()
.password_encrypt(&mut SysRng::new(), password)
}
pub fn password_decrypt(
password: &str,
encrypted: Vec<u8>,
) -> anyhow::Result<Self> {
UnstableRootSeed::password_decrypt(password, encrypted).map(Self)
}
cfg_if::cfg_if! {
if #[cfg(feature = "unstable")] {
pub fn unstable(&self) -> &UnstableRootSeed {
&self.0
}
} else {
pub(crate) fn unstable(&self) -> &UnstableRootSeed {
&self.0
}
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "unstable")] {
pub fn into_unstable(self) -> UnstableRootSeed {
self.0
}
} else {
pub(crate) fn into_unstable(self) -> UnstableRootSeed {
self.0
}
}
}
}
impl fmt::Debug for RootSeed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.unstable(), f)
}
}
impl FromStr for RootSeed {
type Err = <UnstableRootSeed as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
UnstableRootSeed::from_str(s).map(Self)
}
}
impl TryFrom<&[u8]> for RootSeed {
type Error = anyhow::Error;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
UnstableRootSeed::try_from(bytes).map(Self)
}
}
impl TryFrom<Mnemonic> for RootSeed {
type Error = anyhow::Error;
fn try_from(mnemonic: Mnemonic) -> Result<Self, Self::Error> {
UnstableRootSeed::try_from(mnemonic).map(Self)
}
}
#[derive(Clone)]
pub struct ClientCredentials(UnstableClientCredentials);
impl ClientCredentials {
pub fn from_string(s: &str) -> anyhow::Result<Self> {
Self::from_str(s)
}
pub fn export_string(&self) -> String {
self.unstable().to_base64_blob()
}
pub(crate) fn unstable(&self) -> &UnstableClientCredentials {
&self.0
}
}
impl FromStr for ClientCredentials {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
UnstableClientCredentials::try_from_base64_blob(s).map(Self)
}
}
impl fmt::Debug for ClientCredentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Measurement(UnstableMeasurement);
impl ByteArray<32> for Measurement {
fn from_array(array: [u8; 32]) -> Self {
Self(UnstableMeasurement::from_array(array))
}
fn to_array(&self) -> [u8; 32] {
self.0.to_array()
}
fn as_array(&self) -> &[u8; 32] {
self.0.as_array()
}
}
impl AsRef<[u8]> for Measurement {
fn as_ref(&self) -> &[u8] {
self.0.as_array().as_slice()
}
}
impl AsRef<[u8; 32]> for Measurement {
fn as_ref(&self) -> &[u8; 32] {
self.0.as_array()
}
}
impl fmt::Debug for Measurement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for Measurement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Measurement {
type Err = <UnstableMeasurement as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
UnstableMeasurement::from_str(s).map(Self)
}
}
impl Measurement {
#[cfg(feature = "unstable")]
pub fn unstable(self) -> UnstableMeasurement {
self.0
}
cfg_if::cfg_if! {
if #[cfg(feature = "unstable")] {
pub fn from_unstable(inner: UnstableMeasurement) -> Self {
Self(inner)
}
} else {
pub(crate) fn from_unstable(inner: UnstableMeasurement) -> Self {
Self(inner)
}
}
}
}
#[cfg(feature = "unstable")]
impl From<UnstableMeasurement> for Measurement {
fn from(inner: UnstableMeasurement) -> Self {
Self(inner)
}
}
#[cfg(feature = "unstable")]
impl From<Measurement> for UnstableMeasurement {
fn from(outer: Measurement) -> Self {
outer.0
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct UserPk(UnstableUserPk);
impl ByteArray<32> for UserPk {
fn from_array(array: [u8; 32]) -> Self {
Self(UnstableUserPk::from_array(array))
}
fn to_array(&self) -> [u8; 32] {
self.0.to_array()
}
fn as_array(&self) -> &[u8; 32] {
self.0.as_array()
}
}
impl AsRef<[u8]> for UserPk {
fn as_ref(&self) -> &[u8] {
self.0.as_array().as_slice()
}
}
impl AsRef<[u8; 32]> for UserPk {
fn as_ref(&self) -> &[u8; 32] {
self.0.as_array()
}
}
impl fmt::Debug for UserPk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for UserPk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for UserPk {
type Err = <UnstableUserPk as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
UnstableUserPk::from_str(s).map(Self)
}
}
impl UserPk {
cfg_if::cfg_if! {
if #[cfg(feature = "unstable")] {
pub fn unstable(self) -> UnstableUserPk {
self.0
}
} else {
pub(crate) fn unstable(self) -> UnstableUserPk {
self.0
}
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "unstable")] {
pub fn from_unstable(inner: UnstableUserPk) -> Self {
Self(inner)
}
} else {
pub(crate) fn from_unstable(inner: UnstableUserPk) -> Self {
Self(inner)
}
}
}
}
#[cfg(feature = "unstable")]
impl From<UnstableUserPk> for UserPk {
fn from(inner: UnstableUserPk) -> Self {
Self(inner)
}
}
#[cfg(feature = "unstable")]
impl From<UserPk> for UnstableUserPk {
fn from(outer: UserPk) -> Self {
outer.0
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct NodePk(UnstableNodePk);
impl NodePk {
pub fn from_hex(hex_str: &str) -> anyhow::Result<Self> {
Self::from_str(hex_str).map_err(anyhow::Error::from)
}
pub fn to_hex(&self) -> String {
self.to_string()
}
}
impl fmt::Debug for NodePk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Display for NodePk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for NodePk {
type Err = <UnstableNodePk as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
UnstableNodePk::from_str(s).map(Self)
}
}
impl NodePk {
#[cfg(feature = "unstable")]
pub fn unstable(self) -> UnstableNodePk {
self.0
}
cfg_if::cfg_if! {
if #[cfg(feature = "unstable")] {
pub fn from_unstable(inner: UnstableNodePk) -> Self {
Self(inner)
}
} else {
pub(crate) fn from_unstable(inner: UnstableNodePk) -> Self {
Self(inner)
}
}
}
}
#[cfg(feature = "unstable")]
impl From<UnstableNodePk> for NodePk {
fn from(inner: UnstableNodePk) -> Self {
Self(inner)
}
}
#[cfg(feature = "unstable")]
impl From<NodePk> for UnstableNodePk {
fn from(outer: NodePk) -> Self {
outer.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seedphrase_file_roundtrip() {
let root_seed1 = RootSeed::generate();
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("seedphrase.txt");
root_seed1.write_to_path(&path).unwrap();
let root_seed2 = RootSeed::read_from_path(&path).unwrap().unwrap();
assert_eq!(root_seed1.as_bytes(), root_seed2.as_bytes());
let err = root_seed1.write_to_path(&path).unwrap_err();
assert!(err.to_string().contains("already exists"));
let missing = tempdir.path().join("missing.txt");
assert!(RootSeed::read_from_path(&missing).unwrap().is_none());
}
}