stellar-base 0.0.1
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 +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +15 -0
- data/.yardopts +8 -0
- data/Gemfile +15 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +202 -0
- data/README.md +79 -0
- data/Rakefile +6 -0
- data/examples/low_level_transaction_post.rb +53 -0
- data/examples/mid_level_transaction_post.rb +34 -0
- data/examples/non_native_payment.rb +60 -0
- data/generated/stellar/account_entry.rb +37 -0
- data/generated/stellar/account_flags.rb +20 -0
- data/generated/stellar/account_merge_result.rb +25 -0
- data/generated/stellar/account_merge_result_code.rb +30 -0
- data/generated/stellar/allow_trust_op/currency.rb +28 -0
- data/generated/stellar/allow_trust_op.rb +35 -0
- data/generated/stellar/allow_trust_result.rb +25 -0
- data/generated/stellar/allow_trust_result_code.rb +28 -0
- data/generated/stellar/bucket_entry.rb +28 -0
- data/generated/stellar/bucket_entry_type.rb +22 -0
- data/generated/stellar/change_trust_op.rb +22 -0
- data/generated/stellar/change_trust_result.rb +25 -0
- data/generated/stellar/change_trust_result_code.rb +28 -0
- data/generated/stellar/claim_offer_atom.rb +29 -0
- data/generated/stellar/create_offer_effect.rb +24 -0
- data/generated/stellar/create_offer_op.rb +28 -0
- data/generated/stellar/create_offer_result.rb +26 -0
- data/generated/stellar/create_offer_result_code.rb +45 -0
- data/generated/stellar/create_offer_success_result/offer.rb +30 -0
- data/generated/stellar/create_offer_success_result.rb +34 -0
- data/generated/stellar/currency.rb +29 -0
- data/generated/stellar/currency_type.rb +22 -0
- data/generated/stellar/decorated_signature.rb +20 -0
- data/generated/stellar/dont_have.rb +20 -0
- data/generated/stellar/error.rb +20 -0
- data/generated/stellar/hello.rb +24 -0
- data/generated/stellar/inflation_payout.rb +20 -0
- data/generated/stellar/inflation_result.rb +26 -0
- data/generated/stellar/inflation_result_code.rb +24 -0
- data/generated/stellar/iso_currency_issuer.rb +20 -0
- data/generated/stellar/ledger_entry.rb +33 -0
- data/generated/stellar/ledger_entry_type.rb +24 -0
- data/generated/stellar/ledger_header.rb +45 -0
- data/generated/stellar/ledger_header_history_entry.rb +20 -0
- data/generated/stellar/ledger_key/account.rb +20 -0
- data/generated/stellar/ledger_key/offer.rb +22 -0
- data/generated/stellar/ledger_key/trust_line.rb +22 -0
- data/generated/stellar/ledger_key.rb +50 -0
- data/generated/stellar/message_type.rb +45 -0
- data/generated/stellar/offer_entry.rb +34 -0
- data/generated/stellar/operation/body.rb +49 -0
- data/generated/stellar/operation.rb +45 -0
- data/generated/stellar/operation_result/tr.rb +49 -0
- data/generated/stellar/operation_result.rb +47 -0
- data/generated/stellar/operation_result_code.rb +25 -0
- data/generated/stellar/operation_type.rb +32 -0
- data/generated/stellar/payment_op.rb +35 -0
- data/generated/stellar/payment_result.rb +29 -0
- data/generated/stellar/payment_result_code.rb +41 -0
- data/generated/stellar/payment_success_multi_result.rb +20 -0
- data/generated/stellar/peer_address.rb +22 -0
- data/generated/stellar/price.rb +20 -0
- data/generated/stellar/scp_ballot.rb +20 -0
- data/generated/stellar/scp_envelope.rb +22 -0
- data/generated/stellar/scp_quorum_set.rb +20 -0
- data/generated/stellar/scp_statement/pledges/prepare.rb +24 -0
- data/generated/stellar/scp_statement/pledges.rb +40 -0
- data/generated/stellar/scp_statement.rb +42 -0
- data/generated/stellar/scp_statement_type.rb +26 -0
- data/generated/stellar/set_options_op.rb +31 -0
- data/generated/stellar/set_options_result.rb +25 -0
- data/generated/stellar/set_options_result_code.rb +28 -0
- data/generated/stellar/signer.rb +20 -0
- data/generated/stellar/simple_payment_result.rb +22 -0
- data/generated/stellar/stellar_ballot.rb +22 -0
- data/generated/stellar/stellar_ballot_value.rb +22 -0
- data/generated/stellar/stellar_message.rb +66 -0
- data/generated/stellar/transaction.rb +37 -0
- data/generated/stellar/transaction_envelope.rb +20 -0
- data/generated/stellar/transaction_history_entry.rb +20 -0
- data/generated/stellar/transaction_history_result_entry.rb +20 -0
- data/generated/stellar/transaction_meta.rb +18 -0
- data/generated/stellar/transaction_result/result.rb +30 -0
- data/generated/stellar/transaction_result.rb +33 -0
- data/generated/stellar/transaction_result_code.rb +46 -0
- data/generated/stellar/transaction_result_pair.rb +20 -0
- data/generated/stellar/transaction_result_set.rb +18 -0
- data/generated/stellar/transaction_set.rb +20 -0
- data/generated/stellar/trust_line_entry.rb +28 -0
- data/generated/stellar-base-generated.rb +160 -0
- data/lib/stellar/base/version.rb +5 -0
- data/lib/stellar/base.rb +1 -0
- data/lib/stellar/change_trust_op.rb +10 -0
- data/lib/stellar/currency.rb +28 -0
- data/lib/stellar/key_pair.rb +94 -0
- data/lib/stellar/payment_op.rb +38 -0
- data/lib/stellar/transaction.rb +72 -0
- data/lib/stellar/transaction_envelope.rb +32 -0
- data/lib/stellar/util/base58.rb +127 -0
- data/lib/stellar-base.rb +23 -0
- data/ruby-stellar-base.gemspec +34 -0
- data/spec/lib/stellar/key_pair_spec.rb +199 -0
- data/spec/lib/stellar/transaction_envelope_spec.rb +93 -0
- data/spec/lib/stellar/transaction_spec.rb +43 -0
- data/spec/lib/stellar/util/base58_spec.rb +74 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/matchers/be_base58_check.rb +9 -0
- data/spec/support/matchers/eq_bytes.rb +5 -0
- data/spec/support/matchers/have_length.rb +5 -0
- data/tasks/rspec.rake +6 -0
- data/tasks/travis.rake +9 -0
- data/tasks/xdr.rake +48 -0
- data/xdr/SCPXDR.x +61 -0
- data/xdr/Stellar-ledger-entries.x +105 -0
- data/xdr/Stellar-ledger.x +117 -0
- data/xdr/Stellar-overlay.x +100 -0
- data/xdr/Stellar-transaction.x +497 -0
- data/xdr/Stellar-types.x +53 -0
- data/xdr/StellarXDR.x +11 -0
- metadata +342 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module Stellar
|
|
2
|
+
module Util
|
|
3
|
+
class Base58
|
|
4
|
+
STELLAR_ALPHABET = "gsphnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCr65jkm8oFqi1tuvAxyz"
|
|
5
|
+
BITCOIN_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
6
|
+
BASE = 58
|
|
7
|
+
|
|
8
|
+
# TODO: improve the conversion to bitstring, perhaps `Fixnum#to_byte`?
|
|
9
|
+
VERSION_BYTES = {
|
|
10
|
+
none: [1].pack("C").encode("BINARY"),
|
|
11
|
+
account_id: [0].pack("C").encode("BINARY"),
|
|
12
|
+
seed: [33].pack("C").encode("BINARY"),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def self.stellar
|
|
16
|
+
Thread.current[:stellar_base58] ||= new(STELLAR_ALPHABET)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.bitcoin
|
|
20
|
+
Thread.current[:bitcoin_base58] ||= new(BITCOIN_ALPHABET)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(alphabet)
|
|
24
|
+
raise ArgumentError, "Invalid alphabet length" if alphabet.length != BASE
|
|
25
|
+
@alphabet = alphabet
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def encode(byte_str)
|
|
29
|
+
return "" if byte_str.nil? || byte_str.empty?
|
|
30
|
+
|
|
31
|
+
leading_zeros = byte_str.each_byte.take_while{|b| b == 0}.length
|
|
32
|
+
int = bytes_to_int(byte_str) # step 1
|
|
33
|
+
|
|
34
|
+
encode_int(int, leading_zeros)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def check_encode(version, byte_str)
|
|
38
|
+
version_byte = VERSION_BYTES[version]
|
|
39
|
+
raise ArgumentError, "Invalid version: #{version}" if version_byte.blank?
|
|
40
|
+
|
|
41
|
+
payload = version_byte + byte_str.dup.force_encoding("BINARY")
|
|
42
|
+
check = checksum(payload)
|
|
43
|
+
encode(payload + check)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def decode(str)
|
|
47
|
+
leading_zeros = str.each_char.take_while{|c| c == @alphabet[0]}.length
|
|
48
|
+
|
|
49
|
+
("\x00" * leading_zeros) + decode_int(str)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def check_decode(expected_version, str)
|
|
53
|
+
decoded = decode(str)
|
|
54
|
+
version_byte = decoded[0]
|
|
55
|
+
payload = decoded[1...-4]
|
|
56
|
+
check = decoded[-4..-1]
|
|
57
|
+
version = VERSION_BYTES.key(version_byte)
|
|
58
|
+
|
|
59
|
+
raise ArgumentError, "Unexpected version: #{version.inspect}" if version != expected_version
|
|
60
|
+
raise ArgumentError, "Invalid checksum" if check != checksum(decoded[0...-4])
|
|
61
|
+
payload
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def checksum(bytes)
|
|
65
|
+
inner = Digest::SHA256.digest(bytes)
|
|
66
|
+
Digest::SHA256.digest(inner)[0...4]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
def encode_int(int, leading_zeros=0)
|
|
71
|
+
# algorithm:
|
|
72
|
+
# 1. convert the bytes to a bignum
|
|
73
|
+
# 2. turn the bignum into a array of digits, least significant first
|
|
74
|
+
# 3. add any leading zero bytes as leading 0 digits
|
|
75
|
+
# 4. alphabatize the digits based upon the chosen alphabet
|
|
76
|
+
# 5. reverse the alphabetized digits (to get most significant digit first)
|
|
77
|
+
|
|
78
|
+
digits = []
|
|
79
|
+
|
|
80
|
+
# step 2
|
|
81
|
+
while int > 0
|
|
82
|
+
int, rem = int.divmod(BASE)
|
|
83
|
+
digits.push rem
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# step 3
|
|
87
|
+
leading_zeros.times{ digits.push 0 }
|
|
88
|
+
|
|
89
|
+
digits
|
|
90
|
+
.map{|d| @alphabet[d]} # step 4
|
|
91
|
+
.reverse # step 5
|
|
92
|
+
.join
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def decode_int(str)
|
|
96
|
+
int = str.reverse.each_char.with_index.inject(0) do |result, (digit, index)|
|
|
97
|
+
digit_val = @alphabet.index(digit)
|
|
98
|
+
raise ArgumentError, "#{digit} is not a valid base58 digit" if digit_val.nil?
|
|
99
|
+
result + (digit_val * (BASE**index))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
int_to_bytes(int)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def bytes_to_int(bytes)
|
|
106
|
+
bytes.unpack("C*").inject do |result, byte|
|
|
107
|
+
result <<= 8
|
|
108
|
+
result + byte
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def int_to_bytes(int)
|
|
113
|
+
return "\x00" if int == 0
|
|
114
|
+
|
|
115
|
+
bytes = []
|
|
116
|
+
current = int
|
|
117
|
+
|
|
118
|
+
while current > 0
|
|
119
|
+
bytes.unshift(current & 0xFF)
|
|
120
|
+
current >>= 8
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
bytes.pack("C*")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
data/lib/stellar-base.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'xdr'
|
|
2
|
+
require 'rbnacl'
|
|
3
|
+
require 'digest/sha2'
|
|
4
|
+
require 'active_support/core_ext/object/blank'
|
|
5
|
+
require 'active_support/core_ext/enumerable'
|
|
6
|
+
require 'active_support/core_ext/kernel/reporting'
|
|
7
|
+
|
|
8
|
+
# See ../generated for code-gen'ed files
|
|
9
|
+
|
|
10
|
+
silence_warnings do
|
|
11
|
+
require 'stellar-base-generated'
|
|
12
|
+
end
|
|
13
|
+
Stellar.load_all!
|
|
14
|
+
|
|
15
|
+
# extensions onto the generated files must be loaded manually, below
|
|
16
|
+
|
|
17
|
+
require_relative './stellar/change_trust_op'
|
|
18
|
+
require_relative './stellar/currency'
|
|
19
|
+
require_relative './stellar/key_pair'
|
|
20
|
+
require_relative './stellar/payment_op'
|
|
21
|
+
require_relative './stellar/transaction'
|
|
22
|
+
require_relative './stellar/transaction_envelope'
|
|
23
|
+
require_relative './stellar/util/base58'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'stellar/base/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "stellar-base"
|
|
8
|
+
spec.version = Stellar::Base::VERSION
|
|
9
|
+
spec.authors = ["Scott Fleckenstein"]
|
|
10
|
+
spec.email = ["scott@stellar.org"]
|
|
11
|
+
spec.summary = %q{Stellar client library: XDR}
|
|
12
|
+
spec.homepage = "https://github.com/stellar/ruby-stellar-vase"
|
|
13
|
+
spec.license = "Apache 2.0"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["generated", "lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_dependency "xdr"
|
|
21
|
+
spec.add_dependency "rbnacl"
|
|
22
|
+
spec.add_dependency "activesupport", "~> 4"
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
26
|
+
spec.add_development_dependency "xdrgen"
|
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.1"
|
|
28
|
+
spec.add_development_dependency "guard-rspec"
|
|
29
|
+
spec.add_development_dependency "simplecov"
|
|
30
|
+
spec.add_development_dependency "octokit"
|
|
31
|
+
spec.add_development_dependency "netrc"
|
|
32
|
+
spec.add_development_dependency "yard"
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
describe Stellar::KeyPair do
|
|
5
|
+
|
|
6
|
+
describe ".from_seed" do
|
|
7
|
+
subject{ Stellar::KeyPair.from_seed(seed) }
|
|
8
|
+
|
|
9
|
+
context "when provided a base58check encoded seed" do
|
|
10
|
+
let(:seed){ "s9aaUNPaT9t1x7vCeDzQYvLZDm5XxSUKkwnqQowV6D3kMr678uZ" }
|
|
11
|
+
it { should be_a(Stellar::KeyPair) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "provided value is not base58 encoded" do
|
|
15
|
+
let(:seed){ "allmylifemyhearthasbeensearching" }
|
|
16
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "provided value is not base58 encoded as a seed" do
|
|
20
|
+
let(:raw_seed){ "allmylifemyhearthasbeensearching" }
|
|
21
|
+
let(:seed){ Stellar::Util::Base58.stellar.check_encode(:account_id, raw_seed) }
|
|
22
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe ".from_raw_seed" do
|
|
27
|
+
subject{ Stellar::KeyPair.from_raw_seed(raw_seed) }
|
|
28
|
+
|
|
29
|
+
context "when the provided value is a 32-byte string" do
|
|
30
|
+
let(:raw_seed){ "allmylifemyhearthasbeensearching" }
|
|
31
|
+
it { should be_a(Stellar::KeyPair) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "when the provided value is < 32-byte string" do
|
|
35
|
+
let(:raw_seed){ "\xFF" * 31 }
|
|
36
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "when the provided value is > 32-byte string" do
|
|
40
|
+
let(:raw_seed){ "\xFF" * 33 }
|
|
41
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "when the provided value is a 32 character, but > 32 byte string (i.e. multi-byte characters)" do
|
|
45
|
+
let(:raw_seed){ "ü" + ("\x00" * 31) }
|
|
46
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe ".from_public_key" do
|
|
51
|
+
subject{ Stellar::KeyPair.from_public_key(key) }
|
|
52
|
+
|
|
53
|
+
context "when the provided value is a 32-byte string" do
|
|
54
|
+
let(:key){ "\xFF" * 32 }
|
|
55
|
+
it { should be_a(Stellar::KeyPair) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context "when the provided value is < 32-byte string" do
|
|
59
|
+
let(:key){ "\xFF" * 31 }
|
|
60
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "when the provided value is > 32-byte string" do
|
|
64
|
+
let(:key){ "\xFF" * 33 }
|
|
65
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "when the provided value is a 32 character, but > 32 byte string (i.e. multi-byte characters)" do
|
|
69
|
+
let(:key){ "ü" + ("\x00" * 31) }
|
|
70
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe ".from_address" do
|
|
75
|
+
subject{ Stellar::KeyPair.from_address(address) }
|
|
76
|
+
|
|
77
|
+
context "when provided a base58check encoded account_id" do
|
|
78
|
+
let(:address){ "gsYRSEQhTffqA9opPepAENCr2WG6z5iBHHubxxbRzWaHf8FBWcu" }
|
|
79
|
+
it { should be_a(Stellar::KeyPair) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context "provided value is not base58 encoded" do
|
|
83
|
+
let(:address){ "some address" }
|
|
84
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "provided value is not base58 encoded as an account_id" do
|
|
88
|
+
let(:public_key){ "\xFF" * 32 }
|
|
89
|
+
let(:address){ Stellar::Util::Base58.stellar.check_encode(:seed, public_key) }
|
|
90
|
+
it { expect{ subject }.to raise_error(ArgumentError) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe ".random" do
|
|
96
|
+
subject{ Stellar::KeyPair.random }
|
|
97
|
+
|
|
98
|
+
it "returns a new KeyPair every time" do
|
|
99
|
+
other = Stellar::KeyPair.random
|
|
100
|
+
expect(subject.raw_seed == other.raw_seed).to eq(false)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe "#public_key" do
|
|
105
|
+
let(:key_pair){ Stellar::KeyPair.random }
|
|
106
|
+
subject{ key_pair.public_key }
|
|
107
|
+
|
|
108
|
+
it { should be_a(String) }
|
|
109
|
+
it { should have_length(32) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "#raw_seed" do
|
|
113
|
+
let(:key_pair){ Stellar::KeyPair.random }
|
|
114
|
+
subject{ key_pair.raw_seed }
|
|
115
|
+
|
|
116
|
+
it { should be_a(String) }
|
|
117
|
+
it { should have_length(32) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe "#address" do
|
|
121
|
+
let(:key_pair){ Stellar::KeyPair.random }
|
|
122
|
+
subject{ key_pair.address }
|
|
123
|
+
it{ should be_base58_check(:account_id)}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "#seed" do
|
|
127
|
+
let(:key_pair){ Stellar::KeyPair.random }
|
|
128
|
+
subject{ key_pair.seed }
|
|
129
|
+
it{ should be_base58_check(:seed)}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe "#sign" do
|
|
133
|
+
let(:message) { "hello" }
|
|
134
|
+
subject{ key_pair.sign(message) }
|
|
135
|
+
|
|
136
|
+
context "when the key_pair has no private key" do
|
|
137
|
+
let(:key_pair){ Stellar::KeyPair.from_public_key("\x00" * 32)}
|
|
138
|
+
|
|
139
|
+
it{ expect{ subject }.to raise_error("no private key") }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context "when the key_pair has both public/private keys" do
|
|
143
|
+
let(:key_pair){ Stellar::KeyPair.from_raw_seed("\x00" * 32)}
|
|
144
|
+
|
|
145
|
+
it { should have_length(64) }
|
|
146
|
+
|
|
147
|
+
it "should be a ed25519 signature" do
|
|
148
|
+
verification = key_pair.rbnacl_verify_key.verify(subject, message)
|
|
149
|
+
expect(verification).to be_truthy
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context "when the message is nil" do
|
|
153
|
+
let(:message){ nil }
|
|
154
|
+
it { expect{subject}.to raise_error(TypeError) }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
describe "#verify" do
|
|
160
|
+
let(:key_pair) { Stellar::KeyPair.random }
|
|
161
|
+
let(:message) { "hello" }
|
|
162
|
+
subject { key_pair.verify(signature, message) }
|
|
163
|
+
|
|
164
|
+
context "when the signature is correct" do
|
|
165
|
+
let(:signature) { key_pair.sign(message) }
|
|
166
|
+
it{ should be_truthy }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
context "when the signature is incorrect" do
|
|
170
|
+
let(:signature) { key_pair.sign("some other message") }
|
|
171
|
+
it{ should be_falsey }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
context "when the signature is invalid" do
|
|
175
|
+
let(:signature) { "food" }
|
|
176
|
+
it{ should be_falsey }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
context "when the signature is from a different key" do
|
|
180
|
+
let(:signature) { Stellar::KeyPair.random.sign(message) }
|
|
181
|
+
it{ should be_falsey }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
describe "#sign?" do
|
|
187
|
+
subject{ key_pair.sign? }
|
|
188
|
+
|
|
189
|
+
context "when the key_pair has no private key" do
|
|
190
|
+
let(:key_pair){ Stellar::KeyPair.from_public_key("\x00" * 32)}
|
|
191
|
+
it{ should eq(false) }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context "when the key_pair has both public/private keys" do
|
|
195
|
+
let(:key_pair){ Stellar::KeyPair.from_raw_seed("\x00" * 32)}
|
|
196
|
+
it{ should eq(true) }
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Stellar::TransactionEnvelope do
|
|
4
|
+
let(:sender) { Stellar::KeyPair.random }
|
|
5
|
+
let(:receiver){ Stellar::KeyPair.random }
|
|
6
|
+
let(:transaction) do
|
|
7
|
+
Stellar::Transaction.payment({
|
|
8
|
+
account: sender,
|
|
9
|
+
destination: receiver,
|
|
10
|
+
sequence: 1,
|
|
11
|
+
amount: [:native, 20000000]
|
|
12
|
+
})
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:envelope){ transaction.to_envelope(*signers) }
|
|
16
|
+
|
|
17
|
+
describe "#signed_correctly?" do
|
|
18
|
+
subject{ envelope.signed_correctly?(*verifiers) }
|
|
19
|
+
|
|
20
|
+
context "when unsigned" do
|
|
21
|
+
let(:signers) { [] }
|
|
22
|
+
let(:verifiers){ [sender] }
|
|
23
|
+
|
|
24
|
+
it{ should be_falsey }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "when signed by a single account" do
|
|
28
|
+
let(:signers) { [sender] }
|
|
29
|
+
let(:verifiers){ signers }
|
|
30
|
+
|
|
31
|
+
context "and signed correctly" do
|
|
32
|
+
it{ should be_truthy }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "and the signature is corrupted" do
|
|
36
|
+
before(:each){ envelope.signatures.first.signature = "\xFF" * 32}
|
|
37
|
+
it{ should be_falsey }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "and the signature is from a different message" do
|
|
41
|
+
before(:each){ envelope.signatures = [sender.sign_decorated("hello")]}
|
|
42
|
+
it{ should be_falsey }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "and the key for the signing account is not provided" do
|
|
46
|
+
let(:verifiers){ [] }
|
|
47
|
+
it{ should be_falsey }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "and the key for the signing account is wrong" do
|
|
51
|
+
let(:verifiers){ [receiver] }
|
|
52
|
+
it{ should be_falsey }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "when signed by a multiple accounts" do
|
|
57
|
+
let(:alternate_signer){ Stellar::KeyPair.random }
|
|
58
|
+
let(:signers) { [sender, alternate_signer] }
|
|
59
|
+
let(:verifiers){ signers }
|
|
60
|
+
|
|
61
|
+
context "and all public keys are provided" do
|
|
62
|
+
it{ should be_truthy }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "and all public keys are provided, with additional unused keys provided" do
|
|
66
|
+
let(:verifiers){ signers + [Stellar::KeyPair.random] }
|
|
67
|
+
it{ should be_truthy }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context "and not all public keys for the signers are provided" do
|
|
71
|
+
let(:verifiers){ [alternate_signer] }
|
|
72
|
+
it{ should be_falsey }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "and one of the signatures is corrupted" do
|
|
76
|
+
before(:each){ envelope.signatures.last.signature = "\xFF" * 32}
|
|
77
|
+
it{ should be_falsey }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context "and the signature is from a different message" do
|
|
81
|
+
before(:each){ envelope.signatures = signers.map{|s| s.sign_decorated("hello")}}
|
|
82
|
+
it{ should be_falsey }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "#hash" do
|
|
89
|
+
let(:signers) { [sender] }
|
|
90
|
+
subject{ envelope.hash }
|
|
91
|
+
it{ should eq(Digest::SHA256.digest envelope.to_xdr)}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Stellar::Transaction do
|
|
4
|
+
subject do
|
|
5
|
+
Stellar::Transaction.new({
|
|
6
|
+
source_account: "\x00" * 32,
|
|
7
|
+
max_fee: 10,
|
|
8
|
+
seq_num: 1,
|
|
9
|
+
max_ledger: 10,
|
|
10
|
+
min_ledger: 0,
|
|
11
|
+
operations: [
|
|
12
|
+
Stellar::Operation.new(body: Stellar::Operation::Body.new(:inflation, 1))
|
|
13
|
+
]
|
|
14
|
+
})
|
|
15
|
+
end
|
|
16
|
+
let(:key_pair){ Stellar::KeyPair.random }
|
|
17
|
+
|
|
18
|
+
describe "#sign" do
|
|
19
|
+
let(:result){ subject.sign(key_pair) }
|
|
20
|
+
|
|
21
|
+
it "returns a signature of SHA256(xdr form of the transaction)" do
|
|
22
|
+
xdr = subject.to_xdr
|
|
23
|
+
hash = Digest::SHA256.digest(xdr)
|
|
24
|
+
expected = key_pair.sign(hash)
|
|
25
|
+
expect(result).to eq(expected)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "#to_envelope" do
|
|
30
|
+
let(:result){ subject.to_envelope(key_pair) }
|
|
31
|
+
|
|
32
|
+
it "return a Stellar::TransactionEnvelope" do
|
|
33
|
+
expect(result).to be_a(Stellar::TransactionEnvelope)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "correctly signs the transaction" do
|
|
37
|
+
expect(result.signatures.length).to eq(1)
|
|
38
|
+
expect(result.signatures.first).to be_a(Stellar::DecoratedSignature)
|
|
39
|
+
expect(result.signatures.first.hint).to eq(key_pair.public_key_hint)
|
|
40
|
+
expect(result.signatures.first.signature).to eq(subject.sign(key_pair))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Stellar::Util::Base58 do
|
|
4
|
+
subject{ Stellar::Util::Base58.new(Stellar::Util::Base58::BITCOIN_ALPHABET) }
|
|
5
|
+
|
|
6
|
+
describe "#decode" do
|
|
7
|
+
it "properly encodes" do
|
|
8
|
+
expect(decode "z").to eq_bytes("\x39")
|
|
9
|
+
expect(decode "111z").to eq_bytes("\x00\x00\x00\x39")
|
|
10
|
+
expect(decode "2UzHL").to eq_bytes("\xFF\xFF\xFF")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "raises an ArgumentError when the string cantains an invalid character" do
|
|
14
|
+
expect{ decode "OOO" }.to raise_error(ArgumentError)
|
|
15
|
+
expect{ decode "\xFF" }.to raise_error(ArgumentError)
|
|
16
|
+
expect{ decode "\x00" }.to raise_error(ArgumentError)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def decode(bytes)
|
|
20
|
+
subject.decode(bytes)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#check_decode" do
|
|
25
|
+
it "properly decodes" do
|
|
26
|
+
expect(decode :none, "cKyAv51").to eq_bytes("\x39")
|
|
27
|
+
expect(decode :seed, "RN3BaguaZ6Mi").to eq_bytes("\x00\x00\x00\x39")
|
|
28
|
+
expect(decode :account_id, "1Ahg1iQXoss").to eq_bytes("\xFF\xFF\xFF")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "raises an ArgumentError if the decoded version byte does not match the expected value" do
|
|
32
|
+
expect{ decode :none, "1Ahg1iQXoss" }.to raise_error(ArgumentError)
|
|
33
|
+
expect{ decode :seed, "cKyAv51" }.to raise_error(ArgumentError)
|
|
34
|
+
expect{ decode :account_id, "RN3BaguaZ6Mi" }.to raise_error(ArgumentError)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "raises an ArgumentError if the decoded value cannot be validated by the checksum" do
|
|
38
|
+
expect{decode :seed, "RN3BaguaZ6MM"}.to raise_error(ArgumentError)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def decode(version, bytes)
|
|
42
|
+
subject.check_decode(version, bytes)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe"#encode" do
|
|
47
|
+
it "properly encodes" do
|
|
48
|
+
expect(encode "\x39").to eq("z")
|
|
49
|
+
expect(encode "\x00\x00\x00\x39").to eq("111z")
|
|
50
|
+
expect(encode "\xFF\xFF\xFF").to eq("2UzHL")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def encode(bytes)
|
|
54
|
+
subject.encode(bytes)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe"#check_encode" do
|
|
59
|
+
it "properly encodes" do
|
|
60
|
+
expect(encode :none, "\x39").to eq("cKyAv51")
|
|
61
|
+
expect(encode :seed, "\x00\x00\x00\x39").to eq("RN3BaguaZ6Mi")
|
|
62
|
+
expect(encode :account_id, "\xFF\xFF\xFF").to eq("1Ahg1iQXoss")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "raises an ArgumentError when an invalid version is provided" do
|
|
66
|
+
expect{ encode :floob, "\x39" }.to raise_error(ArgumentError)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def encode(version, bytes)
|
|
70
|
+
subject.check_encode(version, bytes)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
Bundler.setup
|
|
3
|
+
|
|
4
|
+
require 'simplecov'
|
|
5
|
+
SimpleCov.start
|
|
6
|
+
|
|
7
|
+
require 'pry'
|
|
8
|
+
require 'stellar-base'
|
|
9
|
+
|
|
10
|
+
SPEC_ROOT = File.dirname(__FILE__)
|
|
11
|
+
|
|
12
|
+
Dir["#{SPEC_ROOT}/support/**/*.rb"].each { |f| require f }
|
|
13
|
+
|
|
14
|
+
RSpec.configure do |config|
|
|
15
|
+
|
|
16
|
+
end
|
data/tasks/rspec.rake
ADDED