import { OpcodesBCH , encodeDataPush , parseBytecode , serializeAuthenticationInstructions , AuthenticationInstructions , hexToBin , disassembleBytecodeBCH , flattenBinArray , } from '@bitauth/libauth' ; import { decodeInt , encodeInt } from './data' ; import OptimisationsEquivFile from './cashproof-optimisations' ; export const Op = OpcodesBCH ; export type Op = number ; export type OpOrData = Op | Uint8Array ; export type Script = OpOrData [ ] ; export function scriptToAsm ( script ) { return bytecodeToAsm ( scriptToBytecode ( script ) ) ; } export function asmToScript ( asm ) { return bytecodeToScript ( asmToBytecode ( asm ) ) ; } export function scriptToBytecode ( script ) { const instructions = script . map ( ( opOrData ) => { if ( typeof opOrData === 'number' ) { return { opcode : opOrData } ; } return parseBytecode ( encodeDataPush ( opOrData ) ) [ 0 ] ; } ) ; return serializeAuthenticationInstructions ( instructions ) ; } export function bytecodeToScript ( bytecode ) { const instructions = parseBytecode ( bytecode ) as AuthenticationInstructions ; const script = instructions . map ( ( instruction ) => ( 'data' in instruction ? instruction . data : instruction . opcode ) ) ; return script ; } export function asmToBytecode ( asm ) { asm = asm . replace ( /\s+/g , ' ' ) . trim ( ) ; const instructions = asm . split ( ' ' ) . map ( ( token ) => { if ( token . startsWith ( 'OP_' ) ) { return { opcode : Op [ token as keyof typeof Op ] } ; } return parseBytecode ( encodeDataPush ( hexToBin ( token ) ) ) [ 0 ] ; } ) ; return serializeAuthenticationInstructions ( instructions ) ; } export function bytecodeToAsm ( bytecode ) { let asm = disassembleBytecodeBCH ( bytecode ) ; asm = asm . replace ( /OP_PUSHBYTES_[^\s]+/g , '' ) ; asm = asm . replace ( /OP_PUSHDATA[^\s]+ [^\s]+/g , '' ) ; asm = asm . replace ( /(^|\s)0x/g , ' ' ) ; asm = asm . replace ( /\s+/g , ' ' ) . trim ( ) ; return asm ; } export function countOpcodes ( script ) { return script . filter ( ( opOrData ) => typeof opOrData === 'number' ) . filter ( ( op ) => op > Op . OP_16 ) . length ; } export function calculateBytesize ( script ) { return scriptToBytecode ( script ) . byteLength ; } export function encodeNullDataScript ( chunks ) { return flattenBinArray ( chunks . map ( ( chunk ) => { if ( typeof chunk === 'number' ) { return new Uint8Array ( [ chunk ] ) ; } const pushdataOpcode = getPushDataOpcode ( chunk ) ; return new Uint8Array ( [ ... pushdataOpcode , ... chunk ] ) ; } ) ) ; } function getPushDataOpcode ( data ) { const { byteLength } = data ; if ( byteLength === 0 ) return Uint8Array . from ( [ 0x4c , 0x00 ] ) ; if ( byteLength < 76 ) return Uint8Array . from ( [ byteLength ] ) ; if ( byteLength < 256 ) return Uint8Array . from ( [ 0x4c , byteLength ] ) ; throw Error ( 'Pushdata too large' ) ; } param script returns export function replaceBytecodeNop ( script ) { const index = script . findIndex ( ( op ) => op === Op . OP_NOP ) ; if ( index < 0 ) return script ; script . splice ( index , 1 ) ; let oldCut = script [ index ] ; if ( oldCut instanceof Uint8Array ) { oldCut = decodeInt ( oldCut ) ; } else if ( oldCut === Op . OP_0 ) { oldCut = 0 ; } else if ( oldCut >= Op . OP_1 && oldCut <= Op . OP_16 ) { oldCut -= 80 ; } else { return script ; } script [ index ] = encodeInt ( oldCut + 1 ) ; const bytecodeSize = calculateBytesize ( script ) ; if ( bytecodeSize > 252 ) { script [ index ] = encodeInt ( oldCut + 3 ) ; } return asmToScript ( scriptToAsm ( script ) ) ; } export function generateRedeemScript ( baseScript , encodedArgs ) { return replaceBytecodeNop ( [ ... encodedArgs , ... baseScript ] ) ; } export function optimiseBytecode ( script , runs = 1000 ) { const optimisations = OptimisationsEquivFile . split ( '\n' ) . map ( ( equiv ) => equiv . trim ( ) ) . filter ( ( equiv ) => ! equiv . startsWith ( '#' ) ) . join ( '' ) . split ( ';' ) . map ( ( equiv ) => equiv . trim ( ) ) . map ( ( equiv ) => equiv . split ( '<=>' ) . map ( ( part ) => part . trim ( ) ) ) . filter ( ( equiv ) => equiv . length === 2 ) ; for ( let i = 0 ; i < runs ; i += 1 ) { const oldScript = script ; script = replaceOps ( script , optimisations ) ; if ( scriptToAsm ( oldScript ) === scriptToAsm ( script ) ) break ; } return script ; } function replaceOps ( script , optimisations ) { let asm = scriptToAsm ( script ) ; optimisations . forEach ( ( [ pattern , replacement ] ) => { asm = asm . replace ( new RegExp ( pattern , 'g' ) , replacement ) ; } ) ; asm = asm . replace ( /OP_NOT OP_IF/g , 'OP_NOTIF' ) ; asm = asm . replace ( /OP_CHECKMULTISIG OP_VERIFY/g , 'OP_CHECKMULTISIGVERIFY' ) ; asm = asm . replace ( /OP_SWAP OP_AND/g , 'OP_AND' ) ; asm = asm . replace ( /OP_SWAP OP_OR/g , 'OP_OR' ) ; asm = asm . replace ( /OP_SWAP OP_XOR/g , 'OP_XOR' ) ; asm = asm . replace ( /OP_DUP OP_AND/g , '' ) ; asm = asm . replace ( /OP_DUP OP_OR/g , '' ) ; asm = asm . replace ( /\s+/g , ' ' ) . trim ( ) ; return asmToScript ( asm ) ; }