BlockstreamResearch / simfony

Rust-like high-level language that compiles down to Simplicity bytecode. Work in progress.
19 stars 6 forks source link

Do not directly expose check_sig_verify jet #66

Closed roconnor-blockstream closed 1 month ago

roconnor-blockstream commented 1 month ago

The check_sig_verify jet is designed to be used with a Simplicity specific signature scheme. However, we would like to ensure domain separation for typical transactions signatures in Simplicity:

[…] applications should ensure that a signed application message intended for one context is never deemed valid in a different context (e.g., a signed plain text message should never be misinterpreted as a signed Bitcoin transaction, because this could cause unintended loss of funds). This is called "domain separation" and it is typically realized by partitioning the message space. Even if key pairs are intended to be used only within a single context, domain separation is a good idea because it makes it easy to add more contexts later.

As a best practice, we recommend applications to use exactly one of the following methods to pre-process application messages before passing it to the signature scheme:

  • Either, pre-hash the application message using hashname, where name identifies the context uniquely (e.g., "foo-app/signed-bar"),
  • or prefix the actual message with a 33-byte string that identifies the context uniquely (e.g., the UTF-8 encoding of "foo-app/signed-bar", padded with null bytes to 33 bytes).

We choose to use the tagged hash method, hashname, where name is (currently, but will change in the final version) the ASCII string "Simplicity-Draft␟Signature". To that end the check_sig_verify just builds a message from this tagged hash of the CMR of some sighash mode (a Simplicity expression of type A ⊢ 𝟚²⁵⁶) concatenated with the output of that expression.

However, it is impractical for Simplicity itself to enforce that messages conform to this structure consisting of a pair of the CMR of an expression with its output, and so we rely on the higher level language that compile to Simplicity, such as Simfony, to enforce this structure instead.

Ideally, Simfony will provide a primitive construct that compiles to the following expression:

(unit >>> scribe <pubkey>) &&& disconnect iden <hashMode> &&& witness <signature> >>> jet check_sig_verify : A ⊢ 𝟙

where

Only the <pubkey> needs to be supplied at commitment time. I would expect, like witness data, each instance of this primitive construct in a Simfony expression can only appear once, and has some sort of name associated with it. I expect the <hashmode> and <signature> would supplied in the witness JSON file as a specific substructure associated with the construction name. Typically one would have the wallet generate this data; under normal operations we wouldn't want Simfony itself managing cryptographic secrets needed to generate signatures.

(If it simplifies your life, it would be acceptable to restrict "A" to only the unit type, 𝟙, as that is likely to be what is used in practice most often; however it would be better to support any type "A" if it is reasonable to do so.)

Even if Simfony cannot (yet) support disconnect in its full generality, it would be best if we can support this one specific construct.

Let me emphasize that in order to support proper domain-separation of signed data it is critical that Simfony enforce that the check_sig_verify jet is only ever used in the above construction and cannot be used in any other way.

roconnor-blockstream commented 1 month ago

cc @apoelstra