Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/lib/bitcoin/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ============================================================================
Expand Down
31 changes: 31 additions & 0 deletions src/lib/buffer-polyfill.ts
Original file line number Diff line number Diff line change
@@ -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;
}
20 changes: 9 additions & 11 deletions src/lib/crypto/bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down
25 changes: 12 additions & 13 deletions src/lib/crypto/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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) {
Expand All @@ -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),
};
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -359,13 +358,13 @@ 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');
}

/**
* Parse private key from hex 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'));
}
18 changes: 8 additions & 10 deletions src/lib/crypto/keyring.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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'),
},
};
}
Expand All @@ -254,15 +252,15 @@ export class Keyring {
): Promise<boolean> {
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;
}
Expand Down