Skip to content

Commit efd3f20

Browse files
Archive key material on wallet closure (#3814)
#Refs: #3797. This PR handles archiving key material on wallet closures. Archiving the wallet data happens in two situations: - when the `WalletClosed` event is received - on node start, if there are closed wallets in the node's wallet registry. Archiving wallet's key material causes the wallet data to be moved to the `archive` directory and the wallet is removed form the registry's cache.. Such wallets will no longer participate in the RFC-12 coordination.
2 parents ea48ec5 + 2743e64 commit efd3f20

File tree

15 files changed

+864
-29
lines changed

15 files changed

+864
-29
lines changed

pkg/chain/ethereum/tbtc.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,39 @@ func (tc *TbtcChain) PastNewWalletRegisteredEvents(
14151415
return convertedEvents, err
14161416
}
14171417

1418+
func (tc *TbtcChain) CalculateWalletID(
1419+
walletPublicKey *ecdsa.PublicKey,
1420+
) ([32]byte, error) {
1421+
return calculateWalletID(walletPublicKey)
1422+
}
1423+
1424+
func calculateWalletID(walletPublicKey *ecdsa.PublicKey) ([32]byte, error) {
1425+
walletPublicKeyBytes, err := convertPubKeyToChainFormat(walletPublicKey)
1426+
if err != nil {
1427+
return [32]byte{}, fmt.Errorf(
1428+
"error while converting wallet public key to chain format: [%v]",
1429+
err,
1430+
)
1431+
}
1432+
1433+
return crypto.Keccak256Hash(walletPublicKeyBytes[:]), nil
1434+
}
1435+
1436+
func (tc *TbtcChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) {
1437+
isWalletRegistered, err := tc.walletRegistry.IsWalletRegistered(
1438+
EcdsaWalletID,
1439+
)
1440+
if err != nil {
1441+
return false, fmt.Errorf(
1442+
"cannot check if wallet with ECDSA ID [0x%x] is registered: [%v]",
1443+
EcdsaWalletID,
1444+
err,
1445+
)
1446+
}
1447+
1448+
return isWalletRegistered, nil
1449+
}
1450+
14181451
func (tc *TbtcChain) GetWallet(
14191452
walletPublicKeyHash [20]byte,
14201453
) (*tbtc.WalletChainData, error) {
@@ -1453,6 +1486,21 @@ func (tc *TbtcChain) GetWallet(
14531486
}, nil
14541487
}
14551488

1489+
func (tc *TbtcChain) OnWalletClosed(
1490+
handler func(event *tbtc.WalletClosedEvent),
1491+
) subscription.EventSubscription {
1492+
onEvent := func(
1493+
walletID [32]byte,
1494+
blockNumber uint64,
1495+
) {
1496+
handler(&tbtc.WalletClosedEvent{
1497+
WalletID: walletID,
1498+
BlockNumber: blockNumber,
1499+
})
1500+
}
1501+
return tc.walletRegistry.WalletClosedEvent(nil, nil).OnEvent(onEvent)
1502+
}
1503+
14561504
func (tc *TbtcChain) ComputeMainUtxoHash(
14571505
mainUtxo *bitcoin.UnspentTransactionOutput,
14581506
) [32]byte {

pkg/chain/ethereum/tbtc_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/ethereum/go-ethereum/common"
1717

1818
"github.com/keep-network/keep-core/internal/testutils"
19+
"github.com/keep-network/keep-core/pkg/chain/local_v1"
1920
"github.com/keep-network/keep-core/pkg/protocol/group"
2021
)
2122

@@ -279,6 +280,49 @@ func TestCalculateInactivityClaimHash(t *testing.T) {
279280
)
280281
}
281282

283+
func TestCalculateWalletID(t *testing.T) {
284+
hexToByte32 := func(hexStr string) [32]byte {
285+
if len(hexStr) != 64 {
286+
t.Fatal("hex string length incorrect")
287+
}
288+
289+
decoded, err := hex.DecodeString(hexStr)
290+
if err != nil {
291+
t.Fatal(err)
292+
}
293+
294+
var result [32]byte
295+
copy(result[:], decoded)
296+
297+
return result
298+
}
299+
300+
xBytes := hexToByte32(
301+
"9a0544440cc47779235ccb76d669590c2cd20c7e431f97e17a1093faf03291c4",
302+
)
303+
304+
yBytes := hexToByte32(
305+
"73e661a208a8a565ca1e384059bd2ff7ff6886df081ff1229250099d388c83df",
306+
)
307+
308+
walletPublicKey := &ecdsa.PublicKey{
309+
Curve: local_v1.DefaultCurve,
310+
X: new(big.Int).SetBytes(xBytes[:]),
311+
Y: new(big.Int).SetBytes(yBytes[:]),
312+
}
313+
314+
actualWalletID, err := calculateWalletID(walletPublicKey)
315+
if err != nil {
316+
t.Fatal(err)
317+
}
318+
319+
expectedWalletID := hexToByte32(
320+
"a6602e554b8cf7c23538fd040e4ff3520ec680e5e5ce9a075259e613a3e5aa79",
321+
)
322+
323+
testutils.AssertBytesEqual(t, expectedWalletID[:], actualWalletID[:])
324+
}
325+
282326
func TestParseDkgResultValidationOutcome(t *testing.T) {
283327
isValid, err := parseDkgResultValidationOutcome(
284328
&struct {

pkg/tbtc/chain.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,35 @@ type DKGParameters struct {
235235
ApprovePrecedencePeriodBlocks uint64
236236
}
237237

238+
// WalletClosedEvent represents a wallet closed event. It is emitted when the
239+
// wallet is closed in the wallet registry.
240+
type WalletClosedEvent struct {
241+
WalletID [32]byte
242+
BlockNumber uint64
243+
}
244+
238245
// BridgeChain defines the subset of the TBTC chain interface that pertains
239246
// specifically to the tBTC Bridge operations.
240247
type BridgeChain interface {
248+
// CalculateWalletID calculates the wallet's ECDSA ID based on the provided
249+
// wallet public key.
250+
CalculateWalletID(walletPublicKey *ecdsa.PublicKey) ([32]byte, error)
251+
252+
// IsWalletRegistered checks whether the given wallet is registered in the
253+
// ECDSA wallet registry.
254+
IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error)
255+
241256
// GetWallet gets the on-chain data for the given wallet. Returns an error
242257
// if the wallet was not found.
243258
GetWallet(walletPublicKeyHash [20]byte) (*WalletChainData, error)
244259

260+
// OnWalletClosed registers a callback that is invoked when an on-chain
261+
// notification of the wallet closed is seen. The notification occurs when
262+
// the wallet is closed or terminated.
263+
OnWalletClosed(
264+
func(event *WalletClosedEvent),
265+
) subscription.EventSubscription
266+
245267
// ComputeMainUtxoHash computes the hash of the provided main UTXO
246268
// according to the on-chain Bridge rules.
247269
ComputeMainUtxoHash(mainUtxo *bitcoin.UnspentTransactionOutput) [32]byte

pkg/tbtc/chain_test.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ import (
1414
"sync"
1515
"time"
1616

17-
"golang.org/x/crypto/sha3"
18-
1917
"github.com/ethereum/go-ethereum/crypto"
2018
"github.com/keep-network/keep-core/pkg/bitcoin"
2119
"github.com/keep-network/keep-core/pkg/chain"
2220
"github.com/keep-network/keep-core/pkg/chain/local_v1"
21+
"github.com/keep-network/keep-core/pkg/internal/byteutils"
2322
"github.com/keep-network/keep-core/pkg/operator"
2423
"github.com/keep-network/keep-core/pkg/protocol/group"
2524
"github.com/keep-network/keep-core/pkg/protocol/inactivity"
2625
"github.com/keep-network/keep-core/pkg/subscription"
2726
"github.com/keep-network/keep-core/pkg/tecdsa/dkg"
27+
"golang.org/x/crypto/sha3"
2828
)
2929

3030
const (
@@ -843,6 +843,40 @@ func buildDepositRequestKey(
843843
return sha256.Sum256(append(fundingTxHash[:], buffer...))
844844
}
845845

846+
func (lc *localChain) CalculateWalletID(
847+
walletPublicKey *ecdsa.PublicKey,
848+
) ([32]byte, error) {
849+
walletPublicKeyBytes, err := convertPubKeyToChainFormat(walletPublicKey)
850+
if err != nil {
851+
return [32]byte{}, fmt.Errorf(
852+
"error while converting wallet public key to chain format: [%v]",
853+
err,
854+
)
855+
}
856+
857+
return crypto.Keccak256Hash(walletPublicKeyBytes[:]), nil
858+
}
859+
860+
func convertPubKeyToChainFormat(publicKey *ecdsa.PublicKey) ([64]byte, error) {
861+
var serialized [64]byte
862+
863+
x, err := byteutils.LeftPadTo32Bytes(publicKey.X.Bytes())
864+
if err != nil {
865+
return serialized, err
866+
}
867+
868+
y, err := byteutils.LeftPadTo32Bytes(publicKey.Y.Bytes())
869+
if err != nil {
870+
return serialized, err
871+
}
872+
873+
serializedBytes := append(x, y...)
874+
875+
copy(serialized[:], serializedBytes)
876+
877+
return serialized, nil
878+
}
879+
846880
func (lc *localChain) GetWallet(walletPublicKeyHash [20]byte) (
847881
*WalletChainData,
848882
error,
@@ -858,6 +892,23 @@ func (lc *localChain) GetWallet(walletPublicKeyHash [20]byte) (
858892
return walletChainData, nil
859893
}
860894

895+
func (lc *localChain) IsWalletRegistered(EcdsaWalletID [32]byte) (bool, error) {
896+
lc.walletsMutex.Lock()
897+
defer lc.walletsMutex.Unlock()
898+
899+
for _, walletData := range lc.wallets {
900+
if EcdsaWalletID == walletData.EcdsaWalletID {
901+
if walletData.State == StateClosed ||
902+
walletData.State == StateTerminated {
903+
return false, nil
904+
}
905+
return true, nil
906+
}
907+
}
908+
909+
return false, fmt.Errorf("wallet not found")
910+
}
911+
861912
func (lc *localChain) setWallet(
862913
walletPublicKeyHash [20]byte,
863914
walletChainData *WalletChainData,
@@ -868,6 +919,12 @@ func (lc *localChain) setWallet(
868919
lc.wallets[walletPublicKeyHash] = walletChainData
869920
}
870921

922+
func (lc *localChain) OnWalletClosed(
923+
handler func(event *WalletClosedEvent),
924+
) subscription.EventSubscription {
925+
panic("unsupported")
926+
}
927+
871928
func (lc *localChain) ComputeMainUtxoHash(
872929
mainUtxo *bitcoin.UnspentTransactionOutput,
873930
) [32]byte {

pkg/tbtc/deduplicator.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const (
1616
// DKGResultHashCachePeriod is the time period the cache maintains
1717
// the given DKG result hash.
1818
DKGResultHashCachePeriod = 7 * 24 * time.Hour
19+
// WalletClosedCachePeriod is the time period the cache maintains the ID of
20+
// a closed wallet.
21+
WalletClosedCachePeriod = 7 * 24 * time.Hour
1922
)
2023

2124
// deduplicator decides whether the given event should be handled by the
@@ -31,15 +34,18 @@ const (
3134
// Those events are supported:
3235
// - DKG started
3336
// - DKG result submitted
37+
// - Wallet closed
3438
type deduplicator struct {
3539
dkgSeedCache *cache.TimeCache
3640
dkgResultHashCache *cache.TimeCache
41+
walletClosedCache *cache.TimeCache
3742
}
3843

3944
func newDeduplicator() *deduplicator {
4045
return &deduplicator{
4146
dkgSeedCache: cache.NewTimeCache(DKGSeedCachePeriod),
4247
dkgResultHashCache: cache.NewTimeCache(DKGResultHashCachePeriod),
48+
walletClosedCache: cache.NewTimeCache(WalletClosedCachePeriod),
4349
}
4450
}
4551

@@ -90,3 +96,23 @@ func (d *deduplicator) notifyDKGResultSubmitted(
9096
// proceed with the execution.
9197
return false
9298
}
99+
100+
func (d *deduplicator) notifyWalletClosed(
101+
WalletID [32]byte,
102+
) bool {
103+
d.walletClosedCache.Sweep()
104+
105+
// Use wallet ID converted to string as the cache key.
106+
cacheKey := hex.EncodeToString(WalletID[:])
107+
108+
// If the key is not in the cache, that means the wallet closure was not
109+
// handled yet and the client should proceed with the execution.
110+
if !d.walletClosedCache.Has(cacheKey) {
111+
d.walletClosedCache.Add(cacheKey)
112+
return true
113+
}
114+
115+
// Otherwise, the wallet closure is a duplicate and the client should not
116+
// proceed with the execution.
117+
return false
118+
}

pkg/tbtc/deduplicator_test.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import (
99
"github.com/keep-network/keep-common/pkg/cache"
1010
)
1111

12-
const testDKGSeedCachePeriod = 1 * time.Second
13-
const testDKGResultHashCachePeriod = 1 * time.Second
12+
const (
13+
testDKGSeedCachePeriod = 1 * time.Second
14+
testDKGResultHashCachePeriod = 1 * time.Second
15+
testWalletClosedCachePeriod = 1 * time.Second
16+
)
1417

1518
func TestNotifyDKGStarted(t *testing.T) {
1619
deduplicator := deduplicator{
@@ -112,3 +115,39 @@ func TestNotifyDKGResultSubmitted(t *testing.T) {
112115
t.Fatal("should be allowed to process")
113116
}
114117
}
118+
119+
func TestNotifyWalletClosed(t *testing.T) {
120+
deduplicator := deduplicator{
121+
walletClosedCache: cache.NewTimeCache(testWalletClosedCachePeriod),
122+
}
123+
124+
wallet1 := [32]byte{1}
125+
wallet2 := [32]byte{2}
126+
127+
// Add the first wallet ID.
128+
canProcess := deduplicator.notifyWalletClosed(wallet1)
129+
if !canProcess {
130+
t.Fatal("should be allowed to process")
131+
}
132+
133+
// Add the second wallet ID.
134+
canProcess = deduplicator.notifyWalletClosed(wallet2)
135+
if !canProcess {
136+
t.Fatal("should be allowed to process")
137+
}
138+
139+
// Add the first wallet ID before caching period elapses.
140+
canProcess = deduplicator.notifyWalletClosed(wallet1)
141+
if canProcess {
142+
t.Fatal("should not be allowed to process")
143+
}
144+
145+
// Wait until caching period elapses.
146+
time.Sleep(testWalletClosedCachePeriod)
147+
148+
// Add the first wallet ID again.
149+
canProcess = deduplicator.notifyWalletClosed(wallet1)
150+
if !canProcess {
151+
t.Fatal("should be allowed to process")
152+
}
153+
}

pkg/tbtc/dkg_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@ func TestDkgExecutor_RegisterSigner(t *testing.T) {
8989
for testName, test := range tests {
9090
t.Run(testName, func(t *testing.T) {
9191
persistenceHandle := &mockPersistenceHandle{}
92-
walletRegistry := newWalletRegistry(persistenceHandle)
92+
chain := Connect()
93+
walletRegistry, err := newWalletRegistry(
94+
persistenceHandle,
95+
chain.CalculateWalletID,
96+
)
97+
if err != nil {
98+
t.Fatal(err)
99+
}
93100

94101
dkgExecutor := &dkgExecutor{
95102
// setting only the fields really needed for this test

0 commit comments

Comments
 (0)