From efcf286372c137bcbf85fb8cea308dadd5017c75 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 27 Apr 2026 16:07:24 -0700 Subject: [PATCH] Implement custom modal to further isolate signing requests from wallets. --- src/lib/wallet/accounts.ts | 1 - src/lib/wallet/index.ts | 8 ++---- src/lib/wallet/sign.ts | 59 ++++++++++++++++++++++++++++++++++---- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/lib/wallet/accounts.ts b/src/lib/wallet/accounts.ts index 771e8cd..9d380de 100644 --- a/src/lib/wallet/accounts.ts +++ b/src/lib/wallet/accounts.ts @@ -26,7 +26,6 @@ export async function _accounts (type: WalletType, accounts: Map 0) { - const publicAccounts = [] if (type === 'Ledger') { for (const index of indexes) { const { status, publicKey } = await Ledger.account(index) diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index 77299bb..f5f39aa 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -371,12 +371,10 @@ export class Wallet { */ async sign (index: number, block: Block, frontier?: Block): Promise async sign (index: number, data: string | string[] | Block, frontier?: Block): Promise { - if (navigator.userActivation?.isActive === false) { - throw new Error('Signing request was blocked due to lack of user activation.') - } + const { address } = await this.account(index) return data instanceof Block - ? await _signBlock(this, this.#vault, index, data, frontier) - : await _signData(this, this.#vault, index, data) + ? await _signBlock(this, this.#vault, index, address, data, frontier) + : await _signData(this, this.#vault, index, address, data) } /** diff --git a/src/lib/wallet/sign.ts b/src/lib/wallet/sign.ts index 35f6609..f307928 100644 --- a/src/lib/wallet/sign.ts +++ b/src/lib/wallet/sign.ts @@ -7,12 +7,15 @@ import { Ledger } from '../ledger' import { Vault } from '../vault' import { Wallet } from '../wallet' -export async function _signData (wallet: Wallet, vault: Vault, index: number, data: string | string[]): Promise -export async function _signData (wallet: Wallet, vault: Vault, index: unknown, data: unknown): Promise { +export async function _signData (wallet: Wallet, vault: Vault, index: number, address: string, data: string | string[]): Promise +export async function _signData (wallet: Wallet, vault: Vault, index: unknown, address: unknown, data: unknown): Promise { try { if (typeof index !== 'number') { throw new TypeError('Index must be a number', { cause: index }) } + if (typeof address !== 'string') { + throw new TypeError('Address must be a string', { cause: address }) + } const message = Array.isArray(data) ? data : [data] if (message.some(s => typeof s !== 'string')) { throw new TypeError('Data to sign must be strings', { cause: data }) @@ -20,6 +23,14 @@ export async function _signData (wallet: Wallet, vault: Vault, index: unknown, d if (wallet.type === 'Ledger') { return await Ledger.sign(index, message[0]) } + const reqId = crypto.randomUUID() + const resId = await confirm(reqId, address, message) + if (resId === null) { + throw new Error('User rejected sign request') + } + if (reqId !== resId) { + throw new Error('Signing confirmation responded to wrong request') + } const { signature } = await vault.request({ action: 'sign', index, @@ -31,13 +42,15 @@ export async function _signData (wallet: Wallet, vault: Vault, index: unknown, d } } - -export async function _signBlock (wallet: Wallet, vault: Vault, index: number, block: Block, frontier?: Block): Promise -export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, block: unknown, frontier?: unknown): Promise { +export async function _signBlock (wallet: Wallet, vault: Vault, index: number, address: string, block: Block, frontier?: Block): Promise +export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, address: unknown, block: unknown, frontier?: unknown): Promise { try { if (typeof index !== 'number') { throw new TypeError('Index must be a number', { cause: index }) } + if (typeof address !== 'string') { + throw new TypeError('Address must be a string', { cause: address }) + } if (!(block instanceof Block)) { throw new TypeError('Invalid Block', { cause: block }) } @@ -51,6 +64,14 @@ export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, } block.signature = await Ledger.sign(index, block) } else { + const reqId = crypto.randomUUID() + const resId = await confirm(reqId, address, block) + if (resId === null) { + throw new Error('User rejected sign request') + } + if (reqId !== resId) { + throw new Error('Signing confirmation responded to wrong request') + } const { signature } = await vault.request({ action: 'sign', index, @@ -62,3 +83,31 @@ export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, throw new Error(`Failed to sign block`, { cause: err }) } } + +async function confirm (id: string, address: string, message: Block | string[]): Promise { + BROWSER: return new Promise((resolve, reject) => { + const dialog = document.createElement('dialog') + dialog.innerHTML = ` +
+

${JSON.stringify(message, null, '\t')} +

+ + +
+
` + dialog.addEventListener('close', (ev) => { + if (ev.isTrusted && navigator.userActivation?.isActive) { + resolve(dialog.returnValue === 'yes' ? id : null) + } else { + reject(new DOMException( + 'Signing request was blocked due to lack of user activation.', + 'NotAllowedError' + )) + } + }) + document.body.appendChild(dialog) + dialog.showModal() + }) + NODE: return id +} -- 2.47.3