From a51da6770f75daf39bc030ca95afcb98b6474f4e Mon Sep 17 00:00:00 2001 From: Jim Hamill Date: Wed, 15 Jan 2025 03:05:53 +0000 Subject: [PATCH] Misc Primitive Fixes --- packages/docs/src/.vitepress/config.ts | 2 + packages/docs/src/.vitepress/theme/custom.css | 4 +- packages/docs/typedoc.json | 2 + packages/primitives/src/address.ts | 6 +- packages/primitives/src/block-header.ts | 10 +-- packages/primitives/src/bytes.ts | 2 +- packages/primitives/src/hd-private-node.ts | 2 +- packages/primitives/src/hd-public-node.ts | 2 +- packages/primitives/src/mnemonic.ts | 11 ++- packages/primitives/src/private-key.ts | 69 +++++++++++-------- packages/primitives/src/public-key.ts | 10 +-- packages/primitives/src/satoshis.ts | 12 ++-- packages/primitives/src/script.ts | 12 ++-- packages/primitives/src/transaction-hash.ts | 16 ++--- packages/primitives/src/transaction.ts | 68 +++++++++--------- 15 files changed, 119 insertions(+), 109 deletions(-) diff --git a/packages/docs/src/.vitepress/config.ts b/packages/docs/src/.vitepress/config.ts index b090876..f0564d9 100644 --- a/packages/docs/src/.vitepress/config.ts +++ b/packages/docs/src/.vitepress/config.ts @@ -41,6 +41,8 @@ function toPascalCase(str: string) { } function convertNavToPascalCase(nav: Array) { + console.log(nav); + return nav.map((item) => { const result = { ...item }; diff --git a/packages/docs/src/.vitepress/theme/custom.css b/packages/docs/src/.vitepress/theme/custom.css index c659d16..1c0e2df 100644 --- a/packages/docs/src/.vitepress/theme/custom.css +++ b/packages/docs/src/.vitepress/theme/custom.css @@ -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); diff --git a/packages/docs/typedoc.json b/packages/docs/typedoc.json index 26a0b93..19b05c3 100644 --- a/packages/docs/typedoc.json +++ b/packages/docs/typedoc.json @@ -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 /* diff --git a/packages/primitives/src/address.ts b/packages/primitives/src/address.ts index 7b573a2..20a7154 100644 --- a/packages/primitives/src/address.ts +++ b/packages/primitives/src/address.ts @@ -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 }); } /** diff --git a/packages/primitives/src/block-header.ts b/packages/primitives/src/block-header.ts index 582174a..9b51873 100644 --- a/packages/primitives/src/block-header.ts +++ b/packages/primitives/src/block-header.ts @@ -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; } diff --git a/packages/primitives/src/bytes.ts b/packages/primitives/src/bytes.ts index 2366a65..f02fbce 100644 --- a/packages/primitives/src/bytes.ts +++ b/packages/primitives/src/bytes.ts @@ -16,7 +16,7 @@ import { * Bytes Primitive. */ export class Bytes { - public readonly bytes: Uint8Array; + protected readonly bytes: Uint8Array; /** * Instantiate Bytes from a Uint8Array. diff --git a/packages/primitives/src/hd-private-node.ts b/packages/primitives/src/hd-private-node.ts index e806ed3..8077e85 100644 --- a/packages/primitives/src/hd-private-node.ts +++ b/packages/primitives/src/hd-private-node.ts @@ -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. diff --git a/packages/primitives/src/hd-public-node.ts b/packages/primitives/src/hd-public-node.ts index baac5c6..753ef42 100644 --- a/packages/primitives/src/hd-public-node.ts +++ b/packages/primitives/src/hd-public-node.ts @@ -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. diff --git a/packages/primitives/src/mnemonic.ts b/packages/primitives/src/mnemonic.ts index a356bca..4003520 100644 --- a/packages/primitives/src/mnemonic.ts +++ b/packages/primitives/src/mnemonic.ts @@ -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, wordList: Array = 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); } /** diff --git a/packages/primitives/src/private-key.ts b/packages/primitives/src/private-key.ts index d48fbd1..8f0420a 100644 --- a/packages/primitives/src/private-key.ts +++ b/packages/primitives/src/private-key.ts @@ -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); diff --git a/packages/primitives/src/public-key.ts b/packages/primitives/src/public-key.ts index 706683f..99e1c2e 100644 --- a/packages/primitives/src/public-key.ts +++ b/packages/primitives/src/public-key.ts @@ -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); } /** diff --git a/packages/primitives/src/satoshis.ts b/packages/primitives/src/satoshis.ts index 6f626d6..793baf1 100644 --- a/packages/primitives/src/satoshis.ts +++ b/packages/primitives/src/satoshis.ts @@ -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); } /** diff --git a/packages/primitives/src/script.ts b/packages/primitives/src/script.ts index 996bcfb..0bd4b19 100644 --- a/packages/primitives/src/script.ts +++ b/packages/primitives/src/script.ts @@ -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); } /** diff --git a/packages/primitives/src/transaction-hash.ts b/packages/primitives/src/transaction-hash.ts index 3a715af..4dadf1c 100644 --- a/packages/primitives/src/transaction-hash.ts +++ b/packages/primitives/src/transaction-hash.ts @@ -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); } /** diff --git a/packages/primitives/src/transaction.ts b/packages/primitives/src/transaction.ts index 3c0f410..da37185 100644 --- a/packages/primitives/src/transaction.ts +++ b/packages/primitives/src/transaction.ts @@ -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; + } }