Skip to content

Commit f388a5e

Browse files
authored
docs: update the privval extension CIP to match the expected implementation (#342)
1 parent 63e147e commit f388a5e

File tree

1 file changed

+53
-30
lines changed

1 file changed

+53
-30
lines changed

cips/cip-040.md

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
| cip | 40 |
2-
| - | - |
3-
| title | Privval Interface Extension for Arbitrary Message Signing |
4-
| description | Extends the CometBFT privval interface to support signing arbitrary messages for offchain protocols. |
5-
| author | CHAMI Rachid ([@rach-id](https://github.com/rach-id)), Evan Forbes ([evan-forbes](https://github.com/evan-forbes)) |
6-
| discussions-to | <https://forum.celestia.org/t/cip-40-privval-interface-extension-for-arbitrary-message-signing/2102> |
7-
| status | Draft |
8-
| type | Standards Track |
9-
| category | Interface |
10-
| created | 2025-07-25 |
1+
| cip | 40 |
2+
|----------------|--------------------------------------------------------------------------------------------------------------------|
3+
| title | Privval Interface Extension for Arbitrary Message Signing |
4+
| description | Extends the CometBFT privval interface to support signing arbitrary messages for offchain protocols. |
5+
| author | CHAMI Rachid ([@rach-id](https://github.com/rach-id)), Evan Forbes ([evan-forbes](https://github.com/evan-forbes)) |
6+
| discussions-to | <https://forum.celestia.org/t/cip-40-privval-interface-extension-for-arbitrary-message-signing/2102> |
7+
| status | Review |
8+
| type | Standards Track |
9+
| category | Interface |
10+
| created | 2025-07-25 |
1111

1212
## Abstract
1313

@@ -33,7 +33,7 @@ The following message types SHALL be added to the `privval.Message` interface:
3333
message SignRawBytesRequest {
3434
string chain_id = 1;
3535
bytes raw_bytes = 2;
36-
uint32 unique_id = 3;
36+
string unique_id = 3;
3737
}
3838
3939
message SignedRawBytesResponse {
@@ -66,37 +66,60 @@ message Message {
6666

6767
### Field Specifications
6868

69-
- `chain_id`: The chain identifier to prevent cross-chain signature reuse.
70-
- `raw_bytes`: The digest that is signed over. This can be any data that the consensus node includes, however note that the actual bytes signed over MUST be constructed as `chain_id + unique_id + raw_bytes`.
71-
- `unique_id`: A required uint32 identifier for the specific protocol or message type being signed.
69+
- `chain_id`: The chain identifier to prevent cross-chain signature reuse. It's required as it's used in signing and also routing in KMS implementation.
70+
- `raw_bytes`: It's the data that needs to be signed over. Worth noting that this shouldn't be a digest, it needs to be the actual data, and it's a required field. The sign bytes are constructed as defined in the [sign bytes construction](#sign-bytes-construction) section.
71+
- `unique_id`: A required string identifier for the specific protocol or message type being signed.
7272
- `signature`: The resulting signature bytes from the signing operation.
7373
- `error`: Error information if the signing operation fails.
7474

7575
### Sign Bytes Construction
7676

77-
The actual bytes that are signed MUST be constructed by concatenating:
78-
79-
```text
80-
sign_bytes = chain_id + unique_id + raw_bytes
77+
The actual bytes that are signed MUST be constructed by concatenating the domain separator `"COMET::RAW_BYTES::SIGN"` with the protobuf encoding of the `SignRawBytesRequest`:
78+
79+
```go
80+
// RawBytesSignBytesPrefix defines a domain separator prefix added to raw bytes to ensure the resulting
81+
// signed message can't be confused with a consensus message, which could lead to double signing
82+
const RawBytesSignBytesPrefix = "COMET::RAW_BYTES::SIGN"
83+
84+
// RawBytesMessageSignBytes returns the canonical bytes for signing raw data messages.
85+
// It requires non-empty chainID, uniqueID, and rawBytes to prevent security issues.
86+
// Returns error if any required parameter is empty or if marshaling fails.
87+
func RawBytesMessageSignBytes(chainID, uniqueID string, rawBytes []byte) ([]byte, error) {
88+
if chainID == "" {
89+
return nil, errors.New("chainID cannot be empty")
90+
}
91+
92+
if uniqueID == "" {
93+
return nil, fmt.Errorf("uniqueID cannot be empty")
94+
}
95+
96+
if len(rawBytes) == 0 {
97+
return nil, fmt.Errorf("rawBytes cannot be empty")
98+
}
99+
100+
prefix := []byte(RawBytesSignBytesPrefix)
101+
102+
signRequest := &privval.SignRawBytesRequest{
103+
ChainId: chainID,
104+
RawBytes: rawBytes,
105+
UniqueId: uniqueID,
106+
}
107+
protoBytes, err := protoio.MarshalDelimited(signRequest)
108+
if err != nil {
109+
return nil, err
110+
}
111+
return append(prefix, protoBytes...), nil
112+
}
81113
```
82114

83-
### Encoding Specifications
84-
85-
For sign bytes construction, each component MUST be encoded as UTF-8 byte sequences:
86-
87-
- `chain_id`: UTF-8 encoded string bytes (e.g., "celestia" → [0x63, 0x65, 0x6c, 0x65, 0x73, 0x74, 0x69, 0x61, 0x2d, 0x6d, 0x61, 0x69, 0x6e, 0x6e, 0x65, 0x74])
88-
- `unique_id`: Decimal string representation of uint32 value encoded as UTF-8 bytes (e.g., uint32(123) → "123" → [0x31, 0x32, 0x33])
89-
- `raw_bytes`: Raw byte sequence as-is, no additional encoding
90-
91115
### Implementation Requirements
92116

93117
1. KMS implementations MUST support the new message types for full compatibility.
94118
2. The signing operation MUST use the same cryptographic key as consensus message signing.
95119
3. The chain_id field MUST match the configured chain identifier.
96120
4. Double-signing protection is NOT REQUIRED for raw message signing operations.
97-
5. Convert each component to its byte representation as specified above
98-
6. Concatenate the byte sequences directly without delimiters
99-
7. Sign the resulting byte array
121+
5. Generate the sign bytes as per the [sign bytes construction](#sign-bytes-construction) section.
122+
6. Sign the resulting byte array.
100123

101124
## Rationale
102125

@@ -114,7 +137,7 @@ For sign bytes construction, each component MUST be encoded as UTF-8 byte sequen
114137

115138
## Backwards Compatibility
116139

117-
This proposal is fully backwards compatible. Existing KMS implementations will continue to function normally, as the new message types use previously unused field numbers in the protobuf oneof union. That being said, all repos that import the interface MUST update their implementions to at least use a noop.
140+
This proposal is fully backwards compatible. Existing KMS implementations will continue to function normally, as the new message types use previously unused field numbers in the protobuf oneof union. That being said, all repos that import the interface MUST update their implementations to at least use a noop.
118141

119142
## Security Considerations
120143

0 commit comments

Comments
 (0)