Skip to content

Commit

Permalink
feat: add StakeAddress support for Address
Browse files Browse the repository at this point in the history
  • Loading branch information
kevink1103 committed Nov 23, 2022
1 parent 5d486b0 commit d5eeb84
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 27 deletions.
64 changes: 49 additions & 15 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (

type AddressType byte

// refer to pg.120 in https://hydra.iohk.io/build/7918420/download/1/ledger-spec.pdf
const (
Base AddressType = 0x00
Ptr AddressType = 0x04
Enterprise AddressType = 0x06
Stake AddressType = 0x0E
)

// Address represents a Cardano address.
Expand Down Expand Up @@ -160,6 +162,22 @@ func NewAddressFromBytes(bytes []byte) (Address, error) {
Type: ScriptCredential,
ScriptHash: bytes[1:29],
}
case Stake:
if len(bytes) != 29 {
return addr, errors.New("enterprise address length should be 29")
}
addr.Stake = StakeCredential{
Type: KeyCredential,
KeyHash: bytes[1:29],
}
case Stake + 1:
if len(bytes) != 29 {
return addr, errors.New("enterprise address length should be 29")
}
addr.Stake = StakeCredential{
Type: ScriptCredential,
ScriptHash: bytes[1:29],
}
}

return addr, nil
Expand Down Expand Up @@ -206,21 +224,23 @@ func (addr *Address) Bytes() []byte {
case Base, Base + 1, Base + 2, Base + 3:
addrBytes = append(addrBytes, addr.Payment.Hash()...)
addrBytes = append(addrBytes, addr.Stake.Hash()...)
case Enterprise, Enterprise + 1:
addrBytes = append(addrBytes, addr.Payment.Hash()...)
case Ptr, Ptr + 1:
addrBytes = append(addrBytes, addr.Payment.Hash()...)
addrBytes = append(addrBytes, encodeToNat(addr.Pointer.Slot)...)
addrBytes = append(addrBytes, encodeToNat(addr.Pointer.TxIndex)...)
addrBytes = append(addrBytes, encodeToNat(addr.Pointer.CertIndex)...)
case Enterprise, Enterprise + 1:
addrBytes = append(addrBytes, addr.Payment.Hash()...)
case Stake, Stake + 1:
addrBytes = append(addrBytes, addr.Stake.Hash()...)
}

return addrBytes
}

// Bech32 returns the Address encoded as bech32.
func (addr *Address) Bech32() string {
addrStr, err := bech32.EncodeFromBase256(getHrp(addr.Network), addr.Bytes())
addrStr, err := bech32.EncodeFromBase256(getHrp(addr.Network, addr.Type), addr.Bytes())
if err != nil {
panic(err)
}
Expand All @@ -245,15 +265,6 @@ func NewBaseAddress(network Network, payment StakeCredential, stake StakeCredent
return Address{Type: addrType, Network: network, Payment: payment, Stake: stake}, nil
}

// NewEnterpriseAddress returns a new Enterprise Address.
func NewEnterpriseAddress(network Network, payment StakeCredential) (Address, error) {
addrType := Enterprise
if payment.Type == ScriptCredential {
addrType = Enterprise + 1
}
return Address{Type: addrType, Network: network, Payment: payment}, nil
}

// Pointer is the location of the Stake Registration Certificate in the blockchain.
type Pointer struct {
Slot uint64
Expand All @@ -270,6 +281,24 @@ func NewPointerAddress(network Network, payment StakeCredential, ptr Pointer) (A
return Address{Type: addrType, Network: network, Payment: payment, Pointer: ptr}, nil
}

// NewEnterpriseAddress returns a new Enterprise Address.
func NewEnterpriseAddress(network Network, payment StakeCredential) (Address, error) {
addrType := Enterprise
if payment.Type == ScriptCredential {
addrType = Enterprise + 1
}
return Address{Type: addrType, Network: network, Payment: payment}, nil
}

// NewStakeAddress returns a new Stake Address.
func NewStakeAddress(network Network, stake StakeCredential) (Address, error) {
addrType := Stake
if stake.Type == ScriptCredential {
addrType = Stake + 1
}
return Address{Type: addrType, Network: network, Stake: stake}, nil
}

func decodeFromNat(data []byte) (uint64, uint, error) {
out := big.NewInt(0)
n := uint(0)
Expand Down Expand Up @@ -316,11 +345,16 @@ func Blake224Hash(b []byte) ([]byte, error) {
return hash.Sum(nil), err
}

func getHrp(network Network) string {
func getHrp(network Network, addrType AddressType) string {
hrp := "addr"
if addrType == Stake || addrType == Stake+1 {
hrp = "stake"
}

switch network {
case Testnet, Preprod:
return "addr_test"
return hrp + "_test"
default:
return "addr"
return hrp
}
}
42 changes: 31 additions & 11 deletions address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import (
)

const (
paymentKey = "addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd"
stakeKey = "stake_vk1px4j0r2fk7ux5p23shz8f3y5y2qam7s954rgf3lg5merqcj6aetsft99wu"
scriptHash = "script1cda3khwqv60360rp5m7akt50m6ttapacs8rqhn5w342z7r35m37"
addrType0 = "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x"
addrType1 = "addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh"
addrType2 = "addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve"
addrType3 = "addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g"
addrType4 = "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrzqf96k"
addrType5 = "addr128phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtupnz75xxcrtw79hu"
addrType6 = "addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8"
addrType7 = "addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx"
paymentKey = "addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd"
stakeKey = "stake_vk1px4j0r2fk7ux5p23shz8f3y5y2qam7s954rgf3lg5merqcj6aetsft99wu"
scriptHash = "script1cda3khwqv60360rp5m7akt50m6ttapacs8rqhn5w342z7r35m37"
addrType0 = "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x"
addrType1 = "addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh"
addrType2 = "addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve"
addrType3 = "addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g"
addrType4 = "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrzqf96k"
addrType5 = "addr128phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtupnz75xxcrtw79hu"
addrType6 = "addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8"
addrType7 = "addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx"
stakeAddrType1 = "stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw"
stakeAddrType2 = "stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5"
)

var (
Expand All @@ -31,6 +33,8 @@ var (
"addr128phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtupnz75xxcrtw79hu",
"addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8",
"addr1w8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcyjy7wx",
"stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw",
"stake178phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcccycj5",
}
)

Expand Down Expand Up @@ -140,6 +144,22 @@ func TestNewAddress(t *testing.T) {
if got, want := enterprise1.Bech32(), addrType7; got != want {
t.Errorf("invalid enterprise address\ngot: %s\nwant: %s", got, want)
}

stake0, err := NewStakeAddress(Mainnet, stakeAddrCred)
if err != nil {
t.Fatal(err)
}
if got, want := stake0.Bech32(), stakeAddrType1; got != want {
t.Errorf("invalid stake address\ngot: %s\nwant: %s", got, want)
}

stake1, err := NewStakeAddress(Mainnet, scriptCred)
if err != nil {
t.Fatal(err)
}
if got, want := stake1.Bech32(), stakeAddrType2; got != want {
t.Errorf("invalid stake address\ngot: %s\nwant: %s", got, want)
}
}

func TestNat(t *testing.T) {
Expand Down
22 changes: 22 additions & 0 deletions wallet/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type TestVector struct {
addrXvk1 string
paymentAddr0 string
paymentAddr1 string
stakeAddr string
}

var testVectors = []TestVector{
Expand All @@ -28,6 +29,7 @@ var testVectors = []TestVector{
addrXvk1: "addr_xvk1y3r70ejyadsaplez83p7uhy8p6l08a5sjl860kszevxu0jaxcwmx6avghwwqluj3eqg8zn7m0847smgh6hf3hs6ycj9wk6lsrplncvsxqj6wd",
paymentAddr0: "addr_test1vpu5vlrf4xkxv2qpwngf6cjhtw542ayty80v8dyr49rf5eg57c2qv",
paymentAddr1: "addr_test1vq0a2lgc2e0r597dr983jrf5ns4hxz027u8n7wlcsjcw4ks96yjys",
stakeAddr: "stake_test1urxr8x34l8s0uquu75gvwcw5m55sgrzga9jhlkk8a8qpm9q9p2w0s",
},
// 18 words
{
Expand All @@ -39,6 +41,7 @@ var testVectors = []TestVector{
addrXvk1: "addr_xvk135hqmkaqydnxnq6wmjkkhasvwjprpnqnzsrwwes6mql45enlcsqs0vz4xasvja4qsvrw93v3gc7mmep76cf67a4yffdrsvuxqm9qhwqlvnay5",
paymentAddr0: "addr_test1vptvyjfjvs7wdn583rv3th3fvf9fauv5f6gylkhh5k245zcdjvdac",
paymentAddr1: "addr_test1vr3nq3kyg9c9t4nn6a5zymz3at3zsmcr9lkqxghxh5v822gcu7ava",
stakeAddr: "stake_test1uzx24u6vjgmgfzqg38uqrmrs720pum2sgxqntv0yzpvf72qhlmveq",
},
// 21 words
{
Expand All @@ -50,6 +53,7 @@ var testVectors = []TestVector{
addrXvk1: "addr_xvk1ndtepmpg06x9nskfasvr50mue356e4rqlvuzf8jjcj6n48feexsg7nsjyplmsky60snfh2kreugf8gdgw6d5q77mr5494jl5swwr0ysprauul",
paymentAddr0: "addr_test1vz83dnlqqtdrlct4kz3f7d07d59w6p4yrtlr62340yklhaqrrykc7",
paymentAddr1: "addr_test1vzr08acccp7s3l9cppvptz7jyflejkkuma2k06vx4vjrcqsl4gkk5",
stakeAddr: "stake_test1urvx673x97g9dadcs2hjse5luwpsempg5fny6u56lx7vv3gpzreln",
},
}

Expand Down Expand Up @@ -94,6 +98,15 @@ func TestCreateWallet(t *testing.T) {
if addresses[0].Bech32() != testVector.paymentAddr0 {
t.Errorf("invalid paymentAddr0:\ngot: %v\nwant: %v", addresses[0], testVector.paymentAddr0)
}

stakeAddr, err := w.StakeAddress()
if err != nil {
t.Fatal(err)
}

if stakeAddr.Bech32() != testVector.stakeAddr {
t.Errorf("invalid stakeAddr:\ngot: %v\nwant: %v", stakeAddr, testVector.stakeAddr)
}
}
}

Expand Down Expand Up @@ -126,5 +139,14 @@ func TestRestoreWallet(t *testing.T) {
if addresses[0].Bech32() != testVector.paymentAddr0 {
t.Errorf("invalid paymentAddr0:\ngot: %v\nwant: %v", addresses[0], testVector.paymentAddr0)
}

stakeAddr, err := w.StakeAddress()
if err != nil {
t.Fatal(err)
}

if stakeAddr.Bech32() != testVector.stakeAddr {
t.Errorf("invalid stakeAddr:\ngot: %v\nwant: %v", stakeAddr, testVector.stakeAddr)
}
}
}
10 changes: 9 additions & 1 deletion wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,15 @@ func (w *Wallet) Addresses() ([]cardano.Address, error) {
return addresses, nil
}

func (w *Wallet) Keys() (crypto.PrvKey, crypto.PrvKey) {
func (w *Wallet) StakeAddress() (cardano.Address, error) {
stake, err := cardano.NewKeyCredential(w.stakeKey.PubKey())
if err != nil {
return cardano.Address{}, err
}
return cardano.NewStakeAddress(w.network, stake)
}

func (w *Wallet) PrvKeys() (crypto.PrvKey, crypto.PrvKey) {
return w.addrKeys[0].PrvKey(), w.stakeKey.PrvKey()
}

Expand Down

0 comments on commit d5eeb84

Please sign in to comment.