UNPKG

@scrow/tapscript

Version:

A development build of tapscript. Built for escrow.

154 lines (137 loc) 3.73 kB
import { Buff, Stream } from '@cmdcode/buff' import { get_asm_code } from './words.js' import { is_hex } from '../util.js' import { ScriptData, ScriptWord } from '../../types/index.js' const MAX_WORD_SIZE = 520 /** * Encode script asm instructions into bytes. */ export function encode_script ( script : ScriptData = [], varint = true ) : Buff { let buff = Buff.num(0) if (Array.isArray(script)) { buff = Buff.raw(encode_words(script)) } if (is_hex(script)) { buff = Buff.hex(script) } if (script instanceof Uint8Array) { buff = Buff.raw(script) } if (varint) { buff = buff.add_varint('le') } return buff } /** * Encode asm words into bytes. */ export function encode_words ( words : ScriptWord[] ) : Uint8Array { const bytes = [] for (const word of words) { bytes.push(format_word(word)) } return (bytes.length > 0) ? Buff.join(bytes) : new Uint8Array() } /** Check if the word is a valid opcode, * and return its integer value. */ export function format_word ( word : ScriptWord ) : Uint8Array { let buff = new Uint8Array() if (typeof (word) === 'string') { if (word.startsWith('OP_')) { // If word is an opcode, return a // number value without size prefix. return Buff.num(get_asm_code(word), 1) } else if (is_hex(word)) { // If word is valid hex, encode as hex. buff = Buff.hex(word) } else { // Else, encode word as UTF8 string. buff = Buff.str(word) } } else { // If not a string, encode as bytes. buff = Buff.bytes(word) } // If the buffer contains a single value: if (buff.length === 1) { // If value is between 1-16: if (buff[0] !== 0 && buff[0] <= 16) { // Number values 1-16 must be treated as opcodes. buff[0] += 0x50 // If the value is between 129-256: } else if (buff[0] > 128 && buff[0] <= 256) { // Number values 129-256 must have a padding byte. buff = new Uint8Array([ buff[0], 0 ]) } // If the buffer is larger than the max word size: } else if (buff.length > MAX_WORD_SIZE) { // Number values larger than max size must be split into chunks. let words : Buff[] // Split bytes into chunks, based on max word size. words = split_word(buff) // Prefix a varint length byte for each chunk. words = words.map(e => prefix_word(e)) // Concatenate the chunks buff = Buff.join(words) } else { // Else, return the word with a varint prefix. buff = prefix_word(buff) } // Return the final result. return buff } /** * Split a word into smaller chunks. */ export function split_word ( word : Uint8Array ) : Buff[] { const words = [] const buff = new Stream(word) while (buff.size > MAX_WORD_SIZE) { // Push a word chunk to the array. words.push(buff.read(MAX_WORD_SIZE)) } // Push the remainder to the array. words.push(buff.read(buff.size)) return words } /** * Prefix a word with its size, encoded as a varint. */ export function prefix_word ( word : Uint8Array ) { const varint = encode_size(word.length) return Buff.join([ varint, word ]) } /** * Return a varint that encodes a size value. */ export function encode_size (size : number) : Uint8Array { const OP_PUSHDATA1 = Buff.num(0x4c, 1) const OP_PUSHDATA2 = Buff.num(0x4d, 1) switch (true) { case (size <= 0x4b): return Buff.num(size) case (size > 0x4b && size < 0x100): return Buff.join([ OP_PUSHDATA1, Buff.num(size, 1, 'le') ]) case (size >= 0x100 && size <= MAX_WORD_SIZE): return Buff.join([ OP_PUSHDATA2, Buff.num(size, 2, 'le') ]) default: throw new Error('Invalid word size:' + size.toString()) } }