github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/stellar.go (about) 1 package libkb 2 3 import ( 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 15 func 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 30 func 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. 40 func 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. 65 func 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" 93 func 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 113 var assetCodePattern = regexp.MustCompile(`^[a-zA-Z0-9]{1,12}$`) 114 115 func 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 }