bsv-sdk 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
- data/lib/bsv/network/protocols/arc.rb +4 -30
- data/lib/bsv/network/protocols/arcade.rb +163 -0
- data/lib/bsv/network/protocols/chaintracks.rb +6 -3
- data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
- data/lib/bsv/network/protocols.rb +1 -0
- data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
- data/lib/bsv/network/util.rb +44 -0
- data/lib/bsv/network.rb +1 -0
- data/lib/bsv/transaction/chain_tracker.rb +74 -13
- data/lib/bsv/transaction/chain_trackers.rb +0 -10
- data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +65 -21
- data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
- data/lib/bsv/wallet/proto_wallet.rb +14 -1
- data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
- data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
- data/lib/bsv/wallet/serializer/certificate.rb +184 -0
- data/lib/bsv/wallet/serializer/common.rb +207 -0
- data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
- data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
- data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
- data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
- data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
- data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
- data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
- data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
- data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
- data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
- data/lib/bsv/wallet/serializer/get_height.rb +46 -0
- data/lib/bsv/wallet/serializer/get_network.rb +65 -0
- data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
- data/lib/bsv/wallet/serializer/get_version.rb +44 -0
- data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
- data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
- data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
- data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
- data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
- data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
- data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
- data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
- data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
- data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
- data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
- data/lib/bsv/wallet/serializer/status.rb +85 -0
- data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
- data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
- data/lib/bsv/wallet/serializer.rb +180 -0
- data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
- data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
- data/lib/bsv/wallet/wallet_wire.rb +20 -0
- data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
- data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
- data/lib/bsv/wallet/wire/calls.rb +79 -0
- data/lib/bsv/wallet/wire/frame.rb +181 -0
- data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
- data/lib/bsv/wallet/wire/validation.rb +213 -0
- data/lib/bsv/wallet/wire.rb +13 -0
- data/lib/bsv/wallet.rb +17 -0
- metadata +46 -2
- data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# BRC-103 serialiser for internalize_action (call byte 5).
|
|
7
|
+
#
|
|
8
|
+
# Wire layout (port of go-sdk/wallet/serializer/internalize_action.go):
|
|
9
|
+
# [varint + bytes] tx (BEEF) — length-prefixed raw bytes
|
|
10
|
+
# [varint] outputs count
|
|
11
|
+
# For each output:
|
|
12
|
+
# [varint] output_index
|
|
13
|
+
# [1 byte] protocol: 0x01=wallet_payment, 0x02=basket_insertion
|
|
14
|
+
# If wallet_payment:
|
|
15
|
+
# [33 bytes] sender_identity_key (compressed pubkey)
|
|
16
|
+
# [int_bytes] derivation_prefix
|
|
17
|
+
# [int_bytes] derivation_suffix
|
|
18
|
+
# If basket_insertion:
|
|
19
|
+
# [string] basket
|
|
20
|
+
# [optional_string] custom_instructions
|
|
21
|
+
# [string_slice] tags
|
|
22
|
+
# [string_slice] labels
|
|
23
|
+
# [string] description
|
|
24
|
+
# [optional_bool] seek_permission
|
|
25
|
+
#
|
|
26
|
+
# Result wire layout: empty (success is implicit from the frame error byte).
|
|
27
|
+
module InternalizeActionArgs
|
|
28
|
+
PROTOCOL_WALLET_PAYMENT = 1
|
|
29
|
+
PROTOCOL_BASKET_INSERTION = 2
|
|
30
|
+
PUBKEY_SIZE = 33
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# @param args [Hash]
|
|
35
|
+
# @return [String] binary
|
|
36
|
+
def serialize(args)
|
|
37
|
+
w = Wire::Writer.new
|
|
38
|
+
|
|
39
|
+
tx = (args[:tx] || ''.b).b
|
|
40
|
+
w.write_varint(tx.bytesize)
|
|
41
|
+
w.write_bytes(tx)
|
|
42
|
+
|
|
43
|
+
outputs = args[:outputs] || []
|
|
44
|
+
w.write_varint(outputs.length)
|
|
45
|
+
outputs.each { |out| serialize_output(w, out) }
|
|
46
|
+
|
|
47
|
+
w.write_string_slice(args[:labels])
|
|
48
|
+
w.write_string(args[:description].to_s)
|
|
49
|
+
w.write_optional_bool(args[:seek_permission])
|
|
50
|
+
|
|
51
|
+
w.buf
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param bytes [String] binary
|
|
55
|
+
# @return [Hash]
|
|
56
|
+
def deserialize(bytes)
|
|
57
|
+
r = Wire::Reader.new(bytes)
|
|
58
|
+
|
|
59
|
+
tx_len = r.read_varint
|
|
60
|
+
tx = r.read_bytes(tx_len)
|
|
61
|
+
|
|
62
|
+
output_count = r.read_varint
|
|
63
|
+
outputs = output_count.times.map { deserialize_output(r) }
|
|
64
|
+
|
|
65
|
+
labels = r.read_string_slice
|
|
66
|
+
description = r.read_string
|
|
67
|
+
seek_permission = r.read_optional_bool
|
|
68
|
+
|
|
69
|
+
result = { tx: tx, outputs: outputs, description: description }
|
|
70
|
+
result[:labels] = labels unless labels.nil?
|
|
71
|
+
result[:seek_permission] = seek_permission unless seek_permission.nil?
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def serialize_output(writer, out)
|
|
76
|
+
writer.write_varint(out[:output_index].to_i)
|
|
77
|
+
|
|
78
|
+
case out[:protocol]
|
|
79
|
+
when :wallet_payment
|
|
80
|
+
writer.write_byte(PROTOCOL_WALLET_PAYMENT)
|
|
81
|
+
payment = out[:payment_remittance] || {}
|
|
82
|
+
writer.write_bytes([payment[:sender_identity_key]].pack('H*'))
|
|
83
|
+
writer.write_int_bytes(payment[:derivation_prefix])
|
|
84
|
+
writer.write_int_bytes(payment[:derivation_suffix])
|
|
85
|
+
when :basket_insertion
|
|
86
|
+
writer.write_byte(PROTOCOL_BASKET_INSERTION)
|
|
87
|
+
insertion = out[:insertion_remittance] || {}
|
|
88
|
+
writer.write_string(insertion[:basket].to_s)
|
|
89
|
+
writer.write_optional_string(insertion[:custom_instructions])
|
|
90
|
+
writer.write_string_slice(insertion[:tags])
|
|
91
|
+
else
|
|
92
|
+
raise ArgumentError, "unknown internalize protocol: #{out[:protocol]}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def deserialize_output(reader)
|
|
97
|
+
output_index = reader.read_varint
|
|
98
|
+
protocol_byte = reader.read_byte
|
|
99
|
+
|
|
100
|
+
case protocol_byte
|
|
101
|
+
when PROTOCOL_WALLET_PAYMENT
|
|
102
|
+
key_bytes = reader.read_bytes(PUBKEY_SIZE)
|
|
103
|
+
prefix = reader.read_int_bytes
|
|
104
|
+
suffix = reader.read_int_bytes
|
|
105
|
+
{
|
|
106
|
+
output_index: output_index,
|
|
107
|
+
protocol: :wallet_payment,
|
|
108
|
+
payment_remittance: {
|
|
109
|
+
sender_identity_key: key_bytes.unpack1('H*'),
|
|
110
|
+
derivation_prefix: prefix,
|
|
111
|
+
derivation_suffix: suffix
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
when PROTOCOL_BASKET_INSERTION
|
|
115
|
+
basket = reader.read_string
|
|
116
|
+
custom = reader.read_optional_string
|
|
117
|
+
tags = reader.read_string_slice
|
|
118
|
+
result = {
|
|
119
|
+
output_index: output_index,
|
|
120
|
+
protocol: :basket_insertion,
|
|
121
|
+
insertion_remittance: { basket: basket }
|
|
122
|
+
}
|
|
123
|
+
result[:insertion_remittance][:custom_instructions] = custom unless custom.nil?
|
|
124
|
+
result[:insertion_remittance][:tags] = tags unless tags.nil?
|
|
125
|
+
result
|
|
126
|
+
else
|
|
127
|
+
raise ArgumentError, "invalid internalize protocol byte: #{protocol_byte}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private_class_method :serialize_output, :deserialize_output
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
module InternalizeActionResult
|
|
135
|
+
module_function
|
|
136
|
+
|
|
137
|
+
# @param _result [Hash] ignored
|
|
138
|
+
# @return [String] empty binary
|
|
139
|
+
def serialize(_result = {})
|
|
140
|
+
''.b
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @param _bytes [String] ignored
|
|
144
|
+
# @return [Hash] { accepted: true }
|
|
145
|
+
def deserialize(_bytes = nil)
|
|
146
|
+
{ accepted: true }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# BRC-103 serialiser for list_actions (call byte 4).
|
|
7
|
+
#
|
|
8
|
+
# Wire layout (port of go-sdk/wallet/serializer/list_actions.go):
|
|
9
|
+
#
|
|
10
|
+
# Args:
|
|
11
|
+
# [string_slice] labels
|
|
12
|
+
# [1 byte] label_query_mode: 0x01=any, 0x02=all, 0xFF=absent
|
|
13
|
+
# [optional_bool] include_labels
|
|
14
|
+
# [optional_bool] include_inputs
|
|
15
|
+
# [optional_bool] include_input_source_locking_scripts
|
|
16
|
+
# [optional_bool] include_input_unlocking_scripts
|
|
17
|
+
# [optional_bool] include_outputs
|
|
18
|
+
# [optional_bool] include_output_locking_scripts
|
|
19
|
+
# [optional_uint32] limit
|
|
20
|
+
# [optional_uint32] offset
|
|
21
|
+
# [optional_bool] seek_permission
|
|
22
|
+
#
|
|
23
|
+
# Result:
|
|
24
|
+
# [varint] total_actions
|
|
25
|
+
# For each action:
|
|
26
|
+
# [32 bytes] txid (display order — reversed from wire storage)
|
|
27
|
+
# [varint] satoshis (int64 as varint)
|
|
28
|
+
# [1 byte] status code
|
|
29
|
+
# [optional_bool ptr] is_outgoing (written as optional_bool pointer in Go)
|
|
30
|
+
# [string] description
|
|
31
|
+
# [string_slice] labels
|
|
32
|
+
# [varint] version
|
|
33
|
+
# [varint] lock_time
|
|
34
|
+
# [inputs] NegativeOne | varint count + input_record…
|
|
35
|
+
# [outputs] NegativeOne | varint count + output_record…
|
|
36
|
+
#
|
|
37
|
+
# Input record:
|
|
38
|
+
# [36 bytes] source_outpoint (32-byte wire txid + varint vout)
|
|
39
|
+
# [varint] source_satoshis
|
|
40
|
+
# [int_bytes_optional] source_locking_script
|
|
41
|
+
# [int_bytes_optional] unlocking_script
|
|
42
|
+
# [string] input_description
|
|
43
|
+
# [varint] sequence_number
|
|
44
|
+
#
|
|
45
|
+
# Output record:
|
|
46
|
+
# [varint] output_index
|
|
47
|
+
# [varint] satoshis
|
|
48
|
+
# [int_bytes_optional] locking_script
|
|
49
|
+
# [optional_bool ptr] spendable
|
|
50
|
+
# [string] output_description
|
|
51
|
+
# [string] basket
|
|
52
|
+
# [string_slice] tags
|
|
53
|
+
# [optional_string] custom_instructions
|
|
54
|
+
module ListActionsArgs
|
|
55
|
+
LABEL_QUERY_MODE_ANY = 1
|
|
56
|
+
LABEL_QUERY_MODE_ALL = 2
|
|
57
|
+
|
|
58
|
+
module_function
|
|
59
|
+
|
|
60
|
+
# @param args [Hash]
|
|
61
|
+
# @return [String] binary
|
|
62
|
+
def serialize(args)
|
|
63
|
+
w = Wire::Writer.new
|
|
64
|
+
|
|
65
|
+
w.write_string_slice(args[:labels])
|
|
66
|
+
|
|
67
|
+
case args[:label_query_mode]
|
|
68
|
+
when :any
|
|
69
|
+
w.write_byte(LABEL_QUERY_MODE_ANY)
|
|
70
|
+
when :all
|
|
71
|
+
w.write_byte(LABEL_QUERY_MODE_ALL)
|
|
72
|
+
else
|
|
73
|
+
w.write_byte(0xFF)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
w.write_optional_bool(args[:include_labels])
|
|
77
|
+
w.write_optional_bool(args[:include_inputs])
|
|
78
|
+
w.write_optional_bool(args[:include_input_source_locking_scripts])
|
|
79
|
+
w.write_optional_bool(args[:include_input_unlocking_scripts])
|
|
80
|
+
w.write_optional_bool(args[:include_outputs])
|
|
81
|
+
w.write_optional_bool(args[:include_output_locking_scripts])
|
|
82
|
+
|
|
83
|
+
w.write_optional_uint32(args[:limit])
|
|
84
|
+
w.write_optional_uint32(args[:offset])
|
|
85
|
+
w.write_optional_bool(args[:seek_permission])
|
|
86
|
+
|
|
87
|
+
w.buf
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @param bytes [String] binary
|
|
91
|
+
# @return [Hash]
|
|
92
|
+
def deserialize(bytes)
|
|
93
|
+
r = Wire::Reader.new(bytes)
|
|
94
|
+
|
|
95
|
+
labels = r.read_string_slice
|
|
96
|
+
|
|
97
|
+
mode_byte = r.read_byte
|
|
98
|
+
label_query_mode = case mode_byte
|
|
99
|
+
when LABEL_QUERY_MODE_ANY then :any
|
|
100
|
+
when LABEL_QUERY_MODE_ALL then :all
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
result = {}
|
|
104
|
+
result[:labels] = labels unless labels.nil?
|
|
105
|
+
result[:label_query_mode] = label_query_mode unless label_query_mode.nil?
|
|
106
|
+
|
|
107
|
+
v = r.read_optional_bool
|
|
108
|
+
result[:include_labels] = v unless v.nil?
|
|
109
|
+
v = r.read_optional_bool
|
|
110
|
+
result[:include_inputs] = v unless v.nil?
|
|
111
|
+
v = r.read_optional_bool
|
|
112
|
+
result[:include_input_source_locking_scripts] = v unless v.nil?
|
|
113
|
+
v = r.read_optional_bool
|
|
114
|
+
result[:include_input_unlocking_scripts] = v unless v.nil?
|
|
115
|
+
v = r.read_optional_bool
|
|
116
|
+
result[:include_outputs] = v unless v.nil?
|
|
117
|
+
v = r.read_optional_bool
|
|
118
|
+
result[:include_output_locking_scripts] = v unless v.nil?
|
|
119
|
+
|
|
120
|
+
lim = r.read_optional_uint32
|
|
121
|
+
result[:limit] = lim unless lim.nil?
|
|
122
|
+
off = r.read_optional_uint32
|
|
123
|
+
result[:offset] = off unless off.nil?
|
|
124
|
+
|
|
125
|
+
v = r.read_optional_bool
|
|
126
|
+
result[:seek_permission] = v unless v.nil?
|
|
127
|
+
|
|
128
|
+
result
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
module ListActionsResult
|
|
133
|
+
ACTION_STATUS_CODES = {
|
|
134
|
+
completed: 1,
|
|
135
|
+
unprocessed: 2,
|
|
136
|
+
sending: 3,
|
|
137
|
+
unproven: 4,
|
|
138
|
+
unsigned: 5,
|
|
139
|
+
no_send: 6,
|
|
140
|
+
non_final: 7
|
|
141
|
+
}.freeze
|
|
142
|
+
|
|
143
|
+
ACTION_CODE_STATUSES = ACTION_STATUS_CODES.invert.freeze
|
|
144
|
+
|
|
145
|
+
module_function
|
|
146
|
+
|
|
147
|
+
# @param result [Hash] { total_actions: Integer, actions: Array<Hash> }
|
|
148
|
+
# @return [String] binary
|
|
149
|
+
def serialize(result)
|
|
150
|
+
w = Wire::Writer.new
|
|
151
|
+
|
|
152
|
+
actions = result[:actions] || []
|
|
153
|
+
w.write_varint(actions.length)
|
|
154
|
+
|
|
155
|
+
actions.each do |action|
|
|
156
|
+
txid_hex = action[:txid].to_s
|
|
157
|
+
w.write_bytes([txid_hex].pack('H*'))
|
|
158
|
+
w.write_varint(action[:satoshis].to_i)
|
|
159
|
+
|
|
160
|
+
code = ACTION_STATUS_CODES.fetch(action[:status]) do
|
|
161
|
+
raise ArgumentError, "invalid action status: #{action[:status]}"
|
|
162
|
+
end
|
|
163
|
+
w.write_byte(code)
|
|
164
|
+
|
|
165
|
+
# is_outgoing: non-optional bool written as optional_bool pointer in Go
|
|
166
|
+
w.write_byte(action[:is_outgoing] ? 1 : 0)
|
|
167
|
+
|
|
168
|
+
w.write_string(action[:description].to_s)
|
|
169
|
+
w.write_string_slice(action[:labels])
|
|
170
|
+
w.write_varint(action[:version].to_i)
|
|
171
|
+
w.write_varint(action[:lock_time].to_i)
|
|
172
|
+
|
|
173
|
+
serialize_action_inputs(w, action[:inputs])
|
|
174
|
+
serialize_action_outputs(w, action[:outputs])
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
w.buf
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @param bytes [String] binary
|
|
181
|
+
# @return [Hash]
|
|
182
|
+
def deserialize(bytes)
|
|
183
|
+
r = Wire::Reader.new(bytes)
|
|
184
|
+
|
|
185
|
+
total = r.read_varint
|
|
186
|
+
actions = total.times.map do
|
|
187
|
+
txid_hex = r.read_bytes(32).unpack1('H*')
|
|
188
|
+
satoshis = r.read_varint
|
|
189
|
+
code = r.read_byte
|
|
190
|
+
status = ACTION_CODE_STATUSES.fetch(code) do
|
|
191
|
+
raise ArgumentError, "invalid action status code: #{code}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
is_outgoing = r.read_byte == 1
|
|
195
|
+
description = r.read_string
|
|
196
|
+
labels = r.read_string_slice
|
|
197
|
+
version = r.read_varint
|
|
198
|
+
lock_time = r.read_varint
|
|
199
|
+
|
|
200
|
+
inputs = deserialize_action_inputs(r)
|
|
201
|
+
outputs = deserialize_action_outputs(r)
|
|
202
|
+
|
|
203
|
+
action = {
|
|
204
|
+
txid: txid_hex,
|
|
205
|
+
satoshis: satoshis,
|
|
206
|
+
status: status,
|
|
207
|
+
is_outgoing: is_outgoing,
|
|
208
|
+
description: description,
|
|
209
|
+
version: version,
|
|
210
|
+
lock_time: lock_time
|
|
211
|
+
}
|
|
212
|
+
action[:labels] = labels unless labels.nil?
|
|
213
|
+
action[:inputs] = inputs unless inputs.nil?
|
|
214
|
+
action[:outputs] = outputs unless outputs.nil?
|
|
215
|
+
action
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
{ total_actions: total, actions: actions }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def serialize_action_inputs(writer, inputs)
|
|
222
|
+
if inputs.nil? || inputs.empty?
|
|
223
|
+
writer.write_negative_one
|
|
224
|
+
return
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
writer.write_varint(inputs.length)
|
|
228
|
+
inputs.each do |inp|
|
|
229
|
+
txid_hex, vout = inp[:source_outpoint].split('.')
|
|
230
|
+
writer.write_bytes([txid_hex].pack('H*'))
|
|
231
|
+
writer.write_varint(vout.to_i)
|
|
232
|
+
|
|
233
|
+
writer.write_varint(inp[:source_satoshis].to_i)
|
|
234
|
+
|
|
235
|
+
script = inp[:source_locking_script]
|
|
236
|
+
if script && !script.b.empty?
|
|
237
|
+
writer.write_int_bytes(script)
|
|
238
|
+
else
|
|
239
|
+
writer.write_negative_one
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
unlock_script = inp[:unlocking_script]
|
|
243
|
+
if unlock_script && !unlock_script.b.empty?
|
|
244
|
+
writer.write_int_bytes(unlock_script)
|
|
245
|
+
else
|
|
246
|
+
writer.write_negative_one
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
writer.write_string(inp[:input_description].to_s)
|
|
250
|
+
writer.write_varint(inp[:sequence_number].to_i)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def serialize_action_outputs(writer, outputs)
|
|
255
|
+
if outputs.nil? || outputs.empty?
|
|
256
|
+
writer.write_negative_one
|
|
257
|
+
return
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
writer.write_varint(outputs.length)
|
|
261
|
+
outputs.each do |out|
|
|
262
|
+
writer.write_varint(out[:output_index].to_i)
|
|
263
|
+
writer.write_varint(out[:satoshis].to_i)
|
|
264
|
+
|
|
265
|
+
script = out[:locking_script]
|
|
266
|
+
if script && !script.b.empty?
|
|
267
|
+
writer.write_int_bytes(script)
|
|
268
|
+
else
|
|
269
|
+
writer.write_negative_one
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
writer.write_byte(out[:spendable] ? 1 : 0)
|
|
273
|
+
writer.write_string(out[:output_description].to_s)
|
|
274
|
+
writer.write_string(out[:basket].to_s)
|
|
275
|
+
writer.write_string_slice(out[:tags])
|
|
276
|
+
writer.write_optional_string(out[:custom_instructions])
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def deserialize_action_inputs(reader)
|
|
281
|
+
count = reader.read_varint
|
|
282
|
+
return nil if count == 0xFFFF_FFFF_FFFF_FFFF
|
|
283
|
+
|
|
284
|
+
count.times.map do
|
|
285
|
+
txid_hex = reader.read_bytes(32).unpack1('H*')
|
|
286
|
+
vout = reader.read_varint
|
|
287
|
+
source_outpoint = "#{txid_hex}.#{vout}"
|
|
288
|
+
|
|
289
|
+
source_satoshis = reader.read_varint
|
|
290
|
+
|
|
291
|
+
source_locking_script = reader.read_int_bytes
|
|
292
|
+
source_locking_script = nil if source_locking_script.empty?
|
|
293
|
+
|
|
294
|
+
unlocking_script = reader.read_int_bytes
|
|
295
|
+
unlocking_script = nil if unlocking_script.empty?
|
|
296
|
+
|
|
297
|
+
input_description = reader.read_string
|
|
298
|
+
sequence_number = reader.read_varint
|
|
299
|
+
|
|
300
|
+
inp = {
|
|
301
|
+
source_outpoint: source_outpoint,
|
|
302
|
+
source_satoshis: source_satoshis,
|
|
303
|
+
input_description: input_description,
|
|
304
|
+
sequence_number: sequence_number
|
|
305
|
+
}
|
|
306
|
+
inp[:source_locking_script] = source_locking_script unless source_locking_script.nil?
|
|
307
|
+
inp[:unlocking_script] = unlocking_script unless unlocking_script.nil?
|
|
308
|
+
inp
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def deserialize_action_outputs(reader)
|
|
313
|
+
count = reader.read_varint
|
|
314
|
+
return nil if count == 0xFFFF_FFFF_FFFF_FFFF
|
|
315
|
+
|
|
316
|
+
count.times.map do
|
|
317
|
+
output_index = reader.read_varint
|
|
318
|
+
satoshis = reader.read_varint
|
|
319
|
+
|
|
320
|
+
locking_script = reader.read_int_bytes
|
|
321
|
+
locking_script = nil if locking_script.empty?
|
|
322
|
+
|
|
323
|
+
spendable = reader.read_byte == 1
|
|
324
|
+
output_description = reader.read_string
|
|
325
|
+
basket = reader.read_string
|
|
326
|
+
tags = reader.read_string_slice
|
|
327
|
+
custom = reader.read_optional_string
|
|
328
|
+
|
|
329
|
+
out = {
|
|
330
|
+
output_index: output_index,
|
|
331
|
+
satoshis: satoshis,
|
|
332
|
+
spendable: spendable,
|
|
333
|
+
output_description: output_description,
|
|
334
|
+
basket: basket
|
|
335
|
+
}
|
|
336
|
+
out[:locking_script] = locking_script unless locking_script.nil?
|
|
337
|
+
out[:tags] = tags unless tags.nil?
|
|
338
|
+
out[:custom_instructions] = custom unless custom.nil?
|
|
339
|
+
out
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
private_class_method :serialize_action_inputs, :serialize_action_outputs,
|
|
344
|
+
:deserialize_action_inputs, :deserialize_action_outputs
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module BSV
|
|
6
|
+
module Wallet
|
|
7
|
+
module Serializer
|
|
8
|
+
# BRC-103 wire codec for the +list_certificates+ call (call byte 18).
|
|
9
|
+
#
|
|
10
|
+
# Args wire layout:
|
|
11
|
+
# [varint: certifier_count] per certifier: [33-byte pubkey]
|
|
12
|
+
# [varint: type_count] per type: [32-byte raw type]
|
|
13
|
+
# [optional_uint32: limit]
|
|
14
|
+
# [optional_uint32: offset]
|
|
15
|
+
# [privileged params]
|
|
16
|
+
#
|
|
17
|
+
# Result wire layout:
|
|
18
|
+
# [varint: total_certificates]
|
|
19
|
+
# per certificate:
|
|
20
|
+
# [varint-int: serialised Certificate bytes]
|
|
21
|
+
# [1 byte: keyring present flag (0/1)]
|
|
22
|
+
# If keyring present:
|
|
23
|
+
# [varint: keyring_count] per entry: [varint-str key][varint-int base64 bytes]
|
|
24
|
+
# [varint-int: verifier bytes]
|
|
25
|
+
module ListCertificates
|
|
26
|
+
CERT_TYPE_SIZE = 32
|
|
27
|
+
PUBKEY_SIZE = 33
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def serialize_args(args)
|
|
32
|
+
w = Wire::Writer.new
|
|
33
|
+
|
|
34
|
+
certifiers = args[:certifiers] || []
|
|
35
|
+
w.write_varint(certifiers.length)
|
|
36
|
+
certifiers.each { |c| w.write_bytes([c.to_s].pack('H*')) }
|
|
37
|
+
|
|
38
|
+
types = args[:types] || []
|
|
39
|
+
w.write_varint(types.length)
|
|
40
|
+
types.each do |t|
|
|
41
|
+
type_bytes = Base64.strict_decode64(t.to_s)
|
|
42
|
+
w.write_bytes(type_bytes.ljust(CERT_TYPE_SIZE, "\x00").byteslice(0, CERT_TYPE_SIZE))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
w.write_optional_uint32(args[:limit])
|
|
46
|
+
w.write_optional_uint32(args[:offset])
|
|
47
|
+
Common.write_privileged_params(w, args[:privileged], args[:privileged_reason])
|
|
48
|
+
w.buf
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def deserialize_args(bytes)
|
|
52
|
+
r = Wire::Reader.new(bytes)
|
|
53
|
+
|
|
54
|
+
certifier_count = r.read_varint
|
|
55
|
+
certifiers = certifier_count.times.map { r.read_bytes(PUBKEY_SIZE).unpack1('H*') }
|
|
56
|
+
|
|
57
|
+
type_count = r.read_varint
|
|
58
|
+
types = type_count.times.map { Base64.strict_encode64(r.read_bytes(CERT_TYPE_SIZE)) }
|
|
59
|
+
|
|
60
|
+
limit = r.read_optional_uint32
|
|
61
|
+
offset = r.read_optional_uint32
|
|
62
|
+
privileged, privileged_reason = Common.read_privileged_params(r)
|
|
63
|
+
|
|
64
|
+
{ certifiers: certifiers, types: types, limit: limit, offset: offset,
|
|
65
|
+
privileged: privileged, privileged_reason: privileged_reason }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def serialize_result(result)
|
|
69
|
+
w = Wire::Writer.new
|
|
70
|
+
certs = result[:certificates] || []
|
|
71
|
+
w.write_varint(certs.length)
|
|
72
|
+
|
|
73
|
+
certs.each do |cert_result|
|
|
74
|
+
cert = cert_result[:certificate] || cert_result
|
|
75
|
+
cert_bytes = Certificate.serialize_certificate(cert, include_signature: true)
|
|
76
|
+
w.write_int_bytes(cert_bytes)
|
|
77
|
+
|
|
78
|
+
keyring = cert_result[:keyring]
|
|
79
|
+
if keyring
|
|
80
|
+
w.write_byte(1)
|
|
81
|
+
w.write_varint(keyring.length)
|
|
82
|
+
keyring.keys.sort.each do |k|
|
|
83
|
+
w.write_str_with_varint_len(k)
|
|
84
|
+
w.write_int_from_base64(keyring[k].to_s)
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
w.write_byte(0)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
verifier = cert_result[:verifier]
|
|
91
|
+
w.write_int_bytes(verifier ? verifier.b : ''.b)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
w.buf
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def deserialize_result(bytes)
|
|
98
|
+
r = Wire::Reader.new(bytes)
|
|
99
|
+
total = r.read_varint
|
|
100
|
+
|
|
101
|
+
certificates = total.times.map do
|
|
102
|
+
cert_bytes = r.read_int_bytes
|
|
103
|
+
cert = Certificate.deserialize_certificate(cert_bytes)
|
|
104
|
+
|
|
105
|
+
keyring = nil
|
|
106
|
+
if r.read_byte == 1
|
|
107
|
+
keyring_len = r.read_varint
|
|
108
|
+
keyring = {}
|
|
109
|
+
keyring_len.times do
|
|
110
|
+
k = r.read_str_with_varint_len
|
|
111
|
+
keyring[k] = r.read_base64_int
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
verifier = r.read_int_bytes
|
|
116
|
+
{ certificate: cert, keyring: keyring, verifier: verifier.empty? ? nil : verifier }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
{ total_certificates: total, certificates: certificates }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|