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>
v0.11
dev 2 days ago
parent cdd1c89b1e
commit 719a703d84

@ -0,0 +1,397 @@
/// The IBE-CCA of tlock on G2 (spec §63 step 11), for Quicknet
/// (bls-unchained-g1-rfc9380): U lies on G2 and the decryption key of a
/// round is its release signature on G1. It is DecryptCCAonG2 and
/// EncryptCCAonG2 of drand/kyber encrypt/ibe, which tlock.TimeUnlock and
/// tlock.TimeLock call for this scheme, on the curve code of bls12381_*.dart,
/// as ibe.ts of datekeys-ts.
///
/// Differences with DecryptCCAonG2, none of which accepts anything kyber
/// rejects:
/// - the signature and U must be the canonical encoding of a point of their
/// subgroup other than the point at infinity (spec §12.2);
/// - a tlock stanza body is exactly U || V || W of 96 + 16 + 16 bytes (spec
/// §63 step 11), as tlock.BytesToCiphertext requires;
/// - the errors are [IbeException], with a fixed reason and message: none
/// carries sigma, the message, r or any input byte. kyber's error carries
/// the candidate message and r, from which whoever edited a stanza learns
/// the file key.
///
/// H2 hashes the element of GT serialized in the order of kilic/bls12-381,
/// c1 before c0 at every level of the tower (testdata/vectors/
/// tlock_ibe.json). sigma, the masks and a rejected message are wiped once
/// used; the caller wipes the message it gets, a file key. The values held
/// as [BigInt], such as r and the pairing, cannot be wiped.
///
/// Not constant time (see bls12381_fp.dart). Decryption handles public data
/// only: the signature of a round, public once drand publishes it, and the
/// stanza. Encryption does not: sigma and r are secret, and the time this
/// code takes depends on them. The writer of a capsule should run it where
/// that timing cannot be observed.
library;
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
import 'bls12381_curve.dart';
import 'bls12381_fp.dart';
import 'bls12381_hash.dart';
import 'bls12381_pairing.dart';
import 'bls12381_tower.dart';
import 'bytes.dart';
/// The size of a compressed signature of Quicknet, on G1.
const signatureLength = g1ByteLength;
/// The size of U, compressed on G2.
const uLength = g2ByteLength;
/// The size of V and of W in a tlock stanza body.
const tlockBlockLength = 16;
/// The size of a tlock stanza body, U || V || W (spec §63 step 11).
const tlockBodyLength = uLength + 2 * tlockBlockLength;
/// The longest message: the output of SHA-256, as kyber checks.
const maxMessageLength = 32;
final Uint8List _h2Tag = ascii.encode('IBE-H2');
final Uint8List _h3Tag = ascii.encode('IBE-H3');
final Uint8List _h4Tag = ascii.encode('IBE-H4');
/// The iterations of H3 end before 65535, as in kyber (a uint16 counter).
const h3Iterations = 65534;
/// Why a ciphertext does not decrypt.
enum IbeReason {
/// A signature, U, V, W, message or sigma of the wrong length.
length,
/// A signature, U or public key that is not the canonical encoding of a
/// point of its subgroup.
encoding,
/// A signature, U or public key that is the point at infinity.
identity,
/// U is not r·G2: a wrong signature or any edit of U, V or W.
proof,
}
/// A failure of the IBE. Its message is fixed by its [reason] and the
/// lengths at fault, and never carries a value of the computation.
final class IbeException implements Exception {
/// The failure of [reason] with the message `ibe: ` [message].
IbeException(this.reason, String message) : message = 'ibe: $message';
/// Why the operation failed.
final IbeReason reason;
/// The text of the failure, `ibe: …`.
final String message;
@override
String toString() => message;
}
/// A tlock ciphertext: U compressed on G2; V and W as long as the message.
final class TlockCiphertext {
/// The ciphertext of [u], [v] and [w], which it copies.
TlockCiphertext(List<int> u, List<int> v, List<int> w)
: u = Uint8List.fromList(u),
v = Uint8List.fromList(v),
w = Uint8List.fromList(w);
/// U, r·G2 compressed.
final Uint8List u;
/// V, sigma XOR H2.
final Uint8List v;
/// W, the message XOR H4.
final Uint8List w;
}
/// The 576 bytes of an element of GT in the order of kilic/bls12-381, which
/// kyber-bls12381 marshals and H2 hashes: c1 before c0 in Fp12, c2, c1, c0
/// in each Fp6, c1 before c0 in each Fp2, each Fp in 48 big-endian bytes.
Uint8List gtBytes(Fp12 gt) => gt.toBytes();
// SHA-256 of the parts, truncated to n bytes; the digest is wiped.
Uint8List _hashTo(int n, List<List<int>> parts) {
final d = Uint8List.fromList(crypto.sha256.convert(concatBytes(parts)).bytes);
final out = Uint8List.fromList(Uint8List.sublistView(d, 0, n));
d.fillRange(0, d.length, 0);
return out;
}
/// H2: SHA-256("IBE-H2" || GT) truncated to [n] bytes, n at most 32.
Uint8List h2(Fp12 gt, int n) {
final b = gtBytes(gt);
try {
return _hashTo(n, [_h2Tag, b]);
} finally {
b.fillRange(0, b.length, 0);
}
}
/// H4: SHA-256("IBE-H4" || sigma) truncated to [n] bytes, n at most 32.
Uint8List h4(List<int> sigma, int n) => _hashTo(n, [_h4Tag, sigma]);
/// H3, the scalar r of [sigma] and [msg]: with base = SHA-256("IBE-H3" ||
/// sigma || msg), the first d = SHA-256(uint16le(i) || base), i = 1, 2, …,
/// whose top bit cleared makes it a big-endian integer below the order of
/// the scalar field. [iterations] bounds i, for tests; after it, the proof
/// fails, as in kyber.
BigInt h3(List<int> sigma, List<int> msg, {int iterations = h3Iterations}) {
final base = _hashTo(32, [_h3Tag, sigma, msg]);
try {
for (var i = 1; i <= iterations; i++) {
final d = _hashTo(32, [
[i & 0xff, i >> 8],
base,
]);
d[0] >>= 1;
final r = BigInt.parse(toHex(d), radix: 16);
d.fillRange(0, d.length, 0);
if (r < groupOrder) return r;
}
} finally {
base.fillRange(0, base.length, 0);
}
throw IbeException(
IbeReason.proof,
'no scalar r below the order of the group (rejection sampling failed)',
);
}
/// Whether U = [r]·G2, the proof of the IBE-CCA. r = 0 never holds: U is
/// never the point at infinity.
bool proofHolds(BigInt r, G2Point u) =>
r.sign != 0 && G2Point.generator.multiply(r).equals(u);
/// The largest round, 2^53 − 1: a round is exact on the VM and on the web
/// alike.
const maxSafeRound = 9007199254740991;
/// The identity of a round for tlock: SHA-256 of its 8 big-endian bytes
/// (DigestBeacon of drand). Throws a [RangeError] for a round outside
/// 0..2^53−1.
Uint8List roundIdentity(int round) {
if (round < 0 || round > maxSafeRound) {
throw RangeError('ibe: round $round is not a safe non-negative integer');
}
final b = ByteData(8)
..setUint32(0, round ~/ 0x100000000)
..setUint32(4, round % 0x100000000);
return Uint8List.fromList(
crypto.sha256.convert(Uint8List.view(b.buffer)).bytes,
);
}
/// Splits a tlock stanza body into U, V and W; its length must be
/// [tlockBodyLength].
TlockCiphertext ciphertextFromBody(List<int> body) {
if (body.length != tlockBodyLength) {
throw IbeException(
IbeReason.length,
'tlock stanza body of ${body.length} bytes, want $tlockBodyLength',
);
}
return TlockCiphertext(
body.sublist(0, uLength),
body.sublist(uLength, uLength + tlockBlockLength),
body.sublist(uLength + tlockBlockLength),
);
}
/// The tlock stanza body U || V || W of a ciphertext of a 16-byte message.
Uint8List ciphertextToBody(TlockCiphertext ct) {
if (ct.u.length != uLength ||
ct.v.length != tlockBlockLength ||
ct.w.length != tlockBlockLength) {
throw IbeException(
IbeReason.length,
'a tlock stanza body holds U of $uLength bytes and V and W of '
'$tlockBlockLength',
);
}
return concatBytes([ct.u, ct.v, ct.w]);
}
/// The point of the signature [bytes] of a round, or the [IbeException] of
/// its gate: `the signature` of the wrong length, the point at infinity, or
/// not the canonical encoding of a point of G1.
G1Point signaturePoint(List<int> bytes) {
if (bytes.length != signatureLength) {
throw IbeException(
IbeReason.length,
'the signature of ${bytes.length} bytes, want $signatureLength',
);
}
final p = G1Point.decode(bytes);
if (p == null) {
throw IbeException(
IbeReason.encoding,
'the signature is not the canonical encoding of a point of the '
'prime-order subgroup of G1',
);
}
if (p.isInfinity) {
throw IbeException(
IbeReason.identity,
'the signature is the point at infinity',
);
}
return p;
}
// The gate of a point of G2: named [name], "U" or "the public key".
G2Point _g2Gate(List<int> bytes, String name) {
if (bytes.length != g2ByteLength) {
throw IbeException(
IbeReason.length,
'$name of ${bytes.length} bytes, want $g2ByteLength',
);
}
final p = G2Point.decode(bytes);
if (p == null) {
throw IbeException(
IbeReason.encoding,
'$name is not the canonical encoding of a point of the prime-order '
'subgroup of G2',
);
}
if (p.isInfinity) {
throw IbeException(IbeReason.identity, '$name is the point at infinity');
}
return p;
}
Uint8List _xor(List<int> a, List<int> b) {
final out = Uint8List(a.length);
for (var i = 0; i < a.length; i++) {
out[i] = a[i] ^ b[i];
}
return out;
}
/// Decrypts [ct] with the round's [signature], a compressed point of G1,
/// and returns the message; the caller wipes it. Throws an [IbeException]:
/// [IbeReason.length] for a signature, U, V or W of the wrong length (V and
/// W as long as each other, at most 32 bytes); [IbeReason.encoding] or
/// [IbeReason.identity] for a signature or a U that is not the canonical
/// encoding of a point of its subgroup other than the point at infinity;
/// and [IbeReason.proof] when U is not r·G2, which covers a wrong signature
/// and any edit of U, V or W.
Uint8List decryptOnG2(List<int> signature, TlockCiphertext ct) =>
decryptWithSignaturePoint(signaturePoint(signature), ct);
/// [decryptOnG2] with the signature already decoded by [signaturePoint].
Uint8List decryptWithSignaturePoint(G1Point signature, TlockCiphertext ct) {
final u = _g2Gate(ct.u, 'U');
final v = ct.v;
final w = ct.w;
if (v.length != w.length || w.length > maxMessageLength) {
throw IbeException(
IbeReason.length,
'V of ${v.length} bytes and W of ${w.length}, want equal lengths of at '
'most $maxMessageLength',
);
}
Uint8List? mask;
Uint8List? sigma;
Uint8List? msg;
try {
mask = h2(pairing(signature, u), w.length);
sigma = _xor(v, mask);
mask.fillRange(0, mask.length, 0);
mask = h4(sigma, w.length);
msg = _xor(w, mask);
if (!proofHolds(h3(sigma, msg), u)) {
throw IbeException(
IbeReason.proof,
'U is not r·G2: the ciphertext does not decrypt under this signature',
);
}
final out = msg;
msg = null;
return out;
} finally {
sigma?.fillRange(0, sigma.length, 0);
mask?.fillRange(0, mask.length, 0);
msg?.fillRange(0, msg.length, 0);
}
}
/// Encrypts [msg], at most 32 bytes, for the identity [id] under the public
/// key of a Quicknet-style scheme, a compressed point of G2, as
/// EncryptCCAonG2 of kyber with the suite of tlock: Q_id = H(id) on G1 with
/// the DST of RFC 9380, a random sigma, r = H3(sigma, msg), U = r·G2, V =
/// sigma XOR H2(e(Q_id, key)^r) and W = msg XOR H4(sigma). The key passes
/// the gate of the canonical encoding first.
///
/// Not constant time: sigma and r are secret (see the library comment).
TlockCiphertext encryptOnG2(List<int> publicKey, List<int> id, List<int> msg) {
_checkMessage(msg);
final sigma = Uint8List(msg.length);
for (var i = 0; i < sigma.length; i++) {
sigma[i] = _random.nextInt(256);
}
try {
return encryptOnG2WithSigma(publicKey, id, msg, sigma);
} finally {
sigma.fillRange(0, sigma.length, 0);
}
}
/// [encryptOnG2] with a given [sigma], so that the vectors of the Go
/// reference can be reproduced byte for byte. Only for tests: a sigma that
/// is not random and secret gives the message away.
TlockCiphertext encryptOnG2WithSigma(
List<int> publicKey,
List<int> id,
List<int> msg,
List<int> sigma,
) {
_checkMessage(msg);
if (sigma.length != msg.length) {
throw IbeException(
IbeReason.length,
'sigma of ${sigma.length} bytes for a message of ${msg.length}',
);
}
final key = _g2Gate(publicKey, 'the public key');
final r = h3(sigma, msg);
// r = 0 would make U the point at infinity; H3 gives it with probability
// 2^-255, and kyber does not check it either.
if (r.sign == 0) throw IbeException(IbeReason.proof, 'r = 0');
// e(Q_id, key)^r = e(r·Q_id, key): one pairing, no exponentiation in GT.
final rq = hashToG1(id, quicknetDst).multiply(r);
final mask2 = h2(pairing(rq, key), msg.length);
final mask4 = h4(sigma, msg.length);
try {
return TlockCiphertext(
G2Point.generator.multiply(r).toBytes(),
_xor(sigma, mask2),
_xor(msg, mask4),
);
} finally {
mask2.fillRange(0, mask2.length, 0);
mask4.fillRange(0, mask4.length, 0);
}
}
// The source of sigma: the generator of the platform, created on first use.
final Random _random = Random.secure();
void _checkMessage(List<int> msg) {
if (msg.length > maxMessageLength) {
throw IbeException(
IbeReason.length,
'a message of ${msg.length} bytes, want at most $maxMessageLength',
);
}
}

@ -0,0 +1,246 @@
/// Releases (spec §45 to §52): their local verification, provider.Verify of
/// the Go reference (spec §17, §51, §63 step 10), and the sources that
/// deliver them (provider.ReleaseSource), as release.ts of datekeys-ts.
///
/// [verifyRelease] checks, in the order of the reference and with its
/// texts: the round against the range of the profile (ERR_DATEKEY_INVALID);
/// 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;
import 'dart:typed_data';
import 'bls12381_curve.dart';
import 'bls12381_hash.dart';
import 'bls12381_pairing.dart';
import 'bytes.dart';
import 'errors.dart';
import 'ibe.dart';
/// 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 {
/// The release of [round] with [signature], which it copies.
Release(this.round, List<int> signature)
: signature = Uint8List.fromList(signature);
/// The round.
final int round;
/// The compressed signature of the round.
final Uint8List signature;
}
/// 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}',
);
}
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);
}
/// The source of a release that the caller supplies directly, as the
/// official vectors do: it hands [release] over for any round, unverified,
/// so that step 10 checks it; without a release, it has none to give
/// (ERR_RELEASE_UNAVAILABLE).
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,
) async {
try {
return await source.fetch(p, round);
} on Object catch (e, stack) {
if (e is DateKeysException && e.code == ErrorCode.releaseUnavailable) {
rethrow;
}
Error.throwWithStackTrace(
DateKeysException(
ErrorCode.releaseUnavailable,
'capsule: release source: $e',
),
stack,
);
}
}

@ -0,0 +1,148 @@
/// The tlock stanza of OUTER_TIME_AGE (spec §32, §35, §63 step 11), as
/// TimeIdentity and TimeRecipient of agewrap in Go and timeIdentity and
/// timeRecipient of datekeys-ts: the stanza `tlock <round> <chain hash>`
/// whose body wraps the file key with the IBE of ibe.dart for one round of a
/// pinned profile.
///
/// The age header is parsed elsewhere; these functions take the arguments
/// and the body of the one tlock stanza. That the header holds exactly one
/// stanza, of type tlock, is checked by the caller with the stanza rules of
/// agewrap, which also check the arguments as [unwrapTlockStanza] does.
///
/// The errors never copy the text of an error of the IBE: each failure has
/// the fixed message of the Go package and its normative code.
library;
import 'dart:typed_data';
import 'bytes.dart';
import 'errors.dart';
import 'ibe.dart';
import 'release.dart';
String _q(String s) => goQuote(utf8Bytes(s));
/// The file key that the tlock stanza with [args] and [body] wraps for
/// [round] of the pinned profile [p], opened with [release], as Go's
/// NewTimeIdentity and the Unwrap of the identity it returns, once the
/// header is known to hold that one stanza:
/// - the profile, as NewTimeIdentity checks it (ERR_UNKNOWN_PROFILE): the
/// scheme of Quicknet, the only one supported, and a pinned public key
/// that is the canonical encoding of a point of G2 other than the point
/// at infinity;
/// - the arguments, the round and the chain hash of the profile (spec §35),
/// with the codes of step 8: ERR_POLICY_STRUCTURE_MISMATCH for a count
/// other than two, ERR_ROUND_MISMATCH and ERR_PROFILE_MISMATCH;
/// - the release, verified again as at step 10 ([verifyRelease]);
/// - the body, U || V || W of 128 bytes, with U the canonical encoding of a
/// point of G2 other than the point at infinity, and the IBE check r·G ==
/// U (ERR_INTEGRITY).
///
/// The caller wipes the file key it gets.
Uint8List unwrapTlockStanza(
PinnedProfile p,
int round,
Release release,
List<String> args,
List<int> body,
) {
checkTlockProfile(p);
if (args.length != 2) {
throw DateKeysException(
ErrorCode.policyStructureMismatch,
'agewrap: tlock stanza has ${args.length} arguments, want 2',
);
}
final wantRound = '$round';
if (args[0] != wantRound) {
throw DateKeysException(
ErrorCode.roundMismatch,
'agewrap: tlock stanza round ${_q(args[0])}, DateKey round $wantRound',
);
}
final chainHash = toHex(p.chainHash);
if (args[1] != chainHash) {
throw DateKeysException(
ErrorCode.profileMismatch,
'agewrap: tlock stanza chain hash ${_q(args[1])}, pinned profile '
'${p.id} uses $chainHash',
);
}
final signature = verifiedSignature(p, round, release);
if (body.length != tlockBodyLength) {
throw DateKeysException(
ErrorCode.integrity,
'agewrap: tlock stanza body of ${body.length} bytes, want '
'$tlockBodyLength',
);
}
final Uint8List fileKey;
try {
fileKey = decryptWithSignaturePoint(signature, ciphertextFromBody(body));
} on IbeException catch (e) {
throw DateKeysException(ErrorCode.integrity, switch (e.reason) {
IbeReason.encoding =>
'agewrap: U of the tlock stanza is not the canonical encoding of a '
'point of the key group',
IbeReason.identity =>
'agewrap: U of the tlock stanza is the point at infinity',
_ =>
'agewrap: the tlock stanza body does not decrypt under the verified '
'release (IBE check r·G == U)',
});
}
return fileKey;
}
/// The arguments and the body of the tlock stanza that wraps [fileKey] for
/// [round] under the pinned profile [p], of the scheme of Quicknet, as Wrap
/// of Go's TimeRecipient. Its checks and texts are those of
/// NewTimeRecipient: the profile first, then the range of the round.
///
/// Encryption: not constant time, see ibe.dart. The writer, stage 6 of the
/// plan, exports it.
(List<String>, Uint8List) wrapTlockStanza(
PinnedProfile p,
int round,
List<int> fileKey,
) {
checkTlockProfile(p);
if (round < 1 || round > p.maxRound) {
throw DateKeysException(
ErrorCode.dateKeyInvalid,
'agewrap: round $round outside the range of ${p.id}',
);
}
final ct = encryptOnG2(p.publicKey, roundIdentity(round), fileKey);
return (['$round', toHex(p.chainHash)], ciphertextToBody(ct));
}
/// The checks of the profile that Go's NewTimeIdentity and NewTimeRecipient
/// run (pinned of agewrap), with their code, ERR_UNKNOWN_PROFILE: the
/// scheme, here that of Quicknet only, and the pinned public key, the
/// canonical encoding of a point of G2 other than the point at infinity. An
/// identity of OUTER_TIME_AGE runs them when it is built, before it sees the
/// stanzas; [unwrapTlockStanza] and [wrapTlockStanza] run them first.
void checkTlockProfile(PinnedProfile p) {
if (p.scheme != quicknetScheme) {
throw DateKeysException(
ErrorCode.unknownProfile,
'agewrap: profile ${p.id} uses scheme ${p.scheme}; only $quicknetScheme '
'is supported here',
);
}
final key = pinnedKey(p.publicKey);
if (key == null) {
throw DateKeysException(
ErrorCode.unknownProfile,
'agewrap: pinned public key of ${p.id} is not the canonical encoding '
'of a point of the key group',
);
}
if (key.isInfinity) {
throw DateKeysException(
ErrorCode.unknownProfile,
'agewrap: pinned public key of ${p.id} is the identity element',
);
}
}

@ -0,0 +1,51 @@
// Go reference values of the IBE of tlock that the tests running on Node.js
// use: they read no file. Each is copied from the JSON that Go wrote,
// test/vectors/ibe_vectors.json or tlock_vectors.json, and
// ibe_constants_test.dart checks it against that JSON on the VM.
library;
// The encryption of a 16-byte message for round 1000 with a given sigma
// (tlock_vectors.json).
/// The identity of round 1000.
const encrypt1000Id =
'f652498d092acd949bad74e40683bf3824fb817980504a0c7e6722cfc5a9c0a3';
/// The message.
const encrypt1000Msg = '16e27bcb7ff0267cd0db9c1dc1459696';
/// sigma.
const encrypt1000Sigma = '3d4725349d1d12947a3195aa2cb5dd00';
/// U.
const encrypt1000U =
'80364e97a868ee0dcdcf79a71ab7901f97ae9d38069110e5eacf630842857a52685ebaaf'
'41b3e8682664893748443a3f1137cc7e1298814bab8d47d13416083a3b32d224713eaf14'
'1bef1c3775d580a939ffb316a603b68bd3d8799a95feb355';
/// V.
const encrypt1000V = '518c9d924cdd84ef24472ee2dae6c5f5';
/// W.
const encrypt1000W = '3cb104bcb0c445ea0b576a9aa64bfa01';
// H3 of an input whose r is accepted at the third iteration, and H4
// (ibe_vectors.json).
/// sigma of H3.
const h3Sigma = 'fdaa01708026990ff60ed74e8875f1b9';
/// The message of H3.
const h3Msg = 'a2eefa73eaa7028f29dfddf86cb2a2bb';
/// r.
const h3R = '6773be5dbb0ac8cbd3aebe463ff5911eee6d6d7f7b012de9f55382a3e99572da';
/// The iteration that accepts r.
const h3Iterations3 = 3;
/// sigma of H4.
const h4Sigma = '00000000000000000000000000000000';
/// H4 truncated to 16 bytes.
const h4Of16 = 'e98934fb796adfa42b207a1b701a473d';

@ -0,0 +1,49 @@
// The Go values that the tests on Node.js carry in ibe_constants.dart are
// those of the JSON that Go wrote.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'package:test/test.dart';
import 'ibe_constants.dart';
typedef Json = Map<String, Object?>;
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
List<Json> section(Json file, String name) =>
(file[name]! as List).cast<Json>();
void main() {
test('the encryption is the one of tlock_vectors.json', () {
final e = section(
readJson('test/vectors/tlock_vectors.json'),
'encrypt',
).firstWhere((e) => e['name'] == 'a 16-byte message for round 1000');
expect(
[
encrypt1000Id,
encrypt1000Msg,
encrypt1000Sigma,
encrypt1000U,
encrypt1000V,
encrypt1000W,
],
[e['id'], e['msg'], e['sigma'], e['u'], e['v'], e['w']],
);
});
test('H3 and H4 are those of ibe_vectors.json', () {
final ibe = readJson('test/vectors/ibe_vectors.json');
final h3 = section(ibe, 'h3').firstWhere((v) => v['iterations'] == 3);
expect(
[h3Sigma, h3Msg, h3R, h3Iterations3],
[h3['sigma'], h3['msg'], h3['r'], h3['iterations']],
);
final h4 = section(ibe, 'h4').first;
expect([h4Sigma, h4Of16], [h4['sigma'], h4['h4_16']]);
});
}

@ -0,0 +1,209 @@
// The IBE, the tlock stanza and the verification of releases on the VM and
// compiled to JavaScript, with the Go values of ibe_constants.dart: the
// tests that read the vectors of Go run on the VM only (ibe_vectors_test,
// tlock_vectors_test and release_vectors_test). Each case here costs a
// pairing or two, about half a second on Node.js: they are few.
library;
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart' show fromHex, toHex;
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/ibe.dart';
import 'package:datekeys/src/release.dart';
import 'package:datekeys/src/tlock.dart';
import 'package:test/test.dart';
import 'ibe_constants.dart';
import 'tlock_support.dart';
Matcher dateKeysError(ErrorCode code, String message) => throwsA(
isA<DateKeysException>()
.having((e) => e.code, 'code', code)
.having((e) => e.message, 'message', message),
);
Matcher ibeError(IbeReason reason, [String? message]) => throwsA(
isA<IbeException>()
.having((e) => e.reason, 'reason', reason)
.having((e) => e.message, 'message', message ?? anything),
);
void main() {
final p = quicknet();
final sig1000 = fromHex(signature1000);
final sig1001 = fromHex(signature1001);
test('H3, H4 and the identity of a round are those of Go', () {
final sigma = fromHex(h3Sigma);
final msg = fromHex(h3Msg);
expect(h3(sigma, msg).toRadixString(16).padLeft(64, '0'), h3R);
expect(h3(sigma, msg, iterations: h3Iterations3), h3(sigma, msg));
expect(
() => h3(sigma, msg, iterations: h3Iterations3 - 1),
ibeError(
IbeReason.proof,
'ibe: no scalar r below the order of the group (rejection sampling '
'failed)',
),
);
expect(toHex(h4(fromHex(h4Sigma), 16)), h4Of16);
expect(toHex(roundIdentity(1000)), encrypt1000Id);
expect(roundIdentity(0), hasLength(32));
expect(roundIdentity(maxSafeRound), hasLength(32));
expect(() => roundIdentity(-1), throwsRangeError);
});
test('encrypts as Go for a given sigma, and decrypts', () {
final ct = encryptOnG2WithSigma(
p.publicKey,
fromHex(encrypt1000Id),
fromHex(encrypt1000Msg),
fromHex(encrypt1000Sigma),
);
expect(
[toHex(ct.u), toHex(ct.v), toHex(ct.w)],
[encrypt1000U, encrypt1000V, encrypt1000W],
);
expect(toHex(decryptOnG2(sig1000, ct)), encrypt1000Msg);
expect(
() => decryptOnG2(sig1001, ct),
ibeError(
IbeReason.proof,
'ibe: U is not r·G2: the ciphertext does not decrypt under this '
'signature',
),
);
});
// Random.secure of dart2js fails under dart test -p node, where
// crypto.getRandomValues is called with another this; it works in a
// browser. The two tests that draw sigma run on the VM only.
test('draws sigma at random, and the round opens what it seals', () {
final msg = fromHex('00112233445566778899aabbccddeeff');
final ct = encryptOnG2(p.publicKey, roundIdentity(1001), msg);
expect(ct.u, hasLength(uLength));
expect(decryptOnG2(sig1001, ct), msg);
}, testOn: 'vm');
test('rejects lengths and encodings before any pairing', () {
final ct = TlockCiphertext(
fromHex(encrypt1000U),
fromHex(encrypt1000V),
fromHex(encrypt1000W),
);
expect(
() => decryptOnG2(sig1000.sublist(1), ct),
ibeError(IbeReason.length, 'ibe: the signature of 47 bytes, want 48'),
);
expect(
() => decryptOnG2(Uint8List(48)..[0] = 0xc0, ct),
ibeError(
IbeReason.identity,
'ibe: the signature is the point at infinity',
),
);
expect(
() => decryptOnG2(Uint8List.fromList(sig1000)..[0] ^= 0x80, ct),
ibeError(IbeReason.encoding),
);
expect(
() => decryptOnG2(
sig1000,
TlockCiphertext(Uint8List(96)..[0] = 0xc0, ct.v, ct.w),
),
ibeError(IbeReason.identity, 'ibe: U is the point at infinity'),
);
expect(
() => decryptOnG2(sig1000, TlockCiphertext(ct.u, ct.v, Uint8List(15))),
ibeError(IbeReason.length),
);
expect(
() => encryptOnG2(p.publicKey, roundIdentity(1000), Uint8List(33)),
ibeError(IbeReason.length, 'ibe: a message of 33 bytes, want at most 32'),
);
});
test('verifies a release of Quicknet, in the order of provider.Verify', () {
verifyRelease(p, 1000, Release(1000, sig1000));
expect(
() => verifyRelease(p, 1000, Release(1001, sig1001)),
dateKeysError(
ErrorCode.roundMismatch,
'provider: release for round 1001, expected 1000: ERR_ROUND_MISMATCH',
),
);
expect(
() => verifyRelease(p, 1000, Release(1000, sig1001)),
dateKeysError(
ErrorCode.releaseInvalid,
'provider: the signature is not a canonical point encoding, or does '
'not verify as the BLS signature of round 1000 under '
'datekeys:quicknet:v1: ERR_RELEASE_INVALID',
),
);
expect(
() => verifyRelease(p, 1000, Release(1000, sig1000.sublist(1))),
dateKeysError(
ErrorCode.releaseInvalid,
'provider: signature is 47 bytes, bls-unchained-g1-rfc9380 uses 48: '
'ERR_RELEASE_INVALID',
),
);
expect(
() => verifyRelease(p, 0, Release(0, sig1000)),
dateKeysError(
ErrorCode.dateKeyInvalid,
'provider: round 0 outside the range of datekeys:quicknet:v1: '
'ERR_DATEKEY_INVALID',
),
);
expect(
() => verifyRelease(
p.copyWith(scheme: 'pedersen-bls-unchained'),
1000,
Release(1000, sig1000),
),
throwsA(
isA<DateKeysException>().having(
(e) => e.code,
'code',
ErrorCode.unknownProfile,
),
),
);
});
test('wraps a file key in a tlock stanza that the release unwraps', () {
final fileKey = fromHex('0f' * 16);
final (args, body) = wrapTlockStanza(p, 1000, fileKey);
expect(args, ['1000', quicknetChainHash]);
expect(
unwrapTlockStanza(p, 1000, Release(1000, sig1000), args, body),
fileKey,
);
expect(
() => unwrapTlockStanza(
p,
1000,
Release(1000, sig1000),
args,
body.sublist(1),
),
dateKeysError(
ErrorCode.integrity,
'agewrap: tlock stanza body of 127 bytes, want 128: ERR_INTEGRITY',
),
);
expect(
() => wrapTlockStanza(p, p.maxRound + 1, fileKey),
throwsA(
isA<DateKeysException>().having(
(e) => e.code,
'code',
ErrorCode.dateKeyInvalid,
),
),
);
}, testOn: 'vm');
}

@ -0,0 +1,381 @@
// The IBE of lib/src/ibe.dart against the Go reference: test/vectors/
// ibe_vectors.json, written by tool/ibe_go_vectors.go with drand/kyber
// encrypt/ibe, tlock and age, the libraries of the reference implementation,
// on the fixtures of testdata/. A port of ibe.test.ts of datekeys-ts.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart' show concatBytes, fromHex, toHex;
import 'package:datekeys/src/bls12381_curve.dart';
import 'package:datekeys/src/bls12381_pairing.dart';
import 'package:datekeys/src/bls12381_tower.dart';
import 'package:datekeys/src/ibe.dart';
import 'package:test/test.dart';
import 'tlock_support.dart';
typedef Json = Map<String, Object?>;
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
final Json v = readJson('test/vectors/ibe_vectors.json');
List<Json> section(Json file, String name) =>
(file[name]! as List).cast<Json>();
String s(Json v, String key) => v[key]! as String;
Uint8List h(Json v, String key) => fromHex(s(v, key));
TlockCiphertext ct(Json v) => TlockCiphertext(h(v, 'u'), h(v, 'v'), h(v, 'w'));
String scalarHex(BigInt r) => r.toRadixString(16).padLeft(64, '0');
Uint8List xor(List<int> a, List<int> b) =>
Uint8List.fromList([for (var i = 0; i < a.length; i++) a[i] ^ b[i]]);
final Json timeOnly = section(
v,
'fixtures',
).firstWhere((f) => f['name'] == 'time_only');
const proofMessage =
'ibe: U is not r·G2: the ciphertext does not decrypt under this signature';
// Runs [body] and returns the IbeException it throws.
IbeException ibeError(void Function() body, String label) {
try {
body();
} on IbeException catch (e) {
return e;
}
fail('$label: no IbeException');
}
// GT serialized c0 first at every level of the tower: the order of noble's
// Fp12.toBytes, which H2 must not hash.
Uint8List c0First(Fp12 gt) {
List<int> fp2(Fp2 a) => [...a.c0.toBytes(), ...a.c1.toBytes()];
List<int> fp6(Fp6 a) => [...fp2(a.c0), ...fp2(a.c1), ...fp2(a.c2)];
return Uint8List.fromList([...fp6(gt.c0), ...fp6(gt.c1)]);
}
void main() {
test('reads the vectors of the Quicknet scheme and key', () {
expect(v['generator'], 'tool/ibe_go_vectors.go');
expect(v['scheme'], 'bls-unchained-g1-rfc9380');
expect(
v['public_key'],
readJson('testdata/vectors/profile_quicknet.json')['public_key'],
);
expect(v['public_key'], quicknetPublicKey);
});
test(
'serializes GT in the order of kilic, which H2 hashes, and never c0 first',
() {
for (final g in section(v, 'gt')) {
final gt = pairing(
G1Point.decode(h(g, 'g1'))!,
G2Point.decode(h(g, 'g2'))!,
);
final name = s(g, 'name');
expect(toHex(gtBytes(gt)), s(g, 'gt'), reason: name);
expect(toHex(h2(gt, 16)), s(g, 'h2_16'), reason: name);
expect(toHex(h2(gt, 32)), s(g, 'h2_32'), reason: name);
expect(toHex(c0First(gt)), isNot(s(g, 'gt')), reason: name);
}
// The first vector is the one every implementation shares.
final shared = section(
readJson('testdata/vectors/tlock_ibe.json'),
'vectors',
).first;
final first = section(v, 'gt').first;
expect(
[s(first, 'gt'), s(first, 'h2_16')],
[s(shared, 'gt'), s(shared, 'h2')],
);
},
);
test('computes H3 through its rejection sampling, and H4', () {
for (final c in section(v, 'h3')) {
final sigma = h(c, 'sigma');
final msg = h(c, 'msg');
final iterations = c['iterations']! as int;
final name = s(c, 'name');
expect(scalarHex(h3(sigma, msg)), s(c, 'r'), reason: name);
expect(
scalarHex(h3(sigma, msg, iterations: iterations)),
s(c, 'r'),
reason: name,
);
if (iterations > 1) {
final e = ibeError(
() => h3(sigma, msg, iterations: iterations - 1),
name,
);
expect(
[e.reason, e.message],
[
IbeReason.proof,
'ibe: no scalar r below the order of the group (rejection sampling '
'failed)',
],
);
}
}
expect([
for (final c in section(v, 'h3')) c['iterations'],
], containsAll([1, 2, 3]));
for (final c in section(v, 'h4')) {
expect(toHex(h4(h(c, 'sigma'), 16)), s(c, 'h4_16'));
expect(toHex(h4(h(c, 'sigma'), 32)), s(c, 'h4_32'));
}
});
test('derives the identity of a round as drand does', () {
for (final c in section(v, 'round_identities')) {
final round = c['round']! as int;
expect(toHex(roundIdentity(round)), s(c, 'id'), reason: '$round');
}
for (final bad in [-1, maxSafeRound + 1]) {
expect(() => roundIdentity(bad), throwsRangeError, reason: '$bad');
}
});
test('opens the tlock stanza of every official fixture with the file key of '
'the reference, which authenticates the age header', () {
final names = [
for (final f in Directory('testdata/fixtures').listSync())
if (f.path.endsWith('.dkc'))
f.uri.pathSegments.last.replaceAll('.dkc', ''),
]..sort();
final fixtures = section(v, 'fixtures');
expect([for (final f in fixtures) s(f, 'name')]..sort(), names);
for (final f in fixtures) {
final name = s(f, 'name');
final dkc = File('testdata/fixtures/$name.dkc').readAsBytesSync();
final record = readJson('testdata/fixtures/$name.json');
expect(record['release'], {
'round': f['round'],
'signature': f['signature'],
});
final header = readAgeHeader(sealedControl(dkc));
expect(header.stanzas, hasLength(1));
final stanza = header.stanzas.single;
expect(
[stanza.type, stanza.args.first, toHex(stanza.body)],
['tlock', '${f['round']}', s(f, 'body')],
reason: name,
);
final sig = h(f, 'signature');
final c = ciphertextFromBody(stanza.body);
final fileKey = decryptOnG2(sig, c);
expect(toHex(fileKey), s(f, 'file_key'), reason: name);
expect(
ageHeaderMacValid(fileKey, header.macInput, header.mac),
isTrue,
reason: name,
);
// The values in between, as the reference computes them.
final gt = pairing(G1Point.decode(sig)!, G2Point.decode(c.u)!);
expect(toHex(gtBytes(gt)), s(f, 'gt'), reason: name);
final sigma = xor(c.v, h2(gt, 16));
expect(toHex(sigma), s(f, 'sigma'), reason: name);
expect(xor(c.w, h4(sigma, 16)), h(f, 'file_key'), reason: name);
expect(scalarHex(h3(sigma, h(f, 'file_key'))), s(f, 'r'), reason: name);
expect(toHex(ciphertextToBody(c)), s(f, 'body'), reason: name);
// Another file key fails the MAC.
expect(
ageHeaderMacValid(
xor(fileKey, List.filled(16, 1)),
header.macInput,
header.mac,
),
isFalse,
);
}
});
test('decrypts what kyber encrypts, for messages of 0 to 32 bytes', () {
final kyber = section(v, 'kyber');
expect([for (final c in kyber) h(c, 'v').length], [0, 1, 16, 32]);
for (final c in kyber) {
expect(
toHex(decryptOnG2(h(c, 'signature'), ct(c))),
c['msg'] ?? '',
reason: s(c, 'name'),
);
}
});
test('rejects what the reference rejects, with the reason of the first '
'failing check', () {
const want = {
'the time_only stanza': null,
'U with p added to c0': IbeReason.encoding,
'U is the point at infinity': IbeReason.identity,
'U negated': IbeReason.proof,
'V with its first bit flipped': IbeReason.proof,
'W with its last bit flipped': IbeReason.proof,
'the signature of round 1001': IbeReason.proof,
'the signature negated': IbeReason.proof,
'the signature is the point at infinity': IbeReason.identity,
'W one byte shorter than V': IbeReason.length,
'V and W of 33 bytes': IbeReason.length,
'V and W empty': IbeReason.proof,
'U is the generator of G2': IbeReason.proof,
};
final cases = section(v, 'decrypt');
expect({for (final c in cases) s(c, 'name')}, want.keys.toSet());
for (final c in cases) {
final name = s(c, 'name');
expect(c['go'] == 'ok', want[name] == null, reason: name);
if (c['go'] == 'ok') {
expect(
toHex(decryptOnG2(h(c, 'signature'), ct(c))),
c['msg'],
reason: name,
);
continue;
}
final e = ibeError(() => decryptOnG2(h(c, 'signature'), ct(c)), name);
expect(e.reason, want[name], reason: name);
if (e.reason == IbeReason.proof) {
expect(e.message, proofMessage, reason: name);
}
}
});
test('gates every encoding of the signature and of U as Go decodes it', () {
final points = section(
readJson('test/vectors/bls12381_vectors.json'),
'points',
);
final c = ciphertextFromBody(h(timeOnly, 'body'));
final sig = h(timeOnly, 'signature');
const reason = {
'invalid': IbeReason.encoding,
'identity': IbeReason.identity,
'point': IbeReason.proof,
};
final seen = <IbeReason>{};
for (final p in points) {
final bytes = h(p, 'hex');
final g1 = p['group'] == 'G1';
final label = s(p, 'label');
final e = ibeError(
() => g1
? decryptOnG2(bytes, c)
: decryptOnG2(sig, TlockCiphertext(bytes, c.v, c.w)),
label,
);
// An encoding of another length is invalid for Go too; the IBE says
// so first.
final want = bytes.length == (g1 ? 48 : 96)
? reason[s(p, 'go')]
: IbeReason.length;
expect(e.reason, want, reason: label);
seen.add(e.reason);
}
expect(seen, IbeReason.values.toSet());
});
test('checks the lengths first, with fixed texts', () {
final c = ciphertextFromBody(h(timeOnly, 'body'));
final sig = h(timeOnly, 'signature');
final cases = <(String, void Function(), String)>[
(
'a signature of 47 bytes',
() => decryptOnG2(sig.sublist(1), c),
'ibe: the signature of 47 bytes, want 48',
),
(
'a signature of 49 bytes',
() => decryptOnG2([...sig, 0], c),
'ibe: the signature of 49 bytes, want 48',
),
(
'U of 95 bytes',
() => decryptOnG2(sig, TlockCiphertext(c.u.sublist(1), c.v, c.w)),
'ibe: U of 95 bytes, want 96',
),
(
'V longer than W',
() => decryptOnG2(sig, TlockCiphertext(c.u, Uint8List(17), c.w)),
'ibe: V of 17 bytes and W of 16, want equal lengths of at most 32',
),
(
'a body of 127 bytes',
() => ciphertextFromBody(Uint8List(tlockBodyLength - 1)),
'ibe: tlock stanza body of 127 bytes, want 128',
),
(
'a body of 129 bytes',
() => ciphertextFromBody(Uint8List(tlockBodyLength + 1)),
'ibe: tlock stanza body of 129 bytes, want 128',
),
(
'a body with V of 15 bytes',
() => ciphertextToBody(TlockCiphertext(c.u, c.v.sublist(1), c.w)),
'ibe: a tlock stanza body holds U of 96 bytes and V and W of 16',
),
(
'a body with W of 17 bytes',
() => ciphertextToBody(TlockCiphertext(c.u, c.v, Uint8List(17))),
'ibe: a tlock stanza body holds U of 96 bytes and V and W of 16',
),
(
'a body with U of 95 bytes',
() => ciphertextToBody(TlockCiphertext(c.u.sublist(1), c.v, c.w)),
'ibe: a tlock stanza body holds U of 96 bytes and V and W of 16',
),
];
for (final (label, body, message) in cases) {
final e = ibeError(body, label);
expect([e.reason, e.message], [IbeReason.length, message], reason: label);
}
});
test('never proves with r = 0', () {
final u = G2Point.decode(h(timeOnly, 'body').sublist(0, 96))!;
expect(proofHolds(BigInt.zero, u), isFalse);
expect(proofHolds(BigInt.parse(s(timeOnly, 'r'), radix: 16), u), isTrue);
});
test('never puts a value of the computation in an error', () {
// The messages are fixed by the reason and the lengths: the file key,
// sigma and r of the stanzas the edits start from appear in none.
final secrets = [
s(timeOnly, 'file_key'),
s(timeOnly, 'sigma'),
s(timeOnly, 'r'),
s(timeOnly, 'signature'),
s(timeOnly, 'body').substring(0, 32),
];
for (final c in section(v, 'decrypt').where((c) => c['go'] == 'reject')) {
final name = s(c, 'name');
final e = ibeError(() => decryptOnG2(h(c, 'signature'), ct(c)), name);
for (final secret in secrets) {
expect(
e.message.toLowerCase(),
isNot(contains(secret.substring(0, 16))),
reason: name,
);
}
expect(e.message, isNot(matches(RegExp('[0-9a-f]{16}'))), reason: name);
}
});
test('a body splits and joins back', () {
final body = h(timeOnly, 'body');
final c = ciphertextFromBody(body);
expect(concatBytes([c.u, c.v, c.w]), body);
expect(ciphertextToBody(c), body);
});
}

@ -0,0 +1,290 @@
// The verification of releases (lib/src/release.dart) against the Go
// reference: the verify section of test/vectors/release_vectors.json,
// written by tool/release_go_vectors.go with provider.Verify, and every
// release of the mutation corpus that fails at step 10. A port of
// release.test.ts of datekeys-ts.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart' show fromHex;
import 'package:datekeys/src/bls12381_curve.dart';
import 'package:datekeys/src/bls12381_hash.dart';
import 'package:datekeys/src/bls12381_pairing.dart';
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/ibe.dart';
import 'package:datekeys/src/release.dart';
import 'package:test/test.dart';
import 'tlock_support.dart';
typedef Json = Map<String, Object?>;
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
final Json g = readJson('test/vectors/release_vectors.json');
List<Json> section(Json file, String name) =>
(file[name]! as List).cast<Json>();
String s(Json v, String key) => v[key]! as String;
TestProfile profileOf(String name) {
final c = section(g, 'profiles').firstWhere((p) => p['name'] == name);
return quicknet().copyWith(
scheme: s(c, 'scheme'),
publicKey: fromHex(s(c, 'public_key')),
);
}
DateKeysException dateKeysError(void Function() body, String label) {
try {
body();
} on DateKeysException catch (e) {
return e;
}
fail('$label: no DateKeysException');
}
const invalid =
'provider: the signature is not a canonical point encoding, or does not '
'verify as the BLS signature of round 1000 under datekeys:quicknet:v1: '
'ERR_RELEASE_INVALID';
String onlyQuicknet(String scheme) =>
'provider: profile datekeys:quicknet:v1 uses scheme $scheme; only '
'bls-unchained-g1-rfc9380 releases are verified here: ERR_UNKNOWN_PROFILE';
void main() {
test('the profile of the tests is the pinned Quicknet of testdata/', () {
final q = readJson('testdata/vectors/profile_quicknet.json');
expect(
[
q['profile_id'],
q['scheme'],
q['public_key'],
q['chain_hash'],
q['genesis_time'],
q['period_seconds'],
],
[
quicknetId,
quicknetScheme,
quicknetPublicKey,
quicknetChainHash,
quicknetGenesisTime,
quicknetPeriod,
],
);
// The last round, as the round resolution of spec §15 gives it.
final last = section(
readJson('testdata/vectors/quicknet_rounds.json'),
'vectors',
).firstWhere((v) => v['name'] == 'last representable round time');
expect(quicknet().maxRound, last['round']);
expect(quicknet().maxRound, g['max_round']);
// The published signatures of the tests are those of the fixtures.
expect(
(readJson('testdata/fixtures/time_only.json')['release']!
as Json)['signature'],
signature1000,
);
expect(
(readJson('testdata/fixtures/empty_payload.json')['release']!
as Json)['signature'],
signature1001,
);
});
test(
'verifies as provider.Verify, in its order and with its codes and texts',
() {
// Where this library departs from Go, on purpose. Only the scheme of
// Quicknet is verified, as in datekeys-ts: Go verifies the other
// schemes of drand, and names an unknown one in its own text. And the
// point at infinity is never a valid signature (spec §63 step 10):
// Go accepts it when the key is the point at infinity too, as kilic
// drops both pairs of its check, a key that no pinned profile has
// (spec §12.1).
final departs = {
'another scheme of drand': onlyQuicknet('pedersen-bls-unchained'),
'another scheme of drand on G1': onlyQuicknet('bls-unchained-on-g1'),
'a scheme that is not of drand': onlyQuicknet('datekeys-test'),
'the key and the signature are the point at infinity': invalid,
};
var accepted = 0;
for (final c in section(g, 'verify')) {
final name = s(c, 'name');
final p = profileOf(s(c, 'profile'));
final round = c['round']! as int;
final r = Release(
c['release_round']! as int,
fromHex(s(c, 'signature')),
);
final want = departs[name];
if (want != null) {
expect(
dateKeysError(() => verifyRelease(p, round, r), name).message,
want,
reason: name,
);
continue;
}
if (c['go'] == 'ok') {
verifyRelease(p, round, r);
accepted++;
continue;
}
final e = dateKeysError(() => verifyRelease(p, round, r), name);
expect([e.code.code, e.message], [c['code'], c['text']], reason: name);
}
expect(accepted, 4);
expect(
departs.keys,
everyElement(isIn(section(g, 'verify').map((c) => c['name']))),
);
},
);
test('gives the code of every release of the mutation corpus that fails at '
'step 10', () {
final corpus = section(
readJson('testdata/vectors/mutations.json'),
'cases',
).where((c) => c['step'] == 10).toList();
const names = [
'DateKey A + release of round B',
'release of another round',
'release signature is the point at infinity',
'release signature negated',
'release signature re-encoded with x + p',
'release signature with the infinity flag and a payload',
'negated release signature and U re-encoded with c0 + p',
];
expect(
[for (final c in corpus) s(c, 'name')]..sort(),
[
...names,
for (final n in names) 'format 2: $n',
for (final n in names) 'format 3: $n',
]..sort(),
);
for (final c in corpus) {
final name = s(c, 'name');
final dkc = c['dkc']! as Json;
final base = dkc['base'] as String?;
final bytes = applyEdits(
base == null
? Uint8List(0)
: File('testdata/fixtures/$base').readAsBytesSync(),
dkc['edits']! as List<Object?>,
);
final rel = c['release']! as Json;
final e = dateKeysError(
() => verifyRelease(
quicknet(),
dateKeyRound(bytes),
Release(rel['round']! as int, fromHex(s(rel, 'signature'))),
),
name,
);
expect(e.code.code, c['error'], reason: name);
}
});
test(
'hashes the round with the DST of RFC 9380 for G1, not the one of G2 that '
'bls-unchained-on-g1 uses',
() {
final key = G2Point.decode(fromHex(quicknetPublicKey))!;
final sig = G1Point.decode(fromHex(signature1000))!;
bool verifies(String dst) => pairingCheck([
(hashToG1(roundIdentity(1000), dst), key),
(-sig, G2Point.generator),
]);
expect(quicknetDst, 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_');
expect(verifies(quicknetDst), isTrue);
expect(verifies('BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_'), isFalse);
},
);
test('the supplied release is handed over for any round, unverified, and '
'there is none without one', () async {
final r = Release(1001, fromHex(signature1001));
expect(await suppliedRelease(r).fetch(quicknet(), 1000), same(r));
await expectLater(
suppliedRelease().fetch(quicknet(), 1000),
throwsA(
isA<DateKeysException>().having(
(e) => e.message,
'message',
'release: no release supplied for round 1000: '
'ERR_RELEASE_UNAVAILABLE',
),
),
);
});
test('whatever a source throws is ERR_RELEASE_UNAVAILABLE at step 9, keeping '
'only its text', () async {
final p = quicknet();
final r = Release(1000, fromHex(signature1000));
expect(await fetchRelease(suppliedRelease(r), p, 1000), same(r));
Future<DateKeysException> failure(Object thrown) async {
try {
await fetchRelease(_Throwing(thrown), p, 1000);
} on DateKeysException catch (e) {
return e;
}
fail('no failure');
}
// A release that is not available keeps its text.
final none = await failure(
DateKeysException(ErrorCode.releaseUnavailable, 'relay: not yet'),
);
expect(none.message, 'relay: not yet: ERR_RELEASE_UNAVAILABLE');
// Another code is kept as text only.
final other = await failure(
DateKeysException(ErrorCode.releaseInvalid, 'relay: bad signature'),
);
expect(
[other.code, other.message],
[
ErrorCode.releaseUnavailable,
'capsule: release source: relay: bad signature: ERR_RELEASE_INVALID: '
'ERR_RELEASE_UNAVAILABLE',
],
);
// And any other failure too.
final io = await failure(const FormatException('no JSON'));
expect(
[io.code, io.message],
[
ErrorCode.releaseUnavailable,
'capsule: release source: FormatException: no JSON: '
'ERR_RELEASE_UNAVAILABLE',
],
);
// The verification of step 10 comes after, on what the source gave.
expect(
dateKeysError(
() => verifyRelease(p, 1000, Release(1001, fromHex(signature1001))),
'another round',
).code,
ErrorCode.roundMismatch,
);
});
}
final class _Throwing implements ReleaseSource {
_Throwing(this.error);
final Object error;
@override
Future<Release> fetch(PinnedProfile p, int round) async => throw error;
}

@ -0,0 +1,183 @@
// Helpers of the tests of stage 3 (BLS12-381, the IBE, tlock and releases):
// a pinned profile, and the little of age and of DKC1 that the tests read
// to reach a tlock stanza and to check a file key. They read no file, so
// that the tests that run on Node.js can use them; age itself is stage 2.
library;
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:datekeys/datekeys.dart' show concatBytes, fromHex;
import 'package:datekeys/src/release.dart';
/// A pinned profile for the tests.
final class TestProfile implements PinnedProfile {
TestProfile({
required this.id,
required this.scheme,
required this.publicKey,
required this.chainHash,
required this.maxRound,
});
@override
final String id;
@override
final String scheme;
@override
final Uint8List publicKey;
@override
final Uint8List chainHash;
@override
final int maxRound;
/// This profile with another [scheme] or [publicKey].
TestProfile copyWith({String? scheme, List<int>? publicKey}) => TestProfile(
id: id,
scheme: scheme ?? this.scheme,
publicKey: publicKey == null
? this.publicKey
: Uint8List.fromList(publicKey),
chainHash: chainHash,
maxRound: maxRound,
);
}
/// The Quicknet Provider Profile V1 of testdata/vectors/
/// profile_quicknet.json, which tlock_support_test.dart checks against it.
const quicknetId = 'datekeys:quicknet:v1';
const quicknetChainHash =
'52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971';
const quicknetPublicKey =
'83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b'
'6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809'
'bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a';
const quicknetGenesisTime = 1692803367;
const quicknetPeriod = 3;
/// 9999-12-31T23:59:59Z, the last representable instant (spec §15).
const maxUnixTime = 253402300799;
/// The pinned Quicknet profile.
TestProfile quicknet() => TestProfile(
id: quicknetId,
scheme: quicknetScheme,
publicKey: fromHex(quicknetPublicKey),
chainHash: fromHex(quicknetChainHash),
maxRound: (maxUnixTime - quicknetGenesisTime) ~/ quicknetPeriod + 1,
);
/// The published release signatures of rounds 1000 and 1001 of Quicknet, as
/// in the fixtures (time_only.json and empty_payload.json).
const signature1000 =
'b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a'
'8dd2bacbe47e4b6b63ed5e39';
const signature1001 =
'b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b2'
'5883abf2853d10337fb8fa41';
/// The edits of the mutation corpus applied to [base] in one pass, as
/// applyEdits of datekeys-ts: each [at, delete, insert hex] refers to
/// offsets of the unmodified base, sorted and not overlapping.
Uint8List applyEdits(List<int> base, List<Object?> edits) {
final parts = <List<int>>[];
var pos = 0;
for (final e in edits.cast<List<Object?>>()) {
final at = e[0]! as int;
final delete = e[1]! as int;
if (at < pos || at + delete > base.length) {
throw StateError('edit at $at out of order or beyond the base');
}
parts
..add(base.sublist(pos, at))
..add(fromHex(e[2]! as String));
pos = at + delete;
}
parts.add(base.sublist(pos));
return concatBytes(parts);
}
/// The round of the DateKey that the dk1_ string in [dkc] names: the tests
/// need it where the opening would read it from the public header, which
/// stage 4 decodes.
int dateKeyRound(List<int> dkc) {
final text = latin1.decode(dkc, allowInvalid: true);
final m = RegExp(r'dk1_([A-Za-z0-9_-]+)').firstMatch(text)!;
final json = utf8.decode(base64Url.decode(base64Url.normalize(m[1]!)));
return (jsonDecode(json) as Map<String, Object?>)['round']! as int;
}
/// SEALED_CONTROL of a DKC1 file: its PRELUDE gives the lengths of the
/// public header and of SEALED_CONTROL (spec §22).
Uint8List sealedControl(List<int> dkc) {
final view = ByteData.sublistView(Uint8List.fromList(dkc));
final header = view.getUint32(8);
final sealed = view.getUint32(12);
return Uint8List.fromList(dkc.sublist(16 + header, 16 + header + sealed));
}
/// A recipient stanza as the tests read it.
typedef Stanza = ({String type, List<String> args, Uint8List body});
/// The recipient stanzas and the MAC of the age header at the start of
/// [file], read leniently: enough for the files of the tests, which age
/// wrote. The grammar and its checks are those of stage 2.
({List<Stanza> stanzas, Uint8List macInput, Uint8List mac}) readAgeHeader(
List<int> file,
) {
var start = 0;
final stanzas = <Stanza>[];
for (var i = 0; i < file.length; i++) {
if (file[i] != 0x0a) continue;
final line = latin1.decode(file.sublist(start, i));
if (line.startsWith('---')) {
// The MAC covers the header up to and including "---".
return (
stanzas: stanzas,
macInput: Uint8List.fromList(file.sublist(0, start + 3)),
mac: base64.decode(base64.normalize(line.substring(4))),
);
}
start = i + 1;
if (line.startsWith('-> ')) {
final words = line.substring(3).split(' ');
final body = StringBuffer();
// The body: lines of 64 columns, the last one shorter.
var j = i + 1;
for (;;) {
final end = file.indexOf(0x0a, j);
final bodyLine = latin1.decode(file.sublist(j, end));
body.write(bodyLine);
j = end + 1;
if (bodyLine.length < 64) break;
}
stanzas.add((
type: words.first,
args: words.sublist(1),
body: base64.decode(base64.normalize(body.toString())),
));
i = j - 1;
start = j;
}
}
throw StateError('no MAC line');
}
/// Whether [fileKey] authenticates the age header whose MAC input and MAC
/// [readAgeHeader] returns: HMAC-SHA-256 under HKDF-SHA-256(fileKey, "",
/// "header"), as age computes it.
bool ageHeaderMacValid(List<int> fileKey, Uint8List macInput, Uint8List mac) {
final prk = Hmac(sha256, Uint8List(32)).convert(fileKey).bytes;
final key = Hmac(sha256, prk).convert([...ascii.encode('header'), 1]).bytes;
final got = Hmac(sha256, key).convert(macInput).bytes;
var diff = 0;
for (var i = 0; i < 32; i++) {
diff |= got[i] ^ mac[i];
}
return mac.length == 32 && diff == 0;
}

@ -0,0 +1,318 @@
// The tlock encryption and the tlock stanza (lib/src/ibe.dart,
// lib/src/tlock.dart) against the Go reference: test/vectors/
// tlock_vectors.json, written by tool/tlock_go_vectors.go, and the unwrap
// and recipient sections of test/vectors/release_vectors.json, written by
// tool/release_go_vectors.go. A port of tlock.test.ts of datekeys-ts.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart' show fromHex, toHex;
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/ibe.dart';
import 'package:datekeys/src/release.dart';
import 'package:datekeys/src/tlock.dart';
import 'package:test/test.dart';
import 'tlock_support.dart';
typedef Json = Map<String, Object?>;
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
final Json v = readJson('test/vectors/tlock_vectors.json');
final Json g = readJson('test/vectors/release_vectors.json');
List<Json> section(Json file, String name) =>
(file[name]! as List).cast<Json>();
String s(Json v, String key) => v[key]! as String;
Uint8List h(Json v, String key) => fromHex(s(v, key));
final Map<int, String> signatures = {
for (final e in section(v, 'encrypt')) e['round']! as int: s(e, 'signature'),
};
Release release(int round) => Release(round, fromHex(signatures[round]!));
// The profiles of release_vectors.json: Quicknet with another scheme or key.
TestProfile profileOf(String name) {
final c = section(g, 'profiles').firstWhere((p) => p['name'] == name);
return quicknet().copyWith(
scheme: s(c, 'scheme'),
publicKey: h(c, 'public_key'),
);
}
// Go's TimeIdentity.Unwrap as stage 4 will compose it: the stanza rules of
// agewrap that need the whole header (its count and the type of its
// stanza), which stage 2 brings, then unwrapTlockStanza. The texts are
// Go's.
Uint8List timeIdentityUnwrap(
PinnedProfile p,
int round,
Release r,
List<Stanza> stanzas,
) {
if (stanzas.length != 1) {
throw DateKeysException(
ErrorCode.policyStructureMismatch,
'agewrap: OUTER_TIME_AGE has ${stanzas.length} stanzas, want exactly '
'one tlock stanza',
);
}
final st = stanzas.single;
if (st.type != 'tlock') {
throw DateKeysException(
ErrorCode.policyStructureMismatch,
'agewrap: OUTER_TIME_AGE stanza type "${st.type}", want "tlock"',
);
}
return unwrapTlockStanza(p, round, r, st.args, st.body);
}
DateKeysException dateKeysError(void Function() body, String label) {
try {
body();
} on DateKeysException catch (e) {
return e;
}
fail('$label: no DateKeysException');
}
void main() {
test('reads vectors of the Quicknet scheme and key', () {
expect(v['generator'], 'tool/tlock_go_vectors.go');
expect([v['scheme'], v['public_key']], [quicknetScheme, quicknetPublicKey]);
expect(signatures.keys.toSet(), {1000, 1001});
expect(g['generator'], 'tool/release_go_vectors.go');
expect(g['max_round'], quicknet().maxRound);
});
test('encrypts as the reference, byte for byte, for a given sigma', () {
final p = quicknet();
for (final e in section(v, 'encrypt')) {
final name = s(e, 'name');
expect(toHex(roundIdentity(e['round']! as int)), s(e, 'id'));
final c = encryptOnG2WithSigma(
p.publicKey,
h(e, 'id'),
h(e, 'msg'),
h(e, 'sigma'),
);
expect(
[toHex(c.u), toHex(c.v), toHex(c.w)],
[s(e, 'u'), s(e, 'v'), s(e, 'w')],
reason: name,
);
expect(toHex(decryptOnG2(h(e, 'signature'), c)), s(e, 'msg'));
}
expect(
[for (final e in section(v, 'encrypt')) h(e, 'msg').length]..sort(),
[0, 1, 16, 16, 32],
);
});
test('opens what datekeys-ts encrypted and the reference opened: IBE bodies, '
'and age files whose header MAC the file key verifies', () {
final interop = v['interop']! as Json;
expect(interop['generator'], 'scripts/tlock-ts-samples.mjs');
final samples = section(interop, 'samples');
expect([for (final x in samples) '${x['kind']} ${x['round']}']..sort(), [
'age 1000',
'age 1001',
'ibe 1000',
'ibe 1001',
]);
for (final x in samples) {
final name = s(x, 'name');
final round = x['round']! as int;
expect(x['go'], 'ok', reason: name);
if (x['kind'] == 'ibe') {
expect(x['go_result'], x['file_key'], reason: name);
final key = decryptOnG2(
release(round).signature,
ciphertextFromBody(h(x, 'body')),
);
expect(toHex(key), s(x, 'file_key'), reason: name);
} else {
expect(x['go_result'], x['plaintext'], reason: name);
final header = readAgeHeader(h(x, 'file'));
final key = timeIdentityUnwrap(
quicknet(),
round,
release(round),
header.stanzas,
);
expect(
ageHeaderMacValid(key, header.macInput, header.mac),
isTrue,
reason: name,
);
}
}
});
test(
'draws a new sigma every time, and the signature of the round opens every '
'ciphertext',
() {
final p = quicknet();
final msg = fromHex('00112233445566778899aabbccddeeff');
final a = encryptOnG2(p.publicKey, roundIdentity(1001), msg);
final b = encryptOnG2(p.publicKey, roundIdentity(1001), msg);
expect(toHex(a.u), isNot(toHex(b.u)));
for (final c in [a, b]) {
expect(decryptOnG2(release(1001).signature, c), msg);
}
expect(
() => decryptOnG2(release(1000).signature, a),
throwsA(isA<IbeException>()),
);
},
);
test(
'rejects a message longer than 32 bytes, a sigma of another length and a '
'key that is not a canonical point',
() {
final p = quicknet();
final id = roundIdentity(1000);
(IbeReason, String) failure(void Function() body) {
try {
body();
} on IbeException catch (e) {
return (e.reason, e.message);
}
fail('no IbeException');
}
final infinity = Uint8List(96)..[0] = 0xc0;
final offCurve = Uint8List.fromList(p.publicKey)..[95] ^= 1;
expect(failure(() => encryptOnG2(p.publicKey, id, Uint8List(33))), (
IbeReason.length,
'ibe: a message of 33 bytes, want at most 32',
));
expect(
failure(
() => encryptOnG2WithSigma(
p.publicKey,
id,
Uint8List(16),
Uint8List(15),
),
),
(IbeReason.length, 'ibe: sigma of 15 bytes for a message of 16'),
);
expect(
failure(() => encryptOnG2(p.publicKey.sublist(1), id, Uint8List(16))),
(IbeReason.length, 'ibe: the public key of 95 bytes, want 96'),
);
expect(failure(() => encryptOnG2(infinity, id, Uint8List(16))), (
IbeReason.identity,
'ibe: the public key is the point at infinity',
));
expect(
failure(() => encryptOnG2(offCurve, id, Uint8List(16))).$1,
IbeReason.encoding,
);
},
);
test('writes the stanza of tlock, which unwraps with the release', () {
final p = quicknet();
final fileKey = fromHex('0f' * 16);
final (args, body) = wrapTlockStanza(p, 1000, fileKey);
expect(args, ['1000', quicknetChainHash]);
expect(body, hasLength(tlockBodyLength));
expect(unwrapTlockStanza(p, 1000, release(1000), args, body), fileKey);
final e = dateKeysError(
() => unwrapTlockStanza(p, 1000, release(1001), args, body),
'another release',
);
expect(e.code, ErrorCode.roundMismatch);
});
test(
'checks the profile, then the round, as NewTimeRecipient, with its codes '
'and texts',
() {
// Only the scheme of Quicknet is supported, as in datekeys-ts: for
// another scheme the code is Go's, the text this library's.
const onlyQuicknet = {
'another scheme of drand':
'agewrap: profile datekeys:quicknet:v1 uses scheme '
'pedersen-bls-unchained; only bls-unchained-g1-rfc9380 is '
'supported here: ERR_UNKNOWN_PROFILE',
'a scheme that is not of drand':
'agewrap: profile datekeys:quicknet:v1 uses scheme datekeys-test; '
'only bls-unchained-g1-rfc9380 is supported here: '
'ERR_UNKNOWN_PROFILE',
};
for (final c in section(g, 'recipient')) {
final name = s(c, 'name');
final p = profileOf(s(c, 'profile'));
final round = c['round']! as int;
if (c['go'] == 'ok') {
final (args, body) = wrapTlockStanza(p, round, Uint8List(16));
expect(args.first, '$round', reason: name);
expect(body, hasLength(tlockBodyLength), reason: name);
continue;
}
final e = dateKeysError(
() => wrapTlockStanza(p, round, Uint8List(16)),
name,
);
expect(e.code.code, c['code'], reason: name);
expect(e.message, onlyQuicknet[name] ?? c['text'], reason: name);
}
},
);
test('unwraps the stanza of OUTER_TIME_AGE as TimeIdentity of Go, with its '
'codes and texts, in its order', () {
// As above, only the scheme of Quicknet; and the profile is checked
// as NewTimeIdentity does, before the stanza.
const onlyQuicknet = {
'another scheme of drand':
'agewrap: profile datekeys:quicknet:v1 uses scheme '
'pedersen-bls-unchained; only bls-unchained-g1-rfc9380 is '
'supported here: ERR_UNKNOWN_PROFILE',
};
var opened = 0;
for (final c in section(g, 'unwrap')) {
final name = s(c, 'name');
final p = profileOf(s(c, 'profile'));
final r = Release(c['release_round']! as int, h(c, 'signature'));
final stanzas = [
for (final st in (c['stanzas']! as List).cast<Json>())
(
type: s(st, 'type'),
args: (st['args']! as List).cast<String>(),
body: h(st, 'body'),
),
];
final round = c['round']! as int;
if (c['go'] == 'ok') {
expect(
toHex(timeIdentityUnwrap(p, round, r, stanzas)),
c['file_key'],
reason: name,
);
opened++;
continue;
}
final e = dateKeysError(
() => timeIdentityUnwrap(p, round, r, stanzas),
name,
);
expect(e.code.code, c['code'], reason: name);
expect(e.message, onlyQuicknet[name] ?? c['text'], reason: name);
}
expect(opened, 1);
});
}

@ -0,0 +1,500 @@
{
"description": "Go reference values of the tlock IBE-CCA on G2 (spec §63 step 11) for lib/src/ibe.dart; see tool/ibe_go_vectors.go for how each block is obtained.",
"generator": "tool/ibe_go_vectors.go",
"libraries": "filippo.io/age v1.3.2, github.com/drand/drand/v2 v2.1.7, github.com/drand/kyber v1.3.2, github.com/drand/kyber-bls12381 v0.3.4, github.com/drand/tlock v1.2.0",
"kyber_from": "the kyber section of src/lib/dkc/testing/ibe-vectors.json of datekeys-ts at 289fe71, decrypted again by this generator",
"scheme": "bls-unchained-g1-rfc9380",
"public_key": "83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a",
"gt": [
{
"name": "e(G1, G2)",
"g1": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb",
"g2": "93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8",
"gt": "0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b67663104c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef03350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a211b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba5706fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1a19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89d018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b601b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934f1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170f1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6",
"h2_16": "cb87319f24560b5231579a09ad79f12e",
"h2_32": "cb87319f24560b5231579a09ad79f12eb60956e693ebb0102a4fb12324c7f789"
},
{
"name": "e(2·G1, G2), the square of e(G1, G2)",
"g1": "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e",
"g2": "93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8",
"gt": "079ab7b345eb23c944c957a36a6b74c37537163d4cbf73bad9751de1dd9c68ef72cb21447e259880f72a871c3eda1b0c017f1c95cf79b22b459599ea57e613e00cb75e35de1f837814a93b443c54241015ac9761f8fb20a44512ff5cfc04ac7f0f6b8b52b2b5d0661cbf232820a257b8c5594309c01c2a45e64c6a7142301e4fb36e6e16b5a85bd2e437599d103c3ace06d8046c6b3424c4cd2d72ce98d279f2290a28a87e8664cb0040580d0c485f34df45267f8c215dcbcd862787ab555c7e113286dee21c9c63a458898beb35914dc8daaac453441e7114b21af7b5f47d559879d477cf2a9cbd5b40c86becd071280900410bb2751d0a6af0fe175dcf9d864ecaac463c6218745b543f9e06289922434ee446030923a3e4c4473b4e3b1914081abd33a78d31eb8d4c1bb3baab0529bb7baf1103d848b4cead1a8e0aa7a7b260fbe79c67dbe41ca4d65ba8a54a72b61692a61ce5f4d7a093b2c46aa4bca6c4a66cf873d405ebc9c35d8aa639763720177b23beffaf522d5e41d3c5310ea3331409cebef9ef393aa00f2ac64673675521e8fc8fddaf90976e607e62a740ac59c3dddf95a6de4fba15beb30c43d4e3f803a3734dbeb064bf4bc4a03f945a4921e49d04ab8d45fd753a28b8fa082616b4b17bbcb685e455ff3bf8f60c3bd32a0c185ef728cf41a1b7b700b7e445f0b372bc29e370bc227d443c70ae9dbcf73fee8acedbd317a286a53266562d817269c004fb0f149dd925d2c590a960936763e519c2b62e14c7759f96672cd852194325904197b0b19c6b528ab33566946af39b",
"h2_16": "73e3dbd40bbe59ac85644aba27a08fc1",
"h2_32": "73e3dbd40bbe59ac85644aba27a08fc19183f393b830fab221565a43c65708af"
}
],
"h3": [
{
"name": "16 zero bytes each",
"sigma": "00000000000000000000000000000000",
"msg": "00000000000000000000000000000000",
"r": "1095e2f350a30d8b57ab5d35948ef05e1c8e32508d3f66873ed8ed95e965b64e",
"iterations": 1
},
{
"name": "32 bytes each",
"sigma": "abababababababababababababababababababababababababababababababab",
"msg": "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
"r": "0a1ddfa03dd187c9020bbbc712f9182472e7b0714a56c06707cdbca883a85180",
"iterations": 1
},
{
"name": "empty",
"sigma": "",
"msg": "",
"r": "70138cd56c5b0e6b00942408356c0b7c26b9893d0c2eb33510e0d2b9db78739d",
"iterations": 1
},
{
"name": "accepted at iteration 2 (sequence item 24)",
"sigma": "2a0e1f7caa8b0767d33890a394c460e3",
"msg": "72a72eda2b46bad46345cae2b80798ca",
"r": "7232567f27f5e7867a3382ff7d84a0490a4dcdd6a570f420bd51353e3f811326",
"iterations": 2
},
{
"name": "accepted at iteration 3 (sequence item 67)",
"sigma": "fdaa01708026990ff60ed74e8875f1b9",
"msg": "a2eefa73eaa7028f29dfddf86cb2a2bb",
"r": "6773be5dbb0ac8cbd3aebe463ff5911eee6d6d7f7b012de9f55382a3e99572da",
"iterations": 3
}
],
"h4": [
{
"sigma": "00000000000000000000000000000000",
"h4_16": "e98934fb796adfa42b207a1b701a473d",
"h4_32": "e98934fb796adfa42b207a1b701a473dc4843617fe8bfa0c766c0fd767c3bd98"
},
{
"sigma": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"h4_16": "66e2e5ff21703ab44b70ad3b9368c406",
"h4_32": "66e2e5ff21703ab44b70ad3b9368c406cbed15e958df8f527895bf9abf9940f2"
}
],
"round_identities": [
{
"round": 1,
"id": "cd2662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a50"
},
{
"round": 1000,
"id": "f652498d092acd949bad74e40683bf3824fb817980504a0c7e6722cfc5a9c0a3"
},
{
"round": 1001,
"id": "ce43c3353a7ad7aac3408cad0bf921b6a7dda89be75d9cb2b3b5a152cefc8afd"
},
{
"round": 83903165811,
"id": "d2f715a9a98312047535c17e4822f1630cb75940d956840e711853b57cf222d7"
},
{
"round": 9007199254740991,
"id": "6ebb1f681bf37ab86a120d042a9feab2875e0513104f6ca9676d6faa0570cedc"
}
],
"fixtures": [
{
"name": "empty_payload",
"round": 1001,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"body": "85ab07e5665d20f28d1837af3986dd863299ceee1ad78abe6be8757f058f1048ad7eb28ff7600a1ff3208aec9ee2da6d0009dddbefaf0011d4127cc6e33e48921fafb2a04e7a4fe8c065d91509ea55409de0205b945671c68830e013aa83e3aed473119aec86a8961f991d1effb418075a5381f66948ce236989f8666bc087fd",
"gt": "033a5320b36bf6e32e6706a94adf2beded51adf74403c8e26787b79971d7378514f775510faa183303398a3dc7e53b5716bf0ddf3fb184df18061b9fd1739ce7d69d6a733be5898f2f3fab2886c017add04ab845af7ac27854eb9a4f146a155e10e23ca6b607107105ad0c68d803fb55f0f9c7bf4aa6dd036a93315bd83d4f00f8ea5971125bd984203f8de491287973158bcff089f4ef1638124eff025dd541f37ca52b5cfc12f54896067c4654a2efef3721466e50e63e208eba444dd2a7ba148716e56c75052dd0da1b545575fc2c18b7398e0596f083d46800f671d606a0a71348d17b6092143bb583d3e2f8e73b148df56784d4b3130ccc717e7ccfa13f6ffe66bf057ba1db61038c2089db273c45a90bf2bcf240d22640ecca4c0bb2de1788ccf0be8dda75ecfe520ea206460ee63e0d2c6b6ce02998e2acdca57b71aa6a2ae2abee9d765820f9595d04b5dca2097d2bcb09074af723bf0d9ac014279dfbf9e90a24c0730a38e5eec87b83fadcffd3d8209b088f23415a9285562733620dc68c956f3570309f004a9be7aca24c8566eb6b6a49489776b4921c053a1ef35a84c1ab5f27ffb5030ba10113afa763045460ba90bfbd7ad035b374a95834c29b83ddda4a413bc06d68cdaea11a2a4d0b791a026aba1c7edc776e716014443819e85989d503f82706aefed5901e69ca3101a597ca8bde764fc7b5067e972173584c8008cd1e6264cdd45793024a3ab7127eb8bf85564618ee589b25f78879200e48b977313798cb7b4664a3c626512a34cb29a796b7c8ad580179b49cc7ef5b",
"sigma": "67b8e06b25bfae89804daa7ace0e0ff5",
"r": "015c0a49507b1208268feac750c97a12b3d08bdd59e367f86b7579c9f07046e4",
"file_key": "6cce480b39dcea2918359a8e77cdedae"
},
{
"name": "format2_empty_payload",
"round": 1001,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"body": "a6e32c1195ab7f032d10aa8003237d4cb1f99f8988cb4812a96a20a24418bcef9d2905924f0cbf9f1375f89c894a283018426d9464787b8cbdd9520a5aaa47a52adb641dbefbc002e25079edeb4b6cb29cb1b00dc5a3f8f2a7b3faffd54629093e36bde7ac65a31321b87345a23e4de345f05f11884107e1e34b38b3debc7642",
"gt": "17000fd35b5cb0c86615b7ebf241c4851922926c545d05236b62a5671cce1d465eb349615af697027a6f06a03932b18610863d02d3713e1ddee780fed14e3c3bf0a8038a9d317d978397d1832d200c7b6f2163d43514f33649c3bafb2fee96f816c218cdf4d3e4dff7bafe69e14d52f7203a0e33ce7a244610391cf7e2cd7b61cdd31bec18db802d4a64d1e63537d5160080f8ef2d7685fbdeef524b5090797c71d36cf1b957b4100e247220355cf02c29fe72dde7513b03f9e81c492ebb449e0c4ff2999bc497127489a88f4c6d4efc9ccfe9a0e5af8fa661141e790dac3871ed8eeb2e7b39876ef2cfed1b9467ce090bd6b1398e7017ed11bbf6b753f908c6ca180c329c74588449455f7fad031d16bc6ab7dcae2696c00cd53a014285c01103519952cb2ad6c03a7b90743add9a951cd7b084ac4fe35dfc28f640ad4d3247bfce348c422d21568f051988490c41dd186696ed8b3822708365243e465c077cde2ee605a375d0e224a779b501b6f57ca3b7e9c041e9e907ef37d571f95b524a17696f82622c929f5f6006d64527d598c6ee10182da10f7ee703f3c9498201cf4210f88b9a5ea81ef103107341a7d0660e1f905f5cb3c207e54d3229d49cfde6e256841740a1a0c6c4b5194583dca97027a46fab676a93c1f1bacb9db07f2606196989c2a13ba0f1ea075df3442ce7c7cbcbc0912e511db52a613354b43caa541a61d17da21eba0eacd082c0f557f6820a0a2bee30d95ae8c2b978f33400490c5cf0fc2bcb33f4eac02d4c02521e5c1970875f04ca27d9c0b71e164c0de6c14c",
"sigma": "48c4539dcf320c99a9eaefa599fa299c",
"r": "157d03c20721d75e44491d2a92320921b8acce96027f07255d8ab6f6c7293af6",
"file_key": "16eb141aa186309a59a7167804b3959e"
},
{
"name": "format2_time_and_key_portable",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "a764423117ba28fc0911bc64203cddc7b7886b13a60055b93d7be6a7091008b275071938ee4be5c97a9a774f5dbc15ad172692911d40e04c04a747184632ee7005f5a4a1e807d0509850cb6a76d643418b25bdac9f47e52a6bad596032a54cc1e862296fd5759f1d8e19f8511a440392b9fc5f078ad0124889a169f7e2b39111",
"gt": "1121cdd18516ea0c21760134f421519fd745ab71d248f8dd7e18c4d13e3d771e5d3be29d89ef0f3ba6305e9bbab7fd7d0d10cbf6b36b9bfb6de0ac385b5f8a301cd378123ce6c91e324c628a0acf4d94454966f414e42266486a4d5543e99a44133a16ac44a019e2ec2c22e3f6a0ca13712e0fc58733c0f5f85d5ffc630df56cbff249a466ee4751261d09040e0867eb10e013a7f065f656421a8b7a752975ce6884280633e106fb174a686ccbe3904efe5918a186d406e92a19e37f49b823c612ef48a7734b9825bcb18b25d544d497906c663a61a039524892d3a0f16254dd554f52edbc39d6ca0fcac632bdac313713362396fc2dcc5e9dedb54f92347b8e5afb7f89b5da9b44744344b331375e683136c2dcfd08c8d1c9e36c740d7ff7c3006deeffc4f5cfdfe7b9b30338512de6e5a8435a21866b3341e06267d35ebe29193beac4b55e0b225317b941a71cfd0105b730f6e0bf70b649da20cf67108eb7e856c38624265ffcf81e320f48c416db2807e3ac57613d93cd5c63fcd35940b10266c883ab19b16905cbb842118df0e60f354084df472dbd5d6a60584dcef71413d132b372a4ce294cfac4b4423aee5907eb9cecb64080f369a449657b714275ca18f07dd6cad0d54a9b9c2ec46013c8e20d86a4a763275fe4dacc9a9bcc044a08c8e1804dbf12baabcee64b4b10e7983cd09c0f609c08116fcee1711fafada680ddc1a25d7d206fd4931b26a27f8c9b0e26b223a41571ae6ff87182297cd66fccc8d85845e91602a9fb84fef9f34dfe557e2d632b91cb3bcb0b79d55490e275",
"sigma": "199acb924ff1436081af3f30f5f3e3bf",
"r": "04e47385f151fb4e74f61945c255b43af52b641a68462c06f7efc3750050a0db",
"file_key": "0f9deda78937a5a73867c2e06cb9a891"
},
{
"name": "format2_time_and_key_recipients",
"round": 1001,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"body": "90e5582236742b544e198a48f1ffd9b265e26988c4f981e8c5e547c0bc7b56c337840607a0d3e7351eb0b51fb4a5ad2101c89ec76b19bd97f4b36f95e263994ddc07835765d9e2ae863048aa4a524361823486d4334edfb7184aab24282a45283df86f773c6a6a9c87cfab26a48193114cde9c6e364bd7f0464e0ddbf5f275a3",
"gt": "0e379baf784d9bd8a0ac29b841f151e12c251afa04cef84985c39900c93212480ca4f71d6c3cd0be66279bd9bfbc344d19674d96bef892b1740177021cc5b3f1fd5e88ce501b361b4324f3a222e1b9db030e47018ae3ca87dac873f1a9d3364a19fc6b70b70eadb077d93be390016bed0f728a9e20c6f0facb74ae7a496e4de79bb256a3ebc7603de33e132e1c905c731841df4e9534d0ad3b18ff6351e562e800e3e00e6b45ef49ff5373bc99c89f376ad63367467f500f87ae56f0d1befeea0b0246eb18ea49609214a2e3c846db46bd05c6f0963bee80ea38a16d287c2269b8ca116b24ff96ba50b68ba916a5f61e132d5d603e979a813555de420ddd731f8f82bc27b093bdf7bb9b63c633c5bf17201a5d5a4f30c0632ec23d18701d1bbb00bb37d76ee5b29e29be293543941543691deba8a713c17547237f2fb8b237c743c5fb051f234d598f44d7d505ab66b015f62703bc208c0984fb1657fb94f36eaacd252054ea83fede2213bbf4e2fd2938bfec1270ada779e156d3393edc542b1028b99dbf40ea00f0ce75e0f3fd555c69f68fe1cc5d44b349309a10686f70eec5bffda9c22c8dfe83062d00ce9239370a7cb7d76f39cf0c64788e626c399f64c01cd1f05a04d2c9fe62c122b54d8ba00535ba357afbae92fe30171b5d66bf491427a34dab1e04101da47aa6e0371b309db519789c53869701b317ae757b49e5f367c7f687ab6e112251733a787e147d0527dc99c12504f6400d44c16f3e6529ee1299e078bc246fe9d0779e1a96881d7ea921bdb480bae996fb6694487ebbb3",
"sigma": "a4a691a1cb4d3298871a4ef464f22721",
"r": "3d57cfabbd284d111f3f06903ee6c5f8b91a1f3b16f562b0d776d77284e0ffbd",
"file_key": "fb54571041b259f50385431259a8fbca"
},
{
"name": "format2_time_and_key_sixteen",
"round": 2000,
"signature": "b6cb8f482a0b15d45936a4c4ea08e98a087e71787caee3f4d07a8a9843b1bc5423c6b3c22f446488b3137eaca799c77e",
"body": "a71f93e59d6fda067a4b63dc6b5acc48138b4ae6b5904775b3adddcd8f96f73bdf1e8fc6a4fa9c2ccfe5d00f19028f310a6ea606974c19948d96c237f1d05f350a078d28121d75f81d9244d075fe5a0426c3ab9d0e7645ecf464d3b790824999e6cbfdd948a44a9287f11da779a1e8f19d32d27aa4c821072ae8f7cac0e2dc88",
"gt": "0b63b57a2d1ffda34327d54cbc1679fd1ed72390d277654961c9b51dca205ceccd36cb39ba919a788913f8a7707ec2fe0b915cf7400b5be6f2e12403faa50928a21da0d924c984214e58ca2538a18372b7dd0ff0202f019b684ad1f6ad162b96074b1217493e1b3bd85375fea9583b189cf032d0829bc522711651d705f69999e42e682b05b2342500e3116086b394f4072c46316413602cff5fcd422ebb55eaa5734d7863932e4b013c3a83e3b46a05cf4fe9226e908753e84749094bed32c7071167225edf00641284910398d3a3d01c520829c1cc920e839061efe61e2f6b7f6a77d36118558169da6a581421069a1101c8ff9415033506ed03bceb55352cbb67ade147391c563aad0ef4286baf58c7f5c017311a7a2880f33fb23c491da40a5eac0e93923d979c142c8457abe1ce73cfc8fe826cd34b19d96da81f9af7d5614823c4b101a1560324fa77d161779c0f7f7369717e8a317a2685128dcd0a1426e82a216af4299a37b86033c7b502b29320304c6d75a76aafd3e576cd6b89820857a6cbe7ef69909f5ca93626a5b1954f5c62590eaa4e5b38385c2c264f594d02b4ee577b71edf07d959f5a48e425440ec466c018dfa6ef434a062270342df0de16d3c52de7fc0666ebb7227983fddf1d6011ed484b8bc72ca62956c05059151527218b7dc21b6e03a176af56ebff24d68fcbb4b54a88831046d7905d40a17d35af263e3bb11bc3f629d949d67b804d0cb1639f28822e21f1169a11af5e75a9110b4d3a425427f3e7006d2325f08daf3d34c31b0284ba6e60271f7de9812492",
"sigma": "d50d85dd51c16d94abc37b1f64cd26c5",
"r": "43588e7e0923356307ad06e5cd147115a64f0d839d5088724f67d2ac187933d2",
"file_key": "192130bc5124b98ce01e1ae0bac64d2e"
},
{
"name": "format2_time_only",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "a41ba7a5a55999d4c8840035a484e9176cab4572e06a6631af9d4577333ff54426c2fd253e86ce6a80cff77c705878ed00fa03f9b6e4089536d8d69f33d5bace38e927a2eaf16b01e84f2bdc4966c0508aef4598c97ba7bfc068d1cf8a6f0d789ba3b7ff0ca0a7da88642228486bb42c1cd6ec2660f44f4cd551f6fe126ab06a",
"gt": "0358a381df0523eb2b1a63c563d8ca2eb3965fc6f20494796cea42b29b59b1c7fabf8b5452f862dabd55ea4452a7e0be039129ced37ecd2608b7d18dfab95279c316b6391ac80e25523cc63bf891075bd56999a11bb93e89eda3cb5dab0f3cb5021404265841476939b7d49beadd7b281d3e980b6f3095833dd3fd07191eefb5f73a3adfd31729354ac586ae5589ea2916a56a5bd00ded2a18f9f0c8389c542fc27e26eb822cf947dbe9c843347c3339946f06b203434619cdf6d1f92a49e85b09c9b62b6c8164f833609d55b25bd839416e61c855e87e93502bf2bddb922248e2785ee428021b879033341a555cef251708526cba2e55e5fccd262a7fa813de4095bb64423856e6e801f364fbec60d320ac238fbbc203001c8ecffe8bd4781219c0a28e987ff6a2f29ece48649f5808fce5a8bde146d34facc02341bf7f90aa136e13557f3c9d68446c1af19c04922b175c130cc7d5bfd75643f7769773f3a6ee77fbcfe3c628e044959d2ec3d7b6efdb56e79e5e46e893b8c564739e01bcea088152951d15095a77d1019e3a383dee8efe374749d785b3a2cfda926e1e0623d2cd0df4f71d9817b150ef2b29238ba513bd82bd09b30356b536eb6e53a5b20e09a98cfd67c23377d18b42a73dba21eca2395ca310c6e86a5cacac785402bf731909b61919832607bf5dc439785def4163ba95a3d1aff874510665bd5f21def8db13a3ce7dd90970265da194e58fed770d3d41db2ed49b8c4fb9519fe75e6504f5eb36d96e61bc921a710ae7fc026b6f293871da663f89039508cd37eb63e63f",
"sigma": "52f4c46330634adbb6ddb51a864be723",
"r": "18a93bf2a22c12f20b82dc48566db62ee760cca3cc215212306daa87b0b1e85f",
"file_key": "c09fa00480a7a742d20a6ab8bb74d99b"
},
{
"name": "format2_time_only_bloque256",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "b3d271f2f0b554becba63162b4081c8f68d23bf7092ef06b6d355fd8dff5bcc55b36f80391286eb7cd3b13f658120d7b0c8b7d3c7ae90b2d16bde731520383157378bf4400863498c8fb02ab04a842353f65551896e6659b1bc2483c67a6886f3f15b788f1fe776265fdd683528f356e8bb0aabc6fd527049ca60a17ebd31fa6",
"gt": "0dfee74aa9e540c57f81e6af154538e1a15e7dec1f05b22d516a498141e6bbf64f07cf856319348c1321cfc046753a5f0ef56d9ea512cd4476584e908e86aec7b148ad861b86c5c8a8aabdd2e1501ecc07afdf52accd60564e40af0388f4604f040ea220cf8a7f4ade72ff4c63b743d511110cf8325eddb0608a85c714051e2891eff5d13bd0cab56ef61ef2a6bb1755046188e58543c3741044eb8a9cf14a82d2f73b7919cf7443ec4b8478ec68066bd3bea3d7ea1fde80998a77029e3b0a41035c5ea4d4e6b3fce0db59e42fe57fb41406ad97fb063412001f1647c88599263be841ddcd47a0a8bce44a7f552bc76915a1a088796885bef44faf88cb069265debcd11236d572f614ab0a768a034d0f07f089c7b56631bcd16d6ee771a7c75b14e394f8e0ec83361f0db83d58a47a7f7409825d0a1b71127df2ac0072f164298b75d196f8378e588e4273a7b3e9ebe313603ac3a641edcbfaa92ae162d9fb6bf7e6d6e03202913d46f62b1a491fc5974bcdffcadd33163bec488300f1def3821878c30a5d355a4ba7181fb0c76d302392937f946921a98a5cf2f822bf5fb144b54f7fd8e053648264b587a6e26591d7161eaab73e00230919cdf402ecc46fc3e1f6ba7ceacdbbd4c099947bbc332aa47002e4be0093d523b7e9bbdfe593fe361601304ecb88d73e11aa700a69c9615caf5d72b3b6982ef55627ed6d5927f6ced2563cbb9254b94a049d125a09da6f65149ddccb92fca9522c36aaf1c763493630f9524f124e1d4a76864b97a686e261507d5379211a8a14603ba4d03b0ac70f",
"sigma": "190ca3978873ff48829690bbb6b21112",
"r": "65529c8a4b77835b0771e72149feefbdaaef3f30537122136d119f97f68f972d",
"file_key": "732a6e8af8fccf4c00c1110e2ef513a8"
},
{
"name": "format2_time_only_extensions",
"round": 2000,
"signature": "b6cb8f482a0b15d45936a4c4ea08e98a087e71787caee3f4d07a8a9843b1bc5423c6b3c22f446488b3137eaca799c77e",
"body": "93ed2308ff61b4e937d67fd887eaef6958339e31c34242aeb95205c829bf42ee308b21edd1a65f68c350ac865d6f7a3c13944cf2723451062a5e95a81ccc060c1da2e78156a293ccba833310856f362c5e303350cb8917b15110e3e9e9e19aeef70d17d4c3e5ead72524b17fc3debd7311753b63783f093edac7b68c45d6961d",
"gt": "05e4ced151188636235a40f784321a733be8655469a91111c4546b0061b95150ce35e471fdf1bb346ad1d0493e3d28c406eca2b00148565ab236ea3554614c22d7c741cfed9ff0fd5129771146faba564b403966bc30671e07f6b132464e156210cd85548efa27c6980801e77fb46124aa272ff3ec2e6a677e8264d92c93a2c2eaf17cd6694f01cfe4e848e4b071611605363b185a54207e7689b7ebd07b674296508eb07bec2aa581e39bcea7b697bd301c046a9858746854c972598ccfe2330345881beef347de38af2e9164e41e70345b4b779a66c3b434c507e3de384a3da18597e8135df0542ba77945455b851413ea964f4952911b096bf6cc4815a32ce8f96fafe42cd51b5dd02cf320cf1119a959c402756f44d009011b4b4c0975e809f1accb9220b7c97ce485168fc3827895883d2d188c92d0b24616a00062fd8c3383801f9620e0f8f653e1578b37daf50e68e91b7c1b31ce737632cd1ca73aa2a9b66e439fbffa1cff7ac55f23a5d78bf27cb9c8da03e79012d63713e8a16b5f10635d625cd4e46ebf271040d1044d8f67fd6d545219c85219fbd810ff215ccacb76afeb93c0da91d48538db8a197fd30ef6eb83f93851e869a63b54d21b2bdb4113eddfd99ff4118701ae02d495ace15c5ec15fd9eefd51fb3812fcb2f04c4d09c5e14c67464feac123bc0c74435933b21acea8fae0f7c763bdc5cbc6867f8d2d2afafaafa81dacf71e1ae12f747ea500d1f1e3637a46ea25b4c3926d87637b7bf0726e1d5009f8646a7e867327674f53058ebb1334d3fa4ebb6c8ae94f9f3d",
"sigma": "665b32ed017c84faf21ed0f30eb75875",
"r": "18b536fc5a38d45e95e4536ef58c802d68bed50fc4e9ac373989708ea6062ab3",
"file_key": "31287dcf0f97929d1cf86f80f30d1bda"
},
{
"name": "format3_area_1024",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "ab9c349abeb16c6c83acc7203f8782a8d7abb1c7c3e6d2a5c038c9183be47eed652b1376e674a08d8f6bae373baabec316a96aecf482eca4ceab2b48b492570466cafa01a40f3f016832cd28be27f06bdfef49a9f80946065403a44d4d30b1c75fe5c10ecce2304925c50f1dd76b5144bc4a5dcf74fdb11135cd2be51937753c",
"gt": "07737ecb22eb1289e6aa989294b2e0034e920f577e9a216ace84f0977bc4aaeefdd0ccff9046ccecd3b8ee8e43af221a0a782bef7e6eece628834f48f0f075dcd414362753bae07230ea84ab839364c0658ab275ae7c814ebd309be0c489409f179fbb21b279a401ae3d4bf4b8b792eca09a9251e21a0b54653165b75271a1753f38167f9f78093cec769ee36d8e93ea0e7d8850df57d4e0f080e24fca58ca4472e651a4248c6cff2580565d758e47b01c69b55a60f9ac1b27fd8f4b69d7c30311776f653f10af92256ab97bd4030b3c88f2ec392c11a5a01b472b565d6b718899c2a62907d4b721635609e062498b6c1702df1bdc5afcd27c732edb4a8e2850403c6a81adc162796758ff8b279c47a4e4182c61505f9a3d8e700c686f774eba19c26d68b2e4365a4258edb5109c57f3b4386dea1a1fd98ca978a435579584120b0de2c41321c28fe4dfc1da8ff6f6cf07ffa3bbd2be1494260a78111f4d70c77103c93c57a00e28bee4658c5f510fd4da173c4171ff5513d3a4c91564212e2817eb668d3f88826b53afcc16ae462732d359cc02cb45b644a1c797acade03c4e8cc8cf234760d9331a5bd7f7917a631c1619f31f000bff25c50a8e99323a9cd7a46f6402ecf3843fedd91884894c3607fea949f47506e10df89909c236c395f5050100a70ee257a516eeb1c4ffbefe789e9ee4880248fd6e93a5811b355d5092340e6dfd00025ad300b319fa1f37eecf0d957ef54440f9e77a47aea9f5afad1cf36883c1f43059a75fb45657a2099edda716df55449fa6b35997a06e38133882",
"sigma": "66718795167700208d463a4579e935f3",
"r": "3a97e5df6c629c1ba39f5b8d91cc44289b358db34d8db08532fb9cfe5f8d64ce",
"file_key": "dace521b9607289349e9dce003a3b19d"
},
{
"name": "format3_bloque256",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "9725a254c438f62ecd51cdf5968d06d2cefe045afca9a97616fa1e89982667561e4a5a7fb1027f467b14072f38f2f44b14afcb6ce616e163c039ad4e374a019a2301051230b22cf2158cfac4785fc9a3f17a60f1b1786576b523fbb5c0629f21d0bd9f360c87f7954ab5b0dc9d1f45c4686e4cbee8b2ef3401292600c7125272",
"gt": "14cf9afa6ba953c2788e2ebbeb63b238bfa65558452b2d808ff8a1f08df17151da38f5c45385591054979a84691471a606c4dc140ec49edf78fdc7ec4751915f86ac5538635bc1acf35bebd8720b357bdb72cd1e2ecc035ef331ed128af143cd10db139904b4187e0bc491734d55eb8bf7efb87544ee943b350be79d56acc9777629fbf797b73bff0ff95f3f30e74c47162ec73a6e2242372dfc92d777e174ad6a1f5c60cf367925bbebff2fe54048edfa3e2b55b776249dd9cbf108916b14b60d1795c1593d3a203f1fb21f7b33ca3c29e704c4ad042953fde8ca865755cf643ec266fca7872435476fe086802d67ac14ebab0e52c41688ac758d564bae9df35c9d2cec8790c600372838699882713c805c41554cd38d001f51dcd839e1381412c816034183eb5327cda6ea9845c0b1333c01e53f30075fe52ee77cc786b16cb873fc2cbc2f0ea99293edf606d8f81a0b3c8725662a8947c05b3c71c3c4305edac9b8975bc185158d215c51446c7545249c7876f075e54fe9e2f2f90e2e53b205651ec7d99e7c127f54a0d600a809f4db0b286f0cd8fae74d73d1f98bbebdc6311a6eb461fb95066cc65ddcf4e95cb30b3ccb5c99c6bd14a7a90cdbb6a095c396c0b492e3434d24570d44e9ff69aa0719ecb28290c5814e5e33f8a08bc818770cb2130eef27a6afdbea485accc134ee2d7ef715cb1fa8efcdfc308adce47df29a840cedb5ba7fb4585b93a9cb9aaab00ab1366e0cfc5ed485412d535824a4ee8e9d12f4b0d902337d96a2b75ea6af51052999ca878fe7c13750aa3c6f064bd4",
"sigma": "30f781ff0cc6749b5330b9ffbb18e1ba",
"r": "3db1c5796e5a2bdc194541a4f46fb29bd9b8cc3bee59a0d6d6aebaf32085dc06",
"file_key": "b4b1967f367ad2fcb59199962b392431"
},
{
"name": "format3_comment_only",
"round": 1004,
"signature": "a40658b820c0f8c10207524179a2031ba9537688a0d04e4851b58026be9a341fee3b96fb48ffad28483d84b40a5864aa",
"body": "a60acf311cf051bfacdfe6fcb2251f05f3d39dd801e0f21000f5d0888f73a92da79c17a26ce142953f5c2eae7043de770b12bf2973a382309f032340c160542b054f4bc4e464959c0ca22bf70498cf74921e5d358c374e57f9d042752e538589b0ac8b9790dee44dcf7885d87354f8f77763967069a85a8d0693bdae985fe772",
"gt": "177d18d467f391cb50a6e924de24131f6478594f66fda55b21fc69d270c78ef8d3ca74dd87533b2ab096ddb9293406fa10e27733f618ac9958f935267db539a2c3c091ce4e6877866bde1c42a286e9bbab85478ed07c892172f01586fb21eec51505c9db7c2690c71a452427eb9f5a16c9e2c12a608cd6336d195a839dc08fffae6142ca4c7a4655462f6ed7b08f86980c2780001fab28ce1fc6b448cad7735c3af6702b5bf0ea18dc22ac087b114982982ae6ea1dc197cb66df0528a286d55f13be7b8f1f1f58ee354189dac3ea60c6324ad19bec1aee08f89939e6044cb5ebe2fb57d6788a7284a043c7b387d0218f0ab88e153da8b3027bbaa4faaaebacc4b37463ed2d369c21f1ea22f39c3536a96a9e5895e8bb88be53218558e5fb29f716a86b37fcb1e8acd221b251950c8d04b8f5050a3cbd24b61fcce1a62eea9b68b63ca80bf64528b145a5d32a7364362b074f2d7ffa7ba5215c69b6d765f27e30d812c79feab88267b6d1fe6b73c4471d4b0dff62d04c3b6a8050c6ed76832be80b2bf8e2d93bd48ccd660386fe4d95ea4946f379a606e32c7bef3105ac700853d81ce2ebfd7876b438954d7aff6b0da60fa4a131fca2ab95008082770fb23e8993f34fb6a4cb071024d74228edb086c551986e56657b95bfae535c837028a3b40ccdae7b021b8f9da3fd37f46082910da7401198010e5080fd9da4ceee315b0a45ceae0fd7e6183f8ff4ca1a385d60d90f0ab2011ca6805029f93c535fc85b21e62db6533f12ad92d783dba6a899a6846e69a7287e02918ed2051f254b9e9996",
"sigma": "dc13a4caa43be8105ef09f20d7c65655",
"r": "5ab044fad730f0470dfe574e1edd192065ff12ee04909e8cfe90ebc762cf5df7",
"file_key": "b6a845d405e6a47733b42802de903b6c"
},
{
"name": "format3_seal_unsupported",
"round": 2000,
"signature": "b6cb8f482a0b15d45936a4c4ea08e98a087e71787caee3f4d07a8a9843b1bc5423c6b3c22f446488b3137eaca799c77e",
"body": "8f2cb7577b036e54ca2019d01c6941d0ea9f18e3caade81e41c8f77f1514c0a5c75583d6372db607f902d5056c942b0c16c48999c5897a1b44953499a2da59eabc7fbd6ec27886882ce0bf2becdb259b8c62b940dcdfa43865e8de37c74dc49bffa90f04530e33134cc4ee5a843aae112b49ea99eaefc5645b5f963866ed94cb",
"gt": "0a6e979e201b2e75f93ab0af0d266ac7cc8a3617b6aff1e69b65b22dc64055057ce04a5b011c9575306692d12c7673c300aa12d22ecfbf09672bb412f563e8fded84b9a21751c88acffe5802d9a7df55a66be413cd3d2212633403270b96ad32142cc55923292008525f833f2d7b747dbb6f9c7653e219bff4e9c9e0516ebef129884263c4121939b278118d94a42d901045dc777ad453d5d0fe2733da0d22659d61391e6bce2b2cf285ccda5ccbd4d55c7b4d6173131e7fc67d9abd4849f67916e95986d61a47008f4c999f4181b6bf252ad1e82a4e8d28b197d76c08d533427159b66b7e1b2f9b07fda931aa2c5055059ec52f03addc43093aa3e4a9128f8853f2f13cafadf09ceb83a6e2a03c4c289fca5a290db1f1ff03037ad33fee894a0ad29276146cbccdf7433b8c24a1be750355e7c4f9c5d8ffc9824596530677d7fbe31da1424572493ab046340201c20708ae6c523eee32e21fc5ecd62de2fd764180372f617b7fb3fc960554cc4594fa31d74041a3fda4c1291a47a853bcbd3714d9735ee0b398de27283f571d68e37dfcf947d135c0f8db36556318a580de154f874271a4ebdde23bb966d3ef9977640e3b0943c1ee44872ddd379b2a307d1c3baae21bf9546b625f6f2a0aa11bd15c63df7d2315cfa16cf1b59629babca09818e8baab411f7943b1d16ca53df07a417eaeb19b1fb48c3451fe53c0b8d00398872b83f6836e7dc37b3005624488cf81123bfc106d79e3f41e8cfc338b9f960153b4f7a011d90cd0eb81ef3bf6264be3fb165f8989621ee8543ca1741e87091d",
"sigma": "25fedf2e4e3d2db30d94a0b468b90e38",
"r": "51d85fb4996c5c96c33e15def42a07200863bcda50cb31b102ac1e062e361e8a",
"file_key": "ad1ef55f85be2eb6be3b14c2e29d3b10"
},
{
"name": "format3_sealed",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "a997ce105da38e371d30e785139a09988fa76d996731b452b367b2f162e45e7e50d6b637ba7859285c30d2c4f820af1b102a566baedafd449bf5822b312e47687e42c0415c75d6a1c28f53535f030161d7f76ed21ad0559c06c944134f76171b999565fdc59b95d4ed7e86cde4d69425cc468c85b1034a7b9b9a59bca57851ec",
"gt": "001c6f2698d3a4bc7f23c878263702983399303c88873844513fe85d10955c2b7e69936c732c1077c153c6601d825cf7062afd39ff72904bf52af7a30585da8cb4f0f6ee9d2ac12baf3814c278c09c1742b36a40705b91fe0b227e06497f8083102a98bd58616c3bbb4fb0aad092bb87ba93e3d86b9a9e68140036de255647436b4accad045b329e91fbf432621b28c413e0ca8716dae6632087060a2925f7714d649528aac5b42071c4252435ec7591fd4c7dd13b99e25a1f9e53d3d2ed51c00a4fa88689bdf09ecd5f4ba2900ea126e217e73192188618cb1f458c9e9f7eceb05fd552fc01d1e51197164ed04a84640745cded02537d0586f312159d36ef30e0d7f67b166159a3cac2d6c7de7b9d3d106bd35bde3336ee925cbd3201056c0a0f8437f0bc1acc98afd51c0f0f729f271cbdccd1481da059f6854efe3843897997353495e4b769de981eb57c5639257b0d3b7a8c53f45c42cee145a111e86abd6def541e2bc10a5626499d4f4aa697c64c005e0b1f3b87944ce8267f3e0dad6a012b5c49b77b9917ffa0bad6983b19370c4cbe51c2af02cb987240faf1bac6492214ea3fa45ec28e075ed88df84553660c63ea07fd57c53ce6342d58b9f41025f252e5344ebba00f0df57fd477604b48516c722c5c11f3bfa0eb5d09b8709711190135845ffd12c9e4fb9f90a2f630006304a92e51412710aa7e4dec55628584aa39a84713225957a9bec21771697bd501b4b3b3c0d17725736a05883e1420af57f4985ea2127f7f088250d4fa1470a50fc37311f01ad479d34f6c58c7fb27ab",
"sigma": "561ecf99d0ee7b65b9b8eabdf1326d49",
"r": "2cb8d938e57ac3f488a86ae32e8b8cf337e53d1dc4026251fbb765c7d55a5f56",
"file_key": "e28fad3c090481bd1c51bbe29c1e51e3"
},
{
"name": "format3_security_v2",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "b64edc6b470f6e17f57dba4611ea879d4535168e88f8a2951287c90f7ae1132fa52a779924d08da2f0e3f6ba4191010906124437afac87f0eb5b65e0a65b20319f62fd4f253227ef8b2ecee54218b51c8248cb5768d6171b9a551926b3a6acba77746b5611621d5e6757f9457aa3147afed8d20b6af00028c78852a4ec6fe0c5",
"gt": "0acbce2d25a128d205dc23fc2659b88237304071d53e34d9cef94b1713523f65d2076415d78b1f7c74a590f2ae54cd9b13a462d96d8684c983bfca13bf412f6bbfd9626ed3d2097082155a83a617ddd2392975db174a7c6d2be3df98a38a3e6c100c1cfecd0f6edaec47b24753d7c0999ba307d2530ab3bda27259bff768bd931a4d5a32555443c0ee0736e609c0489a1945556c20c8fae93451ff5bfd4430887be0ba3dda676bb36029ef50cfe8504fe1fda9b3c727a6d10fcc43a99dadd2651944b61550b7e61ea31ee395685e52c2760fa19c2f0ee8d47c7702ce1d9478c5061227c8366c4e435fdc63fd37f6f3c10306f5c7efaea24758fd4bdf269d773191a9dbf574525778a94d3d0cb611e79674a796fee85bac6ce027e90b3915496014d6ac6fd7051d13ee6d394b909492c74133c005354195346f456cc0e97e0036d9928de45676edd41b33867a513aac940546e242697df36e098edad53641e4732554d56c119cb2c2abfbdeefa37576b615e56018fda750962021139ed67d437c027747689688ac4ef99874239d7501a299f4c65e07f43913900d8f8566e79c2ba9ee74d46e26c2146690e26bb6bbc83318bd40c60fe204e046c9235707c7d8d82f89b40478bc8f2a0e039837dd1f905ca1c7227d3373ec43f2989c80d18ffe0504fa5f72ed29c3658dcab598d353825005d9574ddbb444da5ad34995b368861997f66ff866e3c16a7628678677184467137a61d33920dcc2692876bd5c23a07f81490e0352a77e5e449882908f053343bf6cfe0471a38cb9af557d1dac95da31",
"sigma": "d51272c61a41cf35bfa3e7e0ba9e1846",
"r": "2ca3abbd7a60058b822ddb76e74b77af3fe49aa03e000df4ebd5d1d9656314ba",
"file_key": "6ab0783f5f886ba111e4ca356e38d8d2"
},
{
"name": "format3_signature_unsupported",
"round": 1001,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"body": "956a20295b5d8ff897cdf7fc9e533de6fcb6057fa10b755893897c4c6fb3fb72374e60eec44a7f2389b6b49972e368160c1479b4a14ecaf4ea8c27fb160c585f3c2ac8357be1005d783aa174ffac39775b5f51e4f6f7623fc82b77969cbe6186e5d95281941b126463673b5d33ed12972719d40fc8602bc1952750a42f0d6ea2",
"gt": "14700225b064ece17f372d5137af4c4242db24d8c3bd506b1259ecd21ae3c2845cd56b84fadc5723771e18ae2cc94285051cda39cf875302162539268801b7e9143809495233e5a77842edcbe0baf59ffe085f971329b670b17b0bcf84d8d74b088b75aaf80aae14e624de290d416fa31abe62dedb1c9dd2155d2dc026c4b5eb68121276680026783a36472429454f7f17cecd5400cf6356659665e72b200f750aa9047e84fd5721b5cc40a077dca09d2b9b6b126689439731b196ef1eef50b0095f10ea1f86cdc4d3db1d7ff339584f614f4eaef8463a35c40b6b9025d390dbafde382e73f3394690a3814cdfa65a6615bc8c8bdc4f3a010c6884bc7ff56268d301b4b631698df49a8a8153440cac5a37dda08280dc8bdfce96cbc3193862bb092955dcccd05445bd6fda0b30ef07177a12f241b2b80751528b136bcfd2b92ce0f8d00f892d7523bdb7de3f6f1ab59008d716fe3eaea532f8d76fc46350cb08cab7b28c4aedaa6536ed2023ce0ad4b124f9f734cb8ea7f555510cafb4808e9111a92d0565798da927e5931f6112a132b7e7976c65373e42edbe8444a2f81882e16326c244d6b3bb338dfc54e2c01fe4100a9a2965a72a1d812ecd45fe7d687b2123bd5e2ad48363ebfabe7368c709ee0ae934fe01c4d8328f877f080f334b761568ef4d037787ad6024c736d783c5e10152c2764e6371df3c9d62fd38ad3a5868a46ecb9413ea8f444e25cbf2e655880004d7773e1a83f1396610bee578b948cecab442db420df57323ea0d092001112a10809b0681797825abac68cfd3553f",
"sigma": "d0cc7b69b9ff4006bfff36a70c7987e2",
"r": "42793a9f250083ed5340c13d093381f71f800fdcaa65bf0058c7893e9f29a56a",
"file_key": "7043ce939cf88f8fd50476bb8fc45014"
},
{
"name": "format3_signed",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "a633e4d9d6b63893e7441848e2e8530fa4aa912d86df7644feee085701a3bb1269a640556de1b08f233397edc405969e006d17781bf2e447a0d23c75ed8b8b5dbb14aec6d2b485ee4a58487a5b6c51645dd9bde14f62ab4676ced2da368e3eae4f292d6ba505506c8c854ff0df690005d909ad470606ab6780d56ddb77e09f3e",
"gt": "08afd5eb35cc1269ffcb356ed60fc50514363f9c7ac28e310558bbfd129f039fe4abdcccfd5294ebdbcce0bd61484a8d0533dd91cfe34dd9871a498e3e7e1ad410993906255df086d2c5112b603941f01e9813f94ad6da342c8a699bc83cee7308fcf55b062be1141aa2a9be56f66dc1ed15d9fcf48310aaf1f614844c206d2b10ebba135df7a55f017233fb362896e00f3d95613ca8ee5dc62bd02fdc87cfc074839593157c74afdda2960d65b5c025dbbc4f2949e2e347a743dd0b3b6ad15c11cd03f50f84c7e67a2d02f8831c2da4a07b64b8981742dab4586b1be1b53145624aa83c44447b7d56b725e288fb4ddd195a69cae2e84b3ac99b9d779f1d174f926528198669acda970e8dfafc2252e96501c92d29e62097da1dab49ffd679ee08c86b5c002068cfcac503d945701b94a27056d9474eacb32e7f1d5c20f81c31c77d77315d3b65239f537c7b728c32dd01d991e294dac259982a5b8ecf5c954574f886dfadc91b76fa981229bebd46183b6f098de205dc203e001494d7aa04a017a9939c3a6398651d3bd58b09f93ce42a4831b1e337a923c451dc1b58c174ed709d2a4b65cfbaa86e545d96ab9744cc05daa4e03fe2484769236636e2c014a767c4fa4aa1b34e5fa0e04da48e7d50ebd8f7957a4a656ec35d5670d2f88fbf561827704a5eed2156f22a7e34458ac470095930f2d816c8a128faf5f89bd22f987f355b94012c7b03823c7f3505986cb917e21873b81d2f71ff0ca7deca1de541ceb4821550edb824aea7be01e8b9d3ca69d248a2c2cbb726776661d3965377b0",
"sigma": "05def51763185fe63e7fe7f2ae189eea",
"r": "222d8e58e9e99a41cb3d32f3aa8dce673ed42ddeb04fad64c3a2251c5ca64d85",
"file_key": "956e9ef859948545f4adb5e8d6c76e0e"
},
{
"name": "format3_signed_cms",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "9504af06ca444a66940de36d5c15d197d828d2e5f8de834a29190c4d61efe831d719a5acaf7b598b3338134baf78846e139247bf66b694f39d4b6c756c51d8c22264de003d1188485c8ab853438a01eed8acd71beaec168e8507586b56f9216120bec147e04e1c6e2c1c9faa0a0dc5a9f28ba97f3fca64ce81cf4bfe9f7f56e9",
"gt": "194557dea3992a89e3f3c9e18bd7830dcfa80e6ce353e6a978df7f0747790286aa441c1d6625e7a078f93d3d430bd3d5186a9d396b29f82ef16a9d491bd6a7f95ccbb0e67dbbdf387cdaa98f6a8466932bb879d56cde79f2642184087b89bdac020f1902e5b89e119b611134d2de3dadbfe28573bca6fac7e3bace78ace8a10104643f5ba979a0ff1fe78d92c9f1265c12cb2b75750e810c30ef56da94cc08ab70a728526d541eba29659b3a61924fe6e300231f16de4f0e54add69d0020e256032ea3f894c5de0b39bf15fb6e0851931b78579990d9f339032ef7906834e0173bc7149735a716f3f1f22c3d8741e1ca0af3b81cbde5a78bba9116a9f4700de4b6ea6e38681eac76a113a4a5fda67b6b4742faeb7b18396630e045fee91131f70da39c61e72730299206fed44981f8d5ebfb9cf42b60330db1602d6a91f529e4eb0f6fd312f20db3fdc6b5b1066d5e92019ee088f5e8a2cc8351ad059e5d44831cdf4cdfb2b4c4b5e55ea36c6a02c98bfb38b5353b9fcc55409df61050e9d26f0f5113f8bc466a6a875f44ff724391e2c33df0723ff631e0c12f01d9c85be7a987e7a9b755560b07e40cd74a3cf4e481128c4da457ef651a5b439405ec92507fdfe5dfa15f6d93c0369606427896156a6788672f70fbdcdfde021fc706b1f074130af3acb4b84d1e6c08ebf0ffe331d68db84803e1d6ad49ec9a1903e634a5d25607e0d48425cfab07168615bebac1a60793b2e6b11df0594cd7cb6616f6c412946f364fad59057432b7f635bac46ed65a5cd10ccc6eeb429edc5779bb058471",
"sigma": "644b74a1d11765ce2aea0b119b0bc161",
"r": "12b28076640c9005c18ded711e0d83e826039657477a9ab9c2c0973018d9bd6a",
"file_key": "7a27fbf3d45ec8133a3d27340f76f8b3"
},
{
"name": "format3_single",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "a44728b99cf21c2831a8a3003bc1736ac92a7eeefff9c7e94f8eb2babb1483008599d8667b86fbffff5c529ba886062517a30f8746a717694f3dbd5393a1ee890f3a726fae22192d538db939ad6d98823f7fb2e0470e60f927a76395114d01805e9ebf258dea16d18b70b7cab491fe085f6fd3fd2c98bdbf6caebc710bd55c9a",
"gt": "0e8744c41bba0c89a954901c0a1db04f706b5f7eb3a5f5b944d294afdb409165e9d324c3abd73fe178cc0cca574fc0d1102cb256d3cf02f4d31d03a115cd5e618a5dc228d954d6b7588b686e9ca5227ee00965ab305dff25a107cde28378619c0c64d27a28ae25154a2823a5ba152ef9ffcf111d027f9308aa5ec132cad504361c3a45e75f853e08e43e0aa6b560189106aa8d1f068d1290c007ec3837c640b34b8a34d6dbdba4ef5e58a7de979526c5a0947fc6d07bf1eca588b7e4fe28c2980a5a19d38644317fa020414969a59d638e8401b66659fdc4dec67a36b6a624887f68fb27a90ffd506ff9cc45794b48810eaa6cf366ca3f275f4787e3959c10f24b5b6c45e2419a490613afc9a8e1d6e4078bdf89cf099bf2e24f1995f0007c3f0dc3ae30f1f3e84ad973ff2c90a33cd80fbb0f385f89976e6f73e1bbca0e8d68693f5cdcfeca012f24a411db9b6e6b23144268eacdef67403e7d414555010dc098e2493a075b696b539026c7c02946a67bcc407317e843500cc56c4212d6a8270224e8cabb99eba03706654181f1492ef748d7c295b2363ae47e8b036796bade54a24069208d45ce1f4c919e9bcc39ec0f118c13ba56c274c035afdef9ef7fa069cf0a6e39d312f10497f008cbea527c95476205a815387e57d50244fdcfa0030997b470a4a81b53d2e0206710af827eab9a86ce3538b441e5c90e4432e2996c63d8c7e799b8973ae5ab77741509013d0233fbcb7d18bcfaa08817e155cd35017a1b9eb00a8501c2b0ace30634dc02629759f0aeaa54824817efe309d112e11e",
"sigma": "d9ebc975b07b7ea0060bc0d283859638",
"r": "0aacaf6491845e7ca6e8a214372f16115576cd8f34b54ecdb58e6f8f4a40e252",
"file_key": "442d2b180b4b2d980a43179b6ec23d9f"
},
{
"name": "format3_time_and_key_portable",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "8065863244b4c4f9d1841b5a2b5ffeb642911eb8082f17be8b8d80b2a9264237bb896bbeb2580daafa69ee333761e9050471c66fc5b61fd6392408a931ce1bc5e428e9f21901321a311489dadaa43298b7e27908d14fb33e564078e2765ab49a4d7b1712adc72292ff0d451bf9b98a841acc19d1bce7db2974181ab067c9d0fd",
"gt": "05a999e1de43d976ab2ed229ff78590a6c36a22499b03f158d0af81d9e3dfe304421d6b8c7d14b2302307f8e22843b36068bfac3c1cf0991e5e2db7d3dbae493837fc26b088d96e6262cc51a43827127a7a0b850253d591f6f177a91f9d7e32f02b3bdfee474be7dfa355901ff19ffc89b15319445a06e5a44dac3c91954560044df509485a744fe3961c8648c18eba20fa6a82f35a3d789c311a24cc15f4ce45b26f67de098ea586e5604dedd1c5657efba0021ac9f67af7c693e7cf0cd014c0af93bbf0bd7d49e46a3812fb99f92726797ca18ab3055296d4c56d0ae65e8055583483ddd359a12b64e1c8b09beffc30249a7dd4a58cd1d6e57c537ce0d9a1addc98512340e8819d10671b6a59776b5652bb3077f8ab6de91e17ced189bff7f18fa0c2830855c313fd1aa5e10b23101ed2b2e1214cea1b182ef6dea6545fc1db81c877c72b5b2b6830379899f21bafe154e645a396cf20728d346478d4a84064faedafce6a8a43f6952dd518bbf934fd2d0d78fb0e6719dfa72a48537bf2e540e7fc32203e953a52684231d636550794d6dad45d4fe414361d329e67ac358eed79f3be16aface4360b6dea62338904510e4a64dc6be7627d4b149c0f660e5883a967346d9f6871535ce8ec96a02ffbd5703c0780cefdec3814c3b9dadc0a2bf0dcc202a6acd3ae7f1eee581e50a6d8b54ae70fafd501da4a996d8a1d971b48c174d5e3259f51d18142ff7df9339623004f48180a179e1478eb585c65876124146dbfcb057556cee002b1048176df8ce2808bc04da6b82fd1bcb281c93c1a9e1",
"sigma": "1dfb82058b04fc45d61f985b31e6e388",
"r": "2bd166cc5a3e8664ed5b4cc2932e13f53c313180dea70ca9a10b420d4e83a202",
"file_key": "916c47b45f39c0b4df425f1a248028e4"
},
{
"name": "format3_tree",
"round": 1001,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"body": "930a9ba9013ee4f9a355e2743cbf2385d9fae02d842b7f9fb9b6d5e54a1cf4a71cb345f6eefbe246ecb70c4a52990aa30ca4b1c33b56430aea678e36a56ae73022b88189283c2f2b815bee892676b55220597678ab6d8e6a2f8e27b2ed72e977340f82e9450f58bd6b5fbef915842a14a716b2c8e179c4bc86b84bbe472e4ca8",
"gt": "15f4383d2706c8bbb6c769d102a38edf8adc3e2baaf878977323fc0bbba21a6e2ae225f16ecd401ddc718bb3caef2f790eadd095b4fd3d8da7b8659f7121e026028adb77794f401ecccc90fc85ff84290dde15d5b3aff6adba053072a0bd229f0f56fa0db06147c1826a3f0c350c2346324bb50b26c6caf59a7e00722481e1cc63033e2d350f55a232c5e76c7a0fcf330329864e1140e74cbe942c2009d4df446c09b0384e9b4633e240b776f0f348dbd42da1015b9439026a247df965a163b30d10eba185ec4bc67042be9176a2646a5ebbde1ee5f74a6c3066ba236be914a249455928bc6b071b76f2d59858f7edec05bf5759a551ec1aa7f7d317cbba944fb23585dcb090e309edaa94135349ba41bc17e9f86330c5db19d7d877c1e15158066c6c262ca963b33c941363de52bde6d16e895c9e0ce13b717e5e90424ef67bbc306cfc92ea08140c7838f5a7509cba13013d49d4632f4e46794e13c868e8050c88ce0e8deca92442342132481acf47bf736dcefaeff932c65c0ee284bc1226041d6d667e9e040f2da6fb928263b4cbdb2a8fc7ed1cdc0d48d42df064ff8e024099e0cfedf09e41ce2361215463fecb1365e356968936a9e79d3e9138cc5fbcfac58a385b57aed882e1062e0f4a6cbea532d2556fe8943cb378b667a2d2f31302f0d13df01c6ab9039e5b35962c7049a3fd4d778b627a05093ae291f909b4a5e7cd8ec8036e87076e749fd0388d6ad400963c92f5a19963d13949586b3f6d3300339ed6e14f37e591970e79a33c6c249c554d913cf3a4319a2d102899e2e232",
"sigma": "1fb5617972a4de2cfc3a634792dd9b2e",
"r": "3713ea67697e81dd87689bee1b1eb4171f4fc528695efdab1fa0b6ae2a874cb1",
"file_key": "7bfa8b9f99c10342f5c503851d2f4514"
},
{
"name": "time_and_key_portable",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "a37b9156ffa34b6618b2161a60dbf9a761b5206d5af3f941c73b722bb73c5359fc2da5b781ab84db0f36c2b71510a9e1067f4c287c408b4a0b14f754d5fd3b0509d161888a46c440edb6e99818f0068148d9fb4f50f99b1f8a2f4b567d9fabc055cc003e9d535125c0c2d350b47fa778dc09ac8e8f67d0b30cb4e97c12450179",
"gt": "0889bbf5f48555ce86a8383e97c37cd59d1e25509cd05d5fe716c266016640d558cc2d974bec714f6e510ef914f3c148043e4ea16243afef90094bef83a848b453be67a9dcb58727175a9dc780afda04ab2febd70f4ea6ca8db88f8032a4ec6d19292cd399722067232eadc7ce1d835afad52f6f4211693289fc5b1e0bab29afe8d297bbd60d4d3b18c854f8b6dfc249092e8e9387f62f7707ffbe5c92a83869d6caf4d748660e6fc0c037ef85595984cedf95fe053bcdb885d7302b73f62f3316c0d93eb768471df130e364e2c0ecb0a013587633cce561b188441cc5ed4d79cb9cada8bb0b36fc26fbac6be012dae707361ab567574500724213d211c3b3ba540415ece53a06a918ea3c34ee24b28463293cd604a676061532aedb73efdfd706331afd5dae971e2c16ed7a55ea2fea3ae7fc19f729b73b01f04a69e407d38643ae710c710dddd19ccf3fbb02a9f7500b6969d08cbce9c1e5dd8625f6bcb2a096c85bf2197a8972f66ce1a7170615f417f14bce2d3cce5ea13c7041e1f3c7a709a6724b9391abb6a12039798c73b971d5ab15f14143e60797e1f2cd9a3dd76087070256d0e7a927076ec5d54ecc565219bfca4b84f43fb9ed20e8293ab9ca93860a58b691862418d923dae9dcb88ffc83c6a97d34000b6f696d2ce0cf544695072ad7a618e84de85811bfe26e082da2a614bd201413edae8b6f0b5c42a217a4816c0c803d237a6de7855b3dc38917f4063e45b40b5dd2412fc77aa05f582bd9e61713f985b447096f8ee4fab2da3bc787dc9450230265b84425a8d6a62dbd07",
"sigma": "8558300932ade6715f84c5f476d6f10f",
"r": "00f39949c78198542ecb3e6d0017e984ef548faab2ddffdc960597cf7fb75898",
"file_key": "bd1c70394ef8c69e0660aa2e517ed1ca"
},
{
"name": "time_and_key_recipients",
"round": 1001,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"body": "b81e4e55a134184eb2121c9fed89a854e5821740767332c0662cb70866c9ff322ad4ebc9e4ee54609eee211d6669ce2e112e85d4d8db4764cfc18de7cc554678ebef0c09bc029496d283745b69a24f93dab5aeb98a55ae6e184fb5fb52f2297da52eed2b01c7c25a7af85fe8aa908ccf0c5da33a8ba3d75868bb3f0f14253fdf",
"gt": "10a0bdb058ed5207eeff0c0b2f5772b182e5c2fdd24953666a62e2de5bde0905c9d11fe73a2dafdf3bc8f527a1373919164277af5ba63c30df08ddb7ab1c90200e2975d3502ebbcceca43291f918378f4688258fced78d67f764bb6b9f894e7c0bfb12c2f6e3b14cf7ccccda5cdb28a574f9c79d982c30e492049482cd306be027641a6761f7a4b32c30b6945d3630aa100fe90d3b0ed998c91369c272f0c14eef6cb63434d4773906368dd6b69660332114ef3ca7ac5f9c245f9d45b76505f707b9946dc1da0f05a4de0d972eed0c4eefba67a92fe5869221d11e002bb824038ec96201505aa8fc92bb2df8722030ad0319b77c4fb91b847efe3b926d5d8d692d786f5ddb55dd2a269a153a931371176fa5f3e2a4cc63531ee8acc7b96f3e600e5817019150eb393ac5f246ca198d8023712859d6a6d3d87917fbbf2a3b679c98fc7581ea4fcfba5a732ab5c1b833e80ccba7e3b0905c55fbd71fc1871bb027085e11898711345e2b034578faa2b5970373c7540a9ee9b3133b3e5b531a51d60fa3bb6d05482d06792b76cf6824d9e551e739800f7ee5f1e0c01529db5a5f957ef527b42a32b557f98a467581d9ef49013e01fb37f9fd9bef1005c80e9c7239fa235796c01e41c2a90659571c16a617862ba935a32668b03e297f866c010c7d1917a11deead3d281cf594472c9d37572c31215f734244319cbf419dda60c0b4c60789a91caa6710c2186ec0ad5e16600a2758ab73765cea974e3fd86455e126f16e748f297a0aebe2145359de1ef052a304e8e67ab809fd34c944cb41f6e498",
"sigma": "871f6a3d5028e727597eddd1a306adbd",
"r": "2b8ab95abc6b92c497650b4a87f499d252b64eacab5f0388233e589046920c3f",
"file_key": "d0a706c871a65f4690e3737227720f7f"
},
{
"name": "time_only",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"body": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c6c26076e7a50eae51ac174e025205dc073ebd97ccd56cdb81065c8fbabfb502c",
"gt": "123f4da429bf059c0f67d365508c23a211c91887ef8e12300653ca69b041141c782c917079fc6aaaab00c23411affb06192b9a2d994cf4fb35b95ed10730e91d30ad89155bcca5f314bf02a0e9a966e4b4cbbfb5f0c1ad6a40a7e8be25bc47cd12baed915725d78041253c84d626ee149fb4eb4d5fed6c043ddc18a840bf79fcda71e5fb23efa0ebf886f5a45f1e0377121da9c8c199b9cfed2a20c867bc15ecbf1058b2a194b44920cd27f069254dd961a8b88d69d7c093362fe7d98b210b7a0c5e704227d27ae6683009b0de88fa5ea4e1259eff5a05017c3703f65b656a05ca3a237efbbcd29eb55a2a5a1d658d6c1035e54af149271a1638ae68dcf34699ac850b555952718b7e852a3a5433b5b8c5bb0a3f4f9df09b43069e6854e51558015af9892ef2be85ce8e7c4695ac83eb8fb1a83de80969f155efd81b72361667dbfc552c1c94183b28920ca38d193ed814f4dfaaa3319dc91c2723be7a10dabc1f45e905b4693c100a3203644f7ae95ec1e1100c05729072d61f38ccd7d3db2510f4eeafefda4be8cf2094657faf87e4bed175c6f9f660bc6bd3897563b5891393ae5fe5a87f83bdd6139716110262fb0392758e8be6d3f74315e376c9248fc70ada39aecd2c8120ee6e70b1a9d784564236916bca0881214a1b34b0b259c17918521964b4307335323d8cb6fabdec708c52e42f6990042d3a3a95fce7bb6c48d24d0732eced009e672176fbc9380eff129f664c8634923c1f4b2973eb909f36012a3029122b3733edc2265b6389de56fe4ee3d7534b9acdc4aa166f3e8e94e1",
"sigma": "0028db16b378ebdaf1a95acd61877783",
"r": "520e6f605ac31bfc613f8ba7e35a4ee89652d9ecae2b905f17caa7c47d788869",
"file_key": "684bfc20912fb50a1a7f1b644e1f6f52"
},
{
"name": "time_only_extensions",
"round": 2000,
"signature": "b6cb8f482a0b15d45936a4c4ea08e98a087e71787caee3f4d07a8a9843b1bc5423c6b3c22f446488b3137eaca799c77e",
"body": "b14321735db0542f13770d3743e6bcf20b7489184276242f292d927e57cd635c11d44a2a62e3c4e573b4cd693ac977de1880ea98a63e082b33f0dd0a3dd9d332da04c54221451e9f8990ae0ecc01a6deff4e3d6df23499bcf794e33ae33dd5ee62dc47d9cabdc60591c294e4d6ca2fccff19da312848f77b71eb796045cfc660",
"gt": "0903f246d6751204b3c909175f9eea50d1c6ab235ee4c8493a84855b0fdf8d411b809acc3fbb210b651be539d8df11280e1184d9cc173c537921d970eb89db71dc492322108864f4ad0aef003b33e7ab93490caa83d6127392050237596de8ce0d8fb93bcddd04e6f99e0f64c135eb2b8e54b453aa04082f697ce64aefbc5c3c4bfe6d670708d67662874b3f7e4137b805df11bf21fccbc8340406a872936574ab378e9b8e362d091bef2a8013dedd6e520ef4bac5a9369b4a7d095d86b2d574043124283fa54e6be3c7a2b035f7cd61ee077ea5667554850ebce9a1e87f228e01da13dd73edb353c107c50bc6f8496d15680b646ab8fcde36e5b0900286eab861b596dffae7b46e5707d649f27a3d5212e942a83d6c186b4c24139163dccec2156e5a809557c229a529152360114846b1a58e2cb58aa7483a7d016cbace88eed14d4ad0130fb68f3d1f25a50417237c18d5a2596d851c3ed57c6c6dada06feefb9c359d3131c41108032ae55f599ab4da82d5017f483f439a26ce88ef0e96eb0c527b1d67fa0e31c815558572ba66709399443e19bfbcd1d0b6aa111525b49f955debd6a089aabfdf58a89d7c2436c512ab24d4a0bc3be1f0ce2f382b155e42bb1a3465e890f3009e73750e66bd84cfcc79c1ca1cfed695cb9fbf3432db92841897a510bd8dd9369132d953609705494d7cf2f69be2831764c751b2d089e5df6001ff9c43f65cc257fab5230ccc310d04b52d428e5073a378228f2be742def69f7e8d52560f20d5c6a7507340346bdac4cbc2bf7ce3d117ef08e9ee1c0b7533",
"sigma": "eecb8e723ec29600088e162d5b27c5ee",
"r": "5b6076213aeeaa8eb014c104171f6641c844788766b734fb15f2c5a4be3419cf",
"file_key": "5be87729206e4f7b5f344287a377fd7b"
}
],
"kyber": [
{
"name": "a 0-byte message",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "8e5323386dde85946358148a71f4471948cd8e77b34750138a3cbad5a74cf5c7ff124a3e95c98ed34ec0ad5db411b0d20ab6d1638c51a62f7e68bdb8326dc5786a7baeb80c47d3701914a77af36b3d882d4746184d78589f51feacca232bed95",
"v": "",
"w": "",
"go": "ok"
},
{
"name": "a 1-byte message",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "83a4ab1705c1765a1cd1ea44bcc4bfaf08f39912cf21f985a76d0e3e5abcd2ccfaaa740350c6efd684ec1955b1e7086e0f3f4ef5e6569dc5804bf2129d56ff5384df0b9bdb73f991b3b28bfd78b02a442ccd919abd9dfb0f7b62c7794bef2092",
"v": "37",
"w": "7d",
"go": "ok",
"msg": "6c"
},
{
"name": "a 16-byte message",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "b5fc4ddbbbb4f055d996ee177c3bd8f2ab3b752a86882b2f971376a76ac9b0bbff9fc395e8be090972a20eef7a42301f0958429c32746ac5a27f3b9b7f6f036cd0ef26e17f42e5f2dddbb551b0ed2eb9a0c276ead0abdcb6c5b2418d1c7c9020",
"v": "f12464d1b9867b1add515f800e40770a",
"w": "50fdebae579e4e4d44c1f2db4f523629",
"go": "ok",
"msg": "6c8908a8bca467ce306844af509353fe"
},
{
"name": "a 32-byte message",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "a0966fbdd8e1a3a68f03a0aebfdb85968cfd56041261d5e317bb3fd5b01961f68aa54d9d0fa2057e2d48d8a3e25e02cb0e6d622d6e73963d227d58521475279106f37d17897bcb9a03c69656b3f9520c6317e29b0b068b0b58f79e5809d9dc4a",
"v": "24e5ed8f36a1b9bcca2404432fb20b61b799b5dbd764a016b220fd75dff9e554",
"w": "e8e4b501c753b12fdde90155124e8a4e6366752109b5c77cf99b2d522bcf6d9e",
"go": "ok",
"msg": "6c8908a8bca467ce306844af509353fec59698e0870aa80cea87ea7fb2c1c3d0"
}
],
"decrypt": [
{
"name": "the time_only stanza",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "ok",
"msg": "684bfc20912fb50a1a7f1b644e1f6f52"
},
{
"name": "U with p added to c0",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe309482bdb770815acd72eacc4f8bd69654af04c8f29afb29206e0909171fd9bf7a9fb5eb7378a3b3f114875717605ff9fb3e7",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "U is the point at infinity",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "U negated",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "a08f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "V with its first bit flipped",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "ec26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "W with its last bit flipped",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502d",
"go": "reject"
},
{
"name": "the signature of round 1001",
"round": 1000,
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "the signature negated",
"round": 1000,
"signature": "944679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "the signature is the point at infinity",
"round": 1000,
"signature": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
},
{
"name": "W one byte shorter than V",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb50",
"go": "reject"
},
{
"name": "V and W of 33 bytes",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "6c26076e7a50eae51ac174e025205dc06c26076e7a50eae51ac174e025205dc000",
"w": "73ebd97ccd56cdb81065c8fbabfb502c73ebd97ccd56cdb81065c8fbabfb502c00",
"go": "reject"
},
{
"name": "V and W empty",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "808f8eead90f3eebee2e6e15d490a2ab973214350d179d9c29ffc804e612f8fdd45bc3d2acf8552c2b3bb6666fe3094811da651ddc2cf09461a9510726199e18e817de2abf0cf42129609f5ca546b3d7400b378b89eb1148bb727605ffa0093c",
"v": "",
"w": "",
"go": "reject"
},
{
"name": "U is the generator of G2",
"round": 1000,
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
"u": "93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8",
"v": "6c26076e7a50eae51ac174e025205dc0",
"w": "73ebd97ccd56cdb81065c8fbabfb502c",
"go": "reject"
}
]
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,106 @@
{
"description": "Go reference values for the tlock encryption of lib/src/ibe.dart and lib/src/tlock.dart; see tool/tlock_go_vectors.go for how each block is obtained.",
"encrypt": [
{
"name": "a 16-byte message for round 1000",
"round": 1000,
"id": "f652498d092acd949bad74e40683bf3824fb817980504a0c7e6722cfc5a9c0a3",
"msg": "16e27bcb7ff0267cd0db9c1dc1459696",
"sigma": "3d4725349d1d12947a3195aa2cb5dd00",
"u": "80364e97a868ee0dcdcf79a71ab7901f97ae9d38069110e5eacf630842857a52685ebaaf41b3e8682664893748443a3f1137cc7e1298814bab8d47d13416083a3b32d224713eaf141bef1c3775d580a939ffb316a603b68bd3d8799a95feb355",
"v": "518c9d924cdd84ef24472ee2dae6c5f5",
"w": "3cb104bcb0c445ea0b576a9aa64bfa01",
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
{
"name": "a 16-byte message for round 1001",
"round": 1001,
"id": "ce43c3353a7ad7aac3408cad0bf921b6a7dda89be75d9cb2b3b5a152cefc8afd",
"msg": "4a1a048e28b2a9497373df269686925b",
"sigma": "47bd5bdca9e4341af640abf2e2624261",
"u": "84ea313521e2f15b2498e0dbb1125226648e963f7be8a1314444f7d38a92506f38acd599401bbf27f2f9249a8049734a17beff0781a1810ef7a9d53ea50372835cfa0d2260797d2bffb820f4ad4d78506667132cc4db65a483604df02332da71",
"v": "f0e5eafdb06ff7b9d7a32b607d2ee510",
"w": "d8e72ed588af9cb7ea6bb5b57d737108",
"signature": "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41"
},
{
"name": "a 1-byte message for round 1000",
"round": 1000,
"id": "f652498d092acd949bad74e40683bf3824fb817980504a0c7e6722cfc5a9c0a3",
"msg": "bd",
"sigma": "d6",
"u": "92f597b1d29b056f019cd7afd0eb51ef2f1fc1708915f742762062ddf7e500389373638651935810699f9cab987c57390b9677f844e89fb2c8560f74ae68159dc9df9c0ee667d5c507718b51b490feb780a78cdff5146e27a77ddc36f9e45a48",
"v": "b8",
"w": "4a",
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
{
"name": "a 32-byte message for round 1000",
"round": 1000,
"id": "f652498d092acd949bad74e40683bf3824fb817980504a0c7e6722cfc5a9c0a3",
"msg": "3ccb765d8df56d226b3f7e20ce159302b1d3b2924e9a33345d31fd3aa0f3ec5e",
"sigma": "bdd1ba60cac27c3e6e5eea1678cf1c5586e694a9d775963153acb4402ce10d59",
"u": "841d1a934afcdbf1a6af15d6218406a8512ddc2dff465469a95719548619fc47ef9753fc3f9427066ba8e0b1791a01e80997324dd9f7b3976d340ef73f6461b330e363006519439df2ecad28ed903f48fbcf3d2616f8249b63dd4c0f8771b9a8",
"v": "5d695cd7e98b4cb4f5938941647878361ac7bf7db79843c92825cef241be9675",
"w": "19fcedeece1f38d41bbe569f06b8d3b1f0fd1cdacb1b08d8033d4202cbd7c395",
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
},
{
"name": "a 0-byte message for round 1000",
"round": 1000,
"id": "f652498d092acd949bad74e40683bf3824fb817980504a0c7e6722cfc5a9c0a3",
"msg": "",
"sigma": "",
"u": "8e5323386dde85946358148a71f4471948cd8e77b34750138a3cbad5a74cf5c7ff124a3e95c98ed34ec0ad5db411b0d20ab6d1638c51a62f7e68bdb8326dc5786a7baeb80c47d3701914a77af36b3d882d4746184d78589f51feacca232bed95",
"v": "",
"w": "",
"signature": "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
}
],
"generator": "tool/tlock_go_vectors.go",
"interop": {
"generator": "scripts/tlock-ts-samples.mjs",
"samples": [
{
"name": "IBE, round 1000",
"kind": "ibe",
"round": 1000,
"file_key": "aa07feff048bdc07e5a2ea0c2e35474c",
"body": "add5cd1894c304db02a4134742163bf672b9ff44802a8f388b22bfa2702130b4044612a950b7083da8dae36dea71a86b001ae693d556092d8ccf038e667d61fe8bf5915d9fcbb3373e8d6f46799e610bc580a17aa87174d0c2429ed63b240b4feb23d3bcc12befe90d39c5ab0147a6347bd576ebc613996bc61c4338e38a21b6",
"go": "ok",
"go_result": "aa07feff048bdc07e5a2ea0c2e35474c"
},
{
"name": "age file, round 1000",
"kind": "age",
"round": 1000,
"plaintext": "446174654b6579733a207365616c6564206279207468652054797065536372697074206c69627261727920666f7220726f756e642031303030",
"file": "6167652d656e6372797074696f6e2e6f72672f76310a2d3e20746c6f636b203130303020353264623962613730653063633066366561663738303364643037343437613166353437373733356664336636363137393262613934363030633834653937310a74472b49697069425a395753487674777947497733465761555542646e514a474e36637541303051452f6c59584b4b743664686f7444745352616b362b334a780a4371654e4837436868536970523551324c486a6d6d6f32313478716657637135542b4830446f73797a5369664f5448524371637668655453505a4977797052470a755738495a50364a4d5356642f655145447768524c7834304a6972687258443442474b3273584b6761516f0a2d2d2d2034393658794d664878427677543845625a4c4f6d41345a364f4a36487245497459316171614a79726165550a0aafc6b79c266610bd43455d546ad8215ffd491c2a358b1de42751d9d032a70f8c39cd0bd384885e970020a79805b9547364b7254209f2ae5eda3eae9df7abf8d66ed004180dec058068447c010279de8624c9d06442a77a72",
"go": "ok",
"go_result": "446174654b6579733a207365616c6564206279207468652054797065536372697074206c69627261727920666f7220726f756e642031303030"
},
{
"name": "IBE, round 1001",
"kind": "ibe",
"round": 1001,
"file_key": "a3bbf76b3f17c2a7a3d0b9fd62208d60",
"body": "98a8f585fe3ded2e59b27335996af344ed5b9fb9c80b3016416ba93b9f80e610b6286cf3a0172a775b097da6646e5237039fab9ac191490f0ee900aeb739f85cf14a673b0ebea19dea85454ddd3431679b726a0015adc2c408617ba3e0d0e22c768e45f47369a53dd32cb51c25a89e57ec4ee19294e0f229f259b9293fea8277",
"go": "ok",
"go_result": "a3bbf76b3f17c2a7a3d0b9fd62208d60"
},
{
"name": "age file, round 1001",
"kind": "age",
"round": 1001,
"plaintext": "446174654b6579733a207365616c6564206279207468652054797065536372697074206c69627261727920666f7220726f756e642031303031",
"file": "6167652d656e6372797074696f6e2e6f72672f76310a2d3e20746c6f636b203130303120353264623962613730653063633066366561663738303364643037343437613166353437373733356664336636363137393262613934363030633834653937310a70584e2b37746e6f4137304e524653715a484d6f7253637456326749636a39774a6f6b506c6134772f50732b4665725872774c32554a796a67434164423075530a42595075456b42415178393473654c5a5344694a7a736f6c366e474544705a6966654c6b324c6b2f544b794c766861495037433976645554334643664563314a0a4c59685074457772702b4c4b5633354a597031484d6f524832393970646e656f787a2b674f645a6d4f6d6b0a2d2d2d20644d547177433836776b53376d4c42594b6147684b736a634b4c48725a4532346c612f6b53366c306736670aedaec2f1c62d0f0229007f0e6f730d4cbda71a2ecd781d087b5f08245abe8854f35fa7b8319db3b89270feb1306a772f118caa974ae80255597efcd3b33b1c95ca5e8f930e2338c85f91b72e035c7dac242f4118971607971b",
"go": "ok",
"go_result": "446174654b6579733a207365616c6564206279207468652054797065536372697074206c69627261727920666f7220726f756e642031303031"
}
]
},
"interop_from": "the interop samples of src/lib/dkc/testing/tlock-vectors.json of datekeys-ts at 289fe71, opened again by this generator",
"libraries": "filippo.io/age v1.3.2, github.com/drand/drand/v2 v2.1.7, github.com/drand/kyber v1.3.2, github.com/drand/kyber-bls12381 v0.3.4, github.com/drand/tlock v1.2.0",
"public_key": "83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a",
"scheme": "bls-unchained-g1-rfc9380"
}

@ -0,0 +1,435 @@
//go:build ignore
// Prints test/vectors/ibe_vectors.json: the Go reference values of the tlock
// IBE-CCA on G2 of lib/src/ibe.dart (spec §63 step 11), a port of
// scripts/ibe-go-vectors.go of datekeys-ts. Everything comes from the
// libraries that tlock decrypts with for Quicknet (bls-unchained-g1-rfc9380):
// drand/kyber encrypt/ibe on kyber-bls12381.NewBLS12381Suite(), over
// kilic/bls12-381.
//
// - gt: e(G1, G2) and e(2·G1, G2), serialized by kyber-bls12381 (the order
// of kilic: c1 before c0 at every level of the tower), with H2 truncated
// to 16 and 32 bytes.
// - h3 and h4: H3 and H4 on fixed inputs, among them inputs whose first
// candidates for r are rejected.
// - round_identities: the identity of a round, scheme.DigestBeacon.
// - fixtures: for the tlock stanza of every official .dkc of testdata/
// (spec-v0.11), the pairing of the release signature with U, sigma, r and
// the file key. The file key is the one tlock.TimeUnlock unwraps, and
// age.Decrypt opens OUTER_TIME_AGE with it: the header MAC and the STREAM
// verify.
// - kyber: messages of 0, 1, 16 and 32 bytes encrypted for round 1000 by
// ibe.EncryptCCAonG2 itself, with its random sigma. They are not
// reproducible: they are the frozen ones of
// src/lib/dkc/testing/ibe-vectors.json of datekeys-ts at 289fe71, which
// this program reads and decrypts again with ibe.DecryptCCAonG2; a
// mismatch panics.
// - decrypt: the verdict of ibe.DecryptCCAonG2, after decoding the points
// as the scheme does, on edited copies of the time_only stanza.
//
// H2, H3 and H4 are unexported in kyber: this file restates them with
// kyber's exported tags, and checks them on every fixture against what
// tlock.TimeUnlock unwraps and against U = r·G2. A mismatch panics.
//
// Run it from a scratch module that requires the reference implementation at
// spec-v0.11 (a module scratch whose go.mod has require
// g.activething.com/go/DateKeys v0.0.0 and replace
// g.activething.com/go/DateKeys => an export of that tag, with its go.sum,
// and GOFLAGS=-mod=mod), copied into it, passing the directory of the
// official fixtures and the frozen file of datekeys-ts:
//
// go run ibe_go_vectors.go path/to/testdata/fixtures path/to/ibe-vectors.json > ibe_vectors.json
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/big"
"os"
"path/filepath"
"runtime/debug"
"sort"
"strconv"
"strings"
"filippo.io/age"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/profile"
"github.com/drand/drand/v2/common"
"github.com/drand/drand/v2/crypto"
"github.com/drand/kyber"
bls "github.com/drand/kyber-bls12381"
"github.com/drand/kyber/encrypt/ibe"
"github.com/drand/tlock"
)
// The published Quicknet signature of round 1001, as in the mutation corpus.
const sig1001 = "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41"
var (
suite = bls.NewBLS12381Suite()
// The field and the scalar orders of BLS12-381.
p, _ = new(big.Int).SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16)
order, _ = new(big.Int).SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16)
)
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func unhex(s string) []byte { return must(hex.DecodeString(s)) }
func marshal(m interface{ MarshalBinary() ([]byte, error) }) string {
return hex.EncodeToString(must(m.MarshalBinary()))
}
func concat(parts ...[]byte) []byte { return bytes.Join(parts, nil) }
func xor(a, b []byte) []byte {
out := make([]byte, len(a))
for i := range a {
out[i] = a[i] ^ b[i]
}
return out
}
// H2, H3 and H4 of kyber encrypt/ibe (gtToHash, h3, h4), restated.
func h2(gt []byte, n int) []byte {
sum := sha256.Sum256(concat(ibe.H2Tag(), gt))
return sum[:n]
}
func h4(sigma []byte, n int) []byte {
sum := sha256.Sum256(concat(ibe.H4Tag(), sigma))
return sum[:n]
}
// h3 returns r as 32 big-endian bytes and the iteration that accepted it.
func h3(sigma, msg []byte) ([]byte, int) {
base := sha256.Sum256(concat(ibe.H3Tag(), sigma, msg))
for i := uint16(1); i < 65535; i++ {
d := sha256.Sum256(concat(binary.LittleEndian.AppendUint16(nil, i), base[:]))
d[0] >>= 1
if new(big.Int).SetBytes(d[:]).Cmp(order) < 0 {
return d[:], int(i)
}
}
panic("h3: rejection sampling failed")
}
// rG2 is r·G2 through kyber, with r decoded as kyber's h3 decodes it.
func rG2(r []byte) kyber.Point {
s := suite.G2().Scalar()
if err := s.UnmarshalBinary(r); err != nil {
panic(err)
}
return suite.G2().Point().Mul(s, nil)
}
type gtVector struct {
Name string `json:"name"`
G1 string `json:"g1"`
G2 string `json:"g2"`
GT string `json:"gt"`
H2 string `json:"h2_16"`
H232 string `json:"h2_32"`
}
type h3Vector struct {
Name string `json:"name"`
Sigma string `json:"sigma"`
Msg string `json:"msg"`
R string `json:"r"`
Iterations int `json:"iterations"`
}
type h4Vector struct {
Sigma string `json:"sigma"`
H416 string `json:"h4_16"`
H432 string `json:"h4_32"`
}
type roundIdentity struct {
Round uint64 `json:"round"`
ID string `json:"id"`
}
type fixtureVector struct {
Name string `json:"name"`
Round uint64 `json:"round"`
Signature string `json:"signature"`
Body string `json:"body"`
GT string `json:"gt"`
Sigma string `json:"sigma"`
R string `json:"r"`
FileKey string `json:"file_key"`
}
type ciphertextVector struct {
Name string `json:"name"`
Round uint64 `json:"round"`
Signature string `json:"signature"`
U string `json:"u"`
V string `json:"v"`
W string `json:"w"`
Go string `json:"go"`
Msg string `json:"msg,omitempty"`
}
func main() {
scheme := must(crypto.SchemeFromName(crypto.SigsOnG1ID))
quicknet := profile.Quicknet()
key := scheme.KeyGroup.Point()
if err := key.UnmarshalBinary(quicknet.PublicKey); err != nil {
panic(err)
}
out := struct {
Description string `json:"description"`
Generator string `json:"generator"`
Libraries string `json:"libraries"`
KyberFrom string `json:"kyber_from"`
Scheme string `json:"scheme"`
PublicKey string `json:"public_key"`
GT []gtVector `json:"gt"`
H3 []h3Vector `json:"h3"`
H4 []h4Vector `json:"h4"`
RoundIdentities []roundIdentity `json:"round_identities"`
Fixtures []fixtureVector `json:"fixtures"`
Kyber []ciphertextVector `json:"kyber"`
Decrypt []ciphertextVector `json:"decrypt"`
}{
Description: "Go reference values of the tlock IBE-CCA on G2 (spec §63 step 11) for lib/src/ibe.dart; " +
"see tool/ibe_go_vectors.go for how each block is obtained.",
Generator: "tool/ibe_go_vectors.go",
KyberFrom: "the kyber section of src/lib/dkc/testing/ibe-vectors.json of datekeys-ts at 289fe71, decrypted again by this generator",
Libraries: libraries(),
Scheme: scheme.Name,
PublicKey: hex.EncodeToString(quicknet.PublicKey),
}
// GT and H2.
g1, g2 := suite.G1().Point().Base(), suite.G2().Point().Base()
two := suite.G1().Point().Mul(suite.G1().Scalar().SetInt64(2), g1)
square := suite.GT().Point().Add(suite.Pair(g1, g2), suite.Pair(g1, g2))
if !square.Equal(suite.Pair(two, g2)) {
panic("e(2·G1, G2) is not e(G1, G2) squared")
}
for _, c := range []struct {
name string
a, b kyber.Point
}{{"e(G1, G2)", g1, g2}, {"e(2·G1, G2), the square of e(G1, G2)", two, g2}} {
gt := must(suite.Pair(c.a, c.b).MarshalBinary())
out.GT = append(out.GT, gtVector{c.name, marshal(c.a), marshal(c.b), hex.EncodeToString(gt),
hex.EncodeToString(h2(gt, 16)), hex.EncodeToString(h2(gt, 32))})
}
// H3: fixed inputs, then the first inputs of a deterministic sequence
// whose r is accepted at the second and at the third iteration.
for _, c := range []struct{ name, sigma, msg string }{
{"16 zero bytes each", strings.Repeat("00", 16), strings.Repeat("00", 16)},
{"32 bytes each", strings.Repeat("ab", 32), strings.Repeat("cd", 32)},
{"empty", "", ""},
} {
r, it := h3(unhex(c.sigma), unhex(c.msg))
out.H3 = append(out.H3, h3Vector{c.name, c.sigma, c.msg, hex.EncodeToString(r), it})
}
for want := 2; want <= 3; want++ {
for i := uint32(0); ; i++ {
seed := sha256.Sum256(binary.BigEndian.AppendUint32([]byte("DateKeys H3 vector "), i))
sigma, msg := seed[:16], seed[16:]
if r, it := h3(sigma, msg); it == want {
out.H3 = append(out.H3, h3Vector{fmt.Sprintf("accepted at iteration %d (sequence item %d)", want, i),
hex.EncodeToString(sigma), hex.EncodeToString(msg), hex.EncodeToString(r), it})
break
}
}
}
// H4.
for _, sigma := range []string{strings.Repeat("00", 16), strings.Repeat("5a", 32)} {
out.H4 = append(out.H4, h4Vector{sigma, hex.EncodeToString(h4(unhex(sigma), 16)), hex.EncodeToString(h4(unhex(sigma), 32))})
}
// Round identities.
for _, round := range []uint64{1, 1000, 1001, 83903165811, 1<<53 - 1} {
out.RoundIdentities = append(out.RoundIdentities, roundIdentity{round, hex.EncodeToString(scheme.DigestBeacon(&common.Beacon{Round: round}))})
}
// The fixtures.
dir := os.Args[1]
names := must(filepath.Glob(filepath.Join(dir, "*.dkc")))
sort.Strings(names)
var timeOnly fixtureVector
for _, name := range names {
v := fixture(scheme, key, name)
out.Fixtures = append(out.Fixtures, v)
if v.Name == "time_only" {
timeOnly = v
}
}
if timeOnly.Name == "" {
panic("no time_only fixture")
}
// Encryptions by kyber, for round 1000: the frozen ones of datekeys-ts,
// decrypted again.
sig1000 := unhex(timeOnly.Signature)
var frozen struct {
Kyber []ciphertextVector `json:"kyber"`
}
if err := json.Unmarshal(must(os.ReadFile(os.Args[2])), &frozen); err != nil {
panic(err)
}
for i, f := range frozen.Kyber {
msg := sha256.Sum256([]byte("DateKeys IBE vector message"))
n := []int{0, 1, 16, 32}[i]
v := verdict(scheme, f.Name, f.Round, unhex(f.Signature), unhex(f.U), unhex(f.V), unhex(f.W))
if f.Round != 1000 || f.Signature != timeOnly.Signature || f.Go != "ok" || v.Go != "ok" || v.Msg != f.Msg || v.Msg != hex.EncodeToString(msg[:n]) {
panic("kyber does not decrypt the frozen ciphertext " + f.Name)
}
out.Kyber = append(out.Kyber, v)
}
if len(out.Kyber) != 4 {
panic("want the 4 frozen kyber ciphertexts")
}
// Edited copies of the time_only stanza.
body := unhex(timeOnly.Body)
u, vv, w := body[:96], body[96:112], body[112:]
flip := func(b []byte, i int, mask byte) []byte {
c := bytes.Clone(b)
c[i] ^= mask
return c
}
// c0 is the second coordinate of the compressed encoding of G2.
c0 := new(big.Int).SetBytes(u[48:])
uc0p := concat(u[:48], new(big.Int).Add(c0, p).FillBytes(make([]byte, 48)))
infinityG2 := concat([]byte{0xc0}, make([]byte, 95))
infinityG1 := concat([]byte{0xc0}, make([]byte, 47))
for _, c := range []struct {
name string
sig, u, v, w []byte
}{
{"the time_only stanza", sig1000, u, vv, w},
{"U with p added to c0", sig1000, uc0p, vv, w},
{"U is the point at infinity", sig1000, infinityG2, vv, w},
{"U negated", sig1000, flip(u, 0, 0x20), vv, w},
{"V with its first bit flipped", sig1000, u, flip(vv, 0, 0x80), w},
{"W with its last bit flipped", sig1000, u, vv, flip(w, 15, 0x01)},
{"the signature of round 1001", unhex(sig1001), u, vv, w},
{"the signature negated", flip(sig1000, 0, 0x20), u, vv, w},
{"the signature is the point at infinity", infinityG1, u, vv, w},
{"W one byte shorter than V", sig1000, u, vv, w[:15]},
{"V and W of 33 bytes", sig1000, u, concat(vv, vv, []byte{0}), concat(w, w, []byte{0})},
{"V and W empty", sig1000, u, nil, nil},
{"U is the generator of G2", sig1000, unhex(marshal(suite.G2().Point().Base())), vv, w},
} {
out.Decrypt = append(out.Decrypt, verdict(scheme, c.name, 1000, c.sig, c.u, c.v, c.w))
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.SetEscapeHTML(false)
if err := enc.Encode(out); err != nil {
panic(err)
}
}
// fixture opens the OUTER_TIME_AGE of a .dkc with the release of its sidecar,
// through tlock.TimeUnlock inside an age identity, and restates the IBE.
func fixture(scheme *crypto.Scheme, key kyber.Point, path string) fixtureVector {
file := must(os.ReadFile(path))
var side struct {
Release struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
} `json:"release"`
}
if err := json.Unmarshal(must(os.ReadFile(strings.TrimSuffix(path, ".dkc")+".json")), &side); err != nil {
panic(err)
}
sig := unhex(side.Release.Signature)
pre := must(capsule.ParsePrelude(file))
start := capsule.PreludeSize + int(pre.PublicHeaderLen)
sealed := file[start : start+int(pre.SealedControlLen)]
var body, fileKey []byte
id := unwrap(func(stanzas []*age.Stanza) ([]byte, error) {
if len(stanzas) != 1 || stanzas[0].Type != "tlock" || len(stanzas[0].Args) != 2 || stanzas[0].Args[0] != strconv.FormatUint(side.Release.Round, 10) {
panic("not one tlock stanza for the round of the release")
}
body = stanzas[0].Body
ct := must(tlock.BytesToCiphertext(*scheme, body))
fileKey = must(tlock.TimeUnlock(*scheme, key, common.Beacon{Round: side.Release.Round, Signature: sig}, ct))
return bytes.Clone(fileKey), nil
})
r := must(age.Decrypt(bytes.NewReader(sealed), id))
if _, err := io.Copy(io.Discard, r); err != nil {
panic(fmt.Sprintf("%s: the age payload does not open: %v", path, err))
}
// The IBE restated, checked against tlock.
u, v, w := body[:96], body[96:112], body[112:]
sp, up := scheme.SigGroup.Point(), scheme.KeyGroup.Point()
if sp.UnmarshalBinary(sig) != nil || up.UnmarshalBinary(u) != nil {
panic("points do not decode")
}
gt := must(suite.Pair(sp, up).MarshalBinary())
sigma := xor(v, h2(gt, len(w)))
msg := xor(w, h4(sigma, len(w)))
if !bytes.Equal(msg, fileKey) {
panic(path + ": the restated H2 and H4 disagree with tlock")
}
rb, _ := h3(sigma, msg)
if !rG2(rb).Equal(up) {
panic(path + ": the restated H3 disagrees with U")
}
return fixtureVector{strings.TrimSuffix(filepath.Base(path), ".dkc"), side.Release.Round, side.Release.Signature,
hex.EncodeToString(body), hex.EncodeToString(gt), hex.EncodeToString(sigma), hex.EncodeToString(rb), hex.EncodeToString(fileKey)}
}
// verdict decodes the points as the scheme does and runs ibe.DecryptCCAonG2,
// the decryption of tlock.TimeUnlock for Quicknet after its beacon check.
func verdict(scheme *crypto.Scheme, name string, round uint64, sig, u, v, w []byte) ciphertextVector {
out := ciphertextVector{Name: name, Round: round, Signature: hex.EncodeToString(sig), U: hex.EncodeToString(u),
V: hex.EncodeToString(v), W: hex.EncodeToString(w), Go: "reject"}
sp, up := scheme.SigGroup.Point(), scheme.KeyGroup.Point()
if sp.UnmarshalBinary(sig) != nil || up.UnmarshalBinary(u) != nil {
return out
}
msg, err := ibe.DecryptCCAonG2(suite, sp, &ibe.Ciphertext{U: up, V: v, W: w})
if err != nil {
return out
}
out.Go, out.Msg = "ok", hex.EncodeToString(msg)
return out
}
type unwrap func([]*age.Stanza) ([]byte, error)
func (f unwrap) Unwrap(stanzas []*age.Stanza) ([]byte, error) { return f(stanzas) }
// libraries names the versions of the libraries this program ran with.
func libraries() string {
info, ok := debug.ReadBuildInfo()
if !ok {
panic("no build info")
}
var out []string
for _, d := range info.Deps {
switch d.Path {
case "filippo.io/age", "github.com/drand/tlock", "github.com/drand/kyber", "github.com/drand/kyber-bls12381", "github.com/drand/drand/v2":
out = append(out, d.Path+" "+d.Version)
}
}
sort.Strings(out)
return strings.Join(out, ", ")
}

@ -0,0 +1,384 @@
//go:build ignore
// Prints test/vectors/release_vectors.json: the verdicts, codes and texts of
// the Go reference for lib/src/release.dart and lib/src/tlock.dart.
//
// - verify: provider.Verify (spec §17, §51, §63 step 10) on the published
// releases of the fixtures and on edited ones: other rounds, other
// lengths, re-encodings of the signature (x + p, the point at infinity,
// stray flags, the negation), rounds out of range, and profiles with
// another scheme or another key. The x + p signature of round 1004 is the
// one of the mutation corpus (testdata/vectors/mutations.json).
// - unwrap: agewrap.NewTimeIdentity and its Unwrap (spec §63 step 11) on
// the stanza of OUTER_TIME_AGE of the time_only fixture and on edited
// copies of it: the count and type of the stanzas, their arguments, the
// release, and the body (its length, U re-encoded or the point at
// infinity, V and W edited).
// - recipient: agewrap.NewTimeRecipient on the profile and the round.
//
// Each verdict is "ok", with the file key for an unwrap, or the normative
// code and the text of the error of Go.
//
// Run it from a scratch module that requires the reference implementation at
// spec-v0.11 (a module scratch whose go.mod has require
// g.activething.com/go/DateKeys v0.0.0 and replace
// g.activething.com/go/DateKeys => an export of that tag, with its go.sum,
// and GOFLAGS=-mod=mod), copied into it, passing the testdata directory:
//
// go run release_go_vectors.go path/to/testdata > release_vectors.json
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"math/big"
"os"
"path/filepath"
"runtime/debug"
"sort"
"strings"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
bls "github.com/drand/kyber-bls12381"
)
var p, _ = new(big.Int).SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16)
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func unhex(s string) []byte { return must(hex.DecodeString(s)) }
func flip(b []byte, i int, mask byte) []byte {
c := bytes.Clone(b)
c[i] ^= mask
return c
}
func infinity(n int, first byte) []byte {
b := make([]byte, n)
b[0] = first
return b
}
// minusP is the x + p encoding with p subtracted again, flags kept: the
// canonical one.
func minusP(b []byte) []byte {
flags := b[0] & 0xe0
c := bytes.Clone(b)
c[0] &= 0x1f
x := new(big.Int).Sub(new(big.Int).SetBytes(c), p)
out := x.FillBytes(make([]byte, 48))
out[0] |= flags
return out
}
type result struct {
Go string `json:"go"`
Code string `json:"code,omitempty"`
Text string `json:"text,omitempty"`
FileKey string `json:"file_key,omitempty"`
}
func verdict(err error) result {
if err == nil {
return result{Go: "ok"}
}
return result{Go: "reject", Code: datekeys.Code(err), Text: err.Error()}
}
// A profile edit: the Quicknet profile with another scheme or key.
type profileCase struct {
Name string `json:"name"`
Scheme string `json:"scheme"`
PublicKey string `json:"public_key"`
}
type verifyCase struct {
Name string `json:"name"`
Profile string `json:"profile"`
Round uint64 `json:"round"`
Release uint64 `json:"release_round"`
Signature string `json:"signature"`
result
}
type stanza struct {
Type string `json:"type"`
Args []string `json:"args"`
Body string `json:"body"`
}
type unwrapCase struct {
Name string `json:"name"`
Profile string `json:"profile"`
Round uint64 `json:"round"`
Release uint64 `json:"release_round"`
Signature string `json:"signature"`
Stanzas []stanza `json:"stanzas"`
result
}
type recipientCase struct {
Name string `json:"name"`
Profile string `json:"profile"`
Round uint64 `json:"round"`
result
}
func main() {
dir := os.Args[1]
release := func(name string) (uint64, []byte) {
var side struct {
Release struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
} `json:"release"`
}
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(dir, "fixtures", name+".json"))), &side); err != nil {
panic(err)
}
return side.Release.Round, unhex(side.Release.Signature)
}
_, sig1000 := release("time_only")
_, sig1001 := release("empty_payload")
_, sig2000 := release("time_only_extensions")
var corpus struct {
Cases []struct {
Name string `json:"name"`
Release *struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
} `json:"release"`
} `json:"cases"`
}
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(dir, "vectors", "mutations.json"))), &corpus); err != nil {
panic(err)
}
var xPlusP []byte
for _, c := range corpus.Cases {
if c.Name == "release signature re-encoded with x + p" {
if c.Release.Round != 1004 {
panic("the x + p case is not of round 1004")
}
xPlusP = unhex(c.Release.Signature)
}
}
if xPlusP == nil {
panic("no x + p case in the mutation corpus")
}
sig1004 := minusP(xPlusP)
quicknet := profile.Quicknet()
key := quicknet.PublicKey
g2 := must(bls.NewBLS12381Suite().G2().Point().Base().MarshalBinary())
profiles := []profileCase{
{"quicknet", quicknet.Scheme, hex.EncodeToString(key)},
{"scheme pedersen-bls-unchained", "pedersen-bls-unchained", hex.EncodeToString(key)},
{"scheme bls-unchained-on-g1", "bls-unchained-on-g1", hex.EncodeToString(key)},
{"scheme not of drand", "datekeys-test", hex.EncodeToString(key)},
{"key the generator of G2", quicknet.Scheme, hex.EncodeToString(g2)},
{"key the point at infinity", quicknet.Scheme, hex.EncodeToString(infinity(96, 0xc0))},
{"key with the compression flag cleared", quicknet.Scheme, hex.EncodeToString(flip(key, 0, 0x80))},
}
profileOf := func(name string) *profile.Profile {
for _, c := range profiles {
if c.Name == name {
q := quicknet.Clone()
q.Scheme, q.PublicKey = c.Scheme, unhex(c.PublicKey)
return q
}
}
panic("no profile " + name)
}
max := quicknet.MaxRound()
out := struct {
Description string `json:"description"`
Generator string `json:"generator"`
Libraries string `json:"libraries"`
MaxRound uint64 `json:"max_round"`
Profiles []profileCase `json:"profiles"`
Verify []verifyCase `json:"verify"`
Unwrap []unwrapCase `json:"unwrap"`
Recipient []recipientCase `json:"recipient"`
}{
Description: "Verdicts, codes and texts of provider.Verify, agewrap.NewTimeIdentity with its Unwrap and " +
"agewrap.NewTimeRecipient of the Go reference; see tool/release_go_vectors.go.",
Generator: "tool/release_go_vectors.go",
Libraries: libraries(),
MaxRound: max,
Profiles: profiles,
}
// provider.Verify.
for _, c := range []struct {
name, profile string
round, release uint64
sig []byte
}{
{"the published release of round 1000", "quicknet", 1000, 1000, sig1000},
{"the published release of round 1001", "quicknet", 1001, 1001, sig1001},
{"the published release of round 1004", "quicknet", 1004, 1004, sig1004},
{"the published release of round 2000", "quicknet", 2000, 2000, sig2000},
{"a valid release of another round", "quicknet", 1000, 1001, sig1001},
{"another round and a short signature", "quicknet", 1000, 1001, sig1001[:47]},
{"the signature of another round relabelled", "quicknet", 1000, 1000, sig1001},
{"the signature of round 1000 for round 999", "quicknet", 999, 999, sig1000},
{"an all-zero signature", "quicknet", 1000, 1000, make([]byte, 48)},
{"a flipped bit", "quicknet", 1000, 1000, flip(sig1000, 47, 1)},
{"a short signature", "quicknet", 1000, 1000, sig1000[:47]},
{"a long signature", "quicknet", 1000, 1000, append(bytes.Clone(sig1000), 0)},
{"a G2-sized signature", "quicknet", 1000, 1000, append(bytes.Clone(sig1000), sig1000...)},
{"an empty signature", "quicknet", 1000, 1000, nil},
{"the signature re-encoded with x + p", "quicknet", 1004, 1004, xPlusP},
{"the signature is the point at infinity", "quicknet", 1000, 1000, infinity(48, 0xc0)},
{"the infinity flag and a payload", "quicknet", 1000, 1000, flip(sig1000, 0, 0x40)},
{"the point at infinity with the sign flag", "quicknet", 1000, 1000, infinity(48, 0xe0)},
{"the compression flag cleared", "quicknet", 1000, 1000, flip(sig1000, 0, 0x80)},
{"the signature negated", "quicknet", 1000, 1000, flip(sig1000, 0, 0x20)},
{"round zero", "quicknet", 0, 0, sig1000},
{"the last round of the profile", "quicknet", max, max, sig1000},
{"a round beyond the profile", "quicknet", max + 1, max + 1, sig1000},
{"another scheme of drand", "scheme pedersen-bls-unchained", 1000, 1000, sig1000},
{"another scheme of drand on G1", "scheme bls-unchained-on-g1", 1000, 1000, sig1000},
{"a scheme that is not of drand", "scheme not of drand", 1000, 1000, sig1000},
{"another scheme and another round", "scheme pedersen-bls-unchained", 1000, 1001, sig1001},
{"another valid key", "key the generator of G2", 1000, 1000, sig1000},
{"the key is the point at infinity", "key the point at infinity", 1000, 1000, sig1000},
{"the key and the signature are the point at infinity", "key the point at infinity", 1000, 1000, infinity(48, 0xc0)},
{"a key that is not a canonical encoding", "key with the compression flag cleared", 1000, 1000, sig1000},
{"a key that is not a canonical encoding and a short signature", "key with the compression flag cleared", 1000, 1000, sig1000[:47]},
} {
err := provider.Verify(profileOf(c.profile), provider.Condition{Round: c.round}, provider.Release{Round: c.release, Signature: c.sig})
out.Verify = append(out.Verify, verifyCase{c.name, c.profile, c.round, c.release, hex.EncodeToString(c.sig), verdict(err)})
}
// agewrap.TimeIdentity on the stanza of the time_only fixture.
file := must(os.ReadFile(filepath.Join(dir, "fixtures", "time_only.dkc")))
pre := must(capsule.ParsePrelude(file))
start := capsule.PreludeSize + int(pre.PublicHeaderLen)
sealed := file[start : start+int(pre.SealedControlLen)]
stanzas := must(agewrap.Stanzas(bytes.NewReader(sealed)))
if len(stanzas) != 1 {
panic("time_only has not one stanza")
}
base := stanzas[0]
body := base.Body
u, v, w := body[:96], body[96:112], body[112:]
c0 := new(big.Int).SetBytes(u[48:])
uc0p := append(bytes.Clone(u[:48]), new(big.Int).Add(c0, p).FillBytes(make([]byte, 48))...)
cat := func(parts ...[]byte) []byte { return bytes.Join(parts, nil) }
with := func(edit func(s *age.Stanza)) []*age.Stanza {
s := &age.Stanza{Type: base.Type, Args: append([]string(nil), base.Args...), Body: bytes.Clone(base.Body)}
edit(s)
return []*age.Stanza{s}
}
same := func(*age.Stanza) {}
other := &age.Stanza{Type: "X25519", Args: []string{"LvR2+5baviJPeIiyw96VfTW1GnzTw3DbWIPGuq9amEw"}, Body: make([]byte, 32)}
for _, c := range []struct {
name, profile string
round, release uint64
sig []byte
stanzas []*age.Stanza
}{
{"the stanza of time_only", "quicknet", 1000, 1000, sig1000, with(same)},
{"no stanza", "quicknet", 1000, 1000, sig1000, nil},
{"two stanzas", "quicknet", 1000, 1000, sig1000, append(with(same), with(same)...)},
{"a stanza of type X25519", "quicknet", 1000, 1000, sig1000, []*age.Stanza{other}},
{"one argument", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args = s.Args[:1] })},
{"three arguments", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args = append(s.Args, "x") })},
{"the round of another DateKey", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args[0] = "1001" })},
{"the round with a leading zero", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args[0] = "01000" })},
{"the round with a quote, a newline and a letter that is not ASCII", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args[0] = "1000" + string(rune(34)) + string(rune(10)) + string(rune(233)) })},
{"the chain hash in upper case", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args[1] = strings.ToUpper(s.Args[1]) })},
{"another chain hash", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Args[1] = strings.Repeat("ab", 32) })},
{"a release of another round", "quicknet", 1000, 1001, sig1001, with(same)},
{"the release signature negated", "quicknet", 1000, 1000, flip(sig1000, 0, 0x20), with(same)},
{"the release signature negated and a body of 127 bytes", "quicknet", 1000, 1000, flip(sig1000, 0, 0x20), with(func(s *age.Stanza) { s.Body = s.Body[:127] })},
{"a body of 127 bytes", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = s.Body[:127] })},
{"a body of 129 bytes", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = append(s.Body, 0) })},
{"an empty body", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = nil })},
{"U re-encoded with c0 + p", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(uc0p, v, w) })},
{"U is the point at infinity", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(infinity(96, 0xc0), v, w) })},
{"U with the infinity flag and a payload", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(flip(u, 0, 0x40), v, w) })},
{"U with the compression flag cleared", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(flip(u, 0, 0x80), v, w) })},
{"U negated", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(flip(u, 0, 0x20), v, w) })},
{"U is the generator of G2", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(unhex(hex.EncodeToString(g2)), v, w) })},
{"V with its first bit flipped", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(u, flip(v, 0, 0x80), w) })},
{"W with its last bit flipped", "quicknet", 1000, 1000, sig1000, with(func(s *age.Stanza) { s.Body = cat(u, v, flip(w, 15, 0x01)) })},
{"another scheme of drand", "scheme pedersen-bls-unchained", 1000, 1000, sig1000, with(same)},
{"a key that is not a canonical encoding", "key with the compression flag cleared", 1000, 1000, sig1000, with(same)},
{"the key is the point at infinity", "key the point at infinity", 1000, 1000, sig1000, with(same)},
{"another valid key", "key the generator of G2", 1000, 1000, sig1000, with(same)},
} {
uc := unwrapCase{Name: c.name, Profile: c.profile, Round: c.round, Release: c.release, Signature: hex.EncodeToString(c.sig), Stanzas: []stanza{}}
for _, s := range c.stanzas {
uc.Stanzas = append(uc.Stanzas, stanza{s.Type, append([]string{}, s.Args...), hex.EncodeToString(s.Body)})
}
id, err := agewrap.NewTimeIdentity(profileOf(c.profile), c.round, provider.Release{Round: c.release, Signature: c.sig})
if err == nil {
var fk []byte
if fk, err = id.Unwrap(c.stanzas); err == nil {
uc.result = result{Go: "ok", FileKey: hex.EncodeToString(fk)}
out.Unwrap = append(out.Unwrap, uc)
continue
}
}
uc.result = verdict(err)
out.Unwrap = append(out.Unwrap, uc)
}
// agewrap.NewTimeRecipient.
for _, c := range []struct {
name, profile string
round uint64
}{
{"round 1000", "quicknet", 1000},
{"the last round", "quicknet", max},
{"round zero", "quicknet", 0},
{"a round beyond the profile", "quicknet", max + 1},
{"another scheme of drand", "scheme pedersen-bls-unchained", 1000},
{"a scheme that is not of drand", "scheme not of drand", 1000},
{"a key that is not a canonical encoding", "key with the compression flag cleared", 1000},
{"the key is the point at infinity", "key the point at infinity", 1000},
{"the key is the point at infinity and round zero", "key the point at infinity", 0},
} {
_, err := agewrap.NewTimeRecipient(profileOf(c.profile), c.round)
out.Recipient = append(out.Recipient, recipientCase{c.name, c.profile, c.round, verdict(err)})
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.SetEscapeHTML(false)
if err := enc.Encode(out); err != nil {
panic(err)
}
}
// libraries names the versions of the libraries this program ran with.
func libraries() string {
info, ok := debug.ReadBuildInfo()
if !ok {
panic("no build info")
}
var out []string
for _, d := range info.Deps {
switch d.Path {
case "filippo.io/age", "github.com/drand/tlock", "github.com/drand/kyber", "github.com/drand/kyber-bls12381", "github.com/drand/drand/v2":
out = append(out, d.Path+" "+d.Version)
}
}
sort.Strings(out)
return strings.Join(out, ", ")
}

@ -0,0 +1,281 @@
//go:build ignore
// Prints test/vectors/tlock_vectors.json: the Go reference values for the
// tlock encryption of lib/src/ibe.dart and lib/src/tlock.dart, a port of
// scripts/tlock-go-vectors.go of datekeys-ts.
//
// - encrypt: EncryptCCAonG2 of drand/kyber with the suite of tlock for
// Quicknet, restated with a fixed sigma, for messages of 0, 1, 16 and 32
// bytes to rounds 1000 and 1001. kyber draws sigma from crypto/rand, so
// the restatement is checked: ibe.DecryptCCAonG2 opens every ciphertext
// with the published signature of its round, and tlock.BytesToCiphertext
// with tlock.TimeUnlock opens the 16-byte ones, as a tlock stanza body.
// - interop: the ciphertexts that scripts/tlock-ts-samples.mjs of
// datekeys-ts made with the TypeScript library, frozen in its
// src/lib/dkc/testing/tlock-vectors.json at 289fe71, each opened again by
// the reference here. An IBE body goes through tlock.BytesToCiphertext and
// tlock.TimeUnlock; an age file through age.Decrypt with
// agewrap.NewTimeIdentity, the identity of capsule.Open at step 11. Each
// sample gets the verdict "ok" with what Go recovered, or "reject".
//
// H2, H3 and H4 are unexported in kyber; they are restated with its tags, as
// in tool/ibe_go_vectors.go, and the decryptions above check them.
//
// Run it from a scratch module that requires the reference implementation at
// spec-v0.11 (a module scratch whose go.mod has require
// g.activething.com/go/DateKeys v0.0.0 and replace
// g.activething.com/go/DateKeys => an export of that tag, with its go.sum,
// and GOFLAGS=-mod=mod), copied into it, passing the frozen file of
// datekeys-ts:
//
// go run tlock_go_vectors.go path/to/tlock-vectors.json > tlock_vectors.json
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/big"
"os"
"runtime/debug"
"sort"
"strings"
"filippo.io/age"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
"github.com/drand/drand/v2/common"
"github.com/drand/drand/v2/crypto"
"github.com/drand/kyber"
bls "github.com/drand/kyber-bls12381"
"github.com/drand/kyber/encrypt/ibe"
"github.com/drand/tlock"
)
// The published Quicknet signatures of rounds 1000 and 1001, as in the
// fixtures and the mutation corpus; provider.Verify checks them below.
var published = map[uint64]string{
1000: "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
1001: "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
}
var (
suite = bls.NewBLS12381Suite()
order, _ = new(big.Int).SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16)
)
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func unhex(s string) []byte { return must(hex.DecodeString(s)) }
func concat(parts ...[]byte) []byte { return bytes.Join(parts, nil) }
func xor(a, b []byte) []byte {
out := make([]byte, len(a))
for i := range a {
out[i] = a[i] ^ b[i]
}
return out
}
func h2(gt []byte, n int) []byte {
sum := sha256.Sum256(concat(ibe.H2Tag(), gt))
return sum[:n]
}
func h4(sigma []byte, n int) []byte {
sum := sha256.Sum256(concat(ibe.H4Tag(), sigma))
return sum[:n]
}
func h3(sigma, msg []byte) kyber.Scalar {
base := sha256.Sum256(concat(ibe.H3Tag(), sigma, msg))
for i := uint16(1); i < 65535; i++ {
d := sha256.Sum256(concat(binary.LittleEndian.AppendUint16(nil, i), base[:]))
d[0] >>= 1
if new(big.Int).SetBytes(d[:]).Cmp(order) < 0 {
r := suite.G1().Scalar()
if err := r.UnmarshalBinary(d[:]); err != nil {
panic(err)
}
return r
}
}
panic("h3: rejection sampling failed")
}
// encrypt is EncryptCCAonG2 of kyber with the given sigma.
func encrypt(key kyber.Point, id, msg, sigma []byte) *ibe.Ciphertext {
qid := suite.G1().Point().(kyber.HashablePoint).Hash(id)
gid := suite.Pair(qid, key)
r := h3(sigma, msg)
gt := must(suite.GT().Point().Mul(r, gid).MarshalBinary())
return &ibe.Ciphertext{U: suite.G2().Point().Mul(r, nil), V: xor(sigma, h2(gt, len(msg))), W: xor(msg, h4(sigma, len(msg)))}
}
type encryptVector struct {
Name string `json:"name"`
Round uint64 `json:"round"`
ID string `json:"id"`
Msg string `json:"msg"`
Sigma string `json:"sigma"`
U string `json:"u"`
V string `json:"v"`
W string `json:"w"`
Signature string `json:"signature"`
}
type sample struct {
Name string `json:"name"`
Kind string `json:"kind"`
Round uint64 `json:"round"`
FileKey string `json:"file_key,omitempty"`
Body string `json:"body,omitempty"`
Plaintext string `json:"plaintext,omitempty"`
File string `json:"file,omitempty"`
Go string `json:"go"`
GoResult string `json:"go_result,omitempty"`
}
func main() {
scheme := must(crypto.SchemeFromName(crypto.SigsOnG1ID))
quicknet := profile.Quicknet()
key := scheme.KeyGroup.Point()
if err := key.UnmarshalBinary(quicknet.PublicKey); err != nil {
panic(err)
}
release := func(round uint64) provider.Release {
r := provider.Release{Round: round, Signature: unhex(published[round])}
if err := provider.Verify(quicknet, provider.Condition{Round: round}, r); err != nil {
panic(err)
}
return r
}
var vectors []encryptVector
for i, c := range []struct {
round uint64
n int
}{{1000, 16}, {1001, 16}, {1000, 1}, {1000, 32}, {1000, 0}} {
seed := sha256.Sum256(binary.BigEndian.AppendUint32([]byte("DateKeys tlock vector "), uint32(i)))
msgSeed := sha256.Sum256(seed[:])
msg, sigma := msgSeed[:c.n], seed[:c.n]
id := scheme.DigestBeacon(&common.Beacon{Round: c.round})
ct := encrypt(key, id, msg, sigma)
rel := release(c.round)
sig := scheme.SigGroup.Point()
if err := sig.UnmarshalBinary(rel.Signature); err != nil {
panic(err)
}
if got, err := ibe.DecryptCCAonG2(suite, sig, ct); err != nil || !bytes.Equal(got, msg) {
panic(fmt.Sprintf("kyber does not decrypt the restated encryption %d: %v", i, err))
}
u := must(ct.U.MarshalBinary())
if c.n == 16 {
body := concat(u, ct.V, ct.W)
got := must(tlock.TimeUnlock(*scheme, key, common.Beacon{Round: rel.Round, Signature: rel.Signature}, must(tlock.BytesToCiphertext(*scheme, body))))
if !bytes.Equal(got, msg) {
panic("tlock does not decrypt the restated encryption")
}
}
vectors = append(vectors, encryptVector{
Name: fmt.Sprintf("a %d-byte message for round %d", c.n, c.round), Round: c.round, ID: hex.EncodeToString(id),
Msg: hex.EncodeToString(msg), Sigma: hex.EncodeToString(sigma), U: hex.EncodeToString(u),
V: hex.EncodeToString(ct.V), W: hex.EncodeToString(ct.W), Signature: published[c.round],
})
}
var frozen struct {
Interop struct {
Generator string `json:"generator"`
Samples []sample `json:"samples"`
} `json:"interop"`
}
if err := json.Unmarshal(must(os.ReadFile(os.Args[1])), &frozen); err != nil {
panic(err)
}
in := frozen.Interop
if len(in.Samples) != 4 {
panic("want the 4 frozen samples of datekeys-ts")
}
for i := range in.Samples {
s := &in.Samples[i]
rel := release(s.Round)
s.Go = "reject"
switch s.Kind {
case "ibe":
ct, err := tlock.BytesToCiphertext(*scheme, unhex(s.Body))
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
fk, err := tlock.TimeUnlock(*scheme, key, common.Beacon{Round: rel.Round, Signature: rel.Signature}, ct)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
s.Go, s.GoResult = "ok", hex.EncodeToString(fk)
case "age":
id := must(agewrap.NewTimeIdentity(quicknet, s.Round, rel))
r, err := age.Decrypt(bytes.NewReader(unhex(s.File)), id)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
pt, err := io.ReadAll(r)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
s.Go, s.GoResult = "ok", hex.EncodeToString(pt)
default:
panic("unknown sample kind " + s.Kind)
}
}
out := map[string]any{
"description": "Go reference values for the tlock encryption of lib/src/ibe.dart and lib/src/tlock.dart; " +
"see tool/tlock_go_vectors.go for how each block is obtained.",
"generator": "tool/tlock_go_vectors.go",
"interop_from": "the interop samples of src/lib/dkc/testing/tlock-vectors.json of datekeys-ts at 289fe71, " +
"opened again by this generator",
"libraries": libraries(),
"scheme": scheme.Name,
"public_key": hex.EncodeToString(quicknet.PublicKey),
"encrypt": vectors,
"interop": map[string]any{"generator": in.Generator, "samples": in.Samples},
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.SetEscapeHTML(false)
if err := enc.Encode(out); err != nil {
panic(err)
}
}
// libraries names the versions of the libraries this program ran with.
func libraries() string {
info, ok := debug.ReadBuildInfo()
if !ok {
panic("no build info")
}
var out []string
for _, d := range info.Deps {
switch d.Path {
case "filippo.io/age", "github.com/drand/tlock", "github.com/drand/kyber", "github.com/drand/kyber-bls12381", "github.com/drand/drand/v2":
out = append(out, d.Path+" "+d.Version)
}
}
sort.Strings(out)
return strings.Join(out, ", ")
}
Loading…
Cancel
Save

Powered by TurnKey Linux.