@@ -29,9 +29,11 @@ import (
29
29
capella "github.com/attestantio/go-eth2-client/spec/capella"
30
30
"github.com/attestantio/go-eth2-client/spec/phase0"
31
31
"github.com/pkg/errors"
32
+ "github.com/prysmaticlabs/go-ssz"
32
33
standardchaintime "github.com/wealdtech/ethdo/services/chaintime/standard"
33
34
"github.com/wealdtech/ethdo/signing"
34
35
"github.com/wealdtech/ethdo/util"
36
+ e2types "github.com/wealdtech/go-eth2-types/v2"
35
37
ethutil "github.com/wealdtech/go-eth2-util"
36
38
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
37
39
)
@@ -256,10 +258,12 @@ func (c *command) dumpRequiredInformation(_ context.Context) error {
256
258
func (c * command ) generateOperations (ctx context.Context ) error {
257
259
if c .account == "" && c .mnemonic == "" && c .privateKey == "" && c .validator == "" {
258
260
// No input information; fetch the operations from a file.
259
- if err := c .loadOperations (ctx ); err == nil {
261
+ err := c .loadOperations (ctx )
262
+ if err == nil {
263
+ // Success.
260
264
return nil
261
265
}
262
- return fmt .Errorf ("no account, mnemonic or private key specified and no %s file found; cannot proceed " , changeOperationsFilename )
266
+ return fmt .Errorf ("no account, mnemonic or private key specified and no %s file loaded: %v " , changeOperationsFilename , err )
263
267
}
264
268
265
269
if c .mnemonic != "" {
@@ -330,10 +334,7 @@ func (c *command) loadOperations(ctx context.Context) error {
330
334
// If not, read it from the file with the standard name.
331
335
_ , err := os .Stat (changeOperationsFilename )
332
336
if err != nil {
333
- if c .debug {
334
- fmt .Fprintf (os .Stderr , "Failed to read change operations file: %v\n " , err )
335
- }
336
- return err
337
+ return errors .Wrap (err , "failed to read change operations file" )
337
338
}
338
339
if c .debug {
339
340
fmt .Fprintf (os .Stderr , "%s found; loading operations\n " , changeOperationsFilename )
@@ -346,6 +347,47 @@ func (c *command) loadOperations(ctx context.Context) error {
346
347
return errors .Wrap (err , "failed to parse change operations file" )
347
348
}
348
349
350
+ for _ , op := range c .signedOperations {
351
+ if err := c .verifyOperation (ctx , op ); err != nil {
352
+ return err
353
+ }
354
+ }
355
+
356
+ return nil
357
+ }
358
+
359
+ func (c * command ) verifyOperation (ctx context.Context , op * capella.SignedBLSToExecutionChange ) error {
360
+ root , err := op .Message .HashTreeRoot ()
361
+ if err != nil {
362
+ return errors .Wrap (err , "failed to generate message root" )
363
+ }
364
+
365
+ sigBytes := make ([]byte , len (op .Signature ))
366
+ copy (sigBytes , op .Signature [:])
367
+ sig , err := e2types .BLSSignatureFromBytes (sigBytes )
368
+ if err != nil {
369
+ return errors .Wrap (err , "invalid signature" )
370
+ }
371
+
372
+ container := & phase0.SigningData {
373
+ ObjectRoot : root ,
374
+ Domain : c .chainInfo .Domain ,
375
+ }
376
+ signingRoot , err := ssz .HashTreeRoot (container )
377
+ if err != nil {
378
+ return errors .Wrap (err , "failed to generate signing root" )
379
+ }
380
+
381
+ pubkeyBytes := make ([]byte , len (op .Message .FromBLSPubkey ))
382
+ copy (pubkeyBytes , op .Message .FromBLSPubkey [:])
383
+ pubkey , err := e2types .BLSPublicKeyFromBytes (pubkeyBytes )
384
+ if err != nil {
385
+ return errors .Wrap (err , "invalid public key" )
386
+ }
387
+ if ! sig .Verify (signingRoot [:], pubkey ) {
388
+ return errors .New ("signature does not verify" )
389
+ }
390
+
349
391
return nil
350
392
}
351
393
@@ -567,6 +609,9 @@ func (c *command) createSignedOperation(ctx context.Context,
567
609
if err != nil {
568
610
return nil , err
569
611
}
612
+ if c .debug {
613
+ fmt .Fprintf (os .Stderr , "Using %#x as best public key for %s\n " , pubkey .Marshal (), withdrawalAccount .Name ())
614
+ }
570
615
blsPubkey := phase0.BLSPubKey {}
571
616
copy (blsPubkey [:], pubkey .Marshal ())
572
617
0 commit comments