stellar-sdk 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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