bitcoinrb-grpc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|