Skip to content

The public key response does not say what period the key covers #7

Description

@jwrosewell

The defect

The public key end point returns the key and nothing else. It does not say what period that key covers, so a client cannot tell which identifiers the key it just fetched will verify. It must therefore ask again for every distinct identifier date it sees, and the cache can only ever hit when the very same identifier is verified twice.

This was measured against a running creator on 7 September 2026, not reasoned about.

The request works correctly. Asking for 10 March 2026 (?date=3254400) returns the key created on 8 March, which is the right key, because selection by latest start at or before the date is implemented properly.

The response carries nothing else. It is Content-Type: text/plain and the whole body is the PEM.

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAm/k+dZiFp0xcrIqCywZESYuXphwi7DsvyoilDe7
lkV0RBgNBPGQAFVtcSxkimuoEN8g+ZiAqzVTnwdYb0cs0A==
-----END PUBLIC KEY-----

Nothing in it says that this key covers 8 March to 15 March. The only cache signal is Cache-Control: private,max-age=1800, a fixed thirty minutes chosen by the server and unrelated to how long the key is actually valid.

The cost is that a hundred identifiers spread over a hundred minutes make a hundred requests, even where every one of them is verified by the same key.

The nearest working example, and where it differs

51Did faces the same problem on the same cloud and answers with a schedule as JSON rather than a single key. Read from host/FiftyOne.Pipeline.CloudService/Controllers/FodId/KeyController.cs on main on 7 September 2026.

[
  {
    "startsAt": "2026-03-08T00:00:00.0000000Z",
    "weekStart": "2026-03-08T00:00:00.0000000Z",
    "created": "2026-01-05T11:42:07.0000000Z",
    "publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
  }
]

A client fetches the schedule once and selects locally for any identifier, so one request serves every identifier rather than one request per identifier date. That is the property worth copying. Three details are not, and each is a trap.

  1. It publishes no end date. A key is in force from its own startsAt until the next entry's startsAt, so the end is implied by the neighbour and never stated. If the correction here is to state a start and an end, that is a deliberate departure from the nearest working example rather than a copy of it, and worth settling on purpose.
  2. created is not the start and must not be treated as one. The whole future schedule is written in a single run, so every entry carries the same generation time, months before its own start. The controller's own documentation says to poll with the newest startsAt already held and never the newest created. Anything that models a period from created will be wrong by months.
  3. weekStart is a legacy duplicate of startsAt from when the schedule was fixed at one week. It is marked to be dropped, its only reader is an integration test, and it must not be carried into a new format.

An earlier version of this issue showed this response with only created and publicKey and described created as the date the key came into force. That was read from an older build and was wrong on both counts. Corrected here.

What needs deciding

The correction is a protocol change, so it needs agreeing before any port is edited, and then all seven move together.

  1. Return the period with the key, so the client can key its cache on the period rather than on the identifier's minute. Either the start and end of the period alongside the PEM, or the schedule in the 51Did shape, which also lets a client verify identifiers from several periods without going back.
  2. Keep the existing response available, because creators already answer with a bare PEM and clients already expect one. A new format value, or a content type the client asks for, keeps existing deployments working.
  3. The request still carries the identifier's date, whatever is decided. That is what lets a creator that has rotated return the key current at signing, and it is not what this issue is about.

Not urgent, but not free

The cache in every port is bounded now, so this is request rate and latency rather than memory. It is off the 51Did verification route, where the cloud and the packages pass a key from their own store and never reach this fetch. It does affect anyone verifying a spread of identifiers from a browser or an edge worker, which is the case the caching was there to help.

Where this came from

Found while bounding the .NET cache, which had no limit at all and grew on the domain and date of whatever identifiers were presented to it, fixed in owid-dotnet 15. Bounding the cache does not change the number of requests, which is why this is raised separately. Confirmed against a live creator, and confirmed by James Rosewell on 7 September 2026 as needing correction.

Raised on all six ports that fetch, so the behaviour stays the same in every language. The JavaScript port holds no cache of its own and leans on the browser's, so it is affected by the same missing period through the creator's cache headers rather than through a cache of its own.

Notes

Written with AI assistance and needs human review. Nothing has been changed for this issue. Run book version 1 for the related asynchronous work is at owid-dotnet 17.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions