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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cae8a0e06be77affdb1895724a26137b5fde2bab8cc27fb73d9aa45f2c351c5
4
- data.tar.gz: 2cc677f69e1cc1ad3e6468662e476b4bd5082b0e765f15f44b6b63426b2663f9
3
+ metadata.gz: 53718ece4b629ff6474c139b047e08c4803739cb992b61aa4f49c10ca96bc90f
4
+ data.tar.gz: 774199828793ab8c00c3b5d3a501ffa5e265395e7e036407d7ef0edd7f798998
5
5
  SHA512:
6
- metadata.gz: 3e0a80b28635f37a37b4d37eac3fc3bc697bfb3ce580a8c481c60e40fe7776bfcfcb24720d33771e5d73d19c745d03afbee7b380dedd3d0ec51b7258bbb9363b
7
- data.tar.gz: fd3d0edae5b99212cea7e4a710dd8f52173e1b707617beb881e7f9715afac575e76d5abc18b291f7defa62c7acb4c4e5772163a0953126a2f3fcfeb6a72aa953
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
- @content_hash = Tapyrus.sha256(attributes[:content]).bth
19
- super(wallet_id: attributes[:wallet_id], content_hash: @content_hash,
20
- prefix: attributes[:prefix] ? attributes[:prefix] : '', status: :init, timestamp_type: attributes[:timestamp_type] || :simple)
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
- utxo_provider = Glueby::UtxoProvider.new if Glueby.configuration.use_utxo_provider?
33
- wallet = Glueby::Wallet.load(wallet_id)
34
- funding_tx, tx, p2c_address, payment_base = create_txs(wallet, prefix, content_hash, fee_estimator, utxo_provider, type: timestamp_type.to_sym)
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 => e
50
- logger.error("failed to broadcast (id=#{id}, reason=#{e.message})")
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
- false
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 @txid
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
- funding_tx, @tx, @p2c_address, @payment_base = create_txs(@wallet, @prefix, digest_content, @fee_estimator, @utxo_provider, type: @timestamp_type)
132
- @wallet.internal_wallet.broadcast(funding_tx) if funding_tx
133
- @txid = @wallet.internal_wallet.broadcast(@tx)
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
- private
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 digest_content
139
- case @digest&.downcase
140
- when :sha256
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
@@ -8,6 +8,5 @@ module Glueby
8
8
  autoload :Token, 'glueby/contract/token'
9
9
  autoload :TxBuilder, 'glueby/contract/tx_builder'
10
10
  autoload :AR, 'glueby/contract/active_record'
11
- autoload :Wallet, 'glueby/contract/wallet'
12
11
  end
13
12
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Glueby
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -4,7 +4,6 @@ module Glueby
4
4
  module Timestamp
5
5
  module_function
6
6
  extend Glueby::Contract::TxBuilder
7
- extend Glueby::Contract::Timestamp::Util
8
7
 
9
8
  def create
10
9
  timestamps = Glueby::Contract::AR::Timestamp.where(status: :init)
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.9.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-17 00:00:00.000000000 Z
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