bsv-sdk 0.19.1 → 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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -0
  3. data/README.md +2 -2
  4. data/lib/bsv/auth/transport.rb +1 -1
  5. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
  6. data/lib/bsv/network/protocol.rb +4 -5
  7. data/lib/bsv/network/protocols/arc.rb +4 -30
  8. data/lib/bsv/network/protocols/arcade.rb +163 -0
  9. data/lib/bsv/network/protocols/chaintracks.rb +6 -3
  10. data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
  11. data/lib/bsv/network/protocols.rb +1 -0
  12. data/lib/bsv/network/provider.rb +7 -9
  13. data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
  14. data/lib/bsv/network/util.rb +44 -0
  15. data/lib/bsv/network.rb +1 -0
  16. data/lib/bsv/overlay/lookup_resolver.rb +0 -1
  17. data/lib/bsv/overlay/topic_broadcaster.rb +0 -1
  18. data/lib/bsv/primitives/curve.rb +1 -11
  19. data/lib/bsv/primitives/ecies.rb +1 -8
  20. data/lib/bsv/primitives/hex.rb +1 -1
  21. data/lib/bsv/script/script.rb +1 -1
  22. data/lib/bsv/transaction/beef.rb +0 -2
  23. data/lib/bsv/transaction/chain_tracker.rb +74 -13
  24. data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +3 -3
  25. data/lib/bsv/transaction/chain_trackers.rb +0 -10
  26. data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
  27. data/lib/bsv/transaction/merkle_path.rb +0 -2
  28. data/lib/bsv/version.rb +1 -1
  29. data/lib/bsv/wallet/errors.rb +65 -21
  30. data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
  31. data/lib/bsv/wallet/proto_wallet.rb +15 -8
  32. data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
  33. data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
  34. data/lib/bsv/wallet/serializer/certificate.rb +184 -0
  35. data/lib/bsv/wallet/serializer/common.rb +207 -0
  36. data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
  37. data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
  38. data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
  39. data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
  40. data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
  41. data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
  42. data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
  43. data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
  44. data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
  45. data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
  46. data/lib/bsv/wallet/serializer/get_height.rb +46 -0
  47. data/lib/bsv/wallet/serializer/get_network.rb +65 -0
  48. data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
  49. data/lib/bsv/wallet/serializer/get_version.rb +44 -0
  50. data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
  51. data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
  52. data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
  53. data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
  54. data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
  55. data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
  56. data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
  57. data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
  58. data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
  59. data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
  60. data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
  61. data/lib/bsv/wallet/serializer/status.rb +85 -0
  62. data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
  63. data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
  64. data/lib/bsv/wallet/serializer.rb +180 -0
  65. data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
  66. data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
  67. data/lib/bsv/wallet/wallet_wire.rb +20 -0
  68. data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
  69. data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
  70. data/lib/bsv/wallet/wire/calls.rb +79 -0
  71. data/lib/bsv/wallet/wire/frame.rb +181 -0
  72. data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
  73. data/lib/bsv/wallet/wire/validation.rb +213 -0
  74. data/lib/bsv/wallet/wire.rb +13 -0
  75. data/lib/bsv/wallet.rb +17 -0
  76. metadata +47 -3
  77. data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # Client-side BRC-103 transceiver.
6
+ #
7
+ # Implements every {Interface::BRC100} method by serialising the call
8
+ # arguments to a binary request frame, transmitting it over a {WalletWire},
9
+ # unframing the result, and deserialising the payload back to a Ruby hash.
10
+ #
11
+ # @example In-process loopback (testing / same-process use)
12
+ # proto = BSV::Wallet::ProtoWallet.new(key)
13
+ # processor = BSV::Wallet::WalletWireProcessor.new(proto)
14
+ # client = BSV::Wallet::WalletWireTransceiver.new(processor)
15
+ # client.get_public_key(identity_key: true)
16
+ # #=> { public_key: "02..." }
17
+ #
18
+ # @example Over HTTP
19
+ # wire = BSV::Wallet::Substrates::HTTPWalletWire.new(base_url: 'https://wallet.example')
20
+ # client = BSV::Wallet::WalletWireTransceiver.new(wire)
21
+ #
22
+ # Thread safety: each call is independent. The wire transport is the
23
+ # synchronisation boundary — concurrent calls are serialised only if the
24
+ # underlying wire implementation serialises them.
25
+ class WalletWireTransceiver
26
+ include Interface::BRC100
27
+
28
+ # @param wire [#transmit_to_wallet] any object that includes {WalletWire}
29
+ def initialize(wire)
30
+ @wire = wire
31
+ end
32
+
33
+ # Generate the 28 BRC-100 methods via metaprogramming.
34
+ #
35
+ # For each call byte → method name mapping in CALL_TO_METHOD, define a
36
+ # method that:
37
+ # 1. Extracts originator from kwargs (default '').
38
+ # 2. Serialises the args via the SERIALIZE_ARGS dispatch table.
39
+ # 3. Frames and transmits the binary request.
40
+ # 4. Unframes the result (raises on error frame).
41
+ # 5. Deserialises the payload via DESERIALIZE_RESULT.
42
+ Wire::Calls::CALL_TO_METHOD.each do |call_byte, method_name|
43
+ define_method(method_name) do |**args|
44
+ _dispatch(call_byte, args)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def _dispatch(call_byte, args)
51
+ originator = args[:originator].to_s
52
+ Wire::Validation.originator_domain!('originator', originator) unless originator.empty?
53
+ params = Serializer::SERIALIZE_ARGS.fetch(call_byte).call(args)
54
+ frame = Wire::Frame.write_request(call: call_byte, originator: originator, params: params)
55
+ reply = @wire.transmit_to_wallet(frame)
56
+ payload = Wire::Frame.read_result(reply)
57
+ Serializer::DESERIALIZE_RESULT.fetch(call_byte).call(payload)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Wire
6
+ # BRC-103 call byte constants and dispatch tables.
7
+ #
8
+ # Each constant maps to the corresponding Go SDK CallXxx constant in
9
+ # go-sdk/wallet/substrates/wallet_wire_calls.go. The CALL_TO_METHOD
10
+ # table maps each byte to the actual Ruby method name on Interface::BRC100.
11
+ #
12
+ # Note: call byte 23 maps to :authenticated? (predicate suffix preserved),
13
+ # not :is_authenticated, to match the existing Interface::BRC100 definition.
14
+ module Calls
15
+ CREATE_ACTION = 1
16
+ SIGN_ACTION = 2
17
+ ABORT_ACTION = 3
18
+ LIST_ACTIONS = 4
19
+ INTERNALIZE_ACTION = 5
20
+ LIST_OUTPUTS = 6
21
+ RELINQUISH_OUTPUT = 7
22
+ GET_PUBLIC_KEY = 8
23
+ REVEAL_COUNTERPARTY_KEY_LINKAGE = 9
24
+ REVEAL_SPECIFIC_KEY_LINKAGE = 10
25
+ ENCRYPT = 11
26
+ DECRYPT = 12
27
+ CREATE_HMAC = 13
28
+ VERIFY_HMAC = 14
29
+ CREATE_SIGNATURE = 15
30
+ VERIFY_SIGNATURE = 16
31
+ ACQUIRE_CERTIFICATE = 17
32
+ LIST_CERTIFICATES = 18
33
+ PROVE_CERTIFICATE = 19
34
+ RELINQUISH_CERTIFICATE = 20
35
+ DISCOVER_BY_IDENTITY_KEY = 21
36
+ DISCOVER_BY_ATTRIBUTES = 22
37
+ IS_AUTHENTICATED = 23
38
+ WAIT_FOR_AUTHENTICATION = 24
39
+ GET_HEIGHT = 25
40
+ GET_HEADER_FOR_HEIGHT = 26
41
+ GET_NETWORK = 27
42
+ GET_VERSION = 28
43
+
44
+ CALL_TO_METHOD = {
45
+ CREATE_ACTION => :create_action,
46
+ SIGN_ACTION => :sign_action,
47
+ ABORT_ACTION => :abort_action,
48
+ LIST_ACTIONS => :list_actions,
49
+ INTERNALIZE_ACTION => :internalize_action,
50
+ LIST_OUTPUTS => :list_outputs,
51
+ RELINQUISH_OUTPUT => :relinquish_output,
52
+ GET_PUBLIC_KEY => :get_public_key,
53
+ REVEAL_COUNTERPARTY_KEY_LINKAGE => :reveal_counterparty_key_linkage,
54
+ REVEAL_SPECIFIC_KEY_LINKAGE => :reveal_specific_key_linkage,
55
+ ENCRYPT => :encrypt,
56
+ DECRYPT => :decrypt,
57
+ CREATE_HMAC => :create_hmac,
58
+ VERIFY_HMAC => :verify_hmac,
59
+ CREATE_SIGNATURE => :create_signature,
60
+ VERIFY_SIGNATURE => :verify_signature,
61
+ ACQUIRE_CERTIFICATE => :acquire_certificate,
62
+ LIST_CERTIFICATES => :list_certificates,
63
+ PROVE_CERTIFICATE => :prove_certificate,
64
+ RELINQUISH_CERTIFICATE => :relinquish_certificate,
65
+ DISCOVER_BY_IDENTITY_KEY => :discover_by_identity_key,
66
+ DISCOVER_BY_ATTRIBUTES => :discover_by_attributes,
67
+ IS_AUTHENTICATED => :authenticated?,
68
+ WAIT_FOR_AUTHENTICATION => :wait_for_authentication,
69
+ GET_HEIGHT => :get_height,
70
+ GET_HEADER_FOR_HEIGHT => :get_header_for_height,
71
+ GET_NETWORK => :get_network,
72
+ GET_VERSION => :get_version
73
+ }.freeze
74
+
75
+ METHOD_TO_CALL = CALL_TO_METHOD.invert.freeze
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Wire
6
+ # BRC-103 request and result frame codec.
7
+ #
8
+ # Port of go-sdk/wallet/serializer/frame.go. Two frame types:
9
+ #
10
+ # Request frame (client → wallet):
11
+ # [1 byte: call][1 byte: originator_len][originator_len bytes: UTF-8][remaining: params]
12
+ #
13
+ # Result frame (wallet → client):
14
+ # [1 byte: error_code]
15
+ # On success (0x00): [remaining bytes: payload]
16
+ # On error (non-zero):
17
+ # [VarInt: message_len][message bytes]
18
+ # [VarInt: stack_len][stack bytes]
19
+ module Frame
20
+ # Maximum originator byte length enforced at write time.
21
+ # Matches the BRC-100 +OriginatorDomainNameStringUnder250Bytes+ branded
22
+ # type used by +Wire::Validation.originator_domain!+.
23
+ MAX_ORIGINATOR_BYTES = 250
24
+
25
+ module_function
26
+
27
+ # Encode a request frame.
28
+ #
29
+ # @param call [Integer] call byte (1..28)
30
+ # @param originator [String] originator domain (0..250 bytes UTF-8)
31
+ # @param params [String, nil] binary params payload
32
+ # @return [String] binary frame (ASCII-8BIT encoding)
33
+ # @raise [ArgumentError] if originator exceeds 250 bytes
34
+ def write_request(call:, originator:, params: nil)
35
+ originator_bytes = originator.to_s.b
36
+ if originator_bytes.bytesize > MAX_ORIGINATOR_BYTES
37
+ raise ArgumentError,
38
+ "originator must be at most #{MAX_ORIGINATOR_BYTES} bytes, " \
39
+ "got #{originator_bytes.bytesize}"
40
+ end
41
+
42
+ buf = String.new(encoding: 'BINARY')
43
+ buf << [call, originator_bytes.bytesize].pack('CC')
44
+ buf << originator_bytes
45
+ buf << params.b if params && !params.empty?
46
+ buf
47
+ end
48
+
49
+ # Decode a request frame.
50
+ #
51
+ # @param bytes [String] binary frame
52
+ # @return [Hash] { call: Integer, originator: String, params: String }
53
+ # @raise [ArgumentError] if the frame is truncated or malformed
54
+ def read_request(bytes)
55
+ data = bytes.b
56
+ raise ArgumentError, 'frame too short: need at least 2 bytes' if data.bytesize < 2
57
+
58
+ call = data.getbyte(0)
59
+ originator_len = data.getbyte(1)
60
+
61
+ if data.bytesize < 2 + originator_len
62
+ raise ArgumentError,
63
+ "frame truncated: need #{2 + originator_len} bytes for originator, " \
64
+ "got #{data.bytesize}"
65
+ end
66
+
67
+ originator = data.byteslice(2, originator_len).force_encoding('UTF-8')
68
+ raise ArgumentError, 'frame originator is not valid UTF-8' unless originator.valid_encoding?
69
+
70
+ params = data.byteslice(2 + originator_len, data.bytesize - 2 - originator_len) || ''.b
71
+
72
+ { call: call, originator: originator, params: params }
73
+ end
74
+
75
+ # Encode a success result frame.
76
+ #
77
+ # @param payload [String, nil] binary payload
78
+ # @return [String] binary frame
79
+ def write_result(payload: nil)
80
+ buf = String.new(encoding: 'BINARY')
81
+ buf << "\x00"
82
+ buf << payload.b if payload && !payload.empty?
83
+ buf
84
+ end
85
+
86
+ # Encode an error result frame.
87
+ #
88
+ # @param error [BSV::Wallet::Error] the error to encode
89
+ # @return [String] binary frame
90
+ def write_error(error:)
91
+ wire = error.to_wire
92
+ msg_bytes = wire[:message].to_s.b
93
+ stack_bytes = wire[:stack].to_s.b
94
+
95
+ buf = String.new(encoding: 'BINARY')
96
+ buf << [wire[:code]].pack('C')
97
+ buf << encode_varint(msg_bytes.bytesize)
98
+ buf << msg_bytes
99
+ buf << encode_varint(stack_bytes.bytesize)
100
+ buf << stack_bytes
101
+ buf
102
+ end
103
+
104
+ # Decode a result frame.
105
+ #
106
+ # @param bytes [String] binary frame
107
+ # @return [String] binary payload on success
108
+ # @raise [BSV::Wallet::Error] the appropriate subclass on error
109
+ # @raise [ArgumentError] if the frame is truncated or malformed
110
+ def read_result(bytes)
111
+ data = bytes.b
112
+ raise ArgumentError, 'result frame is empty' if data.empty?
113
+
114
+ code = data.getbyte(0)
115
+
116
+ return data.byteslice(1, data.bytesize - 1) || ''.b if code.zero?
117
+
118
+ offset = 1
119
+ msg_len, vi = decode_varint(data, offset)
120
+ offset += vi
121
+
122
+ raise ArgumentError, 'result frame truncated: message' if data.bytesize < offset + msg_len
123
+
124
+ message = data.byteslice(offset, msg_len).force_encoding('UTF-8')
125
+ raise ArgumentError, 'result frame error message is not valid UTF-8' unless message.valid_encoding?
126
+
127
+ offset += msg_len
128
+
129
+ stack_len, vi = decode_varint(data, offset)
130
+ offset += vi
131
+
132
+ raise ArgumentError, 'result frame truncated: stack' if data.bytesize < offset + stack_len
133
+
134
+ stack = data.byteslice(offset, stack_len).force_encoding('UTF-8')
135
+ raise ArgumentError, 'result frame stack is not valid UTF-8' unless stack.valid_encoding?
136
+
137
+ raise BSV::Wallet.error_from_wire(code, message, stack)
138
+ end
139
+
140
+ # @param n [Integer] unsigned integer
141
+ # @return [String] Bitcoin varint encoding
142
+ def encode_varint(n)
143
+ if n < 0xFD
144
+ [n].pack('C')
145
+ elsif n <= 0xFFFF
146
+ [0xFD, n].pack('Cv')
147
+ elsif n <= 0xFFFFFFFF
148
+ [0xFE, n].pack('CV')
149
+ else
150
+ [0xFF, n].pack('CQ<')
151
+ end
152
+ end
153
+
154
+ # @param data [String] binary data
155
+ # @param offset [Integer] byte offset
156
+ # @return [Array(Integer, Integer)] decoded value, bytes consumed
157
+ def decode_varint(data, offset = 0)
158
+ raise ArgumentError, "varint: need 1 byte at #{offset}" if offset >= data.bytesize
159
+
160
+ first = data.getbyte(offset)
161
+ case first
162
+ when 0..0xFC
163
+ [first, 1]
164
+ when 0xFD
165
+ raise ArgumentError, "varint: need 3 bytes at #{offset}" if data.bytesize < offset + 3
166
+
167
+ [data.byteslice(offset + 1, 2).unpack1('v'), 3]
168
+ when 0xFE
169
+ raise ArgumentError, "varint: need 5 bytes at #{offset}" if data.bytesize < offset + 5
170
+
171
+ [data.byteslice(offset + 1, 4).unpack1('V'), 5]
172
+ when 0xFF
173
+ raise ArgumentError, "varint: need 9 bytes at #{offset}" if data.bytesize < offset + 9
174
+
175
+ [data.byteslice(offset + 1, 8).unpack1('Q<'), 9]
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end