Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Implement Ledger Support #1513

Draft
wants to merge 19 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
baf0620
Add "ledger-support" feature to `zingolib` crate
pacu Nov 14, 2024
a3dc932
Always build all features in the IDE
pacu Nov 14, 2024
af0f5f6
Add a feature-gated `ledger` field in `WalletCapability`
pacu Nov 14, 2024
a742791
Ledger WalletBase, KeyError, WalletCapability::new_with_ledger
pacu Nov 14, 2024
20af87c
Create `InternalCapability` capability trait
pacu Nov 14, 2024
2b43c21
Implement `InternalCapability` trait for `InMemory` keys.
pacu Nov 19, 2024
1929a09
Move to InternalCapability's `get_ua_from_contained_transparent_recei…
pacu Nov 19, 2024
718a8d6
move WalletCapability `addresses()` to delegate to InternalCapability
pacu Nov 19, 2024
6c23655
Move `transparent_child_addresses` to `InternalWalletCapability`
pacu Nov 19, 2024
15075dd
Move `new_address` to InternalCapability implementation
pacu Nov 19, 2024
fae3e23
Move `generate_transparent_receiver` to `InternalCapability` impl
pacu Nov 19, 2024
90c9e8f
Move `get_taddr_to_secretkey_map` to `InternalCapability` impl
pacu Nov 19, 2024
d732441
Move `get_external_taddrs` to `InternalCapability` implementation
pacu Nov 19, 2024
39255ca
remove `get_trees_witness_trees` function from `InternalCapability`
pacu Nov 19, 2024
3edfed2
Scaffolding for introducing a LedgerCapability
pacu Nov 19, 2024
b173147
fix feature compile errors
pacu Nov 19, 2024
efd36a7
move `can_view` to `InternalCapability` Implementation
pacu Nov 19, 2024
324001a
Add ledger-support feature to ZingoCli interface
pacu Nov 21, 2024
1ea07a1
add "ledger-support" feature to libtonode-tests
pacu Nov 21, 2024
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 .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"rust-analyzer.diagnostics.disabled": [
"unresolved-macro-call"
]
],
"rust-analyzer.cargo.features": "all"
}
1 change: 1 addition & 0 deletions libtonode-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[features]
chain_generic_tests = []
ci = ["zingolib/ci"]
ledger-support = []

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
Expand Down
2 changes: 2 additions & 0 deletions libtonode-tests/tests/concrete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,8 @@ mod slow {
Some(client_builder.zingo_datadir),
ChainType::Regtest(regtest_network),
true,
#[cfg(feature = "ledger-support")]
false,
)
.unwrap();

Expand Down
2 changes: 2 additions & 0 deletions libtonode-tests/tests/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ async fn sync_mainnet_test() {
Some(temp_path),
zingolib::config::ChainType::Mainnet,
true,
#[cfg(feature = "ledger-support")]
false,
)
.unwrap();
let mut lightclient = LightClient::create_from_wallet_base_async(
Expand Down
3 changes: 3 additions & 0 deletions zingocli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ rustyline = { workspace = true }
shellwords = { workspace = true }

rustls.workspace = true

[features]
ledger-support = []
136 changes: 82 additions & 54 deletions zingocli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,60 +20,63 @@ pub mod version;

/// TODO: Add Doc Comment Here!
pub fn build_clap_app() -> clap::ArgMatches {
clap::Command::new("Zingo CLI").version(version::VERSION)
.arg(Arg::new("nosync")
.help("By default, zingo-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.")
.long("nosync")
.short('n')
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("regtest")
.long("regtest")
.help("Regtest mode")
.action(clap::ArgAction::SetTrue) )
.arg(Arg::new("no-clean")
.long("no-clean")
.help("Don't clean regtest state before running. Regtest mode only")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("chain")
.long("chain").short('c')
.help(r#"What chain to expect, if it's not inferable from the server URI. One of "mainnet", "testnet", or "regtest""#))
.arg(Arg::new("seed")
.short('s')
.long("seed")
.value_name("SEED PHRASE")
.value_parser(parse_seed)
.help("Create a new wallet with the given 24-word seed phrase. Will fail if wallet already exists"))
.arg(Arg::new("viewkey")
.long("viewkey")
.value_name("UFVK")
.value_parser(parse_ufvk)
.help("Create a new wallet with the given encoded unified full viewing key. Will fail if wallet already exists"))
.arg(Arg::new("birthday")
.long("birthday")
.value_name("birthday")
.value_parser(clap::value_parser!(u32))
.help("Specify wallet birthday when restoring from seed. This is the earliest block height where the wallet has a transaction."))
.arg(Arg::new("server")
.long("server")
.value_name("server")
.help("Lightwalletd server to connect to.")
.value_parser(parse_uri)
.default_value(zingolib::config::DEFAULT_LIGHTWALLETD_SERVER))
.arg(Arg::new("data-dir")
.long("data-dir")
.value_name("data-dir")
.help("Absolute path to use as data directory"))
.arg(Arg::new("COMMAND")
.help("Command to execute. If a command is not specified, zingo-cli will start in interactive mode.")
.required(false)
.index(1))
.arg(Arg::new("extra_args")
.help("Params to execute command with. Run the 'help' command to get usage help.")
.required(false)
.num_args(1..)
.index(2)
.action(clap::ArgAction::Append)
).get_matches()
let cmd = clap::Command::new("Zingo CLI").version(version::VERSION)
.arg(Arg::new("nosync")
.help("By default, zingo-cli will sync the wallet at startup. Pass --nosync to prevent the automatic sync at startup.")
.long("nosync")
.short('n')
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("regtest")
.long("regtest")
.help("Regtest mode")
.action(clap::ArgAction::SetTrue) )
.arg(Arg::new("no-clean")
.long("no-clean")
.help("Don't clean regtest state before running. Regtest mode only")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("chain")
.long("chain").short('c')
.help(r#"What chain to expect, if it's not inferable from the server URI. One of "mainnet", "testnet", or "regtest""#))
.arg(Arg::new("seed")
.short('s')
.long("seed")
.value_name("SEED PHRASE")
.value_parser(parse_seed)
.help("Create a new wallet with the given 24-word seed phrase. Will fail if wallet already exists"))
.arg(Arg::new("viewkey")
.long("viewkey")
.value_name("UFVK")
.value_parser(parse_ufvk)
.help("Create a new wallet with the given encoded unified full viewing key. Will fail if wallet already exists"))
.arg(Arg::new("birthday")
.long("birthday")
.value_name("birthday")
.value_parser(clap::value_parser!(u32))
.help("Specify wallet birthday when restoring from seed. This is the earliest block height where the wallet has a transaction."))
.arg(Arg::new("server")
.long("server")
.value_name("server")
.help("Lightwalletd server to connect to.")
.value_parser(parse_uri)
.default_value(zingolib::config::DEFAULT_LIGHTWALLETD_SERVER))
.arg(Arg::new("data-dir")
.long("data-dir")
.value_name("data-dir")
.help("Absolute path to use as data directory"))
.arg(Arg::new("COMMAND")
.help("Command to execute. If a command is not specified, zingo-cli will start in interactive mode.")
.required(false)
.index(1))
.arg(Arg::new("extra_args")
.help("Params to execute command with. Run the 'help' command to get usage help.")
.required(false)
.num_args(1..)
.index(2)
.action(clap::ArgAction::Append)
);

add_if_ledger(cmd)
.get_matches()
}

/// Custom function to parse a string into an http::Uri
Expand Down Expand Up @@ -280,6 +283,8 @@ pub struct ConfigTemplate {
#[allow(dead_code)] // This field is defined so that it can be used in Drop::drop
child_process_handler: Option<regtest::ChildProcessHandler>,
chaintype: ChainType,
#[cfg(feature = "ledger-support")]
ledger: bool,
}
use commands::ShortCircuitedCommand;
fn short_circuit_on_help(params: Vec<String>) {
Expand Down Expand Up @@ -354,6 +359,10 @@ If you don't remember the block height, you can pass '--birthday 0' to scan from
};

let clean_regtest_data = !matches.get_flag("no-clean");

#[cfg(feature = "ledger-support")]
let ledger = matches.get_flag("ledger");

let data_dir = if let Some(dir) = matches.get_one::<String>("data-dir") {
PathBuf::from(dir.clone())
} else if is_regtest {
Expand Down Expand Up @@ -412,6 +421,8 @@ If you don't remember the block height, you can pass '--birthday 0' to scan from
regtest_manager,
child_process_handler,
chaintype,
#[cfg(feature = "ledger-support")]
ledger,
})
}
}
Expand Down Expand Up @@ -439,6 +450,8 @@ pub fn startup(
Some(data_dir),
filled_template.chaintype,
true,
#[cfg(feature = "ledger-support")]
filled_template.ledger,
)
.unwrap();
regtest_config_check(&filled_template.regtest_manager, &config.chain);
Expand Down Expand Up @@ -551,3 +564,18 @@ pub fn run_cli() {
Err(e) => eprintln!("Error filling config template: {:?}", e),
}
}


fn add_if_ledger(cmd: clap::Command) -> clap::Command {

#[cfg(feature = "ledger-support")]
return cmd
.arg(Arg::new("ledger")
.long("ledger")
.value_name("ledger")
.help("Create a new wallet by connecting to a ledger")
.action(clap::ArgAction::SetTrue));

#[cfg(not(feature = "ledger-support"))]
return cmd;
}
1 change: 1 addition & 0 deletions zingolib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ tempfile = ["dep:tempfile"]
default = ["sync"]
sync = ['dep:zingo-sync']
zaino-test = ['test-elevation']
ledger-support = []

[dependencies]
zingo-memo = { path = "../zingo-memo" }
Expand Down
21 changes: 21 additions & 0 deletions zingolib/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub fn load_clientconfig(
data_dir: Option<PathBuf>,
chain: ChainType,
monitor_mempool: bool,
#[cfg(feature = "ledger-support")]
ledger: bool,
) -> std::io::Result<ZingoConfig> {
use std::net::ToSocketAddrs;
format!(
Expand All @@ -93,6 +95,8 @@ pub fn load_clientconfig(
wallet_name: DEFAULT_WALLET_NAME.into(),
logfile_name: DEFAULT_LOGFILE_NAME.into(),
accept_server_txids: false,
#[cfg(feature = "ledger-support")]
use_ledger: ledger,
};

Ok(config)
Expand Down Expand Up @@ -138,6 +142,9 @@ pub struct ZingoConfigBuilder {
pub logfile_name: Option<PathBuf>,
/// If this option is enabled, the LightClient will replace outgoing TxId records with the TxId picked by the server. necessary for darkside.
pub accept_server_txids: bool,
/// set to true if you will use a ledger hw wallet
#[cfg(feature = "ledger-support")]
pub use_ledger: bool,
}

/// Configuration data that is necessary? and sufficient? for the creation of a LightClient.
Expand All @@ -159,6 +166,9 @@ pub struct ZingoConfig {
pub logfile_name: PathBuf,
/// If this option is enabled, the LightClient will replace outgoing TxId records with the TxId picked by the server. necessary for darkside.
pub accept_server_txids: bool,
#[cfg(feature = "ledger-support")]
/// if this option is enabled, the LightClient will look for a ledger device to initialize or resume the wallet
pub use_ledger: bool,
}

impl ZingoConfigBuilder {
Expand Down Expand Up @@ -203,6 +213,13 @@ impl ZingoConfigBuilder {
self
}

#[cfg(feature = "ledger-support")]
/// set to true to use a ledger hardware wallet
pub fn set_use_ledger(&mut self, ledger: bool) -> &mut Self {
self.use_ledger = ledger;
self
}

/// TODO: Add Doc Comment Here!
pub fn create(&self) -> ZingoConfig {
let lightwalletd_uri = self.lightwalletd_uri.clone().unwrap_or_default();
Expand All @@ -215,6 +232,8 @@ impl ZingoConfigBuilder {
wallet_name: DEFAULT_WALLET_NAME.into(),
logfile_name: DEFAULT_LOGFILE_NAME.into(),
accept_server_txids: self.accept_server_txids,
#[cfg(feature = "ledger-support")]
use_ledger: false,
}
}
}
Expand All @@ -230,6 +249,8 @@ impl Default for ZingoConfigBuilder {
logfile_name: None,
chain: ChainType::Mainnet,
accept_server_txids: false,
#[cfg(feature = "ledger-support")]
use_ledger: false,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions zingolib/src/testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,8 @@ pub mod scenarios {
Some(conf_path),
crate::config::ChainType::Regtest(regtest_network),
true,
#[cfg(feature = "ledger-support")]
false,
)
.unwrap()
}
Expand Down
15 changes: 15 additions & 0 deletions zingolib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ pub enum WalletBase {
Ufvk(String),
/// Unified spending key
Usk(Vec<u8>),
#[cfg(feature = "ledger-support")]
/// A hardware wallet
Ledger
}

impl WalletBase {
Expand Down Expand Up @@ -378,6 +381,18 @@ impl LightWallet {
)?;
(wc, None)
}
#[cfg(feature = "ledger-support")]
WalletBase::Ledger => {
let wc = WalletCapability::new_with_ledger(&config).map_err(
|e| {
Error::new(
ErrorKind::Other,
format!("Error initilizing Ledger Device: {}", e),
)
},
)?;
(wc, None)
}
};

if let Err(e) = wc.new_address(wc.can_view(), false) {
Expand Down
2 changes: 2 additions & 0 deletions zingolib/src/wallet/disk/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ impl LightWallet {
None,
crate::config::ChainType::Regtest(crate::config::RegtestNetwork::all_upgrades_active()),
true,
#[cfg(feature = "ledger-support")]
false,
)
.unwrap();
Self::read_internal(data, &config)
Expand Down
Loading