UNPKG

bitcoin-tx-lib

Version:

A Typescript library for building and signing Bitcoin transactions

259 lines (178 loc) 6.66 kB
import { OP_CODES } from "../constants/opcodes" import { ripemd160 as ripemd160Noble } from "@noble/hashes/ripemd160" import { sha256 as sha256Noble } from "@noble/hashes/sha256" import { Hex } from "../types" export type Response = "hex" | "bytes" export function bytesToHex(bytes: Hex): string { if (bytes.length <= 0) throw new Error("The byte array is empty!") if(typeof(bytes) == "string") throw new Error("Expected the type Uint8Array") let hexValue: string = "" bytes.forEach(byte => { let hexNumber = byte.toString(16) if (hexNumber.length == 1) hexNumber = "0" + hexNumber hexValue += hexNumber }) return hexValue } export function hexToBytes(hex: string, hexadecimal: boolean = true): Uint8Array { if(hex.length <= 0) throw new Error("hex value is empty") if (hexadecimal && hex.length % 2 !== 0) throw new Error("Invalid hex value!") let bytes = new Uint8Array(hexadecimal ? hex.length / 2 : hex.length) for (let i = 0; i <= hex.length; i += hexadecimal ? 2 : 1) { if (hexadecimal) bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16) else bytes[i] = hex.charCodeAt(i) } return bytes; } export function sha256(messageHash: Hex, hash256: boolean = false): Hex { let data = messageHash if(typeof(messageHash) !== "object") data = hexToBytes(messageHash) let hash: Uint8Array = sha256Noble(data) // if is a hash256 return sha256(sha256(content)) (doc: https://en.bitcoin.it/wiki/BIP_0174) if (hash256) hash = sha256Noble(hash) if(typeof(messageHash) == "string") return bytesToHex(hash) return hash } export function hash256(message: Hex) : Hex { let data = message if(typeof(message) !== "object") data = hexToBytes(message) const hash = sha256Noble(sha256Noble(data)) if(typeof(message) == "string") return bytesToHex(hash) return hash } export function ripemd160(messageHash: Hex, address: boolean = false): Hex { let data = messageHash if(typeof(messageHash) !== "object") data = hexToBytes(messageHash) let hash = address ? sha256(data) : data hash = ripemd160Noble(hash) if(typeof(messageHash) == "string") return bytesToHex(hash) return hash } export function checksum(messageHash: Hex, bytes: number = 4): Hex { let data = messageHash if(typeof(messageHash) !== "object") data = hexToBytes(messageHash) // generate the hash256(sha256(content)) and return first 4 bytes (doc: https://en.bitcoin.it/wiki/BIP_0174) let hash = sha256Noble(data) hash = sha256Noble(hash).slice(0, bytes) //.substring(0, bytes * 2) if(typeof(messageHash) == "string") return bytesToHex(hash) return hash } export function reverseEndian(hex: Hex): Hex { if(typeof(hex) == "object") return hex.reverse() let hexLE = "" for (let i = hex.length; i > 0; i -= 2) hexLE += hex[i - 2] + hex[i - 1] return hexLE } export function numberToHex(number: number = 0, bits: number = 64, result: Response = "hex"): Hex { let hexValue = number.toString(16) // string hexadecimal if (hexValue.length == 1) hexValue = "0" + hexValue for (let i = hexValue.length; i < bits / 4; i++) { hexValue = "0" + hexValue } if(result == "hex") return hexValue return hexToBytes(hexValue) } // Convert a integer number in Uint8Array(16) // 64 bits little-endian export function numberToHexLE(number: number = 0, bits: number = 64, result: Response = "hex"): Hex { bits = bits < 8 ? 8 : bits let hexValue = number.toString(16) // string hexadecimal for (let i = hexValue.length; i < bits / 4; i++) hexValue = "0" + hexValue if(result == "hex") return reverseEndian(hexValue) return hexToBytes(hexValue).reverse() } export function hash160ToScript(hash160: Hex): Hex { let data = hash160 if(typeof(hash160) !== "object") data = hexToBytes(hash160) let hash160Length = data.length // 0x14 == 20 && 0x20 == 34 // OP_DUP+OP_HASH160+PK_HASH_LENGTH+PUBKEY_HASH+OP_EQUALVERIFY+OP_CHECKSIG let hexScript = mergeUint8Arrays(new Uint8Array([ OP_CODES.OP_DUP, OP_CODES.OP_HASH160, hash160Length ]), data as Uint8Array, new Uint8Array([ OP_CODES.OP_EQUALVERIFY, OP_CODES.OP_CHECKSIG ]) ) if(typeof(hash160) == "string") return bytesToHex(hexScript) return hexScript } export function reverseHexLE(hex: Hex, isBytes: boolean = true) : Hex { if (isBytes && hex.length <= 0) throw new Error("Invalid hex value!") if(typeof(hex) == "object") return hex.reverse() let hexLE = '' for (let i = hex.length; i > 0; i -= 2) hexLE += hex[i - 2] + hex[i - 1] // return hexadecimal bytes in little-endian return hexLE } export function mergeUint8Arrays(...arrays: Uint8Array[]): Uint8Array { let length = arrays.reduce((sum, e) => sum + e.length, 0) let mergeArray = new Uint8Array(length) arrays.forEach((array, index, arrays) => { let offset = arrays.slice(0, index).reduce((acc, e) => acc + e.length, 0) mergeArray.set(array, offset) }) return mergeArray } export function isEqual(...arrays: Uint8Array[]): boolean { let result: boolean = true arrays.forEach((arr, index, arrays) => { if(index < arrays.length - 1) { if(arr.toString() !== arrays[arrays.length - 1].toString()) result = false } }) return result } export function numberToVarTnt(value: number, resultType: Response = "hex"): Hex { let result: Uint8Array if (value < 0xfd) { result = new Uint8Array([value]) } else if (value <= 0xffff) { var number = numberToHexLE(value, 16, "bytes") as Uint8Array result = mergeUint8Arrays(new Uint8Array([0xfd]), number) } else if (value <= 0xffffffff) { let number = numberToHexLE(value, 32, "bytes") as Uint8Array result = mergeUint8Arrays(new Uint8Array([0xfe]), number) } else { let number = numberToHexLE(value, 64, "bytes") as Uint8Array result = mergeUint8Arrays(new Uint8Array([0xff]), number) } if(resultType == "hex") return bytesToHex(result) return result } export function getBytesCount(hex: string) : number { return hex.length / 2; }