diff --git a/yarn-project/cli-wallet/src/cmds/default_mnemonic_guard.test.ts b/yarn-project/cli-wallet/src/cmds/default_mnemonic_guard.test.ts new file mode 100644 index 000000000000..269843d894a1 --- /dev/null +++ b/yarn-project/cli-wallet/src/cmds/default_mnemonic_guard.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from '@jest/globals'; + +import { DEFAULT_TEST_MNEMONIC, assertSafeL1Signer } from './default_mnemonic_guard.js'; + +describe('assertSafeL1Signer', () => { + const implicitDefault = { + mnemonic: DEFAULT_TEST_MNEMONIC, + mnemonicWasExplicit: false, + privateKey: undefined, + allowDefaultMnemonic: false, + }; + + it.each([31337, 1337])('allows the implicit test mnemonic on local chain %i', chainId => { + expect(() => assertSafeL1Signer({ ...implicitDefault, chainId })).not.toThrow(); + }); + + it.each([1, 11155111, 42])('rejects the implicit test mnemonic on non-local chain %i', chainId => { + expect(() => assertSafeL1Signer({ ...implicitDefault, chainId })).toThrow( + `--l1-chain-id ${chainId} is not a local network`, + ); + }); + + it('allows an explicit private key on a non-local chain', () => { + expect(() => assertSafeL1Signer({ ...implicitDefault, chainId: 1, privateKey: '0x1234' })).not.toThrow(); + }); + + it('allows a custom mnemonic on a non-local chain', () => { + expect(() => assertSafeL1Signer({ ...implicitDefault, chainId: 1, mnemonic: 'custom mnemonic' })).not.toThrow(); + }); + + it('allows an explicitly supplied test mnemonic on a non-local chain', () => { + expect(() => assertSafeL1Signer({ ...implicitDefault, chainId: 1, mnemonicWasExplicit: true })).not.toThrow(); + }); + + it('allows an explicit unsafe opt-in on a non-local chain', () => { + expect(() => assertSafeL1Signer({ ...implicitDefault, chainId: 1, allowDefaultMnemonic: true })).not.toThrow(); + }); +}); diff --git a/yarn-project/cli-wallet/src/cmds/default_mnemonic_guard.ts b/yarn-project/cli-wallet/src/cmds/default_mnemonic_guard.ts new file mode 100644 index 000000000000..a08cb45b40be --- /dev/null +++ b/yarn-project/cli-wallet/src/cmds/default_mnemonic_guard.ts @@ -0,0 +1,29 @@ +export const DEFAULT_TEST_MNEMONIC = 'test test test test test test test test test test test junk'; + +const LOCAL_L1_CHAIN_IDS = new Set([1337, 31337]); + +export function assertSafeL1Signer({ + chainId, + privateKey, + mnemonic, + mnemonicWasExplicit, + allowDefaultMnemonic, +}: { + chainId: number; + privateKey?: string; + mnemonic: string; + mnemonicWasExplicit: boolean; + allowDefaultMnemonic: boolean; +}) { + const usesImplicitTestMnemonic = + !privateKey && mnemonic === DEFAULT_TEST_MNEMONIC && !mnemonicWasExplicit && !allowDefaultMnemonic; + if (LOCAL_L1_CHAIN_IDS.has(chainId) || !usesImplicitTestMnemonic) { + return; + } + + throw new Error( + `--l1-chain-id ${chainId} is not a local network, but no --l1-private-key or explicit --mnemonic was provided. ` + + 'Refusing to sign with the public default test mnemonic. Pass --l1-private-key, an explicit --mnemonic, ' + + 'or --i-know-this-uses-the-public-test-mnemonic to proceed.', + ); +} diff --git a/yarn-project/cli-wallet/src/cmds/index.ts b/yarn-project/cli-wallet/src/cmds/index.ts index b9d362bc93e0..5e5bc4b1039f 100644 --- a/yarn-project/cli-wallet/src/cmds/index.ts +++ b/yarn-project/cli-wallet/src/cmds/index.ts @@ -40,6 +40,7 @@ import { createVerboseOption, integerArgParser, } from '../utils/options/index.js'; +import { DEFAULT_TEST_MNEMONIC, assertSafeL1Signer } from './default_mnemonic_guard.js'; function parseWaitForStatus(status: string): TxStatus { switch (status) { @@ -496,11 +497,16 @@ export function injectCommands( .option( '-m, --mnemonic ', 'The mnemonic to use for deriving the Ethereum address that will mint and bridge', - 'test test test test test test test test test test test junk', + DEFAULT_TEST_MNEMONIC, ) .option('--mint', 'Mint the tokens on L1', false) .option('--l1-private-key ', 'The private key to the eth account bridging', PRIVATE_KEY) .addOption(l1ChainIdOption) + .option( + '--i-know-this-uses-the-public-test-mnemonic', + 'Allow the public default test mnemonic on a non-local L1 network', + false, + ) .option('--json', 'Output the claim in JSON format') // `options.wait` is default true. Passing `--no-wait` will set it to false. // https://github.com/tj/commander.js#other-option-types-negatable-boolean-and-booleanvalue @@ -510,10 +516,28 @@ export function injectCommands( .default('60') .conflicts('wait'), ) - .action(async (amount, recipient, options) => { - const { bridgeL1FeeJuice } = await import('./bridge_fee_juice.js'); - const { l1ChainId, l1RpcUrls, l1PrivateKey, mnemonic, mint, json, wait, interval: intervalS } = options; + .action(async (amount, recipient, options, command) => { + const { + l1ChainId, + l1RpcUrls, + l1PrivateKey, + mnemonic, + mint, + json, + wait, + interval: intervalS, + iKnowThisUsesThePublicTestMnemonic, + } = options; + assertSafeL1Signer({ + chainId: l1ChainId, + privateKey: l1PrivateKey, + mnemonic, + mnemonicWasExplicit: command.getOptionValueSource('mnemonic') !== 'default', + allowDefaultMnemonic: iKnowThisUsesThePublicTestMnemonic, + }); + + const { bridgeL1FeeJuice } = await import('./bridge_fee_juice.js'); const [secret, messageLeafIndex] = await bridgeL1FeeJuice( amount, recipient,