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  }