wormchain: add admin-sign command to continue wormchain validator enrollment
This commit is contained in:
parent
2bc47889f3
commit
63a5654c02
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/status-im/keycard-go/hexutils"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
|
@ -61,6 +63,7 @@ func init() {
|
|||
AdminCmd.AddCommand(AdminClientFindMissingMessagesCmd)
|
||||
AdminCmd.AddCommand(AdminClientGovernanceVAAVerifyCmd)
|
||||
AdminCmd.AddCommand(AdminClientListNodes)
|
||||
AdminCmd.AddCommand(AdminClientSignWormchainAddress)
|
||||
AdminCmd.AddCommand(DumpVAAByMessageID)
|
||||
AdminCmd.AddCommand(SendObservationRequest)
|
||||
AdminCmd.AddCommand(ClientChainGovernorStatusCmd)
|
||||
|
@ -76,6 +79,13 @@ var AdminCmd = &cobra.Command{
|
|||
Short: "Guardian node admin commands",
|
||||
}
|
||||
|
||||
var AdminClientSignWormchainAddress = &cobra.Command{
|
||||
Use: "sign-wormchain-address [/path/to/guardianKey] [wormchain-validator-address]",
|
||||
Short: "Sign a wormchain validator address. Only sign the address that you control the key for and will be for your validator.",
|
||||
RunE: runSignWormchainValidatorAddress,
|
||||
Args: cobra.ExactArgs(2),
|
||||
}
|
||||
|
||||
var AdminClientInjectGuardianSetUpdateCmd = &cobra.Command{
|
||||
Use: "governance-vaa-inject [FILENAME]",
|
||||
Short: "Inject and sign a governance VAA from a prototxt file (see docs!)",
|
||||
|
@ -168,6 +178,30 @@ func getPublicRPCServiceClient(ctx context.Context, addr string) (*grpc.ClientCo
|
|||
return conn, c, err
|
||||
}
|
||||
|
||||
func runSignWormchainValidatorAddress(cmd *cobra.Command, args []string) error {
|
||||
guardianKeyPath := args[0]
|
||||
wormchainAddress := args[1]
|
||||
if !strings.HasPrefix(wormchainAddress, "wormhole") || strings.HasPrefix(wormchainAddress, "wormholeval") {
|
||||
return fmt.Errorf("must provide a bech32 address that has 'wormhole' prefix")
|
||||
}
|
||||
gk, err := loadGuardianKey(guardianKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load guardian key: %w", err)
|
||||
}
|
||||
addr, err := getFromBech32(wormchainAddress, "wormhole")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode wormchain address: %w", err)
|
||||
}
|
||||
// Hash and sign address
|
||||
addrHash := crypto.Keccak256Hash(sdk.SignedWormchainAddressPrefix, addr)
|
||||
sig, err := crypto.Sign(addrHash[:], gk)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign wormchain address: %w", err)
|
||||
}
|
||||
fmt.Println(hex.EncodeToString(sig))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runInjectGovernanceVAA(cmd *cobra.Command, args []string) {
|
||||
path := args[0]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
package guardiand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO(cpatrick): replace this whole file with just "github.com/cosmos/cosmos-sdk/types"
|
||||
// This is added to avoid changing go.mod for guardiand for now.
|
||||
|
||||
// The code in this file was faithfully copied from: https://github.com/scrtlabs/btcutil/blob/master/bech32/bech32.go
|
||||
// The code in this "bech32.go" file, and only this file, has the following license:
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2016-2017 The Lightning Network 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.
|
||||
*/
|
||||
|
||||
// toChars converts the byte slice 'data' to a string where each byte in 'data'
|
||||
// encodes the index of a character in 'charset'.
|
||||
func toChars(data []byte) (string, error) {
|
||||
result := make([]byte, 0, len(data))
|
||||
for _, b := range data {
|
||||
if int(b) >= len(charset) {
|
||||
return "", fmt.Errorf("invalid data byte: %v", b)
|
||||
}
|
||||
result = append(result, charset[b])
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// For more details on the checksum calculation, please refer to BIP 173.
|
||||
func bech32Checksum(hrp string, data []byte) []byte {
|
||||
// Convert the bytes to list of integers, as this is needed for the
|
||||
// checksum calculation.
|
||||
integers := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
integers[i] = int(b)
|
||||
}
|
||||
values := append(bech32HrpExpand(hrp), integers...)
|
||||
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
|
||||
polymod := bech32Polymod(values) ^ 1
|
||||
var res []byte
|
||||
for i := 0; i < 6; i++ {
|
||||
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// For more details on the polymod calculation, please refer to BIP 173.
|
||||
func bech32Polymod(values []int) int {
|
||||
chk := 1
|
||||
for _, v := range values {
|
||||
b := chk >> 25
|
||||
chk = (chk&0x1ffffff)<<5 ^ v
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
chk ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return chk
|
||||
}
|
||||
|
||||
// For more details on HRP expansion, please refer to BIP 173.
|
||||
func bech32HrpExpand(hrp string) []int {
|
||||
v := make([]int, 0, len(hrp)*2+1)
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
v = append(v, int(hrp[i]>>5))
|
||||
}
|
||||
v = append(v, 0)
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
v = append(v, int(hrp[i]&31))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// For more details on the checksum verification, please refer to BIP 173.
|
||||
func bech32VerifyChecksum(hrp string, data []byte) bool {
|
||||
integers := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
integers[i] = int(b)
|
||||
}
|
||||
concat := append(bech32HrpExpand(hrp), integers...)
|
||||
return bech32Polymod(concat) == 1
|
||||
}
|
||||
|
||||
// toBytes converts each character in the string 'chars' to the value of the
|
||||
// index of the correspoding character in 'charset'.
|
||||
func toBytes(chars string) ([]byte, error) {
|
||||
decoded := make([]byte, 0, len(chars))
|
||||
for i := 0; i < len(chars); i++ {
|
||||
index := strings.IndexByte(charset, chars[i])
|
||||
if index < 0 {
|
||||
return nil, fmt.Errorf("invalid character not part of "+
|
||||
"charset: %v", chars[i])
|
||||
}
|
||||
decoded = append(decoded, byte(index))
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||
|
||||
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
|
||||
|
||||
// Decode decodes a bech32 encoded string, returning the human-readable
|
||||
// part and the data part excluding the checksum.
|
||||
func decode(bech string, limit int) (string, []byte, error) {
|
||||
// The maximum allowed length for a bech32 string is 90. It must also
|
||||
// be at least 8 characters, since it needs a non-empty HRP, a
|
||||
// separator, and a 6 character checksum.
|
||||
if len(bech) < 8 || len(bech) > limit {
|
||||
return "", nil, fmt.Errorf("invalid bech32 string length %d",
|
||||
len(bech))
|
||||
}
|
||||
// Only ASCII characters between 33 and 126 are allowed.
|
||||
for i := 0; i < len(bech); i++ {
|
||||
if bech[i] < 33 || bech[i] > 126 {
|
||||
return "", nil, fmt.Errorf("invalid character in "+
|
||||
"string: '%c'", bech[i])
|
||||
}
|
||||
}
|
||||
|
||||
// The characters must be either all lowercase or all uppercase.
|
||||
lower := strings.ToLower(bech)
|
||||
upper := strings.ToUpper(bech)
|
||||
if bech != lower && bech != upper {
|
||||
return "", nil, fmt.Errorf("string not all lowercase or all " +
|
||||
"uppercase")
|
||||
}
|
||||
|
||||
// We'll work with the lowercase string from now on.
|
||||
bech = lower
|
||||
|
||||
// The string is invalid if the last '1' is non-existent, it is the
|
||||
// first character of the string (no human-readable part) or one of the
|
||||
// last 6 characters of the string (since checksum cannot contain '1'),
|
||||
// or if the string is more than 90 characters in total.
|
||||
one := strings.LastIndexByte(bech, '1')
|
||||
if one < 1 || one+7 > len(bech) {
|
||||
return "", nil, fmt.Errorf("invalid index of 1")
|
||||
}
|
||||
|
||||
// The human-readable part is everything before the last '1'.
|
||||
hrp := bech[:one]
|
||||
data := bech[one+1:]
|
||||
|
||||
// Each character corresponds to the byte with value of the index in
|
||||
// 'charset'.
|
||||
decoded, err := toBytes(data)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed converting data to bytes: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
if !bech32VerifyChecksum(hrp, decoded) {
|
||||
moreInfo := ""
|
||||
checksum := bech[len(bech)-6:]
|
||||
expected, err := toChars(bech32Checksum(hrp,
|
||||
decoded[:len(decoded)-6]))
|
||||
if err == nil {
|
||||
moreInfo = fmt.Sprintf("Expected %v, got %v.",
|
||||
expected, checksum)
|
||||
}
|
||||
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
|
||||
}
|
||||
|
||||
// We exclude the last 6 bytes, which is the checksum.
|
||||
return hrp, decoded[:len(decoded)-6], nil
|
||||
}
|
||||
|
||||
func decodeAndConvert(bech string) (string, []byte, error) {
|
||||
hrp, data, err := decode(bech, 1023)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("decoding bech32 failed: %w", err)
|
||||
}
|
||||
|
||||
converted, err := convertBits(data, 5, 8, false)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("decoding bech32 failed: %w", err)
|
||||
}
|
||||
|
||||
return hrp, converted, nil
|
||||
}
|
||||
|
||||
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
|
||||
// to a byte slice where each byte is encoding toBits bits.
|
||||
func convertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
|
||||
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
|
||||
return nil, fmt.Errorf("only bit groups between 1 and 8 allowed")
|
||||
}
|
||||
|
||||
// The final bytes, each byte encoding toBits bits.
|
||||
var regrouped []byte
|
||||
|
||||
// Keep track of the next byte we create and how many bits we have
|
||||
// added to it out of the toBits goal.
|
||||
nextByte := byte(0)
|
||||
filledBits := uint8(0)
|
||||
|
||||
for _, b := range data {
|
||||
|
||||
// Discard unused bits.
|
||||
b = b << (8 - fromBits)
|
||||
|
||||
// How many bits remaining to extract from the input data.
|
||||
remFromBits := fromBits
|
||||
for remFromBits > 0 {
|
||||
// How many bits remaining to be added to the next byte.
|
||||
remToBits := toBits - filledBits
|
||||
|
||||
// The number of bytes to next extract is the minimum of
|
||||
// remFromBits and remToBits.
|
||||
toExtract := remFromBits
|
||||
if remToBits < toExtract {
|
||||
toExtract = remToBits
|
||||
}
|
||||
|
||||
// Add the next bits to nextByte, shifting the already
|
||||
// added bits to the left.
|
||||
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract))
|
||||
|
||||
// Discard the bits we just extracted and get ready for
|
||||
// next iteration.
|
||||
b = b << toExtract
|
||||
remFromBits -= toExtract
|
||||
filledBits += toExtract
|
||||
|
||||
// If the nextByte is completely filled, we add it to
|
||||
// our regrouped bytes and start on the next byte.
|
||||
if filledBits == toBits {
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We pad any unfinished group if specified.
|
||||
if pad && filledBits > 0 {
|
||||
nextByte = nextByte << (toBits - filledBits)
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
|
||||
// Any incomplete group must be <= 4 bits, and all zeroes.
|
||||
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
|
||||
return nil, fmt.Errorf("invalid incomplete group")
|
||||
}
|
||||
|
||||
return regrouped, nil
|
||||
}
|
||||
|
||||
// GetFromBech32 decodes a bytestring from a Bech32 encoded string.
|
||||
func getFromBech32(bech32str, prefix string) ([]byte, error) {
|
||||
if len(bech32str) == 0 {
|
||||
return nil, fmt.Errorf("zero length bech32 address")
|
||||
}
|
||||
|
||||
hrp, bz, err := decodeAndConvert(bech32str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hrp != prefix {
|
||||
return nil, fmt.Errorf("invalid Bech32 prefix; expected %s, got %s", prefix, hrp)
|
||||
}
|
||||
|
||||
return bz, nil
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package sdk
|
||||
|
||||
var (
|
||||
HeartbeatMessagePrefix = []byte("heartbeat|")
|
||||
|
||||
SignedObservationRequestPrefix_old = []byte("signed_observation_request|")
|
||||
SignedObservationRequestPrefix = []byte("signed_observation_request_000000|")
|
||||
SignedWormchainAddressPrefix = []byte("signed_wormchain_address_00000000|")
|
||||
)
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/wormhole-foundation/wormchain/x/wormhole/keeper"
|
||||
"github.com/wormhole-foundation/wormchain/x/wormhole/types"
|
||||
wormholesdk "github.com/wormhole-foundation/wormhole/sdk"
|
||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||
)
|
||||
|
||||
|
@ -55,7 +56,7 @@ func GetGenesisCmd() *cobra.Command {
|
|||
cmd.AddCommand(CmdGenerateVaa())
|
||||
cmd.AddCommand(CmdGenerateGovernanceVaa())
|
||||
cmd.AddCommand(CmdGenerateGuardianSetUpdatea())
|
||||
cmd.AddCommand(CmdSignAddress())
|
||||
cmd.AddCommand(CmdTestSignAddress())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -406,10 +407,10 @@ func CmdGenerateGuardianSetUpdatea() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func CmdSignAddress() *cobra.Command {
|
||||
func CmdTestSignAddress() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "sign-address",
|
||||
Short: "sign the validator address to use for registering as a guardian. read guardian key as hex in $GUARDIAN_KEY env variable. use --from to indicate address to sign.",
|
||||
Use: "test-sign-address",
|
||||
Short: "Test method sign the validator address to use for registering as a guardian. Use guardiand for production, not this method. Read guardian key as hex in $GUARDIAN_KEY env variable. use --from to indicate address to sign.",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
|
@ -427,7 +428,7 @@ func CmdSignAddress() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
addr := info.GetAddress()
|
||||
addrHash := crypto.Keccak256Hash(addr)
|
||||
addrHash := crypto.Keccak256Hash(wormholesdk.SignedWormchainAddressPrefix, addr)
|
||||
sig, err := crypto.Sign(addrHash[:], key)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/wormhole-foundation/wormchain/x/wormhole/types"
|
||||
wormholesdk "github.com/wormhole-foundation/wormhole/sdk"
|
||||
)
|
||||
|
||||
// TODO(csongor): high-level overview of what this does
|
||||
|
@ -19,9 +20,8 @@ func (k msgServer) RegisterAccountAsGuardian(goCtx context.Context, msg *types.M
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// recover guardian key from signature
|
||||
signerHash := crypto.Keccak256Hash(signer)
|
||||
signerHash := crypto.Keccak256Hash(wormholesdk.SignedWormchainAddressPrefix, signer)
|
||||
guardianKey, err := crypto.Ecrecover(signerHash.Bytes(), msg.Signature)
|
||||
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue