LedgerHQ / LedgerHQ/vanadium

Add signing policy embedding in BIP32 keys / resident keys

Open
#263 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bitcoin enhancement
Dominant language
Rust
Stars
44
Forks
7
Avg merge
17h 58m
Merged PRs (30d)
3

Description

We could allow registering a custom signing behavior that differs from the standard for certain keys. For example:

  • an account with timelocked recovery paths could have a specific key that can sign transactions blindly, but only if sending all the funds back to the same account (basically a refresh key)
  • a spending path could have a mandatory key that enforces some user-defined spending limits, or other restrictions on the types of transactions that it will sign. Failing to satisfy the restrictions would fail automatically.
  • as an alternative to timelocked recovery paths, an account could have a spending path that enforces a vault-like withdrawal flow

Of course, if this is done with normal BIP32 keys, nothing prevents from loading the same seed on a different device that doesn't respect those restrictions. However, a solution to that could be to use resident keys as implemented in #262, which are generated in the app and never leave it.

In order to make sure that this is opt-in, the following requirements seem sensible:
a) the custom signing behavior must be opt-in, and users should be able to inspect during account registration
b) the custom signing behavior must strictly enforced by the bitcoin application - that is, it must sign automatically a policy-compliant transaction (if the policy specifies that user enforcement is not required), and refuse signing any policy-violating transaction.

Policy tweaking

In order to enforce the binding between policies and keys in accounts, the keys themselves could embed a commitment to the policy itself. We could use a taproot-style tweak for an xpub that has a policy. For example, a BIP-388 wallet policy could have a key specified as

[48'/1'/4'/1']tpubDK8WPFx4WJo1R9mEL7Wq325wBiXvkAe8ipgb9Q1QBDTDUD2YeCfutWtzY88NPokZqJyRPKHLGwTNLT7jBG59aC6VH8q47LDGQitPB6tX2d7/cdac77a7f83feaeb5f4dcdce78bbffca5f66df1330ccd1948b06e82491e487d2

where cdac77a7f83feaeb5f4dcdce78bbffca5f66df1330ccd1948b06e82491e487d2 == taproot.tagged_hash("SIGNER_POLICY/policy", b"only sign self-transfers").

(of course, English is not the ideal way to represent signing policies - more on this below)

A 'child xpub with embedded data' could then be computed with a BIP32-like derivation:

DataCommitPriv((k, c), data):
   K  = kG
   t  = int(TaggedHash("SIGNER_POLICY/tweak", bytes(K) || data)) mod n
   if t = 0:
       fail

   k' = (k + t) mod n
   if k' = 0:
       fail

   c' = TaggedHash("SIGNER_POLICY/chaincode", c || bytes(K) || data)

   return (k', c')


DataCommitPub((K, c), data):
   t  = int(TaggedHash("SIGNER_POLICY/tweak", bytes(K) || data)) mod n
   if t = 0:
       fail

   K' = K + tG
   if K' is infinity:
       fail

   c' = TaggedHash("SIGNER_POLICY/chaincode", c || bytes(K) || data)

   return (K', c')

This xpub could still be used for the usual unhardened change/address_index derivation steps.

An approach like this ensures that

  • the keys used in the script explicitly commit to the signing behavior
  • the signer must explicitly be provided with the hash of the signing policy in order to reconstruct the required private keys

The data commitment derivation is more akin unhardened derivation, so typically the xpub used in the account would be hardened.

Signer policy language

The easiest approach could be to whitelist a set of interesting signing policies; however, this lacks generality.

The other extreme would be a fully expressive policy language with access to the PSBT, for example Rust-like scripts using rhai; perhaps a simpler other scripting language could be more embedded-friendly, or easier to audit for the user.

Perhaps somewhere in between would be good enough for the purposes of signing policies.

PSBT support

Draft of how this could be introduced in PSBTs:

  • the PSBT_GLOBAL_XPUB as filled with the 'untweaked' xpubs, not with the tweaked ones
  • PSBT_GLOBAL_PROPRIETARY_SIGNINGPOLICY_TWEAK
    • key: <bytes xpub>, the xpub after the key
    • value: ` <32-byte signing policy hash>
  • PSBT_GLOBAL_PROPRIETARY_SIGNINGPOLICY_CONTENT
    • key: <32-byte signing policy hash>, the hash of the signing policy
    • value: <bytes signing policy> the full content of the signing policy. The exact format is TBD.

keys derived from the policy-tweaked key would use normal PSBT_{IN,OUT}_BIP32_DERIVATION and PSBT_TAP_{IN,OUT}_BIP32_DERIVATION fields, but referring to the fingerprint and additional derivation steps of the policy-tweaked key , rather than from the BIP32 master key.

Application UX

During wallet policy registration, at least the policy hash should be shown on screen - but ideally also the full policy content.

During signing, the policy itself might specify if signing for that key happens blindly or not; if only signing for such keys, then it could happen silently.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Begin with the resident-key work in #262 and the proposed DataCommitPriv/DataCommitPub derivations and PSBT_GLOBAL proprietary fields; the issue names no repository files or tests. First resolve the policy language, commitment format, PSBT representation, and wallet UX. Done means policy-compliant transactions can sign as specified and violating transactions are refused.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cryptography, embedded-iot, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.