Skip to content

Commit 9967033

Browse files
committed
Added the signer request guide
1 parent 8b65b1e commit 9967033

3 files changed

Lines changed: 175 additions & 59 deletions

File tree

docs/docs/developing/preconf-signing.md

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Requesting Proposer Commitment Signatures with Commit Boost
2+
3+
When you create a new validator on the Ethereum network, one of the steps is the generation of a new BLS private key (commonly known as the "validator key" or the "signer key") and its corresponding BLS public key (the "validator pubkey", used as an identifier). Typically this private key will be used by an Ethereum consensus client to sign things such as attestations and blocks for publication on the Beacon chain. These signatures prove that you, as the owner of that private key, approve of the data being signed. However, as general-purpose private keys, they can also be used to sign *other* arbitrary messages not destined for the Beacon chain.
4+
5+
Commit Boost takes advantage of this by offering a standard known as **proposer commitments**. These are arbitrary messages (albeit with some important rules), similar to the kind used on the Beacon chain, that have been signed by one of the owner's private keys. Modules interested in leveraging Commit Boost's proposer commitments can construct their own data in whatever format they like and request that Commit Boost's **signer service** generate a signature for it with a particular private key. The module can then use that signature to verify the data was signed by that user.
6+
7+
Commit Boost supports proposer commitment signatures for both BLS private keys (identified by their public key) and ECDSA private keys (identified by their Ethereum address).
8+
9+
10+
## Rules of Preconfirmation Signatures
11+
12+
Preconfirmation signatures produced by Commit Boost's signer service conform to the following rules:
13+
14+
- Signatures are **unique** to a given EVM chain (identified by its [chain ID](https://chainlist.org/)). Signatures generated for one chain will not work on a different chain.
15+
- Signatures are **unique** to Commit Boost proposer commitments. The signer service **cannot** be used to create signatures that could be used for other applications, such as for attestations on the Beacon chain. While the signer service has access to the same validator private keys used to attest on the Beacon chain, it cannot create signatures that would get you slashed on the Beacon chain.
16+
- Signatures are **unique** to a particular module. One module cannot, for example, request an identical payload as another module and effectively "forge" a signature for the second module; identical payloads from two separate modules will result in two separate signatures.
17+
- The data payload being signed must be a **32-byte array**, typically serializd as a 64-character hex string with an optional `0x` prefix. The value itself is arbitrary, as long as it has meaning to the requester - though it is typically the 256-bit hash of some kind of data.
18+
- If requesting a signature from a BLS key, the resulting signature will be a standard BLS signature (96 bytes in length).
19+
- If requesting a signature from an ECDSA key, the resulting signature will be a standard Ethereum RSV signature (65 bytes in length).
20+
21+
22+
## Configuring a Module for Proposer Commitments
23+
24+
Commit Boost's signer service must be configured prior to launching to expect requests from your module. There are two main parts:
25+
26+
1. An entry for your module into [Commit Boost's configuration file](../get_started/configuration.md#custom-module). This must include a unique ID for your module, the line `type = "commit"`, and include a unique [signing ID](#the-signing-id) for your module. Generally you should provide values for these in your documentation, so your users can reference it when configuring their own Commit Boost node.
27+
28+
2. A JWT secret used by your module to authenticate with the signer in HTTP requests. *{Placeholder for more details on setting this here}*
29+
30+
Once the user has configured both Commit Boost and your module with these settings, your module will be able to authenticate with the signer service and request signatures.
31+
32+
33+
## The Signing ID
34+
35+
Your module's signing ID is a 32-byte value that is used as a unique identifier within the signing process. Preconfirmation signatures incorporate this value along with the data being signed as a way to create signatures that are exclusive to your module, so other modules can't maliciously construct signatures that appear to be from your module. Your module must have this ID incorporated into itself ahead of time, and the user must include this same ID within their Commit Boost configuration file section for your module. Commit Boost does not maintain a global registry of signing IDs, so this is a value you should provide to your users in your documentation.
36+
37+
The Signing ID is decoupled from your module's human-readable name (the `module_id` field in the Commit Boost configuration file) so that any changes to your module name will not invalidate signatures from previous versions. Similarly, if you don't change the module ID but *want* to invalidate previous signatures, you can modify the signing ID and it will do so. Just ensure your users are made aware of the change, so they can update it in their Commit Boost configuration files accordingly.
38+
39+
40+
## Structure of a Signature
41+
42+
The form proposer commitment signatures take depends on the type of signature being requested. BLS signatures take the [standard form](https://eth2book.info/latest/part2/building_blocks/signatures/) (96-byte values). ECDSA (Ethereum EL) signatures take the [standard Ethereum ECDSA `r,s,v` signature form](https://forum.openzeppelin.com/t/sign-it-like-you-mean-it-creating-and-verifying-ethereum-signatures/697). In both cases, the data being signed is a 32-byte hash - the root hash of an SSZ Merkle tree, described below:
43+
44+
<center>
45+
46+
<img src="../res/img/prop_commit_tree.png" width=800>
47+
48+
</center>
49+
50+
where:
51+
52+
- `Request Data` is a 32-byte array that serves as the data you want to sign. This is typically a hash of some more complex data on its own that your module constructs.
53+
54+
- `Signing ID` is your module's 32-byte signing ID. The signer service will load this for your module from its configuration file.
55+
56+
- `Domain` is the 32-byte output of the [compute_domain()](https://eth2book.info/capella/part2/building_blocks/signatures/#domain-separation-and-forks) function in the Beacon specification. The 4-byte domain type in this case is not a standard Beacon domain type, but rather Commit Boost's own domain type: `0x6D6D6F43`.
57+
58+
The data signed in a proposer commitment is the 32-byte root of this tree (the green `Root` box). Note that calculating this will involve calculating the Merkle Root of two separate trees: first the blue data subtree (with the original request data and the signing ID) to establish the blue `Root` value, and then again with a tree created from that value and the `Domain`.
59+
60+
Many languages provide libraries for computing the root of an SSZ Merkle tree, such as [fastssz for Go](https://github.com/ferranbt/fastssz) or [tree_hash for Rust](https://docs.rs/tree_hash/latest/tree_hash/). When verifying proposer commitment signatures, use a library that supports Merkle tree root hashing, the `compute_domain()` operation, and validation for signatures generated by your key of choice.
61+
62+
63+
## Requesting a Proposer Commitment from the Signer
64+
65+
Prior to requesting a signature from the signer service, first ensure that Commit Boost has been [configured](#configuring-a-module-for-proposer-commitments) with your module's signing ID and JWT secret.
66+
67+
The signer service can be accessed by an HTTP API. In Docker mode, this will be within the `cb_signer` container at the `/signer/v1/request_signature` route (for example, using the default port of `20000`, the endpoint will be `http://cb_signer:20000/signer/v1/request_signature`). Submitting a request must be done via the `POST` method.
68+
69+
70+
### Headers
71+
72+
- Set `Content-Type` set to `application/json`.
73+
- Set `Accept` to `application/json`, as responses are quoted strings. Other formats are not currently supported.
74+
- Set `Authorization` to a standard JWT string representing your module's JWT authentication information. For the claims, you can add a `module` claim indicating the human-readable name of your module.
75+
76+
77+
### BLS Proposer Keys
78+
79+
If requesting a signature directly from a proposer pubkey, use the following body specification:
80+
81+
```json
82+
{
83+
"type": "consensus",
84+
"pubkey": "0x1234abcd...",
85+
"object_root": "0x01020304..."
86+
}
87+
```
88+
89+
where:
90+
91+
- `pubkey` is the 48-byte BLS public key, with optional `0x` prefix, of the proposer key that you want to request a signature from.
92+
- `object_root` is the 32-byte data you want to sign, with optional `0x` prefix.
93+
94+
95+
### BLS Proxy Keys
96+
97+
If requesting a signature indirectly from a proposer key via a [proxy key](./commit-module.md#with-a-proxy-key), use the following body specification:
98+
99+
```json
100+
{
101+
"type": "proxy_bls",
102+
"proxy": "0x1234abcd...",
103+
"object_root": "0x01020304..."
104+
}
105+
```
106+
107+
where:
108+
109+
- `proxy` is the 48-byte BLS public key, with optional `0x` prefix, of the proxy key that you want to request a signature from.
110+
- `object_root` is the 32-byte data you want to sign, with optional `0x` prefix.
111+
112+
113+
### ECDSA Proxy Keys
114+
115+
**NOTE:** ECDSA proxy key support is not available when using Dirk.
116+
117+
If requesting a signature indirectly from an Ethereum private key via a [proxy key](./commit-module.md#with-a-proxy-key), use the following body specification:
118+
119+
```json
120+
{
121+
"type": "proxy_ecdsa",
122+
"proxy": "0x1234abcd...",
123+
"object_root": "0x01020304..."
124+
}
125+
```
126+
127+
where:
128+
129+
- `proxy` is the 20-byte Ethereum address of the proxy key, with optional `0x` prefix, of the ECDSA private key that you want to request a signature from.
130+
- `object_root` is the 32-byte data you want to sign, with optional `0x` prefix.
131+
132+
133+
### Response
134+
135+
The response for any of the above will be one of the following, provided in plaintext format (not JSON).
136+
137+
138+
#### `200 OK`
139+
140+
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:
141+
```
142+
"0xa43e623f009e615faa3987368f64d6286a4103de70e9a81d82562c50c91eae2d5d6fb9db9fe943aa8ee42fd92d8210c1149f25ed6aa72a557d74a0ed5646fdd0e8255ec58e3e2931695fe913863ba0cdf90d29f651bce0a34169a6f6ce5b3115"
143+
```
144+
145+
#### `401 Unauthorized`
146+
147+
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.
148+
149+
150+
#### `400 Bad Request`
151+
152+
This can occur in several scenarios:
153+
154+
- You requested an operation while using the Dirk signer mode instead of locally-managed signer mode, but Dirk doesn't support that operation.
155+
- Something went wrong while preparing your request; the error text will provide more information.
156+
157+
158+
#### `502 Bad Gateway`
159+
160+
The signer service is running in Dirk signer mode, but Dirk could not be reached.
161+
162+
163+
#### `404 Not Found`
164+
165+
You either requested a route that doesn't exist, or you requested a signature from a key that does not exist.
166+
167+
168+
#### `429 Too Many Requests`
169+
170+
Your module attempted and failed JWT authentication too many times recently, and is currently timed out. It cannot make any more requests until the timeout ends.
171+
172+
173+
#### `500 Internal Server Error`
174+
175+
Your request was valid, but something went wrong internally that prevented it from being fulfilled.
56.1 KB
Loading

0 commit comments

Comments
 (0)