Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 135 additions & 8 deletions ledger/common/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package common

import (
"fmt"
"hash/crc32"
"strings"

"github.com/blinklabs-io/gouroboros/base58"
Expand All @@ -42,16 +43,22 @@ const (
AddressTypeByron = 0b1000
AddressTypeNoneKey = 0b1110
AddressTypeNoneScript = 0b1111

ByronAddressTypePubkey = 0
ByronAddressTypeScript = 1
ByronAddressTypeRedeem = 2
)

type AddrKeyHash Blake2b224

type Address struct {
addressType uint8
networkId uint8
paymentAddress []byte
stakingAddress []byte
extraData []byte
addressType uint8
networkId uint8
paymentAddress []byte
stakingAddress []byte
extraData []byte
byronAddressType uint64
byronAddressAttr ByronAddressAttributes
}

// NewAddress returns an Address based on the provided bech32/base58 address string
Expand Down Expand Up @@ -109,15 +116,59 @@ func NewAddressFromParts(
}, nil
}

func NewByronAddressFromParts(
byronAddrType uint64,
paymentAddr []byte,
attr ByronAddressAttributes,
) (Address, error) {
if len(paymentAddr) != AddressHashSize {
return Address{}, fmt.Errorf(
"invalid payment address hash length: %d",
len(paymentAddr),
)
}
return Address{
addressType: AddressTypeByron,
paymentAddress: paymentAddr,
byronAddressType: byronAddrType,
byronAddressAttr: attr,
}, nil
}

func (a *Address) populateFromBytes(data []byte) error {
// Extract header info
header := data[0]
a.addressType = (header & AddressHeaderTypeMask) >> 4
a.networkId = header & AddressHeaderNetworkMask
// Byron Addresses
if a.addressType == AddressTypeByron {
var rawAddr byronAddress
if _, err := cbor.Decode(data, &rawAddr); err != nil {
return err
}
payloadBytes, ok := rawAddr.Payload.Content.([]byte)
if !ok || rawAddr.Payload.Number != 24 {
return fmt.Errorf("invalid Byron address data: unexpected payload content")
}
payloadChecksum := crc32.ChecksumIEEE(payloadBytes)
if rawAddr.Checksum != payloadChecksum {
return fmt.Errorf("invalid Byron address data: checksum does not match")
}
var byronAddr byronAddressPayload
if _, err := cbor.Decode(payloadBytes, &byronAddr); err != nil {
return err
}
if len(byronAddr.Hash) != AddressHashSize {
return fmt.Errorf("invalid Byron address data: hash is not expected length")
}
a.byronAddressType = byronAddr.AddrType
a.byronAddressAttr = byronAddr.Attr
a.paymentAddress = byronAddr.Hash
return nil
}
// Check length
// We exclude a few address types without fixed sizes that we don't properly support yet
if a.addressType != AddressTypeByron &&
a.addressType != AddressTypeKeyPointer &&
if a.addressType != AddressTypeKeyPointer &&
a.addressType != AddressTypeScriptPointer {
dataLen := len(data)
// Addresses must be at least the address hash size plus header byte
Expand All @@ -135,7 +186,6 @@ func (a *Address) populateFromBytes(data []byte) error {
}
}
// Extract payload
// NOTE: this is definitely incorrect for Byron
payload := data[1:]
a.paymentAddress = payload[:AddressHashSize]
payload = payload[AddressHashSize:]
Expand Down Expand Up @@ -264,6 +314,31 @@ func (a Address) generateHRP() string {

// Bytes returns the underlying bytes for the address
func (a Address) Bytes() []byte {
if a.addressType == AddressTypeByron {
tmpPayload := []any{
a.paymentAddress,
a.byronAddressAttr,
a.byronAddressType,
}
rawPayload, err := cbor.Encode(tmpPayload)
if err != nil {
// TODO: handle error
return nil
}
tmpData := []any{
cbor.Tag{
Number: 24,
Content: rawPayload,
},
crc32.ChecksumIEEE(rawPayload),
}
ret, err := cbor.Encode(tmpData)
if err != nil {
// TODO: handle error
return nil
}
return ret
}
ret := []byte{}
ret = append(
ret,
Expand Down Expand Up @@ -301,3 +376,55 @@ func (a Address) String() string {
func (a Address) MarshalJSON() ([]byte, error) {
return []byte(`"` + a.String() + `"`), nil
}

type byronAddress struct {
cbor.StructAsArray
Payload cbor.Tag
Checksum uint32
}

type byronAddressPayload struct {
cbor.StructAsArray
Hash []byte
Attr ByronAddressAttributes
AddrType uint64
}

type ByronAddressAttributes struct {
Payload []byte
Network *uint8
}

func (a *ByronAddressAttributes) UnmarshalCBOR(data []byte) error {
var tmpData struct {
Payload []byte `cbor:"1,keyasint,omitempty"`
NetworkRaw []byte `cbor:"2,keyasint,omitempty"`
}
if _, err := cbor.Decode(data, &tmpData); err != nil {
return err
}
a.Payload = tmpData.Payload
if len(tmpData.NetworkRaw) > 0 {
var tmpNetwork uint8
if _, err := cbor.Decode(tmpData.NetworkRaw, &tmpNetwork); err != nil {
return err
}
a.Network = &tmpNetwork
}
return nil
}

func (a *ByronAddressAttributes) MarshalCBOR() ([]byte, error) {
tmpData := make(map[int]any)
if len(a.Payload) > 0 {
tmpData[1] = a.Payload
}
if a.Network != nil {
networkRaw, err := cbor.Encode(a.Network)
if err != nil {
return nil, err
}
tmpData[2] = networkRaw
}
return cbor.Encode(tmpData)
}
59 changes: 58 additions & 1 deletion ledger/common/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,16 @@ func TestAddressFromBytes(t *testing.T) {
addressBytesHex: "61549b5a20e449a3e394b762705f64b9a26b99013003a2bfdba239967c00",
expectedAddress: "addr1v92fkk3qu3y68cu5ka38qhmyhx3xhxgpxqp6907m5guevlqqjd7xgj",
},
// Byron address
// Byron address, mainnet with derivation
{
addressBytesHex: "82d818584283581caf56de241bcca83d72c51e74d18487aa5bc68b45e2caa170fa329d3aa101581e581cea1425ccdd649b25af5deb7e6335da2eb8167353a55e77925122e95f001a3a858621",
expectedAddress: "DdzFFzCqrht2ii4Vc7KRchSkVvQtCqdGkQt4nF4Yxg1NpsubFBity2Tpt2eSEGrxBH1eva8qCFKM2Y5QkwM1SFBizRwZgz1N452WYvgG",
},
// Byron address, preview
{
addressBytesHex: "82d818582483581c5d5e698eba3dd9452add99a1af9461beb0ba61b8bece26e7399878dda1024102001a36d41aba",
expectedAddress: "FHnt4NL7yPXvDWHa8bVs73UEUdJd64VxWXSFNqetECtYfTd9TtJguJ14Lu3feth",
},
}
for _, testDef := range testDefs {
addr := Address{}
Expand Down Expand Up @@ -149,6 +154,58 @@ func TestAddressFromParts(t *testing.T) {
}
}

func TestByronAddressFromParts(t *testing.T) {
testDefs := []struct {
addressType uint64
paymentAddr []byte
addressAttr ByronAddressAttributes
expectedAddress string
}{
// Byron address, mainnet with derivation
{
addressType: ByronAddressTypePubkey,
paymentAddr: test.DecodeHexString(
"af56de241bcca83d72c51e74d18487aa5bc68b45e2caa170fa329d3a",
),
addressAttr: ByronAddressAttributes{
Payload: test.DecodeHexString(
"581cea1425ccdd649b25af5deb7e6335da2eb8167353a55e77925122e95f",
),
},
expectedAddress: "DdzFFzCqrht2ii4Vc7KRchSkVvQtCqdGkQt4nF4Yxg1NpsubFBity2Tpt2eSEGrxBH1eva8qCFKM2Y5QkwM1SFBizRwZgz1N452WYvgG",
},
// Byron address, preview
{
addressType: ByronAddressTypePubkey,
paymentAddr: test.DecodeHexString(
"5d5e698eba3dd9452add99a1af9461beb0ba61b8bece26e7399878dd",
),
addressAttr: ByronAddressAttributes{
// We have to jump through this hoop to get an inline pointer to a uint8
Network: func() *uint8 { ret := uint8(2); return &ret }(),
},
expectedAddress: "FHnt4NL7yPXvDWHa8bVs73UEUdJd64VxWXSFNqetECtYfTd9TtJguJ14Lu3feth",
},
}
for _, testDef := range testDefs {
addr, err := NewByronAddressFromParts(
testDef.addressType,
testDef.paymentAddr,
testDef.addressAttr,
)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if addr.String() != testDef.expectedAddress {
t.Fatalf(
"address did not match expected value, got: %s, wanted: %s",
addr.String(),
testDef.expectedAddress,
)
}
}
}

func TestAddressPaymentAddress(t *testing.T) {
testDefs := []struct {
address string
Expand Down
Loading