## zold 0.6 → 0.6.1

checksums.yaml CHANGED
 @@ -1,7 +1,7 @@ 1 1 --- 2 2 SHA1: 3 - metadata.gz: 01a2323174e903dabb35708bfd1d0f756e930035 4 - data.tar.gz: a4f6582b8e645cd480ab74e374cc5cefe3698034 3 + metadata.gz: 42d45258927844c4cc2c07418af8b5077787731b 4 + data.tar.gz: 508255ebe906bd0c4dd9fcb88ed12dee32a1973a 5 5 SHA512: 6 - metadata.gz: 8e6c51f38a8f4d44c86c6cf1d2c02d3db2a1495504613c29676922a91bc03b325524b87d592dabdeb406fc9ca6906725de8666b805ea98731a7fe4271c950123 7 - data.tar.gz: '0906447919a3f02950152613a65b19d648ca46cf480405659751ab6ab4eee1d08b01f8f1dfef2a60de4c5e55b17433913637daf37c0d7451f13e8320aa33c572' 6 + metadata.gz: 4783b3ade202e80fb6b252ed58cab12176a0bcb70cc4584e67e3e6fe49f1ec9fcd2fdb1df0020f7a2d13b20e865e99c591a14604e402b8725acc9168a6365b2b 7 + data.tar.gz: c8c189ee8908b4479b3b9e71f73776995ddcfc407cb2bdb6e32b15339c63bbdfc86a6f5a49845ad064bbd92cc1f50c2da557be6ae9d3a2673dbe478f4c80c0cc
 @@ -6,7 +6,7 @@ AllCops: 6 6 TargetRubyVersion: 2.2 7 7 8 8 Metrics/CyclomaticComplexity: 9 - Max: 15 9 + Max: 25 10 10 Metrics/MethodLength: 11 11 Enabled: false 12 12 Layout/MultilineMethodCallIndentation: @@ -17,13 +17,13 @@ Metrics/BlockLength: 17 17 Max: 100 18 18 Metrics/ClassLength: 19 19 Max: 200 20 - Style/EndOfLine: 20 + Layout/EndOfLine: 21 21 EnforcedStyle: lf 22 22 Metrics/ParameterLists: 23 23 Max: 10 24 24 Layout/AlignParameters: 25 25 Enabled: false 26 26 Metrics/PerceivedComplexity: 27 - Max: 15 27 + Max: 25 28 28 Metrics/LineLength: 29 29 Max: 120
 @@ -17,21 +17,6 @@ 17 17 **NOTICE**: It's an experiment and a very early draft! Please, feel free to 18 18 submit your ideas or pull requests. 19 19 20 - ZOLD principles include: 21 - 22 - * The entire code base is open source; 23 - * There is no mining, the only way to get ZOLD is to receive it from someone else; 24 - * Only 263 numerals (no fractions) can technically be issued; 25 - * The first wallet belongs to the issuer and may have a negative balance; 26 - * A wallet is a plain text file; 27 - * There is no central ledger, each wallet has its own personal ledger; 28 - * Each transaction in the ledger is confirmed by [RSA](https://simple.wikipedia.org/wiki/RSA_%28algorithm%29) encryption; 29 - * The network of communicating nodes maintains wallets of users; 30 - * Anyone can add a node to the network. 31 - 32 - 1 ZLD by convention equals to 224 (16,777,216) _zents_. 33 - Thus, the technical capacity of the currency is 549,755,813,888 ZLD (half a trillion). 34 - 35 20 ## How to Use 36 21 37 22 Install Ruby 2.2+, [Rubygems](https://rubygems.org/pages/download), and then run: @@ -44,20 +29,9 @@ $gem install zold 44 29 Then, either run it as a node: 45 30 46 31 bash 47 -$ zold start 32 + $zold node 48 33  49 34 50 - Or do one of the following: 51 - 52 - * zold remote manipulates the list off remote nodes; 53 - * zold create creates a new wallet (you have to provide PGP keys); 54 - * zold fetch downloads all copies of the wallet from the network; 55 - * zold merge merges all copies of the wallet into the local one; 56 - * zold pull first fetch, then merge; 57 - * zold show prints out all known details of a wallet (incl. its balance); 58 - * zold pay creates a new transaction; 59 - * zold push pushes a wallet to the network. 60 - 61 35 For more options and commands just run: 62 36 63 37 bash @@ -71,60 +45,6 @@ yet, you can run: 71 45$ ssh-keygen -t rsa -b 4096 72 46  73 47 74 - ## Glossary 75 - 76 - **Node** is an HTTP server with a RESTful API, a maintainer of wallets 77 - and a command line Ruby gem [zold](https://rubygems.org/gems/zold). 78 - 79 - **Network** is a set of all nodes available online. 80 - 81 - **Score** is the amount of "hash sufficies" a node has at any given moment of time. 82 - 83 - **Wallet** is a text file with a ledger of all transactions inside. 84 - 85 - **Transaction** is a money transferring operation between two wallets. 86 - 87 - **MSS** (minimum summary score) is a summary of all scores required to trust a wallet. 88 - 89 - ## Score 90 - 91 - Each node calculates its own score. First, it takes the current timestamp 92 - in UTC [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601), 93 - for example 2017-07-19T21:24:51Z  (with a trailing space). Then, it appends 94 - its own host name or IP address to it, space, TCP port number, and a space. 95 - Then, it attempts to append any 96 - arbitrary text (has to match [a-zA-Z0-9]+) to the end of it and to calculate SHA-256 of the text 97 - in the hexadecimal format, for example: 98 - 99 -  100 - Input: "2017-07-19T21:24:51Z b1.zold.io 4096 the-suffix" 101 - SHA-256: "eba36e52e1ee674d198f486e07c8496853ffc8879e7fe25329523177646a96a0" 102 -  103 - 104 - The node attempts to try different sufficies until one of them produces 105 - SHA-256 hash that ends with 0000000 (seven zeros). For example, this 106 - suffix 11edb424c works (it took 212 minutes to find it on 2.3GHz Intel Core i7): 107 - 108 -  109 - Input: "2017-07-19T21:24:51Z b1.zold.io 4096 11edb424c" 110 - SHA-256: "34f48e0eee1ed12ad74cb39418f2f6e7442a776a7b6182697957650e00000000" 111 -  112 - 113 - When the first suffix is found, the score of the node is 1. Then, to 114 - increase the score by one, the node has to find the next suffix, which 115 - can be added to the first 20 characters of the previous hash 116 - in order to obtain a new hash with trailing zeros, for example: 117 - 118 -  119 - Input: "34f48e0eee1ed12ad74c " 120 - SHA-256: "..." 121 -  122 - 123 - And so on. 124 - 125 - The score is valid only when the starting time point is earlier than 126 - current time, but not earlier than 24 hours ago. 127 - 128 48 ## Operations 129 49 130 50 ### Remote
data/bin/zold CHANGED
 @@ -41,7 +41,7 @@ Encoding.default_internal = Encoding::UTF_8 41 41 log = Zold::Log.new 42 42 43 43 args = [] 44 - unless ENV['RACK_ENV'] == 'test' 44 + unless ENV['RACK_ENV'] == 'test' || ARGV.find { |a| a == '--ignore-global-config' } 45 45 config = File.expand_path('~/.zold') 46 46 if File.exist?(config) 47 47 body = File.read(config) @@ -92,6 +92,7 @@ Available options:" 92 92 default: Dir.pwd 93 93 o.bool '-h', '--help', 'Show these instructions' 94 94 o.bool '--trace', 'Show full stack trace in case of a problem' 95 + o.bool '--ignore-global-config', 'Don\'t read options from the ~/.zold file' 95 96 o.on '--no-colors', 'Disable colors in the ouput' do 96 97 Rainbow.enabled = false 97 98 end @@ -104,16 +105,17 @@ Available options:" 104 105 end 105 106 end 106 107 107 - raise 'Try --help' if opts.arguments.empty? && !opts.help? 108 + commands = opts.arguments.reject { |a| a.start_with?('-') } 109 + command = commands[0] 108 110 109 - if opts.help? && opts.arguments.empty? 111 + if command.nil? 112 + raise 'A command required, try --help' unless opts.help? 110 113 log.info(opts.to_s) 111 114 exit 112 115 end 113 116 114 - command = opts.arguments[0] 115 - 116 - args = args[(args.index(command) + 1)..-1] 117 + args = opts.arguments 118 + args << '--help' if opts.help? 117 119 118 120 wallets = Zold::Wallets.new(opts['dir']) 119 121 remotes = Zold::Remotes.new(File.join(opts['dir'], '.zoldata/remotes'))
 @@ -12,6 +12,6 @@ Feature: Command Line Processing 12 12 Then Exit code is zero 13 13 14 14 Scenario: Wallet can be created 15 - When I run bin/zold with "--trace create --public-key=id_rsa.pub" 15 + When I run bin/zold with "--trace --public-key=id_rsa.pub create" 16 16 Then Exit code is zero 17 17
 @@ -9,5 +9,5 @@ zold --help 9 9 declare -a commands=(node create invoice remote pay show fetch clean diff merge propagate pull push) 10 10 for c in "${commands[@]}" 11 11 do 12 - zold --trace$c --help 12 + zold --ignore-global-config --trace $c --help 13 13 done  @@ -3,7 +3,7 @@ set -x 3 3 set -e 4 4 shopt -s expand_aliases 5 5 6 - alias zold="$1" 6 + alias zold="$1 --ignore-global-config --trace" 7 7 8 8 port=python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()' 9 9 @@ -21,21 +21,21 @@ while ! nc -z localhost${port}; do 21 21 ((c++)) && ((c==20)) && break 22 22 done 23 23 24 - zold --trace remote clean 25 - zold --trace remote add localhost ${port} 26 - zold --trace remote show 24 + zold remote clean 25 + zold remote add localhost${port} 26 + zold remote show 27 27 28 - zold --trace create --public-key=id_rsa.pub 0000000000000000 28 + zold create --public-key=id_rsa.pub 0000000000000000 29 29 target=zold create --public-key=id_rsa.pub 30 30 invoice=zold invoice ${target} 31 - zold --trace pay --private-key=id_rsa 0000000000000000${invoice} 14.99 'To save the world!' 32 - zold --trace propagate 0000000000000000 33 - zold --trace show 34 - zold --trace show 0000000000000000 31 + zold pay --private-key=id_rsa 0000000000000000 ${invoice} 14.99 'To save the world!' 32 + zold propagate 0000000000000000 33 + zold show 34 + zold show 0000000000000000 35 35 36 - zold --trace remote show 37 - zold --trace push 0000000000000000 38 - zold --trace fetch 0000000000000000 --ignore-score-weakness 39 - zold --trace diff 0000000000000000 40 - zold --trace merge 0000000000000000 41 - zold --trace clean 0000000000000000 36 + zold remote show 37 + zold push 0000000000000000 38 + zold fetch 0000000000000000 --ignore-score-weakness 39 + zold diff 0000000000000000 40 + zold merge 0000000000000000 41 + zold clean 0000000000000000  @@ -39,7 +39,7 @@ module Zold 39 39 end 40 40 41 41 def run(args = []) 42 - opts = Slop.parse(args, help: true) do |o| 42 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 43 43 o.banner = "Usage: zold clean [ID...] [options] 44 44 Available options:" 45 45 o.bool '--help', 'Print instructions' @@ -48,8 +48,9 @@ Available options:" 48 48 @log.info(opts.to_s) 49 49 return 50 50 end 51 - raise 'At least one wallet ID is required' if opts.arguments.empty? 52 - opts.arguments.each do |id| 51 + mine = opts.arguments[1..-1] 52 + raise 'At least one wallet ID is required' if mine.empty? 53 + mine.each do |id| 53 54 clean(Copies.new(File.join(@copies, id)), opts) 54 55 end 55 56 end  @@ -35,7 +35,7 @@ module Zold 35 35 end 36 36 37 37 def run(args = []) 38 - opts = Slop.parse(args, help: true) do |o| 38 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 39 39 o.banner = "Usage: zold create [options] 40 40 Available options:" 41 41 o.string '--public-key', @@ -48,7 +48,8 @@ Available options:" 48 48 @log.info(opts.to_s) 49 49 return 50 50 end 51 - create(opts.arguments.empty? ? Id.new : Id.new(opts.arguments[0]), opts) 51 + mine = opts.arguments[1..-1] 52 + create(mine.empty? ? Id.new : Id.new(mine[0]), opts) 52 53 end 53 54 54 55 def create(id, opts)  @@ -39,7 +39,7 @@ module Zold 39 39 end 40 40 41 41 def run(args = []) 42 - opts = Slop.parse(args, help: true) do |o| 42 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 43 43 o.banner = "Usage: zold diff [ID...] [options] 44 44 Available options:" 45 45 o.bool '--help', 'Print instructions' @@ -48,9 +48,10 @@ Available options:" 48 48 @log.info(opts.to_s) 49 49 return 50 50 end 51 - raise 'At least one wallet ID is required' if opts.arguments.empty? 51 + mine = opts.arguments[1..-1] 52 + raise 'At least one wallet ID is required' if mine.empty? 52 53 stdout = '' 53 - opts.arguments.each do |id| 54 + mine.each do |id| 54 55 stdout += diff( 55 56 @wallets.find(Id.new(id)), 56 57 Copies.new(File.join(@copies, id)),  @@ -41,7 +41,7 @@ module Zold 41 41 end 42 42 43 43 def run(args = []) 44 - opts = Slop.parse(args, help: true) do |o| 44 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 45 45 o.banner = "Usage: zold fetch [ID...] [options] 46 46 Available options:" 47 47 o.bool '--ignore-score-weakness', @@ -56,8 +56,9 @@ Available options:" 56 56 @log.info(opts.to_s) 57 57 return 58 58 end 59 - raise 'At least one wallet ID is required' if opts.arguments.empty? 60 - opts.arguments.each do |id| 59 + mine = opts.arguments[1..-1] 60 + raise 'At least one wallet ID is required' if mine.empty? 61 + mine.each do |id| 61 62 fetch(id, Copies.new(File.join(@copies, id)), opts) 62 63 end 63 64 end  @@ -35,7 +35,7 @@ module Zold 35 35 end 36 36 37 37 def run(args = []) 38 - opts = Slop.parse(args, help: true) do |o| 38 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 39 39 o.banner = "Usage: zold invoice ID [options] 40 40 Where: 41 41 'ID' is the wallet ID of the money receiver @@ -49,8 +49,9 @@ Available options:" 49 49 @log.info(opts.to_s) 50 50 return 51 51 end 52 - raise 'Receiver wallet ID is required' if opts.arguments[0].nil? 53 - wallet = @wallets.find(Zold::Id.new(opts.arguments[0])) 52 + mine = opts.arguments[1..-1] 53 + raise 'Receiver wallet ID is required' if mine[0].nil? 54 + wallet = @wallets.find(Zold::Id.new(mine[0])) 54 55 raise 'Wallet doesn\'t exist, do \'fetch\' first' unless wallet.exists? 55 56 invoice(wallet, opts) 56 57 end  @@ -39,7 +39,7 @@ module Zold 39 39 40 40 # Returns the array of modified wallets (IDs) 41 41 def run(args = []) 42 - opts = Slop.parse(args, help: true) do |o| 42 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 43 43 o.banner = "Usage: zold merge [ID...] [options] 44 44 Available options:" 45 45 o.bool '--help', 'Print instructions' @@ -48,9 +48,10 @@ Available options:" 48 48 @log.info(opts.to_s) 49 49 return 50 50 end 51 - raise 'At least one wallet ID is required' if opts.arguments.empty? 51 + mine = opts.arguments[1..-1] 52 + raise 'At least one wallet ID is required' if mine.empty? 52 53 modified = [] 53 - opts.arguments.each do |id| 54 + mine.each do |id| 54 55 wallet = @wallets.find(Id.new(id)) 55 56 next unless merge(wallet, Copies.new(File.join(@copies, id)), opts) 56 57 modified << Id.new(id)  @@ -38,7 +38,7 @@ module Zold 38 38 end 39 39 40 40 def run(args = []) 41 - opts = Slop.parse(args, help: true) do |o| 41 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 42 42 o.banner = 'Usage: zold node [options]' 43 43 o.string '--invoice', 44 44 'The invoice you want to collect money to'  @@ -34,7 +34,7 @@ module Zold 34 34 end 35 35 36 36 def run(args = []) 37 - opts = Slop.parse(args, help: true) do |o| 37 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 38 38 o.banner = "Usage: zold pay wallet invoice amount [details] [options] 39 39 Where: 40 40 'wallet' is the sender's wallet ID @@ -55,14 +55,15 @@ Available options:" 55 55 @log.info(opts.to_s) 56 56 return 57 57 end 58 - raise 'Payer wallet ID is required' if opts.arguments[0].nil? 59 - from = @wallets.find(Zold::Id.new(opts.arguments[0])) 58 + mine = opts.arguments[1..-1] 59 + raise 'Payer wallet ID is required as the first argument' if mine[0].nil? 60 + from = @wallets.find(Zold::Id.new(mine[0])) 60 61 raise 'Wallet doesn\'t exist, do \'fetch\' first' unless from.exists? 61 - raise 'Recepient\'s invoice is required' if opts.arguments[1].nil? 62 - invoice = opts.arguments[1] 63 - raise 'Amount is required (in ZLD)' if opts.arguments[2].nil? 64 - amount = Zold::Amount.new(zld: opts.arguments[2].to_f) 65 - details = opts.arguments[3] ? opts.arguments[3] : '-' 62 + raise 'Recepient\'s invoice is required as the second argument' if mine[1].nil? 63 + invoice = mine[1] 64 + raise 'Amount is required (in ZLD) as the third argument' if mine[2].nil? 65 + amount = Zold::Amount.new(zld: mine[2].to_f) 66 + details = mine[3] ? mine[3] : '-' 66 67 pay(from, invoice, amount, details, opts) 67 68 end 68 69  @@ -37,7 +37,7 @@ module Zold 37 37 38 38 # Returns list of Wallet IDs which were affected 39 39 def run(args = []) 40 - opts = Slop.parse(args, help: true) do |o| 40 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 41 41 o.banner = "Usage: zold propagate [ID...] [options] 42 42 Available options:" 43 43 o.bool '--help', 'Print instructions' @@ -46,9 +46,10 @@ Available options:" 46 46 @log.info(opts.to_s) 47 47 return 48 48 end 49 - raise 'At least one wallet ID is required' if opts.arguments.empty? 49 + mine = opts.arguments[1..-1] 50 + raise 'At least one wallet ID is required' if mine.empty? 50 51 modified = [] 51 - opts.arguments.each do |id| 52 + mine.each do |id| 52 53 modified += propagate(@wallets.find(id), opts) 53 54 end 54 55 modified  @@ -39,7 +39,7 @@ module Zold 39 39 end 40 40 41 41 def run(args = []) 42 - opts = Slop.parse(args, help: true) do |o| 42 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 43 43 o.banner = "Usage: zold push [ID...] [options] 44 44 Available options:" 45 45 o.bool '--help', 'Print instructions' @@ -48,8 +48,9 @@ Available options:" 48 48 @log.info(opts.to_s) 49 49 return 50 50 end 51 - raise 'At least one wallet ID is required' if opts.arguments.empty? 52 - opts.arguments.each do |id| 51 + mine = opts.arguments[1..-1] 52 + raise 'At least one wallet ID is required' if mine.empty? 53 + mine.each do |id| 53 54 push(@wallets.find(Id.new(id)), opts) 54 55 end 55 56 end  @@ -41,7 +41,7 @@ module Zold 41 41 end 42 42 43 43 def run(args = []) 44 - opts = Slop.parse(args, help: true) do |o| 44 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 45 45 o.banner = "Usage: zold remote [options] 46 46 Available commands: 47 47 #{Rainbow('remote show').green} @@ -62,7 +62,12 @@ Available options:" 62 62 default: false 63 63 o.bool '--help', 'Print instructions' 64 64 end 65 - command = opts.arguments[0] 65 + if opts.help? 66 + @log.info(opts.to_s) 67 + return 68 + end 69 + mine = opts.arguments[1..-1] 70 + command = mine[0] 66 71 case command 67 72 when 'show' 68 73 show @@ -71,14 +76,14 @@ Available options:" 71 76 when 'reset' 72 77 reset 73 78 when 'add' 74 - add(opts.arguments[1], opts.arguments[2] ? opts.arguments[2].to_i : Remotes::PORT) 79 + add(mine[1], mine[2] ? mine[2].to_i : Remotes::PORT) 75 80 when 'remove' 76 - remove(opts.arguments[1], opts.arguments[2] ? opts.arguments[2].to_i : Remotes::PORT) 81 + remove(mine[1], mine[2] ? mine[2].to_i : Remotes::PORT) 77 82 when 'update' 78 83 update(opts) 79 84 update(opts, false) 80 85 else 81 - @log.info(opts.to_s) 86 + raise "Unknown command '#{command}'" 82 87 end 83 88 end 84 89  @@ -36,7 +36,7 @@ module Zold 36 36 end 37 37 38 38 def run(args = []) 39 - opts = Slop.parse(args, help: true) do |o| 39 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 40 40 o.banner = "Usage: zold show [ID...] [options] 41 41 Available options:" 42 42 o.bool '--help', 'Print instructions' @@ -45,12 +45,13 @@ Available options:" 45 45 @log.info(opts.to_s) 46 46 return 47 47 end 48 - if opts.arguments.empty? 48 + mine = opts.arguments[1..-1] 49 + if mine.empty? 49 50 require_relative 'list' 50 51 List.new(wallets: @wallets, log: @log).run(args) 51 52 else 52 53 total = Amount::ZERO 53 - opts.arguments.each do |id| 54 + mine.each do |id| 54 55 total += show(@wallets.find(Id.new(id)), opts) 55 56 end 56 57 total  @@ -41,7 +41,7 @@ module Zold 41 41 end 42 42 43 43 def run(args = []) 44 - opts = Slop.parse(args, help: true) do |o| 44 + opts = Slop.parse(args, help: true, suppress_errors: true) do |o| 45 45 o.banner = "Usage: zold taxes command [options] 46 46 Available commands: 47 47 #{Rainbow('taxes pay').green} wallet @@ -57,21 +57,22 @@ Available options:" 57 57 default: '~/.ssh/id_rsa' 58 58 o.bool '--help', 'Print instructions' 59 59 end 60 - command = opts.arguments[0] 60 + mine = opts.arguments[1..-1] 61 + command = mine[0] 61 62 case command 62 63 when 'show' 63 - raise 'At least one wallet ID is required' unless opts.arguments[1] 64 - opts.arguments[1..-1].each do |id| 64 + raise 'At least one wallet ID is required' unless mine[1] 65 + mine[1..-1].each do |id| 65 66 show(@wallets.find(Id.new(id), opts)) 66 67 end 67 68 when 'debt' 68 - raise 'At least one wallet ID is required' unless opts.arguments[1] 69 - opts.arguments[1..-1].each do |id| 69 + raise 'At least one wallet ID is required' unless mine[1] 70 + mine[1..-1].each do |id| 70 71 debt(@wallets.find(Id.new(id), opts)) 71 72 end 72 73 when 'pay' 73 - raise 'At least one wallet ID is required' unless opts.arguments[1] 74 - opts.arguments[1..-1].each do |id| 74 + raise 'At least one wallet ID is required' unless mine[1] 75 + mine[1..-1].each do |id| 75 76 pay(@wallets.find(Id.new(id)), opts) 76 77 end 77 78 else  @@ -55,10 +55,10 @@ module Zold 55 55 copies.add(body, 'remote', Remotes::PORT, 0) 56 56 Fetch.new( 57 57 remotes: @remotes, copies: copies.root, log: @log 58 - ).run([id.to_s, "--ignore-node=#{@address}"]) 58 + ).run(['fetch', id.to_s, "--ignore-node=#{@address}"]) 59 59 modified = Merge.new( 60 60 wallets: @wallets, copies: copies.root, log: @log 61 - ).run([id.to_s]) 61 + ).run(['merge', id.to_s]) 62 62 debt = Tax.new(@wallets.find(id)).debt 63 63 if debt > Tax::TRIAL 64 64 raise "Taxes are not paid, the debt is #{debt} (#{debt.to_i} zents), won't promote the wallet" @@ -67,7 +67,7 @@ module Zold 67 67 modified.each do |m| 68 68 Push.new( 69 69 wallets: @wallets, remotes: @remotes, log: @log 70 - ).run([m.to_s]) 70 + ).run(['push', m.to_s]) 71 71 end 72 72 modified 73 73 end  @@ -165,7 +165,7 @@ module Zold 165 165 @suffixes.reduce(start) do |prefix, suffix| 166 166 hex = Digest::SHA256.hexdigest(prefix + ' ' + suffix) 167 167 return false unless hex.end_with?('0' * @strength) 168 - hex[0, 19] 168 + hex[0, 63] 169 169 end 170 170 true 171 171 end  @@ -34,19 +34,25 @@ module Zold 34 34 attr_reader :id, :date, :amount, :prefix, :bnf, :details, :sign 35 35 attr_writer :sign, :amount, :bnf 36 36 def initialize(id, date, amount, prefix, bnf, details) 37 + raise 'The ID can\'t be NIL' if id.nil? 37 38 raise "ID of transaction can't be negative: #{id}" if id < 1 38 39 @id = id 40 + raise 'The time can\'t be NIL' if date.nil? 39 41 raise 'Time have to be of type Time' unless date.is_a?(Time) 40 42 raise "Time can't be in the future: #{date}" if date > Time.now 41 43 @date = date 44 + raise 'The amount can\'t be NIL' if amount.nil? 42 45 raise 'The amount has to be of type Amount' unless amount.is_a?(Amount) 43 46 raise 'The amount can\'t be zero' if amount.zero? 44 47 @amount = amount 48 + raise 'The bnf can\'t be NIL' if bnf.nil? 45 49 raise 'The bnf has to be of type Id' unless bnf.is_a?(Id) 46 50 @bnf = bnf 51 + raise 'Prefix can\'t be NIL' if prefix.nil? 47 52 raise "Prefix is too short: \"#{prefix}\"" if prefix.length < 8 48 53 raise "Prefix is too long: \"#{prefix}\"" if prefix.length > 32 49 54 @prefix = prefix 55 + raise 'Details can\'t be NIL' if details.nil? 50 56 raise 'Details can\'t be empty' if details.empty? 51 57 raise "Details are too long: \"#{details}\"" if details.length > 128 52 58 raise "Details are wrong: \"#{details}\"" unless details =~ /^[a-zA-Z0-9 -\.,]{1,128}$/
 @@ -23,5 +23,5 @@ 23 23 # Copyright:: Copyright (c) 2018 Yegor Bugayenko 24 24 # License:: MIT 25 25 module Zold 26 - VERSION = '0.6'.freeze 26 + VERSION = '0.6.1'.freeze 27 27 end
 @@ -35,7 +35,7 @@ class TestClean < Minitest::Test 35 35 copies = Zold::Copies.new(File.join(dir, "copies/#{id}")) 36 36 copies.add('a1', 'host-1', 80, 1, Time.now - 26 * 60) 37 37 copies.add('a2', 'host-2', 80, 2, Time.now - 26 * 60) 38 - Zold::Clean.new(copies: copies.root).run([id.to_s]) 38 + Zold::Clean.new(copies: copies.root).run(['clean', id.to_s]) 39 39 assert(copies.all.empty?) 40 40 end 41 41 end
 @@ -32,7 +32,7 @@ class TestCreate < Minitest::Test 32 32 def test_creates_wallet 33 33 Dir.mktmpdir 'test' do |dir| 34 34 wallet = Zold::Create.new(wallets: Zold::Wallets.new(dir)).run( 35 - ['--public-key=fixtures/id_rsa.pub'] 35 + ['create', '--public-key=fixtures/id_rsa.pub'] 36 36 ) 37 37 assert wallet.balance.zero? 38 38 assert(
 @@ -47,14 +47,14 @@ class TestDiff < Minitest::Test 47 47 File.write(second.path, File.read(wallet.path)) 48 48 Zold::Pay.new( 49 49 wallets: Zold::Wallets.new(dir) 50 - ).run([id.to_s, second.id.to_s, '14.95', '--force', '--private-key=fixtures/id_rsa']) 50 + ).run(['pay', id.to_s, second.id.to_s, '14.95', '--force', '--private-key=fixtures/id_rsa']) 51 51 copies = Zold::Copies.new(File.join(dir, "copies/#{id}")) 52 52 copies.add(File.read(first.path), 'host-1', 80, 5) 53 53 copies.add(File.read(second.path), 'host-2', 80, 5) 54 54 diff = Zold::Diff.new( 55 55 wallets: Zold::Wallets.new(dir), 56 56 copies: copies.root 57 - ).run([id.to_s]) 57 + ).run(['diff', id.to_s]) 58 58 assert(diff.include?('-1;')) 59 59 end 60 60 end
 @@ -58,7 +58,7 @@ class TestFetch < Minitest::Test 58 58 remotes.add('fake-2', 80) 59 59 copies = Zold::Copies.new(File.join(dir, "copies/#{id}")) 60 60 Zold::Fetch.new(copies: copies.root, remotes: remotes).run( 61 - ['--ignore-score-weakness', id.to_s] 61 + ['fetch', '--ignore-score-weakness', id.to_s] 62 62 ) 63 63 assert_equal(copies.all[0][:name], '1') 64 64 assert_equal(copies.all[0][:score], 0) @@ -79,7 +79,7 @@ class TestFetch < Minitest::Test 79 79 ) 80 80 remotes.add('fake-1', 80) 81 81 copies = Zold::Copies.new(File.join(dir, "copies/#{id}")) 82 - Zold::Fetch.new(copies: copies.root, remotes: remotes).run([id.to_s]) 82 + Zold::Fetch.new(copies: copies.root, remotes: remotes).run(['fetch', id.to_s]) 83 83 assert_equal(copies.all[0][:name], '1') 84 84 assert_equal(copies.all[0][:score], 0) 85 85 end
 @@ -38,7 +38,7 @@ class TestInvoice < Minitest::Test 38 38 source = wallets.find(id) 39 39 source.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub')) 40 40 invoice = Zold::Invoice.new(wallets: wallets).run( 41 - [id.to_s, '--length=16'] 41 + ['invoice', id.to_s, '--length=16'] 42 42 ) 43 43 assert_equal(33, invoice.length) 44 44 end
 @@ -24,6 +24,7 @@ require 'json' 24 24 require 'time' 25 25 require 'webmock/minitest' 26 26 require_relative '../../lib/zold/wallet' 27 + require_relative '../../lib/zold/wallets' 27 28 require_relative '../../lib/zold/id' 28 29 require_relative '../../lib/zold/copies' 29 30 require_relative '../../lib/zold/key' @@ -49,14 +50,14 @@ class TestMerge < Minitest::Test 49 50 File.write(second.path, File.read(wallet.path)) 50 51 Zold::Pay.new( 51 52 wallets: Zold::Wallets.new(dir) 52 - ).run([id.to_s, second.id.to_s, '14.95', '--force', '--private-key=fixtures/id_rsa']) 53 + ).run(['pay', id.to_s, second.id.to_s, '14.95', '--force', '--private-key=fixtures/id_rsa']) 53 54 copies = Zold::Copies.new(File.join(dir, "copies/#{id}")) 54 55 copies.add(File.read(first.path), 'host-1', 80, 5) 55 56 copies.add(File.read(second.path), 'host-2', 80, 5) 56 57 modified = Zold::Merge.new( 57 58 wallets: Zold::Wallets.new(dir), 58 59 copies: copies.root 59 - ).run([id.to_s]) 60 + ).run(['merge', id.to_s]) 60 61 assert(1, modified.count) 61 62 assert(id, modified[0]) 62 63 end @@ -74,14 +75,14 @@ class TestMerge < Minitest::Test 74 75 File.write(second.path, File.read(wallet.path)) 75 76 Zold::Pay.new( 76 77 wallets: Zold::Wallets.new(dir) 77 - ).run([id.to_s, second.id.to_s, '14.95', '--force', '--private-key=fixtures/id_rsa']) 78 + ).run(['pay', id.to_s, second.id.to_s, '14.95', '--force', '--private-key=fixtures/id_rsa']) 78 79 copies = Zold::Copies.new(File.join(dir, "copies/#{id}")) 79 80 copies.add(File.read(first.path), 'host-1', 80, 5) 80 81 copies.add(File.read(second.path), 'host-2', 80, 5) 81 82 modified = Zold::Merge.new( 82 83 wallets: Zold::Wallets.new(dir), 83 84 copies: copies.root 84 - ).run([id.to_s]) 85 + ).run(['merge', id.to_s]) 85 86 assert(1, modified.count) 86 87 assert(id, modified[0]) 87 88 end
 @@ -46,8 +46,8 @@ class TestNode < Minitest::Test 46 46 remotes = Zold::Remotes.new(File.join(dir, 'remotes.csv')) 47 47 remotes.clean 48 48 remotes.add('localhost', port) 49 - Zold::Push.new(wallet: wallet, remotes: remotes).run 50 - Zold::Fetch.new(wallet: wallet, copies: copies, remotes: remotes).run 49 + Zold::Push.new(wallet: wallet, remotes: remotes).run(['push']) 50 + Zold::Fetch.new(wallet: wallet, copies: copies, remotes: remotes).run(['fetch']) 51 51 assert_equal(copies.all[0][:name], '1') 52 52 assert_equal(copies.all[0][:score], 0) 53 53 end
 @@ -41,7 +41,7 @@ class TestPay < Minitest::Test 41 41 amount = Zold::Amount.new(zld: 14.95) 42 42 Zold::Pay.new(wallets: wallets).run( 43 43 [ 44 - '--force', '--private-key=fixtures/id_rsa', 44 + 'pay', '--force', '--private-key=fixtures/id_rsa', 45 45 id.to_s, target.to_s, amount.to_zld, 'For the car' 46 46 ] 47 47 )
 @@ -57,11 +57,11 @@ class TestRemote < Minitest::Test 57 57 status: 404 58 58 ) 59 59 cmd = Zold::Remote.new(remotes: remotes) 60 - cmd.run(['clean']) 61 - cmd.run(['add', zero.host, zero.port.to_s]) 62 - cmd.run(%w[add localhost 2]) 60 + cmd.run(%w[remote clean]) 61 + cmd.run(['remote', 'add', zero.host, zero.port.to_s]) 62 + cmd.run(%w[remote add localhost 2]) 63 63 assert_equal(2, remotes.all.count) 64 - cmd.run(['update', '--ignore-score-weakness']) 64 + cmd.run(['remote', 'update', '--ignore-score-weakness']) 65 65 assert_equal(1, remotes.all.count) 66 66 end 67 67 end
 @@ -37,7 +37,7 @@ class TestShow < Minitest::Test 37 37 wallets = Zold::Wallets.new(dir) 38 38 wallet = wallets.find(id) 39 39 wallet.init(Zold::Id.new, Zold::Key.new(file: 'fixtures/id_rsa.pub')) 40 - balance = Zold::Show.new(wallets: wallets).run([id.to_s]) 40 + balance = Zold::Show.new(wallets: wallets).run(['show', id.to_s]) 41 41 assert_equal(Zold::Amount::ZERO, balance) 42 42 end 43 43 end
 @@ -59,7 +59,7 @@ class TestTaxes < Minitest::Test 59 59 ) 60 60 Zold::Taxes.new( 61 61 wallets: wallets, remotes: remotes 62 - ).run(['pay', '--private-key=fixtures/id_rsa', id.to_s]) 62 + ).run(['taxes', '--private-key=fixtures/id_rsa', id.to_s]) 63 63 assert_equal(Zold::Amount.new(coins: 335_376_547), wallet.balance) 64 64 end 65 65 end
 @@ -0,0 +1,8 @@ 1 + OPTS=-shell-escape -halt-on-error -interaction=errorstopmode -output-directory=. 2 + 3 + wp.pdf: wp.tex 4 + pdflatex ${OPTS} wp.tex 5 + 6 + clean: 7 + rm -rf wp.log wp.pdf wp.out wp.aux 8 + data/wp/wp.tex CHANGED  @@ -1,37 +1,141 @@ 1 1 \documentclass[11pt,oneside]{article} 2 2 \usepackage[utf8]{inputenc} 3 3 \usepackage[american]{babel} 4 - \usepackage[ 5 - paperwidth=6in, paperheight=9in, 6 - bindingoffset=0.25in, left=0.75in, right=0.75in, top=0.75in, bottom=1.25in 7 - ]{geometry} 4 + % \usepackage[ 5 + % paperwidth=6in, paperheight=9in, 6 + % bindingoffset=0.25in, left=0.75in, right=0.75in, top=0.75in, bottom=1.25in 7 + % ]{geometry} 8 8 \usepackage{setspace} 9 - \usepackage{indentfirst} 10 - 11 - \pagestyle{empty} 12 - \setlength{\topskip}{6pt} 13 - \setlength{\parindent}{0pt} % indent first line 14 - \setlength{\parskip}{0pt} % before par 15 - \interfootnotelinepenalty=10000 16 - \setstretch{1.1} 17 - 9 + \usepackage{microtype} 18 10 \usepackage{mathpazo} % Palantino font 19 - 11 + \usepackage{minted} 12 + \setminted{fontsize=\footnotesize} 13 + \setminted{breaklines} 14 + \usemintedstyle{bw} 20 15 \usepackage{hyperref} 21 - \usepackage[style=authoryear,sorting=nyt,backend=biber, 22 - hyperref=true,abbreviate=true, 23 - maxcitenames=1,maxbibnames=1]{biblatex} 24 - \addbibresource{main.bib} 16 + \pagestyle{empty} 17 + \setstretch{1.2} 25 18 26 19 \title{Zold, Lightweight Crypto Currency} 27 - \author{Yegor Bugayenko\\ 28 - CEO of Zerocracy, Inc.\\ 29 - 555 Bryant, Ste 470, Palo Alto, CA 94301\\ 30 - \texttt{yegor@zerocracy.com}\\ 31 - 408.692.4742} 32 - \begin{document} 20 + \author{Yegor Bugayenko\\\texttt{yegor@zold.io}} 33 21 22 + \begin{document} 34 23 \raggedbottom 24 + \maketitle 25 + \begin{abstract} 26 + Works for you? 27 + \end{abstract} 28 + 29 + \section{Motivation} 30 + 31 + Bitcoin, the first decentralized digital currency, was released in 32 + January 2009. Since then a number of similar Blockchain-based products have been 33 + created, including Etherium, Litecoin, and others. 34 + 35 + Zold is also a decentralized digital currency that maintains its transactions 36 + in an unpredicable amount of zero-trust server nodes, trying to guarantee 37 + data consistency. However, the architecture of Zold is not based on Blockchain 38 + principles. The development of Zold was motivated by the desire to overcome 39 + a few obvious disadvantages of existing solutions. 40 + 41 + First, the speed of transaction processing is rather low. 42 + 43 + Second, mining commissions are high. 44 + 45 + Third, the technology is too complex. 46 + 47 + Zold was created as an attempt to resolve these mentioned problems 48 + of existing digital currencies. 49 + 50 + \section{Principles} 51 + 52 + \textbf{Open source}. 53 + Zold is a command line tool. Its entire code base is open source. 54 + 55 + \textbf{Capacity}. 56 + One currency unit is called ZLD. 57 + One ZLD by convention equals to$2^24\$ (16,777,216) \emph{zents}. 58 + All amounts are stored as signed 64-bit integers. 59 + Thus, the technical capacity of the currency is 549,755,813,888 ZLD (half a trillion). 60 + 61 + \textbf{Zero wallet}. 62 + There is no mining, the only way to get ZLD is to receive it from someone else. 63 + The wallet with the \texttt{0x00} ID belongs to the 64 + issuer and may have a negative balance. All other wallets 65 + may have only positive balances. 66 + 67 + \textbf{No general ledger}. 68 + There is no central ledger, each wallet has its own personal ledger. 69 + Each transaction in the ledger is confirmed by RSA signature; 70 + 71 + \textbf{No trust}. 72 + The network of communicating nodes maintains wallets of users. 73 + Anyone can add a node to the network. 74 + It is assumed that any node may contain corrupted data, either by mistake or intentionally. 75 + 76 + \section{Proof of Work} 77 + 78 + Each node calculates its own score. 79 + First, it builds the initial text body, which consists of four parts, 80 + separated by spaces: 81 + 82 + \begin{itemize} 83 + \item The current timestamp in UTC ISO 8601, 84 + \item The host name or IP address, e.g. \texttf{b2.zold.io}, 85 + \item The TCP port number, 86 + \item The invoice. 87 + \end{itemize} 88 + 89 + For example, the body may look like this: 90 + 91 + \begin{minted}{text} 92 + 2017-07-19T21:24:51Z b2.zold.io 4096 Yt0lOy6Rgf@0000000000000000 93 + \end{minted} 94 + 95 + Then, it attempts to append any 96 + arbitrary text (has to match \texttt{[a-zA-Z0-9]+} regular expression) 97 + to the end of it and to calculate SHA-256 of the text 98 + in the hexadecimal format. For example, this would be the body 99 + with \texttt{abcdef} suffix: 100 + 101 + \begin{minted}{text} 102 + 2017-07-19T21:24:51Z b2.zold.io 4096 Yt0lOy6Rgf@0000000000000000 abcdef 103 + \end{minted} 104 + 105 + The SHA-256 of this body will be: 106 + 107 + \begin{minted}{text} 108 + 2017-07-19T21:24:51Z b2.zold.io 4096 Yt0lOy6Rgf@0000000000000000 abcdef 109 + \end{minted} 110 + 111 + The node attempts to try different sufficies until one of them produces 112 + SHA-256 hash that ends with \texttt{000000} (six zeros). For example, this 113 + suffix \texttt{...} works 114 + (it took about an hour to find it on 2.3GHz Intel Core i7): 115 + 116 + When the first suffix is found, the score is 1. Then, to 117 + increase the score by one, the next suffix has to be found, which 118 + can be added to the first 64 characters of the previous hash 119 + in order to obtain a new hash with trailing zeros, for example: 120 + 121 + \begin{minted}{text} 122 + 2017-07-19T21:24:51Z b2.zold.io 4096 Yt0lOy6Rgf@0000000000000000 abcdef abcdef 123 + \end{minted} 124 + 125 + And so on. 126 + 127 + The score is valid only when the starting time point is earlier than 128 + current time, but not earlier than 24 hours ago. The strength of the score 129 + is the amount of the trailing zeros in the hash. In the example above the 130 + strength was equal to six. 131 + 132 + \section{Wallets} 133 + 134 + \section{Push} 135 + 136 + \section{Fetch and Merge} 137 + 138 + \section{Threats Analysis} 35 139 36 140 how are you? 37 141