tierion 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +6 -0
- data/README.md +120 -52
- data/Rakefile +2 -0
- data/lib/tierion.rb +1 -5
- data/lib/tierion/hash_api.rb +3 -0
- data/lib/tierion/hash_api/client.rb +145 -0
- data/lib/tierion/hash_api/hash_item.rb +16 -0
- data/lib/tierion/hash_api/receipt.rb +62 -0
- data/lib/tierion/version.rb +1 -1
- metadata +6 -7
- metadata.gz.sig +0 -0
- data/lib/tierion/blockchain_receipt.rb +0 -62
- data/lib/tierion/blockchain_receipt_header.rb +0 -11
- data/lib/tierion/blockchain_receipt_target.rb +0 -9
- data/lib/tierion/hashitem.rb +0 -138
- data/lib/tierion/hashitem_receipt.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a6c61dbd6130591ba0ae23a7fa8845a8b2a4c8f
|
4
|
+
data.tar.gz: d7023dcf234d38ae0b383eb51ab5480d69427c41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39443a1eca9208226af6a56587ba863df0428a94f647c6d82dac1cdd122109c8a192fc7fa1034e6fb11f3c72780b8d0e9bc5a0e58774ffd8447516fa3b7d3b29
|
7
|
+
data.tar.gz: 1060b935d65af04add356b0679f327cf6ce1175fc6d61cf18f93d4b3c073d4c63fbbd5e97811a81f8e366e97e6c63df8fe418fcc380dfb2bfae5bc28c02a2e30
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## v1.0.0 (8/5/2016)
|
4
|
+
|
5
|
+
- Refactor Ruby API to better match the Client, HashItem, Receipt, Confirmation hierarchy.
|
6
|
+
- Support newly release Chainpoint 2.0 Blockchain receipts
|
7
|
+
- Drop support for Chainpoint 1.0
|
8
|
+
|
3
9
|
## v0.1.0 (8/1/2016)
|
4
10
|
|
5
11
|
This is the initial ALPHA quality release.
|
data/README.md
CHANGED
@@ -25,19 +25,21 @@ Shell commands start with a `$`, Ruby console commands start with `>`.
|
|
25
25
|
Instantiate a new API client
|
26
26
|
|
27
27
|
```
|
28
|
-
> t = Tierion::
|
28
|
+
> t = Tierion::HashApi::Client.new('me@example.com', 'mypassword')
|
29
29
|
```
|
30
30
|
|
31
31
|
You can also set the username and password in environment
|
32
|
-
variables
|
32
|
+
variables...
|
33
33
|
|
34
34
|
```
|
35
35
|
$ export TIERION_USERNAME=me@example.com
|
36
36
|
$ export TIERION_PASSWORD=my_pass
|
37
37
|
```
|
38
38
|
|
39
|
+
... and call it without hardcoding your credentials.
|
40
|
+
|
39
41
|
```
|
40
|
-
> t = Tierion::
|
42
|
+
> t = Tierion::HashApi::Client.new
|
41
43
|
```
|
42
44
|
|
43
45
|
Create the hash you want to record on the blockchain
|
@@ -48,93 +50,159 @@ and send it.
|
|
48
50
|
=> "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae">
|
49
51
|
|
50
52
|
> t.send(my_hash)
|
51
|
-
=> Tierion::
|
53
|
+
=> Tierion::HashApi::HashItem ...
|
52
54
|
```
|
53
55
|
|
54
|
-
|
55
|
-
client. This is the only place to find the hash item
|
56
|
-
ID's. You probably want to store these somewhere in your DB
|
57
|
-
since this client is ephemeral and there is no way to
|
58
|
-
retrieve these ID's later.
|
56
|
+
Now you can take a look at the Array of `HashItem`s.
|
59
57
|
|
60
58
|
```
|
61
|
-
> t.
|
62
|
-
=> [Tierion::
|
59
|
+
> t.hash_items
|
60
|
+
=> [Tierion::HashApi::HashItem ..., Tierion::HashApi::HashItem ...]
|
63
61
|
```
|
64
62
|
|
65
|
-
|
63
|
+
Within the `Tierion::HashApi::HashItem` objects is the only place you
|
64
|
+
can find the hash item ID's for hashes you've sent. You will probably want
|
65
|
+
to store these somewhere in your DB since this client instance is ephemeral
|
66
|
+
and there is no way to retrieve these ID's later.
|
67
|
+
|
68
|
+
Let's grab a single `Tierion::HashApi::HashItem` to work on:
|
66
69
|
|
67
70
|
```
|
68
|
-
> h = t.
|
71
|
+
> h = t.hash_items.first
|
69
72
|
```
|
70
73
|
|
71
|
-
|
72
|
-
the timestamp as a `Time` object (UTC time)
|
74
|
+
By default the `HashItem#timestamp` value is an Integer representing
|
75
|
+
the seconds since the UNIX epoch. For convenience you can use `HashItem#time` to get the `timestamp` as a Ruby `Time` object (UTC time).
|
73
76
|
|
74
77
|
```
|
78
|
+
> h.timestamp
|
79
|
+
=> 1470448435
|
75
80
|
> h.time
|
76
|
-
=> 2016-08-01
|
81
|
+
=> 2016-08-06 01:53:55 UTC
|
77
82
|
```
|
78
83
|
|
79
|
-
You can retrieve an individual `
|
80
|
-
|
84
|
+
You can retrieve an individual `Tierion::HashApi::Receipt`
|
85
|
+
for this `HashItem` by passing the `Hashitem` instance as
|
86
|
+
an arg to `Client#receipt`
|
81
87
|
|
82
88
|
```
|
83
|
-
> t.
|
84
|
-
=> Tierion::
|
89
|
+
> t.receipt(h)
|
90
|
+
=> Tierion::HashApi::Receipt ...
|
85
91
|
```
|
86
92
|
|
87
|
-
Or, call `
|
88
|
-
|
89
|
-
|
90
|
-
API.
|
93
|
+
Or, call `Client#receipts` to loop through each `Hashitem`
|
94
|
+
submitted in this session and collect and cache `Receipts`
|
95
|
+
for each from the API.
|
91
96
|
|
92
|
-
Remember that
|
93
|
-
processed and sent to the blockchain so you may need
|
94
|
-
call this again
|
97
|
+
Remember that `Receipt`s are not available until
|
98
|
+
processed and sent to the blockchain so you may need
|
99
|
+
to call this again to get the `Reciept` for every `HashItem`.
|
95
100
|
|
96
101
|
```
|
97
|
-
> t.
|
98
|
-
=> [Tierion::
|
102
|
+
> t.receipts
|
103
|
+
=> [Tierion::HashApi::Receipt ..., Tierion::HashApi::Receipt ...]
|
99
104
|
```
|
100
105
|
|
101
|
-
Get one `
|
106
|
+
Get one `Tierion::HashApi::Receipt` to work on
|
102
107
|
|
103
108
|
```
|
104
|
-
>
|
105
|
-
=> Tierion::
|
109
|
+
> r = t.receipts.first
|
110
|
+
=> Tierion::HashApi::Receipt ...
|
106
111
|
```
|
107
112
|
|
108
|
-
A `
|
109
|
-
which are populated from the API.
|
113
|
+
A `Tierion::HashApi::Receipt` object has a number of properties
|
114
|
+
which are populated from the API. Here is an example:
|
110
115
|
|
111
116
|
```
|
112
|
-
>
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
> r = t.receipts.first
|
118
|
+
=> {
|
119
|
+
"@context"=>"https://w3id.org/chainpoint/v2",
|
120
|
+
"type"=>"ChainpointSHA256v2",
|
121
|
+
"targetHash"=>"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
122
|
+
"merkleRoot"=>"326c0c924a162c8637b8fa392add7c8c98f64f5194f3d2591caf88d7b0b956bd",
|
123
|
+
"proof"=>[
|
124
|
+
{
|
125
|
+
"left"=>"c108bfba805d899faa0ef53b0c064fad650249f6a648fec2bc79fd563106b1f8"
|
126
|
+
}
|
127
|
+
],
|
128
|
+
"anchors"=>[
|
129
|
+
{
|
130
|
+
"type"=>"BTCOpReturn",
|
131
|
+
"sourceId"=>"579dce214fe0242e3397c9a988622f35b5ee91cef5068b0895a0bbe2c7797b9a"
|
132
|
+
}
|
133
|
+
]
|
134
|
+
}
|
119
135
|
```
|
120
136
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
with the
|
137
|
+
The `Receipt` returns `anchors` which represent one or more trust
|
138
|
+
anchors where your hash has been stored. Currently, the only `anchor`
|
139
|
+
returned is `BTCOpReturn` which also gives you a `sourceId`
|
140
|
+
attribute. This represents the Bitcoin blockchain's OP_RETURN anchor,
|
141
|
+
with the `sourceId` representing the BTC transaction ID.
|
126
142
|
|
127
|
-
|
143
|
+
You can also query whether the transaction associated with a `Receipt`
|
144
|
+
has actually been confirmed on a trust anchor with a call to
|
145
|
+
`Receipt#confirmations`. This will query each supported trust anchor
|
146
|
+
to determine whether or not the expected hash can be found. This depends
|
147
|
+
on an API call to a different third-party site for each anchor. This call
|
148
|
+
will return a hash with the supported trust anchors as the key and a `Boolean` to indicate if the confirmation was successful for that anchor.
|
128
149
|
|
150
|
+
`Receipt`s can take quite a while to be confirmed. Possibly several hours.
|
151
|
+
So, be patient.
|
152
|
+
|
153
|
+
```
|
154
|
+
> r.confirmations
|
155
|
+
=> {"BTCOpReturn"=>true}
|
129
156
|
```
|
130
|
-
> b.confirmed?
|
131
|
-
=> true
|
132
157
|
|
133
|
-
|
134
|
-
|
135
|
-
|
158
|
+
You can of course also manually confirm that a hash is
|
159
|
+
visible at the Transaction ID (`sourceId`) appropriate
|
160
|
+
for your trust anchor. For example, for Bitcoin you can
|
161
|
+
search for the `sourceId` at a URL like:
|
162
|
+
|
163
|
+
[https://blockchain.info/tx/579dce214fe0242e3397c9a988622f35b5ee91cef5068b0895a0bbe2c7797b9a](https://blockchain.info/tx/579dce214fe0242e3397c9a988622f35b5ee91cef5068b0895a0bbe2c7797b9a)
|
164
|
+
|
165
|
+
Or just paste the transaction ID into the search field at [https://blockchain.info](https://blockchain.info).
|
166
|
+
|
167
|
+
Once you are looking at the transaction info page, you want to
|
168
|
+
look for the `OP_RETURN` part of the page, and your hash should be
|
169
|
+
there, with a `326c` prefix followed by the 32 byte (64 hex characters)
|
170
|
+
hash value you originally submitted. The prefix represents the hex values
|
171
|
+
`0x32` and `0x6c` which are the OP_RETURN special code, and the byte length in hex of the OP_RETURN value (your hash).
|
172
|
+
|
173
|
+
You can get a pretty JSON representation of the `Receipt` by calling
|
174
|
+
the `Receipt#to_pretty_json` method.
|
175
|
+
|
176
|
+
```
|
177
|
+
> r.to_pretty_json
|
178
|
+
{
|
179
|
+
"@context": "https://w3id.org/chainpoint/v2",
|
180
|
+
"type": "ChainpointSHA256v2",
|
181
|
+
"targetHash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
182
|
+
"merkleRoot": "326c0c924a162c8637b8fa392add7c8c98f64f5194f3d2591caf88d7b0b956bd",
|
183
|
+
"proof": [
|
184
|
+
{
|
185
|
+
"left": "c108bfba805d899faa0ef53b0c064fad650249f6a648fec2bc79fd563106b1f8"
|
186
|
+
}
|
187
|
+
],
|
188
|
+
"anchors": [
|
189
|
+
{
|
190
|
+
"type": "BTCOpReturn",
|
191
|
+
"sourceId": "579dce214fe0242e3397c9a988622f35b5ee91cef5068b0895a0bbe2c7797b9a"
|
192
|
+
}
|
193
|
+
]
|
194
|
+
}
|
136
195
|
```
|
137
196
|
|
197
|
+
You can validate this JSON representation of the receipt by
|
198
|
+
submitting it to the [Tierion validation](https://tierion.com/validate)
|
199
|
+
web page.
|
200
|
+
|
201
|
+
## TODO
|
202
|
+
|
203
|
+
- Add blockchain receipt subscription functionality.
|
204
|
+
|
205
|
+
|
138
206
|
## Development
|
139
207
|
|
140
208
|
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
data/Rakefile
CHANGED
data/lib/tierion.rb
CHANGED
@@ -7,8 +7,4 @@ require 'httparty'
|
|
7
7
|
require 'hashie'
|
8
8
|
|
9
9
|
require 'tierion/version'
|
10
|
-
require 'tierion/
|
11
|
-
require 'tierion/hashitem_receipt'
|
12
|
-
require 'tierion/blockchain_receipt'
|
13
|
-
require 'tierion/blockchain_receipt_header'
|
14
|
-
require 'tierion/blockchain_receipt_target'
|
10
|
+
require 'tierion/hash_api'
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Tierion
|
2
|
+
module HashApi
|
3
|
+
class Client
|
4
|
+
include ::HTTParty
|
5
|
+
base_uri 'https://hashapi.tierion.com/v1'
|
6
|
+
|
7
|
+
default_timeout 5
|
8
|
+
open_timeout 5
|
9
|
+
# debug_output $stdout
|
10
|
+
|
11
|
+
attr_accessor :hash_items
|
12
|
+
|
13
|
+
def initialize(uname = ENV['TIERION_USERNAME'], pwd = ENV['TIERION_PASSWORD'])
|
14
|
+
@auth = { username: uname, password: pwd }
|
15
|
+
@access_token = nil
|
16
|
+
@expires_at = Time.now.utc - 1
|
17
|
+
@refresh_token = nil
|
18
|
+
@hash_items = []
|
19
|
+
auth
|
20
|
+
end
|
21
|
+
|
22
|
+
def auth
|
23
|
+
options = { body: @auth }
|
24
|
+
response = self.class.post('/auth/token', options)
|
25
|
+
|
26
|
+
if response.success?
|
27
|
+
extract_auth_tokens(response)
|
28
|
+
else
|
29
|
+
raise_error(response)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def auth_refresh
|
34
|
+
if expired_auth?
|
35
|
+
options = { body: { 'refreshToken' => @refresh_token } }
|
36
|
+
response = self.class.post('/auth/refresh', options)
|
37
|
+
|
38
|
+
if response.success?
|
39
|
+
extract_auth_tokens(response)
|
40
|
+
else
|
41
|
+
raise_error(response)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
auth
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def send(hash)
|
49
|
+
unless hash =~ /^[a-f0-9]{64}$/
|
50
|
+
raise ArgumentError, 'is not a valid SHA256 hex hash string'
|
51
|
+
end
|
52
|
+
|
53
|
+
auth_refresh unless logged_in?
|
54
|
+
options = {
|
55
|
+
body: { 'hash' => hash },
|
56
|
+
headers: { 'Authorization' => "Bearer #{@access_token}" }
|
57
|
+
}
|
58
|
+
response = self.class.post('/hashitems', options)
|
59
|
+
|
60
|
+
if response.success?
|
61
|
+
parsed = response.parsed_response
|
62
|
+
Hashie.symbolize_keys!(parsed)
|
63
|
+
parsed.merge!({hash: hash})
|
64
|
+
h = Tierion::HashApi::HashItem.new(parsed)
|
65
|
+
@hash_items << h
|
66
|
+
return h
|
67
|
+
else
|
68
|
+
raise_error(response)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get a Receipt for each HashItem that doesn't have one
|
73
|
+
# and return the collection of Receipts.
|
74
|
+
def receipts
|
75
|
+
@hash_items.each do |h|
|
76
|
+
next if h.receipt.present?
|
77
|
+
h.receipt = receipt(h)
|
78
|
+
end
|
79
|
+
|
80
|
+
@hash_items.collect(&:receipt).compact
|
81
|
+
end
|
82
|
+
|
83
|
+
# Retrieve the receipt for a specific HashItem
|
84
|
+
def receipt(h)
|
85
|
+
unless h.is_a?(Tierion::HashApi::HashItem)
|
86
|
+
raise ArgumentError, 'is not a Tierion::HashApi::HashItem object'
|
87
|
+
end
|
88
|
+
|
89
|
+
auth_refresh unless logged_in?
|
90
|
+
options = { headers: { 'Authorization' => "Bearer #{@access_token}" } }
|
91
|
+
response = self.class.get("/receipts/#{h.id}", options)
|
92
|
+
|
93
|
+
if response.success? && response.parsed_response['receipt'].present?
|
94
|
+
receipt = JSON.parse(response.parsed_response['receipt'])
|
95
|
+
Hashie.symbolize_keys!(receipt)
|
96
|
+
|
97
|
+
if receipt.key?(:type) || receipt.key?('@type')
|
98
|
+
Tierion::HashApi::Receipt.new(receipt)
|
99
|
+
else
|
100
|
+
raise 'Invalid Receipt found'
|
101
|
+
end
|
102
|
+
else
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def logged_in?
|
108
|
+
@access_token.present? &&
|
109
|
+
@refresh_token.present? &&
|
110
|
+
@expires_at >= Time.now.utc
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def raise_error(response)
|
116
|
+
if response['error'].present?
|
117
|
+
raise response['error']
|
118
|
+
else
|
119
|
+
raise 'Unknown Fatal Error'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def expired_auth?
|
125
|
+
@access_token.present? &&
|
126
|
+
@refresh_token.present? &&
|
127
|
+
@expires_at < Time.now.utc
|
128
|
+
end
|
129
|
+
|
130
|
+
def extract_auth_tokens(resp)
|
131
|
+
if resp &&
|
132
|
+
resp.parsed_response &&
|
133
|
+
resp.parsed_response.is_a?(Hash) &&
|
134
|
+
resp.parsed_response.key?('access_token') &&
|
135
|
+
resp.parsed_response.key?('refresh_token') &&
|
136
|
+
resp.parsed_response.key?('expires_in')
|
137
|
+
@access_token = resp.parsed_response['access_token']
|
138
|
+
@refresh_token = resp.parsed_response['refresh_token']
|
139
|
+
@expires_at = Time.now.utc + resp.parsed_response['expires_in']
|
140
|
+
return true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tierion
|
2
|
+
module HashApi
|
3
|
+
class HashItem < Hashie::Dash
|
4
|
+
include Hashie::Extensions::Dash::PropertyTranslation
|
5
|
+
|
6
|
+
property :hash, required: true
|
7
|
+
property :id, from: :receiptId, required: true
|
8
|
+
property :timestamp, required: true
|
9
|
+
property :receipt, required: false, default: nil
|
10
|
+
|
11
|
+
def time
|
12
|
+
timestamp.is_a?(Integer) ? Time.at(timestamp).utc : nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Tierion
|
2
|
+
module HashApi
|
3
|
+
class Receipt < Hash
|
4
|
+
include Hashie::Extensions::MergeInitializer
|
5
|
+
include Hashie::Extensions::MethodAccess
|
6
|
+
include Hashie::Extensions::IndifferentAccess
|
7
|
+
|
8
|
+
def to_pretty_json
|
9
|
+
puts JSON.pretty_generate(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def confirmations
|
13
|
+
get_confirmations
|
14
|
+
@confs
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def get_confirmations
|
20
|
+
@confs = {} if @confs.blank?
|
21
|
+
|
22
|
+
return {} if anchors.blank?
|
23
|
+
|
24
|
+
anchors.each do |a|
|
25
|
+
# allready confirmed this anchor
|
26
|
+
next if @confs[a['type']].is_a?(TrueClass)
|
27
|
+
|
28
|
+
case a['type']
|
29
|
+
when 'BTCOpReturn'
|
30
|
+
# txn_id
|
31
|
+
if a['sourceId'].present?
|
32
|
+
@confs[a['type']] = btc_op_return_confirmed?(a['sourceId'])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@confs
|
38
|
+
end
|
39
|
+
|
40
|
+
# Confirm Bitcoin OP_RETURN anchor
|
41
|
+
def btc_op_return_confirmed?(source_id)
|
42
|
+
url = "https://blockchain.info/tx-index/#{source_id}?format=json"
|
43
|
+
|
44
|
+
# op_return values begin with 0x6a (op_return code) &
|
45
|
+
# 0x20 (length in hex : 32 bytes)
|
46
|
+
op_return = ['6a20', merkleRoot].join('')
|
47
|
+
|
48
|
+
response = HTTParty.get(url)
|
49
|
+
|
50
|
+
if response.success? && response['out'].present?
|
51
|
+
has_op_return = response['out'].any? do |o|
|
52
|
+
o['script'].present? && o['script'] == op_return
|
53
|
+
end
|
54
|
+
|
55
|
+
return has_op_return
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/tierion/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tierion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Glenn Rempe
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
zieXiXZSAojfFx9g91fKdIrlPbInHU/BaCxXSLBwvOM0drE+c2ue9X8gB55XAhzX
|
31
31
|
37oBiw==
|
32
32
|
-----END CERTIFICATE-----
|
33
|
-
date: 2016-08-
|
33
|
+
date: 2016-08-06 00:00:00.000000000 Z
|
34
34
|
dependencies:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: httparty
|
@@ -154,11 +154,10 @@ files:
|
|
154
154
|
- certs/gem-public_cert_grempe.pem
|
155
155
|
- exe/tierion
|
156
156
|
- lib/tierion.rb
|
157
|
-
- lib/tierion/
|
158
|
-
- lib/tierion/
|
159
|
-
- lib/tierion/
|
160
|
-
- lib/tierion/
|
161
|
-
- lib/tierion/hashitem_receipt.rb
|
157
|
+
- lib/tierion/hash_api.rb
|
158
|
+
- lib/tierion/hash_api/client.rb
|
159
|
+
- lib/tierion/hash_api/hash_item.rb
|
160
|
+
- lib/tierion/hash_api/receipt.rb
|
162
161
|
- lib/tierion/version.rb
|
163
162
|
- tierion.gemspec
|
164
163
|
homepage: https://github.com/grempe/tierion
|
metadata.gz.sig
CHANGED
Binary file
|
@@ -1,62 +0,0 @@
|
|
1
|
-
module Tierion
|
2
|
-
class BlockchainReceipt < Hashie::Dash
|
3
|
-
include Hashie::Extensions::Dash::PropertyTranslation
|
4
|
-
|
5
|
-
property :header, required: true, transform_with: ->(v) {
|
6
|
-
BlockchainReceiptHeader.new(v)
|
7
|
-
}
|
8
|
-
|
9
|
-
property :target, required: true, transform_with: ->(v) {
|
10
|
-
BlockchainReceiptTarget.new(v)
|
11
|
-
}
|
12
|
-
|
13
|
-
property :extra, required: false, default: []
|
14
|
-
|
15
|
-
property :blockchain_info_confirmation, required: false, default: nil
|
16
|
-
|
17
|
-
# Output clean JSON in a format that works with the Blockchain
|
18
|
-
# Receipt validator at https://tierion.com/validate
|
19
|
-
def to_pretty_json
|
20
|
-
puts JSON.pretty_generate(self)
|
21
|
-
end
|
22
|
-
|
23
|
-
# Recalculate the merkle tree to ensure the receipt is valid
|
24
|
-
def valid?
|
25
|
-
# TODO
|
26
|
-
end
|
27
|
-
|
28
|
-
# Make an API call to check if the tx_id is a confirmed Transaction
|
29
|
-
# on the Blockchain and contains the expected OP_RETURN value with
|
30
|
-
# the merkle_root from this receipt.
|
31
|
-
def confirmed?
|
32
|
-
return false if header.blank? || header.tx_id.blank?
|
33
|
-
return false if header.merkle_root.blank?
|
34
|
-
response = HTTParty.get(confirmation_url_json)
|
35
|
-
|
36
|
-
if response.success? && response['out'].present?
|
37
|
-
# op_return values begin with 0x6a (op_return code) &
|
38
|
-
# 0x20 (hex length in bytes of string)
|
39
|
-
expected_op_return_value = ['6a20', header.merkle_root].join('')
|
40
|
-
confirmed = response['out'].any? do |o|
|
41
|
-
o['script'].present? && o['script'] == expected_op_return_value
|
42
|
-
end
|
43
|
-
|
44
|
-
# store the parsed output from blockchain.info
|
45
|
-
self.blockchain_info_confirmation = response.parsed_response if confirmed
|
46
|
-
return confirmed
|
47
|
-
else
|
48
|
-
false
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def confirmation_url
|
53
|
-
return nil if header.blank? || header.tx_id.blank?
|
54
|
-
"https://blockchain.info/tx-index/#{header.tx_id}"
|
55
|
-
end
|
56
|
-
|
57
|
-
def confirmation_url_json
|
58
|
-
return nil if header.blank? || header.tx_id.blank?
|
59
|
-
"#{confirmation_url}?format=json"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,11 +0,0 @@
|
|
1
|
-
module Tierion
|
2
|
-
class BlockchainReceiptHeader < Hashie::Dash
|
3
|
-
include Hashie::Extensions::Dash::PropertyTranslation
|
4
|
-
|
5
|
-
property :chainpoint_version, required: true
|
6
|
-
property :hash_type, required: true
|
7
|
-
property :merkle_root, required: true
|
8
|
-
property :tx_id, required: true
|
9
|
-
property :timestamp, required: true
|
10
|
-
end
|
11
|
-
end
|
data/lib/tierion/hashitem.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
module Tierion
|
2
|
-
class Hashitem
|
3
|
-
include ::HTTParty
|
4
|
-
base_uri 'https://hashapi.tierion.com/v1'
|
5
|
-
|
6
|
-
default_timeout 5
|
7
|
-
open_timeout 5
|
8
|
-
# debug_output $stdout
|
9
|
-
|
10
|
-
attr_reader :receipts
|
11
|
-
attr_accessor :debug
|
12
|
-
|
13
|
-
def initialize(uname = ENV['TIERION_USERNAME'], pwd = ENV['TIERION_PASSWORD'])
|
14
|
-
@auth = { username: uname, password: pwd }
|
15
|
-
@access_token = nil
|
16
|
-
@expires_at = Time.now.utc - 1
|
17
|
-
@refresh_token = nil
|
18
|
-
@receipts = []
|
19
|
-
auth
|
20
|
-
end
|
21
|
-
|
22
|
-
def auth
|
23
|
-
options = { body: @auth }
|
24
|
-
response = self.class.post('/auth/token', options)
|
25
|
-
|
26
|
-
if response.success?
|
27
|
-
extract_auth_tokens(response)
|
28
|
-
else
|
29
|
-
raise_error(response)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def auth_refresh
|
34
|
-
if expired_auth?
|
35
|
-
options = { body: { 'refreshToken' => @refresh_token } }
|
36
|
-
response = self.class.post('/auth/refresh', options)
|
37
|
-
|
38
|
-
if response.success?
|
39
|
-
extract_auth_tokens(response)
|
40
|
-
else
|
41
|
-
raise_error(response)
|
42
|
-
end
|
43
|
-
else
|
44
|
-
auth
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def send(hash)
|
49
|
-
unless hash =~ /^[a-f0-9]{64}$/
|
50
|
-
raise ArgumentError, 'is not a valid SHA256 hex hash string'
|
51
|
-
end
|
52
|
-
|
53
|
-
auth_refresh unless logged_in?
|
54
|
-
options = {
|
55
|
-
body: { 'hash' => hash },
|
56
|
-
headers: { 'Authorization' => "Bearer #{@access_token}" }
|
57
|
-
}
|
58
|
-
response = self.class.post('/hashitems', options)
|
59
|
-
|
60
|
-
if response.success?
|
61
|
-
parsed = response.parsed_response
|
62
|
-
Hashie.symbolize_keys!(parsed)
|
63
|
-
hir = HashitemReceipt.new(parsed)
|
64
|
-
@receipts << hir
|
65
|
-
return hir
|
66
|
-
else
|
67
|
-
raise_error(response)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Retrieve and store the BlockchainReceipt from the API for each
|
72
|
-
# HashitemReceipt that does not have one.
|
73
|
-
def blockchain_receipts
|
74
|
-
@receipts.each do |hir|
|
75
|
-
next if hir.blockchain_receipt.is_a?(Tierion::BlockchainReceipt)
|
76
|
-
bcr = blockchain_receipt(hir)
|
77
|
-
hir.blockchain_receipt = bcr
|
78
|
-
end
|
79
|
-
|
80
|
-
@receipts.collect(&:blockchain_receipt).compact
|
81
|
-
end
|
82
|
-
|
83
|
-
# Retrieve the blockchain receipt for a specific HashitemReceipt ID
|
84
|
-
def blockchain_receipt(hir)
|
85
|
-
unless hir.is_a?(Tierion::HashitemReceipt)
|
86
|
-
raise ArgumentError, 'is not a HashitemReceipt object'
|
87
|
-
end
|
88
|
-
|
89
|
-
auth_refresh unless logged_in?
|
90
|
-
options = { headers: { 'Authorization' => "Bearer #{@access_token}" } }
|
91
|
-
response = self.class.get("/receipts/#{hir.id}", options)
|
92
|
-
|
93
|
-
if response.success? && response.parsed_response['receipt'].present?
|
94
|
-
receipt = JSON.parse(response.parsed_response['receipt'])
|
95
|
-
Hashie.symbolize_keys!(receipt)
|
96
|
-
BlockchainReceipt.new(receipt)
|
97
|
-
else
|
98
|
-
return nil
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
-
|
104
|
-
def raise_error(response)
|
105
|
-
if response['error'].present?
|
106
|
-
raise response['error']
|
107
|
-
else
|
108
|
-
raise 'Unknown Fatal Error'
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def logged_in?
|
113
|
-
@access_token.present? &&
|
114
|
-
@refresh_token.present? &&
|
115
|
-
@expires_at >= Time.now.utc
|
116
|
-
end
|
117
|
-
|
118
|
-
def expired_auth?
|
119
|
-
@access_token.present? &&
|
120
|
-
@refresh_token.present? &&
|
121
|
-
@expires_at < Time.now.utc
|
122
|
-
end
|
123
|
-
|
124
|
-
def extract_auth_tokens(resp)
|
125
|
-
if resp &&
|
126
|
-
resp.parsed_response &&
|
127
|
-
resp.parsed_response.is_a?(Hash) &&
|
128
|
-
resp.parsed_response.key?('access_token') &&
|
129
|
-
resp.parsed_response.key?('refresh_token') &&
|
130
|
-
resp.parsed_response.key?('expires_in')
|
131
|
-
@access_token = resp.parsed_response['access_token']
|
132
|
-
@refresh_token = resp.parsed_response['refresh_token']
|
133
|
-
@expires_at = Time.now.utc + resp.parsed_response['expires_in']
|
134
|
-
return true
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module Tierion
|
2
|
-
class HashitemReceipt < Hashie::Dash
|
3
|
-
include Hashie::Extensions::Dash::PropertyTranslation
|
4
|
-
|
5
|
-
property :id, from: :receiptId, required: true
|
6
|
-
property :timestamp, required: true
|
7
|
-
property :blockchain_receipt, required: false, default: nil
|
8
|
-
|
9
|
-
def time
|
10
|
-
timestamp.is_a?(Integer) ? Time.at(timestamp).utc : nil
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|