stellar-sdk 0.24.0 → 0.28.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f79d3c2af3b2cee00a5a7fd4f7ce698bf250c349afa72f9a310b5f101326d168
4
- data.tar.gz: 85b75a06c450fb09c07383dbd0606f72d5b83e5f5ac73f51692302ab27237bd1
3
+ metadata.gz: 16f2babbd6e3fd516c62f8ff0fb2fe264e335dae7bb051721a3f4fefb5195f4d
4
+ data.tar.gz: dd0d6b370c14ca38ae09a7bcfcfaccfc1214af2115308c9f2574f84f133a85a4
5
5
  SHA512:
6
- metadata.gz: ba9a3d5d81b4e5b55af6cb5c14f33001744feea77b706b1b7204189ac638d0c617dcab2e8364822a9a6d4bb0bb222218b4c73da35ae9e18ee649eccab0abd3dc
7
- data.tar.gz: 761d9db6452124b38568ea390ba70ddd98ad307cc4304df1bd5abb12e303398661de13ac3fd8320352db657899aef86b3ace18393254782f28ae33a1d868a138
6
+ metadata.gz: 5b4ce731f7be3b66a5d1cd0f8a03ebb8ba5ffbcb7c55b0fdc9a45189edd95f2d288a3047d603e222e06f69e7079c418c27bc0b82c3bcd0f25f182573630cba67
7
+ data.tar.gz: c74630a8193bc6bfd3bd2ce730491c0de96f4b7b49b9b1523d02b191f9c2358cfe1c6ca7e286db28ded3680c6e8d110c003249928f86682cec6da3e4fc91b35d
data/CHANGELOG.md CHANGED
@@ -1,27 +1,56 @@
1
1
  # Change Log
2
+
2
3
  All notable changes to this project will be documented in this file.
3
4
 
4
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
7
 
7
- ## [Unreleased]
8
+ ## [0.28.0](https://www.github.com/astroband/ruby-stellar-sdk/compare/v0.27.0...v0.28.0) (2021-07-17)
9
+
10
+ ### Changed
11
+ * `Stellar::Account` class is now part of `stellar-base` gem
12
+ * `Stellar::Account.lookup` is deprecated, use `Stellar::Federation.lookup` instead
13
+
14
+ ## [0.27.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.26.0...v0.27.0) (2021-05-08)
15
+ - No changes
16
+
17
+ ## [0.26.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.25.0...v0.26.0) - 2021-02-05
18
+ ### Changed
19
+ - `Stellar::SEP10` is updated to comply with SEP10 v3.0.0 and v3.1.0
20
+ - `read_challenge_tx`` now verifies `domain` in challenge auth operation, as per SEP10 v3.0.0
21
+ - it is now possible to provide `auth_domain` parameter to enforce auth server domain verification:
22
+ - `build_challenge_tx` will encode the extra auth domain operation into the challenge tx
23
+ - `read_challenge_tx` will verify that the challenge includes the correct auth domain operation
24
+
25
+ ## [0.25.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.24.0...v0.25.0) - 2020-10-30
26
+ ### Changed
27
+ - `Stellar::SEP10` is updated to comply with SEP10 v2.1.0
28
+ - `build_challenge_tx` now accepts `domain` instead of `anchor_name`, using the
29
+ old param name will now result in deprecation warning
30
+ - `read_challenge_tx` correctly validates multi-op challenge transactions
31
+ - `verify_challenge_tx_threshold` now expects simple `{'GA...' => weight, ... }` hash for `signers`
32
+ ### Removed:
33
+ - Deprecated `Stellar::SEP10.verify_challenge_tx` method is removed
8
34
 
9
- ## [0.23.1](https://github.com/stellar/ruby-stellar-sdk/compare/v0.23.1...v0.23.0) - 2020-06-18
35
+ ## [0.24.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.23.1...v0.24.0) - 2020-10-20
36
+ - Protocol 14 support
37
+
38
+ ## [0.23.1](https://github.com/astroband/ruby-stellar-sdk/compare/v0.23.0...v0.23.1) - 2020-06-18
10
39
  - Fix SEP10, considering muxed accounts
11
40
 
12
- ## [0.23.0](https://github.com/stellar/ruby-stellar-sdk/compare/v0.23.0...v0.9.0)
41
+ ## [0.23.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.9.0-rc.1...v0.23.0)
13
42
  - No changes. We bumped this version to sync `stellar-sdk` and `stellar-base` versions
14
43
 
15
- ## [0.9.0](https://github.com/stellar/ruby-stellar-sdk/compare/v0.9.0...v0.8.0)
44
+ ## [0.9.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.8.0...v0.9.0-rc.1)
16
45
  ### Added
17
46
  - Stellar Protocol 13 support
18
47
  - Fee-Bump transactions ([CAP-0015](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md))
19
48
  - Multiplexed accounts ([CAP-0027](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0027.md))
20
49
  - Fine-Grained control on trustline authorization ([CAP-0018](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0018.md))
21
-
22
- ## [0.8.0](https://github.com/stellar/ruby-stellar-sdk/compare/v0.7.0...v0.8.0)
50
+
51
+ ## [0.8.0](https://github.com/astroband/ruby-stellar-sdk/compare/v0.7.0...v0.8.0)
23
52
  ### Added
24
- - SEP-10 Multisig Support [#69](https://github.com/stellar/ruby-stellar-sdk/pull/69)
53
+ - SEP-10 Multisig Support [#69](https://github.com/astroband/ruby-stellar-sdk/pull/69)
25
54
  - `X-Client-Name` and `X-Client-Version` headers
26
55
 
27
56
  ## [0.7.0] - 2019-04-26
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # Ruby Stellar
2
-
3
- [![Build Status](https://travis-ci.org/bloom-solutions/ruby-stellar-sdk.svg)](https://travis-ci.org/bloom-solutions/ruby-stellar-sdk)
4
- [![Code Climate](https://codeclimate.com/github/bloom-solutions/ruby-stellar-sdk/badges/gpa.svg)](https://codeclimate.com/github/bloom-solutions/ruby-stellar-sdk)
1
+ # Stellar SDK for Ruby: Horizon Integration and Higher Level Abstractions
2
+ [![stellar-sdk](https://badge.fury.io/rb/stellar-sdk.svg)](https://badge.fury.io/rb/stellar-sdk)
3
+ [![Test](https://github.com/astroband/ruby-stellar-sdk/workflows/Test/badge.svg)](https://github.com/astroband/ruby-stellar-sdk/actions?query=branch%3Amaster)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/dadfcd9396aba493cb93/maintainability)](https://codeclimate.com/github/astroband/ruby-stellar-sdk/maintainability)
5
5
 
6
6
  This library helps you to integrate your application into the [Stellar network](http://stellar.org).
7
7
 
@@ -36,10 +36,10 @@ client.send_payment({
36
36
  from: account,
37
37
  to: recipient,
38
38
  amount: Stellar::Amount.new(100_000_000)
39
- })
39
+ })
40
40
  ```
41
41
 
42
- Be sure to set the network when submitting to the public network (more information in [stellar-base](https://www.github.com/bloom-solutions/ruby-stellar-base)):
42
+ Be sure to set the network when submitting to the public network (more information in [stellar-base](https://www.github.com/astroband/ruby-stellar-base)):
43
43
 
44
44
  ```ruby
45
45
  Stellar.default_network = Stellar::Networks::PUBLIC
@@ -57,7 +57,7 @@ Stellar.default_network = Stellar::Networks::PUBLIC
57
57
  ## Contributing
58
58
 
59
59
  1. Sign the [Contributor License Agreement](https://docs.google.com/forms/d/1g7EF6PERciwn7zfmfke5Sir2n10yddGGSXyZsq98tVY/viewform?usp=send_form)
60
- 2. Fork it ( https://github.com/bloom-solutions/ruby-stellar-lib/fork )
60
+ 2. Fork it ( https://github.com/astroband/ruby-stellar-sdk/fork )
61
61
  2. Create your feature branch (`git checkout -b my-new-feature`)
62
62
  3. Commit your changes (`git commit -am 'Add some feature'`)
63
63
  4. Push to the branch (`git push origin my-new-feature`)
data/lib/stellar-sdk.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  require "stellar-base"
2
- require_relative "stellar/sdk/version"
3
2
 
4
3
  module Stellar
5
- autoload :Account
4
+ module SDK
5
+ VERSION = ::Stellar::VERSION
6
+ end
7
+
6
8
  autoload :Amount
7
9
  autoload :Client
10
+ autoload :Federation
8
11
  autoload :SEP10
9
12
 
10
13
  module Horizon
@@ -16,3 +19,5 @@ module Stellar
16
19
 
17
20
  autoload :TransactionPage
18
21
  end
22
+
23
+ require_relative "stellar/compat"
@@ -0,0 +1,4 @@
1
+ class << Stellar::Account
2
+ delegate :lookup, to: Stellar::Federation
3
+ deprecate deprecator: Stellar::Deprecation, lookup: "Use `Stellar::Federation.lookup` method"
4
+ end
@@ -0,0 +1,51 @@
1
+ require "toml-rb"
2
+ require "uri"
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module Stellar
7
+ class Federation
8
+ def self.lookup(federated_name)
9
+ _, domain = federated_name.split("*")
10
+ if domain.nil?
11
+ raise InvalidFederationAddress.new
12
+ end
13
+
14
+ domain_req = Faraday.new("https://#{domain}/.well-known/stellar.toml").get
15
+
16
+ unless domain_req.status == 200
17
+ raise InvalidStellarDomain, "Domain does not contain stellar.toml file"
18
+ end
19
+
20
+ fed_server_url = TomlRB.parse(domain_req.body)["FEDERATION_SERVER"]
21
+ if fed_server_url.nil?
22
+ raise InvalidStellarTOML, "Invalid Stellar TOML file"
23
+ end
24
+
25
+ unless fed_server_url&.match?(URI::DEFAULT_PARSER.make_regexp)
26
+ raise InvalidFederationURL, "Invalid Federation Server URL"
27
+ end
28
+
29
+ lookup_req = Faraday.new(fed_server_url).get { |req|
30
+ req.params[:q] = federated_name
31
+ req.params[:type] = "name"
32
+ }
33
+
34
+ unless lookup_req.status == 200
35
+ raise AccountNotFound, "Account not found"
36
+ end
37
+
38
+ JSON.parse(lookup_req.body)["account_id"]
39
+ end
40
+ end
41
+
42
+ class AccountNotFound < StandardError; end
43
+
44
+ class InvalidStellarTOML < StandardError; end
45
+
46
+ class InvalidFederationAddress < StandardError; end
47
+
48
+ class InvalidStellarDomain < StandardError; end
49
+
50
+ class InvalidFederationURL < StandardError; end
51
+ end
data/lib/stellar/sep10.rb CHANGED
@@ -2,26 +2,34 @@ module Stellar
2
2
  class InvalidSep10ChallengeError < StandardError; end
3
3
 
4
4
  class SEP10
5
+ include Stellar::DSL
6
+
5
7
  # Helper method to create a valid {SEP0010}[https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md]
6
8
  # challenge transaction which you can use for Stellar Web Authentication.
7
9
  #
8
- # @param server [Stellar::KeyPair] Keypair for server's signing account.
9
- # @param client [Stellar::KeyPair] Keypair for the account whishing to authenticate with the server.
10
- # @param anchor_name [String] Anchor's name to be used in the manage_data key.
11
- # @param timeout [Integer] Challenge duration (default to 5 minutes).
12
- #
13
- # @return [String] A base64 encoded string of the raw TransactionEnvelope xdr struct for the transaction.
10
+ # @example
11
+ # server = Stellar::KeyPair.random # SIGNING_KEY from your stellar.toml
12
+ # user = Stellar::KeyPair.from_address('G...')
13
+ # Stellar::SEP10.build_challenge_tx(server: server, client: user, domain: 'example.com', timeout: 300)
14
14
  #
15
- # = Example
15
+ # @param server [Stellar::KeyPair] server's signing keypair (SIGNING_KEY in service's stellar.toml)
16
+ # @param client [Stellar::KeyPair] account trying to authenticate with the server
17
+ # @param domain [String] service's domain to be used in the manage_data key
18
+ # @param timeout [Integer] challenge duration (default to 5 minutes)
16
19
  #
17
- # Stellar::SEP10.build_challenge_tx(server: server, client: user, anchor_name: anchor, timeout: timeout)
20
+ # @return [String] A base64 encoded string of the raw TransactionEnvelope xdr struct for the transaction.
18
21
  #
19
22
  # @see {SEP0010: Stellar Web Authentication}[https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md]
20
- def self.build_challenge_tx(server:, client:, anchor_name:, timeout: 300)
21
- # The value must be 64 bytes long. It contains a 48 byte
22
- # cryptographic-quality random string encoded using base64 (for a total of
23
- # 64 bytes after encoding).
24
- value = SecureRandom.base64(48)
23
+ def self.build_challenge_tx(server:, client:, domain: nil, timeout: 300, **options)
24
+ if domain.blank? && options.key?(:anchor_name)
25
+ ActiveSupport::Deprecation.new("next release", "stellar-sdk").warn <<~MSG
26
+ SEP-10 v2.0.0 requires usage of service home domain instead of anchor name in the challenge transaction.
27
+ Please update your implementation to use `Stellar::SEP10.build_challenge_tx(..., home_domain: 'example.com')`.
28
+ Using `anchor_name` parameter makes your service incompatible with SEP10-2.0 clients, support for this parameter
29
+ is deprecated and will be removed in the next major release of stellar-base.
30
+ MSG
31
+ domain = options[:anchor_name]
32
+ end
25
33
 
26
34
  now = Time.now.to_i
27
35
  time_bounds = Stellar::TimeBounds.new(
@@ -29,19 +37,34 @@ module Stellar
29
37
  max_time: now + timeout
30
38
  )
31
39
 
32
- tx = Stellar::TransactionBuilder.new(
40
+ tb = Stellar::TransactionBuilder.new(
33
41
  source_account: server,
34
42
  sequence_number: 0,
35
43
  time_bounds: time_bounds
36
- ).add_operation(
44
+ )
45
+
46
+ # The value must be 64 bytes long. It contains a 48 byte
47
+ # cryptographic-quality random string encoded using base64 (for a total of
48
+ # 64 bytes after encoding).
49
+ tb.add_operation(
37
50
  Stellar::Operation.manage_data(
38
- name: "#{anchor_name} auth",
39
- value: value,
51
+ name: "#{domain} auth",
52
+ value: SecureRandom.base64(48),
40
53
  source_account: client
41
54
  )
42
- ).build
55
+ )
43
56
 
44
- tx.to_envelope(server).to_xdr(:base64)
57
+ if options.key?(:auth_domain)
58
+ tb.add_operation(
59
+ Stellar::Operation.manage_data(
60
+ name: "web_auth_domain",
61
+ value: options[:auth_domain],
62
+ source_account: server
63
+ )
64
+ )
65
+ end
66
+
67
+ tb.build.to_envelope(server).to_xdr(:base64)
45
68
  end
46
69
 
47
70
  # Reads a SEP 10 challenge transaction and returns the decoded transaction envelope and client account ID contained within.
@@ -49,76 +72,81 @@ module Stellar
49
72
  # It also verifies that transaction is signed by the server.
50
73
  #
51
74
  # It does not verify that the transaction has been signed by the client or
52
- # that any signatures other than the servers on the transaction are valid. Use
53
- # one of the following functions to completely verify the transaction:
54
- # - Stellar::SEP10.verify_challenge_tx_threshold
55
- # - Stellar::SEP10.verify_challenge_tx_signers
75
+ # that any signatures other than the servers on the transaction are valid.
76
+ # Use either {.verify_challenge_tx_threshold} or {.verify_challenge_tx_signers} to completely verify
77
+ # the signed challenge
78
+ #
79
+ # @example
80
+ # sep10 = Stellar::SEP10
81
+ # server = Stellar::KeyPair.random # this should be the SIGNING_KEY from your stellar.toml
82
+ # challenge = sep10.build_challenge_tx(server: server, client: user, domain: domain, timeout: timeout)
83
+ # envelope, client_address = sep10.read_challenge_tx(server: server, challenge_xdr: challenge)
56
84
  #
57
85
  # @param challenge_xdr [String] SEP0010 transaction challenge in base64.
58
86
  # @param server [Stellar::KeyPair] keypair for server where the challenge was generated.
59
87
  #
60
- # @return [Stellar::TransactionEnvelope, String]
61
- #
62
- # = Example
63
- #
64
- # sep10 = Stellar::SEP10
65
- # challenge = sep10.build_challenge_tx(server: server, client: user, anchor_name: anchor, timeout: timeout)
66
- # envelope, client_address = sep10.read_challenge_tx(challenge: challenge, server: server)
67
- #
68
- def self.read_challenge_tx(challenge_xdr:, server:)
88
+ # @return [Array(Stellar::TransactionEnvelope, String)]
89
+ def self.read_challenge_tx(server:, challenge_xdr:, **options)
69
90
  envelope = Stellar::TransactionEnvelope.from_xdr(challenge_xdr, "base64")
70
91
  transaction = envelope.tx
71
92
 
72
93
  if transaction.seq_num != 0
73
- raise InvalidSep10ChallengeError.new(
74
- "The transaction sequence number should be zero"
75
- )
94
+ raise InvalidSep10ChallengeError, "The transaction sequence number should be zero"
76
95
  end
77
96
 
78
97
  if transaction.source_account != server.muxed_account
79
- raise InvalidSep10ChallengeError.new(
80
- "The transaction source account is not equal to the server's account"
81
- )
98
+ raise InvalidSep10ChallengeError, "The transaction source account is not equal to the server's account"
82
99
  end
83
100
 
84
- if transaction.operations.size != 1
85
- raise InvalidSep10ChallengeError.new(
86
- "The transaction should contain only one operation"
87
- )
101
+ if transaction.operations.size < 1
102
+ raise InvalidSep10ChallengeError, "The transaction should contain at least one operation"
88
103
  end
89
104
 
90
- operation = transaction.operations.first
91
- client_account_id = operation.source_account
105
+ auth_op, *rest_ops = transaction.operations
106
+ client_account_id = auth_op.source_account
92
107
 
93
- if client_account_id.nil?
94
- raise InvalidSep10ChallengeError.new(
95
- "The transaction's operation should contain a source account"
96
- )
108
+ auth_op_body = auth_op.body.value
109
+
110
+ if client_account_id.blank?
111
+ raise InvalidSep10ChallengeError, "The transaction's operation should contain a source account"
97
112
  end
98
113
 
99
- if operation.body.arm != :manage_data_op
100
- raise InvalidSep10ChallengeError.new(
101
- "The transaction's operation should be manageData"
102
- )
114
+ if auth_op.body.arm != :manage_data_op
115
+ raise InvalidSep10ChallengeError, "The transaction's first operation should be manageData"
103
116
  end
104
117
 
105
- if operation.body.value.data_value.unpack1("m").size != 48
106
- raise InvalidSep10ChallengeError.new(
107
- "The transaction's operation value should be a 64 bytes base64 random string"
108
- )
118
+ if options.key?(:domain) && auth_op_body.data_name != "#{options[:domain]} auth"
119
+ raise InvalidSep10ChallengeError, "The transaction's operation data name is invalid"
120
+ end
121
+
122
+ if auth_op_body.data_value.unpack1("m").size != 48
123
+ raise InvalidSep10ChallengeError, "The transaction's operation value should be a 64 bytes base64 random string"
124
+ end
125
+
126
+ rest_ops.each do |op|
127
+ body = op.body
128
+
129
+ if body.arm != :manage_data_op
130
+ raise InvalidSep10ChallengeError, "The transaction has operations that are not of type 'manageData'"
131
+ elsif op.source_account != server.muxed_account
132
+ raise InvalidSep10ChallengeError, "The transaction has operations that are unrecognized"
133
+ else
134
+ op_params = body.value
135
+ if op_params.data_name == "web_auth_domain" && options.key?(:auth_domain) && op_params.data_value != options[:auth_domain]
136
+ raise InvalidSep10ChallengeError, "The transaction has 'manageData' operation with 'web_auth_domain' key and invalid value"
137
+ end
138
+ end
109
139
  end
110
140
 
111
141
  unless verify_tx_signed_by(tx_envelope: envelope, keypair: server)
112
- raise InvalidSep10ChallengeError.new(
113
- "The transaction is not signed by the server"
114
- )
142
+ raise InvalidSep10ChallengeError, "The transaction is not signed by the server"
115
143
  end
116
144
 
117
145
  time_bounds = transaction.time_bounds
118
146
  now = Time.now.to_i
119
147
 
120
- if time_bounds.nil? || !now.between?(time_bounds.min_time, time_bounds.max_time)
121
- raise InvalidSep10ChallengeError.new("The transaction has expired")
148
+ if time_bounds.blank? || !now.between?(time_bounds.min_time, time_bounds.max_time)
149
+ raise InvalidSep10ChallengeError, "The transaction has expired"
122
150
  end
123
151
 
124
152
  # Mirror the return type of the other SDK's and return a string
@@ -127,6 +155,36 @@ module Stellar
127
155
  [envelope, client_kp.address]
128
156
  end
129
157
 
158
+ # Verifies that for a SEP 10 challenge transaction all signatures on the transaction
159
+ # are accounted for and that the signatures meet a threshold on an account. A
160
+ # transaction is verified if it is signed by the server account, and all other
161
+ # signatures match a signer that has been provided as an argument, and those
162
+ # signatures meet a threshold on the account.
163
+ #
164
+ # @param server [Stellar::KeyPair] keypair for server's account.
165
+ # @param challenge_xdr [String] SEP0010 challenge transaction in base64.
166
+ # @param signers [{String => Integer}] The signers of client account.
167
+ # @param threshold [Integer] The medThreshold on the client account.
168
+ #
169
+ # @raise InvalidSep10ChallengeError if the transaction has unrecognized signatures (only server's
170
+ # signing key and keypairs found in the `signing` argument are recognized) or total weight of
171
+ # the signers does not meet the `threshold`
172
+ #
173
+ # @return [<String>] subset of input signers who have signed `challenge_xdr`
174
+ def self.verify_challenge_tx_threshold(server:, challenge_xdr:, signers:, threshold:)
175
+ signers_found = verify_challenge_tx_signers(
176
+ server: server, challenge_xdr: challenge_xdr, signers: signers.keys
177
+ )
178
+
179
+ total_weight = signers.values_at(*signers_found).sum
180
+
181
+ if total_weight < threshold
182
+ raise InvalidSep10ChallengeError, "signers with weight #{total_weight} do not meet threshold #{threshold}."
183
+ end
184
+
185
+ signers_found
186
+ end
187
+
130
188
  # Verifies that for a SEP 10 challenge transaction all signatures on the transaction are accounted for.
131
189
  #
132
190
  # A transaction is verified if it is signed by the server account, and all other signatures match a signer
@@ -135,211 +193,82 @@ module Stellar
135
193
  #
136
194
  # If verification succeeds a list of signers that were found is returned, excluding the server account ID.
137
195
  #
196
+ # @param server [Stellar::Keypair] server's signing key
138
197
  # @param challenge_xdr [String] SEP0010 transaction challenge transaction in base64.
139
- # @param server [Stellar::Keypair] keypair for server's account.
140
- # @param signers [SetOf[String]] The signers of client account.
198
+ # @param signers [<String>] The signers of client account.
141
199
  #
142
- # @return [SetOf[String]]
200
+ # @raise InvalidSep10ChallengeError one or more signatures in the transaction are not identifiable
201
+ # as the server account or one of the signers provided in the arguments
143
202
  #
144
- # Raises a InvalidSep10ChallengeError if:
145
- # - The transaction is invalid according to Stellar::SEP10.read_challenge_tx.
146
- # - One or more signatures in the transaction are not identifiable as the server account or one of the
147
- # signers provided in the arguments.
148
- def self.verify_challenge_tx_signers(
149
- challenge_xdr:,
150
- server:,
151
- signers:
152
- )
153
- if signers.empty?
154
- raise InvalidSep10ChallengeError.new("No signers provided.")
155
- end
156
-
157
- te, _ = read_challenge_tx(
158
- challenge_xdr: challenge_xdr, server: server
159
- )
203
+ # @return [<String>] subset of input signers who have signed `challenge_xdr`
204
+ def self.verify_challenge_tx_signers(server:, challenge_xdr:, signers:)
205
+ raise InvalidSep10ChallengeError, "no signers provided" if signers.empty?
160
206
 
161
- # deduplicate signers and ignore non-G addresses
162
- client_signers = Set.new
163
- signers.each do |signer|
164
- # ignore server kp if passed
165
- if signer == server.address
166
- next
167
- end
168
- begin
169
- Stellar::Util::StrKey.check_decode(:account_id, signer)
170
- rescue
171
- next
172
- else
173
- client_signers.add(signer)
174
- end
175
- end
207
+ te, _ = read_challenge_tx(server: server, challenge_xdr: challenge_xdr)
176
208
 
177
- if client_signers.empty?
178
- raise InvalidSep10ChallengeError.new("At least one signer with a G... address must be provied")
179
- end
209
+ # ignore non-G signers and server's own address
210
+ client_signers = signers.select { |s| s =~ /G[A-Z0-9]{55}/ && s != server.address }.to_set
211
+ raise InvalidSep10ChallengeError, "at least one regular signer must be provided" if client_signers.empty?
180
212
 
181
213
  # verify all signatures in one pass
182
- all_signers = client_signers + Set[server.address]
183
- signers_found = verify_tx_signatures(
184
- tx_envelope: te, signers: all_signers
185
- )
214
+ client_signers.add(server.address)
215
+ signers_found = verify_tx_signatures(tx_envelope: te, signers: client_signers)
186
216
 
187
217
  # ensure server signed transaction and remove it
188
218
  unless signers_found.delete?(server.address)
189
- raise InvalidSep10ChallengeError.new("Transaction not signed by server: #{server.address}")
219
+ raise InvalidSep10ChallengeError, "Transaction not signed by server: #{server.address}"
190
220
  end
191
221
 
192
222
  # Confirm we matched signatures to the client signers.
193
223
  if signers_found.empty?
194
- raise InvalidSep10ChallengeError.new("Transaction not signed by any client signer.")
224
+ raise InvalidSep10ChallengeError, "Transaction not signed by any client signer."
195
225
  end
196
226
 
197
227
  # Confirm all signatures were consumed by a signer.
198
- if signers_found.length != te.signatures.length - 1
199
- raise InvalidSep10ChallengeError.new("Transaction has unrecognized signatures.")
228
+ if signers_found.size != te.signatures.length - 1
229
+ raise InvalidSep10ChallengeError, "Transaction has unrecognized signatures."
200
230
  end
201
231
 
202
232
  signers_found
203
233
  end
204
234
 
205
- # Verifies that for a SEP 10 challenge transaction all signatures on the transaction
206
- # are accounted for and that the signatures meet a threshold on an account. A
207
- # transaction is verified if it is signed by the server account, and all other
208
- # signatures match a signer that has been provided as an argument, and those
209
- # signatures meet a threshold on the account.
210
- #
211
- # @param challenge_xdr [String] SEP0010 transaction challenge transaction in base64.
212
- # @param server [Stellar::KeyPair] keypair for server's account.
213
- # @param threshold [Integer] The medThreshold on the client account.
214
- # @param signers [SetOf[::Hash]]The signers of client account.
215
- #
216
- # @return [SetOf[::Hash]]
217
- #
218
- # Raises a InvalidSep10ChallengeError if:
219
- # - The transaction is invalid according to Stellar::SEP10.read_challenge_transaction.
220
- # - One or more signatures in the transaction are not identifiable as the server
221
- # account or one of the signers provided in the arguments.
222
- # - The signatures are all valid but do not meet the threshold.
223
- def self.verify_challenge_tx_threshold(
224
- challenge_xdr:,
225
- server:,
226
- threshold:,
227
- signers:
228
- )
229
- signer_str_set = signers.map { |s| s["key"] }.to_set
230
- signer_strs_found = verify_challenge_tx_signers(
231
- challenge_xdr: challenge_xdr,
232
- server: server,
233
- signers: signer_str_set
234
- )
235
-
236
- weight = 0
237
- signers_found = Set.new
238
- signers.each do |s|
239
- unless signer_strs_found.include?(s["key"])
240
- next
241
- end
242
- signer_strs_found.delete(s["key"])
243
- signers_found.add(s)
244
- weight += s["weight"]
245
- end
246
-
247
- if weight < threshold
248
- raise InvalidSep10ChallengeError.new(
249
- "signers with weight #{weight} do not meet threshold #{threshold}."
250
- )
251
- end
252
-
253
- signers_found
254
- end
255
-
256
- # DEPRECATED: Use verify_challenge_tx_signers instead.
257
- # This function does not support multiple client signatures.
258
- #
259
- # Verifies if a transaction is a valid per SEP-10 challenge transaction, if the validation
260
- # fails, an exception will be thrown.
261
- #
262
- # This function performs the following checks:
263
- # 1. verify that transaction sequenceNumber is equal to zero;
264
- # 2. verify that transaction source account is equal to the server's signing key;
265
- # 3. verify that transaction has time bounds set, and that current time is between the minimum and maximum bounds;
266
- # 4. verify that transaction contains a single Manage Data operation and it's source account is not null;
267
- # 5. verify that transaction envelope has a correct signature by server's signing key;
268
- # 6. verify that transaction envelope has a correct signature by the operation's source account;
269
- #
270
- # @param challenge_xdr [String] SEP0010 transaction challenge transaction in base64.
271
- # @param server [Stellar::KeyPair] keypair for server's account.
272
- #
273
- # Raises a InvalidSep10ChallengeError if the validation fails
274
- def self.verify_challenge_tx(
275
- challenge_xdr: String, server: Stellar::KeyPair
276
- )
277
- transaction_envelope, client_address = read_challenge_tx(
278
- challenge_xdr: challenge_xdr, server: server
279
- )
280
- client_keypair = Stellar::KeyPair.from_address(client_address)
281
- unless verify_tx_signed_by(tx_envelope: transaction_envelope, keypair: client_keypair)
282
- raise InvalidSep10ChallengeError.new(
283
- "Transaction not signed by client: %s" % [client_keypair.address]
284
- )
285
- end
286
- end
287
-
288
235
  # Verifies every signer passed matches a signature on the transaction exactly once,
289
236
  # returning a list of unique signers that were found to have signed the transaction.
290
237
  #
291
238
  # @param tx_envelope [Stellar::TransactionEnvelope] SEP0010 transaction challenge transaction envelope.
292
- # @param signers [SetOf[String]] The signers of client account.
239
+ # @param signers [<String>] The signers of client account.
293
240
  #
294
- # @return [SetOf[String]]
295
- def self.verify_tx_signatures(
296
- tx_envelope:,
297
- signers:
298
- )
241
+ # @return [Set<Stellar::KeyPair>]
242
+ def self.verify_tx_signatures(tx_envelope:, signers:)
299
243
  signatures = tx_envelope.signatures
300
244
  if signatures.empty?
301
- raise InvalidSep10ChallengeError.new("Transaction has no signatures.")
245
+ raise InvalidSep10ChallengeError, "Transaction has no signatures."
302
246
  end
303
247
 
304
248
  tx_hash = tx_envelope.tx.hash
305
- signatures_used = Set.new
306
- signers_found = Set.new
307
- signers.each do |signer|
308
- kp = Stellar::KeyPair.from_address(signer)
309
- tx_envelope.signatures.each_with_index do |sig, i|
310
- if signatures_used.include?(i)
311
- next
312
- end
313
- if sig.hint != kp.signature_hint
314
- next
315
- end
316
- if kp.verify(sig.signature, tx_hash)
317
- signatures_used.add(i)
318
- signers_found.add(signer)
319
- end
320
- end
321
- end
249
+ to_keypair = Stellar::DSL.method(:KeyPair)
250
+ keys_by_hint = signers.map(&to_keypair).index_by(&:signature_hint)
322
251
 
323
- signers_found
252
+ tx_envelope.signatures.each.with_object(Set.new) do |sig, result|
253
+ key = keys_by_hint.delete(sig.hint)
254
+ result.add(key.address) if key&.verify(sig.signature, tx_hash)
255
+ end
324
256
  end
325
257
 
326
258
  # Verifies if a Stellar::TransactionEnvelope was signed by the given Stellar::KeyPair
327
259
  #
260
+ # @example
261
+ # Stellar::SEP10.verify_tx_signed_by(tx_envelope: envelope, keypair: keypair)
262
+ #
328
263
  # @param tx_envelope [Stellar::TransactionEnvelope]
329
264
  # @param keypair [Stellar::KeyPair]
330
265
  #
331
266
  # @return [Boolean]
332
- #
333
- # = Example
334
- #
335
- # Stellar::SEP10.verify_tx_signed_by(tx_envelope: envelope, keypair: keypair)
336
- #
337
267
  def self.verify_tx_signed_by(tx_envelope:, keypair:)
338
268
  tx_hash = tx_envelope.tx.hash
339
269
  tx_envelope.signatures.any? do |sig|
340
- if sig.hint != keypair.signature_hint
341
- next
342
- end
270
+ next if sig.hint != keypair.signature_hint
271
+
343
272
  keypair.verify(sig.signature, tx_hash)
344
273
  end
345
274
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stellar-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.28.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Fleckenstein
8
8
  - Sergey Nebolsin
9
9
  - Timur Ramazanov
10
- autorequire:
10
+ autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2020-10-20 00:00:00.000000000 Z
13
+ date: 2021-07-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: stellar-base
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 0.24.0
21
+ version: 0.28.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - '='
27
27
  - !ruby/object:Gem::Version
28
- version: 0.24.0
28
+ version: 0.28.0
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: activesupport
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -75,7 +75,7 @@ dependencies:
75
75
  version: 0.7.0
76
76
  - - "<"
77
77
  - !ruby/object:Gem::Version
78
- version: '1.0'
78
+ version: '2.0'
79
79
  type: :runtime
80
80
  prerelease: false
81
81
  version_requirements: !ruby/object:Gem::Requirement
@@ -85,7 +85,7 @@ dependencies:
85
85
  version: 0.7.0
86
86
  - - "<"
87
87
  - !ruby/object:Gem::Version
88
- version: '1.0'
88
+ version: '2.0'
89
89
  - !ruby/object:Gem::Dependency
90
90
  name: toml-rb
91
91
  requirement: !ruby/object:Gem::Requirement
@@ -112,14 +112,14 @@ dependencies:
112
112
  requirements:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
- version: '2.0'
115
+ version: '2.2'
116
116
  type: :development
117
117
  prerelease: false
118
118
  version_requirements: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: '2.0'
122
+ version: '2.2'
123
123
  - !ruby/object:Gem::Dependency
124
124
  name: rake
125
125
  requirement: !ruby/object:Gem::Requirement
@@ -148,8 +148,8 @@ dependencies:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
150
  version: '3.9'
151
- description:
152
- email:
151
+ description:
152
+ email:
153
153
  executables: []
154
154
  extensions: []
155
155
  extra_rdoc_files:
@@ -161,21 +161,24 @@ files:
161
161
  - LICENSE
162
162
  - README.md
163
163
  - lib/stellar-sdk.rb
164
- - lib/stellar/account.rb
165
164
  - lib/stellar/amount.rb
166
165
  - lib/stellar/client.rb
166
+ - lib/stellar/compat.rb
167
+ - lib/stellar/federation.rb
167
168
  - lib/stellar/horizon/problem.rb
168
- - lib/stellar/sdk/version.rb
169
169
  - lib/stellar/sep10.rb
170
170
  - lib/stellar/transaction_page.rb
171
- homepage: https://github.com/stellar/ruby-stellar-sdk/tree/master/sdk
171
+ homepage: https://github.com/astroband/ruby-stellar-sdk
172
172
  licenses:
173
173
  - Apache-2.0
174
174
  metadata:
175
- documentation_uri: https://rubydoc.info/gems/stellar-sdk/0.24.0/
176
- changelog_uri: https://github.com/astroband/ruby-stellar-sdk/blob/v0.24.0/sdk/CHANGELOG.md
177
- source_code_uri: https://github.com/astroband/ruby-stellar-sdk/tree/v0.24.0/sdk
178
- post_install_message:
175
+ bug_tracker_uri: https://github.com/astroband/ruby-stellar-sdk/issues
176
+ changelog_uri: https://github.com/astroband/ruby-stellar-sdk/blob/v0.28.0/sdk/CHANGELOG.md
177
+ documentation_uri: https://rubydoc.info/gems/stellar-sdk/0.28.0/
178
+ github_repo: ssh://github.com/astroband/ruby-stellar-sdk
179
+ homepage_uri: https://github.com/astroband/ruby-stellar-sdk/tree/main/sdk
180
+ source_code_uri: https://github.com/astroband/ruby-stellar-sdk/tree/v0.28.0/sdk
181
+ post_install_message:
179
182
  rdoc_options: []
180
183
  require_paths:
181
184
  - lib
@@ -190,8 +193,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
193
  - !ruby/object:Gem::Version
191
194
  version: '0'
192
195
  requirements: []
193
- rubygems_version: 3.2.0.rc.2
194
- signing_key:
196
+ rubygems_version: 3.2.22
197
+ signing_key:
195
198
  specification_version: 4
196
199
  summary: Stellar client library
197
200
  test_files: []
@@ -1,89 +0,0 @@
1
- require "toml-rb"
2
- require "uri"
3
- require "faraday"
4
- require "json"
5
-
6
- module Stellar
7
- class Account
8
- delegate :address, to: :keypair
9
-
10
- def self.random
11
- keypair = Stellar::KeyPair.random
12
- new(keypair)
13
- end
14
-
15
- def self.from_seed(seed)
16
- keypair = Stellar::KeyPair.from_seed(seed)
17
- new(keypair)
18
- end
19
-
20
- def self.from_address(address)
21
- keypair = Stellar::KeyPair.from_address(address)
22
- new(keypair)
23
- end
24
-
25
- def self.lookup(federated_name)
26
- _, domain = federated_name.split("*")
27
- if domain.nil?
28
- raise InvalidFederationAddress.new
29
- end
30
-
31
- domain_req = Faraday.new("https://#{domain}/.well-known/stellar.toml").get
32
-
33
- unless domain_req.status == 200
34
- raise InvalidStellarDomain.new("Domain does not contain stellar.toml file")
35
- end
36
-
37
- fed_server_url = TomlRB.parse(domain_req.body)["FEDERATION_SERVER"]
38
- if fed_server_url.nil?
39
- raise InvalidStellarTOML.new("Invalid Stellar TOML file")
40
- end
41
-
42
- unless fed_server_url&.match?(URI::DEFAULT_PARSER.make_regexp)
43
- raise InvalidFederationURL.new("Invalid Federation Server URL")
44
- end
45
-
46
- lookup_req = Faraday.new(fed_server_url).get { |req|
47
- req.params[:q] = federated_name
48
- req.params[:type] = "name"
49
- }
50
-
51
- unless lookup_req.status == 200
52
- raise AccountNotFound.new("Account not found")
53
- end
54
-
55
- JSON.parse(lookup_req.body)["account_id"]
56
- end
57
-
58
- def self.master
59
- keypair = Stellar::KeyPair.from_raw_seed("allmylifemyhearthasbeensearching")
60
- new(keypair)
61
- end
62
-
63
- attr_reader :keypair
64
-
65
- # @param [Stellar::KeyPair] keypair
66
- def initialize(keypair)
67
- @keypair = keypair
68
- end
69
-
70
- def to_keypair
71
- keypair
72
- end
73
- end
74
-
75
- class AccountNotFound < StandardError
76
- end
77
-
78
- class InvalidStellarTOML < StandardError
79
- end
80
-
81
- class InvalidFederationAddress < StandardError
82
- end
83
-
84
- class InvalidStellarDomain < StandardError
85
- end
86
-
87
- class InvalidFederationURL < StandardError
88
- end
89
- end
@@ -1,5 +0,0 @@
1
- module Stellar
2
- module SDK
3
- VERSION = "0.24.0"
4
- end
5
- end