* @returns {string} 64-byte hexadecimal signature
*/
static sign (secretKey: string | ArrayBuffer | Uint8Array<ArrayBuffer>, ...input: string[]): string {
+ if (navigator.userActivation?.isActive === false) {
+ throw new Error('Signing request was blocked due to lack of user activation.')
+ }
const k = this.#normalize(secretKey)
try {
const signature = nano25519_sign(utf8.toBytes(input.join('')), k)
const block = new Block(BLAKE2B_ADDRESS_1, '0', OPEN_BLOCK.previous, OPEN_BLOCK.representative)\r
.receive(OPEN_BLOCK.link, OPEN_BLOCK.balance)\r
\r
- await assert.resolves(async () => {\r
- await click(\r
- 'Sign with BLAKE2b',\r
- async () => wallet.sign(1, block)\r
- )\r
- })\r
+ await assert.resolves(click(\r
+ 'Sign with BLAKE2b',\r
+ async () => wallet.sign(1, block)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.ok(await block.verify(BLAKE2B_PUBLIC_1))\r
\r
await assert.resolves(wallet.destroy())\r
const block = new Block(ADDRESS_0, '0', OPEN_BLOCK.previous, OPEN_BLOCK.representative)\r
.receive(OPEN_BLOCK.link, OPEN_BLOCK.balance)\r
\r
- await assert.resolves(async () => {\r
- await click(\r
- 'Sign with BIP-44',\r
- async () => wallet.sign(0, block)\r
- )\r
- })\r
+ await assert.resolves(click(\r
+ 'Sign with BIP-44',\r
+ async () => wallet.sign(0, block)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.ok(await block.verify(PUBLIC_0))\r
\r
await assert.resolves(wallet.destroy())\r
const block = new Block(EXODUS.ADDRESS_0, '0', OPEN_BLOCK.previous, OPEN_BLOCK.representative)\r
.receive(OPEN_BLOCK.link, OPEN_BLOCK.balance)\r
\r
- await assert.resolves(async () => {\r
- await click(\r
- 'Sign with Exodus',\r
- async () => wallet.sign(0, block)\r
- )\r
- })\r
+ await assert.resolves(click(\r
+ 'Sign with Exodus',\r
+ async () => wallet.sign(0, block)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.ok(await block.verify(EXODUS.PUBLIC_0))\r
\r
await assert.resolves(wallet.destroy())\r
const block = new Block(ADDRESS_0, '0', OPEN_BLOCK.previous, OPEN_BLOCK.representative)\r
.receive(OPEN_BLOCK.link, OPEN_BLOCK.balance)\r
\r
- console.log('Waiting 6 seconds...')\r
- await new Promise(r => setTimeout(r, 6000))\r
await assert.rejects(wallet.sign(0, block))\r
assert.ok(block.signature === undefined)\r
\r
const block = new Block(ADDRESS_0, '0', OPEN_BLOCK.previous, OPEN_BLOCK.representative)\r
.receive(OPEN_BLOCK.link, OPEN_BLOCK.balance)\r
\r
- await assert.rejects(async () => {\r
- await click(\r
- 'Fail to sign while locked',\r
- async () => wallet.sign(0, block)\r
- )\r
- })\r
+ await assert.rejects(click(\r
+ 'Fail to sign while locked',\r
+ async () => wallet.sign(0, block)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.ok(block.signature === undefined)\r
\r
await wallet.destroy()\r
const block = await new Block(OPEN_BLOCK.account, '0', OPEN_BLOCK.previous, OPEN_BLOCK.representative)\r
.receive(OPEN_BLOCK.link, OPEN_BLOCK.balance)\r
.pow(OPEN_BLOCK.work)\r
- await block.sign(OPEN_BLOCK.key)\r
+\r
+ await assert.resolves(click(\r
+ 'Sign open block with key',\r
+ async () => block.sign(OPEN_BLOCK.key)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.equal(block.hash, OPEN_BLOCK.hash)\r
assert.equal(block.signature, OPEN_BLOCK.signature)\r
})\r
.receive(RECEIVE_BLOCK.link, 0)\r
.pow(RECEIVE_BLOCK.work)\r
\r
- await assert.resolves(async () => await block.sign(RECEIVE_BLOCK.key))\r
+ await assert.resolves(click(\r
+ 'Sign receive block with key',\r
+ async () => block.sign(RECEIVE_BLOCK.key)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.equal(block.hash, RECEIVE_BLOCK.hash)\r
assert.equal(block.signature, RECEIVE_BLOCK.signature)\r
})\r
const block = new Block(LEDGER_NANOS.OPEN_BLOCK.account, LEDGER_NANOS.OPEN_BLOCK.balance, LEDGER_NANOS.OPEN_BLOCK.previous, LEDGER_NANOS.OPEN_BLOCK.representative)\r
.receive(LEDGER_NANOS.OPEN_BLOCK.link, LEDGER_NANOS.OPEN_BLOCK.balance)\r
assert.equal(block.hash, LEDGER_NANOS.OPEN_BLOCK.hash)\r
- await wallet.sign(0, block)\r
+\r
+ await assert.resolves(click(\r
+ 'Sign Ledger-derived block using BIP-44 wallet',\r
+ async () => wallet.sign(0, block)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
+\r
assert.equal(block.signature, LEDGER_NANOS.OPEN_BLOCK.signature)\r
assert.ok(await block.verify(account.publicKey))\r
- await wallet.destroy()\r
+ await assert.resolves(wallet.destroy())\r
})\r
\r
// skip since nano.org send block sample has receive block work difficulty\r
assert.equal(Ledger.status, 'DISCONNECTED')
assert.equal(status, 'DISCONNECTED')
- await assert.rejects(async () => {
- await click(
- 'Unlock device, quit Nano app, then click to continue',
- async () => wallet.unlock()
- )
- })
+ await assert.rejects(click(
+ 'Unlock device, quit Nano app, then click to continue',
+ async () => wallet.unlock()
+ ))
assert.equal(wallet.isLocked, true)
assert.equal(Ledger.status, 'BUSY')
assert.equal(status, 'BUSY')
}, 6000)
})
- await assert.rejects(async () => {
- await click(
- 'Open Nano app on device, allow device to auto-lock, then click to continue',
- async () => wallet.unlock()
- )
- })
+ await assert.rejects(click(
+ 'Open Nano app on device, allow device to auto-lock, then click to continue',
+ async () => wallet.unlock()
+ ))
assert.equal(wallet.isLocked, true)
assert.equal(Ledger.status, 'LOCKED')
}, 6000)
})
- await assert.resolves(async () => {
- await click(
- 'Unlock device, verify Nano app is open, then click to continue',
- async () => wallet.unlock()
- )
- })
+ await assert.resolves(click(
+ 'Unlock device, verify Nano app is open, then click to continue',
+ async () => wallet.unlock()
+ ))
assert.equal(wallet.isLocked, false)
assert.equal(Ledger.status, 'CONNECTED')
assert.equal(status, 'CONNECTED')
}, 90000)
})
- await assert.resolves(async () => {
- await click(
- 'Unlock device again, then click to continue',
- async () => { }
- )
- })
+ await assert.resolves(click(
+ 'Unlock device again, then click to continue',
+ async () => { }
+ ))
assert.equal(wallet.isLocked, false)
assert.equal(Ledger.status, 'CONNECTED')
assert.equal(status, 'CONNECTED')
})
await test('switch between interfaces', { skip: false || isNode || navigator?.usb == null }, async () => {
- await assert.resolves(async () => {
- await click(
- 'Verify current interface is HID, switch to unlocked Bluetooth device, then click to continue',
- async () => wallet.config({ connection: 'ble' })
- )
- })
+ await assert.resolves(click(
+ 'Verify current interface is HID, switch to unlocked Bluetooth device, then click to continue',
+ async () => wallet.config({ connection: 'ble' })
+ ))
assert.equal(wallet.isLocked, true)
assert.equal(Ledger.status, 'BUSY')
- await assert.resolves(async () => {
- await click(
- 'Verify current interface is BLE, switch back to unlocked USB device, then click to continue',
- async () => wallet.config({ connection: 'usb' })
- )
- })
+ await assert.resolves(click(
+ 'Verify current interface is BLE, switch back to unlocked USB device, then click to continue',
+ async () => wallet.config({ connection: 'usb' })
+ ))
assert.equal(wallet.isLocked, false)
assert.equal(Ledger.status, 'CONNECTED')
- await assert.resolves(async () => {
- await click(
- 'Verify current interface is USB, then click to continue',
- async () => wallet.config({ connection: 'hid' })
- )
- })
+ await assert.resolves(click(
+ 'Verify current interface is USB, then click to continue',
+ async () => wallet.config({ connection: 'hid' })
+ ))
assert.equal(wallet.isLocked, false)
assert.equal(Ledger.status, 'CONNECTED')
})
\r
'use strict'\r
\r
-import { Account, Block, Rpc, Tools, Wallet } from 'libnemo'\r
-import { assert, env, suite, test } from './GLOBALS.mjs'\r
+import { Rpc, Tools, Wallet } from 'libnemo'\r
+import { assert, click, env, suite, test } from './GLOBALS.mjs'\r
import { MAX_RAW, MAX_SUPPLY, NANO_TEST_VECTORS } from './VECTORS.mjs'\r
\r
const rpc = new Rpc(env?.NODE_URL ?? '', env?.API_KEY_NAME)\r
\r
await Promise.all([\r
- suite('unit conversion tests', async () => {\r
+ suite('Tools unit conversion tests', async () => {\r
\r
await test('convert nano to raw', async () => {\r
const result = Tools.convert('1', 'NANO', 'RAW')\r
})\r
}),\r
\r
- suite('signature tests', async () => {\r
+ suite('Tools signature tests', async () => {\r
const m = 'bug-libnemo@'\r
const n = 'codecow.com'\r
\r
- await test('should sign data with a single parameter', async () => {\r
- const result = Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, n)\r
+ await test('sign data with a single parameter', async () => {\r
+ let result = ''\r
+\r
+ await assert.resolves(click(\r
+ 'Sign single string',\r
+ async () => {\r
+ result = Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, n)\r
+ }\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
+\r
assert.equal(result.toLowerCase(), '176469a6bea176bc060889225e2d35c484bee7bcfbe32658645de446236121fa2d2dd90e4c895c5fc3e7e9328752fb1e15195cea54964ef5cfe81a6a56dadb02')\r
})\r
\r
- await test('should sign data with multiple parameters', async () => {\r
- const result = Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, m, n)\r
+ await test('sign data with multiple parameters', async () => {\r
+ let result = ''\r
+\r
+ await assert.resolves(click(\r
+ 'Sign multiple strings',\r
+ async () => {\r
+ result = Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, m, n)\r
+ }\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
+\r
assert.equal(result.toLowerCase(), '0ceebbc0b9b3a270a30bad1eef4eddc0931effd8d5c0b7cab007fcc61f30d3ee9760cd93d7eeb5b654ecc0d9d17bc7a6d53bee54aa163a8272f425fb1184e30e')\r
})\r
\r
- await test('should verify a signature using the public key', async () => {\r
+ await test('verify a signature using the public key', async () => {\r
const result = Tools.verify(NANO_TEST_VECTORS.PUBLIC_0, '176469a6bea176bc060889225e2d35c484bee7bcfbe32658645de446236121fa2d2dd90e4c895c5fc3e7e9328752fb1e15195cea54964ef5cfe81a6a56dadb02', n)\r
assert.equal(result, true)\r
\r
'use strict'\r
\r
import { Account, Block, Tools, Wallet } from 'libnemo'\r
-import { assert, env, suite, test } from './GLOBALS.mjs'\r
-import { MAX_RAW, MAX_SUPPLY, NANO_TEST_VECTORS } from './VECTORS.mjs'\r
+import { assert, click, suite, test } from './GLOBALS.mjs'\r
+import { NANO_TEST_VECTORS } from './VECTORS.mjs'\r
\r
await Promise.all([\r
\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
const data = crypto.randomUUID()\r
- const signature = await wallet.sign(0, data)\r
+ let signature = ''\r
\r
+ await assert.resolves(click(\r
+ 'Sign arbitrary string',\r
+ async () => {\r
+ signature = await wallet.sign(0, data)\r
+ }\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
assert.ok(await Tools.verify(account.publicKey, signature, data))\r
await assert.resolves(wallet.destroy())\r
})\r
const account = await wallet.account()\r
const sendBlock = await new Block(account.address, '5618869000000000000000000000000', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou')\r
.send('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p', '2000000000000000000000000000000')\r
- .sign(wallet, 0)\r
+\r
+ await assert.resolves(click(\r
+ 'Sign to test subsequent verification success',\r
+ async () => sendBlock.sign(wallet, 0)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
\r
assert.ok(await sendBlock.verify(account.publicKey))\r
await assert.resolves(wallet.destroy())\r
const wallet = await Wallet.load('BIP-44', NANO_TEST_VECTORS.PASSWORD, NANO_TEST_VECTORS.BIP39_SEED)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
const account = await wallet.account()\r
-\r
const sendBlock = await new Block(account.address, '5618869000000000000000000000000', '92BA74A7D6DC7557F3EDA95ADC6341D51AC777A0A6FF0688A5C492AB2B2CB40D', 'nano_1stofnrxuz3cai7ze75o174bpm7scwj9jn3nxsn8ntzg784jf1gzn1jjdkou')\r
.send('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p', '2000000000000000000000000000000')\r
- .sign(wallet, 0)\r
+\r
+ await assert.resolves(click(\r
+ 'Sign to test subsequent verification failure',\r
+ async () => sendBlock.sign(wallet, 0)\r
+ ))\r
+ console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
+ await new Promise(r => setTimeout(r, 6000))\r
+\r
assert.ok(await sendBlock.verify(account.publicKey))\r
\r
const wrongAccount = new Account('nano_1q3hqecaw15cjt7thbtxu3pbzr1eihtzzpzxguoc37bj1wc5ffoh7w74gi6p')\r