package Bitcoin::Crypto::Script; our $VERSION = "1.005"; use v5.10; use strict; use warnings; use Moo; use Types::Standard qw(ArrayRef Str); use Crypt::Digest::SHA256 qw(sha256); use Bitcoin::Crypto::Base58 qw(encode_base58check); use Bitcoin::Crypto::Bech32 qw(encode_segwit); use Bitcoin::Crypto::Config; use Bitcoin::Crypto::Helpers qw(hash160 hash256 verify_bytestring); use Bitcoin::Crypto::Exception; use namespace::clean; with "Bitcoin::Crypto::Role::Network"; # list of significant opcodes our %op_codes = ( FALSE => { code => "\x00", }, PUSHDATA1 => { code => "\x4c", }, PUSHDATA2 => { code => "\x4d", }, PUSHDATA4 => { code => "\x4e", }, "1NEGATE" => { code => "\x4f", }, RESERVED => { code => "\x50", }, TRUE => { code => "\x51", }, NOP => { code => "\x61", }, VER => { code => "\x62", }, IF => { code => "\x63", }, NOTIF => { code => "\x64", }, VERIF => { code => "\x65", }, VERNOTIF => { code => "\x66", }, ELSE => { code => "\x67", }, ENDIF => { code => "\x68", }, VERIFY => { code => "\x69", }, RETURN => { code => "\x6a", }, TOALTSTACK => { code => "\x6b", }, FROMALTSTACK => { code => "\x6c", }, "2DROP" => { code => "\x6d", }, "2DUP" => { code => "\x6e", }, "3DUP" => { code => "\x6f", }, "2OVER" => { code => "\x70", }, "2ROT" => { code => "\x71", }, "2SWAP" => { code => "\x72", }, IFDUP => { code => "\x73", }, DEPTH => { code => "\x74", }, DROP => { code => "\x75", }, DUP => { code => "\x76", }, NIP => { code => "\x77", }, OVER => { code => "\x78", }, PICK => { code => "\x79", }, ROLL => { code => "\x7a", }, ROT => { code => "\x7b", }, SWAP => { code => "\x7c", }, TUCK => { code => "\x7d", }, SIZE => { code => "\x82", }, EQUAL => { code => "\x87", }, EQUALVERIFY => { code => "\x88", }, RESERVED1 => { code => "\x89", }, RESERVED2 => { code => "\x8a", }, "1ADD" => { code => "\x8b", }, "1SUB" => { code => "\x8c", }, NEGATE => { code => "\x8f", }, ABS => { code => "\x90", }, NOT => { code => "\x91", }, ONOTEQUAL => { code => "\x92", }, ADD => { code => "\x93", }, SUB => { code => "\x94", }, BOOLAND => { code => "\x9a", }, BOOLOR => { code => "\x9b", }, NUMEQUAL => { code => "\x9c", }, NUMEQUALVERIFY => { code => "\x9d", }, NUMNOTEQUAL => { code => "\x9e", }, LESSTHAN => { code => "\x9f", }, GREATERTHAN => { code => "\xa0", }, LESSTHANOREQUAL => { code => "\xa1", }, GREATERTHANOREQUAL => { code => "\xa2", }, MIN => { code => "\xa3", }, MAX => { code => "\xa4", }, WITHIN => { code => "\xa5", }, RIPEMD160 => { code => "\xa6", }, SHA1 => { code => "\xa7", }, SHA256 => { code => "\xa8", }, HASH160 => { code => "\xa9", }, HASH256 => { code => "\xaa", }, CODESEPARATOR => { code => "\xab", }, CHECKSIG => { code => "\xac", }, CHECKSIGVERIFY => { code => "\xad", }, CHECKMULTISIG => { code => "\xae", }, CHECKMULTISIGVERIFY => { code => "\xaf", }, CHECKLOCKTIMEVERFIY => { code => "\xb1", }, CHECKSEQUENCEVERIFY => { code => "\xb2", }, ); $op_codes{0} = $op_codes{FALSE}; $op_codes{1} = $op_codes{TRUE}; for (2 .. 16) { # OP_N - starts with 0x52, up to 0x60 $op_codes{$_} = { code => pack("C", 0x50 + $_), }; } has "operations" => ( is => "rw", isa => ArrayRef [Str], default => sub { [] }, ); sub _get_op_code { my ($context, $op_code) = @_; if ($op_code =~ /^OP_(.+)/) { $op_code = $1; return $op_codes{$op_code}{code}; } elsif ($op_code =~ /^[0-9]+$/ && $op_code >= 1 && $op_code <= 75) { # standard data push - 0x01 up to 0x4b return pack("C", 0x00 + $op_code); } else { Bitcoin::Crypto::Exception::ScriptOpcode->raise( defined $op_code ? "unknown opcode $op_code" : "undefined opcode variable" ); } } sub add_raw { my ($self, $bytes) = @_; verify_bytestring($bytes); push @{$self->operations}, $bytes; return $self; } sub add_operation { my ($self, $op_code) = @_; my $val = $self->_get_op_code($op_code); $self->add_raw($val); return $self; } sub push_bytes { my ($self, $bytes) = @_; verify_bytestring($bytes); my $len = length $bytes; Bitcoin::Crypto::Exception::ScriptPush->raise( "empty data variable" ) unless $len; if ($bytes =~ /[\x00-\x10]/ && $len == 1) { my $num = unpack "C", $bytes; $self->add_operation("OP_$num"); } else { use bigint; if ($len <= 75) { $self->add_operation($len); } elsif ($len < (2 << 7)) { $self->add_operation("OP_PUSHDATA1") ->add_raw(pack "C", $len); } elsif ($len < (2 << 15)) { $self->add_operation("OP_PUSHDATA2") ->add_raw(pack "S", $len); } elsif ($len < (2 << 31)) { $self->add_operation("OP_PUSHDATA4") ->add_raw(pack "L", $len); } else { Bitcoin::Crypto::Exception::ScriptPush->raise( "too much data to push onto stack in one operation" ); } $self->add_raw($bytes); } return $self; } sub get_script { my ($self) = @_; return join "", @{$self->operations}; } sub get_script_hash { my ($self) = @_; return hash160($self->get_script); } sub witness_program { my ($self) = @_; return pack("C", Bitcoin::Crypto::Config::witness_version) . sha256($self->get_script); } sub get_legacy_address { my ($self) = @_; return encode_base58check($self->network->p2sh_byte . $self->get_script_hash); } sub get_compat_address { my ($self) = @_; my $program = Bitcoin::Crypto::Script->new(network => $self->network); $program->add_operation("OP_" . Bitcoin::Crypto::Config::witness_version) ->push_bytes(sha256($self->get_script)); return $program->get_legacy_address; } sub get_segwit_address { my ($self) = @_; # network field is not required, lazy check for completeness Bitcoin::Crypto::Exception::NetworkConfig->raise( "no segwit_hrp found in network configuration" ) unless defined $self->network->segwit_hrp; return encode_segwit($self->network->segwit_hrp, $self->witness_program); } 1; __END__ =head1 NAME Bitcoin::Crypto::Script - Bitcoin script representations =head1 SYNOPSIS use Bitcoin::Crypto::Script; my $script = Bitcoin::Crypto::Script->new ->add_operation("OP_1") ->add_operation("OP_TRUE") ->add_operation("OP_EQUAL"); # getting serialized script my $serialized = $script->get_script(); # getting address from script (p2wsh) my $address = $script->get_segwit_adress(); =head1 DESCRIPTION This class allows you to create a bitcoin script representations You can use a script object to: =over 2 =item * create a script from opcodes =item * serialize script into byte string =item * create legacy (p2sh), compat (p2sh(p2wsh)) and segwit (p2wsh) adresses =back =head1 METHODS =head2 new $script_object = $class->new($data) A constructor. Returns new script instance =head2 add_operation $script_object = $object->add_operation($opcode) Adds a new opcode at the end of a script. Returns the object instance for chaining. Throws an exception for unknown opcodes. =head2 add_raw $script_object = $object->add_raw($bytes) Adds C<$bytes> at the end of a script. Useful when you need a value in a script that shouldn't be pushed to the execution stack, like the first four bytes after C<PUSHDATA4>. Returns the object instance for chaining. =head2 push_bytes $script_object = $object->push_bytes($bytes) Pushes C<$bytes> to the execution stack at the end of a script, using a minimal push opcode. For example, running C<$script->push_bytes("\x03")> will have the same effect as C<$script->add_operation("OP_3")>. Throws an exception for data exceeding a 4 byte number in length. Returns the object instance for chaining. =head2 get_script $bytestring = $object->get_script() Returns a serialized script as byte string. =head2 get_script_hash $bytestring = $object->get_script_hash() Returns a serialized script parsed with C<HASH160> (ripemd160 of sha256). =head2 set_network $script_object = $object->set_network($val) Change key's network state to C<$val>. It can be either network name present in L<Bitcoin::Crypto::Network> package or an instance of this class. Returns current object instance. =head2 get_legacy_address $address = $object->get_legacy_address() Returns string containing Base58Check encoded script hash (p2sh address) =head2 get_compat_address $address = $object->get_compat_address() Returns string containing Base58Check encoded script hash containing a witness program for compatibility purposes (p2sh(p2wsh) address) =head2 get_segwit_address $address = $object->get_segwit_address() Returns string containing Bech32 encoded witness program (p2wsh address) =head1 EXCEPTIONS This module throws an instance of L<Bitcoin::Crypto::Exception> if it encounters an error. It can produce the following error types from the L<Bitcoin::Crypto::Exception> namespace: =over 2 =item * ScriptOpcode - unknown opcode was specified =item * ScriptPush - data pushed to the execution stack is invalid =item * NetworkConfig - incomplete or corrupted network configuration =back =head1 SEE ALSO =over 2 =item L<Bitcoin::Crypto::PrivateKey> =item L<Bitcoin::Crypto::Network> =back =cut