Skip to content

Commit 550b33d

Browse files
committed
btc/policies: cache which keys is ours
Checking which key is ours fetches the xpub, which requires loading the seed. To avoid doing this many times, we pre-process this info. confirm() is moved to be a method on ParsedPolicy so it can reference `self.is_our_key`.
1 parent f6457ab commit 550b33d

File tree

4 files changed

+125
-126
lines changed

4 files changed

+125
-126
lines changed

src/rust/bitbox02-rust/src/hww/api/bitcoin.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,9 @@ async fn address_policy(
249249
let title = "Receive to";
250250

251251
if display {
252-
policies::confirm(title, coin_params, &name, policy, policies::Mode::Basic).await?;
252+
parsed
253+
.confirm(title, coin_params, &name, policies::Mode::Basic)
254+
.await?;
253255
}
254256

255257
let address = common::Payload::from_policy(&parsed, keypath)?.address(coin_params)?;

src/rust/bitbox02-rust/src/hww/api/bitcoin/policies.rs

Lines changed: 110 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ pub enum Descriptor<T: miniscript::MiniscriptKey> {
197197
#[derive(Debug)]
198198
pub struct ParsedPolicy<'a> {
199199
policy: &'a Policy,
200+
// Cached flags for which keys in `policy.keys` are ours.
201+
// is_our_key[i] is true if policy.keys[i] is our key.
202+
is_our_key: Vec<bool>,
200203
// String for pubkeys so we can parse and process the placeholder wallet policy keys like
201204
// `@0/**` etc.
202205
pub descriptor: Descriptor<String>,
@@ -256,17 +259,8 @@ impl<'a> ParsedPolicy<'a> {
256259

257260
self.validate_keys()?;
258261

259-
let our_root_fingerprint = crate::keystore::root_fingerprint()?;
260-
261262
// Check that at least one key is ours.
262-
let has_our_key = 'block: {
263-
for key in policy.keys.iter() {
264-
if is_our_key(key, &our_root_fingerprint)? {
265-
break 'block true;
266-
}
267-
}
268-
false
269-
};
263+
let has_our_key = self.is_our_key.iter().any(|&b| b);
270264
if !has_our_key {
271265
return Err(Error::InvalidInput);
272266
}
@@ -290,6 +284,103 @@ impl<'a> ParsedPolicy<'a> {
290284
Ok(())
291285
}
292286

287+
/// Confirm the policy. In advanced mode, all details are shown. In basic mode, the advanced
288+
/// details are optional. Used to verify the policy during account registration (advanced mode),
289+
/// creating a receive address (basic mode) and signing a transaction (basic mode).
290+
pub async fn confirm(
291+
&self,
292+
title: &str,
293+
params: &Params,
294+
name: &str,
295+
mode: Mode,
296+
) -> Result<(), Error> {
297+
let policy = self.policy;
298+
confirm::confirm(&confirm::Params {
299+
title,
300+
body: &format!("{}\npolicy with\n{} keys", params.name, policy.keys.len(),),
301+
accept_is_nextarrow: true,
302+
..Default::default()
303+
})
304+
.await?;
305+
306+
confirm::confirm(&confirm::Params {
307+
title: "Name",
308+
body: name,
309+
scrollable: true,
310+
accept_is_nextarrow: true,
311+
..Default::default()
312+
})
313+
.await?;
314+
315+
if matches!(mode, Mode::Basic) {
316+
if let Err(confirm::UserAbort) = confirm::confirm(&confirm::Params {
317+
body: "Show policy\ndetails?",
318+
accept_is_nextarrow: true,
319+
..Default::default()
320+
})
321+
.await
322+
{
323+
return Ok(());
324+
}
325+
}
326+
327+
confirm::confirm(&confirm::Params {
328+
title: "Policy",
329+
body: &policy.policy,
330+
scrollable: true,
331+
accept_is_nextarrow: true,
332+
..Default::default()
333+
})
334+
.await?;
335+
336+
let output_xpub_type = match params.coin {
337+
BtcCoin::Btc | BtcCoin::Ltc => bip32::XPubType::Xpub,
338+
BtcCoin::Tbtc | BtcCoin::Tltc => bip32::XPubType::Tpub,
339+
};
340+
let num_keys = policy.keys.len();
341+
for (i, key) in policy.keys.iter().enumerate() {
342+
let key_str = match key {
343+
pb::KeyOriginInfo {
344+
root_fingerprint,
345+
keypath,
346+
xpub: Some(xpub),
347+
} => {
348+
let xpub_str = bip32::Xpub::from(xpub)
349+
.serialize_str(output_xpub_type)
350+
.or(Err(Error::InvalidInput))?;
351+
if root_fingerprint.is_empty() {
352+
xpub_str
353+
} else if root_fingerprint.len() != 4 {
354+
return Err(Error::InvalidInput);
355+
} else {
356+
format!(
357+
"[{}/{}]{}",
358+
hex::encode(root_fingerprint),
359+
util::bip32::to_string_no_prefix(keypath),
360+
xpub_str
361+
)
362+
}
363+
}
364+
_ => return Err(Error::InvalidInput),
365+
};
366+
confirm::confirm(&confirm::Params {
367+
title: &format!("Key {}/{}", i + 1, num_keys),
368+
body: (if self.is_our_key[i] {
369+
format!("This device: {}", key_str)
370+
} else {
371+
key_str
372+
})
373+
.as_str(),
374+
scrollable: true,
375+
longtouch: i == num_keys - 1 && matches!(mode, Mode::Advanced),
376+
accept_is_nextarrow: true,
377+
..Default::default()
378+
})
379+
.await?;
380+
}
381+
Ok(())
382+
}
383+
293384
/// Derive the descriptor of the policy at a receive or change path.
294385
/// This turns key placeholders into actual pubkeys.
295386
/// If is_change is false, the descriptor for the receive address is derived.
@@ -368,6 +459,14 @@ pub fn parse(policy: &Policy, coin: BtcCoin) -> Result<ParsedPolicy, Error> {
368459
}
369460

370461
let desc = policy.policy.as_str();
462+
let our_root_fingerprint = crate::keystore::root_fingerprint()?;
463+
464+
let is_our_key: Vec<bool> = policy
465+
.keys
466+
.iter()
467+
.map(|key| is_our_key(key, &our_root_fingerprint))
468+
.collect::<Result<Vec<bool>, ()>>()?;
469+
371470
let parsed = match desc.as_bytes() {
372471
// Match wsh(...).
373472
[b'w', b's', b'h', b'(', .., b')'] => {
@@ -377,6 +476,7 @@ pub fn parse(policy: &Policy, coin: BtcCoin) -> Result<ParsedPolicy, Error> {
377476

378477
ParsedPolicy {
379478
policy,
479+
is_our_key,
380480
descriptor: Descriptor::Wsh(Wsh { miniscript_expr }),
381481
}
382482
}
@@ -394,104 +494,6 @@ pub enum Mode {
394494
Advanced,
395495
}
396496

397-
/// Confirm the policy. In advanced mode, all details are shown. In basic mode, the advanced details
398-
/// are optional. Used to verify the policy during account registration (advanced mode), creating a
399-
/// receive address (basic mode) and signing a transaction (basic mode).
400-
pub async fn confirm(
401-
title: &str,
402-
params: &Params,
403-
name: &str,
404-
policy: &Policy,
405-
mode: Mode,
406-
) -> Result<(), Error> {
407-
confirm::confirm(&confirm::Params {
408-
title,
409-
body: &format!("{}\npolicy with\n{} keys", params.name, policy.keys.len(),),
410-
accept_is_nextarrow: true,
411-
..Default::default()
412-
})
413-
.await?;
414-
415-
confirm::confirm(&confirm::Params {
416-
title: "Name",
417-
body: name,
418-
scrollable: true,
419-
accept_is_nextarrow: true,
420-
..Default::default()
421-
})
422-
.await?;
423-
424-
if matches!(mode, Mode::Basic) {
425-
if let Err(confirm::UserAbort) = confirm::confirm(&confirm::Params {
426-
body: "Show policy\ndetails?",
427-
accept_is_nextarrow: true,
428-
..Default::default()
429-
})
430-
.await
431-
{
432-
return Ok(());
433-
}
434-
}
435-
436-
confirm::confirm(&confirm::Params {
437-
title: "Policy",
438-
body: &policy.policy,
439-
scrollable: true,
440-
accept_is_nextarrow: true,
441-
..Default::default()
442-
})
443-
.await?;
444-
445-
let our_root_fingerprint = crate::keystore::root_fingerprint()?;
446-
447-
let output_xpub_type = match params.coin {
448-
BtcCoin::Btc | BtcCoin::Ltc => bip32::XPubType::Xpub,
449-
BtcCoin::Tbtc | BtcCoin::Tltc => bip32::XPubType::Tpub,
450-
};
451-
let num_keys = policy.keys.len();
452-
for (i, key) in policy.keys.iter().enumerate() {
453-
let key_str = match key {
454-
pb::KeyOriginInfo {
455-
root_fingerprint,
456-
keypath,
457-
xpub: Some(xpub),
458-
} => {
459-
let xpub_str = bip32::Xpub::from(xpub)
460-
.serialize_str(output_xpub_type)
461-
.or(Err(Error::InvalidInput))?;
462-
if root_fingerprint.is_empty() {
463-
xpub_str
464-
} else if root_fingerprint.len() != 4 {
465-
return Err(Error::InvalidInput);
466-
} else {
467-
format!(
468-
"[{}/{}]{}",
469-
hex::encode(root_fingerprint),
470-
util::bip32::to_string_no_prefix(keypath),
471-
xpub_str
472-
)
473-
}
474-
}
475-
_ => return Err(Error::InvalidInput),
476-
};
477-
confirm::confirm(&confirm::Params {
478-
title: &format!("Key {}/{}", i + 1, num_keys),
479-
body: (if is_our_key(key, &our_root_fingerprint)? {
480-
format!("This device: {}", key_str)
481-
} else {
482-
key_str
483-
})
484-
.as_str(),
485-
scrollable: true,
486-
longtouch: i == num_keys - 1 && matches!(mode, Mode::Advanced),
487-
accept_is_nextarrow: true,
488-
..Default::default()
489-
})
490-
.await?;
491-
}
492-
Ok(())
493-
}
494-
495497
/// Creates a hash of this policy config, useful for registration and identification.
496498
pub fn get_hash(coin: BtcCoin, policy: &Policy) -> Result<Vec<u8>, ()> {
497499
let mut hasher = Sha256::new();

src/rust/bitbox02-rust/src/hww/api/bitcoin/registration.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,10 @@ pub async fn process_register_script_config(
149149
let coin = BtcCoin::try_from(*coin)?;
150150
let coin_params = params::get(coin);
151151
let name = get_name(request).await?;
152-
super::policies::parse(policy, coin)?;
153-
super::policies::confirm(
154-
title,
155-
coin_params,
156-
&name,
157-
policy,
158-
super::policies::Mode::Advanced,
159-
)
160-
.await?;
152+
let parsed = super::policies::parse(policy, coin)?;
153+
parsed
154+
.confirm(title, coin_params, &name, super::policies::Mode::Advanced)
155+
.await?;
161156
let hash = super::policies::get_hash(coin, policy)?;
162157
match bitbox02::memory::multisig_set_by_hash(&hash, &name) {
163158
Ok(()) => {

src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,14 @@ async fn validate_script_configs<'a>(
421421
// the input keypath, and the computation of the pk_script checks that full keypath is
422422
// valid.
423423

424-
super::policies::confirm(
425-
"Spend from",
426-
coin_params,
427-
&name,
428-
policy,
429-
super::policies::Mode::Basic,
430-
)
431-
.await?;
424+
parsed_policy
425+
.confirm(
426+
"Spend from",
427+
coin_params,
428+
&name,
429+
super::policies::Mode::Basic,
430+
)
431+
.await?;
432432

433433
return Ok(vec![ValidatedScriptConfigWithKeypath {
434434
keypath,

0 commit comments

Comments
 (0)