-
Notifications
You must be signed in to change notification settings - Fork 1
/
messaging_service.proto
415 lines (349 loc) · 16.9 KB
/
messaging_service.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
syntax = "proto3";
package code.messaging.v1;
option go_package = "github.com/code-payments/code-protobuf-api/generated/go/messaging/v1;messaging";
option java_package = "com.codeinc.gen.messaging.v1";
option objc_class_prefix = "CPBMessagingV1";
import "common/v1/model.proto";
import "transaction/v2/transaction_service.proto";
import "validate/validate.proto";
import "google/protobuf/timestamp.proto";
service Messaging {
// OpenMessageStream opens a stream of messages. Messages are routed using the
// public key of a rendezvous keypair derived by both the sender and the
// recipient of the messages. The sender may be a client or server.
//
// Messages are expected to be acked once they have been processed by the client.
// Ack'd messages will no longer be delivered on future OpenMessageStream calls,
// and are eligible for deletion from the service. Clients should, however, handle
// duplicate delivery of messages.
//
// For grabbing a bill, the expected flow is as follows:
// 1. The payment sender creates a cash scan code
// 2. The payment sender calls OpenMessageStream on the rendezvous public key, which is
// derived by using sha256(scan payload) as the keypair seed.
// 3. The payment recipient scans the code and uses SendMessage to send their account ID
// back to the sender via the rendezvous public key.
// 4. The payment sender receives the message, submits the intent, and closes the stream.
//
// For receiving a bill of requested value, the expected flow is as follows:
// 1. The payment recipient uses SendMessage to send their account ID and payment amount to
// the sender via the rendezvous public key, which is derived by using sha256(scan payload)
// as the keypair seed.
// 2. The payment recipient calls OpenMessageStream on the rendezvous public key to listen
// for status messages generated by client/server. It must ignore the original message it sent
// as part of step 1.
// 3. The payment recipient creates a payment request scan code
// 4. The payment sender calls PollMessages on the rendezvous public key. This is ok because
// we know the message exists per step 1, and doesn't actually incur a long poll. This is a
// required hack because we don't have the infrastructure in place to allow multiple listens
// on the same stream, and the recipient needs real-time status updates.
// 5. The payment sender receives the message (any status messages are ignored), and submits the
// intent.
// 6. The payment recipient observes status message (eg. IntentSubmitted, ClientRejectedPayment,
// WebhookCalled) for payment state.
// 7. The payment recipient closes the stream once the payment hits a terminal state, or times out.
//
// For logging in, the expected flow is as follows:
// 1. The third party uses SendMessage to send their login challenge to the user via the rendezvous
// public key, which is derived by using sha256(scan payload) as the keypair seed.
// 2. The third party calls OpenMessageStream on the rendezvous public key to listen for status
// messages generated by server. It must ignore the original message it sent as part of step 1.
// 3. The third party creates a login scan code
// 4. The user logging in calls PollMessages on the rendezvous public key. This is ok because
// we know the message exists per step 1, and doesn't actually incur a long poll. This is a
// required hack because we don't have the infrastructure in place to allow multiple listens
// on the same stream, and the recipient needs real-time status updates.
// 5. The user logging in receives the message (any status messages are ignored), verifies it,
// then submits a login attempt.
// 6. The third party observes status message (eg. IntentSubmitted, ClientRejectedLogin,
// WebhookCalled) for login state.
// 7. The third party closes the stream once the login hits a terminal state, or times out.
rpc OpenMessageStream(OpenMessageStreamRequest) returns (stream OpenMessageStreamResponse);
// OpenMessageStreamWithKeepAlive is like OpenMessageStream, but enables a ping/pong
// keepalive to determine the health of the stream at both the client and server.
//
// The keepalive protocol is as follows:
// 1. Client initiates a stream by sending an OpenMessageStreamRequest.
// 2. Upon stream initialization, server begins the keepalive protocol.
// 3. Server sends a ping to the client.
// 4. Client responds with a pong as fast as possible, making note of
// the delay for when to expect the next ping.
// 5. Steps 3 and 4 are repeated until the stream is explicitly terminated
// or is deemed to be unhealthy.
//
// Client notes:
// * Client should be careful to process messages async, so any responses to pings are
// not delayed.
// * Clients should implement a reasonable backoff strategy upon continued timeout failures.
// * Clients that abuse pong messages may have their streams terminated by server.
//
// At any point in the stream, server will respond with messages in real time as
// they are observed. Messages sent over the stream should not affect the ping/pong
// protocol timings. Individual protocols for payment flows remain the same, and are
// documented in OpenMessageStream.
//
// Note: This API will enforce OpenMessageStreamRequest.signature is set as part of migration
// to this newer protocol
rpc OpenMessageStreamWithKeepAlive(stream OpenMessageStreamWithKeepAliveRequest) returns (stream OpenMessageStreamWithKeepAliveResponse);
// PollMessages is like OpenMessageStream, but uses a polling flow for receiving
// messages. Updates are not real-time and depedent on the polling interval.
// This RPC supports all message types.
//
// This is a temporary RPC until OpenMessageStream can be built out generically on
// both client and server, while supporting things like multiple listeners.
rpc PollMessages(PollMessagesRequest) returns (PollMessagesResponse);
// AckMessages acks one or more messages that have been successfully delivered to
// the client.
rpc AckMessages(AckMessagesRequest) returns (AckMesssagesResponse);
// SendMessage sends a message.
rpc SendMessage(SendMessageRequest) returns (SendMessageResponse);
}
message OpenMessageStreamRequest {
RendezvousKey rendezvous_key = 1 [(validate.rules).message.required = true];
// The signature is of serialize(OpenMessageStreamRequest) using rendezvous_key.
//
// todo: Make required once clients migrate
common.v1.Signature signature = 2 [(validate.rules).message.required = false];
}
message OpenMessageStreamResponse {
repeated Message messages = 1 [(validate.rules).repeated = {
min_items: 1
max_items: 1024
}];
}
message OpenMessageStreamWithKeepAliveRequest {
oneof request_or_pong {
option (validate.required) = true;
OpenMessageStreamRequest request = 1;
common.v1.ClientPong pong = 2;
}
}
message OpenMessageStreamWithKeepAliveResponse {
oneof response_or_ping {
option (validate.required) = true;
OpenMessageStreamResponse response = 1;
common.v1.ServerPing ping = 2;
}
}
message PollMessagesRequest {
RendezvousKey rendezvous_key = 1 [(validate.rules).message.required = true];
// The signature is of serialize(PollMessagesRequest) using rendezvous_key.
common.v1.Signature signature = 2 [(validate.rules).message.required = true];
}
message PollMessagesResponse {
repeated Message messages = 1 [(validate.rules).repeated = {
min_items: 0
max_items: 1024
}];
}
message AckMessagesRequest {
RendezvousKey rendezvous_key = 1 [(validate.rules).message.required = true];
repeated MessageId message_ids = 2 [(validate.rules).repeated = {
min_items: 1
max_items: 1024
}];
}
message AckMesssagesResponse {
Result result = 1;
enum Result {
OK = 0;
}
}
message SendMessageRequest {
// The message to send. Types of messages clients can send are restricted.
Message message = 1 [(validate.rules).message.required = true];
// The rendezvous key that the message should be routed to.
RendezvousKey rendezvous_key = 2 [(validate.rules).message.required = true];
// The signature is of serialize(Message) using the PrivateKey of the keypair.
common.v1.Signature signature = 3 [(validate.rules).message.required = true];
}
message SendMessageResponse {
Result result = 1;
enum Result {
OK = 0;
NO_ACTIVE_STREAM = 1;
}
// Set if result == OK.
MessageId message_id = 2;
}
// RendezvousKey is a unique key pair, typically derived from a scan code payload,
// which is used to establish a secure communication channel anonymously to coordinate
// a flow using messages.
message RendezvousKey {
bytes value = 1 [(validate.rules).bytes = {
min_len: 32
max_len: 32
}];
}
// MessageId identifies a message. It is only guaranteed to be unique when
// paired with a destination (i.e. the rendezvous public key).
message MessageId {
bytes value = 1 [(validate.rules).bytes = {
min_len: 16
max_len: 16
}];
}
// Request that a pulled out bill be sent to the requested address.
//
// This message type is only initiated by clients.
message RequestToGrabBill {
// Requestor is the Kin token account on Solana to which a payment should be sent.
common.v1.SolanaAccountId requestor_account = 1 [(validate.rules).message.required = true];
}
// Request that a bill of a requested value is created and sent to the requested
// address.
//
// This message type is only initiated by clients.
message RequestToReceiveBill {
// Requestor is the Kin token account on Solana to which a payment should be sent.
common.v1.SolanaAccountId requestor_account = 1 [(validate.rules).message.required = true];
// The exchange data for the requested bill value.
oneof exchange_data {
option (validate.required) = true;
// An exact amount of Kin. Payment is guaranteed to transfer the specified
// quarks in the requested currency and exchange rate.
//
// Only supports Kin. Use exchange_data.partial for fiat amounts.
transaction.v2.ExchangeData exact = 2;
// Fiat amount request. The amount of Kin is determined at time of payment
// with a recent exchange rate provided by the paying client and validatd
// by server.
//
// Only supports fiat amounts. Use exchange_data.exact for Kin.
transaction.v2.ExchangeDataWithoutRate partial = 3;
}
//
// Optional fields below to identify a domain requesting to receive a bill.
// Verification of the domain is optional. When verified, clients can establish
// relationships and third parties will by able to identify users with that
// account after payment is made.
//
// Note on field requirements:
// - Verified: All of domain, verifier, signature and rendezvous_key are required
// - Unverified: Only domain is requried
//
// The third-party's domain name, which is its primary identifier. Server
// guarantees to perform domain verification against the verifier account.
common.v1.Domain domain = 4;
// Owner account owned by the third party used in domain verification.
common.v1.SolanaAccountId verifier = 5;
// Signature of this message using the verifier private key, which in addition
// to domain verification, authenticates the third party.
common.v1.Signature signature = 6;
// Rendezvous key to avoid replay attacks
RendezvousKey rendezvous_key = 7;
// Additional fee payments splitting the requested amount. This is in addition
// to the hard-coded Code $0.01 USD fee.
repeated transaction.v2.AdditionalFeePayment additional_fees = 8 [(validate.rules).repeated.max_items = 3];
}
// A status update on a stream to indicate a scan code was scanned. This can appear
// multiple times for the same stream.
//
// This message type is only initiated by client
message CodeScanned {
// Timestamp the client scanned the code
google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true];
}
// Payment is rejected by the client
//
// This message type is only initiated by clients
message ClientRejectedPayment {
common.v1.IntentId intent_id = 1 [(validate.rules).message.required = true];
}
// Intent was submitted via SubmitIntent
//
// This message type is only initiated by server
message IntentSubmitted {
common.v1.IntentId intent_id = 1 [(validate.rules).message.required = true];
// Metadata is available for intents where it can be safely propagated publicly.
// Anything else requires an additional authenticated RPC call (eg. login).
transaction.v2.Metadata metadata = 2;
}
// Webhook was successfully called
//
// This message type is only initiated by server
message WebhookCalled {
// Estimated time webhook was received
google.protobuf.Timestamp timestamp = 1 [(validate.rules).timestamp.required = true];
}
// Request that an account logs in
//
// This message type is only initiated by third-parties through the SDK.
message RequestToLogin {
// The third-party's domain name, which is its primary identifier. Server
// guarantees to perform domain verification against the verifier account.
//
// Clients should expect subdomains for future feature compatiblity, but must
// use the ASCII base domain in the RELATIONSHIP account derivation strategy.
common.v1.Domain domain = 1 [(validate.rules).message.required = true];
// Deprecated nonce value, which is replaced by the rendezvous_key field which
// is effectively derived off a random nonce.
reserved 2;
// Reserved for a timestamp field, which may be used in the future.
reserved 3;
// Owner account owned by the third party used in domain verification.
common.v1.SolanaAccountId verifier = 4 [(validate.rules).message.required = true];
// Signature of this message using the verifier private key, which in addition
// to domain verification, authenticates the third party.
common.v1.Signature signature = 5 [(validate.rules).message.required = true];
// Rendezvous key to avoid replay attacks
RendezvousKey rendezvous_key = 6 [(validate.rules).message.required = true];
}
// Login is rejected by the client
//
// This message type is only initiated by user clients
message ClientRejectedLogin {
// Timestamp the login was rejected
google.protobuf.Timestamp timestamp = 4 [(validate.rules).timestamp.required = true];
}
// Client has received an aidrop from server
//
// This message type is only initiated by server.
message AirdropReceived {
// The type of airdrop received
transaction.v2.AirdropType airdrop_type = 1 [(validate.rules).enum.not_in = 0];
// Exchange data relating to the amount of Kin and fiat value of the airdrop
transaction.v2.ExchangeData exchange_data = 2 [(validate.rules).message.required = true];
// Time the airdrop was received
google.protobuf.Timestamp timestamp = 3 [(validate.rules).timestamp.required = true];
}
message Message {
// MessageId is the Id of the message. This ID is generated by the
// server, and will _always_ be set when receiving a message.
//
// Server generates the message to:
// 1. Reserve the ability for any future ID changes
// 2. Prevent clients attempting to collide message IDs.
MessageId id = 1 [(validate.rules).message.required = false];
// The signature sent from SendMessageRequest, which will be injected by server.
// This enables clients to ensure no MITM attacks were performed to hijack contents
// of the typed message. This is only applicable for messages not generated by server.
common.v1.Signature send_message_request_signature = 3 [(validate.rules).message.required = false];
// Next field number is 13
oneof kind {
option (validate.required) = true;
//
// Section: Cash
//
RequestToGrabBill request_to_grab_bill = 2;
//
// Section: Payment Requests
//
RequestToReceiveBill request_to_receive_bill = 5;
CodeScanned code_scanned = 6;
ClientRejectedPayment client_rejected_payment = 7;
IntentSubmitted intent_submitted = 8;
WebhookCalled webhook_called = 9;
//
// Section: Login
//
RequestToLogin request_to_login = 10;
ClientRejectedLogin client_rejected_login = 12;
//
// Section: Airdrops
//
AirdropReceived airdrop_received = 4;
}
// Reserved for deprecated LoginAttempt field
reserved 11;
}