stellar-sdk 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,23 @@ describe Stellar::Client do
4
4
 
5
5
  subject(:client) { Stellar::Client.default_testnet }
6
6
 
7
+ describe "headers" do
8
+ let(:headers) { client.horizon.headers }
9
+
10
+ it "has 'Accept'" do
11
+ expect(headers["Accept"]).
12
+ to eq "application/hal+json,application/problem+json,application/json"
13
+ end
14
+
15
+ it "has 'X-Client-Name'" do
16
+ expect(headers["X-Client-Name"]).to eq "ruby-stellar-sdk"
17
+ end
18
+
19
+ it "has 'X-Client-Version'" do
20
+ expect(headers["X-Client-Version"]).to eq Stellar::VERSION
21
+ end
22
+ end
23
+
7
24
  describe "#default_testnet" do
8
25
  it 'instantiates a client pointing to horizon testnet' do
9
26
  client = described_class.default_testnet
@@ -410,4 +427,4 @@ describe Stellar::Client do
410
427
  end
411
428
  end
412
429
 
413
- end
430
+ end
@@ -0,0 +1,1148 @@
1
+ require "spec_helper"
2
+
3
+ describe Stellar::SEP10 do
4
+
5
+ subject(:sep10) { Stellar::SEP10 }
6
+
7
+ let(:server) { Stellar::KeyPair.random }
8
+ let(:user) { Stellar::KeyPair.random }
9
+ let(:anchor) { "SDF" }
10
+ let(:timeout) { 600 }
11
+ let(:envelope) { Stellar::TransactionEnvelope.from_xdr(subject, "base64") }
12
+ let(:transaction) { envelope.tx }
13
+
14
+ subject do
15
+ sep10.build_challenge_tx(server: server, client: user, anchor_name: anchor, timeout: timeout)
16
+ end
17
+
18
+ describe "#build_challenge_tx" do
19
+ it "generates a valid SEP10 challenge" do
20
+ expect(transaction.seq_num).to eql(0)
21
+ expect(transaction.operations.size).to eql(1);
22
+ expect(transaction.source_account).to eql(server.public_key);
23
+
24
+ time_bounds = transaction.time_bounds
25
+ expect(time_bounds.max_time - time_bounds.min_time).to eql(600)
26
+ operation = transaction.operations.first
27
+
28
+ expect(operation.body.arm).to eql(:manage_data_op)
29
+ expect(operation.body.value.data_name).to eql("SDF auth")
30
+ expect(operation.source_account).to eql(user.public_key)
31
+ data_value = operation.body.value.data_value
32
+ expect(data_value.bytes.size).to eql(64)
33
+ expect(data_value.unpack("m")[0].size).to eql(48)
34
+ end
35
+
36
+ describe "defaults" do
37
+ subject do
38
+ sep10.build_challenge_tx(server: server, client: user, anchor_name: anchor)
39
+ end
40
+
41
+ it "has a default timeout of 300 seconds (5 minutes)" do
42
+ time_bounds = transaction.time_bounds
43
+ expect(time_bounds.max_time - time_bounds.min_time).to eql(300)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#read_challenge_tx" do
49
+ subject do
50
+ challenge = super()
51
+ envelope = Stellar::TransactionEnvelope.from_xdr(challenge, 'base64')
52
+ envelope.tx.to_envelope(server, user).to_xdr(:base64)
53
+ end
54
+
55
+ it "returns the envelope and client public key if the transaction is valid" do
56
+ expect(sep10.read_challenge_tx(challenge_xdr: subject, server: server)).to eql([envelope, user.address])
57
+ end
58
+
59
+ it "returns the envelope even if transaction signed by server but not client" do
60
+ envelope = Stellar::TransactionEnvelope.from_xdr(subject, 'base64')
61
+ expected_envelope = envelope.tx.to_envelope(server)
62
+ expect(
63
+ sep10.read_challenge_tx(challenge_xdr: expected_envelope.to_xdr(:base64), server: server)
64
+ ).to eql(
65
+ [expected_envelope, user.address]
66
+ )
67
+ end
68
+
69
+ it "throws an error if there are too many operations on the transaction" do
70
+ envelope = Stellar::TransactionEnvelope.from_xdr(subject, 'base64')
71
+ envelope.tx.operations += [Stellar::Operation.bump_sequence({bump_to: 1})]
72
+ bad_challege = envelope.tx.to_envelope(server, user).to_xdr(:base64)
73
+ expect {
74
+ sep10.read_challenge_tx(challenge_xdr: bad_challege, server:server)
75
+ }.to raise_error(
76
+ Stellar::InvalidSep10ChallengeError,
77
+ /The transaction should contain only one operation/
78
+ )
79
+ end
80
+
81
+ it "throws an error if transaction sequence number is different to zero" do
82
+ envelope.tx.seq_num = 1
83
+
84
+ expect {
85
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: server)
86
+ }.to raise_error(Stellar::InvalidSep10ChallengeError, /The transaction sequence number should be zero/)
87
+ end
88
+
89
+ it "throws an error if transaction source account is different to server account id" do
90
+ expect {
91
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: Stellar::KeyPair.random)
92
+ }.to raise_error(Stellar::InvalidSep10ChallengeError, /The transaction source account is not equal to the server's account/)
93
+ end
94
+
95
+ it "throws an error if transaction doesn't contain any operation" do
96
+ envelope.tx.operations = []
97
+
98
+ expect {
99
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: server)
100
+ }.to raise_error(Stellar::InvalidSep10ChallengeError, /The transaction should contain only one operation/)
101
+ end
102
+
103
+ it "throws an error if operation does not contain the source account" do
104
+ op = envelope.tx.operations[0]
105
+ op.source_account = nil
106
+
107
+ expect {
108
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: server)
109
+ }.to raise_error(Stellar::InvalidSep10ChallengeError, /The transaction's operation should contain a source account/)
110
+ end
111
+
112
+ it "throws an error if operation is not manage data" do
113
+ envelope.tx.operations = [
114
+ Stellar::Operation.payment(
115
+ destination: Stellar::KeyPair.random,
116
+ amount: [:native, 20],
117
+ source_account: Stellar::KeyPair.random
118
+ )
119
+ ]
120
+
121
+ expect {
122
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: server)
123
+ }.to raise_error(Stellar::InvalidSep10ChallengeError, /The transaction's operation should be manageData/)
124
+ end
125
+
126
+ it "throws an error if operation value is not a 64 bytes base64 string" do
127
+ transaction.operations[0].body.value.data_value = SecureRandom.random_bytes(64)
128
+ expect {
129
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: server)
130
+ }.to raise_error(
131
+ Stellar::InvalidSep10ChallengeError,
132
+ /The transaction's operation value should be a 64 bytes base64 random string/
133
+ )
134
+ end
135
+
136
+ it "throws an error if transaction is not signed by the server" do
137
+ envelope.signatures = envelope.signatures.slice(1, 2)
138
+
139
+ expect {
140
+ sep10.read_challenge_tx(challenge_xdr: envelope.to_xdr(:base64), server: server)
141
+ }.to raise_error(
142
+ Stellar::InvalidSep10ChallengeError,
143
+ /The transaction is not signed by the server/
144
+ )
145
+ end
146
+
147
+
148
+ it "throws an error if transaction does not contain valid timeBounds" do
149
+ envelope.tx.time_bounds = nil
150
+ challenge = envelope.tx.to_envelope(server, user).to_xdr(:base64)
151
+
152
+ expect {
153
+ sep10.read_challenge_tx(challenge_xdr: challenge, server: server)
154
+ }.to raise_error(
155
+ Stellar::InvalidSep10ChallengeError,
156
+ /The transaction has expired/
157
+ )
158
+
159
+ envelope.tx.time_bounds = Stellar::TimeBounds.new(min_time: 0, max_time: 5)
160
+ challenge = envelope.tx.to_envelope(server, user).to_xdr(:base64)
161
+
162
+ expect {
163
+ sep10.read_challenge_tx(challenge_xdr: challenge, server: server)
164
+ }.to raise_error(
165
+ Stellar::InvalidSep10ChallengeError,
166
+ /The transaction has expired/
167
+ )
168
+
169
+ now = Time.now.to_i
170
+ envelope.tx.time_bounds = Stellar::TimeBounds.new(
171
+ min_time: now + 100,
172
+ max_time: now + 500
173
+ )
174
+ challenge = envelope.tx.to_envelope(server, user).to_xdr(:base64)
175
+
176
+ expect {
177
+ sep10.read_challenge_tx(challenge_xdr: challenge, server: server)
178
+ }.to raise_error(
179
+ Stellar::InvalidSep10ChallengeError,
180
+ /The transaction has expired/
181
+ )
182
+ end
183
+ end
184
+
185
+ describe "#verify_challenge_tx_threshold" do
186
+ it "raises transaction not signed by server" do
187
+ server_kp = Stellar::KeyPair.random
188
+ client_kp_a = Stellar::KeyPair.random
189
+ client_kp_b = Stellar::KeyPair.random
190
+ client_kp_c = Stellar::KeyPair.random
191
+ timeout = 600
192
+ anchor_name = "SDF"
193
+
194
+ challenge = sep10.build_challenge_tx(
195
+ server: server_kp,
196
+ client: client_kp_a,
197
+ anchor_name: anchor_name,
198
+ timeout: timeout,
199
+ )
200
+
201
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
202
+ challenge_envelope.signatures = [client_kp_a, client_kp_b, client_kp_c].map {
203
+ |kp| challenge_envelope.tx.sign_decorated(kp)
204
+ }
205
+
206
+ signers = Set[
207
+ {'key' => client_kp_a.address, 'weight': 1},
208
+ {'key' => client_kp_b.address, 'weight': 1},
209
+ {'key' => client_kp_c.address, 'weight': 1}
210
+ ]
211
+
212
+ expect {
213
+ sep10.verify_challenge_tx_threshold(
214
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
215
+ server: server_kp,
216
+ signers: signers,
217
+ threshold: 3
218
+ )
219
+ }.to raise_error(
220
+ Stellar::InvalidSep10ChallengeError,
221
+ /The transaction is not signed by the server/
222
+ )
223
+ end
224
+
225
+ it "succeeds with extra signers passed" do
226
+ server_kp = Stellar::KeyPair.random
227
+ client_kp_a = Stellar::KeyPair.random
228
+ client_kp_b = Stellar::KeyPair.random
229
+ client_kp_c = Stellar::KeyPair.random
230
+
231
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
232
+ sep10.build_challenge_tx(
233
+ server: server_kp,
234
+ client: client_kp_a,
235
+ anchor_name: "SDF",
236
+ timeout: 600,
237
+ ),
238
+ "base64"
239
+ )
240
+ challenge_envelope.signatures += [
241
+ challenge_envelope.tx.sign_decorated(client_kp_a),
242
+ challenge_envelope.tx.sign_decorated(client_kp_b)
243
+ ]
244
+ challenge = challenge_envelope.to_xdr(:base64)
245
+
246
+ expect(
247
+ sep10.verify_challenge_tx_threshold(
248
+ challenge_xdr: challenge,
249
+ server: server_kp,
250
+ signers: Set[
251
+ {'key' => client_kp_a.address, 'weight' => 1},
252
+ {'key' => client_kp_b.address, 'weight' => 1},
253
+ {'key' => client_kp_c.address, 'weight' => 1}
254
+ ],
255
+ threshold: 2
256
+ )
257
+ ).to eql(
258
+ Set[
259
+ {'key' => client_kp_a.address, 'weight' => 1},
260
+ {'key' => client_kp_b.address, 'weight' => 1}
261
+ ]
262
+ )
263
+ end
264
+
265
+ it "rasies an error for unrecognizied signatures" do
266
+ server_kp = Stellar::KeyPair.random
267
+ client_kp_a = Stellar::KeyPair.random
268
+ client_kp_b = Stellar::KeyPair.random
269
+ client_kp_c = Stellar::KeyPair.random
270
+
271
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
272
+ sep10.build_challenge_tx(
273
+ server: server_kp,
274
+ client: client_kp_a,
275
+ anchor_name: "SDF",
276
+ timeout: 600,
277
+ ),
278
+ "base64"
279
+ )
280
+ challenge_envelope.signatures += [
281
+ challenge_envelope.tx.sign_decorated(client_kp_a),
282
+ challenge_envelope.tx.sign_decorated(client_kp_b),
283
+ challenge_envelope.tx.sign_decorated(client_kp_c)
284
+ ]
285
+ challenge = challenge_envelope.to_xdr(:base64)
286
+
287
+ expect {
288
+ sep10.verify_challenge_tx_threshold(
289
+ challenge_xdr: challenge,
290
+ server: server_kp,
291
+ signers: Set[
292
+ {'key' => client_kp_a.address, 'weight' => 1},
293
+ {'key' => client_kp_b.address, 'weight' => 1},
294
+ ],
295
+ threshold: 2
296
+ )
297
+ }.to raise_error(
298
+ Stellar::InvalidSep10ChallengeError,
299
+ /Transaction has unrecognized signatures./
300
+ )
301
+ end
302
+
303
+ it "verifies proper challenge and threshold" do
304
+ server_kp = Stellar::KeyPair.random
305
+ client_kp_a = Stellar::KeyPair.random
306
+ client_kp_b = Stellar::KeyPair.random
307
+ client_kp_c = Stellar::KeyPair.random
308
+ timeout = 600
309
+ anchor_name = "SDF"
310
+
311
+ challenge = sep10.build_challenge_tx(
312
+ server: server_kp,
313
+ client: client_kp_a,
314
+ anchor_name: anchor_name,
315
+ timeout: timeout,
316
+ )
317
+
318
+ transaction = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
319
+ transaction.signatures += [
320
+ client_kp_a, client_kp_b, client_kp_c
321
+ ].map { |kp| transaction.tx.sign_decorated(kp) }
322
+ challenge_tx = transaction.to_xdr(:base64)
323
+
324
+ signers = Set[
325
+ {"key" => client_kp_a.address, "weight" => 1},
326
+ {"key" => client_kp_b.address, "weight" => 2},
327
+ {"key" => client_kp_c.address, "weight" => 4},
328
+ ]
329
+
330
+ signers_found = sep10.verify_challenge_tx_threshold(
331
+ challenge_xdr: challenge_tx,
332
+ server: server_kp,
333
+ threshold: 7,
334
+ signers: signers,
335
+ )
336
+ expect(signers_found).to eql(signers)
337
+ end
338
+
339
+ it "raises error when signers don't meet threshold" do
340
+ server_kp = Stellar::KeyPair.random
341
+ client_kp_a = Stellar::KeyPair.random
342
+ client_kp_b = Stellar::KeyPair.random
343
+ client_kp_c = Stellar::KeyPair.random
344
+ timeout = 600
345
+ anchor_name = "SDF"
346
+
347
+ challenge = sep10.build_challenge_tx(
348
+ server: server_kp,
349
+ client: client_kp_a,
350
+ anchor_name: anchor_name,
351
+ timeout: timeout,
352
+ )
353
+
354
+ transaction = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
355
+ transaction.signatures.push(transaction.tx.sign_decorated(client_kp_a))
356
+ challenge_tx = transaction.to_xdr(:base64)
357
+
358
+ signers = Set[
359
+ {"key" => client_kp_a.address, "weight" => 1},
360
+ {"key" => client_kp_b.address, "weight" => 2},
361
+ {"key" => client_kp_c.address, "weight" => 4},
362
+ ]
363
+
364
+ expect {
365
+ sep10.verify_challenge_tx_threshold(
366
+ challenge_xdr: challenge_tx,
367
+ server: server_kp,
368
+ threshold: 7,
369
+ signers: signers,
370
+ )
371
+ }.to raise_error(
372
+ Stellar::InvalidSep10ChallengeError,
373
+ "signers with weight %d do not meet threshold %d." % [1, 7]
374
+ )
375
+ end
376
+
377
+ it "ignores non-G address" do
378
+ preauth_tx_hash = "TAQCSRX2RIDJNHFIFHWD63X7D7D6TRT5Y2S6E3TEMXTG5W3OECHZ2OG4"
379
+ x_hash = "XDRPF6NZRR7EEVO7ESIWUDXHAOMM2QSKIQQBJK6I2FB7YKDZES5UCLWD"
380
+ server_kp = Stellar::KeyPair.random
381
+ client_kp = Stellar::KeyPair.random
382
+
383
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
384
+ sep10.build_challenge_tx(
385
+ server: server_kp,
386
+ client: client_kp,
387
+ anchor_name: "SDF",
388
+ timeout: 600,
389
+ ),
390
+ "base64"
391
+ )
392
+ challenge_envelope.signatures += [challenge_envelope.tx.sign_decorated(client_kp)]
393
+ challenge = challenge_envelope.to_xdr(:base64)
394
+
395
+ expect(
396
+ sep10.verify_challenge_tx_threshold(
397
+ challenge_xdr: challenge,
398
+ server: server_kp,
399
+ signers: Set[
400
+ {'key'=> client_kp.address, 'weight' => 1},
401
+ {'key'=> preauth_tx_hash, 'weight' => 1},
402
+ {'key'=> x_hash, 'weight' => 1}
403
+ ],
404
+ threshold: 1
405
+ )
406
+ ).to eql(
407
+ Set[
408
+ {'key' => client_kp.address, 'weight' => 1}
409
+ ]
410
+ )
411
+ end
412
+
413
+ it "raises no signers error" do
414
+ server_kp = Stellar::KeyPair.random
415
+ client_kp_a = Stellar::KeyPair.random
416
+ client_kp_b = Stellar::KeyPair.random
417
+ client_kp_c = Stellar::KeyPair.random
418
+ timeout = 600
419
+ anchor_name = "SDF"
420
+
421
+ challenge = sep10.build_challenge_tx(
422
+ server: server_kp,
423
+ client: client_kp_a,
424
+ anchor_name: anchor_name,
425
+ timeout: timeout,
426
+ )
427
+
428
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
429
+ challenge_envelope.signatures += [
430
+ client_kp_a, client_kp_b, client_kp_c
431
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
432
+
433
+ expect {
434
+ sep10.verify_challenge_tx_threshold(
435
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
436
+ server: server_kp,
437
+ signers: Set.new,
438
+ threshold: 2
439
+ )
440
+ }.to raise_error(
441
+ Stellar::InvalidSep10ChallengeError,
442
+ /No signers provided./
443
+ )
444
+ end
445
+
446
+ it "raises an error for no signatures" do
447
+ server_kp = Stellar::KeyPair.random
448
+ client_kp = Stellar::KeyPair.random
449
+
450
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
451
+ sep10.build_challenge_tx(
452
+ server: server_kp,
453
+ client: client_kp,
454
+ anchor_name: "SDF",
455
+ timeout: 600,
456
+ ),
457
+ "base64"
458
+ )
459
+ challenge_envelope.signatures.clear
460
+ challenge = challenge_envelope.to_xdr(:base64)
461
+
462
+ expect {
463
+ sep10.verify_challenge_tx_threshold(
464
+ challenge_xdr: challenge,
465
+ server: server_kp,
466
+ signers: Set[
467
+ {'key' => client_kp.address, 'weight' => 1}
468
+ ],
469
+ threshold: 2
470
+ )
471
+ }.to raise_error(
472
+ Stellar::InvalidSep10ChallengeError,
473
+ /The transaction is not signed by the server/
474
+ )
475
+ end
476
+
477
+ it "does not pass back duplicate signers or double-count weights" do
478
+ server_kp = Stellar::KeyPair.random
479
+ client_kp = Stellar::KeyPair.random
480
+
481
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
482
+ sep10.build_challenge_tx(
483
+ server: server_kp,
484
+ client: client_kp,
485
+ anchor_name: "SDF",
486
+ timeout: 600,
487
+ ),
488
+ "base64"
489
+ )
490
+ challenge_envelope.signatures += [challenge_envelope.tx.sign_decorated(client_kp)]
491
+ challenge = challenge_envelope.to_xdr(:base64)
492
+
493
+ expect(
494
+ sep10.verify_challenge_tx_threshold(
495
+ challenge_xdr: challenge,
496
+ server: server_kp,
497
+ signers: Set[
498
+ {'key' => client_kp.address, 'weight' => 1},
499
+ {'key' => client_kp.address, 'weight' => 2}
500
+ ],
501
+ threshold: 1
502
+ )
503
+ ).to eql(
504
+ Set[{'key' => client_kp.address, 'weight' => 1}]
505
+ )
506
+
507
+ expect {
508
+ sep10.verify_challenge_tx_threshold(
509
+ challenge_xdr: challenge,
510
+ server: server_kp,
511
+ signers: Set[
512
+ {'key' => client_kp.address, 'weight' => 1},
513
+ {'key' => client_kp.address, 'weight' => 2}
514
+ ],
515
+ threshold: 3
516
+ )
517
+ }.to raise_error(
518
+ Stellar::InvalidSep10ChallengeError,
519
+ /signers with weight 1 do not meet threshold 3./
520
+ )
521
+ end
522
+
523
+ it "raises an error for duplicate signatures" do
524
+ server_kp = Stellar::KeyPair.random
525
+ client_kp = Stellar::KeyPair.random
526
+
527
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
528
+ sep10.build_challenge_tx(
529
+ server: server_kp,
530
+ client: client_kp,
531
+ anchor_name: "SDF",
532
+ timeout: 600,
533
+ ),
534
+ "base64"
535
+ )
536
+ challenge_envelope.signatures += [
537
+ challenge_envelope.tx.sign_decorated(client_kp),
538
+ challenge_envelope.tx.sign_decorated(client_kp)
539
+ ]
540
+ challenge = challenge_envelope.to_xdr(:base64)
541
+
542
+ expect {
543
+ sep10.verify_challenge_tx_threshold(
544
+ challenge_xdr: challenge,
545
+ server: server_kp,
546
+ signers: Set[{'key' => client_kp.address, 'weight' => 1}],
547
+ threshold: 1
548
+ )
549
+ }.to raise_error(
550
+ Stellar::InvalidSep10ChallengeError,
551
+ /Transaction has unrecognized signatures./
552
+ )
553
+ end
554
+
555
+ it "raises an error for duplicate signatures and signers" do
556
+ server_kp = Stellar::KeyPair.random
557
+ client_kp = Stellar::KeyPair.random
558
+
559
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
560
+ sep10.build_challenge_tx(
561
+ server: server_kp,
562
+ client: client_kp,
563
+ anchor_name: "SDF",
564
+ timeout: 600,
565
+ ),
566
+ "base64"
567
+ )
568
+ challenge_envelope.signatures += [
569
+ challenge_envelope.tx.sign_decorated(client_kp),
570
+ challenge_envelope.tx.sign_decorated(client_kp)
571
+ ]
572
+ challenge = challenge_envelope.to_xdr(:base64)
573
+
574
+ expect {
575
+ sep10.verify_challenge_tx_threshold(
576
+ challenge_xdr: challenge,
577
+ server: server_kp,
578
+ signers: Set[
579
+ {'key' => client_kp.address, 'weight' => 1},
580
+ {'key' => client_kp.address, 'weight' => 2}
581
+ ],
582
+ threshold: 1
583
+ )
584
+ }.to raise_error(
585
+ Stellar::InvalidSep10ChallengeError,
586
+ /Transaction has unrecognized signatures./
587
+ )
588
+ end
589
+ end
590
+
591
+ describe "#verify_challenge_tx" do
592
+ it "verifies proper challenge transaction" do
593
+ server_kp = Stellar::KeyPair.random
594
+ client_kp = Stellar::KeyPair.random
595
+ timeout = 600
596
+ anchor_name = "SDF"
597
+
598
+ challenge = sep10.build_challenge_tx(
599
+ server: server_kp,
600
+ client: client_kp,
601
+ anchor_name: anchor_name,
602
+ timeout: timeout,
603
+ )
604
+
605
+ envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
606
+ envelope.signatures.push(envelope.tx.sign_decorated(client_kp))
607
+ challenge_tx = envelope.to_xdr(:base64)
608
+
609
+ sep10.verify_challenge_tx(
610
+ challenge_xdr: challenge_tx,
611
+ server: server_kp
612
+ )
613
+ end
614
+
615
+ it "raises not signed by client" do
616
+ server_kp = Stellar::KeyPair.random
617
+ client_kp = Stellar::KeyPair.random
618
+ timeout = 600
619
+ anchor_name = "SDF"
620
+
621
+ challenge = sep10.build_challenge_tx(
622
+ server: server_kp,
623
+ client: client_kp,
624
+ anchor_name: anchor_name,
625
+ timeout: timeout,
626
+ )
627
+
628
+ expect {
629
+ sep10.verify_challenge_tx(
630
+ challenge_xdr: challenge,
631
+ server: server_kp
632
+ )
633
+ }.to raise_error(
634
+ Stellar::InvalidSep10ChallengeError,
635
+ "Transaction not signed by client: %s" % [client_kp.address]
636
+ )
637
+ end
638
+ end
639
+
640
+ describe "#verify_challenge_tx_signers" do
641
+ it "returns expected signatures" do
642
+ server_kp = Stellar::KeyPair.random
643
+ client_kp_a = Stellar::KeyPair.random
644
+ client_kp_b = Stellar::KeyPair.random
645
+ client_kp_c = Stellar::KeyPair.random
646
+ timeout = 600
647
+ anchor_name = "SDF"
648
+
649
+ challenge = sep10.build_challenge_tx(
650
+ server: server_kp,
651
+ client: client_kp_a,
652
+ anchor_name: anchor_name,
653
+ timeout: timeout,
654
+ )
655
+
656
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
657
+ challenge_envelope.signatures += [
658
+ client_kp_a, client_kp_b, client_kp_c
659
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
660
+
661
+ signers = Set[
662
+ client_kp_a.address,
663
+ client_kp_b.address,
664
+ client_kp_c.address,
665
+ Stellar::KeyPair.random.address
666
+ ]
667
+ signers_found = sep10.verify_challenge_tx_signers(
668
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
669
+ server: server_kp,
670
+ signers: signers
671
+ )
672
+ expect(signers_found).to eql(Set[
673
+ client_kp_a.address,
674
+ client_kp_b.address,
675
+ client_kp_c.address,
676
+ ])
677
+ end
678
+
679
+ it "raises no signers error" do
680
+ server_kp = Stellar::KeyPair.random
681
+ client_kp_a = Stellar::KeyPair.random
682
+ client_kp_b = Stellar::KeyPair.random
683
+ client_kp_c = Stellar::KeyPair.random
684
+ timeout = 600
685
+ anchor_name = "SDF"
686
+
687
+ challenge = sep10.build_challenge_tx(
688
+ server: server_kp,
689
+ client: client_kp_a,
690
+ anchor_name: anchor_name,
691
+ timeout: timeout,
692
+ )
693
+
694
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
695
+ challenge_envelope.signatures += [
696
+ client_kp_a, client_kp_b, client_kp_c
697
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
698
+
699
+ expect {
700
+ sep10.verify_challenge_tx_signers(
701
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
702
+ server: server_kp,
703
+ signers: Set.new
704
+ )
705
+ }.to raise_error(
706
+ Stellar::InvalidSep10ChallengeError,
707
+ /No signers provided./
708
+ )
709
+ end
710
+
711
+ it "raises transaction not signed by server" do
712
+ server_kp = Stellar::KeyPair.random
713
+ client_kp_a = Stellar::KeyPair.random
714
+ client_kp_b = Stellar::KeyPair.random
715
+ client_kp_c = Stellar::KeyPair.random
716
+ timeout = 600
717
+ anchor_name = "SDF"
718
+
719
+ challenge = sep10.build_challenge_tx(
720
+ server: server_kp,
721
+ client: client_kp_a,
722
+ anchor_name: anchor_name,
723
+ timeout: timeout,
724
+ )
725
+
726
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
727
+ challenge_envelope.signatures = [client_kp_a, client_kp_b, client_kp_c].map {
728
+ |kp| challenge_envelope.tx.sign_decorated(kp)
729
+ }
730
+
731
+ signers = Set[
732
+ client_kp_a.address,
733
+ client_kp_b.address,
734
+ client_kp_c.address
735
+ ]
736
+
737
+ expect {
738
+ sep10.verify_challenge_tx_signers(
739
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
740
+ server: server_kp,
741
+ signers: signers
742
+ )
743
+ }.to raise_error(
744
+ Stellar::InvalidSep10ChallengeError,
745
+ /The transaction is not signed by the server/
746
+ )
747
+ end
748
+
749
+ it "raises no client signers found" do
750
+ server_kp = Stellar::KeyPair.random
751
+ client_kp_a = Stellar::KeyPair.random
752
+ client_kp_b = Stellar::KeyPair.random
753
+ client_kp_c = Stellar::KeyPair.random
754
+ timeout = 600
755
+ anchor_name = "SDF"
756
+
757
+ challenge = sep10.build_challenge_tx(
758
+ server: server_kp,
759
+ client: client_kp_a,
760
+ anchor_name: anchor_name,
761
+ timeout: timeout,
762
+ )
763
+
764
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
765
+ challenge_envelope.signatures += [
766
+ client_kp_a, client_kp_b, client_kp_c
767
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
768
+
769
+ # Different signers than those on the transaction envelope
770
+ signers = Set[
771
+ Stellar::KeyPair.random.address,
772
+ Stellar::KeyPair.random.address,
773
+ Stellar::KeyPair.random.address
774
+ ]
775
+
776
+ expect {
777
+ sep10.verify_challenge_tx_signers(
778
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
779
+ server: server_kp,
780
+ signers: signers
781
+ )
782
+ }.to raise_error(
783
+ Stellar::InvalidSep10ChallengeError,
784
+ /Transaction not signed by any client signer./
785
+ )
786
+ end
787
+
788
+ it "raises unrecognized signatures" do
789
+ server_kp = Stellar::KeyPair.random
790
+ client_kp_a = Stellar::KeyPair.random
791
+ client_kp_b = Stellar::KeyPair.random
792
+ client_kp_c = Stellar::KeyPair.random
793
+ timeout = 600
794
+ anchor_name = "SDF"
795
+
796
+ challenge = sep10.build_challenge_tx(
797
+ server: server_kp,
798
+ client: client_kp_a,
799
+ anchor_name: anchor_name,
800
+ timeout: timeout,
801
+ )
802
+
803
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
804
+ # Add random signature
805
+ challenge_envelope.signatures += [
806
+ client_kp_a, client_kp_b, client_kp_c, Stellar::KeyPair.random
807
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
808
+
809
+ signers = Set[
810
+ client_kp_a.address,
811
+ client_kp_b.address,
812
+ client_kp_c.address
813
+ ]
814
+
815
+ expect {
816
+ sep10.verify_challenge_tx_signers(
817
+ challenge_xdr: challenge_envelope.to_xdr(:base64),
818
+ server: server_kp,
819
+ signers: signers
820
+ )
821
+ }.to raise_error(
822
+ Stellar::InvalidSep10ChallengeError,
823
+ /Transaction has unrecognized signatures./
824
+ )
825
+ end
826
+
827
+ it "raises an error when transaction only has server signature" do
828
+ server_kp = Stellar::KeyPair.random
829
+ client_kp = Stellar::KeyPair.random
830
+
831
+ challenge = sep10.build_challenge_tx(
832
+ server: server_kp,
833
+ client: client_kp,
834
+ anchor_name: "SDF",
835
+ timeout: 600,
836
+ )
837
+
838
+ expect {
839
+ sep10.verify_challenge_tx_signers(
840
+ challenge_xdr: challenge,
841
+ server: server_kp,
842
+ signers: Set[server_kp.address]
843
+ )
844
+ }.to raise_error(
845
+ Stellar::InvalidSep10ChallengeError,
846
+ /At least one signer with a G... address must be provied/
847
+ )
848
+ end
849
+
850
+ it "succeeds even when the server is included in the passed signers" do
851
+ server_kp = Stellar::KeyPair.random
852
+ client_kp = Stellar::KeyPair.random
853
+
854
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
855
+ sep10.build_challenge_tx(
856
+ server: server_kp,
857
+ client: client_kp,
858
+ anchor_name: "SDF",
859
+ timeout: 600,
860
+ ),
861
+ "base64"
862
+ )
863
+ challenge_envelope.signatures += [challenge_envelope.tx.sign_decorated(client_kp)]
864
+ challenge = challenge_envelope.to_xdr(:base64)
865
+
866
+ expect(
867
+ sep10.verify_challenge_tx_signers(
868
+ challenge_xdr: challenge,
869
+ server: server_kp,
870
+ signers: Set[server_kp.address, client_kp.address]
871
+ )
872
+ ).to eql(
873
+ Set[client_kp.address]
874
+ )
875
+ end
876
+
877
+ it "succeeds with extra signers passed" do
878
+ server_kp = Stellar::KeyPair.random
879
+ client_kp_a = Stellar::KeyPair.random
880
+ client_kp_b = Stellar::KeyPair.random
881
+ client_kp_c = Stellar::KeyPair.random
882
+
883
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
884
+ sep10.build_challenge_tx(
885
+ server: server_kp,
886
+ client: client_kp_a,
887
+ anchor_name: "SDF",
888
+ timeout: 600,
889
+ ),
890
+ "base64"
891
+ )
892
+ challenge_envelope.signatures += [
893
+ challenge_envelope.tx.sign_decorated(client_kp_a),
894
+ challenge_envelope.tx.sign_decorated(client_kp_b)
895
+ ]
896
+ challenge = challenge_envelope.to_xdr(:base64)
897
+
898
+ expect(
899
+ sep10.verify_challenge_tx_signers(
900
+ challenge_xdr: challenge,
901
+ server: server_kp,
902
+ signers: Set[
903
+ client_kp_a.address,
904
+ client_kp_b.address,
905
+ client_kp_c.address
906
+ ]
907
+ )
908
+ ).to eql(
909
+ Set[client_kp_a.address, client_kp_b.address]
910
+ )
911
+ end
912
+
913
+ it "does not pass back duplicate signers" do
914
+ server_kp = Stellar::KeyPair.random
915
+ client_kp = Stellar::KeyPair.random
916
+
917
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
918
+ sep10.build_challenge_tx(
919
+ server: server_kp,
920
+ client: client_kp,
921
+ anchor_name: "SDF",
922
+ timeout: 600,
923
+ ),
924
+ "base64"
925
+ )
926
+ challenge_envelope.signatures += [challenge_envelope.tx.sign_decorated(client_kp)]
927
+ challenge = challenge_envelope.to_xdr(:base64)
928
+
929
+ expect(
930
+ sep10.verify_challenge_tx_signers(
931
+ challenge_xdr: challenge,
932
+ server: server_kp,
933
+ signers: Set[client_kp.address, client_kp.address]
934
+ )
935
+ ).to eql(
936
+ Set[client_kp.address]
937
+ )
938
+ end
939
+
940
+ it "raises an error for duplicate signatures" do
941
+ server_kp = Stellar::KeyPair.random
942
+ client_kp = Stellar::KeyPair.random
943
+
944
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
945
+ sep10.build_challenge_tx(
946
+ server: server_kp,
947
+ client: client_kp,
948
+ anchor_name: "SDF",
949
+ timeout: 600,
950
+ ),
951
+ "base64"
952
+ )
953
+ challenge_envelope.signatures += [
954
+ challenge_envelope.tx.sign_decorated(client_kp),
955
+ challenge_envelope.tx.sign_decorated(client_kp)
956
+ ]
957
+ challenge = challenge_envelope.to_xdr(:base64)
958
+
959
+ expect {
960
+ sep10.verify_challenge_tx_signers(
961
+ challenge_xdr: challenge,
962
+ server: server_kp,
963
+ signers: Set[server_kp.address, client_kp.address]
964
+ )
965
+ }.to raise_error(
966
+ Stellar::InvalidSep10ChallengeError,
967
+ /Transaction has unrecognized signatures./
968
+ )
969
+ end
970
+
971
+ it "ignores non-G address" do
972
+ preauth_tx_hash = "TAQCSRX2RIDJNHFIFHWD63X7D7D6TRT5Y2S6E3TEMXTG5W3OECHZ2OG4"
973
+ x_hash = "XDRPF6NZRR7EEVO7ESIWUDXHAOMM2QSKIQQBJK6I2FB7YKDZES5UCLWD"
974
+ server_kp = Stellar::KeyPair.random
975
+ client_kp = Stellar::KeyPair.random
976
+
977
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
978
+ sep10.build_challenge_tx(
979
+ server: server_kp,
980
+ client: client_kp,
981
+ anchor_name: "SDF",
982
+ timeout: 600,
983
+ ),
984
+ "base64"
985
+ )
986
+ challenge_envelope.signatures += [challenge_envelope.tx.sign_decorated(client_kp)]
987
+ challenge = challenge_envelope.to_xdr(:base64)
988
+
989
+ expect(
990
+ sep10.verify_challenge_tx_signers(
991
+ challenge_xdr: challenge,
992
+ server: server_kp,
993
+ signers: Set[client_kp.address, preauth_tx_hash, x_hash]
994
+ )
995
+ ).to eql(
996
+ Set[client_kp.address]
997
+ )
998
+ end
999
+
1000
+ it "raises an error for no signatures" do
1001
+ server_kp = Stellar::KeyPair.random
1002
+ client_kp = Stellar::KeyPair.random
1003
+
1004
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(
1005
+ sep10.build_challenge_tx(
1006
+ server: server_kp,
1007
+ client: client_kp,
1008
+ anchor_name: "SDF",
1009
+ timeout: 600,
1010
+ ),
1011
+ "base64"
1012
+ )
1013
+ challenge_envelope.signatures.clear
1014
+ challenge = challenge_envelope.to_xdr(:base64)
1015
+
1016
+ expect {
1017
+ sep10.verify_challenge_tx_signers(
1018
+ challenge_xdr: challenge,
1019
+ server: server_kp,
1020
+ signers: Set[client_kp.address]
1021
+ )
1022
+ }.to raise_error(
1023
+ Stellar::InvalidSep10ChallengeError,
1024
+ /The transaction is not signed by the server/
1025
+ )
1026
+ end
1027
+ end
1028
+
1029
+ describe "#verify_tx_signatures" do
1030
+ it "returns expected signatures" do
1031
+ server_kp = Stellar::KeyPair.random
1032
+ client_kp_a = Stellar::KeyPair.random
1033
+ client_kp_b = Stellar::KeyPair.random
1034
+ client_kp_c = Stellar::KeyPair.random
1035
+ timeout = 600
1036
+ anchor_name = "SDF"
1037
+
1038
+ challenge = sep10.build_challenge_tx(
1039
+ server: server_kp,
1040
+ client: client_kp_a,
1041
+ anchor_name: anchor_name,
1042
+ timeout: timeout
1043
+ )
1044
+
1045
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
1046
+
1047
+ challenge_envelope.signatures += [
1048
+ client_kp_a, client_kp_b, client_kp_c
1049
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
1050
+
1051
+ signers = Set[
1052
+ client_kp_a.address,
1053
+ client_kp_b.address,
1054
+ client_kp_c.address,
1055
+ Stellar::KeyPair.random.address
1056
+ ]
1057
+ signers_found = sep10.verify_tx_signatures(
1058
+ tx_envelope: challenge_envelope, signers: signers
1059
+ )
1060
+ expect(signers_found).to eql(Set[
1061
+ client_kp_a.address,
1062
+ client_kp_b.address,
1063
+ client_kp_c.address
1064
+ ])
1065
+ end
1066
+
1067
+ it "raises no signature error" do
1068
+ server_kp = Stellar::KeyPair.random
1069
+ client_kp = Stellar::KeyPair.random
1070
+ value = SecureRandom.base64(48)
1071
+
1072
+ tx = Stellar::Transaction.manage_data({
1073
+ account: server_kp,
1074
+ sequence: 0,
1075
+ name: "SDF auth",
1076
+ value: value,
1077
+ source_account: client_kp
1078
+ })
1079
+
1080
+ now = Time.now.to_i
1081
+ tx.time_bounds = Stellar::TimeBounds.new(
1082
+ min_time: now,
1083
+ max_time: now + timeout
1084
+ )
1085
+
1086
+ signers = Set[client_kp.address]
1087
+ expect{
1088
+ sep10.verify_tx_signatures(
1089
+ tx_envelope: tx.to_envelope(), signers: signers
1090
+ )
1091
+ }.to raise_error(
1092
+ Stellar::InvalidSep10ChallengeError,
1093
+ /Transaction has no signatures./
1094
+ )
1095
+ end
1096
+
1097
+ it "removes duplicate signers" do
1098
+ server_kp = Stellar::KeyPair.random
1099
+ client_kp_a = Stellar::KeyPair.random
1100
+ timeout = 600
1101
+ anchor_name = "SDF"
1102
+
1103
+ challenge = sep10.build_challenge_tx(
1104
+ server: server_kp,
1105
+ client: client_kp_a,
1106
+ anchor_name: anchor_name,
1107
+ timeout: timeout
1108
+ )
1109
+
1110
+ challenge_envelope = Stellar::TransactionEnvelope.from_xdr(challenge, "base64")
1111
+
1112
+ # Sign the transaction with the same keypair twice
1113
+ challenge_envelope.signatures += [
1114
+ client_kp_a, client_kp_a
1115
+ ].map { |kp| challenge_envelope.tx.sign_decorated(kp) }
1116
+
1117
+ signers = Set[
1118
+ client_kp_a.address,
1119
+ client_kp_a.address,
1120
+ Stellar::KeyPair.random.address
1121
+ ]
1122
+ signers_found = sep10.verify_tx_signatures(
1123
+ tx_envelope: challenge_envelope, signers: signers
1124
+ )
1125
+ expect(signers_found).to eql(Set[client_kp_a.address])
1126
+ end
1127
+ end
1128
+
1129
+ describe "#verify_tx_signed_by" do
1130
+ let(:keypair) { Stellar::KeyPair.random }
1131
+ let(:envelope) do
1132
+ Stellar::Transaction.bump_sequence(account: keypair, bump_to: 1000, sequence: 0).to_envelope(keypair)
1133
+ end
1134
+
1135
+ it "returns true if transaction envelope is signed by keypair" do
1136
+ result = sep10.verify_tx_signed_by(tx_envelope: envelope, keypair: keypair)
1137
+ expect(result).to eql(true)
1138
+ end
1139
+
1140
+ it "returns false if transaction envelope is not signed by keypair" do
1141
+ result = sep10.verify_tx_signed_by(
1142
+ tx_envelope: envelope,
1143
+ keypair: Stellar::KeyPair.random
1144
+ )
1145
+ expect(result).to eql(false)
1146
+ end
1147
+ end
1148
+ end