bitcoinrb-grpc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/README.md +66 -0
- data/Rakefile +6 -0
- data/bin/bitcoinrbd +60 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bitcoinrb-grpc.gemspec +36 -0
- data/lib/bitcoin/grpc.rb +35 -0
- data/lib/bitcoin/grpc/grpc_pb.rb +99 -0
- data/lib/bitcoin/grpc/grpc_services_pb.rb +26 -0
- data/lib/bitcoin/grpc/oap_service.rb +28 -0
- data/lib/bitcoin/grpc/server.rb +113 -0
- data/lib/bitcoin/grpc/version.rb +5 -0
- data/lib/bitcoin/wallet/asset_feature.rb +132 -0
- data/lib/bitcoin/wallet/asset_handler.rb +58 -0
- data/lib/bitcoin/wallet/publisher.rb +32 -0
- data/lib/bitcoin/wallet/signer.rb +71 -0
- data/lib/bitcoin/wallet/utxo_db.rb +163 -0
- data/lib/bitcoin/wallet/utxo_handler.rb +90 -0
- data/lib/extensions/bitcoin/rpc/request_handler.rb +99 -0
- data/lib/extensions/bitcoin/tx.rb +11 -0
- data/lib/extensions/bitcoin/wallet/base.rb +50 -0
- data/proto/bitcoin/grpc/grpc.proto +97 -0
- metadata +210 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# Source: bitcoin/grpc/grpc.proto for package 'bitcoin.grpc'
|
3
|
+
|
4
|
+
require 'grpc'
|
5
|
+
require 'bitcoin/grpc/grpc_pb'
|
6
|
+
|
7
|
+
module Bitcoin
|
8
|
+
module Grpc
|
9
|
+
module Blockchain
|
10
|
+
class Service
|
11
|
+
|
12
|
+
include GRPC::GenericService
|
13
|
+
|
14
|
+
self.marshal_class_method = :encode
|
15
|
+
self.unmarshal_class_method = :decode
|
16
|
+
self.service_name = 'bitcoin.grpc.Blockchain'
|
17
|
+
|
18
|
+
rpc :WatchTxConfirmed, WatchTxConfirmedRequest, stream(WatchTxConfirmedResponse)
|
19
|
+
rpc :WatchUtxo, WatchUtxoRequest, stream(WatchUtxoResponse)
|
20
|
+
rpc :WatchToken, WatchTokenRequest, stream(WatchTokenResponse)
|
21
|
+
end
|
22
|
+
|
23
|
+
Stub = Service.rpc_stub_class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'jsonclient'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Grpc
|
5
|
+
module OapService
|
6
|
+
def self.outputs_with_open_asset_id(tx_hash)
|
7
|
+
client = JSONClient.new
|
8
|
+
client.debug_dev = STDOUT
|
9
|
+
|
10
|
+
response = client.get("#{oae_url}#{tx_hash.rhex}?format=json")
|
11
|
+
response.body['outputs']
|
12
|
+
rescue RuntimeError => _
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.oae_url
|
17
|
+
case
|
18
|
+
when Bitcoin.chain_params.mainnet?
|
19
|
+
'https://www.oaexplorer.com/tx/'
|
20
|
+
when Bitcoin.chain_params.testnet?
|
21
|
+
'https://testnet.oaexplorer.com/tx/'
|
22
|
+
when Bitcoin.chain_params.regtest?
|
23
|
+
'http://localhost:9292/tx/'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Grpc
|
3
|
+
class Server < Bitcoin::Grpc::Blockchain::Service
|
4
|
+
def self.run(spv)
|
5
|
+
addr = "0.0.0.0:8080"
|
6
|
+
s = GRPC::RpcServer.new
|
7
|
+
s.add_http2_port(addr, :this_port_is_insecure)
|
8
|
+
s.handle(new(spv))
|
9
|
+
s.run_till_terminated
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :spv, :utxo_handler, :asset_handler, :publisher, :logger
|
13
|
+
|
14
|
+
def initialize(spv)
|
15
|
+
@spv = spv
|
16
|
+
@publisher = Bitcoin::Wallet::Publisher.spawn(:publisher)
|
17
|
+
@utxo_handler = Bitcoin::Wallet::UtxoHandler.spawn(:utxo_handler, spv, publisher)
|
18
|
+
@asset_handler = Bitcoin::Wallet::AssetHandler.spawn(:asset_handler, spv, publisher)
|
19
|
+
@logger = Bitcoin::Logger.create(:debug)
|
20
|
+
end
|
21
|
+
|
22
|
+
def watch_tx_confirmed(request, call)
|
23
|
+
logger.info("watch_tx_confirmed: #{request}")
|
24
|
+
utxo_handler << request
|
25
|
+
channel = Concurrent::Channel.new
|
26
|
+
Receiver.spawn(:receiver, channel, publisher, [Bitcoin::Grpc::EventTxConfirmed])
|
27
|
+
ResponseEnum.new(request, channel, WatchTxConfirmedResponseBuilder).each
|
28
|
+
end
|
29
|
+
|
30
|
+
def watch_utxo(request, call)
|
31
|
+
logger.info("watch_utxo: #{request}")
|
32
|
+
utxo_handler << request
|
33
|
+
channel = Concurrent::Channel.new
|
34
|
+
Receiver.spawn(:receiver, channel, publisher, [Bitcoin::Grpc::EventUtxoRegistered, Bitcoin::Grpc::EventUtxoSpent])
|
35
|
+
ResponseEnum.new(request, channel, WatchUtxoResponseBuilder).each
|
36
|
+
end
|
37
|
+
|
38
|
+
def watch_token(request, call)
|
39
|
+
logger.info("watch_token: #{request}")
|
40
|
+
utxo_handler << request
|
41
|
+
channel = Concurrent::Channel.new
|
42
|
+
Receiver.spawn(:receiver, channel, publisher, [Bitcoin::Grpc::EventTokenIssued, Bitcoin::Grpc::EventTokenTransfered])
|
43
|
+
ResponseEnum.new(request, channel, WatchTokenResponseBuilder).each
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class WatchTxConfirmedResponseBuilder
|
48
|
+
def self.build(event)
|
49
|
+
case event
|
50
|
+
when Bitcoin::Grpc::EventTxConfirmed
|
51
|
+
Bitcoin::Grpc::WatchTxConfirmedResponse.new(confirmed: event)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class WatchUtxoResponseBuilder
|
57
|
+
def self.build(event)
|
58
|
+
case event
|
59
|
+
when Bitcoin::Grpc::EventUtxoRegistered
|
60
|
+
Bitcoin::Grpc::WatchUtxoResponse.new(registered: event)
|
61
|
+
when Bitcoin::Grpc::EventUtxoSpent
|
62
|
+
Bitcoin::Grpc::WatchUtxoResponse.new(spent: event)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class WatchTokenResponseBuilder
|
68
|
+
def self.build(event)
|
69
|
+
case event
|
70
|
+
when Bitcoin::Grpc::EventTokenIssued
|
71
|
+
Bitcoin::Grpc::WatchTokenResponse.new(issued: event)
|
72
|
+
when Bitcoin::Grpc::EventTokenTransfered
|
73
|
+
Bitcoin::Grpc::WatchTokenResponse.new(transfered: event)
|
74
|
+
when Bitcoin::Grpc::EventTokenBurned
|
75
|
+
Bitcoin::Grpc::WatchTokenResponse.new(burned: event)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Receiver < Concurrent::Actor::Context
|
81
|
+
include Concurrent::Concern::Logging
|
82
|
+
|
83
|
+
attr_reader :channel
|
84
|
+
def initialize(channel, publisher, classes)
|
85
|
+
@channel = channel
|
86
|
+
classes.each {|c| publisher << [:subscribe, c] }
|
87
|
+
end
|
88
|
+
def on_message(message)
|
89
|
+
log(::Logger::DEBUG, "Receiver#on_message:#{message}")
|
90
|
+
channel << message
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class ResponseEnum
|
95
|
+
attr_reader :req, :channel, :wrapper_classs, :logger
|
96
|
+
|
97
|
+
def initialize(req, channel, wrapper_classs)
|
98
|
+
@req = req
|
99
|
+
@channel = channel
|
100
|
+
@wrapper_classs = wrapper_classs
|
101
|
+
@logger = Bitcoin::Logger.create(:debug)
|
102
|
+
end
|
103
|
+
|
104
|
+
def each
|
105
|
+
logger.info("ResponseEnum#each")
|
106
|
+
return enum_for(:each) unless block_given?
|
107
|
+
loop do
|
108
|
+
yield wrapper_classs.build(channel.take)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Wallet
|
3
|
+
module AssetFeature
|
4
|
+
module AssetType
|
5
|
+
OPEN_ASSETS = 1
|
6
|
+
end
|
7
|
+
|
8
|
+
KEY_PREFIX = {
|
9
|
+
asset_out_point: 'ao', # key: out_point, value AssetOutput
|
10
|
+
asset_script_pubkey: 'as', # key: asset_type, script_pubkey and out_point, value AssetOutput
|
11
|
+
asset_height: 'ah', # key: asset_type, block_height and out_point, value AssetOutput
|
12
|
+
}
|
13
|
+
|
14
|
+
def save_token(asset_type, asset_id, asset_quantity, utxo)
|
15
|
+
logger.info("UtxoDB#save_token:#{[asset_type, asset_id, asset_quantity, utxo.inspect]}")
|
16
|
+
level_db.batch do
|
17
|
+
asset_output = Bitcoin::Grpc::AssetOutput.new(
|
18
|
+
asset_type: [asset_type].pack('C'),
|
19
|
+
asset_id: asset_id,
|
20
|
+
asset_quantity: asset_quantity,
|
21
|
+
tx_hash: utxo.tx_hash,
|
22
|
+
index: utxo.index,
|
23
|
+
block_height: utxo.block_height
|
24
|
+
)
|
25
|
+
out_point = Bitcoin::OutPoint.new(utxo.tx_hash, utxo.index)
|
26
|
+
payload = asset_output.to_proto.bth
|
27
|
+
|
28
|
+
# out_point
|
29
|
+
key = KEY_PREFIX[:asset_out_point] + out_point.to_payload.bth
|
30
|
+
return if level_db.contains?(key)
|
31
|
+
level_db.put(key, payload)
|
32
|
+
|
33
|
+
# script_pubkey
|
34
|
+
if utxo.script_pubkey
|
35
|
+
key = KEY_PREFIX[:asset_script_pubkey] + [asset_type].pack('C').bth + utxo.script_pubkey + out_point.to_payload.bth
|
36
|
+
level_db.put(key, payload)
|
37
|
+
end
|
38
|
+
|
39
|
+
# block_height
|
40
|
+
key = KEY_PREFIX[:asset_height] + [asset_type].pack('C').bth + [utxo.block_height].pack('N').bth + out_point.to_payload.bth
|
41
|
+
level_db.put(key, payload)
|
42
|
+
return asset_output
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_token(utxo)
|
47
|
+
logger.info("UtxoDB#delete_token:#{utxo.inspect}")
|
48
|
+
level_db.batch do
|
49
|
+
out_point = Bitcoin::OutPoint.new(utxo.tx_hash, utxo.index)
|
50
|
+
|
51
|
+
key = KEY_PREFIX[:asset_out_point] + out_point.to_payload.bth
|
52
|
+
return unless level_db.contains?(key)
|
53
|
+
asset_output = Bitcoin::Grpc::AssetOutput.decode(level_db.get(key).htb)
|
54
|
+
level_db.delete(key)
|
55
|
+
|
56
|
+
if utxo.script_pubkey
|
57
|
+
key = KEY_PREFIX[:asset_script_pubkey] + asset_output.asset_type.bth + utxo.script_pubkey + out_point.to_payload.bth
|
58
|
+
level_db.delete(key)
|
59
|
+
end
|
60
|
+
|
61
|
+
key = KEY_PREFIX[:asset_height] + asset_output.asset_type.bth + [utxo.block_height].pack('N').bth + out_point.to_payload.bth
|
62
|
+
level_db.delete(key)
|
63
|
+
return asset_output
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def list_unspent_assets(asset_type, asset_id, current_block_height: 9999999, min: 0, max: 9999999, addresses: nil)
|
68
|
+
raise NotImplementedError.new("asset_type should not be nil.") unless asset_type
|
69
|
+
raise ArgumentError.new('asset_id should not be nil') unless asset_id
|
70
|
+
|
71
|
+
if addresses
|
72
|
+
list_unspent_assets_by_addresses(asset_type, asset_id, current_block_height, min: min, max: max, addresses: addresses)
|
73
|
+
else
|
74
|
+
list_unspent_assets_by_block_height(asset_type, asset_id, current_block_height, min: min, max: max)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def list_unspent_assets_in_account(asset_type, asset_id, account, current_block_height: 9999999, min: 0 , max: 9999999)
|
79
|
+
return [] unless account
|
80
|
+
script_pubkeys = account.watch_targets.map { |t| Bitcoin::Script.to_p2wpkh(t).to_payload.bth }
|
81
|
+
list_unspent_assets_by_script_pubkeys(asset_type, asset_id, current_block_height, min: min, max: max, script_pubkeys: script_pubkeys)
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_asset_balance(asset_type, asset_id, account, current_block_height: 9999999, min: 0, max: 9999999, addresses: nil)
|
85
|
+
raise NotImplementedError.new("asset_type should not be nil.") unless asset_type
|
86
|
+
raise ArgumentError.new('asset_id should not be nil') unless asset_id
|
87
|
+
|
88
|
+
list_unspent_assets_in_account(asset_type, asset_id, account, current_block_height: current_block_height, min: min, max: max).sum { |u| u.asset_quantity }
|
89
|
+
end
|
90
|
+
|
91
|
+
def list_uncolored_unspent_in_account(account, current_block_height: 9999999, min: 0, max: 9999999)
|
92
|
+
utxos = list_unspent_in_account(account, current_block_height: current_block_height, min: min, max: max)
|
93
|
+
utxos.delete_if do |utxo|
|
94
|
+
out_point = Bitcoin::OutPoint.new(utxo.tx_hash, utxo.index)
|
95
|
+
level_db.contains?(KEY_PREFIX[:asset_out_point] + out_point.to_payload.bth)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def assets_between(from, to, asset_id)
|
102
|
+
level_db.each(from: from, to: to)
|
103
|
+
.map { |k, v| Bitcoin::Grpc::AssetOutput.decode(v.htb) }
|
104
|
+
.select {|asset| asset.asset_id == asset_id }
|
105
|
+
end
|
106
|
+
|
107
|
+
def list_unspent_assets_by_block_height(asset_type, asset_id, current_block_height, min: 0, max: 9999999)
|
108
|
+
max_height = [current_block_height - min, 0].max
|
109
|
+
min_height = [current_block_height - max, 0].max
|
110
|
+
|
111
|
+
from = KEY_PREFIX[:asset_height] + [asset_type, min_height].pack('CN').bth + '000000000000000000000000000000000000000000000000000000000000000000000000'
|
112
|
+
to = KEY_PREFIX[:asset_height] + [asset_type, max_height].pack('CN').bth + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
113
|
+
assets_between(from, to, asset_id)
|
114
|
+
end
|
115
|
+
|
116
|
+
def list_unspent_assets_by_addresses(asset_type, asset_id, current_block_height, min: 0, max: 9999999, addresses: [])
|
117
|
+
script_pubkeys = addresses.map { |a| Bitcoin::Script.parse_from_addr(a).to_payload.bth }
|
118
|
+
list_unspent_assets_by_script_pubkeys(asset_type, asset_id, current_block_height, min: min, max: max, script_pubkeys: script_pubkeys)
|
119
|
+
end
|
120
|
+
|
121
|
+
def list_unspent_assets_by_script_pubkeys(asset_type, asset_id, current_block_height, min: 0, max: 9999999, script_pubkeys: [])
|
122
|
+
max_height = current_block_height - min
|
123
|
+
min_height = current_block_height - max
|
124
|
+
script_pubkeys.map do |key|
|
125
|
+
from = KEY_PREFIX[:asset_script_pubkey] + [asset_type].pack('C').bth + key + '000000000000000000000000000000000000000000000000000000000000000000000000'
|
126
|
+
to = KEY_PREFIX[:asset_script_pubkey] + [asset_type].pack('C').bth + key + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
127
|
+
assets_between(from, to, asset_id).with_height(min_height, max_height)
|
128
|
+
end.flatten
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Wallet
|
5
|
+
class AssetHandler < Concurrent::Actor::RestartingContext
|
6
|
+
attr_reader :utxo_db, :publisher
|
7
|
+
|
8
|
+
def initialize(spv, publisher)
|
9
|
+
publisher << [:subscribe, Bitcoin::Grpc::EventUtxoSpent]
|
10
|
+
publisher << [:subscribe, Bitcoin::Grpc::WatchAssetIdAssignedRequest]
|
11
|
+
@publisher = publisher
|
12
|
+
@utxo_db = spv.wallet.utxo_db
|
13
|
+
@logger = Bitcoin::Logger.create(:debug)
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_message(message)
|
17
|
+
case message
|
18
|
+
when Bitcoin::Grpc::EventUtxoSpent
|
19
|
+
tx = Bitcoin::Tx.parse_from_payload(message.tx_payload.htb)
|
20
|
+
log(::Logger::DEBUG, "tx=#{tx}, open_assets?=#{tx.open_assets?}")
|
21
|
+
utxo_db.delete_token(message.utxo) if tx.open_assets?
|
22
|
+
when Bitcoin::Grpc::WatchAssetIdAssignedRequest
|
23
|
+
tx = Bitcoin::Tx.parse_from_payload(message.tx_payload.htb)
|
24
|
+
log(::Logger::DEBUG, "tx=#{tx}, open_assets?=#{tx.open_assets?}")
|
25
|
+
case
|
26
|
+
when tx.open_assets?
|
27
|
+
outputs = Bitcoin::Grpc::OapService.outputs_with_open_asset_id(message.tx_hash)
|
28
|
+
if outputs
|
29
|
+
outputs.each do |output|
|
30
|
+
asset_id = output['asset_id']
|
31
|
+
asset_quantity = output['asset_quantity']
|
32
|
+
oa_output_type = output['oa_output_type']
|
33
|
+
next unless asset_id
|
34
|
+
out_point = Bitcoin::OutPoint.new(tx.tx_hash, output['n'])
|
35
|
+
utxo = utxo_db.get_utxo(out_point)
|
36
|
+
next unless utxo
|
37
|
+
asset_output = utxo_db.save_token(AssetFeature::AssetType::OPEN_ASSETS, asset_id, asset_quantity, utxo)
|
38
|
+
next unless asset_output
|
39
|
+
if oa_output_type == 'issuance'
|
40
|
+
publisher << Bitcoin::Grpc::EventTokenIssued.new(asset: asset_output)
|
41
|
+
else
|
42
|
+
publisher << Bitcoin::Grpc::EventTokenTransfered.new(asset: asset_output)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
task = Concurrent::TimerTask.new(execution_interval: 60) do
|
47
|
+
self << message
|
48
|
+
task.shutdown
|
49
|
+
end
|
50
|
+
task.execute
|
51
|
+
end
|
52
|
+
else
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Wallet
|
5
|
+
class Publisher < Concurrent::Actor::RestartingContext
|
6
|
+
attr_reader :receivers
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@receivers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_message(message)
|
13
|
+
case message
|
14
|
+
when :unsubscribe
|
15
|
+
receivers.each { |receiver| receiver.delete(envelope.sender) }
|
16
|
+
when Array
|
17
|
+
if message[0] == :subscribe
|
18
|
+
if envelope.sender.is_a? Concurrent::Actor::Reference
|
19
|
+
receivers[message[1].name] ||= []
|
20
|
+
receivers[message[1].name] << envelope.sender
|
21
|
+
end
|
22
|
+
elsif message[0] == :subscribe?
|
23
|
+
receivers[message[1].name]&.include?(envelope.sender)
|
24
|
+
else
|
25
|
+
end
|
26
|
+
else
|
27
|
+
receivers[message&.class&.name]&.each { |r| r << message }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Wallet
|
5
|
+
module Signer
|
6
|
+
def self.sign(node, account_name, tx)
|
7
|
+
account = find_account(node, account_name)
|
8
|
+
return unless account
|
9
|
+
|
10
|
+
tx.inputs.each.with_index do |input, index|
|
11
|
+
spec = input_spec(node, account, tx, index)
|
12
|
+
next unless spec
|
13
|
+
sign_tx_for_p2wpkh(tx, index, spec[0], spec[1])
|
14
|
+
end
|
15
|
+
tx
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.input_spec(node, account, tx, index)
|
21
|
+
input = tx.inputs[index]
|
22
|
+
return unless input
|
23
|
+
utxo = node.wallet.utxo_db.get_utxo(input.out_point)
|
24
|
+
return unless utxo
|
25
|
+
script_pubkey = utxo.script_pubkey
|
26
|
+
keys = account.watch_targets
|
27
|
+
key = nil
|
28
|
+
(0..account.receive_depth + 1).reverse_each do |key_index|
|
29
|
+
path = [account.path, 0, key_index].join('/')
|
30
|
+
temp_key = node.wallet.master_key.derive(path).key
|
31
|
+
if to_p2wpkh(temp_key).to_payload.bth == script_pubkey
|
32
|
+
key = temp_key
|
33
|
+
break
|
34
|
+
end
|
35
|
+
end
|
36
|
+
unless key
|
37
|
+
(0..account.change_depth + 1).reverse_each do |key_index|
|
38
|
+
path = [account.path, 1, key_index].join('/')
|
39
|
+
temp_key = node.wallet.master_key.derive(path).key
|
40
|
+
if to_p2wpkh(temp_key).to_payload.bth == script_pubkey
|
41
|
+
key = temp_key
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
return unless key
|
47
|
+
[key, utxo.value]
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.find_account(node, account_name)
|
51
|
+
node.wallet.accounts.find{|a| a.name == account_name}
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.sign_tx_for_p2wpkh(tx, index, key, amount)
|
55
|
+
sig = to_sighash(tx, index, key, amount)
|
56
|
+
tx.inputs[index].script_witness = Bitcoin::ScriptWitness.new.tap do |witness|
|
57
|
+
witness.stack << sig << key.pubkey.htb
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.to_sighash(tx, index, key, amount)
|
62
|
+
sighash = tx.sighash_for_input(index, to_p2wpkh(key), amount: amount, sig_version: :witness_v0)
|
63
|
+
key.sign(sighash) + [Bitcoin::SIGHASH_TYPE[:all]].pack('C')
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.to_p2wpkh(key)
|
67
|
+
Bitcoin::Script.to_p2wpkh(Bitcoin.hash160(key.pubkey))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|