github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/stellar/relays/relays.go (about)

     1  package relays
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/keybase/client/go/libkb"
     9  	"github.com/keybase/client/go/msgpack"
    10  	"github.com/keybase/client/go/protocol/keybase1"
    11  	"github.com/keybase/client/go/protocol/stellar1"
    12  	"github.com/keybase/client/go/stellar/stellarcommon"
    13  	"github.com/keybase/client/go/teams"
    14  	"github.com/keybase/stellarnet"
    15  	"github.com/stellar/go/build"
    16  	"github.com/stellar/go/keypair"
    17  	"golang.org/x/crypto/nacl/secretbox"
    18  )
    19  
    20  // Get the key used to encrypt the stellar key for a relay transfer
    21  // A key from the implicit team betwen the logged-in user and `to`.
    22  // If `generation` is nil, gets the latest key.
    23  func GetKey(mctx libkb.MetaContext, recipient stellarcommon.Recipient) (key keybase1.TeamApplicationKey, teamID keybase1.TeamID, err error) {
    24  	meUsername, err := mctx.G().GetUPAKLoader().LookupUsername(mctx.Ctx(), mctx.ActiveDevice().UID())
    25  	if err != nil {
    26  		return key, teamID, err
    27  	}
    28  	impTeamNameStruct := keybase1.ImplicitTeamDisplayName{
    29  		Writers: keybase1.ImplicitTeamUserSet{
    30  			KeybaseUsers: []string{meUsername.String()},
    31  		},
    32  	}
    33  	switch {
    34  	case recipient.User != nil:
    35  		impTeamNameStruct.Writers.KeybaseUsers = append(impTeamNameStruct.Writers.KeybaseUsers, recipient.User.Username.String())
    36  	case recipient.Assertion != nil:
    37  		impTeamNameStruct.Writers.UnresolvedUsers = append(impTeamNameStruct.Writers.UnresolvedUsers, *recipient.Assertion)
    38  	default:
    39  		return key, teamID, fmt.Errorf("recipient unexpectly not user nor assertion: %v", recipient.Input)
    40  	}
    41  	impTeamDisplayName, err := teams.FormatImplicitTeamDisplayName(mctx.Ctx(), mctx.G(), impTeamNameStruct)
    42  	if err != nil {
    43  		return key, teamID, err
    44  	}
    45  	team, _, _, err := teams.LookupOrCreateImplicitTeam(mctx.Ctx(), mctx.G(), impTeamDisplayName, false /*public*/)
    46  	if err != nil {
    47  		return key, teamID, err
    48  	}
    49  	key, err = team.ApplicationKey(mctx.Ctx(), keybase1.TeamApplication_STELLAR_RELAY)
    50  	return key, team.ID, err
    51  }
    52  
    53  func getKeyForDecryption(mctx libkb.MetaContext, teamID keybase1.TeamID,
    54  	generation keybase1.PerTeamKeyGeneration) (res keybase1.TeamApplicationKey, err error) {
    55  	arg := keybase1.FastTeamLoadArg{
    56  		ID:                   teamID,
    57  		Applications:         []keybase1.TeamApplication{keybase1.TeamApplication_STELLAR_RELAY},
    58  		KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{generation},
    59  	}
    60  	team, err := teams.FTL(mctx, arg)
    61  	if err != nil {
    62  		return res, err
    63  	}
    64  	if len(team.ApplicationKeys) != 1 {
    65  		return res, errors.New("expected one ApplicationKey")
    66  	}
    67  	return team.ApplicationKeys[0], nil
    68  }
    69  
    70  type Input struct {
    71  	From       stellar1.SecretKey
    72  	AmountXLM  string
    73  	Note       string
    74  	PublicMemo *stellarnet.Memo
    75  	// Implicit-team key to encrypt for
    76  	EncryptFor    keybase1.TeamApplicationKey
    77  	SeqnoProvider build.SequenceProvider
    78  	Timebounds    *build.Timebounds
    79  	BaseFee       uint64
    80  }
    81  
    82  type Output struct {
    83  	// Account ID of the shared account.
    84  	RelayAccountID stellar1.AccountID
    85  	// Encrypted box containing the secret key to the account.
    86  	EncryptedB64 string
    87  	FundTx       stellarnet.SignResult
    88  }
    89  
    90  // Create generates a stellar account, encrypts its key, and signs a transaction funding it.
    91  func Create(in Input) (res Output, err error) {
    92  	_, _, senderKp, err := libkb.ParseStellarSecretKey(string(in.From))
    93  	if err != nil {
    94  		return res, err
    95  	}
    96  	senderSeed, err := stellarnet.NewSeedStr(senderKp.Seed())
    97  	if err != nil {
    98  		return res, err
    99  	}
   100  	relayKp, err := keypair.Random()
   101  	if err != nil {
   102  		return res, err
   103  	}
   104  	relayAccountID, err := stellarnet.NewAddressStr(relayKp.Address())
   105  	if err != nil {
   106  		return res, err
   107  	}
   108  	sig, err := stellarnet.CreateAccountXLMTransactionWithMemo(senderSeed, relayAccountID, in.AmountXLM,
   109  		in.PublicMemo, in.SeqnoProvider, in.Timebounds, in.BaseFee)
   110  	if err != nil {
   111  		return res, err
   112  	}
   113  	enc, err := Encrypt(stellar1.RelayContents{
   114  		StellarID: stellar1.TransactionID(sig.TxHash),
   115  		Sk:        stellar1.SecretKey(relayKp.Seed()),
   116  		Note:      in.Note,
   117  	}, in.EncryptFor)
   118  	if err != nil {
   119  		return res, err
   120  	}
   121  	pack, err := msgpack.Encode(enc)
   122  	if err != nil {
   123  		return res, err
   124  	}
   125  	return Output{
   126  		RelayAccountID: stellar1.AccountID(relayKp.Address()),
   127  		EncryptedB64:   base64.StdEncoding.EncodeToString(pack),
   128  		FundTx:         sig,
   129  	}, nil
   130  }
   131  
   132  func Encrypt(relay stellar1.RelayContents, encryptFor keybase1.TeamApplicationKey) (res stellar1.EncryptedRelaySecret, err error) {
   133  	if encryptFor.Key.IsBlank() {
   134  		return res, errors.New("attempt to use blank team application key")
   135  	}
   136  	clearpack, err := msgpack.Encode(relay)
   137  	if err != nil {
   138  		return res, err
   139  	}
   140  	nonce, err := libkb.RandomNaclDHNonce()
   141  	if err != nil {
   142  		return res, err
   143  	}
   144  	secbox := secretbox.Seal(
   145  		nil, clearpack, &nonce, (*[32]byte)(&encryptFor.Key))
   146  	return stellar1.EncryptedRelaySecret{
   147  		V:   1,
   148  		E:   secbox,
   149  		N:   nonce,
   150  		Gen: encryptFor.KeyGeneration,
   151  	}, nil
   152  }
   153  
   154  // `boxB64` should be a stellar1.EncryptedRelaySecret
   155  func DecryptB64(mctx libkb.MetaContext, teamID keybase1.TeamID, boxB64 string) (res stellar1.RelayContents, err error) {
   156  	pack, err := base64.StdEncoding.DecodeString(boxB64)
   157  	if err != nil {
   158  		return res, fmt.Errorf("error decoding relay box: %v", err)
   159  	}
   160  	var box stellar1.EncryptedRelaySecret
   161  	err = msgpack.Decode(&box, pack)
   162  	if err != nil {
   163  		return res, err
   164  	}
   165  	appKey, err := getKeyForDecryption(mctx, teamID, box.Gen)
   166  	if err != nil {
   167  		return res, err
   168  	}
   169  	return decrypt(box, appKey)
   170  }
   171  
   172  func decrypt(box stellar1.EncryptedRelaySecret, key keybase1.TeamApplicationKey) (res stellar1.RelayContents, err error) {
   173  	if box.V != 1 {
   174  		return res, fmt.Errorf("unsupported relay secret box version: %v", box.V)
   175  	}
   176  	clearpack, ok := secretbox.Open(
   177  		nil, box.E, (*[24]byte)(&box.N), (*[32]byte)(&key.Key))
   178  	if !ok {
   179  		return res, libkb.NewDecryptOpenError("relay payment secretbox")
   180  	}
   181  	err = msgpack.Decode(&res, clearpack)
   182  	if err != nil {
   183  		return res, err
   184  	}
   185  	_, _, _, err = libkb.ParseStellarSecretKey(res.Sk.SecureNoLogString())
   186  	return res, err
   187  }