Skip to content

compiler: reject policy keys invalid in the target script context - #1044

Closed
portlandhodl wants to merge 2 commits into
rust-bitcoin:masterfrom
portlandhodl:2026-09/compiler-context-keys
Closed

portlandhodl wants to merge 2 commits into
rust-bitcoin:masterfrom
portlandhodl:2026-09/compiler-context-keys

Conversation

@portlandhodl

Copy link
Copy Markdown
Collaborator

Supersedes and closes #761.

Concrete::pk(xonly).compile::<Segwitv0>() used to panic (Terminal creation must always succeed, reported in #761). Since the compiler rewrite (ea221af, part of the recent compiler refactor series), it no longer panics — it silently returns Ok with a Miniscript that is invalid for the target context, e.g. wsh(pkh(<32-byte x-only key>)): a fundable address whose coins can never be spent (a 32-byte key is not a valid segwit-v0 ECDSA pubkey, so CHECKSIG can never succeed). Verified end-to-end against Bitcoin Core 26.0 consensus (libbitcoinconsensus): funding succeeds, and the owner's spend with the correct private key and a valid signature is rejected.

The same hole lets uncompressed keys compile under Segwitv0/Tap, and x-only keys under Legacy/Bare.

Fix: call Ctx::check_pk when compiling Concrete::Key fragments — the single point where policy keys become terminals, so multi/multi_a and all public entry points (compile, compile_to_descriptor, compile_tr*) are covered. Invalid keys now produce CompilerError::ContextError, reusing the informative ScriptContextError messages from #760. (CompilerError loses its Copy/Hash derives since ScriptContextError has neither; nothing in-tree relied on them.)

Test: context_invalid_keys covers x-only keys under Segwitv0/Legacy/Bare (Err) and Tap (Ok), the multi path, and uncompressed keys under Segwitv0/Tap (Err) and Legacy (Ok). Adapted from #761; @shesek is credited as co-author.

@apoelstra

Copy link
Copy Markdown
Member

Thanks for finding this! Yeah, this was definitely not an intentional regression.

Can you

  1. Move the regression test to a separate commit
  2. Also check that you can't compile uncompressed keys into segwit v0 policies?

@portlandhodl
portlandhodl force-pushed the 2026-09/compiler-context-keys branch from dc94cfa to 8b93927 Compare September 4, 2026 14:56
Comment thread src/policy/compiler.rs Outdated
}

#[doc(hidden)]
impl From<ScriptContextError> for CompilerError {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In d2b2290:

Please drop this from impl and add a .map_err(CompilerError::ContextError wherever it's used. These From impls hide error conversions behind ?s, making the code harder to read and follow, plus they're an API commitment that doc(hidden) does little to absolve us of. (Users won't avoid this because it's missing from docs ... they'll just type ? and it'll "just work" and they'll expect it to continue working.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the solid reasoning behind this! Easy enough as a change to make. Will be more cognizant of this going forward.

portlandhodl and others added 2 commits September 4, 2026 08:59
Compiling a policy containing a key that is not valid in the target
script context (e.g. an x-only key under Segwitv0/Legacy/Bare, or an
uncompressed key under Segwitv0/Tap) used to panic, and since the
compiler rewrite it silently produced a Miniscript that is invalid for
that context, such as wsh(pkh(xonly)) whose outputs are unspendable.

Check Ctx::check_pk() when compiling Key fragments and return a new
CompilerError::ContextError instead. The check sits at the single point
where keys become terminals, so multi/multi_a fragments and all public
compilation entry points are covered as well.

Assisted-by: Kimi kimi-latest
X-only keys must not compile under Segwitv0/Legacy/Bare (but are fine
under Tap), and uncompressed keys must not compile under Segwitv0/Tap
(but are fine under Legacy). Adapted from the test case in rust-bitcoin#761.

Closes rust-bitcoin#761

Co-authored-by: Nadav Ivgi <nadav@shesek.info>
Assisted-by: Kimi kimi-latest
@portlandhodl
portlandhodl force-pushed the 2026-09/compiler-context-keys branch from 8b93927 to 545c89a Compare September 4, 2026 15:59
@portlandhodl

Copy link
Copy Markdown
Collaborator Author
  • Move the regression test to a separate commit
  • Also check that you can't compile uncompressed keys into segwit v0 policies?
  1. 545c89a
  2. This was already covered in the previous tests but now made more explicit.

@apoelstra

apoelstra commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

Oh, #991 fixes this bug. I'd kinda prefer to close this in favor of that, since this one introduces new usage of ScriptChecks (on its way out) and it specifically checks keys. Vs #991 which does all the "non-toplevel" checks which include a few other things and which can be configured by the user via ValidationParams.

I will cherry-pick your tests (or at least check that they're covered elsewhere).

@portlandhodl

Copy link
Copy Markdown
Collaborator Author

More than fine.

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