]> git.codecow.com Git - libnemo.git/commitdiff
Implement custom modal to further isolate signing requests from wallets.
authorChris Duncan <chris@zoso.dev>
Mon, 27 Apr 2026 23:07:24 +0000 (16:07 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 27 Apr 2026 23:07:24 +0000 (16:07 -0700)
src/lib/wallet/accounts.ts
src/lib/wallet/index.ts
src/lib/wallet/sign.ts

index 771e8cdf8571c36752668d3bbeac5f02bed964ac..9d380debeb2a171a6486618ac6579c503ae4f88c 100644 (file)
@@ -26,7 +26,6 @@ export async function _accounts (type: WalletType, accounts: Map<number, Account
                }
        }
        if (indexes.length > 0) {
-               const publicAccounts = []
                if (type === 'Ledger') {
                        for (const index of indexes) {
                                const { status, publicKey } = await Ledger.account(index)
index 77299bbc5fea94908033df0f11aca26549d9ff31..f5f39aa3dd7e2fb0e8887d1a0e606197ed63d975 100644 (file)
@@ -371,12 +371,10 @@ export class Wallet {
        */\r
        async sign (index: number, block: Block, frontier?: Block): Promise<void>\r
        async sign (index: number, data: string | string[] | Block, frontier?: Block): Promise<void | string> {\r
-               if (navigator.userActivation?.isActive === false) {\r
-                       throw new Error('Signing request was blocked due to lack of user activation.')\r
-               }\r
+               const { address } = await this.account(index)\r
                return data instanceof Block\r
-                       ? await _signBlock(this, this.#vault, index, data, frontier)\r
-                       : await _signData(this, this.#vault, index, data)\r
+                       ? await _signBlock(this, this.#vault, index, address, data, frontier)\r
+                       : await _signData(this, this.#vault, index, address, data)\r
        }\r
 \r
        /**\r
index 35f6609d78526374e4f7604e5b45e0b311c4f9bc..f307928c75bcbca5547e1aa6358770b2b2b35bca 100644 (file)
@@ -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<string>
-export async function _signData (wallet: Wallet, vault: Vault, index: unknown, data: unknown): Promise<string> {
+export async function _signData (wallet: Wallet, vault: Vault, index: number, address: string, data: string | string[]): Promise<string>
+export async function _signData (wallet: Wallet, vault: Vault, index: unknown, address: unknown, data: unknown): Promise<string> {
        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<ArrayBuffer>({
                        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<void>
-export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, block: unknown, frontier?: unknown): Promise<void> {
+export async function _signBlock (wallet: Wallet, vault: Vault, index: number, address: string, block: Block, frontier?: Block): Promise<void>
+export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, address: unknown, block: unknown, frontier?: unknown): Promise<void> {
        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<ArrayBuffer>({
                                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<string | null> {
+       BROWSER: return new Promise((resolve, reject) => {
+               const dialog = document.createElement('dialog')
+               dialog.innerHTML = `
+               <form method="dialog">
+                       <p style="font-size=1rem !important;><strong>Sign using account ${address}?</strong></p>
+                       <pre style="font-size=1rem !important;>${JSON.stringify(message, null, '\t')}</pre>
+                       <div style="text-align:right;">
+                               <button value="no" autofocus>NO, cancel</button>
+                               <button value="yes">YES, sign</button>
+                       </div>
+               </form>`
+               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
+}