Skip to content

Commit

Permalink
Add Checksum for Address (#1699)
Browse files Browse the repository at this point in the history
  • Loading branch information
RodrigoVillar authored Nov 5, 2024
1 parent ec4a313 commit 25b75aa
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 25 deletions.
2 changes: 1 addition & 1 deletion abi/testdata/transfer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"to": "0x0102030405060708090a0b0c0d0e0f101112131400000000000000000000000000",
"to": "0x0102030405060708090a0b0c0d0e0f10111213140000000000000000000000000020db0e6c",
"value": 1000,
"memo": "aGk="
}
2 changes: 1 addition & 1 deletion abi/testdata/transferField.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"transfer": {
"to": "0x0102030405060708090a0b0c0d0e0f101112131400000000000000000000000000",
"to": "0x0102030405060708090a0b0c0d0e0f10111213140000000000000000000000000020db0e6c",
"value": 1000,
"memo": "aGk="
}
Expand Down
4 changes: 2 additions & 2 deletions abi/testdata/transfersArray.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"transfers": [
{
"to": "0x0102030405060708090a0b0c0d0e0f101112131400000000000000000000000000",
"to": "0x0102030405060708090a0b0c0d0e0f10111213140000000000000000000000000020db0e6c",
"value": 1000,
"memo": "aGk="
},
{
"to": "0x0102030405060708090a0b0c0d0e0f101112131400000000000000000000000000",
"to": "0x0102030405060708090a0b0c0d0e0f10111213140000000000000000000000000020db0e6c",
"value": 1000,
"memo": "aGk="
}
Expand Down
71 changes: 50 additions & 21 deletions codec/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@
package codec

import (
"bytes"
"encoding/hex"
"errors"
"fmt"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/hashing"
)

// TODO: add a checksum to the hex address format (ideally similar to EIP55).
const AddressLen = 33
const (
AddressLen = 33
checksumLen = 4
)

var (
ErrBadChecksum = errors.New("invalid input checksum")
ErrMissingChecksum = errors.New("input string is smaller than the checksum size")
)

// Address represents the 33 byte address of a HyperSDK account
type Address [AddressLen]byte
Expand Down Expand Up @@ -42,40 +52,59 @@ func ToAddress(b []byte) (Address, error) {
// either AddressLen or the length of the hex decoded string.
func StringToAddress(s string) (Address, error) {
var a Address
b, err := LoadHex(s, AddressLen)
if err != nil {
return a, err
if err := a.UnmarshalText([]byte(s)); err != nil {
return EmptyAddress, err
}
copy(a[:], b)
return a, nil
}

// String implements fmt.Stringer.
func (a Address) String() string {
return ToHex(a[:])
return encodeWithChecksum(a[:])
}

// MarshalText returns the hex representation of a.
// MarshalText returns the checksummed hex representation of a.
func (a Address) MarshalText() ([]byte, error) {
result := make([]byte, len(a)*2+2)
copy(result, `0x`)
hex.Encode(result[2:], a[:])
return result, nil
return []byte(encodeWithChecksum(a[:])), nil
}

// UnmarshalText parses a hex-encoded address.
// UnmarshalText parses a checksummed hex-encoded address
func (a *Address) UnmarshalText(input []byte) error {
// Check if the input has the '0x' prefix and skip it
if len(input) >= 2 && input[0] == '0' && input[1] == 'x' {
input = input[2:]
}

// Decode the hex string
decoded, err := hex.DecodeString(string(input))
decoded, err := fromChecksum(string(input))
if err != nil {
return err // Return the error if the hex string is invalid
return err
}

copy(a[:], decoded)
return nil
}

// encodeWithChecksum appends a 4 byte checksum and encodes the result in hex format
func encodeWithChecksum(bytes []byte) string {
bytesLen := len(bytes)
checked := make([]byte, bytesLen+checksumLen)
copy(checked, bytes)
copy(checked[AddressLen:], hashing.Checksum(bytes, checksumLen))
return "0x" + hex.EncodeToString(checked)
}

// fromChecksum interprets the hex bytes, validates the 4 byte checksum, and returns the original bytes
func fromChecksum(s string) ([]byte, error) {
if len(s) >= 2 && s[0] == '0' && s[1] == 'x' {
s = s[2:]
}
decoded, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
if len(decoded) < checksumLen {
return nil, ErrMissingChecksum
}
// Verify checksum
originalBytes := decoded[:len(decoded)-checksumLen]
checksum := decoded[len(decoded)-checksumLen:]
if !bytes.Equal(checksum, hashing.Checksum(originalBytes, checksumLen)) {
return nil, ErrBadChecksum
}
return originalBytes, nil
}
88 changes: 88 additions & 0 deletions codec/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,91 @@ func TestAddressJSON(t *testing.T) {
require.NoError(json.Unmarshal(addrJSONBytes, &parsedAddr))
require.Equal(addr, parsedAddr)
}

func TestAddressStringIdentity(t *testing.T) {
require := require.New(t)
typeID := byte(0)
addrID := ids.GenerateTestID()

addr := CreateAddress(typeID, addrID)

originalAddr, err := StringToAddress(addr.String())
require.NoError(err)
require.Equal(addr, originalAddr)
}

func TestZeroAddressToString(t *testing.T) {
require := require.New(t)
expectedStr := "0x000000000000000000000000000000000000000000000000000000000000000000a7396ce9"
require.Equal(expectedStr, EmptyAddress.String())
}

func TestAddressToString(t *testing.T) {
require := require.New(t)
expectedStr := "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a10df6ab"
addr := CreateAddress(
byte(0),
[32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32},
)
require.Equal(expectedStr, addr.String())
}

func TestStringToAddress(t *testing.T) {
addr := CreateAddress(
byte(0),
[32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32},
)
addrStr := "0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20a10df6ab"

// Remove checksum from address string
addrStrWithNoChecksum := addrStr[:len(addrStr)-4]
// Manipulate last character of address string
addrStrWithBadChecksum := addrStr[:len(addrStr)-1] + "c"

tests := []struct {
name string
inputStr string
expectedAddress Address
expectedErr error
}{
{
name: "simpleChecksummedAddress",
inputStr: addrStr,
expectedAddress: addr,
expectedErr: nil,
},
{
name: "simpleChecksummedAddressWithNoPrefix",
inputStr: addrStr[2:],
expectedAddress: addr,
expectedErr: nil,
},
{
name: "addressWithNoChecksum",
inputStr: addrStrWithNoChecksum,
expectedAddress: EmptyAddress,
expectedErr: ErrBadChecksum,
},
{
name: "addressLengthSmallerThanChecksum",
inputStr: "0x0000",
expectedAddress: EmptyAddress,
expectedErr: ErrMissingChecksum,
},
{
name: "addressWithBadChecksum",
inputStr: addrStrWithBadChecksum,
expectedAddress: EmptyAddress,
expectedErr: ErrBadChecksum,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
addr, err := StringToAddress(test.inputStr)
require.Equal(test.expectedAddress, addr)
require.Equal(test.expectedErr, err)
})
}
}

0 comments on commit 25b75aa

Please sign in to comment.