mirror of
https://github.com/tompro/sattle.git
synced 2026-10-26 01:49:58 +00:00
Formatting
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {sha256} from '@noble/hashes/sha2.js'
|
||||
import {bytesToHex, utf8ToBytes} from '@noble/hashes/utils.js'
|
||||
import {beforeEach, describe, expect, it} from 'vitest'
|
||||
import { sha256 } from '@noble/hashes/sha2.js';
|
||||
import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
decryptSavedWalletMaterial,
|
||||
@@ -16,103 +16,102 @@ import {
|
||||
savedWalletMaterialOwnerId,
|
||||
walletMaterialLinkingKey,
|
||||
WalletMaterialProofMismatchError,
|
||||
} from './keys'
|
||||
import {walletMaterialOwnerId} from './storage/storedSecret'
|
||||
import {parseJsonObject, stubLocalStorage} from './test-utils'
|
||||
} from './keys';
|
||||
import { walletMaterialOwnerId } from './storage/storedSecret';
|
||||
import { parseJsonObject, stubLocalStorage } from './test-utils';
|
||||
|
||||
const MNEMONIC =
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const OTHER_MNEMONIC =
|
||||
'legal winner thank year wave sausage worth useful legal winner thank yellow'
|
||||
const MATERIAL_STORAGE_KEY = 'sattle_wallet_material_v2'
|
||||
const PASSWORD = 'correct horse battery staple'
|
||||
'legal winner thank year wave sausage worth useful legal winner thank yellow';
|
||||
const MATERIAL_STORAGE_KEY = 'sattle_wallet_material_v2';
|
||||
const PASSWORD = 'correct horse battery staple';
|
||||
|
||||
const materialHash = (serialized: string): string =>
|
||||
bytesToHex(sha256(utf8ToBytes(serialized)))
|
||||
const materialHash = (serialized: string): string => bytesToHex(sha256(utf8ToBytes(serialized)));
|
||||
|
||||
const readRawMaterialRecord = (): Record<string, unknown> => {
|
||||
const raw = localStorage.getItem(MATERIAL_STORAGE_KEY)
|
||||
if (raw === null) throw new Error('expected a saved wallet-material record')
|
||||
return parseJsonObject(raw)
|
||||
}
|
||||
const raw = localStorage.getItem(MATERIAL_STORAGE_KEY);
|
||||
if (raw === null) throw new Error('expected a saved wallet-material record');
|
||||
return parseJsonObject(raw);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
stubLocalStorage()
|
||||
})
|
||||
stubLocalStorage();
|
||||
});
|
||||
|
||||
describe('baseline: wallet material identity', () => {
|
||||
it('derives identity only from the linking key when the cash root changes', () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const other = deriveWalletMaterial(OTHER_MNEMONIC)
|
||||
const alteredRoot = {...material, cashRootHex: other.cashRootHex}
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const other = deriveWalletMaterial(OTHER_MNEMONIC);
|
||||
const alteredRoot = { ...material, cashRootHex: other.cashRootHex };
|
||||
|
||||
expect(walletMaterialOwnerId(alteredRoot)).toBe(walletMaterialOwnerId(material))
|
||||
expect(serializeWalletMaterial(alteredRoot)).not.toBe(serializeWalletMaterial(material))
|
||||
})
|
||||
})
|
||||
expect(walletMaterialOwnerId(alteredRoot)).toBe(walletMaterialOwnerId(material));
|
||||
expect(serializeWalletMaterial(alteredRoot)).not.toBe(serializeWalletMaterial(material));
|
||||
});
|
||||
});
|
||||
|
||||
describe('saved wallet material commitment', () => {
|
||||
it('stores the canonical material commitment on plaintext and encrypted saves', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const expectedHash = materialHash(serializeWalletMaterial(material))
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const expectedHash = materialHash(serializeWalletMaterial(material));
|
||||
|
||||
await saveWalletMaterial(material)
|
||||
const plaintext = readRawMaterialRecord()
|
||||
await saveWalletMaterial(material, PASSWORD)
|
||||
const encrypted = readRawMaterialRecord()
|
||||
await saveWalletMaterial(material);
|
||||
const plaintext = readRawMaterialRecord();
|
||||
await saveWalletMaterial(material, PASSWORD);
|
||||
const encrypted = readRawMaterialRecord();
|
||||
|
||||
expect(plaintext.materialHash).toBe(expectedHash)
|
||||
expect(encrypted.materialHash).toBe(expectedHash)
|
||||
})
|
||||
expect(plaintext.materialHash).toBe(expectedHash);
|
||||
expect(encrypted.materialHash).toBe(expectedHash);
|
||||
});
|
||||
|
||||
it('rejects authenticated encrypted material when its commitment differs', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
await saveWalletMaterial(material, PASSWORD)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
await saveWalletMaterial(material, PASSWORD);
|
||||
localStorage.setItem(
|
||||
MATERIAL_STORAGE_KEY,
|
||||
JSON.stringify({...readRawMaterialRecord(), materialHash: '00'.repeat(32)}),
|
||||
)
|
||||
JSON.stringify({ ...readRawMaterialRecord(), materialHash: '00'.repeat(32) }),
|
||||
);
|
||||
|
||||
await expect(decryptSavedWalletMaterial(PASSWORD)).rejects.toBeInstanceOf(
|
||||
WalletMaterialProofMismatchError,
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects plaintext material when its commitment differs', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
await saveWalletMaterial(material)
|
||||
const before = JSON.stringify({...readRawMaterialRecord(), materialHash: '00'.repeat(32)})
|
||||
localStorage.setItem(MATERIAL_STORAGE_KEY, before)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
await saveWalletMaterial(material);
|
||||
const before = JSON.stringify({ ...readRawMaterialRecord(), materialHash: '00'.repeat(32) });
|
||||
localStorage.setItem(MATERIAL_STORAGE_KEY, before);
|
||||
|
||||
expect(getPlainWalletMaterial()).toBeNull()
|
||||
expect(localStorage.getItem(MATERIAL_STORAGE_KEY)).toBe(before)
|
||||
})
|
||||
expect(getPlainWalletMaterial()).toBeNull();
|
||||
expect(localStorage.getItem(MATERIAL_STORAGE_KEY)).toBe(before);
|
||||
});
|
||||
|
||||
it('refuses to stamp same-owner material with an altered cash root', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const other = deriveWalletMaterial(OTHER_MNEMONIC)
|
||||
await saveWalletMaterial(material, PASSWORD)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const other = deriveWalletMaterial(OTHER_MNEMONIC);
|
||||
await saveWalletMaterial(material, PASSWORD);
|
||||
|
||||
expect(() =>
|
||||
ensureSavedWalletMaterialOwner({...material, cashRootHex: other.cashRootHex}),
|
||||
).toThrow(WalletMaterialProofMismatchError)
|
||||
})
|
||||
ensureSavedWalletMaterialOwner({ ...material, cashRootHex: other.cashRootHex }),
|
||||
).toThrow(WalletMaterialProofMismatchError);
|
||||
});
|
||||
|
||||
it('requires exactly one lowercase 64-hex commitment at the storage boundary', () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const serialized = serializeWalletMaterial(material)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const serialized = serializeWalletMaterial(material);
|
||||
const record = {
|
||||
enc: false,
|
||||
value: serialized,
|
||||
materialHash: materialHash(serialized),
|
||||
ownerId: walletMaterialOwnerId(material),
|
||||
version: 2,
|
||||
}
|
||||
};
|
||||
|
||||
expect(isValidStoredWalletMaterial(record)).toBe(true)
|
||||
expect(isValidStoredWalletMaterial({...record, materialHash: record.materialHash.toUpperCase()})).toBe(
|
||||
false,
|
||||
)
|
||||
expect(isValidStoredWalletMaterial(record)).toBe(true);
|
||||
expect(
|
||||
isValidStoredWalletMaterial({ ...record, materialHash: record.materialHash.toUpperCase() }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isValidStoredWalletMaterial({
|
||||
enc: record.enc,
|
||||
@@ -120,13 +119,13 @@ describe('saved wallet material commitment', () => {
|
||||
ownerId: record.ownerId,
|
||||
version: record.version,
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects malformed authenticated material after its correct commitment matches', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const malformed = JSON.stringify({...material, cashRootHex: '00'.repeat(64)})
|
||||
const parts = await encryptSecretParts(malformed, PASSWORD)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const malformed = JSON.stringify({ ...material, cashRootHex: '00'.repeat(64) });
|
||||
const parts = await encryptSecretParts(malformed, PASSWORD);
|
||||
localStorage.setItem(
|
||||
MATERIAL_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
@@ -136,16 +135,14 @@ describe('saved wallet material commitment', () => {
|
||||
ownerId: linkingPubKeyHex(walletMaterialLinkingKey(material)),
|
||||
version: 2,
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
await expect(decryptSavedWalletMaterial(PASSWORD)).rejects.toThrow(
|
||||
'valid v2 wallet material',
|
||||
)
|
||||
})
|
||||
await expect(decryptSavedWalletMaterial(PASSWORD)).rejects.toThrow('valid v2 wallet material');
|
||||
});
|
||||
|
||||
it('preserves a verified restored commitment while discarding its owner claim', () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const serialized = serializeWalletMaterial(material)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const serialized = serializeWalletMaterial(material);
|
||||
|
||||
restoreWalletMaterialStored({
|
||||
enc: false,
|
||||
@@ -153,50 +150,50 @@ describe('saved wallet material commitment', () => {
|
||||
materialHash: materialHash(serialized),
|
||||
ownerId: linkingPubKeyHex(new Uint8Array(32).fill(9)),
|
||||
version: 2,
|
||||
})
|
||||
});
|
||||
|
||||
expect(readRawMaterialRecord()).toEqual({
|
||||
enc: false,
|
||||
value: serialized,
|
||||
materialHash: materialHash(serialized),
|
||||
})
|
||||
expect(getPlainWalletMaterial()).toEqual(material)
|
||||
})
|
||||
});
|
||||
expect(getPlainWalletMaterial()).toEqual(material);
|
||||
});
|
||||
|
||||
it('proves an encrypted restored commitment before owner stamping', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const serialized = serializeWalletMaterial(material)
|
||||
const parts = await encryptSecretParts(serialized, PASSWORD)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const serialized = serializeWalletMaterial(material);
|
||||
const parts = await encryptSecretParts(serialized, PASSWORD);
|
||||
restoreWalletMaterialStored({
|
||||
enc: true,
|
||||
...parts,
|
||||
materialHash: materialHash(serialized),
|
||||
ownerId: linkingPubKeyHex(new Uint8Array(32).fill(9)),
|
||||
version: 2,
|
||||
})
|
||||
});
|
||||
|
||||
expect(savedWalletMaterialOwnerId()).toBeNull()
|
||||
const proven = await decryptSavedWalletMaterial(PASSWORD)
|
||||
ensureSavedWalletMaterialOwner(proven)
|
||||
expect(savedWalletMaterialOwnerId()).toBeNull();
|
||||
const proven = await decryptSavedWalletMaterial(PASSWORD);
|
||||
ensureSavedWalletMaterialOwner(proven);
|
||||
|
||||
expect(savedWalletMaterialOwnerId()).toBe(walletMaterialOwnerId(material))
|
||||
})
|
||||
expect(savedWalletMaterialOwnerId()).toBe(walletMaterialOwnerId(material));
|
||||
});
|
||||
|
||||
it('rejects an unproven restored hash that contradicts encrypted material', async () => {
|
||||
const material = deriveWalletMaterial(MNEMONIC)
|
||||
const serialized = serializeWalletMaterial(material)
|
||||
const parts = await encryptSecretParts(serialized, PASSWORD)
|
||||
const material = deriveWalletMaterial(MNEMONIC);
|
||||
const serialized = serializeWalletMaterial(material);
|
||||
const parts = await encryptSecretParts(serialized, PASSWORD);
|
||||
restoreWalletMaterialStored({
|
||||
enc: true,
|
||||
...parts,
|
||||
materialHash: '00'.repeat(32),
|
||||
ownerId: walletMaterialOwnerId(material),
|
||||
version: 2,
|
||||
})
|
||||
});
|
||||
|
||||
await expect(decryptSavedWalletMaterial(PASSWORD)).rejects.toBeInstanceOf(
|
||||
WalletMaterialProofMismatchError,
|
||||
)
|
||||
expect(savedWalletMaterialOwnerId()).toBeNull()
|
||||
})
|
||||
})
|
||||
);
|
||||
expect(savedWalletMaterialOwnerId()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user