UNPKG

@vbyte/btc-dev

Version:

Batteries-included toolset for plebian bitcoin development

130 lines (116 loc) 3.53 kB
import { Buff, Stream } from '@vbyte/buff' import { get_asm_code, } from './words.js' // The maximum size of a word in bytes. const MAX_WORD_SIZE = 520 /** * Encode script asm instructions into a hex string. */ export function encode_script ( words : (string | number | Uint8Array)[], varint = false ) : Buff { if (words.length === 0) return Buff.num(0, 1) const bytes = [] for (const word of words) { bytes.push(encode_script_word(word)) } const buffer = Buff.join(bytes) return (varint) ? buffer.prepend(Buff.varint(buffer.length, 'le')) : buffer } /** Check if the word is a valid opcode, * and return its integer value. */ export function encode_script_word (word : string | number | Uint8Array) : Uint8Array { let buff : Buff // If word is a string: if (typeof (word) === 'string') { // If word is an opcode: if (word.startsWith('OP_')) { // Get the opcode number value. const asm_code = get_asm_code(word) // Return the opcode as a single byte. return Buff.num(asm_code, 1) // If word is valid hex: } else if (Buff.is_hex(word)) { // Encode as hex. buff = Buff.hex(word) } else { // Encode as UTF8 string. buff = Buff.str(word) } // If word is a number: } else if (typeof (word) === 'number') { // Encode the number value. buff = Buff.num(word) // If word is a Uint8Array: } else if (word instanceof Uint8Array) { // Encode as bytes. buff = new Buff(word) } else { // If word is not a string, number, or Uint8Array, throw an error. throw new Error('invalid word type:' + typeof (word)) } // Format and return the word based on its size. if (buff.length === 1 && buff[0] <= 16) { // Number values 0-16 must be treated as opcodes. if (buff[0] !== 0) buff[0] += 0x50 } 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_script_word(buff) // Prefix a varint length byte for each chunk. words = words.map(e => prefix_word_size(e)) // Concatenate the chunks buff = Buff.join(words) } else { // Else, return the word with a varint prefix. buff = prefix_word_size(buff) } // Return the final result. return buff } /** * Split a word into smaller chunks. */ export function split_script_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_size ( word : Uint8Array ) : Buff { const varint = get_size_varint(word.length) return Buff.join([ varint, word ]) } /** * Return a varint that encodes a size value. */ export function get_size_varint (size : number) : Buff { 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()) } }