Repository navigation
Conversation
Owner
Author
|
Note 🤖 Claude Opus 5.5 responding on behalf of bennyhodl Review against orange-grove's consumers ( The biggest deletion of the set: with #231, roughly 400–600 lines of request building and completion in
|
bennyhodl
force-pushed
the
benny/225-external-signer
branch
from
October 15, 2026 15:50
55efeeb to
f4ab2c5
Compare
bennyhodl
changed the base branch from
master
to
benny/225-verify-signed-contract
October 15, 2026 15:54
bennyhodl
force-pushed
the
benny/225-verify-signed-contract
branch
from
October 15, 2026 18:11
4b72d44 to
785180c
Compare
accept_offer, sign_accept and finalize_sign took the DLC funding keys in-process, and the funding PSBT only covered wallet inputs, so a vault, HSM, browser extension or hardware signer could not drive the stateless API. The new contract::external module splits each step around the signer: accept_request / sign_request / finalize_request say what to sign (refund and CET PSBTs with their adaptor points, and the funding PSBT with the splice inputs to half-sign), and complete_accept / complete_sign / complete_finalize verify the answer before building the message. Completion rejects a changed funding transaction, a wrong key, a sighash other than SIGHASH_ALL and a wrong adaptor signature count. The key-in-process functions are now those same halves with an in-process signer between them, so there is one code path, and there is one place a funding transaction is completed: complete_finalize. finalize_request verifies the three messages with verify_signed_contract, so finalizing keeps the fee-rule recovery for contracts signed under the pre-rc.4 rule, the strict parsing of the offering party's splice halves, and the check of its funding witnesses. Nothing a signer returns is taken on trust, wallet witnesses included: complete_sign and complete_finalize verify this party's own wallet witnesses with verify_funding_witness, with the same strictness as the counterparty's. A witness for a script type DDK cannot verify is refused, on either side, with ContractError::UnverifiableFundingWitness. The funding transaction is fixed at accept, so AcceptRequest::funding asks for the accepting party's funding there too. A signer that signs in one batch, such as an HSM, signs its splice halves with the refund and the CETs; complete_accept takes the funding PSBT, verifies the halves it carries and returns it in AcceptResult::funding_psbt, and that PSBT finalizes the contract without asking the signer again. A half left out at accept is signed at finalize, as before. accept_request draws omitted payout and change serial ids. The request reports them (payout_serial_id, change_serial_id), so a signer that crashed can be sent the identical request again by passing them back in the parameters. Supporting primitives, each with one implementation: - ContractInfo::get_adaptor_points (ddk-manager, over a new DlcTrie::generate_adaptor_points) and advanced::cet_adaptor_points; signing and verification in ddk run over the points, computed once per step. - contract_id_from_outpoint and its inverse temporary_contract_id_from_outpoint in ddk-manager, replacing utils::compute_id and ddk's own copy; ContractKeyProvider:: dlc_input_signing_key now takes the splice input and finds the prior key from it. - create_funding_psbt fills witness_utxo, the 2-of-2 witness_script and SIGHASH_ALL on splice inputs; a party's splice half travels as a partial signature in the funding PSBT, and is verified by ddk-dlc's verify_dlc_funding_input_signature. - network_from_chain_hash is public and knows Testnet4; funding_input_index and decode_previous_transaction are public in advanced.
bennyhodl
force-pushed
the
benny/225-external-signer
branch
from
October 15, 2026 18:33
f4ab2c5 to
f4cef5d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Item 1 of #225.
accept_offer,sign_acceptandsign_cettook the DLC funding secret key in-process, and the funding PSBT only covered wallet inputs. So a vault, an HSM, a browser extension or a hardware signer could not drive the stateless API. Lygos wrote ~1,700 lines (lygos_utils::dlc) to fill the gap. The newddk::contract::externalmodule splits each step around the signer.API (
ddk::contract::external, builds withoutmanager)accept_request(&OfferDlc, AcceptOfferParams) -> AcceptRequestcomplete_accept(AcceptRequest, ContractSignatures) -> AcceptResultsign_request(&OfferDlc, &AcceptDlc) -> SignRequestcomplete_sign(SignRequest, ContractSignatures, &Psbt) -> SignResultfinalize_request(&OfferDlc, &AcceptDlc, &SignDlc) -> FinalizeRequestcomplete_finalize(FinalizeRequest, &Psbt) -> TransactionContractSigningRequest { funding_pubkey, refund: Psbt, cets: Vec<Psbt>, adaptor_points }is what the contract key signs. Each PSBT carrieswitness_utxo, the 2-of-2witness_scriptandSIGHASH_ALL.ContractSignatures { refund_signature, cet_adaptor_signatures }is the signer's answer.FundingSigningRequest { psbt, splice_inputs }is the funding PSBT plus the splice inputs this party half-signs. A splice half travels back as aSIGHASH_ALLpartial signature in that PSBT.PsbtMismatch), a wrong key, a non-SIGHASH_ALLsighash and a wrong adaptor count (InvalidSignature).accept_offer,sign_accept(_spliced)andfinalize_sign(_spliced)are now request → in-process signer → complete. There is one code path.accept_requestdraws random serial ids unless the params pin them.Supporting primitives (one implementation each)
advanced::cet_adaptor_points(&OfferDlc), fromContractInfo::get_adaptor_points(ddk-manager) over a newDlcTrie::generate_adaptor_points. Signing and verification compute the points once per step.advanced::contract_id_from_outpointand its inversetemporary_contract_id_from_outpointlive in ddk-manager and replaceutils::compute_idand ddk's copy. Contract-id bytes are unchanged.funding_input_indexanddecode_previous_transactionare public inadvanced.contract::network_from_chain_hashis public and knows Testnet4.create_funding_psbtfillswitness_utxo,witness_scriptand the sighash on splice inputs. The DDK wallet signer skips those inputs; the xpriv and descriptor signers reject them as unsupported.ContractError::InvalidSignature.Breaking changes
ContractKeyProvider::dlc_input_signing_key(&self, splice_input: &FundingInput)now takes only the splice input. It recovers the previous temporary id from the input and returns whichever of the two prior funding keys the provider derives. ddk-ffi (ffi/src/contract.rs:282) drops itsprior_temporary_contract_id/prior_funding_pubkeyplumbing and passes the input.ContractErrorvariant needs a match arm in ddk-ffi's error conversion.InvalidSignature. It used to beInvalidFundingInput("missing prior funding secret key").Behaviour notes
accept_offerdoes about twice the adaptor-signature crypto it did;sign_acceptadds one verification pass and drops one point computation.Follow-up
verify_funding_witness, in a separate PR). Once both land,complete_signandcomplete_finalizeshould run it on the signer's wallet witnesses.Testing
ddk/tests/stateless.rs:external_signers_fund_a_contract_that_settles(settles throughsign_cetandsign_refund);external_signers_splice_a_contract;tests/external_signer) uses only rust-bitcoin and secp256k1-zkp, as a real external signer would.external_signers_fund_splice_and_closeinstateless_execution.rs.cargo test -p ddk --lib --test stateless(80 + 48) andcargo test -p ddk-trie -p ddk-manager --lib(59 + 42) pass.stateless_executionignored tests pass on regtest: numeric-with-difference splices, disjoint, accept-side splice, mnemonic key recovery, and the new external-signer flow.cargo fmt --check,cargo clippy -- -D warnings,cargo check -p ddk --no-default-featuresandcargo run --example stateless_splicepass.dlc_input_signing_keybreak is kept as described above.