Skip to content

Commit 50b05b6

Browse files
committed
Merge branch 'policy-refactor'
2 parents 1d0a095 + 42bc075 commit 50b05b6

File tree

5 files changed

+219
-220
lines changed

5 files changed

+219
-220
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod payment_request;
2525
mod policies;
2626
mod registration;
2727
mod script;
28+
mod script_configs;
2829
pub mod signmsg;
2930
pub mod signtx;
3031

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

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use pb::btc_script_config::SimpleType;
2424
pub use pb::btc_sign_init_request::FormatUnit;
2525
pub use pb::{BtcCoin, BtcOutputType};
2626

27+
use super::script_configs::{ValidatedScriptConfig, ValidatedScriptConfigWithKeypath};
2728
use super::{multisig, params::Params, script};
2829

2930
use sha2::{Digest, Sha256};
@@ -155,8 +156,8 @@ impl Payload {
155156
keypath: &[u32],
156157
) -> Result<Self, Error> {
157158
let witness_script = policy.witness_script_at_keypath(keypath)?;
158-
match policy {
159-
super::policies::ParsedPolicy::Wsh { .. } => Ok(Payload {
159+
match &policy.descriptor {
160+
super::policies::Descriptor::Wsh { .. } => Ok(Payload {
160161
data: Sha256::digest(witness_script).to_vec(),
161162
output_type: BtcOutputType::P2wsh,
162163
}),
@@ -169,39 +170,19 @@ impl Payload {
169170
xpub_cache: &mut Bip32XpubCache,
170171
params: &Params,
171172
keypath: &[u32],
172-
script_config_account: &pb::BtcScriptConfigWithKeypath,
173+
script_config_account: &ValidatedScriptConfigWithKeypath,
173174
) -> Result<Self, Error> {
174-
match script_config_account {
175-
pb::BtcScriptConfigWithKeypath {
176-
script_config:
177-
Some(pb::BtcScriptConfig {
178-
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
179-
}),
180-
..
181-
} => {
182-
let simple_type = pb::btc_script_config::SimpleType::try_from(*simple_type)?;
183-
Self::from_simple(xpub_cache, params, simple_type, keypath)
175+
match &script_config_account.config {
176+
ValidatedScriptConfig::SimpleType(simple_type) => {
177+
Self::from_simple(xpub_cache, params, *simple_type, keypath)
184178
}
185-
pb::BtcScriptConfigWithKeypath {
186-
script_config:
187-
Some(pb::BtcScriptConfig {
188-
config: Some(pb::btc_script_config::Config::Multisig(multisig)),
189-
}),
190-
..
191-
} => Self::from_multisig(
179+
ValidatedScriptConfig::Multisig(multisig) => Self::from_multisig(
192180
params,
193181
multisig,
194182
keypath[keypath.len() - 2],
195183
keypath[keypath.len() - 1],
196184
),
197-
pb::BtcScriptConfigWithKeypath {
198-
script_config:
199-
Some(pb::BtcScriptConfig {
200-
config: Some(pb::btc_script_config::Config::Policy(policy)),
201-
}),
202-
..
203-
} => Self::from_policy(&super::policies::parse(policy)?, keypath),
204-
_ => Err(Error::InvalidInput),
185+
ValidatedScriptConfig::Policy(policy) => Self::from_policy(policy, keypath),
205186
}
206187
}
207188

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

Lines changed: 84 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,47 @@ fn get_change_and_address_index<R: core::convert::AsRef<str>, T: core::iter::Ite
137137
Err(Error::InvalidInput)
138138
}
139139

140+
/// Check that it is impossible to create a derivation with duplicate pubkeys, assuming all the keys
141+
/// in the key vector are distinct.
142+
///
143+
/// Even though the rust-miniscript library checks for duplicate keys, it does so on the raw
144+
/// miniscript, which would not catch e.g. that `wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<2;1>/*)))` has a
145+
/// duplicate change derivation if we derive at the receive path.
146+
///
147+
/// Also checks that each key is used, e.g. if there are 3 keys in the key vector, @0, @1 and @2
148+
/// must be present.
149+
fn check_dups<R: core::convert::AsRef<str>, T: core::iter::Iterator<Item = R>>(
150+
pubkeys: T,
151+
keys: &[pb::KeyOriginInfo],
152+
) -> Result<(), Error> {
153+
// in "@key_index/<left;right>", keeps track of (key_index,left) and
154+
// (key_index,right) to check for duplicates.
155+
let mut derivations_seen: Vec<(usize, u32)> = Vec::new();
156+
157+
let mut keys_seen: Vec<bool> = vec![false; keys.len()];
158+
159+
for pk in pubkeys {
160+
let (key_index, multipath_index_left, multipath_index_right) =
161+
parse_wallet_policy_pk(pk.as_ref()).or(Err(Error::InvalidInput))?;
162+
163+
if derivations_seen.contains(&(key_index, multipath_index_left)) {
164+
return Err(Error::InvalidInput);
165+
}
166+
derivations_seen.push((key_index, multipath_index_left));
167+
if derivations_seen.contains(&(key_index, multipath_index_right)) {
168+
return Err(Error::InvalidInput);
169+
}
170+
derivations_seen.push((key_index, multipath_index_right));
171+
172+
*keys_seen.get_mut(key_index).ok_or(Error::InvalidInput)? = true;
173+
}
174+
175+
if !keys_seen.into_iter().all(|b| b) {
176+
return Err(Error::InvalidInput);
177+
}
178+
Ok(())
179+
}
180+
140181
struct WalletPolicyPkTranslator<'a> {
141182
keys: &'a [pb::KeyOriginInfo],
142183
is_change: bool,
@@ -174,67 +215,30 @@ impl<'a> miniscript::Translator<String, bitcoin::PublicKey, Error>
174215

175216
/// See `ParsedPolicy`.
176217
#[derive(Debug)]
177-
pub struct Wsh<'a> {
178-
policy: &'a Policy,
218+
pub struct Wsh {
179219
miniscript_expr: miniscript::Miniscript<String, miniscript::Segwitv0>,
180220
}
181221

182-
/// Result of `parse()`.
222+
/// See `ParsedPolicy`.
183223
#[derive(Debug)]
184-
pub enum ParsedPolicy<'a> {
224+
pub enum Descriptor {
185225
// `wsh(...)` policies
186-
Wsh(Wsh<'a>),
226+
Wsh(Wsh),
187227
// `tr(...)` Taproot etc. in the future.
188228
}
189229

190-
impl<'a> ParsedPolicy<'a> {
191-
fn get_policy(&self) -> &Policy {
192-
match self {
193-
Self::Wsh(Wsh { policy, .. }) => policy,
194-
}
195-
}
230+
/// Result of `parse()`.
231+
#[derive(Debug)]
232+
pub struct ParsedPolicy<'a> {
233+
policy: &'a Policy,
234+
pub descriptor: Descriptor,
235+
}
196236

197-
/// Check that it is impossible to create a derivation with duplicate pubkeys, assuming all the
198-
/// keys in the key vector are distinct.
199-
///
200-
/// Even though the rust-miniscript library checks for duplicate keys, it does so on the raw
201-
/// miniscript, which would not catch e.g. that `wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<2;1>/*)))` has
202-
/// a duplicate change derivation if we derive at the receive path.
203-
///
204-
/// Also checks that each key is used, e.g. if there are 3 keys in the key vector, @0, @1 and @2
205-
/// must be present.
237+
impl<'a> ParsedPolicy<'a> {
206238
fn validate_keys(&self) -> Result<(), Error> {
207-
match self {
208-
Self::Wsh(Wsh {
209-
policy,
210-
miniscript_expr,
211-
}) => {
212-
// in "@key_index/<left;right>", keeps track of (key_index,left) and
213-
// (key_index,right) to check for duplicates.
214-
let mut derivations_seen: Vec<(usize, u32)> = Vec::new();
215-
216-
let mut keys_seen: Vec<bool> = vec![false; policy.keys.len()];
217-
218-
for pk in miniscript_expr.iter_pk() {
219-
let (key_index, multipath_index_left, multipath_index_right) =
220-
parse_wallet_policy_pk(&pk).or(Err(Error::InvalidInput))?;
221-
222-
if derivations_seen.contains(&(key_index, multipath_index_left)) {
223-
return Err(Error::InvalidInput);
224-
}
225-
derivations_seen.push((key_index, multipath_index_left));
226-
if derivations_seen.contains(&(key_index, multipath_index_right)) {
227-
return Err(Error::InvalidInput);
228-
}
229-
derivations_seen.push((key_index, multipath_index_right));
230-
231-
*keys_seen.get_mut(key_index).ok_or(Error::InvalidInput)? = true;
232-
}
233-
234-
if !keys_seen.into_iter().all(|b| b) {
235-
return Err(Error::InvalidInput);
236-
}
237-
Ok(())
239+
match &self.descriptor {
240+
Descriptor::Wsh(Wsh { miniscript_expr }) => {
241+
check_dups(miniscript_expr.iter_pk(), &self.policy.keys)
238242
}
239243
}
240244
}
@@ -248,7 +252,7 @@ impl<'a> ParsedPolicy<'a> {
248252
pub fn validate(&self, coin: BtcCoin) -> Result<(), Error> {
249253
check_enabled(coin)?;
250254

251-
let policy = self.get_policy();
255+
let policy = self.policy;
252256

253257
if policy.keys.len() > MAX_KEYS {
254258
return Err(Error::InvalidInput);
@@ -297,13 +301,10 @@ impl<'a> ParsedPolicy<'a> {
297301
/// wsh(and_v(v:pk(@0/0/5),pk(@1/20/5))).
298302
/// The same derived using `is_change=true` derives: wsh(and_v(v:pk(@0/1/5),pk(@1/21/5)))
299303
pub fn witness_script(&self, is_change: bool, address_index: u32) -> Result<Vec<u8>, Error> {
300-
match self {
301-
Self::Wsh(Wsh {
302-
policy,
303-
miniscript_expr,
304-
}) => {
304+
match &self.descriptor {
305+
Descriptor::Wsh(Wsh { miniscript_expr }) => {
305306
let mut translator = WalletPolicyPkTranslator {
306-
keys: policy.keys.as_ref(),
307+
keys: self.policy.keys.as_ref(),
307308
is_change,
308309
address_index,
309310
};
@@ -322,27 +323,27 @@ impl<'a> ParsedPolicy<'a> {
322323
/// derived using keypath m/48'/1'/0'/3'/11/5 derives:
323324
/// wsh(and_v(v:pk(@0/11/5),pk(@1/21/5))).
324325
pub fn witness_script_at_keypath(&self, keypath: &[u32]) -> Result<Vec<u8>, Error> {
325-
match self {
326-
Self::Wsh(Wsh {
327-
policy,
328-
miniscript_expr,
329-
}) => {
330-
let (is_change, address_index) =
331-
get_change_and_address_index(miniscript_expr.iter_pk(), &policy.keys, keypath)?;
326+
match &self.descriptor {
327+
Descriptor::Wsh(Wsh { miniscript_expr }) => {
328+
let (is_change, address_index) = get_change_and_address_index(
329+
miniscript_expr.iter_pk(),
330+
&self.policy.keys,
331+
keypath,
332+
)?;
332333
self.witness_script(is_change, address_index)
333334
}
334335
}
335336
}
336337

337338
/// Returns true if the address-level keypath points to a change address.
338339
pub fn is_change_keypath(&self, keypath: &[u32]) -> Result<bool, Error> {
339-
match self {
340-
Self::Wsh(Wsh {
341-
policy,
342-
miniscript_expr,
343-
}) => {
344-
let (is_change, _) =
345-
get_change_and_address_index(miniscript_expr.iter_pk(), &policy.keys, keypath)?;
340+
match &self.descriptor {
341+
Descriptor::Wsh(Wsh { miniscript_expr }) => {
342+
let (is_change, _) = get_change_and_address_index(
343+
miniscript_expr.iter_pk(),
344+
&self.policy.keys,
345+
keypath,
346+
)?;
346347
Ok(is_change)
347348
}
348349
}
@@ -364,10 +365,10 @@ pub fn parse(policy: &Policy) -> Result<ParsedPolicy, Error> {
364365
miniscript::Miniscript::from_str(&desc[4..desc.len() - 1])
365366
.or(Err(Error::InvalidInput))?;
366367

367-
Ok(ParsedPolicy::Wsh(Wsh {
368+
Ok(ParsedPolicy {
368369
policy,
369-
miniscript_expr,
370-
}))
370+
descriptor: Descriptor::Wsh(Wsh { miniscript_expr }),
371+
})
371372
}
372373
_ => Err(Error::InvalidInput),
373374
}
@@ -595,10 +596,9 @@ mod tests {
595596
fn test_parse_wsh_miniscript() {
596597
// Parse a valid example and check that the keys are collected as is as strings.
597598
let policy = make_policy("wsh(pk(@0/**))", &[]);
598-
match parse(&policy).unwrap() {
599-
ParsedPolicy::Wsh(Wsh {
600-
ref miniscript_expr,
601-
..
599+
match &parse(&policy).unwrap().descriptor {
600+
Descriptor::Wsh(Wsh {
601+
miniscript_expr, ..
602602
}) => {
603603
assert_eq!(
604604
miniscript_expr.iter_pk().collect::<Vec<String>>(),
@@ -609,10 +609,9 @@ mod tests {
609609

610610
// Parse another valid example and check that the keys are collected as is as strings.
611611
let policy = make_policy("wsh(or_b(pk(@0/**),s:pk(@1/**)))", &[]);
612-
match parse(&policy).unwrap() {
613-
ParsedPolicy::Wsh(Wsh {
614-
ref miniscript_expr,
615-
..
612+
match &parse(&policy).unwrap().descriptor {
613+
Descriptor::Wsh(Wsh {
614+
miniscript_expr, ..
616615
}) => {
617616
assert_eq!(
618617
miniscript_expr.iter_pk().collect::<Vec<String>>(),
@@ -770,8 +769,8 @@ mod tests {
770769
assert!(parse(&pol).unwrap().validate(coin).is_ok());
771770

772771
// Duplicate path, one time in change, one time in receive. While the keys technically are
773-
// never duplicate in the final miniscript with the pubkeys inserted, we still prohibit, as
774-
// it does not look like there would be a sane use case for this and would likely be an
772+
// never duplicate in the final miniscript with the pubkeys inserted, we still prohibit it,
773+
// as it does not look like there would be a sane use case for this and would likely be an
775774
// accident.
776775
let pol = make_policy(
777776
"wsh(or_b(pk(@0/<0;1>/*),s:pk(@0/<1;2>/*)))",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use super::pb;
16+
use pb::btc_script_config::{Multisig, SimpleType};
17+
18+
use super::policies::ParsedPolicy;
19+
20+
/// Parsed and validated form of `pb::BtcScriptConfig`.
21+
pub enum ValidatedScriptConfig<'a> {
22+
SimpleType(SimpleType),
23+
Multisig(&'a Multisig),
24+
Policy(ParsedPolicy<'a>),
25+
}
26+
27+
/// Parsed and validated form of `pb::BtcScriptConfigWithKeypath`.
28+
pub struct ValidatedScriptConfigWithKeypath<'a> {
29+
pub keypath: &'a [u32],
30+
pub config: ValidatedScriptConfig<'a>,
31+
}

0 commit comments

Comments
 (0)