Skip to content

Conversation

@jurvis
Copy link

@jurvis jurvis commented Oct 15, 2025

We propose a new BIP for Chain Code Delegation, a collaborative custody technique that involves privileged participants (delegatee) withholding BIP32 chain codes at key setup time from a delegator, and sharing only enough information for non‑privileged participants to provide their signature.

For non-blinded signing, the delegatee derives a per‑spend scalar tweak t from the (withheld) chain code, the delegator computes the child key (x+t, P+tG), and produces a standard signature over the transaction’s sighash. For blind signing, the nonce and challenge are blinded so the delegator returns a blind Schnorr signature that the counterparty unblinds; thanks to Schnorr’s linearity, the same tweak is incorporated without revealing the final message or linkable details (optionally with predicate proofs for policy).

This enables participants like collaborative custodians to co‑sign when needed, while avoiding the broad visibility that comes with holding an xpub.

More background and discussions can be found: https://delvingbitcoin.org/t/chain-code-delegation-private-access-control-for-bitcoin-keys/1837.

This is joint work with @jesseposner. Feedback appreciated!

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

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

Looks complete, labelling as needing number assignment.

@jurvis
Copy link
Author

jurvis commented Nov 30, 2025

@arminsabouri @jonatack thank you for taking the time to review! I've gone ahead and addressed your comments in
9a47c29

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

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

Assigned 89.

Please update the file names and BIP draft headers, including "Created: 2025-12-03" for the date of assignment, and add an entry to the README.

@jonatack jonatack changed the title BIP: Chain Code Delegation for Private Collaborative Custody BIP 89: Chain Code Delegation for Private Collaborative Custody Dec 3, 2025
@jurvis jurvis force-pushed the master branch 2 times, most recently from 2604b20 to af0ec04 Compare December 3, 2025 22:33
@jurvis
Copy link
Author

jurvis commented Dec 3, 2025

thanks @jonatack! i think i got everything!

@jonatack
Copy link
Member

jonatack commented Dec 6, 2025

Thanks for updating.

Final verdict: 95 % LLM-generated or LLM-heavy, with ~5 % human origination of the core idea and final review.

@jurvis can you help me out, please: Is this accurate? I'm trying to adapt our process and understanding.

@jurvis
Copy link
Author

jurvis commented Dec 6, 2025

hi @jonatack, happy to help. does the scan run through the sample code as well? the sample code contains a lot of boilerplate that we leveraged LLMs to help us with, and may be the reason why it registers high.

However, the contents of the mediawiki itself should be ~90% original. We leaned on LLM use in the mediawiki mostly to ensure that our formatting was aligned with existing conventions (heavily referencing BIP 340, BIP 352, BIP 32, and BIP 3), and to convert it from Markdown, which we had originally used to write our draft.

For example, we wrote out our original one of the algorithms in the following format originally:

Signing
======
When the counterparty requests that the collaborative custodian sign a transaction, it derives the BIP32 scalar tweak from the xpub (i.e. the value `parse_256(I_L)` from BIP32) and provides it to the custodian:

# Inputs:
#   chain_code : 32-byte chain code (hidden from custodian)
#   P_par      : custodian’s parent public key (compressed)
#   i          : child index for spending

I   = HMAC-SHA512(key = chain_code,
                 data = serP(P_par) || ser32(i))
I_L = I[0:32]            # left half
t_i = parse256(I_L)      # scalar tweak mod n
# (I_R would be the child chain code—discarded here.)

# Counterparty → Custodian: send t_i

Which we got ultimately turned into this, to align with how BIP 32 reads:

=== Tweak Calculation ===
To produce CCD tweak data, a delegatee computes a per-participant scalar that aggregates the non-hardened derivation tweaks along the remaining path. Let the extended key retained by the delegatee be P at depth d, and let the target index vector be I = (i<sub>d+1</sub>, …, i<sub>n</sub>) with each i<sub>k</sub> < 2<sup>31</sup>.

<div>
Algorithm ''ComputeBIP32Tweak(P, I)'':
* Inputs:
** ''P'': base public key at depth ''d''
** ''I = (i<sub>d+1</sub>, …, i<sub>n</sub>)'': ordered sequence of non-hardened child indices
* Let ''t = 0'' and ''E = P''.
* For each index ''i'' in ''I'' (from left to right):
** Run the BIP32 non-hardened derivation ''CKDpub'' on ''E'' with child index ''i'', yielding the child extended key ''P<sub>child</sub>'' and its scalar tweak ''δ'' (the parse<sub>256</sub>(''I<sub>L</sub>'') term from BIP32).
** Let ''t = (t + δ) mod n''.
** Let ''E = P<sub>child</sub>''.
* If ''I'' is empty, let ''P′ = P''; otherwise let ''P′ = P<sub>child</sub>'' from the final iteration.
* Return ''(t, P′)''.
</div>

Hope that helps.

@jonatack
Copy link
Member

jonatack commented Dec 6, 2025

hi @jonatack, happy to help. does the scan run through the sample code as well?

Gave it the BIP draft only.

@jurvis
Copy link
Author

jurvis commented Dec 18, 2025

hi @jonatack just following up to see if there's anything I can do here to move things along. thanks!

@real-or-random
Copy link
Contributor

The inclusion of major parts of secp256k1lab in this form violates the (only) requirement in the MIT license under which secp256k1lab is distributed, see its COPYING file.

@real-or-random
Copy link
Contributor

A related editorial question is whether it's a good idea to include distributions of secp256k1lab in the BIPs repo. This question is relevant not only for this BIP draft but also for #2070 and for the ChillDKG draft BIP (no PR yet).

We had raised that question in the context of ChillDKG over a year ago on the mailing list, specifically hoping to hear the opinion of the BIPs editors, but noone has commented so far: https://groups.google.com/g/bitcoindev/c/HE3HSnGTpoQ/m/Y2VhaMCrCAAJ (This post refers to the library as "secp256k1proto". This was our working title before we switched to "secp256k1lab" when publishing the code in a separate repo.)

Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

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

I gave the proposal draft a first read. I find myself somewhat confused as to the terms “Delegator and Delegatee”. The scenario I envisioned was one Delegatee and one or multiple Delegators, but at times it seems that maybe multiple Delegatees are in play. If that’s the case, it could perhaps be clarified what scenarios are covered and how multiple Delegatees would collaborate.
I skipped some of the paragraphs of the Blinded Signing section.

Comment on lines +24 to +25
* A "Delegatee" is a participant who retains a BIP32 chain code for another participant's base public key and computes derivation tweaks for that participant.
* A "Delegator" is a participant who holds only a non-extended keypair and receives scalar tweaks from a delegatee when asked to sign.
Copy link
Contributor

Choose a reason for hiding this comment

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

I found the terminology confusing here, because the privileged user delegates a co-signing duty, so I initially assumed the term “Delegator” would apply to the privileged user, contrary to how it is defined here.

In multisig deployments, sharing extended public keys (xpubs) or descriptors enables all participants to scan the chain and infer counterparties' activity. CCD limits that visibility by ensuring non-privileged participants only ever hold a non-extended keypair and only receive the minimum per-spend data needed to sign. The procedure keeps policy enforcement feasible for the non-privileged signer while preserving balance privacy, which is particularly useful in collaborative custody arrangements where the wallet owner wants balance privacy from their custodian.

== Terminology ==
* A "Delegatee" is a participant who retains a BIP32 chain code for another participant's base public key and computes derivation tweaks for that participant.
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn’t the receiver of a delegation simply be a “Delegate”?

Suggested change
* A "Delegatee" is a participant who retains a BIP32 chain code for another participant's base public key and computes derivation tweaks for that participant.
* A "Delegate" is a participant who retains a BIP32 chain code for another participant's base public key and computes derivation tweaks for that participant.

== Terminology ==
* A "Delegatee" is a participant who retains a BIP32 chain code for another participant's base public key and computes derivation tweaks for that participant.
* A "Delegator" is a participant who holds only a non-extended keypair and receives scalar tweaks from a delegatee when asked to sign.
* A "Participant" is any key holder in the wallet quorum (including delegators and delegatees).
Copy link
Contributor

Choose a reason for hiding this comment

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

Just checking my understanding: conventionally “quorum” refers to the minimum necessary members of a body to be able to proceed, so I would understand it as referring to only the active participants of a signing rather than the full set of signers, however, this seems potentially contradictory to “any key holder” which makes me wonder whether the full body of key holders is meant instead.

* '''Delegator key:''' Each delegator generates a standard (non-extended) secp256k1 keypair and provides the public key to the counterparties. A delegator MUST NOT retain or be provided a chain code for this key.
* '''Delegated chain code:''' A designated delegatee computes and retains a BIP32 chain code bound to the delegator's public key, forming an xpub that MUST NOT be disclosed to the delegator. The delegatee MAY share this xpub with other delegatees.
* '''Other participants:''' Non-delegator participants use conventional extended keys and share the public half as appropriate for the wallet descriptor.
* '''Derivation constraints:''' All key paths used with CCD MUST be non-hardened beyond the depth visible to the delegator. Hardened steps prevent computation of the needed tweak and are therefore NOT supported.
Copy link
Contributor

Choose a reason for hiding this comment

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

It’s not clear to me what “beyond” means here. Is hardened derivation completely forbidden, or just forbidden in the range covering the delegator’s tweak?

Comment on lines +96 to +98
CCD requires the delegatee to provide per-participant tweaks for inputs and (optionally) change outputs. Change outputs are only required if a delegator wants to be able to compute the amount of bitcoin they are spending.

A delegatee MUST provide each delegator with, for every signing context, a collection of tuples (P<sub>i</sub>, t<sub>i</sub>) where P<sub>i</sub> is the participant's base public key disclosed to the delegator and t<sub>i</sub> is the aggregated tweak returned by ''ComputeBIP32Tweak''. The scalar t<sub>i</sub> MUST be encoded as a 32-byte big-endian integer.
Copy link
Contributor

Choose a reason for hiding this comment

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

It’s not clear to me how a Delegator would create a (sighash-default) signature for the transaction without knowing all outputs. Is a sighash/message provided per input? Is this part of the “tweak”?

* Inputs:
** ''t'': aggregated tweak for the signing context (scalar mod ''n'')
** ''x'': delegator base secret key
** ''m'': message to be signed (for example, a transaction digest under the desired SIGHASH policy)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, here is the expected message. Shouldn’t it then be part of the “Delegation Bundle” above then?

* Return ''σ''.
</div>

The caller is responsible for inserting ''σ'' into the surrounding protocol (e.g., a PSBT, transaction witness, or adaptor signature exchange).
Copy link
Contributor

Choose a reason for hiding this comment

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

“Caller” is previously not introduced. Is this a synonym for the Delegatee?


The plain protocol here is '''not''' concurrently secure. A signer '''MUST NOT''' run multiple blind signing sessions in parallel or interleave state across sessions. A signer '''MUST''' refuse any new blind‑nonce requests while a previous blind‑signature request is outstanding, or '''MUST''' irrevocably discard (and never reuse) any in‑flight blind nonce commitments that have not resulted in a signature, before accepting new ones.

To obtain concurrency security as in ([https://eprint.iacr.org/2022/1676 ePrint 2022/1676]), the delegatee first sends an encryption of (m, a, b) before the signer commits to the blind nonce; later, the delegatee includes a zero‑knowledge proof binding the produced challenge to that encrypted tuple. That proof can additionally encode policy predicates about m (spend limits, velocity controls, etc.). A complete specification of this variant is outside the scope of this BIP.
Copy link
Contributor

Choose a reason for hiding this comment

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

“A complete specification of this variant is outside the scope”[…] followed by a fairly detailed description seems a bit contradictory. Either the following sections could be dropped, or this sentence should perhaps be amended to better explain what to expect in the following paragraphs.


== Security Considerations ==
* Exposure of any delegated tweak scalar <code>t</code> enables signing only for the specific child key(s) that scalar was derived for, and is typically short-lived if disclosed immediately before spending.
* Delegatees MUST ensure every delegated path remains non-hardened and that ''ComputeBIP32Tweak'' yields the correct tweak <code>t</code>; incorrect scalars can render funds unspendable by the delegator.
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought the Delegatee is the primary owner of the wallet, so wouldn’t it be:

Suggested change
* Delegatees MUST ensure every delegated path remains non-hardened and that ''ComputeBIP32Tweak'' yields the correct tweak <code>t</code>; incorrect scalars can render funds unspendable by the delegator.
* Delegatees MUST ensure every delegated path remains non-hardened and that ''ComputeBIP32Tweak'' yields the correct tweak <code>t</code>; incorrect scalars could render the delegator incapable of producing a signature.

== Security Considerations ==
* Exposure of any delegated tweak scalar <code>t</code> enables signing only for the specific child key(s) that scalar was derived for, and is typically short-lived if disclosed immediately before spending.
* Delegatees MUST ensure every delegated path remains non-hardened and that ''ComputeBIP32Tweak'' yields the correct tweak <code>t</code>; incorrect scalars can render funds unspendable by the delegator.
* Delegators MUST verify change outputs when tweak data is provided (for example via ''ChangeOutputVerification'') to avoid authorizing unexpected scripts.
Copy link
Contributor

Choose a reason for hiding this comment

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

The Delegator is the privileged user, right? So wouldn’t they be the one that creates the transaction? Why would they need to verify change outputs? Are we talking about multiple delegators?

@murchandamus
Copy link
Contributor

We had raised that question in the context of ChillDKG over a year ago on the mailing list, specifically hoping to hear the opinion of the BIPs editors, but noone has commented so far: https://groups.google.com/g/bitcoindev/c/HE3HSnGTpoQ/m/Y2VhaMCrCAAJ (This post refers to the library as "secp256k1proto". This was our working title before we switched to "secp256k1lab" when publishing the code in a separate repo.)

@real-or-random: TBH, I don’t think I fully understand the trade-offs of the approaches you proposed. I would prefer that any software with on-going development were maintained outside of the BIPs repository, but maybe we could chat some time so that I better understand the situation.

@real-or-random
Copy link
Contributor

We had raised that question in the context of ChillDKG over a year ago on the mailing list, specifically hoping to hear the opinion of the BIPs editors, but noone has commented so far: groups.google.com/g/bitcoindev/c/HE3HSnGTpoQ/m/Y2VhaMCrCAAJ (This post refers to the library as "secp256k1proto". This was our working title before we switched to "secp256k1lab" when publishing the code in a separate repo.)

@real-or-random: TBH, I don’t think I fully understand the trade-offs of the approaches you proposed. I would prefer that any software with on-going development were maintained outside of the BIPs repository, but maybe we could chat some time so that I better understand the situation.

Let me try to simplify the discussion: If the BIP editors are okay with integrating a snapshot of secp256klab as a subtree in each bip-xxxx folder of a BIP whose code uses secp256k1lab (as currently proposed in this PR and in #2070), then I'm happy with that. I think it's the best solution.

"Snapshot" means that it is a static thing, and maintenance of secp256k1lab will, of course, happen in its repo. I guess the only reason to ever touch a snapshot subtree in the BIPs repo is to fix a secp256k1 bug that specifically affects the BIP code that uses the library (and even in that case, other ways to resolve the overall issue could be considered).

@murchandamus
Copy link
Contributor

We had raised that question in the context of ChillDKG over a year ago on the mailing list, specifically hoping to hear the opinion of the BIPs editors, but noone has commented so far: groups.google.com/g/bitcoindev/c/HE3HSnGTpoQ/m/Y2VhaMCrCAAJ (This post refers to the library as "secp256k1proto". This was our working title before we switched to "secp256k1lab" when publishing the code in a separate repo.)

@real-or-random: TBH, I don’t think I fully understand the trade-offs of the approaches you proposed. I would prefer that any software with on-going development were maintained outside of the BIPs repository, but maybe we could chat some time so that I better understand the situation.

Let me try to simplify the discussion: If the BIP editors are okay with integrating a snapshot of secp256klab as a subtree in each bip-xxxx folder of a BIP whose code uses secp256k1lab (as currently proposed in this PR and in #2070), then I'm happy with that. I think it's the best solution.

"Snapshot" means that it is a static thing, and maintenance of secp256k1lab will, of course, happen in its repo. I guess the only reason to ever touch a snapshot subtree in the BIPs repo is to fix a secp256k1 bug that specifically affects the BIP code that uses the library (and even in that case, other ways to resolve the overall issue could be considered).

That seems reasonable to me, as it ensures that a functional reference implementation is retained in the context of the BIPs. The only concern that comes to mind would be that we might get more pull requests by LLM-commit farmers on those, but I guess we’d just have to filter those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants