bitcoin-tx-lib
Version:
A Typescript library for building and signing Bitcoin transactions
259 lines (178 loc) • 6.66 kB
text/typescript
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;
}