diff --git a/address.go b/address.go index 5f7fba8..acea8ce 100644 --- a/address.go +++ b/address.go @@ -15,6 +15,7 @@ const ( Base AddressType = 0x00 Ptr AddressType = 0x04 Enterprise AddressType = 0x06 + Stake AddressType = 0x0e ) // Address represents a Cardano address. @@ -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) @@ -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) @@ -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 @@ -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)...) @@ -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) } @@ -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 @@ -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 } diff --git a/bech32/bech32.go b/bech32/bech32.go new file mode 100644 index 0000000..35c0f1d --- /dev/null +++ b/bech32/bech32.go @@ -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 +} diff --git a/bech32/prefixes/cip5.go b/bech32/prefixes/cip5.go new file mode 100644 index 0000000..9eae84f --- /dev/null +++ b/bech32/prefixes/cip5.go @@ -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 +) diff --git a/credential.go b/credential.go index ccc1134..3086f8a 100644 --- a/credential.go +++ b/credential.go @@ -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)