From fa6212ec4c7a1f53467a1f7e460d9d9bf894100a Mon Sep 17 00:00:00 2001 From: Marko Boben Date: Thu, 5 Dec 2024 01:08:57 +0100 Subject: [PATCH] Implemented public key registration in case of eth style signatures --- indexer/cronjob/mirror.go | 19 ++++---- utils/chain/hashing.go | 37 +++++++++++++++ utils/chain/p_chain_pk_test.go | 83 ++++++++++++++++++++++++---------- utils/chain/p_chain_tx.go | 70 +++++++++++++++++----------- 4 files changed, 149 insertions(+), 60 deletions(-) create mode 100644 utils/chain/hashing.go diff --git a/indexer/cronjob/mirror.go b/indexer/cronjob/mirror.go index d33101b..b6a1533 100644 --- a/indexer/cronjob/mirror.go +++ b/indexer/cronjob/mirror.go @@ -332,19 +332,18 @@ func (c *mirrorCronJob) registerAddress(txID string, address string) error { return errors.New("tx not found") } - publicKeys, err := chain.PublicKeysFromPChainBlock(txID, tx.Bytes) + addrBytes, err := chain.ParseAddress(address) if err != nil { - return err + return errors.Wrap(err, "chain.ParseAddress") } - if tx.InputIndex >= uint32(len(publicKeys)) { - return errors.New("input index out of range") + + publicKey, err := chain.PublicKeyFromPChainBlock(txID, addrBytes, tx.InputIndex, tx.Bytes) + if err != nil { + return err } - publicKey := publicKeys[tx.InputIndex] - for _, k := range publicKey { - err := c.contracts.RegisterPublicKey(k) - if err != nil { - return errors.Wrap(err, "mirroringContract.RegisterPublicKey") - } + err = c.contracts.RegisterPublicKey(publicKey) + if err != nil { + return errors.Wrap(err, "mirroringContract.RegisterPublicKey") } c.registeredAddresses.Add(address) logger.Info("registered address %s on address binder contract", address) diff --git a/utils/chain/hashing.go b/utils/chain/hashing.go new file mode 100644 index 0000000..a240a41 --- /dev/null +++ b/utils/chain/hashing.go @@ -0,0 +1,37 @@ +package chain + +import ( + "fmt" + + "golang.org/x/crypto/sha3" +) + +// Taken from github.com/ava-labs/coreth/accounts/accounts.go to avoid dependency on coreth + +// TextHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextHash(data []byte) []byte { + hash, _ := TextAndHash(data) + return hash +} + +// TextAndHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func TextAndHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data)) + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(msg)) + return hasher.Sum(nil), msg +} diff --git a/utils/chain/p_chain_pk_test.go b/utils/chain/p_chain_pk_test.go index 73baa8e..0aa0957 100644 --- a/utils/chain/p_chain_pk_test.go +++ b/utils/chain/p_chain_pk_test.go @@ -3,26 +3,28 @@ package chain import ( "encoding/hex" "testing" + + "github.com/ava-labs/avalanchego/utils/formatting/address" ) func TestPublicKeysFromProposalBlock(t *testing.T) { - hex, err := hex.DecodeString("000000000000826FB5EA1379555D479E3C87A4F76E5F0C42529FDF0EB29DB76DD67A5E64A78F000000006745E0B50000000000001A8600000000000001FF000000000000588C7E625CB1463441FE927D9CA8DC638666F3F27BBA3CF1A769065340B5F06D0000000000001A870000000E0000007200000000000000000000000000000000000000000000000000000000000000000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D2000000000000000000000000001000000019D18C04FC87D206177303996C1D366D6CB401752000000016C39BD263CF1FA57BA28A80E1BF8472FE77854EBD98B977D8BF893EF99AB6BB10000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000500005AF3107A40000000000100000000000000009DFABB9DF1E96C6391C44D7BA383FC0856F37796000000006745E2AC00000000675857AC00002D79883D20000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D2000000000000000000000000001000000019D18C04FC87D206177303996C1D366D6CB4017520000000B000000000000000000000001000000019D18C04FC87D206177303996C1D366D6CB4017520000000100000009000000017DCB61D3051A582599B595B913056EE2A75F4480ECEF6920DF93DB16CD9D7F9258ECF9FE5A4A46F1B998D4F77F98ECA14754CFEAFA20C34BB16A0652330629B20000000000") + txBytes, err := hex.DecodeString("000000000000826FB5EA1379555D479E3C87A4F76E5F0C42529FDF0EB29DB76DD67A5E64A78F000000006745E0B50000000000001A8600000000000001FF000000000000588C7E625CB1463441FE927D9CA8DC638666F3F27BBA3CF1A769065340B5F06D0000000000001A870000000E0000007200000000000000000000000000000000000000000000000000000000000000000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D2000000000000000000000000001000000019D18C04FC87D206177303996C1D366D6CB401752000000016C39BD263CF1FA57BA28A80E1BF8472FE77854EBD98B977D8BF893EF99AB6BB10000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000500005AF3107A40000000000100000000000000009DFABB9DF1E96C6391C44D7BA383FC0856F37796000000006745E2AC00000000675857AC00002D79883D20000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D2000000000000000000000000001000000019D18C04FC87D206177303996C1D366D6CB4017520000000B000000000000000000000001000000019D18C04FC87D206177303996C1D366D6CB4017520000000100000009000000017DCB61D3051A582599B595B913056EE2A75F4480ECEF6920DF93DB16CD9D7F9258ECF9FE5A4A46F1B998D4F77F98ECA14754CFEAFA20C34BB16A0652330629B20000000000") if err != nil { t.Fatal(err) } - pks, err := PublicKeysFromPChainBlock("2JXfmg5DmADsQsSu5Kb1xRa8zJTkPBVM4FtKembYCj8KVWyHU7", hex) + address := "costwo1n5vvqn7g05sxzaes8xtvr5mx6m95q96jesrg5g" + addressBytes, err := parseAddress(address) if err != nil { t.Fatal(err) } - if len(pks) != 1 { - t.Fatal("Expected 1 input") - } - if len(pks[0]) != 1 { - t.Fatal("Expected one pk") + pk, err := PublicKeyFromPChainBlock("2JXfmg5DmADsQsSu5Kb1xRa8zJTkPBVM4FtKembYCj8KVWyHU7", addressBytes, 0, txBytes) + if err != nil { + t.Fatal(err) } - ethAddress, err := PublicKeyToEthAddress(pks[0][0]) + + ethAddress, err := PublicKeyToEthAddress(pk) if err != nil { t.Fatal(err) } @@ -31,31 +33,64 @@ func TestPublicKeysFromProposalBlock(t *testing.T) { } } -func TestPublicKeysFromStandardBlock(t *testing.T) { - hex, err := hex.DecodeString("00000000000082146647D70EBD1E0C735CE8C7CE95B951A7077AE3276A9CFDBE5920FF6654C00000000067460C080000000000001A8D000000000000048F0000000000200000000067460C08AD2801124E11CC33EDAAA752007C7D428F01BE6EA2C0B266AA63CF085D21E9460000000000001A8E000000010000000E0000007200000000000000000000000000000000000000000000000000000000000000000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000007000000E987662BC000000000000000000000000100000001237605140994862F6BD6F3A3740EE6F586BBDAFF0000000575F4E911AC0D6797086FA6EA4BCF4B278955B29F5874252D4F35C61571BA3DF50000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000003B9ACA000000000100000000844DC320BD32F382FF20E64912A9574146B4E7C207B780CE029847E55699E00D0000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000003B8B87C00000000100000000E2589EB05CAFF60AB53EBB0B5F2AE8CAABEF7E9C50AF882800A3F7FA1AB82FF10000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000500002D79882DDDC00000000100000000E33ADDBE17D9B2DF8DB8AA7E7F107792C193477945D0242188B18DC6E8BF50C90000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000003BAA0C400000000100000000E33ADDBE17D9B2DF8DB8AA7E7F107792C193477945D0242188B18DC6E8BF50C90000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000E8D4A51000000000010000000000000000D52B09D698E36EE1406681AA40CFA53414B582EB0000000067460CDC00000000675881DC00002D79883D20000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D200000000000000000000000000100000001237605140994862F6BD6F3A3740EE6F586BBDAFF0000000B00000000000000000000000100000001237605140994862F6BD6F3A3740EE6F586BBDAFF000000050000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB100000000000") +func TestPublicKeysFromStandardBlockEth(t *testing.T) { + txBytes, err := hex.DecodeString("00000000000082146647D70EBD1E0C735CE8C7CE95B951A7077AE3276A9CFDBE5920FF6654C00000000067460C080000000000001A8D000000000000048F0000000000200000000067460C08AD2801124E11CC33EDAAA752007C7D428F01BE6EA2C0B266AA63CF085D21E9460000000000001A8E000000010000000E0000007200000000000000000000000000000000000000000000000000000000000000000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000007000000E987662BC000000000000000000000000100000001237605140994862F6BD6F3A3740EE6F586BBDAFF0000000575F4E911AC0D6797086FA6EA4BCF4B278955B29F5874252D4F35C61571BA3DF50000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000003B9ACA000000000100000000844DC320BD32F382FF20E64912A9574146B4E7C207B780CE029847E55699E00D0000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000003B8B87C00000000100000000E2589EB05CAFF60AB53EBB0B5F2AE8CAABEF7E9C50AF882800A3F7FA1AB82FF10000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000500002D79882DDDC00000000100000000E33ADDBE17D9B2DF8DB8AA7E7F107792C193477945D0242188B18DC6E8BF50C90000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000003BAA0C400000000100000000E33ADDBE17D9B2DF8DB8AA7E7F107792C193477945D0242188B18DC6E8BF50C90000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD00000005000000E8D4A51000000000010000000000000000D52B09D698E36EE1406681AA40CFA53414B582EB0000000067460CDC00000000675881DC00002D79883D20000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D200000000000000000000000000100000001237605140994862F6BD6F3A3740EE6F586BBDAFF0000000B00000000000000000000000100000001237605140994862F6BD6F3A3740EE6F586BBDAFF000000050000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB10000000000900000001BF66061FE62D556F2B6E9DD469ED4A147557687075D3B7F32BCF9610266799EE04767F4020C111DD4CF4E0A839704FEA67C8B8AD006332285DA154AC7C19AB100000000000") + if err != nil { + t.Fatal(err) + } + + address := "costwo1ydmq29qfjjrz767k7w3hgrhx7krthkhlw7rqk8" + addressBytes, err := parseAddress(address) + if err != nil { + t.Fatal(err) + } + + pk, err := PublicKeyFromPChainBlock("pehEi5CRYEoiyofEsvmajtD7AJ1A1fNQs4dZcqKyhfcSd9PxU", addressBytes, 3, txBytes) + if err != nil { + t.Fatal(err) + } + + ethAddress, err := PublicKeyToEthAddress(pk) + if err != nil { + t.Fatal(err) + } + if ethAddress.Hex() != "0x8ab7028638854AE968EF5174996C17D010Af4bD5" { + t.Fatalf("Wrong address") + } +} + +func TestPublicKeysFromStandardBlockAvalanche(t *testing.T) { + txBytes, err := hex.DecodeString("00000000000062C6CAE4B7DE2BC16B0BA15475FD2AF82CE46DDF9780AA9063232CF4187701F60000000067506E350000000000001AD800000000000001BB0000000000200000000067506E35C715FF0AE8AA6CE74655B67EC3128C4ED281ADCE924C3B727FDF41D31C1FE4400000000000001ADA000000010000000E00000072000000000000000000000000000000000000000000000000000000000000000000000000000000019AAC305E8A7F9276CA14B806C02562456EFE3C2B252967C2EDAC7473C468DB1D0000000058734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000500002D79883D20000000000100000000000000004E15BC646C48B65A576623C7E4689200C0E3EF2D0000000067506E70000000006762E37000002D79883D20000000000158734F94AF871C3D131B56131B6FB7A0291EACADD261E69DFB42A9CDF6F7FDDD0000000700002D79883D20000000000000000000000000010000000154819F71826B5D65E171C3691DC508C6690F1BE60000000B0000000000000000000000010000000154819F71826B5D65E171C3691DC508C6690F1BE600000001000000090000000174F08C887C7DE36E1AE1C61390314129A68D0E1F19BE32445CE1EC5AA8D68757016D56010C794B9ECDC5B3EB4DDE7E5B7E7EDB6FE82CBFF939A04066774C51AA0000000000") + if err != nil { + t.Fatal(err) + } + + address := "costwo12jqe7uvzddwktct3cd53m3ggce5s7xlxf2zklw" + addressBytes, err := parseAddress(address) if err != nil { t.Fatal(err) } - pks, err := PublicKeysFromPChainBlock("pehEi5CRYEoiyofEsvmajtD7AJ1A1fNQs4dZcqKyhfcSd9PxU", hex) + pk, err := PublicKeyFromPChainBlock("UAHoUQRCyrguiD2L5qaUyuTXEu8WZCQKEietGbno5AnEoS1Bu", addressBytes, 0, txBytes) if err != nil { t.Fatal(err) } - if len(pks) != 5 { - t.Fatal("Expected 5 inputs") + ethAddress, err := PublicKeyToEthAddress(pk) + if err != nil { + t.Fatal(err) } - for i, pk := range pks { - if len(pk) != 1 { - t.Fatalf("Expected one pk for input %d", i) - } - ethAddress, err := PublicKeyToEthAddress(pk[0]) - if err != nil { - t.Fatal(err) - } - if ethAddress.Hex() != "0xfbD1Cd44714e241dAF3FC72f76EcAf3d186FC24C" { - t.Fatalf("Wrong address for input %d", i) - } + if ethAddress.Hex() != "0x9327a86e5942da03Bd397576546ABBe7eAA4bd03" { + t.Fatalf("Wrong address") } +} +func parseAddress(addr string) ([20]byte, error) { + address20 := [20]byte{} + _, address, err := address.ParseBech32(addr) + if err != nil { + return address20, err + } + copy(address20[:], address) + return address20, nil } diff --git a/utils/chain/p_chain_tx.go b/utils/chain/p_chain_tx.go index 28784ef..0d4a364 100644 --- a/utils/chain/p_chain_tx.go +++ b/utils/chain/p_chain_tx.go @@ -1,20 +1,23 @@ package chain import ( + "encoding/hex" "fmt" "github.com/ava-labs/avalanchego/utils/crypto" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/platformvm/blocks" - "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/proposervm/block" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/pkg/errors" ) var ( - ErrInvalidBlockType = errors.New("invalid block type") - ErrInvalidTransactionBlock = errors.New("transaction not found in block") - ErrInvalidCredentialType = errors.New("invalid credential type") + ErrInvalidBlockType = errors.New("invalid block type") + ErrInvalidTransactionBlock = errors.New("transaction not found in block") + ErrInvalidCredentialType = errors.New("invalid credential type") + ErrCredentialForAddressNotFound = errors.New("public key not found for address") ) // If block.Parse fails, try to parse as a "pre-fork" block @@ -37,8 +40,8 @@ func ParsePChainBlock(blockBytes []byte) (blocks.Block, error) { } // For a given block (byte array) return a list of public keys for -// signatures of inputs of the transaction with txID in this block -func PublicKeysFromPChainBlock(txID string, blockBytes []byte) ([][]crypto.PublicKey, error) { +// a +func PublicKeyFromPChainBlock(txID string, addrBytes [20]byte, addrIndex uint32, blockBytes []byte) (crypto.PublicKey, error) { innerBlk, err := ParsePChainBlock(blockBytes) if err != nil { return nil, err @@ -51,13 +54,31 @@ func PublicKeysFromPChainBlock(txID string, blockBytes []byte) ([][]crypto.Publi if blk.Tx.ID().String() != txID { return nil, ErrInvalidTransactionBlock } - return PublicKeysFromPChainTx(blk.Tx) + if len(blk.Tx.Creds) <= int(addrIndex) { + return nil, fmt.Errorf("invalid credential index %d", addrIndex) + } + txBytes := blk.Tx.Unsigned.Bytes() + return PublicKeyForAddressAndSignedHash(blk.Tx.Creds[addrIndex], addrBytes, hashing.ComputeHash256(txBytes)) case *blocks.BanffStandardBlock: // In Banff blocks, add delegator and add validator transactions // are in standard blocks. We extract public keys from them. for _, tx := range blk.Txs() { if tx.ID().String() == txID { - return PublicKeysFromPChainTx(tx) + if len(tx.Creds) <= int(addrIndex) { + return nil, fmt.Errorf("invalid credential index %d", addrIndex) + } + + // Try with avalanche-style signature + txHash := hashing.ComputeHash256(tx.Unsigned.Bytes()) + pk, err := PublicKeyForAddressAndSignedHash(tx.Creds[addrIndex], addrBytes, txHash) + if err == nil { + return pk, nil + } + + // Try with eth-style signature + txHashStr := hex.EncodeToString(txHash) + txHashEth := TextHash([]byte(txHashStr)) + return PublicKeyForAddressAndSignedHash(tx.Creds[addrIndex], addrBytes, txHashEth) } } return nil, ErrInvalidTransactionBlock @@ -66,26 +87,23 @@ func PublicKeysFromPChainBlock(txID string, blockBytes []byte) ([][]crypto.Publi } } -// For a given P-chain transaction return a list of public keys for -// signatures of inputs of this transaction -func PublicKeysFromPChainTx(tx *txs.Tx) ([][]crypto.PublicKey, error) { - creds := tx.Creds +// For a given P-chain transaction hash return a public key for +// a signature of a transaction hash that matches the provided address +func PublicKeyForAddressAndSignedHash(cred verify.Verifiable, address [20]byte, signedTxHash []byte) (crypto.PublicKey, error) { factory := crypto.FactorySECP256K1R{} - response := make([][]crypto.PublicKey, len(creds)) - for ci, cred := range creds { - if secpCred, ok := cred.(*secp256k1fx.Credential); !ok { - return nil, ErrInvalidCredentialType - } else { - sigs := secpCred.Sigs - response[ci] = make([]crypto.PublicKey, len(sigs)) - for si, sig := range sigs { - pubKey, err := factory.RecoverPublicKey(tx.Unsigned.Bytes(), sig[:]) - if err != nil { - return nil, fmt.Errorf("failed to recover public key from cred %d sig %d: %w", ci, si, err) - } - response[ci][si] = pubKey + if secpCred, ok := cred.(*secp256k1fx.Credential); !ok { + return nil, ErrInvalidCredentialType + } else { + sigs := secpCred.Sigs + for si, sig := range sigs { + pubKey, err := factory.RecoverHashPublicKey(signedTxHash, sig[:]) + if err != nil { + return nil, fmt.Errorf("failed to recover public key from cred sig %d: %w", si, err) + } + if pubKey.Address() == address { + return pubKey, nil } } + return nil, ErrCredentialForAddressNotFound } - return response, nil }