zold 0.0.4 → 0.0.5

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
  SHA1:
3
- metadata.gz: d41d9a57531584dc4084646cd4d211fa55d5e7af
4
- data.tar.gz: 50a0a58ed765f7efc239b9dddb870ee89efa6a86
3
+ metadata.gz: 4af388b06015493f1ef9524f79afae08cdf1c1b6
4
+ data.tar.gz: 59a0f2cbd9f03218906777eb44008a5deebbcf6a
5
5
  SHA512:
6
- metadata.gz: 2d4576d6471e6c6bd4589ae2221b8f321291528f6e752d3cddf8ca70df135664d94499b7e5332c06053c87423fbbfdf3392e86c578305bb464b09b944db8b393
7
- data.tar.gz: 043d5fdeb31a4afe610074c8426303b3378e8a239087c24ed496c0b40bcdd8bccf01ef3225393527404f1795953aed01099eecfab1d45c53e1e37e4725efa50b
6
+ metadata.gz: 6b14cca3133a4e94ebf9618d61b65992f2dbbd7b10a81251ee39aa8051f98b35787b305c47e42c1ff7a5bc42d191f644719299d03ac1d1195775b39a457cb372
7
+ data.tar.gz: d76056868277add9a8cf30134d58e930dd06c9c20dd31a6a80a72c0ffef293e92001b2c95e83351ad471d8e77d2be0b61c6f44df4841b284f485c14034c2eb50
@@ -9,9 +9,11 @@ Metrics/CyclomaticComplexity:
9
9
  Max: 10
10
10
  Metrics/MethodLength:
11
11
  Enabled: false
12
- Style/MultilineMethodCallIndentation:
12
+ Layout/MultilineMethodCallIndentation:
13
13
  Enabled: false
14
14
  Metrics/AbcSize:
15
15
  Enabled: false
16
16
  Metrics/BlockLength:
17
17
  Max: 50
18
+ Metrics/ClassLength:
19
+ Max: 200
data/.simplecov CHANGED
@@ -35,6 +35,6 @@ else
35
35
  SimpleCov.start do
36
36
  add_filter "/test/"
37
37
  add_filter "/features/"
38
- minimum_coverage 10
38
+ minimum_coverage 60
39
39
  end
40
40
  end
data/README.md CHANGED
@@ -60,6 +60,7 @@ Or do one of the following:
60
60
 
61
61
  * `zold init` creates a new wallet (you have to provide PGP keys)
62
62
  * `zold pull` pulls a wallet from the network
63
+ * `zold balance` checks the balance of a wallet
63
64
  * `zold send` creates a new transaction
64
65
  * `zold push` pushes a wallet to the network
65
66
 
data/Rakefile CHANGED
@@ -59,7 +59,8 @@ RuboCop::RakeTask.new(:rubocop) do |task|
59
59
  end
60
60
 
61
61
  require 'cucumber/rake/task'
62
- Cucumber::Rake::Task.new(:features) do
62
+ Cucumber::Rake::Task.new(:features) do |t|
63
+ t.cucumber_opts = 'features --format progress'
63
64
  Rake::Cleaner.cleanup_files(['coverage'])
64
65
  end
65
66
  Cucumber::Rake::Task.new(:'features:html') do |t|
@@ -38,7 +38,7 @@ SOFTWARE.
38
38
  <xs:all>
39
39
  <xs:element name="beneficiary" type="wallet" minOccurs="1" maxOccurs="1"/>
40
40
  <xs:element name="date" type="xs:dateTime" minOccurs="1" maxOccurs="1"/>
41
- <xs:element name="sign" type="xs:string" minOccurs="1" maxOccurs="1"/>
41
+ <xs:element name="sign" type="xs:string" minOccurs="0" maxOccurs="1"/>
42
42
  <xs:element name="amount" type="amount" minOccurs="1" maxOccurs="1"/>
43
43
  </xs:all>
44
44
  <xs:attribute name="id" use="required">
@@ -51,7 +51,7 @@ SOFTWARE.
51
51
  </xs:complexType>
52
52
  <xs:complexType name="ledger">
53
53
  <xs:sequence>
54
- <xs:element name="txn" minOccurs="0" maxOccurs="unbounded"/>
54
+ <xs:element name="txn" type="txn" minOccurs="0" maxOccurs="unbounded"/>
55
55
  </xs:sequence>
56
56
  </xs:complexType>
57
57
  <xs:element name="wallet">
data/bin/zold CHANGED
@@ -28,11 +28,16 @@ require 'rainbow'
28
28
  require_relative '../lib/zold'
29
29
  require_relative '../lib/zold/version'
30
30
  require_relative '../lib/zold/wallet'
31
+ require_relative '../lib/zold/wallets'
31
32
  require_relative '../lib/zold/log'
32
33
  require_relative '../lib/zold/key'
33
34
  require_relative '../lib/zold/amount'
34
35
  require_relative '../lib/zold/commands/create'
35
36
  require_relative '../lib/zold/commands/send'
37
+ require_relative '../lib/zold/commands/balance'
38
+ require_relative '../lib/zold/commands/check'
39
+ require_relative '../lib/zold/commands/pull'
40
+ require_relative '../lib/zold/commands/push'
36
41
 
37
42
  Encoding.default_external = Encoding::UTF_8
38
43
  Encoding.default_internal = Encoding::UTF_8
@@ -47,6 +52,10 @@ Available commands:
47
52
  Creates a new wallet with a random ID
48
53
  #{Rainbow('pull').green} [id...]
49
54
  Pulls all local wallets and new ones explicitly required
55
+ #{Rainbow('check').green} id
56
+ Checks the validity of the wallet
57
+ #{Rainbow('balance').green} id
58
+ Prints the balance of the wallet
50
59
  #{Rainbow('send').green} source target amount
51
60
  Send ZOLD from one wallet to another
52
61
  #{Rainbow('push').green} [id...]
@@ -55,14 +64,17 @@ Available options:"
55
64
  o.string '-d', '--dir',
56
65
  'The directory where wallets are stored (default: current directory)',
57
66
  default: '.'
58
- o.string '-p', '--private-key',
67
+ o.string '--private-key',
59
68
  'The location of RSA private key (default: ~/.ssh/id_rsa)',
60
69
  default: '~/.ssh/id_rsa'
61
- o.string '-u', '--public-key',
70
+ o.string '--public-key',
62
71
  'The location of RSA public key (default: ~/.ssh/id_rsa.pub)',
63
72
  default: '~/.ssh/id_rsa.pub'
64
73
  o.bool '-h', '--help', 'Show these instructions'
65
74
  o.bool '--trace', 'Show full stack trace in case of a problem'
75
+ o.on '--no-colors', 'Disable colors in the ouput' do
76
+ Rainbow.enabled = false
77
+ end
66
78
  o.on '-v', '--version', 'Show current version' do
67
79
  puts Zold::VERSION
68
80
  exit
@@ -78,33 +90,51 @@ Available options:"
78
90
 
79
91
  command = opts.arguments[0]
80
92
 
93
+ wallets = Zold::Wallets.new(opts['dir'])
94
+
81
95
  case command
82
96
  when 'create'
83
97
  Zold::Create.new(
84
- dir: opts['dir'],
85
- pubkey: Zold::Key.new(opts['public-key']),
98
+ wallets: wallets,
99
+ pubkey: Zold::Key.new(file: opts['public-key']),
86
100
  log: log
87
101
  ).run
88
102
  when 'send'
103
+ raise "Payer wallet ID is required" if opts.arguments[1].nil?
104
+ raise "Recepient wallet ID is required" if opts.arguments[2].nil?
89
105
  Zold::Send.new(
90
- payer: Zold::Wallet.new(
91
- File.join(opts['dir'], "#{opts.arguments[1]}.xml")
92
- ),
93
- receiver: Zold::Wallet.new(
94
- File.join(opts['dir'], "#{opts.arguments[2]}.xml")
95
- ),
106
+ payer: wallets.find(Zold::Id.new(opts.arguments[1])),
107
+ receiver: wallets.find(Zold::Id.new(opts.arguments[2])),
96
108
  amount: Zold::Amount.new(zld: opts.arguments[3].to_f),
97
- pvtkey: Zold::Key.new(opts['private-key']),
109
+ pvtkey: Zold::Key.new(file: opts['private-key']),
110
+ log: log
111
+ ).run
112
+ when 'balance'
113
+ raise "Wallet ID is required" if opts.arguments[1].nil?
114
+ Zold::Balance.new(
115
+ wallet: wallets.find(Zold::Id.new(opts.arguments[1])),
116
+ log: log
117
+ ).run
118
+ when 'check'
119
+ raise "Wallet ID is required" if opts.arguments[1].nil?
120
+ Zold::Check.new(
121
+ wallet: wallets.find(Zold::Id.new(opts.arguments[1])),
122
+ wallets: wallets,
98
123
  log: log
99
124
  ).run
100
125
  when 'pull'
101
- raise 'PULL is not implemented yet'
126
+ Zold::Pull.new(
127
+ wallet: wallets.find(Zold::Id.new(opts.arguments[1])),
128
+ log: log
129
+ ).run
102
130
  when 'push'
103
- raise 'PUSH is not implemented yet'
131
+ Zold::Push.new(
132
+ wallet: wallets.find(Zold::Id.new(opts.arguments[1])),
133
+ log: log
134
+ ).run
104
135
  else
105
136
  raise "Command '#{command}' is not supported"
106
137
  end
107
-
108
138
  rescue StandardError => ex
109
139
  log.error(ex.message)
110
140
  puts ex.backtrace if opts['trace']
@@ -11,3 +11,7 @@ Feature: Command Line Processing
11
11
  When I run bin/zold with "--version"
12
12
  Then Exit code is zero
13
13
 
14
+ Scenario: Wallet can be created
15
+ When I run bin/zold with "create --private-key id_rsa --public-key id_rsa.pub"
16
+ Then Exit code is zero
17
+
@@ -26,6 +26,8 @@ require_relative '../../lib/zold'
26
26
  Before do
27
27
  @cwd = Dir.pwd
28
28
  @dir = Dir.mktmpdir('test')
29
+ FileUtils.copy('fixtures/id_rsa', @dir)
30
+ FileUtils.copy('fixtures/id_rsa.pub', @dir)
29
31
  FileUtils.mkdir_p(@dir) unless File.exist?(@dir)
30
32
  Dir.chdir(@dir)
31
33
  end
@@ -18,6 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
+ require 'rainbow'
22
+
21
23
  # The amount.
22
24
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
23
25
  # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
@@ -29,8 +31,11 @@ module Zold
29
31
  raise 'You can\'t specify both coints and zld' if !coins.nil? && !zld.nil?
30
32
  @coins = coins unless coins.nil?
31
33
  @coins = (zld * 2**24).to_i unless zld.nil?
34
+ raise "Integer is required: #{@coins.class}" unless @coins.is_a?(Integer)
32
35
  end
33
36
 
37
+ ZERO = Amount.new(coins: 0)
38
+
34
39
  def to_i
35
40
  @coins
36
41
  end
@@ -40,13 +45,22 @@ module Zold
40
45
  end
41
46
 
42
47
  def to_s
43
- "#{to_zld}ZLD"
48
+ text = "#{to_zld}ZLD"
49
+ if negative?
50
+ Rainbow(text).red
51
+ else
52
+ Rainbow(text).green
53
+ end
44
54
  end
45
55
 
46
56
  def ==(other)
47
57
  @coins == other.to_i
48
58
  end
49
59
 
60
+ def +(other)
61
+ Amount.new(coins: @coins + other.to_i)
62
+ end
63
+
50
64
  def zero?
51
65
  @coins.zero?
52
66
  end
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2018 Zerocracy, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require_relative '../log.rb'
22
+
23
+ # BALANCE command.
24
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
+ # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
26
+ # License:: MIT
27
+ module Zold
28
+ # Balance checking command
29
+ class Balance
30
+ def initialize(wallet:, log: Log::Quiet.new)
31
+ @wallet = wallet
32
+ @log = log
33
+ end
34
+
35
+ def run
36
+ balance = @wallet.balance
37
+ @log.info("The balance of #{@wallet} is #{balance}")
38
+ balance
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2018 Zerocracy, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require_relative '../log.rb'
22
+ require_relative '../id.rb'
23
+ require_relative 'pull.rb'
24
+
25
+ # CHECK command.
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
28
+ # License:: MIT
29
+ module Zold
30
+ # Wallet checking command
31
+ class Check
32
+ def initialize(wallet:, wallets:, log: Log::Quiet.new)
33
+ @wallet = wallet
34
+ @wallets = wallets
35
+ @log = log
36
+ end
37
+
38
+ def run
39
+ clean = true
40
+ @wallet.income do |t|
41
+ bnf = Pull.new(
42
+ wallet: @wallets.find(Id.new(t[:beneficiary])),
43
+ log: @log
44
+ ).run
45
+ clean = bnf.check(t[:id], t[:amount], @wallet.id)
46
+ next if clean
47
+ @log.error("Txn ##{t[:id]} for #{t[:amount]} is absent at #{bnf.id}")
48
+ break
49
+ end
50
+ if clean
51
+ @log.info("The #{@wallet} is clean")
52
+ else
53
+ @log.error("The #{@wallet} is compromised")
54
+ end
55
+ clean
56
+ end
57
+ end
58
+ end
@@ -29,18 +29,17 @@ require_relative '../id.rb'
29
29
  module Zold
30
30
  # Create command
31
31
  class Create
32
- def initialize(dir:, pubkey:, log: Log::Quiet.new)
33
- @dir = dir
32
+ def initialize(wallets:, pubkey:, log: Log::Quiet.new)
33
+ @wallets = wallets
34
34
  @pubkey = pubkey
35
35
  @log = log
36
36
  end
37
37
 
38
38
  def run
39
39
  id = Id.new
40
- file = File.join(@dir, "#{id}.xml")
41
- wallet = Wallet.new(file)
40
+ wallet = @wallets.find(id)
42
41
  wallet.init(id, @pubkey)
43
- @log.info("#{wallet} created at #{file}")
42
+ @log.info("#{wallet} created at #{@wallets}")
44
43
  wallet
45
44
  end
46
45
  end
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2018 Zerocracy, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require_relative '../log.rb'
22
+
23
+ # PULL command.
24
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
+ # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
26
+ # License:: MIT
27
+ module Zold
28
+ # Wallet pulling command
29
+ class Pull
30
+ def initialize(wallet:, log: Log::Quiet.new)
31
+ @wallet = wallet
32
+ @log = log
33
+ end
34
+
35
+ def run
36
+ raise 'PULL doesn\'t work and the wallet is absent' unless @wallet.exists?
37
+ @log.info("The #{@wallet} is here")
38
+ @wallet
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2018 Zerocracy, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require_relative '../log.rb'
22
+
23
+ # PUSH command.
24
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
+ # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
26
+ # License:: MIT
27
+ module Zold
28
+ # Wallet pushing command
29
+ class Push
30
+ def initialize(wallet:, log: Log::Quiet.new)
31
+ @wallet = wallet
32
+ @log = log
33
+ end
34
+
35
+ def run
36
+ raise 'The wallet is absent' unless @wallet.exists?
37
+ raise 'PUSH is not implemented yet'
38
+ end
39
+ end
40
+ end
@@ -37,8 +37,10 @@ module Zold
37
37
 
38
38
  def run
39
39
  raise "The amount can't be negative: #{@amount}" if @amount.negative?
40
- @receiver.add(@payer.sub(@amount, @receiver.id, @pvtkey))
40
+ txn = @payer.sub(@amount, @receiver.id, @pvtkey)
41
+ @receiver.add(txn)
41
42
  @log.info("#{@amount} sent from #{@payer} to #{@receiver}")
43
+ txn[:id]
42
44
  end
43
45
  end
44
46
  end
@@ -25,8 +25,8 @@
25
25
  module Zold
26
26
  # Id of the wallet
27
27
  class Id
28
- def initialize
29
- @id = rand(2**32..2**64 - 1)
28
+ def initialize(id = nil)
29
+ @id = id.nil? ? rand(2**32..2**64 - 1) : Integer("0x#{id}", 16)
30
30
  end
31
31
 
32
32
  def to_s
@@ -28,23 +28,31 @@ require 'base64'
28
28
  module Zold
29
29
  # A key
30
30
  class Key
31
- def initialize(file)
32
- @file = File.expand_path(file)
31
+ def initialize(file: nil, text: nil)
32
+ unless file.nil?
33
+ path = File.expand_path(file)
34
+ raise "Can't find RSA key at #{file} (#{path})" unless File.exist?(path)
35
+ @body = File.read(path)
36
+ end
37
+ @body = text unless text.nil?
33
38
  end
34
39
 
35
40
  def to_s
36
41
  rsa.to_s.strip
37
42
  end
38
43
 
39
- def encrypt(text)
40
- Base64.encode64(rsa.private_encrypt(text))
44
+ def sign(text)
45
+ Base64.encode64(rsa.sign(OpenSSL::Digest::SHA256.new, text)).delete("\n")
46
+ end
47
+
48
+ def verify(signature, text)
49
+ rsa.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), text)
41
50
  end
42
51
 
43
52
  private
44
53
 
45
54
  def rsa
46
- raise "Can't find RSA key at #{@file}" unless File.exist?(@file)
47
- text = File.read(@file).strip
55
+ text = @body.strip
48
56
  unless text.start_with?('-----BEGIN')
49
57
  text = OpenSSHKeyConverter.decode_pubkey(text.split[1])
50
58
  end
@@ -23,5 +23,5 @@
23
23
  # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
24
24
  # License:: MIT
25
25
  module Zold
26
- VERSION = '0.0.4'.freeze
26
+ VERSION = '0.0.5'.freeze
27
27
  end
@@ -36,7 +36,12 @@ module Zold
36
36
  id
37
37
  end
38
38
 
39
+ def exists?
40
+ File.exist?(@file)
41
+ end
42
+
39
43
  def init(id, pubkey)
44
+ raise "File '#{@file}' already exists" if File.exist?(@file)
40
45
  File.write(
41
46
  @file,
42
47
  valid(
@@ -65,16 +70,22 @@ module Zold
65
70
  end
66
71
 
67
72
  def sub(amount, target, pvtkey)
68
- txn = 1
69
- date = Time.now.iso8601
70
73
  xml = load
74
+ txn = 1
75
+ unless xml.xpath('/wallet/ledger[txn]').empty?
76
+ txn = xml.xpath('/wallet/ledger/txn/@id')
77
+ .map(&:to_s)
78
+ .map(&:to_i)
79
+ .max + 1
80
+ end
81
+ date = Time.now
71
82
  t = xml.xpath('/wallet/ledger')[0].add_child('<txn/>')[0]
72
83
  t['id'] = txn
73
- t.add_child('<date/>')[0].content = date
84
+ t.add_child('<date/>')[0].content = date.iso8601
74
85
  t.add_child('<amount/>')[0].content = -amount.to_i
75
86
  t.add_child('<beneficiary/>')[0].content = target
76
- t.add_child('<sign/>')[0].content = pvtkey.encrypt(
77
- "#{id} #{date} #{amount.to_i} #{target}"
87
+ t.add_child('<sign/>')[0].content = pvtkey.sign(
88
+ "#{txn} #{amount.to_i} #{target}"
78
89
  )
79
90
  save(xml)
80
91
  { id: txn, date: date, amount: amount, beneficiary: id }
@@ -84,15 +95,39 @@ module Zold
84
95
  xml = load
85
96
  t = xml.xpath('/wallet/ledger')[0].add_child('<txn/>')[0]
86
97
  t['id'] = "/#{txn[:id]}"
87
- t.add_child('<date/>')[0].content = txn[:date]
98
+ t.add_child('<date/>')[0].content = txn[:date].iso8601
88
99
  t.add_child('<amount/>')[0].content = txn[:amount].to_i
89
100
  t.add_child('<beneficiary/>')[0].content = txn[:beneficiary]
90
101
  save(xml).to_s
91
102
  end
92
103
 
104
+ def check(id, amount, beneficiary)
105
+ xml = load
106
+ txn = xml.xpath("/wallet/ledger/txn[@id='#{id}']")[0]
107
+ Amount.new(coins: txn.xpath('amount/text()')[0].to_s.to_i).mul(-1) ==
108
+ amount &&
109
+ txn.xpath('beneficiary/text()')[0].to_s == beneficiary &&
110
+ Key.new(text: xml.xpath('/wallet/pkey/text()')[0].to_s).verify(
111
+ txn.xpath('sign/text()')[0].to_s,
112
+ "#{id} #{amount.to_i} #{beneficiary}"
113
+ )
114
+ end
115
+
116
+ def income
117
+ load.xpath('/wallet/ledger/txn[amount > 0]').each do |txn|
118
+ hash = {
119
+ id: txn['id'][1..-1].to_i,
120
+ beneficiary: Id.new(txn.xpath('beneficiary/text()')[0].to_s),
121
+ amount: Amount.new(coins: txn.xpath('amount/text()')[0].to_s.to_i)
122
+ }
123
+ yield hash
124
+ end
125
+ end
126
+
93
127
  private
94
128
 
95
129
  def load
130
+ raise "File '#{@file}' is absent" unless File.exist?(@file)
96
131
  valid(Nokogiri::XML(File.read(@file)))
97
132
  end
98
133
 
@@ -101,7 +136,14 @@ module Zold
101
136
  end
102
137
 
103
138
  def valid(xml)
104
- xsd = Nokogiri::XML::Schema(File.open('assets/wallet.xsd'))
139
+ xsd = Nokogiri::XML::Schema(
140
+ File.open(
141
+ File.join(
142
+ File.dirname(__FILE__),
143
+ '../../assets/wallet.xsd'
144
+ )
145
+ )
146
+ )
105
147
  errors = xsd.validate(xml)
106
148
  unless errors.empty?
107
149
  errors.each do |error|
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2018 Zerocracy, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require_relative 'wallet.rb'
22
+
23
+ # The local collection of wallets.
24
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
+ # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
26
+ # License:: MIT
27
+ module Zold
28
+ # Collection of local wallets
29
+ class Wallets
30
+ def initialize(dir)
31
+ @dir = dir
32
+ end
33
+
34
+ def to_s
35
+ @dir
36
+ end
37
+
38
+ def find(id)
39
+ Zold::Wallet.new(File.join(@dir, "z-#{id}.xml"))
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2018 Zerocracy, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require 'minitest/autorun'
22
+ require 'tmpdir'
23
+ require_relative '../../lib/zold/wallet.rb'
24
+ require_relative '../../lib/zold/key.rb'
25
+ require_relative '../../lib/zold/id.rb'
26
+ require_relative '../../lib/zold/commands/balance.rb'
27
+
28
+ # BALANCE test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
31
+ # License:: MIT
32
+ class TestBalance < Minitest::Test
33
+ def test_checks_wallet_balance
34
+ Dir.mktmpdir 'test' do |dir|
35
+ id = Zold::Id.new
36
+ wallet = Zold::Wallets.new(dir).find(id)
37
+ wallet.init(Zold::Id.new, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
38
+ balance = Zold::Balance.new(
39
+ wallet: wallet
40
+ ).run
41
+ assert balance == Zold::Amount::ZERO
42
+ end
43
+ end
44
+ end
@@ -20,7 +20,7 @@
20
20
 
21
21
  require 'minitest/autorun'
22
22
  require 'tmpdir'
23
- require_relative '../../lib/zold/wallet.rb'
23
+ require_relative '../../lib/zold/wallets.rb'
24
24
  require_relative '../../lib/zold/key.rb'
25
25
  require_relative '../../lib/zold/commands/create.rb'
26
26
 
@@ -32,12 +32,12 @@ class TestCreate < Minitest::Test
32
32
  def test_creates_wallet
33
33
  Dir.mktmpdir 'test' do |dir|
34
34
  wallet = Zold::Create.new(
35
- dir: dir,
36
- pubkey: Zold::Key.new('fixtures/id_rsa.pub')
35
+ wallets: Zold::Wallets.new(dir),
36
+ pubkey: Zold::Key.new(file: 'fixtures/id_rsa.pub')
37
37
  ).run
38
38
  assert wallet.balance.zero?
39
39
  assert(
40
- File.exist?(File.join(dir, "#{wallet.id}.xml")),
40
+ File.exist?(File.join(dir, "z-#{wallet.id}.xml")),
41
41
  "Wallet file not found: #{wallet.id}.xml"
42
42
  )
43
43
  end
@@ -33,15 +33,15 @@ class TestSend < Minitest::Test
33
33
  def test_sends_from_wallet_to_wallet
34
34
  Dir.mktmpdir 'test' do |dir|
35
35
  source = Zold::Wallet.new(File.join(dir, 'source.xml'))
36
- source.init(Zold::Id.new, Zold::Key.new('fixtures/id_rsa.pub'))
36
+ source.init(Zold::Id.new, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
37
37
  target = Zold::Wallet.new(File.join(dir, 'target.xml'))
38
- target.init(Zold::Id.new, Zold::Key.new('fixtures/id_rsa.pub'))
38
+ target.init(Zold::Id.new, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
39
39
  amount = Zold::Amount.new(zld: 14.95)
40
40
  Zold::Send.new(
41
41
  payer: source,
42
42
  receiver: target,
43
43
  amount: amount,
44
- pvtkey: Zold::Key.new('fixtures/id_rsa')
44
+ pvtkey: Zold::Key.new(file: 'fixtures/id_rsa')
45
45
  ).run
46
46
  assert source.balance == amount.mul(-1)
47
47
  assert target.balance == amount
@@ -30,7 +30,7 @@ class TestAmount < Minitest::Test
30
30
  def test_parses_zld
31
31
  amount = Zold::Amount.new(zld: 14.95)
32
32
  assert(
33
- amount.to_s == '14.95ZLD',
33
+ amount.to_s.include?('14.95ZLD'),
34
34
  "#{amount} is not equal to '14.95ZLD'"
35
35
  )
36
36
  end
@@ -38,7 +38,7 @@ class TestAmount < Minitest::Test
38
38
  def test_parses_coins
39
39
  amount = Zold::Amount.new(coins: 900_000_000)
40
40
  assert(
41
- amount.to_s == '53.64ZLD',
41
+ amount.to_s.include?('53.64ZLD'),
42
42
  "#{amount} is not equal to '53.64ZLD'"
43
43
  )
44
44
  end
@@ -41,4 +41,10 @@ class TestId < Minitest::Test
41
41
  assert id.to_s == before
42
42
  end
43
43
  end
44
+
45
+ def test_parses_id
46
+ hex = 'ff01889402fe0954'
47
+ id = Zold::Id.new(hex)
48
+ assert(id.to_s == hex, "#{id} is not equal to #{hex}")
49
+ end
44
50
  end
@@ -28,14 +28,31 @@ require_relative '../lib/zold/key.rb'
28
28
  # License:: MIT
29
29
  class TestKey < Minitest::Test
30
30
  def test_reads_public_rsa
31
- key = Zold::Key.new('fixtures/id_rsa.pub')
31
+ key = Zold::Key.new(file: 'fixtures/id_rsa.pub')
32
32
  assert key.to_s.start_with?("-----BEGIN PUBLIC KEY-----\nMIICI")
33
33
  assert key.to_s.end_with?("EAAQ==\n-----END PUBLIC KEY-----")
34
34
  end
35
35
 
36
36
  def test_reads_private_rsa
37
- key = Zold::Key.new('fixtures/id_rsa')
37
+ key = Zold::Key.new(file: 'fixtures/id_rsa')
38
38
  assert key.to_s.start_with?("-----BEGIN RSA PRIVATE KEY-----\nMIIJJ")
39
39
  assert key.to_s.end_with?("Sg==\n-----END RSA PRIVATE KEY-----")
40
40
  end
41
+
42
+ def test_signs_and_verifies
43
+ pub = Zold::Key.new(file: 'fixtures/id_rsa.pub')
44
+ pvt = Zold::Key.new(file: 'fixtures/id_rsa')
45
+ text = 'How are you, my friend?'
46
+ signature = pvt.sign(text)
47
+ assert pub.verify(signature, text)
48
+ end
49
+
50
+ def test_signs_and_verifies_with_random_key
51
+ key = OpenSSL::PKey::RSA.new(2048)
52
+ pub = Zold::Key.new(text: key.public_key.to_s)
53
+ pvt = Zold::Key.new(text: key.to_s)
54
+ text = 'How are you doing, dude?'
55
+ signature = pvt.sign(text)
56
+ assert pub.verify(signature, text)
57
+ end
41
58
  end
@@ -24,6 +24,7 @@ require_relative '../lib/zold/key.rb'
24
24
  require_relative '../lib/zold/id.rb'
25
25
  require_relative '../lib/zold/wallet.rb'
26
26
  require_relative '../lib/zold/amount.rb'
27
+ require_relative '../lib/zold/commands/send.rb'
27
28
 
28
29
  # Wallet test.
29
30
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -32,28 +33,79 @@ require_relative '../lib/zold/amount.rb'
32
33
  class TestWallet < Minitest::Test
33
34
  def test_adds_transaction
34
35
  Dir.mktmpdir 'test' do |dir|
35
- file = File.join(dir, 'source.xml')
36
- wallet = Zold::Wallet.new(file)
37
- wallet.init(Zold::Id.new, Zold::Key.new('fixtures/id_rsa.pub'))
36
+ wallet = wallet(dir)
38
37
  amount = Zold::Amount.new(zld: 39.99)
39
- wallet.sub(amount, 100, Zold::Key.new('fixtures/id_rsa'))
38
+ key = Zold::Key.new(file: 'fixtures/id_rsa')
39
+ wallet.sub(amount, Zold::Id.new, key)
40
+ wallet.sub(amount, Zold::Id.new, key)
40
41
  assert(
41
- wallet.balance == amount.mul(-1),
42
- "#{wallet.balance} is not equal to #{amount.mul(-1)}"
42
+ wallet.balance == amount.mul(-2),
43
+ "#{wallet.balance} is not equal to #{amount.mul(-2)}"
43
44
  )
44
45
  end
45
46
  end
46
47
 
47
48
  def test_initializes_it
48
49
  Dir.mktmpdir 'test' do |dir|
49
- file = File.join(dir, 'source.xml')
50
- wallet = Zold::Wallet.new(file)
51
- id = Zold::Id.new.to_s
52
- wallet.init(id, Zold::Key.new('fixtures/id_rsa.pub'))
50
+ pkey = Zold::Key.new(file: 'fixtures/id_rsa.pub')
51
+ Dir.chdir(dir) do
52
+ file = File.join(dir, 'source.xml')
53
+ wallet = Zold::Wallet.new(file)
54
+ id = Zold::Id.new.to_s
55
+ wallet.init(id, pkey)
56
+ assert(
57
+ wallet.id == id,
58
+ "#{wallet.id} is not equal to #{id}"
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_iterates_income_transactions
65
+ Dir.mktmpdir 'test' do |dir|
66
+ wallet = wallet(dir)
67
+ wallet.add(
68
+ id: 1,
69
+ date: Time.now, amount: Zold::Amount.new(zld: 39.99),
70
+ beneficiary: Zold::Id.new
71
+ )
72
+ wallet.add(
73
+ id: 2,
74
+ date: Time.now, amount: Zold::Amount.new(zld: 14.95),
75
+ beneficiary: Zold::Id.new
76
+ )
77
+ sum = Zold::Amount::ZERO
78
+ wallet.income do |t|
79
+ sum += t[:amount]
80
+ end
53
81
  assert(
54
- wallet.id == id,
55
- "#{wallet.id} is not equal to #{id}"
82
+ sum == Zold::Amount.new(coins: 921_740_246),
83
+ "#{sum} is not equal to #{Zold::Amount.new(zld: 54.94)}"
56
84
  )
57
85
  end
58
86
  end
87
+
88
+ def test_checks_transaction
89
+ Dir.mktmpdir 'test' do |dir|
90
+ payer = wallet(dir)
91
+ receiver = wallet(dir)
92
+ amount = Zold::Amount.new(zld: 14.95)
93
+ txn = Zold::Send.new(
94
+ payer: payer, receiver: receiver,
95
+ amount: amount,
96
+ pvtkey: Zold::Key.new(file: 'fixtures/id_rsa')
97
+ ).run
98
+ assert payer.check(txn, amount, receiver.id)
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def wallet(dir)
105
+ id = Zold::Id.new
106
+ file = File.join(dir, "#{id}.xml")
107
+ wallet = Zold::Wallet.new(file)
108
+ wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
109
+ wallet
110
+ end
59
111
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -212,13 +212,19 @@ files:
212
212
  - fixtures/id_rsa.pub
213
213
  - lib/zold.rb
214
214
  - lib/zold/amount.rb
215
+ - lib/zold/commands/balance.rb
216
+ - lib/zold/commands/check.rb
215
217
  - lib/zold/commands/create.rb
218
+ - lib/zold/commands/pull.rb
219
+ - lib/zold/commands/push.rb
216
220
  - lib/zold/commands/send.rb
217
221
  - lib/zold/id.rb
218
222
  - lib/zold/key.rb
219
223
  - lib/zold/log.rb
220
224
  - lib/zold/version.rb
221
225
  - lib/zold/wallet.rb
226
+ - lib/zold/wallets.rb
227
+ - test/commands/test_balance.rb
222
228
  - test/commands/test_create.rb
223
229
  - test/commands/test_send.rb
224
230
  - test/test__helper.rb
@@ -258,6 +264,7 @@ test_files:
258
264
  - features/gem_package.feature
259
265
  - features/step_definitions/steps.rb
260
266
  - features/support/env.rb
267
+ - test/commands/test_balance.rb
261
268
  - test/commands/test_create.rb
262
269
  - test/commands/test_send.rb
263
270
  - test/test__helper.rb