You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
4.2 KiB
92 lines
4.2 KiB
// The release that the person opening a capsule supplies directly (spec §63
|
|
// step 10): pasted from drand's HTTP API, or taken from the record of an
|
|
// official fixture. The page never fetches it: it links to the drand URL of
|
|
// the round, which the person opens themselves, and verifies what comes back
|
|
// at step 10 like any release the caller supplies (plan of phase 2,
|
|
// decision 4, confirmed by the author on 28-09-2026).
|
|
//
|
|
// Of what is pasted only the round and the signature are read, with the
|
|
// strict reader of drand's JSON of the library (spec v0.16, §47.1), so that
|
|
// the page never reads another round than step 10 would: no name twice, names
|
|
// exact, the round a number of 1 to 2^53 - 1. drand's answer also carries
|
|
// `randomness`, and other drand endpoints carry a public key, a period or a
|
|
// chain hash: none of them is read, because the root of trust is the pinned
|
|
// profile and never a remote input (spec §11, §13).
|
|
//
|
|
// No noble here: this module is part of the page's initial bundle.
|
|
|
|
import { fromHex } from '../dkc/index.ts';
|
|
import { jsonRound, strictJSON } from '../dkc/releaseobject.ts';
|
|
|
|
/** A release as the caller supplies it, before any verification. */
|
|
export interface SuppliedRelease {
|
|
readonly round: number;
|
|
readonly signature: Uint8Array;
|
|
}
|
|
|
|
/** The longest text read as a release: drand's answer is about 230 characters. */
|
|
export const MAX_RELEASE_TEXT = 4096;
|
|
|
|
/** What the person pasted, read as a release, or why it cannot be. */
|
|
export type ReleaseInput =
|
|
| { readonly ok: true; readonly release: SuppliedRelease; readonly form: 'json' | 'hex' }
|
|
| { readonly ok: false; readonly problem: string };
|
|
|
|
/**
|
|
* Reads the text pasted as the release of `round`: drand's JSON answer,
|
|
* `{"round": …, "signature": "…"}`, or a signature alone in hexadecimal,
|
|
* which is then taken as the release of `round`. Nothing is verified here:
|
|
* a round other than `round` or a signature that is not a valid one goes to
|
|
* step 10, which reports it with its normative code.
|
|
*/
|
|
export function parseReleaseText(text: string, round: number): ReleaseInput {
|
|
const s = text.trim();
|
|
if (s === '') return { ok: false, problem: 'Pega la respuesta de drand o la firma de la ronda.' };
|
|
if (s.length > MAX_RELEASE_TEXT) {
|
|
return { ok: false, problem: `El texto pegado tiene ${s.length} caracteres; la respuesta de drand tiene unos 230.` };
|
|
}
|
|
if (s.startsWith('{')) return fromJSON(s);
|
|
if (!/^[0-9a-fA-F]+$/.test(s) || s.length % 2 !== 0) {
|
|
return {
|
|
ok: false,
|
|
problem: 'No es la respuesta de drand (un objeto JSON) ni una firma en hexadecimal (un número par de cifras 0-9 y a-f).',
|
|
};
|
|
}
|
|
return { ok: true, release: { round, signature: fromHex(s) }, form: 'hex' };
|
|
}
|
|
|
|
function fromJSON(s: string): ReleaseInput {
|
|
// JSON text that starts with { and that the strict reader reads is an
|
|
// object without a name twice.
|
|
const members = strictJSON(new TextEncoder().encode(s));
|
|
if (members === undefined) {
|
|
return { ok: false, problem: 'Empieza por { pero no es JSON válido. Copia la respuesta de drand entera.' };
|
|
}
|
|
const member = (name: string) => members.find((m) => m.name === name);
|
|
const r = member('round');
|
|
const round = r === undefined ? undefined : jsonRound(r);
|
|
if (round === undefined) {
|
|
return { ok: false, problem: 'El campo round falta o no es un número entero de ronda.' };
|
|
}
|
|
const signature = member('signature');
|
|
if (signature?.kind !== '"' || !/^(?:[0-9a-fA-F]{2})+$/.test(signature.str)) {
|
|
return { ok: false, problem: 'El campo signature falta o no es hexadecimal.' };
|
|
}
|
|
return { ok: true, release: { round, signature: fromHex(signature.str) }, form: 'json' };
|
|
}
|
|
|
|
/**
|
|
* The drand HTTP API URL of the release of `round` on the network of the
|
|
* chain hash `chainHash` (lowercase hexadecimal), for the person to open:
|
|
* https://api.drand.sh/<chain hash>/public/<round>. The page links to it and
|
|
* never fetches it.
|
|
*/
|
|
export function drandReleaseURL(chainHash: string, round: number): string {
|
|
return `https://api.drand.sh/${chainHash}/public/${round}`;
|
|
}
|
|
|
|
/** The JSON text of a release, as drand writes its round and signature. */
|
|
export function releaseText(round: number, signatureHex: string): string {
|
|
return JSON.stringify({ round, signature: signatureHex });
|
|
}
|