bsv-sdk 0.2.0 → 0.3.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 +16 -0
- data/lib/bsv/network/broadcast_response.rb +1 -2
- data/lib/bsv/primitives/bsm.rb +2 -6
- data/lib/bsv/primitives/curve.rb +1 -2
- data/lib/bsv/primitives/encrypted_message.rb +100 -0
- data/lib/bsv/primitives/extended_key.rb +1 -2
- data/lib/bsv/primitives/key_shares.rb +83 -0
- data/lib/bsv/primitives/mnemonic.rb +1 -3
- data/lib/bsv/primitives/point_in_finite_field.rb +72 -0
- data/lib/bsv/primitives/polynomial.rb +95 -0
- data/lib/bsv/primitives/private_key.rb +101 -5
- data/lib/bsv/primitives/signed_message.rb +104 -0
- data/lib/bsv/primitives/symmetric_key.rb +128 -0
- data/lib/bsv/primitives.rb +18 -12
- data/lib/bsv/script/interpreter/interpreter.rb +1 -3
- data/lib/bsv/script/interpreter/operations/bitwise.rb +1 -3
- data/lib/bsv/script/interpreter/operations/crypto.rb +3 -9
- data/lib/bsv/script/interpreter/operations/flow_control.rb +2 -6
- data/lib/bsv/script/interpreter/operations/splice.rb +1 -3
- data/lib/bsv/script/interpreter/script_number.rb +2 -7
- data/lib/bsv/script/script.rb +256 -1
- data/lib/bsv/transaction/beef.rb +8 -11
- data/lib/bsv/transaction/transaction.rb +131 -59
- data/lib/bsv/transaction/transaction_input.rb +1 -2
- data/lib/bsv/transaction/transaction_output.rb +1 -2
- data/lib/bsv/transaction/var_int.rb +4 -16
- data/lib/bsv/transaction.rb +14 -14
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet_interface/errors/invalid_hmac_error.rb +11 -0
- data/lib/bsv/wallet_interface/errors/invalid_parameter_error.rb +14 -0
- data/lib/bsv/wallet_interface/errors/invalid_signature_error.rb +11 -0
- data/lib/bsv/wallet_interface/errors/unsupported_action_error.rb +11 -0
- data/lib/bsv/wallet_interface/errors/wallet_error.rb +14 -0
- data/lib/bsv/wallet_interface/interface.rb +384 -0
- data/lib/bsv/wallet_interface/key_deriver.rb +142 -0
- data/lib/bsv/wallet_interface/memory_store.rb +115 -0
- data/lib/bsv/wallet_interface/proto_wallet.rb +361 -0
- data/lib/bsv/wallet_interface/storage_adapter.rb +51 -0
- data/lib/bsv/wallet_interface/validators.rb +126 -0
- data/lib/bsv/wallet_interface/version.rb +7 -0
- data/lib/bsv/wallet_interface/wallet_client.rb +486 -0
- data/lib/bsv/wallet_interface.rb +25 -0
- data/lib/bsv-wallet.rb +4 -0
- metadata +24 -3
- /data/{LICENCE → LICENSE} +0 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module BSV
|
|
6
|
+
module Primitives
|
|
7
|
+
# BRC-77 signed messages.
|
|
8
|
+
#
|
|
9
|
+
# Provides authenticated messaging using BRC-42 derived signing keys.
|
|
10
|
+
# The sender proves their identity to a specific recipient (or anyone)
|
|
11
|
+
# without encrypting the message content.
|
|
12
|
+
#
|
|
13
|
+
# @example Sign and verify for a specific recipient
|
|
14
|
+
# sig = SignedMessage.sign(message, sender_priv, recipient_pub)
|
|
15
|
+
# SignedMessage.verify(message, sig, recipient_priv) #=> true
|
|
16
|
+
#
|
|
17
|
+
# @example Sign for anyone to verify
|
|
18
|
+
# sig = SignedMessage.sign(message, sender_priv)
|
|
19
|
+
# SignedMessage.verify(message, sig) #=> true
|
|
20
|
+
#
|
|
21
|
+
# @see https://github.com/bitcoin-sv/BRCs/blob/master/peer-to-peer/0077.md
|
|
22
|
+
module SignedMessage
|
|
23
|
+
# Protocol version bytes: "BB3\x01"
|
|
24
|
+
VERSION = "\x42\x42\x33\x01".b.freeze
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# Sign a message using the BRC-77 protocol.
|
|
29
|
+
#
|
|
30
|
+
# @param message [String] the message to sign
|
|
31
|
+
# @param signer [PrivateKey] the sender's private key
|
|
32
|
+
# @param verifier [PublicKey, nil] the recipient's public key (nil for anyone-can-verify)
|
|
33
|
+
# @return [String] binary signed message (version + keys + key_id + DER signature)
|
|
34
|
+
def sign(message, signer, verifier = nil)
|
|
35
|
+
anyone = verifier.nil?
|
|
36
|
+
verifier = PrivateKey.new(OpenSSL::BN.new(1)).public_key if anyone
|
|
37
|
+
|
|
38
|
+
key_id = SecureRandom.random_bytes(32)
|
|
39
|
+
invoice = "2-message signing-#{[key_id].pack('m0')}"
|
|
40
|
+
|
|
41
|
+
signing_key = signer.derive_child(verifier, invoice)
|
|
42
|
+
hash = Digest.sha256(message.b)
|
|
43
|
+
signature = signing_key.sign(hash)
|
|
44
|
+
|
|
45
|
+
VERSION +
|
|
46
|
+
signer.public_key.compressed +
|
|
47
|
+
(anyone ? "\x00".b : verifier.compressed) +
|
|
48
|
+
key_id +
|
|
49
|
+
signature.to_der
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Verify a BRC-77 signed message.
|
|
53
|
+
#
|
|
54
|
+
# @param message [String] the original message
|
|
55
|
+
# @param sig [String] the binary signature (from {.sign})
|
|
56
|
+
# @param recipient [PrivateKey, nil] the recipient's private key (nil for anyone-can-verify)
|
|
57
|
+
# @return [Boolean] true if the signature is valid
|
|
58
|
+
# @raise [ArgumentError] if the version is wrong, recipient is required but missing, or recipient doesn't match
|
|
59
|
+
def verify(message, sig, recipient = nil)
|
|
60
|
+
sig = sig.b
|
|
61
|
+
raise ArgumentError, "signed message too short: #{sig.bytesize} bytes" if sig.bytesize < 38
|
|
62
|
+
|
|
63
|
+
version = sig.byteslice(0, 4)
|
|
64
|
+
raise ArgumentError, "message version mismatch: expected #{VERSION.unpack1('H*')}, received #{version.unpack1('H*')}" if version != VERSION
|
|
65
|
+
|
|
66
|
+
sender_pub = PublicKey.from_bytes(sig.byteslice(4, 33))
|
|
67
|
+
verifier_first = sig.getbyte(37)
|
|
68
|
+
|
|
69
|
+
if verifier_first.zero?
|
|
70
|
+
# Anyone-can-verify mode
|
|
71
|
+
recipient = PrivateKey.new(OpenSSL::BN.new(1))
|
|
72
|
+
key_id_offset = 38
|
|
73
|
+
else
|
|
74
|
+
# Specific recipient
|
|
75
|
+
verifier_pub_bytes = sig.byteslice(37, 33)
|
|
76
|
+
verifier_pub_hex = verifier_pub_bytes.unpack1('H*')
|
|
77
|
+
|
|
78
|
+
if recipient.nil?
|
|
79
|
+
raise ArgumentError,
|
|
80
|
+
"this signature can only be verified with knowledge of a specific private key. The associated public key is: #{verifier_pub_hex}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
recipient_pub_hex = recipient.public_key.compressed.unpack1('H*')
|
|
84
|
+
if verifier_pub_hex != recipient_pub_hex
|
|
85
|
+
raise ArgumentError,
|
|
86
|
+
"the recipient public key is #{recipient_pub_hex} but the signature requires the recipient to have public key #{verifier_pub_hex}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
key_id_offset = 70
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
key_id = sig.byteslice(key_id_offset, 32)
|
|
93
|
+
der_bytes = sig.byteslice(key_id_offset + 32, sig.bytesize - key_id_offset - 32)
|
|
94
|
+
|
|
95
|
+
invoice = "2-message signing-#{[key_id].pack('m0')}"
|
|
96
|
+
signing_pub = sender_pub.derive_child(recipient, invoice)
|
|
97
|
+
|
|
98
|
+
signature = Signature.from_der(der_bytes)
|
|
99
|
+
hash = Digest.sha256(message.b)
|
|
100
|
+
ECDSA.verify(hash, signature, signing_pub.point)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
module BSV
|
|
7
|
+
module Primitives
|
|
8
|
+
# AES-256-GCM symmetric encryption.
|
|
9
|
+
#
|
|
10
|
+
# Provides authenticated encryption matching the interface used by the
|
|
11
|
+
# TS, Go, and Python reference SDKs. The wire format is:
|
|
12
|
+
#
|
|
13
|
+
# |--- 32-byte IV ---|--- ciphertext ---|--- 16-byte auth tag ---|
|
|
14
|
+
#
|
|
15
|
+
# All three reference SDKs use a 32-byte IV (non-standard but
|
|
16
|
+
# cross-SDK compatible) and 16-byte authentication tag.
|
|
17
|
+
#
|
|
18
|
+
# @example Round-trip encryption
|
|
19
|
+
# key = BSV::Primitives::SymmetricKey.from_random
|
|
20
|
+
# encrypted = key.encrypt('hello world')
|
|
21
|
+
# key.decrypt(encrypted) #=> "hello world"
|
|
22
|
+
class SymmetricKey
|
|
23
|
+
IV_SIZE = 32
|
|
24
|
+
TAG_SIZE = 16
|
|
25
|
+
KEY_SIZE = 32
|
|
26
|
+
|
|
27
|
+
# @param key_bytes [String] 32-byte binary key (shorter keys are left-zero-padded)
|
|
28
|
+
# @raise [ArgumentError] if key is empty or longer than 32 bytes
|
|
29
|
+
def initialize(key_bytes)
|
|
30
|
+
key_bytes = key_bytes.b
|
|
31
|
+
raise ArgumentError, 'key must not be empty' if key_bytes.empty?
|
|
32
|
+
raise ArgumentError, "key must be at most #{KEY_SIZE} bytes, got #{key_bytes.bytesize}" if key_bytes.bytesize > KEY_SIZE
|
|
33
|
+
|
|
34
|
+
@key = if key_bytes.bytesize < KEY_SIZE
|
|
35
|
+
("\x00".b * (KEY_SIZE - key_bytes.bytesize)) + key_bytes
|
|
36
|
+
else
|
|
37
|
+
key_bytes
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Generate a random symmetric key.
|
|
42
|
+
#
|
|
43
|
+
# @return [SymmetricKey]
|
|
44
|
+
def self.from_random
|
|
45
|
+
new(SecureRandom.random_bytes(KEY_SIZE))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Derive a symmetric key from an ECDH shared secret.
|
|
49
|
+
#
|
|
50
|
+
# Computes the shared point between the two parties and uses the
|
|
51
|
+
# X-coordinate as the key material. The X-coordinate may be 31 or
|
|
52
|
+
# 32 bytes; shorter values are left-zero-padded automatically.
|
|
53
|
+
#
|
|
54
|
+
# @example Alice and Bob derive the same key
|
|
55
|
+
# alice_key = SymmetricKey.from_ecdh(alice_priv, bob_pub)
|
|
56
|
+
# bob_key = SymmetricKey.from_ecdh(bob_priv, alice_pub)
|
|
57
|
+
# alice_key.to_bytes == bob_key.to_bytes #=> true
|
|
58
|
+
#
|
|
59
|
+
# @param private_key [PrivateKey] one party's private key
|
|
60
|
+
# @param public_key [PublicKey] the other party's public key
|
|
61
|
+
# @return [SymmetricKey]
|
|
62
|
+
def self.from_ecdh(private_key, public_key)
|
|
63
|
+
shared = private_key.derive_shared_secret(public_key)
|
|
64
|
+
# X-coordinate = bytes 1..32 of the compressed point (skip the 02/03 prefix)
|
|
65
|
+
x_bytes = shared.compressed.byteslice(1, 32)
|
|
66
|
+
new(x_bytes)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Encrypt a message with AES-256-GCM.
|
|
70
|
+
#
|
|
71
|
+
# Generates a random 32-byte IV per call. Returns the concatenation
|
|
72
|
+
# of IV, ciphertext, and 16-byte authentication tag.
|
|
73
|
+
#
|
|
74
|
+
# @param plaintext [String] the message to encrypt
|
|
75
|
+
# @return [String] binary string: IV (32) + ciphertext + auth tag (16)
|
|
76
|
+
def encrypt(plaintext)
|
|
77
|
+
iv = SecureRandom.random_bytes(IV_SIZE)
|
|
78
|
+
|
|
79
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm')
|
|
80
|
+
cipher.encrypt
|
|
81
|
+
cipher.key = @key
|
|
82
|
+
cipher.iv_len = IV_SIZE
|
|
83
|
+
cipher.iv = iv
|
|
84
|
+
cipher.auth_data = ''.b
|
|
85
|
+
|
|
86
|
+
plaintext_bytes = plaintext.b
|
|
87
|
+
ciphertext = plaintext_bytes.empty? ? cipher.final : cipher.update(plaintext_bytes) + cipher.final
|
|
88
|
+
tag = cipher.auth_tag(TAG_SIZE)
|
|
89
|
+
|
|
90
|
+
iv + ciphertext + tag
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Decrypt an AES-256-GCM encrypted message.
|
|
94
|
+
#
|
|
95
|
+
# Expects the wire format: IV (32) + ciphertext + auth tag (16).
|
|
96
|
+
#
|
|
97
|
+
# @param data [String] the encrypted message
|
|
98
|
+
# @return [String] the decrypted plaintext (binary)
|
|
99
|
+
# @raise [ArgumentError] if the data is too short
|
|
100
|
+
# @raise [OpenSSL::Cipher::CipherError] if authentication fails (wrong key or tampered data)
|
|
101
|
+
def decrypt(data)
|
|
102
|
+
data = data.b
|
|
103
|
+
raise ArgumentError, "ciphertext too short: #{data.bytesize} bytes (minimum #{IV_SIZE + TAG_SIZE})" if data.bytesize < IV_SIZE + TAG_SIZE
|
|
104
|
+
|
|
105
|
+
iv = data.byteslice(0, IV_SIZE)
|
|
106
|
+
tag = data.byteslice(-TAG_SIZE, TAG_SIZE)
|
|
107
|
+
ciphertext = data.byteslice(IV_SIZE, data.bytesize - IV_SIZE - TAG_SIZE)
|
|
108
|
+
|
|
109
|
+
decipher = OpenSSL::Cipher.new('aes-256-gcm')
|
|
110
|
+
decipher.decrypt
|
|
111
|
+
decipher.key = @key
|
|
112
|
+
decipher.iv_len = IV_SIZE
|
|
113
|
+
decipher.iv = iv
|
|
114
|
+
decipher.auth_tag = tag
|
|
115
|
+
decipher.auth_data = ''.b
|
|
116
|
+
|
|
117
|
+
ciphertext.empty? ? decipher.final : decipher.update(ciphertext) + decipher.final
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Return the raw key bytes.
|
|
121
|
+
#
|
|
122
|
+
# @return [String] 32-byte binary key
|
|
123
|
+
def to_bytes
|
|
124
|
+
@key.dup
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
data/lib/bsv/primitives.rb
CHANGED
|
@@ -7,17 +7,23 @@ module BSV
|
|
|
7
7
|
# HD key derivation (BIP-32), and mnemonic phrase generation (BIP-39).
|
|
8
8
|
# All cryptography uses Ruby's stdlib +openssl+ — no external gems.
|
|
9
9
|
module Primitives
|
|
10
|
-
autoload :Curve,
|
|
11
|
-
autoload :Digest,
|
|
12
|
-
autoload :Base58,
|
|
13
|
-
autoload :Signature,
|
|
14
|
-
autoload :ECDSA,
|
|
15
|
-
autoload :ECIES,
|
|
16
|
-
autoload :BSM,
|
|
17
|
-
autoload :Schnorr,
|
|
18
|
-
autoload :PublicKey,
|
|
19
|
-
autoload :PrivateKey,
|
|
20
|
-
autoload :ExtendedKey,
|
|
21
|
-
autoload :Mnemonic,
|
|
10
|
+
autoload :Curve, 'bsv/primitives/curve'
|
|
11
|
+
autoload :Digest, 'bsv/primitives/digest'
|
|
12
|
+
autoload :Base58, 'bsv/primitives/base58'
|
|
13
|
+
autoload :Signature, 'bsv/primitives/signature'
|
|
14
|
+
autoload :ECDSA, 'bsv/primitives/ecdsa'
|
|
15
|
+
autoload :ECIES, 'bsv/primitives/ecies'
|
|
16
|
+
autoload :BSM, 'bsv/primitives/bsm'
|
|
17
|
+
autoload :Schnorr, 'bsv/primitives/schnorr'
|
|
18
|
+
autoload :PublicKey, 'bsv/primitives/public_key'
|
|
19
|
+
autoload :PrivateKey, 'bsv/primitives/private_key'
|
|
20
|
+
autoload :ExtendedKey, 'bsv/primitives/extended_key'
|
|
21
|
+
autoload :Mnemonic, 'bsv/primitives/mnemonic'
|
|
22
|
+
autoload :SymmetricKey, 'bsv/primitives/symmetric_key'
|
|
23
|
+
autoload :SignedMessage, 'bsv/primitives/signed_message'
|
|
24
|
+
autoload :EncryptedMessage, 'bsv/primitives/encrypted_message'
|
|
25
|
+
autoload :PointInFiniteField, 'bsv/primitives/point_in_finite_field'
|
|
26
|
+
autoload :Polynomial, 'bsv/primitives/polynomial'
|
|
27
|
+
autoload :KeyShares, 'bsv/primitives/key_shares'
|
|
22
28
|
end
|
|
23
29
|
end
|
|
@@ -96,9 +96,7 @@ module BSV
|
|
|
96
96
|
break if @early_return && script_idx == 1
|
|
97
97
|
|
|
98
98
|
# Between scripts: verify conditionals balanced
|
|
99
|
-
unless @cond_stack.empty?
|
|
100
|
-
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'unbalanced conditional')
|
|
101
|
-
end
|
|
99
|
+
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'unbalanced conditional') unless @cond_stack.empty?
|
|
102
100
|
|
|
103
101
|
# Clear alt stack between scripts
|
|
104
102
|
@astack.clear
|
|
@@ -55,9 +55,7 @@ module BSV
|
|
|
55
55
|
def pop_equal_length_pair
|
|
56
56
|
a = @dstack.pop_bytes
|
|
57
57
|
b = @dstack.pop_bytes
|
|
58
|
-
if a.bytesize != b.bytesize
|
|
59
|
-
raise ScriptError.new(ScriptErrorCode::INVALID_INPUT_LENGTH, 'byte arrays are not the same length')
|
|
60
|
-
end
|
|
58
|
+
raise ScriptError.new(ScriptErrorCode::INVALID_INPUT_LENGTH, 'byte arrays are not the same length') if a.bytesize != b.bytesize
|
|
61
59
|
|
|
62
60
|
[a, b]
|
|
63
61
|
end
|
|
@@ -52,9 +52,7 @@ module BSV
|
|
|
52
52
|
result = verify_checksig(full_sig, pubkey_bytes)
|
|
53
53
|
|
|
54
54
|
# NULLFAIL: non-empty signature that failed verification
|
|
55
|
-
unless result
|
|
56
|
-
raise ScriptError.new(ScriptErrorCode::SIG_NULLFAIL, 'non-empty signature failed verification')
|
|
57
|
-
end
|
|
55
|
+
raise ScriptError.new(ScriptErrorCode::SIG_NULLFAIL, 'non-empty signature failed verification') unless result
|
|
58
56
|
|
|
59
57
|
@dstack.push_bool(true)
|
|
60
58
|
end
|
|
@@ -85,18 +83,14 @@ module BSV
|
|
|
85
83
|
|
|
86
84
|
# Dummy element (off-by-one bug compatibility)
|
|
87
85
|
dummy = @dstack.pop_bytes
|
|
88
|
-
unless dummy.empty?
|
|
89
|
-
raise ScriptError.new(ScriptErrorCode::SIG_NULLDUMMY, 'CHECKMULTISIG dummy element must be empty')
|
|
90
|
-
end
|
|
86
|
+
raise ScriptError.new(ScriptErrorCode::SIG_NULLDUMMY, 'CHECKMULTISIG dummy element must be empty') unless dummy.empty?
|
|
91
87
|
|
|
92
88
|
success = multisig_match?(signatures, pubkeys)
|
|
93
89
|
|
|
94
90
|
# NULLFAIL: if failed, all signatures must be empty
|
|
95
91
|
unless success
|
|
96
92
|
signatures.each do |sig|
|
|
97
|
-
unless sig.empty?
|
|
98
|
-
raise ScriptError.new(ScriptErrorCode::SIG_NULLFAIL, 'non-empty signature failed verification')
|
|
99
|
-
end
|
|
93
|
+
raise ScriptError.new(ScriptErrorCode::SIG_NULLFAIL, 'non-empty signature failed verification') unless sig.empty?
|
|
100
94
|
end
|
|
101
95
|
end
|
|
102
96
|
|
|
@@ -30,9 +30,7 @@ module BSV
|
|
|
30
30
|
|
|
31
31
|
# OP_ELSE: toggle conditional branch (only one ELSE per IF after genesis)
|
|
32
32
|
def op_else
|
|
33
|
-
if @cond_stack.empty?
|
|
34
|
-
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'OP_ELSE without matching OP_IF')
|
|
35
|
-
end
|
|
33
|
+
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'OP_ELSE without matching OP_IF') if @cond_stack.empty?
|
|
36
34
|
|
|
37
35
|
# After genesis: only one ELSE per IF
|
|
38
36
|
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'duplicate OP_ELSE') if @else_stack.pop
|
|
@@ -47,9 +45,7 @@ module BSV
|
|
|
47
45
|
|
|
48
46
|
# OP_ENDIF: close conditional block
|
|
49
47
|
def op_endif
|
|
50
|
-
if @cond_stack.empty?
|
|
51
|
-
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'OP_ENDIF without matching OP_IF')
|
|
52
|
-
end
|
|
48
|
+
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'OP_ENDIF without matching OP_IF') if @cond_stack.empty?
|
|
53
49
|
|
|
54
50
|
@cond_stack.pop
|
|
55
51
|
@else_stack.pop
|
|
@@ -44,9 +44,7 @@ module BSV
|
|
|
44
44
|
size = @dstack.pop_int.to_i32
|
|
45
45
|
data = @dstack.pop_bytes
|
|
46
46
|
|
|
47
|
-
if size.negative?
|
|
48
|
-
raise ScriptError.new(ScriptErrorCode::INVALID_INPUT_LENGTH, 'OP_NUM2BIN: size is negative')
|
|
49
|
-
end
|
|
47
|
+
raise ScriptError.new(ScriptErrorCode::INVALID_INPUT_LENGTH, 'OP_NUM2BIN: size is negative') if size.negative?
|
|
50
48
|
|
|
51
49
|
minimal = ScriptNumber.minimally_encode(data)
|
|
52
50
|
|
|
@@ -44,10 +44,7 @@ module BSV
|
|
|
44
44
|
return new(0) if bytes.empty?
|
|
45
45
|
|
|
46
46
|
if bytes.bytesize > max_length
|
|
47
|
-
raise ScriptError.new
|
|
48
|
-
ScriptErrorCode::NUMBER_TOO_BIG,
|
|
49
|
-
"script number overflow: #{bytes.bytesize} > #{max_length}"
|
|
50
|
-
)
|
|
47
|
+
raise ScriptError.new ScriptErrorCode::NUMBER_TOO_BIG, "script number overflow: #{bytes.bytesize} > #{max_length}"
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
check_minimal_encoding!(bytes) if require_minimal
|
|
@@ -202,9 +199,7 @@ module BSV
|
|
|
202
199
|
return if msb.anybits?(0x7f)
|
|
203
200
|
|
|
204
201
|
# Single byte that is pure sign/zero (0x00 or 0x80) — not minimal
|
|
205
|
-
if bytes.bytesize == 1
|
|
206
|
-
raise ScriptError.new(ScriptErrorCode::MINIMAL_DATA, 'non-minimal script number encoding')
|
|
207
|
-
end
|
|
202
|
+
raise ScriptError.new(ScriptErrorCode::MINIMAL_DATA, 'non-minimal script number encoding') if bytes.bytesize == 1
|
|
208
203
|
|
|
209
204
|
# Padding is justified if second-to-last byte has high bit set
|
|
210
205
|
return if bytes.getbyte(bytes.bytesize - 2) & 0x80 != 0
|