The languages registry

How languages are published, reviewed, signed and installed with larva languages, and the registry's HTTP API.

Last updated 6 min read Edit on GitHub

On this page

Larva compiles any language that has a language pack: a native library with the language's frontend, its view and its editor services, plus a language.json manifest. The packs installed with Larva come with it. Every other language comes from the languages registry at https://www.antril.org/languages-api/.

Anyone can ask to publish a language. An administrator of antril.org reviews each request by hand before it is published.

Trust: two signatures

Every published pack carries two signatures, and larva checks both before it loads a pack it installed:

  1. The author's signature: the signature the author's key made with larva languages pack --key author.pem, over the manifest. The first time larva installs a language, it remembers the author's key fingerprint for that language id. From then on it only accepts versions signed by that same key.
  2. The registry's signature: the registry object antril.org adds when an administrator approves the request. larva ships with the registry's public key and refuses an installed pack without a valid registry signature.

So a pack must be both approved by antril.org and made by the language's original author.

The manifest of a published pack looks like this. signature is the author's, as today; registry is new:

json
{
  "id": "mylang",
  "name": "MyLang",
  "version": "1.2.0",
  "abi": 1,
  "larva": "0.8.0",
  "entry": "MyLang.dll",
  "extensions": [".ml"],
  "files": { "MyLang.dll": "<sha-256>" },
  "publicKey": "<author's key, base64 SubjectPublicKeyInfo>",
  "fingerprint": "<sha-256 of the author's key>",
  "signature": "<author's ECDSA P-256 signature>",
  "registry": {
    "fingerprint": "<sha-256 of the registry's key>",
    "signature": "<registry's ECDSA P-256 signature>"
  }
}

What each one signs:

  • The author's signature covers the canonical manifest without signature and without registry.
  • The registry's signature covers the canonical manifest without registry, so the author's signature is included.

Both use ECDSA P-256 with SHA-256 and DER-encoded signatures, and both are base64. A key's fingerprint is the lowercase hex SHA-256 of its SubjectPublicKeyInfo bytes.

The canonical form is UTF-8 JSON:

  • every object's keys are sorted by ordinal (byte) comparison;
  • there is no whitespace;
  • numbers keep the text they were written with;
  • strings are escaped the way a strict JSON writer escapes them for safe embedding: " and \ as usual, control characters, and also every non-ASCII character and each of <, >, &, ', + and ` as \uXXXX with uppercase hex.

The + rule matters most: it appears in every base64 key and signature, so + is always written \u002B.

Installing languages

Terminal
larva languages search
larva languages install mylang
larva languages install mylang@1.2.0
larva languages update
larva languages remove mylang

remove keeps the author key larva remembered. remove --forget-key also forgets it, for when an author really changed keys.

Installed languages go to the user's language folder, %LOCALAPPDATA%\Larva\Languages on Windows. The author keys larva remembers are kept in trust.json there.

larva config languages.provider <url> points larva at another registry; it must be signed by a key larva trusts. larva config languages.trust <public key> adds one, for a private registry or for testing.

Publishing a language

  1. Make a key once: larva languages key author.pem. Keep it private, because every version of your language must be signed with it.
  2. Build your pack for each platform you support, one folder per platform named after it: win-x64, win-arm64, linux-x64, linux-arm64, osx-x64 or osx-arm64.
  3. Send the request with your API token from your account page:
Terminal
set HIVE_TOKEN=hive_...
larva languages publish packs/ --key author.pem

larva completes and signs each platform's manifest, checks it, and sends the request. The first request for a language id makes you its owner and pins your key to it; later versions must come from you, signed with the same key. An administrator reviews it on antril.org. larva languages status <request> and your account page show where it stands.

The HTTP API

The root is https://www.antril.org/languages-api/. Larva's tools send Authorization: Bearer <token> where a token is needed. A token is the same API token Hive uses.

Request Answer
GET {root}index.json 200 with { "languages": [ { "id", "name", "description", "author", "homepage", "extensions", "latest" } ] }, only approved languages.
GET {root}{id}/index.json 200 with { "id", "name", "description", "author", "homepage", "extensions", "fingerprint", "publicKey", "versions": [ { "version", "larva", "abi", "published", "packs": [ { "platform", "file", "sha256", "size" } ] } ] }; 404 for an unknown or never-approved language. fingerprint and publicKey are the author's pinned key, and a pack's file is its bare name, {id}-{version}-{platform}.zip. Versions are listed oldest first; latest in the root index is the highest.
GET {root}{id}/{version}/{file} 200, the pack: a zip whose root holds the pack's files and its doubly signed language.json.
POST {root}requests A publication request. The body is multipart/form-data with one part per platform, named after it (win-x64, …), each a zip of that platform's pack folder. Each part is Content-Type: application/zip with the file name {id}-{version}-{platform}.zip, and the zip's entries use / separators. Answers 202 { "id": "<string>", "status": "pending" }. 401 without a valid token. 400 when a pack is invalid, the platforms disagree on id/version, or a platform is unknown. 403 when the id belongs to another user or the author key differs from the language's pinned key. 409 when that version exists or is already pending.
GET {root}requests/{requestId} 200 { "id", "language", "version", "platforms", "status": "pending" | "approved" | "rejected", "reason", "submitted", "reviewed" } for its author (or an administrator); 404 otherwise.

Error bodies are plain text, and the same text goes in the HTTP reason phrase so larva can show it.

When the server accepts a request, a pack must pass these checks:

  • its files match their hashes, and no unlisted code is present;
  • the author signature is valid;
  • abi is one the registry supports;
  • id is 1 to 64 lowercase letters, digits and -, and not a reserved name such as antril;
  • version is exact: major.minor.patch, with an optional - pre-release;
  • the zip holds only relative /-separated paths (no .., no drive letters, no backslashes), and the manifest has no registry yet.

When an administrator approves, the server adds registry to every platform's manifest, signed with the registry key (Languages:SigningKey, a PEM file). It then zips each pack and publishes the version. Without a configured key, approval answers 503 and the request stays pending.

A private registry is a folder with the same layout. larva languages approve <packs> --registry-key registry.pem --into <folder> is the administrator's tool for one: it does what the server does on approval. larva config languages.provider <folder> uses it.

Was this page helpful?

No address is stored; your user name is, if you are signed in.

Esc