Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ RUN mkdir -p $APP_DIR $TEST_RESULTS_DIR

WORKDIR $APP_DIR

# Get dependencies.
# Get dependencies. The third_party directory holds an in-tree module served
# by a directory `replace` in go.mod, so it must be present before the module
# graph can be resolved.
COPY go.mod go.sum $APP_DIR/
COPY ./third_party $APP_DIR/third_party
RUN go mod download

# Copy source code for generation.
Expand Down
37 changes: 26 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ toolchain go1.25.10

replace (
github.com/bnb-chain/tss-lib => github.com/threshold-network/tss-lib v0.0.0-20260615180949-86bd1a375cc0
// btcd in version v.0.23 extracted `btcd/btcec` to a separate package `btcd/btcec/v2`.
// Some of the dependencies still require the old version, which we workaround
// here:
github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.3
github.com/btcsuite/btcd/v2 => github.com/btcsuite/btcd v0.23.4
// btcd v0.23 extracted `btcd/btcec` into the separate module `btcd/btcec/v2`
// and later btcd versions no longer ship the pre-split package. The tss-lib
// fork (replace above) and first-party key-handling code still import the
// pre-split `btcd/btcec` path, so that single package is served from an
// in-tree copy of the btcec sources from btcd v0.22.3 (unmodified except
// for a mechanical gofmt pass — see third_party/btcsuite/btcec/VENDOR.md
// for provenance and verification). This lets the main btcd module stay
// on a current, fully patched release instead of being downgraded to v0.22.3
// for btcec's sake, which reintroduced fixed security advisories
// (GO-2022-1098, GO-2024-2818, GO-2024-3189) into the Bitcoin consensus,
// wire, and script packages.
github.com/btcsuite/btcd/btcec => ./third_party/btcsuite/btcec
github.com/checksum0/go-electrum => github.com/keep-network/go-electrum v0.0.0-20240206170935-6038cb594daa
// v1.7.1-tlabs.1 fixes the //go:linkname targets in the Ethereum codegen
// (bind -> abigen) so it links against go-ethereum v1.16+.
Expand All @@ -19,11 +26,17 @@ replace (

require (
github.com/bnb-chain/tss-lib v1.3.5
github.com/btcsuite/btcd v0.23.2
github.com/btcsuite/btcd/btcec/v2 v2.2.0
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/btcsuite/btcd/v2 v2.0.0-00010101000000-000000000000
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
// v0.25.0 is the last release with the classic single-module layout. v0.26
// moved wire, txscript, chaincfg and friends into separate /v2 modules and
// dropped the plain import paths, which the go-electrum and tss-lib forks
// above still use. Every advisory open against the former v0.22.3 pin is
// already fixed here, so staying on v0.25.0 is a layout constraint rather
// than a security gap.
github.com/btcsuite/btcd v0.25.0
github.com/btcsuite/btcd/btcec v0.0.0-00010101000000-000000000000
github.com/btcsuite/btcd/btcec/v2 v2.3.5
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/checksum0/go-electrum v0.0.0-20220912200153-b862ac442cf9
github.com/ethereum/go-ethereum v1.17.3
github.com/ferranbt/fastssz v0.1.4
Expand Down Expand Up @@ -60,8 +73,10 @@ require (

require (
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d // indirect
github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/emicklei/dot v1.6.2 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
Expand All @@ -84,6 +99,7 @@ require (
github.com/pion/turn/v2 v2.1.6 // indirect
github.com/pion/webrtc/v3 v3.3.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect
Expand All @@ -96,7 +112,6 @@ require (
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/btcsuite/btcd/btcutil v1.1.1 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/consensys/gnark-crypto v0.18.1 // indirect
Expand Down
59 changes: 43 additions & 16 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/bitcoin/electrum/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"encoding/hex"

"github.com/btcsuite/btcd/v2/wire"
"github.com/btcsuite/btcd/wire"
"github.com/checksum0/go-electrum/electrum"

"github.com/keep-network/keep-core/pkg/bitcoin"
Expand Down
13 changes: 12 additions & 1 deletion pkg/bitcoin/electrum/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"encoding/hex"
"fmt"

"github.com/btcsuite/btcd/v2/wire"
"github.com/btcsuite/btcd/wire"
"github.com/checksum0/go-electrum/electrum"
"github.com/keep-network/keep-core/pkg/bitcoin"
)
Expand All @@ -18,6 +18,17 @@ func decodeTransaction(rawTx string) (*wire.MsgTx, error) {
return nil, fmt.Errorf("failed to decode a hex string: [%w]", err)
}

// The Electrum server is untrusted and the deserialization below panics
// on transactions holding more script data than any consensus-valid
// transaction can carry.
if len(headerBytes) > bitcoin.MaxTransactionByteLength {
return nil, fmt.Errorf(
"transaction byte length [%v] exceeds the maximum of [%v]",
len(headerBytes),
bitcoin.MaxTransactionByteLength,
)
}

buf := bytes.NewBuffer(headerBytes)

var t wire.MsgTx
Expand Down
2 changes: 1 addition & 1 deletion pkg/bitcoin/estimator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package bitcoin

import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)

// About half of all signatures generated with a random nonce are 72-byte, about
Expand Down
2 changes: 1 addition & 1 deletion pkg/bitcoin/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"crypto/sha256"
"fmt"

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
)

// ScriptType represents the possible types of Script.
Expand Down
21 changes: 21 additions & 0 deletions pkg/bitcoin/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ const (
Witness
)

// MaxTransactionByteLength is the maximum byte length of a serialized
// transaction accepted by the deserialization routines. A consensus-valid
// transaction can never reach it: a transaction's weight is
// `3*base_size + total_size` and cannot exceed the maximum block weight of
// 4,000,000 weight units, so its serialized length is always strictly below
// that number.
//
// Rejecting longer input keeps deserialization defensive against untrusted
// sources. The underlying btcd decoder slices all scripts of a single
// transaction out of one fixed-size 4 MiB buffer and panics, rather than
// returning an error, once their cumulative length exceeds it.
const MaxTransactionByteLength = 4_000_000

// Transaction represents a Bitcoin transaction. For reference, see:
// https://developer.bitcoin.org/reference/transactions.html#raw-transaction-format
type Transaction struct {
Expand Down Expand Up @@ -151,6 +164,14 @@ func (t *Transaction) SerializeLocktime() [4]byte {

// Deserialize deserializes the given byte array to a Transaction.
func (t *Transaction) Deserialize(data []byte) error {
if len(data) > MaxTransactionByteLength {
return fmt.Errorf(
"transaction byte length [%v] exceeds the maximum of [%v]",
len(data),
MaxTransactionByteLength,
)
}

internal := newInternalTransaction()
err := internal.Deserialize(bytes.NewReader(data))
if err != nil {
Expand Down
27 changes: 25 additions & 2 deletions pkg/bitcoin/transaction_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type TransactionBuilder struct {
internal *internalTransaction
sigHashArgs []*inputSigHashArgs
sigHashes []*big.Int
// prevOuts holds the locking script and value of the UTXO pointed by
// each added input. The txscript sighash pre-computation consults it
// to determine the witness version of the spent outputs.
prevOuts *txscript.MultiPrevOutFetcher
}

// NewTransactionBuilder constructs a new TransactionBuilder instance.
Expand All @@ -29,6 +33,7 @@ func NewTransactionBuilder(chain Chain) *TransactionBuilder {
chain: chain,
internal: newInternalTransaction(),
sigHashArgs: make([]*inputSigHashArgs, 0),
prevOuts: txscript.NewMultiPrevOutFetcher(nil),
}
}

Expand Down Expand Up @@ -69,6 +74,7 @@ func (tb *TransactionBuilder) AddPublicKeyHashInput(

hash := chainhash.Hash(utxo.Outpoint.TransactionHash)
outpoint := wire.NewOutPoint(&hash, utxo.Outpoint.OutputIndex)
tb.prevOuts.AddPrevOut(*outpoint, wire.NewTxOut(utxo.Value, utxoScript))

// Deliberately set both `signatureScript` and `witness` arguments to nil
// because at this point, the input does not contain any signature data.
Expand Down Expand Up @@ -115,6 +121,7 @@ func (tb *TransactionBuilder) AddScriptHashInput(

hash := chainhash.Hash(utxo.Outpoint.TransactionHash)
outpoint := wire.NewOutPoint(&hash, utxo.Outpoint.OutputIndex)
tb.prevOuts.AddPrevOut(*outpoint, wire.NewTxOut(utxo.Value, utxoScript))

// Signature data required to unlock a P2SH/P2WSH UTXO needs the plain-text
// redeem script to be placed as the last item of the `witness` field for
Expand Down Expand Up @@ -170,8 +177,24 @@ func (tb *TransactionBuilder) ComputeSignatureHashes() ([]*big.Int, error) {
sigHashes := make([]*big.Int, len(tb.internal.TxIn))

// Calculation of sighashes for witness inputs can be faster as common
// sighash fragments can be pre-computed upfront and reused.
witnessSigHashFragments := txscript.NewTxSigHashes(tb.internal.MsgTx)
// sighash fragments can be pre-computed upfront and reused. The previous
// outputs of all added inputs must be provided so the pre-computation can
// determine the witness version of the spent outputs. A missing entry
// makes the pre-computation panic, so make sure the builder's state is
// consistent before handing it over.
for i, input := range tb.internal.TxIn {
if tb.prevOuts.FetchPrevOutput(input.PreviousOutPoint) == nil {
return nil, fmt.Errorf(
"missing previous output for input [%v]",
i,
)
}
}

witnessSigHashFragments := txscript.NewTxSigHashes(
tb.internal.MsgTx,
tb.prevOuts,
)

for i := range tb.internal.TxIn {
sigHashArgs := tb.sigHashArgs[i]
Expand Down
74 changes: 74 additions & 0 deletions pkg/bitcoin/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package bitcoin

import (
"crypto/ecdsa"
"encoding/binary"
"encoding/hex"
"fmt"
"reflect"
"testing"

Expand Down Expand Up @@ -277,6 +279,78 @@ func transactionFrom(t *testing.T, hex string) *Transaction {
return tx
}

// TestTransaction_DeserializeOversized makes sure an oversized transaction is
// rejected with an error. The underlying btcd decoder slices all scripts of a
// single transaction out of one fixed-size 4 MiB buffer and panics once their
// cumulative length exceeds it, so the deserializer must reject such input
// before handing it over. No consensus-valid transaction gets anywhere near
// this length.
func TestTransaction_DeserializeOversized(t *testing.T) {
// A well-formed transaction with a single input and outputs whose scripts
// jointly exceed the decoder's script buffer.
var data []byte

appendVarInt := func(value uint64) {
switch {
case value < 0xfd:
data = append(data, byte(value))
case value <= 0xffff:
data = append(data, 0xfd)
data = binary.LittleEndian.AppendUint16(data, uint16(value))
case value <= 0xffffffff:
data = append(data, 0xfe)
data = binary.LittleEndian.AppendUint32(data, uint32(value))
default:
data = append(data, 0xff)
data = binary.LittleEndian.AppendUint64(data, value)
}
}

const (
outputCount = 5
outputScriptSize = 900000
)

data = binary.LittleEndian.AppendUint32(data, 1) // version
appendVarInt(1) // input count
data = append(data, make([]byte, 32)...) // previous transaction hash
data = binary.LittleEndian.AppendUint32(data, 0) // previous output index
appendVarInt(0) // empty signature script
data = binary.LittleEndian.AppendUint32(data, 0xffffffff)
appendVarInt(outputCount)
for i := 0; i < outputCount; i++ {
data = binary.LittleEndian.AppendUint64(data, 1000) // value
appendVarInt(outputScriptSize)
data = append(data, make([]byte, outputScriptSize)...)
}
data = binary.LittleEndian.AppendUint32(data, 0) // locktime

if len(data) <= MaxTransactionByteLength {
t.Fatalf(
"test transaction of [%v] bytes does not exceed the maximum "+
"of [%v] bytes",
len(data),
MaxTransactionByteLength,
)
}

transaction := new(Transaction)
err := transaction.Deserialize(data)

expectedError := fmt.Errorf(
"transaction byte length [%v] exceeds the maximum of [%v]",
len(data),
MaxTransactionByteLength,
)
if !reflect.DeepEqual(expectedError, err) {
t.Errorf(
"unexpected error\nexpected: %v\nactual: %v",
expectedError,
err,
)
}
}

func hexToSlice(t *testing.T, hexString string) []byte {
bytes, err := hex.DecodeString(hexString)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions third_party/btcsuite/btcec/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ISC License

Copyright (c) 2013-2017 The btcsuite developers
Copyright (c) 2015-2016 The Decred developers

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Loading