@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
498 lines (405 loc) • 17.8 kB
text/typescript
import { describe, expect, it } from 'vitest';
import { AddressTypes, AddressVerificator, MLDSASecurityLevel, Mnemonic } from '../build/opnet.js';
import { networks } from '@btc-vision/bitcoin';
describe('AddressVerificator ML-DSA Support', () => {
const testMnemonic =
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
describe('isValidMLDSAPublicKey', () => {
it('should validate ML-DSA-44 (Level 2) public key from hex string', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const publicKeyHex = wallet.quantumPublicKeyHex;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyHex);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL2);
});
it('should validate ML-DSA-44 (Level 2) public key from hex string with 0x prefix', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const publicKeyHex = '0x' + wallet.quantumPublicKeyHex;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyHex);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL2);
});
it('should validate ML-DSA-44 (Level 2) public key from Buffer', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const publicKeyBuffer = wallet.quantumPublicKey;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyBuffer);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL2);
});
it('should validate ML-DSA-44 (Level 2) public key from Uint8Array', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const publicKeyArray = new Uint8Array(wallet.quantumPublicKey);
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyArray);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL2);
});
it('should validate ML-DSA-65 (Level 3) public key', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL3,
);
const wallet = mnemonic.derive(0);
const publicKeyHex = wallet.quantumPublicKeyHex;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyHex);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL3);
});
it('should validate ML-DSA-65 (Level 3) public key from Buffer', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL3,
);
const wallet = mnemonic.derive(0);
const publicKeyBuffer = wallet.quantumPublicKey;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyBuffer);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL3);
});
it('should validate ML-DSA-87 (Level 5) public key', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL5,
);
const wallet = mnemonic.derive(0);
const publicKeyHex = wallet.quantumPublicKeyHex;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyHex);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL5);
});
it('should validate ML-DSA-87 (Level 5) public key from Buffer', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL5,
);
const wallet = mnemonic.derive(0);
const publicKeyBuffer = wallet.quantumPublicKey;
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(publicKeyBuffer);
expect(securityLevel).toBe(MLDSASecurityLevel.LEVEL5);
});
it('should reject invalid hex string', () => {
const invalidHex = 'not a valid hex string';
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(invalidHex);
expect(securityLevel).toBeNull();
});
it('should reject public key with wrong length', () => {
const wrongLength = 'a'.repeat(100);
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(wrongLength);
expect(securityLevel).toBeNull();
});
it('should reject empty string', () => {
const securityLevel = AddressVerificator.isValidMLDSAPublicKey('');
expect(securityLevel).toBeNull();
});
it('should reject empty Buffer', () => {
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(Buffer.alloc(0));
expect(securityLevel).toBeNull();
});
it('should reject classical public key (33 bytes)', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const classicalPublicKey = wallet.publicKey; // 33 bytes
const securityLevel = AddressVerificator.isValidMLDSAPublicKey(classicalPublicKey);
expect(securityLevel).toBeNull();
});
it('should verify all valid ML-DSA lengths', () => {
// Test all three security levels
const levels = [
MLDSASecurityLevel.LEVEL2,
MLDSASecurityLevel.LEVEL3,
MLDSASecurityLevel.LEVEL5,
];
for (const level of levels) {
const mnemonic = new Mnemonic(testMnemonic, '', networks.bitcoin, level);
const wallet = mnemonic.derive(0);
const securityLevelHex = AddressVerificator.isValidMLDSAPublicKey(
wallet.quantumPublicKeyHex,
);
const securityLevelBuffer = AddressVerificator.isValidMLDSAPublicKey(
wallet.quantumPublicKey,
);
expect(securityLevelHex).toBe(level);
expect(securityLevelBuffer).toBe(level);
}
});
});
describe('isValidP2OPAddress', () => {
it('should validate mainnet P2OP address', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
p2opAddress,
networks.bitcoin,
);
expect(isValidLegacyPublicKey).toBe(true);
});
it('should validate testnet P2OP address', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.testnet,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.testnet);
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
p2opAddress,
networks.testnet,
);
expect(isValidLegacyPublicKey).toBe(true);
});
it('should validate regtest P2OP address', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.regtest,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.regtest);
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
p2opAddress,
networks.regtest,
);
expect(isValidLegacyPublicKey).toBe(true);
});
it('should reject P2TR address as P2OP', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2trAddress = wallet.p2tr;
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
p2trAddress,
networks.bitcoin,
);
expect(isValidLegacyPublicKey).toBe(false);
});
it('should reject P2WPKH address as P2OP', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2wpkhAddress = wallet.p2wpkh;
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
p2wpkhAddress,
networks.bitcoin,
);
expect(isValidLegacyPublicKey).toBe(false);
});
it('should reject invalid address string', () => {
const invalidAddress = 'not a valid address';
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
invalidAddress,
networks.bitcoin,
);
expect(isValidLegacyPublicKey).toBe(false);
});
it('should reject empty string', () => {
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
'',
networks.bitcoin,
);
expect(isValidLegacyPublicKey).toBe(false);
});
it('should reject address on wrong network', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
// Try to validate mainnet address on testnet
const isValidLegacyPublicKey = AddressVerificator.isValidP2OPAddress(
p2opAddress,
networks.testnet,
);
expect(isValidLegacyPublicKey).toBe(false);
});
});
describe('detectAddressType - P2OP support', () => {
it('should detect mainnet P2OP address type', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
const addressType = AddressVerificator.detectAddressType(p2opAddress, networks.bitcoin);
expect(addressType).toBe(AddressTypes.P2OP);
});
it('should detect testnet P2OP address type', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.testnet,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.testnet);
const addressType = AddressVerificator.detectAddressType(p2opAddress, networks.testnet);
expect(addressType).toBe(AddressTypes.P2OP);
});
it('should detect regtest P2OP address type', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.regtest,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.regtest);
const addressType = AddressVerificator.detectAddressType(p2opAddress, networks.regtest);
expect(addressType).toBe(AddressTypes.P2OP);
});
it('should not confuse P2OP with P2TR', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
const p2trAddress = wallet.p2tr;
const p2opType = AddressVerificator.detectAddressType(p2opAddress, networks.bitcoin);
const p2trType = AddressVerificator.detectAddressType(p2trAddress, networks.bitcoin);
expect(p2opType).toBe(AddressTypes.P2OP);
expect(p2trType).toBe(AddressTypes.P2TR);
expect(p2opType).not.toBe(p2trType);
});
it('should not confuse P2OP with P2WPKH', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
const p2wpkhAddress = wallet.p2wpkh;
const p2opType = AddressVerificator.detectAddressType(p2opAddress, networks.bitcoin);
const p2wpkhType = AddressVerificator.detectAddressType(
p2wpkhAddress,
networks.bitcoin,
);
expect(p2opType).toBe(AddressTypes.P2OP);
expect(p2wpkhType).toBe(AddressTypes.P2WPKH);
expect(p2opType).not.toBe(p2wpkhType);
});
it('should detect P2OP for different security levels', () => {
const levels = [
MLDSASecurityLevel.LEVEL2,
MLDSASecurityLevel.LEVEL3,
MLDSASecurityLevel.LEVEL5,
];
for (const level of levels) {
const mnemonic = new Mnemonic(testMnemonic, '', networks.bitcoin, level);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
const addressType = AddressVerificator.detectAddressType(
p2opAddress,
networks.bitcoin,
);
expect(addressType).toBe(AddressTypes.P2OP);
}
});
it('should return null for invalid P2OP address on wrong network', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2opAddress = wallet.address.p2op(networks.bitcoin);
// Try to detect mainnet address on testnet
const addressType = AddressVerificator.detectAddressType(p2opAddress, networks.testnet);
expect(addressType).toBeNull();
});
});
describe('ML-DSA integration with other address types', () => {
it('should correctly identify different address types from same wallet', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const p2trType = AddressVerificator.detectAddressType(wallet.p2tr, networks.bitcoin);
const p2wpkhType = AddressVerificator.detectAddressType(
wallet.p2wpkh,
networks.bitcoin,
);
const p2opType = AddressVerificator.detectAddressType(
wallet.address.p2op(networks.bitcoin),
networks.bitcoin,
);
expect(p2trType).toBe(AddressTypes.P2TR);
expect(p2wpkhType).toBe(AddressTypes.P2WPKH);
expect(p2opType).toBe(AddressTypes.P2OP);
});
it('should validate classical and ML-DSA public keys separately', () => {
const mnemonic = new Mnemonic(
testMnemonic,
'',
networks.bitcoin,
MLDSASecurityLevel.LEVEL2,
);
const wallet = mnemonic.derive(0);
const classicalValid = AddressVerificator.isValidPublicKey(
wallet.toPublicKeyHex(),
networks.bitcoin,
);
const mldsaLevel = AddressVerificator.isValidMLDSAPublicKey(wallet.quantumPublicKeyHex);
expect(classicalValid).toBe(true);
expect(mldsaLevel).toBe(MLDSASecurityLevel.LEVEL2);
});
});
});