github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }