Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nym-vpn-app/src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub enum ErrorKey {
MixnetNoBandwidth,
// Some specific account management errors
AccountInvalidMnemonic,
AccountInvalidSecret,
NoAccountStored,
NoDeviceStored,
ExistingAccount,
Expand Down
5 changes: 5 additions & 0 deletions nym-vpn-app/src-tauri/src/vpnd/account_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ impl From<lib::AccountCommandError> for BackendError {
ErrorKey::AccountInvalidMnemonic,
format!("invalid mnemonic: {e}"),
),
lib::AccountCommandError::InvalidSecret(e) => BackendError::with_detail(
"invalid secret",
ErrorKey::AccountInvalidSecret,
format!("invalid secret: {e}"),
),
lib::AccountCommandError::NyxdConnectionFailure(e) => {
BackendError::internal_with_detail("failed to connect to nyxd", e)
}
Expand Down
6 changes: 4 additions & 2 deletions nym-vpn-app/src-tauri/src/vpnd/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use super::{

use anyhow::Result;
use lib::UserAgent;
use nym_vpn_lib_types as lib;
use nym_vpn_lib_types::{self as lib};
use nym_vpn_proto::rpc_client::RpcClient;
use once_cell::sync::Lazy;
use std::{
Expand Down Expand Up @@ -390,7 +390,9 @@ impl VpndClient {
let mut vpnd = self.vpnd().await?;

let response = vpnd
.store_account(lib::StoreAccountRequest::Vpn { mnemonic })
.store_account(lib::StoreAccountRequest::Vpn {
secret: lib::LoginSecret::Mnemonic(mnemonic),
})
.await
.map_err(VpndError::RpcClient)
.inspect_err(|e| {
Expand Down
1 change: 1 addition & 0 deletions nym-vpn-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add custom DNS setting for mobile platforms (https://github.com/nymtech/nym-vpn-client/pull/4106)
- Login with signature string in addition to mnemonic (https://github.com/nymtech/nym-vpn-client/pull/4117)

### Fixed

Expand Down
24 changes: 24 additions & 0 deletions nym-vpn-core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion nym-vpn-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ members = [
"crates/nym-macos",
"crates/nym-offline-monitor",
"crates/nym-platform-metadata",
"crates/nym-privy",
"crates/nym-utils",
"crates/nym-routing",
"crates/nym-setup",
"crates/nym-statistics",
Expand Down Expand Up @@ -189,9 +191,12 @@ nym-gateway-directory = { path = "crates/nym-gateway-directory" }
nym-ipc = { path = "crates/nym-ipc" }
nym-macos = { path = "crates/nym-macos" }
nym-offline-monitor = { path = "crates/nym-offline-monitor" }
nym-platform-metadata = { path = "crates/nym-platform-metadata" }
nym-privy = { path = "crates/nym-privy" }
nym-routing = { path = "crates/nym-routing" }
nym-statistics = { path = "crates/nym-statistics" }
nym-statistics-api-client = { path = "crates/nym-statistics-api-client" }
nym-utils = { path = "crates/nym-utils" }
nym-vpn-account-controller = { path = "crates/nym-vpn-account-controller" }
nym-vpn-api-client = { path = "crates/nym-vpn-api-client" }
nym-vpn-lib = { path = "crates/nym-vpn-lib" }
Expand All @@ -202,7 +207,6 @@ nym-vpn-store = { path = "crates/nym-vpn-store" }
nym-wg-go = { path = "crates/nym-wg-go" }
nym-wg-metadata-client = { path = "crates/nym-wg-metadata-client" }
nym-windows = { path = "crates/nym-windows" }
nym-platform-metadata = { path = "crates/nym-platform-metadata" }

# For normal development
nym-authenticator-client = { git = "https://github.com/nymtech/nym", branch = "develop" }
Expand Down
19 changes: 19 additions & 0 deletions nym-vpn-core/crates/nym-privy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "nym-privy"
version.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true

[lints]
workspace = true

[dependencies]
bip39.workspace = true
hex.workspace = true
sha2.workspace = true
thiserror.workspace = true

11 changes: 11 additions & 0 deletions nym-vpn-core/crates/nym-privy/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

#[derive(thiserror::Error, Debug, Clone, PartialEq)]
pub enum PrivyError {
#[error(transparent)]
Hex(#[from] hex::FromHexError),

#[error(transparent)]
Bip39(#[from] bip39::Error),
}
38 changes: 38 additions & 0 deletions nym-vpn-core/crates/nym-privy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use bip39::Mnemonic;
use sha2::{Digest, Sha256};

use crate::error::PrivyError;

pub mod error;

pub const PRIVY_DERIVATION_MESSAGE: &str = "DeriveAccount:NymVPN";

pub fn message_to_sign() -> String {
hex::encode(PRIVY_DERIVATION_MESSAGE.as_bytes())
}

pub fn hex_signature_to_mnemonic(hex_signature: &str) -> Result<Mnemonic, PrivyError> {
let bytes_signature = hex::decode(hex_signature)?;

let mut hasher = Sha256::new();
hasher.update(&bytes_signature);
let hashed_signature = hasher.finalize();

let mnemonic = Mnemonic::from_entropy(&hashed_signature)?;

Ok(mnemonic)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_hex_signature() {
assert!(hex_signature_to_mnemonic("deadbeef").is_ok());
assert!(hex_signature_to_mnemonic("invalidhex").is_err());
}
}
22 changes: 22 additions & 0 deletions nym-vpn-core/crates/nym-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "nym-utils"
version.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true

[lints]
workspace = true

[dependencies]
bip39.workspace = true
thiserror.workspace = true

nym-privy.workspace = true
nym-vpn-lib-types.workspace = true

[dev-dependencies]
bip39 = { workspace = true, features = ["rand"] }
8 changes: 8 additions & 0 deletions nym-vpn-core/crates/nym-utils/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
pub enum UtilsError {
#[error(transparent)]
Bip39(#[from] bip39::Error),

#[error(transparent)]
Privy(#[from] nym_privy::error::PrivyError),
}
42 changes: 42 additions & 0 deletions nym-vpn-core/crates/nym-utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use bip39::Mnemonic;
use nym_vpn_lib_types::LoginSecret;

use crate::error::UtilsError;

pub mod error;

pub fn parse_secret(secret: &LoginSecret) -> Result<Mnemonic, UtilsError> {
let mnemonic = match secret {
LoginSecret::Mnemonic(mnemonic) => Mnemonic::parse(mnemonic)?,
LoginSecret::PrivyHexSignature(signature) => {
nym_privy::hex_signature_to_mnemonic(signature)?
}
};

Ok(mnemonic)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse_mnemonic() {
let mnemonic = Mnemonic::generate(24).unwrap();
let parsed_mnemonic = parse_secret(&LoginSecret::Mnemonic(mnemonic.to_string())).unwrap();
assert_eq!(mnemonic, parsed_mnemonic);

assert!(parse_secret(&LoginSecret::Mnemonic(String::from("invalid mnemonic"))).is_err());
}

#[test]
fn parse_hex_signature() {
let hex_signature = String::from(
"a564a87ccbed5cb5be4929201e555f5b5e26cb01d300d621520d724e57c582c33fa374caf21fd0c5e3118d70d14894845a32acfee47da7f347a0b9a57cba07931c",
);
assert!(parse_secret(&LoginSecret::PrivyHexSignature(hex_signature)).is_ok());
}
}
3 changes: 3 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub enum AccountCommandError {

#[error("invalid mnemonic: {0}")]
InvalidMnemonic(String),

#[error("invalid secret: {0}")]
InvalidSecret(String),
}

impl AccountCommandError {
Expand Down
4 changes: 3 additions & 1 deletion nym-vpn-core/crates/nym-vpn-lib-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod gateway;
mod log_path;
mod network;
mod network_stats;
mod privy;
mod rpc_requests;
mod service;
mod socks5;
Expand Down Expand Up @@ -74,9 +75,10 @@ pub use network::{
SystemConfiguration, SystemMessage, ValidatorDetails,
};
pub use network_stats::{NetworkStatisticsConfig, NetworkStatisticsIdentity};
pub use privy::PrivyDerivationMessage;
pub use rpc_requests::{
AccountBalanceResponse, AccountCommandResponse, Coin, DecentralisedObtainTicketbooksRequest,
ListGatewaysOptions, StoreAccountRequest,
ListGatewaysOptions, LoginSecret, StoreAccountRequest,
};
pub use service::{TargetState, VpnServiceConfig, VpnServiceInfo};
pub use socks5::{HttpRpcSettings, Socks5Settings, Socks5State, Socks5Status};
Expand Down
16 changes: 16 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib-types/src/privy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2025 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

#[derive(Clone, Debug)]
#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Record))]
#[cfg_attr(
feature = "typescript-bindings",
derive(TS),
ts(export),
ts(export_to = "bindings.ts")
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "typescript-bindings", serde(rename_all = "camelCase"))]
pub struct PrivyDerivationMessage {
pub message: String,
}
19 changes: 17 additions & 2 deletions nym-vpn-core/crates/nym-vpn-lib-types/src/rpc_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@ pub struct ListGatewaysOptions {
pub user_agent: Option<UserAgent>,
}

#[derive(zeroize::Zeroize)]
#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Enum))]
#[cfg_attr(
feature = "typescript-bindings",
derive(TS),
ts(export),
ts(export_to = "bindings.ts")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "typescript-bindings", serde(rename_all = "camelCase"))]
pub enum LoginSecret {
Mnemonic(String),
PrivyHexSignature(String),
}

#[derive(zeroize::Zeroize)]
#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Enum))]
pub enum StoreAccountRequest {
Vpn { mnemonic: String },
Decentralised { mnemonic: String },
Vpn { secret: LoginSecret },
Decentralised { secret: LoginSecret },
}

impl std::fmt::Debug for StoreAccountRequest {
Expand Down
2 changes: 2 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ nym-sdk.workspace = true
nym-statistics.workspace = true
nym-statistics-api-client.workspace = true
nym-offline-monitor.workspace = true
nym-privy.workspace = true
nym-vpn-lib.workspace = true
nym-vpn-lib-types = { workspace = true, features = [
"uniffi-bindings",
Expand All @@ -55,6 +56,7 @@ nym-http-api-client.workspace = true
nym-common.workspace = true
nym-gateway-directory.workspace = true
nym-platform-metadata.workspace = true
nym-utils.workspace = true

[target.'cfg(target_os = "macos")'.dependencies]
nym-vpn-proto = { workspace = true, features = ["rpc_client"] }
Expand Down
Loading
Loading