249 lines
6.5 KiB
TypeScript
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();
|
|
}
|
|
}
|