stellar-sdk 0.24.0 → 0.28.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.
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