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.
220 lines
7.2 KiB
220 lines
7.2 KiB
|
2 days ago
|
// 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;
|
||
|
|
}
|