Merge branch 'primitive_fixes' into 'development'

Misc Primitive Fixes

See merge request GeneralProtocols/xo/stack!18
This commit is contained in:
Jim Hamill
2025-01-15 03:05:54 +00:00
15 changed files with 119 additions and 109 deletions
+2
View File
@@ -41,6 +41,8 @@ function toPascalCase(str: string) {
}
function convertNavToPascalCase(nav: Array<any>) {
console.log(nav);
return nav.map((item) => {
const result = { ...item };
@@ -28,8 +28,8 @@ body {
}
.vp-doc h4 {
font-size: 14px;
opacity: 0.75;
font-size: 18px;
opacity: 1;
color: var(--primary);
padding-top: 3em;
border-top: 1px solid var(--vp-c-text-1);
+2
View File
@@ -19,12 +19,14 @@
"enumMembersFormat": "table",
"useCodeBlocks": true,
"sort": ["static-first"],
"groupOrder": ["Methods", "Properties", "*"],
"exclude": "**/*+(index|.spec|.e2e|.browser|.node|.types).ts",
"excludeInternal": true,
"excludeProtected": true,
"excludePrivate": true,
"excludeReferences": true,
"outputFileStrategy": "modules",
"hidePageTitle": true,
"hideGroupHeadings": true
/*
+3 -3
View File
@@ -29,7 +29,7 @@ export const DEFAULT_ADDRESS_PREFIXES = [
* Address Entity.
*/
export class Address {
public readonly addressContents: KnownAddressTypeContents;
protected readonly addressContents: KnownAddressTypeContents;
protected constructor(addressContents: KnownAddressTypeContents) {
this.addressContents = addressContents;
@@ -102,7 +102,7 @@ export class Address {
}
// Return new instance of address.
return new Address({ payload: decodeResult.payload, type: encodeType });
return new this({ payload: decodeResult.payload, type: encodeType });
}
/**
@@ -140,7 +140,7 @@ export class Address {
throw new Error(`Unsupported address type: ${decodeResult.version}`);
}
return new Address({ payload: decodeResult.payload, type: encodeType });
return new this({ payload: decodeResult.payload, type: encodeType });
}
/**
+5 -5
View File
@@ -47,8 +47,8 @@ export class BlockHeader {
*/
public static from(blockHeader: Uint8Array | string) {
return blockHeader instanceof Uint8Array
? BlockHeader.fromBytes(blockHeader)
: BlockHeader.fromHex(blockHeader);
? this.fromBytes(blockHeader)
: this.fromHex(blockHeader);
}
/**
@@ -85,7 +85,7 @@ export class BlockHeader {
throw new Error('The given string is not valid hexadecimal.');
}
return BlockHeader.fromBytes(hexToBin(hex));
return this.fromBytes(hexToBin(hex));
}
/**
@@ -97,7 +97,7 @@ export class BlockHeader {
* @returns A new BlockHeader instance
*/
static fromRaw(obj: BlockHeaderRaw): BlockHeader {
return new BlockHeader(
return new this(
obj.version,
obj.previousBlockHash,
obj.merkleRoot,
@@ -116,7 +116,7 @@ export class BlockHeader {
*/
public static isValid(blockHeader: Uint8Array | string): boolean {
try {
BlockHeader.from(blockHeader);
this.from(blockHeader);
} catch (_error) {
return false;
}
+1 -1
View File
@@ -16,7 +16,7 @@ import {
* Bytes Primitive.
*/
export class Bytes {
public readonly bytes: Uint8Array;
protected readonly bytes: Uint8Array;
/**
* Instantiate Bytes from a Uint8Array.
+1 -1
View File
@@ -24,7 +24,7 @@ import type {
*/
export class HDPrivateNode {
// The HD Private Node.
public readonly node: HdPrivateNodeValid;
protected readonly node: HdPrivateNodeValid;
/**
* Construct a new HD Private Node.
+1 -1
View File
@@ -17,7 +17,7 @@ import type {
*/
export class HDPublicNode {
// The HD Public Node.
public readonly node: HdPublicNodeValid;
protected readonly node: HdPublicNodeValid;
/**
* Construct a new HD Public Node.
+4 -7
View File
@@ -14,7 +14,7 @@ export type MnemonicRaw = {
* Supports 12, 15, 18, 21, and 24 word phrases with optional passphrase.
*/
export class Mnemonic {
public static VALID_WORD_COUNTS = new Set([12, 15, 18, 21, 24]);
protected static VALID_WORD_COUNTS = new Set([12, 15, 18, 21, 24]);
/**
* Instantiate Mnemonic from a phrase string.
@@ -40,12 +40,9 @@ export class Mnemonic {
phraseOrWords: string | Array<string>,
wordList: Array<string> = bip39WordListEnglish,
) {
// If this is a list of words...
if (Array.isArray(phraseOrWords)) {
return this.fromWords(phraseOrWords, wordList);
}
return this.fromPhrase(phraseOrWords, wordList);
return Array.isArray(phraseOrWords)
? this.fromWords(phraseOrWords, wordList)
: this.fromPhrase(phraseOrWords, wordList);
}
/**
+39 -30
View File
@@ -1,3 +1,4 @@
import { Bytes } from './bytes.js';
import { PublicKey } from './public-key.js';
import {
@@ -5,9 +6,6 @@ import {
decodePrivateKeyWif,
encodePrivateKeyWif,
generatePrivateKey,
hexToBin,
isHex,
utf8ToBin,
bigIntToCompactUint,
secp256k1,
sha256,
@@ -23,7 +21,7 @@ import type {
*/
export class PrivateKey {
// The raw bytes representing the Private Key.
public readonly bytes: Uint8Array;
protected readonly bytes: Uint8Array;
/**
* Construct a new Private Key.
@@ -54,14 +52,14 @@ export class PrivateKey {
*/
public static from(privateKey: Uint8Array | string) {
if (privateKey instanceof Uint8Array) {
return PrivateKey.fromBytes(privateKey);
return this.fromBytes(privateKey);
}
if (PrivateKey.isWIF(privateKey)) {
return PrivateKey.fromWIF(privateKey);
if (this.isWIF(privateKey)) {
return this.fromWIF(privateKey);
}
return PrivateKey.fromHex(privateKey);
return this.fromHex(privateKey);
}
/**
@@ -74,7 +72,7 @@ export class PrivateKey {
* @returns {PrivateKey} The created Private Key Entity.
*/
public static fromBytes(privateKeyBytes: Uint8Array): PrivateKey {
return new PrivateKey(privateKeyBytes);
return new this(privateKeyBytes);
}
/**
@@ -87,18 +85,11 @@ export class PrivateKey {
* @returns {PrivateKey} The created Private Key Entity.
*/
public static fromHex(privateKeyHex: string): PrivateKey {
// Ensure that the given string is hex.
if (!isHex(privateKeyHex)) {
throw new Error(
`The given string (${privateKeyHex}) is not valid hexadecimal.`,
);
}
// Convert the hex to binary (Uint8Array).
const privateKeyBin = hexToBin(privateKeyHex);
const privateKeyBin = Bytes.fromHex(privateKeyHex).toBytes();
// Create a Private Key Entity from the binary.
return new PrivateKey(privateKeyBin);
return new this(privateKeyBin);
}
/**
@@ -142,7 +133,7 @@ export class PrivateKey {
}
// Return a new Private Key entity.
return new PrivateKey(decodeResult.privateKey);
return new this(decodeResult.privateKey);
}
/**
@@ -166,7 +157,7 @@ export class PrivateKey {
public static generateRandom(): PrivateKey {
const bytes = generatePrivateKey();
return new PrivateKey(bytes);
return new this(bytes);
}
public static isWIF(wif: string, removePrefixes = ['bitcoincash:', 'wif:']) {
@@ -261,7 +252,7 @@ export class PrivateKey {
*/
public signMessageRecoverableCompact(message: Uint8Array | string) {
if (typeof message === 'string') {
message = utf8ToBin(message);
message = Bytes.fromUtf8(message).toBytes();
}
const messageHash = sha256.hash(message);
@@ -281,7 +272,7 @@ export class PrivateKey {
*/
public signMessageCompact(message: Uint8Array | string) {
if (typeof message === 'string') {
message = utf8ToBin(message);
message = Bytes.fromUtf8(message).toBytes();
}
const messageHash = sha256.hash(message);
@@ -301,7 +292,7 @@ export class PrivateKey {
*/
public signMessageSchnorr(message: Uint8Array | string) {
if (typeof message === 'string') {
message = utf8ToBin(message);
message = Bytes.fromUtf8(message).toBytes();
}
const messageHash = sha256.hash(message);
@@ -321,7 +312,7 @@ export class PrivateKey {
*/
public signMessageDER(message: Uint8Array | string) {
if (typeof message === 'string') {
message = utf8ToBin(message);
message = Bytes.fromUtf8(message).toBytes();
}
const messageHash = sha256.hash(message);
@@ -344,8 +335,10 @@ export class PrivateKey {
*/
public signMessageElectron(message: string): Uint8Array {
// Compile the message.
const magicBytes = utf8ToBin('\x18Bitcoin Signed Message:\n');
const messageBytes = utf8ToBin(message);
const magicBytes = Bytes.fromUtf8(
'\x18Bitcoin Signed Message:\n',
).toBytes();
const messageBytes = Bytes.fromUtf8(message).toBytes();
const messageLengthBytes = bigIntToCompactUint(BigInt(messageBytes.length)); // hexToBin(messageBytes.length.toString(16)); //
const messageFinal = new Uint8Array([
...magicBytes,
@@ -385,7 +378,11 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = hexToBin(messageHash);
messageHash = Bytes.fromHex(messageHash).toBytes();
}
if (messageHash.length !== 32) {
throw new Error('messageHash must be a hash of length 32 bytes');
}
// Attempt to sign the message.
@@ -416,7 +413,11 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = hexToBin(messageHash);
messageHash = Bytes.fromHex(messageHash).toBytes();
}
if (messageHash.length !== 32) {
throw new Error('messageHash must be a hash of length 32 bytes');
}
// Attempt to sign the message.
@@ -447,7 +448,11 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = hexToBin(messageHash);
messageHash = Bytes.fromHex(messageHash).toBytes();
}
if (messageHash.length !== 32) {
throw new Error('messageHash must be a hash of length 32 bytes');
}
// Attempt to sign the message.
@@ -478,7 +483,11 @@ export class PrivateKey {
// If a string was given, convert it from hex to bytes.
// TODO: Consider using Bytes class - it ensures valid hex.
if (typeof messageHash === 'string') {
messageHash = hexToBin(messageHash);
messageHash = Bytes.fromHex(messageHash).toBytes();
}
if (messageHash.length !== 32) {
throw new Error('messageHash must be a hash of length 32 bytes');
}
const signResult = secp256k1.signMessageHashDER(this.bytes, messageHash);
+5 -5
View File
@@ -15,7 +15,7 @@ import {
*/
export class PublicKey {
// The raw bytes representing the Public Key.
public readonly bytes: Uint8Array;
protected readonly bytes: Uint8Array;
/**
* Construct a new Public Key.
@@ -37,8 +37,8 @@ export class PublicKey {
*/
public static from(publicKey: Uint8Array | string) {
return publicKey instanceof Uint8Array
? PublicKey.fromBytes(publicKey)
: PublicKey.fromHex(publicKey);
? this.fromBytes(publicKey)
: this.fromHex(publicKey);
}
/**
@@ -56,7 +56,7 @@ export class PublicKey {
throw new Error(`Invalid Public Key.`);
}
return new PublicKey(publicKeyBytes);
return new this(publicKeyBytes);
}
/**
@@ -80,7 +80,7 @@ export class PublicKey {
const publicKey = hexToBin(publicKeyHex);
// Create a Public Key Entity from the binary.
return new PublicKey(publicKey);
return new this(publicKey);
}
/**
+6 -6
View File
@@ -4,7 +4,7 @@ export type BCHUnits = ['sats', 'bits', 'mBCH', 'BCH'];
* Satoshis Primitive
*/
export class Satoshis {
public readonly satoshis: bigint;
protected readonly satoshis: bigint;
/**
* Construct a new instance of Satoshis.
@@ -26,7 +26,7 @@ export class Satoshis {
* @returns {Satoshis} New instance of Satoshis.
*/
public static fromSats(satoshis: bigint | number): Satoshis {
return new Satoshis(satoshis);
return new this(satoshis);
}
/**
@@ -39,7 +39,7 @@ export class Satoshis {
* @returns {Satoshis} New instance of Satoshis.
*/
public static fromSatoshis(satoshis: bigint | number): Satoshis {
return new Satoshis(satoshis);
return new this(satoshis);
}
/**
@@ -52,7 +52,7 @@ export class Satoshis {
public static fromBits(bits: number): Satoshis {
const satoshis = Satoshis.roundToDigits(bits * 100, 0);
return new Satoshis(satoshis);
return new this(satoshis);
}
/**
@@ -65,7 +65,7 @@ export class Satoshis {
public static fromMBCH(mBCH: number): Satoshis {
const satoshis = Satoshis.roundToDigits(mBCH * 100_000, 0);
return new Satoshis(satoshis);
return new this(satoshis);
}
/**
@@ -78,7 +78,7 @@ export class Satoshis {
public static fromBCH(bch: number): Satoshis {
const satoshis = Satoshis.roundToDigits(bch * 100_000_000, 0);
return new Satoshis(satoshis);
return new this(satoshis);
}
/**
+6 -6
View File
@@ -10,7 +10,7 @@ import {
* Script Entity.
*/
export class Script {
public readonly bytes: Uint8Array;
protected readonly bytes: Uint8Array;
/**
* Construct a new Script
@@ -32,8 +32,8 @@ export class Script {
*/
public static from(script: Uint8Array | string) {
return script instanceof Uint8Array
? Script.fromBytes(script)
: Script.fromHex(script);
? this.fromBytes(script)
: this.fromHex(script);
}
/**
@@ -46,7 +46,7 @@ export class Script {
* @returns {Script} The created Script Entity.
*/
public static fromBytes(bytes: Uint8Array): Script {
return new Script(bytes);
return new this(bytes);
}
/**
@@ -73,7 +73,7 @@ export class Script {
}
// Return new Script Entity from the assembled bytecode.
return new Script(assembleResult.bytecode);
return new this(assembleResult.bytecode);
}
/**
@@ -95,7 +95,7 @@ export class Script {
const scriptBin = hexToBin(hex);
// Return new Script entity constructed from the binary.
return new Script(scriptBin);
return new this(scriptBin);
}
/**
+8 -8
View File
@@ -4,7 +4,7 @@ import { binToHex, hexToBin, hashTransaction, isHex } from '@bitauth/libauth';
* Transaction Hash Primitive.
*/
export class TransactionHash {
public readonly bytes: Uint8Array;
protected readonly bytes: Uint8Array;
/**
* Instantiates TransactionHash from a Transaction Hash's bytes.
@@ -26,8 +26,8 @@ export class TransactionHash {
*/
public static from(hash: Uint8Array | string) {
return hash instanceof Uint8Array
? TransactionHash.fromBytes(hash)
: TransactionHash.fromHex(hash);
? this.fromBytes(hash)
: this.fromHex(hash);
}
/**
@@ -47,7 +47,7 @@ export class TransactionHash {
);
}
return new TransactionHash(bytes);
return new this(bytes);
}
/**
@@ -80,8 +80,8 @@ export class TransactionHash {
*/
public static fromTransaction(transaction: Uint8Array | string) {
return transaction instanceof Uint8Array
? TransactionHash.fromTransactionBytes(transaction)
: TransactionHash.fromTransactionHex(transaction);
? this.fromTransactionBytes(transaction)
: this.fromTransactionHex(transaction);
}
/**
@@ -96,7 +96,7 @@ export class TransactionHash {
): TransactionHash {
const transactionHash = hashTransaction(transactionBytes);
return new TransactionHash(hexToBin(transactionHash));
return new this(hexToBin(transactionHash));
}
/**
@@ -115,7 +115,7 @@ export class TransactionHash {
const bytes = hexToBin(transactionHex);
return TransactionHash.fromTransactionBytes(bytes);
return this.fromTransactionBytes(bytes);
}
/**
+34 -34
View File
@@ -29,8 +29,8 @@ export class Transaction {
*/
public static from(transaction: Uint8Array | string) {
return transaction instanceof Uint8Array
? Transaction.fromBytes(transaction)
: Transaction.fromHex(transaction);
? this.fromBytes(transaction)
: this.fromHex(transaction);
}
/**
@@ -52,7 +52,7 @@ export class Transaction {
}
// Return a new instance of the Transaction Primitive created from the decode result.
return new Transaction(decodeResult);
return new this(decodeResult);
}
/**
@@ -74,7 +74,7 @@ export class Transaction {
const bytes = hexToBin(hex);
// Return the transaction constructed from our bytes.
return Transaction.fromBytes(bytes);
return this.fromBytes(bytes);
}
/**
@@ -99,7 +99,7 @@ export class Transaction {
*/
public static isValid(transaction: Uint8Array | string): boolean {
try {
Transaction.from(transaction);
this.from(transaction);
} catch (_error) {
return false;
}
@@ -159,35 +159,6 @@ export class Transaction {
return { ...this.transaction };
}
/**
* Checks whether this transaction is a Coinbase Transaction.
*
* @remarks UNTESTED
*
* @experimental
*
* @returns True if this is a Coinbase Transaction
*/
public isCoinbase(): boolean {
// A coinbase transaction MUST only have one input
if (this.getInputs().length !== 1) {
return false;
}
const input = this.getInputs()[0];
// Check if the outpoint transaction hash is all zeros (32 bytes of 0x00)
const isOutpointZero = input.outpointTransactionHash.every(
(byte) => byte === 0x00,
);
// Check if the outpoint index is 0xFFFFFFFF (or -1 in signed 32-bit integer representation)
const isOutpointIndexMax = input.outpointIndex === 0xffffffff;
// Both conditions must be true for a coinbase transaction
return isOutpointZero && isOutpointIndexMax;
}
/**
* Gets the version for this transaction.
*
@@ -231,4 +202,33 @@ export class Transaction {
public getLocktime() {
return this.transaction.locktime;
}
/**
* Checks whether this transaction is a Coinbase Transaction.
*
* @remarks UNTESTED
*
* @experimental
*
* @returns True if this is a Coinbase Transaction
*/
public isCoinbase(): boolean {
// A coinbase transaction MUST only have one input
if (this.getInputs().length !== 1) {
return false;
}
const input = this.getInputs()[0];
// Check if the outpoint transaction hash is all zeros (32 bytes of 0x00)
const isOutpointZero = input.outpointTransactionHash.every(
(byte) => byte === 0x00,
);
// Check if the outpoint index is 0xFFFFFFFF (or -1 in signed 32-bit integer representation)
const isOutpointIndexMax = input.outpointIndex === 0xffffffff;
// Both conditions must be true for a coinbase transaction
return isOutpointZero && isOutpointIndexMax;
}
}