bips icon indicating copy to clipboard operation
bips copied to clipboard

BIP 89: Chain Code Delegation for Private Collaborative Custody

Open jurvis opened this issue 2 months ago • 5 comments

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!

jurvis avatar Oct 15 '25 03:10 jurvis

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

jurvis avatar Nov 30 '25 23:11 jurvis

thanks @jonatack! i think i got everything!

jurvis avatar Dec 03 '25 22:12 jurvis

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.

jonatack avatar Dec 06 '25 17:12 jonatack

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.

jurvis avatar Dec 06 '25 18:12 jurvis

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

Gave it the BIP draft only.

jonatack avatar Dec 06 '25 18:12 jonatack

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

jurvis avatar Dec 18 '25 22:12 jurvis