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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
  4. data/lib/bsv/network/protocols/arc.rb +4 -30
  5. data/lib/bsv/network/protocols/arcade.rb +163 -0
  6. data/lib/bsv/network/protocols/chaintracks.rb +6 -3
  7. data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
  8. data/lib/bsv/network/protocols.rb +1 -0
  9. data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
  10. data/lib/bsv/network/util.rb +44 -0
  11. data/lib/bsv/network.rb +1 -0
  12. data/lib/bsv/transaction/chain_tracker.rb +74 -13
  13. data/lib/bsv/transaction/chain_trackers.rb +0 -10
  14. data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
  15. data/lib/bsv/version.rb +1 -1
  16. data/lib/bsv/wallet/errors.rb +65 -21
  17. data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
  18. data/lib/bsv/wallet/proto_wallet.rb +14 -1
  19. data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
  20. data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
  21. data/lib/bsv/wallet/serializer/certificate.rb +184 -0
  22. data/lib/bsv/wallet/serializer/common.rb +207 -0
  23. data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
  24. data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
  25. data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
  26. data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
  27. data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
  28. data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
  29. data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
  30. data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
  31. data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
  32. data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
  33. data/lib/bsv/wallet/serializer/get_height.rb +46 -0
  34. data/lib/bsv/wallet/serializer/get_network.rb +65 -0
  35. data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
  36. data/lib/bsv/wallet/serializer/get_version.rb +44 -0
  37. data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
  38. data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
  39. data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
  40. data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
  41. data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
  42. data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
  43. data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
  44. data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
  45. data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
  46. data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
  47. data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
  48. data/lib/bsv/wallet/serializer/status.rb +85 -0
  49. data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
  50. data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
  51. data/lib/bsv/wallet/serializer.rb +180 -0
  52. data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
  53. data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
  54. data/lib/bsv/wallet/wallet_wire.rb +20 -0
  55. data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
  56. data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
  57. data/lib/bsv/wallet/wire/calls.rb +79 -0
  58. data/lib/bsv/wallet/wire/frame.rb +181 -0
  59. data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
  60. data/lib/bsv/wallet/wire/validation.rb +213 -0
  61. data/lib/bsv/wallet/wire.rb +13 -0
  62. data/lib/bsv/wallet.rb +17 -0
  63. metadata +46 -2
  64. 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