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(); } }