import { binToHex , flattenBinArray , numberToBinUint16LE , numberToBinUint32LE , } from '../../format/format' ; import { createCompilerCommonSynchronous } from '../../template/compiler' ; import { AuthenticationProgramStateCommon } from '../vm-types' ; import { AuthenticationErrorBCH , OpcodesBCH } from './bch/bch' ; import { OpcodesBTC } from './btc/btc' ; import { AuthenticationInstruction , AuthenticationInstructionPush , ParsedAuthenticationInstruction , ParsedAuthenticationInstructionMalformed , ParsedAuthenticationInstructionPushMalformedLength , ParsedAuthenticationInstructions , } from './instruction-sets-types' ; param instruction export const authenticationInstructionIsMalformed = < Opcodes > ( instruction ) : instruction is ParsedAuthenticationInstructionMalformed < Opcodes > => 'malformed' in instruction ; param instruction export const authenticationInstructionsAreMalformed = < Opcodes > ( instructions ) : instructions is ParsedAuthenticationInstructionMalformed < Opcodes > [ ] => instructions . length > 0 && authenticationInstructionIsMalformed ( instructions [ instructions . length - 1 ] ) ; param instruction export const authenticationInstructionsAreNotMalformed = < Opcodes > ( instructions ) : instructions is AuthenticationInstruction < Opcodes > [ ] => ! authenticationInstructionsAreMalformed ( instructions ) ; enum CommonPushOpcodes { OP_0 = 0x00 , OP_PUSHDATA_1 = 0x4c , OP_PUSHDATA_2 = 0x4d , OP_PUSHDATA_4 = 0x4e } const uint8Bytes = 1 ; const uint16Bytes = 2 ; const uint32Bytes = 4 ; const readLittleEndianNumber = ( script , index , length ) => { const view = new DataView ( script . buffer , index , length ) ; const readAsLittleEndian = true ; return length === uint8Bytes ? view . getUint8 ( 0 ) : length === uint16Bytes ? view . getUint16 ( 0 , readAsLittleEndian ) : view . getUint32 ( 0 , readAsLittleEndian ) ; } ; param opcode export const lengthBytesForPushOpcode = ( opcode ) => opcode < CommonPushOpcodes . OP_PUSHDATA_1 ? 0 : opcode === CommonPushOpcodes . OP_PUSHDATA_1 ? uint8Bytes : opcode === CommonPushOpcodes . OP_PUSHDATA_2 ? uint16Bytes : uint32Bytes ; param script param index export const readAuthenticationInstruction = < Opcodes = number > ( script , index ) : { instruction : ParsedAuthenticationInstruction < Opcodes > ; nextIndex : number ; } => { const opcode = script [ index ] ; if ( opcode > CommonPushOpcodes . OP_PUSHDATA_4 ) { return { instruction : { opcode : ( opcode as unknown ) as Opcodes , } , nextIndex : index + 1 , } ; } const lengthBytes = lengthBytesForPushOpcode ( opcode ) ; if ( lengthBytes !== 0 && index + lengthBytes >= script . length ) { const sliceStart = index + 1 ; const sliceEnd = sliceStart + lengthBytes ; return { instruction : { expectedLengthBytes : lengthBytes , length : script . slice ( sliceStart , sliceEnd ) , malformed : true , opcode : ( opcode as unknown ) as Opcodes , } , nextIndex : sliceEnd , } ; } const dataBytes = lengthBytes === 0 ? opcode : readLittleEndianNumber ( script , index + 1 , lengthBytes ) ; const dataStart = index + 1 + lengthBytes ; const dataEnd = dataStart + dataBytes ; return { instruction : { data : script . slice ( dataStart , dataEnd ) , ... ( dataEnd > script . length ? { expectedDataBytes : dataEnd - dataStart , malformed : true , } : undefined ) , opcode : ( opcode as unknown ) as Opcodes , } , nextIndex : dataEnd , } ; } ; param script export const parseBytecode = < Opcodes = number > ( script ) => { const instructions = [ ] ; let i = 0 ; while ( i < script . length ) { const { instruction , nextIndex } = readAuthenticationInstruction < Opcodes > ( script , i ) ; i = nextIndex ; instructions . push ( instruction ) ; } return instructions ; } ; /**
 * OP_0 is the only single-word push. All other push instructions will
 * disassemble to multiple ASM words. (OP_1-OP_16 are handled like normal
 * operations.)
 */ const isMultiWordPush = ( opcode ) => opcode !== CommonPushOpcodes . OP_0 ; const formatAsmPushHex = ( data ) => data . length > 0 ? `0x${ binToHex ( data ) }` : '' ; const formatMissingBytesAsm = ( missing ) => `[missing ${ missing } byte${ missing === 1 ? '' : 's' }]` ; const hasMalformedLength = < Opcodes > ( instruction ) : instruction is ParsedAuthenticationInstructionPushMalformedLength < Opcodes > => 'length' in instruction ; const isPushData = ( pushOpcode ) => pushOpcode >= CommonPushOpcodes . OP_PUSHDATA_1 ; param opcodes param instruction export const disassembleParsedAuthenticationInstructionMalformed = < Opcodes = number > ( opcodes , instruction ) : string => `${ opcodes [ ( instruction . opcode as unknown ) as number ] } ${ hasMalformedLength ( instruction ) ? `${ formatAsmPushHex ( instruction . length ) }${ formatMissingBytesAsm ( instruction . expectedLengthBytes - instruction . length . length ) }` : `${ isPushData ( ( instruction . opcode as unknown ) as number ) ? `${ instruction . expectedDataBytes } ` : '' }${ formatAsmPushHex ( instruction . data ) }${ formatMissingBytesAsm ( instruction . expectedDataBytes - instruction . data . length ) }` }` ; param opcodes param instruction export const disassembleAuthenticationInstruction = < Opcodes = number > ( opcodes , instruction ) : string => `${ opcodes [ ( instruction . opcode as unknown ) as number ] }${ 'data' in instruction && isMultiWordPush ( ( instruction . opcode as unknown ) as number ) ? ` ${ isPushData ( ( instruction . opcode as unknown ) as number ) ? `${ instruction . data . length } ` : '' }${ formatAsmPushHex ( instruction . data ) }` : '' }` ; param script export const disassembleParsedAuthenticationInstruction = < Opcodes = number > ( opcodes , instruction ) : string => authenticationInstructionIsMalformed ( instruction ) ? disassembleParsedAuthenticationInstructionMalformed < Opcodes > ( opcodes , instruction ) : disassembleAuthenticationInstruction < Opcodes > ( opcodes , instruction ) ; param script export const disassembleParsedAuthenticationInstructions = < Opcodes = number > ( opcodes , instructions ) : string => instructions . map ( ( instruction ) => disassembleParsedAuthenticationInstruction < Opcodes > ( opcodes , instruction ) ) . join ( ' ' ) ; param opcodes param bytecode export const disassembleBytecode = < Opcode = number > ( opcodes , bytecode ) => disassembleParsedAuthenticationInstructions ( opcodes , parseBytecode < Opcode > ( bytecode ) ) ; param bytecode export const disassembleBytecodeBCH = ( bytecode ) => disassembleParsedAuthenticationInstructions ( OpcodesBCH , parseBytecode < OpcodesBCH > ( bytecode ) ) ; param bytecode export const disassembleBytecodeBTC = ( bytecode ) => disassembleParsedAuthenticationInstructions ( OpcodesBTC , parseBytecode < OpcodesBTC > ( bytecode ) ) ; param opcodes export const generateBytecodeMap = ( opcodes ) => Object . entries ( opcodes ) . filter < [ string , number ] > ( ( entry ) : entry is [ string , number ] => typeof entry [ 1 ] === 'number' ) . reduce < { [ opcode ] : Uint8Array ; } > ( ( identifiers , pair ) => ( { ... identifiers , [ pair [ 0 ] ] : Uint8Array . of ( pair [ 1 ] ) , } ) , { } ) ; param opcodes param disassembledBytecode export const assembleBytecode = < Opcodes extends number = OpcodesBCH , Errors = AuthenticationErrorBCH > ( opcodes , disassembledBytecode ) => { const environment = { opcodes , scripts : { asm : disassembledBytecode } , } ; return createCompilerCommonSynchronous < typeof environment , AuthenticationProgramStateCommon < Opcodes , Errors > , Opcodes , Errors > ( environment ) . generateBytecode ( 'asm' , { } ) ; } ; param disassembledBytecode export const assembleBytecodeBCH = ( disassembledBytecode ) => assembleBytecode ( generateBytecodeMap ( OpcodesBCH ) , disassembledBytecode ) ; param disassembledBytecode export const assembleBytecodeBTC = ( disassembledBytecode ) => assembleBytecode < OpcodesBTC > ( generateBytecodeMap ( OpcodesBTC ) , disassembledBytecode ) ; const getInstructionLengthBytes = < Opcodes > ( instruction ) => { const opcode = ( instruction . opcode as unknown ) as number ; const expectedLength = lengthBytesForPushOpcode ( opcode ) ; return expectedLength === uint8Bytes ? Uint8Array . of ( instruction . data . length ) : expectedLength === uint16Bytes ? numberToBinUint16LE ( instruction . data . length ) : numberToBinUint32LE ( instruction . data . length ) ; } ; param instruction export const serializeAuthenticationInstruction = < Opcodes = number > ( instruction ) => Uint8Array . from ( [ ( instruction . opcode as unknown ) as number , ... ( 'data' in instruction ? [ ... ( isPushData ( ( instruction . opcode as unknown ) as number ) ? getInstructionLengthBytes ( instruction ) : [ ] ) , ... instruction . data , ] : [ ] ) , ] ) ; param instruction export const serializeParsedAuthenticationInstructionMalformed = < Opcodes = number > ( instruction ) => { const opcode = ( instruction . opcode as unknown ) as number ; if ( hasMalformedLength ( instruction ) ) { return Uint8Array . from ( [ opcode , ... instruction . length ] ) ; } if ( isPushData ( opcode ) ) { return Uint8Array . from ( [ opcode , ... ( opcode === CommonPushOpcodes . OP_PUSHDATA_1 ? Uint8Array . of ( instruction . expectedDataBytes ) : opcode === CommonPushOpcodes . OP_PUSHDATA_2 ? numberToBinUint16LE ( instruction . expectedDataBytes ) : numberToBinUint32LE ( instruction . expectedDataBytes ) ) , ... instruction . data , ] ) ; } return Uint8Array . from ( [ opcode , ... instruction . data ] ) ; } ; param instruction export const serializeParsedAuthenticationInstruction = < Opcodes = number > ( instruction ) : Uint8Array => authenticationInstructionIsMalformed ( instruction ) ? serializeParsedAuthenticationInstructionMalformed ( instruction ) : serializeAuthenticationInstruction ( instruction ) ; param instructions export const serializeAuthenticationInstructions = < Opcodes = number > ( instructions ) => flattenBinArray ( instructions . map ( serializeAuthenticationInstruction ) ) ; param instructions export const serializeParsedAuthenticationInstructions = < Opcodes = number > ( instructions ) => flattenBinArray ( instructions . map ( serializeParsedAuthenticationInstruction ) ) ;