@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
739 lines (685 loc) • 27.9 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()
})
})
})