Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f6b15b3
fix(tecdsa): TOB-TBTCACEXT-11 — count the third operator's seats in t…
lrsaturnino Jul 1, 2026
0841959
fix(ethereum): TOB-TBTCACEXT-10 — read TxMaxFee from its own field in…
lrsaturnino Jul 1, 2026
169f45f
fix(tbtc): TOB-TBTCACEXT-5 — guard doneSigners map on every access in…
lrsaturnino Jul 1, 2026
a2b258e
fix(retransmission): TOB-TBTCACEXT-6 — synchronize BackoffStrategy ti…
lrsaturnino Jul 1, 2026
798380d
fix(tbtc): TOB-TBTCACEXT-26 — reject signing-done messages from non-a…
lrsaturnino Jul 1, 2026
1ec14a5
fix(tbtc): TOB-TBTCACEXT-70 — reject non-canonical high-S signer appr…
lrsaturnino Jul 1, 2026
4d44e3e
fix(tbtc): TOB-TBTCACEXT-2 — derive P2WSH output script for migration…
lrsaturnino Jul 1, 2026
531f60f
fix(covenantsigner): TOB-TBTCACEXT-65 — use a flat jobs directory so …
lrsaturnino Jul 1, 2026
7419c5e
fix(tbtc): TOB-TBTCACEXT-87 — deauthorize archived and non-live walle…
lrsaturnino Jul 1, 2026
e28237a
fix(covenantsigner): TOB-TBTCACEXT-92 — fail production signer startu…
lrsaturnino Jul 1, 2026
876e4c6
fix(tbtc): TOB-TBTCACEXT-36 — do not archive wallets that are pending…
lrsaturnino Jul 1, 2026
31fbc2d
fix(escrow): TOB-TBTCACEXT-27 — accumulate the withdrawn counter on r…
lrsaturnino Jul 1, 2026
b80fda2
fix(tbtc): TOB-TBTCACEXT-56 — deduplicate DKG results only after hand…
lrsaturnino Jul 1, 2026
1eb2c78
fix(tbtc): TOB-TBTCACEXT-57 — deduplicate wallet closures only after …
lrsaturnino Jul 1, 2026
cb1d17a
fix(tbtc): TOB-TBTCACEXT-72 — deduplicate DKG started events only aft…
lrsaturnino Jul 1, 2026
33fbe68
fix(spv): TOB-TBTCACEXT-22 — scan past spam when discovering SPV proo…
lrsaturnino Jul 1, 2026
874403c
fix(tbtc): TOB-TBTCACEXT-1 — fail closed on covenant signing without …
lrsaturnino Jul 1, 2026
69b2f07
Merge feat/psbt-covenant-final-project-pr into fix/tob-tbtcacext-reme…
lrsaturnino Jul 13, 2026
f7afa4e
feat(covenantsigner): enforce signer approval certificate v2 and fail…
lrsaturnino Jul 16, 2026
20379db
feat(tbtc): bind EndBlock into signer approval certificate v2 and wir…
lrsaturnino Jul 16, 2026
c28e6f8
fix(covenantsigner): accept full uint64 EndBlock and recheck freshnes…
lrsaturnino Jul 16, 2026
6ed4396
test(tbtc): prove signer approval certificate EndBlock handles the fu…
lrsaturnino Jul 16, 2026
b5e1166
fix(covenantsigner): recheck certificate freshness before Submit's st…
lrsaturnino Jul 16, 2026
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
19 changes: 17 additions & 2 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,12 @@ func initCovenantSignerFlags(cmd *cobra.Command, cfg *config.Config) {
false,
"Fail startup when enabled covenant routes are missing route-level approval trust roots. Request-time validation still enforces exact reserve/network trust-root matches.",
)
cmd.Flags().BoolVar(
&cfg.CovenantSigner.BridgeCovenantFraudDefenseConfirmed,
"covenantSigner.bridgeCovenantFraudDefenseConfirmed",
false,
"Set only after confirming the tBTC Bridge covenant fraud-defense path is deployed. Until set, the covenant signer fails closed and refuses to sign, because a covenant signature would otherwise expose the wallet to an undefeatable fraud challenge.",
)
}

// Initialize flags for Maintainer configuration.
Expand Down Expand Up @@ -380,8 +386,17 @@ func initMaintainerFlags(command *cobra.Command, cfg *config.Config) {
&cfg.Maintainer.Spv.TransactionLimit,
"spv.transactionLimit",
spv.DefaultTransactionLimit,
"The maximum number of confirmed transactions returned when getting "+
"transactions for a public key hash.",
"The maximum number of matching (unproven protocol) transactions "+
"returned when getting transactions for a public key hash.",
)

command.Flags().IntVar(
&cfg.Maintainer.Spv.TransactionScanLimit,
"spv.transactionScanLimit",
spv.DefaultTransactionScanLimit,
"The maximum number of not-yet-classified confirmed transactions "+
"examined, per wallet, each time the maintainer searches for "+
"matching (unproven protocol) transactions.",
)

command.Flags().DurationVar(
Expand Down
7 changes: 7 additions & 0 deletions cmd/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ var cmdFlagsTests = map[string]struct {
expectedValueFromFlag: true,
defaultValue: false,
},
"covenantSigner.bridgeCovenantFraudDefenseConfirmed": {
readValueFunc: func(c *config.Config) interface{} { return c.CovenantSigner.BridgeCovenantFraudDefenseConfirmed },
flagName: "--covenantSigner.bridgeCovenantFraudDefenseConfirmed",
flagValue: "",
expectedValueFromFlag: true,
defaultValue: false,
},
"tbtc.preParamsPoolSize": {
readValueFunc: func(c *config.Config) interface{} { return c.Tbtc.PreParamsPoolSize },
flagName: "--tbtc.preParamsPoolSize",
Expand Down
2 changes: 2 additions & 0 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type startDeps struct {
clientInfoRegistry *clientinfo.Registry,
perfMetrics *clientinfo.PerformanceMetrics,
minActiveOutpointConfirmations uint,
bridgeCovenantFraudDefenseConfirmed bool,
) (covenantsigner.Engine, error)
initializeSigner func(
ctx context.Context,
Expand Down Expand Up @@ -254,6 +255,7 @@ func startWithDeps(cmd *cobra.Command, deps startDeps) error {
clientInfoRegistry,
perfMetrics, // Pass the existing performance metrics instance to avoid duplicate registrations
clientConfig.CovenantSigner.MinActiveOutpointConfirmations,
clientConfig.CovenantSigner.BridgeCovenantFraudDefenseConfirmed,
)
if err != nil {
return fmt.Errorf("error initializing TBTC: [%v]", err)
Expand Down
40 changes: 27 additions & 13 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,32 @@ func (tc *TbtcChain) PastDepositRevealedEvents(
return convertedEvents, err
}

// convertPastRedemptionRequestedEvent maps a single on-chain RedemptionRequested
// event to its off-chain tbtc.RedemptionRequestedEvent representation. The event
// carries two distinct fee fields, TreasuryFee and TxMaxFee, that must be mapped
// to their matching destination fields independently; keeping the mapping in a
// standalone helper lets that field-by-field correspondence be unit tested.
func convertPastRedemptionRequestedEvent(
event *tbtcabi.BridgeRedemptionRequested,
) (*tbtc.RedemptionRequestedEvent, error) {
redeemerOutputScript, err := bitcoin.NewScriptFromVarLenData(
event.RedeemerOutputScript,
)
if err != nil {
return nil, err
}

return &tbtc.RedemptionRequestedEvent{
WalletPublicKeyHash: event.WalletPubKeyHash,
RedeemerOutputScript: redeemerOutputScript,
Redeemer: chain.Address(event.Redeemer.Hex()),
RequestedAmount: event.RequestedAmount,
TreasuryFee: event.TreasuryFee,
TxMaxFee: event.TxMaxFee,
BlockNumber: event.Raw.BlockNumber,
}, nil
}

func (tc *TbtcChain) PastRedemptionRequestedEvents(
filter *tbtc.RedemptionRequestedEventFilter,
) ([]*tbtc.RedemptionRequestedEvent, error) {
Expand Down Expand Up @@ -1282,23 +1308,11 @@ func (tc *TbtcChain) PastRedemptionRequestedEvents(

convertedEvents := make([]*tbtc.RedemptionRequestedEvent, 0)
for _, event := range events {
redeemerOutputScript, err := bitcoin.NewScriptFromVarLenData(
event.RedeemerOutputScript,
)
convertedEvent, err := convertPastRedemptionRequestedEvent(event)
if err != nil {
return nil, err
}

convertedEvent := &tbtc.RedemptionRequestedEvent{
WalletPublicKeyHash: event.WalletPubKeyHash,
RedeemerOutputScript: redeemerOutputScript,
Redeemer: chain.Address(event.Redeemer.Hex()),
RequestedAmount: event.RequestedAmount,
TreasuryFee: event.TreasuryFee,
TxMaxFee: event.TreasuryFee,
BlockNumber: event.Raw.BlockNumber,
}

convertedEvents = append(convertedEvents, convertedEvent)
}

Expand Down
86 changes: 86 additions & 0 deletions pkg/chain/ethereum/tbtc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/keep-network/keep-core/pkg/chain"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
commonEthereum "github.com/keep-network/keep-common/pkg/chain/ethereum"

"github.com/keep-network/keep-core/internal/testutils"
Expand All @@ -24,6 +25,91 @@ import (
tbtcpkg "github.com/keep-network/keep-core/pkg/tbtc"
)

func TestConvertPastRedemptionRequestedEvent(t *testing.T) {
// RedeemerOutputScript is stored on-chain as variable-length data (a
// CompactSizeUint length prefix followed by the script bytes), so build the
// fixture from a known script to guarantee the helper can parse it back.
redeemerOutputScript := bitcoin.Script([]byte{0x76, 0xa9, 0x14})
redeemerOutputScriptVarLen, err := redeemerOutputScript.ToVarLenData()
if err != nil {
t.Fatal(err)
}

walletPublicKeyHash := [20]byte{1, 2, 3}
redeemer := common.HexToAddress("0x1111111111111111111111111111111111111111")

// The treasury fee and the per-transaction max fee are two independent
// on-chain fields. They are set to distinct values so that a mapping which
// reads the wrong source (e.g. populating TxMaxFee from TreasuryFee) is
// caught rather than masked by equal values.
const (
requestedAmount = uint64(1_000_000)
treasuryFee = uint64(2_000)
txMaxFee = uint64(7_500)
blockNumber = uint64(123_456)
)

event := &tbtcabi.BridgeRedemptionRequested{
WalletPubKeyHash: walletPublicKeyHash,
RedeemerOutputScript: redeemerOutputScriptVarLen,
Redeemer: redeemer,
RequestedAmount: requestedAmount,
TreasuryFee: treasuryFee,
TxMaxFee: txMaxFee,
Raw: types.Log{BlockNumber: blockNumber},
}

converted, err := convertPastRedemptionRequestedEvent(event)
if err != nil {
t.Fatalf("unexpected error: [%v]", err)
}

testutils.AssertUintsEqual(
t,
"requested amount",
requestedAmount,
converted.RequestedAmount,
)

// Both fee fields must be populated from their own distinct source field.
testutils.AssertUintsEqual(
t,
"treasury fee",
treasuryFee,
converted.TreasuryFee,
)
testutils.AssertUintsEqual(
t,
"tx max fee",
txMaxFee,
converted.TxMaxFee,
)

testutils.AssertUintsEqual(
t,
"block number",
blockNumber,
converted.BlockNumber,
)

testutils.AssertBytesEqual(t, redeemerOutputScript, converted.RedeemerOutputScript)

testutils.AssertStringsEqual(
t,
"redeemer",
chain.Address(redeemer.Hex()).String(),
converted.Redeemer.String(),
)

if converted.WalletPublicKeyHash != walletPublicKeyHash {
t.Errorf(
"unexpected wallet public key hash\nexpected: [%x]\nactual: [%x]",
walletPublicKeyHash,
converted.WalletPublicKeyHash,
)
}
}

func TestComputeOperatorsIDsHash(t *testing.T) {
operatorIDs := []chain.OperatorID{
5, 1, 55, 45435534, 33, 345, 23, 235, 3333, 2,
Expand Down
8 changes: 8 additions & 0 deletions pkg/covenantsigner/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ type Config struct {
// covenant signer accepts it. When zero (unset), the system defaults to 6
// to align with the deposit sweep finality threshold.
MinActiveOutpointConfirmations uint `mapstructure:"minActiveOutpointConfirmations"`
// BridgeCovenantFraudDefenseConfirmed must be set only when the operator has
// confirmed that the tBTC Bridge recognizes covenant active UTXO spends as
// honest spends in Fraud.defeatFraudChallenge (the covenant fraud-defense
// path is deployed). Until then the covenant signer fails closed and refuses
// to produce signatures, because a covenant signature over a covenant active
// UTXO would otherwise be a valid, undefeatable tBTC fraud proof that could
// slash the signing wallet.
BridgeCovenantFraudDefenseConfirmed bool `mapstructure:"bridgeCovenantFraudDefenseConfirmed"`
// DataDir is the base directory path used by the disk persistence handle.
// When set, the store acquires an exclusive file lock to prevent concurrent
// process corruption. When empty, file locking is skipped.
Expand Down
Loading
Loading