Test: MetaMask external-signer transaction signing harness#341
Open
heifner wants to merge 6 commits into
Open
Conversation
End-to-end test that a Wire transaction signed by an external EIP-191 personal_sign wallet (MetaMask) is accepted by nodeop, exercising both the HTTP /v1/chain/send_transaction2 path and clio push transaction. Components under tests/metamask/: - metamask-sign.html: browser page, signs a 32-byte trx digest via personal_sign and surfaces the Wire SIG_EM_/PUB_EM_ forms. - metamask_sim.py: headless eth_account substitute, byte-identical to MetaMask for the same digest; drives unattended CI. - verify_em_sig.py: offline recovery check using the same EIP-191 envelope nodeop applies. - em_sig_to_wire.py: bidirectional converter (Wire EM form is hex, not base58+ripemd160 like K1/R1/BLS). - push_metamask_trx.py: TestHarness orchestrator; expandauth-registers the EM key, builds an unsigned transfer, signs, pushes both ways. Registered as the nonparallelizable ctest metamask_trx_signing_test, which runs the simulator (--simulate) so CI needs no browser. The manual (real-MetaMask) path is hardened against two issues the simulator never hits: an offline pre-flight recovers the pasted signature and checks it against the registered PUB_EM before pushing, so a stale or wrong paste is rejected locally with recovered-vs-expected instead of as an opaque unsatisfied_authorization; and --trx-expiration (default 600s) builds the transfer with a window wide enough for human browser signing, since clio's 30s default expires first.
First-class CLI support for Wire's external-signer key types, built on libfc's own crypto (the same code nodeop runs to validate these signatures), all offline (no node, no wallet): create key --em EM key (Ethereum-style secp256k1, PVT_EM_/PUB_EM_) -- the form a MetaMask / EIP-191 personal_sign wallet uses to sign Wire transactions --sol Solana key (ed25519, PVT_ED_/PUB_ED_) --r1/--em/--sol are mutually exclusive via CLI11 ->excludes() convert em_private_key import a raw Ethereum hex secret (what MetaMask/eth tooling exports) or a PVT_EM_ string, print the Wire PVT_EM_/PUB_EM_ forms em_sign sign a 32-byte sha256 digest with an EM key; dispatches to em::sign_sha256, which applies the EIP-191 personal_sign envelope, byte-identical to MetaMask and to what nodeop recovers; prints SIG_EM_ em_recover recover the PUB_EM_ from a SIG_EM_ over a digest These mirror the existing convert k1_private_key/k1_public_key idiom and share two local parse helpers. clio_em_key_test (offline, parallelizable) covers create-key round-trips and a frozen known-answer vector minted from eth_account, the Ethereum-ecosystem reference that is byte-identical to MetaMask personal_sign. The vector pins libfc's EIP-191 envelope, low-s normalization and recovery-id handling against an independent implementation with no pip/apt/browser dependency; any divergence fails hermetically. Provenance is documented in the test for deliberate, reviewed regeneration.
…k-trx-signing-test # Conflicts: # tests/CMakeLists.txt
The metamask_trx_signing_test ctest failed in CI: it required the Python eth_account stack (eth_account/eth_keys/eth_utils), which the CI test images do not provide -- they have python3-numpy via apt for the performance tests, but the eth stack is not in apt for Ubuntu noble and was never pip-installed. The harness now drives clio's own EM tooling, so its only dependency is the clio binary the build already produces: no pip, no apt, no browser, no .venv. - --simulate keygen: clio create key --em (or convert em_private_key for a fixed --sim-private-key, which now accepts a PVT_EM_ or a raw 0x Ethereum secret) - --simulate sign: clio convert em_sign over the same sha256 sig_digest - manual real-MetaMask pre-flight: clio convert em_recover, compared to the registered PUB_EM_ (replaces verify_em_sig.py) clio's EM path is libfc's own crypto -- the exact code nodeop runs to validate these signatures. Its byte-for-byte equivalence to the Ethereum-ecosystem reference (eth_account / MetaMask personal_sign) is pinned hermetically by the separate clio_em_key_test ctest, so the cross-implementation assurance the eth_account simulator used to provide is preserved without the runtime dependency. Removes metamask_sim.py, verify_em_sig.py, em_sig_to_wire.py and the _dep_python interpreter-resolution machinery. The build/push/balance-assertion flow and both push paths (HTTP send_transaction2, clio push transaction) are unchanged. README updated; metamask-sign.html (real MetaMask manual path) kept. Manual pre-flight now hints that a persistent recover-mismatch may mean the pasted PUB_EM_ is not this wallet's key; drop a dead sys.path entry; README documents the --private-key argv exposure and uses a copy-pasteable digest example.
create key: --k1/--r1/--em/--sol are now mutually exclusive (CLI11 ->excludes()), so any contradictory combination is a non-zero parse error instead of being silently resolved by precedence. em_private_key/em_sign --private-key help: note the secret is visible in ps and shell history; omit the option to enter it at the interactive prompt instead. clio_em_key_test: add a digest-binding negative (the reference signature recovered against a different well-formed digest must yield a different key -- the property the external-signer pre-flight depends on), a 64-char non-hex digest rejection, and the new --k1 exclusion pairs. elliptic_em.hpp: fix the from_native_string doc comment, which described a public key; it takes the raw Ethereum private-key hex.
…k-trx-signing-test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
End-to-end test that a Wire transaction signed by an external EIP-191
personal_signwallet (MetaMask) is accepted bynodeop.Wire's C++ side already supports this:
signature_shim::recover()applies the EIP-191 prefix to asha256trx digest and recovers anem::public_key, and theexpandauth_sign_with_eth_keyunit test proves the in-process flow. This adds the external-signer half: a real browser wallet signing a real trx that gets pushed to a running node, exercised both via HTTP/v1/chain/send_transaction2andclio push transaction.What's added (
tests/metamask/)metamask-sign.html-- static page;personal_signover a 32-byte digest, local ecRecover, surfaces the WireSIG_EM_/PUB_EM_forms. ethers.js pinned with a Subresource Integrity hash.metamask_sim.py-- headlesseth_accountsubstitute, byte-identical to MetaMask for the same digest; drives unattended CI.verify_em_sig.py-- offline recovery check using the same EIP-191 envelopenodeopapplies.em_sig_to_wire.py-- bidirectional converter. Wire's EM form is plain hex (SIG_EM_0x<130hex>/PUB_EM_<130hex>), not base58 + ripemd160 like K1/R1/BLS.push_metamask_trx.py-- TestHarness orchestrator: stands up a single-node cluster,expandauth-registers the EM key, seeds via transfer fromsysio, then signs and pushes two transfers (HTTP then clio), asserting the sender balance dropped by exactly the transfer amount.Registered as the nonparallelizable ctest
metamask_trx_signing_test, which runs--simulateso CI needs no browser.Manual-path robustness
The real-MetaMask path has a human pasting values, so failures are caught offline with actionable messages instead of opaque chain rejections:
PUB_EM_) is cross-checked for mutual consistency beforeexpandauth.PUB_EM_before being sent; a stale or wrong-digest paste is rejected locally with recovered-vs-expected and re-prompted, distinct from a non-recoverable paste, with tooling/env errors fatal rather than an infinite loop.--trx-expiration(default 600s, bounded to the chainmax_transaction_lifetime) gives a human time to sign before the trx expires, since the expiration is baked into the signed bytes.eth_accountinterpreter for the sim / pre-flight subprocesses is auto-resolved (launcher if it has the deps, else$VIRTUAL_ENV, else the repo.venv), failing fast before cluster launch if none qualifies, so a barepython3launch works.