Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Miniscript wallet policies of the form wsh(<miniscript expression>) #1095

Merged
merged 20 commits into from
Jul 11, 2023

Conversation

benma
Copy link
Collaborator

@benma benma commented Jul 8, 2023

No description provided.

benma added 20 commits June 29, 2023 02:14
Some Rust vendored deps can contain C code. We don't need to lint
these.

The drivers filter is removed because it does not exist anymore.
We will support miniscript as part of output descriptors.

Descriptors are an expression language to specify Bitcoin outputs. For
example, `wsh(multi(2,<pubkey1>,<pubkey2>,<pubkey3>))` is a 2-of-3
multisig wrapped in a P2WSH (pay-to-witness-script-hash) output.
Descriptors specification: https://github.com/bitcoin/bitcoin/blob/8f402710371a40c5777dc3f9c4ba6ca8505a2f90/doc/descriptors.md

In fragments wrapping SCRIPT expressions, SCRIPT can also be a
miniscript expression, for example: `wsh(or_b(pk(<pubkey1>),s:pk(<pubkey2>)))`.

With hardware signers, we want to use xpubs with derivations instead
of raw pubkeys, and also have a shorter representation than inlining
the xpubs for readability. That is why we will follow the 'Wallet
Policies for Descriptor Wallets' proposal specied here:

https://github.com/bigspider/bips/blob/bip-wallet-policies/bip-wallet-policies.mediawiki (bitcoin/bips#1389).

The above example then become `wsh(or_b(pk(@0/**),s:pk(@1/**)))`. The
`@NUM` references a key provided in the keys list.

For the policy with the keys, one can then derive pkScripts for
receive and change addresses and use these to create addresses.
rust-miniscript implements miniscrpit for the `wsh()` (Pay to witness
script hash) context as well as for the `tr()` context (Pay to
taproot). We use this dep as it works out of the box. The downside is
that this library uses a lot of generics and binary-size-inefficent
code. We could address much of that with PRs upstream, or in the worst
case roll our own miniscript implementation that is space-efficient in
the future.

The rust-bitcoin dep is needed explicitly as rust-miniscript requires
the `bitcoin::PublicKey` type to serialize pubkeys inside of
miniscript expressions. Maybe we can make upstream changes that allow
us to use our own types for that, as we already have all the code
needed to serialize pubkeys. rust-bitcoin is a dependency to
rust-miniscript as rust-bitcoin contains the necessary script
generation functions.

secp256k1 and secp256k1-sys are also transitive dependencies, as
`bitcoin::PublicKey` is a re-export of `secp256k1::PublicKey`. Also
here, we might be able to make upstream changes to remove the reliance
on these deps.

bech32 is updated to 0.9 as that is used by rust-bitcoin, so we don't
vendor two versions of the same lib.
We add a policies.rs file with functions to parse and validate a
policy containing miniscript.

Policy specification: bitcoin/bips#1389

We only support `wsh(<miniscript>)` policies for now. Taproot or other
policy fragments could be added in the future.

More validation checks are coming in later commits, such as:
- At least one key must be ours
- No duplicate keys possible in the policy
- No duplicate keys in the keys list
- All keys in the keys list are used, and all key references (@0, ...)
are valid.
- ...?

Also coming in later commits:
- Derive a pkScript at a keypath, generate receive address from that
- Policy registration (very similar to how multisig registration works
today)
- Signing transactions
In a pubkey reference like `@0/<10;11>/*`, the receive address
derivation is `<xpub>/10/<addressIndex>` and the change deriation is
`<xpub>/11/<addressIndex>`. The pubkey reference would be replaced by
a pubkey derived at a particular change/receive derivation.

We check that a policy cannot contain the same pubkeys under any
derivation because Miniscript prohibits duplicate keys.
The witness script is the translation of the miniscript to Bitcoin
Script. See the translation table at
https://bitcoin.sipa.be/miniscript/.

This is the script that is SHA256'd to be put into a P2WSH output,
with the witness script going into the witness when spending the
output.
Given a full keypath, we want to derive the witness script there to be
able to 1) sign at that keypath 2) show an address at that keypath.

The keypath is a different derivation mechanism to deriving using the
"native" derivation mechnism of providing the (is_change,address_index) tuple.

Example: wsh(and_v(v:pk(@0/<10;11>/*),pk(@1/<20;21>/*))) with our key [fp/48'/1'/0'/3']xpub...]
derived using keypath m/48'/1'/0'/3'/11/5 derives:
wsh(and_v(v:pk(@0/11/5),pk(@1/21/5))).

For signing at a keypath, we need to be able derive using the keypath,
as PSBTs etc. all have a keypath at the input to be signed.

For showing receive addresses, it is less clear if the API should take
a full keypath or a (is_change,address_index) tuple. We'll go with the
full keypath option for now as it is more in line with the other
script types (single sig, multisig).
So far we prohibited unusual keypaths (e.g. non bip-44/49/84
keypaths).

When using descriptors/miniscript, there are no specific keypaths to
whitelist - the user confirms the keypath at policy registration
time. We need to allow exporting the xpub at any keypath so the a
descriptor wallet app can construct a policy using BitBox02 xpubs.
The policy is registered just like a multisig account, by computing a
hash of the policy and storing it with a user-chosen account name.

The registration is checked before displaying a receive address.

The hash function does not include the origin and keypath of the key
origin info, as they don't influence the result: if e.g. a keypath
would change to one of our own keys, the xpub would change and so
would the hash. For keys that are not ours, we don't care about the
origin/keypath.
It works the same way as other script types.
The current signing code checks that all changes are at the keypath
.../1/*. For policies, the change keypath can be at a different
derivation, e.g. .../11/* when the key is specified as @0/<10;11>/*.

This fixes the change keypath check to accomodate for that and adds a
unit test for it.
Getting the fingerprint requires decrypting the seed. We don't need to
do it for every key. Once is enough, yielding a speedup.
@benma benma requested a review from Beerosagos July 8, 2023 08:58
Copy link
Collaborator

@Beerosagos Beerosagos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK!

@benma benma merged commit 0fba7d4 into master Jul 11, 2023
2 checks passed
@benma benma deleted the staging-miniscript branch July 11, 2023 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants