liability-proof 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|