Class: BSV::Script::Script

Inherits:
Object
  • Object
show all
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.

Examples:

Build a P2PKH locking script

script = BSV::Script::Script.p2pkh_lock(pubkey_hash)
script.type #=> "pubkeyhash"

Parse from hex and inspect

script = BSV::Script::Script.from_hex('76a914...')
script.p2pkh? #=> true
script.to_asm #=> "OP_DUP OP_HASH160 ... OP_EQUALVERIFY OP_CHECKSIG"

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.reject { |k, _| k == :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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bytes = ''.b) ⇒ Script

Returns a new instance of Script.

Parameters:

  • bytes (String) (defaults to: ''.b)

    raw script bytes (default: empty)



29
30
31
32
# File 'lib/bsv/script/script.rb', line 29

def initialize(bytes = ''.b)
  @bytes = bytes.b
  @chunks = nil
end

Instance Attribute Details

#bytesString (readonly)

Returns the raw script bytes.

Returns:

  • (String)

    the raw script bytes



26
27
28
# File 'lib/bsv/script/script.rb', line 26

def bytes
  @bytes
end

Class Method Details

.builderBuilder

Create a new Builder for fluent script construction.

Returns:



127
128
129
# File 'lib/bsv/script/script.rb', line 127

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 <len> <hex>+, etc.) are consumed as a unit.

Parameters:

  • asm_string (String)

    space-separated ASM tokens

Returns:



59
60
61
62
63
64
65
66
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
# File 'lib/bsv/script/script.rb', line 59

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.

Parameters:

  • binary (String)

    raw script bytes

Returns:



38
39
40
# File 'lib/bsv/script/script.rb', line 38

def self.from_binary(binary)
  new(binary)
end

.from_chunks(chunks) ⇒ Script

Build a script from an array of Chunk objects.

Parameters:

  • chunks (Array<Chunk>)

    script chunks

Returns:



117
118
119
120
121
122
# File 'lib/bsv/script/script.rb', line 117

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.

Parameters:

  • hex (String)

    hex-encoded script

Returns:



46
47
48
# File 'lib/bsv/script/script.rb', line 46

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.

Parameters:

  • expected_data (String)

    binary string — the expected result of concatenating the two unlocking values

Returns:



389
390
391
392
393
394
# File 'lib/bsv/script/script.rb', line 389

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.

Parameters:

  • data1 (String)

    binary string — first item (pushed first, deeper on stack)

  • data2 (String)

    binary string — second item (pushed second, top of stack)

Returns:



404
405
406
407
408
# File 'lib/bsv/script/script.rb', line 404

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).

Parameters:

  • data_items (Array<String>)

    one or more data payloads to embed

Returns:



139
140
141
142
143
# File 'lib/bsv/script/script.rb', line 139

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.

Parameters:

  • required (Integer)

    number of required signatures (M)

  • pubkeys (Array<String>)

    array of public key byte strings (N keys)

Returns:

Raises:

  • (ArgumentError)

    if M or N is out of range



242
243
244
245
246
247
248
249
250
251
252
# File 'lib/bsv/script/script.rb', line 242

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.

Parameters:

  • signatures (Array<String>)

    DER-encoded signatures with sighash bytes

Returns:



258
259
260
261
262
# File 'lib/bsv/script/script.rb', line 258

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.

Parameters:

  • pubkey_bytes (String)

    33-byte compressed or 65-byte uncompressed public key

Returns:

Raises:

  • (ArgumentError)

    if pubkey_bytes is not 33 or 65 bytes



220
221
222
223
224
225
226
# File 'lib/bsv/script/script.rb', line 220

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.

Parameters:

  • signature_der (String)

    DER-encoded signature with sighash byte appended

Returns:



232
233
234
# File 'lib/bsv/script/script.rb', line 232

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.

Parameters:

  • pubkey_hash_or_address (String)

    20-byte binary pubkey hash, or a Base58Check address string

Returns:

Raises:

  • (ArgumentError)

    if the argument is not a valid 20-byte hash, if the address has an unrecognised prefix, or if a P2SH address is supplied

  • (BSV::Primitives::Base58::ChecksumError)

    if the address checksum is invalid



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/bsv/script/script.rb', line 158

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.

Parameters:

  • signature_der (String)

    DER-encoded signature with sighash byte appended

  • pubkey_bytes (String)

    compressed or uncompressed public key bytes

Returns:



209
210
211
212
213
# File 'lib/bsv/script/script.rb', line 209

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.

Parameters:

  • fields (Array<String>)

    data payloads to embed (binary strings)

  • lock_script (Script)

    the underlying locking condition (e.g. P2PKH)

  • lock_position (Symbol) (defaults to: :before)

    :before (default) or :after

Returns:

Raises:

  • (ArgumentError)

    if fields is empty, lock_script is not a Script, or lock_position is not :before or :after



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/bsv/script/script.rb', line 290

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.

Parameters:

  • unlock_script (Script)

    unlocking script for the underlying condition

Returns:



322
323
324
# File 'lib/bsv/script/script.rb', line 322

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.

Parameters:

  • hash_value (String)

    the R-value or hash of R-value to lock against

  • hash_type (Symbol) (defaults to: :hash160)

    one of :raw, :sha1, :ripemd160, :sha256, :hash160, :hash256

Returns:

Raises:

  • (ArgumentError)

    if hash_type is invalid



358
359
360
361
362
363
364
365
366
367
# File 'lib/bsv/script/script.rb', line 358

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.

Parameters:

  • signature_der (String)

    DER-encoded signature with sighash byte

  • pubkey_bytes (String)

    compressed or uncompressed public key bytes

Returns:



376
377
378
# File 'lib/bsv/script/script.rb', line 376

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.

Parameters:

  • other (Object)

    the object to compare

Returns:

  • (Boolean)

    true if both scripts have identical bytes



701
702
703
# File 'lib/bsv/script/script.rb', line 701

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.

Parameters:

  • network (Symbol) (defaults to: :mainnet)

    :mainnet or :testnet

Returns:

  • (Array<String>)

    array of derived addresses (empty if unsupported type)



679
680
681
682
683
684
685
686
# File 'lib/bsv/script/script.rb', line 679

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

#chunksArray<Chunk>

Parse the script into an array of Chunk objects.

Results are cached after first parse.

Returns:

  • (Array<Chunk>)

    the parsed chunks



695
696
697
# File 'lib/bsv/script/script.rb', line 695

def chunks
  @chunks ||= parse_chunks
end

#lengthInteger

Returns script length in bytes.

Returns:

  • (Integer)

    script length in bytes



428
429
430
# File 'lib/bsv/script/script.rb', line 428

def length
  @bytes.bytesize
end

#multisig?Boolean

Whether this is a bare multisig script.

Pattern: OP_M <pubkey1> … <pubkeyN> OP_N OP_CHECKMULTISIG

Returns:

  • (Boolean)


551
552
553
554
555
556
557
558
# File 'lib/bsv/script/script.rb', line 551

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

Returns:

  • (Boolean)


538
539
540
541
542
543
544
# File 'lib/bsv/script/script.rb', line 538

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.

Returns:

  • (Boolean)


468
469
470
471
472
# File 'lib/bsv/script/script.rb', line 468

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_dataArray<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).

Returns:

  • (Array<String>, nil)

    array of data pushes, or nil if not OP_RETURN



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/bsv/script/script.rb', line 609

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

Returns:

  • (Boolean)


479
480
481
482
483
484
485
486
487
# File 'lib/bsv/script/script.rb', line 479

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

Returns:

  • (Boolean)


439
440
441
442
443
444
445
446
447
# File 'lib/bsv/script/script.rb', line 439

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

Returns:

  • (Boolean)


455
456
457
458
459
460
461
# File 'lib/bsv/script/script.rb', line 455

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_hashString?

Extract the 20-byte public key hash from a P2PKH script.

Returns:

  • (String, nil)

    the pubkey hash, or nil if not P2PKH



586
587
588
589
590
# File 'lib/bsv/script/script.rb', line 586

def pubkey_hash
  return unless p2pkh?

  @bytes.byteslice(3, 20)
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…]

Returns:

  • (Boolean)


497
498
499
# File 'lib/bsv/script/script.rb', line 497

def pushdrop?
  pushdrop_layout ? true : false
end

#pushdrop_fieldsArray<String>?

Extract the embedded data fields from a PushDrop script.

Works for both :before and :after lock positions.

Returns:

  • (Array<String>, nil)

    array of field data, or nil if not PushDrop



654
655
656
657
658
659
# File 'lib/bsv/script/script.rb', line 654

def pushdrop_fields
  layout = pushdrop_layout
  return unless layout

  layout[:field_chunks].map { |ch| decode_minimal_push(ch) }
end

#pushdrop_lock_scriptScript?

Extract the underlying lock script from a PushDrop script.

Works for both :before and :after lock positions.

Returns:

  • (Script, nil)

    the lock script portion, or nil if not PushDrop



666
667
668
669
670
671
# File 'lib/bsv/script/script.rb', line 666

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.

Returns:

  • (Boolean)


507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/bsv/script/script.rb', line 507

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_hashString?

Extract the hash value from an RPuzzle script.

Returns:

  • (String, nil)

    the locked hash/R-value, or nil if not RPuzzle



634
635
636
637
638
# File 'lib/bsv/script/script.rb', line 634

def rpuzzle_hash
  return unless rpuzzle?

  chunks[-3].data
end

#rpuzzle_hash_typeSymbol?

Detect the hash type used in an RPuzzle script.

Returns:

  • (Symbol, nil)

    the hash type (e.g. :hash160, :raw), or nil if not RPuzzle



643
644
645
646
647
# File 'lib/bsv/script/script.rb', line 643

def rpuzzle_hash_type
  return unless rpuzzle?

  chunks.length == 12 ? :raw : RPUZZLE_OP_TO_TYPE[chunks[9].opcode]
end

#script_hashString?

Extract the 20-byte script hash from a P2SH script.

Returns:

  • (String, nil)

    the script hash, or nil if not P2SH



595
596
597
598
599
# File 'lib/bsv/script/script.rb', line 595

def script_hash
  return unless p2sh?

  @bytes.byteslice(2, 20)
end

#to_asmString

Returns human-readable ASM representation.

Returns:

  • (String)

    human-readable ASM representation



423
424
425
# File 'lib/bsv/script/script.rb', line 423

def to_asm
  chunks.map(&:to_asm).join(' ')
end

#to_binaryString

Returns a copy of the raw script bytes.

Returns:

  • (String)

    a copy of the raw script bytes



413
414
415
# File 'lib/bsv/script/script.rb', line 413

def to_binary
  @bytes.dup
end

#to_hexString

Returns hex-encoded script.

Returns:

  • (String)

    hex-encoded script



418
419
420
# File 'lib/bsv/script/script.rb', line 418

def to_hex
  @bytes.unpack1('H*')
end

#typeString

Classify the script as a standard type.

Returns:

  • (String)

    one of “empty”, “pubkeyhash”, “pubkey”, “scripthash”, “nulldata”, “multisig”, “pushdrop”, “rpuzzle”, or “nonstandard”



567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/bsv/script/script.rb', line 567

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