vchain_client 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/vchain_client/bitcoind_blockchain_adapter.rb +135 -0
- data/lib/vchain_client/blockchain_adapter.rb +11 -0
- data/lib/vchain_client/blockchain_adapter_factory.rb +18 -0
- data/lib/vchain_client/blockchain_connection.rb +85 -0
- data/lib/vchain_client/blockcypher_blockchain_adapter.rb +79 -0
- data/lib/vchain_client/blockstack_client.rb +155 -0
- data/lib/vchain_client/signatures.rb +103 -0
- data/lib/vchain_client.rb +302 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5331089e365d13a224dc8b586d015a5b38de6929
|
4
|
+
data.tar.gz: a6883aff699c11c312a38f66cdf4a7142f606853
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d393052fc49844ce44434dfda1a277cf516fca9a6455c907acd73f5c4982069d5f1582f71422b4f66b06637d40f4fb639ce368f8d2212cb3dc23d0d0e95687d9
|
7
|
+
data.tar.gz: 0d1dafe6329b2a54c9622ce358b1578488e7b5a11820200a2ccc4a1ef0456df65f98a13f9e910645717c54f7addf0de79940b421f6a8679bd75be6cf7aafb7b9
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class BitcoindBlockchainAdapter < VChainClient::BlockchainAdapter
|
4
|
+
|
5
|
+
@server = nil
|
6
|
+
@port = nil
|
7
|
+
@rpc_username = nil
|
8
|
+
@rpc_password = nil
|
9
|
+
|
10
|
+
def initialize(server, port, rpc_username, rpc_password)
|
11
|
+
@server = server
|
12
|
+
@port = port
|
13
|
+
@rpc_username = rpc_username
|
14
|
+
@rpc_password = rpc_password
|
15
|
+
end
|
16
|
+
|
17
|
+
def getTx(txid)
|
18
|
+
raw_tx = self.getRawTx(txid)
|
19
|
+
|
20
|
+
if raw_tx != nil
|
21
|
+
if raw_tx.key?("confirmations")
|
22
|
+
if raw_tx["confirmations"] > 5
|
23
|
+
if raw_tx.key?("blockhash")
|
24
|
+
if raw_tx.key?("blocktime")
|
25
|
+
|
26
|
+
op_return = self.getOpReturn(raw_tx)
|
27
|
+
|
28
|
+
if op_return != nil
|
29
|
+
prefix = op_return[0..2]
|
30
|
+
if prefix == "VCH"
|
31
|
+
op_return = op_return[3..op_return.length]
|
32
|
+
|
33
|
+
return {
|
34
|
+
"size" => raw_tx["size"],
|
35
|
+
"block_hash" => raw_tx["blockhash"],
|
36
|
+
"block_timestamp" => raw_tx["blocktime"],
|
37
|
+
"op_return" => op_return
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def getRawTx(txid)
|
52
|
+
request = {
|
53
|
+
"id" => Random.rand(0...999999),
|
54
|
+
"method" => "getrawtransaction",
|
55
|
+
"params" => [txid, 1]
|
56
|
+
}
|
57
|
+
|
58
|
+
url = "http://"+ @rpc_username +":"+ @rpc_password +"@"+ @server +":"+ @port +"/"
|
59
|
+
|
60
|
+
begin
|
61
|
+
|
62
|
+
req = RestClient.post(url, request.to_json)
|
63
|
+
|
64
|
+
if req.code != 200
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
|
68
|
+
raw_tx = JSON.parse req.body
|
69
|
+
|
70
|
+
return raw_tx["result"]
|
71
|
+
|
72
|
+
rescue => e
|
73
|
+
raise e
|
74
|
+
end
|
75
|
+
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def getOpReturn(raw_tx)
|
80
|
+
if raw_tx != nil
|
81
|
+
tx_unpacked = {}
|
82
|
+
tx_unpacked["vout"] = []
|
83
|
+
|
84
|
+
if raw_tx.key?("vout")
|
85
|
+
if raw_tx["vout"].length > 0
|
86
|
+
raw_tx["vout"].each_with_index { |vout,index|
|
87
|
+
vout_t = {}
|
88
|
+
|
89
|
+
vout_t["value"] = 0
|
90
|
+
|
91
|
+
if vout.key?("value")
|
92
|
+
vout_t["value"] = vout["value"]
|
93
|
+
end
|
94
|
+
|
95
|
+
vout_t["scriptPubKey"] = ""
|
96
|
+
if vout.key?("scriptPubKey")
|
97
|
+
if vout["scriptPubKey"].key?("hex")
|
98
|
+
vout_t["scriptPubKey"] = vout["scriptPubKey"]["hex"]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
tx_unpacked["vout"][index] = vout_t;
|
103
|
+
}
|
104
|
+
|
105
|
+
tx_unpacked["vout"].each { |output|
|
106
|
+
|
107
|
+
scriptPubKeyArr = output["scriptPubKey"].split()
|
108
|
+
scriptPubKeyBinary = scriptPubKeyArr.pack("H*")
|
109
|
+
|
110
|
+
if scriptPubKeyBinary[0] == "\x6a"
|
111
|
+
first_ord = scriptPubKeyBinary[1].ord()
|
112
|
+
|
113
|
+
if first_ord <= 75
|
114
|
+
return scriptPubKeyBinary[2..first_ord+1]
|
115
|
+
|
116
|
+
elsif first_ord == 0x4c
|
117
|
+
return scriptPubKeyBinary[3..scriptPubKeyBinary[2].ord()+1]
|
118
|
+
|
119
|
+
elsif first_ord == 0x4d
|
120
|
+
return scriptPubKeyBinary[4..scriptPubKeyBinary[2].ord()+1+256*scriptPubKeyBinary[3].ord()+1]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
}
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
return nil
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class BlockchainAdapterFactory
|
4
|
+
|
5
|
+
def self.getAdapter(type, config)
|
6
|
+
if type == "bitcoind"
|
7
|
+
return VChainClient::BitcoindBlockchainAdapter.new(config["server"], config["port"], config["rpc_username"], config["rpc_password"])
|
8
|
+
|
9
|
+
elsif type == "blockcypher"
|
10
|
+
return VChainClient::BlockcypherBlockchainAdapter.new(config["api_token"])
|
11
|
+
end
|
12
|
+
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class BlockchainConnection
|
4
|
+
|
5
|
+
@config = nil
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def getTx(txid)
|
12
|
+
|
13
|
+
adapters = []
|
14
|
+
|
15
|
+
config_adapters = @config["blockchain_adapters"]
|
16
|
+
|
17
|
+
config_adapters.each { |adapter_config|
|
18
|
+
adapter = VChainClient::BlockchainAdapterFactory.getAdapter(adapter_config["type"], adapter_config)
|
19
|
+
|
20
|
+
if adapter != nil
|
21
|
+
adapters.push(adapter)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
if adapters.length > 0
|
26
|
+
|
27
|
+
prev_size = nil
|
28
|
+
prev_block_hash = nil
|
29
|
+
prev_block_timestamp = nil
|
30
|
+
prev_op_return = nil
|
31
|
+
|
32
|
+
adapters.each { |adapter|
|
33
|
+
|
34
|
+
tx = adapter.getTx(txid)
|
35
|
+
|
36
|
+
if tx == nil
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
if !tx.key?("block_hash")
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
if !tx.key?("block_timestamp")
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
if tx["block_hash"] == nil || tx["block_hash"] == ""
|
46
|
+
return nil
|
47
|
+
end
|
48
|
+
|
49
|
+
size = tx["size"]
|
50
|
+
block_hash = tx["block_hash"]
|
51
|
+
block_timestamp = tx["block_timestamp"]
|
52
|
+
op_return = tx["op_return"]
|
53
|
+
|
54
|
+
if prev_size != nil && prev_size != size
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
if prev_block_hash != nil && prev_block_hash != block_hash
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
if prev_block_timestamp != nil && prev_block_timestamp != block_timestamp
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
if prev_op_return != nil && prev_op_return != op_return
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
|
67
|
+
prev_size = size
|
68
|
+
prev_block_hash = block_hash
|
69
|
+
prev_block_timestamp = block_timestamp
|
70
|
+
prev_op_return = op_return
|
71
|
+
}
|
72
|
+
|
73
|
+
return {
|
74
|
+
"block_hash" => prev_block_hash,
|
75
|
+
"block_timestamp" => prev_block_timestamp,
|
76
|
+
"op_return" => prev_op_return
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
return nil
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class BlockcypherBlockchainAdapter < VChainClient::BlockchainAdapter
|
4
|
+
|
5
|
+
@api_token = nil
|
6
|
+
|
7
|
+
def initialize(api_token)
|
8
|
+
@api_token = api_token
|
9
|
+
end
|
10
|
+
|
11
|
+
def getTx(txid)
|
12
|
+
url = "https://api.blockcypher.com/v1/btc/main/txs/"
|
13
|
+
url += txid
|
14
|
+
url += "?token="+ @api_token
|
15
|
+
url += "&rand="+ Random.rand(0...999999).to_s
|
16
|
+
|
17
|
+
req = RestClient.get(url)
|
18
|
+
|
19
|
+
if req.code != 200
|
20
|
+
return nil
|
21
|
+
end
|
22
|
+
|
23
|
+
res = JSON.parse req.body
|
24
|
+
|
25
|
+
if res != nil
|
26
|
+
if res.key?("confirmations")
|
27
|
+
if res["confirmations"] > 5
|
28
|
+
if res.key?("block_hash")
|
29
|
+
if res.key?("confirmed")
|
30
|
+
block_timestamp = DateTime.parse(res["confirmed"]).to_i
|
31
|
+
|
32
|
+
op_return = self.getOpReturn(res)
|
33
|
+
|
34
|
+
if op_return != nil
|
35
|
+
prefix = op_return[0..2]
|
36
|
+
|
37
|
+
if prefix == "VCH"
|
38
|
+
op_return = op_return[3..op_return.length]
|
39
|
+
|
40
|
+
return {
|
41
|
+
"size" => res["size"],
|
42
|
+
"block_hash" => res["block_hash"],
|
43
|
+
"block_timestamp" => block_timestamp,
|
44
|
+
"op_return" => op_return
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def getOpReturn(raw_tx)
|
58
|
+
if raw_tx != nil
|
59
|
+
if raw_tx.key?("outputs")
|
60
|
+
if raw_tx["outputs"].length > 0
|
61
|
+
raw_tx["outputs"].each { |vout|
|
62
|
+
if vout.key?("script_type")
|
63
|
+
if vout["script_type"] == "null-data"
|
64
|
+
if vout.key?("data_string")
|
65
|
+
return vout["data_string"]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class BlockstackClient
|
4
|
+
|
5
|
+
MASTER_PUBLIC_KEY = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEXGOuvJT5hb/bR5y/JADHxJEEaQzuJUzw
|
6
|
+
Xet0UYuBrILjHB9HcxFc+WwuCflIRWtRDsNfaY6Ra7j/cRYLeiocYA=="
|
7
|
+
|
8
|
+
@config = nil
|
9
|
+
|
10
|
+
@@recs_cache = {}
|
11
|
+
@@keys_cache = {}
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
def checkFederativeServer(federative_server_id)
|
18
|
+
return self.checkBlockstackRecord(federative_server_id, 'federative_server')
|
19
|
+
end
|
20
|
+
|
21
|
+
def checkVerificator(verificator_id)
|
22
|
+
return self.checkBlockstackRecord(verificator_id, 'verificator')
|
23
|
+
end
|
24
|
+
|
25
|
+
def checkValidator(validator_id)
|
26
|
+
return self.checkBlockstackRecord(validator_id, 'validator')
|
27
|
+
end
|
28
|
+
|
29
|
+
def checkBlockstackRecord(blockstack_id, type)
|
30
|
+
|
31
|
+
record = self.getBlockstackRecord(blockstack_id)
|
32
|
+
|
33
|
+
signaturesHelper = VChainClient::Signatures.new(@config)
|
34
|
+
|
35
|
+
if record != nil
|
36
|
+
if record.key?("pubkey")
|
37
|
+
if record.key?("vchain_role")
|
38
|
+
|
39
|
+
if record["vchain_role"] != type
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
|
43
|
+
validator_blockstack_id = record["validator_blockstack_id"]
|
44
|
+
if validator_blockstack_id != nil
|
45
|
+
|
46
|
+
if record["vchain_role"] != 'validator'
|
47
|
+
if !self.checkValidator(validator_blockstack_id)
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
validator_pub_key = nil
|
53
|
+
if record["vchain_role"] != 'validator'
|
54
|
+
validator_pub_key = self.getPublicKey(validator_blockstack_id)
|
55
|
+
else
|
56
|
+
validator_pub_key = MASTER_PUBLIC_KEY;
|
57
|
+
end
|
58
|
+
|
59
|
+
# check client's sig
|
60
|
+
|
61
|
+
client_sig_to_check = record["vchain_id"] + record["vchain_role"] + blockstack_id + record["pubkey"] + record["sig_version"];
|
62
|
+
|
63
|
+
if signaturesHelper.verifySignature(client_sig_to_check, record["client_sig"], record["pubkey"])
|
64
|
+
# check validator's sig
|
65
|
+
validator_sig_to_check = record["vchain_id"] + record["vchain_role"] + blockstack_id + record["pubkey"] + record["sig_version"] + record["validator_vchain_id"] + validator_blockstack_id
|
66
|
+
|
67
|
+
if signaturesHelper.verifySignature(validator_sig_to_check, record["validator_sig"], validator_pub_key)
|
68
|
+
return true;
|
69
|
+
|
70
|
+
else
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
else
|
75
|
+
return nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def getPublicKey(blockstack_id)
|
86
|
+
if @@keys_cache.key?(blockstack_id)
|
87
|
+
return @@keys_cache[blockstack_id]
|
88
|
+
end
|
89
|
+
|
90
|
+
record = self.getBlockstackRecord(blockstack_id);
|
91
|
+
if record != nil
|
92
|
+
if record.key?("pubkey")
|
93
|
+
|
94
|
+
@@keys_cache[blockstack_id] = record["pubkey"]
|
95
|
+
|
96
|
+
return record["pubkey"]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
return nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def getBlockstackRecord(blockstack_id)
|
104
|
+
if @@recs_cache.key?(blockstack_id)
|
105
|
+
return @@recs_cache[blockstack_id]
|
106
|
+
end
|
107
|
+
|
108
|
+
blockstack_path = @config["blockstack"]["path"]
|
109
|
+
|
110
|
+
cmd = blockstack_path +" get_name_zonefile "+ blockstack_id
|
111
|
+
|
112
|
+
ret = `#{cmd}`
|
113
|
+
if ret == nil || ret == "Name has no user record hash defined"
|
114
|
+
return nil
|
115
|
+
end
|
116
|
+
ret = JSON.parse ret
|
117
|
+
|
118
|
+
fz = {}
|
119
|
+
if ret != nil
|
120
|
+
if ret.key?("txt")
|
121
|
+
ret["txt"].each { |rec|
|
122
|
+
if rec.key?("name") && rec.key?("txt")
|
123
|
+
fz[rec["name"]] = rec["txt"]
|
124
|
+
end
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if fz.key?("A1")
|
130
|
+
pubkey_aligned = fz["A1"]
|
131
|
+
pubkey = pubkey_aligned[0..63] +"\n"+ pubkey_aligned[64..pubkey_aligned.length]
|
132
|
+
|
133
|
+
output = {
|
134
|
+
"pubkey" => pubkey,
|
135
|
+
"vchain_id" => fz["A2"],
|
136
|
+
"validator_sig" => Base64.decode64(fz["A3"]),
|
137
|
+
"validator_vchain_id" => fz["A4"],
|
138
|
+
"validator_blockstack_id" => fz["A5"],
|
139
|
+
"vchain_role" => fz["A6"],
|
140
|
+
"sig_version" => fz["A7"],
|
141
|
+
"client_sig" => Base64.decode64(fz["A8"])
|
142
|
+
}
|
143
|
+
|
144
|
+
@@recs_cache[blockstack_id] = output
|
145
|
+
@@keys_cache[blockstack_id] = pubkey
|
146
|
+
|
147
|
+
return output
|
148
|
+
end
|
149
|
+
|
150
|
+
return nil
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class Signatures
|
4
|
+
|
5
|
+
@config = nil
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def signRequest(document, timestamp)
|
12
|
+
priv_key_path = @config["private_key_location"]
|
13
|
+
|
14
|
+
priv_key = File.read(priv_key_path)
|
15
|
+
|
16
|
+
whole_sign = document.to_json + timestamp.to_s
|
17
|
+
|
18
|
+
ec = OpenSSL::PKey::EC.new(priv_key)
|
19
|
+
|
20
|
+
digest = OpenSSL::Digest::SHA256.new
|
21
|
+
|
22
|
+
whole_signature = ec.sign(digest, whole_sign)
|
23
|
+
|
24
|
+
return Base64.encode64(whole_signature).gsub(/\n/, "")
|
25
|
+
end
|
26
|
+
|
27
|
+
def verifySignature(what_to_check, signature, public_key)
|
28
|
+
pub_key = "-----BEGIN PUBLIC KEY-----\n"
|
29
|
+
pub_key += public_key
|
30
|
+
pub_key += "\n-----END PUBLIC KEY-----"
|
31
|
+
|
32
|
+
ec = OpenSSL::PKey::EC.new(pub_key)
|
33
|
+
|
34
|
+
digest = OpenSSL::Digest::SHA256.new
|
35
|
+
|
36
|
+
return ec.verify(digest, signature, what_to_check)
|
37
|
+
end
|
38
|
+
|
39
|
+
def signVerification(verification_type, data, timestamp)
|
40
|
+
|
41
|
+
this_client_id = @config["blockstack"]["client_id"]
|
42
|
+
priv_key_path = @config["private_key_location"]
|
43
|
+
|
44
|
+
priv_key = File.read(priv_key_path)
|
45
|
+
|
46
|
+
output = {}
|
47
|
+
|
48
|
+
data.each{ |rec|
|
49
|
+
field = rec[0]
|
50
|
+
value = rec[1]
|
51
|
+
|
52
|
+
if field != 'type' && field != 'client_id'
|
53
|
+
|
54
|
+
field_hash = Digest::SHA512.hexdigest(field)
|
55
|
+
|
56
|
+
value_hash = Digest::SHA512.hexdigest(value)
|
57
|
+
|
58
|
+
what_to_sign = field_hash
|
59
|
+
what_to_sign += value_hash
|
60
|
+
what_to_sign += verification_type
|
61
|
+
what_to_sign += timestamp.to_s
|
62
|
+
what_to_sign += this_client_id
|
63
|
+
|
64
|
+
ec = OpenSSL::PKey::EC.new(priv_key)
|
65
|
+
|
66
|
+
digest = OpenSSL::Digest::SHA256.new
|
67
|
+
|
68
|
+
signature = ec.sign(digest, what_to_sign)
|
69
|
+
|
70
|
+
output[field] = Base64.encode64(signature).gsub(/\n/, "")
|
71
|
+
|
72
|
+
end
|
73
|
+
}
|
74
|
+
|
75
|
+
return output
|
76
|
+
end
|
77
|
+
|
78
|
+
def checkTreeSignature(tree_root_hash, blockchain_txid, blockchain_block_hash, blockchain_timestamp, blockstack_client_id, sig_version, signature, pubkey)
|
79
|
+
|
80
|
+
what_to_check = tree_root_hash
|
81
|
+
what_to_check += blockchain_txid
|
82
|
+
what_to_check += blockchain_block_hash
|
83
|
+
what_to_check += blockchain_timestamp.to_s
|
84
|
+
what_to_check += blockstack_client_id
|
85
|
+
what_to_check += sig_version
|
86
|
+
|
87
|
+
return self.verifySignature(what_to_check, signature, pubkey)
|
88
|
+
end
|
89
|
+
|
90
|
+
def checkVerificationSignature(field_hash, data_hash, verification_type, timestamp, blockstack_client_id, pubkey, signature)
|
91
|
+
|
92
|
+
what_to_check = field_hash
|
93
|
+
what_to_check += data_hash
|
94
|
+
what_to_check += verification_type
|
95
|
+
what_to_check += timestamp.to_s
|
96
|
+
what_to_check += blockstack_client_id
|
97
|
+
|
98
|
+
return self.verifySignature(what_to_check, signature, pubkey)
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
module VChainClient
|
2
|
+
|
3
|
+
class Client
|
4
|
+
|
5
|
+
require 'rest-client'
|
6
|
+
require 'base64'
|
7
|
+
gem "openssl"
|
8
|
+
require 'openssl'
|
9
|
+
|
10
|
+
require 'vchain_client/blockchain_adapter'
|
11
|
+
require 'vchain_client/bitcoind_blockchain_adapter'
|
12
|
+
require 'vchain_client/blockcypher_blockchain_adapter'
|
13
|
+
require 'vchain_client/blockchain_adapter_factory'
|
14
|
+
require 'vchain_client/blockchain_connection'
|
15
|
+
require 'vchain_client/signatures'
|
16
|
+
require 'vchain_client/blockstack_client'
|
17
|
+
|
18
|
+
FIELD_TYPE_HASHED = "fbb6889f44061c2a91e17a411cf168f9457981257a5e0a31fb706cd5cd1e64c263780a42a1fd858ee69429869ab2e2c53b9d94c4a26946f2b0c12f8ce2812d6b"
|
19
|
+
|
20
|
+
@config = nil
|
21
|
+
|
22
|
+
def initialize(config)
|
23
|
+
@config = config
|
24
|
+
end
|
25
|
+
|
26
|
+
def hash(arr)
|
27
|
+
arr.each { |k, v|
|
28
|
+
arr[k] = Digest::SHA512.hexdigest(v.downcase)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_credentials_hash(document)
|
33
|
+
if document["type"] == FIELD_TYPE_HASHED
|
34
|
+
what_to_hash = document["type"] + document["citizenship"] + document["number"] + document["first_name"] + document["last_name"] + document["birthdate"]
|
35
|
+
|
36
|
+
return Digest::SHA512.hexdigest(what_to_hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_merkle_tree(tree, timestamp)
|
43
|
+
previous_parent = nil
|
44
|
+
|
45
|
+
tree.each { |tree_level|
|
46
|
+
if previous_parent != nil
|
47
|
+
if previous_parent != tree_level["left"] && previous_parent != tree_level["right"]
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
what_to_hash = tree_level["left"]
|
53
|
+
if tree_level["right"] != nil
|
54
|
+
what_to_hash += tree_level["right"]
|
55
|
+
else
|
56
|
+
what_to_hash += tree_level["left"]
|
57
|
+
end
|
58
|
+
what_to_hash += timestamp.to_s
|
59
|
+
|
60
|
+
level_hash = Digest::SHA256.hexdigest(what_to_hash);
|
61
|
+
|
62
|
+
if level_hash != tree_level["parent"]
|
63
|
+
return nil
|
64
|
+
end
|
65
|
+
|
66
|
+
previous_parent = level_hash
|
67
|
+
}
|
68
|
+
|
69
|
+
return previous_parent
|
70
|
+
end
|
71
|
+
|
72
|
+
def do_use(input, ignore_possible_matches = false)
|
73
|
+
|
74
|
+
client_id = @config["client_id"]
|
75
|
+
api_url = @config["api"]["url"] + "v0.1/use/";
|
76
|
+
|
77
|
+
document = input
|
78
|
+
document = self.hash(document)
|
79
|
+
|
80
|
+
document["client_id"] = client_id
|
81
|
+
document["ignore_possible_matches"] = ignore_possible_matches
|
82
|
+
|
83
|
+
req = RestClient.post(api_url,
|
84
|
+
document.to_json,
|
85
|
+
{'Content-Type' => 'application/json'})
|
86
|
+
|
87
|
+
if req.code != 200
|
88
|
+
return false
|
89
|
+
end
|
90
|
+
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
|
94
|
+
def do_verify(verification_type, input)
|
95
|
+
|
96
|
+
client_id = @config["client_id"]
|
97
|
+
api_url = @config["api"]["url"] + "v0.1/verify/";
|
98
|
+
|
99
|
+
time = Time.now.getutc
|
100
|
+
timestamp = time.to_i
|
101
|
+
|
102
|
+
document = input
|
103
|
+
document = self.hash(document)
|
104
|
+
|
105
|
+
signaturesHelper = VChainClient::Signatures.new(@config)
|
106
|
+
|
107
|
+
verifications_data = signaturesHelper.signVerification(verification_type, document, timestamp)
|
108
|
+
|
109
|
+
document = Hash[document.sort]
|
110
|
+
|
111
|
+
whole_signature = signaturesHelper.signRequest(document, timestamp)
|
112
|
+
|
113
|
+
send_data = {}
|
114
|
+
send_data["client_id"] = client_id
|
115
|
+
send_data["data"] = document
|
116
|
+
send_data["timestamp"] = timestamp.to_s
|
117
|
+
send_data["verification_type"] = verification_type;
|
118
|
+
send_data["verifications_signatures"] = verifications_data
|
119
|
+
send_data["signature"] = whole_signature
|
120
|
+
|
121
|
+
req = RestClient.post(api_url,
|
122
|
+
send_data.to_json,
|
123
|
+
{'Content-Type' => 'application/json'})
|
124
|
+
|
125
|
+
if req.code != 200
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
|
129
|
+
return true
|
130
|
+
end
|
131
|
+
|
132
|
+
def do_check(input, is_already_hashed=false)
|
133
|
+
|
134
|
+
client_id = @config["client_id"]
|
135
|
+
api_url = @config["api"]["url"] + "v0.1/check/";
|
136
|
+
|
137
|
+
document = input
|
138
|
+
|
139
|
+
if (!is_already_hashed)
|
140
|
+
document = self.hash(document)
|
141
|
+
end
|
142
|
+
|
143
|
+
document["client_id"] = client_id
|
144
|
+
|
145
|
+
req = RestClient.post(api_url,
|
146
|
+
document.to_json,
|
147
|
+
{'Content-Type' => 'application/json'})
|
148
|
+
|
149
|
+
if req.code != 200
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
|
153
|
+
res = JSON.parse req.body
|
154
|
+
|
155
|
+
if res["status"] == "ERROR" || res["status"] == "error"
|
156
|
+
|
157
|
+
raise res["error_reason_code"]
|
158
|
+
|
159
|
+
else
|
160
|
+
res_document = res["document"]
|
161
|
+
|
162
|
+
validated_verifications = {}
|
163
|
+
|
164
|
+
credentials_hash = self.get_credentials_hash(document)
|
165
|
+
|
166
|
+
blockchainConnection = VChainClient::BlockchainConnection.new(@config)
|
167
|
+
|
168
|
+
blockstackClient = VChainClient::BlockstackClient.new(@config)
|
169
|
+
|
170
|
+
signaturesHelper = VChainClient::Signatures.new(@config)
|
171
|
+
|
172
|
+
res_document.each { |field, v|
|
173
|
+
if v["verifications"].length > 0 && field != "type"
|
174
|
+
v["verifications"].each { |verification|
|
175
|
+
|
176
|
+
next if !verification.key?("blockchain_reciepts")
|
177
|
+
|
178
|
+
next if verification["blockchain_reciepts"].length <= 0
|
179
|
+
|
180
|
+
# 1a. check credentials_hash
|
181
|
+
next if credentials_hash != verification["credentials_hash"]
|
182
|
+
|
183
|
+
# 1b. check field_hash
|
184
|
+
field_hash = Digest::SHA512.hexdigest(field)
|
185
|
+
next if field_hash != verification["field_hash"]
|
186
|
+
|
187
|
+
# 1c. check data_hash
|
188
|
+
data_hash = Digest::SHA512.hexdigest(document[field])
|
189
|
+
next if data_hash != verification["data_hash"]
|
190
|
+
|
191
|
+
# 1d. check checksum
|
192
|
+
checksum_to_hash = credentials_hash + field_hash + data_hash
|
193
|
+
checksum = Digest::SHA512.hexdigest(checksum_to_hash)
|
194
|
+
next if checksum != verification["checksum"]
|
195
|
+
|
196
|
+
verification_hash = verification["verification_hash"]
|
197
|
+
|
198
|
+
# 2. check verification_hash to be in target_proof's
|
199
|
+
# first element
|
200
|
+
reciepts_validated = 0
|
201
|
+
verification["blockchain_reciepts"].each { |reciept|
|
202
|
+
|
203
|
+
break if !reciept.key?("target_proof")
|
204
|
+
|
205
|
+
break if reciept["target_proof"].length <= 0
|
206
|
+
|
207
|
+
break if reciept["target_proof"][0]["left"] != verification_hash && reciept["target_proof"][0]["right"] != verification_hash
|
208
|
+
|
209
|
+
# verification_hash is in tree
|
210
|
+
# and in right position
|
211
|
+
# now,
|
212
|
+
|
213
|
+
# 3. check tree convergence to root hash
|
214
|
+
# and compare this computed root hash
|
215
|
+
# with merkle_tree_root_hash from response
|
216
|
+
computed_tree_root_hash = self.build_merkle_tree(reciept["target_proof"], reciept["timestamp"])
|
217
|
+
|
218
|
+
break if computed_tree_root_hash == nil
|
219
|
+
break if computed_tree_root_hash != reciept["merkle_tree_root_hash"]
|
220
|
+
|
221
|
+
last_proof_index = reciept["target_proof"].length - 1
|
222
|
+
reciept_stored_last_parent = reciept["target_proof"][last_proof_index]["parent"]
|
223
|
+
break if reciept_stored_last_parent != computed_tree_root_hash
|
224
|
+
|
225
|
+
# 4. check OP_RETURN in Bitcoin's tx,
|
226
|
+
# compare it to computed root hash of a tree
|
227
|
+
# retrieve some info from tx to verify signature
|
228
|
+
tx = blockchainConnection.getTx(reciept["blockchain_txid"])
|
229
|
+
|
230
|
+
break if tx == nil
|
231
|
+
break if tx["block_hash"] != reciept["blockchain_block_hash"]
|
232
|
+
break if tx["block_timestamp"] != reciept["blockchain_timestamp"]
|
233
|
+
break if tx["op_return"] != computed_tree_root_hash
|
234
|
+
|
235
|
+
blockchain_txid = reciept["blockchain_txid"];
|
236
|
+
blockchain_block_hash = tx["block_hash"];
|
237
|
+
blockchain_timestamp = tx["block_timestamp"];
|
238
|
+
|
239
|
+
# 5. check tree signature:
|
240
|
+
# a) federative server record in Blockstack (recursive)
|
241
|
+
# b) tree_signature
|
242
|
+
|
243
|
+
# a) federative server record in Blockstack (recursive)
|
244
|
+
break if !blockstackClient.checkFederativeServer(reciept["federative_server_id"])
|
245
|
+
|
246
|
+
# b) check tree signature
|
247
|
+
federative_server_pubkey = blockstackClient.getPublicKey(reciept["federative_server_id"])
|
248
|
+
|
249
|
+
break if !signaturesHelper.checkTreeSignature(computed_tree_root_hash, blockchain_txid, blockchain_block_hash, blockchain_timestamp, reciept["federative_server_id"], reciept["federative_server_version"], Base64.decode64(reciept["tree_signature"]), federative_server_pubkey)
|
250
|
+
|
251
|
+
reciepts_validated += 1
|
252
|
+
}
|
253
|
+
next if reciepts_validated != verification["blockchain_reciepts"].length
|
254
|
+
|
255
|
+
# 6. check verification signatures:
|
256
|
+
# a) check verificator record in Blockstack (recursive)
|
257
|
+
# b) check validator record in Blockstack (recursive)
|
258
|
+
# c) verificator_sig
|
259
|
+
# d) validtor_sig
|
260
|
+
|
261
|
+
# a) check verificator record in Blockstack (recursive)
|
262
|
+
next if !blockstackClient.checkVerificator(verification["verificator_id"])
|
263
|
+
|
264
|
+
# b) check validator record in Blockstack (recursive)
|
265
|
+
next if !blockstackClient.checkValidator(verification["validator_id"])
|
266
|
+
|
267
|
+
# c) check verificator's signature
|
268
|
+
verificator_pubkey = blockstackClient.getPublicKey(verification["verificator_id"])
|
269
|
+
|
270
|
+
next if !signaturesHelper.checkVerificationSignature(field_hash, data_hash, verification["type"], verification["timestamp"], verification["verificator_id"], verificator_pubkey, Base64.decode64(verification["verificator_sig"]))
|
271
|
+
|
272
|
+
# d) check validator's signature
|
273
|
+
validator_pubkey = blockstackClient.getPublicKey(verification["validator_id"])
|
274
|
+
|
275
|
+
next if !signaturesHelper.checkVerificationSignature(field_hash, data_hash, verification["type"], verification["timestamp"], verification["validator_id"], validator_pubkey, Base64.decode64(verification["validator_sig"]))
|
276
|
+
|
277
|
+
# 7. timestamps checking
|
278
|
+
# TODO
|
279
|
+
if !validated_verifications.key?(field)
|
280
|
+
validated_verifications[field] = 0
|
281
|
+
end
|
282
|
+
validated_verifications[field] = validated_verifications[field] + 1
|
283
|
+
}
|
284
|
+
end
|
285
|
+
}
|
286
|
+
|
287
|
+
# check input fields
|
288
|
+
input.each_with_index { |field,index|
|
289
|
+
if field[0] != 'type' && field[0] != 'client_id'
|
290
|
+
if !validated_verifications.key?(field[0])
|
291
|
+
validated_verifications[field[0]] = 0
|
292
|
+
end
|
293
|
+
end
|
294
|
+
}
|
295
|
+
|
296
|
+
return validated_verifications
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vchain_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aleksandr Gorelik
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.8.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.8'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.8.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rest-client
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '2.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.0.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '2.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 2.0.0
|
53
|
+
description: Fully functional client for VChain Platform written on Ruby
|
54
|
+
email: alexander@vchain.tech
|
55
|
+
executables: []
|
56
|
+
extensions: []
|
57
|
+
extra_rdoc_files: []
|
58
|
+
files:
|
59
|
+
- lib/vchain_client.rb
|
60
|
+
- lib/vchain_client/bitcoind_blockchain_adapter.rb
|
61
|
+
- lib/vchain_client/blockchain_adapter.rb
|
62
|
+
- lib/vchain_client/blockchain_adapter_factory.rb
|
63
|
+
- lib/vchain_client/blockchain_connection.rb
|
64
|
+
- lib/vchain_client/blockcypher_blockchain_adapter.rb
|
65
|
+
- lib/vchain_client/blockstack_client.rb
|
66
|
+
- lib/vchain_client/signatures.rb
|
67
|
+
homepage: http://rubygems.org/gems/vchain_client
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 2.6.6
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: VChain Platform client written on Ruby
|
91
|
+
test_files: []
|