Skip to content

Commit 05b0940

Browse files
committed
feat(hwi): add hwi sign subcommand
- add signing psbt with hardware wallet
1 parent 131587f commit 05b0940

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
@@ -92,7 +92,7 @@ const NUMS_UNSPENDABLE_KEY_HEX: &str =
9292
/// Execute an offline wallet sub-command
9393
///
9494
/// Offline wallet sub-commands are described in [`OfflineWalletSubCommand`].
95-
pub async fn handle_offline_wallet_subcommand(
95+
pub fn handle_offline_wallet_subcommand(
9696
wallet: &mut Wallet,
9797
wallet_opts: &WalletOpts,
9898
cli_opts: &CliOpts,
@@ -586,56 +586,6 @@ pub async fn handle_offline_wallet_subcommand(
586586
&json!({ "psbt": BASE64_STANDARD.encode(final_psbt.serialize()) }),
587587
)?)
588588
}
589-
#[cfg(feature = "hwi")]
590-
Hwi { subcommand } => match subcommand {
591-
HwiSubCommand::Devices => {
592-
let device = crate::utils::connect_to_hardware_wallet(
593-
wallet.network(),
594-
wallet_opts,
595-
Some(wallet),
596-
)
597-
.await?;
598-
let device = if let Some(device) = device {
599-
json!({
600-
"type": device.device_kind().to_string(),
601-
"fingerprint": device.get_master_fingerprint().await?.to_string(),
602-
"model": device.device_kind().to_string(),
603-
})
604-
} else {
605-
json!(null)
606-
};
607-
Ok(json!({ "devices": device }))
608-
}
609-
HwiSubCommand::Register => {
610-
let policy = wallet_opts.ext_descriptor.clone().ok_or_else(|| {
611-
Error::Generic(
612-
"External descriptor required for wallet registration".to_string(),
613-
)
614-
})?;
615-
let wallet_name = wallet_opts.wallet.clone().ok_or_else(|| {
616-
Error::Generic("Wallet name is required for wallet registration".to_string())
617-
})?;
618-
619-
let device = crate::utils::connect_to_hardware_wallet(
620-
wallet.network(),
621-
wallet_opts,
622-
Some(wallet),
623-
)
624-
.await?;
625-
let hmac = if let Some(device) = device {
626-
let hmac = device.register_wallet(&wallet_name, &policy).await?;
627-
hmac.map(|h| h.to_lower_hex_string())
628-
} else {
629-
None
630-
};
631-
//TODO: return status of wallet registration
632-
Ok(json!({ "hmac": hmac }))
633-
}
634-
HwiSubCommand::Address => {
635-
let address = wallet.next_unused_address(KeychainKind::External);
636-
Ok(json!({ "address": address.address }))
637-
}
638-
},
639589
}
640590
}
641591

@@ -1119,6 +1069,138 @@ pub(crate) fn handle_compile_subcommand(
11191069
}
11201070
}
11211071

1072+
/// Handle hardware wallet operations
1073+
#[cfg(feature = "hwi")]
1074+
pub async fn handle_hwi_subcommand(
1075+
network: Network,
1076+
wallet_opts: &WalletOpts,
1077+
subcommand: HwiSubCommand,
1078+
) -> Result<serde_json::Value, Error> {
1079+
match subcommand {
1080+
HwiSubCommand::Devices => {
1081+
let devices = crate::utils::connect_to_hardware_wallet(
1082+
wallet.network(),
1083+
wallet_opts,
1084+
Some(wallet),
1085+
)
1086+
.await?;
1087+
let device = if let Some(device) = device {
1088+
json!({
1089+
"type": device.device_kind().to_string(),
1090+
"fingerprint": device.get_master_fingerprint().await?.to_string(),
1091+
"model": device.device_kind().to_string(),
1092+
})
1093+
} else {
1094+
json!(null)
1095+
};
1096+
Ok(json!({ "devices": device }))
1097+
}
1098+
HwiSubCommand::Register => {
1099+
let policy = wallet_opts.ext_descriptor.clone().ok_or_else(|| {
1100+
Error::Generic("External descriptor required for wallet registration".to_string())
1101+
})?;
1102+
let wallet_name = wallet_opts.wallet.clone().ok_or_else(|| {
1103+
Error::Generic("Wallet name is required for wallet registration".to_string())
1104+
})?;
1105+
1106+
let home_dir = prepare_home_dir(None)?;
1107+
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1108+
#[cfg(feature = "sqlite")]
1109+
let wallet = {
1110+
let mut persister = match &wallet_opts.database_type {
1111+
DatabaseType::Sqlite => {
1112+
let db_file = database_path.join("wallet.sqlite");
1113+
let connection = Connection::open(db_file)?;
1114+
log::debug!("Sqlite database opened successfully");
1115+
connection
1116+
}
1117+
};
1118+
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1119+
wallet.persist(&mut persister)?;
1120+
wallet
1121+
};
1122+
#[cfg(not(feature = "sqlite"))]
1123+
let wallet = new_wallet(network, wallet_opts)?;
1124+
1125+
let device = crate::utils::connect_to_hardware_wallet(
1126+
wallet.network(),
1127+
wallet_opts,
1128+
Some(wallet),
1129+
)
1130+
.await?;
1131+
let hmac = if let Some(device) = device {
1132+
let hmac = device.register_wallet(&wallet_name, &policy).await?;
1133+
hmac.map(|h| h.to_lower_hex_string())
1134+
} else {
1135+
None
1136+
};
1137+
Ok(json!({ "hmac": hmac }))
1138+
}
1139+
HwiSubCommand::Address => {
1140+
let home_dir = prepare_home_dir(None)?;
1141+
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1142+
#[cfg(feature = "sqlite")]
1143+
let wallet = {
1144+
let mut persister = match &wallet_opts.database_type {
1145+
DatabaseType::Sqlite => {
1146+
let db_file = database_path.join("wallet.sqlite");
1147+
let connection = Connection::open(db_file)?;
1148+
log::debug!("Sqlite database opened successfully");
1149+
connection
1150+
}
1151+
};
1152+
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1153+
wallet.persist(&mut persister)?;
1154+
wallet
1155+
};
1156+
#[cfg(not(feature = "sqlite"))]
1157+
let wallet = new_wallet(network, wallet_opts)?;
1158+
1159+
let address = wallet.next_unused_address(KeychainKind::External);
1160+
Ok(json!({ "address": address.address }))
1161+
}
1162+
HwiSubCommand::Sign { psbt } => {
1163+
let home_dir = prepare_home_dir(None)?;
1164+
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1165+
#[cfg(feature = "sqlite")]
1166+
let wallet = {
1167+
let mut persister = match &wallet_opts.database_type {
1168+
DatabaseType::Sqlite => {
1169+
let db_file = database_path.join("wallet.sqlite");
1170+
let connection = Connection::open(db_file)?;
1171+
log::debug!("Sqlite database opened successfully");
1172+
connection
1173+
}
1174+
};
1175+
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1176+
wallet.persist(&mut persister)?;
1177+
wallet
1178+
};
1179+
#[cfg(not(feature = "sqlite"))]
1180+
let wallet = new_wallet(network, wallet_opts)?;
1181+
1182+
let mut psbt = Psbt::from_str(&psbt)
1183+
.map_err(|e| Error::Generic(format!("Failed to parse PSBT: {e}")))?;
1184+
let device = crate::utils::connect_to_hardware_wallet(
1185+
wallet.network(),
1186+
wallet_opts,
1187+
Some(wallet),
1188+
)
1189+
.await?;
1190+
let signed_psbt = if let Some(device) = device {
1191+
device
1192+
.sign_tx(&mut psbt)
1193+
.await
1194+
.map_err(|e| Error::Generic(format!("Failed to sign PSBT: {e}")))?;
1195+
Some(psbt.to_string())
1196+
} else {
1197+
None
1198+
};
1199+
Ok(json!({ "psbt": signed_psbt }))
1200+
}
1201+
}
1202+
}
1203+
11221204
/// The global top level handler.
11231205
pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
11241206
let network = cli_opts.network;
@@ -1222,8 +1304,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
12221304

12231305
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
12241306
let result =
1225-
handle_offline_wallet_subcommand(&mut wallet, wallet_opts, &cli_opts, offline_subcommand.clone())
1226-
.await?;
1307+
handle_offline_wallet_subcommand(&mut wallet, wallet_opts, &cli_opts, offline_subcommand.clone());
12271308
wallet.persist(&mut persister)?;
12281309
result
12291310
};
@@ -1237,7 +1318,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
12371318
offline_subcommand.clone(),
12381319
)?
12391320
};
1240-
Ok(result)
1321+
Ok(result?)
12411322
}
12421323
CliSubCommand::Key {
12431324
subcommand: key_subcommand,
@@ -1366,7 +1447,6 @@ async fn respond(
13661447
subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
13671448
} => {
13681449
let value = handle_offline_wallet_subcommand(wallet, wallet_opts, cli_opts, offline_subcommand)
1369-
.await
13701450
.map_err(|e| e.to_string())?;
13711451
Some(value)
13721452
}

0 commit comments

Comments
 (0)