zold 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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