diff --git a/src/__tests__/storageKeys.test.ts b/src/__tests__/storageKeys.test.ts new file mode 100644 index 0000000..4cd01fe --- /dev/null +++ b/src/__tests__/storageKeys.test.ts @@ -0,0 +1,22 @@ +import { fingerprint, expiryNotifiedStorageKey } from '../lib/storageKeys'; + +const WALLET_A = 'GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H'; +const WALLET_B = 'GCKFBEIYTKP5RDBQMTVVALONAOPBXICILMAFL6ANEHMIYX7XFBZ7ZMGT'; + +describe('storageKeys (#548)', () => { + it('does not include the raw wallet address in the expiry storage key', () => { + const key = expiryNotifiedStorageKey(WALLET_A); + expect(key.startsWith('ps_expiry_notified_')).toBe(true); + expect(key).not.toContain(WALLET_A); + expect(key).not.toContain(WALLET_A.slice(0, 8)); + }); + + it('is deterministic per wallet so notifications persist across renders', () => { + expect(expiryNotifiedStorageKey(WALLET_A)).toBe(expiryNotifiedStorageKey(WALLET_A)); + }); + + it('gives different wallets different keys', () => { + expect(expiryNotifiedStorageKey(WALLET_A)).not.toBe(expiryNotifiedStorageKey(WALLET_B)); + expect(fingerprint('a')).not.toBe(fingerprint('b')); + }); +}); diff --git a/src/lib/storageKeys.ts b/src/lib/storageKeys.ts new file mode 100644 index 0000000..6f33e75 --- /dev/null +++ b/src/lib/storageKeys.ts @@ -0,0 +1,24 @@ +/** + * Non-reversible, deterministic fingerprint of a string (cyrb53, 53-bit). + * Used to scope browser-storage keys per wallet without writing the raw + * address into the key name, where it is visible to anyone inspecting + * DevTools > Application > Storage (#548). Not a cryptographic hash -- it only + * needs to be stable and collision-resistant across a handful of wallets. + */ +export function fingerprint(value: string): string { + let h1 = 0xdeadbeef; + let h2 = 0x41c6ce57; + for (let i = 0; i < value.length; i++) { + const ch = value.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36); +} + +/** sessionStorage key for the policy IDs already warned about for a wallet. */ +export function expiryNotifiedStorageKey(address: string): string { + return `ps_expiry_notified_${fingerprint(address)}`; +}