UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

1,069 lines (835 loc) 39.2 kB
import { describe, expect, it } from 'vitest'; import { Address, MLDSASecurityLevel, Mnemonic } from '../build/opnet.js'; import { networks, toHex } from '@btc-vision/bitcoin'; describe('Address - Comprehensive Tests', () => { const testMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; // Helper to get a valid address with both keys const getValidAddress = (securityLevel = MLDSASecurityLevel.LEVEL2) => { const mnemonic = new Mnemonic(testMnemonic, '', networks.bitcoin, securityLevel); const wallet = mnemonic.derive(0); return wallet.address; }; describe('Constructor', () => { it('should create an empty address with no parameters', () => { const address = new Address(); expect(address.length).toBe(32); // ADDRESS_BYTE_LENGTH }); it('should create an address with ML-DSA public key only (32 bytes)', () => { const mldsaHash = Buffer.alloc(32); mldsaHash.fill(0x01); const address = new Address(mldsaHash); expect(address.length).toBe(32); expect(address.toHex()).toBe('0x' + mldsaHash.toString('hex')); }); it('should create an address with ML-DSA public key (1312 bytes - LEVEL2)', () => { const mldsaPubKey = Buffer.alloc(1312); mldsaPubKey.fill(0x02); const address = new Address(mldsaPubKey); expect(address.length).toBe(32); expect(address.mldsaPublicKey).toBeDefined(); expect(address.mldsaPublicKey?.length).toBe(1312); }); it('should create an address with ML-DSA public key (1952 bytes - LEVEL3)', () => { const mldsaPubKey = Buffer.alloc(1952); mldsaPubKey.fill(0x03); const address = new Address(mldsaPubKey); expect(address.mldsaPublicKey?.length).toBe(1952); }); it('should create an address with ML-DSA public key (2592 bytes - LEVEL5)', () => { const mldsaPubKey = Buffer.alloc(2592); mldsaPubKey.fill(0x04); const address = new Address(mldsaPubKey); expect(address.mldsaPublicKey?.length).toBe(2592); }); it('should create an address with both ML-DSA and classical public key (compressed)', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); expect(wallet.address.mldsaPublicKey).toBeDefined(); expect(wallet.address.originalPublicKey).toBeDefined(); }); it('should create an address with ML-DSA hash and 32-byte classical key hash', () => { const mldsaHash = Buffer.alloc(32, 0x01); const classicHash = Buffer.alloc(32, 0x02); const address = new Address(mldsaHash, classicHash); expect(address.length).toBe(32); }); it('should create an address with Uint8Array inputs', () => { const mldsaHash = new Uint8Array(32); mldsaHash.fill(0x05); const address = new Address(mldsaHash); expect(address.toHex()).toContain('05050505'); }); }); describe('Static Methods - dead()', () => { it('should return a dead address', () => { const deadAddr = Address.dead(); expect(deadAddr).toBeInstanceOf(Address); expect(deadAddr.toHex()).toBe( '0x0000000000000000000000000000000000000000000000000000000000000000', ); }); it('should return same dead address instance properties', () => { const dead1 = Address.dead(); const dead2 = Address.dead(); expect(dead1.toHex()).toBe(dead2.toHex()); }); }); describe('Static Methods - fromString()', () => { it('should create address from 32-byte hex string', () => { const hex = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; const address = Address.fromString(hex); expect(address.toHex()).toBe('0x' + hex); }); it('should create address from hex string with 0x prefix', () => { const hex = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; const address = Address.fromString(hex); expect(address.toHex()).toBe(hex); }); it('should create address from hex string without 0x prefix', () => { const hex = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; const address = Address.fromString(hex); expect(address.toHex()).toBe('0x' + hex); }); it('should create address with classical public key', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const mldsaHex = wallet.quantumPublicKeyHex; const classicHex = wallet.toPublicKeyHex(); const address = Address.fromString(mldsaHex, classicHex); expect(address).toBeInstanceOf(Address); expect(address.originalPublicKey).toBeDefined(); }); it('should create address with classical public key with 0x prefix', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const mldsaHex = '0x' + wallet.quantumPublicKeyHex; const classicHex = '0x' + wallet.toPublicKeyHex(); const address = Address.fromString(mldsaHex, classicHex); expect(address).toBeInstanceOf(Address); }); it('should throw error for empty string', () => { expect(() => Address.fromString('')).toThrow('Invalid public key'); }); it('should throw error for invalid hex', () => { expect(() => Address.fromString('not_hex')).toThrow('hexadecimal format'); }); it('should throw error for invalid classical public key hex', () => { const mldsaHex = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; expect(() => Address.fromString(mldsaHex, 'not_hex')).toThrow('hexadecimal format'); }); }); describe('Static Methods - wrap()', () => { it('should wrap bytes into an Address', () => { const bytes = Buffer.alloc(32, 0x42); const address = Address.wrap(bytes); expect(address).toBeInstanceOf(Address); expect(address.toHex()).toContain('42424242'); }); it('should wrap Uint8Array into an Address', () => { const bytes = new Uint8Array(32); bytes.fill(0x77); const address = Address.wrap(bytes); expect(address.toHex()).toContain('77777777'); }); }); describe('Static Methods - uncompressedToCompressed()', () => { it('should compress an uncompressed public key (even y-coordinate)', () => { const uncompressed = Buffer.alloc(65); uncompressed[0] = 0x04; // Fill x-coordinate for (let i = 1; i <= 32; i++) uncompressed[i] = 0x01; // Fill y-coordinate with even last byte for (let i = 33; i < 65; i++) uncompressed[i] = 0x02; uncompressed[64] = 0x00; // Even const compressed = Address.uncompressedToCompressed(uncompressed); expect(compressed.length).toBe(33); expect(compressed[0]).toBe(0x02); // Even y-coordinate }); it('should compress an uncompressed public key (odd y-coordinate)', () => { const uncompressed = Buffer.alloc(65); uncompressed[0] = 0x04; // Fill x-coordinate for (let i = 1; i <= 32; i++) uncompressed[i] = 0x01; // Fill y-coordinate with odd last byte for (let i = 33; i < 65; i++) uncompressed[i] = 0x02; uncompressed[64] = 0x01; // Odd const compressed = Address.uncompressedToCompressed(uncompressed); expect(compressed.length).toBe(33); expect(compressed[0]).toBe(0x03); // Odd y-coordinate }); it('should compress from Uint8Array', () => { const uncompressed = new Uint8Array(65); uncompressed[0] = 0x04; for (let i = 1; i < 65; i++) uncompressed[i] = i % 256; const compressed = Address.uncompressedToCompressed(uncompressed); expect(compressed.length).toBe(33); expect(compressed[0] === 0x02 || compressed[0] === 0x03).toBe(true); }); }); describe('Getters', () => { it('should return undefined originalPublicKey for address without classical key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(address.originalPublicKey).toBeUndefined(); }); it('should return originalPublicKey when set', () => { const addr = getValidAddress(); expect(addr.originalPublicKey).toBeDefined(); expect(addr.originalPublicKey?.length).toBe(33); }); it('should return mldsaPublicKey when full key is provided', () => { const mldsaPubKey = Buffer.alloc(1312, 0xab); const address = new Address(mldsaPubKey); expect(address.mldsaPublicKey).toBeDefined(); expect(address.mldsaPublicKey?.length).toBe(1312); if (!address.mldsaPublicKey) { throw new Error('mldsaPublicKey is undefined'); } expect(Buffer.from(address.mldsaPublicKey).toString('hex')).toContain('abab'); }); it('should return undefined mldsaPublicKey when only hash is provided', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(address.mldsaPublicKey).toBeUndefined(); }); }); describe('Conversion Methods - toHex/toBuffer', () => { it('should convert to hex with 0x prefix', () => { const bytes = Buffer.alloc(32, 0xff); const address = new Address(bytes); const hex = address.toHex(); expect(hex).toMatch(/^0x[0-9a-f]{64}$/); expect(hex).toContain('ffffff'); }); it('should convert to buffer', () => { const bytes = Buffer.alloc(32, 0xaa); const address = new Address(bytes); const buffer = address.toBuffer(); expect(buffer).toBeInstanceOf(Uint8Array); expect(buffer.length).toBe(32); expect(toHex(buffer)).toContain('aaaa'); }); it('should toString return same as toHex', () => { const addr = getValidAddress(); expect(addr.toString()).toBe(addr.toHex()); }); it('should toJSON return same as toHex', () => { const addr = getValidAddress(); expect(addr.toJSON()).toBe(addr.toHex()); }); }); describe('Conversion Methods - tweaked', () => { it('should convert tweaked to hex', () => { const addr = getValidAddress(); const tweakedHex = addr.tweakedToHex(); expect(tweakedHex).toMatch(/^0x[0-9a-f]{64}$/); }); it('should throw error for tweakedToHex without classical key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.tweakedToHex()).toThrow('Legacy public key not set'); }); it('should convert tweaked public key to buffer', () => { const addr = getValidAddress(); const buffer = addr.tweakedPublicKeyToBuffer(); expect(buffer).toBeInstanceOf(Uint8Array); expect(buffer.length).toBe(32); }); it('should throw error for tweakedPublicKeyToBuffer without classical key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.tweakedPublicKeyToBuffer()).toThrow('Legacy public key not set'); }); it('should get toTweakedHybridPublicKeyHex', () => { const addr = getValidAddress(); const hybridHex = addr.toTweakedHybridPublicKeyHex(); expect(hybridHex).toMatch(/^0x[0-9a-f]+$/); }); it('should throw error for toTweakedHybridPublicKeyHex without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toTweakedHybridPublicKeyHex()).toThrow( 'Legacy public key not set', ); }); it('should get toTweakedHybridPublicKeyBuffer', () => { const addr = getValidAddress(); const buffer = addr.toTweakedHybridPublicKeyBuffer(); expect(buffer).toBeInstanceOf(Uint8Array); }); it('should throw error for toTweakedHybridPublicKeyBuffer without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toTweakedHybridPublicKeyBuffer()).toThrow( 'Legacy public key not set', ); }); }); describe('Conversion Methods - uncompressed and hybrid', () => { it('should get uncompressed hex', () => { const addr = getValidAddress(); const uncompressedHex = addr.toUncompressedHex(); expect(uncompressedHex).toMatch(/^0x[0-9a-f]{130}$/); // 65 bytes = 130 hex chars }); it('should throw error for toUncompressedHex without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toUncompressedHex()).toThrow('Legacy public key not set'); }); it('should get uncompressed buffer', () => { const addr = getValidAddress(); const buffer = addr.toUncompressedBuffer(); expect(buffer).toBeInstanceOf(Uint8Array); expect(buffer.length).toBe(65); }); it('should throw error for toUncompressedBuffer without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toUncompressedBuffer()).toThrow('Legacy public key not set'); }); it('should get hybrid public key hex', () => { const addr = getValidAddress(); const hybridHex = addr.toHybridPublicKeyHex(); expect(hybridHex).toMatch(/^0x[0-9a-f]+$/); }); it('should throw error for toHybridPublicKeyHex without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toHybridPublicKeyHex()).toThrow('Legacy public key not set'); }); it('should get hybrid public key buffer', () => { const addr = getValidAddress(); const buffer = addr.toHybridPublicKeyBuffer(); expect(buffer).toBeInstanceOf(Uint8Array); }); it('should throw error for toHybridPublicKeyBuffer without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toHybridPublicKeyBuffer()).toThrow('Legacy public key not set'); }); it('should get original public key buffer', () => { const addr = getValidAddress(); const buffer = addr.originalPublicKeyBuffer(); expect(buffer).toBeInstanceOf(Uint8Array); expect(buffer.length).toBe(33); }); it('should throw error for originalPublicKeyBuffer without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.originalPublicKeyBuffer()).toThrow('Legacy public key not set'); }); }); describe('Comparison Methods - equals', () => { it('should return true for equal addresses', () => { const bytes = Buffer.alloc(32, 0x42); const addr1 = new Address(bytes); const addr2 = new Address(bytes); expect(addr1.equals(addr2)).toBe(true); }); it('should return false for different addresses', () => { const bytes1 = Buffer.alloc(32, 0x01); const bytes2 = Buffer.alloc(32, 0x02); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.equals(addr2)).toBe(false); }); it('should return false for addresses with different content', () => { const bytes1 = Buffer.alloc(32, 0x11); const bytes2 = Buffer.alloc(32, 0x22); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.equals(addr2)).toBe(false); }); it('should return true when comparing same instance', () => { const addr = getValidAddress(); expect(addr.equals(addr)).toBe(true); }); }); describe('Comparison Methods - lessThan', () => { it('should return true when address is less than another', () => { const bytes1 = Buffer.alloc(32, 0x01); const bytes2 = Buffer.alloc(32, 0x02); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.lessThan(addr2)).toBe(true); }); it('should return false when address is greater than another', () => { const bytes1 = Buffer.alloc(32, 0x02); const bytes2 = Buffer.alloc(32, 0x01); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.lessThan(addr2)).toBe(false); }); it('should return false when addresses are equal', () => { const bytes = Buffer.alloc(32, 0x42); const addr1 = new Address(bytes); const addr2 = new Address(bytes); expect(addr1.lessThan(addr2)).toBe(false); }); it('should compare byte by byte correctly', () => { const bytes1 = Buffer.alloc(32, 0x01); const bytes2 = Buffer.alloc(32, 0x01); bytes1[31] = 0x01; // Last byte smaller bytes2[31] = 0x02; const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.lessThan(addr2)).toBe(true); }); }); describe('Comparison Methods - greaterThan', () => { it('should return true when address is greater than another', () => { const bytes1 = Buffer.alloc(32, 0x02); const bytes2 = Buffer.alloc(32, 0x01); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.greaterThan(addr2)).toBe(true); }); it('should return false when address is less than another', () => { const bytes1 = Buffer.alloc(32, 0x01); const bytes2 = Buffer.alloc(32, 0x02); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.greaterThan(addr2)).toBe(false); }); it('should return false when addresses are equal', () => { const bytes = Buffer.alloc(32, 0x42); const addr1 = new Address(bytes); const addr2 = new Address(bytes); expect(addr1.greaterThan(addr2)).toBe(false); }); it('should compare byte by byte correctly', () => { const bytes1 = Buffer.alloc(32, 0x01); const bytes2 = Buffer.alloc(32, 0x01); bytes1[0] = 0x02; // First byte greater bytes2[0] = 0x01; const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); expect(addr1.greaterThan(addr2)).toBe(true); }); }); describe('Set Method - ML-DSA validation', () => { it('should set 32-byte ML-DSA hash', () => { const address = new Address(); const hash = Buffer.alloc(32, 0x77); address.set(hash); expect(address.toHex()).toContain('77777777'); }); it('should set 1312-byte ML-DSA public key (LEVEL2)', () => { const address = new Address(); const mldsaPubKey = Buffer.alloc(1312, 0xaa); address.set(mldsaPubKey); expect(address.mldsaPublicKey?.length).toBe(1312); }); it('should set 1952-byte ML-DSA public key (LEVEL3)', () => { const address = new Address(); const mldsaPubKey = Buffer.alloc(1952, 0xbb); address.set(mldsaPubKey); expect(address.mldsaPublicKey?.length).toBe(1952); }); it('should set 2592-byte ML-DSA public key (LEVEL5)', () => { const address = new Address(); const mldsaPubKey = Buffer.alloc(2592, 0xcc); address.set(mldsaPubKey); expect(address.mldsaPublicKey?.length).toBe(2592); }); it('should throw error for invalid ML-DSA public key length', () => { const address = new Address(); const invalidPubKey = Buffer.alloc(1000); expect(() => address.set(invalidPubKey)).toThrow('Invalid ML-DSA public key length'); }); it('should throw error for invalid classical public key length', () => { const mldsaHash = Buffer.alloc(32, 0x01); const invalidClassicKey = Buffer.alloc(20); // Invalid length expect(() => new Address(mldsaHash, invalidClassicKey)).toThrow( 'Invalid public key length', ); }); }); describe('Address Generation - p2pk', () => { it('should return p2pk address (hex)', () => { const addr = getValidAddress(); const p2pk = addr.p2pk(); expect(p2pk).toBe(addr.toHex()); }); }); describe('Address Generation - p2wpkh', () => { it('should generate p2wpkh address for mainnet', () => { const addr = getValidAddress(); const p2wpkh = addr.p2wpkh(networks.bitcoin); expect(p2wpkh).toMatch(/^bc1q/); }); it('should generate p2wpkh address for testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2wpkh = wallet.address.p2wpkh(networks.testnet); expect(p2wpkh).toMatch(/^tb1q/); }); it('should generate p2wpkh address for regtest', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.regtest, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2wpkh = wallet.address.p2wpkh(networks.regtest); expect(p2wpkh).toMatch(/^bcrt1q/); }); it('should throw error for p2wpkh without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.p2wpkh(networks.bitcoin)).toThrow('Legacy public key not set'); }); }); describe('Address Generation - p2pkh', () => { it('should generate p2pkh address for mainnet', () => { const addr = getValidAddress(); const p2pkh = addr.p2pkh(networks.bitcoin); expect(p2pkh).toMatch(/^1/); }); it('should generate p2pkh address for testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2pkh = wallet.address.p2pkh(networks.testnet); expect(p2pkh).toMatch(/^[mn]/); }); it('should throw error for p2pkh without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.p2pkh(networks.bitcoin)).toThrow( 'Legacy public key not set for address', ); }); }); describe('Address Generation - p2shp2wpkh', () => { it('should generate p2shp2wpkh address for mainnet', () => { const addr = getValidAddress(); const p2sh = addr.p2shp2wpkh(networks.bitcoin); expect(p2sh).toMatch(/^3/); }); it('should generate p2shp2wpkh address for testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2sh = wallet.address.p2shp2wpkh(networks.testnet); expect(p2sh).toMatch(/^2/); }); it('should throw error for p2shp2wpkh without key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.p2shp2wpkh(networks.bitcoin)).toThrow( 'Legacy public key not set for address', ); }); }); describe('Address Generation - p2tr', () => { it('should generate p2tr address for mainnet', () => { const addr = getValidAddress(); const p2tr = addr.p2tr(networks.bitcoin); expect(p2tr).toMatch(/^bc1p/); }); it('should generate p2tr address for testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2tr = wallet.address.p2tr(networks.testnet); expect(p2tr).toMatch(/^tb1p/); }); it('should cache p2tr address for same network', () => { const addr = getValidAddress(); const p2tr1 = addr.p2tr(networks.bitcoin); const p2tr2 = addr.p2tr(networks.bitcoin); expect(p2tr1).toBe(p2tr2); }); it('should generate different p2tr for different networks', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); // Can't easily test cross-network without recreating, but we can verify it works const p2trMain = wallet.address.p2tr(networks.bitcoin); expect(p2trMain).toMatch(/^bc1p/); }); it('should throw error for p2tr without classical key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.p2tr(networks.bitcoin)).toThrow('Legacy public key not set'); }); }); describe('Address Generation - p2op', () => { it('should generate p2op address for mainnet', () => { const addr = getValidAddress(); const p2op = addr.p2op(networks.bitcoin); expect(p2op).toBeDefined(); expect(typeof p2op).toBe('string'); }); it('should generate p2op address for testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2op = wallet.address.p2op(networks.testnet); expect(p2op).toBeDefined(); expect(typeof p2op).toBe('string'); }); it('should cache p2op address for same network', () => { const addr = getValidAddress(); const p2op1 = addr.p2op(networks.bitcoin); const p2op2 = addr.p2op(networks.bitcoin); expect(p2op1).toBe(p2op2); }); it('should generate p2op for different security levels', () => { const addr2 = getValidAddress(MLDSASecurityLevel.LEVEL2); const addr3 = getValidAddress(MLDSASecurityLevel.LEVEL3); const addr5 = getValidAddress(MLDSASecurityLevel.LEVEL5); const p2op2 = addr2.p2op(networks.bitcoin); const p2op3 = addr3.p2op(networks.bitcoin); const p2op5 = addr5.p2op(networks.bitcoin); expect(p2op2).toBeDefined(); expect(p2op3).toBeDefined(); expect(p2op5).toBeDefined(); }); }); describe('Address Generation - p2wda', () => { it('should generate p2wda address', () => { const addr = getValidAddress(); const p2wda = addr.p2wda(networks.bitcoin); expect(p2wda.address).toMatch(/^bc1q/); expect(p2wda.witnessScript).toBeInstanceOf(Uint8Array); }); it('should cache p2wda for same network', () => { const addr = getValidAddress(); const p2wda1 = addr.p2wda(networks.bitcoin); const p2wda2 = addr.p2wda(networks.bitcoin); expect(p2wda1.address).toBe(p2wda2.address); }); it('should generate p2wda for testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const p2wda = wallet.address.p2wda(networks.testnet); expect(p2wda.address).toMatch(/^tb1q/); }); it('should throw error for p2wda without original public key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.p2wda(networks.bitcoin)).toThrow('Cannot create P2WDA address'); }); }); describe('Address Generation - toCSV (timelocked)', () => { it('should generate CSV address with valid duration (number)', () => { const addr = getValidAddress(); const csv = addr.toCSV(100, networks.bitcoin); expect(csv.address).toMatch(/^bc1q/); expect(csv.witnessScript).toBeInstanceOf(Uint8Array); }); it('should generate CSV address with valid duration (bigint)', () => { const addr = getValidAddress(); const csv = addr.toCSV(BigInt(1000), networks.bitcoin); expect(csv.address).toMatch(/^bc1q/); }); it('should generate CSV address with valid duration (string)', () => { const addr = getValidAddress(); const csv = addr.toCSV('500', networks.bitcoin); expect(csv.address).toMatch(/^bc1q/); }); it('should generate CSV address with minimum duration (1)', () => { const addr = getValidAddress(); const csv = addr.toCSV(1, networks.bitcoin); expect(csv.address).toMatch(/^bc1q/); }); it('should generate CSV address with maximum duration (65535)', () => { const addr = getValidAddress(); const csv = addr.toCSV(65535, networks.bitcoin); expect(csv.address).toMatch(/^bc1q/); }); it('should throw error for CSV duration less than 1', () => { const addr = getValidAddress(); expect(() => addr.toCSV(0, networks.bitcoin)).toThrow( 'CSV block number must be between 1 and 65535', ); }); it('should throw error for CSV duration greater than 65535', () => { const addr = getValidAddress(); expect(() => addr.toCSV(65536, networks.bitcoin)).toThrow( 'CSV block number must be between 1 and 65535', ); }); it('should throw error for negative CSV duration', () => { const addr = getValidAddress(); expect(() => addr.toCSV(-1, networks.bitcoin)).toThrow( 'CSV block number must be between 1 and 65535', ); }); it('should throw error for CSV without original public key', () => { const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash); expect(() => address.toCSV(100, networks.bitcoin)).toThrow('Cannot create CSV address'); }); it('should generate different CSV addresses for different durations', () => { const addr = getValidAddress(); const csv1 = addr.toCSV(100, networks.bitcoin); const csv2 = addr.toCSV(200, networks.bitcoin); expect(csv1.address).not.toBe(csv2.address); }); }); describe('isValidLegacyPublicKey Method', () => { it('should validate address on mainnet', () => { const addr = getValidAddress(); const isValidLegacyPublicKey = addr.isValidLegacyPublicKey(networks.bitcoin); expect(isValidLegacyPublicKey).toBe(true); }); it('should validate address on testnet', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.testnet, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); const isValidLegacyPublicKey = wallet.address.isValidLegacyPublicKey(networks.testnet); expect(isValidLegacyPublicKey).toBe(true); }); }); describe('Edge Cases and Complex Scenarios', () => { it('should handle multiple format conversions', () => { const addr = getValidAddress(); const hex = addr.toHex(); const buffer = addr.toBuffer(); const string = addr.toString(); const json = addr.toJSON(); expect(hex).toBe(string); expect(hex).toBe(json); expect(Buffer.from(buffer).toString('hex')).toBe(hex.slice(2)); }); it('should handle multiple address generations', () => { const addr = getValidAddress(); const p2wpkh = addr.p2wpkh(networks.bitcoin); const p2tr = addr.p2tr(networks.bitcoin); const p2op = addr.p2op(networks.bitcoin); expect(p2wpkh).toMatch(/^bc1q/); expect(p2tr).toMatch(/^bc1p/); expect(p2op).toBeDefined(); expect(typeof p2op).toBe('string'); }); it('should handle comparison chain', () => { const bytes1 = Buffer.alloc(32, 0x01); const bytes2 = Buffer.alloc(32, 0x02); const bytes3 = Buffer.alloc(32, 0x03); const addr1 = new Address(bytes1); const addr2 = new Address(bytes2); const addr3 = new Address(bytes3); expect(addr1.lessThan(addr2)).toBe(true); expect(addr2.lessThan(addr3)).toBe(true); expect(addr1.lessThan(addr3)).toBe(true); expect(addr3.greaterThan(addr2)).toBe(true); expect(addr2.greaterThan(addr1)).toBe(true); expect(addr3.greaterThan(addr1)).toBe(true); }); it('should handle all ML-DSA security levels', () => { const level2 = getValidAddress(MLDSASecurityLevel.LEVEL2); const level3 = getValidAddress(MLDSASecurityLevel.LEVEL3); const level5 = getValidAddress(MLDSASecurityLevel.LEVEL5); expect(level2.mldsaPublicKey?.length).toBe(1312); expect(level3.mldsaPublicKey?.length).toBe(1952); expect(level5.mldsaPublicKey?.length).toBe(2592); }); it('should handle address with 65-byte uncompressed classical key', () => { const mnemonic = new Mnemonic( testMnemonic, '', networks.bitcoin, MLDSASecurityLevel.LEVEL2, ); const wallet = mnemonic.derive(0); // Get the uncompressed public key from the wallet const uncompressedKey = wallet.address.toUncompressedBuffer(); const mldsaHash = Buffer.alloc(32, 0x01); const address = new Address(mldsaHash, uncompressedKey); expect(address.originalPublicKey).toBeDefined(); }); it('should handle dead address operations', () => { const dead = Address.dead(); expect(dead.toHex()).toContain('000000'); expect(dead.p2tr(networks.bitcoin)).toBeDefined(); expect(dead.p2op(networks.bitcoin)).toBeDefined(); }); it('should correctly order addresses', () => { const addresses: Address[] = []; for (let i = 0; i < 5; i++) { const bytes = Buffer.alloc(32, i); addresses.push(new Address(bytes)); } for (let i = 0; i < addresses.length - 1; i++) { expect((addresses[i] as Address).lessThan(addresses[i + 1] as Address)).toBe(true); expect((addresses[i + 1] as Address).greaterThan(addresses[i] as Address)).toBe( true, ); } }); }); describe('Network Caching', () => { it('should cache and reuse p2tr for same network', () => { const addr = getValidAddress(); const first = addr.p2tr(networks.bitcoin); const second = addr.p2tr(networks.bitcoin); expect(first).toBe(second); }); it('should cache and reuse p2op for same network', () => { const addr = getValidAddress(); const first = addr.p2op(networks.bitcoin); const second = addr.p2op(networks.bitcoin); expect(first).toBe(second); }); it('should cache and reuse p2wda for same network', () => { const addr = getValidAddress(); const first = addr.p2wda(networks.bitcoin); const second = addr.p2wda(networks.bitcoin); expect(first.address).toBe(second.address); }); }); });