Files
xo-stack/packages/primitives/src/block-header.ts
T
2025-07-23 00:27:41 +00:00

249 lines
6.5 KiB
TypeScript

import { Bytes } from './bytes.js';
import { hash256 } from '@bitauth/libauth';
/**
* Represents a Bitcoin Cash block header.
* This class handles parsing and serialization of BCH block headers between different formats
* (raw object, bytes, hex) and provides access to individual header fields.
*
* @example
*
* ```ts
* // Import our primitives.
* import { BlockHeader } from '@xocash/primitives';
*
* // Instantiate BlockHeader Primitive from Hex.
* const blockHeader = BlockHeader.from('blockHeaderHex');
*
* // Output Block Header information.
* console.log(blockHeader.getVersion());
* console.log(blockHeader.getTimestamp());
* console.log(blockHeader.getNonce());
* ```
*/
export class BlockHeader {
protected readonly bytes: Bytes;
// View of our data so that we can extract as numbers.
protected readonly view: DataView;
/**
* Creates a new BlockHeader instance.
* @param bytes The bytes representing our Block Header (Big Endian)
*/
protected constructor(bytes: Bytes | Uint8Array) {
this.bytes = Bytes.from(bytes);
this.view = new DataView(this.bytes.buffer);
}
/**
* Instantiates a blockheader from either:
*
* 1. Transaction bytecode
* 2. Transaction bytecode (in hexadecimal format)
*
* @param blockHeader The block header in one of the above formats.
*
* @throws {Error} If the block header cannot be decoded.
*
* @returns The created BlockHeader Primitive.
*/
public static from(blockHeader: Uint8Array | string) {
return blockHeader instanceof Uint8Array
? this.fromBytes(blockHeader)
: this.fromHex(blockHeader);
}
/**
* Creates a BlockHeader instance from a byte array.
* @param bytes - 80-byte array containing the serialized block header
* @returns A new BlockHeader instance
* @throws Error if the byte array length is not 80
*/
static fromBytes(bytes: Bytes | Uint8Array): BlockHeader {
if (bytes.length !== 80) {
throw new Error('Invalid block header length');
}
return new BlockHeader(bytes);
}
/**
* Creates a BlockHeader instance from a hex string.
* @param hex - 160-character hex string representing the block header
* @returns A new BlockHeader instance
* @throws Error if the hex string length is not 160
*/
static fromHex(hex: string): BlockHeader {
return this.fromBytes(Bytes.fromHex(hex).toUint8Array());
}
/**
* Creates a BlockHeader instance from a raw object representation.
*
* @remarks This method is UNSAFE and MUST only be used where input is already verified or trusted.
*
* @param obj - Object containing the block header fields
* @returns A new BlockHeader instance
*/
static fromRaw(bytes: Uint8Array): BlockHeader {
return new this(bytes);
}
static generateRandom(timestampUnix?: number): BlockHeader {
// Create buffer for the 80-byte header
const header = new Uint8Array(80);
// Fill with random bytes
crypto.getRandomValues(header);
// Optional: Set specific version if needed
// First 4 bytes is version (little-endian)
const version = 1;
header[0] = version & 0xff;
header[1] = (version >> 8) & 0xff;
header[2] = (version >> 16) & 0xff;
header[3] = (version >> 24) & 0xff;
// Optional: Set realistic timestamp (current time)
const timestamp = timestampUnix || Math.floor(Date.now() / 1000);
header[68] = timestamp & 0xff;
header[69] = (timestamp >> 8) & 0xff;
header[70] = (timestamp >> 16) & 0xff;
header[71] = (timestamp >> 24) & 0xff;
return this.fromBytes(header);
}
/**
* Checks whether the given BlochHeader is of a valid structure.
*
* @param blockHeader The blockheader as hex or a Uint8Array.
*
* @returns boolean indicating whether the blockheader is of a valid structure.
*/
public static isValid(blockHeader: Uint8Array | string): boolean {
try {
this.from(blockHeader);
} catch (_error) {
return false;
}
return true;
}
/**
* Serializes the block header to a byte array.
* @returns 80-byte array containing the serialized block header
*/
toBytes(): Bytes {
return this.bytes;
}
/**
* Converts the block header to a hex string.
*
* @returns 160-character hex string representing the block header
*/
toHex(): string {
return Bytes.fromBytes(this.bytes).toHex();
}
/**
* Converts the block header to a serializable raw object representation.
*
* @returns Object containing the block header fields
*/
toRaw() {
return this.bytes;
}
// Getters
getVersion(): number {
return this.view.getUint32(0, true);
}
getVersionBytes(): Uint8Array {
return this.bytes.slice(0, 4).toReversed();
}
getVersionHex(): string {
return Bytes.fromBytes(this.getVersionBytes()).toHex();
}
getPreviousBlockHash(): Uint8Array {
// NOTE: We want to return this as a Big Endian, so we reverse it.
return this.bytes.slice(4, 36).toReversed();
}
getMerkleRoot(): Uint8Array {
// NOTE: We want to return this as a Big Endian, so we reverse it.
return this.bytes.slice(36, 68).toReversed();
}
getTimestamp(): number {
return this.view.getUint32(68, true);
}
getTimestampDate(): Date {
return new Date(Math.round(this.getTimestamp() * 1000));
}
getBits(): number {
return this.view.getUint32(72, true);
}
getBitsBytes(): Uint8Array {
return this.bytes.slice(72, 76).toReversed();
}
getBitsHex(): string {
return Bytes.fromBytes(this.getBitsBytes()).toHex();
}
getNonce(): number {
return this.view.getUint32(76, true);
}
getNonceBytes(): Uint8Array {
return this.bytes.slice(76, 80).toReversed();
}
getNonceHex(): string {
return Bytes.fromBytes(this.getNonceBytes()).toHex();
}
/**
* Gets the previous block hash as a hex string.
* @returns 64-character hex string
*/
getPreviousBlockHashHex(): string {
return Bytes.fromBytes(this.getPreviousBlockHash()).toHex();
}
/**
* Gets the merkle root as a hex string.
* @returns 64-character hex string
*/
getMerkleRootHex(): string {
return Bytes.fromBytes(this.getMerkleRoot()).toHex();
}
/**
* Calculates the hash of this block header.
* @returns 32-byte array containing the block hash
*/
getHash(): Uint8Array {
return hash256(this.bytes).toReversed();
}
/**
* Gets the block hash as a hex string.
* @returns 64-character hex string
*/
getHashHex(): string {
return Bytes.fromBytes(this.getHash()).toHex();
}
}