liability-proof 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.markdown +44 -0
- data/bin/lproof +40 -0
- data/lib/liability-proof.rb +13 -0
- data/lib/liability-proof/generator.rb +48 -0
- data/lib/liability-proof/tree.rb +82 -0
- data/lib/liability-proof/tree/interior_node.rb +23 -0
- data/lib/liability-proof/tree/leaf_node.rb +34 -0
- data/lib/liability-proof/tree/node.rb +17 -0
- data/lib/liability-proof/verifier.rb +56 -0
- data/lib/liability-proof/version.rb +3 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 977147c8250bb363215bad2fd155bc13aec6269f
|
4
|
+
data.tar.gz: c84794a53390071cb9800bf692eff4c0e2bf2b0d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 662738943559c4e770f91d11de77a39af291418c775b8a0b7a774aa710bf5d51e3f5a60e26a887c4486676c815df9eb1615314391ce65e2d82861eac84dbae89
|
7
|
+
data.tar.gz: e5e98a75e127860b41ed67cc102a8bc370c0ffdea3b6960daf59b9599ea346e7e5fec3311307d8ec2a7d6a9683949f6ea64f9d0a3104bb75f1cfd19035e2f395
|
data/README.markdown
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Liability Proof
|
2
|
+
===============
|
3
|
+
|
4
|
+
If you're not familiar with *liability proof* or *the Merkle approach*, check this page: [Proving Your Bitcoin Reserves](https://iwilcox.me.uk/2014/proving-bitcoin-reserves). Basically, every mordern exchanges should prove they really hold the bitcoins/money they claim to.
|
5
|
+
|
6
|
+
### Requirements ###
|
7
|
+
|
8
|
+
* ruby 2.0.0 or higher (if you want to run 'rake test' in this gem you'll need ruby 2.1.0 or higher)
|
9
|
+
* openssl
|
10
|
+
|
11
|
+
### Install ###
|
12
|
+
|
13
|
+
gem install liability-proof
|
14
|
+
|
15
|
+
### Usage ###
|
16
|
+
|
17
|
+
As command line tool:
|
18
|
+
|
19
|
+
# Generate root.json and partial tree json for each account in accounts.json.
|
20
|
+
# The generated file format conforms with the standard in progress:
|
21
|
+
# https://github.com/olalonde/blind-liability-proof#serialized-data-formats-work-in-progress--draft
|
22
|
+
lproof generate -f accounts.json
|
23
|
+
|
24
|
+
# verify specified partial tree is valid, i.e. the root node calculated from
|
25
|
+
# from the partial tree matches the root node in root.json
|
26
|
+
lproof verify -r root.json -f partial_trees/jan.json
|
27
|
+
|
28
|
+
As library: check `LiabilityProof::Generator` and `LiabilityProof::Verifier` for example.
|
29
|
+
|
30
|
+
### License ###
|
31
|
+
|
32
|
+
LiabilityProof is a ruby gem released under MIT license. See [http://peatio.mit-license.org](http://peatio.mit-license.org) for more information.
|
33
|
+
|
34
|
+
### How To Contribute ###
|
35
|
+
|
36
|
+
Just create an issue or open a pull request :)
|
37
|
+
|
38
|
+
### Other implementations ###
|
39
|
+
|
40
|
+
* clojure: https://github.com/zw/PoLtree/blob/master/src/uk/me/iwilcox/poltree.clj
|
41
|
+
* node.js: http://olalonde.github.io/blind-liability-proof
|
42
|
+
* c++: https://github.com/bifubao/proof_of_reserves
|
43
|
+
* python: https://github.com/ConceptPending/proveit
|
44
|
+
|
data/bin/lproof
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'liability-proof'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
opt_parser = OptionParser.new do |opt|
|
9
|
+
opt.banner = "Usage: #{$0} COMMAND [OPTIONS]"
|
10
|
+
opt.separator ""
|
11
|
+
opt.separator "Commands"
|
12
|
+
opt.separator " generate: generate json of root node and partial trees"
|
13
|
+
opt.separator " verify: verify specified partial tree is valid (matches root node)"
|
14
|
+
opt.separator ""
|
15
|
+
opt.separator "Options"
|
16
|
+
|
17
|
+
opt.on("-f", "--file source.json", "Specify source file. For generate command, it should contain accounts data; for verify command, it should contain the partial tree.") do |f|
|
18
|
+
options[:file] = f
|
19
|
+
end
|
20
|
+
|
21
|
+
opt.on("-r", "--root root.json", "Specify root node data file. For generate command it's the destination to write data; for verify command it's the source to read data.") do |f|
|
22
|
+
options[:root] = f
|
23
|
+
end
|
24
|
+
|
25
|
+
options[:partial_trees_dir] = 'partial_trees'
|
26
|
+
opt.on("--partial-trees-dir DIR", "Specify the directory to store generated partial tree json files, default to 'partial_trees'.") do |d|
|
27
|
+
options[:partial_trees_dir] = d
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
opt_parser.parse!
|
32
|
+
|
33
|
+
case ARGV[0]
|
34
|
+
when 'generate'
|
35
|
+
LiabilityProof::Generator.new(options).write!
|
36
|
+
when 'verify'
|
37
|
+
LiabilityProof::Verifier.new(options).verify!
|
38
|
+
else
|
39
|
+
puts opt_parser
|
40
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module LiabilityProof
|
2
|
+
|
3
|
+
autoload :Tree, 'liability-proof/tree'
|
4
|
+
autoload :Generator, 'liability-proof/generator'
|
5
|
+
autoload :Verifier, 'liability-proof/verifier'
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def sha256_base64(message)
|
10
|
+
Base64.encode64(OpenSSL::Digest::SHA256.new.digest(message)).strip
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module LiabilityProof
|
5
|
+
class Generator
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@accounts_path = options.delete(:file) || 'accounts.json'
|
9
|
+
@root_path = options.delete(:root) || 'root.json'
|
10
|
+
|
11
|
+
@partial_trees_dir = options.delete(:partial_trees_dir) || 'partial_trees'
|
12
|
+
FileUtils.mkdir @partial_trees_dir unless File.exists?(@partial_trees_dir)
|
13
|
+
|
14
|
+
accounts = JSON.parse File.read(@accounts_path)
|
15
|
+
@tree = LiabilityProof::Tree.new accounts
|
16
|
+
end
|
17
|
+
|
18
|
+
def root_json
|
19
|
+
{ 'root' => {
|
20
|
+
'hash' => @tree.root.hash,
|
21
|
+
'value' => @tree.root.value_string }}
|
22
|
+
end
|
23
|
+
|
24
|
+
def partial_tree_json(user)
|
25
|
+
{ 'partial_tree' => @tree.partial(user) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def write!
|
29
|
+
write_root_json
|
30
|
+
@tree.indices.keys.each {|user| write_partial_tree(user) }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def write_root_json
|
36
|
+
File.open(@root_path, 'w') do |f|
|
37
|
+
f.write JSON.dump(root_json)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def write_partial_tree(user)
|
42
|
+
File.open(File.join(@partial_trees_dir, "#{user}.json"), 'w') do |f|
|
43
|
+
f.write JSON.dump(partial_tree_json(user))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module LiabilityProof
|
6
|
+
class Tree
|
7
|
+
|
8
|
+
autoload :Node, 'liability-proof/tree/node'
|
9
|
+
autoload :LeafNode, 'liability-proof/tree/leaf_node'
|
10
|
+
autoload :InteriorNode, 'liability-proof/tree/interior_node'
|
11
|
+
|
12
|
+
attr :root, :indices
|
13
|
+
|
14
|
+
def initialize(accounts)
|
15
|
+
raise ArgumentError, 'accounts is empty' unless accounts && accounts.size > 0
|
16
|
+
|
17
|
+
@accounts = accounts
|
18
|
+
@root = generate
|
19
|
+
@indices = Hash[index_leaves(@root)]
|
20
|
+
end
|
21
|
+
|
22
|
+
def partial(user)
|
23
|
+
h = { 'data' => nil }
|
24
|
+
_partial user, @root, @indices[user].dup, h
|
25
|
+
h
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def _partial(user, node, index, acc)
|
31
|
+
if node.is_a?(LeafNode)
|
32
|
+
acc['data'] = node.as_json
|
33
|
+
|
34
|
+
if node.user == user
|
35
|
+
acc['data'].merge!({
|
36
|
+
'user' => user,
|
37
|
+
'nonce' => node.nonce
|
38
|
+
})
|
39
|
+
end
|
40
|
+
else
|
41
|
+
follow_direction = index.shift
|
42
|
+
other_direction = follow_direction == :left ? :right : :left
|
43
|
+
follow_child = node.send follow_direction
|
44
|
+
other_child = node.send other_direction
|
45
|
+
|
46
|
+
acc[other_direction.to_s] = { 'data' => other_child.as_json }
|
47
|
+
acc[follow_direction.to_s] = { 'data' => nil }
|
48
|
+
_partial user, follow_child, index, acc[follow_direction.to_s]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate
|
53
|
+
leaves = @accounts.map {|a| LeafNode.new(a) }
|
54
|
+
combine leaves
|
55
|
+
end
|
56
|
+
|
57
|
+
def combine(nodes)
|
58
|
+
return nodes.first if nodes.size <= 1
|
59
|
+
|
60
|
+
parents = nodes.each_slice(2).map do |(left, right)|
|
61
|
+
# if right is not nil, return combined interior node;
|
62
|
+
# otherwise keep the left leaf node
|
63
|
+
right ? InteriorNode.new(left, right) : left
|
64
|
+
end
|
65
|
+
|
66
|
+
combine parents
|
67
|
+
end
|
68
|
+
|
69
|
+
# Walk the tree and produce indices, each index include the destination
|
70
|
+
# leaf and the path from given node to it.
|
71
|
+
#
|
72
|
+
# The path is expressed as an array of directions, e.g. :left, :right
|
73
|
+
def index_leaves(node, index=[])
|
74
|
+
if node.is_a?(LeafNode)
|
75
|
+
[[node.user, index]]
|
76
|
+
else
|
77
|
+
index_leaves(node.left, index+[:left]) + index_leaves(node.right, index+[:right])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module LiabilityProof
|
2
|
+
class Tree
|
3
|
+
|
4
|
+
class InteriorNode < Struct.new(:left, :right, :value, :hash)
|
5
|
+
include ::LiabilityProof::Tree::Node
|
6
|
+
|
7
|
+
def initialize(left, right)
|
8
|
+
super(left, right)
|
9
|
+
|
10
|
+
self.value = left.value + right.value
|
11
|
+
self.hash = generate_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def generate_hash
|
17
|
+
LiabilityProof.sha256_base64 "#{value_string}#{left.hash}#{right.hash}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module LiabilityProof
|
2
|
+
class Tree
|
3
|
+
|
4
|
+
class LeafNode < Struct.new(:user, :value, :nonce, :hash)
|
5
|
+
include ::LiabilityProof::Tree::Node
|
6
|
+
|
7
|
+
def initialize(account)
|
8
|
+
value = BigDecimal.new(account['value'] || account['balance'])
|
9
|
+
super(account['user'], value)
|
10
|
+
|
11
|
+
self.nonce = account['nonce'] || generate_nonce
|
12
|
+
self.hash = account['hash'] || generate_hash
|
13
|
+
|
14
|
+
if account['user'] && account['hash'] && account['nonce']
|
15
|
+
raise ArgumentError, "Hash doesn't match" if generate_hash != account['hash']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# a 16 bytes random string encoded in 32 hex digits
|
22
|
+
def generate_nonce
|
23
|
+
OpenSSL::Random.random_bytes(16).unpack("H*").first
|
24
|
+
end
|
25
|
+
|
26
|
+
# a sha256 hash encoded in 64 hex digits
|
27
|
+
def generate_hash
|
28
|
+
LiabilityProof.sha256_base64 "#{user}|#{value_string}|#{nonce}"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module LiabilityProof
|
4
|
+
class Verifier
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@root_path = options.delete(:root) || 'root.json'
|
8
|
+
@partial_tree_path = options.delete(:file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def root_json
|
12
|
+
@root_json ||= JSON.parse File.read(@root_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def partial_tree_json
|
16
|
+
@partial_tree_json ||= JSON.parse File.read(@partial_tree_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def expect_root
|
20
|
+
root_json['root']
|
21
|
+
end
|
22
|
+
|
23
|
+
def match?
|
24
|
+
partial_tree = partial_tree_json['partial_tree']
|
25
|
+
reduce(partial_tree).as_json == expect_root
|
26
|
+
end
|
27
|
+
|
28
|
+
def verify!
|
29
|
+
if match?
|
30
|
+
puts "Partial tree verified successfully!\n\n"
|
31
|
+
puts "User: #{@user_node.user}"
|
32
|
+
puts "Balance: #{@user_node.value_string}"
|
33
|
+
else
|
34
|
+
raise "Mismatch! Expected root: #{expect_root.inspect}"
|
35
|
+
end
|
36
|
+
rescue
|
37
|
+
puts "INVALID partial tree!"
|
38
|
+
puts "ERROR: #{$!}"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def reduce(node)
|
44
|
+
if node['data']
|
45
|
+
leaf = Tree::LeafNode.new node['data']
|
46
|
+
@user_node = leaf if leaf.user
|
47
|
+
leaf
|
48
|
+
else
|
49
|
+
left = reduce node['left']
|
50
|
+
right = reduce node['right']
|
51
|
+
Tree::InteriorNode.new(left, right)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: liability-proof
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peatio Opensource
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Check https://iwilcox.me.uk/2014/proving-bitcoin-reserves for more details.
|
14
|
+
email:
|
15
|
+
- community@peatio.com
|
16
|
+
executables:
|
17
|
+
- lproof
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.markdown
|
22
|
+
- bin/lproof
|
23
|
+
- lib/liability-proof.rb
|
24
|
+
- lib/liability-proof/generator.rb
|
25
|
+
- lib/liability-proof/tree.rb
|
26
|
+
- lib/liability-proof/tree/interior_node.rb
|
27
|
+
- lib/liability-proof/tree/leaf_node.rb
|
28
|
+
- lib/liability-proof/tree/node.rb
|
29
|
+
- lib/liability-proof/verifier.rb
|
30
|
+
- lib/liability-proof/version.rb
|
31
|
+
homepage: https://github.com/peatio/liability-proof
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.2.0
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: An implementation of Greg Maxwell's Merkle approach to prove Bitcoin liabilities.
|
55
|
+
test_files: []
|