Skip to content

Commit 7011a77

Browse files
committed
feat: return stake address from address
Fixes #302 feat: add function to create Address from bech32 string
1 parent e8de9a7 commit 7011a77

File tree

2 files changed

+111
-13
lines changed

2 files changed

+111
-13
lines changed

ledger/common.go

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -202,34 +202,68 @@ type Address struct {
202202
networkId uint8
203203
paymentAddress []byte
204204
stakingAddress []byte
205-
hrp string
206205
}
207206

208-
func (a *Address) UnmarshalCBOR(data []byte) error {
209-
// Decode bytes from CBOR
210-
tmpData := []byte{}
211-
if _, err := cbor.Decode(data, &tmpData); err != nil {
212-
return err
207+
// NewAddress returns an Address based on the provided bech32 address string
208+
func NewAddress(addr string) (Address, error) {
209+
_, data, err := bech32.DecodeNoLimit(addr)
210+
if err != nil {
211+
return Address{}, err
213212
}
213+
decoded, err := bech32.ConvertBits(data, 5, 8, false)
214+
if err != nil {
215+
return Address{}, err
216+
}
217+
a := Address{}
218+
a.populateFromBytes(decoded)
219+
return a, nil
220+
}
221+
222+
func (a *Address) populateFromBytes(data []byte) {
214223
// Extract header info
215-
header := tmpData[0]
224+
header := data[0]
216225
a.addressType = (header & addressHeaderTypeMask) >> 4
217226
a.networkId = header & addressHeaderNetworkMask
218227
// Extract payload
219228
// NOTE: this is probably incorrect for Byron
220-
payload := tmpData[1:]
229+
payload := data[1:]
221230
a.paymentAddress = payload[:addressHashSize]
222231
a.stakingAddress = payload[addressHashSize:]
223-
// Generate human readable part of address for output
224-
a.hrp = a.generateHRP()
232+
// Adjust stake addresses
233+
if a.addressType == addressTypeNoneKey || a.addressType == addressTypeNoneScript {
234+
a.stakingAddress = a.paymentAddress[:]
235+
a.paymentAddress = make([]byte, 0)
236+
}
237+
}
238+
239+
func (a *Address) UnmarshalCBOR(data []byte) error {
240+
// Decode bytes from CBOR
241+
tmpData := []byte{}
242+
if _, err := cbor.Decode(data, &tmpData); err != nil {
243+
return err
244+
}
245+
a.populateFromBytes(tmpData)
225246
return nil
226247
}
227248

228249
func (a *Address) MarshalCBOR() ([]byte, error) {
229250
return cbor.Encode(a.Bytes())
230251
}
231252

232-
func (a *Address) generateHRP() string {
253+
// StakeAddress returns a new Address with only the stake key portion. This will return nil if the address is not a payment/staking key pair
254+
func (a *Address) StakeAddress() *Address {
255+
if a.addressType != addressTypeKeyKey {
256+
return nil
257+
}
258+
newAddr := &Address{
259+
addressType: addressTypeNoneKey,
260+
networkId: a.networkId,
261+
stakingAddress: a.stakingAddress[:],
262+
}
263+
return newAddr
264+
}
265+
266+
func (a Address) generateHRP() string {
233267
var ret string
234268
if a.addressType == addressTypeNoneKey || a.addressType == addressTypeNoneScript {
235269
ret = "stake"
@@ -243,14 +277,16 @@ func (a *Address) generateHRP() string {
243277
return ret
244278
}
245279

246-
func (a *Address) Bytes() []byte {
280+
// Bytes returns the underlying bytes for the address
281+
func (a Address) Bytes() []byte {
247282
ret := []byte{}
248283
ret = append(ret, (byte(a.addressType)<<4)|(byte(a.networkId)&addressHeaderNetworkMask))
249284
ret = append(ret, a.paymentAddress...)
250285
ret = append(ret, a.stakingAddress...)
251286
return ret
252287
}
253288

289+
// String returns the bech32-encoded version of the address
254290
func (a Address) String() string {
255291
data := a.Bytes()
256292
if a.addressType == addressTypeByron {
@@ -263,7 +299,9 @@ func (a Address) String() string {
263299
if err != nil {
264300
panic(fmt.Sprintf("unexpected error converting data to base32: %s", err))
265301
}
266-
encoded, err := bech32.Encode(a.hrp, convData)
302+
// Generate human readable part of address for output
303+
hrp := a.generateHRP()
304+
encoded, err := bech32.Encode(hrp, convData)
267305
if err != nil {
268306
panic(fmt.Sprintf("unexpected error encoding data as bech32: %s", err))
269307
}

ledger/common_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,63 @@ func TestMultiAssetJson(t *testing.T) {
103103
}
104104
}
105105
}
106+
107+
func TestAddressFromBytes(t *testing.T) {
108+
testDefs := []struct {
109+
addressBytesHex string
110+
expectedAddress string
111+
}{
112+
{
113+
addressBytesHex: "11e1317b152faac13426e6a83e06ff88a4d62cce3c1634ab0a5ec1330952563c5410bff6a0d43ccebb7c37e1f69f5eb260552521adff33b9c2",
114+
expectedAddress: "addr1z8snz7c4974vzdpxu65ruphl3zjdvtxw8strf2c2tmqnxz2j2c79gy9l76sdg0xwhd7r0c0kna0tycz4y5s6mlenh8pq0xmsha",
115+
},
116+
{
117+
addressBytesHex: "013f35615835258addded1c2e169f3a2ab4ae94d606bde030e7947f5184ff5f8e3d43ce6b19ec4197e331e86d0f5e58b02d7a75b5e74cff95d",
118+
expectedAddress: "addr1qyln2c2cx5jc4hw768pwz60n5245462dvp4auqcw09rl2xz07huw84puu6cea3qe0ce3apks7hjckqkh5ad4uax0l9ws0q9xty",
119+
},
120+
{
121+
addressBytesHex: "7121bd8c2e0df2fbe92137f78dbaba48f62308e52303049f0d628b6c4c",
122+
expectedAddress: "addr1wysmmrpwphe0h6fpxlmcmw46frmzxz89yvpsf8cdv29kcnqsw3vw6",
123+
},
124+
{
125+
addressBytesHex: "61cfe224295a282d69edda5fa8de4f131e2b9cd21a6c9235597fa4ff6b",
126+
expectedAddress: "addr1v887yfpftg5z660dmf063hj0zv0zh8xjrfkfyd2e07j076cecha5k",
127+
},
128+
}
129+
for _, testDef := range testDefs {
130+
addr := Address{}
131+
addr.populateFromBytes(test.DecodeHexString(testDef.addressBytesHex))
132+
if addr.String() != testDef.expectedAddress {
133+
t.Fatalf("address did not match expected value, got: %s, wanted: %s", addr.String(), testDef.expectedAddress)
134+
}
135+
}
136+
}
137+
138+
func TestAddressStakeAddress(t *testing.T) {
139+
testDefs := []struct {
140+
address string
141+
expectedStakeAddress string
142+
}{
143+
{
144+
address: "addr1q8fv95d4g2599v3gzq7wnva34ykt4d2zerl0wyke36zml0neqj84x95mgp694rv8gfqy6u67ms38lx30texma843yd5qmvkqcz",
145+
expectedStakeAddress: "stake1u9usfr6nz6d5qaz63kr5yszdwd0dcgnlngh4und7n6cjx6qh02h9m",
146+
},
147+
{
148+
address: "addr1q8uas4shlrnxhd8dnqxesk9hlgmtx65xlmkq9c7acfa2ksvjn4k4w8d7lwnzkf3dkq26kxz3re50h89adduskx08rr6qq2g0f2",
149+
expectedStakeAddress: "stake1uxff6m2hrkl0hf3tyckmq9dtrpg3u68mnj7kk7gtr8n33aq5fqskz",
150+
},
151+
{
152+
address: "addr1q9h4f2vhh5vnqgnsejan3psw6mj3a504fxlqm2eh3262qufesdvfs83ulr22vprsv9mwnt0vgkfwxlflxkns32twqzdqjpq2na",
153+
expectedStakeAddress: "stake1uyucxkycrc70349xq3cxzahf4hkytyhr05lntfcg49hqpxsqlrayk",
154+
},
155+
}
156+
for _, testDef := range testDefs {
157+
addr, err := NewAddress(testDef.address)
158+
if err != nil {
159+
t.Fatalf("failed to decode address: %s", err)
160+
}
161+
if addr.StakeAddress().String() != testDef.expectedStakeAddress {
162+
t.Fatalf("stake address did not match expected value, got: %s, wanted: %s", addr.StakeAddress().String(), testDef.expectedStakeAddress)
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)