glueby 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +4 -0
- data/lib/glueby/contract/active_record/timestamp.rb +140 -14
- data/lib/glueby/contract/errors.rb +4 -0
- data/lib/glueby/contract/timestamp/tx_builder/simple.rb +97 -0
- data/lib/glueby/contract/timestamp/tx_builder/trackable.rb +23 -0
- data/lib/glueby/contract/timestamp/tx_builder/updating_trackable.rb +28 -0
- data/lib/glueby/contract/timestamp/tx_builder.rb +14 -0
- data/lib/glueby/contract/timestamp.rb +36 -92
- data/lib/glueby/contract.rb +0 -1
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +25 -0
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +45 -0
- data/lib/glueby/internal/wallet.rb +8 -0
- data/lib/glueby/version.rb +1 -1
- data/lib/tasks/glueby/contract/timestamp.rake +0 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53718ece4b629ff6474c139b047e08c4803739cb992b61aa4f49c10ca96bc90f
|
4
|
+
data.tar.gz: 774199828793ab8c00c3b5d3a501ffa5e265395e7e036407d7ef0edd7f798998
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89cb7a6e1a9573bd840811bf75da2d83f9a77eaf1d265e0ba617093e788eeb785f23d80ea67e934714f2773565d26065a4a3daa56bc8d544cc13d2bbd66f3049
|
7
|
+
data.tar.gz: d1616df8862ede1f1046119f321b22faf8dd7f6193d4a14ca7a38388d60c6f9e5ac97408480b4c38c2d817036424aeba9f2c50574c655b7ac7f9f3e0522a0cac
|
@@ -9,6 +9,10 @@ class CreateTimestamp < ActiveRecord::Migration<%= migration_version %>
|
|
9
9
|
t.integer :timestamp_type, null: false, default: 0
|
10
10
|
t.string :p2c_address
|
11
11
|
t.string :payment_base
|
12
|
+
t.bigint :prev_id
|
13
|
+
t.boolean :latest, null: false, default: true
|
12
14
|
end
|
15
|
+
|
16
|
+
add_index :glueby_timestamps, [:prev_id], unique: true
|
13
17
|
end
|
14
18
|
end
|
@@ -3,35 +3,94 @@ module Glueby
|
|
3
3
|
module AR
|
4
4
|
class Timestamp < ::ActiveRecord::Base
|
5
5
|
include Glueby::GluebyLogger
|
6
|
-
include Glueby::Contract::Timestamp::Util
|
7
|
-
include Glueby::Contract::TxBuilder
|
8
6
|
|
9
7
|
enum status: { init: 0, unconfirmed: 1, confirmed: 2 }
|
10
8
|
enum timestamp_type: { simple: 0, trackable: 1 }
|
11
9
|
|
10
|
+
attr_reader :tx
|
11
|
+
|
12
|
+
belongs_to :prev, class_name: 'Glueby::Contract::AR::Timestamp'
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def digest_content(content, digest)
|
16
|
+
case digest&.downcase
|
17
|
+
when :sha256
|
18
|
+
Tapyrus.sha256(content).bth
|
19
|
+
when :double_sha256
|
20
|
+
Tapyrus.double_sha256(content).bth
|
21
|
+
when :none
|
22
|
+
content
|
23
|
+
else
|
24
|
+
raise Glueby::Contract::Errors::UnsupportedDigestType
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
12
29
|
# @param [Hash] attributes attributes which consist of:
|
13
30
|
# - wallet_id
|
14
31
|
# - content
|
15
32
|
# - prefix(optional)
|
16
33
|
# - timestamp_type(optional)
|
34
|
+
# @raise [Glueby::ArgumentError] If the timestamp_type is not in :simple or :trackable
|
17
35
|
def initialize(attributes = nil)
|
18
|
-
|
19
|
-
|
20
|
-
|
36
|
+
# Set content_hash from :content attribute
|
37
|
+
content_hash = Timestamp.digest_content(attributes[:content], attributes[:digest] || :sha256)
|
38
|
+
super(
|
39
|
+
wallet_id: attributes[:wallet_id],
|
40
|
+
content_hash: content_hash,
|
41
|
+
prefix: attributes[:prefix] ? attributes[:prefix] : '',
|
42
|
+
status: :init,
|
43
|
+
timestamp_type: attributes[:timestamp_type] || :simple,
|
44
|
+
prev_id: attributes[:prev_id]
|
45
|
+
)
|
46
|
+
rescue ::ArgumentError => e
|
47
|
+
raise Glueby::ArgumentError, e.message
|
21
48
|
end
|
22
49
|
|
23
50
|
# Return true if timestamp type is 'trackable' and output in timestamp transaction has not been spent yet, otherwise return false.
|
24
|
-
def latest
|
25
|
-
trackable?
|
51
|
+
def latest?
|
52
|
+
trackable? && attributes['latest']
|
53
|
+
end
|
54
|
+
alias_method :latest, :latest?
|
55
|
+
|
56
|
+
# Returns a UTXO that corresponds to the timestamp
|
57
|
+
# @return [Hash] UTXO
|
58
|
+
# - [String] script_pubkey A script pubkey hex string
|
59
|
+
# - [String] txid A txid
|
60
|
+
# - [Integer] vout An index of the tx
|
61
|
+
# - [Integer] amount A value of
|
62
|
+
def utxo
|
63
|
+
{
|
64
|
+
script_pubkey: Tapyrus::Script.parse_from_addr(p2c_address).to_hex,
|
65
|
+
txid: txid,
|
66
|
+
vout: Contract::Timestamp::TxBuilder::PAY_TO_CONTRACT_INPUT_INDEX,
|
67
|
+
amount: Glueby::Contract::Timestamp::P2C_DEFAULT_VALUE
|
68
|
+
}
|
26
69
|
end
|
27
70
|
|
28
71
|
# Broadcast and save timestamp
|
29
72
|
# @param [Glueby::Contract::FixedFeeEstimator] fee_estimator
|
73
|
+
# @param [Glueby::UtxoProvider] utxo_provider
|
30
74
|
# @return true if tapyrus transactions were broadcasted and the timestamp was updated successfully, otherwise false.
|
31
|
-
def save_with_broadcast(fee_estimator: Glueby::Contract::FixedFeeEstimator.new)
|
32
|
-
|
33
|
-
|
34
|
-
|
75
|
+
def save_with_broadcast(fee_estimator: Glueby::Contract::FixedFeeEstimator.new, utxo_provider: nil)
|
76
|
+
save_with_broadcast!(fee_estimator: fee_estimator, utxo_provider: utxo_provider)
|
77
|
+
rescue Errors::FailedToBroadcast, Errors::PrevTimestampNotFound, Errors::PrevTimestampIsNotTrackable => e
|
78
|
+
logger.error("failed to broadcast (id=#{id}, reason=#{e.message})")
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Broadcast and save timestamp, and it raises errors
|
83
|
+
# @param [Glueby::Contract::FixedFeeEstimator] fee_estimator
|
84
|
+
# @param [Glueby::UtxoProvider] utxo_provider
|
85
|
+
# @return true if tapyrus transactions were broadcasted and the timestamp was updated successfully
|
86
|
+
# @raise [Glueby::Contract::Errors::FailedToBroadcast] If the broadcasting is failure
|
87
|
+
# @raise [Glueby::Contract::Errors::PrevTimestampNotFound] If it is not available that the timestamp record which correspond with the prev_id attribute
|
88
|
+
# @raise [Glueby::Contract::Errors::PrevTimestampIsNotTrackable] If the timestamp record by prev_id is not trackable
|
89
|
+
def save_with_broadcast!(fee_estimator: Glueby::Contract::FixedFeeEstimator.new, utxo_provider: nil)
|
90
|
+
utxo_provider = Glueby::UtxoProvider.new if !utxo_provider && Glueby.configuration.use_utxo_provider?
|
91
|
+
|
92
|
+
funding_tx, tx, p2c_address, payment_base = create_txs(fee_estimator, utxo_provider)
|
93
|
+
|
35
94
|
if funding_tx
|
36
95
|
::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
|
37
96
|
wallet.internal_wallet.broadcast(funding_tx)
|
@@ -41,15 +100,82 @@ module Glueby
|
|
41
100
|
::ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
|
42
101
|
wallet.internal_wallet.broadcast(tx) do |tx|
|
43
102
|
assign_attributes(txid: tx.txid, status: :unconfirmed, p2c_address: p2c_address, payment_base: payment_base)
|
103
|
+
@tx = tx
|
44
104
|
save!
|
105
|
+
|
106
|
+
if update_trackable?
|
107
|
+
prev.latest = false
|
108
|
+
prev.save!
|
109
|
+
end
|
45
110
|
end
|
46
111
|
end
|
47
112
|
logger.info("timestamp tx was broadcasted (id=#{id}, txid=#{tx.txid})")
|
48
113
|
true
|
49
|
-
rescue
|
50
|
-
|
114
|
+
rescue ActiveRecord::RecordInvalid,
|
115
|
+
Tapyrus::RPC::Error,
|
116
|
+
Internal::Wallet::Errors::WalletAlreadyLoaded,
|
117
|
+
Internal::Wallet::Errors::WalletNotFound,
|
118
|
+
Errors::InsufficientFunds => e
|
51
119
|
errors.add(:base, "failed to broadcast (id=#{id}, reason=#{e.message})")
|
52
|
-
|
120
|
+
raise Errors::FailedToBroadcast, "failed to broadcast (id=#{id}, reason=#{e.message})"
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def wallet
|
126
|
+
@wallet ||= Glueby::Wallet.load(wallet_id)
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_txs(fee_estimator, utxo_provider)
|
130
|
+
builder = builder_class.new(wallet, fee_estimator)
|
131
|
+
|
132
|
+
if builder.instance_of?(Contract::Timestamp::TxBuilder::UpdatingTrackable)
|
133
|
+
unless prev
|
134
|
+
message = "The previous timestamp(id: #{prev_id}) not found."
|
135
|
+
errors.add(:prev_id, message)
|
136
|
+
raise Errors::PrevTimestampNotFound, message
|
137
|
+
end
|
138
|
+
|
139
|
+
unless prev.trackable?
|
140
|
+
message = "The previous timestamp(id: #{prev_id}) type must be trackable"
|
141
|
+
errors.add(:prev_id, message)
|
142
|
+
raise Errors::PrevTimestampIsNotTrackable, message
|
143
|
+
end
|
144
|
+
|
145
|
+
builder.set_prev_timestamp_info(
|
146
|
+
timestamp_utxo: prev.utxo,
|
147
|
+
payment_base: prev.payment_base,
|
148
|
+
prefix: prev.prefix,
|
149
|
+
data: prev.content_hash
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
tx = builder.set_data(prefix, content_hash)
|
154
|
+
.set_inputs(utxo_provider)
|
155
|
+
.build
|
156
|
+
|
157
|
+
if builder.instance_of?(Contract::Timestamp::TxBuilder::Simple)
|
158
|
+
[builder.funding_tx, tx, nil, nil]
|
159
|
+
else
|
160
|
+
[builder.funding_tx, tx, builder.p2c_address, builder.payment_base]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def builder_class
|
165
|
+
case timestamp_type.to_sym
|
166
|
+
when :simple
|
167
|
+
Contract::Timestamp::TxBuilder::Simple
|
168
|
+
when :trackable
|
169
|
+
if prev_id
|
170
|
+
Contract::Timestamp::TxBuilder::UpdatingTrackable
|
171
|
+
else
|
172
|
+
Contract::Timestamp::TxBuilder::Trackable
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def update_trackable?
|
178
|
+
trackable? && prev_id
|
53
179
|
end
|
54
180
|
end
|
55
181
|
end
|
@@ -4,8 +4,10 @@ module Glueby
|
|
4
4
|
class InsufficientFunds < Error; end
|
5
5
|
class InsufficientTokens < Error; end
|
6
6
|
class TxAlreadyBroadcasted < Error; end
|
7
|
+
class FailedToBroadcast < Error; end
|
7
8
|
|
8
9
|
# Argument Errors
|
10
|
+
class ArgumentError < ArgumentError; end
|
9
11
|
class InvalidAmount < ArgumentError; end
|
10
12
|
class InvalidSplit < ArgumentError; end
|
11
13
|
class InvalidTokenType < ArgumentError; end
|
@@ -13,6 +15,8 @@ module Glueby
|
|
13
15
|
class UnsupportedTokenType < ArgumentError; end
|
14
16
|
class UnknownScriptPubkey < ArgumentError; end
|
15
17
|
class UnsupportedDigestType < ArgumentError; end
|
18
|
+
class PrevTimestampNotFound < ArgumentError; end
|
19
|
+
class PrevTimestampIsNotTrackable < ArgumentError; end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
class Timestamp
|
4
|
+
module TxBuilder
|
5
|
+
# The simple Timestamp method
|
6
|
+
class Simple
|
7
|
+
include Glueby::Contract::TxBuilder
|
8
|
+
|
9
|
+
attr_reader :funding_tx
|
10
|
+
|
11
|
+
def initialize(wallet, fee_estimator)
|
12
|
+
@wallet = wallet
|
13
|
+
@fee_estimator = fee_estimator
|
14
|
+
|
15
|
+
@txb = Tapyrus::TxBuilder.new
|
16
|
+
@prev_txs = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
@txb.fee(fee).change_address(@wallet.internal_wallet.change_address)
|
21
|
+
sign_tx
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_data(prefix, data)
|
25
|
+
@prefix = prefix
|
26
|
+
@data = data
|
27
|
+
|
28
|
+
@txb.data(prefix + data)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_inputs(utxo_provider)
|
33
|
+
if utxo_provider
|
34
|
+
script_pubkey = Tapyrus::Script.parse_from_addr(@wallet.internal_wallet.receive_address)
|
35
|
+
@funding_tx, index = utxo_provider.get_utxo(script_pubkey, fee)
|
36
|
+
|
37
|
+
utxo = {
|
38
|
+
script_pubkey: @funding_tx.outputs[index].script_pubkey.to_hex,
|
39
|
+
txid: @funding_tx.txid,
|
40
|
+
vout: index,
|
41
|
+
amount: funding_tx.outputs[index].value
|
42
|
+
}
|
43
|
+
|
44
|
+
@txb.add_utxo(to_tapyrusrb_utxo_hash(utxo))
|
45
|
+
@prev_txs << to_sign_tx_utxo_hash(utxo)
|
46
|
+
else
|
47
|
+
_, outputs = @wallet.internal_wallet.collect_uncolored_outputs(fee)
|
48
|
+
outputs.each do |utxo|
|
49
|
+
@txb.add_utxo(to_tapyrusrb_utxo_hash(utxo))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def fee
|
58
|
+
@fee ||= @fee_estimator.fee(dummy_tx(@txb.build))
|
59
|
+
end
|
60
|
+
|
61
|
+
def sign_tx
|
62
|
+
# General signing process skips signing to p2c inputs because no key of the p2c address in the wallet.
|
63
|
+
@wallet.internal_wallet.sign_tx(@txb.build, @prev_txs)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param utxo
|
67
|
+
# @option utxo [String] :txid The txid
|
68
|
+
# @option utxo [Integer] :vout The index of the output in the tx
|
69
|
+
# @option utxo [Integer] :amount The value of the output
|
70
|
+
# @option utxo [String] :script_pubkey The hex string of the script pubkey
|
71
|
+
def to_tapyrusrb_utxo_hash(utxo)
|
72
|
+
{
|
73
|
+
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
74
|
+
txid: utxo[:txid],
|
75
|
+
index: utxo[:vout],
|
76
|
+
value: utxo[:amount]
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param utxo
|
81
|
+
# @option utxo [String] :txid The txid
|
82
|
+
# @option utxo [Integer] :vout The index of the output in the tx
|
83
|
+
# @option utxo [Integer] :amount The value of the output
|
84
|
+
# @option utxo [String] :script_pubkey The hex string of the script pubkey
|
85
|
+
def to_sign_tx_utxo_hash(utxo)
|
86
|
+
{
|
87
|
+
scriptPubKey: utxo[:script_pubkey],
|
88
|
+
txid: utxo[:txid],
|
89
|
+
vout: utxo[:vout],
|
90
|
+
amount: utxo[:amount]
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
class Timestamp
|
4
|
+
module TxBuilder
|
5
|
+
class Trackable < Simple
|
6
|
+
attr_reader :p2c_address, :payment_base
|
7
|
+
|
8
|
+
# @override
|
9
|
+
def set_data(prefix, data)
|
10
|
+
@prefix = prefix
|
11
|
+
@data = data
|
12
|
+
|
13
|
+
# Create a new trackable timestamp
|
14
|
+
@p2c_address, @payment_base = @wallet.internal_wallet
|
15
|
+
.create_pay_to_contract_address([prefix, data].join)
|
16
|
+
@txb.pay(p2c_address, P2C_DEFAULT_VALUE)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
class Timestamp
|
4
|
+
module TxBuilder
|
5
|
+
class UpdatingTrackable < Trackable
|
6
|
+
def set_prev_timestamp_info(timestamp_utxo:, payment_base:, prefix:, data:)
|
7
|
+
@prev_timestamp_utxo = timestamp_utxo
|
8
|
+
@prev_payment_base = payment_base
|
9
|
+
@prev_prefix = prefix
|
10
|
+
@prev_data = data
|
11
|
+
@txb.add_utxo(to_tapyrusrb_utxo_hash(@prev_timestamp_utxo))
|
12
|
+
end
|
13
|
+
|
14
|
+
def sign_tx
|
15
|
+
tx = super
|
16
|
+
# Generates signature for the remain p2c input.
|
17
|
+
@wallet.internal_wallet.sign_to_pay_to_contract_address(
|
18
|
+
tx,
|
19
|
+
@prev_timestamp_utxo,
|
20
|
+
@prev_payment_base,
|
21
|
+
[@prev_prefix, @prev_data].join
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
class Timestamp
|
4
|
+
module TxBuilder
|
5
|
+
autoload :Simple, 'glueby/contract/timestamp/tx_builder/simple'
|
6
|
+
autoload :Trackable, 'glueby/contract/timestamp/tx_builder/trackable'
|
7
|
+
autoload :UpdatingTrackable, 'glueby/contract/timestamp/tx_builder/updating_trackable'
|
8
|
+
|
9
|
+
# A transaction input to update trackable timestamp must placed at this index in the tx inputs.
|
10
|
+
PAY_TO_CONTRACT_INPUT_INDEX = 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -8,87 +8,16 @@ module Glueby
|
|
8
8
|
#
|
9
9
|
# Storing timestamp transaction to the blockchain enables everyone to verify that the data existed at that time and a user signed it.
|
10
10
|
class Timestamp
|
11
|
-
include Glueby::Contract::TxBuilder
|
12
|
-
|
13
11
|
P2C_DEFAULT_VALUE = 1_000
|
14
12
|
|
15
13
|
autoload :Syncer, 'glueby/contract/timestamp/syncer'
|
16
|
-
|
17
|
-
module Util
|
18
|
-
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
19
|
-
module_function
|
20
|
-
|
21
|
-
# @param [Glueby::Wallet] wallet
|
22
|
-
# @param [Array] contents The data to be used for generating pay-to-contract address
|
23
|
-
# @return [pay-to-contract address, public key used for generating address]
|
24
|
-
def create_pay_to_contract_address(wallet, contents: nil)
|
25
|
-
pubkey = wallet.internal_wallet.create_pubkey.pubkey
|
26
|
-
# Calculate P + H(P || contents)G
|
27
|
-
group = ECDSA::Group::Secp256k1
|
28
|
-
p = Tapyrus::Key.new(pubkey: pubkey).to_point # P
|
29
|
-
commitment = Tapyrus.sha256(p.to_hex(true).htb + contents.join).bth.to_i(16) % group.order # H(P || contents)
|
30
|
-
point = p + group.generator.multiply_by_scalar(commitment) # P + H(P || contents)G
|
31
|
-
[Tapyrus::Key.new(pubkey: point.to_hex(true)).to_p2pkh, pubkey] # [p2c address, P]
|
32
|
-
end
|
33
|
-
|
34
|
-
def create_txs(wallet, prefix, data, fee_estimator, utxo_provider, type: :simple)
|
35
|
-
txb = Tapyrus::TxBuilder.new
|
36
|
-
if type == :simple
|
37
|
-
txb.data(prefix + data)
|
38
|
-
elsif type == :trackable
|
39
|
-
p2c_address, payment_base = create_pay_to_contract_address(wallet, contents: [prefix, data])
|
40
|
-
txb.pay(p2c_address, P2C_DEFAULT_VALUE)
|
41
|
-
end
|
42
|
-
|
43
|
-
fee = fee_estimator.fee(dummy_tx(txb.build))
|
44
|
-
if utxo_provider
|
45
|
-
script_pubkey = Tapyrus::Script.parse_from_addr(wallet.internal_wallet.receive_address)
|
46
|
-
funding_tx, index = utxo_provider.get_utxo(script_pubkey, fee)
|
47
|
-
txb.add_utxo({
|
48
|
-
script_pubkey: funding_tx.outputs[index].script_pubkey,
|
49
|
-
txid: funding_tx.txid,
|
50
|
-
index: index,
|
51
|
-
value: funding_tx.outputs[index].value
|
52
|
-
})
|
53
|
-
else
|
54
|
-
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
|
55
|
-
outputs.each do |utxo|
|
56
|
-
txb.add_utxo({
|
57
|
-
script_pubkey: Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb),
|
58
|
-
txid: utxo[:txid],
|
59
|
-
index: utxo[:vout],
|
60
|
-
value: utxo[:amount]
|
61
|
-
})
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
prev_txs = if funding_tx
|
66
|
-
output = funding_tx.outputs.first
|
67
|
-
[{
|
68
|
-
txid: funding_tx.txid,
|
69
|
-
vout: 0,
|
70
|
-
scriptPubKey: output.script_pubkey.to_hex,
|
71
|
-
amount: output.value
|
72
|
-
}]
|
73
|
-
else
|
74
|
-
[]
|
75
|
-
end
|
76
|
-
|
77
|
-
txb.fee(fee).change_address(wallet.internal_wallet.change_address)
|
78
|
-
[funding_tx, wallet.internal_wallet.sign_tx(txb.build, prev_txs), p2c_address, payment_base]
|
79
|
-
end
|
80
|
-
|
81
|
-
def get_transaction(tx)
|
82
|
-
Glueby::Internal::RPC.client.getrawtransaction(tx.txid, 1)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
include Glueby::Contract::Timestamp::Util
|
14
|
+
autoload :TxBuilder, 'glueby/contract/timestamp/tx_builder'
|
86
15
|
|
87
16
|
attr_reader :tx, :txid
|
88
|
-
|
89
17
|
# p2c_address and payment_base is used in `trackable` type
|
90
18
|
attr_reader :p2c_address, :payment_base
|
91
19
|
|
20
|
+
# @param [Gleuby::Wallet] wallet The wallet that is sender of the timestamp transaction.
|
92
21
|
# @param [String] content Data to be hashed and stored in blockchain.
|
93
22
|
# @param [String] prefix prefix of op_return data
|
94
23
|
# @param [Glueby::Contract::FeeEstimator] fee_estimator
|
@@ -99,6 +28,7 @@ module Glueby
|
|
99
28
|
# @param [Symbol] timestamp_type
|
100
29
|
# - :simple
|
101
30
|
# - :trackable
|
31
|
+
# @param [Integer] prev_timestamp_id The id column of glueby_timestamps that will be updated by the timestamp that will be created
|
102
32
|
# @raise [Glueby::Contract::Errors::UnsupportedDigestType] if digest is unsupported
|
103
33
|
# @raise [Glueby::Contract::Errors::InvalidTimestampType] if timestamp_type is unsupported
|
104
34
|
def initialize(
|
@@ -108,7 +38,8 @@ module Glueby
|
|
108
38
|
fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
|
109
39
|
digest: :sha256,
|
110
40
|
utxo_provider: nil,
|
111
|
-
timestamp_type: :simple
|
41
|
+
timestamp_type: :simple,
|
42
|
+
prev_timestamp_id: nil
|
112
43
|
)
|
113
44
|
@wallet = wallet
|
114
45
|
@content = content
|
@@ -119,33 +50,46 @@ module Glueby
|
|
119
50
|
@utxo_provider = utxo_provider
|
120
51
|
raise Glueby::Contract::Errors::InvalidTimestampType, "#{timestamp_type} is invalid type, supported types are :simple, and :trackable." unless [:simple, :trackable].include?(timestamp_type)
|
121
52
|
@timestamp_type = timestamp_type
|
53
|
+
@prev_timestamp_id = prev_timestamp_id
|
122
54
|
end
|
123
55
|
|
124
56
|
# broadcast to Tapyrus Core
|
125
57
|
# @return [String] txid
|
126
|
-
# @raise [TxAlreadyBroadcasted] if tx has been broadcasted.
|
127
|
-
# @raise [InsufficientFunds] if result of listunspent is not enough to pay the specified amount
|
58
|
+
# @raise [Glueby::Contract::Errors::TxAlreadyBroadcasted] if tx has been broadcasted.
|
59
|
+
# @raise [Glueby::Contract::Errors::InsufficientFunds] if result of listunspent is not enough to pay the specified amount
|
128
60
|
def save!
|
129
|
-
raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @
|
61
|
+
raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @ar
|
62
|
+
|
63
|
+
@ar = Glueby::Contract::AR::Timestamp.new(
|
64
|
+
wallet_id: @wallet.id,
|
65
|
+
prefix: @prefix,
|
66
|
+
content: @content,
|
67
|
+
timestamp_type: @timestamp_type,
|
68
|
+
digest: @digest,
|
69
|
+
prev_id: @prev_timestamp_id
|
70
|
+
)
|
71
|
+
@ar.save_with_broadcast!(fee_estimator: @fee_estimator, utxo_provider: @utxo_provider)
|
72
|
+
@ar.txid
|
73
|
+
end
|
130
74
|
|
131
|
-
|
132
|
-
|
133
|
-
@txid
|
75
|
+
def txid
|
76
|
+
raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
|
77
|
+
@ar.txid
|
134
78
|
end
|
135
79
|
|
136
|
-
|
80
|
+
def tx
|
81
|
+
raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
|
82
|
+
@ar.tx
|
83
|
+
end
|
84
|
+
|
85
|
+
def p2c_address
|
86
|
+
raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
|
87
|
+
@ar.p2c_address
|
88
|
+
end
|
137
89
|
|
138
|
-
def
|
139
|
-
|
140
|
-
|
141
|
-
Tapyrus.sha256(@content)
|
142
|
-
when :double_sha256
|
143
|
-
Tapyrus.double_sha256(@content)
|
144
|
-
when :none
|
145
|
-
@content
|
146
|
-
else
|
147
|
-
raise Glueby::Contract::Errors::UnsupportedDigestType
|
148
|
-
end
|
90
|
+
def payment_base
|
91
|
+
raise Glueby::Error, 'The timestamp tx is not broadcasted yet.' unless @ar
|
92
|
+
@ar.payment_base
|
149
93
|
end
|
150
94
|
end
|
151
95
|
end
|
data/lib/glueby/contract.rb
CHANGED
@@ -148,6 +148,31 @@ module Glueby
|
|
148
148
|
def get_addresses(wallet_id, label = nil)
|
149
149
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
150
150
|
end
|
151
|
+
|
152
|
+
# Create and returns pay to contract address
|
153
|
+
# @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
|
154
|
+
# @param [String] contents - The data to be used for generating pay-to-contract address
|
155
|
+
# @return [String] pay to contract P2PKH address
|
156
|
+
def create_pay_to_contract_address(wallet_id, contents)
|
157
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
158
|
+
end
|
159
|
+
|
160
|
+
# Sign to the pay to contract input
|
161
|
+
# @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method.
|
162
|
+
# @param [Tapyrus::Tx] tx - The tx that has pay to contract input in the inputs list
|
163
|
+
# @param [Hash] utxo - The utxo that indicates pay to contract output to be signed
|
164
|
+
# @option utxo [String] txid - Transaction id
|
165
|
+
# @option utxo [Integer] vout - Output index
|
166
|
+
# @option utxo [Integer] amount - The amount the output has
|
167
|
+
# @option utxo [String] script_pubkey - The script_pubkey hex string
|
168
|
+
# @param [String] payment_base - The public key that is used to generate pay to contract public key
|
169
|
+
# @param [String] contents - The data to be used for generating pay-to-contract address
|
170
|
+
# @param [Integer] sighashtype - The sighash flag for each signature that would be produced here.
|
171
|
+
# @return [Tapyrus::Tx]
|
172
|
+
# @raise [Glueby::Internal::Wallet::Errors::InvalidSighashType] when the specified sighashtype is invalid
|
173
|
+
def sign_to_pay_to_contract_address(wallet_id, tx, utxo, payment_base, contents, sighashtype: Tapyrus::SIGHASH_TYPE[:all])
|
174
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
175
|
+
end
|
151
176
|
end
|
152
177
|
end
|
153
178
|
end
|
@@ -153,6 +153,51 @@ module Glueby
|
|
153
153
|
keys = keys.where(label: label) if label
|
154
154
|
keys.map(&:address)
|
155
155
|
end
|
156
|
+
|
157
|
+
def create_pay_to_contract_address(wallet_id, contents)
|
158
|
+
# Calculate P + H(P || contents)G
|
159
|
+
group = ECDSA::Group::Secp256k1
|
160
|
+
pubkey = create_pubkey(wallet_id)
|
161
|
+
p = pubkey.to_point # P
|
162
|
+
commitment = create_pay_to_contract_commitment(pubkey, contents)
|
163
|
+
point = p + group.generator.multiply_by_scalar(commitment) # P + H(P || contents)G
|
164
|
+
[Tapyrus::Key.new(pubkey: point.to_hex(true)).to_p2pkh, pubkey.pubkey] # [p2c address, P]
|
165
|
+
end
|
166
|
+
|
167
|
+
def sign_to_pay_to_contract_address(wallet_id, tx, utxo, payment_base, contents)
|
168
|
+
key = create_pay_to_contract_private_key(wallet_id, payment_base, contents)
|
169
|
+
sighash = tx.sighash_for_input(utxo[:vout], Tapyrus::Script.parse_from_payload(utxo[:script_pubkey].htb))
|
170
|
+
|
171
|
+
sig = key.sign(sighash, algo: :schnorr) + [Tapyrus::SIGHASH_TYPE[:all]].pack('C')
|
172
|
+
script_sig = Tapyrus::Script.parse_from_payload(Tapyrus::Script.pack_pushdata(sig) + Tapyrus::Script.pack_pushdata(key.pubkey.htb))
|
173
|
+
tx.inputs[utxo[:vout]].script_sig = script_sig
|
174
|
+
tx
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
# Calculate commitment = H(P || contents)
|
180
|
+
# @param [Tapyrus::Key] pubkey The public key
|
181
|
+
# @param [String] contents
|
182
|
+
# @return Integer
|
183
|
+
def create_pay_to_contract_commitment(pubkey, contents)
|
184
|
+
group = ECDSA::Group::Secp256k1
|
185
|
+
p = pubkey.to_point # P
|
186
|
+
Tapyrus.sha256(p.to_hex(true).htb + contents).bth.to_i(16) % group.order # H(P || contents)
|
187
|
+
end
|
188
|
+
|
189
|
+
# @param [String] wallet_id
|
190
|
+
# @param [String] payment_base The public key hex string
|
191
|
+
# @param [String] contents
|
192
|
+
# @return [Tapyrus::Key] pay to contract private key
|
193
|
+
def create_pay_to_contract_private_key(wallet_id, payment_base, contents)
|
194
|
+
group = ECDSA::Group::Secp256k1
|
195
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
196
|
+
ar_key = wallet.keys.where(public_key: payment_base).first
|
197
|
+
key = Tapyrus::Key.new(pubkey: payment_base)
|
198
|
+
commitment = create_pay_to_contract_commitment(key, contents)
|
199
|
+
Tapyrus::Key.new(priv_key: ((ar_key.private_key.to_i(16) + commitment) % group.order).to_even_length_hex) # K + commitment
|
200
|
+
end
|
156
201
|
end
|
157
202
|
end
|
158
203
|
end
|
@@ -154,6 +154,14 @@ module Glueby
|
|
154
154
|
wallet_adapter.get_addresses(id, label)
|
155
155
|
end
|
156
156
|
|
157
|
+
def create_pay_to_contract_address(contents)
|
158
|
+
wallet_adapter.create_pay_to_contract_address(id, contents)
|
159
|
+
end
|
160
|
+
|
161
|
+
def sign_to_pay_to_contract_address(tx, utxo, payment_base, contents)
|
162
|
+
wallet_adapter.sign_to_pay_to_contract_address(id, tx, utxo, payment_base, contents)
|
163
|
+
end
|
164
|
+
|
157
165
|
private
|
158
166
|
|
159
167
|
def wallet_adapter
|
data/lib/glueby/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glueby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02-
|
11
|
+
date: 2022-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tapyrus
|
@@ -113,6 +113,10 @@ files:
|
|
113
113
|
- lib/glueby/contract/payment.rb
|
114
114
|
- lib/glueby/contract/timestamp.rb
|
115
115
|
- lib/glueby/contract/timestamp/syncer.rb
|
116
|
+
- lib/glueby/contract/timestamp/tx_builder.rb
|
117
|
+
- lib/glueby/contract/timestamp/tx_builder/simple.rb
|
118
|
+
- lib/glueby/contract/timestamp/tx_builder/trackable.rb
|
119
|
+
- lib/glueby/contract/timestamp/tx_builder/updating_trackable.rb
|
116
120
|
- lib/glueby/contract/token.rb
|
117
121
|
- lib/glueby/contract/tx_builder.rb
|
118
122
|
- lib/glueby/fee_provider.rb
|