import { bigToBufLE } from "@node-lightning/bufio" ; import { encodeVarInt } from "@node-lightning/bufio" ; import { BufferReader } from "@node-lightning/bufio" ; import { StreamReader } from "@node-lightning/bufio" ; import { hash160 , isDERSig , sha256 , validPublicKey } from "@node-lightning/crypto" ; import { Address } from "./Address" ; import { BitcoinError } from "./BitcoinError" ; import { BitcoinErrorCode } from "./BitcoinErrorCode" ; import { ICloneable } from "./ICloneable" ; import { Network } from "./Network" ; import { OpCode } from "./OpCodes" ; import { ScriptCmd } from "./ScriptCmd" ; import { isSigHashTypeValid } from "./SigHashType" ; import { Stack } from "./Stack" ; function asssertValidSig ( sig ) { const der = sig . slice ( 0 , sig . length - 1 ) ; const hashtype = sig [ sig . length - 1 ] ; if ( ! isDERSig ( der ) ) { throw new BitcoinError ( BitcoinErrorCode . SigEncodingInvalid ) ; } if ( ! isSigHashTypeValid ( hashtype ) ) { throw new BitcoinError ( BitcoinErrorCode . SigHashTypeInvalid , hashtype ) ; } } function assertValidPubKey ( pubkey ) { if ( ! validPublicKey ( pubkey ) ) { throw new BitcoinError ( BitcoinErrorCode . PubKeyInvalid ) ; } } /**
 * Bitcoin Script
 */ export class Script implements ICloneable < Script > { param val public static number ( val ) { if ( val === 0 || val === 0n ) { return OpCode . OP_0 ; } if ( val >= 1 && val <= 16 ) { return 0x50 + Number ( val ) ; } return Stack . encodeNum ( val ) ; } param pubkey public static p2pkLock ( pubkey ) { assertValidPubKey ( pubkey ) ; return new Script ( pubkey , OpCode . OP_CHECKSIG ) ; } param sig public static p2pkUnlock ( sig ) { asssertValidSig ( sig ) ; return new Script ( sig ) ; } param value public static p2pkhLock ( value ) { if ( value . length !== 20 ) { assertValidPubKey ( value ) ; } const hash160PubKey = value . length === 20 ? value : hash160 ( value ) ; return new Script ( OpCode . OP_DUP , OpCode . OP_HASH160 , hash160PubKey , OpCode . OP_EQUALVERIFY , OpCode . OP_CHECKSIG ) ; } param sig param pubkey public static p2pkhUnlock ( sig , pubkey ) { asssertValidSig ( sig ) ; assertValidPubKey ( pubkey ) ; return new Script ( sig , pubkey ) ; } /**
     * Creates a standard Pay-to-MultiSig scriptPubKey by accepting m of n
     * public keys as inputs in the format:
     *   OP_<m> <pubkey1> <pubkey2> <pubkey..m> OP_<n> OP_CHECKMULTISIG
     */ public static p2msLock ( m , ... pubkeys ) { for ( const pubkey of pubkeys ) { assertValidPubKey ( pubkey ) ; } if ( m < 1 || m > pubkeys . length || pubkeys . length === 0 || pubkeys . length > 20 ) { throw new BitcoinError ( BitcoinErrorCode . MultiSigSetupInvalid ) ; } return new Script ( Script . number ( m ) , ... pubkeys , Script . number ( pubkeys . length ) , OpCode . OP_CHECKMULTISIG ) ; } param pubkeys public static p2msUnlock ( ... sigs ) { for ( const sig of sigs ) { asssertValidSig ( sig ) ; } return new Script ( OpCode . OP_0 , ... sigs ) ; } param value public static p2shLock ( value ) { const scriptHash160 = value instanceof Script ? value . hash160 ( ) : value ; if ( scriptHash160 . length !== 20 ) { throw new BitcoinError ( BitcoinErrorCode . Hash160Invalid , { got : scriptHash160 . length , expected : 20 , } ) ; } return new Script ( OpCode . OP_HASH160 , scriptHash160 , OpCode . OP_EQUAL ) ; } param redeemScript param data public static p2shUnlock ( redeemScript , data ) ; param redeemScript param data public static p2shUnlock ( redeemScript , ... data ) ; public static p2shUnlock ( redeemScript , ... data ) { if ( data [ 0 ] instanceof Script ) { return new Script ( ... data [ 0 ] . cmds , redeemScript . serializeCmds ( ) ) ; } else { return new Script ( ... ( data as ScriptCmd [ ] ) , redeemScript . serializeCmds ( ) ) ; } } param value public static p2wpkhLock ( value ) { if ( value . length !== 20 ) { assertValidPubKey ( value ) ; } const hash160PubKey = value . length === 20 ? value : hash160 ( value ) ; return new Script ( OpCode . OP_0 , hash160PubKey ) ; } /**
     * Create a standard Pay-to-Witness-Script-Hash scriptPubKey by accepting
     * the sha256 of the witness script as input. It is of the format:
     *   OP_0 <sha256_redeem_script>
     */ public static p2wshLock ( value ) { const sha256Script = value instanceof Buffer ? value : sha256 ( value . serializeCmds ( ) ) ; if ( sha256Script . length !== 32 ) { throw new BitcoinError ( BitcoinErrorCode . Hash256Invalid ) ; } return new Script ( OpCode . OP_0 , sha256Script ) ; } param stream public static parse ( reader ) { const len = reader . readVarInt ( ) ; const buf = reader . readBytes ( Number ( len ) ) ; const cmds = Script . parseCmds ( buf ) ; return new Script ( ... cmds ) ; } param buf public static parseCmds ( buf ) { const br = new BufferReader ( buf ) ; const cmds = [ ] ; while ( ! br . eof ) { const op = br . readUInt8 ( ) ; if ( op >= 0x01 && op <= 0x4b ) { const n = op ; const bytes = br . readBytes ( n ) ; cmds . push ( bytes ) ; } else if ( op === OpCode . OP_PUSHDATA1 ) { const n = br . readUInt8 ( ) ; cmds . push ( br . readBytes ( n ) ) ; } else if ( op === OpCode . OP_PUSHDATA2 ) { const n = br . readUInt16LE ( ) ; cmds . push ( br . readBytes ( n ) ) ; } else { cmds . push ( op ) ; } } return cmds ; } /**
     * Commands belonging to the script
     */ public readonly cmds : ScriptCmd [ ] ; param cmds constructor ( ... cmds ) { this . cmds = cmds ; } param other public equals ( other ) { if ( this . cmds . length !== other . cmds . length ) return false ; for ( let i = 0 ; i < this . cmds . length ; i ++ ) { const l = this . cmds [ i ] ; const r = other . cmds [ i ] ; if ( Buffer . isBuffer ( l ) && Buffer . isBuffer ( r ) ) { if ( ! l . equals ( r ) ) return false ; } else { if ( l !== r ) return false ; } } return true ; } /**
     * Returns a string with the friendly name of the opcode. For data,
     * it returns the value in hexadecimal format.
     */ public toString ( ) { return this . cmds . map ( cmd => { if ( Buffer . isBuffer ( cmd ) ) { return cmd . toString ( "hex" ) ; } else { return OpCode [ cmd as OpCode ] ; } } ) . join ( " " ) ; } /**
     * Returns a JSON serialization of the Script.
     */ public toJSON ( ) { return this . toString ( ) ; } /**
     * Serializes the Script to a Buffer by serializing the cmds prefixed with
     * the overall length as a varint. Therefore the format of this method is
     * the format used when encoding a Script and is:
     *
     * [varint]: length
     * [length]: script_cmds
     */ public serialize ( ) { const cmdBuf = this . serializeCmds ( ) ; const len = encodeVarInt ( cmdBuf . length ) ; return Buffer . concat ( [ len , cmdBuf ] ) ; } /**
     * Serializes the commands to a buffer. This information is the raw
     * serialization and can be directly parsed with the `parseCmds` method.
     */ public serializeCmds ( ) { const results = [ ] ; for ( const op of this . cmds ) { if ( typeof op === "number" ) { const opBuf = Buffer . from ( [ op ] ) ; results . push ( opBuf ) ; } else if ( op instanceof Buffer ) { if ( op . length >= 1 && op . length <= 75 ) { results . push ( Buffer . from ( [ op . length ] ) ) ; results . push ( op ) ; } else if ( op . length >= 76 && op . length <= 255 ) { results . push ( Buffer . from ( [ OpCode . OP_PUSHDATA1 ] ) ) ; results . push ( Buffer . from ( [ op . length ] ) ) ; results . push ( op ) ; } else if ( op . length >= 256 && op . length <= 520 ) { results . push ( Buffer . from ( [ OpCode . OP_PUSHDATA2 ] ) ) ; results . push ( bigToBufLE ( BigInt ( op . length ) , 2 ) ) ; results . push ( op ) ; } else { throw new Error ( "Data too long" ) ; } } } return Buffer . concat ( results ) ; } /**
     * Clone via deep copy
     */ public clone ( ) { return new Script ( ... this . cmds . map ( cmd => { if ( Buffer . isBuffer ( cmd ) ) { return Buffer . from ( cmd ) ; } else { return cmd ; } } ) ) ; } /**
     * Performs a hash160 on the serialized commands. This is useful for
     * turning a script into a P2SH redeem script.
     */ public hash160 ( ) { return hash160 ( this . serializeCmds ( ) ) ; } /**
     * Performs a sha256 hash on the serialized commands. This is useful
     * fro turning a script into a P2WSH lock script.
     */ public sha256 ( ) { return sha256 ( this . serializeCmds ( ) ) ; } param network returns public toP2shAddress ( network ) { return Address . encodeBase58 ( network . p2shPrefix , this . hash160 ( ) ) ; } param network returns public toP2wshAddress ( network ) { return Address . encodeBech32 ( network . p2wshPrefix , 0x00 , this . sha256 ( ) ) ; } }