wallet-storage
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
688 lines (634 loc) • 30.7 kB
text/typescript
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()
});
});
})