beckn / beckn/protocol-specifications-v2
CounterSignature spec — circular dependency when digest covers the Ack body that contains the signature
- Dominant language
- No language data
- Stars
- 15
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Description
We are implementing `CounterSignature` support in the ONIX adapter as part of v2.0.0 LTS and have paused on this to seek clarification before shipping.
The spec defines the `CounterSignature` signing string as:
```
(created): {unixTimestamp}
(expires): {unixTimestamp}
digest: BLAKE-512={base64DigestOfAckBody}
(request-digest): BLAKE-512={base64DigestOfInboundRequestBody}
(message-id): {messageId}
```
And the `AckSchema` places the `CounterSignature` inside the Ack response body at `message.ack.signature`.
**The problem — circular dependency:**
The signing string requires `digest` = BLAKE-512 of the Ack body. But the signature itself is embedded inside that same Ack body. This creates a self-referential loop:
> To compute the signature → you need the digest of the Ack body
> To know the Ack body → you need the signature already placed inside it
This cannot be resolved without an unstated convention — for example, computing the digest over the Ack body with the `signature` field absent or empty, then inserting the computed value. The spec does not describe this process, which means two independent implementations could do this differently and fail to interoperate.
**What we believe the intent is:**
The primary semantic purpose of `CounterSignature` is to prove that the receiver received, authenticated, and processed the specific inbound request. That proof is carried by `(request-digest)` — the BLAKE-512 of the inbound request body. This field has no circularity problem because the inbound request body is fixed and available before the Ack is constructed.
If `(request-digest)` is the main field, the `digest` covering the Ack body appears to be an additional binding — but one that introduces the circular problem described above.
**Questions:**
1. **Is the `digest` field (covering the Ack body) intentional, or is `(request-digest)` the primary signed field?** If the intent is only to prove receipt of the inbound request, removing `digest` from the signing string would eliminate the circularity entirely.
2. **If `digest` over the Ack body is intentional, how should the signer construct the Ack body before computing that digest?** Specifically: should the `signature` field be absent, empty, or set to a placeholder when the digest is computed? The verifier needs to apply the exact same transformation to recompute and verify.
3. **Why is the signature placed in the Ack response body rather than in a response header?** Placing it in a header (analogous to how the request `Authorization` header carries the regular signature) would eliminate the circularity entirely, since the header is not part of the body being digested.
We want to implement this correctly and interoperably. A clarification on the intended construction and verification algorithm would unblock our implementation.
---
Contributor guide
Assessment
This issue has not been assessed yet.