diff --git a/src/lib/bitcoin/transaction.ts b/src/lib/bitcoin/transaction.ts index 0b1182a..ffd473f 100644 --- a/src/lib/bitcoin/transaction.ts +++ b/src/lib/bitcoin/transaction.ts @@ -17,18 +17,12 @@ * - Legacy signature scripts */ -import { Buffer } from 'buffer'; import { sha256 } from '@noble/hashes/sha256'; import { ripemd160 } from '@noble/hashes/ripemd160'; import * as secp256k1 from '@noble/secp256k1'; import { UTXO } from './client'; import { BitcoinNetworkConfig } from '../networks/types'; -// Ensure Buffer is available -if (typeof globalThis.Buffer === 'undefined') { - globalThis.Buffer = Buffer; -} - // ============================================================================ // Types // ============================================================================ diff --git a/src/lib/buffer-polyfill.ts b/src/lib/buffer-polyfill.ts new file mode 100644 index 0000000..884b222 --- /dev/null +++ b/src/lib/buffer-polyfill.ts @@ -0,0 +1,31 @@ +/** + * Buffer Polyfill Utility + * + * Provides runtime checks to ensure the Buffer polyfill is available. + * This prevents issues with module initialization order in browser environments. + */ + +import { Buffer } from 'buffer'; + +// Ensure Buffer is available globally on initial import +if (typeof globalThis.Buffer === 'undefined') { + globalThis.Buffer = Buffer; +} + +/** + * Runtime check to ensure Buffer is available + * This provides a safeguard against initialization order issues + * + * Note: While the module-level check (lines 11-13) should guarantee Buffer + * is available, this function provides an additional safety layer for edge cases + * where modules might be loaded in unexpected order or the module initialization + * is bypassed (e.g., dynamic imports, circular dependencies). + * + * @returns The Buffer constructor, guaranteed to be available + */ +export function ensureBuffer(): typeof Buffer { + if (typeof globalThis.Buffer === 'undefined') { + globalThis.Buffer = Buffer; + } + return globalThis.Buffer; +} diff --git a/src/lib/crypto/bitcoin.ts b/src/lib/crypto/bitcoin.ts index 6735cce..d62ce06 100644 --- a/src/lib/crypto/bitcoin.ts +++ b/src/lib/crypto/bitcoin.ts @@ -6,18 +6,16 @@ * BIP84 (native SegWit), and BIP141 (SegWit encoding). */ -import { Buffer } from 'buffer'; -// Polyfill Buffer for browser environment -if (typeof globalThis.Buffer === 'undefined') { - globalThis.Buffer = Buffer; -} - +import { ensureBuffer } from '../buffer-polyfill'; import { sha256 } from '@noble/hashes/sha256'; import { ripemd160 } from '@noble/hashes/ripemd160'; import * as secp256k1 from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac'; import { sha512 } from '@noble/hashes/sha512'; +// Ensure Buffer is available at module load time +const BufferImpl = ensureBuffer(); + // UTXO network parameters for address generation export const UTXO_NETWORKS = { // Bitcoin @@ -293,13 +291,13 @@ function deriveChild( // Add IL to parent key (mod n) const n = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'); - const parentBig = BigInt('0x' + Buffer.from(parentKey).toString('hex')); - const ILBig = BigInt('0x' + Buffer.from(IL).toString('hex')); + const parentBig = BigInt('0x' + BufferImpl.from(parentKey).toString('hex')); + const ILBig = BigInt('0x' + BufferImpl.from(IL).toString('hex')); const childKey = (parentBig + ILBig) % n; const keyHex = childKey.toString(16).padStart(64, '0'); return { - key: new Uint8Array(Buffer.from(keyHex, 'hex')), + key: new Uint8Array(BufferImpl.from(keyHex, 'hex')), chainCode: new Uint8Array(IR), // Return copy }; } finally { @@ -333,7 +331,7 @@ function isValidPrivateKey(key: Uint8Array): boolean { // Check if less than curve order n const n = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'); - const keyBig = BigInt('0x' + Buffer.from(key).toString('hex')); + const keyBig = BigInt('0x' + BufferImpl.from(key).toString('hex')); return keyBig < n; } @@ -564,7 +562,7 @@ function base58CheckEncode(payload: Uint8Array, version: number | number[]): str const bytes = new Uint8Array([...data, ...checksum]); // Convert to base58 - let num = BigInt('0x' + Buffer.from(bytes).toString('hex')); + let num = BigInt('0x' + BufferImpl.from(bytes).toString('hex')); let result = ''; while (num > 0n) { diff --git a/src/lib/crypto/evm.ts b/src/lib/crypto/evm.ts index 18c9a40..62860b4 100644 --- a/src/lib/crypto/evm.ts +++ b/src/lib/crypto/evm.ts @@ -5,17 +5,16 @@ * Uses BIP32/BIP44 with coin type 60 for Ethereum-compatible chains. */ -import { Buffer } from 'buffer'; +import { ensureBuffer } from '../buffer-polyfill'; import * as bip39 from 'bip39'; import * as secp256k1 from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac'; import { sha512 } from '@noble/hashes/sha512'; import { keccak_256 } from '@noble/hashes/sha3'; -// Polyfill Buffer for browser environment -if (typeof globalThis.Buffer === 'undefined') { - globalThis.Buffer = Buffer; -} +// Ensure Buffer is available at module load time +const BufferImpl = ensureBuffer(); + /** * EVM Key Pair */ @@ -45,7 +44,7 @@ function deriveChild( ): { key: Uint8Array; chainCode: Uint8Array } { // Curve order for secp256k1 const n = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'); - const parentBig = BigInt('0x' + Buffer.from(parentKey).toString('hex')); + const parentBig = BigInt('0x' + BufferImpl.from(parentKey).toString('hex')); let currentIndex = index; const intermediates: Uint8Array[] = []; @@ -84,7 +83,7 @@ function deriveChild( const IR = I.slice(32); intermediates.push(IL); - const ILBig = BigInt('0x' + Buffer.from(IL).toString('hex')); + const ILBig = BigInt('0x' + BufferImpl.from(IL).toString('hex')); // BIP32: if IL == 0 or IL >= n, discard this child and try next index if (ILBig === 0n || ILBig >= n) { @@ -103,7 +102,7 @@ function deriveChild( const keyHex = childKey.toString(16).padStart(64, '0'); return { - key: new Uint8Array(Buffer.from(keyHex, 'hex')), + key: new Uint8Array(BufferImpl.from(keyHex, 'hex')), chainCode: new Uint8Array(IR), }; } @@ -148,7 +147,7 @@ function isValidPrivateKey(key: Uint8Array): boolean { // Check if less than curve order n const n = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141'); - const keyBig = BigInt('0x' + Buffer.from(key).toString('hex')); + const keyBig = BigInt('0x' + BufferImpl.from(key).toString('hex')); return keyBig < n; } @@ -235,7 +234,7 @@ export async function deriveEvmKeyPairFromSeed( const addressBytes = addressHash.slice(-20); // Convert to checksummed address - const address = toChecksumAddress('0x' + Buffer.from(addressBytes).toString('hex')); + const address = toChecksumAddress('0x' + BufferImpl.from(addressBytes).toString('hex')); // Create copies for return (originals will be zeroed) const privateKeyCopy = key.slice() as Uint8Array; @@ -274,7 +273,7 @@ export async function deriveEvmKeyPair( */ export function toChecksumAddress(address: string): string { const addr = address.toLowerCase().replace('0x', ''); - const hash = Buffer.from(keccak_256(new TextEncoder().encode(addr))).toString('hex'); + const hash = BufferImpl.from(keccak_256(new TextEncoder().encode(addr))).toString('hex'); let checksumAddress = '0x'; for (let i = 0; i < addr.length; i++) { @@ -359,7 +358,7 @@ export function hasValidChecksum(address: string): boolean { * Get private key as hex string */ export function privateKeyToHex(privateKey: Uint8Array): string { - return '0x' + Buffer.from(privateKey).toString('hex'); + return '0x' + BufferImpl.from(privateKey).toString('hex'); } /** @@ -367,5 +366,5 @@ export function privateKeyToHex(privateKey: Uint8Array): string { */ export function hexToPrivateKey(hex: string): Uint8Array { const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex; - return new Uint8Array(Buffer.from(cleanHex, 'hex')); + return new Uint8Array(BufferImpl.from(cleanHex, 'hex')); } diff --git a/src/lib/crypto/keyring.ts b/src/lib/crypto/keyring.ts index 29c2bde..3032264 100644 --- a/src/lib/crypto/keyring.ts +++ b/src/lib/crypto/keyring.ts @@ -1,9 +1,4 @@ -import { Buffer } from 'buffer'; -// Polyfill Buffer for browser environment (needed by bip39) -if (typeof globalThis.Buffer === 'undefined') { - globalThis.Buffer = Buffer; -} - +import { ensureBuffer } from '../buffer-polyfill'; import { DirectSecp256k1HdWallet, makeCosmoshubPath } from '@cosmjs/proto-signing'; import { AminoSignResponse, @@ -29,6 +24,9 @@ import { import { deriveEvmKeyPair, getEvmDerivationPath } from './evm'; import { networkRegistry } from '@/lib/networks'; +// Ensure Buffer is available at module load time +const BufferImpl = ensureBuffer(); + export interface KeyringAccount { id: string; name: string; @@ -238,7 +236,7 @@ export class Keyring { signature: response.signature.signature, pub_key: { type: 'tendermint/PubKeySecp256k1', - value: Buffer.from(account.pubKey).toString('base64'), + value: BufferImpl.from(account.pubKey).toString('base64'), }, }; } @@ -254,15 +252,15 @@ export class Keyring { ): Promise { try { // Decode the signature and public key from base64 - const signatureBytes = Buffer.from(signature.signature, 'base64'); - const pubKeyBytes = Buffer.from(signature.pub_key.value, 'base64'); + const signatureBytes = BufferImpl.from(signature.signature, 'base64'); + const pubKeyBytes = BufferImpl.from(signature.pub_key.value, 'base64'); // Verify the public key matches the signer address // Derive address from public key and compare const account = this.accounts.find((acc) => acc.address === signerAddress); if (account) { // If we have the account, verify the pubkey matches - const storedPubKeyBase64 = Buffer.from(account.pubKey).toString('base64'); + const storedPubKeyBase64 = BufferImpl.from(account.pubKey).toString('base64'); if (storedPubKeyBase64 !== signature.pub_key.value) { return false; }