You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dateKeys-dart/test/bls12381_vectors_test.dart

220 lines
7.2 KiB

// The BLS12-381 code against the Go reference: test/vectors/
// bls12381_vectors.json, written by tool/bls12381_go_vectors.go with
// kilic/bls12-381 and drand/kyber-bls12381, the libraries of drand and
// tlock. Decoding verdicts, sums, multiples, pairings, hashes to G1, the map
// of one element and BLS signatures on G1.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'package:datekeys/datekeys.dart' show fromHex, toHex;
import 'package:datekeys/src/bls12381_curve.dart';
import 'package:datekeys/src/bls12381_fp.dart';
import 'package:datekeys/src/bls12381_hash.dart';
import 'package:datekeys/src/bls12381_pairing.dart';
import 'package:datekeys/src/bls12381_tower.dart';
import 'package:test/test.dart';
typedef Json = Map<String, Object?>;
final Json vectors = jsonDecode(
File('test/vectors/bls12381_vectors.json').readAsStringSync(),
) as Json;
List<Json> section(String name) => (vectors[name]! as List).cast<Json>();
String s(Json v, String key) => v[key]! as String;
BlsGroup group(Json v) => s(v, 'group') == 'G1' ? BlsGroup.g1 : BlsGroup.g2;
PointVerdict verdictOf(String go) => PointVerdict.values.byName(go);
G1Point g1(String hex) => G1Point.decode(fromHex(hex))!;
G2Point g2(String hex) => G2Point.decode(fromHex(hex))!;
void main() {
test('the vectors come from the Go libraries of the reference', () {
expect(vectors['generator'], 'tool/bls12381_go_vectors.go');
expect(
vectors['libraries'],
allOf(
contains('github.com/kilic/bls12-381 v0.1.0'),
contains('github.com/drand/kyber-bls12381 v0.3.4'),
),
);
expect(section('points'), hasLength(157));
});
test('decodes every frozen edge case of datekeys-ts as Go does', () {
final seen = <String>{};
for (final v in section('points')) {
final got = checkCompressedPoint(group(v), fromHex(s(v, 'hex')));
expect(got, verdictOf(s(v, 'go')), reason: s(v, 'label'));
seen.add('${s(v, 'group')} ${s(v, 'go')} ${v['class'] ?? ''}');
}
// Every class of verdict and of failure, in both groups.
expect(
seen,
containsAll([
'G1 point ',
'G1 identity ',
'G1 invalid format',
'G1 invalid curve',
'G1 invalid subgroup',
'G2 point ',
'G2 identity ',
'G2 invalid format',
'G2 invalid curve',
'G2 invalid subgroup',
]),
);
});
test('decodes the encodings drawn from the seed as Go does, failing where Go '
'fails', () {
final classes = <String, int>{};
for (final v in section('decode')) {
final b = fromHex(s(v, 'hex'));
final label = s(v, 'label');
expect(
checkCompressedPoint(group(v), b),
verdictOf(s(v, 'go')),
reason: label,
);
final cls = v['class'] as String?;
final key = '${s(v, 'group')} ${cls ?? s(v, 'go')}';
classes[key] = (classes[key] ?? 0) + 1;
// Where Go finds a point of the curve outside the subgroup, so does
// this code: the subgroup check is what rejects it.
if (cls == 'subgroup' || cls == 'curve') {
expect(
onCurveOutsideSubgroup(group(v), b),
cls == 'subgroup',
reason: label,
);
}
// A point re-encodes to its bytes.
if (s(v, 'go') == 'point') {
final encoded = group(v) == BlsGroup.g1
? g1(s(v, 'hex')).toBytes()
: g2(s(v, 'hex')).toBytes();
expect(toHex(encoded), s(v, 'hex'), reason: label);
}
}
expect(classes['G1 subgroup'], greaterThanOrEqualTo(16));
expect(classes['G2 subgroup'], greaterThanOrEqualTo(8));
expect(classes['G1 curve'], greaterThanOrEqualTo(8));
expect(classes['G2 curve'], greaterThanOrEqualTo(4));
});
test('adds as kilic', () {
for (final v in section('add')) {
final label = s(v, 'label');
if (group(v) == BlsGroup.g1) {
final sum = g1(s(v, 'a')) + g1(s(v, 'b'));
expect(toHex(sum.toBytes()), s(v, 'sum'), reason: label);
// The mixed addition agrees.
final b = g1(s(v, 'b')).toAffine();
if (b != null) {
expect(
toHex(g1(s(v, 'a')).addAffine(b.$1, b.$2).toBytes()),
s(v, 'sum'),
reason: label,
);
}
} else {
final sum = g2(s(v, 'a')) + g2(s(v, 'b'));
expect(toHex(sum.toBytes()), s(v, 'sum'), reason: label);
final b = g2(s(v, 'b')).toAffine();
if (b != null) {
expect(
toHex(g2(s(v, 'a')).addAffine(b.$1, b.$2).toBytes()),
s(v, 'sum'),
reason: label,
);
}
}
}
});
test('multiplies as kilic, also by 0, r and scalars above r', () {
for (final v in section('multiply')) {
final k = BigInt.parse(s(v, 'scalar'), radix: 16);
final product = group(v) == BlsGroup.g1
? g1(s(v, 'point')).multiply(k).toBytes()
: g2(s(v, 'point')).multiply(k).toBytes();
expect(toHex(product), s(v, 'product'), reason: s(v, 'label'));
}
});
test('pairs as kilic, serialized as kyber-bls12381 marshals GT', () {
for (final v in section('pairing')) {
final gt = pairing(g1(s(v, 'g1')), g2(s(v, 'g2')));
expect(toHex(gt.toBytes()), s(v, 'gt'), reason: s(v, 'label'));
}
});
test('hashes to G1 as kilic, for the DST of Quicknet and of RFC 9380', () {
final dsts = <String>{};
for (final v in section('hash_to_g1')) {
final p = hashToG1(fromHex(s(v, 'msg')), s(v, 'dst'));
final label = s(v, 'label');
expect(toHex(p.toBytes()), s(v, 'point'), reason: label);
final (x, y) = p.toAffine()!;
expect(
[toHex(x.toBytes()), toHex(y.toBytes())],
[s(v, 'x'), s(v, 'y')],
reason: label,
);
dsts.add(s(v, 'dst'));
}
expect(dsts, {
quicknetDst,
'QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_',
});
});
test('maps one element to G1 as kilic, the exceptional ones included', () {
for (final v in section('map_to_g1')) {
final u = Fp.fromBytes(fromHex(s(v, 'u')))!;
expect(toHex(mapToG1(u).toBytes()), s(v, 'point'), reason: s(v, 'label'));
}
});
test('verifies BLS signatures on G1 as kyber sign/bls', () {
for (final v in section('signatures')) {
final key = g2(s(v, 'public_key'));
final sig = G1Point.decode(fromHex(s(v, 'signature')));
final ok =
sig != null &&
!sig.isInfinity &&
pairingCheck([
(hashToG1(fromHex(s(v, 'msg')), quicknetDst), key),
(-sig, G2Point.generator),
]);
expect(ok, v['go'], reason: s(v, 'label'));
}
});
}
// Whether the compressed [b] has an x of a point of the curve, which then
// lies outside the subgroup: what kilic reports as "not on correct
// subgroup" rather than "not on curve".
bool onCurveOutsideSubgroup(BlsGroup group, List<int> b) {
final raw = [b[0] & 0x1f, ...b.sublist(1)];
if (group == BlsGroup.g1) {
final x = Fp.fromBytes(raw)!;
final y = (x.square() * x + G1Point.b).sqrt();
return y != null && !G1Point.affine(x, y).isInSubgroup;
}
final x = Fp2(
Fp.fromBytes(raw.sublist(48))!,
Fp.fromBytes(raw.sublist(0, 48))!,
);
final y = (x.square() * x + G2Point.b).sqrt();
return y != null && !G2Point.affine(x, y).isInSubgroup;
}

Powered by TurnKey Linux.