UNPKG

wallet-storage

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

688 lines (634 loc) 30.7 kB
import { PrivilegedKeyManager } from '../PrivilegedKeyManager' import { Utils, PrivateKey, Hash, Random } from '@bsv/sdk' const sampleData = [3, 1, 4, 1, 5, 9] // A helper function to get a 32-byte hex function getRandom32ByteHex(): string { const rawBytes = Random(32); return Utils.toHex(rawBytes); } describe('PrivilegedKeyManager', () => { it('Validates the BRC-3 compliance vector', async () => { const wallet = new PrivilegedKeyManager(async () => new PrivateKey(1)) const { valid } = await wallet.verifySignature({ data: Utils.toArray('BRC-3 Compliance Validated!', 'utf8'), signature: [48, 68, 2, 32, 43, 34, 58, 156, 219, 32, 50, 70, 29, 240, 155, 137, 88, 60, 200, 95, 243, 198, 201, 21, 56, 82, 141, 112, 69, 196, 170, 73, 156, 6, 44, 48, 2, 32, 118, 125, 254, 201, 44, 87, 177, 170, 93, 11, 193, 134, 18, 70, 9, 31, 234, 27, 170, 177, 54, 96, 181, 140, 166, 196, 144, 14, 230, 118, 106, 105], protocolID: [2, 'BRC3 Test'], keyID: '42', counterparty: '0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1' }) expect(valid).toBe(true) await wallet.destroyKey() }) it('Validates the BRC-2 HMAC compliance vector', async () => { const wallet = new PrivilegedKeyManager(async () => new PrivateKey('6a2991c9de20e38b31d7ea147bf55f5039e4bbc073160f5e0d541d1f17e321b8', 'hex')) const { valid } = await wallet.verifyHmac({ data: Utils.toArray('BRC-2 HMAC Compliance Validated!', 'utf8'), hmac: [81, 240, 18, 153, 163, 45, 174, 85, 9, 246, 142, 125, 209, 133, 82, 76, 254, 103, 46, 182, 86, 59, 219, 61, 126, 30, 176, 232, 233, 100, 234, 14], protocolID: [2, 'BRC2 Test'], keyID: '42', counterparty: '0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1' }) expect(valid).toBe(true) await wallet.destroyKey() }) it('Validates the BRC-2 Encryption compliance vector', async () => { const wallet = new PrivilegedKeyManager(async () => new PrivateKey('6a2991c9de20e38b31d7ea147bf55f5039e4bbc073160f5e0d541d1f17e321b8', 'hex')) const { plaintext } = await wallet.decrypt({ ciphertext: [252, 203, 216, 184, 29, 161, 223, 212, 16, 193, 94, 99, 31, 140, 99, 43, 61, 236, 184, 67, 54, 105, 199, 47, 11, 19, 184, 127, 2, 165, 125, 9, 188, 195, 196, 39, 120, 130, 213, 95, 186, 89, 64, 28, 1, 80, 20, 213, 159, 133, 98, 253, 128, 105, 113, 247, 197, 152, 236, 64, 166, 207, 113, 134, 65, 38, 58, 24, 127, 145, 140, 206, 47, 70, 146, 84, 186, 72, 95, 35, 154, 112, 178, 55, 72, 124], protocolID: [2, 'BRC2 Test'], keyID: '42', counterparty: '0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1' }) expect(Utils.toUTF8(plaintext)).toEqual('BRC-2 Encryption Compliance Validated!') await wallet.destroyKey() }) it('Encrypts messages decryptable by the counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { ciphertext } = await user.encrypt({ plaintext: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) const { plaintext } = await counterparty.decrypt({ ciphertext, protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() }) expect(plaintext).toEqual(sampleData) expect(ciphertext).not.toEqual(plaintext) await user.destroyKey() await counterparty.destroyKey() }) it('Fails to decryupt messages for the wrong protocol, key, and counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { ciphertext } = await user.encrypt({ plaintext: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) await expect(async () => await counterparty.decrypt({ ciphertext, protocolID: [1, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.decrypt({ ciphertext, protocolID: [2, 'tests'], keyID: '5', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.decrypt({ ciphertext, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() })).rejects.toThrow() await user.destroyKey() await counterparty.destroyKey() }) it('Correctly derives keys for a counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { publicKey: identityKey } = await user.getPublicKey({ identityKey: true }) expect(identityKey).toEqual(userKey.toPublicKey().toString()) const { publicKey: derivedForCounterparty } = await user.getPublicKey({ protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) const { publicKey: derivedByCounterparty } = await counterparty.getPublicKey({ protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString(), forSelf: true }) expect(derivedForCounterparty).toEqual(derivedByCounterparty) await user.destroyKey() await counterparty.destroyKey() }) it('Signs messages verifiable by the counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { signature } = await user.createSignature({ data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) const { valid } = await counterparty.verifySignature({ signature, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() }) expect(valid).toEqual(true) expect(signature.length).not.toEqual(0) await user.destroyKey() await counterparty.destroyKey() }) it('Directly signs hash of message verifiable by the counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { signature } = await user.createSignature({ hashToDirectlySign: Hash.sha256(sampleData), protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) const { valid } = await counterparty.verifySignature({ signature, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() }) expect(valid).toEqual(true) const { valid: hashValid } = await counterparty.verifySignature({ signature, hashToDirectlyVerify: Hash.sha256(sampleData), protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() }) expect(hashValid).toEqual(true) expect(signature.length).not.toEqual(0) await user.destroyKey() await counterparty.destroyKey() }) it('Fails to verify signature for the wrong data, protocol, key, and counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { signature } = await user.createSignature({ data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) await expect(async () => await counterparty.verifySignature({ signature, data: [0, ...sampleData], protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.verifySignature({ signature, data: sampleData, protocolID: [2, 'wrong'], keyID: '4', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.verifySignature({ signature, data: sampleData, protocolID: [2, 'tests'], keyID: '2', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.verifySignature({ signature, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() })).rejects.toThrow() await user.destroyKey() await counterparty.destroyKey() }) it('Computes HMAC over messages verifiable by the counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { hmac } = await user.createHmac({ data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) const { valid } = await counterparty.verifyHmac({ hmac, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() }) expect(valid).toEqual(true) expect(hmac.length).toEqual(32) await user.destroyKey() await counterparty.destroyKey() }) it('Fails to verify HMAC for the wrong data, protocol, key, and counterparty', async () => { const userKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const counterparty = new PrivilegedKeyManager(async () => counterpartyKey) const { hmac } = await user.createHmac({ data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() }) await expect(async () => await counterparty.verifyHmac({ hmac, data: [0, ...sampleData], protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.verifyHmac({ hmac, data: sampleData, protocolID: [2, 'wrong'], keyID: '4', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.verifyHmac({ hmac, data: sampleData, protocolID: [2, 'tests'], keyID: '2', counterparty: userKey.toPublicKey().toString() })).rejects.toThrow() await expect(async () => await counterparty.verifyHmac({ hmac, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: counterpartyKey.toPublicKey().toString() })).rejects.toThrow() await user.destroyKey() await counterparty.destroyKey() }) it('Uses anyone for creating signatures and self for other operations if no counterparty is provided', async () => { const userKey = PrivateKey.fromRandom() const user = new PrivilegedKeyManager(async () => userKey) const { hmac } = await user.createHmac({ data: sampleData, protocolID: [2, 'tests'], keyID: '4' }) const { valid: hmacValid } = await user.verifyHmac({ hmac, data: sampleData, protocolID: [2, 'tests'], keyID: '4' }) expect(hmacValid).toEqual(true) const { valid: explicitSelfHmacValid } = await user.verifyHmac({ hmac, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: 'self' }) expect(explicitSelfHmacValid).toEqual(true) expect(hmac.length).toEqual(32) const { signature: anyoneSig } = await user.createSignature({ data: sampleData, protocolID: [2, 'tests'], keyID: '4' // counterparty=anyone is implicit for creating signatures }) const anyone = new PrivilegedKeyManager(async () => new PrivateKey(1)) const { valid: anyoneSigValid } = await anyone.verifySignature({ signature: anyoneSig, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: userKey.toPublicKey().toString() }) expect(anyoneSigValid).toEqual(true) const { signature: selfSig } = await user.createSignature({ data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: 'self' }) const { valid: selfSigValid } = await user.verifySignature({ signature: selfSig, data: sampleData, protocolID: [2, 'tests'], keyID: '4' // Self is implicit when verifying signatures }) expect(selfSigValid).toEqual(true) const { valid: explicitSelfSigValid } = await user.verifySignature({ signature: selfSig, data: sampleData, protocolID: [2, 'tests'], keyID: '4', counterparty: 'self' }) expect(explicitSelfSigValid).toEqual(true) const { publicKey } = await user.getPublicKey({ protocolID: [2, 'tests'], keyID: '4' }) const { publicKey: explicitSelfPublicKey } = await user.getPublicKey({ protocolID: [2, 'tests'], keyID: '4', counterparty: 'self' }) expect(publicKey).toEqual(explicitSelfPublicKey) const { ciphertext } = await user.encrypt({ plaintext: sampleData, protocolID: [2, 'tests'], keyID: '4' }) const { plaintext } = await user.decrypt({ ciphertext, protocolID: [2, 'tests'], keyID: '4' }) const { plaintext: explicitSelfPlaintext } = await user.decrypt({ ciphertext, protocolID: [2, 'tests'], keyID: '4', counterparty: 'self' }) expect(plaintext).toEqual(explicitSelfPlaintext) expect(plaintext).toEqual(sampleData) await user.destroyKey() await anyone.destroyKey() }) describe('PrivilegedKeyManager Key Linkage Revelation', () => { it('Validates the revealCounterpartyKeyLinkage function', async () => { // Initialize keys const proverKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const verifierKey = PrivateKey.fromRandom() // Initialize wallets const proverWallet = new PrivilegedKeyManager(async () => proverKey) const verifierWallet = new PrivilegedKeyManager(async () => verifierKey) // Prover reveals counterparty key linkage const revelation = await proverWallet.revealCounterpartyKeyLinkage({ counterparty: counterpartyKey.toPublicKey().toString(), verifier: verifierKey.toPublicKey().toString() }) // Verifier decrypts the encrypted linkage const { plaintext: linkage } = await verifierWallet.decrypt({ ciphertext: revelation.encryptedLinkage, protocolID: [2, 'counterparty linkage revelation'], keyID: revelation.revelationTime, counterparty: proverKey.toPublicKey().toString() }) // Compute expected linkage const expectedLinkage = proverKey.deriveSharedSecret(counterpartyKey.toPublicKey()).encode(true) // Compare linkage and expectedLinkage expect(linkage).toEqual(expectedLinkage) await proverWallet.destroyKey() await verifierWallet.destroyKey() }) it('Validates the revealSpecificKeyLinkage function', async () => { // Initialize keys const proverKey = PrivateKey.fromRandom() const counterpartyKey = PrivateKey.fromRandom() const verifierKey = PrivateKey.fromRandom() // Initialize wallets const proverWallet = new PrivilegedKeyManager(async () => proverKey) const verifierWallet = new PrivilegedKeyManager(async () => verifierKey) const protocolID: [0 | 1 | 2, string] = [0, 'tests'] const keyID = 'test key id' // Prover reveals specific key linkage const revelation = await proverWallet.revealSpecificKeyLinkage({ counterparty: counterpartyKey.toPublicKey().toString(), verifier: verifierKey.toPublicKey().toString(), protocolID, keyID }) // Verifier decrypts the encrypted linkage const { plaintext: linkage } = await verifierWallet.decrypt({ ciphertext: revelation.encryptedLinkage, protocolID: [2, `specific linkage revelation ${protocolID[0]} ${protocolID[1]}`], keyID, counterparty: proverKey.toPublicKey().toString() }) // Compute expected linkage const sharedSecret = proverKey.deriveSharedSecret(counterpartyKey.toPublicKey()).encode(true) // Function to compute the invoice number const computeInvoiceNumber = function (protocolID, keyID) { const securityLevel = protocolID[0] if (!Number.isInteger(securityLevel) || securityLevel < 0 || securityLevel > 2) { throw new Error('Protocol security level must be 0, 1, or 2') } const protocolName = protocolID[1].toLowerCase().trim() if (keyID.length > 800) { throw new Error('Key IDs must be 800 characters or less') } if (keyID.length < 1) { throw new Error('Key IDs must be 1 character or more') } if (protocolName.length > 400) { throw new Error('Protocol names must be 400 characters or less') } if (protocolName.length < 5) { throw new Error('Protocol names must be 5 characters or more') } if (protocolName.includes(' ')) { throw new Error('Protocol names cannot contain multiple consecutive spaces (" ")') } if (!/^[a-z0-9 ]+$/g.test(protocolName)) { throw new Error('Protocol names can only contain letters, numbers and spaces') } if (protocolName.endsWith(' protocol')) { throw new Error('No need to end your protocol name with " protocol"') } return `${securityLevel}-${protocolName}-${keyID}` } const invoiceNumber = computeInvoiceNumber(protocolID, keyID) const invoiceNumberBin = Utils.toArray(invoiceNumber, 'utf8') // Compute expected linkage const expectedLinkage = Hash.sha256hmac(sharedSecret, invoiceNumberBin) // Compare linkage and expectedLinkage expect(linkage).toEqual(expectedLinkage) await proverWallet.destroyKey() await verifierWallet.destroyKey() }) }) describe('PrivilegedKeyManager - Internal Logic Tests', () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.clearAllTimers(); jest.useRealTimers(); }); it('Calls keyGetter only once if getPrivilegedKey is invoked multiple times within retention period', async () => { // Create a mock keyGetter that returns a PrivateKey const keyGetterMock = jest.fn(async (reason: string) => { return new PrivateKey(getRandom32ByteHex(), 'hex'); }); // Retention period is 100 ms for testing const km = new PrivilegedKeyManager(keyGetterMock, 100); // 1) First call, should call keyGetter once const key1 = await (km as any).getPrivilegedKey('first reason'); expect(keyGetterMock).toHaveBeenCalledTimes(1); // 2) Second call, if within retention, should NOT call keyGetter again const key2 = await (km as any).getPrivilegedKey('second reason'); expect(keyGetterMock).toHaveBeenCalledTimes(1); // 3) Check that both keys match the same underlying private key expect(key1.toHex()).toBe(key2.toHex()); await km.destroyKey() }); it('Destroys key after retention period elapses', async () => { const keyGetterMock = jest.fn(async (reason: string) => { return new PrivateKey(getRandom32ByteHex(), 'hex'); }); const retentionMs = 200; const km = new PrivilegedKeyManager(keyGetterMock, retentionMs); // Acquire the key await (km as any).getPrivilegedKey('test reason'); // We have chunkPropNames set expect((km as any).chunkPropNames.length).toBeGreaterThan(0); // Fast-forward time beyond the retention period jest.advanceTimersByTime(retentionMs + 1); // The destroyKey logic should have run expect((km as any).chunkPropNames.length).toBe(0); expect((km as any).chunkPadPropNames.length).toBe(0); expect((km as any).decoyPropNamesDestroy.length).toBe(0); await km.destroyKey() }); it('Explicitly calls destroyKey() and removes all chunk properties', async () => { const keyGetterMock = jest.fn(async (reason: string) => { return new PrivateKey(getRandom32ByteHex(), 'hex'); }); const km = new PrivilegedKeyManager(keyGetterMock, 5000); // Acquire the key await (km as any).getPrivilegedKey('destroy test'); // Verify chunk props exist expect((km as any).chunkPropNames.length).toBeGreaterThan(0); expect((km as any).chunkPadPropNames.length).toBeGreaterThan(0); // Explicitly call destroyKey (km as any).destroyKey(); // Now chunkPropNames and chunkPadPropNames should be cleared expect((km as any).chunkPropNames.length).toBe(0); expect((km as any).chunkPadPropNames.length).toBe(0); await km.destroyKey() }); it('Reuses in-memory obfuscated key if data is valid, otherwise fetches a new key', async () => { const mockHex = getRandom32ByteHex(); const keyGetterMock = jest.fn(async () => new PrivateKey(mockHex, 'hex')); const km = new PrivilegedKeyManager(keyGetterMock, 5000); // 1) First retrieval => calls keyGetter const key1 = await (km as any).getPrivilegedKey('reuse test'); expect(keyGetterMock).toHaveBeenCalledTimes(1); expect(key1.toHex()).toBe(mockHex); // 2) Tamper with chunk data so reassembleKeyFromChunks returns null // We can zero out one chunk or remove it (km as any)[(km as any).chunkPropNames[0]] = undefined; // 3) Second retrieval => chunk data is invalid => calls keyGetter again const key2 = await (km as any).getPrivilegedKey('reuse test 2'); expect(keyGetterMock).toHaveBeenCalledTimes(2); // The newly fetched key must still match mockHex, // because the mock always returns the same key. expect(key2.toHex()).toBe(mockHex); await km.destroyKey() }); it('Ensures chunk-splitting logic is correct for a 32-byte key', async () => { const km = new PrivilegedKeyManager(async () => new PrivateKey(1), 5000); const testBytes = new Uint8Array(32); // Fill with some pattern, e.g. 0..31 testBytes.forEach((_, i) => { testBytes[i] = i; }); const chunks = (km as any).splitKeyIntoChunks(testBytes); expect(chunks.length).toBe((km as any).CHUNK_COUNT); // By default CHUNK_COUNT = 4 // Typically each chunk would be 8 bytes (for a 32-byte key). chunks.forEach((chunk: Uint8Array, i: number) => { if (i < 3) { expect(chunk.length).toBe(8); } else { // last chunk picks up leftover expect(chunk.length).toBe(8); } }); // Reassemble logic typically is done by reassembleKeyFromChunks, // but let's test it in isolation. We'll XOR with random pads, // store them, reassemble, etc. // For demonstration, we can do a quick test: const pad = chunks.map((c: Uint8Array) => Uint8Array.from(Random(c.length))); const obfuscated = chunks.map((c: Uint8Array, i: number) => (km as any).xorBytes(c, pad[i])); // Then "store" and reassemble (km as any).chunkPropNames = []; (km as any).chunkPadPropNames = []; obfuscated.forEach((obf: Uint8Array, i: number) => { const chunkProp = `chunk${i}`; const padProp = `pad${i}`; (km as any).chunkPropNames.push(chunkProp); (km as any).chunkPadPropNames.push(padProp); (km as any)[chunkProp] = obf; (km as any)[padProp] = pad[i]; }); const reassembled = (km as any).reassembleKeyFromChunks(); expect(reassembled.length).toBe(32); expect(Array.from(reassembled)).toEqual(Array.from(testBytes)); await km.destroyKey() }); it('XOR function works as expected', async () => { const km = new PrivilegedKeyManager(async () => new PrivateKey(1), 5000); const a = Uint8Array.from([0, 1, 255]); const b = Uint8Array.from([255, 1, 0]); const result = (km as any).xorBytes(a, b); // 0 ^ 255 = 255, 1 ^ 1 = 0, 255 ^ 0 = 255 expect(Array.from(result)).toEqual([255, 0, 255]); // XOR with zero array => same array const zero = new Uint8Array(3); const result2 = (km as any).xorBytes(a, zero); expect(Array.from(result2)).toEqual([0, 1, 255]); await km.destroyKey() }); it('Generates random property names', async () => { const km = new PrivilegedKeyManager(async () => new PrivateKey(1), 5000); const prop1 = (km as any).generateRandomPropName(); const prop2 = (km as any).generateRandomPropName(); expect(prop1).not.toBe(prop2); // Just check format (roughly) expect(prop1).toMatch(/^_[0-9a-f]{8}_[0-9]{1,6}$/); expect(prop2).toMatch(/^_[0-9a-f]{8}_[0-9]{1,6}$/); await km.destroyKey() }); it('Sets up initial decoy properties in the constructor', async () => { const km = new PrivilegedKeyManager(async () => new PrivateKey(1), 5000); // decoyPropNamesRemain has length 2 expect((km as any).decoyPropNamesRemain.length).toBe(2); // Validate those properties actually exist on the object for (const propName of (km as any).decoyPropNamesRemain) { expect((km as any)[propName]).toBeInstanceOf(Uint8Array); expect((km as any)[propName].length).toBe(16); } await km.destroyKey() }); it('New decoy properties are created on each key fetch and destroyed on destroy', async () => { const km = new PrivilegedKeyManager(async () => new PrivateKey(1), 5000); await (km as any).getPrivilegedKey('decoy test'); // We should have 2 decoy props that remain, plus 2 that are "destroyable" expect((km as any).decoyPropNamesRemain.length).toBe(2); expect((km as any).decoyPropNamesDestroy.length).toBe(2); // Destroy them (km as any).destroyKey(); expect((km as any).decoyPropNamesDestroy.length).toBe(0); await km.destroyKey() }); }); })