From 8011af7acf8f6cdf7cf788ac3d09731a7a365d15 Mon Sep 17 00:00:00 2001 From: Marco Bardelli Date: Mon, 5 Dec 2022 09:56:51 +0100 Subject: [PATCH] Improve encoding, closes #37 * expose bech32 module for easy marshalling * added convinient method to build stake credential from key hash Signed-off-by: Marco Bardelli --- bech32/bech32.go | 122 ++++++++++++++++++++++++++++++++++++++++ bech32/prefixes/cip5.go | 91 ++++++++++++++++++++++++++++++ credential.go | 8 +++ 3 files changed, 221 insertions(+) create mode 100644 bech32/bech32.go create mode 100644 bech32/prefixes/cip5.go 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)