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,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# Shared binary encoding helpers for BRC-103 per-call serializers.
|
|
7
|
+
#
|
|
8
|
+
# Port of the shared helpers in go-sdk/wallet/serializer/serializer.go.
|
|
9
|
+
# All methods operate on BSV::Wallet::Wire::Writer / Reader instances.
|
|
10
|
+
module Common
|
|
11
|
+
COUNTERPARTY_SELF = 11 # 0x0B
|
|
12
|
+
COUNTERPARTY_ANYONE = 12 # 0x0C
|
|
13
|
+
PUBKEY_SIZE = 33 # compressed secp256k1 public key
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Coerce a byte payload to a binary String (ASCII-8BIT encoding).
|
|
18
|
+
#
|
|
19
|
+
# Accepts either an Array<Integer> (as returned by ProtoWallet) or a
|
|
20
|
+
# String. Serialisers use this so they remain compatible with both the
|
|
21
|
+
# in-process wallet interface (Arrays) and the wire interface (Strings).
|
|
22
|
+
def to_binary(bytes)
|
|
23
|
+
return ''.b if bytes.nil?
|
|
24
|
+
return bytes.pack('C*').b if bytes.is_a?(Array)
|
|
25
|
+
|
|
26
|
+
bytes.b
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Encode a BRC-43 protocol ID: [security_level (0-2), protocol_name].
|
|
30
|
+
#
|
|
31
|
+
# Wire format: 1-byte security level + varint-len string.
|
|
32
|
+
def write_protocol(writer, protocol_id)
|
|
33
|
+
level, name = protocol_id
|
|
34
|
+
writer.write_byte(level.to_i)
|
|
35
|
+
writer.write_str_with_varint_len(name.to_s)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def read_protocol(reader)
|
|
39
|
+
level = reader.read_byte
|
|
40
|
+
name = reader.read_str_with_varint_len
|
|
41
|
+
[level, name]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Encode a counterparty: 'self' | 'anyone' | 66-char hex compressed pubkey.
|
|
45
|
+
#
|
|
46
|
+
# Wire format:
|
|
47
|
+
# 0x0B — self
|
|
48
|
+
# 0x0C — anyone
|
|
49
|
+
# 02/03 <32 bytes> — specific pubkey (first byte is the prefix of the 33-byte key)
|
|
50
|
+
def write_counterparty(writer, counterparty)
|
|
51
|
+
case counterparty
|
|
52
|
+
when 'self'
|
|
53
|
+
writer.write_byte(COUNTERPARTY_SELF)
|
|
54
|
+
when 'anyone'
|
|
55
|
+
writer.write_byte(COUNTERPARTY_ANYONE)
|
|
56
|
+
else
|
|
57
|
+
writer.write_bytes([counterparty].pack('H*'))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def read_counterparty(reader)
|
|
62
|
+
first = reader.read_byte
|
|
63
|
+
case first
|
|
64
|
+
when COUNTERPARTY_SELF
|
|
65
|
+
'self'
|
|
66
|
+
when COUNTERPARTY_ANYONE
|
|
67
|
+
'anyone'
|
|
68
|
+
else
|
|
69
|
+
rest = reader.read_bytes(PUBKEY_SIZE - 1)
|
|
70
|
+
([first].pack('C') + rest).unpack1('H*')
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Encode privileged flag + privileged_reason.
|
|
75
|
+
#
|
|
76
|
+
# Wire format: optional_bool (0xFF=nil, 0x00=false, 0x01=true) + reason as varint-len string,
|
|
77
|
+
# or 0xFF if reason is nil/empty (NegativeOneByte sentinel).
|
|
78
|
+
def write_privileged_params(writer, privileged, privileged_reason)
|
|
79
|
+
writer.write_optional_bool(privileged)
|
|
80
|
+
reason = privileged_reason.to_s
|
|
81
|
+
if reason.empty?
|
|
82
|
+
writer.write_byte(0xFF)
|
|
83
|
+
else
|
|
84
|
+
writer.write_str_with_varint_len(reason)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def read_privileged_params(reader)
|
|
89
|
+
privileged = reader.read_optional_bool
|
|
90
|
+
# 0xFF as the leading byte is the nil-reason sentinel. Otherwise the
|
|
91
|
+
# bytes start a Bitcoin varint length prefix (which can be 0xFD or
|
|
92
|
+
# 0xFE for reasons >= 253 bytes; 0xFF would only collide if the
|
|
93
|
+
# reason exceeded 4 GiB, which the protocol does not permit).
|
|
94
|
+
if reader.peek_byte == 0xFF
|
|
95
|
+
reader.read_byte
|
|
96
|
+
[privileged, nil]
|
|
97
|
+
else
|
|
98
|
+
[privileged, reader.read_str_with_varint_len]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Encode key-related params: protocol + key_id + counterparty + privileged params.
|
|
103
|
+
def write_key_related_params(writer, protocol_id:, key_id:, counterparty:,
|
|
104
|
+
privileged: nil, privileged_reason: nil)
|
|
105
|
+
write_protocol(writer, protocol_id)
|
|
106
|
+
writer.write_str_with_varint_len(key_id.to_s)
|
|
107
|
+
write_counterparty(writer, counterparty)
|
|
108
|
+
write_privileged_params(writer, privileged, privileged_reason)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def read_key_related_params(reader)
|
|
112
|
+
protocol_id = read_protocol(reader)
|
|
113
|
+
key_id = reader.read_str_with_varint_len
|
|
114
|
+
counterparty = read_counterparty(reader)
|
|
115
|
+
privileged, reason = read_privileged_params(reader)
|
|
116
|
+
{
|
|
117
|
+
protocol_id: protocol_id,
|
|
118
|
+
key_id: key_id,
|
|
119
|
+
counterparty: counterparty,
|
|
120
|
+
privileged: privileged,
|
|
121
|
+
privileged_reason: reason
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Encode a list of outpoints (varint count + 32-byte wire-order txid + varint vout each).
|
|
126
|
+
# Returns nil bytes for nil input.
|
|
127
|
+
# @param outpoints [Array<String>, nil] array of "txid_hex.vout" strings
|
|
128
|
+
# @return [String, nil] binary or nil
|
|
129
|
+
def encode_outpoints(outpoints)
|
|
130
|
+
return nil if outpoints.nil?
|
|
131
|
+
|
|
132
|
+
w = Wire::Writer.new
|
|
133
|
+
w.write_varint(outpoints.length)
|
|
134
|
+
outpoints.each do |op|
|
|
135
|
+
txid_hex, vout = op.split('.')
|
|
136
|
+
w.write_bytes([txid_hex].pack('H*'))
|
|
137
|
+
w.write_varint(vout.to_i)
|
|
138
|
+
end
|
|
139
|
+
w.buf
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Decode a list of outpoints from binary (encoded as encode_outpoints).
|
|
143
|
+
# @param bytes [String, nil] binary data
|
|
144
|
+
# @return [Array<String>, nil] array of "txid_hex.vout" strings
|
|
145
|
+
def decode_outpoints(bytes)
|
|
146
|
+
return nil if bytes.nil? || bytes.b.empty?
|
|
147
|
+
|
|
148
|
+
r = Wire::Reader.new(bytes)
|
|
149
|
+
count = r.read_varint
|
|
150
|
+
return nil if count == 0xFFFF_FFFF_FFFF_FFFF
|
|
151
|
+
|
|
152
|
+
count.times.map do
|
|
153
|
+
txid_hex = r.read_bytes(32).unpack1('H*')
|
|
154
|
+
vout = r.read_varint
|
|
155
|
+
"#{txid_hex}.#{vout}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Send-with result status codes (Go status.go).
|
|
160
|
+
SEND_WITH_STATUS_UNPROVEN = 1
|
|
161
|
+
SEND_WITH_STATUS_SENDING = 2
|
|
162
|
+
SEND_WITH_STATUS_FAILED = 3
|
|
163
|
+
|
|
164
|
+
SEND_WITH_STATUS_CODES = {
|
|
165
|
+
unproven: SEND_WITH_STATUS_UNPROVEN,
|
|
166
|
+
sending: SEND_WITH_STATUS_SENDING,
|
|
167
|
+
failed: SEND_WITH_STATUS_FAILED
|
|
168
|
+
}.freeze
|
|
169
|
+
|
|
170
|
+
SEND_WITH_CODE_STATUSES = SEND_WITH_STATUS_CODES.invert.freeze
|
|
171
|
+
|
|
172
|
+
# Write a send_with_results array: varint count + txid (32 bytes) + status byte each.
|
|
173
|
+
# nil or empty → writes 0 count.
|
|
174
|
+
# @param writer [Wire::Writer]
|
|
175
|
+
# @param results [Array<Hash>, nil] array of { txid: String, status: Symbol }
|
|
176
|
+
def write_send_with_results(writer, results)
|
|
177
|
+
arr = results || []
|
|
178
|
+
writer.write_varint(arr.length)
|
|
179
|
+
arr.each do |res|
|
|
180
|
+
writer.write_bytes([res[:txid]].pack('H*'))
|
|
181
|
+
code = SEND_WITH_STATUS_CODES.fetch(res[:status]) do
|
|
182
|
+
raise ArgumentError, "invalid send_with status: #{res[:status]}"
|
|
183
|
+
end
|
|
184
|
+
writer.write_byte(code)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Read a send_with_results array.
|
|
189
|
+
# @param reader [Wire::Reader]
|
|
190
|
+
# @return [Array<Hash>, nil]
|
|
191
|
+
def read_send_with_results(reader)
|
|
192
|
+
count = reader.read_varint
|
|
193
|
+
return nil if count.zero?
|
|
194
|
+
|
|
195
|
+
count.times.map do
|
|
196
|
+
txid_hex = reader.read_bytes(32).unpack1('H*')
|
|
197
|
+
code = reader.read_byte
|
|
198
|
+
status = SEND_WITH_CODE_STATUSES.fetch(code) do
|
|
199
|
+
raise ArgumentError, "invalid send_with status code: #{code}"
|
|
200
|
+
end
|
|
201
|
+
{ txid: txid_hex, status: status }
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# BRC-103 serialiser for create_action args (call byte 1).
|
|
7
|
+
#
|
|
8
|
+
# Wire layout (port of go-sdk/wallet/serializer/create_action_args.go):
|
|
9
|
+
# [string] description
|
|
10
|
+
# [optional_bytes] input_beef (NegativeOne = nil)
|
|
11
|
+
# [inputs] NegativeOne | varint_count + input_record…
|
|
12
|
+
# [outputs] NegativeOne | varint_count + output_record…
|
|
13
|
+
# [optional_uint32] lock_time
|
|
14
|
+
# [optional_uint32] version
|
|
15
|
+
# [string_slice] labels
|
|
16
|
+
# [options_block] 0x00 = absent | 0x01 + fields
|
|
17
|
+
#
|
|
18
|
+
# Input record:
|
|
19
|
+
# [32 bytes] outpoint txid (wire order)
|
|
20
|
+
# [varint] outpoint vout
|
|
21
|
+
# [int_bytes or NegativeOne + varint] unlocking_script or length placeholder
|
|
22
|
+
# [string] input_description
|
|
23
|
+
# [optional_uint32] sequence_number
|
|
24
|
+
#
|
|
25
|
+
# Output record:
|
|
26
|
+
# [int_bytes] locking_script
|
|
27
|
+
# [varint] satoshis
|
|
28
|
+
# [string] output_description
|
|
29
|
+
# [optional_string] basket
|
|
30
|
+
# [optional_string] custom_instructions
|
|
31
|
+
# [string_slice] tags
|
|
32
|
+
#
|
|
33
|
+
# Options block (after 0x01 presence byte):
|
|
34
|
+
# [optional_bool] sign_and_process
|
|
35
|
+
# [optional_bool] accept_delayed_broadcast
|
|
36
|
+
# [0x01 or 0xFF] trust_self (1=known, 0xFF=absent)
|
|
37
|
+
# [txid_slice] known_txids
|
|
38
|
+
# [optional_bool] return_txid_only
|
|
39
|
+
# [optional_bool] no_send
|
|
40
|
+
# [optional_bytes] no_send_change (encoded outpoints, NegativeOne = nil)
|
|
41
|
+
# [txid_slice] send_with
|
|
42
|
+
# [optional_bool] randomize_outputs
|
|
43
|
+
module CreateActionArgs
|
|
44
|
+
TRUST_SELF_KNOWN = 1
|
|
45
|
+
|
|
46
|
+
module_function
|
|
47
|
+
|
|
48
|
+
# @param args [Hash]
|
|
49
|
+
# @return [String] binary
|
|
50
|
+
def serialize(args)
|
|
51
|
+
w = Wire::Writer.new
|
|
52
|
+
|
|
53
|
+
w.write_string(args[:description].to_s)
|
|
54
|
+
input_beef = args[:input_beef]
|
|
55
|
+
if input_beef.nil? || input_beef.b.empty?
|
|
56
|
+
w.write_negative_one
|
|
57
|
+
else
|
|
58
|
+
w.write_int_bytes(input_beef)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
serialize_inputs(w, args[:inputs])
|
|
62
|
+
serialize_outputs(w, args[:outputs])
|
|
63
|
+
|
|
64
|
+
w.write_optional_uint32(args[:lock_time])
|
|
65
|
+
w.write_optional_uint32(args[:version])
|
|
66
|
+
w.write_string_slice(args[:labels])
|
|
67
|
+
|
|
68
|
+
serialize_options(w, args[:options])
|
|
69
|
+
|
|
70
|
+
w.buf
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param bytes [String] binary
|
|
74
|
+
# @return [Hash]
|
|
75
|
+
def deserialize(bytes)
|
|
76
|
+
raise ArgumentError, 'empty message' if bytes.b.empty?
|
|
77
|
+
|
|
78
|
+
r = Wire::Reader.new(bytes)
|
|
79
|
+
|
|
80
|
+
description = r.read_string
|
|
81
|
+
input_beef = r.read_int_bytes
|
|
82
|
+
input_beef = nil if input_beef.empty?
|
|
83
|
+
|
|
84
|
+
inputs = deserialize_inputs(r)
|
|
85
|
+
outputs = deserialize_outputs(r)
|
|
86
|
+
|
|
87
|
+
lock_time = r.read_optional_uint32
|
|
88
|
+
version = r.read_optional_uint32
|
|
89
|
+
labels = r.read_string_slice
|
|
90
|
+
|
|
91
|
+
options = deserialize_options(r)
|
|
92
|
+
|
|
93
|
+
result = { description: description }
|
|
94
|
+
result[:input_beef] = input_beef unless input_beef.nil?
|
|
95
|
+
result[:inputs] = inputs unless inputs.nil?
|
|
96
|
+
result[:outputs] = outputs unless outputs.nil?
|
|
97
|
+
result[:lock_time] = lock_time unless lock_time.nil?
|
|
98
|
+
result[:version] = version unless version.nil?
|
|
99
|
+
result[:labels] = labels unless labels.nil?
|
|
100
|
+
result[:options] = options unless options.nil?
|
|
101
|
+
result
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def serialize_inputs(writer, inputs)
|
|
105
|
+
if inputs.nil?
|
|
106
|
+
writer.write_negative_one
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
writer.write_varint(inputs.length)
|
|
111
|
+
inputs.each do |inp|
|
|
112
|
+
txid_hex, vout = inp[:outpoint].split('.')
|
|
113
|
+
writer.write_bytes([txid_hex].pack('H*'))
|
|
114
|
+
writer.write_varint(vout.to_i)
|
|
115
|
+
|
|
116
|
+
script = inp[:unlocking_script]
|
|
117
|
+
if script && !script.b.empty?
|
|
118
|
+
writer.write_int_bytes(script)
|
|
119
|
+
else
|
|
120
|
+
writer.write_negative_one
|
|
121
|
+
writer.write_varint(inp[:unlocking_script_length].to_i)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
writer.write_string(inp[:input_description].to_s)
|
|
125
|
+
writer.write_optional_uint32(inp[:sequence_number])
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def serialize_outputs(writer, outputs)
|
|
130
|
+
if outputs.nil?
|
|
131
|
+
writer.write_negative_one
|
|
132
|
+
return
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
writer.write_varint(outputs.length)
|
|
136
|
+
outputs.each do |out|
|
|
137
|
+
writer.write_int_bytes(out[:locking_script])
|
|
138
|
+
writer.write_varint(out[:satoshis].to_i)
|
|
139
|
+
writer.write_string(out[:output_description].to_s)
|
|
140
|
+
writer.write_optional_string(out[:basket])
|
|
141
|
+
writer.write_optional_string(out[:custom_instructions])
|
|
142
|
+
writer.write_string_slice(out[:tags])
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def serialize_options(writer, opts)
|
|
147
|
+
if opts.nil?
|
|
148
|
+
writer.write_byte(0)
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
writer.write_byte(1)
|
|
153
|
+
writer.write_optional_bool(opts[:sign_and_process])
|
|
154
|
+
writer.write_optional_bool(opts[:accept_delayed_broadcast])
|
|
155
|
+
|
|
156
|
+
if opts[:trust_self] == :known
|
|
157
|
+
writer.write_byte(TRUST_SELF_KNOWN)
|
|
158
|
+
else
|
|
159
|
+
writer.write_byte(0xFF)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
writer.write_txid_slice(opts[:known_txids])
|
|
163
|
+
writer.write_optional_bool(opts[:return_txid_only])
|
|
164
|
+
writer.write_optional_bool(opts[:no_send])
|
|
165
|
+
|
|
166
|
+
no_send_change_bytes = Common.encode_outpoints(opts[:no_send_change])
|
|
167
|
+
writer.write_int_bytes(no_send_change_bytes)
|
|
168
|
+
|
|
169
|
+
writer.write_txid_slice(opts[:send_with])
|
|
170
|
+
writer.write_optional_bool(opts[:randomize_outputs])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def deserialize_inputs(reader)
|
|
174
|
+
count = reader.read_varint
|
|
175
|
+
return nil if count == 0xFFFF_FFFF_FFFF_FFFF
|
|
176
|
+
|
|
177
|
+
count.times.map do
|
|
178
|
+
txid_hex = reader.read_bytes(32).unpack1('H*')
|
|
179
|
+
vout = reader.read_varint
|
|
180
|
+
outpoint = "#{txid_hex}.#{vout}"
|
|
181
|
+
|
|
182
|
+
script_len = reader.read_varint
|
|
183
|
+
inp = { outpoint: outpoint }
|
|
184
|
+
|
|
185
|
+
if script_len == 0xFFFF_FFFF_FFFF_FFFF
|
|
186
|
+
length = reader.read_varint
|
|
187
|
+
inp[:unlocking_script_length] = length
|
|
188
|
+
else
|
|
189
|
+
script = reader.read_bytes(script_len)
|
|
190
|
+
inp[:unlocking_script] = script
|
|
191
|
+
inp[:unlocking_script_length] = script_len
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
inp[:input_description] = reader.read_string
|
|
195
|
+
seq = reader.read_optional_uint32
|
|
196
|
+
inp[:sequence_number] = seq unless seq.nil?
|
|
197
|
+
inp
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def deserialize_outputs(reader)
|
|
202
|
+
count = reader.read_varint
|
|
203
|
+
return nil if count == 0xFFFF_FFFF_FFFF_FFFF
|
|
204
|
+
|
|
205
|
+
count.times.map do
|
|
206
|
+
script = reader.read_int_bytes
|
|
207
|
+
satoshis = reader.read_varint
|
|
208
|
+
out_desc = reader.read_string
|
|
209
|
+
basket = reader.read_optional_string
|
|
210
|
+
custom = reader.read_optional_string
|
|
211
|
+
tags = reader.read_string_slice
|
|
212
|
+
|
|
213
|
+
out = { locking_script: script, satoshis: satoshis, output_description: out_desc }
|
|
214
|
+
out[:basket] = basket unless basket.nil?
|
|
215
|
+
out[:custom_instructions] = custom unless custom.nil?
|
|
216
|
+
out[:tags] = tags unless tags.nil?
|
|
217
|
+
out
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def deserialize_options(reader)
|
|
222
|
+
return nil if reader.read_byte != 1
|
|
223
|
+
|
|
224
|
+
opts = {}
|
|
225
|
+
|
|
226
|
+
sign_and_process = reader.read_optional_bool
|
|
227
|
+
accept_delayed_broadcast = reader.read_optional_bool
|
|
228
|
+
|
|
229
|
+
trust_byte = reader.read_byte
|
|
230
|
+
trust_self = trust_byte == TRUST_SELF_KNOWN ? :known : nil
|
|
231
|
+
|
|
232
|
+
known_txids = reader.read_txid_slice
|
|
233
|
+
return_txid_only = reader.read_optional_bool
|
|
234
|
+
no_send = reader.read_optional_bool
|
|
235
|
+
|
|
236
|
+
no_send_change_bytes = reader.read_int_bytes
|
|
237
|
+
no_send_change = Common.decode_outpoints(no_send_change_bytes.empty? ? nil : no_send_change_bytes)
|
|
238
|
+
|
|
239
|
+
send_with = reader.read_txid_slice
|
|
240
|
+
randomize_outputs = reader.read_optional_bool
|
|
241
|
+
|
|
242
|
+
opts[:sign_and_process] = sign_and_process unless sign_and_process.nil?
|
|
243
|
+
opts[:accept_delayed_broadcast] = accept_delayed_broadcast unless accept_delayed_broadcast.nil?
|
|
244
|
+
opts[:trust_self] = trust_self unless trust_self.nil?
|
|
245
|
+
opts[:known_txids] = known_txids unless known_txids.nil?
|
|
246
|
+
opts[:return_txid_only] = return_txid_only unless return_txid_only.nil?
|
|
247
|
+
opts[:no_send] = no_send unless no_send.nil?
|
|
248
|
+
opts[:no_send_change] = no_send_change unless no_send_change.nil?
|
|
249
|
+
opts[:send_with] = send_with unless send_with.nil?
|
|
250
|
+
opts[:randomize_outputs] = randomize_outputs unless randomize_outputs.nil?
|
|
251
|
+
opts
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private_class_method :serialize_inputs, :serialize_outputs, :serialize_options,
|
|
255
|
+
:deserialize_inputs, :deserialize_outputs, :deserialize_options
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# BRC-103 serialiser for create_action result (call byte 1).
|
|
7
|
+
#
|
|
8
|
+
# Wire layout (port of go-sdk/wallet/serializer/create_action_result.go):
|
|
9
|
+
# [1 byte] 0x00 success sentinel (frame-level, already checked by Frame)
|
|
10
|
+
# [flag + 32 bytes] txid (wire-order) with flag byte: 0=absent, 1=present
|
|
11
|
+
# [flag + int_bytes] tx (BEEF bytes) with flag byte: 0=absent, 1=present + varint_len
|
|
12
|
+
# [optional_bytes] no_send_change encoded outpoints (NegativeOne = nil)
|
|
13
|
+
# [send_with_results] varint count + txid + status_byte each
|
|
14
|
+
# [1 byte flag] signable_transaction: 0=absent, 1=present
|
|
15
|
+
# If signable_transaction present:
|
|
16
|
+
# [int_bytes] tx (BEEF bytes)
|
|
17
|
+
# [int_bytes] reference bytes
|
|
18
|
+
module CreateActionResult
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# @param result [Hash]
|
|
22
|
+
# @return [String] binary
|
|
23
|
+
def serialize(result)
|
|
24
|
+
w = Wire::Writer.new
|
|
25
|
+
w.write_byte(0) # success sentinel
|
|
26
|
+
|
|
27
|
+
txid_bytes = result[:txid] ? [result[:txid]].pack('H*').reverse : nil
|
|
28
|
+
w.write_optional_bytes_with_flag(txid_bytes, fixed_size: 32)
|
|
29
|
+
w.write_optional_bytes_with_flag(result[:tx])
|
|
30
|
+
|
|
31
|
+
no_send_change_bytes = Common.encode_outpoints(result[:no_send_change])
|
|
32
|
+
w.write_int_bytes(no_send_change_bytes)
|
|
33
|
+
|
|
34
|
+
Common.write_send_with_results(w, result[:send_with_results])
|
|
35
|
+
|
|
36
|
+
signable = result[:signable_transaction]
|
|
37
|
+
if signable
|
|
38
|
+
w.write_byte(1)
|
|
39
|
+
w.write_int_bytes(signable[:tx])
|
|
40
|
+
w.write_int_bytes(signable[:reference])
|
|
41
|
+
else
|
|
42
|
+
w.write_byte(0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
w.buf
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param bytes [String] binary
|
|
49
|
+
# @return [Hash]
|
|
50
|
+
def deserialize(bytes)
|
|
51
|
+
raise ArgumentError, 'empty response data' if bytes.b.empty?
|
|
52
|
+
|
|
53
|
+
r = Wire::Reader.new(bytes)
|
|
54
|
+
|
|
55
|
+
status = r.read_byte
|
|
56
|
+
raise ArgumentError, "response indicates failure: #{status}" unless status.zero?
|
|
57
|
+
|
|
58
|
+
txid_raw = r.read_optional_bytes_with_flag(fixed_size: 32)
|
|
59
|
+
txid_hex = txid_raw&.reverse&.unpack1('H*')
|
|
60
|
+
tx = r.read_optional_bytes_with_flag
|
|
61
|
+
|
|
62
|
+
no_send_change_bytes = r.read_int_bytes
|
|
63
|
+
no_send_change = Common.decode_outpoints(no_send_change_bytes.empty? ? nil : no_send_change_bytes)
|
|
64
|
+
|
|
65
|
+
send_with_results = Common.read_send_with_results(r)
|
|
66
|
+
|
|
67
|
+
signable_flag = r.read_byte
|
|
68
|
+
signable_transaction = if signable_flag == 1
|
|
69
|
+
tx_bytes = r.read_int_bytes
|
|
70
|
+
ref_bytes = r.read_int_bytes
|
|
71
|
+
{ tx: tx_bytes, reference: ref_bytes }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
result = {}
|
|
75
|
+
result[:txid] = txid_hex unless txid_hex.nil?
|
|
76
|
+
result[:tx] = tx unless tx.nil?
|
|
77
|
+
result[:no_send_change] = no_send_change unless no_send_change.nil?
|
|
78
|
+
result[:send_with_results] = send_with_results unless send_with_results.nil?
|
|
79
|
+
result[:signable_transaction] = signable_transaction unless signable_transaction.nil?
|
|
80
|
+
result
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# BRC-103 wire codec for the +create_hmac+ call (call byte 13).
|
|
7
|
+
#
|
|
8
|
+
# Port of go-sdk/wallet/serializer/create_hmac.go.
|
|
9
|
+
module CreateHmac
|
|
10
|
+
HMAC_SIZE = 32
|
|
11
|
+
|
|
12
|
+
# Args wire layout:
|
|
13
|
+
# [key-related params]
|
|
14
|
+
# [VarInt data_len][data bytes]
|
|
15
|
+
# [optional_bool seek_permission]
|
|
16
|
+
module Args
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def serialize(args)
|
|
20
|
+
w = BSV::Wallet::Wire::Writer.new
|
|
21
|
+
Common.write_key_related_params(
|
|
22
|
+
w,
|
|
23
|
+
protocol_id: args[:protocol_id],
|
|
24
|
+
key_id: args[:key_id],
|
|
25
|
+
counterparty: args[:counterparty],
|
|
26
|
+
privileged: args[:privileged],
|
|
27
|
+
privileged_reason: args[:privileged_reason]
|
|
28
|
+
)
|
|
29
|
+
data = Common.to_binary(args[:data])
|
|
30
|
+
w.write_varint(data.bytesize)
|
|
31
|
+
w.write_bytes(data)
|
|
32
|
+
w.write_optional_bool(args[:seek_permission])
|
|
33
|
+
w.buf
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def deserialize(bytes)
|
|
37
|
+
r = BSV::Wallet::Wire::Reader.new(bytes)
|
|
38
|
+
params = Common.read_key_related_params(r)
|
|
39
|
+
len = r.read_varint
|
|
40
|
+
data = r.read_bytes(len)
|
|
41
|
+
seek_permission = r.read_optional_bool
|
|
42
|
+
params.merge(data: data, seek_permission: seek_permission)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Result wire layout:
|
|
47
|
+
# [32 bytes: HMAC-SHA-256]
|
|
48
|
+
module Result
|
|
49
|
+
module_function
|
|
50
|
+
|
|
51
|
+
def serialize(result)
|
|
52
|
+
hmac = Common.to_binary(result[:hmac])
|
|
53
|
+
raise ArgumentError, "HMAC must be #{HMAC_SIZE} bytes, got #{hmac.bytesize}" unless hmac.bytesize == HMAC_SIZE
|
|
54
|
+
|
|
55
|
+
hmac
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def deserialize(bytes)
|
|
59
|
+
raise ArgumentError, "HMAC result too short: #{bytes.bytesize}" if bytes.bytesize < HMAC_SIZE
|
|
60
|
+
|
|
61
|
+
{ hmac: bytes.byteslice(0, HMAC_SIZE) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|