From bf0beb3543efd78a6ce4526f220cf0bee6840cd3 Mon Sep 17 00:00:00 2001 From: Elis Jackson Date: Fri, 26 Jun 2026 15:11:54 +0100 Subject: [PATCH] fix(sdk): sign user Sig params at primary covenant input for stateful contracts --- packages/runar-rs/src/sdk/contract.rs | 33 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/runar-rs/src/sdk/contract.rs b/packages/runar-rs/src/sdk/contract.rs index 004f92cb..6c1a58fe 100644 --- a/packages/runar-rs/src/sdk/contract.rs +++ b/packages/runar-rs/src/sdk/contract.rs @@ -880,21 +880,28 @@ impl RunarContract { tx_change_amount: i64| -> Result<(String, String, String), String> { let (op_sig, preimage) = compute_op_push_tx_with_code_sep(tx, input_idx, subscript, sats, code_sep_idx)?; - // Only sign Sig params for extra inputs, not the primary - if input_idx > 0 { - // In stateful contracts, user checkSig is AFTER OP_CODESEPARATOR — trim. - let mut sig_subscript = subscript.to_string(); - if code_sep_idx >= 0 { - let trim_pos = ((code_sep_idx as usize) + 1) * 2; - if trim_pos <= sig_subscript.len() { - sig_subscript = sig_subscript[trim_pos..].to_string(); - } - } - for &idx in sig_indices { - let real_sig = signer.sign(tx, input_idx, &sig_subscript, sats, None)?; - resolved_args[idx] = SdkValue::Bytes(real_sig); + // Sign Sig params for ALL inputs. Stateful contracts that mix + // OP_PUSH_TX preimage verification with user `check_sig` calls + // require a real signature at the primary covenant input + // (input_idx == 0) too — leaving the placeholder zero bytes + // there fails on-chain `check_sig` and the relay rejects the + // transaction. The BIP-143 sighash for input 0 is identical + // to the OP_PUSH_TX sighash for that input (same tx, same + // input_idx, same trimmed subscript), so both checks in the + // unlock context see consistent message bytes. + // + // In stateful contracts, user checkSig is AFTER OP_CODESEPARATOR — trim. + let mut sig_subscript = subscript.to_string(); + if code_sep_idx >= 0 { + let trim_pos = ((code_sep_idx as usize) + 1) * 2; + if trim_pos <= sig_subscript.len() { + sig_subscript = sig_subscript[trim_pos..].to_string(); } } + for &idx in sig_indices { + let real_sig = signer.sign(tx, input_idx, &sig_subscript, sats, None)?; + resolved_args[idx] = SdkValue::Bytes(real_sig); + } // Resolve ByteString params (auto-compute allPrevouts from tx) if !prevouts_indices.is_empty() {