Skip to content

Commit

Permalink
Implemented public key registration in case of eth style signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
mboben committed Dec 5, 2024
1 parent 77bbdab commit fa6212e
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 60 deletions.
19 changes: 9 additions & 10 deletions indexer/cronjob/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions utils/chain/hashing.go
Original file line number Diff line number Diff line change
@@ -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
}
83 changes: 59 additions & 24 deletions utils/chain/p_chain_pk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
}
70 changes: 44 additions & 26 deletions utils/chain/p_chain_tx.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}

0 comments on commit fa6212e

Please sign in to comment.