Skip to content

Commit a75cfb6

Browse files
author
jeffyanta
authored
Add blockchain message for fiat onramp purchase (#65)
1 parent a0fca6d commit a75cfb6

File tree

4 files changed

+138
-46
lines changed

4 files changed

+138
-46
lines changed

pkg/code/async/geyser/messenger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func processPotentialBlockchainMessage(ctx context.Context, data code_data.Provi
108108
return nil
109109
}
110110

111-
blockchainMessage, err := thirdparty.DecodeBlockchainMessage(memoIxn.Data)
111+
blockchainMessage, err := thirdparty.DecodeNaclBoxBlockchainMessage(memoIxn.Data)
112112
if err != nil {
113113
return nil
114114
}

pkg/code/chat/message_blockchain.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package chat
33
import (
44
"time"
55

6-
"github.com/pkg/errors"
7-
86
chatpb "github.com/code-payments/code-protobuf-api/generated/go/chat/v1"
97

108
"github.com/code-payments/code-server/pkg/code/common"
@@ -16,26 +14,19 @@ import (
1614
func ToBlockchainMessage(
1715
signature string,
1816
sender *common.Account,
19-
blockchainMessage *thirdparty.BlockchainMessage,
17+
blockchainMessage *thirdparty.NaclBoxBlockchainMessage,
2018
ts time.Time,
2119
) (*chatpb.ChatMessage, error) {
22-
var content []*chatpb.Content
23-
switch blockchainMessage.Type {
24-
case thirdparty.NaclBoxBlockchainMessage:
25-
content = []*chatpb.Content{
26-
{
27-
Type: &chatpb.Content_NaclBox{
28-
NaclBox: &chatpb.NaclBoxEncryptedContent{
29-
PeerPublicKey: sender.ToProto(),
30-
Nonce: blockchainMessage.Nonce,
31-
EncryptedPayload: blockchainMessage.EncryptedMessage,
32-
},
20+
content := []*chatpb.Content{
21+
{
22+
Type: &chatpb.Content_NaclBox{
23+
NaclBox: &chatpb.NaclBoxEncryptedContent{
24+
PeerPublicKey: sender.ToProto(),
25+
Nonce: blockchainMessage.Nonce,
26+
EncryptedPayload: blockchainMessage.EncryptedMessage,
3327
},
3428
},
35-
}
36-
default:
37-
return nil, errors.Errorf("%d blockchain message type not supported", blockchainMessage.Type)
29+
},
3830
}
39-
4031
return newProtoChatMessage(signature, content, ts)
4132
}

pkg/code/thirdparty/message.go

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
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

2324
const (
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

3132
type BlockchainMessageType uint8
3233

3334
const (
34-
NaclBoxBlockchainMessage BlockchainMessageType = iota
35+
NaclBoxType BlockchainMessageType = iota
36+
FiatOnrampPurchase
3537
)
3638

3739
const naclBoxNonceLength = 24
3840

3941
type 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.
5556
func 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+
}

pkg/code/thirdparty/message_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"strings"
55
"testing"
66

7+
"github.com/google/uuid"
78
"github.com/stretchr/testify/assert"
89
"github.com/stretchr/testify/require"
910

@@ -25,7 +26,6 @@ func TestNaclBoxBlockchainMessage_RoundTrip(t *testing.T) {
2526
)
2627
require.NoError(t, err)
2728

28-
assert.Equal(t, NaclBoxBlockchainMessage, blockchainMessage.Type)
2929
assert.EqualValues(t, 0, blockchainMessage.Version)
3030
assert.EqualValues(t, 0, blockchainMessage.Flags)
3131
assert.Equal(t, senderDomain, blockchainMessage.SenderDomain)
@@ -38,10 +38,9 @@ func TestNaclBoxBlockchainMessage_RoundTrip(t *testing.T) {
3838
require.NoError(t, err)
3939
assert.False(t, strings.Contains(string(encodedBytes), plaintextMessage))
4040

41-
decodedBlockchainMessage, err := DecodeBlockchainMessage(encodedBytes)
41+
decodedBlockchainMessage, err := DecodeNaclBoxBlockchainMessage(encodedBytes)
4242
require.NoError(t, err)
4343

44-
assert.Equal(t, blockchainMessage.Type, decodedBlockchainMessage.Type)
4544
assert.Equal(t, blockchainMessage.Version, decodedBlockchainMessage.Version)
4645
assert.Equal(t, blockchainMessage.Flags, decodedBlockchainMessage.Flags)
4746
assert.Equal(t, blockchainMessage.SenderDomain, decodedBlockchainMessage.SenderDomain)
@@ -53,3 +52,24 @@ func TestNaclBoxBlockchainMessage_RoundTrip(t *testing.T) {
5352
require.NoError(t, err)
5453
assert.Equal(t, plaintextMessage, string(decrypted))
5554
}
55+
56+
func TestFiatOnrampPurchaseMessage_RoundTrip(t *testing.T) {
57+
nonce := uuid.New()
58+
59+
blockchainMessage, err := NewFiatOnrampPurchaseMessage(nonce)
60+
require.NoError(t, err)
61+
62+
assert.EqualValues(t, 0, blockchainMessage.Version)
63+
assert.EqualValues(t, 0, blockchainMessage.Flags)
64+
assert.Equal(t, nonce, blockchainMessage.Nonce)
65+
66+
encodedBytes, err := blockchainMessage.Encode()
67+
require.NoError(t, err)
68+
69+
decodedBlockchainMessage, err := DecodeFiatOnrampPurchaseMessage(encodedBytes)
70+
require.NoError(t, err)
71+
72+
assert.Equal(t, blockchainMessage.Version, decodedBlockchainMessage.Version)
73+
assert.Equal(t, blockchainMessage.Flags, decodedBlockchainMessage.Flags)
74+
assert.Equal(t, blockchainMessage.Nonce, decodedBlockchainMessage.Nonce)
75+
}

0 commit comments

Comments
 (0)