mirror of
https://github.com/hzrd149/blossom.git
synced 2026-10-05 15:38:22 +00:00
BUD-11 merkle tree
This commit is contained in:
+91
@@ -82,6 +82,97 @@ The table below defines, for each endpoint, the required `t` tag action, the imp
|
|||||||
| `PUT /media` | `media` | `X-SHA-256` request header | required |
|
| `PUT /media` | `media` | `X-SHA-256` request header | required |
|
||||||
| `HEAD /media` | `media` | `X-SHA-256` request header | required |
|
| `HEAD /media` | `media` | `X-SHA-256` request header | required |
|
||||||
|
|
||||||
|
## Authorization Header Size Limits
|
||||||
|
|
||||||
|
HTTP servers impose limits on the size of individual request headers. Because the authorization token is base64url-encoded and carried in a single `Authorization` header, the number of `x` tags that can be included in one token is bounded by those limits.
|
||||||
|
|
||||||
|
A baseline kind-24242 event with one `x` tag encodes to approximately **636 bytes** as a header line. Each additional `x` tag (a 64-character hex sha256 hash) adds approximately **97 bytes** after base64url encoding.
|
||||||
|
|
||||||
|
| Server / proxy | Header limit | Max `x` tags |
|
||||||
|
|-----------------------------------------|-------------:|-------------:|
|
||||||
|
| Nginx (`large_client_header_buffers`) | 8,192 B | 78 |
|
||||||
|
| Apache (`LimitRequestFieldSize`) | 8,190 B | 78 |
|
||||||
|
| Cloudflare (max single header) | 16,384 B | 163 |
|
||||||
|
| IIS (`MaxRequestBytes`) | 16,384 B | 163 |
|
||||||
|
| Node.js / undici default | 16,384 B | 163 |
|
||||||
|
|
||||||
|
The conservative safe limit across common deployments is **8,192 bytes**, which allows up to **78 `x` tags** per authorization event.
|
||||||
|
|
||||||
|
Clients SHOULD keep the number of `x` tags in a single authorization event at or below **77** (leaving one slot for the required final blob hash). When an upload requires more blob hashes than this, clients MUST either split the hashes across multiple authorization events or use [Merkle root authorization](#merkle-root-authorization).
|
||||||
|
|
||||||
|
These figures were derived from the script at [`scripts/auth_header_limits.py`](../scripts/auth_header_limits.py).
|
||||||
|
|
||||||
|
## Merkle root authorization
|
||||||
|
|
||||||
|
When a client needs to authorize more blob hashes than can fit in a single `Authorization` header using individual `x` tags (see [Authorization Header Size Limits](#authorization-header-size-limits)), it MAY instead commit to the full set of hashes using a Merkle root.
|
||||||
|
|
||||||
|
> **Status:** This section is `draft`. The construction algorithm and proof format described here must be validated by implementors before being considered stable. Feedback is welcome.
|
||||||
|
|
||||||
|
### When to use Merkle root authorization
|
||||||
|
|
||||||
|
A client SHOULD use Merkle root authorization when the number of blob hashes to authorize exceeds **77**. For 77 or fewer hashes, clients SHOULD use individual `x` tags as defined in [Tag scoping](#tag-scoping).
|
||||||
|
|
||||||
|
### Tree construction
|
||||||
|
|
||||||
|
The Merkle tree MUST be constructed as follows:
|
||||||
|
|
||||||
|
1. **Leaves**: Each leaf value is the blob hash itself (the raw 32-byte SHA-256 digest of the blob content). The blob hashes MUST be sorted in ascending lexicographic order before constructing the tree to ensure a deterministic tree layout.
|
||||||
|
2. **Internal nodes**: Each internal node is computed as `SHA-256(left_child || right_child)`, where `||` denotes concatenation of the two 32-byte child values.
|
||||||
|
3. **Odd number of nodes**: If a level has an odd number of nodes, the last node is promoted unchanged to the next level (not duplicated) to avoid the well-known duplicate-leaf vulnerability.
|
||||||
|
4. **Root**: The root is the single 32-byte value at the top of the tree, hex-encoded as 64 lowercase characters.
|
||||||
|
|
||||||
|
### Authorization event format
|
||||||
|
|
||||||
|
The authorization event MUST include a `merkle` tag containing the hex-encoded Merkle root in place of individual `x` tags. A `merkle` tag and individual `x` tags MUST NOT appear in the same authorization event.
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"kind": 24242,
|
||||||
|
"pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
"created_at": 1772019044,
|
||||||
|
"tags": [
|
||||||
|
["t", "upload"],
|
||||||
|
["expiration", "1740000000"],
|
||||||
|
// A single merkle tag replaces any number of x tags
|
||||||
|
["merkle", "e3b0c44298fc1c149afb4c8996fb92427ae41e4649b934ca495991b7852b855"]
|
||||||
|
],
|
||||||
|
"content": "Upload chunks",
|
||||||
|
"id": "...",
|
||||||
|
"sig": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Merkle proof header
|
||||||
|
|
||||||
|
For each request, the client MUST supply an `X-Merkle-Proof` HTTP header containing the proof that the request's blob hash belongs to the committed set.
|
||||||
|
|
||||||
|
The proof is a JSON array of sibling nodes. Each element is a two-item array `[direction, hex_hash]` where:
|
||||||
|
|
||||||
|
- `direction` is `"l"` (the sibling is the **left** child) or `"r"` (the sibling is the **right** child).
|
||||||
|
- `hex_hash` is the 64-character lowercase hex-encoded SHA-256 sibling hash at that level.
|
||||||
|
|
||||||
|
Nodes are ordered from the **leaf level up to (but not including) the root**.
|
||||||
|
|
||||||
|
```
|
||||||
|
X-Merkle-Proof: [["r","a1b2c3..."],["l","d4e5f6..."],["r","07a8b9..."]]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server validation
|
||||||
|
|
||||||
|
To validate a Merkle proof, the server MUST:
|
||||||
|
|
||||||
|
1. Derive the **leaf value** from the blob hash for this request (taken from the `X-SHA-256` header or the URL path, depending on endpoint). The server MUST re-derive the leaf itself and MUST NOT accept a client-supplied leaf value.
|
||||||
|
2. Set `current = leaf_value`.
|
||||||
|
3. For each `[direction, sibling]` node in the proof, in order:
|
||||||
|
- If `direction` is `"l"`: `current = SHA-256(sibling || current)`
|
||||||
|
- If `direction` is `"r"`: `current = SHA-256(current || sibling)`
|
||||||
|
4. After processing all proof nodes, `current` MUST equal the `merkle` tag value in the authorization event.
|
||||||
|
5. If the computed root does not match, the server MUST reject the request with `401 Unauthorized`.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
A client uploading 200 chunks constructs a Merkle tree over all 200 blob hashes (sorted lexicographically). It embeds the single 64-character root in the `merkle` tag of one signed kind-24242 event. For each `PATCH /upload` request it sends the same `Authorization` header plus an `X-Merkle-Proof` header containing the ~8-node sibling path (⌈log₂ 200⌉ = 8 levels) for that chunk's hash. The server re-derives the leaf from the request's `X-SHA-256` value, walks the proof, and confirms the root matches — authorizing 200 chunks with a single signed event that fits comfortably within the 8 KiB header limit.
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
### Unscoped Tokens
|
### Unscoped Tokens
|
||||||
|
|||||||
Reference in New Issue
Block a user