Class: BSV::Script::Script
- Inherits:
-
Object
- Object
- BSV::Script::Script
- Defined in:
- lib/bsv/script/script.rb
Overview
A Bitcoin script — a sequence of opcodes and data pushes.
Scripts are the programmable spending conditions attached to transaction outputs (locking scripts) and inputs (unlocking scripts). This class provides construction from multiple formats, type detection, data extraction, and template constructors for standard script types.
Follows the SDK's "recognise everything, construct only what's valid"
principle — detection methods (e.g. p2sh?) work for all script types,
but constructors are only provided for types valid on BSV.
Constant Summary collapse
- RPUZZLE_HASH_OPS =
Hash type to opcode mapping for RPuzzle scripts.
{ raw: nil, sha1: Opcodes::OP_SHA1, ripemd160: Opcodes::OP_RIPEMD160, sha256: Opcodes::OP_SHA256, hash160: Opcodes::OP_HASH160, hash256: Opcodes::OP_HASH256 }.freeze
- RPUZZLE_OP_TO_TYPE =
Reverse lookup: opcode → hash type symbol (excludes :raw).
RPUZZLE_HASH_OPS.except(:raw).invert.freeze
- RPUZZLE_PREFIX =
The fixed opcode prefix shared by all RPuzzle locking scripts. OP_OVER OP_3 OP_SPLIT OP_NIP OP_1 OP_SPLIT OP_SWAP OP_SPLIT OP_DROP
[ Opcodes::OP_OVER, Opcodes::OP_3, Opcodes::OP_SPLIT, Opcodes::OP_NIP, Opcodes::OP_1, Opcodes::OP_SPLIT, Opcodes::OP_SWAP, Opcodes::OP_SPLIT, Opcodes::OP_DROP ].freeze
Instance Attribute Summary collapse
-
#bytes ⇒ String
readonly
The raw script bytes (frozen, binary encoding).
Class Method Summary collapse
-
.builder ⇒ Builder
Create a new Builder for fluent script construction.
-
.from_asm(asm_string) ⇒ Script
Parse a script from ASM notation.
-
.from_binary(binary) ⇒ Script
Parse a script from raw binary bytes.
-
.from_chunks(chunks) ⇒ Script
Build a script from an array of Chunk objects.
-
.from_hex(hex) ⇒ Script
Parse a script from a hex string.
-
.op_cat_lock(expected_data) ⇒ Script
Construct an OP_CAT locking script.
-
.op_cat_unlock(data1, data2) ⇒ Script
Construct an OP_CAT unlocking script.
-
.op_return(*data_items) ⇒ Script
Construct an OP_RETURN data carrier script.
-
.p2ms_lock(required, pubkeys) ⇒ Script
Construct an M-of-N bare multisig locking script.
-
.p2ms_unlock(*signatures) ⇒ Script
Construct a bare multisig unlocking script.
-
.p2pk_lock(pubkey_bytes) ⇒ Script
Construct a Pay-to-Public-Key (P2PK) locking script.
-
.p2pk_unlock(signature_der) ⇒ Script
Construct a P2PK unlocking script.
-
.p2pkh_lock(pubkey_hash_or_address) ⇒ Script
Construct a Pay-to-Public-Key-Hash (P2PKH) locking script.
-
.p2pkh_unlock(signature_der, pubkey_bytes) ⇒ Script
Construct a P2PKH unlocking script.
-
.pushdrop_lock(fields, lock_script, lock_position: :before) ⇒ Script
Construct a PushDrop locking script.
-
.pushdrop_unlock(unlock_script) ⇒ Script
Construct a PushDrop unlocking script.
-
.rpuzzle_lock(hash_value, hash_type: :hash160) ⇒ Script
Construct an RPuzzle locking script.
-
.rpuzzle_unlock(signature_der, pubkey_bytes) ⇒ Script
Construct an RPuzzle unlocking script.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
trueif both scripts have identical bytes. -
#addresses(network: :mainnet) ⇒ Array<String>
Derive Bitcoin addresses from this script.
-
#chunks ⇒ Array<Chunk>
Parse the script into an array of Chunk objects.
-
#initialize(bytes = ''.b) ⇒ Script
constructor
A new instance of Script.
-
#length ⇒ Integer
Script length in bytes.
-
#multisig? ⇒ Boolean
Whether this is a bare multisig script.
-
#op_cat? ⇒ Boolean
Whether this is an OP_CAT puzzle script.
-
#op_return? ⇒ Boolean
Whether this is an OP_RETURN data carrier script.
-
#op_return_data ⇒ Array<String>?
Extract data payloads from an OP_RETURN script.
-
#p2pk? ⇒ Boolean
Whether this is a Pay-to-Public-Key (P2PK) script.
-
#p2pkh? ⇒ Boolean
Whether this is a Pay-to-Public-Key-Hash (P2PKH) script.
-
#p2sh? ⇒ Boolean
Whether this is a Pay-to-Script-Hash (P2SH) script.
-
#pubkey_hash ⇒ String?
Extract the 20-byte public key hash from a P2PKH script.
-
#push_only? ⇒ Boolean
Whether the script consists entirely of push-data operations.
-
#pushdrop? ⇒ Boolean
Whether this is a PushDrop script.
-
#pushdrop_fields ⇒ Array<String>?
Extract the embedded data fields from a PushDrop script.
-
#pushdrop_lock_script ⇒ Script?
Extract the underlying lock script from a PushDrop script.
-
#rpuzzle? ⇒ Boolean
Whether this is an RPuzzle script.
-
#rpuzzle_hash ⇒ String?
Extract the hash value from an RPuzzle script.
-
#rpuzzle_hash_type ⇒ Symbol?
Detect the hash type used in an RPuzzle script.
-
#script_hash ⇒ String?
Extract the 20-byte script hash from a P2SH script.
-
#to_asm ⇒ String
Human-readable ASM representation.
-
#to_binary ⇒ String
A copy of the raw script bytes.
-
#to_hex ⇒ String
Hex-encoded script.
-
#type ⇒ String
Classify the script as a standard type.
Constructor Details
#initialize(bytes = ''.b) ⇒ Script
Returns a new instance of Script.
33 34 35 36 37 38 39 40 |
# File 'lib/bsv/script/script.rb', line 33 def initialize(bytes = ''.b) # Defensive copy + freeze: scripts are immutable in the protocol; freezing # @bytes prevents external in-place mutation from staling caches that # depend on the script's serialisation (see # docs/reference/sighash-cache.md). @bytes = bytes.b.dup.freeze @chunks = nil end |
Instance Attribute Details
#bytes ⇒ String (readonly)
Scripts are protocol-level immutable; the underlying bytes are
defensively copied + frozen on construction. Downstream callers
that need to build a script incrementally must compose bytes
into a new String and pass it to a fresh Script.new.
Returns the raw script bytes (frozen, binary encoding).
30 31 32 |
# File 'lib/bsv/script/script.rb', line 30 def bytes @bytes end |
Class Method Details
.builder ⇒ Builder
Create a new Builder for fluent script construction.
135 136 137 |
# File 'lib/bsv/script/script.rb', line 135 def self.builder Builder.new end |
.from_asm(asm_string) ⇒ Script
Parse a script from ASM notation.
Opcodes are given by name (e.g. "OP_DUP"), data pushes as hex.
Supports the canonical aliases "0" (OP_0) and "-1" (OP_1NEGATE).
Explicit PUSHDATA sequences (+OP_PUSHDATA1
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/bsv/script/script.rb', line 67 def self.from_asm(asm_string) buf = ''.b tokens = asm_string.split i = 0 while i < tokens.length token = tokens[i] # Canonical short-hand aliases if token == '0' buf << [Opcodes::OP_0].pack('C') i += 1 next end if token == '-1' buf << [Opcodes::OP_1NEGATE].pack('C') i += 1 next end opcode = resolve_opcode(token) if [Opcodes::OP_PUSHDATA1, Opcodes::OP_PUSHDATA2, Opcodes::OP_PUSHDATA4].include?(opcode) # Explicit PUSHDATA sequence: OP_PUSHDATAn <len> <hex> # Consume the following length token and hex token as a unit. raise ArgumentError, "#{token} requires <length> <hex> tokens" unless tokens[i + 1] && tokens[i + 2] hex_token = tokens[i + 2] data = BSV::Primitives::Hex.decode(hex_token.to_s, name: 'ASM PUSHDATA hex token') max_len = { Opcodes::OP_PUSHDATA1 => 0xFF, Opcodes::OP_PUSHDATA2 => 0xFFFF, Opcodes::OP_PUSHDATA4 => 0xFFFFFFFF } raise ArgumentError, "data too long for #{token}: #{data.bytesize} > #{max_len[opcode]}" if data.bytesize > max_len[opcode] case opcode when Opcodes::OP_PUSHDATA1 buf << ([Opcodes::OP_PUSHDATA1, data.bytesize].pack('CC') + data) when Opcodes::OP_PUSHDATA2 buf << ([Opcodes::OP_PUSHDATA2].pack('C') + [data.bytesize].pack('v') + data) when Opcodes::OP_PUSHDATA4 buf << ([Opcodes::OP_PUSHDATA4].pack('C') + [data.bytesize].pack('V') + data) end i += 3 elsif opcode buf << [opcode].pack('C') i += 1 else # Data push — token is hex data = BSV::Primitives::Hex.decode(token, name: 'ASM hex token') buf << encode_push_data(data) i += 1 end end new(buf) end |
.from_binary(binary) ⇒ Script
Parse a script from raw binary bytes.
46 47 48 |
# File 'lib/bsv/script/script.rb', line 46 def self.from_binary(binary) new(binary) end |
.from_chunks(chunks) ⇒ Script
Build a script from an array of Chunk objects.
125 126 127 128 129 130 |
# File 'lib/bsv/script/script.rb', line 125 def self.from_chunks(chunks) buf = chunks.map(&:to_binary).join script = new(buf) script.instance_variable_set(:@chunks, chunks.dup) script end |
.from_hex(hex) ⇒ Script
Parse a script from a hex string.
54 55 56 |
# File 'lib/bsv/script/script.rb', line 54 def self.from_hex(hex) new(BSV::Primitives::Hex.decode(hex, name: 'script hex')) end |
.op_cat_lock(expected_data) ⇒ Script
Construct an OP_CAT locking script.
The script concatenates two stack items and compares the result
against the expected data. The spender must push two values whose
concatenation equals expected_data.
397 398 399 400 401 402 |
# File 'lib/bsv/script/script.rb', line 397 def self.op_cat_lock(expected_data) buf = [Opcodes::OP_CAT].pack('C') buf << encode_push_data(expected_data.b) buf << [Opcodes::OP_EQUAL].pack('C') new(buf) end |
.op_cat_unlock(data1, data2) ⇒ Script
Construct an OP_CAT unlocking script.
Pushes two data items onto the stack. The locking script's OP_CAT will concatenate them and compare against the expected value.
412 413 414 415 416 |
# File 'lib/bsv/script/script.rb', line 412 def self.op_cat_unlock(data1, data2) buf = encode_push_data(data1.b) buf << encode_push_data(data2.b) new(buf) end |
.op_return(*data_items) ⇒ Script
Construct an OP_RETURN data carrier script.
Uses the safe OP_FALSE OP_RETURN prefix (provably unspendable).
147 148 149 150 151 |
# File 'lib/bsv/script/script.rb', line 147 def self.op_return(*data_items) buf = [Opcodes::OP_FALSE, Opcodes::OP_RETURN].pack('CC') data_items.each { |d| buf << encode_push_data(d.b) } new(buf) end |
.p2ms_lock(required, pubkeys) ⇒ Script
Construct an M-of-N bare multisig locking script.
250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/bsv/script/script.rb', line 250 def self.p2ms_lock(required, pubkeys) n = pubkeys.length raise ArgumentError, 'm must be between 1 and n' unless required.between?(1, n) raise ArgumentError, 'n must be <= 16' unless n <= 16 buf = [Opcodes::OP_1 + required - 1].pack('C') pubkeys.each { |pk| buf << encode_push_data(pk.b) } buf << [Opcodes::OP_1 + n - 1].pack('C') buf << [Opcodes::OP_CHECKMULTISIG].pack('C') new(buf) end |
.p2ms_unlock(*signatures) ⇒ Script
Construct a bare multisig unlocking script.
266 267 268 269 270 |
# File 'lib/bsv/script/script.rb', line 266 def self.p2ms_unlock(*signatures) buf = [Opcodes::OP_0].pack('C') signatures.each { |sig| buf << encode_push_data(sig.b) } new(buf) end |
.p2pk_lock(pubkey_bytes) ⇒ Script
Construct a Pay-to-Public-Key (P2PK) locking script.
228 229 230 231 232 233 234 |
# File 'lib/bsv/script/script.rb', line 228 def self.p2pk_lock(pubkey_bytes) raise ArgumentError, 'pubkey must be 33 or 65 bytes' unless [33, 65].include?(pubkey_bytes.bytesize) buf = encode_push_data(pubkey_bytes) buf << [Opcodes::OP_CHECKSIG].pack('C') new(buf) end |
.p2pk_unlock(signature_der) ⇒ Script
Construct a P2PK unlocking script.
240 241 242 |
# File 'lib/bsv/script/script.rb', line 240 def self.p2pk_unlock(signature_der) new(encode_push_data(signature_der)) end |
.p2pkh_lock(pubkey_hash_or_address) ⇒ Script
Construct a Pay-to-Public-Key-Hash (P2PKH) locking script.
Accepts either a raw 20-byte binary hash or a Base58Check address string.
When given an address string, the version prefix is validated: 0x00
(mainnet) and 0x6f (testnet) are accepted; 0x05 (P2SH) is rejected
with a clear error message.
166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/bsv/script/script.rb', line 166 def self.p2pkh_lock(pubkey_hash_or_address) pubkey_hash = resolve_pubkey_hash(pubkey_hash_or_address) buf = [ Opcodes::OP_DUP, Opcodes::OP_HASH160 ].pack('CC') buf << encode_push_data(pubkey_hash) buf << [Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG].pack('CC') new(buf) end |
.p2pkh_unlock(signature_der, pubkey_bytes) ⇒ Script
Construct a P2PKH unlocking script.
217 218 219 220 221 |
# File 'lib/bsv/script/script.rb', line 217 def self.p2pkh_unlock(signature_der, pubkey_bytes) buf = encode_push_data(signature_der) buf << encode_push_data(pubkey_bytes) new(buf) end |
.pushdrop_lock(fields, lock_script, lock_position: :before) ⇒ Script
Construct a PushDrop locking script.
Pushes arbitrary data fields onto the stack, then drops them all before the locking condition executes. Used for token protocols where data must be embedded in spendable outputs.
The lock_position parameter controls where the locking script is placed
relative to the push/drop sequence:
:'before'(default) — lock script appears before the data pushes and drops. Matches the ts-sdk default and the canonical PushDrop layout used by overlay token protocols. Structure:[lock_script] [field0] ... [fieldN] [OP_2DROP...] [OP_DROP?]:'after'— lock script appears after the drops (legacy behaviour). Structure:[field0] ... [fieldN] [OP_2DROP...] [OP_DROP?] [lock_script]
Breaking change (v0.9): the default changed from :'after' to
:'before' to match the ts-sdk. Callers that relied on the old default
must pass lock_position: :after explicitly.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/bsv/script/script.rb', line 298 def self.pushdrop_lock(fields, lock_script, lock_position: :before) raise ArgumentError, 'fields must not be empty' if fields.empty? raise ArgumentError, 'lock_script must be a Script' unless lock_script.is_a?(Script) raise ArgumentError, "lock_position must be :before or :after, got #{lock_position.inspect}" \ unless %i[before after].include?(lock_position) field_chunks = fields.map { |f| encode_minimally(f.b) } drop_chunks = [] remaining = fields.length while remaining > 1 drop_chunks << Chunk.new(opcode: Opcodes::OP_2DROP) remaining -= 2 end drop_chunks << Chunk.new(opcode: Opcodes::OP_DROP) if remaining == 1 all_chunks = if lock_position == :before lock_script.chunks + field_chunks + drop_chunks else field_chunks + drop_chunks + lock_script.chunks end from_chunks(all_chunks) end |
.pushdrop_unlock(unlock_script) ⇒ Script
Construct a PushDrop unlocking script.
Pass-through wrapper — the data fields are dropped during execution, so the unlocking script just needs to satisfy the underlying lock.
330 331 332 |
# File 'lib/bsv/script/script.rb', line 330 def self.pushdrop_unlock(unlock_script) unlock_script end |
.rpuzzle_lock(hash_value, hash_type: :hash160) ⇒ Script
Construct an RPuzzle locking script.
RPuzzle enables hash-puzzle-based spending where the spender proves knowledge of the ECDSA K-value (nonce) that produced a signature's R component.
366 367 368 369 370 371 372 373 374 375 |
# File 'lib/bsv/script/script.rb', line 366 def self.rpuzzle_lock(hash_value, hash_type: :hash160) raise ArgumentError, "unknown hash_type: #{hash_type}" unless RPUZZLE_HASH_OPS.key?(hash_type) buf = RPUZZLE_PREFIX.pack('C*') hash_op = RPUZZLE_HASH_OPS[hash_type] buf << [hash_op].pack('C') if hash_op buf << encode_push_data(hash_value.b) buf << [Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG].pack('CC') new(buf) end |
.rpuzzle_unlock(signature_der, pubkey_bytes) ⇒ Script
Construct an RPuzzle unlocking script.
Same wire format as P2PKH: signature + public key.
384 385 386 |
# File 'lib/bsv/script/script.rb', line 384 def self.rpuzzle_unlock(signature_der, pubkey_bytes) p2pkh_unlock(signature_der, pubkey_bytes) end |
Instance Method Details
#==(other) ⇒ Boolean
Returns true if both scripts have identical bytes.
720 721 722 |
# File 'lib/bsv/script/script.rb', line 720 def ==(other) other.is_a?(self.class) && @bytes == other.bytes end |
#addresses(network: :mainnet) ⇒ Array<String>
Derive Bitcoin addresses from this script.
Currently supports P2PKH scripts only.
698 699 700 701 702 703 704 705 |
# File 'lib/bsv/script/script.rb', line 698 def addresses(network: :mainnet) if p2pkh? prefix = network == :testnet ? BSV::Primitives::PublicKey::TESTNET_PUBKEY_HASH : BSV::Primitives::PublicKey::MAINNET_PUBKEY_HASH [BSV::Primitives::Base58.check_encode(prefix + pubkey_hash)] else [] end end |
#chunks ⇒ Array<Chunk>
Parse the script into an array of Chunk objects.
Results are cached after first parse.
714 715 716 |
# File 'lib/bsv/script/script.rb', line 714 def chunks @chunks ||= parse_chunks end |
#length ⇒ Integer
Returns script length in bytes.
436 437 438 |
# File 'lib/bsv/script/script.rb', line 436 def length @bytes.bytesize end |
#multisig? ⇒ Boolean
Whether this is a bare multisig script.
Pattern: OP_M <pubkey1> ... <pubkeyN> OP_N OP_CHECKMULTISIG
570 571 572 573 574 575 576 577 |
# File 'lib/bsv/script/script.rb', line 570 def multisig? c = chunks return false if c.length < 3 return false unless small_int_opcode?(c[0].opcode) return false unless small_int_opcode?(c[-2].opcode) && c[-1].opcode == Opcodes::OP_CHECKMULTISIG c[1..-3].all?(&:data?) end |
#op_cat? ⇒ Boolean
Whether this is an OP_CAT puzzle script.
Pattern: OP_CAT <expected_data> OP_EQUAL
557 558 559 560 561 562 563 |
# File 'lib/bsv/script/script.rb', line 557 def op_cat? c = chunks c.length == 3 && c[0].opcode == Opcodes::OP_CAT && c[1].data? && c[2].opcode == Opcodes::OP_EQUAL end |
#op_return? ⇒ Boolean
Whether this is an OP_RETURN data carrier script.
Matches both OP_RETURN ... and OP_FALSE OP_RETURN ... forms.
487 488 489 490 491 |
# File 'lib/bsv/script/script.rb', line 487 def op_return? b = @bytes (b.bytesize.positive? && b.getbyte(0) == Opcodes::OP_RETURN) || (b.bytesize > 1 && b.getbyte(0) == Opcodes::OP_FALSE && b.getbyte(1) == Opcodes::OP_RETURN) end |
#op_return_data ⇒ Array<String>?
Extract data payloads from an OP_RETURN script.
After F3.1's fix, the parser absorbs all bytes following a top-level OP_RETURN into a single raw-data chunk. This method re-parses that tail so callers receive one entry per push (including bare opcodes that appear as raw bytes in the tail).
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 |
# File 'lib/bsv/script/script.rb', line 628 def op_return_data return unless op_return? # Determine where the payload starts (after OP_RETURN or OP_FALSE OP_RETURN) start = @bytes.getbyte(0) == Opcodes::OP_RETURN ? 1 : 2 # Parse the tail bytes as a sub-script to recover individual push items. # The tail was stored as a raw-data chunk by parse_chunks; parsing it again # yields all the contained push operations. # # IMPORTANT: disable OP_RETURN termination during re-parse — the tail # may legitimately contain 0x6a bytes as data, and we don't want the # parser to stop at them. tail_bytes = @bytes.byteslice(start, @bytes.bytesize - start) return [] if tail_bytes.nil? || tail_bytes.empty? tail_script = Script.new(tail_bytes) tail_script.send(:parse_chunks, terminate_on_op_return: false).map do |ch| ch.data? ? ch.data : [ch.opcode].pack('C') end end |
#p2pk? ⇒ Boolean
Whether this is a Pay-to-Public-Key (P2PK) script.
Pattern: <pubkey> OP_CHECKSIG
498 499 500 501 502 503 504 505 506 |
# File 'lib/bsv/script/script.rb', line 498 def p2pk? c = chunks return false unless c.length == 2 && c[0].data? && c[1].opcode == Opcodes::OP_CHECKSIG pubkey = c[0].data version = pubkey.getbyte(0) ([0x02, 0x03].include?(version) && pubkey.bytesize == 33) || ([0x04, 0x06, 0x07].include?(version) && pubkey.bytesize == 65) end |
#p2pkh? ⇒ Boolean
Whether this is a Pay-to-Public-Key-Hash (P2PKH) script.
Pattern: OP_DUP OP_HASH160 <20 bytes> OP_EQUALVERIFY OP_CHECKSIG
447 448 449 450 451 452 453 454 455 |
# File 'lib/bsv/script/script.rb', line 447 def p2pkh? b = @bytes b.bytesize == 25 && b.getbyte(0) == Opcodes::OP_DUP && b.getbyte(1) == Opcodes::OP_HASH160 && b.getbyte(2) == 0x14 && b.getbyte(23) == Opcodes::OP_EQUALVERIFY && b.getbyte(24) == Opcodes::OP_CHECKSIG end |
#p2sh? ⇒ Boolean
Whether this is a Pay-to-Script-Hash (P2SH) script.
Detection only — P2SH is not valid on BSV, so no constructor is provided.
Pattern: OP_HASH160 <20 bytes> OP_EQUAL
463 464 465 466 467 468 469 |
# File 'lib/bsv/script/script.rb', line 463 def p2sh? b = @bytes b.bytesize == 23 && b.getbyte(0) == Opcodes::OP_HASH160 && b.getbyte(1) == 0x14 && b.getbyte(22) == Opcodes::OP_EQUAL end |
#pubkey_hash ⇒ String?
Extract the 20-byte public key hash from a P2PKH script.
605 606 607 608 609 |
# File 'lib/bsv/script/script.rb', line 605 def pubkey_hash return unless p2pkh? @bytes.byteslice(3, 20) end |
#push_only? ⇒ Boolean
Whether the script consists entirely of push-data operations.
A script is push-only iff every chunk's opcode is at most OP_16
(0x60). Used by the script interpreter to enforce the SIGPUSHONLY
verification flag on unlock scripts.
478 479 480 |
# File 'lib/bsv/script/script.rb', line 478 def push_only? chunks.all? { |chunk| chunk.opcode <= Opcodes::OP_16 } end |
#pushdrop? ⇒ Boolean
Whether this is a PushDrop script.
Detects both lock_position: :before and lock_position: :after layouts:
:before:[lock_chunks...] [field0] ... [fieldN] [OP_2DROP...] [OP_DROP?]:after:[field0] ... [fieldN] [OP_2DROP...] [OP_DROP?] [lock_chunks...]
516 517 518 |
# File 'lib/bsv/script/script.rb', line 516 def pushdrop? pushdrop_layout ? true : false end |
#pushdrop_fields ⇒ Array<String>?
Extract the embedded data fields from a PushDrop script.
Works for both :before and :after lock positions.
673 674 675 676 677 678 |
# File 'lib/bsv/script/script.rb', line 673 def pushdrop_fields layout = pushdrop_layout return unless layout layout[:field_chunks].map { |ch| decode_minimal_push(ch) } end |
#pushdrop_lock_script ⇒ Script?
Extract the underlying lock script from a PushDrop script.
Works for both :before and :after lock positions.
685 686 687 688 689 690 |
# File 'lib/bsv/script/script.rb', line 685 def pushdrop_lock_script layout = pushdrop_layout return unless layout self.class.from_chunks(layout[:lock_chunks]) end |
#rpuzzle? ⇒ Boolean
Whether this is an RPuzzle script.
Detects the fixed R-value extraction prefix followed by an optional hash opcode, a data push, OP_EQUALVERIFY, and OP_CHECKSIG.
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 |
# File 'lib/bsv/script/script.rb', line 526 def rpuzzle? c = chunks # Minimum: 9 prefix + hash_data + OP_EQUALVERIFY + OP_CHECKSIG = 12 # With hash op: 13 return false unless c.length >= 12 # Verify the 9-opcode prefix RPUZZLE_PREFIX.each_with_index do |op, i| return false unless c[i].opcode == op end # After prefix: optional hash op, then data push, OP_EQUALVERIFY, OP_CHECKSIG return false unless c[-1].opcode == Opcodes::OP_CHECKSIG return false unless c[-2].opcode == Opcodes::OP_EQUALVERIFY return false unless c[-3].data? # Either exactly 12 chunks (raw) or 13 chunks (with hash op) if c.length == 12 true elsif c.length == 13 RPUZZLE_HASH_OPS.values.compact.include?(c[9].opcode) else false end end |
#rpuzzle_hash ⇒ String?
Extract the hash value from an RPuzzle script.
653 654 655 656 657 |
# File 'lib/bsv/script/script.rb', line 653 def rpuzzle_hash return unless rpuzzle? chunks[-3].data end |
#rpuzzle_hash_type ⇒ Symbol?
Detect the hash type used in an RPuzzle script.
662 663 664 665 666 |
# File 'lib/bsv/script/script.rb', line 662 def rpuzzle_hash_type return unless rpuzzle? chunks.length == 12 ? :raw : RPUZZLE_OP_TO_TYPE[chunks[9].opcode] end |
#script_hash ⇒ String?
Extract the 20-byte script hash from a P2SH script.
614 615 616 617 618 |
# File 'lib/bsv/script/script.rb', line 614 def script_hash return unless p2sh? @bytes.byteslice(2, 20) end |
#to_asm ⇒ String
Returns human-readable ASM representation.
431 432 433 |
# File 'lib/bsv/script/script.rb', line 431 def to_asm chunks.map(&:to_asm).join(' ') end |
#to_binary ⇒ String
Returns a copy of the raw script bytes.
421 422 423 |
# File 'lib/bsv/script/script.rb', line 421 def to_binary @bytes.dup end |
#to_hex ⇒ String
Returns hex-encoded script.
426 427 428 |
# File 'lib/bsv/script/script.rb', line 426 def to_hex @bytes.unpack1('H*') end |
#type ⇒ String
Classify the script as a standard type.
586 587 588 589 590 591 592 593 594 595 596 597 598 |
# File 'lib/bsv/script/script.rb', line 586 def type if @bytes.empty? then 'empty' elsif p2pkh? then 'pubkeyhash' elsif p2pk? then 'pubkey' elsif p2sh? then 'scripthash' elsif op_return? then 'nulldata' elsif multisig? then 'multisig' elsif pushdrop? then 'pushdrop' elsif rpuzzle? then 'rpuzzle' elsif op_cat? then 'opcat' else 'nonstandard' end end |