Skip to content

Commit 0cc925a

Browse files
committed
feat(hwi): add hwi sign subcommand
- add signing psbt with hardware wallet
1 parent 72ea478 commit 0cc925a

File tree

2 files changed

+148
-61
lines changed

2 files changed

+148
-61
lines changed

src/commands.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ pub enum CliSubCommand {
112112
#[command(flatten)]
113113
wallet_opts: WalletOpts,
114114
},
115+
/// Hardware wallet interface operations.
116+
#[cfg(feature = "hwi")]
117+
Hwi {
118+
#[command(flatten)]
119+
wallet_opts: WalletOpts,
120+
#[clap(subcommand)]
121+
subcommand: HwiSubCommand,
122+
},
115123
}
116124

117125
/// Wallet operation subcommands.
@@ -386,12 +394,6 @@ pub enum OfflineWalletSubCommand {
386394
#[arg(env = "BASE64_PSBT", required = true)]
387395
psbt: Vec<String>,
388396
},
389-
#[cfg(feature = "hwi")]
390-
/// Hardware wallet interface operations.
391-
Hwi {
392-
#[clap(subcommand)]
393-
subcommand: HwiSubCommand,
394-
},
395397
}
396398

397399
/// Wallet subcommands that needs a blockchain backend.
@@ -480,6 +482,11 @@ pub enum HwiSubCommand {
480482
Register,
481483
/// Generate address
482484
Address,
485+
/// Sign PSBT with hardware wallet
486+
Sign {
487+
/// The base64-encoded PSBT to sign
488+
psbt: String,
489+
},
483490
}
484491

485492
/// Subcommands available in REPL mode.

src/handlers.rs

Lines changed: 135 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ use {
8787
/// Execute an offline wallet sub-command
8888
///
8989
/// Offline wallet sub-commands are described in [`OfflineWalletSubCommand`].
90-
pub async fn handle_offline_wallet_subcommand(
90+
pub fn handle_offline_wallet_subcommand(
9191
wallet: &mut Wallet,
9292
wallet_opts: &WalletOpts,
9393
cli_opts: &CliOpts,
@@ -581,56 +581,6 @@ pub async fn handle_offline_wallet_subcommand(
581581
&json!({ "psbt": BASE64_STANDARD.encode(final_psbt.serialize()) }),
582582
)?)
583583
}
584-
#[cfg(feature = "hwi")]
585-
Hwi { subcommand } => match subcommand {
586-
HwiSubCommand::Devices => {
587-
let device = crate::utils::connect_to_hardware_wallet(
588-
wallet.network(),
589-
wallet_opts,
590-
Some(wallet),
591-
)
592-
.await?;
593-
let device = if let Some(device) = device {
594-
json!({
595-
"type": device.device_kind().to_string(),
596-
"fingerprint": device.get_master_fingerprint().await?.to_string(),
597-
"model": device.device_kind().to_string(),
598-
})
599-
} else {
600-
json!(null)
601-
};
602-
Ok(json!({ "devices": device }))
603-
}
604-
HwiSubCommand::Register => {
605-
let policy = wallet_opts.ext_descriptor.clone().ok_or_else(|| {
606-
Error::Generic(
607-
"External descriptor required for wallet registration".to_string(),
608-
)
609-
})?;
610-
let wallet_name = wallet_opts.wallet.clone().ok_or_else(|| {
611-
Error::Generic("Wallet name is required for wallet registration".to_string())
612-
})?;
613-
614-
let device = crate::utils::connect_to_hardware_wallet(
615-
wallet.network(),
616-
wallet_opts,
617-
Some(wallet),
618-
)
619-
.await?;
620-
let hmac = if let Some(device) = device {
621-
let hmac = device.register_wallet(&wallet_name, &policy).await?;
622-
hmac.map(|h| h.to_lower_hex_string())
623-
} else {
624-
None
625-
};
626-
//TODO: return status of wallet registration
627-
Ok(json!({ "hmac": hmac }))
628-
}
629-
HwiSubCommand::Address => {
630-
let address = wallet.next_unused_address(KeychainKind::External);
631-
Ok(json!({ "address": address.address }))
632-
}
633-
},
634584
}
635585
}
636586

@@ -1093,6 +1043,138 @@ pub(crate) fn handle_compile_subcommand(
10931043
}
10941044
}
10951045

1046+
/// Handle hardware wallet operations
1047+
#[cfg(feature = "hwi")]
1048+
pub async fn handle_hwi_subcommand(
1049+
network: Network,
1050+
wallet_opts: &WalletOpts,
1051+
subcommand: HwiSubCommand,
1052+
) -> Result<serde_json::Value, Error> {
1053+
match subcommand {
1054+
HwiSubCommand::Devices => {
1055+
let devices = crate::utils::connect_to_hardware_wallet(
1056+
wallet.network(),
1057+
wallet_opts,
1058+
Some(wallet),
1059+
)
1060+
.await?;
1061+
let device = if let Some(device) = device {
1062+
json!({
1063+
"type": device.device_kind().to_string(),
1064+
"fingerprint": device.get_master_fingerprint().await?.to_string(),
1065+
"model": device.device_kind().to_string(),
1066+
})
1067+
} else {
1068+
json!(null)
1069+
};
1070+
Ok(json!({ "devices": device }))
1071+
}
1072+
HwiSubCommand::Register => {
1073+
let policy = wallet_opts.ext_descriptor.clone().ok_or_else(|| {
1074+
Error::Generic("External descriptor required for wallet registration".to_string())
1075+
})?;
1076+
let wallet_name = wallet_opts.wallet.clone().ok_or_else(|| {
1077+
Error::Generic("Wallet name is required for wallet registration".to_string())
1078+
})?;
1079+
1080+
let home_dir = prepare_home_dir(None)?;
1081+
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1082+
#[cfg(feature = "sqlite")]
1083+
let wallet = {
1084+
let mut persister = match &wallet_opts.database_type {
1085+
DatabaseType::Sqlite => {
1086+
let db_file = database_path.join("wallet.sqlite");
1087+
let connection = Connection::open(db_file)?;
1088+
log::debug!("Sqlite database opened successfully");
1089+
connection
1090+
}
1091+
};
1092+
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1093+
wallet.persist(&mut persister)?;
1094+
wallet
1095+
};
1096+
#[cfg(not(feature = "sqlite"))]
1097+
let wallet = new_wallet(network, wallet_opts)?;
1098+
1099+
let device = crate::utils::connect_to_hardware_wallet(
1100+
wallet.network(),
1101+
wallet_opts,
1102+
Some(wallet),
1103+
)
1104+
.await?;
1105+
let hmac = if let Some(device) = device {
1106+
let hmac = device.register_wallet(&wallet_name, &policy).await?;
1107+
hmac.map(|h| h.to_lower_hex_string())
1108+
} else {
1109+
None
1110+
};
1111+
Ok(json!({ "hmac": hmac }))
1112+
}
1113+
HwiSubCommand::Address => {
1114+
let home_dir = prepare_home_dir(None)?;
1115+
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1116+
#[cfg(feature = "sqlite")]
1117+
let wallet = {
1118+
let mut persister = match &wallet_opts.database_type {
1119+
DatabaseType::Sqlite => {
1120+
let db_file = database_path.join("wallet.sqlite");
1121+
let connection = Connection::open(db_file)?;
1122+
log::debug!("Sqlite database opened successfully");
1123+
connection
1124+
}
1125+
};
1126+
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1127+
wallet.persist(&mut persister)?;
1128+
wallet
1129+
};
1130+
#[cfg(not(feature = "sqlite"))]
1131+
let wallet = new_wallet(network, wallet_opts)?;
1132+
1133+
let address = wallet.next_unused_address(KeychainKind::External);
1134+
Ok(json!({ "address": address.address }))
1135+
}
1136+
HwiSubCommand::Sign { psbt } => {
1137+
let home_dir = prepare_home_dir(None)?;
1138+
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1139+
#[cfg(feature = "sqlite")]
1140+
let wallet = {
1141+
let mut persister = match &wallet_opts.database_type {
1142+
DatabaseType::Sqlite => {
1143+
let db_file = database_path.join("wallet.sqlite");
1144+
let connection = Connection::open(db_file)?;
1145+
log::debug!("Sqlite database opened successfully");
1146+
connection
1147+
}
1148+
};
1149+
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1150+
wallet.persist(&mut persister)?;
1151+
wallet
1152+
};
1153+
#[cfg(not(feature = "sqlite"))]
1154+
let wallet = new_wallet(network, wallet_opts)?;
1155+
1156+
let mut psbt = Psbt::from_str(&psbt)
1157+
.map_err(|e| Error::Generic(format!("Failed to parse PSBT: {e}")))?;
1158+
let device = crate::utils::connect_to_hardware_wallet(
1159+
wallet.network(),
1160+
wallet_opts,
1161+
Some(wallet),
1162+
)
1163+
.await?;
1164+
let signed_psbt = if let Some(device) = device {
1165+
device
1166+
.sign_tx(&mut psbt)
1167+
.await
1168+
.map_err(|e| Error::Generic(format!("Failed to sign PSBT: {e}")))?;
1169+
Some(psbt.to_string())
1170+
} else {
1171+
None
1172+
};
1173+
Ok(json!({ "psbt": signed_psbt }))
1174+
}
1175+
}
1176+
}
1177+
10961178
/// The global top level handler.
10971179
pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
10981180
let network = cli_opts.network;
@@ -1196,8 +1278,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
11961278

11971279
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
11981280
let result =
1199-
handle_offline_wallet_subcommand(&mut wallet, wallet_opts, &cli_opts, offline_subcommand.clone())
1200-
.await?;
1281+
handle_offline_wallet_subcommand(&mut wallet, wallet_opts, &cli_opts, offline_subcommand.clone());
12011282
wallet.persist(&mut persister)?;
12021283
result
12031284
};
@@ -1211,7 +1292,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
12111292
offline_subcommand.clone(),
12121293
)?
12131294
};
1214-
Ok(result)
1295+
Ok(result?)
12151296
}
12161297
CliSubCommand::Key {
12171298
subcommand: key_subcommand,
@@ -1340,7 +1421,6 @@ async fn respond(
13401421
subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
13411422
} => {
13421423
let value = handle_offline_wallet_subcommand(wallet, wallet_opts, cli_opts, offline_subcommand)
1343-
.await
13441424
.map_err(|e| e.to_string())?;
13451425
Some(value)
13461426
}

0 commit comments

Comments
 (0)