Skip to content

Commit e00600f

Browse files
committed
feat: Byron address support
This adds proper support for Byron addresses, including building one from parts. Fixes #540
1 parent 4f0812b commit e00600f

File tree

2 files changed

+193
-9
lines changed

2 files changed

+193
-9
lines changed

ledger/common/address.go

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package common
1616

1717
import (
1818
"fmt"
19+
"hash/crc32"
1920
"strings"
2021

2122
"github.com/blinklabs-io/gouroboros/base58"
@@ -42,16 +43,22 @@ const (
4243
AddressTypeByron = 0b1000
4344
AddressTypeNoneKey = 0b1110
4445
AddressTypeNoneScript = 0b1111
46+
47+
ByronAddressTypePubkey = 0
48+
ByronAddressTypeScript = 1
49+
ByronAddressTypeRedeem = 2
4550
)
4651

4752
type AddrKeyHash Blake2b224
4853

4954
type Address struct {
50-
addressType uint8
51-
networkId uint8
52-
paymentAddress []byte
53-
stakingAddress []byte
54-
extraData []byte
55+
addressType uint8
56+
networkId uint8
57+
paymentAddress []byte
58+
stakingAddress []byte
59+
extraData []byte
60+
byronAddressType uint64
61+
byronAddressAttr ByronAddressAttributes
5562
}
5663

5764
// NewAddress returns an Address based on the provided bech32/base58 address string
@@ -109,15 +116,59 @@ func NewAddressFromParts(
109116
}, nil
110117
}
111118

119+
func NewByronAddressFromParts(
120+
byronAddrType uint64,
121+
paymentAddr []byte,
122+
attr ByronAddressAttributes,
123+
) (Address, error) {
124+
if len(paymentAddr) != AddressHashSize {
125+
return Address{}, fmt.Errorf(
126+
"invalid payment address hash length: %d",
127+
len(paymentAddr),
128+
)
129+
}
130+
return Address{
131+
addressType: AddressTypeByron,
132+
paymentAddress: paymentAddr,
133+
byronAddressType: byronAddrType,
134+
byronAddressAttr: attr,
135+
}, nil
136+
}
137+
112138
func (a *Address) populateFromBytes(data []byte) error {
113139
// Extract header info
114140
header := data[0]
115141
a.addressType = (header & AddressHeaderTypeMask) >> 4
116142
a.networkId = header & AddressHeaderNetworkMask
143+
// Byron Addresses
144+
if a.addressType == AddressTypeByron {
145+
var rawAddr byronAddress
146+
if _, err := cbor.Decode(data, &rawAddr); err != nil {
147+
return err
148+
}
149+
payloadBytes, ok := rawAddr.Payload.Content.([]byte)
150+
if !ok || rawAddr.Payload.Number != 24 {
151+
return fmt.Errorf("invalid Byron address data: unexpected payload content")
152+
}
153+
payloadChecksum := crc32.ChecksumIEEE(payloadBytes)
154+
if rawAddr.Checksum != payloadChecksum {
155+
return fmt.Errorf("invalid Byron address data: checksum does not match")
156+
}
157+
var byronAddr byronAddressPayload
158+
if _, err := cbor.Decode(payloadBytes, &byronAddr); err != nil {
159+
return err
160+
}
161+
if len(byronAddr.Hash) != AddressHashSize {
162+
return fmt.Errorf("invalid Byron address data: hash is not expected length")
163+
}
164+
a.byronAddressType = byronAddr.AddrType
165+
a.byronAddressAttr = byronAddr.Attr
166+
a.paymentAddress = byronAddr.Hash
167+
return nil
168+
}
117169
// Check length
118170
// We exclude a few address types without fixed sizes that we don't properly support yet
119-
if a.addressType != AddressTypeByron &&
120-
a.addressType != AddressTypeKeyPointer &&
171+
if a.addressType != AddressTypeKeyPointer &&
121172
a.addressType != AddressTypeScriptPointer {
122173
dataLen := len(data)
123174
// Addresses must be at least the address hash size plus header byte
@@ -135,7 +186,6 @@ func (a *Address) populateFromBytes(data []byte) error {
135186
}
136187
}
137188
// Extract payload
138-
// NOTE: this is definitely incorrect for Byron
139189
payload := data[1:]
140190
a.paymentAddress = payload[:AddressHashSize]
141191
payload = payload[AddressHashSize:]
@@ -264,6 +314,31 @@ func (a Address) generateHRP() string {
264314

265315
// Bytes returns the underlying bytes for the address
266316
func (a Address) Bytes() []byte {
317+
if a.addressType == AddressTypeByron {
318+
tmpPayload := []any{
319+
a.paymentAddress,
320+
a.byronAddressAttr,
321+
a.byronAddressType,
322+
}
323+
rawPayload, err := cbor.Encode(tmpPayload)
324+
if err != nil {
325+
// TODO: handle error
326+
return nil
327+
}
328+
tmpData := []any{
329+
cbor.Tag{
330+
Number: 24,
331+
Content: rawPayload,
332+
},
333+
crc32.ChecksumIEEE(rawPayload),
334+
}
335+
ret, err := cbor.Encode(tmpData)
336+
if err != nil {
337+
// TODO: handle error
338+
return nil
339+
}
340+
return ret
341+
}
267342
ret := []byte{}
268343
ret = append(
269344
ret,
@@ -301,3 +376,55 @@ func (a Address) String() string {
301376
func (a Address) MarshalJSON() ([]byte, error) {
302377
return []byte(`"` + a.String() + `"`), nil
303378
}
379+
380+
type byronAddress struct {
381+
cbor.StructAsArray
382+
Payload cbor.Tag
383+
Checksum uint32
384+
}
385+
386+
type byronAddressPayload struct {
387+
cbor.StructAsArray
388+
Hash []byte
389+
Attr ByronAddressAttributes
390+
AddrType uint64
391+
}
392+
393+
type ByronAddressAttributes struct {
394+
Payload []byte
395+
Network *uint8
396+
}
397+
398+
func (a *ByronAddressAttributes) UnmarshalCBOR(data []byte) error {
399+
var tmpData struct {
400+
Payload []byte `cbor:"1,keyasint,omitempty"`
401+
NetworkRaw []byte `cbor:"2,keyasint,omitempty"`
402+
}
403+
if _, err := cbor.Decode(data, &tmpData); err != nil {
404+
return err
405+
}
406+
a.Payload = tmpData.Payload
407+
if len(tmpData.NetworkRaw) > 0 {
408+
var tmpNetwork uint8
409+
if _, err := cbor.Decode(tmpData.NetworkRaw, &tmpNetwork); err != nil {
410+
return err
411+
}
412+
a.Network = &tmpNetwork
413+
}
414+
return nil
415+
}
416+
417+
func (a *ByronAddressAttributes) MarshalCBOR() ([]byte, error) {
418+
tmpData := make(map[int]any)
419+
if len(a.Payload) > 0 {
420+
tmpData[1] = a.Payload
421+
}
422+
if a.Network != nil {
423+
networkRaw, err := cbor.Encode(a.Network)
424+
if err != nil {
425+
return nil, err
426+
}
427+
tmpData[2] = networkRaw
428+
}
429+
return cbor.Encode(tmpData)
430+
}

ledger/common/address_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,16 @@ func TestAddressFromBytes(t *testing.T) {
5353
addressBytesHex: "61549b5a20e449a3e394b762705f64b9a26b99013003a2bfdba239967c00",
5454
expectedAddress: "addr1v92fkk3qu3y68cu5ka38qhmyhx3xhxgpxqp6907m5guevlqqjd7xgj",
5555
},
56-
// Byron address
56+
// Byron address, mainnet with derivation
5757
{
5858
addressBytesHex: "82d818584283581caf56de241bcca83d72c51e74d18487aa5bc68b45e2caa170fa329d3aa101581e581cea1425ccdd649b25af5deb7e6335da2eb8167353a55e77925122e95f001a3a858621",
5959
expectedAddress: "DdzFFzCqrht2ii4Vc7KRchSkVvQtCqdGkQt4nF4Yxg1NpsubFBity2Tpt2eSEGrxBH1eva8qCFKM2Y5QkwM1SFBizRwZgz1N452WYvgG",
6060
},
61+
// Byron address, preprod
62+
{
63+
addressBytesHex: "82d818582483581c5d5e698eba3dd9452add99a1af9461beb0ba61b8bece26e7399878dda1024102001a36d41aba",
64+
expectedAddress: "FHnt4NL7yPXvDWHa8bVs73UEUdJd64VxWXSFNqetECtYfTd9TtJguJ14Lu3feth",
65+
},
6166
}
6267
for _, testDef := range testDefs {
6368
addr := Address{}
@@ -149,6 +154,58 @@ func TestAddressFromParts(t *testing.T) {
149154
}
150155
}
151156

157+
func TestByronAddressFromParts(t *testing.T) {
158+
testDefs := []struct {
159+
addressType uint64
160+
paymentAddr []byte
161+
addressAttr ByronAddressAttributes
162+
expectedAddress string
163+
}{
164+
// Byron address, mainnet with derivation
165+
{
166+
addressType: ByronAddressTypePubkey,
167+
paymentAddr: test.DecodeHexString(
168+
"af56de241bcca83d72c51e74d18487aa5bc68b45e2caa170fa329d3a",
169+
),
170+
addressAttr: ByronAddressAttributes{
171+
Payload: test.DecodeHexString(
172+
"581cea1425ccdd649b25af5deb7e6335da2eb8167353a55e77925122e95f",
173+
),
174+
},
175+
expectedAddress: "DdzFFzCqrht2ii4Vc7KRchSkVvQtCqdGkQt4nF4Yxg1NpsubFBity2Tpt2eSEGrxBH1eva8qCFKM2Y5QkwM1SFBizRwZgz1N452WYvgG",
176+
},
177+
// Byron address, preprod
178+
{
179+
addressType: ByronAddressTypePubkey,
180+
paymentAddr: test.DecodeHexString(
181+
"5d5e698eba3dd9452add99a1af9461beb0ba61b8bece26e7399878dd",
182+
),
183+
addressAttr: ByronAddressAttributes{
184+
// We have to jump through this hoop to get an inline pointer to a uint8
185+
Network: func() *uint8 { ret := uint8(2); return &ret }(),
186+
},
187+
expectedAddress: "FHnt4NL7yPXvDWHa8bVs73UEUdJd64VxWXSFNqetECtYfTd9TtJguJ14Lu3feth",
188+
},
189+
}
190+
for _, testDef := range testDefs {
191+
addr, err := NewByronAddressFromParts(
192+
testDef.addressType,
193+
testDef.paymentAddr,
194+
testDef.addressAttr,
195+
)
196+
if err != nil {
197+
t.Fatalf("unexpected error: %s", err)
198+
}
199+
if addr.String() != testDef.expectedAddress {
200+
t.Fatalf(
201+
"address did not match expected value, got: %s, wanted: %s",
202+
addr.String(),
203+
testDef.expectedAddress,
204+
)
205+
}
206+
}
207+
}
208+
152209
func TestAddressPaymentAddress(t *testing.T) {
153210
testDefs := []struct {
154211
address string

0 commit comments

Comments
 (0)