Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion clap-utils/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ use {
solana_presigner::Presigner,
solana_pubkey::Pubkey,
solana_remote_wallet::{
errors::RemoteWalletError,
locator::{Locator as RemoteWalletLocator, LocatorError as RemoteWalletLocatorError},
remote_keypair::generate_remote_keypair,
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
remote_wallet::{maybe_wallet_manager, RemoteWalletManager},
},
solana_seed_phrase::generate_seed_from_seed_phrase_and_passphrase,
solana_signature::Signature,
Expand Down
3 changes: 2 additions & 1 deletion clap-v3-utils/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ use {
solana_presigner::Presigner,
solana_pubkey::Pubkey,
solana_remote_wallet::{
errors::RemoteWalletError,
remote_keypair::generate_remote_keypair,
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
remote_wallet::{maybe_wallet_manager, RemoteWalletManager},
},
solana_seed_derivable::SeedDerivable,
solana_seed_phrase::generate_seed_from_seed_phrase_and_passphrase,
Expand Down
148 changes: 141 additions & 7 deletions remote-wallet/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,144 @@
Solana Remote Wallet
===
# Solana Remote Wallet

Library for interacting with "remote" wallets, meaning any wallet where the private key bytes are not directly available,
such as Ledger devices.
A Rust library for interacting with hardware wallets in the Solana ecosystem. This library provides a unified interface for discovering, connecting to, and performing operations with various hardware wallet devices.

## Ledger udev-rules
## Features

In order to use a Ledger device on Linux machines, users must apply certain udev rules. These are available at the
[udev-rules repository](https://github.com/LedgerHQ/udev-rules) maintained by the Ledger team.
- **Multi-Wallet Support**: Currently supports Ledger hardware wallets, with extensible architecture for additional wallet types
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does also support Trezor now!

- **USB HID Communication**: Secure communication with hardware wallets via USB HID protocol
- **Device Discovery**: Automatic detection and connection to connected hardware wallets
- **Transaction Signing**: Sign Solana transactions and messages using hardware wallet private keys
- **Public Key Derivation**: Derive Solana public keys from hardware wallet derivation paths
- **Cross-Platform**: Works on Linux, macOS, and Windows
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure about that? Windows support doesn't always work 😅

- **Thread-Safe**: Concurrent access to wallet manager with proper synchronization

## Supported Hardware Wallets

### Ledger
- **Protocol**: USB HID with APDU commands
- **Features**: Transaction signing, message signing, public key derivation
- **Ledger udev-rules**: In order to use a Ledger device on Linux machines, users must apply certain udev rules. These are available at the [udev-rules repository](https://github.com/LedgerHQ/udev-rules) maintained by the Ledger team.

### Keystone (Planned/In Development)
- Support for Keystone hardware wallets is being added

## Architecture

The library is organized into several key modules:

```
remote-wallet/
├── src/
│ ├── lib.rs # Main library entry point
│ ├── errors.rs # Error types and conversions
│ ├── remote_wallet.rs # Core wallet manager and traits
│ ├── remote_keypair.rs # Remote keypair implementation
│ ├── locator.rs # Device locator and URI parsing
│ ├── wallet/ # Wallet implementations
│ │ ├── mod.rs # Wallet module definitions
│ │ ├── types.rs # Common wallet types
│ │ ├── errors.rs # Wallet-specific errors
│ │ └── ledger/ # Ledger wallet implementation
│ │ ├── mod.rs # Ledger module
│ │ ├── ledger.rs # Ledger wallet logic
│ │ └── error.rs # Ledger-specific errors
│ └── transport/ # Communication layer
│ ├── mod.rs # Transport module
│ ├── transport_trait.rs # Transport trait definition
│ ├── hid_transport.rs # HID transport implementation
│ └── common.rs # Common transport utilities
```
Comment on lines +25 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this since it definitely won't be kept in sync with the code


### Core Components

#### RemoteWalletManager
The main entry point for hardware wallet operations. It manages:
- Device discovery and connection
- Device lifecycle management
- Concurrent access to multiple devices

#### RemoteWallet Trait
Defines the interface that all hardware wallet implementations must provide:
- Device initialization and information reading
- Public key derivation
- Transaction and message signing

#### WalletProbe Trait
Handles device discovery and connection for specific wallet types:
- Device identification
- Connection establishment
- Device validation

#### Transport Layer
Abstracts the communication protocol:
- USB HID transport for Ledger devices
- Extensible for other communication methods
Comment on lines +66 to +75
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove these since they probably won't go in


### Device Locators

Hardware wallets are identified using URI-style locators:
```
usb://ledger
usb://ledger?key=0
```

### Security Considerations

- **Private Keys**: Private keys never leave the hardware wallet
- **User Confirmation**: Critical operations require physical confirmation on the device
- **Secure Communication**: All communication uses encrypted USB HID protocol
- **Input Validation**: All inputs are validated before being sent to the device

## Development

### Building

```bash
# Build the library
cargo build

# Build with specific features
cargo build --features "hidapi,linux-static-hidraw"

# Run tests
cargo test

# Build documentation
cargo doc --open
```

### Adding New Wallet Types

To add support for a new hardware wallet:

1. Create a new module in `src/wallet/`
2. Implement the `RemoteWallet` trait
3. Implement the `WalletProbe` trait
4. Add the wallet type to `RemoteWalletType` enum
5. Register the probe in `create_wallet_probes()`
6. Update error handling and keypair generation
Comment on lines +114 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also update this for a simpler flow

Suggested change
1. Create a new module in `src/wallet/`
2. Implement the `RemoteWallet` trait
3. Implement the `WalletProbe` trait
4. Add the wallet type to `RemoteWalletType` enum
5. Register the probe in `create_wallet_probes()`
6. Update error handling and keypair generation
1. Create a new module in `src/<WALLET_NAME>/`
2. Implement the `RemoteWallet` trait
3. Add the wallet type to `RemoteWalletType` enum
4. Add the device in `RemoteWalletManager::update_devices()`


Example structure for a new wallet:

```rust
// src/wallet/new_wallet/mod.rs
pub mod wallet;
pub mod probe;
pub mod error;

// src/wallet/new_wallet/wallet.rs
pub struct NewWallet {
// Implementation
}

impl RemoteWallet<hidapi::DeviceInfo> for NewWallet {
// Implement required methods
}

// src/wallet/new_wallet/probe.rs
pub struct NewWalletProbe;

impl WalletProbe for NewWalletProbe {
// Implement device discovery
}
```
Comment on lines +121 to +144
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can just remove this too

70 changes: 70 additions & 0 deletions remote-wallet/src/errors.rs
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's call the file error.rs to be consistent with how the code was before, ie ledger_error.rs

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::locator::LocatorError;
use crate::wallet::ledger::error::LedgerError;
use solana_derivation_path::DerivationPathError;
use solana_signer::SignerError;
use thiserror::Error;

/// Remote wallet error.
#[derive(Error, Debug, Clone)]
pub enum RemoteWalletError {
#[error("hidapi error")]
Hid(String),

#[error("device type mismatch")]
DeviceTypeMismatch,

#[error("device with non-supported product ID or vendor ID was detected")]
InvalidDevice,

#[error(transparent)]
DerivationPathError(#[from] DerivationPathError),

#[error("invalid input: {0}")]
InvalidInput(String),

#[error("invalid path: {0}")]
InvalidPath(String),

#[error(transparent)]
LedgerError(#[from] LedgerError),

#[error("no device found")]
NoDeviceFound,

#[error("protocol error: {0}")]
Protocol(&'static str),

#[error("pubkey not found for given address")]
PubkeyNotFound,

#[error("remote wallet operation rejected by the user")]
UserCancel,

#[error(transparent)]
LocatorError(#[from] LocatorError),
}

#[cfg(feature = "hidapi")]
impl From<hidapi::HidError> for RemoteWalletError {
fn from(err: hidapi::HidError) -> RemoteWalletError {
RemoteWalletError::Hid(err.to_string())
}
}

impl From<RemoteWalletError> for SignerError {
fn from(err: RemoteWalletError) -> SignerError {
match err {
RemoteWalletError::Hid(hid_error) => SignerError::Connection(hid_error),
RemoteWalletError::DeviceTypeMismatch => SignerError::Connection(err.to_string()),
RemoteWalletError::InvalidDevice => SignerError::Connection(err.to_string()),
RemoteWalletError::InvalidInput(input) => SignerError::InvalidInput(input),
RemoteWalletError::LedgerError(e) => SignerError::Protocol(e.to_string()),
RemoteWalletError::NoDeviceFound => SignerError::NoDeviceFound,
RemoteWalletError::Protocol(e) => SignerError::Protocol(e.to_string()),
RemoteWalletError::UserCancel => {
SignerError::UserCancel("remote wallet operation rejected by the user".to_string())
}
_ => SignerError::Custom(err.to_string()),
}
}
}
6 changes: 4 additions & 2 deletions remote-wallet/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![allow(clippy::arithmetic_side_effects)]
#![allow(dead_code)]
pub mod ledger;
pub mod ledger_error;

pub mod errors;
pub mod locator;
pub mod remote_keypair;
pub mod remote_wallet;
pub mod transport;
pub mod wallet;
8 changes: 3 additions & 5 deletions remote-wallet/src/remote_keypair.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use {
crate::{
ledger::get_ledger_from_info,
errors::RemoteWalletError,
locator::{Locator, Manufacturer},
remote_wallet::{
RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager,
RemoteWalletType,
},
remote_wallet::{RemoteWallet, RemoteWalletInfo, RemoteWalletManager},
wallet::{ledger::wallet::get_ledger_from_info, types::RemoteWalletType},
},
solana_derivation_path::DerivationPath,
solana_pubkey::Pubkey,
Expand Down
Loading