1package libkb
2
3import (
4	"encoding/base32"
5	"errors"
6	"fmt"
7	"regexp"
8	"strings"
9
10	stellar1 "github.com/keybase/client/go/protocol/stellar1"
11	"github.com/stellar/go/keypair"
12	"github.com/stellar/go/strkey"
13)
14
15func MakeNaclSigningKeyPairFromStellarAccountID(accountID stellar1.AccountID) (res NaclSigningKeyPair, err error) {
16	byteSlice, err := strkey.Decode(strkey.VersionByteAccountID, accountID.String())
17	if err != nil {
18		return res, err
19	}
20	bytes32, err := MakeByte32Soft(byteSlice)
21	if err != nil {
22		return res, err
23	}
24	return NaclSigningKeyPair{
25		Public:  bytes32,
26		Private: nil,
27	}, nil
28}
29
30func MakeNaclSigningKeyPairFromStellarSecretKey(sec stellar1.SecretKey) (res NaclSigningKeyPair, err error) {
31	byteSlice, err := strkey.Decode(strkey.VersionByteSeed, sec.SecureNoLogString())
32	if err != nil {
33		return res, err
34	}
35	return MakeNaclSigningKeyPairFromSecretBytes(byteSlice)
36}
37
38// ParseStellarSecretKey parses a secret key and returns it and the AccountID it is the master key of.
39// Returns helpful error messages than can be shown to users.
40func ParseStellarSecretKey(secStr string) (stellar1.SecretKey, stellar1.AccountID, *keypair.Full, error) {
41	secStr = strings.ToUpper(secStr)
42	if len(secStr) != 56 {
43		return "", "", nil, fmt.Errorf("Stellar secret key must be 56 chars long: was %v", len(secStr))
44	}
45	_, err := base32.StdEncoding.DecodeString(secStr)
46	if err != nil {
47		return "", "", nil, fmt.Errorf("invalid characters in Stellar secret key")
48	}
49	kp, err := keypair.Parse(secStr)
50	if err != nil {
51		return "", "", nil, fmt.Errorf("invalid Stellar secret key: %v", err)
52	}
53	switch kp := kp.(type) {
54	case *keypair.FromAddress:
55		return "", "", nil, errors.New("unexpected Stellar account ID, expected secret key")
56	case *keypair.Full:
57		return stellar1.SecretKey(kp.Seed()), stellar1.AccountID(kp.Address()), kp, nil
58	default:
59		return "", "", nil, fmt.Errorf("invalid Stellar secret key")
60	}
61}
62
63// ParseStellarAccountID parses an account ID and returns it.
64// Returns helpful error messages than can be shown to users.
65func ParseStellarAccountID(idStr string) (stellar1.AccountID, error) {
66	idStr = strings.ToUpper(idStr)
67	if len(idStr) != 56 {
68		return "", NewInvalidStellarAccountIDError(fmt.Sprintf("Stellar account ID must be 56 chars long: was %v", len(idStr)))
69	}
70	_, err := base32.StdEncoding.DecodeString(idStr)
71	if err != nil {
72		return "", NewInvalidStellarAccountIDError("invalid characters in Stellar account ID")
73	}
74	kp, err := keypair.Parse(idStr)
75	if err != nil {
76		return "", NewInvalidStellarAccountIDError(fmt.Sprintf("invalid Stellar account ID key: %s", err))
77	}
78	switch kp := kp.(type) {
79	case *keypair.FromAddress:
80		return stellar1.AccountID(kp.Address()), nil
81	case *keypair.Full:
82		return "", NewInvalidStellarAccountIDError("unexpected Stellar secret key, expected account ID")
83	default:
84		return "", NewInvalidStellarAccountIDError("invalid keypair type")
85	}
86}
87
88// SimplifyAmount
89// Amount must be a decimal amount like "1.0" or "50"
90// Strip trailing zeros after a "."
91// Example: "1.0010000" -> "1.001"
92// Example: "1.0000000" -> "1"
93func StellarSimplifyAmount(amount string) string {
94	sides := strings.Split(amount, ".")
95	if len(sides) != 2 {
96		return amount
97	}
98	simpleRight := strings.TrimRight(sides[1], "0")
99	if strings.Contains(sides[0], ",") {
100		// If integer part has the thousands separator (comma), always
101		// print the fractional part with at least two digits - this
102		// is to ensure that thousands separator is not confused with
103		// decimal point.
104		for len(simpleRight) < 2 {
105			simpleRight += "0"
106		}
107	} else if len(simpleRight) == 0 {
108		return sides[0]
109	}
110	return sides[0] + "." + simpleRight
111}
112
113var assetCodePattern = regexp.MustCompile(`^[a-zA-Z0-9]{1,12}$`)
114
115func ParseStellarAssetCode(codeStr string) (res stellar1.AssetCode, err error) {
116	if len(codeStr) < 1 && len(codeStr) > 12 {
117		return res, fmt.Errorf("asset code must be between 1 and 12 characters long")
118	}
119	if !assetCodePattern.MatchString(codeStr) {
120		return res, fmt.Errorf("asset code must contain alphanumeric characters only")
121	}
122	fmt.Printf("%s parsed\n", codeStr)
123	return stellar1.AssetCode(codeStr), nil
124}
125