tierion 0.1.0 → 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 +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
|