bsv-sdk 0.1.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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +58 -0
  3. data/LICENCE +86 -0
  4. data/README.md +155 -0
  5. data/lib/bsv/attest/configuration.rb +9 -0
  6. data/lib/bsv/attest/response.rb +19 -0
  7. data/lib/bsv/attest/verification_error.rb +7 -0
  8. data/lib/bsv/attest/version.rb +7 -0
  9. data/lib/bsv/attest.rb +71 -0
  10. data/lib/bsv/network/arc.rb +113 -0
  11. data/lib/bsv/network/broadcast_error.rb +15 -0
  12. data/lib/bsv/network/broadcast_response.rb +29 -0
  13. data/lib/bsv/network/chain_provider_error.rb +14 -0
  14. data/lib/bsv/network/utxo.rb +28 -0
  15. data/lib/bsv/network/whats_on_chain.rb +82 -0
  16. data/lib/bsv/network.rb +12 -0
  17. data/lib/bsv/primitives/base58.rb +117 -0
  18. data/lib/bsv/primitives/bsm.rb +131 -0
  19. data/lib/bsv/primitives/curve.rb +115 -0
  20. data/lib/bsv/primitives/digest.rb +99 -0
  21. data/lib/bsv/primitives/ecdsa.rb +224 -0
  22. data/lib/bsv/primitives/ecies.rb +128 -0
  23. data/lib/bsv/primitives/extended_key.rb +315 -0
  24. data/lib/bsv/primitives/mnemonic/wordlist.rb +270 -0
  25. data/lib/bsv/primitives/mnemonic.rb +192 -0
  26. data/lib/bsv/primitives/private_key.rb +139 -0
  27. data/lib/bsv/primitives/public_key.rb +118 -0
  28. data/lib/bsv/primitives/schnorr.rb +108 -0
  29. data/lib/bsv/primitives/signature.rb +136 -0
  30. data/lib/bsv/primitives.rb +23 -0
  31. data/lib/bsv/script/builder.rb +73 -0
  32. data/lib/bsv/script/chunk.rb +77 -0
  33. data/lib/bsv/script/interpreter/error.rb +54 -0
  34. data/lib/bsv/script/interpreter/interpreter.rb +281 -0
  35. data/lib/bsv/script/interpreter/operations/arithmetic.rb +243 -0
  36. data/lib/bsv/script/interpreter/operations/bitwise.rb +68 -0
  37. data/lib/bsv/script/interpreter/operations/crypto.rb +209 -0
  38. data/lib/bsv/script/interpreter/operations/data_push.rb +34 -0
  39. data/lib/bsv/script/interpreter/operations/flow_control.rb +94 -0
  40. data/lib/bsv/script/interpreter/operations/splice.rb +89 -0
  41. data/lib/bsv/script/interpreter/operations/stack_ops.rb +112 -0
  42. data/lib/bsv/script/interpreter/script_number.rb +218 -0
  43. data/lib/bsv/script/interpreter/stack.rb +203 -0
  44. data/lib/bsv/script/opcodes.rb +165 -0
  45. data/lib/bsv/script/script.rb +424 -0
  46. data/lib/bsv/script.rb +20 -0
  47. data/lib/bsv/transaction/beef.rb +323 -0
  48. data/lib/bsv/transaction/merkle_path.rb +250 -0
  49. data/lib/bsv/transaction/p2pkh.rb +44 -0
  50. data/lib/bsv/transaction/sighash.rb +48 -0
  51. data/lib/bsv/transaction/transaction.rb +380 -0
  52. data/lib/bsv/transaction/transaction_input.rb +109 -0
  53. data/lib/bsv/transaction/transaction_output.rb +51 -0
  54. data/lib/bsv/transaction/unlocking_script_template.rb +36 -0
  55. data/lib/bsv/transaction/var_int.rb +50 -0
  56. data/lib/bsv/transaction.rb +21 -0
  57. data/lib/bsv/version.rb +5 -0
  58. data/lib/bsv/wallet/insufficient_funds_error.rb +15 -0
  59. data/lib/bsv/wallet/wallet.rb +119 -0
  60. data/lib/bsv/wallet.rb +8 -0
  61. data/lib/bsv-attest.rb +4 -0
  62. data/lib/bsv-sdk.rb +11 -0
  63. metadata +104 -0
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module BSV
6
+ module Primitives
7
+ # A secp256k1 public key for address derivation and signature verification.
8
+ #
9
+ # Public keys are points on the secp256k1 curve. They can be serialised
10
+ # in compressed (33-byte) or uncompressed (65-byte) form, converted to
11
+ # Bitcoin addresses, and used to verify ECDSA signatures.
12
+ #
13
+ # @example Derive address from a public key
14
+ # pub = private_key.public_key
15
+ # pub.address #=> "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
16
+ class PublicKey
17
+ # Address version byte for mainnet P2PKH addresses.
18
+ MAINNET_PUBKEY_HASH = "\x00".b
19
+
20
+ # Address version byte for testnet P2PKH addresses.
21
+ TESTNET_PUBKEY_HASH = "\x6f".b
22
+
23
+ # @return [OpenSSL::PKey::EC::Point] the underlying curve point
24
+ attr_reader :point
25
+
26
+ # @param point [OpenSSL::PKey::EC::Point] a point on the secp256k1 curve
27
+ # @raise [ArgumentError] if point is not an EC point or is at infinity
28
+ def initialize(point)
29
+ raise ArgumentError, 'point must be an EC point' unless point.is_a?(OpenSSL::PKey::EC::Point)
30
+ raise ArgumentError, 'point is at infinity' if point.infinity?
31
+
32
+ @point = point
33
+ end
34
+
35
+ # Create a public key from raw bytes (compressed or uncompressed).
36
+ #
37
+ # @param bytes [String] 33-byte compressed or 65-byte uncompressed encoding
38
+ # @return [PublicKey]
39
+ def self.from_bytes(bytes)
40
+ point = Curve.point_from_bytes(bytes)
41
+ new(point)
42
+ end
43
+
44
+ # Create a public key from a hex string.
45
+ #
46
+ # @param hex [String] hex-encoded compressed or uncompressed public key
47
+ # @return [PublicKey]
48
+ def self.from_hex(hex)
49
+ from_bytes([hex].pack('H*'))
50
+ end
51
+
52
+ # Derive the public key from a {PrivateKey}.
53
+ #
54
+ # @param private_key [PrivateKey] the private key
55
+ # @return [PublicKey]
56
+ def self.from_private_key(private_key)
57
+ private_key.public_key
58
+ end
59
+
60
+ # Return the compressed (33-byte) encoding.
61
+ #
62
+ # @return [String] compressed public key bytes
63
+ def compressed
64
+ @point.to_octet_string(:compressed)
65
+ end
66
+
67
+ # Return the uncompressed (65-byte) encoding.
68
+ #
69
+ # @return [String] uncompressed public key bytes
70
+ def uncompressed
71
+ @point.to_octet_string(:uncompressed)
72
+ end
73
+
74
+ # Return the public key as a hex string.
75
+ #
76
+ # @param compressed [Boolean] whether to use compressed encoding (default: true)
77
+ # @return [String] hex-encoded public key
78
+ def to_hex(compressed: true)
79
+ if compressed
80
+ self.compressed.unpack1('H*')
81
+ else
82
+ uncompressed.unpack1('H*')
83
+ end
84
+ end
85
+
86
+ # Compute the Hash160 (RIPEMD-160 of SHA-256) of the compressed public key.
87
+ #
88
+ # @return [String] 20-byte public key hash
89
+ def hash160
90
+ Digest.hash160(compressed)
91
+ end
92
+
93
+ # Derive a Base58Check-encoded Bitcoin address.
94
+ #
95
+ # @param network [Symbol] +:mainnet+ or +:testnet+
96
+ # @return [String] the P2PKH address
97
+ def address(network: :mainnet)
98
+ prefix = network == :mainnet ? MAINNET_PUBKEY_HASH : TESTNET_PUBKEY_HASH
99
+ Base58.check_encode(prefix + hash160)
100
+ end
101
+
102
+ # Verify an ECDSA signature against a message hash.
103
+ #
104
+ # @param hash [String] 32-byte message digest
105
+ # @param signature [Signature] the signature to verify
106
+ # @return [Boolean] +true+ if the signature is valid
107
+ def verify(hash, signature)
108
+ ECDSA.verify(hash, signature, @point)
109
+ end
110
+
111
+ # @param other [Object] the object to compare
112
+ # @return [Boolean] +true+ if both keys represent the same curve point
113
+ def ==(other)
114
+ other.is_a?(PublicKey) && compressed == other.compressed
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module BSV
6
+ module Primitives
7
+ # BRC-94 Schnorr zero-knowledge proof protocol.
8
+ #
9
+ # Provides generation and verification of Schnorr proofs for verifiable
10
+ # revelation of ECDH shared secrets. Given two public keys A and B and
11
+ # a shared secret S = a*B (where a is A's private key), the prover can
12
+ # demonstrate knowledge of the discrete log relationship without
13
+ # revealing the private key.
14
+ #
15
+ # @see https://github.com/bitcoin-sv/BRCs/blob/master/peer-to-peer/0094.md BRC-94
16
+ module Schnorr
17
+ # A Schnorr zero-knowledge proof consisting of a commitment point,
18
+ # blinded shared secret, and response scalar.
19
+ class Proof
20
+ # @return [PublicKey] the commitment point R
21
+ attr_reader :r
22
+
23
+ # @return [PublicKey] the blinded shared secret S'
24
+ attr_reader :s_prime
25
+
26
+ # @return [OpenSSL::BN] the response scalar z
27
+ attr_reader :z
28
+
29
+ # @param r [PublicKey] commitment point
30
+ # @param s_prime [PublicKey] blinded shared secret
31
+ # @param z [OpenSSL::BN] response scalar
32
+ def initialize(r, s_prime, z)
33
+ @r = r
34
+ @s_prime = s_prime
35
+ @z = z
36
+ end
37
+ end
38
+
39
+ module_function
40
+
41
+ # Generate a Schnorr proof of knowledge of a shared secret.
42
+ #
43
+ # Proves that the prover knows the private key +a+ such that
44
+ # +shared_secret = a * public_key_b+, without revealing +a+.
45
+ #
46
+ # @param private_key [PrivateKey] the prover's private key (a)
47
+ # @param public_key_a [PublicKey] the prover's public key (A = a*G)
48
+ # @param public_key_b [PublicKey] the counterparty's public key (B)
49
+ # @param shared_secret [PublicKey] the ECDH shared secret (S = a*B)
50
+ # @return [Proof] the Schnorr proof
51
+ def generate_proof(private_key, public_key_a, public_key_b, shared_secret)
52
+ nonce = PrivateKey.generate
53
+ r_pub = nonce.public_key
54
+ s_prime = PublicKey.new(Curve.multiply_point(public_key_b.point, nonce.bn))
55
+
56
+ e = compute_challenge(public_key_a, public_key_b, shared_secret, s_prime, r_pub)
57
+
58
+ z = (nonce.bn + (e * private_key.bn)) % Curve::N
59
+
60
+ Proof.new(r_pub, s_prime, z)
61
+ end
62
+
63
+ # Verify a Schnorr proof of knowledge of a shared secret.
64
+ #
65
+ # Checks the two verification equations:
66
+ # 1. z*G == R + e*A
67
+ # 2. z*B == S' + e*S
68
+ #
69
+ # @param public_key_a [PublicKey] the prover's public key
70
+ # @param public_key_b [PublicKey] the counterparty's public key
71
+ # @param shared_secret [PublicKey] the claimed shared secret
72
+ # @param proof [Proof] the Schnorr proof to verify
73
+ # @return [Boolean] +true+ if the proof is valid
74
+ def verify_proof(public_key_a, public_key_b, shared_secret, proof)
75
+ e = compute_challenge(public_key_a, public_key_b, shared_secret, proof.s_prime, proof.r)
76
+
77
+ # Equation 1: z·G == R + e·A
78
+ z_g = Curve.multiply_generator(proof.z)
79
+ e_a = Curve.multiply_point(public_key_a.point, e)
80
+ r_plus_ea = Curve.add_points(proof.r.point, e_a)
81
+
82
+ return false unless points_equal?(z_g, r_plus_ea)
83
+
84
+ # Equation 2: z·B == S' + e·S
85
+ z_b = Curve.multiply_point(public_key_b.point, proof.z)
86
+ e_s = Curve.multiply_point(shared_secret.point, e)
87
+ sp_plus_es = Curve.add_points(proof.s_prime.point, e_s)
88
+
89
+ points_equal?(z_b, sp_plus_es)
90
+ end
91
+
92
+ class << self
93
+ private
94
+
95
+ def compute_challenge(pub_a, pub_b, s, s_prime, r)
96
+ message = pub_a.compressed + pub_b.compressed +
97
+ s.compressed + s_prime.compressed + r.compressed
98
+ hash = Digest.sha256(message)
99
+ OpenSSL::BN.new(hash, 2) % Curve::N
100
+ end
101
+
102
+ def points_equal?(p1, p2)
103
+ p1.to_octet_string(:compressed) == p2.to_octet_string(:compressed)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module BSV
6
+ module Primitives
7
+ # An ECDSA signature consisting of (r, s) components.
8
+ #
9
+ # Supports DER encoding/decoding with strict BIP-66 validation,
10
+ # low-S normalisation (BIP-62 rule 5), and hex convenience methods.
11
+ #
12
+ # @example Parse a DER-encoded signature
13
+ # sig = BSV::Primitives::Signature.from_der(der_bytes)
14
+ # sig.low_s? #=> true
15
+ class Signature
16
+ # @return [OpenSSL::BN] the r component
17
+ attr_reader :r
18
+
19
+ # @return [OpenSSL::BN] the s component
20
+ attr_reader :s
21
+
22
+ # @param r [OpenSSL::BN, Integer] the r component
23
+ # @param s [OpenSSL::BN, Integer] the s component
24
+ def initialize(r, s)
25
+ @r = r.is_a?(OpenSSL::BN) ? r : OpenSSL::BN.new(r.to_s, 10)
26
+ @s = s.is_a?(OpenSSL::BN) ? s : OpenSSL::BN.new(s.to_s, 10)
27
+ end
28
+
29
+ # Parse a signature from DER-encoded bytes with strict BIP-66 validation.
30
+ #
31
+ # @param der_bytes [String] DER-encoded signature bytes
32
+ # @return [Signature]
33
+ # @raise [ArgumentError] if the DER encoding is invalid
34
+ def self.from_der(der_bytes)
35
+ der_bytes = der_bytes.b if der_bytes.encoding != Encoding::ASCII_8BIT
36
+ bytes = der_bytes.bytes
37
+
38
+ raise ArgumentError, 'signature too short' if bytes.length < 8
39
+ raise ArgumentError, 'invalid sequence tag' unless bytes[0] == 0x30
40
+
41
+ total_len = bytes[1]
42
+ raise ArgumentError, 'length mismatch' unless total_len == bytes.length - 2
43
+
44
+ # Parse R
45
+ raise ArgumentError, 'invalid integer tag for R' unless bytes[2] == 0x02
46
+
47
+ r_len = bytes[3]
48
+ raise ArgumentError, 'R length overflows' if 4 + r_len > bytes.length
49
+ raise ArgumentError, 'R is zero length' if r_len.zero?
50
+
51
+ r_bytes = bytes[4, r_len]
52
+ raise ArgumentError, 'R has negative flag' if r_bytes[0] & 0x80 != 0
53
+ raise ArgumentError, 'R has excessive padding' if r_len > 1 && r_bytes[0].zero? && r_bytes[1].nobits?(0x80)
54
+
55
+ # Parse S
56
+ s_offset = 4 + r_len
57
+ raise ArgumentError, 'invalid integer tag for S' unless bytes[s_offset] == 0x02
58
+
59
+ s_len = bytes[s_offset + 1]
60
+ raise ArgumentError, 'S length overflows' if s_offset + 2 + s_len > bytes.length
61
+ raise ArgumentError, 'S is zero length' if s_len.zero?
62
+
63
+ s_bytes = bytes[s_offset + 2, s_len]
64
+ raise ArgumentError, 'S has negative flag' if s_bytes[0] & 0x80 != 0
65
+ raise ArgumentError, 'S has excessive padding' if s_len > 1 && s_bytes[0].zero? && s_bytes[1].nobits?(0x80)
66
+
67
+ raise ArgumentError, 'trailing bytes' unless s_offset + 2 + s_len == bytes.length
68
+
69
+ r = OpenSSL::BN.new(r_bytes.pack('C*'), 2)
70
+ s = OpenSSL::BN.new(s_bytes.pack('C*'), 2)
71
+ new(r, s)
72
+ end
73
+
74
+ # Serialise the signature in DER format.
75
+ #
76
+ # @return [String] DER-encoded binary string
77
+ def to_der
78
+ rb = canonicalise_int(@r)
79
+ sb = canonicalise_int(@s)
80
+
81
+ der = [0x30, rb.length + sb.length + 4,
82
+ 0x02, rb.length, *rb,
83
+ 0x02, sb.length, *sb]
84
+ der.pack('C*')
85
+ end
86
+
87
+ # Parse a signature from a hex-encoded DER string.
88
+ #
89
+ # @param hex [String] hex-encoded DER signature
90
+ # @return [Signature]
91
+ def self.from_hex(hex)
92
+ from_der([hex].pack('H*'))
93
+ end
94
+
95
+ # Serialise the signature as a hex-encoded DER string.
96
+ #
97
+ # @return [String] hex-encoded DER signature
98
+ def to_hex
99
+ to_der.unpack1('H*')
100
+ end
101
+
102
+ # Check whether the S value is in the lower half of the curve order.
103
+ #
104
+ # BIP-62 rule 5 requires S <= N/2 for transaction malleability protection.
105
+ #
106
+ # @return [Boolean] +true+ if S is in the lower half
107
+ def low_s?
108
+ @s <= Curve::HALF_N
109
+ end
110
+
111
+ # Return a new signature with S normalised to the lower half of the curve order.
112
+ #
113
+ # @return [Signature] a new signature with low-S, or +self+ if already low-S
114
+ def to_low_s
115
+ return self if low_s?
116
+
117
+ self.class.new(@r, Curve::N - @s)
118
+ end
119
+
120
+ # @param other [Object] the object to compare
121
+ # @return [Boolean] +true+ if both signatures have equal r and s values
122
+ def ==(other)
123
+ other.is_a?(Signature) && @r == other.r && @s == other.s
124
+ end
125
+
126
+ private
127
+
128
+ def canonicalise_int(bn)
129
+ bytes = bn.to_s(2).bytes
130
+ bytes = [0] if bytes.empty?
131
+ bytes.unshift(0) if bytes[0] & 0x80 != 0
132
+ bytes
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ # Cryptographic primitives for the BSV blockchain.
5
+ #
6
+ # Provides keys, curves, hashing, digital signatures, encryption,
7
+ # HD key derivation (BIP-32), and mnemonic phrase generation (BIP-39).
8
+ # All cryptography uses Ruby's stdlib +openssl+ — no external gems.
9
+ module Primitives
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
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Script
5
+ # Fluent builder for constructing scripts incrementally.
6
+ #
7
+ # Provides a chainable API for building scripts from opcodes and data
8
+ # pushes, as an alternative to the template constructors on {Script}.
9
+ #
10
+ # @example Build a custom script
11
+ # script = BSV::Script::Script.builder
12
+ # .push_op(:OP_DUP)
13
+ # .push_op(:OP_HASH160)
14
+ # .push_data(pubkey_hash)
15
+ # .push_op(:OP_EQUALVERIFY)
16
+ # .push_op(:OP_CHECKSIG)
17
+ # .build
18
+ class Builder
19
+ def initialize
20
+ @chunks = []
21
+ end
22
+
23
+ # Push an opcode onto the script.
24
+ #
25
+ # @param opcode [Symbol, Integer] opcode name (e.g. +:OP_DUP+) or byte value
26
+ # @return [self] for chaining
27
+ def push_op(opcode)
28
+ code = opcode.is_a?(Symbol) ? Opcodes.const_get(opcode) : opcode
29
+ @chunks << Chunk.new(opcode: code)
30
+ self
31
+ end
32
+
33
+ # Push raw binary data (automatically selects PUSHDATA encoding).
34
+ #
35
+ # @param data [String] binary data to push
36
+ # @return [self] for chaining
37
+ def push_data(data)
38
+ bytes = data.b
39
+ @chunks << Chunk.new(opcode: push_opcode_for(bytes.bytesize), data: bytes)
40
+ self
41
+ end
42
+
43
+ # Push hex-encoded data.
44
+ #
45
+ # @param hex [String] hex-encoded data to push
46
+ # @return [self] for chaining
47
+ def push_hex(hex)
48
+ push_data([hex].pack('H*'))
49
+ end
50
+
51
+ # Finalise and return the constructed {Script}.
52
+ #
53
+ # @return [Script] the built script
54
+ def build
55
+ Script.from_chunks(@chunks)
56
+ end
57
+
58
+ private
59
+
60
+ def push_opcode_for(len)
61
+ if len <= 0x4b
62
+ len
63
+ elsif len <= 0xff
64
+ Opcodes::OP_PUSHDATA1
65
+ elsif len <= 0xffff
66
+ Opcodes::OP_PUSHDATA2
67
+ else
68
+ Opcodes::OP_PUSHDATA4
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Script
5
+ # A single element of a parsed script — either an opcode or a data push.
6
+ #
7
+ # Scripts are composed of a sequence of chunks. Each chunk is either
8
+ # a bare opcode (e.g. +OP_DUP+) or a data push (opcode + data payload).
9
+ class Chunk
10
+ # @return [Integer] the opcode byte
11
+ attr_reader :opcode
12
+
13
+ # @return [String, nil] the pushed data bytes, or +nil+ for bare opcodes
14
+ attr_reader :data
15
+
16
+ # @param opcode [Integer] the opcode byte value
17
+ # @param data [String, nil] data payload for push operations
18
+ def initialize(opcode:, data: nil)
19
+ @opcode = opcode
20
+ @data = data&.b
21
+ end
22
+
23
+ # Whether this chunk carries a data payload.
24
+ #
25
+ # @return [Boolean] +true+ if this is a data push chunk
26
+ def data?
27
+ !@data.nil?
28
+ end
29
+
30
+ # Serialise this chunk back to raw script bytes.
31
+ #
32
+ # @return [String] binary script bytes for this chunk
33
+ def to_binary
34
+ if @data
35
+ push_data_binary(@data)
36
+ else
37
+ [@opcode].pack('C')
38
+ end
39
+ end
40
+
41
+ # Render this chunk as human-readable ASM.
42
+ #
43
+ # Data pushes are shown as hex strings; opcodes are shown by name.
44
+ #
45
+ # @return [String] ASM representation
46
+ def to_asm
47
+ if @data
48
+ @data.unpack1('H*')
49
+ else
50
+ Opcodes.name_for(@opcode) || @opcode.to_s
51
+ end
52
+ end
53
+
54
+ # @param other [Object] the object to compare
55
+ # @return [Boolean] +true+ if both chunks have equal opcode and data
56
+ def ==(other)
57
+ other.is_a?(Chunk) && @opcode == other.opcode && @data == other.data
58
+ end
59
+
60
+ private
61
+
62
+ def push_data_binary(data)
63
+ len = data.bytesize
64
+
65
+ if len <= 0x4b
66
+ [len].pack('C') + data
67
+ elsif len <= 0xff
68
+ [Opcodes::OP_PUSHDATA1, len].pack('CC') + data
69
+ elsif len <= 0xffff
70
+ [Opcodes::OP_PUSHDATA2, len].pack('Cv') + data
71
+ else
72
+ [Opcodes::OP_PUSHDATA4, len].pack('CV') + data
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Script
5
+ # Error raised during script execution.
6
+ #
7
+ # Carries a machine-readable error code from {ScriptErrorCode} alongside
8
+ # a human-readable message.
9
+ class ScriptError < StandardError
10
+ # @return [Symbol] the error code from {ScriptErrorCode}
11
+ attr_reader :code
12
+
13
+ # @param code [Symbol] error code from {ScriptErrorCode}
14
+ # @param message [String, nil] human-readable description (auto-generated from code if omitted)
15
+ def initialize(code, message = nil)
16
+ @code = code
17
+ super(message || code.to_s.tr('_', ' '))
18
+ end
19
+ end
20
+
21
+ # Error codes for script execution failures.
22
+ #
23
+ # Each constant corresponds to a specific script validation rule.
24
+ module ScriptErrorCode
25
+ EVAL_FALSE = :eval_false
26
+ EMPTY_STACK = :empty_stack
27
+ VERIFY_FAILED = :verify_failed
28
+ EQUALVERIFY_FAILED = :equalverify_failed
29
+ NUMEQUALVERIFY_FAILED = :numequalverify_failed
30
+ CHECKSIGVERIFY_FAILED = :checksigverify_failed
31
+ CHECKMULTISIGVERIFY_FAILED = :checkmultisigverify_failed
32
+ UNBALANCED_CONDITIONAL = :unbalanced_conditional
33
+ DISABLED_OPCODE = :disabled_opcode
34
+ RESERVED_OPCODE = :reserved_opcode
35
+ INVALID_STACK_OPERATION = :invalid_stack_operation
36
+ MALFORMED_PUSH = :malformed_push
37
+ NUMBER_TOO_BIG = :number_too_big
38
+ DIVIDE_BY_ZERO = :divide_by_zero
39
+ INVALID_INPUT_LENGTH = :invalid_input_length
40
+ INVALID_PUBKEY_COUNT = :invalid_pubkey_count
41
+ INVALID_SIG_COUNT = :invalid_sig_count
42
+ SIG_NULLFAIL = :sig_nullfail
43
+ SIG_NULLDUMMY = :sig_nulldummy
44
+ SIG_DER = :sig_der
45
+ SIG_HIGH_S = :sig_high_s
46
+ PUBKEY_TYPE = :pubkey_type
47
+ INVALID_SIGHASH_TYPE = :invalid_sighash_type
48
+ EARLY_RETURN = :early_return
49
+ IMPOSSIBLE_ENCODING = :impossible_encoding
50
+ INVALID_OPCODE = :invalid_opcode
51
+ MINIMAL_DATA = :minimal_data
52
+ end
53
+ end
54
+ end