Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
/// Releases (spec §45 to §52): their local verification, provider.Verify of
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
/// the Go reference (spec §17, §51, §63 step 10), the sources that deliver
|
|
|
|
|
/// them (provider.ReleaseSource), as release.ts of datekeys-ts, and, since
|
|
|
|
|
/// spec v0.15, the release object (§47.1), drand's JSON as an input of the
|
|
|
|
|
/// caller, a release in the caller's hand (provider.Supplier) and the lookup
|
|
|
|
|
/// of a local release archive (§50).
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
///
|
|
|
|
|
/// [verifyRelease] checks, in the order of the reference and with its
|
|
|
|
|
/// texts: the round against the range of the profile (ERR_DATEKEY_INVALID);
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
/// the chain hash that the release names, if any, against the pinned profile
|
|
|
|
|
/// (ERR_PROFILE_MISMATCH, spec v0.15);
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
/// the round of the release against the expected one (ERR_ROUND_MISMATCH),
|
|
|
|
|
/// before the signature; the length of the signature; the pinned public key
|
|
|
|
|
/// (ERR_UNKNOWN_PROFILE if it is not the canonical encoding of a point); and
|
|
|
|
|
/// the signature (ERR_RELEASE_INVALID): the canonical encoding of a point of
|
|
|
|
|
/// G1 other than the point at infinity (spec §12.2) that verifies as the BLS
|
|
|
|
|
/// signature of the round under the pinned key.
|
|
|
|
|
///
|
|
|
|
|
/// Only the scheme of Quicknet, bls-unchained-g1-rfc9380, is verified, as in
|
|
|
|
|
/// datekeys-ts: a profile of another scheme fails with ERR_UNKNOWN_PROFILE
|
|
|
|
|
/// after the round checks, where the reference would verify it.
|
|
|
|
|
///
|
|
|
|
|
/// The HTTP client is not part of the library: the application supplies a
|
|
|
|
|
/// [ReleaseSource], as OpenOptions.Source in Go.
|
|
|
|
|
///
|
|
|
|
|
/// Not constant time (see bls12381_fp.dart): everything it handles is
|
|
|
|
|
/// public, the signature of a round once drand publishes it.
|
|
|
|
|
library;
|
|
|
|
|
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'dart:convert';
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
|
|
import 'bls12381_curve.dart';
|
|
|
|
|
import 'bls12381_hash.dart';
|
|
|
|
|
import 'bls12381_pairing.dart';
|
|
|
|
|
import 'bytes.dart';
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'cbor.dart';
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
import 'errors.dart';
|
|
|
|
|
import 'ibe.dart';
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'schema.dart';
|
|
|
|
|
import 'sha256.dart';
|
|
|
|
|
import 'source.dart';
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
|
|
|
|
|
/// The drand scheme of Quicknet, the only one this library verifies.
|
|
|
|
|
const quicknetScheme = 'bls-unchained-g1-rfc9380';
|
|
|
|
|
|
|
|
|
|
/// What the verification of a release and the tlock stanza read of a
|
|
|
|
|
/// locally pinned Provider Profile (spec §10, §13): its id, its drand
|
|
|
|
|
/// scheme, its public key, its chain hash and the last round of its range
|
|
|
|
|
/// (spec §15). The Provider Profile of the library implements it.
|
|
|
|
|
abstract interface class PinnedProfile {
|
|
|
|
|
/// profile_id, such as `datekeys:quicknet:v1`.
|
|
|
|
|
String get id;
|
|
|
|
|
|
|
|
|
|
/// The drand scheme, such as `bls-unchained-g1-rfc9380`.
|
|
|
|
|
String get scheme;
|
|
|
|
|
|
|
|
|
|
/// The compressed public key of the drand network.
|
|
|
|
|
Uint8List get publicKey;
|
|
|
|
|
|
|
|
|
|
/// The 32 bytes of the chain hash.
|
|
|
|
|
Uint8List get chainHash;
|
|
|
|
|
|
|
|
|
|
/// The last round whose round time is not after 9999-12-31T23:59:59Z
|
|
|
|
|
/// (spec §15), 0 when there is none.
|
|
|
|
|
int get maxRound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The material that satisfies a round: for drand, the BLS signature of the
|
|
|
|
|
/// round.
|
|
|
|
|
final class Release {
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
/// The release of [round] with [signature], which it copies, and the
|
|
|
|
|
/// chain it names, [chainHash], which it copies too.
|
|
|
|
|
Release(this.round, List<int> signature, {List<int>? chainHash})
|
|
|
|
|
: signature = Uint8List.fromList(signature),
|
|
|
|
|
chainHash = chainHash == null ? null : Uint8List.fromList(chainHash);
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
|
|
|
|
|
/// The round.
|
|
|
|
|
final int round;
|
|
|
|
|
|
|
|
|
|
/// The compressed signature of the round.
|
|
|
|
|
final Uint8List signature;
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
|
|
|
|
|
/// The chain the release names, key 2 of a release object (spec v0.15,
|
|
|
|
|
/// §47.1), or null when it names none, as the answer of a relay and
|
|
|
|
|
/// drand's JSON. [verifyRelease] compares it with the pinned profile.
|
|
|
|
|
final Uint8List? chainHash;
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Verifies a release of [round] locally against the pinned profile [p], as
|
|
|
|
|
/// provider.Verify does. Throws a [DateKeysException]; returns normally when
|
|
|
|
|
/// the release is valid.
|
|
|
|
|
void verifyRelease(PinnedProfile p, int round, Release r) {
|
|
|
|
|
verifiedSignature(p, round, r);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// [verifyRelease], returning the point of the verified signature, for the
|
|
|
|
|
/// decryption of the tlock stanza that follows.
|
|
|
|
|
G1Point verifiedSignature(PinnedProfile p, int round, Release r) {
|
|
|
|
|
if (round < 1 || round > p.maxRound) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.dateKeyInvalid,
|
|
|
|
|
'provider: round $round outside the range of ${p.id}',
|
|
|
|
|
);
|
|
|
|
|
}
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
final chain = r.chainHash;
|
|
|
|
|
if (chain != null && !equalBytes(chain, p.chainHash)) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.profileMismatch,
|
|
|
|
|
'provider: release of chain ${toHex(chain)}, the pinned profile '
|
|
|
|
|
'${p.id} is chain ${toHex(p.chainHash)}',
|
|
|
|
|
);
|
|
|
|
|
}
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
if (r.round != round) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.roundMismatch,
|
|
|
|
|
'provider: release for round ${r.round}, expected $round',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (p.scheme != quicknetScheme) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.unknownProfile,
|
|
|
|
|
'provider: profile ${p.id} uses scheme ${p.scheme}; only '
|
|
|
|
|
'$quicknetScheme releases are verified here',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (r.signature.length != signatureLength) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.releaseInvalid,
|
|
|
|
|
'provider: signature is ${r.signature.length} bytes, $quicknetScheme '
|
|
|
|
|
'uses $signatureLength',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final key = pinnedKey(p.publicKey);
|
|
|
|
|
if (key == null) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.unknownProfile,
|
|
|
|
|
'provider: pinned public key of ${p.id} is not the canonical encoding '
|
|
|
|
|
'of a point of the key group',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final last = _lastVerified;
|
|
|
|
|
if (last != null &&
|
|
|
|
|
last.round == r.round &&
|
|
|
|
|
equalBytes(last.signature, r.signature) &&
|
|
|
|
|
equalBytes(last.publicKey, p.publicKey)) {
|
|
|
|
|
return last.point;
|
|
|
|
|
}
|
|
|
|
|
// kyber decodes the point at infinity as a key, and with it, the point at
|
|
|
|
|
// infinity as a signature verifies (both pairs drop out of kilic's
|
|
|
|
|
// check). Spec §63 step 10 rejects such a signature, and so does this
|
|
|
|
|
// code, with any key: a profile with that key is never pinned (spec
|
|
|
|
|
// §12.1).
|
|
|
|
|
final signature = G1Point.decode(r.signature);
|
|
|
|
|
if (key.isInfinity ||
|
|
|
|
|
signature == null ||
|
|
|
|
|
signature.isInfinity ||
|
|
|
|
|
!_verifies(signature, r.round, key)) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.releaseInvalid,
|
|
|
|
|
'provider: the signature is not a canonical point encoding, or does not '
|
|
|
|
|
'verify as the BLS signature of round ${r.round} under ${p.id}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
_lastVerified = (
|
|
|
|
|
publicKey: Uint8List.fromList(p.publicKey),
|
|
|
|
|
round: r.round,
|
|
|
|
|
signature: Uint8List.fromList(r.signature),
|
|
|
|
|
point: signature,
|
|
|
|
|
);
|
|
|
|
|
return signature;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The last release that verified, under its key: the opening verifies the
|
|
|
|
|
// same release at step 10 and again in the tlock identity of step 11, as Go
|
|
|
|
|
// does, and the second time costs no pairing. Only the BLS check is skipped:
|
|
|
|
|
// the checks before it run every time.
|
|
|
|
|
({Uint8List publicKey, int round, Uint8List signature, G1Point point})?
|
|
|
|
|
_lastVerified;
|
|
|
|
|
|
|
|
|
|
// BLS on G1, as Verify of kyber's sign/bls with NewSchemeOnG1: e(H(m), key)
|
|
|
|
|
// = e(signature, G2), with m = SHA-256(uint64be(round)), the message drand
|
|
|
|
|
// signs for an unchained scheme, hashed to G1 with the DST of RFC 9380.
|
|
|
|
|
bool _verifies(G1Point signature, int round, G2Point key) => pairingCheck([
|
|
|
|
|
(hashToG1(roundIdentity(round), quicknetDst), key),
|
|
|
|
|
(-signature, G2Point.generator),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// The last public key decoded: the pinned key of Quicknet, every time.
|
|
|
|
|
Uint8List? _lastKeyBytes;
|
|
|
|
|
G2Point? _lastKey;
|
|
|
|
|
|
|
|
|
|
/// The point of the pinned public key [bytes], or null when it is not the
|
|
|
|
|
/// canonical encoding of a point of G2 (the point at infinity is returned
|
|
|
|
|
/// as such). The last key decoded is kept, so that the pinned key of a
|
|
|
|
|
/// profile is decoded once.
|
|
|
|
|
G2Point? pinnedKey(List<int> bytes) {
|
|
|
|
|
final last = _lastKeyBytes;
|
|
|
|
|
if (last != null && equalBytes(last, bytes)) return _lastKey;
|
|
|
|
|
final key = G2Point.decode(bytes);
|
|
|
|
|
_lastKeyBytes = Uint8List.fromList(bytes);
|
|
|
|
|
_lastKey = key;
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A source of releases (spec §45 to §50), as provider.ReleaseSource:
|
|
|
|
|
/// [fetch] returns the release of a round of the profile, or throws.
|
|
|
|
|
///
|
|
|
|
|
/// A source that fetches releases over a network (a relay, the Release API
|
|
|
|
|
/// or a cache) verifies each response with [verifyRelease] and discards the
|
|
|
|
|
/// one that fails; when none passes, it throws ERR_RELEASE_UNAVAILABLE,
|
|
|
|
|
/// which the opening reports at step 9. Only a release that the caller
|
|
|
|
|
/// supplies directly gets the codes of step 10 (spec §63 steps 9 and 10).
|
|
|
|
|
/// Whatever a source throws, step 9 reports it with ERR_RELEASE_UNAVAILABLE
|
|
|
|
|
/// and no other code, keeping only its text (spec §76, correction 6): see
|
|
|
|
|
/// [fetchRelease].
|
|
|
|
|
abstract interface class ReleaseSource {
|
|
|
|
|
/// The release of [round] of the profile [p].
|
|
|
|
|
Future<Release> fetch(PinnedProfile p, int round);
|
|
|
|
|
}
|
|
|
|
|
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
/// A source that hands [release] over for any round, unverified, so that
|
|
|
|
|
/// step 10 checks it; without a release, it has none to give
|
|
|
|
|
/// (ERR_RELEASE_UNAVAILABLE). It is a [ReleaseSource], so the opening does
|
|
|
|
|
/// not ask it before the round time; a release in the caller's hand, which
|
|
|
|
|
/// is not compared with the clock (spec v0.15, §63 step 9.c), is a
|
|
|
|
|
/// [ReleaseSupplier] such as [EncodedRelease], given as OpenOptions.release.
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
ReleaseSource suppliedRelease([Release? release]) => _Supplied(release);
|
|
|
|
|
|
|
|
|
|
final class _Supplied implements ReleaseSource {
|
|
|
|
|
_Supplied(this.release);
|
|
|
|
|
|
|
|
|
|
final Release? release;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<Release> fetch(PinnedProfile p, int round) async {
|
|
|
|
|
final r = release;
|
|
|
|
|
if (r == null) {
|
|
|
|
|
throw DateKeysException(
|
|
|
|
|
ErrorCode.releaseUnavailable,
|
|
|
|
|
'release: no release supplied for round $round',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The release of [round] from [source], as step 9 of the opening obtains
|
|
|
|
|
/// it (spec §63): whatever the source throws becomes ERR_RELEASE_UNAVAILABLE,
|
|
|
|
|
/// the one code of that step. A [DateKeysException] of that code is kept as
|
|
|
|
|
/// it is; anything else, of another code or none, is kept as text only:
|
|
|
|
|
/// `capsule: release source: <text>: ERR_RELEASE_UNAVAILABLE`, as
|
|
|
|
|
/// sourceFailure of capsule.Open in Go.
|
|
|
|
|
Future<Release> fetchRelease(
|
|
|
|
|
ReleaseSource source,
|
|
|
|
|
PinnedProfile p,
|
|
|
|
|
int round,
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
) => _step9(() => source.fetch(p, round));
|
|
|
|
|
|
|
|
|
|
// Whatever [body] throws, as sourceFailure of capsule.Open: a
|
|
|
|
|
// DateKeysException of ERR_RELEASE_UNAVAILABLE as it is, anything else as
|
|
|
|
|
// text only.
|
|
|
|
|
Future<T> _step9<T>(Future<T> Function() body) async {
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
try {
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
return await body();
|
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
|
|
|
} on Object catch (e, stack) {
|
|
|
|
|
if (e is DateKeysException && e.code == ErrorCode.releaseUnavailable) {
|
|
|
|
|
rethrow;
|
|
|
|
|
}
|
|
|
|
|
Error.throwWithStackTrace(
|
|
|
|
|
DateKeysException(
|
|
|
|
|
ErrorCode.releaseUnavailable,
|
|
|
|
|
'capsule: release source: $e',
|
|
|
|
|
),
|
|
|
|
|
stack,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
Specification 0.15: the release object, a release in hand and step 9.c
specVersion is 0.15 and testdata is synced with datekeys-go at 3c3e737
(branch v0.15): every file changes its spec field, mutations.json gains the
field source and four cases, and vectors/release.json and releases/ are new
(142 files).
release.dart, as provider of Go at 2eeca40, with its texts: encodeRelease
and decodeRelease of the release object (spec v0.15, 47.1) with the CBOR
profile of the library, its size first, then its type and version, then its
schema; parseRelease, which also reads drand's JSON with the rules of
encoding/json of Go for its three fields; verifyRelease checks the chain hash
a release names, ERR_PROFILE_MISMATCH, before its round and its signature;
ReleaseSupplier, EncodedRelease and supplyRelease, a release in the caller's
hand; and ReleaseArchive, the lookup of a local release archive (50), whose
failures are ERR_RELEASE_UNAVAILABLE at step 9.
open: OpenOptions.release takes a release in hand, exclusive with source. It
is not compared with the clock (step 9.c); Opened.clockBehind reports a clock
behind it, and the step 9 detail is Go's. Step 10 starts with the layers of
the object. A network source keeps its behaviour. Opened.release carries the
chain hash of the pinned profile.
Vectors: mutation_texts.json is regenerated with tool/mutation_go_texts.go,
which now replays each case with its source as testkit does: 149 cases
change only the detail of step 9 ("release supplied by the caller"), the
case "round not reached yet" now opens, and the four new cases are added.
release_vectors.json, the open vectors, and the formats, locator, security
and seal vectors come out the same at 3c3e737 but for their spec field.
tool/release_archive_go_texts.go writes the texts of provider.Archive on
edited archives; tool/release_copy.dart copies release.json, releases/ and
those texts to release.g.dart for the tests compiled to JavaScript.
errors_spec_test reads the spec at the commit of testdata/SOURCE.json rather
than at its tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// The release object (spec v0.15, §47.1)
|
|
|
|
|
|
|
|
|
|
/// The type tag of the release object.
|
|
|
|
|
const releaseTypeTag = 'datekeys-release';
|
|
|
|
|
|
|
|
|
|
/// The schema version of the release object.
|
|
|
|
|
const releaseSchemaVersion = 1;
|
|
|
|
|
|
|
|
|
|
/// The largest release object a reader decodes, in bytes (spec v0.15,
|
|
|
|
|
/// §47.1): the object has no frame, so a larger input is rejected before it
|
|
|
|
|
/// is decoded, with ERR_NON_CANONICAL_CBOR. No valid encoding comes close.
|
|
|
|
|
const maxReleaseObjectSize = 1024;
|
|
|
|
|
|
|
|
|
|
/// The longest signature of a release object: a compressed point of G2.
|
|
|
|
|
/// Quicknet signs with 48 bytes, a point of G1.
|
|
|
|
|
const maxReleaseSignatureLength = 96;
|
|
|
|
|
|
|
|
|
|
/// The largest drand JSON that [parseRelease] reads, in bytes, the bound of
|
|
|
|
|
/// a relay response in provider/drand of Go.
|
|
|
|
|
const maxReleaseJsonSize = 8 << 10;
|
|
|
|
|
|
|
|
|
|
// The keys of the release object, all required.
|
|
|
|
|
const _releaseKeys = 5;
|
|
|
|
|
|
|
|
|
|
void _encodeReleaseWire(CborEncoder e, Release r) {
|
|
|
|
|
e
|
|
|
|
|
..map(_releaseKeys)
|
|
|
|
|
..uint(0)
|
|
|
|
|
..text(releaseTypeTag)
|
|
|
|
|
..uint(1)
|
|
|
|
|
..uint(releaseSchemaVersion)
|
|
|
|
|
..uint(2)
|
|
|
|
|
..bstr(r.chainHash!)
|
|
|
|
|
..uint(3)
|
|
|
|
|
..uint(r.round)
|
|
|
|
|
..uint(4)
|
|
|
|
|
..bstr(r.signature);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DateKeysException _nonCanonical(String context) =>
|
|
|
|
|
DateKeysException(ErrorCode.nonCanonicalCbor, context);
|
|
|
|
|
|
|
|
|
|
// Reads the map with every CDDL rule of the release object, all of them
|
|
|
|
|
// ERR_NON_CANONICAL_CBOR: what each field means against the pinned profile
|
|
|
|
|
// and the DateKey is checked by verifyRelease, at step 10.
|
|
|
|
|
Release _decodeReleaseWire(CborDecoder d) {
|
|
|
|
|
final pairs = d.map(_releaseKeys);
|
|
|
|
|
if (pairs != _releaseKeys) {
|
|
|
|
|
throw _nonCanonical('$pairs keys, want all $_releaseKeys');
|
|
|
|
|
}
|
|
|
|
|
late Uint8List chainHash;
|
|
|
|
|
late int round;
|
|
|
|
|
late Uint8List signature;
|
|
|
|
|
for (var want = 0; want < _releaseKeys; want++) {
|
|
|
|
|
final k = d.key();
|
|
|
|
|
if (k != want) throw _nonCanonical('key $k where key $want was expected');
|
|
|
|
|
inKey(k, () {
|
|
|
|
|
switch (want) {
|
|
|
|
|
case 0:
|
|
|
|
|
d.text(releaseTypeTag.length);
|
|
|
|
|
case 1:
|
|
|
|
|
d.uint(releaseSchemaVersion);
|
|
|
|
|
case 2:
|
|
|
|
|
chainHash = d.bstr(32, 32);
|
|
|
|
|
case 3:
|
|
|
|
|
round = d.uint();
|
|
|
|
|
if (round == 0) throw _nonCanonical('round 0');
|
|
|
|
|
default:
|
|
|
|
|
signature = d.bstr(1, maxReleaseSignatureLength);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
d.endMap();
|
|
|
|
|
return Release(round, signature, chainHash: chainHash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The release object of [r], its chain hash, its round and its signature
|
|
|
|
|
/// (spec v0.15, §47.1), as EncodeRelease of Go. It does not verify the
|
|
|
|
|
/// release: [verifyRelease] does, against the pinned profile. A chain hash
|
|
|
|
|
/// other than 32 bytes, a round outside 1..2^53-1 or a signature outside
|
|
|
|
|
/// 1..96 bytes is ERR_NON_CANONICAL_CBOR.
|
|
|
|
|
Uint8List encodeRelease(Release r) {
|
|
|
|
|
final chain = r.chainHash;
|
|
|
|
|
if (chain == null || chain.length != 32) {
|
|
|
|
|
throw _nonCanonical(
|
|
|
|
|
'provider: release object: chain hash of ${chain?.length ?? 0} bytes, '
|
|
|
|
|
'want 32',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (r.round < 1 || r.round > maxSafeUint) {
|
|
|
|
|
throw _nonCanonical(
|
|
|
|
|
'provider: release object: round ${r.round} outside 1..$maxSafeUint',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (r.signature.isEmpty || r.signature.length > maxReleaseSignatureLength) {
|
|
|
|
|
throw _nonCanonical(
|
|
|
|
|
'provider: release object: signature of ${r.signature.length} bytes '
|
|
|
|
|
'outside 1..$maxReleaseSignatureLength',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final e = CborEncoder();
|
|
|
|
|
_encodeReleaseWire(e, r);
|
|
|
|
|
return e.out();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Decodes a release object (spec v0.15, §47.1) with the layers of spec
|
|
|
|
|
/// §69.1 that it has, as DecodeRelease of Go: its size, 1 to
|
|
|
|
|
/// [maxReleaseObjectSize] bytes; its type and schema version
|
|
|
|
|
/// (ERR_NON_CANONICAL_CBOR, then ERR_UNSUPPORTED_VERSION); its encoding and
|
|
|
|
|
/// schema (ERR_NON_CANONICAL_CBOR). The release names its chain, which
|
|
|
|
|
/// [verifyRelease] checks against the pinned profile at step 10, before the
|
|
|
|
|
/// round and the signature.
|
|
|
|
|
Release decodeRelease(List<int> b) {
|
|
|
|
|
if (b.isEmpty || b.length > maxReleaseObjectSize) {
|
|
|
|
|
throw _nonCanonical(
|
|
|
|
|
'provider: release object of ${b.length} bytes, outside '
|
|
|
|
|
'1..$maxReleaseObjectSize',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return withContext('provider: release object', () {
|
|
|
|
|
checkSchema(b, releaseTypeTag, releaseSchemaVersion);
|
|
|
|
|
late Release r;
|
|
|
|
|
unmarshalCbor(
|
|
|
|
|
b,
|
|
|
|
|
(d) => r = _decodeReleaseWire(d),
|
|
|
|
|
(e) => _encodeReleaseWire(e, r),
|
|
|
|
|
);
|
|
|
|
|
return r;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reads a release that the caller supplies, as ParseRelease of Go: drand's
|
|
|
|
|
/// JSON when its first byte other than a JSON space is `{`, or else a
|
|
|
|
|
/// release object, with [decodeRelease].
|
|
|
|
|
///
|
|
|
|
|
/// drand's JSON is the answer of a relay, `{"round": …, "signature": "…"}`,
|
|
|
|
|
/// with an optional `randomness` that must be the SHA-256 of the signature,
|
|
|
|
|
/// in hexadecimal; other fields are ignored. It does not name its chain, so
|
|
|
|
|
/// the release has no chain hash. Any failure to read it, and an input of
|
|
|
|
|
/// more than [maxReleaseJsonSize] bytes, is ERR_RELEASE_INVALID. It is an
|
|
|
|
|
/// input, never written.
|
|
|
|
|
Release parseRelease(List<int> b) {
|
|
|
|
|
for (final c in b) {
|
|
|
|
|
if (c == 0x20 || c == 0x09 || c == 0x0d || c == 0x0a) continue;
|
|
|
|
|
if (c == 0x7b) return _parseDrandJson(b);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return decodeRelease(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DateKeysException _invalidJson(String context) =>
|
|
|
|
|
DateKeysException(ErrorCode.releaseInvalid, 'provider: drand JSON$context');
|
|
|
|
|
|
|
|
|
|
// The name of a JSON key as encoding/json of Go matches it with the name of
|
|
|
|
|
// a field: without case, with the two letters of Unicode that fold to an
|
|
|
|
|
// ASCII letter of these names, the long s and the Kelvin sign.
|
|
|
|
|
String _foldKey(String k) => k
|
|
|
|
|
.replaceAll(String.fromCharCode(0x017f), 's')
|
|
|
|
|
.replaceAll(String.fromCharCode(0x212a), 'k')
|
|
|
|
|
.toLowerCase();
|
|
|
|
|
|
|
|
|
|
// Reads the JSON of a drand relay, as parseDrandJSON of Go, with the rules
|
|
|
|
|
// of encoding/json for its three fields: keys without case, the last one
|
|
|
|
|
// wins, null unsets round and signature and leaves randomness as it is, a
|
|
|
|
|
// value of another type is an error, and round is an unsigned integer
|
|
|
|
|
// written as such, without a sign, a fraction or an exponent. Its own reader
|
|
|
|
|
// ([_GoJson]), because jsonDecode of Dart does not keep how a number is
|
|
|
|
|
// written, and on the web reads 1000.0 as the int 1000.
|
|
|
|
|
Release _parseDrandJson(List<int> b) {
|
|
|
|
|
if (b.length > maxReleaseJsonSize) {
|
|
|
|
|
throw _invalidJson(
|
|
|
|
|
' of ${b.length} bytes, larger than $maxReleaseJsonSize',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final malformed = _invalidJson(': malformed, or without round or signature');
|
|
|
|
|
final List<(String, _JsonValue)> fields;
|
|
|
|
|
try {
|
|
|
|
|
fields = _GoJson(b).topObject();
|
|
|
|
|
} on FormatException {
|
|
|
|
|
throw malformed;
|
|
|
|
|
}
|
|
|
|
|
String? round;
|
|
|
|
|
String? signature;
|
|
|
|
|
var randomness = '';
|
|
|
|
|
var typeError = false;
|
|
|
|
|
for (final (key, value) in fields) {
|
|
|
|
|
switch (key) {
|
|
|
|
|
case 'round':
|
|
|
|
|
if (value.isNull) {
|
|
|
|
|
round = null;
|
|
|
|
|
} else if (value.number != null &&
|
|
|
|
|
RegExp(r'^(0|[1-9][0-9]*)$').hasMatch(value.number!)) {
|
|
|
|
|
round = value.number;
|
|
|
|
|
} else {
|
|
|
|
|
typeError = true;
|
|
|
|
|
}
|
|
|
|
|
case 'signature':
|
|
|
|
|
if (value.isNull) {
|
|
|
|
|
signature = null;
|
|
|
|
|
} else if (value.string != null) {
|
|
|
|
|
signature = value.string;
|
|
|
|
|
} else {
|
|
|
|
|
typeError = true;
|
|
|
|
|
}
|
|
|
|
|
case 'randomness':
|
|
|
|
|
if (value.string != null) {
|
|
|
|
|
randomness = value.string!;
|
|
|
|
|
} else if (!value.isNull) {
|
|
|
|
|
typeError = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (typeError || round == null || signature == null) throw malformed;
|
|
|
|
|
// A round above 2^53-1 does not fit an int on every platform: it is an
|
|
|
|
|
// unsigned integer of Go up to 2^64-1, and no DateKey has it (spec §58).
|
|
|
|
|
final wide = BigInt.parse(round);
|
|
|
|
|
if (wide > maxUint64) throw malformed;
|
|
|
|
|
if (wide > BigInt.from(maxSafeUint)) {
|
|
|
|
|
throw _invalidJson(
|
|
|
|
|
': round $round above $maxSafeUint, the largest round of a DateKey',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final Uint8List sig;
|
|
|
|
|
try {
|
|
|
|
|
sig = fromHex(signature);
|
|
|
|
|
} on FormatException {
|
|
|
|
|
throw _invalidJson(': signature is not hex');
|
|
|
|
|
}
|
|
|
|
|
if (randomness.isNotEmpty && randomness.toLowerCase() != toHex(sha256(sig))) {
|
|
|
|
|
throw _invalidJson(': randomness does not match the signature');
|
|
|
|
|
}
|
|
|
|
|
return Release(wide.toInt(), sig);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A value of the drand JSON that [_parseDrandJson] reads: null, a string, the
|
|
|
|
|
// literal of a number, or anything else (a boolean, an array or an object).
|
|
|
|
|
final class _JsonValue {
|
|
|
|
|
const _JsonValue({this.isNull = false, this.string, this.number});
|
|
|
|
|
|
|
|
|
|
final bool isNull;
|
|
|
|
|
final String? string;
|
|
|
|
|
final String? number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A reader of JSON with the syntax that encoding/json of Go accepts (RFC
|
|
|
|
|
// 8259, with invalid UTF-8 in strings read as U+FFFD): the members of the top
|
|
|
|
|
// object whose names fold to round, signature or randomness, in order, each
|
|
|
|
|
// one that appears, as Go sets them one after another. It
|
|
|
|
|
// throws a FormatException for anything else than one object between JSON
|
|
|
|
|
// spaces.
|
|
|
|
|
final class _GoJson {
|
|
|
|
|
_GoJson(this._b);
|
|
|
|
|
|
|
|
|
|
final List<int> _b;
|
|
|
|
|
int _i = 0;
|
|
|
|
|
|
|
|
|
|
Never _bad() => throw const FormatException('malformed JSON');
|
|
|
|
|
|
|
|
|
|
void _space() {
|
|
|
|
|
while (_i < _b.length) {
|
|
|
|
|
final c = _b[_i];
|
|
|
|
|
if (c != 0x20 && c != 0x09 && c != 0x0a && c != 0x0d) return;
|
|
|
|
|
_i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _peek() => _i < _b.length ? _b[_i] : -1;
|
|
|
|
|
|
|
|
|
|
void _expect(int c) {
|
|
|
|
|
if (_peek() != c) _bad();
|
|
|
|
|
_i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<(String, _JsonValue)> topObject() {
|
|
|
|
|
_space();
|
|
|
|
|
final out = <(String, _JsonValue)>[];
|
|
|
|
|
_object(out);
|
|
|
|
|
_space();
|
|
|
|
|
if (_i != _b.length) _bad();
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reads an object; with [out], keeps its members of the three names.
|
|
|
|
|
void _object([List<(String, _JsonValue)>? out]) {
|
|
|
|
|
_expect(0x7b);
|
|
|
|
|
_space();
|
|
|
|
|
if (_peek() == 0x7d) {
|
|
|
|
|
_i++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (;;) {
|
|
|
|
|
_space();
|
|
|
|
|
final name = _string();
|
|
|
|
|
_space();
|
|
|
|
|
_expect(0x3a);
|
|
|
|
|
_space();
|
|
|
|
|
final v = _value();
|
|
|
|
|
if (out != null) {
|
|
|
|
|
final k = _foldKey(name);
|
|
|
|
|
if (k == 'round' || k == 'signature' || k == 'randomness') {
|
|
|
|
|
// In the order of the input: Go sets each in turn.
|
|
|
|
|
out.add((k, v));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_space();
|
|
|
|
|
if (_peek() == 0x2c) {
|
|
|
|
|
_i++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
_expect(0x7d);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _array() {
|
|
|
|
|
_expect(0x5b);
|
|
|
|
|
_space();
|
|
|
|
|
if (_peek() == 0x5d) {
|
|
|
|
|
_i++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (;;) {
|
|
|
|
|
_space();
|
|
|
|
|
_value();
|
|
|
|
|
_space();
|
|
|
|
|
if (_peek() == 0x2c) {
|
|
|
|
|
_i++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
_expect(0x5d);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_JsonValue _value() {
|
|
|
|
|
final c = _peek();
|
|
|
|
|
switch (c) {
|
|
|
|
|
case 0x7b:
|
|
|
|
|
_object();
|
|
|
|
|
return const _JsonValue();
|
|
|
|
|
case 0x5b:
|
|
|
|
|
_array();
|
|
|
|
|
return const _JsonValue();
|
|
|
|
|
case 0x22:
|
|
|
|
|
return _JsonValue(string: _string());
|
|
|
|
|
case 0x74:
|
|
|
|
|
_literal('true');
|
|
|
|
|
return const _JsonValue();
|
|
|
|
|
case 0x66:
|
|
|
|
|
_literal('false');
|
|
|
|
|
return const _JsonValue();
|
|
|
|
|
case 0x6e:
|
|
|
|
|
_literal('null');
|
|
|
|
|
return const _JsonValue(isNull: true);
|
|
|
|
|
}
|
|
|
|
|
if (c == 0x2d || (c >= 0x30 && c <= 0x39)) {
|
|
|
|
|
return _JsonValue(number: _number());
|
|
|
|
|
}
|
|
|
|
|
_bad();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _literal(String word) {
|
|
|
|
|
for (final c in word.codeUnits) {
|
|
|
|
|
_expect(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool _digit() {
|
|
|
|
|
final c = _peek();
|
|
|
|
|
return c >= 0x30 && c <= 0x39;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String _number() {
|
|
|
|
|
final start = _i;
|
|
|
|
|
if (_peek() == 0x2d) _i++;
|
|
|
|
|
if (_peek() == 0x30) {
|
|
|
|
|
_i++;
|
|
|
|
|
} else if (_digit()) {
|
|
|
|
|
while (_digit()) {
|
|
|
|
|
_i++;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
_bad();
|
|
|
|
|
}
|
|
|
|
|
if (_peek() == 0x2e) {
|
|
|
|
|
_i++;
|
|
|
|
|
if (!_digit()) _bad();
|
|
|
|
|
while (_digit()) {
|
|
|
|
|
_i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (_peek() == 0x65 || _peek() == 0x45) {
|
|
|
|
|
_i++;
|
|
|
|
|
if (_peek() == 0x2b || _peek() == 0x2d) _i++;
|
|
|
|
|
if (!_digit()) _bad();
|
|
|
|
|
while (_digit()) {
|
|
|
|
|
_i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return String.fromCharCodes(_b.sublist(start, _i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _hex4() {
|
|
|
|
|
if (_i + 4 > _b.length) _bad();
|
|
|
|
|
var v = 0;
|
|
|
|
|
for (var k = 0; k < 4; k++) {
|
|
|
|
|
final c = _b[_i++];
|
|
|
|
|
final d = c >= 0x30 && c <= 0x39
|
|
|
|
|
? c - 0x30
|
|
|
|
|
: (c | 0x20) >= 0x61 && (c | 0x20) <= 0x66
|
|
|
|
|
? (c | 0x20) - 0x61 + 10
|
|
|
|
|
: -1;
|
|
|
|
|
if (d < 0) _bad();
|
|
|
|
|
v = v << 4 | d;
|
|
|
|
|
}
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String _string() {
|
|
|
|
|
_expect(0x22);
|
|
|
|
|
final out = StringBuffer();
|
|
|
|
|
final raw = <int>[];
|
|
|
|
|
void flush() {
|
|
|
|
|
if (raw.isEmpty) return;
|
|
|
|
|
out.write(utf8.decode(raw, allowMalformed: true));
|
|
|
|
|
raw.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
if (_i >= _b.length) _bad();
|
|
|
|
|
final c = _b[_i++];
|
|
|
|
|
if (c == 0x22) break;
|
|
|
|
|
if (c < 0x20) _bad();
|
|
|
|
|
if (c != 0x5c) {
|
|
|
|
|
raw.add(c);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
flush();
|
|
|
|
|
if (_i >= _b.length) _bad();
|
|
|
|
|
final e = _b[_i++];
|
|
|
|
|
switch (e) {
|
|
|
|
|
case 0x22 || 0x5c || 0x2f:
|
|
|
|
|
out.writeCharCode(e);
|
|
|
|
|
case 0x62:
|
|
|
|
|
out.writeCharCode(0x08);
|
|
|
|
|
case 0x66:
|
|
|
|
|
out.writeCharCode(0x0c);
|
|
|
|
|
case 0x6e:
|
|
|
|
|
out.writeCharCode(0x0a);
|
|
|
|
|
case 0x72:
|
|
|
|
|
out.writeCharCode(0x0d);
|
|
|
|
|
case 0x74:
|
|
|
|
|
out.writeCharCode(0x09);
|
|
|
|
|
case 0x75:
|
|
|
|
|
var u = _hex4();
|
|
|
|
|
if (u >= 0xd800 && u < 0xdc00) {
|
|
|
|
|
// A pair of surrogates is one character; a lone one is U+FFFD.
|
|
|
|
|
final save = _i;
|
|
|
|
|
if (_i + 6 <= _b.length && _b[_i] == 0x5c && _b[_i + 1] == 0x75) {
|
|
|
|
|
_i += 2;
|
|
|
|
|
final low = _hex4();
|
|
|
|
|
if (low >= 0xdc00 && low < 0xe000) {
|
|
|
|
|
u = 0x10000 + ((u - 0xd800) << 10) + (low - 0xdc00);
|
|
|
|
|
} else {
|
|
|
|
|
_i = save;
|
|
|
|
|
u = 0xfffd;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
u = 0xfffd;
|
|
|
|
|
}
|
|
|
|
|
} else if (u >= 0xdc00 && u < 0xe000) {
|
|
|
|
|
u = 0xfffd;
|
|
|
|
|
}
|
|
|
|
|
out.writeCharCode(u);
|
|
|
|
|
default:
|
|
|
|
|
_bad();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
flush();
|
|
|
|
|
return out.toString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// A release in the caller's hand (spec v0.15, §49, §63 step 9.c)
|
|
|
|
|
|
|
|
|
|
/// Hands over a release that the caller has in hand, as provider.Supplier of
|
|
|
|
|
/// Go: a release object, drand's JSON that the person saved, or an entry of
|
|
|
|
|
/// a local release archive. It makes no network request, so the opening asks
|
|
|
|
|
/// it for the release without comparing its clock with the round time: a
|
|
|
|
|
/// valid signature proves that the round was published.
|
|
|
|
|
///
|
|
|
|
|
/// [supply] returns the encoding of the release of [round], as it is: a
|
|
|
|
|
/// release object or drand's JSON, which the opening reads with
|
|
|
|
|
/// [parseRelease] and verifies at step 10, with the codes of that step.
|
|
|
|
|
/// Without a release for the round it throws ERR_RELEASE_UNAVAILABLE, the
|
|
|
|
|
/// code of step 9; whatever else it throws is reported at step 9 with that
|
|
|
|
|
/// code alone, keeping only its text ([supplyRelease]).
|
|
|
|
|
abstract interface class ReleaseSupplier {
|
|
|
|
|
/// The encoding of the release of [round] of the profile [p].
|
|
|
|
|
Future<Uint8List> supply(PinnedProfile p, int round);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A release in hand, already read: the bytes of a release object or of
|
|
|
|
|
/// drand's JSON, as provider.Encoded of Go. It supplies itself whatever the
|
|
|
|
|
/// round; step 10 compares its round with the DateKey.
|
|
|
|
|
final class EncodedRelease implements ReleaseSupplier {
|
|
|
|
|
/// The release encoded in [bytes], which it copies.
|
|
|
|
|
EncodedRelease(List<int> bytes) : _bytes = Uint8List.fromList(bytes);
|
|
|
|
|
|
|
|
|
|
final Uint8List _bytes;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<Uint8List> supply(PinnedProfile p, int round) async =>
|
|
|
|
|
Uint8List.fromList(_bytes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The encoding of the release of [round] from [supplier], as step 9 of the
|
|
|
|
|
/// opening obtains it: whatever the supplier throws becomes
|
|
|
|
|
/// ERR_RELEASE_UNAVAILABLE, as [fetchRelease] does for a source.
|
|
|
|
|
Future<Uint8List> supplyRelease(
|
|
|
|
|
ReleaseSupplier supplier,
|
|
|
|
|
PinnedProfile p,
|
|
|
|
|
int round,
|
|
|
|
|
) => _step9(() => supplier.supply(p, round));
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// The local release archive (spec v0.15, §50, informative)
|
|
|
|
|
|
|
|
|
|
/// The type tag of the header of a release archive.
|
|
|
|
|
const releaseArchiveTypeTag = 'datekeys-release-archive';
|
|
|
|
|
|
|
|
|
|
/// The schema version of the header of a release archive.
|
|
|
|
|
const releaseArchiveSchemaVersion = 1;
|
|
|
|
|
|
|
|
|
|
// The bound of the header of an archive: its five keys take at most
|
|
|
|
|
// 1 + 26 + 2 + 35 + 9 + 9 bytes.
|
|
|
|
|
const _maxArchiveHeader = 128;
|
|
|
|
|
|
|
|
|
|
const _archiveKeys = 5;
|
|
|
|
|
|
|
|
|
|
// The header of an archive, as archiveHeader of Go.
|
|
|
|
|
final class _ArchiveHeader {
|
|
|
|
|
Uint8List chainHash = Uint8List(0);
|
|
|
|
|
int first = 0;
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
|
|
void encode(CborEncoder e) {
|
|
|
|
|
e
|
|
|
|
|
..map(_archiveKeys)
|
|
|
|
|
..uint(0)
|
|
|
|
|
..text(releaseArchiveTypeTag)
|
|
|
|
|
..uint(1)
|
|
|
|
|
..uint(releaseArchiveSchemaVersion)
|
|
|
|
|
..uint(2)
|
|
|
|
|
..bstr(chainHash)
|
|
|
|
|
..uint(3)
|
|
|
|
|
..uint(first)
|
|
|
|
|
..uint(4)
|
|
|
|
|
..uint(count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Throws a DateKeysException of the codec, whose text Go prints with its
|
|
|
|
|
// code, or an _ArchiveError, a text of Go without a code.
|
|
|
|
|
void decode(CborDecoder d) {
|
|
|
|
|
final pairs = d.map(_archiveKeys);
|
|
|
|
|
if (pairs != _archiveKeys) {
|
|
|
|
|
throw _ArchiveError('$pairs keys, want all $_archiveKeys');
|
|
|
|
|
}
|
|
|
|
|
for (var want = 0; want < _archiveKeys; want++) {
|
|
|
|
|
final k = d.key();
|
|
|
|
|
if (k != want) {
|
|
|
|
|
throw _ArchiveError('key $k where key $want was expected');
|
|
|
|
|
}
|
|
|
|
|
inKey(k, () {
|
|
|
|
|
switch (want) {
|
|
|
|
|
case 0:
|
|
|
|
|
d.text(releaseArchiveTypeTag.length);
|
|
|
|
|
case 1:
|
|
|
|
|
d.uint(releaseArchiveSchemaVersion);
|
|
|
|
|
case 2:
|
|
|
|
|
chainHash = d.bstr(32, 32);
|
|
|
|
|
case 3:
|
|
|
|
|
first = d.uint();
|
|
|
|
|
default:
|
|
|
|
|
count = d.uint();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
d.endMap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final class _ArchiveError implements Exception {
|
|
|
|
|
_ArchiveError(this.text);
|
|
|
|
|
final String text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A local release archive, the informative format of spec v0.15, §50, as
|
|
|
|
|
/// provider.Archive of Go: a header in deterministic CBOR,
|
|
|
|
|
/// `{0: "datekeys-release-archive", 1: 1, 2: chain_hash, 3: first round,
|
|
|
|
|
/// 4: number of rounds}`, followed by the signatures, so that the one of
|
|
|
|
|
/// round r starts at the end of the header plus (r - first)·n, with n the
|
|
|
|
|
/// length of a signature of the chain, 48 bytes in Quicknet. A round written
|
|
|
|
|
/// as zeros is missing.
|
|
|
|
|
///
|
|
|
|
|
/// Read locally, it is a release in hand: its entry is the release object of
|
|
|
|
|
/// the round, with the chain hash of the header, decoded and verified at
|
|
|
|
|
/// step 10 like any other. A round it lacks, a header it cannot read, an
|
|
|
|
|
/// archive of another chain or of another length, and a failure to read the
|
|
|
|
|
/// [ByteSource], are failures to supply a release: ERR_RELEASE_UNAVAILABLE at
|
|
|
|
|
/// step 9, with the texts of Go. The format has no codes of its own.
|
|
|
|
|
final class ReleaseArchive implements ReleaseSupplier {
|
|
|
|
|
/// The archive that [source] reads by ranges. Nothing is read until
|
|
|
|
|
/// [supply].
|
|
|
|
|
ReleaseArchive(this.source);
|
|
|
|
|
|
|
|
|
|
/// The bytes of the archive.
|
|
|
|
|
final ByteSource source;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<Uint8List> supply(PinnedProfile p, int round) async {
|
|
|
|
|
DateKeysException unavailable(String text) => DateKeysException(
|
|
|
|
|
ErrorCode.releaseUnavailable,
|
|
|
|
|
'provider: release archive: $text',
|
|
|
|
|
);
|
|
|
|
|
final size = source.length;
|
|
|
|
|
final Uint8List head;
|
|
|
|
|
try {
|
|
|
|
|
head = await readRange(
|
|
|
|
|
source,
|
|
|
|
|
0,
|
|
|
|
|
size < _maxArchiveHeader ? size : _maxArchiveHeader,
|
|
|
|
|
);
|
|
|
|
|
} on Object catch (e) {
|
|
|
|
|
throw unavailable('$e');
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
checkSchema(head, releaseArchiveTypeTag, releaseArchiveSchemaVersion);
|
|
|
|
|
} on DateKeysException {
|
|
|
|
|
throw unavailable(
|
|
|
|
|
'not an archive of version $releaseArchiveSchemaVersion',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final h = _ArchiveHeader();
|
|
|
|
|
try {
|
|
|
|
|
h.decode(CborDecoder(head));
|
|
|
|
|
} on DateKeysException catch (e) {
|
|
|
|
|
throw unavailable('its header does not decode: ${e.message}');
|
|
|
|
|
} on _ArchiveError catch (e) {
|
|
|
|
|
throw unavailable('its header does not decode: ${e.text}');
|
|
|
|
|
}
|
|
|
|
|
// The header is the deterministic encoding of what it says: its length
|
|
|
|
|
// is that of the encoding, and the signatures follow it.
|
|
|
|
|
final e = CborEncoder();
|
|
|
|
|
h.encode(e);
|
|
|
|
|
final enc = e.out();
|
|
|
|
|
final shown = enc.length < head.length ? enc.length : head.length;
|
|
|
|
|
if (!equalBytes(enc, Uint8List.sublistView(head, 0, shown))) {
|
|
|
|
|
throw unavailable(
|
|
|
|
|
'its header is not the deterministic encoding of its value',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (!equalBytes(h.chainHash, p.chainHash)) {
|
|
|
|
|
throw unavailable(
|
|
|
|
|
'archive of chain ${toHex(h.chainHash)}, the pinned profile ${p.id} '
|
|
|
|
|
'is chain ${toHex(p.chainHash)}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (h.first == 0 ||
|
|
|
|
|
h.count == 0 ||
|
|
|
|
|
round < h.first ||
|
|
|
|
|
round - h.first >= h.count) {
|
|
|
|
|
throw unavailable(
|
|
|
|
|
'round $round is not in the archive, which holds ${h.count} rounds '
|
|
|
|
|
'from ${h.first}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (p.scheme != quicknetScheme) {
|
|
|
|
|
throw unavailable(
|
|
|
|
|
'profile ${p.id} uses scheme ${p.scheme}; only $quicknetScheme '
|
|
|
|
|
'archives are read here',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const n = signatureLength;
|
|
|
|
|
// count is at most 2^53-1: the product is exact on the VM, and on the
|
|
|
|
|
// web it is inexact only above 2^53, which no source measures.
|
|
|
|
|
if (size != enc.length + h.count * n) {
|
|
|
|
|
throw unavailable(
|
|
|
|
|
'$size bytes, its header announces ${h.count} rounds of $n bytes',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
final Uint8List sig;
|
|
|
|
|
try {
|
|
|
|
|
sig = await readRange(source, enc.length + (round - h.first) * n, n);
|
|
|
|
|
} on Object catch (e) {
|
|
|
|
|
throw unavailable('$e');
|
|
|
|
|
}
|
|
|
|
|
if (sig.every((b) => b == 0)) {
|
|
|
|
|
throw unavailable('round $round is missing: its entry is zeros');
|
|
|
|
|
}
|
|
|
|
|
return encodeRelease(Release(round, sig, chainHash: h.chainHash));
|
|
|
|
|
}
|
|
|
|
|
}
|