diff --git a/bech32/bech32.go b/bech32/bech32.go new file mode 100644 index 0000000..9d44cce --- /dev/null +++ b/bech32/bech32.go @@ -0,0 +1,128 @@ +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() + // Bytes are 8 bits encoded, convert it to 5 bits encoding + 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) + } + 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/bech32_test.go b/bech32/bech32_test.go new file mode 100644 index 0000000..2fb2ba7 --- /dev/null +++ b/bech32/bech32_test.go @@ -0,0 +1,187 @@ +package bech32 + +import ( + "encoding/hex" + "testing" + + "github.com/echovl/cardano-go/bech32/prefixes" +) + +var ( + tests = []struct { + be32 string + prefix string + dataB32 []byte // "byte" encoded in 5 bits, max value per-byte 32 + dataB256 []byte // "byte" encoded in 8 bits, max value per-byte 256 + dataB256Hex string + }{ + { + be32: "addr_vk1w0l2sr2zgfm26ztc6nl9xy8ghsk5sh6ldwemlpmp9xylzy4dtf7st80zhd", + prefix: prefixes.AddrPublicKey, + dataB32: []byte{0xe, 0xf, 0x1f, 0xa, 0x10, 0x3, 0xa, 0x2, 0x8, 0x9, 0x1b, 0xa, 0x1a, 0x2, 0xb, 0x18, 0x1a, 0x13, 0x1f, 0x5, 0x6, 0x4, 0x7, 0x8, 0x17, 0x10, 0x16, 0x14, 0x10, 0x17, 0x1a, 0x1f, 0xd, 0xe, 0x19, 0x1b, 0x1f, 0x1, 0x1b, 0x1, 0x5, 0x6, 0x4, 0x1f, 0x2, 0x4, 0x15, 0xd, 0xb, 0x9, 0x1e, 0x10}, + dataB256: []byte{0x73, 0xfe, 0xa8, 0xd, 0x42, 0x42, 0x76, 0xad, 0x9, 0x78, 0xd4, 0xfe, 0x53, 0x10, 0xe8, 0xbc, 0x2d, 0x48, 0x5f, 0x5f, 0x6b, 0xb3, 0xbf, 0x87, 0x61, 0x29, 0x89, 0xf1, 0x12, 0xad, 0x5a, 0x7d}, + dataB256Hex: "73fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d", + }, + { + be32: "stake_vk1px4j0r2fk7ux5p23shz8f3y5y2qam7s954rgf3lg5merqcj6aetsft99wu", + prefix: prefixes.StakePublicKey, + dataB32: []byte{0x1, 0x6, 0x15, 0x12, 0xf, 0x3, 0xa, 0x9, 0x16, 0x1e, 0x1c, 0x6, 0x14, 0x1, 0xa, 0x11, 0x10, 0x17, 0x2, 0x7, 0x9, 0x11, 0x4, 0x14, 0x4, 0xa, 0x0, 0x1d, 0x1b, 0x1e, 0x10, 0x5, 0x14, 0x15, 0x3, 0x8, 0x9, 0x11, 0x1f, 0x8, 0x14, 0x1b, 0x19, 0x3, 0x0, 0x18, 0x12, 0x1a, 0x1d, 0x19, 0xb, 0x10}, + dataB256: []byte{0x9, 0xab, 0x27, 0x8d, 0x49, 0xb7, 0xb8, 0x6a, 0x5, 0x51, 0x85, 0xc4, 0x74, 0xc4, 0x94, 0x22, 0x81, 0xdd, 0xfa, 0x5, 0xa5, 0x46, 0x84, 0xc7, 0xe8, 0xa6, 0xf2, 0x30, 0x62, 0x5a, 0xee, 0x57}, + dataB256Hex: "09ab278d49b7b86a055185c474c4942281ddfa05a54684c7e8a6f230625aee57", + }, + { + be32: "script1cda3khwqv60360rp5m7akt50m6ttapacs8rqhn5w342z7r35m37", + prefix: prefixes.Script, + dataB32: []byte{0x18, 0xd, 0x1d, 0x11, 0x16, 0x17, 0xe, 0x0, 0xc, 0x1a, 0xf, 0x11, 0x1a, 0xf, 0x3, 0x1, 0x14, 0x1b, 0x1e, 0x1d, 0x16, 0xb, 0x14, 0xf, 0x1b, 0x1a, 0xb, 0xb, 0x1d, 0x1, 0x1d, 0x18, 0x10, 0x7, 0x3, 0x0, 0x17, 0x13, 0x14, 0xe, 0x11, 0x15, 0xa, 0x2, 0x1e}, + dataB256: []byte{0xc3, 0x7b, 0x1b, 0x5d, 0xc0, 0x66, 0x9f, 0x1d, 0x3c, 0x61, 0xa6, 0xfd, 0xdb, 0x2e, 0x8f, 0xde, 0x96, 0xbe, 0x87, 0xb8, 0x81, 0xc6, 0xb, 0xce, 0x8e, 0x8d, 0x54, 0x2f}, + dataB256Hex: "c37b1b5dc0669f1d3c61a6fddb2e8fde96be87b881c60bce8e8d542f", + }, + { + be32: "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x", + prefix: prefixes.Addr, + dataB32: []byte{0x0, 0x6, 0xa, 0x9, 0x6, 0xc, 0xa, 0x1c, 0x1b, 0x4, 0x17, 0xb, 0xb, 0x16, 0x6, 0x4, 0x6, 0x1, 0x7, 0x6, 0xf, 0xd, 0x1f, 0x1, 0xd, 0xb, 0x11, 0x16, 0x1a, 0x18, 0xe, 0x13, 0x8, 0x14, 0x1, 0x6, 0x12, 0x11, 0x12, 0x17, 0x10, 0x4, 0xd, 0x2, 0x19, 0x3, 0x11, 0x13, 0xf, 0xd, 0x11, 0xc, 0x1f, 0x1f, 0x1b, 0x4, 0x0, 0xe, 0x10, 0x6, 0x14, 0xe, 0x16, 0xb, 0x18, 0xd, 0x7, 0x18, 0x18, 0x11, 0x10, 0x0, 0x7, 0x11, 0x14, 0x1f, 0x1c, 0x1e, 0xd, 0x3, 0xc, 0xa, 0x6, 0xe, 0x1f, 0xa, 0xe, 0x4, 0xe, 0x9, 0x8, 0x10}, + dataB256: []byte{0x1, 0x94, 0x93, 0x31, 0x5c, 0xd9, 0x2e, 0xb5, 0xd8, 0xc4, 0x30, 0x4e, 0x67, 0xb7, 0xe1, 0x6a, 0xe3, 0x6d, 0x61, 0xd3, 0x45, 0x2, 0x69, 0x46, 0x57, 0x81, 0x1a, 0x2c, 0x8e, 0x33, 0x7b, 0x62, 0xcf, 0xff, 0x64, 0x3, 0xa0, 0x6a, 0x3a, 0xcb, 0xc3, 0x4f, 0x8c, 0x46, 0x0, 0x3c, 0x69, 0xfe, 0x79, 0xa3, 0x62, 0x8c, 0xef, 0xa9, 0xc4, 0x72, 0x51}, + dataB256Hex: "019493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e337b62cfff6403a06a3acbc34f8c46003c69fe79a3628cefa9c47251", + }, + { + be32: "addr1gx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer5pnz75xxcrzqf96k", + prefix: prefixes.Addr, + dataB32: []byte{0x8, 0x6, 0xa, 0x9, 0x6, 0xc, 0xa, 0x1c, 0x1b, 0x4, 0x17, 0xb, 0xb, 0x16, 0x6, 0x4, 0x6, 0x1, 0x7, 0x6, 0xf, 0xd, 0x1f, 0x1, 0xd, 0xb, 0x11, 0x16, 0x1a, 0x18, 0xe, 0x13, 0x8, 0x14, 0x1, 0x6, 0x12, 0x11, 0x12, 0x17, 0x10, 0x4, 0xd, 0x2, 0x19, 0x3, 0x14, 0x1, 0x13, 0x2, 0x1e, 0x14, 0x6, 0x6, 0x18, 0x3}, + dataB256: []byte{0x41, 0x94, 0x93, 0x31, 0x5c, 0xd9, 0x2e, 0xb5, 0xd8, 0xc4, 0x30, 0x4e, 0x67, 0xb7, 0xe1, 0x6a, 0xe3, 0x6d, 0x61, 0xd3, 0x45, 0x2, 0x69, 0x46, 0x57, 0x81, 0x1a, 0x2c, 0x8e, 0x81, 0x98, 0xbd, 0x43, 0x1b, 0x3}, + dataB256Hex: "419493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e8198bd431b03", + }, + { + be32: "addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8", + prefix: prefixes.Addr, + dataB32: []byte{0xc, 0x6, 0xa, 0x9, 0x6, 0xc, 0xa, 0x1c, 0x1b, 0x4, 0x17, 0xb, 0xb, 0x16, 0x6, 0x4, 0x6, 0x1, 0x7, 0x6, 0xf, 0xd, 0x1f, 0x1, 0xd, 0xb, 0x11, 0x16, 0x1a, 0x18, 0xe, 0x13, 0x8, 0x14, 0x1, 0x6, 0x12, 0x11, 0x12, 0x17, 0x10, 0x4, 0xd, 0x2, 0x19, 0x3, 0x10}, + dataB256: []byte{0x61, 0x94, 0x93, 0x31, 0x5c, 0xd9, 0x2e, 0xb5, 0xd8, 0xc4, 0x30, 0x4e, 0x67, 0xb7, 0xe1, 0x6a, 0xe3, 0x6d, 0x61, 0xd3, 0x45, 0x2, 0x69, 0x46, 0x57, 0x81, 0x1a, 0x2c, 0x8e}, + dataB256Hex: "619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e", + }, + } +) + +// test encoding from string +func TestEncodeWithHrpAndBytes(t *testing.T) { + for _, test := range tests { + hrp, data, err := DecodeNoLimit(test.be32) + if err != nil { + t.Fatal(err) + } + if hrp != test.prefix { + t.Errorf("decoded wrong prefix: %s expected %s", hrp, test.prefix) + } + + if hex.EncodeToString(data) != hex.EncodeToString(test.dataB32) { + t.Errorf("decoded wrong data: %#v expected %#v", data, test.dataB32) + } + + be32, err := Encode(test.prefix, data) + if err != nil { + t.Fatal(err) + } + if be32 != test.be32 { + t.Errorf("encoded wrong: %s expected %s", be32, test.be32) + } + } +} + +// test encoding from bytes +func TestEncodeFromBase256WithHrpAndBytes(t *testing.T) { + for _, test := range tests { + hrp, data, err := DecodeToBase256(test.be32) + if err != nil { + t.Fatal(err) + } + if hrp != test.prefix { + t.Errorf("decoded wrong prefix: %s expected %s", hrp, test.prefix) + } + + if hex.EncodeToString(data) != test.dataB256Hex { + t.Errorf("decoded wrong data: %#v expected %#v", data, test.dataB256Hex) + } + + be32, err := EncodeFromBase256(test.prefix, data) + if err != nil { + t.Fatal(err) + } + if be32 != test.be32 { + t.Errorf("encoded wrong: %s expected %s", be32, test.be32) + } + } +} + +// test Codec implementations +type AddrPublicKey [32]byte + +func (vk AddrPublicKey) Bytes() []byte { return vk[:] } +func (vk AddrPublicKey) Len() int { return len(vk) } +func (vk *AddrPublicKey) SetBytes(b []byte) { copy((*vk)[:], b) } +func (vk AddrPublicKey) Prefix() string { return prefixes.AddrPublicKey } + +type StakePublicKey struct { + AddrPublicKey +} + +func (vk StakePublicKey) Prefix() string { return prefixes.StakePublicKey } + +func TestBech32Codec(t *testing.T) { + want1 := tests[0] + want2 := tests[1] + got1 := AddrPublicKey{} + got2 := StakePublicKey{} + if err := DecodeInto(want1.be32, &got1); err != nil { + t.Fatal(err) + } + if err := DecodeInto(want2.be32, &got2); err != nil { + t.Fatal(err) + } + + if got1.Prefix() != want1.prefix { + t.Errorf("decoded wrong prefix: %s expected %s", got1.Prefix(), want1.prefix) + } + + if hex.EncodeToString(got1.Bytes()) != want1.dataB256Hex { + t.Errorf("decoded wrong data: %#v expected %#v", got1.Bytes(), want1.dataB256Hex) + } + + if got2.Prefix() != want2.prefix { + t.Errorf("decoded wrong prefix: %s expected %s", got1.Prefix(), want1.prefix) + } + + if hex.EncodeToString(got2.Bytes()) != want2.dataB256Hex { + t.Errorf("decoded wrong data: %#v expected %#v", got2.Bytes(), want2.dataB256Hex) + } + + if s, err := Encode(got1); err != nil { + t.Fatal(err) + } else { + if s != want1.be32 { + t.Errorf("encoded wrong Bech32Codec: %s expected %s", s, want1.be32) + } + } + + if s, err := EncodeFromBase256(got1); err != nil { + t.Fatal(err) + } else { + if s != want1.be32 { + t.Errorf("encoded wrong Bech32Codec: %s expected %s", s, want1.be32) + } + } + + if s, err := Encode(got2); err != nil { + t.Fatal(err) + } else { + if s != want2.be32 { + t.Errorf("encoded wrong Bech32Codec: %s expected %s", s, want2.be32) + } + } + + if s, err := EncodeFromBase256(got2); err != nil { + t.Fatal(err) + } else { + if s != want2.be32 { + t.Errorf("encoded wrong Bech32Codec: %s expected %s", s, want2.be32) + } + } + +} 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)