bsv-sdk 0.14.0 → 0.16.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 +43 -0
- data/README.md +14 -2
- data/lib/bsv/auth/auth_middleware.rb +6 -6
- data/lib/bsv/auth/certificate.rb +16 -16
- data/lib/bsv/auth/master_certificate.rb +5 -5
- data/lib/bsv/auth/nonce.rb +13 -13
- data/lib/bsv/auth/peer.rb +53 -53
- data/lib/bsv/auth/verifiable_certificate.rb +1 -1
- data/lib/bsv/identity/client.rb +26 -32
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +17 -11
- data/lib/bsv/mcp/tools/check_balance.rb +16 -4
- data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
- data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
- data/lib/bsv/network/arc.rb +13 -153
- data/lib/bsv/network/whats_on_chain.rb +13 -107
- data/lib/bsv/overlay/admin_token_template.rb +4 -4
- data/lib/bsv/primitives/base58.rb +2 -1
- data/lib/bsv/primitives/curve.rb +37 -12
- data/lib/bsv/primitives/ecdsa.rb +4 -4
- data/lib/bsv/primitives/openssl_ec_shim.rb +32 -5
- data/lib/bsv/primitives/private_key.rb +2 -2
- data/lib/bsv/primitives/public_key.rb +1 -1
- data/lib/bsv/primitives/schnorr.rb +4 -4
- data/lib/bsv/primitives/secp256k1.rb +4 -595
- data/lib/bsv/primitives/signature.rb +2 -0
- data/lib/bsv/primitives/signed_message.rb +6 -5
- data/lib/bsv/registry/client.rb +23 -27
- data/lib/bsv/script/push_drop_template.rb +4 -4
- data/lib/bsv/secp256k1_native.bundle +0 -0
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +47 -0
- data/lib/bsv/wallet/interface/brc100.rb +267 -0
- data/lib/bsv/wallet/interface.rb +9 -0
- data/lib/bsv/wallet/proto_wallet/key_deriver.rb +150 -0
- data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
- data/lib/bsv/wallet/proto_wallet.rb +321 -0
- data/lib/bsv/wallet.rb +16 -0
- data/lib/bsv-sdk.rb +4 -1
- metadata +37 -1
|
@@ -49,8 +49,17 @@ module BSV
|
|
|
49
49
|
|
|
50
50
|
def self.call(txid:, network: nil, server_context: nil)
|
|
51
51
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
53
|
+
fetch_result = provider.call(:get_tx, txid)
|
|
54
|
+
|
|
55
|
+
unless fetch_result.success?
|
|
56
|
+
code = fetch_result.metadata[:status_code]
|
|
57
|
+
msg = fetch_result.message
|
|
58
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
59
|
+
return Helpers.error_response(msg)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
tx = BSV::Transaction::Transaction.from_hex(fetch_result.data)
|
|
54
63
|
|
|
55
64
|
result = {
|
|
56
65
|
hex: tx.to_hex,
|
|
@@ -61,8 +70,6 @@ module BSV
|
|
|
61
70
|
[::MCP::Content::Text.new(result.to_json)],
|
|
62
71
|
structured_content: result
|
|
63
72
|
)
|
|
64
|
-
rescue BSV::Network::ChainProviderError => e
|
|
65
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
66
73
|
rescue ArgumentError => e
|
|
67
74
|
Helpers.error_response(e.message)
|
|
68
75
|
end
|
|
@@ -50,8 +50,22 @@ module BSV
|
|
|
50
50
|
|
|
51
51
|
def self.call(address:, network: nil, server_context: nil)
|
|
52
52
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
54
|
+
utxo_result = provider.call(:get_utxos_all, address)
|
|
55
|
+
|
|
56
|
+
unless utxo_result.success?
|
|
57
|
+
code = utxo_result.metadata[:status_code]
|
|
58
|
+
msg = utxo_result.message
|
|
59
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
60
|
+
return Helpers.error_response(msg)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
utxos = utxo_result.data.map do |entry|
|
|
64
|
+
BSV::Network::UTXO.new(
|
|
65
|
+
tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
|
|
66
|
+
satoshis: entry[:satoshis], height: entry[:height]
|
|
67
|
+
)
|
|
68
|
+
end
|
|
55
69
|
|
|
56
70
|
result = {
|
|
57
71
|
address: address,
|
|
@@ -63,8 +77,6 @@ module BSV
|
|
|
63
77
|
[::MCP::Content::Text.new(result.to_json)],
|
|
64
78
|
structured_content: result
|
|
65
79
|
)
|
|
66
|
-
rescue BSV::Network::ChainProviderError => e
|
|
67
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
68
80
|
end
|
|
69
81
|
end
|
|
70
82
|
end
|
data/lib/bsv/network/arc.rb
CHANGED
|
@@ -2,164 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# the protocol into +BroadcastResponse+ instances or raised +BroadcastError+
|
|
10
|
-
# exceptions as required by consumer code.
|
|
11
|
-
#
|
|
12
|
-
# Any object responding to #broadcast(tx) can serve as a broadcaster;
|
|
13
|
-
# this class implements that contract using the ARC API.
|
|
5
|
+
# @deprecated Use {BSV::Network::Protocols::ARC} directly instead.
|
|
6
|
+
# The facade converted clean Result objects into exceptions — every
|
|
7
|
+
# consumer immediately caught them and converted back to data.
|
|
8
|
+
# Use the protocol layer, which returns Result objects natively.
|
|
14
9
|
class ARC
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
# @param testnet [Boolean] when true, uses the GorillaPool testnet endpoint
|
|
18
|
-
# @param api_key [String, nil] optional bearer token for Authorization
|
|
19
|
-
# @param http_client [#request, nil] injectable HTTP client for testing
|
|
20
|
-
# @param opts [Hash] ARC-specific options forwarded to the protocol
|
|
21
|
-
# (e.g. +deployment_id:+, +callback_url:+, +callback_token:+)
|
|
22
|
-
# @return [ARC]
|
|
23
|
-
def self.default(testnet: false, api_key: nil, http_client: nil, **opts)
|
|
24
|
-
provider = Providers::GorillaPool.default(testnet: testnet)
|
|
25
|
-
arc_protocol = provider.protocol_for(:broadcast)
|
|
26
|
-
base_url = arc_protocol.base_url
|
|
27
|
-
new(
|
|
28
|
-
base_url,
|
|
29
|
-
api_key: api_key,
|
|
30
|
-
http_client: http_client,
|
|
31
|
-
**opts
|
|
32
|
-
)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# ARC response statuses that indicate the transaction was NOT accepted.
|
|
36
|
-
REJECTED_STATUSES = %w[
|
|
37
|
-
REJECTED
|
|
38
|
-
DOUBLE_SPEND_ATTEMPTED
|
|
39
|
-
INVALID
|
|
40
|
-
MALFORMED
|
|
41
|
-
MINED_IN_STALE_BLOCK
|
|
42
|
-
].freeze
|
|
43
|
-
|
|
44
|
-
# @param url_or_protocol [String, Protocols::ARC] ARC base URL (without
|
|
45
|
-
# trailing slash) or a pre-configured +Protocols::ARC+ instance. When a
|
|
46
|
-
# URL string is supplied, the remaining keyword arguments are forwarded to
|
|
47
|
-
# the underlying protocol constructor. When a protocol instance is supplied,
|
|
48
|
-
# all keyword arguments are ignored.
|
|
49
|
-
# @param api_key [String, nil] optional bearer token for Authorization
|
|
50
|
-
# @param deployment_id [String, nil] optional deployment identifier for
|
|
51
|
-
# the +XDeployment-ID+ header; defaults to a per-instance random value
|
|
52
|
-
# @param callback_url [String, nil] optional +X-CallbackUrl+ for ARC
|
|
53
|
-
# status callbacks
|
|
54
|
-
# @param callback_token [String, nil] optional +X-CallbackToken+ for
|
|
55
|
-
# ARC status callback authentication
|
|
56
|
-
# @param http_client [#request, nil] injectable HTTP client for testing
|
|
57
|
-
def initialize(url_or_protocol, api_key: nil, deployment_id: nil, callback_url: nil,
|
|
58
|
-
callback_token: nil, http_client: nil)
|
|
59
|
-
@protocol =
|
|
60
|
-
if url_or_protocol.is_a?(String)
|
|
61
|
-
Protocols::ARC.new(
|
|
62
|
-
base_url: url_or_protocol,
|
|
63
|
-
api_key: api_key,
|
|
64
|
-
deployment_id: deployment_id,
|
|
65
|
-
callback_url: callback_url,
|
|
66
|
-
callback_token: callback_token,
|
|
67
|
-
http_client: http_client
|
|
68
|
-
)
|
|
69
|
-
else
|
|
70
|
-
url_or_protocol
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Submit a transaction to ARC.
|
|
75
|
-
#
|
|
76
|
-
# @param tx [Transaction] the transaction to broadcast
|
|
77
|
-
# @param wait_for [String, nil] ARC wait condition
|
|
78
|
-
# @param skip_fee_validation [Boolean, nil] when truthy, sends X-SkipFeeValidation header
|
|
79
|
-
# @param skip_script_validation [Boolean, nil] when truthy, sends X-SkipScriptValidation header
|
|
80
|
-
# @return [BroadcastResponse]
|
|
81
|
-
# @raise [BroadcastError] when ARC returns a non-2xx HTTP status or a
|
|
82
|
-
# rejected/orphan +txStatus+
|
|
83
|
-
def broadcast(tx, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
|
|
84
|
-
result = @protocol.call(
|
|
85
|
-
:broadcast, tx,
|
|
86
|
-
wait_for: wait_for,
|
|
87
|
-
skip_fee_validation: skip_fee_validation,
|
|
88
|
-
skip_script_validation: skip_script_validation
|
|
89
|
-
)
|
|
90
|
-
result_to_response!(result)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Submit multiple transactions to ARC in a single batch request.
|
|
94
|
-
#
|
|
95
|
-
# Returns a mixed array of {BroadcastResponse} and {BroadcastError} objects.
|
|
96
|
-
# Per-transaction rejections are returned as {BroadcastError} values rather
|
|
97
|
-
# than raised. Only HTTP-level errors raise a {BroadcastError} for the whole batch.
|
|
98
|
-
#
|
|
99
|
-
# @param txs [Array<Transaction>] transactions to broadcast
|
|
100
|
-
# @param wait_for [String, nil] ARC wait condition
|
|
101
|
-
# @param skip_fee_validation [Boolean, nil]
|
|
102
|
-
# @param skip_script_validation [Boolean, nil]
|
|
103
|
-
# @return [Array<BroadcastResponse, BroadcastError>]
|
|
104
|
-
# @raise [BroadcastError] when ARC returns a non-2xx HTTP status
|
|
105
|
-
def broadcast_many(txs, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)
|
|
106
|
-
result = @protocol.call(
|
|
107
|
-
:broadcast_many, txs,
|
|
108
|
-
wait_for: wait_for,
|
|
109
|
-
skip_fee_validation: skip_fee_validation,
|
|
110
|
-
skip_script_validation: skip_script_validation
|
|
111
|
-
)
|
|
10
|
+
# Raised when deprecated facade classes are instantiated.
|
|
11
|
+
class DeprecationError < StandardError; end
|
|
112
12
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
else
|
|
117
|
-
raise broadcast_error_from_result(result)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Query the status of a previously submitted transaction.
|
|
122
|
-
#
|
|
123
|
-
# @param txid [String] transaction ID to query
|
|
124
|
-
# @return [BroadcastResponse]
|
|
125
|
-
# @raise [BroadcastError] on failure
|
|
126
|
-
def status(txid)
|
|
127
|
-
result = @protocol.call(:get_tx_status, txid)
|
|
128
|
-
result_to_response!(result)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
private
|
|
132
|
-
|
|
133
|
-
# Translate a single-tx Result into a BroadcastResponse or raise BroadcastError.
|
|
134
|
-
def result_to_response!(result)
|
|
135
|
-
case result
|
|
136
|
-
when Result::Success
|
|
137
|
-
BroadcastResponse.new(result.data)
|
|
138
|
-
else
|
|
139
|
-
raise broadcast_error_from_result(result)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
13
|
+
MESSAGE = 'BSV::Network::ARC is deprecated. ' \
|
|
14
|
+
'Use BSV::Network::Protocols::ARC directly — it returns Result objects ' \
|
|
15
|
+
'instead of raising exceptions. See BSV::Network::Protocols::ARC for usage.'
|
|
142
16
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def item_to_response_or_error(item)
|
|
146
|
-
case item
|
|
147
|
-
when Result::Success
|
|
148
|
-
BroadcastResponse.new(item.data)
|
|
149
|
-
else
|
|
150
|
-
broadcast_error_from_result(item)
|
|
151
|
-
end
|
|
17
|
+
def self.default(**)
|
|
18
|
+
raise DeprecationError, MESSAGE
|
|
152
19
|
end
|
|
153
20
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
meta = result.metadata || {}
|
|
157
|
-
BroadcastError.new(
|
|
158
|
-
result.message,
|
|
159
|
-
status_code: meta[:status_code],
|
|
160
|
-
txid: meta[:txid],
|
|
161
|
-
arc_status: meta[:arc_status]
|
|
162
|
-
)
|
|
21
|
+
def initialize(*)
|
|
22
|
+
raise DeprecationError, MESSAGE
|
|
163
23
|
end
|
|
164
24
|
end
|
|
165
25
|
end
|
|
@@ -2,118 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Network
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# #fetch_transaction(txid), #current_height,
|
|
10
|
-
# #get_block_header(height), and optionally
|
|
11
|
-
# #valid_root_for_height?(root_hex, height) can serve as a chain
|
|
12
|
-
# data source;
|
|
13
|
-
# this class implements that contract by delegating to
|
|
14
|
-
# Protocols::WoCREST.
|
|
15
|
-
#
|
|
16
|
-
# The HTTP client is injectable for testability. It must respond to
|
|
17
|
-
# #request(uri, request) and return an object with #code and #body.
|
|
5
|
+
# @deprecated Use {BSV::Network::Protocols::WoCREST} directly instead.
|
|
6
|
+
# The facade converted clean Result objects into exceptions — every
|
|
7
|
+
# consumer immediately caught them and converted back to data.
|
|
8
|
+
# Use the protocol layer, which returns Result objects natively.
|
|
18
9
|
class WhatsOnChain
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
# @param testnet [Boolean] when true, uses the testnet endpoint
|
|
22
|
-
# @param opts [Hash] forwarded to the underlying protocol (e.g. +api_key:+, +http_client:+)
|
|
23
|
-
# @return [WhatsOnChain]
|
|
24
|
-
def self.default(testnet: false, **opts)
|
|
25
|
-
provider = Providers::WhatsOnChain.default(testnet: testnet, **opts)
|
|
26
|
-
new(protocol: provider.protocol_for(:get_tx))
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# @param network [Symbol] :main, :mainnet, :test, :testnet, :stn (legacy compat)
|
|
30
|
-
# @param http_client [#request, nil] injectable HTTP client
|
|
31
|
-
# @param protocol [BSV::Network::Protocols::WoCREST, nil] pre-configured protocol
|
|
32
|
-
def initialize(network: :mainnet, http_client: nil, protocol: nil)
|
|
33
|
-
if protocol
|
|
34
|
-
@protocol = protocol
|
|
35
|
-
else
|
|
36
|
-
provider = Providers::WhatsOnChain.default(network: network, http_client: http_client)
|
|
37
|
-
@protocol = provider.protocol_for(:get_tx)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Fetch unspent transaction outputs for an address.
|
|
42
|
-
# @param address [String] BSV address
|
|
43
|
-
# @return [Array<UTXO>]
|
|
44
|
-
def fetch_utxos(address)
|
|
45
|
-
result = @protocol.call(:get_utxos_all, address)
|
|
46
|
-
raise_on_error(result)
|
|
47
|
-
|
|
48
|
-
result.data.map do |entry|
|
|
49
|
-
UTXO.new(
|
|
50
|
-
tx_hash: entry[:tx_hash],
|
|
51
|
-
tx_pos: entry[:tx_pos],
|
|
52
|
-
satoshis: entry[:satoshis],
|
|
53
|
-
height: entry[:height]
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Fetch a raw transaction by its txid and parse it.
|
|
59
|
-
# @param txid [String] transaction ID (hex)
|
|
60
|
-
# @return [BSV::Transaction::Transaction]
|
|
61
|
-
def fetch_transaction(txid)
|
|
62
|
-
result = @protocol.call(:get_tx, txid)
|
|
63
|
-
raise_on_error(result)
|
|
64
|
-
|
|
65
|
-
BSV::Transaction::Transaction.from_hex(result.data)
|
|
66
|
-
end
|
|
10
|
+
# Raised when deprecated facade classes are instantiated.
|
|
11
|
+
class DeprecationError < StandardError; end
|
|
67
12
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def current_height
|
|
72
|
-
result = @protocol.call(:current_height)
|
|
73
|
-
raise_on_error(result)
|
|
13
|
+
MESSAGE = 'BSV::Network::WhatsOnChain is deprecated. ' \
|
|
14
|
+
'Use BSV::Network::Protocols::WoCREST directly — it returns Result objects ' \
|
|
15
|
+
'instead of raising exceptions. See BSV::Network::Protocols::WoCREST for usage.'
|
|
74
16
|
|
|
75
|
-
|
|
17
|
+
def self.default(**)
|
|
18
|
+
raise DeprecationError, MESSAGE
|
|
76
19
|
end
|
|
77
20
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# @return [Hash] parsed block header JSON
|
|
81
|
-
# @raise [BSV::Network::ChainProviderError] on network or API error
|
|
82
|
-
def get_block_header(height)
|
|
83
|
-
result = @protocol.call(:get_block_header, height)
|
|
84
|
-
raise_on_error(result)
|
|
85
|
-
|
|
86
|
-
result.data
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Verify that a merkle root is valid for the given block height.
|
|
90
|
-
# Returns +false+ when the block is not found (404); raises on other errors.
|
|
91
|
-
# @param root [String] expected merkle root as hex
|
|
92
|
-
# @param height [Integer] block height
|
|
93
|
-
# @return [Boolean]
|
|
94
|
-
# @raise [BSV::Network::ChainProviderError] on network or non-404 API error
|
|
95
|
-
def valid_root_for_height?(root, height)
|
|
96
|
-
result = @protocol.call(:valid_root, root, height)
|
|
97
|
-
return false if result.not_found?
|
|
98
|
-
|
|
99
|
-
raise_on_error(result)
|
|
100
|
-
|
|
101
|
-
result.data == true
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
private
|
|
105
|
-
|
|
106
|
-
# Translates a non-success Protocol result into a raised ChainProviderError.
|
|
107
|
-
#
|
|
108
|
-
# @param result [Result::Error, Result::NotFound]
|
|
109
|
-
# @raise [ChainProviderError]
|
|
110
|
-
def raise_on_error(result)
|
|
111
|
-
return if result.success?
|
|
112
|
-
|
|
113
|
-
raise ChainProviderError.new(
|
|
114
|
-
result.message || 'Request failed',
|
|
115
|
-
status_code: result.metadata[:status_code]
|
|
116
|
-
)
|
|
21
|
+
def initialize(*)
|
|
22
|
+
raise DeprecationError, MESSAGE
|
|
117
23
|
end
|
|
118
24
|
end
|
|
119
25
|
end
|
|
@@ -90,7 +90,7 @@ module BSV
|
|
|
90
90
|
|
|
91
91
|
sig_args = { hash_to_directly_sign: hash_bytes, protocol_id: @protocol_id, key_id: '1', counterparty: 'self' }
|
|
92
92
|
sig_args[:originator] = @originator if @originator
|
|
93
|
-
result = @wallet.create_signature(sig_args)
|
|
93
|
+
result = @wallet.create_signature(**sig_args)
|
|
94
94
|
|
|
95
95
|
sig_bytes = result[:signature].pack('C*')
|
|
96
96
|
sig_with_hashtype = sig_bytes + [sighash_type].pack('C')
|
|
@@ -171,14 +171,14 @@ module BSV
|
|
|
171
171
|
# Fetch the wallet's identity key (compressed public key hex)
|
|
172
172
|
id_args = { identity_key: true }
|
|
173
173
|
id_args[:originator] = @originator if @originator
|
|
174
|
-
identity_result = @wallet.get_public_key(id_args)
|
|
174
|
+
identity_result = @wallet.get_public_key(**id_args)
|
|
175
175
|
identity_key_hex = identity_result[:public_key]
|
|
176
176
|
identity_key_bytes = [identity_key_hex].pack('H*')
|
|
177
177
|
|
|
178
178
|
# Derive the locking public key for this protocol
|
|
179
179
|
lock_args = { protocol_id: protocol_id, key_id: '1', counterparty: 'self' }
|
|
180
180
|
lock_args[:originator] = @originator if @originator
|
|
181
|
-
locking_result = @wallet.get_public_key(lock_args)
|
|
181
|
+
locking_result = @wallet.get_public_key(**lock_args)
|
|
182
182
|
locking_pubkey_hex = locking_result[:public_key]
|
|
183
183
|
locking_pubkey_bytes = [locking_pubkey_hex].pack('H*')
|
|
184
184
|
|
|
@@ -192,7 +192,7 @@ module BSV
|
|
|
192
192
|
data_to_sign = (field_protocol + field_identity + field_domain + field_topic).unpack('C*')
|
|
193
193
|
sig_args = { data: data_to_sign, protocol_id: protocol_id, key_id: '1', counterparty: 'self' }
|
|
194
194
|
sig_args[:originator] = @originator if @originator
|
|
195
|
-
sig_result = @wallet.create_signature(sig_args)
|
|
195
|
+
sig_result = @wallet.create_signature(**sig_args)
|
|
196
196
|
field_sig = sig_result[:signature].pack('C*')
|
|
197
197
|
|
|
198
198
|
fields = [field_protocol, field_identity, field_domain, field_topic, field_sig]
|
|
@@ -42,6 +42,7 @@ module BSV
|
|
|
42
42
|
bytes.each_byte { |b| b.zero? ? leading_zeros += 1 : break }
|
|
43
43
|
|
|
44
44
|
# Convert to big integer and repeatedly divide by 58
|
|
45
|
+
# C-backed hex conversion — 10x faster than pure-Ruby byte shifting; not a porting artefact.
|
|
45
46
|
n = bytes.unpack1('H*').to_i(16)
|
|
46
47
|
result = +''
|
|
47
48
|
while n.positive?
|
|
@@ -78,7 +79,7 @@ module BSV
|
|
|
78
79
|
n = (n * BASE) + digit
|
|
79
80
|
end
|
|
80
81
|
|
|
81
|
-
# Convert integer to bytes
|
|
82
|
+
# Convert integer to bytes — C-backed hex round-trip is the fastest pure-Ruby integer→bytes path.
|
|
82
83
|
hex = n.zero? ? '' : n.to_s(16)
|
|
83
84
|
hex = "0#{hex}" if hex.length.odd?
|
|
84
85
|
result = [hex].pack('H*')
|
data/lib/bsv/primitives/curve.rb
CHANGED
|
@@ -25,10 +25,11 @@ module BSV
|
|
|
25
25
|
|
|
26
26
|
module_function
|
|
27
27
|
|
|
28
|
-
# Multiply the generator point by a scalar (
|
|
28
|
+
# Multiply the generator point by a scalar (constant-time).
|
|
29
29
|
#
|
|
30
|
-
#
|
|
31
|
-
# scalars
|
|
30
|
+
# Uses the Montgomery ladder by default, matching OpenSSL convention.
|
|
31
|
+
# Safe for both secret and public scalars. For explicit variable-time
|
|
32
|
+
# multiplication of public scalars, use {multiply_generator_vt}.
|
|
32
33
|
#
|
|
33
34
|
# @param scalar_bn [OpenSSL::BN] the scalar multiplier
|
|
34
35
|
# @return [OpenSSL::PKey::EC::Point] the resulting curve point
|
|
@@ -38,19 +39,31 @@ module BSV
|
|
|
38
39
|
|
|
39
40
|
# Multiply the generator point by a secret scalar (constant-time).
|
|
40
41
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
42
|
+
# Alias for {multiply_generator} — retained for backward compatibility
|
|
43
|
+
# and expressiveness.
|
|
43
44
|
#
|
|
44
45
|
# @param scalar_bn [OpenSSL::BN] the secret scalar multiplier
|
|
45
46
|
# @return [OpenSSL::PKey::EC::Point] the resulting curve point
|
|
46
47
|
def multiply_generator_ct(scalar_bn)
|
|
47
|
-
G.
|
|
48
|
+
G.mul(scalar_bn)
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
# Multiply
|
|
51
|
+
# Multiply the generator point by a public scalar (variable-time, wNAF).
|
|
51
52
|
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
53
|
+
# Faster than {multiply_generator} but leaks timing information about
|
|
54
|
+
# the scalar. Use only for public scalars (e.g. signature verification).
|
|
55
|
+
#
|
|
56
|
+
# @param scalar_bn [OpenSSL::BN] the public scalar multiplier
|
|
57
|
+
# @return [OpenSSL::PKey::EC::Point] the resulting curve point
|
|
58
|
+
def multiply_generator_vt(scalar_bn)
|
|
59
|
+
G.mul_vt(scalar_bn)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Multiply an arbitrary curve point by a scalar (constant-time).
|
|
63
|
+
#
|
|
64
|
+
# Uses the Montgomery ladder by default, matching OpenSSL convention.
|
|
65
|
+
# Safe for both secret and public scalars. For explicit variable-time
|
|
66
|
+
# multiplication of public scalars, use {multiply_point_vt}.
|
|
54
67
|
#
|
|
55
68
|
# @param point [OpenSSL::PKey::EC::Point] the point to multiply
|
|
56
69
|
# @param scalar_bn [OpenSSL::BN] the scalar multiplier
|
|
@@ -61,14 +74,26 @@ module BSV
|
|
|
61
74
|
|
|
62
75
|
# Multiply an arbitrary curve point by a secret scalar (constant-time).
|
|
63
76
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
77
|
+
# Alias for {multiply_point} — retained for backward compatibility
|
|
78
|
+
# and expressiveness.
|
|
66
79
|
#
|
|
67
80
|
# @param point [OpenSSL::PKey::EC::Point] the base point
|
|
68
81
|
# @param scalar_bn [OpenSSL::BN] the secret scalar multiplier
|
|
69
82
|
# @return [OpenSSL::PKey::EC::Point] the resulting curve point
|
|
70
83
|
def multiply_point_ct(point, scalar_bn)
|
|
71
|
-
point.
|
|
84
|
+
point.mul(scalar_bn)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Multiply an arbitrary curve point by a public scalar (variable-time, wNAF).
|
|
88
|
+
#
|
|
89
|
+
# Faster than {multiply_point} but leaks timing information about
|
|
90
|
+
# the scalar. Use only for public scalars (e.g. signature verification).
|
|
91
|
+
#
|
|
92
|
+
# @param point [OpenSSL::PKey::EC::Point] the point to multiply
|
|
93
|
+
# @param scalar_bn [OpenSSL::BN] the public scalar multiplier
|
|
94
|
+
# @return [OpenSSL::PKey::EC::Point] the resulting curve point
|
|
95
|
+
def multiply_point_vt(point, scalar_bn)
|
|
96
|
+
point.mul_vt(scalar_bn)
|
|
72
97
|
end
|
|
73
98
|
|
|
74
99
|
# Add two curve points together.
|
data/lib/bsv/primitives/ecdsa.rb
CHANGED
|
@@ -82,8 +82,8 @@ module BSV
|
|
|
82
82
|
u1 = ((n - e) * r_inv) % n
|
|
83
83
|
u2 = (s * r_inv) % n
|
|
84
84
|
|
|
85
|
-
p1 = Curve.
|
|
86
|
-
p2 = Curve.
|
|
85
|
+
p1 = Curve.multiply_generator_vt(u1)
|
|
86
|
+
p2 = Curve.multiply_point_vt(r_point, u2)
|
|
87
87
|
q = Curve.add_points(p1, p2)
|
|
88
88
|
|
|
89
89
|
raise ArgumentError, 'recovered point is at infinity' if q.infinity?
|
|
@@ -112,8 +112,8 @@ module BSV
|
|
|
112
112
|
u2 = (r * s_inv) % n
|
|
113
113
|
|
|
114
114
|
# R' = u1*G + u2*Q
|
|
115
|
-
point1 = Curve.
|
|
116
|
-
point2 = Curve.
|
|
115
|
+
point1 = Curve.multiply_generator_vt(u1)
|
|
116
|
+
point2 = Curve.multiply_point_vt(public_key_point, u2)
|
|
117
117
|
result_point = Curve.add_points(point1, point2)
|
|
118
118
|
|
|
119
119
|
return false if result_point.infinity?
|
|
@@ -66,6 +66,22 @@ class BSVShimECPoint
|
|
|
66
66
|
pt
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Scalar multiplication: self * scalar (constant-time, Montgomery ladder).
|
|
70
|
+
#
|
|
71
|
+
# Matches OpenSSL convention where +EC_POINT_mul+ is always constant-time.
|
|
72
|
+
# Safe for both secret and public scalars.
|
|
73
|
+
#
|
|
74
|
+
# Also supports the multi-scalar form: +mul(bns, points)+ computes
|
|
75
|
+
# <tt>bns[0]*self + bns[1]*points[0] + ...</tt>
|
|
76
|
+
# where +bns.length == points.length + 1+.
|
|
77
|
+
#
|
|
78
|
+
# @overload mul(scalar_bn)
|
|
79
|
+
# @param scalar_bn [OpenSSL::BN, Integer] the scalar multiplier
|
|
80
|
+
# @overload mul(bns, points)
|
|
81
|
+
# @param bns [Array<OpenSSL::BN>] scalars; must have +points.length + 1+ elements
|
|
82
|
+
# @param points [Array<BSVShimECPoint>] additional points
|
|
83
|
+
# @raise [NoMethodError] if +bns+ and +points+ lengths are mismatched
|
|
84
|
+
# @return [BSVShimECPoint]
|
|
69
85
|
def mul(*args)
|
|
70
86
|
if args.length == 1
|
|
71
87
|
scalar = bn_to_int(args[0])
|
|
@@ -85,16 +101,27 @@ class BSVShimECPoint
|
|
|
85
101
|
end
|
|
86
102
|
end
|
|
87
103
|
|
|
88
|
-
# Constant-time scalar multiplication
|
|
104
|
+
# Constant-time scalar multiplication (alias for {#mul}).
|
|
89
105
|
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
106
|
+
# Retained for backward compatibility and expressiveness. Delegates
|
|
107
|
+
# to {#mul}, which is constant-time by default.
|
|
92
108
|
#
|
|
93
|
-
# @param scalar_bn [OpenSSL::BN, Integer] the
|
|
109
|
+
# @param scalar_bn [OpenSSL::BN, Integer] the scalar multiplier
|
|
94
110
|
# @return [BSVShimECPoint]
|
|
95
111
|
def mul_ct(scalar_bn)
|
|
112
|
+
mul(scalar_bn)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Variable-time scalar multiplication (wNAF).
|
|
116
|
+
#
|
|
117
|
+
# Faster than {#mul} but leaks timing information about the scalar.
|
|
118
|
+
# Use only for public scalars (e.g. signature verification).
|
|
119
|
+
#
|
|
120
|
+
# @param scalar_bn [OpenSSL::BN, Integer] the public scalar multiplier
|
|
121
|
+
# @return [BSVShimECPoint]
|
|
122
|
+
def mul_vt(scalar_bn)
|
|
96
123
|
scalar = bn_to_int(scalar_bn)
|
|
97
|
-
result = @secp_point.
|
|
124
|
+
result = @secp_point.mul_vt(scalar)
|
|
98
125
|
self.class.from_secp_point(@group, result)
|
|
99
126
|
end
|
|
100
127
|
|
|
@@ -165,7 +165,7 @@ module BSV
|
|
|
165
165
|
def derive_child(public_key, invoice_number)
|
|
166
166
|
shared = derive_shared_secret(public_key)
|
|
167
167
|
hmac = Digest.hmac_sha256(shared.compressed, invoice_number.encode('UTF-8'))
|
|
168
|
-
hmac_bn = OpenSSL::BN.new(hmac
|
|
168
|
+
hmac_bn = OpenSSL::BN.new(hmac, 2)
|
|
169
169
|
PrivateKey.new(@bn.mod_add(hmac_bn, Curve::N))
|
|
170
170
|
end
|
|
171
171
|
|
|
@@ -207,7 +207,7 @@ module BSV
|
|
|
207
207
|
loop do
|
|
208
208
|
counter_bytes = [i, attempts].pack('N*') + SecureRandom.random_bytes(32)
|
|
209
209
|
h = Digest.hmac_sha512(seed, counter_bytes)
|
|
210
|
-
candidate = OpenSSL::BN.new(h
|
|
210
|
+
candidate = OpenSSL::BN.new(h, 2) % PointInFiniteField::P
|
|
211
211
|
|
|
212
212
|
attempts += 1
|
|
213
213
|
raise ArgumentError, 'failed to generate unique x-coordinate after 5 attempts' if attempts > 5
|
|
@@ -132,7 +132,7 @@ module BSV
|
|
|
132
132
|
def derive_child(private_key, invoice_number)
|
|
133
133
|
shared = derive_shared_secret(private_key)
|
|
134
134
|
hmac = Digest.hmac_sha256(shared.compressed, invoice_number.encode('UTF-8'))
|
|
135
|
-
hmac_bn = OpenSSL::BN.new(hmac
|
|
135
|
+
hmac_bn = OpenSSL::BN.new(hmac, 2)
|
|
136
136
|
hmac_point = Curve.multiply_generator_ct(hmac_bn)
|
|
137
137
|
child_point = Curve.add_points(@point, hmac_point)
|
|
138
138
|
PublicKey.new(child_point)
|
|
@@ -97,15 +97,15 @@ module BSV
|
|
|
97
97
|
e = compute_challenge(public_key_a, public_key_b, shared_secret, proof.s_prime, proof.r)
|
|
98
98
|
|
|
99
99
|
# Equation 1: z·G == R + e·A
|
|
100
|
-
z_g = Curve.
|
|
101
|
-
e_a = Curve.
|
|
100
|
+
z_g = Curve.multiply_generator_vt(proof.z)
|
|
101
|
+
e_a = Curve.multiply_point_vt(public_key_a.point, e)
|
|
102
102
|
r_plus_ea = Curve.add_points(proof.r.point, e_a)
|
|
103
103
|
|
|
104
104
|
return false unless points_equal?(z_g, r_plus_ea)
|
|
105
105
|
|
|
106
106
|
# Equation 2: z·B == S' + e·S
|
|
107
|
-
z_b = Curve.
|
|
108
|
-
e_s = Curve.
|
|
107
|
+
z_b = Curve.multiply_point_vt(public_key_b.point, proof.z)
|
|
108
|
+
e_s = Curve.multiply_point_vt(shared_secret.point, e)
|
|
109
109
|
sp_plus_es = Curve.add_points(proof.s_prime.point, e_s)
|
|
110
110
|
|
|
111
111
|
points_equal?(z_b, sp_plus_es)
|