Skip to content

Commit 7c2c8a9

Browse files
committed
btc/policies: validate automatically as part of parsing
validate() should be called always after parse anyway, so we just make it one function so a caller cannot fail to validate.
1 parent 1e3053e commit 7c2c8a9

File tree

4 files changed

+103
-95
lines changed

4 files changed

+103
-95
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,7 @@ async fn address_policy(
239239
keypath::validate_address_policy(keypath, keypath::ReceiveSpend::Receive)
240240
.or(Err(Error::InvalidInput))?;
241241

242-
let parsed = policies::parse(policy)?;
243-
parsed.validate(coin)?;
242+
let parsed = policies::parse(policy, coin)?;
244243

245244
let name = match policies::get_name(coin, policy)? {
246245
Some(name) => name,

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

Lines changed: 100 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ impl<'a> ParsedPolicy<'a> {
251251
/// - At least one of the keys is ours
252252
/// - There are no duplicate or missing xpubs
253253
/// - No duplicate keys in the policy
254-
pub fn validate(&self, coin: BtcCoin) -> Result<(), Error> {
254+
fn validate(&self, coin: BtcCoin) -> Result<(), Error> {
255255
check_enabled(coin)?;
256256

257257
let policy = self.policy;
@@ -367,22 +367,24 @@ impl<'a> ParsedPolicy<'a> {
367367
///
368368
/// The parsed output keeps the key strings as is (e.g. "@0/**"). They will be processed and
369369
/// replaced with actual pubkeys in a later step.
370-
pub fn parse(policy: &Policy) -> Result<ParsedPolicy, Error> {
370+
pub fn parse(policy: &Policy, coin: BtcCoin) -> Result<ParsedPolicy, Error> {
371371
let desc = policy.policy.as_str();
372-
match desc.as_bytes() {
372+
let parsed = match desc.as_bytes() {
373373
// Match wsh(...).
374374
[b'w', b's', b'h', b'(', .., b')'] => {
375375
let miniscript_expr: miniscript::Miniscript<String, miniscript::Segwitv0> =
376376
miniscript::Miniscript::from_str(&desc[4..desc.len() - 1])
377377
.or(Err(Error::InvalidInput))?;
378378

379-
Ok(ParsedPolicy {
379+
ParsedPolicy {
380380
policy,
381381
descriptor: Descriptor::Wsh(Wsh { miniscript_expr }),
382-
})
382+
}
383383
}
384-
_ => Err(Error::InvalidInput),
385-
}
384+
_ => return Err(Error::InvalidInput),
385+
};
386+
parsed.validate(coin)?;
387+
Ok(parsed)
386388
}
387389

388390
/// Confirmation mode.
@@ -605,9 +607,11 @@ mod tests {
605607

606608
#[test]
607609
fn test_parse_wsh_miniscript() {
610+
let coin = BtcCoin::Tbtc;
611+
let our_key = make_our_key(KEYPATH_ACCOUNT);
608612
// Parse a valid example and check that the keys are collected as is as strings.
609-
let policy = make_policy("wsh(pk(@0/**))", &[]);
610-
match &parse(&policy).unwrap().descriptor {
613+
let policy = make_policy("wsh(pk(@0/**))", &[our_key.clone()]);
614+
match &parse(&policy, coin).unwrap().descriptor {
611615
Descriptor::Wsh(Wsh {
612616
miniscript_expr, ..
613617
}) => {
@@ -619,8 +623,11 @@ mod tests {
619623
}
620624

621625
// Parse another valid example and check that the keys are collected as is as strings.
622-
let policy = make_policy("wsh(or_b(pk(@0/**),s:pk(@1/**)))", &[]);
623-
match &parse(&policy).unwrap().descriptor {
626+
let policy = make_policy(
627+
"wsh(or_b(pk(@0/**),s:pk(@1/**)))",
628+
&[our_key.clone(), make_key(SOME_XPUB_1)],
629+
);
630+
match &parse(&policy, coin).unwrap().descriptor {
624631
Descriptor::Wsh(Wsh {
625632
miniscript_expr, ..
626633
}) => {
@@ -633,125 +640,127 @@ mod tests {
633640

634641
// Unknown top-level fragment.
635642
assert_eq!(
636-
parse(&make_policy("unknown(pk(@0/**))", &[])).unwrap_err(),
643+
parse(&make_policy("unknown(pk(@0/**))", &[our_key.clone()]), coin).unwrap_err(),
637644
Error::InvalidInput,
638645
);
639646

640647
// Unknown script fragment.
641648
assert_eq!(
642-
parse(&make_policy("wsh(unknown(@0/**))", &[])).unwrap_err(),
649+
parse(
650+
&make_policy("wsh(unknown(@0/**))", &[our_key.clone()]),
651+
coin
652+
)
653+
.unwrap_err(),
643654
Error::InvalidInput,
644655
);
645656

646657
// Miniscript type-check fails (should be `or_b(pk(@0/**),s:pk(@1/**))`).
647658
assert_eq!(
648-
parse(&make_policy("wsh(or_b(pk(@0/**),pk(@1/**)))", &[])).unwrap_err(),
659+
parse(
660+
&make_policy(
661+
"wsh(or_b(pk(@0/**),pk(@1/**)))",
662+
&[our_key.clone(), make_key(SOME_XPUB_1)]
663+
),
664+
coin
665+
)
666+
.unwrap_err(),
649667
Error::InvalidInput,
650668
);
651669
}
652670

653671
#[test]
654-
fn test_parse_validate() {
672+
fn test_parse() {
655673
mock_unlocked();
656674

657675
let our_key = make_our_key(KEYPATH_ACCOUNT);
676+
let coin = BtcCoin::Tbtc;
658677

659678
// All good.
660-
assert!(parse(&make_policy("wsh(pk(@0/**))", &[our_key.clone()]))
661-
.unwrap()
662-
.validate(BtcCoin::Tbtc)
663-
.is_ok());
679+
assert!(parse(&make_policy("wsh(pk(@0/**))", &[our_key.clone()]), coin).is_ok());
664680

665681
// Unsupported coins
666682
for coin in [BtcCoin::Ltc, BtcCoin::Tltc] {
667-
assert_eq!(
668-
parse(&make_policy("wsh(pk(@0/**))", &[our_key.clone()]))
669-
.unwrap()
670-
.validate(coin),
683+
assert!(matches!(
684+
parse(&make_policy("wsh(pk(@0/**))", &[our_key.clone()]), coin),
671685
Err(Error::InvalidInput)
672-
);
686+
));
673687
}
674688

675689
// Too many keys.
676690
let many_keys: Vec<pb::KeyOriginInfo> = (0..=20)
677691
.map(|i| make_our_key(&[48 + HARDENED, 1 + HARDENED, i + HARDENED, 3 + HARDENED]))
678692
.collect();
679-
assert_eq!(
680-
parse(&make_policy("wsh(pk(@0/**))", &many_keys))
681-
.unwrap()
682-
.validate(BtcCoin::Tbtc),
693+
assert!(matches!(
694+
parse(&make_policy("wsh(pk(@0/**))", &many_keys), coin),
683695
Err(Error::InvalidInput)
684-
);
696+
));
685697

686698
// Our key is not present - fingerprint missing.
687-
assert_eq!(
688-
parse(&make_policy("wsh(pk(@0/**))", &[make_key(SOME_XPUB_1)]))
689-
.unwrap()
690-
.validate(BtcCoin::Tbtc),
699+
assert!(matches!(
700+
parse(
701+
&make_policy("wsh(pk(@0/**))", &[make_key(SOME_XPUB_1)]),
702+
coin
703+
),
691704
Err(Error::InvalidInput)
692-
);
705+
));
693706

694707
// Our key is not present - fingerprint and keypath exit but xpub does not match.
695708
let mut wrong_key = our_key.clone();
696709
wrong_key.xpub = Some(parse_xpub(SOME_XPUB_1).unwrap());
697-
assert_eq!(
698-
parse(&make_policy("wsh(pk(@0/**))", &[wrong_key]))
699-
.unwrap()
700-
.validate(BtcCoin::Tbtc),
710+
assert!(matches!(
711+
parse(&make_policy("wsh(pk(@0/**))", &[wrong_key]), coin),
701712
Err(Error::InvalidInput)
702-
);
713+
));
703714

704715
// Contains duplicate keys.
705-
assert_eq!(
706-
parse(&make_policy(
707-
"wsh(multi(2,@0/**,@1/**,@2/**))",
708-
&[
709-
make_key(SOME_XPUB_1),
710-
our_key.clone(),
711-
make_key(SOME_XPUB_1)
712-
]
713-
))
714-
.unwrap()
715-
.validate(BtcCoin::Tbtc),
716+
assert!(matches!(
717+
parse(
718+
&make_policy(
719+
"wsh(multi(2,@0/**,@1/**,@2/**))",
720+
&[
721+
make_key(SOME_XPUB_1),
722+
our_key.clone(),
723+
make_key(SOME_XPUB_1)
724+
]
725+
),
726+
coin
727+
),
716728
Err(Error::InvalidInput)
717-
);
729+
));
718730

719731
// Contains a key with missing xpub.
720-
assert_eq!(
721-
parse(&make_policy(
722-
"wsh(multi(2,@0/**,@1/**))",
723-
&[
724-
our_key.clone(),
725-
pb::KeyOriginInfo {
726-
root_fingerprint: vec![],
727-
keypath: vec![],
728-
xpub: None // missing
729-
}
730-
]
731-
))
732-
.unwrap()
733-
.validate(BtcCoin::Tbtc),
732+
assert!(matches!(
733+
parse(
734+
&make_policy(
735+
"wsh(multi(2,@0/**,@1/**))",
736+
&[
737+
our_key.clone(),
738+
pb::KeyOriginInfo {
739+
root_fingerprint: vec![],
740+
keypath: vec![],
741+
xpub: None // missing
742+
}
743+
]
744+
),
745+
coin
746+
),
734747
Err(Error::InvalidInput)
735-
);
748+
));
736749

737750
// Not all keys are used.
738-
assert_eq!(
739-
parse(&make_policy(
740-
"wsh(pk(@0/**))",
741-
&[our_key.clone(), make_key(SOME_XPUB_1)]
742-
))
743-
.unwrap()
744-
.validate(BtcCoin::Tbtc),
751+
assert!(matches!(
752+
parse(
753+
&make_policy("wsh(pk(@0/**))", &[our_key.clone(), make_key(SOME_XPUB_1)]),
754+
coin
755+
),
745756
Err(Error::InvalidInput)
746-
);
757+
));
747758

748759
// Referenced key does not exist
749-
assert_eq!(
750-
parse(&make_policy("wsh(pk(@1/**))", &[our_key.clone()]))
751-
.unwrap()
752-
.validate(BtcCoin::Tbtc),
760+
assert!(matches!(
761+
parse(&make_policy("wsh(pk(@1/**))", &[our_key.clone()]), coin),
753762
Err(Error::InvalidInput)
754-
);
763+
));
755764
}
756765

757766
#[test]
@@ -763,21 +772,21 @@ mod tests {
763772

764773
// Ok, one key.
765774
let pol = make_policy("wsh(pk(@0/**))", &[our_key.clone()]);
766-
assert!(parse(&pol).unwrap().validate(coin).is_ok());
775+
assert!(parse(&pol, coin).is_ok());
767776

768777
// Ok, two keys.
769778
let pol = make_policy(
770779
"wsh(or_b(pk(@0/**),s:pk(@1/**)))",
771780
&[our_key.clone(), make_key(SOME_XPUB_1)],
772781
);
773-
assert!(parse(&pol).unwrap().validate(coin).is_ok());
782+
assert!(parse(&pol, coin).is_ok());
774783

775784
// Ok, one key with different derivations
776785
let pol = make_policy(
777786
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<2;3>/*)))",
778787
&[our_key.clone()],
779788
);
780-
assert!(parse(&pol).unwrap().validate(coin).is_ok());
789+
assert!(parse(&pol, coin).is_ok());
781790

782791
// Duplicate path, one time in change, one time in receive. While the keys technically are
783792
// never duplicate in the final miniscript with the pubkeys inserted, we still prohibit it,
@@ -787,33 +796,33 @@ mod tests {
787796
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<1;2>/*)))",
788797
&[our_key.clone()],
789798
);
790-
assert!(parse(&pol).unwrap().validate(coin).is_err());
799+
assert!(parse(&pol, coin).is_err());
791800

792801
// Duplicate key inside policy.
793802
let pol = make_policy("wsh(or_b(pk(@0/**),s:pk(@0/**)))", &[our_key.clone()]);
794-
assert!(parse(&pol).is_err());
803+
assert!(parse(&pol, coin).is_err());
795804

796805
// Duplicate key inside policy (same change and receive).
797806
let pol = make_policy("wsh(pk(@0/<0;0>/*))", &[our_key.clone()]);
798-
assert!(parse(&pol).unwrap().validate(coin).is_err());
807+
assert!(parse(&pol, coin).is_err());
799808

800809
// Duplicate key inside policy, using different notations for the same thing.
801810
let pol = make_policy("wsh(or_b(pk(@0/**),s:pk(@0/<0;1>/*)))", &[our_key.clone()]);
802-
assert!(parse(&pol).unwrap().validate(coin).is_err());
811+
assert!(parse(&pol, coin).is_err());
803812

804813
// Duplicate key inside policy, using same receive but different change.
805814
let pol = make_policy(
806815
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<0;2>/*)))",
807816
&[our_key.clone()],
808817
);
809-
assert!(parse(&pol).unwrap().validate(coin).is_err());
818+
assert!(parse(&pol, coin).is_err());
810819

811820
// Duplicate key inside policy, using same change but different receive.
812821
let pol = make_policy(
813822
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<2;1>/*)))",
814823
&[our_key.clone()],
815824
);
816-
assert!(parse(&pol).unwrap().validate(coin).is_err());
825+
assert!(parse(&pol, coin).is_err());
817826
}
818827

819828
#[test]
@@ -922,9 +931,10 @@ mod tests {
922931
let some_key = make_key(SOME_XPUB_1);
923932
let some_xpub = bip32::Xpub::from(some_key.xpub.as_ref().unwrap());
924933
let address_index = 5;
934+
let coin = BtcCoin::Tbtc;
925935

926936
let witness_script = |pol: &str, keys: &[pb::KeyOriginInfo], is_change: bool| {
927-
let derived = parse(&make_policy(pol, keys))
937+
let derived = parse(&make_policy(pol, keys), coin)
928938
.unwrap()
929939
.derive(is_change, address_index)
930940
.unwrap();
@@ -933,7 +943,7 @@ mod tests {
933943
}
934944
};
935945
let witness_script_at_keypath = |pol: &str, keys: &[pb::KeyOriginInfo], keypath: &[u32]| {
936-
let derived = parse(&make_policy(pol, keys))
946+
let derived = parse(&make_policy(pol, keys), coin)
937947
.unwrap()
938948
.derive_at_keypath(keypath)
939949
.unwrap();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ 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)?.validate(coin)?;
152+
super::policies::parse(policy, coin)?;
153153
super::policies::confirm(
154154
title,
155155
coin_params,

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,7 @@ async fn validate_script_configs<'a>(
411411
keypath,
412412
}] = script_configs
413413
{
414-
let parsed_policy = super::policies::parse(policy)?;
415-
parsed_policy.validate(coin_params.coin)?;
414+
let parsed_policy = super::policies::parse(policy, coin_params.coin)?;
416415
let name =
417416
super::policies::get_name(coin_params.coin, policy)?.ok_or(Error::InvalidInput)?;
418417

0 commit comments

Comments
 (0)