vchain_client 1.0.0
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/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: []
|