Skip to content

deploy: refuse what a node would not take, and keep keys off the command line - #14

Merged
mjthatch merged 1 commit into
mainfrom
research/deploy-checks
Oct 23, 2026
Merged

mjthatch merged 1 commit into
mainfrom
research/deploy-checks

Conversation

@mjthatch

Copy link
Copy Markdown
Collaborator

Hardening of tools/harness/src/bin/deploy.rs and tools/regtest.sh from the security audit (build/security-audit/). Stacked on the generator-checks PR. The addresses, CMRs and transactions deploy builds are unchanged. It now refuses more, and reads keys from files.

Summary

before after
Bit Machine cells checked against rust-simplicity's cap (2^31 - 1) checked against the consensus cap, 5,242,880; printed
budget not covered the transaction was printed anyway refused (--force to print it)
transaction above 400,000 WU printed refused (--force)
the budget 1-byte length prefixes assumed, a few bytes low the serialized witness stack, as a node computes it
.prog/.wit spends built without executing, silently built without executing, and it says so
an unknown or misspelt flag (--internal_key) ignored: a NUMS address refused
a wrong number of arguments (-internal-key, —internal-key) extra arguments ignored refused
a flag twice, a flag without a value, --internal-key ≠ the secret's key last one wins, or the next flag as its value refused
secret and blinding keys only on the command line --secret-file, --blinding-file (--secret/--blinding still work)
send: sats + fee unchecked add checked
a confidential destination paid explicitly, silently paid explicitly, with a note
a duplicated witness node in .wnames not checked refused by compile and spend

Generated programs today need at most about 1.6 M cells and 365 kWU (22 inputs, the most window_plan accepts), so no current program is refused.

tools/regtest.sh

  • The key-path secret. It is kept in a file, $OUT/keypath.secret, and passed with --secret-file. The file holds SECRET, the one from an earlier run, or a fresh one. It used to be the well-known 0x11…11.
  • Paths. The tools are found from the script's own directory, and every path is quoted.
  • The redirect check. It checks that the sed replaced the output script. It also checks that the node refuses the redirected spend for its script, with Assertion failed inside jet, and not for some other reason.

Intentional behaviour changes

  • A command line with an unknown flag, a missing flag value or a wrong number of arguments now exits with status 1 and prints the usage.
  • deploy spend exits 1 when the budget does not cover the cost, the transaction is above 400,000 WU, or the program is above the cell cap. --force prints the transaction anyway.
  • The spend report adds cells N (cap 5242880). The witness stack size is now exact, a few bytes larger than before.
  • regtest.sh no longer uses the fixed demo secret.

Testing

  • Regtest end to end. tools/regtest.sh ran on Elements 23.3.1 with this deploy:

    • the proof spend was accepted: the report shows witness stack 52649 bytes, covered: true and cells 989264 (cap 5242880), and the transaction weighs 53,304 WU;
    • the redirected proof was refused with non-mandatory-script-verify-flag (Assertion failed inside jet);
    • the second spend was accepted;
    • the key-path spend, using the secret file, was accepted at 721 WU.
  • Flags and arguments. These were refused:

    • an unknown flag, and the same flag given twice;
    • --secret together with --secret-file;
    • --internal-key with another key's secret;
    • one-dash and em-dash flags, as a wrong argument count;
    • a flag followed by another flag.

    These still work: sighash dummy; --secret-file and --secret give the same address.

  • Example. deploy address of examples/multiplier/verifier.uprog still gives CMR bab3e5fa…ae222d55.

  • Lint. cargo fmt --check and cargo clippy --all-targets -D warnings are clean (Rust 1.97.0).

🤖 Generated with Claude Code

…mmand line

deploy spend checked the program with rust-simplicity's Bit Machine,
whose cap on cells is 2^31 - 1, not the consensus cap of 5,242,880, and
printed the transaction even when its own report said the budget was
not covered.  It now refuses a program above the cell cap (before
executing it), a cost the input's budget does not cover, and a
transaction above the standard weight of 400,000 WU; --force builds and
prints it anyway.  The budget is the one a node computes, from the serialized
witness stack (it counted 1-byte length prefixes and was a few bytes
low), the cell bound is printed with the cost, and a spend built from
saved .prog and .wit files says that they were not executed.
Generated programs today need at most about 1.6 M cells and 365 kWU (22
inputs, the most window_plan accepts), so no current one is refused.

Other changes:

- A flag a command does not take is refused: a misspelt
  --internal_key used to give a NUMS address silently.  So are a flag
  given twice, a flag without a value (the next argument starting with
  --), --internal-key with a secret of another key, and a wrong number
  of arguments, which a flag typed with one dash, or with a dash from a
  document, used to be.
- --secret-file and --blinding-file read the keys from files, so that
  they appear in no command line or shell history; --secret and
  --blinding still work.
- send computes sats + fee with a checked add.
- An output to a confidential address is explicit: deploy says so.
- A program argument that names an existing file is read as a file even
  if its name is 64 hex digits.
- compile, and spend from a .uprog, refuse a witness node that occurs
  twice.

tools/regtest.sh keeps the key-path secret in a file, a fresh one (or
the one of an earlier run) instead of the well-known 0x11..11, resolves
the tools from its own directory, quotes its paths, and checks that the redirected spend differs from the
original and is refused by the program ("Assertion failed inside jet"),
not for another reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from research/generator-checks to main October 23, 2026 09:00
@mjthatch
mjthatch merged commit ec9a33a into main Oct 23, 2026
14 checks passed
@mjthatch
mjthatch deleted the research/deploy-checks branch October 23, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants