-
Notifications
You must be signed in to change notification settings - Fork 63
CBST2-02: Make proposer commitment signatures unique to modules #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…he source" This reverts commit 58c6117.
crates/common/src/config/module.rs
Outdated
| #[serde(rename = "type")] | ||
| pub kind: ModuleKind, | ||
| /// Signing ID for the module to use when requesting signatures | ||
| pub signing_id: Option<B256>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why make it optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also worth creating a type alias for clarity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why make it optional?
So this was a concession I had to make in order to support the other use cases where the signature generation pipeline is used with COMMIT_BOOST_DOMAIN, but not tied to a specific module. For example, proxy key generation will do this. Proxy keys are already stored by module ID and don't have any sense of signing ID, so coupling them to the signing ID as well would either involve storing that as part of their path or adding a lookup from module ID to signing ID to the signer. Either way, any time the signing ID changes (e.g, when previous signatures need to be invalidated), you'd have to regenerate all of the proxy keys. It seemed easier to make this field optional to support the existing calls instead of refactoring all of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if we want to still make it optional internally, it should not be optional in the module configuration. After this PR, modules need to have this set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that makes sense. Fixed in 509dba8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with making it required for commit modules, but I think for PBS Events modules it's useless as they won't sign anything. With that said, it's not a tedious config to set, so I wouldn't mind to leave it required in favor of a cleaner codebase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#338 is going to remove PBS events modules anyway, so assuming that goes in soon-ish then this may end up being OBE?
| pub const SIGNER_JWT_AUTH_FAIL_LIMIT_DEFAULT: u32 = 3; | ||
|
|
||
| /// How long to rate limit the client after auth failures | ||
| pub const SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_ENV: &str = | ||
| "CB_SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS"; | ||
| pub const SIGNER_JWT_AUTH_FAIL_TIMEOUT_SECONDS_DEFAULT: u32 = 5 * 60; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this supposed to be in this PR or does it need to be rebased?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was some incidental cleanup of #310 that landed in here after it was merged. There are a few little non-functional changes related to it. For cleanliness I can pull all of those out and make a separate PR for them if you'd like, no problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep let's do that to keep the PR clean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in PR #346 which has been merged into this now.
crates/common/src/config/signer.rs
Outdated
| let mut seen_jwt_secrets = HashMap::new(); | ||
| let mut seen_signing_ids = HashMap::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we go through modules one by one, then these could just be hashsets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value (the module_id) is used in the error messages that get thrown during duplication detection so the user knows which module entries are conflicting. We can swap to HashSets, just print the offending duplicated value itself, and let the user find it in the config if that's preferable, but listing the modules directly seems like good UX.
crates/common/src/types.rs
Outdated
| #[derive(Default, Debug, TreeHash)] | ||
| pub struct PropCommitSigningInfo { | ||
| pub data: [u8; 32], | ||
| pub module_signing_id: [u8; 32], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
B256 implements TreeHash
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I left these as [u8; 32] because the original SigningData above it used those. Happy to convert everything to B256 though, in fact I'd prefer it if that's ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep let's do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, this is a large enough change that I made it a PR for this PR, see #347.
| pubkey: &BlsPublicKey, | ||
| msg: &T, | ||
| signature: &BlsSignature, | ||
| module_signing_id: Option<&B256>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option here and below can be removed, we only do signatures in the signer module and modules will always need to have the id set after this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the earlier response - the proxy key generator uses it too, and it (currently) doesn't include signing IDs which is why it's an Option in here. Validating the signature in e.g. SignedProxyDelegationBls won't work without it.
| #### `200 OK` | ||
|
|
||
| A successful signing request, with the signature provided as a plaintext quoted hex-encoded string, with a `0x` prefix. For example, the response body would look like: | ||
| ``` | ||
| "0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115" | ||
| ``` | ||
|
|
||
| #### `401 Unauthorized` | ||
|
|
||
| Your module did not provide a JWT string in the request's authorization header, or the JWT string was not configured in the signer service's configuration file as belonging to your module. | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i wonder if this part is actually needed? we do have an OpenAPI schema after all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be lovely, because maintaining it manually is a chore. I'll ping you about it and add the results to this PR once we have them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in ff716a0.
crates/common/src/signature.rs
Outdated
| } | ||
|
|
||
| let signing_data = SigningData { object_root, signing_domain }; | ||
| pub fn compute_signing_root<T: TreeHash>(signing_data: &T) -> [u8; 32] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function should be either renamed or removed, as technically it's just the tree hash and not a signing root
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also included in #347.
Co-authored-by: Manuel Iñaki Bilbao <[email protected]> Co-authored-by: ltitanb <[email protected]>
This is a large change that modifies the ways proposer commitment signatures are generated. Currently, the requesting module isn't embedded into the data being signed so any authorized module can request the signature of any data - including data that might be used or expected by other modules. This PR precludes that possibility by embedding a new special per-module signing ID into the data being signed.
The signing ID is a 32-byte hex string provided in the Commit Boost configuration's
[[modules]]section, per module. Module authors can make it whatever they want, and should publish it within their own documentation so users know which unique value to configure for that module. Since Commit Boost doesn't (and won't) maintain a global registry of such IDs, the onus is on the user to ensure the correct ID is entered for each module. Module authors can then directly bake this into their module code, in their signature validation routines, to confirm that any proposer commitments returned by the signer used the correct ID.Since different modules will have different signing IDs (a condition enforced by the loader logic), this prevents one module from "forging" a signature destined for another one.
Using a discrete signing ID means module authors can change their module ID at any time (such as with a version number bump or a vendor rename) without invalidating previous signatures. Conversely, if they do want to invalidate previous signatures while retaining the module ID, they can do so by simply changing their signing ID and informing their clients to update with the new configuration accordingly.
This also adds new round-trip integration tests that confirm signing works as expected with the new paradigm and cross-module signatures are no longer possible.
Finally, it starts some documentation on how to request proposer commitments from the signer service, including how to request a signature and verify it. See
docs/docs/developing/prop-commit-signing.md.