77 "strings"
88 "time"
99
10+ "github.com/google/uuid"
1011 "github.com/jdgcs/ed25519/extra25519"
1112 "github.com/mr-tron/base58"
1213 "github.com/pkg/errors"
@@ -16,32 +17,32 @@ import (
1617
1718 chatpb "github.com/code-payments/code-protobuf-api/generated/go/chat/v1"
1819
19- "github.com/code-payments/code-server/pkg/netutil"
2020 "github.com/code-payments/code-server/pkg/code/common"
21+ "github.com/code-payments/code-server/pkg/netutil"
2122)
2223
2324const (
2425 // Shared buffer for domain, encrypted message, and other dynamically sized values in encoding scheme
2526 //
2627 // Note: May be different per type and version of blockchain message in the future
2728 // Note: Assumes up to two additional token transfer instructions in the transactions for paying fees and user
28- maxDynamicContentSize = 650
29+ maxNaclBoxDynamicContentSize = 650
2930)
3031
3132type BlockchainMessageType uint8
3233
3334const (
34- NaclBoxBlockchainMessage BlockchainMessageType = iota
35+ NaclBoxType BlockchainMessageType = iota
36+ FiatOnrampPurchase
3537)
3638
3739const naclBoxNonceLength = 24
3840
3941type naclBoxNonce [naclBoxNonceLength ]byte
4042
41- // BlockchainMessage is an encrypted message, plus associated metadata, sent
43+ // NaclBoxBlockchainMessage is an encrypted message, plus associated metadata, sent
4244// over the blockchain.
43- type BlockchainMessage struct {
44- Type BlockchainMessageType
45+ type NaclBoxBlockchainMessage struct {
4546 Version uint8
4647 Flags uint32
4748 SenderDomain string // Subdomains are allowed, but unused. There could be something there as a feature.
@@ -50,14 +51,14 @@ type BlockchainMessage struct {
5051 EncryptedMessage []byte
5152}
5253
53- // NewNaclBoxBlockchainMessage returns a new BlockchainMessage using a NCAL box
54+ // NewNaclBoxBlockchainMessage returns a new BlockchainMessage using a NACL box
5455// where the shared key is derived using ECDH.
5556func NewNaclBoxBlockchainMessage (
5657 senderDomain string ,
5758 plaintextMessage string ,
5859 sender * common.Account ,
5960 receiver * common.Account ,
60- ) (* BlockchainMessage , error ) {
61+ ) (* NaclBoxBlockchainMessage , error ) {
6162 if err := netutil .ValidateDomainName (senderDomain ); err != nil {
6263 return nil , errors .Wrap (err , "domain is invalid" )
6364 }
@@ -73,12 +74,11 @@ func NewNaclBoxBlockchainMessage(
7374
7475 encryptedMessage , nonce := encryptMessageUsingNaclBox (sender , receiver , plaintextMessage )
7576
76- if len (encryptedMessage )+ len (senderDomain ) > maxDynamicContentSize {
77+ if len (encryptedMessage )+ len (senderDomain ) > maxNaclBoxDynamicContentSize {
7778 return nil , errors .New ("encrypted message length exceeds limit" )
7879 }
7980
80- return & BlockchainMessage {
81- Type : NaclBoxBlockchainMessage ,
81+ return & NaclBoxBlockchainMessage {
8282 Version : 0 ,
8383 Flags : 0 ,
8484 SenderDomain : senderDomain ,
@@ -88,12 +88,12 @@ func NewNaclBoxBlockchainMessage(
8888 }, nil
8989}
9090
91- // Encode encodes the BlockchainMessage into a format compatible with the Solana
91+ // Encode encodes the NaclBoxBlockchainMessage into a format compatible with the Solana
9292// memo instruction.
93- func (m * BlockchainMessage ) Encode () ([]byte , error ) {
93+ func (m * NaclBoxBlockchainMessage ) Encode () ([]byte , error ) {
9494 var buffer []byte
9595
96- buffer = append (buffer , uint8 (m . Type ))
96+ buffer = append (buffer , uint8 (NaclBoxType ))
9797
9898 buffer = append (buffer , m .Version )
9999
@@ -113,8 +113,8 @@ func (m *BlockchainMessage) Encode() ([]byte, error) {
113113 return base122 .Encode (buffer )
114114}
115115
116- // ToProto creates the proto representation of a BlockchainMessage
117- func (m * BlockchainMessage ) ToProto (
116+ // ToProto creates the proto representation of a NaclBoxBlockchainMessage
117+ func (m * NaclBoxBlockchainMessage ) ToProto (
118118 sender * common.Account ,
119119 signature string ,
120120 ts time.Time ,
@@ -148,8 +148,8 @@ func (m *BlockchainMessage) ToProto(
148148 return msg , nil
149149}
150150
151- // DecodeBlockchainMessages attempts to decode a byte payload into a BlockchainMessage
152- func DecodeBlockchainMessage (payload []byte ) (* BlockchainMessage , error ) {
151+ // DecodeNaclBoxBlockchainMessage attempts to decode a byte payload into a NaclBoxBlockchainMessage
152+ func DecodeNaclBoxBlockchainMessage (payload []byte ) (* NaclBoxBlockchainMessage , error ) {
153153 errInvalidPayload := errors .New ("invalid payload" )
154154
155155 buffer , err := base122 .Decode (payload )
@@ -162,8 +162,8 @@ func DecodeBlockchainMessage(payload []byte) (*BlockchainMessage, error) {
162162 }
163163
164164 messageType := BlockchainMessageType (buffer [0 ])
165- if messageType != NaclBoxBlockchainMessage {
166- return nil , errors .Errorf ("message type %d is not supported " , messageType )
165+ if messageType != NaclBoxType {
166+ return nil , errors .Errorf ("expected message type %d" , NaclBoxType )
167167 }
168168
169169 // 1 + (messageType)
@@ -181,7 +181,7 @@ func DecodeBlockchainMessage(payload []byte) (*BlockchainMessage, error) {
181181
182182 version := buffer [1 ]
183183 if version != 0 {
184- return nil , errors .Errorf ("message type %d version %d is not supported" , version , messageType )
184+ return nil , errors .Errorf ("version %d is not supported" , version )
185185 }
186186
187187 flags := binary .LittleEndian .Uint32 (buffer [2 :6 ])
@@ -209,12 +209,11 @@ func DecodeBlockchainMessage(payload []byte) (*BlockchainMessage, error) {
209209 offset += len (nonce )
210210
211211 encryptedMessage := buffer [offset :]
212- if len (encryptedMessage )+ len (senderDomain ) > maxDynamicContentSize {
212+ if len (encryptedMessage )+ len (senderDomain ) > maxNaclBoxDynamicContentSize {
213213 return nil , errors .New ("encrypted message length exceeds limit" )
214214 }
215215
216- return & BlockchainMessage {
217- Type : messageType ,
216+ return & NaclBoxBlockchainMessage {
218217 Version : version ,
219218 Flags : flags ,
220219 SenderDomain : senderDomain ,
@@ -260,3 +259,85 @@ func decryptMessageUsingNaclBox(receiver, sender *common.Account, encryptedPaylo
260259 }
261260 return string (message ), nil
262261}
262+
263+ type FiatOnrampPurchaseMessage struct {
264+ Version uint8
265+ Flags uint32
266+ Nonce uuid.UUID
267+ }
268+
269+ // NewFiatOnrampPurchaseMessage returns a new BlockchainMessage used to indicate
270+ // fulfillmenet of a fiat purchase by an onramp provider.
271+ func NewFiatOnrampPurchaseMessage (nonce uuid.UUID ) (* FiatOnrampPurchaseMessage , error ) {
272+ return & FiatOnrampPurchaseMessage {
273+ Version : 0 ,
274+ Flags : 0 ,
275+ Nonce : nonce ,
276+ }, nil
277+ }
278+
279+ // Encode encodes the FiatOnrampPurchaseMessage into a format compatible with the Solana
280+ // memo instruction.
281+ func (m * FiatOnrampPurchaseMessage ) Encode () ([]byte , error ) {
282+ var buffer []byte
283+
284+ buffer = append (buffer , uint8 (FiatOnrampPurchase ))
285+
286+ buffer = append (buffer , m .Version )
287+
288+ var encodedFlags [4 ]byte
289+ binary .LittleEndian .PutUint32 (encodedFlags [:], m .Flags )
290+ buffer = append (buffer , encodedFlags [:]... )
291+
292+ buffer = append (buffer , m .Nonce [:]... )
293+
294+ // Because memo requires UTF-8, and this is more space efficient than base64
295+ return base122 .Encode (buffer )
296+ }
297+
298+ // DecodeFiatOnrampPurchaseMessage attempts to decode a byte payload into a FiatOnrampPurchaseMessage
299+ func DecodeFiatOnrampPurchaseMessage (payload []byte ) (* FiatOnrampPurchaseMessage , error ) {
300+ errInvalidPayload := errors .New ("invalid payload" )
301+
302+ buffer , err := base122 .Decode (payload )
303+ if err != nil {
304+ return nil , errors .Wrap (err , errInvalidPayload .Error ())
305+ }
306+
307+ if len (buffer ) == 0 {
308+ return nil , errInvalidPayload
309+ }
310+
311+ messageType := BlockchainMessageType (buffer [0 ])
312+ if messageType != FiatOnrampPurchase {
313+ return nil , errors .Errorf ("expected message type %d" , FiatOnrampPurchase )
314+ }
315+
316+ // 1 + (messageType)
317+ // 1 + (version)
318+ // 4 + (flags)
319+ // 16 + (nonce)
320+ const messageSize = 22
321+
322+ if len (buffer ) != messageSize {
323+ return nil , errInvalidPayload
324+ }
325+
326+ version := buffer [1 ]
327+ if version != 0 {
328+ return nil , errors .Errorf ("version %d is not supported" , version )
329+ }
330+
331+ flags := binary .LittleEndian .Uint32 (buffer [2 :6 ])
332+
333+ nonce , err := uuid .FromBytes (buffer [6 :])
334+ if err != nil {
335+ return nil , errors .Wrap (err , "nonce is invalid" )
336+ }
337+
338+ return & FiatOnrampPurchaseMessage {
339+ Version : version ,
340+ Flags : flags ,
341+ Nonce : nonce ,
342+ }, nil
343+ }
0 commit comments