diff --git a/cmd/root.go b/cmd/root.go index 1d21d7b..546b6f5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -27,9 +27,10 @@ import ( homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" + "github.com/prysmaticlabs/go-ssz" "github.com/spf13/cobra" "github.com/spf13/viper" - types "github.com/wealdtech/go-eth2-types/v2" + e2types "github.com/wealdtech/go-eth2-types/v2" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -335,14 +336,42 @@ func accountsFromPath(path string) ([]wtypes.Account, error) { return accounts, nil } -// sign signs data in a domain. -func sign(account wtypes.Account, data []byte, domain []byte) (types.Signature, error) { +// signStruct signs an arbitrary structure. +func signStruct(account wtypes.Account, data interface{}, domain []byte) (e2types.Signature, error) { + objRoot, err := ssz.HashTreeRoot(data) + if err != nil { + return nil, err + } + + return signRoot(account, objRoot, domain) +} + +// SigningContainer is the container for signing roots with a domain. +// Contains SSZ sizes to allow for correct calculation of root. +type SigningContainer struct { + Root []byte `ssz-size:"32"` + Domain []byte `ssz-size:"32"` +} + +// signRoot signs a root. +func signRoot(account wtypes.Account, root [32]byte, domain []byte) (e2types.Signature, error) { + container := &SigningContainer{ + Root: root[:], + Domain: domain, + } + signingRoot, err := ssz.HashTreeRoot(container) + if err != nil { + return nil, err + } + return sign(account, signingRoot[:]) +} + +// sign signs arbitrary data. +func sign(account wtypes.Account, data []byte) (e2types.Signature, error) { if !account.IsUnlocked() { return nil, errors.New("account must be unlocked to sign") } - // TODO combine data and domain for signing. - return account.Sign(data) } diff --git a/cmd/signaturesign.go b/cmd/signaturesign.go index 354bf81..5903a4b 100644 --- a/cmd/signaturesign.go +++ b/cmd/signaturesign.go @@ -22,40 +22,40 @@ import ( "github.com/spf13/viper" pb "github.com/wealdtech/eth2-signer-api/pb/v1" "github.com/wealdtech/go-bytesutil" - types "github.com/wealdtech/go-eth2-types/v2" + e2types "github.com/wealdtech/go-eth2-types/v2" ) // signatureSignCmd represents the signature sign command var signatureSignCmd = &cobra.Command{ Use: "sign", - Short: "Sign data", + Short: "Sign a 32-byte piece of data", Long: `Sign presented data. For example: - ethereal signature sign --data="0x5FfC014343cd971B7eb70732021E26C35B744cc4" --account="Personal wallet/Operations" --passphrase="my account passphrase" + ethereal signature sign --data=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7 --account="Personal wallet/Operations" --passphrase="my account passphrase" In quiet mode this will return 0 if the data can be signed, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { assert(signatureData != "", "--data is required") data, err := bytesutil.FromHexString(signatureData) errCheck(err, "Failed to parse data") + assert(len(data) == 32, "data to sign must be 32 bytes") - domain := types.Domain([]byte{0, 0, 0, 0}, []byte{0, 0, 0, 0}) + domain := e2types.Domain(e2types.DomainType([4]byte{0, 0, 0, 0}), e2types.ZeroForkVersion, e2types.ZeroGenesisValidatorsRoot) if signatureDomain != "" { domainBytes, err := bytesutil.FromHexString(signatureDomain) errCheck(err, "Failed to parse domain") - assert(len(domainBytes) == 8, "Domain data invalid") + assert(len(domainBytes) == 32, "Domain data invalid") } assert(rootAccount != "", "--account is required") - var signature types.Signature + var signature e2types.Signature if remote { signClient := pb.NewSignerClient(remoteGRPCConn) - domainBytes := bytesutil.Bytes64(domain) signReq := &pb.SignRequest{ Id: &pb.SignRequest_Account{Account: rootAccount}, Data: data, - Domain: domainBytes, + Domain: domain, } ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() @@ -67,7 +67,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`, case pb.ResponseState_FAILED: die("Signing request failed") case pb.ResponseState_SUCCEEDED: - signature, err = types.BLSSignatureFromBytes(resp.Signature) + signature, err = e2types.BLSSignatureFromBytes(resp.Signature) errCheck(err, "Invalid signature") } } else { @@ -75,9 +75,10 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`, errCheck(err, "Failed to access account for signing") err = account.Unlock([]byte(rootAccountPassphrase)) errCheck(err, "Failed to unlock account for signing") + var fixedSizeData [32]byte + copy(fixedSizeData[:], data) defer account.Lock() - // TODO fix domain. - signature, err = sign(account, data, []byte{}) + signature, err = signRoot(account, fixedSizeData, domain) errCheck(err, "Failed to sign data") } diff --git a/cmd/signatureverify.go b/cmd/signatureverify.go index 9f45fec..2c9656f 100644 --- a/cmd/signatureverify.go +++ b/cmd/signatureverify.go @@ -17,10 +17,11 @@ import ( "context" "os" + "github.com/prysmaticlabs/go-ssz" "github.com/spf13/cobra" pb "github.com/wealdtech/eth2-signer-api/pb/v1" "github.com/wealdtech/go-bytesutil" - types "github.com/wealdtech/go-eth2-types/v2" + e2types "github.com/wealdtech/go-eth2-types/v2" ) var signatureVerifySignature string @@ -32,28 +33,29 @@ var signatureVerifyCmd = &cobra.Command{ Short: "Verify signed data", Long: `Verify signed data. For example: - ethereal signature verify --data="0x5FfC014343cd971B7eb70732021E26C35B744cc4" --signature="0x8888..." --account="Personal wallet/Operations" + ethereal signature verify --data=0x5f24e819400c6a8ee2bfc014343cd971b7eb707320025a7bcd83e621e26c35b7 --signature=0x8888... --account="Personal wallet/Operations" In quiet mode this will return 0 if the data can be signed, otherwise 1.`, Run: func(cmd *cobra.Command, args []string) { assert(signatureData != "", "--data is required") data, err := bytesutil.FromHexString(signatureData) errCheck(err, "Failed to parse data") + assert(len(data) == 32, "data to verify must be 32 bytes") assert(signatureVerifySignature != "", "--signature is required") signatureBytes, err := bytesutil.FromHexString(signatureVerifySignature) errCheck(err, "Failed to parse signature") - signature, err := types.BLSSignatureFromBytes(signatureBytes) + signature, err := e2types.BLSSignatureFromBytes(signatureBytes) errCheck(err, "Invalid signature") - // domain := types.Domain([]byte{0, 0, 0, 0}, []byte{0, 0, 0, 0}) + domain := e2types.Domain(e2types.DomainType([4]byte{0, 0, 0, 0}), e2types.ZeroForkVersion, e2types.ZeroGenesisValidatorsRoot) if signatureDomain != "" { domainBytes, err := bytesutil.FromHexString(signatureDomain) errCheck(err, "Failed to parse domain") - assert(len(domainBytes) == 8, "Domain data invalid") + assert(len(domainBytes) == 32, "Domain data invalid") } - var pubKey types.PublicKey + var pubKey e2types.PublicKey assert(signatureVerifyPubKey == "" || rootAccount == "", "Either --pubkey or --account should be supplied") if rootAccount != "" { if remote { @@ -67,7 +69,7 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`, errCheck(err, "Failed to access account") assert(resp.State == pb.ResponseState_SUCCEEDED, "Failed to obtain account") assert(len(resp.Accounts) == 1, "No such account") - pubKey, err = types.BLSPublicKeyFromBytes(resp.Accounts[0].PublicKey) + pubKey, err = e2types.BLSPublicKeyFromBytes(resp.Accounts[0].PublicKey) errCheck(err, "Invalid public key provided for account") } else { account, err := accountFromPath(rootAccount) @@ -77,11 +79,17 @@ In quiet mode this will return 0 if the data can be signed, otherwise 1.`, } else { pubKeyBytes, err := bytesutil.FromHexString(signatureVerifyPubKey) errCheck(err, "Invalid public key") - pubKey, err = types.BLSPublicKeyFromBytes(pubKeyBytes) + pubKey, err = e2types.BLSPublicKeyFromBytes(pubKeyBytes) errCheck(err, "Invalid public key") } - // TODO data + domain -> root - verified := signature.Verify(data, pubKey) + container := &SigningContainer{ + Root: data, + Domain: domain, + } + root, err := ssz.HashTreeRoot(container) + errCheck(err, "Failed to create signing root") + + verified := signature.Verify(root[:], pubKey) if !verified { outputIf(!quiet, "Not verified") os.Exit(_exitFailure) diff --git a/cmd/validatordepositdata.go b/cmd/validatordepositdata.go index 4897c8c..a61b6ce 100644 --- a/cmd/validatordepositdata.go +++ b/cmd/validatordepositdata.go @@ -1,4 +1,4 @@ -// Copyright © 2019 Weald Technology Trading +// Copyright © 2019, 2020 Weald Technology Trading // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -20,6 +20,7 @@ import ( "github.com/prysmaticlabs/go-ssz" "github.com/spf13/cobra" + e2types "github.com/wealdtech/go-eth2-types/v2" util "github.com/wealdtech/go-eth2-util" string2eth "github.com/wealdtech/go-string2eth" ) @@ -84,16 +85,12 @@ In quiet mode this will return 0 if the the data can be generated correctly, oth WithdrawalCredentials: withdrawalCredentials, Value: val, } - signingRoot, err := ssz.HashTreeRoot(depositData) - errCheck(err, "Failed to generate deposit data signing root") - outputIf(debug, fmt.Sprintf("Signing root is %x", signingRoot)) - // domain := types.Domain(types.DomainDeposit, []byte{0, 0, 0, 0}) + domain := e2types.Domain(e2types.DomainDeposit, e2types.ZeroForkVersion, e2types.ZeroGenesisValidatorsRoot) err = validatorAccount.Unlock([]byte(rootAccountPassphrase)) errCheck(err, "Failed to unlock validator account") - // TODO data + domain -> root - signature, err := validatorAccount.Sign(signingRoot[:]) + signature, err := signStruct(validatorAccount, depositData, domain) validatorAccount.Lock() - errCheck(err, "Failed to sign deposit data signing root") + errCheck(err, "Failed to generate deposit data signature") signedDepositData := struct { PubKey []byte `ssz-size:"48"` diff --git a/cmd/validatorexit.go b/cmd/validatorexit.go index 3e71d9b..ec77f5e 100644 --- a/cmd/validatorexit.go +++ b/cmd/validatorexit.go @@ -20,10 +20,10 @@ import ( "time" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - ssz "github.com/prysmaticlabs/go-ssz" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/wealdtech/ethdo/grpc" + e2types "github.com/wealdtech/go-eth2-types/v2" ) var validatorExitEpoch int64 @@ -78,16 +78,15 @@ In quiet mode this will return 0 if the transaction has been sent, otherwise 1.` Epoch: currentEpoch, ValidatorIndex: index, } - root, err := ssz.HashTreeRoot(exit) - errCheck(err, "Failed to generate exit proposal root") // TODO fetch current fork version from config (currently using genesis fork version) - // currentForkVersion := config["GenesisForkVersion"].([]byte) - // domain := types.Domain(types.DomainVoluntaryExit, currentForkVersion) + currentForkVersion := config["GenesisForkVersion"].([]byte) + // TODO fetch genesis validators root from somewhere. + //domain := e2types.Domain(e2types.DomainVoluntaryExit, currentForkVersion, genesisValidatorsRoot) + domain := e2types.Domain(e2types.DomainVoluntaryExit, currentForkVersion, e2types.ZeroGenesisValidatorsRoot) err = account.Unlock([]byte(rootAccountPassphrase)) errCheck(err, "Failed to unlock account; please confirm passphrase is correct") - // TODO supply domain - signature, err := sign(account, root[:], []byte{}) + signature, err := signStruct(account, exit, domain) errCheck(err, "Failed to sign exit proposal") proposal := ðpb.SignedVoluntaryExit{ diff --git a/go.mod b/go.mod index dfa4949..35d0b71 100644 --- a/go.mod +++ b/go.mod @@ -29,13 +29,12 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.6.2 - github.com/wealdtech/eth2-signer-api v1.2.0 + github.com/wealdtech/eth2-signer-api v1.3.0 github.com/wealdtech/go-bytesutil v1.1.1 - github.com/wealdtech/go-eth2-types v1.0.0 - github.com/wealdtech/go-eth2-types/v2 v2.0.2 + github.com/wealdtech/go-eth2-types/v2 v2.1.0 github.com/wealdtech/go-eth2-util v1.1.2 github.com/wealdtech/go-eth2-wallet v1.9.2 - github.com/wealdtech/go-eth2-wallet-types v1.10.0 + github.com/wealdtech/go-eth2-wallet-types v1.10.0 // indirect github.com/wealdtech/go-eth2-wallet-types/v2 v2.0.0 github.com/wealdtech/go-string2eth v1.1.0 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 // indirect diff --git a/go.sum b/go.sum index 6daeba9..904c07b 100644 --- a/go.sum +++ b/go.sum @@ -215,8 +215,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/wealdtech/eth2-signer-api v1.2.0 h1:AL4bRJDW6lyRc0ROPruVTEHt7Xs+EV2lRBPen2plOr8= -github.com/wealdtech/eth2-signer-api v1.2.0/go.mod h1:H8OpAoTBl6CaBvZEnhxWDjjWXNc3kwVFKWMAZd6sHlk= +github.com/wealdtech/eth2-signer-api v1.3.0 h1:Fs0GfrdhboBKW7zaMvIvUHJaOB1ibpAmRG3lkB53in4= +github.com/wealdtech/eth2-signer-api v1.3.0/go.mod h1:H8OpAoTBl6CaBvZEnhxWDjjWXNc3kwVFKWMAZd6sHlk= github.com/wealdtech/go-bytesutil v1.0.0/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s= github.com/wealdtech/go-bytesutil v1.0.1 h1:6xzMM+VEHf5WNh1PsIFcRwScgcno+CP8Rw1rGvT6Cew= github.com/wealdtech/go-bytesutil v1.0.1/go.mod h1:jENeMqeTEU8FNZyDFRVc7KqBdRKSnJ9CCh26TcuNb9s= @@ -230,6 +230,8 @@ github.com/wealdtech/go-eth2-types v1.0.0 h1:ggrbQ5HeFcxVm20zxVWr8Sc3uCditaetzWB github.com/wealdtech/go-eth2-types v1.0.0/go.mod h1:fWUgtKQ7hiNVl6263bGeyjlydYuaxkxcUIPIopgz2CM= github.com/wealdtech/go-eth2-types/v2 v2.0.2 h1:L1Eg55aArRpUR2H8dnpSevHlSGRDuRQbQwA4IyYh0Js= github.com/wealdtech/go-eth2-types/v2 v2.0.2/go.mod h1:CXyZc4kcDaJ+s2SglGofkbHbSjXC6zFwKLJpEVYm5tw= +github.com/wealdtech/go-eth2-types/v2 v2.1.0 h1:nGtVjYm4zb/YerzzsKOG48BWLayqJPpZwR1tRkb+PfI= +github.com/wealdtech/go-eth2-types/v2 v2.1.0/go.mod h1:CXyZc4kcDaJ+s2SglGofkbHbSjXC6zFwKLJpEVYm5tw= github.com/wealdtech/go-eth2-util v1.1.2 h1:m56HKJgWSuNy53Gt5GN7HcoFaGRCl1uE3OGWhIhWh1M= github.com/wealdtech/go-eth2-util v1.1.2/go.mod h1:6Enc18JSH8FDy0sv/4ydEBbfoh3EOF++3HqLd66sO3M= github.com/wealdtech/go-eth2-wallet v1.9.2 h1:H/T1n0SNd0jTsbf4rA4YxigsBPFWRUWgobsTOjzW4Hw=