UNPKG

accounts

Version:

Tempo Accounts SDK

172 lines (152 loc) 5.92 kB
import { Address, Hex, Json, P256, PublicKey } from 'ox' import { SignatureEnvelope } from 'ox/tempo' import { KeyAuthorizationManager } from 'viem/tempo' import { describe, expect, test, vi } from 'vp/test' import { accounts } from '../../test/config.js' import * as Keystore from './Keystore.js' const rootAddress = accounts[0]!.address function toAccountContext() { return { access: rootAddress, keyAuthorizationManager: KeyAuthorizationManager.memory(), } } describe('webCryptoP256', () => { test('default: creates a non-extractable key with a structured-clone handle', async () => { const keystore = Keystore.webCryptoP256() expect(keystore.requiresStructuredClone).toBe(true) const key = await keystore.createKey() expect(key.publicKey).toMatch(/^0x[0-9a-f]+$/i) const handle = key.handle as { kind: string; keyPair?: { privateKey: CryptoKey } } expect(handle.kind).toBe('webcrypto-p256') expect(handle.keyPair?.privateKey.extractable).toBe(false) const account = await keystore.toAccount( { handle: key.handle, keyType: 'p256', publicKey: key.publicKey }, toAccountContext(), ) expect(account.accessKeyAddress).toBe( Address.fromPublicKey(PublicKey.fromHex(key.publicKey)).toLowerCase(), ) }) test('behavior: extractable handles survive JSON and verify against the public key', async () => { const keystore = Keystore.webCryptoP256({ extractable: true }) expect(keystore.requiresStructuredClone).toBe(false) const key = await keystore.createKey() expect(JSON.parse(JSON.stringify(key.handle))).toEqual(key.handle) const account = await keystore.toAccount( { handle: JSON.parse(JSON.stringify(key.handle)), keyType: 'p256', publicKey: key.publicKey, }, toAccountContext(), ) expect(account.accessKeyAddress).toBe( Address.fromPublicKey(PublicKey.fromHex(key.publicKey)).toLowerCase(), ) // Signature verifies against the persisted public key. const payload = Hex.random(32) const signature = await account.sign({ hash: payload, raw: true }) const envelope = SignatureEnvelope.deserialize(signature) if (envelope.type !== 'p256') throw new Error('expected p256 envelope') expect( P256.verify({ hash: envelope.prehash, payload, publicKey: PublicKey.fromHex(key.publicKey), signature: envelope.signature, }), ).toBe(true) }) test('behavior: a live handle mangled by JSON storage is permanently unavailable', async () => { const keystore = Keystore.webCryptoP256() const key = await keystore.createKey() // Simulate a structured-clone handle squeezed through a string-based // storage adapter: the CryptoKey degrades to a plain object. const mangled = Json.parse(Json.stringify(key.handle)) await expect( keystore.toAccount( { handle: mangled, keyType: 'p256', publicKey: key.publicKey }, toAccountContext(), ), ).rejects.toBeInstanceOf(Keystore.KeyUnavailableError) }) test('error: corrupt key material is permanently unavailable', async () => { const keystore = Keystore.webCryptoP256({ extractable: true }) const key = await keystore.createKey() await expect( keystore.toAccount( { handle: { jwk: { kty: 'EC' }, kind: 'webcrypto-p256' }, keyType: 'p256', publicKey: key.publicKey, }, toAccountContext(), ), ).rejects.toBeInstanceOf(Keystore.KeyUnavailableError) }) test('error: rejects handles written by another keystore', async () => { const keystore = Keystore.webCryptoP256() const key = await keystore.createKey() const toAccount = keystore.toAccount( { handle: { keyTag: 'app.example.key', kind: 'secure-enclave' }, keyType: 'p256', publicKey: key.publicKey, }, toAccountContext(), ) await expect(toAccount).rejects.toThrowErrorMatchingInlineSnapshot( `[Error: Unrecognized \`webCryptoP256\` keystore handle.]`, ) await expect(toAccount).rejects.not.toBeInstanceOf(Keystore.KeyUnavailableError) }) test('error: fails loudly when WebCrypto is unavailable', async () => { const keystore = Keystore.webCryptoP256() vi.stubGlobal('crypto', {}) try { await expect(keystore.createKey()).rejects.toThrowErrorMatchingInlineSnapshot( `[Error: \`webCryptoP256\` keystore requires WebCrypto (\`crypto.subtle\`) support.]`, ) } finally { vi.unstubAllGlobals() } }) }) describe('p256 / secp256k1', () => { test.each([ { factory: Keystore.p256, kind: 'p256' }, { factory: Keystore.secp256k1, kind: 'secp256k1' }, ] as const)('default: $kind handles survive JSON and rehydrate', async ({ factory, kind }) => { const keystore = factory() expect(keystore.requiresStructuredClone).toBeFalsy() const key = await keystore.createKey() expect(key.handle).toMatchObject({ kind }) expect(JSON.parse(JSON.stringify(key.handle))).toEqual(key.handle) const account = await keystore.toAccount( { handle: JSON.parse(JSON.stringify(key.handle)), keyType: kind, publicKey: key.publicKey, }, toAccountContext(), ) expect(account.accessKeyAddress).toBe( Address.fromPublicKey(PublicKey.fromHex(key.publicKey)).toLowerCase(), ) }) test('error: rejects handles written by another keystore', async () => { const keystore = Keystore.p256() const key = await keystore.createKey() expect(() => keystore.toAccount( { handle: { kind: 'secp256k1', privateKey: '0x01' }, keyType: 'p256', publicKey: key.publicKey, }, toAccountContext(), ), ).toThrowErrorMatchingInlineSnapshot(`[Error: Unrecognized \`p256\` keystore handle.]`) }) })