UNPKG

@ledgerhq/hw-app-canton

Version:
137 lines 5.6 kB
const INS = { GET_VERSION: 0x04, GET_ADDR: 0x05, SIGN: 0x06, }; const STATUS = { OK: 0x9000, USER_CANCEL: 0x6985, }; // SECP256R1-compatible mock addresses const SECP256R1_MOCK_ADDRESSES = { "44'/6767'/0'/0'/0'": { // Uncompressed SECP256R1 public key (65 bytes: 0x04 + 32-byte X + 32-byte Y) publicKey: "0x04" + "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", address: "canton_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z", }, "44'/6767'/0'/0'/1'": { // Another valid SECP256R1 public key publicKey: "0x04" + "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd", address: "canton_2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a", }, }; /** * Mock Canton "@ledgerhq/hw-app-canton" device implementation for development and testing */ export class MockCantonDevice { mockAddresses = new Map(); mockSignatures = new Map(); constructor() { // Initialize with SECP256R1-compatible addresses Object.entries(SECP256R1_MOCK_ADDRESSES).forEach(([path, address]) => { this.mockAddresses.set(path, address); }); } async send(cla, ins, p1, p2, data) { switch (ins) { case INS.GET_ADDR: return this.getAddressResponse(data); case INS.SIGN: return this.signTransactionResponse(data); case INS.GET_VERSION: return this.getAppConfigurationResponse(); default: throw new Error(`Unsupported instruction: ${ins}`); } } async getAddressResponse(data) { await this.simulateDeviceDelay(); // Parse the path from the data const pathData = data.slice(1); // Skip length byte const path = this.parsePathFromData(pathData); const mockAddress = this.mockAddresses.get(path); if (!mockAddress) { // Generate SECP256R1-compatible mock address const pathHash = this.hashString(path); const newAddress = { // Create valid uncompressed SECP256R1 public key format publicKey: "0x04" + pathHash.substring(0, 64).padEnd(64, "0") + pathHash.substring(64, 128).padEnd(64, "0"), address: `canton_${pathHash.substring(0, 36)}`, }; this.mockAddresses.set(path, newAddress); } const address = this.mockAddresses.get(path); // Return 65-byte uncompressed public key (0x04 + 32-byte X + 32-byte Y) const publicKeyBytes = Buffer.from(address.publicKey.slice(2), "hex"); const response = Buffer.alloc(65 + 2); publicKeyBytes.copy(response, 0); response.writeUInt16BE(STATUS.OK, 65); return response; } async signTransactionResponse(data) { await this.simulateDeviceDelay(); // Parse the path and transaction from the data const pathData = data.slice(0, 21); // First 21 bytes are path const txData = data.slice(21); // Rest is transaction data const path = this.parsePathFromData(pathData); const signatureKey = `${path}:${txData.toString("hex")}`; let signature = this.mockSignatures.get(signatureKey); if (!signature) { // Generate SECP256R1-compatible mock signature (64 bytes: r + s) const combinedHash = this.hashString(signatureKey); signature = `0x${combinedHash.substring(0, 64).padEnd(64, "0")}`; this.mockSignatures.set(signatureKey, signature); } // Return 64-byte signature (r + s components) const response = Buffer.alloc(64 + 2); Buffer.from(signature.slice(2), "hex").copy(response, 0); // Remove '0x' prefix response.writeUInt16BE(STATUS.OK, 64); return response; } async getAppConfigurationResponse() { await this.simulateDeviceDelay(); // Create response buffer: version data + status code const versionData = Buffer.from([0x00, 0x01, 0x00, 0x06]); // Version 0.1.0 const response = Buffer.alloc(versionData.length + 2); versionData.copy(response, 0); response.writeUInt16BE(STATUS.OK, versionData.length); return response; } parsePathFromData(data) { // Convert the path data back to a BIP32 path string const segments = []; for (let i = 0; i < data.length; i += 4) { const segment = data.readUInt32BE(i); segments.push(segment); } // Convert to BIP32 path string const pathParts = segments.map(seg => { const isHardened = (seg & 0x80000000) !== 0; const value = seg & 0x7fffffff; return isHardened ? `${value}'` : `${value}`; }); return pathParts.join("/"); } async simulateDeviceDelay() { await new Promise(resolve => setTimeout(resolve, 50)); } /** * Simple deterministic hash function for generating mock data */ hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(16); } } //# sourceMappingURL=MockDevice.js.map