Skip to content

Commit

Permalink
Improve addresses and encoding, closes #37
Browse files Browse the repository at this point in the history
* expose bech32 module for easy marshalling
* added support for stake addresses
* added convinient method to build stake credential from key hash

Signed-off-by: Marco Bardelli <[email protected]>
  • Loading branch information
safanaj committed Nov 15, 2022
1 parent 5d486b0 commit ca21a63
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 6 deletions.
49 changes: 43 additions & 6 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
Base AddressType = 0x00
Ptr AddressType = 0x04
Enterprise AddressType = 0x06
Stake AddressType = 0x0e
)

// Address represents a Cardano address.
Expand Down Expand Up @@ -94,7 +95,7 @@ func NewAddressFromBytes(bytes []byte) (Address, error) {
}
case Ptr:
if len(bytes) <= 29 {
return addr, errors.New("enterprise address length should be greater than 29")
return addr, errors.New("pointer address length should be greater than 29")
}

index := uint(29)
Expand All @@ -120,7 +121,7 @@ func NewAddressFromBytes(bytes []byte) (Address, error) {
addr.Pointer = Pointer{Slot: slot, TxIndex: txIndex, CertIndex: certIndex}
case Ptr + 1:
if len(bytes) <= 29 {
return addr, errors.New("enterprise address length should be greater than 29")
return addr, errors.New("pointer address length should be greater than 29")
}

index := uint(29)
Expand Down Expand Up @@ -160,6 +161,22 @@ func NewAddressFromBytes(bytes []byte) (Address, error) {
Type: ScriptCredential,
ScriptHash: bytes[1:29],
}
case Stake:
if len(bytes) != 29 {
return addr, errors.New("stake 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("stake address length should be 29")
}
addr.Stake = StakeCredential{
Type: ScriptCredential,
ScriptHash: bytes[1:29],
}
}

return addr, nil
Expand Down Expand Up @@ -208,6 +225,8 @@ func (addr *Address) Bytes() []byte {
addrBytes = append(addrBytes, addr.Stake.Hash()...)
case Enterprise, Enterprise + 1:
addrBytes = append(addrBytes, addr.Payment.Hash()...)
case Stake, Stake + 1:
addrBytes = append(addrBytes, addr.Stake.Hash()...)
case Ptr, Ptr + 1:
addrBytes = append(addrBytes, addr.Payment.Hash()...)
addrBytes = append(addrBytes, encodeToNat(addr.Pointer.Slot)...)
Expand All @@ -220,7 +239,7 @@ func (addr *Address) Bytes() []byte {

// 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 Down Expand Up @@ -254,6 +273,15 @@ func NewEnterpriseAddress(network Network, payment StakeCredential) (Address, er
return Address{Type: addrType, Network: network, Payment: payment}, nil
}

// NewStakeAddress returns a new Staake 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
}

// Pointer is the location of the Stake Registration Certificate in the blockchain.
type Pointer struct {
Slot uint64
Expand Down Expand Up @@ -316,11 +344,20 @@ func Blake224Hash(b []byte) ([]byte, error) {
return hash.Sum(nil), err
}

func getHrp(network Network) string {
func getHrp(network Network, atyp ...AddressType) string {
prefix := "addr"
suffix := ""
switch network {
case Testnet, Preprod:
return "addr_test"
suffix = "_test"
default:
return "addr"
}
if len(atyp) == 1 {
switch atyp[0] {
case Stake:
prefix = "stake"
default:
}
}
return prefix + suffix
}
122 changes: 122 additions & 0 deletions bech32/bech32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package bech32

import (
"fmt"

"github.com/echovl/cardano-go/bech32/prefixes"
"github.com/echovl/cardano-go/internal/bech32"
)

type Bech32Prefix = prefixes.Bech32Prefix

var (
EncodeWithPrefix = bech32.Encode
Decode = bech32.Decode
DecodeNoLimit = bech32.DecodeNoLimit

EncodeFromBase256WithPrefix = bech32.EncodeFromBase256
DecodeToBase256 = bech32.DecodeToBase256
)

type (
Bech32Codec interface {
Prefix() string
Bytes() []byte
SetBytes([]byte)
Len() int
}
Bech32Encoder interface {
Prefix() string
Bytes() []byte
}
)

func Encode(args ...any) (string, error) {
var hrp string
var data []byte
switch len(args) {
case 1:
// the argument have to be a Bech32Codec
if c, ok := args[0].(Bech32Encoder); !ok {
return "", fmt.Errorf("Wrong parameter: %T is not a Bech32Encoder", c)
}
hrp = args[0].(Bech32Encoder).Prefix()
data = args[0].(Bech32Encoder).Bytes()
case 2:
// the argument haave to be a Bech32Codec or a string, and the second have to be a []byte
a1 := args[0]
a2 := args[1]

switch a1.(type) {
case Bech32Encoder:
hrp = a1.(Bech32Codec).Prefix()
case string:
hrp = a1.(string)
default:
return "", fmt.Errorf("Wrong 1st parameter: %T is not a string or Bech32Codec", a1)
}

if _, ok := a2.([]byte); !ok {
return "", fmt.Errorf("Wrong 2nd parameter: %T is not a []byte", a2)
}
data = a2.([]byte)
}
return bech32.Encode(hrp, data)
}

func EncodeFromBase256(args ...any) (string, error) {
var hrp string
var data []byte
switch len(args) {
case 1:
// the argument have to be a Bech32Codec
if c, ok := args[0].(Bech32Encoder); !ok {
return "", fmt.Errorf("Wrong parameter: %T is not a Bech32Encoder", c)
}
hrp = args[0].(Bech32Encoder).Prefix()
if converted, err := bech32.ConvertBits(args[0].(Bech32Encoder).Bytes(), 8, 5, true); err != nil {
return "", err
} else {
data = converted
}
case 2:
// the argument haave to be a Bech32Codec or a string, and the second have to be a []byte
a1 := args[0]
a2 := args[1]

switch a1.(type) {
case Bech32Encoder:
hrp = a1.(Bech32Encoder).Prefix()
case string:
hrp = a1.(string)
default:
return "", fmt.Errorf("Wrong 1st parameter: %T is not a string or Bech32Codec", a1)
}

if _, ok := a2.([]byte); !ok {
return "", fmt.Errorf("Wrong 2nd parameter: %T is not a []byte", a2)
}
if converted, err := bech32.ConvertBits(a2.([]byte), 8, 5, true); err != nil {
} else {
data = converted
}
}
return bech32.Encode(hrp, data)
}

func DecodeInto(be32 string, codec Bech32Codec) error {
expectedLen := codec.Len()
expectedPrefix := codec.Prefix()
hrp, data, err := bech32.DecodeToBase256(be32)
if err != nil {
return err
}
if hrp != expectedPrefix {
return fmt.Errorf("Wrong prefix: want %s got %s", expectedPrefix, hrp)
}
codec.SetBytes(data)
if len(codec.Bytes()) != expectedLen || string(codec.Bytes()) != string(data) {
return fmt.Errorf("Set bytes failed")
}
return nil
}
91 changes: 91 additions & 0 deletions bech32/prefixes/cip5.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package prefixes

// As specified in [CIP-5](https://github.com/cardano-foundation/CIPs/tree/master/CIP5)
//
// copied from cardano-addresses core/lib/Cardano/Codec/Bech32/Prefixes.hs

type Bech32Prefix = string

const (
// -- * Addresses

Addr Bech32Prefix = "addr"
AddrTest Bech32Prefix = "addr_test"
Script Bech32Prefix = "script"
Stake Bech32Prefix = "stake"
StakeTest Bech32Prefix = "stake_test"

// -- * Hashes

AddrPublicKeyHash Bech32Prefix = "addr_vkh"
StakePublicKeyHash Bech32Prefix = "stake_vkh"
AddrSharedPublicKeyHash Bech32Prefix = "addr_shared_vkh"
StakeSharedPublicKeyHash Bech32Prefix = "stake_shared_vkh"

// -- * Keys for 1852H
AddrPublicKey Bech32Prefix = "addr_vk"
AddrPrivateKey Bech32Prefix = "addr_sk"
AddrXPub Bech32Prefix = "addr_xvk"
AddrXPrv Bech32Prefix = "addr_xsk"
AddrExtendedPublicKey = AddrXPub
AddrExtendedPrivateKey = AddrXPrv

AcctPublicKey Bech32Prefix = "acct_vk"
AcctPrivateKey Bech32Prefix = "acct_sk"
AcctXPub Bech32Prefix = "acct_xvk"
AcctXPrv Bech32Prefix = "acct_xsk"
AcctExtendedPublicKey = AcctXPub
AcctExtendedPrivateKey = AcctXPrv

RootPublicKey Bech32Prefix = "root_vk"
RootPrivateKey Bech32Prefix = "root_sk"
RootXPub Bech32Prefix = "root_xvk"
RootXPrv Bech32Prefix = "root_xsk"
RootExtendedPublicKey = RootXPub
RootExtendedPrivateKey = RootXPrv

StakePublicKey Bech32Prefix = "stake_vk"
StakePrivateKey Bech32Prefix = "stake_sk"
StakeXPub Bech32Prefix = "stake_xvk"
StakeXPrv Bech32Prefix = "stake_xsk"
StakeExtendedPublicKey = StakeXPub
StakeExtendedPrivateKey = StakeXPrv

// -- * Keys for 1854H

AddrSharedPublicKey Bech32Prefix = "addr_shared_vk"
AddrSharedPrivateKey Bech32Prefix = "addr_shared_sk"
AddrSharedXPub Bech32Prefix = "addr_shared_xvk"
AddrSharedXPrv Bech32Prefix = "addr_shared_xsk"
AddrSharedExtendedPublicKey = AddrSharedXPub
AddrSharedExtendedPrivateKey = AddrSharedXPrv

AcctSharedPublicKey Bech32Prefix = "acct_shared_vk"
AcctSharedPrivateKey Bech32Prefix = "acct_shared_sk"
AcctSharedXPub Bech32Prefix = "acct_shared_xvk"
AcctSharedXPrv Bech32Prefix = "acct_shared_xsk"
AcctSharedExtendedPublicKey = AcctSharedXPub
AcctSharedExtendedPrivateKey = AcctSharedXPrv

RootSharedPublicKey Bech32Prefix = "root_shared_vk"
RootSharedPrivateKey Bech32Prefix = "root_shared_sk"
RootSharedXPub Bech32Prefix = "root_shared_xvk"
RootSharedXPrv Bech32Prefix = "root_shared_xsk"
RootSharedExtendedPublicKey = RootSharedXPub
RootSharedExtendedPrivateKey = RootSharedXPrv

StakeSharedPublicKey Bech32Prefix = "stake_shared_vk"
StakeSharedPrivateKey Bech32Prefix = "stake_shared_sk"
StakeSharedXPub Bech32Prefix = "stake_shared_xvk"
StakeSharedXPrv Bech32Prefix = "stake_shared_xsk"
StakeSharedExtendedPublicKey = StakeSharedXPub
StakeSharedExtendedPrivateKey = StakeSharedXPrv

// -- * Keys for 1855H
PolicyPublicKey Bech32Prefix = "policy_vk"
PolicyPrivateKey Bech32Prefix = "policy_sk"
PolicyXPub Bech32Prefix = "policy_xvk"
PolicyXPrv Bech32Prefix = "policy_xsk"
PolicyExtendedPublicKey = PolicyXPub
PolicyExtendedPrivateKey = PolicyXPrv
)
8 changes: 8 additions & 0 deletions credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ func NewKeyCredential(publicKey crypto.PubKey) (StakeCredential, error) {
return StakeCredential{Type: KeyCredential, KeyHash: keyHash}, nil
}

// NewKeyCredential creates a Key Credential from an AddrKeyHash (28 bytes key hash).
func NewKeyCredentialFromHash(keyHash AddrKeyHash) (StakeCredential, error) {
if len(keyHash) < 28 {
return StakeCredential{}, fmt.Errorf("Wrong argument: expected 28 bytes key hash")
}
return StakeCredential{Type: KeyCredential, KeyHash: keyHash[:28]}, nil
}

// NewKeyCredential creates a Script Credential.
func NewScriptCredential(script []byte) (StakeCredential, error) {
scriptHash, err := Blake224Hash(script)
Expand Down

0 comments on commit ca21a63

Please sign in to comment.